Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Observes over Pub-Sub

Here's how to build an app with something like CanJS and AMD-like modules (using steal). For this example app, consider a tree-like nested grid where you can select locations and get more information. The app might look something like:

IL
Chicago
Naperville
California
San Fran
LA
Naperville X

Naperville details

Chicago X

Chicago details

But, you can only select items of the same "level" (items that have the same parent). And, you can also remove an item's details by clicking the X.

1: Organize client state into observes.

So there are rules about what can be selected. Lets make that as an Observable list:

steal('can', function(can){
  return can.Observe.List({
    add: function(location){
      if( this.length ) {
        for(var i = 0 ; i < this.length ; i++ ){
          if( this[i].parentId !== location.parentId ) {
            this.splice(0, this.length);
            break;
          }
        }
      }
      this.push(location)
    },
    remove: function(location){
      var index = this.indexOf(location)
      index >= 0 && this.splice(index, 1)
    },
    toggle: function(location){
      var index = this.indexOf(location)
      index >= 0 ? this.remove(location) : this.add(location)
    }
  })
})

This list's add / remove / toggle methods only allow locations with the same parentId to be in the list.

2: Decide how you want to glue widgets to state

There are 2 ways to do create the widgets:

  1. They accept an observe, listen to changes in it and make changes on it.
  2. They produce events and have methods that allow you to toggle their internal state.

#2 option is more reusable, but depending on the situation #1 is just so easy that it's worth it. Lets explore each one:

Widgets that accept an observe

To create the tree and details panel, you might do:

steal('tree','details','selected_list',
  function(Tree, Details, SelectedList){

  var selectedList = new SelectedList()
  new Tree("#locations",{
    selected: selectedList, 
    items: Location.findAll()
  });
  new Details("#details",{selected: selectedList });
})

Widgets that produce events and you update the observe yourself

To create the tree and details panel, you might do:

steal('tree','details','selected_list',
  function(Tree, Details, SelectedList){

  var selectedList = new SelectedList();

  // create and hookup the Tree
  var tree = new Tree("#locations",{items: Location.findAll()});

  // when someone selects an item, toggle it in the list
  $("#locations").on(".item","selected", function(ev, location){
    selectedList.toggle(location)
  })
  // when the selectedList is updated, update the tree
  selectedList.bind('add', function(ev, items, index){
    tree.activate(index)
  }).bind('remove', function(ev, items, index){
    tree.deactivate(index)
  });


  // create an hookup Details
  var details = new Details("#details");

  // when a detail's X is clicked, remove
  $("#details").on(".item","removeSelect", function(ev, location){
    selectedList.remove(location);
  });
 
  selectedList.bind("length", function(){
    details.update(selectedList)
  })
})

3: Create widgets

Depending on how you intend to glue the widgets together, their code will change. I'll show the details panel for each case:

Widgets that accept an observe

steal('can', './details.ejs',function(can, detailsEJS){
  return can.Control({
    init: function(){
       this.element.html("Empty")
    },
    "{selected} length":function(selected){
       this.element.html( detailsEJS(selected) )
    },
    ".item .destroy click": function(el, ev){
      var itemEl = el.closest('.item')
      this.options.selected.remove( itemEl.data('item') )
    }
  })
});

Widgets that produce events and you update the observe yourself

steal('can', './details.ejs',function(can, detailsEJS){
  return can.Control({
    init: function(){
       this.element.html("Empty")
    },
    "update":function(selected){
       this.element.html( detailsEJS(selected) )
    },
    ".item .destroy click": function(el, ev){
      var itemEl = el.closest('.item')
      itemEl.trigger("removeSelect", itemEl.data('item') )
    }
  })
});

4: Repeat

Using this pattern you can build bigger and bigger functionality. But, when you build layers above this, we also organize the code in step #2 into a control. For example:

steal('can','jquery',
  'tree','details','selected_list',
  function(can, $, Tree, Details, SelectedList){

  return can.Control({
    init: function(){
       this.options.selectedList = new SelectedList();

       // create and hookup the Tree
       this.tree = new Tree(this.element.find(".locations"),{
         items: Location.findAll()
       });
       this.details = new Details("#details");
       this.on();
    },
    ".locations .item .selected": function(el, ev, location){
      selectedList.toggle(location)
    },
    "{selected} add": function(selected, ev, items, index){
      this.tree.activate(index);
      this.details.update(selected)
    },
    "{selected} remove": function(selected, ev, items, index){
      this.tree.deactivate(index)
      this.details.update(selected)
    },
    ".details .item removeSelect", function(el, ev, location){
      selectedList.remove(location);
    }
  })
@cherifGsoul

This comment has been minimized.

Copy link

cherifGsoul commented Sep 26, 2013

A year ago but this gist still rocks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.