javascript - ejemplos - angularjs tutorial
¿Hay un patrón para tratar con "Cancelar" en los diálogos modales de AngularJS? (6)
A partir de Angular 1.3, existe la directiva ngModelOptions que permite obtener el mismo comportamiento de forma nativa.
<form name="userForm">
<input type="text" ng-model="user.name" ng-model-options="{ updateOn: ''submit'' }" name="userName">
<button type="submit">save</button>
<button type="button" ng-click="userForm.userName.$rollbackViewValue();">cancel</button>
</form>
JSFiddle: http://jsfiddle.net/8btk5/104/
Nota: ¡No se trata de mostrar un diálogo modal con AngularJS, ese tema tiene muchas preguntas y respuestas!
Esta pregunta se trata de cómo reaccionar a Aceptar y Cancelar dentro de un cuadro de diálogo modal en una página. Digamos que tienes un alcance con solo una variable:
$scope.description = "Oh, how I love porcupines..."
Si le proporciono un cuadro de diálogo modal en la página y uso ng-model = "description" dentro de ese cuadro de diálogo, todos los cambios que realice en realidad se realizan en tiempo real para la propia descripción a medida que escribe. Eso es malo, porque ¿cómo cancelas ese diálogo?
Hay una pregunta que dice hacer lo que explico a continuación. La respuesta aceptada es la misma "solución" que se me ocurrió: AngularJS: modal basado en datos: guarde los cambios solo cuando se hace clic en "Guardar" u olvídese de los cambios si se hace clic en "Cancelar"
Puedo ver cómo hacerlo si al hacer clic en el botón para abrir un modal vuelvo a una función en la parte posterior y eso crea una copia temporal de los datos relevantes para el modal y luego aparece el modal. Luego, "OK" (o "Guardar" o lo que sea) podría copiar los valores temporales a los valores reales del modelo.
main.js (extracto):
$scope.descriptionUncommitted = $scope.description;
$scope.commitChanges = function () {
$scope.description = $scope.descriptionUncommitted;
}
main.html (extracto):
<input type="text" ng-model="descriptionUncommitted"/>
<button ng-click="commitChanges()">Save</button>
¡El problema con eso es que no es declarativo ! De hecho, no se parece en nada a AngularJS en ningún otro lado. Es casi como si necesitáramos una "descripción" de ng-model-uncommitted donde pudieran hacer todos los cambios que quisieran pero solo se comprometerían cuando disparamos con otra declaración. ¿Hay algo así en un complemento en alguna parte o AngularJS lo está agregando?
Editar: parece que un ejemplo de una forma diferente de hacerlo podría estar en orden.
main.js:
$scope.filename = "panorama.jpg";
$scope.description = "A panorama of the mountains.";
$scope.persist = function () { // Some function to hit a back end service. };
main.html:
<form>
<input type="text" ng-model-uncommitted="filename"/>
<input type="text" ng-model-uncommitted="description"/>
<button ng-commit ng-click="persist()">Save</button>
<button ng-discard>Cancel</button>
</form>
Puse una etiqueta de formulario porque no sé cómo agruparías los artículos, así que estaba claro que era parte de la misma "transacción" (a falta de una palabra mejor). Pero debería haber alguna forma de que todo esto ocurra automáticamente y las copias clonadas de las variables del modelo se usen para los valores iniciales, se usen para la entrada y se actualicen automáticamente, se validen, etc. y finalmente se descarten o copien al mismo valor que Inicialmente se usó para crearlos si el usuario decide comprometerse.
¿No es algo así más fácil que el código en el controlador hacer ese trabajo una y otra vez para 20 modales en un gran sitio web? ¿O estoy loco?
Aquí está mi intento de mantenerlo simple, por lo que es declarativo y no depende de etiquetas de formulario u otras cosas.
Una directiva simple:
.directive("myDirective", function(){
return {
scope: {
item: "=myDirective"
},
link: function($scope){
$scope.stateEnum = {
view: 0,
edit: 1
};
$scope.state = $scope.stateEnum.view;
$scope.edit = function(){
$scope.tmp1 = $scope.item.text;
$scope.tmp2 = $scope.item.description;
$scope.state = $scope.stateEnum.edit;
};
$scope.save = function(){
$scope.item.text = $scope.tmp1;
$scope.item.description = $scope.tmp2;
$scope.state = $scope.stateEnum.view;
};
$scope.cancel = function(){
$scope.state = $scope.stateEnum.view;
};
},
templateUrl: "viewTemplate.html"
};
})
viewTemplate.html:
<div>
<span ng-show="state == stateEnum.view" ng-click="edit()">{{item.text}}, {{item.description}}</span>
<div ng-show="state == stateEnum.edit"><input ng-model="tmp1" type="text"/> <input ng-model="tmp2" type="text"/><a href="javascript:void(0)" ng-click="save()">save</a> <a href="javascript:void(0)" ng-click="cancel()">cancel</a></div>
</div>
Luego establece el contexto (elemento):
<div ng-repeat="item in myItems">
<div my-directive="item"></div>
</div>
Véalo en acción: http://plnkr.co/edit/VqoKQoIyhtYnge2hzrFk?p=preview
Básicamente, en angular si algo no es declarativo, se hace una directiva .
.directive(''shadow'', function() {
return {
scope: {
target: ''=shadow''
},
link: function(scope, el, att) {
scope[att.shadow] = angular.copy(scope.target);
scope.commit = function() {
scope.target = scope[att.shadow];
};
}
};
Entonces:
<div shadow="data">
<input ng-model="data">
<button ng-click="commit()">save</button>
</div>
Entonces, los data
dentro de la directiva shadow
serán una copia de los data
originales. Y se volverá a copiar al original cuando se haga clic en el botón.
Y aquí está el ejemplo de trabajo: jsbin
No lo he probado más allá de este ejemplo, por lo que puede no funcionar en otros casos, pero creo que da una idea de las posibilidades.
Editar:
Otro ejemplo con un objeto en lugar de una cadena, y varios campos en el formulario ( angular.copy
se requiere una angular.copy
adicional): jsbin
Edit2, versiones angulares 1.2.x
Según este change , la input
dentro de la directiva ya no está accediendo al alcance aislado. Una alternativa es crear un ámbito hijo no aislado ( scope:true
), para contener la copia de los datos y acceder al alcance principal para guardarla.
Entonces, para versiones posteriores de angular, este es el mismo enfoque que antes ligeramente modificado para hacer el truco:
.directive(''shadow'', function() {
return {
scope: true,
link: function(scope, el, att) {
scope[att.shadow] = angular.copy(scope[att.shadow]);
scope.commit = function() {
scope.$parent[att.shadow] = angular.copy(scope[att.shadow]);
};
}
};
});
Ejemplo: jsbin
Tenga en cuenta que el problema con el uso de $parent
es que se puede romper si finalmente hay otro ámbito en el medio.
Enfrentando el mismo problema y yendo a través de este hilo, se me ocurrió una directiva de lazy-model
que funciona exactamente como ng-model
pero guarda los cambios solo cuando se envía el formulario .
Uso:
<input type="text" lazy-model="user.name">
Tenga en cuenta que debe envolverlo en la etiqueta <form>
contrario, el modelo perezoso no sabrá cuándo insertar cambios en el modelo original.
Demostración de trabajo completo : http://jsfiddle.net/8btk5/3/
Código de directiva lazyModel:
(mejor uso versión actual en github )
app.directive(''lazyModel'', function($parse, $compile) {
return {
restrict: ''A'',
require: ''^form'',
scope: true,
compile: function compile(elem, attr) {
// getter and setter for original model
var ngModelGet = $parse(attr.lazyModel);
var ngModelSet = ngModelGet.assign;
// set ng-model to buffer in isolate scope
elem.attr(''ng-model'', ''buffer'');
// remove lazy-model attribute to exclude recursion
elem.removeAttr("lazy-model");
return function postLink(scope, elem, attr) {
// initialize buffer value as copy of original model
scope.buffer = ngModelGet(scope.$parent);
// compile element with ng-model directive poining to buffer value
$compile(elem)(scope);
// bind form submit to write back final value from buffer
var form = elem.parent();
while(form[0].tagName !== ''FORM'') {
form = form.parent();
}
form.bind(''submit'', function() {
scope.$apply(function() {
ngModelSet(scope.$parent, scope.buffer);
});
});
form.bind(''reset'', function(e) {
e.preventDefault();
scope.$apply(function() {
scope.buffer = ngModelGet(scope.$parent);
});
});
};
}
};
});
Otra forma es copiar el modelo antes de editarlo, y al cancelar, restaurar el original. Código de controlador angular:
//on edit, make a copy of the original model and store it on scope
function edit(model){
//put model on scope for the cancel method to access
$scope.modelBeingEdited = model;
//copy from model -> scope.originalModel
angular.copy(model,$scope.originalModel);
}
function cancelEdit(){
//copy from scope.original back to your model
angular.copy($scope.originalModel, $scope.modelBeingEdited)
}
Espero que ayude a alguien!
Parece que estás pensando demasiado en esto. No hay un complemento porque el proceso es bastante simple. Si desea una copia prístina del modelo, haga una y guárdela en el controlador. Si un usuario cancela, restablezca el modelo a su copia y use el método FormController. $ SetPristine () para volver a hacer el formulario prístino.
//Controller:
myService.findOne({$route.current.params[''id'']}, function(results) {
$scope.myModel = results;
var backup = results;
}
//cancel
$scope.cancel = function() {
$scope.myModel = backup;
$scope.myForm.$setPristine();
}
Entonces en tu opinión:
<form name="myForm">
Debe nombrar el formulario para crear el controlador $ scope.myForm.