Friday, July 10, 2009

Javascript MVC


När man jobbat mycket med Javascript så är det lätt hänt att man tappar lite av den kontroll man gärna vill ha i ett projekt. Så därför håller jag på att utveckla en MVC-modell för våra projekt, baserat på prototype.js. So far, efter knappt 600 rader kod, verkar det lovande, jag har försökt att efterlikna sättet Zend Frameworks API, då det onekligen är ett rätt genomtänkt API.

Ett snabbt exempel på hur det kan se ut:

// Script-delar:
...
// Modell för favoriter:
var FNLModel_Favorites = Class.create(FNLModel,{
   init:function($super, params){
      this.primary_key='user_product_id';
      $super(params);
   }
});   
...
// Javascriptdelen av view för favoriter
var FNLView_SingleSmallFavorite = Class.create(FNLView_Simple,{
   init:function(params){
      this.template = 'TEMPLATE_SINGLE_FAVORITE';
   }   
});
// Javascriptdel av en view för en favoritlista
var FNLView_SmallFavoriteList = Class.create(FNLView_Simple,{
   init:function(params){
      this.template = 'TEMPLATE_SMALL_FAVORITE_LIST';      
   },
   render:function($super,self){
      var str = '';
      for(var i=0;i<self.favorites.length && i<15;i++){
         self.favorite = self.favorites[i];
         str += self.render('SingleSmallFavorite');
      }
      self.favoriteContent = str;
      return $super(self);      
   }
});
...
// Controller för favoriter
var FNLController_FavoriteListLeft = Class.create(FNLController,{
   init:function(params){
      var self = this;      
      this.target = params.target;
      var callback = this.callback.bind(this);
// Lyssna efter uppdateringar av favoriter via ajax
      this.fnlTools.addEventListener({id:'favorites',event:FNLToolsEvents.ajaxResponse,callbackFunction:callback});
// Och se till att favoriterna uppdateras vid serveranrop.
      this.fnlTools.registerAjaxListener(this,FNL_CLUB_LISTENER_URL, 'favorites','user_product_id',-1);
      this.id=fnlTools.newUID;
   },
   callback:function(event){
      this.view.favorites = event.model.data;
      this.view.fnl_id = this.id;         
      str = this.view.render('SmallFavoriteList');         
      $(this.target).innerHTML = str;   
            
// Uppdatera serveranroparen så att den bara kollar efter nya favoriter
      this.fnlTools.updateAjaxListener(FNL_CLUB_LISTENER_URL, 'favorites',event.model.max());            
   }
});

...

// init-kod
fnlTools = new FNLToolsClass();
var productListUpdater = fnlTools.getController('ProductList',{target:'productList3'});


samt html-element som fungerar som templates:
<div id='TEMPLATE_SMALL_FAVORITE_LIST' style='display:none'>
#favoriteContent#
</div>
<div id='TEMPLATE_SINGLE_FAVORITE' style='display:none'>
<a target="_blank" href="#favorite.p_url#"><img width="44" title="#favorite.name#" src="http://stat2.shoppinggatan.se/public/images#favorite.thumb_local_url#" style="border: 2px solid rgb(201, 205, 208);"/></a>
</div>



Och vad gör då detta exempel? Jo, ett element med favoritprodukter skapas, och hålls dessutom uppdaterad om nya favoriter läggs till, endera i samma dokument, eller från en annan instans (för samma användare). Givetvis hanterar den dessutom hur många element som helst med samma innehåll, alla hålls uppdaterade i vilket fall. Det är också mycket lätt att hålla koll på alla events som sker på samma sida.

Om man tex skulle skriva ett litet chatrum med den nuvarande modellen så tror jag att det skulle handla om ca 30 minuters arbete, från start till mål. Givetvis inte aktuellt, då jag inte känner någon som chattar längre, men jag tycker ändå att det visar på kodens styrka. Javascripts svaghet med bara visst stöd för klasser syns i koden, men med Prototypes klasshantering så blir det lite enklare att koda.

En fundering som jag har är om det ska vara en instanserad controller per element, eller en instanserad kontroller per aktivitetstyp.

Om du är intresserad av att veta mer om det här, så är det bara att höra av dig.

No comments: