Вашему вниманию предлагается статья, в которой описывается способ встраивания стандартного функционала форм Drupal в редактор BUEditor для удобного добавления ссылок на внутренние материалы сайта.
Начну с дисклеймера. К сожалению, не имею возможностей разбирать весь процесс создания по косточкам, т.к. было потрачено немало времени и допущено и исправлено множество ошибок прежде, чем получился результат, достойный публикации. Тратить это время здесь смысла большого не имеет, лучше идти дальше. Вот решение - пользуйтесь, экспериментируйте, дорабатывайте, делитесь. Здесь же постараюсь описать все, что необходимо для реализации задуманного функционала. Описание дается для блоками, а не построчно.
Инструменты
- Module API
- Drupal form API
- Ajax
- JQuery
- BUEditor
Предыстория
Основным редактором на нашем научном портале является BUEditor, т.к. есть твердая необходимость форматировать текст с использованием модуля DruTeX. Ну и просто постараться сохранить предсказуемый вид для исходных текстов (в отличие от wysiwyg-редактора) для их дальнейшего использования по-жизни. По мере наполнения появилась необходимость для кросс-линкинга между статьями, терминами словаря и другими видами материалов. Все это осложнялось отсутствием таких возможностей для BUEditor, в отличие от других редакторов, для которых есть такой модуль как Linkit. Потратив некоторое время на изучение матчасти (Drupal form API), было принято решение, что во что бы то ни стало нужно прикрутить стандартную форму Drupal к диалогам BUEditor. Ведь autocomplete уже реализован, стили сделаны, а пока форма ждет ответа, там бегает кружёк и выпадет готовая менюха - вообщем просто и красиво.
Первым делом начал искать, что было сделано. Например - http://www.drupal.ru/node/12619, но по делу там ничего не нашел. Начал экспериментировать с маленьким неофициальным модулем ajax-demo и его autocomplete-возможностями. Все быстро встало на свои места. Дальше появился вопрос - как заставить друпаловскую форму залезть в диалог редактора?
Кстати, бился с поиском до последнего, т.к. не сторонник, чтобы на сайте был такой hand-made. Но готовых решений нет и был вынужден изменить своим же принципам.
Hands on
Итак, приступаем.
Первым делом надо сделать свой модуль (назвал его callbacks), в который войдет основной функционал, а именно:
- Описание формы
- Описание функций обращения к базе
- Клиентский js-функционал
- Стили ответа
callbacks.info
name = callbacks module
core = "6.x"
version = "6.x-1.1"
callbacks.module
<?php
// $Id$
/**
* Implementation of hook_init().
*/
function callbacks_init(){
variable_set('callbacks_link_form_var', drupal_get_form('callbacks_link_form'));
}
/**
* Implementation of hook_menu().
*/
function callbacks_menu() {
return array(
'callback/link' => array(
'access arguments' => array('access content'),
'page callback' => 'callbacks_link_func',
'type' => MENU_CALLBACK,
),
);
}
/**
* Retrieve a pipe delimited string of autocomplete suggestions for existing users
*/
function callbacks_link_func($string) {
$matches = array();
$dst = "";
$result = db_query_range("SELECT nid, language, type, title FROM {node} n WHERE LOWER(n.title) LIKE LOWER('%s%%')", $string, 0, 10);
while ($node = db_fetch_object($result)) {
$get_alias = db_query("SELECT dst FROM {url_alias} WHERE src = 'node/%s'", $node->nid );
if( $alias = db_fetch_object($get_alias) )
$dst = $alias->dst;
else
$dst = "node/" . $node->nid;
$matches[$node->nid] = array(
'type' => check_plain($node->type),
'title' => check_plain($node->title),
'alias' => check_plain($dst),
'lang' => check_plain($node->language),
);
}
$results = array();
if (count($matches)) {
foreach( $matches as $key_nid => $values ) {
$text = '<div class="clear-block">';
// $text .= '<div class="callbacks-nid">['. $key_nid .']</div>';
$text .= '<div class="callbacks-title">'. $values['title'] .'</div>';
$text .= '<div class="callbacks-type">['. $values['type'] . ':' . ( $values['lang'] ? $values['lang'] : 'any' ) . ']</div>';
$text .= '<div class="callbacks-alias">'. $values['alias'] .'</div>';
$text .= "</div>";
$dst = $values['alias'];
$results[$dst] = $text;
}
}
drupal_json($results);
}
/**
* Defines a form.
*/
function callbacks_link_form() {
drupal_add_js(drupal_get_path('module', 'callbacks') . '/js/callbacks.js');
drupal_add_css(drupal_get_path('module', 'callbacks') . '/css/callbacks.css');
return array(
'link-path-ac' => array(
'#autocomplete_path' => 'callback/link',
'#title' => t('URL or Title'),
'#description' => t('Start type title to get its internal URL'),
'#type' => 'textfield',
'#required' => TRUE,
),
'link-text' => array(
'#title' => t('Link text'),
'#description' => t('Select text in editor or enter link text'),
'#type' => 'textfield',
),
'cancel' => array(
'#value' => t('Cancel'),
'#attributes' => array('class' => 'callbacks-button'),
'#type' => 'button',
),
'add-link' => array(
'#value' => t('Add link'),
'#attributes' => array('class' => 'callbacks-button'),
'#type' => 'button',
),
);
}
?>
hook_init() - нужен, чтобы заранее подготовить форму, иначе BUEditor ее не съест.
hook_menu() - это осознанная необходимость для autocomplete в Form API.
callbacks_link_func($string) - стандартный функционал для json-ответов. Здесь можно выбирать из базы любые поля и отдавать их для отображения, в данном случае поиск ноды в базе происходит по заголовку, но этим, как вы понимаете, можно не ограничиваться. Тут же происходит стилевое оформление - дело вкуса прячется в конце функции. Да, кстати, здесь приходится делать двойной запрос к базе, т.к. все, что мы делаем, мы делаем для живых людей и поэтому подставлять красивый alias вместо ссылки вида node/435 насущная необходимость. Для красоты (см. картинку в конце) выводится название, алиас, тип материала и язык.
callbacks_link_form() - описываем форму по-друпаловски: быстро, просто, удобно, с аутокомплитом.
JS часть. Файлик js/callbacks.js
$('#edit-add-link').unbind().click(
function(){
var url = $('input#edit-link-path-ac').val();
var text = $('input#edit-link-text').val();
E = BUE.active;
E.replaceSelection( '[url'+ (text ? ('='+ url) : '') +']'+ (text || url) +'[/url]' );
E.dialog.close('fadeOut');
return false;
}
);
$('#edit-cancel').unbind().click(
function(){
BUE.active.dialog.close('fadeOut');
return false;
}
);
$('#edit-add-link').unbind().click() - отцепляем стандартный submit и говорим форме, что же нам от нее нужно.
$('#edit-cancel').unbind().click() - то же самое, но для кнопочки Cancel, для порядка.
CSS часть. Файлик css/callbacks.css
display: inline;
}
.callbacks-nid {
float: left;
}
.callbacks-title {
float: left;
}
.callbacks-alias {
color: #333;
font-size: 0.9em;
clear: both;
margin-bottom: 5px;
font-weight: bold;
}
.callbacks-type {
float: right;
font-size: 0.8em;
}
.callbacks-button {
float: right;
}
Тут самая скука, расставить цвета и отступы.
Вторым делом добавить php-кнопку в BUEditor.
<?php
php:
$button_form = drupal_to_js(variable_get('callbacks_link_form_var', ''));
return
"js:
var S = E.getSelection();
E.dialog.open('Internal linking', $button_form, 'fadeIn');
$('input#edit-link-text').val( S );
Drupal.attachBehaviors($button_form);
";
?>
variable_get() - важен, т.к. вариант с drupal_get_form() в данном случае приводит к непредсказуемым результатам и его использовать нельзя.
drupal_to_js() - важен, т.к. иначе форма выдается в неадаптированном для js виде.
Drupal.attachBehaviors() - важен, т.к. без этой функции форма не слушается и плохо себя ведет.
Все
По идее, этого достаточно, чтобы начать экспериментировать. В оригинальном виде у меня используются две формы и, соответственно, все описанное сделано дважды в одном модуле. Если все это после доведения до ума выльется в новый модуль для BUEditor - будет приятно, и, надеюсь, не только мне.
Буду рад получить отзывы и предложения, а так же все сообщения об ошибках. В моем случае некоторые из них проявлялись через месяц-другой использования. Но в целом все довольно стабильно. Если нужен исходник - выложу или пришлю, но там почти все так же как описано.
Вложение | Размер |
---|---|
callbacks_form.png | 6.79 КБ |
callbacks_form_2.png | 34.29 КБ |
Комментарии
hook_init() + генерация формы, очень интересно, очень.
Синкора негодуэ.
Не лучше было бы делать drupal_get_form() в кнопке или форму аяксом гнать?
drupal_get_form() в кнопке серьезно конфликтует с другими модулями (date) и убивает весь сайт. Лучше использовать любой вариант с кэшированной переменной, содержащей форму, а hook_init() было решением, которое заработало как раз после первого варианта с кнопкой. Суть не в этом.
пригодится. Спасибо.
Спасибо! По-моему, очень интересно.
Все сделал как написано, но при нажатии на кнопку вставить ссылку страница перезагружается и ссылка не вставляется, помогите, буду благодарен.
Drupal 6.22, Bueditor-6.x-2.4
Он перегружает страницу, потому что стандартное действие формы не переназначено из js файла. Проверь правильно ли подключился js сценарий, где submit action заменяется на вставку в текстовое поле.
А кеш вы очистили?
Да, очистил, не работает
а как кнопку импортирвоать то?
Настройка сайта — BUEditor — Default — Редактировать — в содержимое я вставил:
php:
$button_form = drupal_to_js(variable_get('callbacks_link_form_var', ''));
return "js:
var S = E.getSelection();
E.dialog.open('Internal linking', $button_form, 'fadeIn');
$('input#edit-link-text').val( S );
Drupal.attachBehaviors($button_form);
";
+ картинку добавить и заголовок
если не так прошу поправить
в какое такое содержимое? а как картинку вставить?
картинку надо предварительно загрузить в папку images модуля BUEditor
У меня тоже перегружает страницу, и как я понимаю js-файл не срабатывает... вставлял тестовые алерты, ничего не высканивает... фаербаг показывает, что js-файл подключён... в чём может быть проблема, почему js не срабатывает...
Кеш сайта не забыли почистить?
чистил... всё равно не работает...
круто - спс
Для тех у кого CKEditor, линкинг можно подключить через модуль CKEditor Link. Просто и сердито.
т.с. а как бы до 7-ки обновить и выложить в виде исходника?
Подпишусь ... порт на D7 крайне желателен
тоже хотел бы такой модуль.
как и у людей выше - при клике на кнопку, страничка перезагружается, хотя js есть в исходном коде станицы.
и еще косяк - ищет ноды если только вводить с самого начала заголовка, например:
есть нода "сильные слоны долбят воду"
если набирать сильн.. - то находит ноду
но если набрать слоны.. - то ноду не находит(
то есть хотелось бы как тут