2011-05-03 14 views
23

Tengo una viewModel con una matriz observable de objetos con variables observables.Cómo cancelar/revertir los cambios a un modelo observable (o reemplazar el modelo en la matriz con la copia intacta)

Mi plantilla muestra los datos con un botón de edición que oculta los elementos de visualización y muestra los elementos de entrada con los valores encuadernados. Puede comenzar a editar los datos y luego tiene la opción de cancelar. Me gustaría que esta cancelación revierte a la versión sin modificar del objeto.

He tratado clon del objeto al hacer algo como esto:

viewModel.tempContact = jQuery.extend({}, contact); 

o

viewModel.tempContact = jQuery.extend(true, {}, contact); 

pero viewModel.tempContact es modificado tan pronto como el contacto hace.

¿Hay algo incorporado en KnockoutJS para manejar este tipo de situación o es mejor que simplemente cree un nuevo contacto con exactamente los mismos detalles y reemplace el contacto modificado con el nuevo contacto en cancelar?

Cualquier consejo es muy apreciado. ¡Gracias!

Respuesta

16

Hay algunas maneras de manejar algo como esto. Puede construir un nuevo objeto con los mismos valores que su actual y tirarlo en una cancelación. Puede agregar observables adicionales para enlazar a los campos de edición y persistir en aceptar o echar un vistazo a este post para tener una idea sobre el encapsulamiento de esta funcionalidad en un tipo reutilizable (este es mi método preferido).

+0

¡Gracias, Ryan! Leí un par de publicaciones en su sitio hoy, pero de alguna manera me perdí esa. ¡Buen material! – gabrielsond

+0

¡Gracias! Estoy usando este código ahora y funciona muy bien. Modifiqué para hacer que el tempvalue también fuera observable. También agregué un cálculo para verificar si el protectedObservable estaba sucio. – Gunslinger

3

Me encontré con esta publicación mientras buscaba resolver un problema similar y pensé que publicaría mi enfoque y solución para el siguiente tipo.

Fui con su línea de pensamiento - clonar el objeto y repoblar con datos antiguos en "deshacer":

1) copia del objeto de datos en una página nueva variable ("_initData") 2) Crear observable desde el objeto servidor original 3) en "deshacer" recargar observable con los datos inalterados ("_initData")

simplificado JS: _viewModel var; var _initData = {};

$(function() { 
    //on initial load 
    $.post("/loadMeUp", {}, function (data) { 
     $.extend(_initData , data); 
     _viewModel = ko.mapping.fromJS(data); 
    }); 

    //to rollback changes 
    $("#undo").live("click", function(){ 
     var data = {}; 
     $.extend(data, _initData); 
     ko.mapping.fromJS(data, {}, _viewModel); 
    }); 

    //when updating whole object from server 
    $("#updateFromServer).live("click", function(){ 
     $.post("/loadMeUp", {}, function (data) { 
      $.extend(_initData , data); 
      ko.mapping.fromJS(data, {}, _viewModel); 
     }); 
    }); 

    //to just load a single item within the observable (for instance, nested objects) 
    $("#updateSpecificItemFromServer).live("click", function(){ 
     $.post("/loadMeUpSpecificItem", {}, function (data) { 
      $.extend(_initData.SpecificItem, data); 
      ko.mapping.fromJS(data, {}, _viewModel.SpecificItem); 
     }); 
    }); 

    //updating subItems from both lists 
    $(".removeSpecificItem").live("click", function(){ 
     //object id = "element_" + id 
     var id = this.id.split("_")[1]; 
     $.post("/deleteSpecificItem", { itemID: id }, function(data){ 
      //Table of items with the row elements id = "tr_" + id 
      $("#tr_" + id).remove(); 
      $.each(_viewModel.SpecificItem.Members, function(index, value){ 
       if(value.ID == id) 
        _viewModel.SpecificItem.Members.splice(index, 1); 
      }); 
      $.each(_initData.SpecificItem.Members, function(index, value){ 
       if(value.ID == id) 
        _initData.SpecificItem.Members.splice(index, 1); 
      }); 
     }); 
    }); 
}); 

Tenía un objeto lo suficientemente complicado como para no querer agregar controladores para cada propiedad individual.

Algunos cambios se realizan en mi objeto en tiempo real, esos cambios editan tanto el observable como el "_initData".

Cuando recibo datos del servidor actualizo mi objeto "_initData" para intentar mantenerlo sincronizado con el servidor.

0

Puede considerar el uso de KO-UndoManager para esto. He aquí un ejemplo de código para registrar su modelo de vista:

viewModel.undoMgr = ko.undoManager(viewModel, { 
    levels: 12, 
    undoLabel: "Undo (#COUNT#)", 
    redoLabel: "Redo" 
}); 

A continuación, puede añadir deshacer/rehacer botones en su html de la siguiente manera:

<div class="row center-block"> 
    <button class="btn btn-primary" data-bind=" 
     click: undoMgr.undoCommand.execute, 
     text: undoMgr.undoCommand.name, 
     css: { disabled: !undoMgr.undoCommand.enabled() }">UNDO</button> 
    <button class="btn btn-primary" data-bind=" 
     click: undoMgr.redoCommand.execute, 
     text: undoMgr.redoCommand.name, 
     css: { disabled: !undoMgr.redoCommand.enabled() }">REDO</button> 
    </div> 

Y here 's un Plunkr mostrando en acción.Para deshacer todos los cambios, deberá hacer una llamada en bucle al undoMgr.undoCommand.execute en javascript hasta que todos los cambios se deshagan.

2

Pregunta muy antigua, pero acabo de hacer algo muy similar y encontré una manera muy simple, rápida y efectiva de hacer esto usando el plugin de mapeo.

Fondo; Estoy editando una lista de objetos KO enlazados usando foreach. Cada objeto está configurado para estar en modo de edición usando un simple observable, que le dice a la vista que muestre etiquetas o entradas.

Las funciones están diseñadas para su uso en el enlace click para cada artículo foreach.

Entonces, el editar/guardar/cancelar es simplemente:

this.edit = function(model, e) 
{ 
    model.__undo = ko.mapping.toJS(model); 
    model._IsEditing(true); 
}; 

this.cancel = function(model, e) 
{ 
    // Assumes you have variable _mapping in scope that contains any 
    // advanced mapping rules (this is optional) 
    ko.mapping.fromJS(model.__undo, _mapping, model); 
    model._IsEditing(false); 
}; 

this.save = function(model, e) 
{ 
    $.ajax({ 
     url: YOUR_SAVE_URL, 
     dataType: 'json', 
     type: 'POST', 
     data: ko.mapping.toJSON(model), 
     success: 
      function(data, status, jqxhr) 
      { 
       model._IsEditing(false); 
      } 
    }); 
}; 

Esto es muy útil para editar listas de objetos simples, aunque en la mayoría de los casos me encuentro con una lista que contiene los objetos ligeros, a continuación, cargar una modelo de detalle completo para la edición real, por lo que este problema no surge.

Se podría añadir saveUndo/restoreUndo métodos para el modelo si no te gusta viradas la propiedad __undo de esa manera, pero personalmente creo que de esta manera es más clara, además de ser mucho menos código y utilizable en cualquier modelo, incluso uno sin una declaración explícita.

0

Necesitaba algo similar, y no podía usar los observables protegidos, ya que necesitaba el calculado para actualizar los valores temporales. Así que escribí esta extensión nocaut:

Esta extensión crea una versión de cada guión bajo observables self.Comments es decir() -> (self._Comments)

ko.Underscore = function (data) { 
    var obj = data; 
    var result = {}; 
    // Underscore Property Check 
    var _isOwnProperty = function (isUnderscore, prop) { 
     return (isUnderscore == null || prop.startsWith('_') == isUnderscore) && typeof obj[prop] == 'function' && obj.hasOwnProperty(prop) && ko.isObservable(obj[prop]) && !ko.isComputed(obj[prop]) 
    } 
    // Creation of Underscore Properties 
    result.init = function() { 
     for (var prop in obj) { 
      if (_isOwnProperty(null, prop)) { 
       var val = obj[prop](); 
       var temp = '_' + prop; 
       if (obj[prop].isObservableArray) 
        obj[temp] = ko.observableArray(val); 
       else 
        obj[temp] = ko.observable(val); 
      } 
     } 
    }; 
    // Cancel 
    result.Cancel = function() { 
     for (var prop in obj) { 
      if (_isOwnProperty(false, prop)) { 
       var val = obj[prop](); 
       var p = '_' + prop; 
       obj[p](val); 
      } 
     } 
    } 
    // Confirm 
    result.Confirm = function() { 
     for (var prop in obj) { 
      if (_isOwnProperty(true, prop)) { 
       var val = obj[prop](); 
       var p = prop.replace('_', ''); 
       obj[p](val); 
      } 
     } 
    } 
    // Observables 
    result.Properties = function() { 
     var obs = []; 
     for (var prop in obj) { 
      if (typeof obj[prop] == 'function' && obj.hasOwnProperty(prop) && ko.isObservable(obj[prop]) && !ko.isComputed(obj[prop])) { 
       var val = obj[prop](); 
       obs.push({ 'Name': prop, 'Value': val }); 
      } 
     } 
     return obs; 
    } 

    if (obj != null) 
     result.init(); 

    return result; 
} 

Esta extensión le ahorrará escribir duplicados de cada uno de sus observables e ignora su calculado. Funciona así:

var BF_BCS = function (data) { 
    var self = this; 

    self.Score = ko.observable(null); 
    self.Comments = ko.observable(''); 

    self.Underscore = ko.Underscore(self); 

    self.new = function() { 
     self._Score(null); 
     self._Comments(''); 
     self.Confirm(); 
    } 

    self.Cancel = function() { 
     self.Pause(); 
     self.Underscore.Cancel(); 
     self.Resume(); 
    } 

    self.Confirm = function() { 
     self.Pause(); 
     self.Underscore.Confirm(); 
     self.Resume(); 
    } 

    self.Pause = function() { 

    } 

    self.Resume = function() { 

    } 

    self.setData = function (data) { 
     self.Pause(); 

     self._Score(data.Score); 
     self._Comments(data.Comments); 
     self.Confirm(); 
     self.Resume(); 
    } 

    if (data != null) 
     self.setData(data); 
    else 
     self.new(); 
}; 

Así como se puede ver si tiene botones en html:

<div class="panel-footer bf-panel-footer"> 
    <div class="bf-panel-footer-50" data-bind="click: Cancel.bind($data)"> 
     Cancel 
    </div> 
    <div class="bf-panel-footer-50" data-bind="click: Confirm.bind($data)"> 
     Save 
    </div> 
</div> 

Cancelar anula y revertir sus características observables de nuevo a lo que eran, al igual que guardar actualizará la valores reales con los valores temp en una línea

Cuestiones relacionadas