2012-11-14 16 views
8

Ich möchte den JavaScript Editor CodeMirror in KnockoutJS integrieren. Ich weiß, dass es auch Ace gibt, aber es scheint mir mit CodeMirror einfacher zu sein.Wie kann CodeMirror in KnockoutJS integriert werden?

Ich habe bereits benutzerdefinierte Bindungen für JQueryUI-Widgets und QTip integriert, aber das waren Teile von Code, die ich im Internet gefunden habe, und ich musste dann nur sehr kleine Teile modifizieren.

Leider scheint es, dass ich an meinen Grenzen bei Javascript angelangt bin, deshalb wende ich mich hier JavaScript Sith Masters zu. Ich will nicht unbedingt, dass das Ganze für mich geschrieben wird, Hinweise und Ratschläge, wie ich weitermachen könnte, wären eine große Hilfe.

Das Stück Code ich habe:

Die HTML (I benutzerdefinierte Bindungen entfernt ich bereits auf dem Textfeld haben, tun sie hier keine Rolle)

<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> 

Beginn meiner benutzerdefinierten Bindung Handler für CodeMirror:

Im Moment erzeugt dies keine JS-Fehler, aber 2 Textbereiche werden anstelle eines angezeigt.

Was soll ich als nächstes tun?

+0

Könnten Sie bitte es in einer jsfiddle reproduzieren? Ich sehe nichts Falsches mit Ihrem Bindungshandler. Nur seltsames Ding ist, dass Sie Element mit jquery einwickeln und dann das erste Element herausnehmen. Sie können das überspringen und nur das Element liefern. – Anders

+0

@Anders danke, ich habe die Jquery Wrap entfernt. JSFiddle: http://jsfiddle.net/SKZSm/ – Jalayn

Antwort

2

Die posted Lösungen vor scheinen ein wenig aus Datum und würde nicht funktionieren, für mich so habe ich sie in einer Form neu geschrieben, das funktioniert: für Code Spiegel

// 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); 

Mit <div data-bind="codemirror:fileContent"></div> als Ziel zu befestigen Dadurch wird eine neue Codemirrorinstanz erstellt und Optionen aus dem Ansichtsmodell übergeben, wenn sie festgelegt wurden.

[bearbeiten] Ich habe die Update-Methode des Codemirror Bindungs ​​Handler geändert den übergebenen valueAccessor auszupacken, ohne dass diese Linie Knockout würde die Update-Methode nicht ausgelöst, wenn die beobachtbare aktualisiert wird - es funktioniert jetzt, wie Sie es erwarten würden zu.

+0

akzeptiert Ihre Antwort, da es viel aktueller als meine ist altes Zeug :-) – Jalayn

+0

@Jalayan wow, danke, ich habe mein Codebeispiel gebessert, um einen kleinen Fehler zu beheben.Es funktioniert nun wie erwartet – carbontwelve

+0

@carbontwelve Warum wiederholst du die Definition von viewModel? War das nur ein Tippfehler? – sidewinderguy

3

Nun, endlich habe ich es geschafft (siehe die aktualisierte fiddle).

Ich habe es schnell geschafft, den Anfangswert im benutzerdefinierten Textfeld festzulegen. Aber danach wurde das gebundene Element nicht aktualisiert.

Mit der API von CodeMirror können Sie jedoch eine Rückrufmethode für das Ereignis onChange registrieren, das aufgerufen wird, wenn der Inhalt des Textfelds geändert wird. Es ging also nur darum, den Callback zu implementieren, der den Wert des gebundenen Elements aktualisiert. Dies geschieht bei der Erstellung des benutzerdefinierten Textbereichs in den Optionen.

Hier ist die kundenspezifische Bindung:

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(); 
     }); 
    } 
}; 

Es möglicherweise einige Features fehlen, aber für das, was ich brauche, es funktioniert perfekt.

Edit: Nach Anders 'Bemerkung habe ich den addDisposeCallback Teil hinzugefügt, der das von CodeMirror erzeugte DOM-Element effektiv zerstört, wenn die Vorlage neu gerendert wird. Da alles, was CodeMirror erzeugt, innerhalb eines Knotens liegt, muss nur dieser Knoten entfernt werden.

+0

Bietet die CodeMirror-API eine Abreißfunktion? Seine gute KO-Praxis, um den ko.utils.domNodeDisposal.addDisposeCallback-Callback zu hören, wird dies von der Engine aufgerufen, wenn eine Template-Bindung die View komplett neu gerendert hat. – Anders

+0

@Anders vielen Dank! Ich habe einige Zeit gebraucht, um das Problem mit den Vorlagen zu reproduzieren und zu verstehen, aber ich habe jetzt einen benutzerdefinierten Rückruf hinzugefügt, der zu funktionieren scheint. – Jalayn

+1

onChange-Handler funktioniert nicht mehr. So gefixt: 'var editor = CodeMirror.fromTextArea (element, options); editor.on ("ändern", Funktion (cm)) { allBindingsAccessor(). Value (cm.getValue()); }); ' – nexuzzz

1

Ich versuche, den Binding-Handler oben zu verwenden, aber ich habe ein Problem mit keinem Text in der TextArea sichtbar, bis ich etwas (ein Leerzeichen oder ein anderes Zeichen) eingeben.

Es ist leer, wenn das Laden abgeschlossen ist, und sobald ich etwas in dem Bereich eingeben, werden alle gebundenen Daten sichtbar.

Noch eine Frage, wenn ich eine einfache ko.observable() an den Wert von TextArea binde, funktioniert alles gut, aber wenn ich dies durch eine der Spalten in einem ko.observableArray, z. Wert: selectedProfile(). QueryString, ich bekomme den folgenden Fehler vom Bindungshandler:

Zeile in Frage: editor.setValue (allBindingsAccessor().Wert()); Fehler: Uncaught TypeError: Eigenschaft 'Wert' von Objekt # ist keine Funktion

Gedanken?

/LM

+0

Hallo Lars, ich bin mir nicht sicher, dass du das hier posten solltest eine Antwort, aber ich werde deine Frage beantworten. Was Sie in Ihrem Array haben, ist kein beobachtbares Objekt, sondern nur ein einfacher String, weshalb der "value()" nicht funktioniert. Sie könnten meinen Code ändern, indem Sie zuerst prüfen, ob "allBindingsAccessor(). Value" eine Funktion ist (siehe http://www.idealog.us/2007/02/check_if_a_java.html) und entweder "allBindingsAccessor()" verwenden.Wert "oder" allBindingsAccessor(). value() "je nach Fall. Hoffe, dass hilft! – Jalayn

+0

Dank Jalayn, ich erinnere mich daran, beim nächsten Mal eine neue Frage zu erstellen. –

4

Der Code von Jalayn aufgelistet (oder einer in jsfiddle) nicht die beobachtete Variable aktualisiert auch der Editor den Wert auf Last zeigen tut .. hier ist meine aktualisierte Code

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

Ich stolperte in einer der obigen Antworten auf das gleiche Problem wie Lars, dh Codemirror lud die Daten nicht bis zur ersten Interaktion ein. Ich habe es gelöst, indem ich ein Flag hinzugefügt habe, um zu bestimmen, ob sich der Codemirror-Wert durch Eingabe oder Programmatierung geändert hat, und abonniere den Wert.

Ich glaube, es wäre eine "sauberere" Methode, um das gleiche zu erreichen, aber das scheint vorerst in Ordnung zu sein.

2

Ich hatte ein Problem mit dem Cursor auf die erste Position gesetzt wurde. This fiddle Fixes, und es Codemirror options Objekt als verbindlich Wert, und der Inhalt wird gebunden an eine options.value beobachtbaren akzeptiert (I, dass weniger verwirrend finden, denn das ist eigentlich der Name der Eigenschaft, von dem CM bekommt es starting value) auch

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(); 
     } 
    } 
}; 

Beispiel HTML:

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

Das Lösung funktioniert besser für mich. Ich habe immer noch Probleme, aber ich denke, das liegt daran, dass ich Durandal verwende. Wenn die Ansicht zusammengesetzt ist, wird der Editor nicht angezeigt. Erst wenn ich darauf klicke, erscheint es. Dann funktioniert es richtig. – Cilyan

Verwandte Themen