Диалоговая кнопка BUEditor + стандартная drupal-форма для внутреннего линкинга

Главные вкладки

Аватар пользователя popovtv popovtv 10 мая 2011 в 1:40

Вашему вниманию предлагается статья, в которой описывается способ встраивания стандартного функционала форм 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

; $Id$

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%%')"$string010);
  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

Drupal.behaviors.callbacks = function(){

  $('#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

.form-radios .form-item {
    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.png6.79 КБ
Иконка изображения callbacks_form_2.png34.29 КБ

Комментарии

Аватар пользователя popovtv popovtv 10 мая 2011 в 12:03

drupal_get_form() в кнопке серьезно конфликтует с другими модулями (date) и убивает весь сайт. Лучше использовать любой вариант с кэшированной переменной, содержащей форму, а hook_init() было решением, которое заработало как раз после первого варианта с кнопкой. Суть не в этом.

Аватар пользователя Klain Klain 7 июня 2011 в 17:57

Все сделал как написано, но при нажатии на кнопку вставить ссылку страница перезагружается и ссылка не вставляется, помогите, буду благодарен.

Drupal 6.22, Bueditor-6.x-2.4

Аватар пользователя popovtv popovtv 15 июня 2011 в 11:09

Klain wrote:
Все сделал как написано, но при нажатии на кнопку вставить ссылку страница перезагружается и ссылка не вставляется, помогите, буду благодарен.

Drupal 6.22, Bueditor-6.x-2.4

Он перегружает страницу, потому что стандартное действие формы не переназначено из js файла. Проверь правильно ли подключился js сценарий, где submit action заменяется на вставку в текстовое поле.

Аватар пользователя ingumsky@drupal.org ingumsky@drupal.org 7 июня 2011 в 23:14

"Klain" wrote:
Все сделал как написано, но при нажатии на кнопку вставить ссылку страница перезагружается и ссылка не вставляется, помогите, буду благодарен.
Drupal 6.22, Bueditor-6.x-2.4

А кеш вы очистили?

Аватар пользователя Klain Klain 8 июня 2011 в 8:27

"iNFerNo" wrote:

а как кнопку импортирвоать то?

Настройка сайта — 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);
";

+ картинку добавить и заголовок

если не так прошу поправить

Аватар пользователя Klain Klain 10 ноября 2015 в 11:47

"iNFerNo" wrote:

в какое такое содержимое? а как картинку вставить?

картинку надо предварительно загрузить в папку images модуля BUEditor

Аватар пользователя KA4AH KA4AH 8 августа 2011 в 14:39

У меня тоже перегружает страницу, и как я понимаю js-файл не срабатывает... вставлял тестовые алерты, ничего не высканивает... фаербаг показывает, что js-файл подключён... в чём может быть проблема, почему js не срабатывает...

Аватар пользователя ingumsky@drupal.org ingumsky@drupal.org 10 августа 2011 в 23:27

"KA4AH" wrote:
У меня тоже перегружает страницу, и как я понимаю js-файл не срабатывает... вставлял тестовые алерты, ничего не высканивает... фаербаг показывает, что js-файл подключён... в чём может быть проблема, почему js не срабатывает...

Кеш сайта не забыли почистить?

Аватар пользователя Jean-Claude Jean-Claude 28 июня 2014 в 19:54

тоже хотел бы такой модуль.

как и у людей выше - при клике на кнопку, страничка перезагружается, хотя js есть в исходном коде станицы.

и еще косяк - ищет ноды если только вводить с самого начала заголовка, например:
есть нода "сильные слоны долбят воду"
если набирать сильн.. - то находит ноду
но если набрать слоны.. - то ноду не находит(