2013-08-15 4 views
13

Quello che sto cercando di ottenere è aggiungere un'interfaccia extra per i campi di input per poter aumentare e diminuire il valore numerico in essi facendo clic sui pulsanti + e -.Come aggiornare il modello AngularJS dalla funzione di collegamento direttiva?

(In sostanza è quello che i campi di input [type = number] hanno su chrome, ma voglio che questo sia compatibile con cross-broswer e che abbia anche il pieno controllo della presentazione su tutti i browser).

Codice in vista:

<input data-ng-model="session.amountChosen" type="text" min="1" class="form-control input-small" data-number-input> 

codice direttiva:.

app.directive('numberInput', function() { 
return { 
    require: 'ngModel', 
    scope: true, 
    link: function(scope, elm, attrs, ctrl) { 

     var currValue = parseInt(scope.$eval(attrs.ngModel)), 
      minValue = attrs.min || 0, 
      maxValue = attrs.max || Infinity, 
      newValue; 

     //puts a wrap around the input and adds + and - buttons 
     elm.wrap('<div class="number-input-wrap"></div>').parent().append('<div class="number-input-controls"><a href="#" class="btn btn-xs btn-pluimen">+</a><a href="#" class="btn btn-xs btn-pluimen">-</a></div>'); 

     //finds the buttons ands binds a click event to them where the model increase/decrease should happen 
     elm.parent().find('a').bind('click',function(e){ 

      if(this.text=='+' && currValue<maxValue) { 
       newValue = currValue+1;  
      } else if (this.text=='-' && currValue>minValue) { 
       newValue = currValue-1;  
      } 

      scope.$apply(function(){ 
       scope.ngModel = newValue; 
      }); 

      e.preventDefault(); 
     }); 


    } 
    }; 

})

Questo è in grado di recuperare il valore attuale modello con portata $ eval (attrs.ngModel) , ma non riesce a impostare il nuovo valore.

Aftermath edit: questo è il codice che ora lavora (nel caso in cui si wan't di vedere la soluzione per questo problema)

app.directive('numberInput', function() { 
    return { 
    require: 'ngModel', 
    scope: true, 
    link: function(scope, elm, attrs, ctrl) { 

     var minValue = attrs.min || 0, 
      maxValue = attrs.max || Infinity; 

     elm.wrap('<div class="number-input-wrap"></div>').parent().append('<div class="number-input-controls"><a href="#" class="btn btn-xs btn-pluimen">+</a><a href="#" class="btn btn-xs btn-pluimen">-</a></div>'); 
     elm.parent().find('a').bind('click',function(e){ 

      var currValue = parseInt(scope.$eval(attrs.ngModel)), 
       newValue = currValue; 

      if(this.text=='+' && currValue<maxValue) { 
       newValue = currValue+1;  
      } else if (this.text=='-' && currValue>minValue) { 
       newValue = currValue-1;  
      } 

      scope.$eval(attrs.ngModel + "=" + newValue); 
      scope.$apply();    

      e.preventDefault(); 
     }); 
    } 
    }; 
}) 
+0

possono essere 'ctrl. $ SetViewValue (newValue)'? – Cherniv

+0

usa ng-clic e un modello. –

+0

Cherniv, no, non ha fatto il trucco, ma il tuo suggerimento mi ha aiutato a capire meglio Angular. Grazie. –

risposta

28

ngModelController metodi devono essere utilizzati al posto di $ eval() per ottenere e impostare il valore della proprietà ng-modello.

parseInt() non è necessario quando si valuta un attributo con un valore numerico, poiché $ eval convertirà il valore in un numero. $ eval dovrebbe essere usato per impostare le variabili minValue e maxValue.

Non è necessario che la direttiva crei un ambito figlio.

$ apply() non è necessario perché i metodi ngModelController ($ render() in particolare) aggiorneranno automaticamente la vista. Tuttavia, come note di @Harijs in un commento qui sotto, $ apply() è necessario se anche altre parti dell'app devono essere aggiornate.

app.directive('numberInput', function ($parse) { 
    return { 
     require: 'ngModel', 
     link: function (scope, elm, attrs, ctrl) { 
      var minValue = scope.$eval(attrs.min) || 0, 
       maxValue = scope.$eval(attrs.max) || Infinity; 
      elm.wrap('<div class="number-input-wrap"></div>').parent() 
       .append('<div class="number-input-controls"><a href="#" class="btn btn-xs btn-pluimen">+</a><a href="#" class="btn btn-xs btn-pluimen">-</a></div>'); 
      elm.parent().find('a').bind('click', function (e) { 
       var currValue = ctrl.$modelValue, 
        newValue; 
       if (this.text === '+' && currValue < maxValue) { 
        newValue = currValue + 1; 
       } else if (this.text === '-' && currValue > minValue) { 
        newValue = currValue - 1; 
       } 
       ctrl.$setViewValue(newValue); 
       ctrl.$render(); 
       e.preventDefault(); 
       scope.$apply(); // needed if other parts of the app need to be updated 
      }); 
     } 
    }; 
}); 

fiddle

+0

Il problema (o l'insufficienza) con questo nel mio caso d'uso è che aggiorna solo questo campo di input, non l'intero modello - tutti gli altri campi di app che dipendono da questo input. Pertanto l'ambito. $ Apply() è necessario per me. E tu sei corrretto riguardo l'ambito, tuttavia non vedo completamente perché $ eval debba essere chiamato sugli attributi. Funziona altrettanto bene con parseInt. –

+1

@HarijsDeksnis, grazie, ho aggiornato la mia risposta per riflettere i vostri commenti. Sì, puoi usare parseInt() o $ eval. Nel tuo codice originale, li hai usati entrambi, quindi stavo cercando di farti notare che devi solo usarne uno. In "Aftermath edit" non si utilizza né con minValue né con maxValue e si dovrebbe usare uno di questi, altrimenti minValue e maxValue saranno di tipo string. $ eval() è un po 'più generale, in quanto analizza correttamente i booleani, gli interi o le stringhe e assegna il tipo corretto alla variabile, mentre parseInt() ovviamente funziona solo con valori numerici. Quindi tendo a favorire $ eval(). –

+0

Grazie per il giro aggiuntivo di aggiornamento. –

4

Non si vorrebbe sostituire la variabile scope.ngModel, ma il valore che è dietro quella variabile. L'hai fatto già quando si legge il valore nella prima riga della funzione di collegamento:

currValue = parseInt(scope.$eval(attrs.ngModel)) 
//       ^^^^^^^^^^^^^^^^^^^^ 

Se si tratta di un valore normale, come myProperty, è possibile utilizzare che sul campo di applicazione:

scope[attr.ngModel] = newValue 

Ma questo non funzionerà, se hai un valore di espressione, come container.myProperty. In tal caso (e questo è il tipo più generico, si dovrebbe essere puntando per) dovreste eval il valore viene impostato il campo di applicazione, in questo modo:

scope.$eval(attrs.ngModel + "=" + newValue) 

devo ammettere, che la parte $eval è un po 'brutto, come in JavaScript con il pendente eval, ma fa il trucco. Tieni presente che potrebbe non funzionare in questo modo, quando desideri impostare i valori stringa. Allora dovresti scappare da quei valori.

Speranza che aiuta;)

+0

Grazie, aggiorna il valore del modello! Anche se non cambia la vista su se stesso. Devo aggiungere questo per ottenere il campo di input da aggiornare sulla vista. ctrl. $ ViewValue = newValue; ctrl. $ Render(); Tuttavia, devo ancora capire come aggiornare altri campi nella mia app che usano il valore di questo modello. –

+0

Era più facile del previsto. Ho appena dovuto aggiungere scope. $ Apply() alla fine. –

+0

Ah, sì, ho dimenticato di menzionare che qualsiasi listener di eventi jQuery come il 'bind (" click ")' non innesca il ciclo 'digest' dell'angolare, quindi dovresti farlo manualmente. Ma a quanto pare l'hai già capito;) – Tharabas

3
.directive('numberInput', function ($parse) { 
    return { 
     require: 'ngModel', 
     link: function (scope, elm, attrs){ 
     elm.bind("click", function() { 
      var model = $parse(attrs.ngModel); 
      var modelSetter = model.assign; 
      scope.$apply(function() { 
       modelSetter(scope, model); 
      }); 
    } 
    } 
}) 
+0

Grazie, mi ha davvero aiutato! –