SimpleGallery: расширение функционала, часть 2
- Дополнения
- SimpleGallery
- Расширение функционала, часть 2
SimpleGallery: расширение функционала, часть 2
В этой статье речь пойдет о том, как изменить интерфейс SimpleGallery под конкретную задачу. При этом файлы плагина останутся без изменений — а значит, работа не пропадет после обновления SimpleGallery. Дальше много текста и говнокода.
Задача была такая: cайт для производителя мебели, состоящий, по сути, из галерей с фотками мебели, которые были реализованы с помощью SimpleGallery. Когда сайт уже был готов, заказчик внезапно пожелал, чтобы с фотографией выводились:
- цена;
- размеры;
- материал.
Кроме этого, для некоторых фотографий нужно показывать значок «Наша работа». Ну и пусть поле «Описание» редактируется с помощью TinyMCE (: А чтобы было красиво, основные поля и дополнительные поля будут редактироваться в отдельных вкладках.
Для решения задачи нужно изменить форму редактирования картинки, добавив туда дополнительные поля, а также внести изменения в контроллер и класс, отвечающий за работу с базой данных (модель).
Так как выборки и сортировки по новым полям не нужны, было решено не создавать дополнительные поля в таблице, а воспользоваться полем sg_properties, в котором хранятся параметры картинки в формате json. А для флажка «Наша работа» пригодится существующее поле sg_add.
Расширение классов
Так как было решено не добавлять дополнительные поля в таблицу, то достаточно внести изменения в контроллер. А именно изменить метод edit. Для этого создадим файл custom.class.php в папке assets/plugins/simplegallery/lib/:
<?php namespace SimpleGallery; require_once(MODX_BASE_PATH . 'assets/plugins/simplegallery/lib/controller.class.php'); //расширяем класс sgController class customController extends sgController { public function edit() { $out = array(); $id = isset($_REQUEST['sg_id']) ? (int)$_REQUEST['sg_id'] : 0; if ($id) { $fields = array( 'sg_title' => $_REQUEST['sg_title'], 'sg_description' => $_REQUEST['sg_description'], 'sg_add' => $_REQUEST['sg_add'] ); $fields['sg_isactive'] = isset($_REQUEST['sg_isactive']) ? 1 : 0; $row = $this->data->edit($id); $_data = $row->toArray(); //получили значения дополнительных полей $addProperties = array( 'price' => $_REQUEST['price'], 'msize' => $_REQUEST['msize'], 'material' => $_REQUEST['material'] ); //добавили их в поле sg_properties $_data['sg_properties'] = array_merge($_data['sg_properties'], $addProperties); //отправили все поля на сохранение $_data = array_merge($_data, $fields); $out['success'] = $row->fromArray($_data)->save(); } else { $out['success'] = false; } return $out; } }
Теперь в настройки плагина SimpleGallery вписываем имя нашего класса (SimpleGallery\customController) и создаем еще один плагин на событие OnManagerPageInit, который этот класс загрузит:
$e = $modx->event; if ($e->name == "OnManagerPageInit") { if (isset($invokedBy) && $invokedBy=="SimpleGallery") include_once(MODX_BASE_PATH.'assets/plugins/simplegallery/lib/custom.class.php'); }
На этом с серверной частью все.
Изменение интерфейса
Здесь возникает две подзадачи. Во-первых, нужно изменить форму редактирования, во-вторых, внести изменения в обработку этой формы.
Если посмотреть папку assets/plugins/simplegallery/js/tpl/, то можно обнаружить там файлы с расширением *.handlebars. Это шаблоны для js-шаблонизатора Handlebars, в которые вынесены формы SimpleGallery + шаблон для превьюшки.
Синтаксис Handlebars достаточно простой и работа с такими шаблонами не сильно отличается от работы с чанками. Однако простое изменение файла editForm.handlebars ничего не даст — шаблоны нужно компилировать. Этот аспект я рассматривать не буду, скажу лишь, что потребуется установить где-нибудь nodejs и прочитать небольшую инструкцию в файле readme.txt, который можно найти в папке с шаблонами.
Создадим файл editFormMebel.handlebars:
![]()
ID {{data.sg_id}} {{sgLang.file}} {{data.sg_image}} {{sgLang.size}} {{data.sg_properties.width}}x{{data.sg_properties.height}}, {{bytesToSize data.sg_properties.size}} {{sgLang.createdon}} {{data.sg_createdon}} {{sgLang.save}}
{{sgLang.cancel}}
{{sgLang.save}}
{{sgLang.cancel}}
Разметка, конечно, не очень, но не важно. Компилируем:
handlebars editFormMebel.handlebars -f editFormMebel.js -m
Эта команда скомпилирует шаблон в файл editFormMebel.js и минифицирует его.
Теперь создадим еще одну форму, для редактирования описания с помощью TinyMCE. Эта форма будет показываться в окошке, если кликнуть по ссылке «Редактировать». Файл rteForm.handlebars:
{{sgLang.save}}
{{sgLang.cancel}}
Компилируем:
handlebars rteForm.handlebars -f rteForm.js -m
Файлы editFormMebel.js и rteForm.js копируем в assets/plugins/simplegallery/js/tpl/
Теперь добавим методы для вызова окошка с формой редактирования описания, для инициализации TinyMCE, а также изменим метод sgHelper.edit(). Создаем в папке assets/plugins/simplegallery/js/plugin/ файл editForm.js:
(function ($) { sgHelper.rteForm = function (textarea) { //готовим данные для передачи в шаблон var context = { textarea: textarea.val(), modxTheme: sgConfig._modxTheme, modxSiteUrl: sgConfig._modxSiteUrl, sgLang: _sgLang }; //получаем заполненный шаблон var rteForm = $(Handlebars.templates.rteForm(context)); //создаем окошко rteForm.window({ modal: true, title: "Редактирование", collapsible: false, minimizable: false, maximizable: false, resizable: false, onOpen: function () { //когда окошко создано, нужно назначить обработчики для кнопок в форме $('#rteCancel').click(function (e) { rteForm.window('close', true); }); $('#rteSave').click(function (e) { //берем то, что наредактировали и сохраняем обратно в поле "Описание" var content = tinyMCE.activeEditor.getContent(); textarea.val(content); rteForm.window('close', true); }); //цепляем редактор к полю в форме sgHelper.initRTE(); }, onClose: function () { //если окошко закрывается, то удаляем его вообще //после закрытия окна с редактором нужно восстановить оверлей var mask = $('.window-mask'); sgHelper.destroyWindow(rteForm); $('body').append(mask); } }) }; sgHelper.edit = function (image) { var data = image.data('properties'); var context = { data: data, modxTheme: sgConfig._modxTheme, modxSiteUrl: sgConfig._modxSiteUrl, sgLang: _sgLang }; //получаем форму редактирования var editForm = $(Handlebars.templates.editFormMebel(context)); editForm.window({ modal: true, title: sgHelper.escape(this.stripText(data.sg_title, 80)), doSize: true, collapsible: false, minimizable: false, maximizable: false, resizable: false, onOpen: function () { //создаем вкладки $('#editTabs').tabs({ border: false, onSelect: function (title, index) { if (index) { var tab = $('#editTabs').tabs('getTab', index); $(tab).show(); editForm.window('resize'); } } }); //обработчики кнопок //показываем окошко с редактором $('.btn-rte').click(function (e) { sgHelper.rteForm($('textarea', '#sgEdit')); }); $('.sgEditCancel').click(function (e) { editForm.window('close', true); }); $('.sgEditSave').click(function (e) { $.post( sgConfig._xtAjaxUrl + '?mode=edit', $('#sgForm,#sgAdd').serialize(), function (data) { data = sgHelper.getData(data); if (data.success) { editForm.window('close', true); sgHelper.update(); } else { $.messager.alert(_sgLang['error'], _sgLang['save_fail']); } }) }) }, onClose: function () { sgHelper.destroyWindow(editForm); } }); } })(jQuery);
Инициализацию TinyMCE я вынес в отдельный файл, assets/plugins/simplegallery/js/plugin/tinymce.js:
(function ($) { sgHelper.initRTE = function () { tinyMCE.init({ theme: 'advanced', skin: 'cirkuit', skin_variant: '', mode: 'exact', elements: 'rteField', width: '100%', height: '500', language: 'ru', element_format: 'xhtml', schema: 'html4', paste_text_use_dialog: true, document_base_url: sgConfig._modxSiteUrl, relative_urls: true, remove_script_host: true, convert_urls: true, force_br_newlines: false, force_p_newlines: true, forced_root_block: 'p', //valid_elements: mce_valid_elements, popup_css_add: '/assets/plugins/tinymce/style/popup_add.css', theme_advanced_source_editor_height: 400, accessibility_warnings: false, theme_advanced_toolbar_location: 'top', theme_advanced_statusbar_location: 'bottom', theme_advanced_toolbar_align: 'ltr', theme_advanced_font_sizes: '80%,90%,100%,120%,140%,160%,180%,220%,260%,320%,400%,500%,700%', content_css: '/assets/plugins/tinymce/style/content.css', formats: { alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'justifyleft'}, alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'justifyright'}, alignfull: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'justifyfull'} }, apply_source_formatting: true, remove_linebreaks: false, convert_fonts_to_spans: true, plugins: 'visualblocks,autolink,inlinepopups,autosave,save,advlist,style,fullscreen,advimage,paste,advlink,media,contextmenu,table,youtubeIframe', theme_advanced_buttons1: 'undo,redo,|,bold,forecolor,backcolor,strikethrough,formatselect,fontsizeselect,pastetext,pasteword,code,|,fullscreen,help', theme_advanced_buttons2: 'image,media,youtubeIframe,link,unlink,anchor,|,justifyleft,justifycenter,justifyright,|,bullist,numlist,|,blockquote,outdent,indent,|,table,hr,|,visualblocks,styleprops,removeformat', theme_advanced_buttons3: '', theme_advanced_buttons4: '', theme_advanced_resize_horizontal: false, external_link_list_url: '/assets/plugins/tinymce/js/tinymce.linklist.php', template_external_list_url: '/assets/plugins/tinymce/js/get_template.php', template_popup_width: 550, template_popup_height: 350, theme_advanced_blockformats: 'p,h1,h2,h3,h4,h5,h6,div,blockquote,code,pre', theme_advanced_styles: 'left=justifyleft;right=justifyright', theme_advanced_disable: '', theme_advanced_resizing: true, fullscreen_settings: { theme_advanced_buttons1_add_before: 'save' }, plugin_insertdate_dateFormat: '%d-%m-%Y', plugin_insertdate_timeFormat: '%H:%M:%S', entity_encoding: 'named', file_browser_callback: 'mceOpenServerBrowser', paste_text_sticky: true, setup: function (ed) { ed.onPostProcess.add(function (ed, o) { // State get is set when contents is extracted from editor if (o.get) { o.content = o.content.replace('<p>{' + '{', '{' + '{'); o.content = o.content.replace('}}</p>', '}}'); o.content = o.content.replace(/<p>\[([[!\~\^])/g, '[$1'); o.content = o.content.replace(/([\]\!\~\^])\]<\/p>/g, '$1]'); } }); }, onchange_callback: false, valid_elements: "*[*]" }) } })(jQuery);
Здесь я просто взял код инициализации TinyMCE из редактирования документа. Да, нужно учитывать, чтобы это все работало, на странице должен быть включен TinyMCE.
Теперь создаем в папке assets/plugins/simplegallery/js/ файл custom.json:
{ "scripts": { "editFormMebelTemplate": { "version": "1.0.0", "src": "assets/plugins/simplegallery/js/tpl/editFormMebel.js" }, "editForm": { "version": "1.0.0", "src": "assets/plugins/simplegallery/js/plugin/editForm.js" }, "sgTinyMCE": { "version": "0.0.0", "src": "assets/plugins/simplegallery/js/plugin/tinymce.js" }, "rteForm": { "version": "1.4.1", "src": "assets/plugins/simplegallery/js/tpl/rteForm.js" } } }
Если есть файл custom.json, то SimpleGallery загружает из него скрипты дополнительно к скриптам, описанным в файле scripts.json. Аналогично загружаются и css-файлы.
На этом работа закончена. Итак, измененная форма:

Вкладка с дополнительными полями:

Редактор:

Редактор описания мне понадобился сегодня для другого сайта, поэтому я решил положить его в коробку (: Так что если кому-то вдруг нужно, но разбираться лень, то достаточно переименовать файл _custom.json.