2012-06-04 17 views
5

Sto cercando di avvolgere knockout.js in clojurescript, ma è molto difficile. Il problema che sto avendo è il riferimento alla variabile "this". Sto pensando di rinunciare e usare solo javascript direttamente.wrapping knockout.js con clojurescript

Ho preso esempi off http://knockoutjs.com/examples/helloWorld.html e http://knockoutjs.com/examples/contactsEditor.html

Sono riuscito ad avvolgere funzioni semplici con alcune macro. Per esempio:

var ViewModel = function() { 
    this.firstName = ko.observable("Bert"); 
    this.lastName = ko.observable("Bertington"); 

    this.fullName = ko.computed(function() { 
     // Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName. 
     return this.firstName() + " " + this.lastName(); 
    }, this); 
}; 

diventa:

(defviewmodel data 
    (observing :first_name "Bert") 
    (observing :last_name "Bertington") 
    (computing :name [:first_name :last_name] 
    (str :first_name " " :last_name))) 

Tuttavia, per qualcosa di più simile:

var BetterListModel = function() { 
    this.itemToAdd = ko.observable(""); 
    this.allItems = ko.observableArray(["Fries", "Eggs Benedict", "Ham", "Cheese"]); // Initial items 
    this.selectedItems = ko.observableArray(["Ham"]);        // Initial selection 

    this.addItem = function() { 
     if ((this.itemToAdd() != "") && (this.allItems.indexOf(this.itemToAdd()) < 0)) // Prevent blanks and duplicates 
      this.allItems.push(this.itemToAdd()); 
     this.itemToAdd(""); // Clear the text box 
    }; 

    this.removeSelected = function() { 
     this.allItems.removeAll(this.selectedItems()); 
     this.selectedItems([]); // Clear selection 
    }; 

    this.sortItems = function() { 
     this.allItems.sort(); 
    }; 
}; 

ko.applyBindings(new BetterListModel()); 

Non sono sicuro di quello che posso fare in modo che corrisponda ClojureScript codice come questo: this.allItems.push(this.itemToAdd())

Qualche idea?

+0

Se riesci a tenerti stretto per un mese, apriremo la libreria di calcolo computabile osservabile ispirata a Knockout.js che abbiamo utilizzato internamente a Keming Labs. Tieni d'occhio Github (@lynaghk). –

+0

Grazie Kevin! Non vedo davvero l'ora di giocare con la biblioteca. Tuttavia, ci sono troppe grandi librerie javascript là fuori che hanno tipi simili di problemi nel dichiarare variabili che accedono ad altre variabili interne che il clojure non ha. Sento che è importante avere un modo chiaro per interpolare tra js e cljs. Più gioco con clojurescript ee javascript, più trovo che buone librerie js sono in un modo lusingato ... che ho visto solo la connessione dopo aver imparato il clojure. Comunque, spero di ottenere i tuoi commenti sulla mia risposta sotto – zcaudate

+0

Dai un'occhiata a http://fluentsoftware.github.com/cljs-binding/, non così maturo come Knockout, ma .. – edtsech

risposta

5

Dopo un sacco di tentativi ed errori, ho capito come avere la stessa struttura per ClojureScript come per javascript.

Il this-as macro ha un paio di idiosincrasie e funziona solo quando il metodo viene messo nella classe

per esempio voglio creare qualcosa che assomiglia a questo in javascript:

var anobj = {a: 9, 
      get_a: function(){return this.a;}}; 

devo fare molto di più di codifica per ottenere lo stesso oggetto in ClojureScript:

(def anobj (js-obj)) 
(def get_a (fn [] (this-as me (.-a me)))) 
(aset anobj "a" 9) 
(aset anobj "get_a" get_a) 

che è seriamente brutto per una lingua bella come cloj Ure. Le cose peggiorano quando hai funzioni che si collegano l'un l'altro, come quello che succede a eliminazione diretta.

Ho trovato che il modo migliore per creare un js-object con un sacco di this è di definire un metodo __init__, aggiungerlo alla classe e quindi eseguirlo, quindi rimuoverlo dalla classe. Per esempio, se volevo fare un altro oggetto:

var avobj = {a: this, 
      b: 98, 
      c: this.a 
      get_a: function(){return str(this.a) + str(this.c);}}; 

scritto come ClojureScript con e __init__ metodo è simile al seguente:

(def avobj (js-obj)) 
(def av__init__ 
    #(this-as this 
     (aset this "a" this) 
     (aset this "b" 9) 
     (aset this "c" (.-a this)) 
     (aset this "get_a" (fn [] (str (.-a this) (.-c this)))))) 
(aset avobj "__init__" av__init__) 
(. avobj __init__) 
(js-delete stuff "__init__") 

C'è ancora un sacco più codice di javascript ... ma la la cosa più importante è che ottieni lo stesso oggetto di javascript. L'impostazione di tutte le variabili utilizzando questo modulo consente anche l'utilizzo di macro per semplificare. Così ora ho definito una macro:

(defmacro defvar [name & body] 
    (list 'do 
    (list 'def name 
     (list 'map->js 
     { 
      :__init__ 
      (list 'fn [] 
       (list 'this-as 'this 
       (list 'aset 'this "a" "blah")))   
     })) 
    ;(. js/console log ~name) 
    (list '. name '__init__) 
    (list 'js-delete name "__init__"))) 

e con map-> js tratto da jayq.utils:

(defn map->js [m] 
    (let [out (js-obj)] 
    (doseq [[k v] m] 
     (aset out (name k) v)) 
    out)) 

Ora posso scrivere codice come questo:

(defvar avobj 
    a this 
    b 9 
    c (.-a this) 
    get_a (fn [] (str (.-a this) (.-c this)))) 

e per la risposta a eliminazione diretta:

(defvar name_model 
    first_name (observable "My") 
    last_name (observable "Name") 
    name (computed (fn [] (str (. this first_name) " " (. this last_name))))) 

(. js/ko (applyBindings name_model)); 

che è veramente bello per me perché corrisponde javascript davvero bene ed è interamente leggibile!