Предпросмотр для InstantCMS 2

+28
5.22K

Всем привет!

В ходе работы над одним дополнением, которым поделюсь позже, вспомнилась идея: "а не плохо было бы перед публикацией записи посмотреть что получится". Многие ресурсы имеют такой функционал в своем комплекте, а чем инстант хуже?))
Сначала двигался по пути реализации дополнительного функционала указанного выше дополнения, затем посмотрел в сторону отдельного компонента и, наконец, остановился на хаке системного компонента content. При удачном стечении обстоятельств, надеюсь опубликовать pull request. Ну а что это за обстоятельства — читаем ниже...

Иллюстрация

Этот пост скорее представляет собой развернутую тему для обсуждения сообществом "как лучше сделать" некоторые моменты в функционале предпросмотра создаваемых или редактируемых записей.

Ближе к телу:
1. Создаем файл ..\system\controllers\content\actions\item_preview.php и кидаем в него следующий код
  1. <?php
  2.  
  3. class actionContentItemPreview extends cmsAction {
  4.  
  5. public function run($data){
  6.  
  7. list($ctype, $item) = $data;
  8.  
  9. if (!isset($item['id'])){ $item['id'] = 1; }
  10. if (!isset($item['parent_id'])){ $item['parent_id'] = false; }
  11.  
  12. $item['ctype_name'] = $ctype['name'];
  13. $item['date_pub'] = date("d.m.Y");
  14. $item['user'] = array(
  15. 'id' => $this->cms_user->id,
  16. 'nickname' => $this->cms_user->nickname
  17. );
  18.  
  19. if ($ctype['is_cats'] && $item['category_id'] > 1){
  20. $item['category'] = $this->model->getCategory($ctype['name'], $item['category_id']);
  21. }
  22.  
  23. // Получаем поля для данного типа контента
  24. $fields = $this->model->getContentFields($ctype['name']);
  25.  
  26. // Парсим значения полей
  27. foreach($fields as $name=>$field){
  28. if (!isset($item[$name])) { $item[$name] = false; continue; }
  29. $fields[ $name ]['html'] = $field['handler']->setItem($item)->parse( $item[$name] ); // <- предложение внести флаг is_preview (полезно для нестандартных полей)
  30. }
  31.  
  32. // Получаем поля-свойства
  33. if ($ctype['is_cats'] && $item['category_id'] > 1){
  34. $props = $this->model->getContentProps($ctype['name'], $item['category_id']);
  35. $props_values = $this->model->getPropsValues($ctype['name'], $item['id']);
  36. }
  37.  
  38. // Рейтинг
  39. if ($ctype['is_rating'] && $this->isControllerEnabled('rating')){
  40. $item['rating_widget'] = cmsCore::getController('rating')->getWidget(1, false, false);
  41. }
  42.  
  43. // Формируем теги
  44. if ($ctype['is_tags']){
  45. $item['tags'] = array_filter( explode(',', trim($item['tags'])) );
  46. }
  47.  
  48. list($ctype, $item, $fields) = cmsEventsManager::hook('content_before_item', array($ctype, $item, $fields));
  49. list($ctype, $item, $fields) = cmsEventsManager::hook("content_{$ctype['name']}_before_item", array($ctype, $item, $fields));
  50.  
  51. $html = $this->cms_template->renderContentItem($ctype['name'], array(
  52. 'item' => $item,
  53. 'ctype' => $ctype,
  54. 'fields' => $fields,
  55. 'props' => isset($props) ? $props : false,
  56. 'props_values' => isset($props_values) ? $props_values : false,
  57. ), new cmsRequest(array(), cmsRequest::CTX_INTERNAL));
  58.  
  59. $this->cms_template->renderJSON(array(
  60. 'errors' => false,
  61. 'html' => $html
  62. ));
  63.  
  64. }
  65.  
  66. }
2. Далее модифицируем файлы ..\system\controllers\content\actions\item_add.php и ..\system\controllers\content\actions\item_edit.php
для первого добавляем перед $item = $this->model->addContentItem($ctype, $item, $fields); (205 строка), для второго $item = $this->model->updateContentItem($ctype, $id, $item, $fields); (182 строка)
  1. // Блок предпросмотра
  2. if ($this->request->has('is_preview')){
  3.  
  4. $this->runAction('item_preview', array(array($ctype, $item)));
  5.  
  6. }
и также добавляем для item_add.php (238 строка), для item_edit.php (213 строка) перед if ($errors){ cmsUser::addSessionMessage(LANG_FORM_ERRORS, 'error'); }:
  1. // Запрос предпросмотра с ошибками заполнения полей
  2. if ($errors && $this->request->has('is_preview')){
  3.  
  4. $this->cms_template->renderJSON(array(
  5. 'errors' => $errors,
  6. 'message' => LANG_FORM_ERRORS
  7. ));
  8.  
  9. }
3. Теперь добавим кнопку взаимодействия с новым функционалом и стилизуем её. Дописываем в файле ..\templates\default\controllers\content\item_form.tpl.php строку в рендере формы (она помечена комментарием):
  1. $this->renderForm($form, $item, array(
  2. 'action' => '',
  3. 'method' => 'post',
  4. 'toolbar' => false,
  5. 'hook' => array(
  6. 'event' => "content_{$ctype['name']}_form_html",
  7. 'param' => array(
  8. 'do' => $do,
  9. 'id' => $do=='edit' ? $item['id'] : null
  10. )
  11. ),
  12. 'append_html' => html_button('Предпросмотр', 'preview', "icms.content.getPreview();", array('class'=>'button-preview', 'data-description' => 'Предварительный просмотр')), // <- Вот эта строка !!!
  13. ), $errors);
Здесь заголовок кнопки и описание поля вывода предпросмотра прописаны хардкорно, но в готовом решении конечно же будут языковые константы.
data-description — необходим чтобы передать текст в js-скрипт на фронтенде. Есть и другие способы, но я выбрал этот.
Добавляем немного стилей в конец файла ..\templates\default\css\theme-content.css
  1. #preview {
  2. border: 5px solid #d6d6d6;
  3. padding: 10px;
  4. margin: 7px 0 20px;
  5. }
  6. .preview-title {
  7. margin-bottom: 10px;
  8. font-size: 12px;
  9. color: #aaa;
  10. }
  11. input.button-preview {
  12. float: right;
  13. background: #dedede;
  14. color: #333;
  15. }
  16.  
  17. input.button-preview:hover {
  18. background: #e7e7e7;
  19. }
4. И небольшой скрипт. Добавляем в ..\templates\default\js\content.js функцию

  1. this.getPreview = function() {
  2.  
  3. var preview_title = $('.button-preview').data('description');
  4. // объект формы контента
  5. var $form = $('.button-preview').closest('form');
  6.  
  7. var url = $form.attr('action');
  8.  
  9. // получаем данные из формы
  10. var form_data = icms.forms.toJSON($form);
  11. // добавляем данные из нестандартных полей
  12. // $('[data-preview]', $form).each(function(i, element){
  13. // var key = $(element).closest('.field').attr('id').substr(2);
  14. // form_data[key] = $(element).data('preview');
  15. // });
  16.  
  17. form_data.submit = true; // флаг события submit
  18. form_data.is_preview = true; // устанавливаем флаг предпросмотра
  19.  
  20. $.post(url, form_data, function(result) {
  21.  
  22. $('#preview').remove();
  23.  
  24. // Очищаем старые сообщения об ошибках
  25. $('.sess_messages').remove();
  26. $('.error_text', $form).remove();
  27. $('.field_error', $form).removeClass('field_error');
  28.  
  29. var errors = result.errors || false;
  30.  
  31. // если есть ошибки заполнения полей...
  32. if (errors instanceof Object) {
  33. // ...выводим их
  34. for (var key in errors) {
  35. if (key === 'length' || !errors.hasOwnProperty(key)) continue;
  36. $('#f_'+key)
  37. .addClass('field_error')
  38. .prepend('<div class="error_text">'+errors[key]+'</div>');
  39. }
  40. // добавляем сообщение о наличии ошибок на форме
  41. $('#body').prepend('<div class="sess_messages"><div class="message_error">'+result.message+'</div></div>');
  42.  
  43. return;
  44. }
  45.  
  46. // удаляем скрипты кроме подключаемых по ссылке
  47. var html = $(result.html.bold());
  48. html.find('script:not([src])').remove();
  49. // выводим html страницы
  50. $form.before('<div id="preview"><div class="preview-title">'+preview_title+'</div>'+html.html()+'</div>');
  51. }, 'json');
  52.  
  53. }
  54.  
Чистим кэш браузера и проверяем работу.

Вопросы и предложения

Как я написал в начале поста есть ряд моментов которые требуют обсуждения:

1. Сторонние разработчики могут делать и делают специфические поля, которые рендеряться по своему внутреннему механизму вывода контента (например в моем компоненте Опросы). Для этих целей предлагаю ввести флаг is_preview в функцию ..\system\core\formfield.php parse($value) -> parse($value, $is_preview=false). Соответственно по этому флагу можно особым образом выводить html-содержимое "нестандартных" полей.

2. Для аналогичных целей с п.1. можно добавить обработчик получения данных поля из атрибута data-preview. В js-скрипте выше этот участок кода закомментирован. Вопрос для разработчиков — нужно ли оно? Или достаточно флага is_preview, или всё же добавить еще возможность формирования данных на фронтэнеде.

3. В представленном решении есть одна большая проблема — подключение стилей CSS! Если поле выводит данные, которые в своем составе содержат стили подключаемые, например, через addCSS(...), то они в текущем виде никак не подгружаются, и соответственно такие данные криво отображаются в области предпросмотра. Тоже самое происходит и с подключаемыми скриптами, функции которых могут запрашиваться при рендере контента, поэтому внутренние скрипты я вычищаю из результатов предпросмотра.

4. Очень проблематично получить ID формы добавления/редактирования контента! Эту проблему я обошел указанием вместо id непосредственно тега form, но механизм получения id был бы не лишним))

5. И в заключении интересует вопрос куда лучше выводить области предпросмотра?
Иллюстрация
Сейчас, по примеру хабра, я показываю превьюшку вверху формы (позиция 1). Может стоит задействовать позицию 2 или 3? Или выводить в модальном окне, или еще какие варианты?

Демо здесь
demo@demo.ru / 123456


UPD 3 вопрос можно решить заменой подключения стилей с addCSS(...) на addCSSFromContext(...). Также и для js — addJSFromContext($file, $comment='')
+1
Алексей Т Алексей Т 8 лет назад #
Отличное дополнение!
Я думаю позиция 2 будет самое то - посмотрел - сохранил.
+1
Jestik Jestik 8 лет назад #
Поддерживаю
+1
Val Val 8 лет назад #
Cтудия Sitestroi:
Отличное дополнение!
Дополнение это слишком громко - очередной хак, с претензией на pull request laugh

Поменял вывод предпросмотра на демо во вторую позицию.
Согласен, здесь более логично увидеть превьюшку.
+1
WebMan WebMan 8 лет назад #
Позиция 3 точно не логична и неудобна - можно исключить.

При размещении и в позиции 1, и в 2, всё равно приходится прокручивать и область предпросмотра, и форму до кнопки "Сохранить". Также нужно учитывать, что обычно предпросмотр используется там, где текст по высоте больше, чем форма. Поэтому на мой взгляд, правильнее оставить предпросмотр в позиции 1. Посмотрел весь текст сверху вниз, что-то решил изменить и форма уже сразу перед глазами - удобно и без лишних движений. Если всё подходит, то кнопка "Сохранить" тут же под формой - прокручивать если и нужно, то невысокую форму и вниз, а не большой текст и в обратном направлении. А если делать в п.2, то для изменения нужно прокручивать весь текст обратно до верха, потом прокрутить форму и там менять в форме - это нелогично, это лишние манипуляции и неудобно.

Да и привычнее видеть кнопку "Сохранить" под формой ввода, а не под текстом. Ведь сохраняем мы не просмотренный текст, а то, что набрано в форме. Если предпросмотр будет в п.2, то при изменении текста в форме и неизменности текста предпросмотра могут быть непонятки: что мы сохраняем, просмотренный текст, раз кнопка "Сохранить" под ним, или набранное в форме? Так что и с этой точки зрения удобнее делать предпросмотр сверху в позиции 1, как Val сразу интуитивно и сделал.
+1
Val Val 8 лет назад #
WebMan:
Посмотрел весь текст сверху вниз, что-то решил изменить и форма уже сразу перед глазами - удобно и без лишних движений. Если всё подходит, то кнопка "Сохранить" тут же под формой - прокручивать если и нужно, то невысокую форму и вниз, а не большой текст и в обратном направлении.
Действительно, если вдуматься, UX лежит именно в описанной вами плоскости))

WebMan:
Да и привычнее видеть кнопку "Сохранить" под формой ввода, а не под текстом. Ведь сохраняем мы не просмотренный текст, а то, что набрано в форме.
Тоже согласен с этим

WebMan, а как вы смотрите на модальное окно предпросмотра?
+1
Zau4man Zau4man 8 лет назад #
Имхо, надо пилить предпросмотр полей fieldText и все. Остальные не трогать.
Какая разница пользователю как будут выглядеть теги, или заголовок. Или прикрепленный к материалу файл. Или карта из поля карты.

Избавьте разработчиков и себя от ненужной возни. Сделайте предпросмотр для поля html.
+2
Val Val 8 лет назад #
Если мы юзаем WYSIWYG редакторы для html-полей, зачем тогда вообще предпросмотр? IMHO просматривая конечный результат мы хотим увидеть до публикации как все должно смотреться, возможно пробежаться взглядом по тексту чтобы найти различные неточности и т.д. Да, все это можно сделать и без функционала предпросмотра))

Zau4man:
Избавьте разработчиков и себя от ненужной возни.
О кокой возне вы говорите? Если поле рендерится (а это большинство случаев) то ничего не надо специально делать, если нет, то решение остается на выбор разработчика. Хочет он показать результат в предпросмотре - внесет пару строк кода, считает что это лишнее - ничего писать не надо.

Примеры таких нестандартных полей, как уже упоминал в посте, мои опросы - в форме редактирования пользователь видит менеджер опросов, т.е. список их заголовков, а в записи - уже выводятся полноценные вопросы с вариантами ответов. Или недавно опубликованное поле Loadыря "Список количества", и множество других подобных полей. Это примеры полей которые интересно посмотреть в "препродакшене", т.е. перед публикацией записи.
+1
kirkr kirkr 8 лет назад #
Val, к сожалению редакторы не дают возможность видить все, так как не всегда корректно форматируют. Поэтому данное дополнение очень хорошее решение, если оно будет доступно людям, то ничего плохого не произойдет.

Можно сделать предпросмотр и сейчас, не ставя публиковать в статьях (типа черновик). Но если решение будет универсальным, то бует удобно. даже пускай если будет открываться в отдельном окне.
+1
Val Val 8 лет назад #
Без публикации получается, как вы правильно заметили, что то вроде черновика)) И основной момент здесь в том что запись сохраняется в БД.
kirkr:
Но если решение будет универсальным, то бует удобно. даже пускай если будет открываться в отдельном окне.
Именно универсальности я и хочу добиться - поэтому и опубликовал текущий пост с вопросами.
Вы за вариант модального окна?
+1
Val Val 8 лет назад #
Нашел косячек в положении кнопки "Предпросмотр". Если включить опцию модерации контента перед публикацией, то при добавлении записи показывается надпись "Материал будет опубликован после проверки модератором", которая налазит на кнопку "Предпросмотр".
Отсюда вопрос: куда девать эту пимпу?
Также есть предложение при включенной опции премодерации, переименовывать кнопку "Сохранить" в "Отправить на модерацию", тогда и надпись о публикации после проверки можно убрать совсем, или выводить ее в виде хинта под кнопкой. Получается более наглядный UX))
""
0
GluK GluK 8 лет назад #
сделай слева от "сохранить"
+1
Val Val 8 лет назад #
Слева от "Сохранить" рассматривал такой вариант в самом начале. Меня он смущает последовательностью кнопок с точки зрения UX и UI. Кнопка "Сохранить" здесь главная - это основное действие на странице, поэтому она и выделена цветом и должна стоять по ходу движения глаз и мыши на первом месте.
Вот такой вариант мне кажется неуместным:
""

А как поместить "Предпросмотр" справа от "Сохранить" (не выравнивание по правому краю, а следом за сохранить) без костылей я пока не знаю.
+6
Val Val 8 лет назад #
В идеале считаю такой вариант лучшим:
""

joke
0
Def Def 8 лет назад #
а передаются только поля или свойства тоже передаются? и можно ли в определенных типах контента убирать кнопку предпросмотра?)
+1
Val Val 8 лет назад #
Схема работы предпросмотра полностью совпадает с сохранением записи типа контента, но непосредственно перед записью данных в БД они (данные) отправляются через рендер обратно клиенту и выводятся в требуемое место. Таким образом, передается ровно так как в движке изначально реализовано.
Зайдите на демо-сайт и потестируйте =)
+1
vikont vikont 8 лет назад #
А куда само дополнение делось?
Есть только пункт 1 описания со Спойлером и все!
+1
Val Val 8 лет назад #
Проблему поправили smile
+1
vikont vikont 8 лет назад #
Спасибо!
В отдельный компонент когда завернете?
Хорошее дело. А вот Хаки только головняк добавляют.
0
vikont vikont 8 лет назад #
У меня не заработал. Все прописал как в описании - кнопки нет
Перенес настройки из Дефолтного шаблона в свой кнопка появилась, но не работает!
Возможно неправильно прописал код js/ Куда лучше всего вносить код из 4-го пункта и в какой шаблон?
+1
Val Val 8 лет назад #
В данном случае кнопку гораздо правильнее выводить не через "append_html", а напрямую в шаблоне формы рядом с кнопкой сабмита.
vikont:
Куда лучше всего вносить код из 4-го пункта и в какой шаблон?
Нужно вносить в тот файл который у вас грузится клиенту (вероятнее всего это файл с вашего шаблона).
Зайдите на демосайт и посмотрите как там реализовано.
0
vikont vikont 8 лет назад #
Мне конечно приятно, что считают знатоком РНР, но увы, это совсем не так!
Поэтому будет проще, если как выше напишите что и куда вставить.
Надеюсь когда все получится, сделать патч к Компоненту Мои патчи, чтобы другие могли воспользоваться Хаком без головной боли.
Думаю именно по этой причине не появилось много заинтересованных. Сразу не пошло...
+1
Val Val 8 лет назад #
vikont, готов вам помочь, но напишите мне подробнее что у вас не получается. Просто переписывать пост сюда не вижу смысла. Вроде старался максимально подробно описать с указанием где какую строчку заменить и на что и как))
Попробуйте еще раз на дефолтном шаблоне чистого движка. Если получится, то нужно искать проблему в стороннем шаблоне (возможно что-то не вписали или не туда вписали и, например, просто не выводится кнопка, а функционал присутствует) ?
Про демосайт, я имел ввиду, посмотреть через devtools что и как, например, в скриптах и html-коде страниц. Но там все ровно так как и описано в тексте поста.
Обращайтесь в личку по возникшим трудностям, победим их вместе smile
+1
Val Val 8 лет назад #
Делать в виде компонента данный функционал не планируется, в первую очередь по причине невозможности обойтись без хаков. Идея предпросмотра была опубликована для получения фибдека от сообщества и формирования своеобразного видения. Судя по всему функционал не вызвал особого интереса у большинства, поэтому заинтересоанные могут пользоваться хаком.
+2
vikont vikont 8 лет назад #
Тогда ему самое место в коробочке! Лишним предосмотр бывает только у тех кто мало постит руками!
+1
vikont vikont 8 лет назад #
Уважаемый Val, ну не каждый же такой въедливый как я и будет настойчиво допытываться как реализовать ваше предложение! smile
Сам предосмотр нужен! Конечно можно без него обойтись, да и без вилок обходятся..., но макароны удобнее вилкой...
Вот этот предосмотр, когда он есть, будет использоваться некоторыми на постоянной основе! Остальные ихредка по ходу жизни!
Так что в коробочку его и никаких хаков! Будет приятное расширение функционала с нуля.
+1
Val Val 8 лет назад #
Я тоже за предпросмотр)) Обязательно вернусь к нему в недалеком будущем.
p.s. а на счёт "коробки"... в последнее время какая-то тенденция нехорошая любое дополнение тянуть в коробку. Вот мне оно нужно, и вам и еще 5-10 пользователям, но абсолютному большинству нет! Так что до коробки надо еще дорасти =)
Но почва подготовлена, и семена посажены...
0
Михаил Михаил 8 лет назад #
Уважаемый Val, сделал по вашей инструкции - все работает, но есть два вопроса)
1 Использую редактор markitup, и в предпросмотре нет абзацев http://joxi.ru/p27R9Dgig6pDm7 Как это можно исправить?
2 Возможно ли, чтобыпосле того, как первый раз нажата кнопка Предпросмотр, вместе с формой (и после нее) добавилась еще одна кнопка Сохранить?
+1
Val Val 8 лет назад #
1. Тут скорее вопрос к самому редактору. Он также выводит текст без абзацев если просто нажать на кнопку "сохранить". Глубоко не разбирался но предполагаю что абзацы нужно заполнять в ручную - <br> или <p>...</p>

2. Если я правильно вас понял, можно добавить в конец функции getPreview() следующий код:
Код JAVASCRIPT:
  1. $form.find('.button-submit').clone().appendTo('#preview').click(function(){
  2. $form.submit();
  3. });
0
Михаил Михаил 8 лет назад #
Наверное, я не туда добавил (после 51 строки) т.к. по клику на Предпросмотр - кнопка Сохранить появляется в теле формы предпросомтра и сразу же исчезает.

Код PHP:
  1. $form.after('<div id="preview"><div class="preview-title">'+preview_title+'</div>'+html.html()+'</div>');
  2. }, 'json');
  3. $form.find('.button-submit').clone().appendTo('#preview').click(function(){
  4. $form.submit();
  5. });
+2
Val Val 8 лет назад #
попробуйте так:
Спойлер
+1
Михаил Михаил 8 лет назад #
Работает, спс.
+2
Владимир Владимир 8 лет назад #
Предпросмотр нужен! И желательно коробочный.
+2
Bonefacei Bonefacei 8 лет назад #
Этот бы функционал в коробку!
0
Саня Саня 6 лет назад #
А где его взять или скачать? Убрали из дополнений что ли?
0
ParadoX ParadoX 6 лет назад #
К огромному сожалению в 2.9 не работает. Пользователи в один голос ругаются, что нет предпросмотра - очень неудобно постить, приходится каждый раз редактировать((
0
Vlad Vlad 2 года назад #

На 2.15 актуально?

0
Def Def 1 год назад #

тоже интересно

Еще от автора

СтопХам - userscript для instantcms.ru
Всем привет! Презентую чужую разработку со своей небольшой доработкой .
Just4Fun - Аватарки!
Здравствуйте, любители InstantCMS 2.
Используя этот сайт, вы соглашаетесь с тем, что мы используем файлы cookie.