2012-11-14 20 views
8

Vorrei integrare l'editor JavaScript CodeMirror in KnockoutJS. So che c'è anche Ace, ma mi sembra che sarebbe più facile con CodeMirror.Come integrare CodeMirror in KnockoutJS?

Ho già integrato i binding personalizzati per i widget JQueryUI e QTip ma questi erano pezzi di codice che ho trovato su Internet e quindi avevo solo bisogno di modificare parti molto piccole.

Sfortunatamente, sembra che abbia raggiunto i miei limiti su Javascript, quindi mi rivolgo a JavaScript Sith Masters qui. Non voglio necessariamente che tutto sia scritto per me, indicazioni e consigli su come continuare sarebbe di grande aiuto.

Il pezzo di codice che ho:

Il codice HTML (ho tolto associazioni personalizzate Ho già sulla textarea, essi non contano qui)

<body> 
    <textarea id="code" cols="60" rows="8" 
       data-bind="value: condition, 
       tooltip: 'Enter the conditions', 
       codemirror: { 'lineNumbers': true, 'matchBrackets': true, 'mode': 'text/typescript' }"></textarea> 
</body> 

L'inizio della mia abitudine gestore vincolante per CodeMirror:

ko.bindingHandlers.codemirror = { 
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) { 
     var options = valueAccessor() || {}; 
     var editor = CodeMirror.fromTextArea($(element)[0], options); 
    } 
}; 

al momento, questo non produce JS errori ma 2 aree di testo vengono visualizzati invece di uno.

Quindi cosa dovrei fare dopo?

+0

Potresti riprodurlo in un jsfiddle? Non vedo nulla di sbagliato nel tuo gestore di binding. L'unica cosa strana è che avvolgi l'elemento con jquery e poi estrai il primo elemento. Puoi saltarlo e solo fornire l'elemento. – Anders

+0

@Anders grazie, ho rimosso il jQuery Wrap. JSFiddle: http://jsfiddle.net/SKZSm/ – Jalayn

risposta

2

Le soluzioni postato prima sembrano un po 'fuori moda e non avrebbe funzionato per me, così li ho riscritto in una forma che funziona:

// Example view model with observable. 
var viewModel = { 
    fileContent: ko.observable(''), 
    options: { 
     mode: "markdown", 
     lineNumbers: true 
    } 
}; 

// Knockout codemirror binding handler 
ko.bindingHandlers.codemirror = { 
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { 

     var options = viewModel.options || {}; 
     options.value = ko.unwrap(valueAccessor()); 
     var editor = CodeMirror(element, options); 

     editor.on('change', function(cm) { 
      var value = valueAccessor(); 
      value(cm.getValue()); 
     }); 

     element.editor = editor; 
    }, 
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) { 
     var observedValue = ko.unwrap(valueAccessor()); 
     if (element.editor) { 
      element.editor.setValue(observedValue); 
      element.editor.refresh(); 
     } 
    } 
}; 

ko.applyBindings(viewModel); 

Con <div data-bind="codemirror:fileContent"></div> come destinazione per lo specchio di codice da allegare al questo creerà una nuova istanza di codemirror e passerà le opzioni dal modello di vista se sono state impostate.

[modifica] Ho modificato il metodo di aggiornamento del gestore di legame per aprire il valueAccessor passato CodeMirror, senza che la linea ad eliminazione diretta, non avrebbe sparato il metodo di aggiornamento quando il osservabile viene aggiornata - ora funziona come ci si aspetterebbe che a.

+0

accettato la tua risposta dal momento che è molto più aggiornato di mio roba vecchia :-) – Jalayn

+0

@Jalayn wow, grazie, ho modificato il mio esempio di codice per correggere un piccolo bug e ora funziona come previsto – carbontwelve

+0

@carbontwelve Perché stai ripetendo la definizione di viewModel? Era solo un refuso? – sidewinderguy

3

Bene, finalmente sono riuscito a farlo (vedere l'aggiornamento fiddle).

Sono riuscito rapidamente a impostare il valore iniziale nella textarea personalizzata. Ma dopo, l'elemento associato non è stato aggiornato.

Tuttavia, l'API CodeMirror consente di registrare un metodo di richiamata all'evento onChange da chiamare ogni volta che viene modificato il contenuto dell'area di testo. Quindi era solo questione di implementare il callback che aggiorna il valore dell'elemento associato. Questo viene fatto alla creazione dell'area di testo personalizzata, nelle opzioni.

Ecco la consuetudine di legame:

ko.bindingHandlers.codemirror = { 
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) { 
     var options = $.extend(valueAccessor(), { 
      onChange: function(cm) { 
       allBindingsAccessor().value(cm.getValue()); 
      } 
     }); 
     var editor = CodeMirror.fromTextArea(element, options); 
     element.editor = editor; 
     editor.setValue(allBindingsAccessor().value()); 
     editor.refresh(); 
     var wrapperElement = $(editor.getWrapperElement()); 

     ko.utils.domNodeDisposal.addDisposeCallback(element, function() { 
      wrapperElement.remove(); 
     }); 
    } 
}; 

Può mancare alcune caratteristiche forse, ma per quello che ho bisogno funziona perfettamente.

Modifica: seguendo l'osservazione di Anders, ho aggiunto la parte addDisposeCallback che effettivamente distrugge l'elemento DOM prodotto da CodeMirror quando il modello viene nuovamente sottoposto a rendering. Poiché tutto ciò che CodeMirror produce è all'interno di un nodo, è solo questione di rimuovere questo nodo.

+0

L'API CodeMirror fornisce una funzione di rimozione? La sua buona pratica KO per ascoltare la callback ko.utils.domNodeDisposal.addDisposeCallback è chiamata dal motore se un binding del template ha completamente rerenderizzato la vista – Anders

+0

@Anders molte grazie! Mi ci è voluto un po 'di tempo per riprodurre e capire il problema con i modelli, ma ora ho aggiunto un callback personalizzato che sembra funzionare. – Jalayn

+1

onChange handler non funziona più. Risolto come questo: 'var editor = CodeMirror.fromTextArea (elemento, opzioni); editor.on ("change", function (cm) { allBindingsAccessor(). Value (cm.getValue()); }); ' – nexuzzz

1

Sto tentando di utilizzare il gestore di binding sopra, ma sto avendo un problema senza che nessun testo sia visibile in TextArea finché non inserisco qualcosa (uno spazio o qualsiasi altro carattere).

È vuoto quando il caricamento è completato e non appena inserisco qualcosa nell'area, tutti i dati associati diventano visibili.

Un'altra domanda, se associo un semplice ko.observable() al valore di TextArea, tutto funziona alla grande, ma se lo sostituisco con una delle colonne in un ko.observableArray, ad es. valore: selectedProfile(). QueryString, sto ricevendo il seguente errore dal gestore di bind:

Riga in questione: editor.setValue (allBindingsAccessor().valore()); Errore: Uncaught TypeError: Property 'value' of object # non è una funzione

Pensieri?

/LM

+0

Ciao Lars, non sono sicuro che dovresti postarlo come una risposta, ma risponderò alla tua domanda. Quello che hai nel tuo array non è un oggetto osservabile, ma solo una semplice stringa, ed è per questo che "value()" non funziona. È possibile modificare il mio codice verificando prima se "allBindingsAccessor(). Value" è una funzione (vedere http://www.idealog.us/2007/02/check_if_a_java.html) e utilizzare "allBindingsAccessor().valore "o" allBindingsAccessor(). value() "a seconda del caso Spero che questo aiuti! – Jalayn

+0

Grazie Jalayn, mi ricorderò di creare una nuova domanda la prossima volta. –

4

Il codice elencato da Jalayn (o uno in jsfiddle) non aggiorna la variabile osservando anche l'editor non mostra il valore del carico .. ecco il mio codice aggiornato

ko.bindingHandlers.codemirror = { 
     init: function (element, valueAccessor, allBindingsAccessor, viewModel) { 
      var options = valueAccessor(); 
      var editor = CodeMirror.fromTextArea(element, options); 
      editor.on('change', function(cm) { 
       allBindingsAccessor().value(cm.getValue()); 
      }); 
      element.editor = editor; 
      if(allBindingsAccessor().value()) 
       editor.setValue(allBindingsAccessor().value()); 
      editor.refresh(); 
      var wrapperElement = $(editor.getWrapperElement()); 

      ko.utils.domNodeDisposal.addDisposeCallback(element, function() { 
       wrapperElement.remove(); 
      }); 
     }, 
     update: function (element, valueAccessor) { 
      if(element.editor) 
       element.editor.refresh(); 
     } 
    }; 
0

Eseguo lo stesso problema di Lars in una delle risposte precedenti, ovvero codemirror non ha inizialmente caricato i dati fino alla prima interazione. L'ho risolto aggiungendo un flag per determinare se il valore di codemirror fosse cambiato digitando o programmaticamente e sottoscrivendo il valore.

ko.bindingHandlers.codemirror = { 
     init: function (element, valueAccessor, allBindingsAccessor) { 
      var typed = false; 
      var options = $.extend(valueAccessor(), { 
       onChange: function (cm) { 
        typed = true; 
        allBindingsAccessor().value(cm.getValue()); 
        typed = false; 
       } 
      }); 
      var editor = CodeMirror.fromTextArea(element, options); 
      element.editor = editor; 
      editor.setValue(allBindingsAccessor().value()); 
      editor.refresh(); 
      var wrapperElement = $(editor.getWrapperElement()); 
      ko.utils.domNodeDisposal.addDisposeCallback(element, function() { 
       wrapperElement.remove(); 
      }); 

      allBindingsAccessor().value.subscribe(function (newValue) { 
       if (!typed) { 
        editor.setValue(newValue); 
        editor.refresh(); 
       } 
      }); 
     } 
    }; 

Credo che ci sarebbe stato un metodo di 'pulito' per ottenere lo stesso, ma questo sembra che vada bene per il momento.

2

Si è verificato un problema con il cursore impostato sulla prima posizione. This fiddle correzioni e accetta anche CodeMirror options oggetto come valore vincolante, e il contenuto è legato ad un options.value osservabile (trovo che meno confusione, perché questo è in realtà il nome della proprietà da dove CM ottiene è starting value)

ko.bindingHandlers.codemirror = { 
    init: function(element, valueAccessor) { 
     var options = ko.unwrap(valueAccessor()); 
     element.editor = CodeMirror(element, ko.toJS(options)); 
     element.editor.on('change', function(cm) { 
      options.value(cm.getValue()); 
     }); 

     ko.utils.domNodeDisposal.addDisposeCallback(element, function() { 
      var wrapper = element.editor.getWrapperElement(); 
      wrapper.parentNode.removeChild(wrapper); 
     }); 
    }, 
    update: function(element, valueAccessor) { 
     var value = ko.toJS(valueAccessor()).value; 
     if (element.editor) { 
      var cur = element.editor.getCursor(); 
      element.editor.setValue(value); 
      element.editor.setCursor(cur); 
      element.editor.refresh(); 
     } 
    } 
}; 

Esempio HTML:

<div data-bind="codemirror: { 
    mode: 'javascript', 
    value: text 
}"></div> 
+0

Quello la soluzione funziona meglio per me. Ho ancora problemi, ma penso che sia perché uso Durandal. Quando la vista è composta, l'editor non viene visualizzato. Solo quando clicco su di esso, si apre. Quindi funziona correttamente. – Cilyan