21 мая 2011 г.

Пишем редактор для форума на javascript (продолжение)

Итак, наконец-то у меня появилось немного свободного времени, чтобы дописать наш редактор. В прошлый раз я объяснил, как я начинал работу над ним, и мы разобрали как написан внешний вид, а также прикинули в уме, какие функции нам понадобятся и что они должны делать.
Переходим к самому коду, т.е. сейчас мы с вами напишем функции, которые будут выполнять все необходимые действия. Для начала вспомним отрывок листинга файла index.html, созданного в прошлый раз. Сейчас нас интересует следующие строки:

<script>
...
...
</script>

Сюда, вместо многоточия мы вставим все, что напишем сегодня.
Итак, начнем.
Первый элемент меню нашего редактора отвечает за шрифт. Т.е., если рассматривать это с точки зрения css, то мы под этим подразумеваем font-family. Чтобы заставить его работать нам надо прикрепить к нему обработчик события jQuery change():

$('#fontFamily').change(function(){
    var ff = $(this).val();
    insTag('[font=' + ff + ']','[/font]', true);
    $(this).css('font-family', ff);
   });

Первой строкой мы прикрепляем к элементу select с ID="fontFamily" обработчик события, который будет реагировать на смену значения данного элемента.
Во второй строке мы создаем переменную ff, которой присваиваем значение элемента. Дальше мы вызываем функцию insTag() с тремя параметрами (эту функцию мы напишем чуть позже), третий параметр - true, чтобы пользователю не приходилось переставлять курсор, а сразу мог набирать текст. Первые два параметра содержат открывающий и закрывающий теги соответственно.
И в третьей строке мы изменяем font-family самого элемента select. Эта строчка для красоты. В принципе, ее можно убрать, кто считает лишним.
Идем дальше. Обратите внимание на, еще неопределенную функцию insTag(). Она еще не написана, но при решении первой же задачи она нам уже понадобилась. Значит сейчас самое время, чтобы ее написать:

insTag = function (text1, text2, edit)
   {
   el = document.getElementById('text');
   if ((document.selection))
   {
     el.content.focus();
     el.document.selection.createRange().text = text1+document.form.document.selection.createRange().text+text2;
   } else if(el.selectionStart != undefined && ((edit && edit === true) || !edit)) {
    var element    = el;
    var str     = element.value;
    var str_len = element.value.length;
    var end = element.selectionEnd;
    var start    = element.selectionStart;
    var length    = element.selectionEnd - element.selectionStart;
    element.value = str.substr(0, start) + text1 + str.substr(start, length) + text2 + str.substr(start + length);
   } else if(el.selectionStart != undefined && edit && edit !== 'true') {
    var element    = el;
    var str     = element.value;
    var start    = element.selectionStart;
    var length    = element.selectionEnd - element.selectionStart;
    element.value = str.substr(0, start) + text1 + edit + text2 + str.substr(start + length);
   } else if (el.selectionStart == undefined && edit && edit !== true) {
    document.form.content.value += text1+edit+text2;
   } else document.form.content.value += text1+text2;
     el.focus();
     if(edit === true){
      el.selectionStart = start + text1.length;
      el.selectionEnd = el.selectionStart;
     }
 
   }

Сначала мы объявляем функцию и переменные, в которые будут заносится значения переданные в вызове функции. text1 - начальный тег, text2 - конечный тег, edit - дополнительный параметр, который можно и не указывать при вызове функции. Он у нас отвечает, за то, куда поставить курсор. Если edit === true, курсор между вставленных тегов, если не указан или false, то после вставленных тегов. Если же edit - строка, значит мы эту строку должны вставить между тегами. Описывать здесь много чего можно и подробно, но т.к. моя статья рассчитана на тех,кто уже работает на javascript, то просто объясню логику. Если что, задавайте вопросы.
Сначала мы определяем где у нас курсор, выделен ли участок текста, если выделение не нулевое, то запоминаем выделенный текст, определяем начальную и конечную позицию выделения. Так же мы проверяем значение переменной edit. Если edit === true или идентичен false, т.е. является false, undefined, 0 или пустой строкой, то выполняем вставку, размещая выделенный текст между тегами. Если edit содержит какое-либо значение отличное от 0 и "", то делаем вставку тегов и заменяем выделенный текст (если таковой имеет место быть, иначе просто вставляем между тегами) на содержимое переменной edit. В конце мы сравниваем переменную edit с true, если они эквивалентны, то курсор располагаем перед конечным тегом. Если браузер не может работать с выделением, то все будет просто добавляться в конец всего написанного в textarea.

Ну, вот самую необходимую функцию, на которой, собственно говоря и будет работать весь редактор, мы написали. Дальше проще.

$('#fontSize').change(function(){
    var fs = $(this).val();
    insTag('[size=' + fs + ']', '[/size]', true);
   });

Это мы прикрепили обработчик события change() на select, который отвечает за размер шрифта. Тут объяснять, я думаю, нечего, потому что он работает точно так же, как и обработчик события change() первого элемента.

А теперь мы приступим с вами к выбору цвета. Здесь нам понадобится несколько функций. Одна будет создавать окошко, в котором будет расположена таблица цветов. При нажатии на поле с определенным цветом, должен формироваться тег в кодом цвета. Итак, листинг:

showColor = function(id){
    $('#popup').remove();
    var el = $('#' + id);
    var prop = get_obj_prop(el);
    show_color_table(prop);
    return void(0);
   }
   
   show_color_table = function(prop){
    var top = prop.offset.top + prop.size.height;
    var left = prop.offset.left;
    var txt = '<h3>Цвет текста</h3><table class="editor-color-table">';
    txt += '<tr><td data-color="#000000"></td><td data-color="#A0522D"></td><td data-color="#556B2F"></td><td data-color="#006400"></td><td data-color="#483D8B"></td><td data-color="#000080"></td><td data-color="#4B0082"></td><td data-color="#2F4F4F"></td></tr>';
    txt += '<tr><td data-color="#8B0000"></td><td data-color="#FF8C00"></td><td data-color="#808000"></td><td data-color="#008000"></td><td data-color="#008080"></td><td data-color="#0000FF"></td><td data-color="#708090"></td><td data-color="#696969"></td></tr>';
    txt += '<tr><td data-color="#FF0000"></td><td data-color="#F4A460"></td><td data-color="#9ACD32"></td><td data-color="#2E8B57"></td><td data-color="#48D1CC"></td><td data-color="#4169E1"></td><td data-color="#800080"></td><td data-color="#808080"></td></tr>';
    txt += '<tr><td data-color="#FF00FF"></td><td data-color="#FFA500"></td><td data-color="#FFFF00"></td><td data-color="#00FF00"></td><td data-color="#00FFFF"></td><td data-color="#00BFFF"></td><td data-color="#9932CC"></td><td data-color="#C0C0C0"></td></tr>';
    txt += '<tr><td data-color="#FFC0CB"></td><td data-color="#F5DEB3"></td><td data-color="#FFFACD"></td><td data-color="#98FB98"></td><td data-color="#AFEEEE"></td><td data-color="#ADD8E6"></td><td data-color="#DDA0DD"></td><td data-color="#FFFFFF"></td></tr></table>';
    $('#editor').append('<div id="popup" style="position:absolute;top:' + top + ';left:' + left + ';" />');
    $('#popup').html(txt);
    for(i = 0; i < 40; i++)
   $('#popup table td:eq(' + i + ')').css('background', $('#popup table td:eq(' + i + ')').data('color'));
    $('#popup table td').html('&nbsp;');
   }
   
   $('#popup table.editor-color-table td').live('click', function(){
    var color = $(this).data('color');
    insTag('[color=' + color + ']', '[/color]', true);
    $('#popup').remove();
   });

Здесь все просто. Сначала, мы удаляем окно, если такое есть, и создаем его заново. Генерируем html-код таблицы. Каждая ячейка таблицы содержит данные о цвете. Потом с помощью цикла проходим по каждой ячейке и закрашиваем тем цветом, данные о котором она содержит. Вы можете задать резонный вопрос: А не проще ли сразу генерировать таблицу со style="background-color: #xxxxxx"? Может и проще, но дело в том что потом проще редактировать выводимую цветовую гамму. Если бы мы сделали "как проще", то нам пришлось бы менять в два раза больше значений, дублирующих друг друга. А так, в атрибуте data-color мы сразу задаем цвет и значение которое будем вставлять в тег.
Еще нам надо прикрепить обработчик события click() на каждую ячейку. Здесь мы извлекаем данные о цвете и вставляем в начальный тег и вызываем insTag, после чего удаляем окошко выбора цвета.

Следующие 8 кнопок меня нашего редактора сами вызывают функцию insTag() с определенными параметрами. Поэтому на них мы задерживаться не будем и пойдем дальше. А дальше у нас кнопка вставки ссылки. Для ее работы, так же как и для работы кнопки вставки e-mail, нам понадобятся следующие функции:

var label = new Object;
 label = {
  video: 'Видео',
  link: 'Ссылка',
  image: 'Изображение',
  email: 'E-mail',
  media: 'Медиа'
 }
 
 var tags = new Object;
 tags = {
  video:{
   start: '[video]',
   end: '[/video]'
  },
  link:{
   start: '[link href=',
   startEnd: ']',
   end: '[/link]'
  },
  image:{
   start: '[image]',
   end: '[/image]'
  },
  email:{
   start: '[email=',
   startEnd: ']',
   end: '[/email]'
  },
  media:{
   start: '[media]',
   end: '[/media]'
  }
 }

   pre_ins2 = function(id){
    $('#popup').remove();
    var el = $('#' + id);
    var prop = get_obj_prop(el);
    show_ins2(id, prop);
    return void(0);
   }

   get_obj_prop = function(el){
    var offset = el.offset();
    var size = new Object;
    size.height = el.outerHeight();
    size.width = el.outerWidth();
    return ({
     offset: offset,
     size: size
    });
   }

   show_ins2 = function(type, prop){
    var top = prop.offset.top + prop.size.height;
    var left = prop.offset.left;
    var txt = '';
    el = document.getElementById('text');
    if ((document.selection))
   {
    el.content.focus();
    txt = document.form.document.selection.createRange().text;
   }
   else if(el.selectionStart != undefined) {
    var element    = el;
    var str     = element.value;
    var start    = element.selectionStart;
    var length    = element.selectionEnd - element.selectionStart;
    txt = str.substr(start, length);
   }
    var tag = '<div id="popup" style="position:absolute;top:' + top + ';left:' + left + ';" />';
    var ins_form = '<h3>' + label[type] + '</h3><small><br />Введите url<br /></small><input type="text" name="url" id="url" /><br /><small>Введите текст</small><br /><input type="text" name="txt" id="txt" value="' + txt + '" /><br /><br /><button onclick="doIns2(\'' + type + '\');">Вставить</button>';
    $('#editor').append(tag);
    $('#popup').html(ins_form);
   }

   doIns2 = function(type){
    var url = $('#url').val();
    var txt = $('#txt').val();
    if(!txt) txt = 'No_text';
    insTag(tags[type].start + url + tags[type].startEnd, tags[type].end, txt);
    $('#popup').remove();
   }

Для удобства создаем объекты, которые будут хранить определенные данные. Потом при вызове функции pre_ins2(id) мы получаем нужный нам объект, необходимые данные о положении на странице с помощью get_obj_prop(). Потом вызываем show_ins2(). В ней мы генерируем окошко в нужном месте страницы. Поле "введите текст" будет заполнено выделение, если выделен какой-то текст и браузер может работать с выделениями. После того, как пользователь заполнит форму и нажмет кнопку вставки, произойдет вызов функции doIns2(). Дальше все понятно.

Функция для вставки данных, где пользователь может добавить какое-то одно значение pre_ins() организовано точно также. Здесь практически они продублированы, потому что у меня не было времени на какие-либо раздумья, когда я выполнял этот заказ, изменения приходилось вносить очень быстро, поэтому много недоработок. Итак, листинг:

pre_ins = function(id){
    $('#popup').remove();
    var el = $('#' + id);
    var prop = get_obj_prop(el);
    show_ins(id, prop);
    return void(0);
   }

   show_ins = function(type, prop){
    var top = prop.offset.top + prop.size.height;
    var left = prop.offset.left;
    var tag = '<div id="popup" style="position:absolute;top:' + top + ';left:' + left + ';" />';
    var ins_form = '<h3>' + label[type] + '</h3><br /><small>Введите адрес</small><br /><input type="text" name="url" /><br /><br /><button onclick="doIns(\'' + type + '\');">Вставить</button>';
    $('#editor').append(tag);
    $('#popup').html(ins_form);
   }
   
   doIns = function(type){
    var str = $('#popup input').val();
    insTag(tags[type].start, tags[type].end, str);
    $('#popup').remove();
   }

Тут без объяснений.

Теперь у нас осталась кнопка добавления смайликов. Организуем работу следующим образом:

var smiles_shown = false;

   show_smiles = function(){
    var el = $('#smiles');
    var w_width = $('#work_pan').width() + 200;
    if(smiles_shown){
     el.animate({width: 0}, 1000);
     $('#work_pan').animate({width: w_width}, 1000);
     smiles_shown = false;
     return;
    }
    smiles_shown = true;
    w_width = $('#work_pan').width() - 200;
    $('#work_pan').animate({width: w_width}, 1000);
    el.animate({width: 200}, 1000);
   }

Для начала объявим одну переменную, которая будет хранить информацию о том, спрятан блок смайликов или нет. Дальше аккуратненько выдвигаем.

Напишем еще немного кода, для решения разных проблем, которые могут возникнуть при перезагрузке страницы и чтобы кликнув на textarea исчезали разные окна, требующие ввода информации от пользователя:

$('textarea').focus(function(){
    $('#popup').remove();
   });

   $('#fontFamily').css('font-family', $('#fontFamily').val());

Теперь нам весь этот скрипт надо поместить вот сюда:

$(function(){

       // Сюда мы помещаем теперь все написанное

});

Вот и все. Если кто-то желает посмотреть все это целиком, то вот ссылка на архив, в котором находится весь редактор.

На днях мы с вами напишем php обработчик для этого редактора. А пока до новых встреч!

6 комментариев:

  1. if ((document.selection)) - на эту строку браузер вообще не реагирует, даже если есть выделение

    ОтветитьУдалить
    Ответы
    1. if (document.selection) - данная строка поверят, может ли браузер работать выделением а не проверяет есть выделение на странице или нет

      Удалить
  2. вместо textarea используйте iframe, designMode и execCommand.

    ОтветитьУдалить
  3. stile="background-color: #xxxxxx" все таки style )))

    ОтветитьУдалить