asp.net-mvc knockout.js ckeditor singlepage

asp.net mvc - Knockout, CKEditor y aplicación de una sola página



asp.net-mvc knockout.js (7)

A partir del trabajo realizado en las otras respuestas, aquí está mi solución:

  • maneja los cambios usando el propio evento de change ckeditor (actualizaciones al presionar teclas pero no solo eso)
  • usa getData getData() de getData() para que no obtengas HTML no deseado como "línea mágica" y cosas similares
  • maneja la gestión de la memoria (sin probar)

Código:

ko.bindingHandlers.ckeditor = { init: function(element, valueAccessor, allBindingsAccessor, context) { var options = allBindingsAccessor().ckeditorOptions || {}; var modelValue = valueAccessor(); var value = ko.utils.unwrapObservable(valueAccessor()); $(element).html(value); $(element).ckeditor(); var editor = $(element).ckeditorGet(); //handle edits made in the editor editor.on(''change'', function(e) { var self = this; if (ko.isWriteableObservable(self)) { self($(e.listenerData).val()); } }, modelValue, element); //handle disposal (if KO removes by the template binding) ko.utils.domNodeDisposal.addDisposeCallback(element, function() { if (editor) { CKEDITOR.remove(editor); }; }); }, update: function(element, valueAccessor, allBindingsAccessor, context) { // handle programmatic updates to the observable var newValue = ko.utils.unwrapObservable(valueAccessor()); if ($(element).ckeditorGet().getData() != newValue) $(element).ckeditorGet().setData(newValue) } };

El marcado que uso (note el afterkeydown ):

<textarea id="editor1" data-bind="ckeditor: text, valueUpdate: ''afterkeydown''" ></textarea>

Actualización : tal como se solicita en los comentarios, aquí hay un mínimo de trabajo Fiddle .

Tengo una situación relacionada con KnockoutJS & CKEditor.

Básicamente, tenemos una parte de nuestro sitio que es el estilo de aplicación de "una sola página", actualmente solo involucra 2 páginas pero probablemente se expandirá con el tiempo, actualmente es solo una página de "listados" y una página de "administración" para los elementos en el lista.

La página de administración en sí misma requiere algún tipo de editor de texto enriquecido, hemos optado por CKEditor para una solución para toda la empresa.

Debido a que estas 2 páginas tienen un estilo de "una sola página", obviamente CKEditor no puede registrarse en los elementos de administración porque no están allí en la carga de la página, un problema lo suficientemente simple como para solucionarlo. Así que como muestra adjunté CKEditor en un evento de clic que funcionó muy bien. El siguiente problema fue que los observables Knockout que se habían configurado no se estaban actualizando porque CKEditor no modifica realmente el área de texto que se adjunta, sino que crea todos los elementos / html de div que realmente editas.

Después de un poco de google, encontré un ejemplo de alguien haciendo esto con TinyMCE - http://jsfiddle.net/rniemeyer/GwkRQ/ así que pensé que podría adaptar algo similar a esto para CKEditor.

Actualmente estoy bastante cerca de tener una solución operativa, lo tengo inicializando y actualizando los observables correctos usando esta técnica (publicaré el código en la parte inferior) e incluso publicaré en el servidor de forma correcta, fantástico.

El problema que estoy experimentando actualmente es con la parte de la aplicación "Página única" y la reinicialización de CKEditor.

Básicamente, lo que sucede es que puede hacer clic en la lista para administrar y luego guardar (que se remonta a la página de la lista) luego, cuando vaya a otro ''administrar'', el CKEditor se inicializa pero no tiene ningún valor, lo he comprobado El código de actualización (a continuación) y el "valor" definitivamente tienen el valor correcto, pero no se están enviando al CKEditor en sí.

Tal vez sea una falta de comprensión sobre el proceso de flujo / inicialización de CKEditor o una falta de comprensión sobre los enlaces de eliminación directa o quizás sea un problema con el marco que se ha configurado para nuestra aplicación de una sola página. No estoy seguro.

Aquí está el código:

//Test one for ckeditor ko.bindingHandlers.ckeditor = { init: function (element, valueAccessor, allBindingsAccessor, context) { var options = allBindingsAccessor().ckeditorOptions || {}; var modelValue = valueAccessor(); $(element).ckeditor(); var editor = $(element).ckeditorGet(); //handle edits made in the editor editor.on(''blur'', function (e) { var self = this; if (ko.isWriteableObservable(self)) { self($(e.listenerData).val()); } }, modelValue, element); //handle destroying an editor (based on what jQuery plugin does) ko.utils.domNodeDisposal.addDisposeCallback(element, function () { var existingEditor = CKEDITOR.instances[element.name]; existingEditor.destroy(true); }); }, update: function (element, valueAccessor, allBindingsAccessor, context) { //handle programmatic updates to the observable var value = ko.utils.unwrapObservable(valueAccessor()); $(element).html(value); } };

Así que en el HTML es un ''data-bind: ckeditor'' bastante típico que aplica los enlaces cuando se inicializa ViewModel.

He puesto depurador; en el código para ver el flujo, parece que cuando cargo la primera vez que llama a init, luego actualizo, cuando voy en la segunda vez que golpea la disposición ko.utils.domNode para deshacerse de los elementos.

He intentado no destruirlo, y CKEditor se queja de que ya existe algo con ese nombre. He intentado no destruirlo y comprobar si existe e inicializar si no existe; funciona la primera vez, pero la segunda vez no tenemos CKEditor.

Me imagino que solo falta una cosa que funcionará, pero he agotado todas las opciones.

¿Alguien tiene algún conocimiento sobre la integración de estas 3 cosas que pueden ayudarme?

¿Hay algún experto en eliminatorias que pueda ayudarme?

Cualquier ayuda sería muy apreciada.

Maryland


Acabo de usar esta técnica con CKEditor 4 para sobrescribir el enlace "html" existente (de 1 vía) con un enlace de 2 vías. Estoy usando el CKEditor en línea que puede comportarse de manera diferente (no estoy seguro) que el editor completo / estático. Comencé con el enlace de "valor" y lo modifiqué para que funcionara con el innerHTML lugar:

ko.bindingHandlers.html = { ''init'': function (element, valueAccessor, allBindingsAccessor) { var eventsToCatch = ["blur"]; var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"]; var valueUpdateHandler = null; if (requestedEventsToCatch) { if (typeof requestedEventsToCatch == "string") requestedEventsToCatch = [requestedEventsToCatch]; ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch); eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch); } valueUpdateHandler = function () { var modelValue = valueAccessor(); var oldValue = ko.utils.unwrapObservable(modelValue); var elementValue = element.innerHTML; var valueHasChanged = (oldValue !== elementValue); if (valueHasChanged) modelValue(elementValue); } ko.utils.arrayForEach(eventsToCatch, function (eventName) { var handler = valueUpdateHandler; if (eventName.indexOf("after") == 0) { handler = function () { setTimeout(valueUpdateHandler, 0) }; eventName = eventName.substring("after".length); } ko.utils.registerEventHandler(element, eventName, handler); }); }, ''update'': function (element, valueAccessor) { var newValue = ko.utils.unwrapObservable(valueAccessor()); var elementValue = element.innerHTML; var valueHasChanged = (newValue !== elementValue); if (valueHasChanged) element.innerHTML = newValue; } };

Advertencia: probablemente debería actualizarse para usar el evento de change propio de CKEditor.


He estado trabajando con esto por un tiempo y me encontré con varios problemas con el enfoque .on ("desenfoque"). Es decir, cuando las personas hicieron clic en el texto enriquecido y el texto ingresó y luego se desplazaron directamente al botón Guardar en mi formulario, el observable no se actualizó lo suficientemente rápido. Hay muchas maneras de manejar los retrasos, pero quería algo más oficial. Busqué en la documentación de CKEditor y encontré esta gema: focusManager

Esta es una funcionalidad incorporada que maneja todos los casos de enfoque y desenfoque y le permite conectar un verdadero evento de desenfoque al control.

Aquí está mi enlace Handler para texto enriquecido entonces

ko.bindingHandlers.richText = { init: function (element, valueAccessor, allBindingsAccessor, viewModel) { var txtBoxID = $(element).attr("id"); var instance = CKEDITOR.instances[txtBoxID]; var options = allBindingsAccessor().richTextOptions || {}; options.toolbar_Full = [ [''Source'', ''-'', ''Format'', ''Font'', ''FontSize'', ''TextColor'', ''BGColor'', ''-'', ''Bold'', ''Italic'', ''Underline'', ''SpellChecker''], [''NumberedList'', ''BulletedList'', ''-'', ''Outdent'', ''Indent'', ''-'', ''Blockquote'', ''CreateDiv'', ''-'', ''JustifyLeft'', ''JustifyCenter'', ''JustifyRight'', ''JustifyBlock'', ''-'', ''BidiLtr'', ''BidiRtl''], [''Link'', ''Unlink'', ''Image'', ''Table''] ]; //handle disposal (if KO removes by the template binding) ko.utils.domNodeDisposal.addDisposeCallback(element, function () { if (CKEDITOR.instances[txtBoxID]) { CKEDITOR.remove(CKEDITOR.instances[txtBoxID]); }; }); $(element).ckeditor(options); //wire up the blur event to ensure our observable is properly updated CKEDITOR.instances[txtBoxID].focusManager.blur = function () { var observable = valueAccessor(); observable($(element).val()); }; }, update: function (element, valueAccessor, allBindingsAccessor, viewModel) { var val = ko.utils.unwrapObservable(valueAccessor()); $(element).val(val); } }


Para cualquier persona interesada lo ordené:

Todo era un orden de ejecución básico, solo necesitaba establecer el valor en el área de texto de HTML antes de que se inicializara.

Tenga en cuenta que esto utiliza una extensión de adaptador jquery para hacer el .ckeditor () en el elemento.

Probablemente hay también una mejor manera de hacer la parte ''borrosa''.

Esta extensión tampoco funciona con opciones en este momento, pero eso debería ser bastante simple en comparación.

ko.bindingHandlers.ckeditor = { init: function (element, valueAccessor, allBindingsAccessor, context) { var options = allBindingsAccessor().ckeditorOptions || {}; var modelValue = valueAccessor(); var value = ko.utils.unwrapObservable(valueAccessor()); $(element).html(value); $(element).ckeditor(); var editor = $(element).ckeditorGet(); //handle edits made in the editor editor.on(''blur'', function (e) { var self = this; if (ko.isWriteableObservable(self)) { self($(e.listenerData).val()); } }, modelValue, element); } };


Para la parte de ''desenfoque'', probé el código de abajo y parece funcionar

editor.on(''blur'', function (e) { var self = this; if (ko.isWriteableObservable(self)) { var ckValue = e.editor.getData(); self(ckValue); } }, modelValue, element);

Creo que la parte de ''Actualizar'' todavía es necesaria si "actualiza" lo observable desde otro lugar (no a través de la edición, ya que esto se soluciona con el ''desenfoque'')


Reescribí esto para actualizar lo observable en cada keyup, en lugar de en blur. Estas son muchas más actualizaciones de lo observable, pero mientras esté ahorrando con un botón, ¡esto parece funcionar muy bien hasta ahora!

//handle edits made in the editor CKEDITOR.instances.thread_message.on(''contentDom'', function() { CKEDITOR.instances.thread_message.document.on(''keyup'', function(e) { var self = this; if (ko.isWriteableObservable(self)) { var ckValue = CKEDITOR.instances.element_id.getData(); self(ckValue); //console.log("value: " + ckValue); } }, modelValue, element); });


Primer post así que avísame si he hecho algo mal.

En mi proyecto, proporcioné información visual sobre si había cambios no guardados y, por lo tanto, necesitaba la actualización de observable en keyup . Y click para cuando se hizo click en un botón de la barra de herramientas. Esto también fue consistente conmigo usando valueUpdate:[''afterkeydown'',''propertychange'',''input''] en mis atributos de data-bind .

Además, para el rendimiento, utilicé el parámetro del método de devolución de llamada de .ckeditor(callback,options) lugar de .on(eventName,handler) .

Este es el enlace personalizado que se me ocurrió:

ko.bindingHandlers.ckeditor = { init: function (element, valueAccessor, allBindingsAccessor, context) { // get observable var modelValue = valueAccessor();; $(element).ckeditor(function(textarea) { // <span> element that contains the CKEditor markup var $ckeContainer = $(this.container.$); // <body> element within the iframe (<html> is contentEditable) var $editorBody = $ckeContainer.find(''iframe'').contents().find(''body''); // sets the initial value $editorBody.html( modelValue() ); // handle edits made in the editor - by typing $editorBody.keyup(function() { modelValue( $(this).html() ); }); // handle edits made in the editor - by clicking in the toolbar $ckeContainer.find(''table.cke_editor'').click(function() { modelValue( $editorBody.html() ); }); }); // when ko disposes of <textarea>, destory the ckeditor instance ko.utils.domNodeDisposal.addDisposeCallback(element, function () { $(element).ckeditorGet().destroy(true); }); }, update: function (element, valueAccessor, allBindingsAccessor, context) { // handle programmatic updates to the observable var newValue = ko.utils.unwrapObservable(valueAccessor()); var $ckeContainer = $(element).ckeditorGet().container; if( $ckeContainer ) { // <span> element that contains the CKEditor markup $ckeContainer = $($ckeContainer.$); // <body> element within the iframe (<html> is contentEditable) var $editorBody = $ckeContainer.find(''iframe'').contents().find(''body''); // if new value != existing value, replace it in the editor if( $editorBody.html() != newValue ) $editorBody.html( newValue ); } } };

Justificación:

Sé que probablemente debería usar .getData() y .setData(html) lugar de esta forma bastante difícil de encontrar <body> y <table class="cke_editor"> dentro del contenido del iframe .

La razón es, para update: la condición dentro de:

if( $(element).ckeditorGet().getData() != newValue ) $(element).ckeditorGet().setData( newValue )

fue inicialmente cierto debido al formato HTML que hace CKEditor. Y así, notificó al usuario sobre un registro sucio a pesar de que no lo era. Muy específico para mí, así que pensé que deberías saberlo, en caso de que te preguntaras por qué.