jsfiddle example dirty flag

160 views
Skip to first unread message

Federico Croletti

unread,
Mar 9, 2017, 6:43:03 AM3/9/17
to KnockoutJS

noirabys

unread,
Mar 15, 2017, 5:05:12 AM3/15/17
to KnockoutJS
Hi, 
i would recommend to separate such logic into one or two custom bindings. 

This has major advantages: 

  the viewmodel is not wasted with non business relevant stuff
  Behaviour has its own model hosted in the custom binding and its api is exposed as what i call a "custom binding context" 
  after that the , gui will only use the api and the implementation may be changed on the fly

example to understand what i mean:

<!DOCTYPE html>
<html>     
  <head>    
<script src="jquery.js"></script>    
<script src="knockout-3.3.0.debug.js"></script>         
<script>

<!-- custom binding which models dirty items, exposed api $dirtyItems.addItem,$dirtyItems.removeItem , $dirtyItems.getItems . $dirtyItem.isDirty --> 
ko.bindingHandlers['dirtyItems'] = {
    'init': function (element, valueAccessor, allBindingsAccessor, 
                      viewModel,bindingContext) {
                      
         var DirtyItems = function(){
            var self = this;
            
            self.items = ko.observableArray();
            
            self.addItem = function(item){
               if(! ko.utils.arrayFirst(self.items(),function(x){ return x == item;}))
               self.items.push(item);
            }
            self.removeItem = function(item){
               self.items.remove(item);
            }
            self.getItems = function(){
               var reply = [];
               ko.utils.arrayForEach(self.items(),function(item){reply.push(item.data);});
               return reply;
            }
            self.isDirty = ko.computed(function(){ return self.items().length > 0 });
            
         }
          
         var props = {  };
         props["$dirtyItems"] = new DirtyItems();
 
        var innerBindingContext = bindingContext.extend(props);
        ko.applyBindingsToDescendants(innerBindingContext, element);
        // Also tell KO *not* to bind the descendants itself, 
        //otherwise they will be bound twice
        return { controlsDescendantBindings: true };
    }} ;
    
<!-- custom binding which models a dirty item, exposed api $dirty.reset, $dirty.isDirty --> 
  
  ko.bindingHandlers['dirty'] = {
    'init': function (element, valueAccessor, allBindingsAccessor, 
                      viewModel,bindingContext) {
          
          var data = valueAccessor();
          var DirtyModel = function(data,dirtyItems){
               var self = this;
               self.originalValue = ko.unwrap(data);
               self.data = data;
               self.dirtyItems = dirtyItems;
               self.reset = function(){
                  self.data(self.originalValue);
                  self.dirtyItems.removeItem(self);
               }
               self.isDirty = ko.computed(function(){
                return self.originalValue != ko.unwrap(self.data); 
               });
               self.data.subscribe(function(newVal){
                    if(self.dirtyItems) {
                       self.dirtyItems.addItem(self);
                    }
               });
            }
          
         var props = {  };
         props['$dirty'] = new DirtyModel(data, bindingContext['$dirtyItems']);
         bindingContext['$dirty'] =props['$dirty']; 
        var innerBindingContext = bindingContext.extend(props);
        ko.applyBindingsToDescendants(innerBindingContext, element);
        // Also tell KO *not* to bind the descendants itself, 
        //otherwise they will be bound twice
        return { controlsDescendantBindings: true };
    }};

    
   function Item(id, name) {
      this.id = ko.observable(id);
      this.name = ko.observable(name);
   }

<!-- wow wonderful clean model -->

    var ViewModel = function(items) {
      var self =this;
      self.items = ko.observableArray([
        new Item(1, "one"),
        new Item(2, "two"),
        new Item(3, "three")
      ]);
      self.save = function(items){
         alert(''+ko.toJSON(items));
      }
     }
     
  
  $(document).ready(function(){
     model = new ViewModel();
     ko.applyBindings(model);
     
  });
  </script>        
  <style>
    li { padding: 2px; margin: 2px; }
    input { width: 75px; }
    .dirty { border: solid yellow 2px; }
  </style>
  </head>       
  <body  >
    <div data-bind="dirtyItems: {}" >
     <ul data-bind="foreach: items">
       <li data-bind="dirty: $data.name,css: { dirty: $dirty.isDirty }">
         <span data-bind="text: id"></span>
         <input data-bind="value: name" />
         <!-- here we go , that what i call "custom binding context"  $dirty -->
         <button data-bind="click: $dirty.reset">Reset</button>
       </li>
    </ul>
    <button data-bind="enable: $dirtyItems.isDirty, click: function(){ save($dirtyItems.getItems());}">Save</button>
    <hr />
    <h3>Just Dirty Items</h3>
    <div data-bind="text: ko.toJSON($dirtyItems.getItems())"></div>
    </div>
  </body>
</html>

btw. i would like to know what the knockout "cracks" think about my concept of custom binding context and separating behaviour in its on models ?
  
best regards,
  noirabys
Reply all
Reply to author
Forward
0 new messages