2015-04-09 17 views
11

Diciamo che ho un modello che itera su una collezione di oggetti, e voglio chiamare una funzione con ogni elemento, che è specifico per il controller, e non un modello a livello di preoccupazione:Come si chiama una funzione controller da un modello in Ember?

{{#each people as |person|}} 
    icon name: {{findIconFor(person)}} 
{{/each}} 

I' Mi piacerebbe definire findIconFor nel controller, perché questo è qualcosa di specifico per questa particolare vista.

export default Ember.Controller.extend({ 
    findIconFor: function(person) { 
    // figure out which icon to use 
    } 
); 

Ma questo non funziona. Il modello non riesce a compilare. Errore: In attesa di "STRING", "NUMBER", "ID", "DATA", ottenuto "INVALID"

Qual è il "modo ember" per fare ciò?

+0

Beh, non puoi farlo, perché non puoi farlo. Potresti mettere una proprietà calcolata su 'person', quindi fare' person.icon'. –

risposta

7

userei una proprietà calcolata nel controller:

iconPeople: Ember.computed('[email protected]', function(){ 
    var that = this; 
    return this.get('people').map(function(person){ 
    return { 
     'person': person, 
     'icon': that.findIconFor(person) 
    }; 
    }); 
}) 

Ora si potrebbe ottenere l'icona {{person.icon}} e il nome da {{person.person.name}}. Potresti voler migliorare (e il codice non è stato verificato), ma questa è l'idea generale.

+0

Oh, mi dispiace, si. L'ho cambiato di conseguenza. – jnfingerle

+0

Perché "persone. @ Ciascuna" e non "persone"? –

+2

Si desidera ascoltare le modifiche non solo della matrice di persone stessa (vale a dire una diversa matrice) ma anche sul contenuto della matrice. – jnfingerle

0

Se l'icona è associata a una persona, poiché la persona è rappresentata da un modello, è meglio implementarla come proprietà calcolata sul modello di persona. Qual è il tuo intento nel provare a inserirlo nel controller?

// person.js 
export default DS.Model.extend({ 
    icon: function() { return "person-icon-" + this.get('name'); }.property('name') 
    .. 
}; 

Quindi partendo dal presupposto che people è un array di person:

L'alternativa fornita da opere @jnfingerle (suppongo che voi capito che egli propone che un ciclo su iconPeople), ma sembra un sacco di lavoro extra per andare a creare un nuovo array contenente oggetti. L'icona dipende da qualcosa noto solo al controller? Se no, come ho detto, perché la logica per calcolarlo dovrebbe essere nel controller?

Dove mettere le cose è una questione di filosofia e preferenza. Ad alcune persone piacciono i modelli bare-bones che contengono nient'altro che i campi che scendono dal server; altre persone calcolano i risultati di stato e intermedi nel modello. Alcune persone mettono un sacco di cose nei controller, mentre altri preferiscono i controller leggeri con più logica nei "servizi". Personalmente, sono dalla parte dei modelli più pesanti, dei controller più leggeri e dei servizi. Non sto affermando che la logica di business, o le pesanti trasformazioni dei dati, o la vista dei preparativi dovrebbero andare nel modello, ovviamente. Ma ricorda, il modello rappresenta un oggetto. Se c'è qualcosa di interessante nell'oggetto, che sia disceso dal server o che venga calcolato in qualche modo, per me ha molto senso inserirlo nel modello.

Ricordare inoltre che i controller fanno parte di un percorso/controller/vista nettamente accoppiati. Se c'è qualche cosa specifica del modello che si calcola in un controller, potrebbe essere necessario aggiungerla ad un altro controller che gestisce lo stesso modello. Quindi, prima che tu te ne accorga, stai scrivendo controller mixin che condividono la logica tra i controller che non dovrebbero esserci stati in essi.

In ogni caso, si dice che l'icona proviene da un "archivio dati non correlato". Sembra asincrono. Per me, questo suggerisce che forse è un sottomodello chiamato PersonIcon che è un belongsTo nel modello person.Puoi farlo funzionare con il giusto mix di adattatori e serializzatori per quel modello. La cosa bella di questo approccio è che tutta l'asincronicità nel recupero dell'icona sarà gestita in modo semi-magico, sia quando viene creato il modello person, sia quando effettivamente hai bisogno dell'icona (se dici async: true).

Ma forse non stai utilizzando Ember Data, o non vuoi andare a tutti quei guai. In tal caso, si potrebbe prendere in considerazione che adornano la persona con l'icona in gancio il modello del percorso, avvalendosi della capacità di Ember di gestire la risoluzione modello asincrono, facendo qualcosa di simile al seguente:

model: function() { 
    return this.store.find('person') . 
    then(function(people) { 
     return Ember.RSVP.Promise.all(people.map(getIcon)) . 
     then(function(icons) { 
      people.forEach(function(person, i) { 
      person.set('icon') = icons[i]; 
      }); 
      return people; 
     }) 
     ; 
    }) 
    ; 
} 

dove getIcon è qualcosa di simile

function getIcon(person) { 
    return new Ember.RSVP.Promise(function(resolve, reject) { 
    $.ajax('http://icon-maker.com?' + person.get('name'), resolve); 
    }); 
} 

Oppure, se è più pulita, si potrebbe rompere la roba icona di fuori in un gancio afterModel:

model: function() { return this.store.find('person'); }, 

afterModel: function(model) { 
    return Ember.RSVP.Promise.all(model.map(getIcon)) . 
    then(function(icons) { 
     model.forEach(function(person, i) { 
     person.set('icon') = icons[i]; 
     }); 
    }) 
    ; 
} 

Ora Ember aspetterà che l'intera promessa si risolva, tra cui ottenere le persone e le loro icone e incollare le icone sulle persone, prima di procedere.

HTH.

+0

L'intento è di utilizzare altri dati del modello nel controller per determinare il risultato dinamico. Questo esempio sopra è un caso semplice, ma anche così, davvero non mi piace mettere preoccupazioni specifiche della vista sul modello.Ma in questo caso, ha bisogno di ottenere le informazioni da un archivio dati non correlato, quindi certamente non dovrebbe essere nel modello. –

+0

Ricevo quello che dici, ma il mio esempio qui è semplice. Nel mio caso reale, i dati che devono essere correlati con gli articoli sono completamente estranei. Creare relazioni per esso non avrebbe senso. Il controller ha le informazioni di cui ha bisogno per fare la correlazione e il modello non ha e non dovrebbe avere questa informazione. L'approccio di proprietà proposto da @jnfingerle è corretto per le mie esigenze. Vorrei poter chiamare semplicemente una funzione nel controller. Sembra una cosa ragionevole da fare. Ma questo è il modo Ember ... scendi un po '"rotaie" e le cose si fanno difficili. –

+0

Non è il modo "ember", è il modo "manubri", per non mettere la logica come le chiamate di funzione, molto meno in un controller, nel modello. In ogni caso, chiamare il controller dalla vista/modello è una violazione fondamentale della separazione delle preoccupazioni. Potresti anche rinunciare a MVC se vuoi farlo. Il modello mostra tutto ciò che il controller fornisce. A proposito, puoi fornire un codice di esempio per 'getIconFor'? È asincrono o no? –

12

Come ho passato quasi un'intera giornata su un problema simile, ecco la mia soluzione.

Perché Ember per qualche motivo non consente di eseguire direttamente le funzioni di un controller direttamente dal modello (che è ridicolo e lega le mani in alcuni modi molto stupidi e non so chi ha deciso che questo è un buona idea ...) la cosa che ha più senso per me è creare un helper personalizzato universale, che ti permetta di eseguire funzioni dal modello :) Il trucco qui è che devi sempre passare l'ambito corrente (il "questo" variabile) a quell'assistente.

Così l'assistente potrebbe essere qualcosa di simile:

export default Ember.Helper.helper(function([scope, fn]) { 
    let args = arguments[0].slice(2); 
    let res = fn.apply(scope, args); 
    return res; 
}); 

Quindi, è possibile effettuare una funzione all'interno controller, che si desidera eseguire, ad esempio:

testFn: function(element){ 
    return element.get('name'); 
} 

e poi in il tuo template lo chiami semplicemente con l'helper personalizzato:

{{#each items as |element|}} 
    {{{custom-helper this testFn element}}} 
{{/each}} 

I primi due argomenti a L'helper dovrebbe sempre essere "questo" e il nome della funzione, che si desidera eseguire, e quindi è possibile passare tutti gli argomenti extra che si desidera.


Edit: In ogni caso, ogni volta che pensi di aver bisogno di fare questo, si dovrebbe pensare se non sarà meglio per creare un nuovo componente, invece (che sarà nel 90% dei casi)

+0

Solo errore di battitura, ma credo che {{{custom-helper this testFn element}}} abbia una parentesi troppo alta su ciascun lato – bdavidxyz

+0

in realtà non è un refuso, il "triple-stash" significa "non sfuggire ai tag HTML", ma tu può farlo anche con 2 parentesi – Arntor

+1

"che è ridicolo e lega le mani in modi molto stupidi ..." Una voce alle mie frustrazioni. Sembra una cosa semplice che un framework dovrebbe gestire. – aero

Problemi correlati