Skip to content

Instantly share code, notes, and snippets.

@justinbmeyer
Created August 17, 2012 15:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save justinbmeyer/3380049 to your computer and use it in GitHub Desktop.
Save justinbmeyer/3380049 to your computer and use it in GitHub Desktop.
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
Copy link

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