Вот уже несколько месяцев я активно использую AngularJS в одном из рабочих проектов. Петь “похвальные песни” или возносить этот фреймверк я не буду, потому что идеальных вещей нет (да и наверно было бы очень скучно жить в мире с такими вещами, которые не оставляют возможности побороть их недостатки своим “творчеством”). Скажу только пару слов относительно результатов: идеология AngularJS очень хорошо справляется с организацией кода в моем лице и дает волшебный инструмент Directives. Кстати, недавно уже была заметка о CornerJS, в котором директивы выведены в центр технологии, а на Google I/O в этом году проскакивала новость о возможной поддержки custom-elements(не просто тегов, а комплексных html компонентов, встраиваемых в страницу).
На очередном этапе разработки встал вопрос о интеграции с продвинутым WYSIWYG редактором и мой взор сразу же пал на ckEditor, так как я его уже неоднократно использовал в рамках проектов на базе DotNetNuke и впечатления остались весьма положительные (ну или скажем по другому: сильных огрех в компоненте найдено не было а интеграция заняла считанные часы).
Взывая к Google мольбы о помощи в интеграционной магии, я получил несколько ссылок на Stackoverflow и другие частные блоги, где решением всех проблем выступает событие pasteState и директива в целом выглядит просто и доступно:
app.directive('ckEditor', [function () { return { require: '?ngModel', link: function ($scope, elm, attr, ngModel) { var ck = CKEDITOR.replace(elm[0]); ck.on('pasteState', function () { $scope.$apply(function () { ngModel.$setViewValue(ck.getData()); }); }); ngModel.$render = function (value) { ck.setData(ngModel.$modelValue); }; } }; }]);
Но после очередного мейлстоуна и деплоя, заказчик начал замечать, что иногда отредактированный текст не сохраняется в полной мере или компонент вообще вылетает с Access Violation, в случае ранних версий InternetExplorer (< 9). Проблема была воспроизведена, идеальные условия получены и я отправился искать решение проблемы.
Перечитав еще раз документацию и потыкав страничку на предмет общей картины, я пришел к выводу, что данное событие отлично работает с клавиатурой, но совершенно игнорирует вставки элементов (будь то текст, картинки и т.п.) из плагинов, идущих в комплекте. Сразу же было решено искать новое событие, которое носит более глобальный характер. Но результаты поисков привели к вот такому “неправославному” методу, который в довесок еще и плагин (а это чревато при миграции или обновлениях самого ckEditor). Поэтому было решено поднять версию редактора до последней доступной (CKEditor 4.2.1 на тот момент), рискнув стабильностью и получить волшебное событие change на уровне нативного api (возможно они просто интегрировали вышеупомянутый плагин в ядро, за историей я, честно, не следил). Замена события на change помогла решить вопрос с сохранением измененного содержимого, но не решила проблемы с Access Violation.
Наученный горьким опытом popup окон для IE < 9 (IE разносит выполнение разных окон, на разные потоки с полным сбросом cookies и т.п.) я пришел к выводу, что проблема в iframe компоненте и последовательности создания и обращения к нему внутри самого ckEditor, а также цикла жизни scope в рамках AngularJS. Проблема, связана с тем, что метод CKEDITOR.replace(…) срабатывает несинхронно в старых IE и “отпустив” контекст исполнения AngularJS пытается сделать биндинг модели и вызвать setData, который пытается обратиться к еще неготовому iframe, что и вызывает Access Violation. Ничего “лучше” и “надежнее” очереди я не придумал, поэтому результатом стал следующий код директивы:
app.directive('ckEditor', [function () { return { require: '?ngModel', restrict: 'C', link: function (scope, elm, attr, model) { var isReady = false; var data = []; var ck = CKEDITOR.replace(elm[0]); function setData() { if (!data.length) { return; } var d = data.splice(0, 1); ck.setData(d[0] || '<span></span>', function () { setData(); isReady = true; }); } ck.on('instanceReady', function (e) { if (model) { setData(); } }); elm.bind('$destroy', function () { ck.destroy(false); }); if (model) { ck.on('change', function () { scope.$apply(function () { var data = ck.getData(); if (data == '<span></span>') { data = null; } model.$setViewValue(data); }); }); model.$render = function (value) { if (model.$viewValue === undefined) { model.$setViewValue(null); model.$viewValue = null; } data.push(model.$viewValue); if (isReady) { isReady = false; setData(); } }; } } }; }]);
В коде есть неоднозначный моменты, связанные с очередью и значениями model.$viewValue (в частности, это были попытки справиться с замиранием компонента в модальных диалогах, которая была решена патчем twitter bootstrap modal компонета, но это уже другая история).
Так же я не в полной мере раскрыл моменты, связанные с setData(…, callback), которая по сути является одним из синхронизирующих механизмов, но, как мне кажется, код выглядит информативно в данном контексте и заменит слова.
Буду раз выслушать предложения и критику по поводу данного подхода.
P.S.
Рабочий пример того, что получилось у меня http://jsfiddle.net/jWANb/2/
Рабочий пример того, что советуют в интернетах http://jsfiddle.net/fvApg/1/
Попробуйте вставить картинку во втором примере и посмотреть на “result html”. Будет видно, что контекст не изменился.
ссылка на оригинал статьи http://habrahabr.ru/post/200058/
Добавить комментарий