Index: /trunk/prototype/app/datalayer.js
===================================================================
--- /trunk/prototype/app/datalayer.js	(revision 5283)
+++ /trunk/prototype/app/datalayer.js	(revision 5283)
@@ -0,0 +1,1948 @@
+internalUrl = /^([A-z0-9-_]+)(:[A-z0-9-_]+)?$/;
+internalUri = /^([a-zA-Z0-9-_]+)\(([a-zA-Z0-9-_]+)\):\/\/(.*)|([a-zA-Z0-9-_]+):\/\/(.*)$/;
+isGeneratedId = /^\d+\(javascript\)$/;
+arrayName = /^([A-z0-9-_]+)\[\]$/;
+startsDoubleDot = /^:/;
+// cached_urls = {};
+
+$.ajaxPrefilter(function( options, originalOptions, jqXHR ){
+
+      if( options.url != 'undefined' && internalUrl.test( options.url ) ){
+
+// 	  if( !cached_urls[options.url] )
+// 	      return;
+// 	  alert( options.url + " dentro" );
+	  jqXHR.abort(); 
+
+	  var callback = ( options.success || options.complete || $.noop );
+
+	  switch( options.type.toUpperCase() )
+	  {
+	    case 'GET':
+		  return callback( DataLayer.get( options.url, /*false,*/ options.data ) );
+
+	    case 'POST':
+		  return callback( DataLayer.put( options.url, options.data ) );
+	  }
+
+	  //return( false );
+
+// 	  options.url = params[1];
+// 	  options.data = ( options.data || "" ) + "&" + params[2];
+      }
+
+});
+
+// $("a").live("click", function( event ){
+// 
+//     event.preventDefault();
+// 
+//     $.ajax({
+// 
+// 	
+// 
+//     });
+// 
+// });
+
+$("form").live( "submit", function( event ){
+
+    var action = $(this).attr("action"), res = false;
+    
+    if( res = internalUrl.exec( action ) )
+    {
+	event.preventDefault();
+
+	var data = DataLayer.form( this );
+	DataLayer.put( res[1], data );
+
+	return( false );
+    }
+
+    return( true );
+});
+
+$.storage = new $.store();
+
+DataLayer = {
+
+    links: {},
+    concepts: {},
+    listeners: {},
+    encoders: {},
+    decoders: {},
+    templates: {},
+    criterias: {},
+    tasks: [],
+
+    render: function( templateName, data, filter, formatter, force ){
+
+	if( $.isFunction( filter ) )
+	{
+	    force = formatter;
+	    formatter = filter;
+	    filter = false;
+	}
+
+	if( typeof data === "string" )
+	{
+	    data = this.get( data, filter, force ) || {};
+	}
+	
+	var formatting = function( template ){
+
+	      if( template === false ) return( false );
+
+	      if( template )
+		  DataLayer.templates[ templateName ] = new EJS({ text: template, cache: false });
+
+	      var html = DataLayer.templates[ templateName ].render( { data: data } );
+
+	      if( !formatter )
+		  return( html );
+
+	      return formatter( html );
+	}
+
+	if( this.templates[ templateName ] )
+	{
+	    return formatting();
+	}
+
+	return this.send( DataLayer.templatePath + templateName, 'get', false, formatting, !!!formatter );
+    },
+    
+    send: function( url, type, data, callback, sync ){
+      
+	  var result = false, fired = false;
+      
+	  var envelope = {
+
+	      'async': ( typeof sync !== "undefined" ? !sync : !!callback ),
+	      'url': url,
+	      'success': function( dt, textStatus, jqXHR ){
+
+		    if( callback )
+		    {
+			fired = true;
+			result = callback( dt, textStatus, jqXHR );
+		    }
+		    else
+			result = dt;
+
+		},
+	      'complete': function( jqXHR, textStatus ){
+
+		  if( !fired && callback )
+		      result = callback( false, textStatus, jqXHR );
+
+	      },
+
+	      'type': $.isArray( type ) ? type[0] : type,
+	      'data': data
+
+	    };
+
+	  if( $.isArray( type ) && type[1] )
+	      envelope['dataType'] = type[1];
+
+	  $.ajax( envelope );
+      
+	  return( result );
+    },
+    
+    dispatch: function( dispatcher, data, callback, isPost ){
+      
+      return this.send( this.dispatchPath + dispatcher + ".php", 
+			[ ( isPost ? 'post' : 'get' ), 'json' ],
+			data,
+			callback );
+
+//       $.ajax({
+// 	      'async': !!callback,
+// 	      'url': this.dispatchPath + dispatcher + ".php",
+// 	      'type': ( isPost ? 'post' : 'get' ),
+// 	      'dataType': 'json',
+// 	      'data': data,
+// 	      'success': function( dt, textStatus, jqXHR ){
+// 
+// 		    if( callback )
+// 		    {
+// 			fired = true;
+// 			callback( dt, textStatus, jqXHR );
+// 		    }
+// 		    else
+// 			result = dt;
+// 
+// 		},
+// 	      'complete': function( jqXHR, textStatus ){
+// 
+// 		  if( !fired && callback )
+// 		      callback( false, textStatus, jqXHR );
+// 
+// 	      }/*,
+// 	      'processData': false*/
+// 	  });
+
+      //return( result );
+    },
+
+    form: function( target ){
+
+	var params = {}, $this = $(target);
+
+	if( !$this.is( "form" ) )
+	    $this = $this.parents( "form" );
+
+	$.each( $this.serializeArray(), function( i, el ){
+
+	    if( newName = arrayName.exec( el.name ) )
+		el.name = newName[1];
+	    else if( !params[ el.name ] )
+		return( params[ el.name ] = el.value );
+
+	    params[ el.name ] = params[ el.name ] || [];
+
+	    if( $.type(params[ el.name ]) !== "array" )
+		params[ el.name ] = [ params[ el.name ] ];
+
+	    params[ el.name ].push( el.value );
+	});
+
+// 	alert(dump(params));
+
+	return this.decode( $this.attr( "action" ), params );
+    },
+
+    blend: function( action, data ){
+
+// 	if( notArray = (!$.isArray(data)) )
+// 	    data = [ data ];
+
+	var form = $('form[action="'+action+'"]');
+
+	form.get(0).reset();
+
+	var named = form.find( 'input[name]' );
+
+	for( var name in data )
+	{
+	    named.filter( '[name="'+name+'"]' ).val( data[name] );
+	}
+    },
+
+ 
+    
+    put: function( concept, filter, data, oneSide ){
+      
+      ///////////////////////////// normalize ////////////////////////////////
+	if( arguments.length == 2 )
+	{
+	    data = filter;
+	    filter = false;
+	}
+	if( typeof data === "undefined" ||
+	    $.type(data) === "boolean" )
+	{
+	    oneSide = data;
+	    data = filter;
+	    filter = false;
+	}
+	
+	if( !concept || !data )
+	    return( false );
+
+	var decoder = "", id = false, bothSides = (typeof oneSide === "undefined"), notArray, res;
+	
+	if( $.type(filter) === "string" )
+	{
+	    id = filter;
+	    filter = false;
+	}
+
+	if( id )
+	    data.id = id;
+
+	if( notArray = ( $.type( data ) !== "array" ) )
+	    data = [ data ];
+
+	if( res = internalUrl.exec( concept ) )
+	{
+	    //TODO: verificar se a decodificaÃ§ao deve ser feita em cada item do array
+	    data = this.decode( concept, data );
+	    concept = res[1];
+	    decoder = res[2];
+	}
+
+      ////////////////////////////////////////////////////////////////////////
+
+	if( bothSides || !oneSide )
+	{
+	    var result = false, links = this.links( concept ), 
+	    current = this.check( concept ) || {}, ids = [];
+
+	    for( var i = 0; i < data.length; i++ )
+	    {
+		var key = ids[ ids.length ] = data[i].id || this.generateId( concept ), updateSet = {};
+
+		////////////////////////////// linkage /////////////////////////////////    
+		for( var link in links )
+		{
+		    if( data[i][link] )
+		    {
+			var isConcept = false;
+		      
+			if( isConcept = this.isConcept( concept, link ) )
+			    data[i][link] = [ data[i][link] ];
+
+			var _this = this;
+
+			$.each( data[i][link], function( ii, el ){
+
+				var isRef = false
+
+				if( isRef = ($.type(el) === "string") )
+				    el = { id: el };
+
+				var nestedLinks = _this.links( links[link], true );
+				
+				if( isConcept )
+				{
+				    el[ nestedLinks[concept] ] = el[ nestedLinks[concept] ] || [];
+				    el[ nestedLinks[concept] ].push( key );
+				}
+				else
+				    el[ nestedLinks[concept] ] = key;
+
+				if( isRef && ( !current[ key ] || !current[ key ][ link ] || 
+				               (isConcept ? current[ key ][ link ] !== el.id : !$.inArray( el.id, current[ key ][ link ] )) ) )
+				{
+				    updateSet[ links[link] ] = updateSet[ links[link] ] || [];
+				    updateSet[ links[link] ].push( el );
+				}
+				else if( !isRef )
+				    data[i][link][ii] = _this.put( links[link], el, oneSide );
+			    });
+
+			if( isConcept )
+			    data[i][link] = data[i][link][0];
+		    }
+		}
+		//////////////////////////////////////////////////////////////////////////
+
+		if( data[i].id )
+		    data[i] = this.merge(  concept, current[ data[i].id ], data[i] );
+
+		 current[ key ] = data[i];
+
+		if( bothSides )
+		  this.report( concept, key, data[i] );
+	    }
+
+	    this.store( concept, current );
+
+	    for( var setKey in updateSet )
+	    {
+		DataLayer.put( setKey, updateSet[ setKey ], false );
+
+		if( bothSides )
+		    for( var i = 0; i < updateSet[ setKey ].length; i++ )
+		      this.report( setKey, updateSet[ setKey ][i].id, updateSet[ setKey ][i] );
+	    }
+	}
+
+	if( oneSide ) 
+	    this.commit( concept, ids/*, true */);
+
+	this.broadcast( concept, oneSide ? 'server' : bothSides ? 'serverclient' : 'client', true );
+
+	return( notArray ? ids[0] : ids );
+
+    },
+    
+    remove: function( concept, id, oneSide ){
+      
+	var bothSides = (typeof oneSide === "undefined");
+
+	if( bothSides || !oneSide )
+	    this.del( concept, id );
+
+	if( bothSides )
+	    this.report( concept, id, false );
+	if( oneSide )
+	    this.commit( concept, id );
+
+	this.broadcast( concept, oneSide ? 'server' : bothSides ? 'serverclient' : 'client', false );
+    },
+    
+    report: function( concept, id, data )
+    {      
+	var current = this.check( ':current', concept ) || {};
+
+	if( !current[ id ] )
+	    current[ id ] = this.check( concept, id ) || {};
+	
+	this.store( ':current', concept, current );
+
+	var diff = this.diff( current[ id ], data );
+
+	var diffs = this.check( ':diff', concept ) || {};
+
+	diffs[ id ] = diff;
+
+	this.store( ':diff', concept, diffs );
+    },
+
+//     enqueue: function( queueName, concept, id, data ){
+// 
+// 	var queue = this.check( ':' + queueName, concept ) || {};
+// 
+// 
+//     },
+//     
+//     dequeue: function( queueName, concept, id ){
+// 
+// 	
+// 
+//     },
+    
+    
+    
+    rollback: function( concept, ids ){
+      
+	var queue = this.prepareQ( 'current', concept, ids );
+
+	ids = [];
+
+	for( var id in queue )
+	{
+	     this.put( concept, id, queue[id], false );
+
+	     ids[ ids.length ] = id;
+	}
+
+	this.clearQ( concept, ( ids.length ? ids : false ) );
+
+	this.broadcast( concept, 'revert' );
+      
+    },
+    
+    prepareQ: function( queueName, concept, ids ){
+      
+      if( notArray = ($.type(concept) !== "array") )
+	  concept = [ concept ];
+      
+      for( var i = 0; i < concept.length; i++ )
+      {
+	  var queue = this.check( ':' + queueName, concept[i] || false );
+	  
+	  if( ids )
+	  {
+	      if( $.type(ids) !== "array" )
+		  ids = [ ids ];
+
+	      var filtered = {};
+
+	      for( var ii = 0; ii < ids.length; ii++ )
+	      {
+		  filtered[ ids[ii] ] = queue[ ids[ii] ];
+	      }
+
+	      queue = filtered;
+	  }
+      }
+      
+      return( queue );
+    },
+    
+    clearQ: function( concept, ids ){
+      
+      	var current = this.check( ':current', concept || false );
+	var diffs = this.check( ':diff', concept || false );
+
+	if( !ids )
+	    current = diffs = {};
+	else
+	{
+	    if( notArray = ($.type(ids) !== "array") )
+	      ids = [ ids ];
+
+	    for( var i = 0; i < ids.length; i++ )
+	    {
+		delete current[ ids[i] ];
+		delete diffs[ ids[i] ];
+	    }
+	}
+
+	this.store( ':current', concept, current );
+	this.store( ':diff', concept, diffs );
+    },
+
+    commit: function( concept, ids, callback ){
+      
+	var queue = this.prepareQ( 'diff', concept, ids );
+
+	this.sync( queue, concept || false, callback );
+    },
+    
+    sync: function( queue, concept, callback ){
+
+	if( !queue || $.isEmptyObject( queue ) )
+	    return;
+
+	if( concept )
+	{
+	  var helper = {}; 
+	  helper[concept] = queue; 
+	  queue = helper;
+	}
+
+	var data = {}, URIs = {};
+
+	for( var concept in queue )
+	    for( var id in queue[concept] )
+	    {
+		data[ this.URI( concept, id ) ] = queue[concept][id];
+		URIs[ this.URI( concept, id ) ] = { concept: concept, id: id };
+	    }
+
+	if( $.isEmptyObject( data ) )
+	    return;
+
+	this.dispatch( "Sync", data, function( data, status, jqXHR ){
+
+// 	    switch( status )
+// 	    {
+// 	      case "error":
+// 	      case "parsererror":
+// 		return DataLayer.rollback( concept, URI );
+// 	      case "success":
+// 		return DataLayer.commit();
+// 	      case "timeout":
+// 	      case "notmodified":
+// 	    }
+
+	    for( var URI in data )
+	    { 
+		var parsed = DataLayer.parseURI( URI ),
+   
+		concept = parsed[1], id = parsed[3];
+
+		if( $.type(data[URI]) === "string" )
+		{
+		  //TODO:threat the exception thrown
+		  DataLayer.rollback( concept, id );
+		  delete URIs[ URI ];
+		  continue;
+		}
+
+		if( data[URI] === false ){
+		  DataLayer.remove( concept, id, false );
+		  continue;
+		}
+
+		if( id !== data[URI].id )
+		  DataLayer.move( concept, id, data[URI].id );
+		
+		DataLayer.put( concept, id, data[URI], false );
+	    }
+	    
+	    for( var URI in URIs )
+		 DataLayer.clearQ( URIs[URI].concept, URIs[URI].id );
+	    
+	    if( callback )
+		callback();
+
+	}, true );
+
+    },
+    
+    receive: function( concept, data ){
+      
+      this.put( concept, data, false );
+      
+//       if( typeof data.URI === "undefined" )
+// 	{
+// 	    URI = this.add( concept, data, oneSide );
+// 	}
+// 	else if( data.URI === false )
+// 	{
+// 	    status = this.remove( concept, URI, oneSide );
+// 	}
+// 	else
+// 	{
+// 	    status = this.set( concept, URI, data, oneSide );
+// 	}
+// 
+// 	if( URI && data.URI && URI !== data.URI )
+// 	    this.move( concept, URI, data.URI );
+	
+      
+    },
+
+    
+
+    merge: function( concept, current, data ){
+
+	return $.extend( current, data );
+
+    },
+ 
+    diff: function( base, toDiff ){
+
+// 	var df = toDiff;
+//       
+// 	for( var key in base ){
+// 
+// 	    if( base[key] === df[key] )
+// 		delete df[key];
+// 
+// 	    
+// 	}
+	if( toDiff === false )
+	    return( false );
+
+	var toDiff = $.extend( {}, toDiff );
+
+	if( isGeneratedId.test( toDiff.id ) )
+	    delete toDiff['id'];
+
+	return( toDiff );
+
+    },
+    
+    links: function( concept, reverse ){
+
+	if( !this.links[ concept ] )
+	{
+	    var result = this.dispatch( "links", { concept: concept } ) || false;
+
+	    if( !result )
+		return( false );
+
+	    this.concepts[ concept ] = $.extend( this.concepts[ concept ] || {}, 
+						 result['concepts'] || {} );
+
+	    this.links[ concept ] =  result['links'] || {};
+	}
+
+	if( reverse )
+	{
+	    var reverted = {}, llinks = this.links[ concept ];
+    
+	    for( var key in llinks )
+		reverted[ llinks[key] ] = key;
+
+	    return( reverted );
+	}
+
+	return( this.links[ concept ] );
+
+    },
+    
+    isConcept: function( concept, attr ){
+      
+	if( typeof this.concepts[concept] === "undefined" )
+	{
+	    this.links( concept );
+	}
+
+	return !!this.concepts[ concept ][ attr ];
+    },
+    
+    URI: function( concept, URI, context ){
+      
+	if( res = internalUrl.exec( concept ) )
+	    concept = res[1];
+	
+	context = context ? "(" + context + ")" : "";
+      
+	if( URI )
+	    return( concept + context + "://" + URI );
+	else
+	    return( concept );
+      
+    },
+    
+    parseURI: function( URI ){
+
+	return internalUri.exec( URI ) || false;
+
+    },
+    
+    
+   
+    
+    generateId: function( concept ){
+      
+	var newId = this.counter + "(javascript)";
+      
+	this.store( ":counter", (this.counter++) + "" );
+	
+	return( newId );
+    },
+   
+
+   
+
+    get: function( concept, /*URI, */filter, oneSide ){
+
+	///////////////////////////// normalize ////////////////////////////////
+	if( arguments.length == 2 && $.type(filter) === "boolean" )
+	{
+	    oneSide = filter;
+	    filter = false;
+	}
+	
+	var encoder = false, id = false, bothSides = (typeof oneSide === 'undefined'), res;
+	
+	if( $.type(filter) === "string" )
+	{
+	    id = filter;
+	    filter = false;
+	}
+
+	filter = filter || false;
+
+	if( !concept )
+	    return( false );
+
+	if( res = internalUrl.exec( concept ) )
+	{
+	    encoder = concept;
+	    concept = res[1];
+
+	    if( filter )
+		filter = this.criteria( encoder, filter );
+	}
+	
+	if ( $.type(filter) === "array" )
+	{
+	    filter = { filter: filter, criteria: false };
+	}
+	
+	//////////////////////////////////////////////////////////////////////////
+	
+	var result = false;
+
+	if( bothSides || !oneSide )
+	    result = this.check( concept, id || filter );
+
+	if( !result && (bothSides || oneSide) )
+	{
+	    result = this.request( concept, id || filter.filter, filter.criteria );
+
+	    if( result && bothSides )
+	    {
+	      var newResult = [];
+	    
+	      for( var i = 0; i < result.length; i++ )
+		  newResult[i] = $.extend( {}, result[i] );
+
+	      this.put( concept, id, newResult, false );
+	    }
+	}
+
+	if( /*result &&*/ encoder )
+	    result = this.encode( encoder, result, filter ); //TODO: retirar o filtro no mÃ©todo encode
+
+	return( result );
+    },
+    
+    
+    filter: function( base, filter ){
+      
+	var filtered = [];
+      
+// 	var operator = filter.shift();
+      
+	/*for( var key in base )
+	{
+	    switch( operator )
+	    {
+		case 'AND':
+		  for( var i = 0, current = true; i < filter.length && current; i++ )
+		    current = this.compare( '&', current, this.compare( base[key], filter[i] ) );
+		break;
+		case 'OR':
+		  for( var i = 0, current = false; i < filter.length && !current; i++ )
+		    current = this.compare( '|', current, this.compare( base[key], filter[i] ) );
+		break;
+		case 'IN':
+		  for( var i = 0, current = false; i < filter[1].length && !current; i++ )
+		    current = this.compare( '|', current, this.compare( base[key], [ '=', filter[0], filter[1][i] ] ) );
+		break;
+		default : current = this.compare( operator, base[key], );
+	    }
+	*/    
+// 	    if( !noGroup )
+// 		for( var i = 0, current = original; i < filter.length && ( current === original ); i++ )
+// 		    current = this.compare( operator, current, this.compare( base[key], filter[i] ) );
+
+// 	    if( current )
+// 		filtered[ filtered.length ] = key;
+// 	}
+
+	return( filtered );
+    },
+    
+    compare: function( operator, base, test ){
+      
+      switch( operator )
+      {
+	  case '*':  return  RegExp( "*" + base + "*" ).test( test );
+	  case '^':  return  RegExp( "^" + base +  "" ).test( test );
+	  case '$':  return  RegExp( ""  + base + "$" ).test( test );
+
+	  case '&': return ( base && test );
+	  case '|': return ( base || test );
+
+	  case '=':  return ( base == test );
+	  case '<=': return ( base <= test );
+	  case '>=': return ( base >= test );
+	  case '>':  return ( base <  test );
+	  case '<':  return ( base >  test );
+      }
+      
+    },
+    
+
+    check: function( namespace, keys ){
+
+	if( !namespace )
+	    return( false );
+
+	var result = $.storage.get( namespace );
+
+	if( !keys || !result )
+	  return( result || false );
+
+	if( notArray = $.type(keys) === "string" )
+	    keys = [ keys ];
+	else if( $.type(keys) !== "array" )
+	    keys = this.filter( result, keys.filter );
+
+	var res = [];
+
+	for( var i = 0; i < keys.length; i++ )
+	    res[ res.length ] = result[keys[i]];
+
+	return( notArray ? res[0] || false : res.length ? res : false );
+    },
+
+    store: function( namespace, key, data ){
+
+	if( !data )
+	  return $.storage.set( namespace, key );
+
+	var res = this.check( namespace ) || {};
+
+	res[key] = data;
+
+	return $.storage.set( namespace, res );
+    },
+
+    del: function( namespace, key ){
+      
+	if( !key )
+	  return $.storage.del( namespace );
+
+	var res = this.check( namespace ) || {};
+
+	delete res[key];
+
+	return $.storage.set( namespace, res );
+      
+    },
+    
+     move: function( concept, oldId, newId ){
+
+	this.store( concept, newId, this.check( concept, oldId ) );
+
+	this.del( concept, oldId );
+    },
+    
+
+    
+    
+    
+    request: function( concept, filter, criteria ){
+
+      var id = false, criteria = criteria || {};
+
+      if( $.type(filter) === "string" )
+      {
+	  id = filter;
+	  filter = false;
+      }
+
+      return this.dispatch( "request", { 
+
+	  concept: concept || '',
+	  id: id || '',
+	  filter: filter || '',
+	  criteria: criteria || '',
+	  service: criteria.service || '',
+	  properties: criteria.properties || ''
+
+      } );
+    },
+
+    
+    //         sync: function( data, callback ){
+// 
+// 	if( !data || $.isEmptyObject( data ) )
+// 	    return;
+//       
+// 	this.send( "Sync", data, function( data, status, jqXHR ){
+// 
+// // 	    switch( status )
+// // 	    {
+// // 	      case "error":
+// // 	      case "parsererror":
+// // 		return DataLayer.rollback( concept, URI );
+// // 	      case "success":
+// // 		return DataLayer.commit();
+// // 	      case "timeout":
+// // 	      case "notmodified":
+// // 	    }
+// 
+// 	    if( callback )
+// 	    {
+// 		var result = callback( data, status, jqXHR );
+// 
+// 		if( result === false )
+// 		    return;
+// 		else if( typeof result != "undefined" )
+// 		    data = result;
+// 	    }
+// 
+// 	    for( var URI in data )
+// 	    { 
+// 		var parsed = DataLayer.parseURI( URI ), 
+//    
+// 		concept = parsed[1], /*URI = parsed[3],*/
+// 
+// 		links = DataLayer.links( concept );
+// 
+// 		for( var linkName in links )
+// 		{
+// 		    var subURI = data[URI][linkName];
+// 
+// 		    if( subURI && data[subURI] )
+// 		    {
+// 			data[URI][linkName] = DataLayer.put( linkName, subURI, data[subURI], false );
+// 
+// 			delete( data[subURI] );
+// 		    }
+// 		}
+// 
+// 		DataLayer.put( concept, URI, data[URI], false );
+// 	    }
+// 	}, true );
+// 
+//     },
+
+//     report: function( concept, URI, data, sync )
+//     {
+// 	var current = this.dequeue( 'current', concept, URI );
+// 
+// 	if( !current )
+// 	    this.enqueue( 'current', concept, URI, ( current = this.check( concept, URI ) || {} ) );
+// 
+// 	var diff = this.diff( current, data );
+// 
+// 	if( !diff )
+// 	    this.dequeue( 'current', concept, URI, true );
+// 	else
+// 	    this.enqueue( 'diff', concept, URI, diff );
+// 	
+// 	if( sync )
+// 	    this.commit( concept, URI, function(){ 
+// 
+// 		DataLayer.set( concept, URI, data, false );
+// 
+// 	    });
+//     },
+    
+//     enqueue: function( type, concept, URI, obj ){
+//       
+// 	//var newURI = this.URI( concept, URI );
+// 	
+// 	if( !this.queue[type] )
+// 	    this.queue[type] = {};
+// 
+// 	if( !this.queue['all'] )
+// 	    this.queue['all'] = {};
+// 	
+// 	if( !this.queue[type][concept] )
+// 	    this.queue[type][concept] = {};
+// 	
+// 	if( !this.queue['all'][type] )
+// 	    this.queue['all'][type] = {};
+// 	
+// 	if( !this.queue['all'][type][/*new*/URI] )
+// 	    this.queue[type][concept][URI] = this.queue['all'][type][/*new*/URI] = obj;
+// 
+// 	this.store( ':queue', this.queue );
+//     },
+//     
+//     dequeue: function( type, concept, URI, remove ){
+//       
+//       ///////////////////////////// normalize ////////////////////////////////
+// 	if( arguments.length < 4 && $.type(URI) === 'boolean' )
+// 	{
+// 	    remove = URI;
+// 	    URI = false;
+// 	}
+// 	if( arguments.length < 3 && $.type(concept) === 'boolean' )
+// 	{
+// 	    remove = concept;
+// 	    concept = false;
+// 	}
+//       //////////////////////////////////////////////////////////////////////////
+//       
+// 	if( !this.queue[type] || !this.queue['all'] )
+// 	    return( false );
+// 	
+// 	if( !concept )
+// 	{
+// 	    var obj = this.queue['all'][type];
+// 	    
+// 	    if( remove )
+// 	    {
+// 		delete this.queue['all'][type];
+// 		delete this.queue[type];
+// 	    }
+// 
+// 	    this.store( ':queue', this.queue );
+// 	    return( obj );
+// 	}
+// 
+// 	if( !this.queue[type][concept] )
+// 	    return( false );
+// 	
+// 	if( !URI )
+// 	{
+// 	    var obj = this.queue[type][concept];
+// 
+// 	    if( remove )
+// 	    {
+// 		var URIs = this.queue[type][concept];
+// 
+// 		for( var subURI in URIs )
+// 		     delete this.queue['all'][type][subURI];
+// 
+// 		delete this.queue[type][concept];
+// 	    }
+// 
+// 	    this.store( ':queue', this.queue );
+// 	    return( obj );
+// 	}
+// 
+// // 	var newURI = URI ? this.URI( concept, URI ) : concept;
+// 	
+// 	var obj = this.queue['all'][type][/*new*/URI];
+//   
+// 	if( remove )
+// 	{
+// 	    delete this.queue['all'][type][/*new*/URI];
+// 	    delete this.queue[type][concept][URI];
+// 	}
+// 
+// 	this.store( ':queue', this.queue );
+// 	return( obj );
+//     },
+    
+           //TODO: definir a 'usage' desta funÃ§Ã£o e refatora-la
+//     set: function( concept, filter, data, oneSide ){
+// 
+// 	///////////////////////////// normalize ////////////////////////////////
+// 	if( arguments.length == 2 )
+// 	{
+// 	    data = filter;
+// 	    filter = false;
+// 	}
+// 	if( $.type(data) === "boolean" )
+// 	{
+// 	    oneSide = data;
+// 	    data = filter;
+// 	    filter = false;
+// 	}
+// 	
+// 	if( !concept || !data )
+// 	    return( false );
+// 
+// 	var decoder = "", URI = false, bothSides = (typeof oneSide === "undefined");
+// 	
+// 	if( $.type(filter) === "string" )
+// 	{
+// 	    URI = filter;
+// 	    filter = false;
+// 	}
+// 
+// 	if( res = internalUrl.exec( concept ) )
+// 	{
+// 	    //TODO: verificar se a decodificaÃ§ao deve ser feita em cada item do array
+// 	    data = this.decode( concept, data );
+// 	    concept = res[1];
+// 	    decoder = res[2];
+// 	}
+// 	///////////////////////////////////////////////////////////////////////////
+// 
+// 	if( bothSides || oneSide )
+// 	    this.report( concept, URI, data, !bothSides );
+// 
+// 	if( bothSides || !oneSide )
+// 	{
+// 	    if( URI )
+// 	    {
+// 	      var helper = {}; 
+// 	      helper[URI] = data; 
+// 	      data = helper;
+// 	    }
+// 
+// 	    for( var URI in data )
+// 	    {
+// 		var current = this.check( concept, URI ) || {};
+// 
+// 		data[URI] = this.merge( current, data[URI] );
+// 
+// 		this.store( concept, URI, data[URI] );
+// 	    }
+// 
+// 	}
+// 
+// 	this.broadcast( concept, oneSide ? 'client' : 'server' );
+// 
+// 	return( true );
+//     },
+//     put: function( concept, URI, data, oneSide ){
+//       
+//       ///////////////////////////// normalize ////////////////////////////////
+// 	if( $.type(URI) !== "string" && arguments.length < 4 )
+// 	{
+// 	    oneSide = data;
+// 	    data = URI;
+// 	    URI = false;
+// 	}
+//       ////////////////////////////////////////////////////////////////////////
+//       
+//       ////////////////////////////// linkage /////////////////////////////////
+// 	var result = false, links = this.links( concept );
+// 
+// 	for( var link in links )
+// 	{
+// 	    if( data[link] )
+// 	    {
+// 		if( $.isArray( data[link] ) )
+// 		{
+// 		    data[link] = this.put( links[link], data[link].URI, data[link], oneSide );
+// 		}
+// 		else if( $.isObject( data[link] ) )
+// 		{
+// 		    $.each( data[link], function( i, el ){
+// 
+// 			  data[link][i] = this.put( links[link], el.URI, el, oneSide );
+// 
+// 		    });
+// 		}
+// 	    }
+// 	}
+//       //////////////////////////////////////////////////////////////////////////
+//     
+// 	if( typeof data.URI === "undefined" )
+// 	{
+// 	    URI = this.add( concept, data, oneSide );
+// 	}
+// 	else if( data.URI === false )
+// 	{
+// 	    status = this.remove( concept, URI, oneSide );
+// 	}
+// 	else
+// 	{
+// 	    status = this.set( concept, URI, data, oneSide );
+// 	}
+// 
+// 	if( URI && data.URI && URI !== data.URI )
+// 	    this.move( concept, URI, data.URI );
+// 
+// 	return( data.URI || URI );
+// 
+//     },
+    
+    //     add: function( concept, data, oneSide ){
+//       
+//       ///////////////////////////// normalize ////////////////////////////////
+// 	if( !concept || !data )
+// 	    return( false );
+// 
+// 	if( res = internalUrl.exec( concept ) )
+// 	{
+// 	    //TODO: verificar se a decodificaÃ¯Â¿ÅÃ¯Â¿Åo deve ser feita em cada item do array
+// 	    data = this.decode( concept, data );
+// 	    concept = res[1];
+// 	    decoder = res[2];
+// 	}
+// 
+// 	var bothSides = (typeof oneSide === "undefined"), uris = [];
+// 
+// 	if( notArray = $.type(data) !== "array" )
+// 	    data = [ data ];
+//       //////////////////////////////////////////////////////////////////////////
+// 
+// 	for( var i = 0; i < data.length; i++ )
+// 	{
+// 	    var URI = uris[i] = this.generateURI( concept );
+// 
+// 	    this.set( concept, URI, data[i], oneSide );
+// 	}
+// 
+// 	return( notArray ? uris[0] : uris );
+//     },
+//      put: function( concept, data ){
+// 
+// 	var decoder = "";
+// 
+// 	if( res = internalUrl.exec( concept ) )
+// 	{
+// 	    data = this.decode( concept, data );
+// 	    concept = res[1];
+// 	    decoder = res[2];
+// 	}
+// 
+// 	var New = [], Update = [], uris = [];
+// 
+// 	if( notArray = $.type(data) !== "array" )
+// 	    data = [ data ];
+// 	
+// 	for( var i = 0; i < data.length; i++ )
+// 	{
+// 	    if( !data[i].URI )
+// 	    {
+// 		uris[ uris.length ] = data[i].URI = this.create( concept, data[i] );
+// 		New[ New.length ] = data[i];
+// 		continue;
+// 	    }
+// 
+// 	    for( var key in data[i] )
+// 		if( klass = this.isReference( concept, key, data[i][key] ) )
+// 		      data[i][key] = this.put( klass + decoder, data[i][key] );
+// 
+// 	    Update[ Update.length ] = this.update( concept, data[i].URI, data[i] );
+// 	}
+// 
+// 	this.report( concept, { "created": New, "updated": Update });
+// 
+// 	return( notArray ? uris[0] : uris );
+//     },
+//     merge: function( concept, current, data ){
+// 
+// 	current = current || {};
+// 
+// 	for( var key in data )
+// 	    current[key] = (klass = this.isReference( concept, key, data[key] )) ? 
+// 			   this.merge( klass, current[key], data[key] ) : data[key];
+// 
+// 	return( current );
+//     },
+// 
+//     isReference: function( concept, key, value ){
+// 
+//       return( ($.type(value) === "object" || 
+// 	       $.type(value) === "array" )? this.links[concept][key] : false );
+// 
+//     },
+//     
+//     set: function( concept, data, URI, mergeable ){
+// 	
+// 	if( URI )
+// 	{
+// 	    var res = this.get( concept, true ) || {};
+// 	    
+// 	    if( mergeable )
+// 		data = this.merge( res[URI] || {}, data );
+// 
+// 	    res[URI] = data;
+// 
+// 	    data = res;
+// 	}
+// 
+// 	return this.store( concept, data );
+//     },    
+// 
+//     create: function( concept, data ){
+// 
+//       if( notArray = ($.type(data) !== "array") )
+// 	    data = [ data ];
+// 
+//       var uris = [];
+// 
+//       for( var i = 0; i < data.length; i++ )
+//       {
+// 	  uris[ uris.length ] = data[i].URI = "javascript://" + (this.counter + i);
+// 
+// 	  this.set( concept, data[i], data[i].URI );
+//       }
+//  
+//       this.set( ":counter", (this.counter += data.length) );
+// 
+//       return notArray ? uris[0] : uris;
+//     },
+// 
+//     update: function( concept, URI, data )
+//     {
+// 	var target = this.check( concept, URI ) || {};
+// 
+// 	target = this.merge( concept, target, data );
+// 
+// 	if( target.URI !== URI )
+// 	    this.remove( concept, URI );
+// 
+// 	this.set( concept, target, target.URI );
+// 
+// 	return( target );
+//     },
+// 
+//     remove: function( concept, URI ){
+// 
+// 	if( !URI )
+// 	    return $.storage.del( concept );
+// 
+// 	var res = this.check( concept );
+// 
+// 	delete res[URI];
+// 	
+// 	this.set( concept, res );
+//     },
+// 
+//     del: function( concept, URI ){
+// 
+// 	this.remove( concept, URI );
+// 
+// 	this.report( concept, { "deleted": { 'URI': URI } });
+//     },
+// 
+//     report: function( concept, changes ){
+// 
+//       this.broadcast( concept, changes.created, changes.updated, changes.deleted );
+// 
+// 	if( changes.created )
+// 	    this.sync( concept, changes.created, 'create' );
+// 	if( changes.updated )
+// 	    this.sync( concept, changes.updated, 'update' );
+// 	if( changes.deleted )
+// 	    this.sync( concept, changes.deleted, 'delete' );
+// 
+//     },
+// 
+// 
+//    sync: function( concept, data, type ){
+// 
+// 	if( $.type(data) !== "array" )
+// 	    data = [ data ];
+// 
+// 	$.each( data, function( i, el ){
+// 
+// 	   DataLayer.send( concept, el, type );
+// 
+// 	});
+// 
+//     },
+//     
+//     
+//     
+//     
+// 
+//     request: function( concept, URI, filter ){
+// 
+// //       if( startsDoubleDot.test(concept) )
+// // 	  return( false );
+// 
+//       filter = filter || {};
+// 
+//       if( URI )
+// 	filter.URI = URI;
+// 
+//       return this.send( concept, filter, "read", true );
+// 
+//     },
+// 
+//     send: function( concept, data, type, wait ){
+// 
+//       switch( type )
+//       {
+// 	case "create": type = "POST"; break;
+// 	case "update": type = "PUT"; break;
+// 	case "delete": type = "DELETE"; break;
+// 	case "read": type = "GET"; break;
+//       }
+// 
+//       var url = this.basePath + concept;
+// 
+//       var result = [], notArray = false;
+// 
+// //      alert( data.URI );
+// 
+//       if( data.URI && data.URI.indexOf("javascript://") !== 0 )
+// 	  url += "/" + data.URI;
+// 
+//       var callback = function( dt, textStatus, jqXHR ){
+// 
+// 	    if( notArray = (!$.isArray( dt )) )
+// 		dt = [ dt ];
+// 
+// 	    $.each( dt, function( i, el ){
+// 
+// 		if( !el || !el.URI )
+// 		    return;
+// 
+// 		if( data.URI )
+// 		    el = DataLayer.update( concept, data.URI, el );
+// 		else
+// 		    DataLayer.set( concept, el, el.URI );
+// 
+// 		result[ result.length ] = el;
+// 		DataLayer.broadcast( concept );
+// 	  });
+//       };
+// 
+//       $.ajax({
+// 	      'async': ( !wait ),
+// 	      'url': url,
+// 	      'type': type,
+// 	      'success': callback,
+// 	      'dataType': 'json',
+// 	      'data': data/*,
+// 	      'processData': false*/
+// 	  });
+// 
+//       return( notArray ? result[0] || false : result );
+//     },
+    
+    
+    generateURI: function( concept ){
+      
+	return this.URI( concept, this.generateId( concept ), "javascript" );
+
+    },
+    
+
+    broadcast: function( concept, status, diff ){
+
+	if( this.listeners[ concept ] )
+	    for( var i = 0; 
+		i < this.listeners[ concept ].length; 
+		this.listeners[ concept ][ i++ ]( status, diff ) );
+    },
+
+    listen: function( concept, listener ){
+
+	this.register( "listeners", concept, listener );
+
+    },
+
+    codec: function( concept, namespace, codec ){
+
+	if( codec.encoder )
+	    this.encoder( concept, namespace, codec.encoder );
+	if( codec.decoder )
+	    this.decoder( concept, namespace, codec.decoder );
+	if( codec.criteria )
+	    this.register( "criterias", concept + ":" + namespace, codec.criteria );
+
+    },
+
+    encoder: function( concept, namespace, encoder ){
+
+	this.register( "encoders", concept + ":" + namespace, encoder );
+
+    },
+
+    encode: function( encoder, data, filter ){
+
+	if( this.encoders[ encoder ] )
+	    for( var i = 0;
+		i < this.encoders[ encoder ].length;
+		data = this.encoders[ encoder ][ i++ ]( data, filter ) );
+
+	return( data );
+    },
+
+    decoder: function( concept, namespace, decoder ){
+
+	this.register( "decoders", concept + ":" + namespace, decoder );
+
+    },
+
+    decode: function( decoder, data ){
+
+	if( this.decoders[ decoder ] )
+	    for( var i = 0;
+		i < this.decoders[ decoder ].length;
+		data = this.decoders[ decoder ][ i++ ]( data ) );
+
+	return( data );
+    },
+
+    criteria: function( codec, filter ){
+
+	if( this.criterias[ codec ] )
+	    for( var i = 0;
+		i < this.criterias[ codec ].length;
+		filter = this.criterias[ codec ][ i++ ]( filter ) );
+
+	return( filter );
+
+    },
+
+    register: function( kind, concept, deployable ){
+
+      if( !this[ kind ][ concept ] )
+	    this[ kind ][ concept ] = [];
+
+	this[ kind ][ concept ][ this[ kind ][ concept ].length ] = deployable;
+
+    },
+    
+    start: function(){
+
+	var timer = function(){
+
+	      setTimeout( timer, 1 );
+
+	      var now = parseInt( $.now() / 1000 );
+
+	      var tasks = DataLayer.tasks[ now ];
+
+	      if( !tasks ) return;
+
+	      for( var i = 0; i < tasks.length; i++ )
+	      {
+		  var result = tasks[i].task( now );
+
+		  DataLayer.schedule( tasks[i].task, tasks[i].factor );
+	      }
+      
+	      delete DataLayer.tasks[ now ];
+	};
+
+	setTimeout( timer, 1 );
+    },
+    
+    schedule: function( task, time ){
+
+	time = time || 1;
+	
+	var index = parseInt( $.now() / 1000 ) + time;
+
+	if( !this.tasks[ index ] )
+	    this.tasks[ index ] = [];
+
+	this.tasks[ index ][ this.tasks[ index ].length ] = { task: task, factor: time };
+    },
+    
+    poll: function( concept, time ){
+      
+      this.schedule( function( now ){
+  
+	  DataLayer.commit( concept );
+
+	}, time || 5 );
+    },
+    
+    init: function(){
+      
+	this.counter = parseInt( this.get( ":counter", false ) ) || 0;
+
+	if( !this.dispatchPath )
+	    this.dispatchPath = "../../";
+
+	if( !this.templatePath )
+	    this.templatePath = "";
+
+	if( !this.basePath )
+	    this.basePath = this.dispatchPath + "REST.php?q=";
+
+	this.start();
+    }
+}
+
+DataLayer.init();
+
+// setTimeout(function(){
+//  
+//     
+// 
+// }, 1000 );
+
+// var DataLayer = {
+// 
+//     get: function( concept, filter ){
+// 
+// 	var data = $.storage.get( concept );
+// 
+// 	if( !filter )
+// 	    return( data );
+// 
+// 	return this.filter( data, filter );
+//     },
+//     
+//     filter:function( data, filter ){
+//       
+// 	if( filter.charAt )
+// 	    filter = { URI: filter };
+// 	
+// 	var filtered = [];
+// 
+// 	$.each(data, function(i, obj){
+// 	  
+// 	    for( var attr in filter )
+// 		if( filter[attr] !== obj[attr] )
+// 		    return( true );
+// 
+// 	    filtered[i] = obj;
+// 	});
+// 
+// 	return( filtered );
+//     },
+// 
+//     find: function( concept, filter, callback ){
+// 
+// 	var data = this.get( concept, filter ); 
+// 
+// 	if( data )
+// 	    return callback( data );
+// 
+// 	//TODO: register callback like a weak listener
+// 
+// // 	$.ajax({ 
+// // 	      type: 'GET',
+// // 	      data: $.param( filter ),
+// // 	      success: callback,  
+// // 	      url: BASE_PATH + filter.URI || concept
+// // 	});
+// 	this.report( concept, filter );
+//     },
+// 
+//     put: function( concept, data, filter ){
+// 
+// 	var beforeDiff = this.store( concept, $.extend({},data) );
+// 
+// 	this.report( concept, data, filter, beforeDiff );
+//     },
+//     
+//     
+//     /*var data = {
+// 			startTime: $.now(),
+// 			endTime: $.now() + 1800000,
+// 			summary: "meu querido evento",
+// 			description: "desc do evento",
+// 			location: "prognus software livre",
+// 			class: 1,
+// 			calendar: 1,
+// 			category: 1,
+// 			participants: [ { 
+// 					   user: { isExternal: true, mail: "user7@prognus.org", name: "user7" }
+// 				      },{ 
+// 					   user: "1003"
+// 				      } ]
+// 
+// 		  };*/
+//     
+// 
+//     merge:function( data, target ){
+//       
+// 	var diff = { New: {}, Update:{}, Delete: {} };
+//       
+// 	for( var key in data )
+// 	{
+// 	    if( !target[ key ] )
+// 		diff.New[ key ] = target[ key ] = data[ key ];
+// 
+// 	    
+// 	  
+// 	}
+//       
+//     }
+// 
+//     store: function( concept, data, filter ){
+// 
+// 	if( !data.spline )
+// 	    data = [ data ];
+// 
+// 	var target = $.storage.get( concept );
+// 	
+// 	var diff = { New: {}, Update:{}, Delete: {} };
+// 
+// 	for( var i = 0; i < data.length; i++ )
+// 	{
+// 	    if( data[i].URI && target[ data[i].URI ] )
+// 	    {
+// 		diff.Update[ data[i].URI ] = this.merge( target[ data[i].URI ], data[i] );
+// 	    }
+// 	    else
+// 	    {
+// 		diff.New[] = data[i];
+// 	    }
+// 	}
+// 
+// 	
+// 
+// 	this.broadcast( concept, data );
+// 
+// 	if( filter )
+// 	    target = this.filter( target, filter );
+// 
+// 	if( target )
+// 	    data = $.extend( target, data );
+// 
+// 	$.storage.set( concept, data );
+// 
+// // 	return;
+//     },
+//     
+//     set: function( concept, data, filter ){
+// 
+//       
+// 
+//     },
+// 
+//     post: function( concept, data, filter, isNew ){
+// 
+// 	var callback = function(  ){ DataLayer.store( concept, data, filter ) };
+// 
+// 	//TODO: register callback like a weak listener
+// 
+// 	this.report( concept, data, filter, isNew );
+//     },
+//     
+//     report: function( concept, filter, postData, isNew ){
+//       
+// 	$.ajax({ 
+// 		type: postData ? isNew ? 'POST' : 'PUT' : 'GET',
+// 		data: postData || $.param( filter ),
+// 		success: function( data ){ DataLayer.broadcast( concept ) },
+// 		url: BASE_PATH + filter.URI || concept
+// 	  });
+//     },
+//     
+//     del:function( concept, filter ){
+// 
+//       
+// 
+//     }
+//     
+//     broadcast: function( concept, data ){
+// 
+// 	
+// 
+//     },
+// 
+//     pool: function(){
+//       
+//     },
+//
+//     refresh: function(){
+//       
+//     }
+// };
+
+// 
+// DataLayer = {
+//   
+//     get: function( concept, filter ){
+// 
+// 	var data = $.storage.get( concept );
+// 
+// 	if( !filter )
+// 	    return( data );
+// 
+// 	if( filter.charAt )
+// 	    filter = { URI: filter };
+// 	
+// 	var filtered = [];
+// 
+// 	$.each(data, function(i, obj){
+// 	  
+// 	    for( var attr in filter )
+// 		if( filter[attr] !== obj[attr] )
+// 		    return( true );
+// 
+// 	    filtered[i] = obj;
+// 	});
+// 
+// 	return( filtered );
+//     },
+// 
+//     find: function( concept, filter, callback ){
+// 
+// 	var data = this.get( concept, filter ); 
+// 
+// 	if( data )
+// 	    return callback( data );
+// 
+// 	 $.ajax({ 
+// 	      type: 'GET', 
+// 	      data: $.param( filter ),
+// 	      success: callback,  
+// 	      url: filter.URI || concept
+// 	});
+//     },
+// 
+//     put: function( concept, data, filter ){
+// 
+// 	var target = this.get( concept, filter );
+// 
+// 	if( target )
+// 	    data = $.extend( target, data );
+//       
+// 	$.storage.set( concept, data );
+// 	
+// 	//diff
+//     },
+//     
+//     post: function( concept, data, filter ){
+// 
+// 	
+// 
+//     },
+//     
+//     pool: function(){
+//       
+//     },
+//     
+//     queue: function(){
+//       
+//     },
+//     
+//     dequeue: function(){
+//       
+//     },
+//     
+//     refresh: function(){
+//       
+//     }
+// }
+
+// var DataLayer = {
+  
+//       cache: {},
+  
+//       get: function( concept, location ){
+	
+	   /* if( location )
+	    {*/
+// 		var schema = $.data( this.cache, concept + ':schema' );
+// 		var uri = [];
+// 
+// 		$.each( schema, function( i, addrs ){
+// 		      uri[ uri.length ] = location[addrs];
+// 		});
+
+		/*var filter = [], result = false;
+
+		while( !(result = $.data( this.cache, uri.join( '.' ) )) || !(uri = uri.join('.')) )
+		  filter[ filter.length ] = uri.pop();
+  
+		if( !filter.length )
+		{
+		    var indexes = $.data( this.cache, uri + ':indexes' );
+
+		    if( indexes )
+			Array.prototype.concat.apply( result, indexes );
+		    
+		    return( result );
+		}
+
+		for( var i = 0; i < result.length; i++ )
+		{
+		    
+		}
+
+		if( result.length )
+		    return( result );
+	    }*/
+
+// 	    var data = $.data( this.cache, concept );
+
+// 	    if( !data )
+// 		$.ajax( );
+
+// 	    return( data );
+//       },
+      
+//       data: function(){
+// 
+// 	  
+// 
+//       }
+//       
+//       search: function( concept, filter ){
+// 
+// 	  var schema = $.data( this.cache, concept + ':schema' );
+// 	  var uri = [];
+// 
+// 	  $.each( schema, function( i, addrs ){
+// 		uri[ uri.length ] = location[addrs];
+// 	  });
+//       }
+//       put: function( concept, data, location ){
+
+// 	    if( location )
+// 	    {
+// 		var schema = $.data( this.cache, concept + ':schema');
+// 		var uri = [];
+
+// 		$.each( schema, function( i, addrs ){
+// 		      uri[ uri.length ] = location[addrs];
+// 		});
+
+// 		var result = false, filter = [];
+
+// 		while( !(result = $.data( this.cache, uri.join('.')) )
+// 		    filter[ filter.length ] = uri.pop();
+
+// 		$.data( this.cache, '
+
+// 	    }
+
+// 		var model = $.storage.get( concept );
+// 
+// 		$.each( model, function( i, o ){
+// 		    $.each( location, function( ii, attr ){
+// 			 if( o[ii] === attr )
+// 			    return( false );
+// 		    }); 
+// 		});
+
+// 	    return $.data( this.cache, concept, data );
+
+//       },
+//       del: function( concept, location ){
+// 
+// 	    if( location )
+// 	    {
+// 		var schema = $.data( this.cache, 'concepts', concept );
+// 		var uri = [];
+// 
+// 		$.each( schema, function( i, addrs ){
+// 		      uri[ uri.length ] = location[addrs];
+// 		});
+// 
+// 		concept = uri.join( '.' );
+
+// 		var model = $.storage.get( concept );
+// 
+// 		$.each( model, function( i, o ){
+// 		    $.each( location, function( ii, attr ){
+// 			 if( o[ii] === attr )
+// 			    return( false );
+// 		    }); 
+// 		});
+// 	    }
+// 	    
+// 	
+// 	    $.removeData( this.cache, concept );
+//       }
+// }
Index: /trunk/prototype/app/plugins/ejs/ejs_production.js
===================================================================
--- /trunk/prototype/app/plugins/ejs/ejs_production.js	(revision 5283)
+++ /trunk/prototype/app/plugins/ejs/ejs_production.js	(revision 5283)
@@ -0,0 +1,1 @@
+(function(){var rsplit=function(string,regex){var result=regex.exec(string),retArr=new Array(),first_idx,last_idx,first_bit;while(result!=null){first_idx=result.index;last_idx=regex.lastIndex;if((first_idx)!=0){first_bit=string.substring(0,first_idx);retArr.push(string.substring(0,first_idx));string=string.slice(first_idx)}retArr.push(result[0]);string=string.slice(result[0].length);result=regex.exec(string)}if(!string==""){retArr.push(string)}return retArr},chop=function(string){return string.substr(0,string.length-1)},extend=function(d,s){for(var n in s){if(s.hasOwnProperty(n)){d[n]=s[n]}}};EJS=function(options){options=typeof options=="string"?{view:options}:options;this.set_options(options);if(options.precompiled){this.template={};this.template.process=options.precompiled;EJS.update(this.name,this);return }if(options.element){if(typeof options.element=="string"){var name=options.element;options.element=document.getElementById(options.element);if(options.element==null){throw name+"does not exist!"}}if(options.element.value){this.text=options.element.value}else{this.text=options.element.innerHTML}this.name=options.element.id;this.type="["}else{if(options.url){options.url=EJS.endExt(options.url,this.extMatch);this.name=this.name?this.name:options.url;var url=options.url;var template=EJS.get(this.name,this.cache);if(template){return template}if(template==EJS.INVALID_PATH){return null}try{this.text=EJS.request(url+(this.cache?"":"?"+Math.random()))}catch(e){}if(this.text==null){throw ({type:"EJS",message:"There is no template at "+url})}}}var template=new EJS.Compiler(this.text,this.type);template.compile(options,this.name);EJS.update(this.name,this);this.template=template};EJS.prototype={render:function(object,extra_helpers){object=object||{};this._extra_helpers=extra_helpers;var v=new EJS.Helpers(object,extra_helpers||{});return this.template.process.call(object,object,v)},update:function(element,options){if(typeof element=="string"){element=document.getElementById(element)}if(options==null){_template=this;return function(object){EJS.prototype.update.call(_template,element,object)}}if(typeof options=="string"){params={};params.url=options;_template=this;params.onComplete=function(request){var object=eval(request.responseText);EJS.prototype.update.call(_template,element,object)};EJS.ajax_request(params)}else{element.innerHTML=this.render(options)}},out:function(){return this.template.out},set_options:function(options){this.type=options.type||EJS.type;this.cache=options.cache!=null?options.cache:EJS.cache;this.text=options.text||null;this.name=options.name||null;this.ext=options.ext||EJS.ext;this.extMatch=new RegExp(this.ext.replace(/\./,"."))}};EJS.endExt=function(path,match){if(!path){return null}match.lastIndex=0;return path+(match.test(path)?"":this.ext)};EJS.Scanner=function(source,left,right){extend(this,{left_delimiter:left+"%",right_delimiter:"%"+right,double_left:left+"%%",double_right:"%%"+right,left_equal:left+"%=",left_comment:left+"%#"});this.SplitRegexp=left=="["?/(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/:new RegExp("("+this.double_left+")|(%%"+this.double_right+")|("+this.left_equal+")|("+this.left_comment+")|("+this.left_delimiter+")|("+this.right_delimiter+"\n)|("+this.right_delimiter+")|(\n)");this.source=source;this.stag=null;this.lines=0};EJS.Scanner.to_text=function(input){if(input==null||input===undefined){return""}if(input instanceof Date){return input.toDateString()}if(input.toString){return input.toString()}return""};EJS.Scanner.prototype={scan:function(block){scanline=this.scanline;regex=this.SplitRegexp;if(!this.source==""){var source_split=rsplit(this.source,/\n/);for(var i=0;i<source_split.length;i++){var item=source_split[i];this.scanline(item,regex,block)}}},scanline:function(line,regex,block){this.lines++;var line_split=rsplit(line,regex);for(var i=0;i<line_split.length;i++){var token=line_split[i];if(token!=null){try{block(token,this)}catch(e){throw {type:"EJS.Scanner",line:this.lines}}}}}};EJS.Buffer=function(pre_cmd,post_cmd){this.line=new Array();this.script="";this.pre_cmd=pre_cmd;this.post_cmd=post_cmd;for(var i=0;i<this.pre_cmd.length;i++){this.push(pre_cmd[i])}};EJS.Buffer.prototype={push:function(cmd){this.line.push(cmd)},cr:function(){this.script=this.script+this.line.join("; ");this.line=new Array();this.script=this.script+"\n"},close:function(){if(this.line.length>0){for(var i=0;i<this.post_cmd.length;i++){this.push(pre_cmd[i])}this.script=this.script+this.line.join("; ");line=null}}};EJS.Compiler=function(source,left){this.pre_cmd=["var ___ViewO = [];"];this.post_cmd=new Array();this.source=" ";if(source!=null){if(typeof source=="string"){source=source.replace(/\r\n/g,"\n");source=source.replace(/\r/g,"\n");this.source=source}else{if(source.innerHTML){this.source=source.innerHTML}}if(typeof this.source!="string"){this.source=""}}left=left||"<";var right=">";switch(left){case"[":right="]";break;case"<":break;default:throw left+" is not a supported deliminator";break}this.scanner=new EJS.Scanner(this.source,left,right);this.out=""};EJS.Compiler.prototype={compile:function(options,name){options=options||{};this.out="";var put_cmd="___ViewO.push(";var insert_cmd=put_cmd;var buff=new EJS.Buffer(this.pre_cmd,this.post_cmd);var content="";var clean=function(content){content=content.replace(/\\/g,"\\\\");content=content.replace(/\n/g,"\\n");content=content.replace(/"/g,'\\"');return content};this.scanner.scan(function(token,scanner){if(scanner.stag==null){switch(token){case"\n":content=content+"\n";buff.push(put_cmd+'"'+clean(content)+'");');buff.cr();content="";break;case scanner.left_delimiter:case scanner.left_equal:case scanner.left_comment:scanner.stag=token;if(content.length>0){buff.push(put_cmd+'"'+clean(content)+'")')}content="";break;case scanner.double_left:content=content+scanner.left_delimiter;break;default:content=content+token;break}}else{switch(token){case scanner.right_delimiter:switch(scanner.stag){case scanner.left_delimiter:if(content[content.length-1]=="\n"){content=chop(content);buff.push(content);buff.cr()}else{buff.push(content)}break;case scanner.left_equal:buff.push(insert_cmd+"(EJS.Scanner.to_text("+content+")))");break}scanner.stag=null;content="";break;case scanner.double_right:content=content+scanner.right_delimiter;break;default:content=content+token;break}}});if(content.length>0){buff.push(put_cmd+'"'+clean(content)+'")')}buff.close();this.out=buff.script+";";var to_be_evaled="/*"+name+"*/this.process = function(_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) {"+this.out+" return ___ViewO.join('');}}}catch(e){e.lineNumber=null;throw e;}};";try{eval(to_be_evaled)}catch(e){if(typeof JSLINT!="undefined"){JSLINT(this.out);for(var i=0;i<JSLINT.errors.length;i++){var error=JSLINT.errors[i];if(error.reason!="Unnecessary semicolon."){error.line++;var e=new Error();e.lineNumber=error.line;e.message=error.reason;if(options.view){e.fileName=options.view}throw e}}}else{throw e}}}};EJS.config=function(options){EJS.cache=options.cache!=null?options.cache:EJS.cache;EJS.type=options.type!=null?options.type:EJS.type;EJS.ext=options.ext!=null?options.ext:EJS.ext;var templates_directory=EJS.templates_directory||{};EJS.templates_directory=templates_directory;EJS.get=function(path,cache){if(cache==false){return null}if(templates_directory[path]){return templates_directory[path]}return null};EJS.update=function(path,template){if(path==null){return }templates_directory[path]=template};EJS.INVALID_PATH=-1};EJS.config({cache:true,type:"<",ext:".ejs"});EJS.Helpers=function(data,extras){this._data=data;this._extras=extras;extend(this,extras)};EJS.Helpers.prototype={view:function(options,data,helpers){if(!helpers){helpers=this._extras}if(!data){data=this._data}return new EJS(options).render(data,helpers)},to_text:function(input,null_text){if(input==null||input===undefined){return null_text||""}if(input instanceof Date){return input.toDateString()}if(input.toString){return input.toString().replace(/\n/g,"<br />").replace(/''/g,"'")}return""}};EJS.newRequest=function(){var factories=[function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new XMLHttpRequest()},function(){return new ActiveXObject("Microsoft.XMLHTTP")}];for(var i=0;i<factories.length;i++){try{var request=factories[i]();if(request!=null){return request}}catch(e){continue}}};EJS.request=function(path){var request=new EJS.newRequest();request.open("GET",path,false);try{request.send(null)}catch(e){return null}if(request.status==404||request.status==2||(request.status==0&&request.responseText=="")){return null}return request.responseText};EJS.ajax_request=function(params){params.method=(params.method?params.method:"GET");var request=new EJS.newRequest();request.onreadystatechange=function(){if(request.readyState==4){if(request.status==200){params.onComplete(request)}else{params.onComplete(request)}}};request.open(params.method,params.url);request.send(null)}})();EJS.Helpers.prototype.date_tag=function(C,O,A){if(!(O instanceof Date)){O=new Date()}var B=["January","February","March","April","May","June","July","August","September","October","November","December"];var G=[],D=[],P=[];var J=O.getFullYear();var H=O.getMonth();var N=O.getDate();for(var M=J-15;M<J+15;M++){G.push({value:M,text:M})}for(var E=0;E<12;E++){D.push({value:(E),text:B[E]})}for(var I=0;I<31;I++){P.push({value:(I+1),text:(I+1)})}var L=this.select_tag(C+"[year]",J,G,{id:C+"[year]"});var F=this.select_tag(C+"[month]",H,D,{id:C+"[month]"});var K=this.select_tag(C+"[day]",N,P,{id:C+"[day]"});return L+F+K};EJS.Helpers.prototype.form_tag=function(B,A){A=A||{};A.action=B;if(A.multipart==true){A.method="post";A.enctype="multipart/form-data"}return this.start_tag_for("form",A)};EJS.Helpers.prototype.form_tag_end=function(){return this.tag_end("form")};EJS.Helpers.prototype.hidden_field_tag=function(A,C,B){return this.input_field_tag(A,C,"hidden",B)};EJS.Helpers.prototype.input_field_tag=function(A,D,C,B){B=B||{};B.id=B.id||A;B.value=D||"";B.type=C||"text";B.name=A;return this.single_tag_for("input",B)};EJS.Helpers.prototype.is_current_page=function(A){return(window.location.href==A||window.location.pathname==A?true:false)};EJS.Helpers.prototype.link_to=function(B,A,C){if(!B){var B="null"}if(!C){var C={}}if(C.confirm){C.onclick=' var ret_confirm = confirm("'+C.confirm+'"); if(!ret_confirm){ return false;} ';C.confirm=null}C.href=A;return this.start_tag_for("a",C)+B+this.tag_end("a")};EJS.Helpers.prototype.submit_link_to=function(B,A,C){if(!B){var B="null"}if(!C){var C={}}C.onclick=C.onclick||"";if(C.confirm){C.onclick=' var ret_confirm = confirm("'+C.confirm+'"); if(!ret_confirm){ return false;} ';C.confirm=null}C.value=B;C.type="submit";C.onclick=C.onclick+(A?this.url_for(A):"")+"return false;";return this.start_tag_for("input",C)};EJS.Helpers.prototype.link_to_if=function(F,B,A,D,C,E){return this.link_to_unless((F==false),B,A,D,C,E)};EJS.Helpers.prototype.link_to_unless=function(E,B,A,C,D){C=C||{};if(E){if(D&&typeof D=="function"){return D(B,A,C,D)}else{return B}}else{return this.link_to(B,A,C)}};EJS.Helpers.prototype.link_to_unless_current=function(B,A,C,D){C=C||{};return this.link_to_unless(this.is_current_page(A),B,A,C,D)};EJS.Helpers.prototype.password_field_tag=function(A,C,B){return this.input_field_tag(A,C,"password",B)};EJS.Helpers.prototype.select_tag=function(D,G,H,F){F=F||{};F.id=F.id||D;F.value=G;F.name=D;var B="";B+=this.start_tag_for("select",F);for(var E=0;E<H.length;E++){var C=H[E];var A={value:C.value};if(C.value==G){A.selected="selected"}B+=this.start_tag_for("option",A)+C.text+this.tag_end("option")}B+=this.tag_end("select");return B};EJS.Helpers.prototype.single_tag_for=function(A,B){return this.tag(A,B,"/>")};EJS.Helpers.prototype.start_tag_for=function(A,B){return this.tag(A,B)};EJS.Helpers.prototype.submit_tag=function(A,B){B=B||{};B.type=B.type||"submit";B.value=A||"Submit";return this.single_tag_for("input",B)};EJS.Helpers.prototype.tag=function(C,E,D){if(!D){var D=">"}var B=" ";for(var A in E){if(E[A]!=null){var F=E[A].toString()}else{var F=""}if(A=="Class"){A="class"}if(F.indexOf("'")!=-1){B+=A+'="'+F+'" '}else{B+=A+"='"+F+"' "}}return"<"+C+B+D};EJS.Helpers.prototype.tag_end=function(A){return"</"+A+">"};EJS.Helpers.prototype.text_area_tag=function(A,C,B){B=B||{};B.id=B.id||A;B.name=B.name||A;C=C||"";if(B.size){B.cols=B.size.split("x")[0];B.rows=B.size.split("x")[1];delete B.size}B.cols=B.cols||50;B.rows=B.rows||4;return this.start_tag_for("textarea",B)+C+this.tag_end("textarea")};EJS.Helpers.prototype.text_tag=EJS.Helpers.prototype.text_area_tag;EJS.Helpers.prototype.text_field_tag=function(A,C,B){return this.input_field_tag(A,C,"text",B)};EJS.Helpers.prototype.url_for=function(A){return'window.location="'+A+'";'};EJS.Helpers.prototype.img_tag=function(B,C,A){A=A||{};A.src=B;A.alt=C;return this.single_tag_for("img",A)}
Index: /trunk/prototype/app/plugins/ejs/view.js
===================================================================
--- /trunk/prototype/app/plugins/ejs/view.js	(revision 5283)
+++ /trunk/prototype/app/plugins/ejs/view.js	(revision 5283)
@@ -0,0 +1,200 @@
+EJS.Helpers.prototype.date_tag = function(name, value , html_options) {
+    if(! (value instanceof Date))
+		value = new Date()
+	
+	var month_names = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
+	var years = [], months = [], days =[];
+	var year = value.getFullYear();
+	var month = value.getMonth();
+	var day = value.getDate();
+	for(var y = year - 15; y < year+15 ; y++)
+	{
+		years.push({value: y, text: y})
+	}
+	for(var m = 0; m < 12; m++)
+	{
+		months.push({value: (m), text: month_names[m]})
+	}
+	for(var d = 0; d < 31; d++)
+	{
+		days.push({value: (d+1), text: (d+1)})
+	}
+	var year_select = this.select_tag(name+'[year]', year, years, {id: name+'[year]'} )
+	var month_select = this.select_tag(name+'[month]', month, months, {id: name+'[month]'})
+	var day_select = this.select_tag(name+'[day]', day, days, {id: name+'[day]'})
+	
+    return year_select+month_select+day_select;
+}
+
+EJS.Helpers.prototype.form_tag = function(action, html_options) {
+                 
+    
+    html_options     = html_options                     || {};
+	html_options.action = action
+    if(html_options.multipart == true) {
+        html_options.method = 'post';
+        html_options.enctype = 'multipart/form-data';
+    }
+    
+    return this.start_tag_for('form', html_options)
+}
+
+EJS.Helpers.prototype.form_tag_end = function() { return this.tag_end('form'); }
+
+EJS.Helpers.prototype.hidden_field_tag   = function(name, value, html_options) { 
+    return this.input_field_tag(name, value, 'hidden', html_options); 
+}
+
+EJS.Helpers.prototype.input_field_tag = function(name, value , inputType, html_options) {
+    
+    html_options = html_options || {};
+    html_options.id  = html_options.id  || name;
+    html_options.value = value || '';
+    html_options.type = inputType || 'text';
+    html_options.name = name;
+    
+    return this.single_tag_for('input', html_options)
+}
+
+EJS.Helpers.prototype.is_current_page = function(url) {
+	return (window.location.href == url || window.location.pathname == url ? true : false);
+}
+
+EJS.Helpers.prototype.link_to = function(name, url, html_options) {
+    if(!name) var name = 'null';
+    if(!html_options) var html_options = {}
+	
+	if(html_options.confirm){
+		html_options.onclick = 
+		" var ret_confirm = confirm(\""+html_options.confirm+"\"); if(!ret_confirm){ return false;} "
+		html_options.confirm = null;
+	}
+    html_options.href=url
+    return this.start_tag_for('a', html_options)+name+ this.tag_end('a');
+}
+
+EJS.Helpers.prototype.submit_link_to = function(name, url, html_options){
+	if(!name) var name = 'null';
+    if(!html_options) var html_options = {}
+    html_options.onclick = html_options.onclick  || '' ;
+	
+	if(html_options.confirm){
+		html_options.onclick = 
+		" var ret_confirm = confirm(\""+html_options.confirm+"\"); if(!ret_confirm){ return false;} "
+		html_options.confirm = null;
+	}
+	
+    html_options.value = name;
+	html_options.type = 'submit'
+    html_options.onclick=html_options.onclick+
+		(url ? this.url_for(url) : '')+'return false;';
+    //html_options.href='#'+(options ? Routes.url_for(options) : '')
+	return this.start_tag_for('input', html_options)
+}
+
+EJS.Helpers.prototype.link_to_if = function(condition, name, url, html_options, post, block) {
+	return this.link_to_unless((condition == false), name, url, html_options, post, block);
+}
+
+EJS.Helpers.prototype.link_to_unless = function(condition, name, url, html_options, block) {
+	html_options = html_options || {};
+	if(condition) {
+		if(block && typeof block == 'function') {
+			return block(name, url, html_options, block);
+		} else {
+			return name;
+		}
+	} else
+		return this.link_to(name, url, html_options);
+}
+
+EJS.Helpers.prototype.link_to_unless_current = function(name, url, html_options, block) {
+	html_options = html_options || {};
+	return this.link_to_unless(this.is_current_page(url), name, url, html_options, block)
+}
+
+
+EJS.Helpers.prototype.password_field_tag = function(name, value, html_options) { return this.input_field_tag(name, value, 'password', html_options); }
+
+EJS.Helpers.prototype.select_tag = function(name, value, choices, html_options) {     
+    html_options = html_options || {};
+    html_options.id  = html_options.id  || name;
+    html_options.value = value;
+	html_options.name = name;
+    
+    var txt = ''
+    txt += this.start_tag_for('select', html_options)
+    
+    for(var i = 0; i < choices.length; i++)
+    {
+        var choice = choices[i];
+        var optionOptions = {value: choice.value}
+        if(choice.value == value)
+            optionOptions.selected ='selected'
+        txt += this.start_tag_for('option', optionOptions )+choice.text+this.tag_end('option')
+    }
+    txt += this.tag_end('select');
+    return txt;
+}
+
+EJS.Helpers.prototype.single_tag_for = function(tag, html_options) { return this.tag(tag, html_options, '/>');}
+
+EJS.Helpers.prototype.start_tag_for = function(tag, html_options)  { return this.tag(tag, html_options); }
+
+EJS.Helpers.prototype.submit_tag = function(name, html_options) {  
+    html_options = html_options || {};
+    //html_options.name  = html_options.id  || 'commit';
+    html_options.type = html_options.type  || 'submit';
+    html_options.value = name || 'Submit';
+    return this.single_tag_for('input', html_options);
+}
+
+EJS.Helpers.prototype.tag = function(tag, html_options, end) {
+    if(!end) var end = '>'
+    var txt = ' '
+    for(var attr in html_options) { 
+	   if(html_options[attr] != null)
+        var value = html_options[attr].toString();
+       else
+        var value=''
+       if(attr == "Class") // special case because "class" is a reserved word in IE
+        attr = "class";
+       if( value.indexOf("'") != -1 )
+            txt += attr+'=\"'+value+'\" ' 
+       else
+            txt += attr+"='"+value+"' " 
+    }
+    return '<'+tag+txt+end;
+}
+
+EJS.Helpers.prototype.tag_end = function(tag)             { return '</'+tag+'>'; }
+
+EJS.Helpers.prototype.text_area_tag = function(name, value, html_options) { 
+    html_options = html_options || {};
+    html_options.id  = html_options.id  || name;
+    html_options.name  = html_options.name  || name;
+	value = value || ''
+    if(html_options.size) {
+        html_options.cols = html_options.size.split('x')[0]
+        html_options.rows = html_options.size.split('x')[1];
+        delete html_options.size
+    }
+    
+    html_options.cols = html_options.cols  || 50;
+    html_options.rows = html_options.rows  || 4;
+    
+    return  this.start_tag_for('textarea', html_options)+value+this.tag_end('textarea')
+}
+EJS.Helpers.prototype.text_tag = EJS.Helpers.prototype.text_area_tag
+
+EJS.Helpers.prototype.text_field_tag     = function(name, value, html_options) { return this.input_field_tag(name, value, 'text', html_options); }
+
+EJS.Helpers.prototype.url_for = function(url) {
+        return 'window.location="'+url+'";'
+}
+EJS.Helpers.prototype.img_tag = function(image_location, alt, options){
+	options = options || {};
+	options.src = image_location
+	options.alt = alt
+	return this.single_tag_for('img', options)
+}
Index: /trunk/prototype/app/plugins/ejs/ejs.js
===================================================================
--- /trunk/prototype/app/plugins/ejs/ejs.js	(revision 5283)
+++ /trunk/prototype/app/plugins/ejs/ejs.js	(revision 5283)
@@ -0,0 +1,505 @@
+(function(){
+    
+
+var rsplit = function(string, regex) {
+	var result = regex.exec(string),retArr = new Array(), first_idx, last_idx, first_bit;
+	while (result != null)
+	{
+		first_idx = result.index; last_idx = regex.lastIndex;
+		if ((first_idx) != 0)
+		{
+			first_bit = string.substring(0,first_idx);
+			retArr.push(string.substring(0,first_idx));
+			string = string.slice(first_idx);
+		}		
+		retArr.push(result[0]);
+		string = string.slice(result[0].length);
+		result = regex.exec(string);	
+	}
+	if (! string == '')
+	{
+		retArr.push(string);
+	}
+	return retArr;
+},
+chop =  function(string){
+    return string.substr(0, string.length - 1);
+},
+extend = function(d, s){
+    for(var n in s){
+        if(s.hasOwnProperty(n))  d[n] = s[n]
+    }
+}
+
+
+EJS = function( options ){
+	options = typeof options == "string" ? {view: options} : options
+    this.set_options(options);
+	if(options.precompiled){
+		this.template = {};
+		this.template.process = options.precompiled;
+		EJS.update(this.name, this);
+		return;
+	}
+    if(options.element)
+	{
+		if(typeof options.element == 'string'){
+			var name = options.element
+			options.element = document.getElementById(  options.element )
+			if(options.element == null) throw name+'does not exist!'
+		}
+		if(options.element.value){
+			this.text = options.element.value
+		}else{
+			this.text = options.element.innerHTML
+		}
+		this.name = options.element.id
+		this.type = '['
+	}else if(options.url){
+        options.url = EJS.endExt(options.url, this.extMatch);
+		this.name = this.name ? this.name : options.url;
+        var url = options.url
+        //options.view = options.absolute_url || options.view || options.;
+		var template = EJS.get(this.name /*url*/, this.cache);
+		if (template) return template;
+	    if (template == EJS.INVALID_PATH) return null;
+        try{
+            this.text = EJS.request( url+(this.cache ? '' : '?'+Math.random() ));
+        }catch(e){}
+
+		if(this.text == null){
+            throw( {type: 'EJS', message: 'There is no template at '+url}  );
+		}
+		//this.name = url;
+	}
+	var template = new EJS.Compiler(this.text, this.type);
+
+	template.compile(options, this.name);
+
+	
+	EJS.update(this.name, this);
+	this.template = template;
+};
+/* @Prototype*/
+EJS.prototype = {
+	/**
+	 * Renders an object with extra view helpers attached to the view.
+	 * @param {Object} object data to be rendered
+	 * @param {Object} extra_helpers an object with additonal view helpers
+	 * @return {String} returns the result of the string
+	 */
+    render : function(object, extra_helpers){
+        object = object || {};
+        this._extra_helpers = extra_helpers;
+		var v = new EJS.Helpers(object, extra_helpers || {});
+		return this.template.process.call(object, object,v);
+	},
+    update : function(element, options){
+        if(typeof element == 'string'){
+			element = document.getElementById(element)
+		}
+		if(options == null){
+			_template = this;
+			return function(object){
+				EJS.prototype.update.call(_template, element, object)
+			}
+		}
+		if(typeof options == 'string'){
+			params = {}
+			params.url = options
+			_template = this;
+			params.onComplete = function(request){
+				var object = eval( request.responseText )
+				EJS.prototype.update.call(_template, element, object)
+			}
+			EJS.ajax_request(params)
+		}else
+		{
+			element.innerHTML = this.render(options)
+		}
+    },
+	out : function(){
+		return this.template.out;
+	},
+    /**
+     * Sets options on this view to be rendered with.
+     * @param {Object} options
+     */
+	set_options : function(options){
+        this.type = options.type || EJS.type;
+		this.cache = options.cache != null ? options.cache : EJS.cache;
+		this.text = options.text || null;
+		this.name =  options.name || null;
+		this.ext = options.ext || EJS.ext;
+		this.extMatch = new RegExp(this.ext.replace(/\./, '\.'));
+	}
+};
+EJS.endExt = function(path, match){
+	if(!path) return null;
+	match.lastIndex = 0
+	return path+ (match.test(path) ? '' : this.ext )
+}
+
+
+
+
+/* @Static*/
+EJS.Scanner = function(source, left, right) {
+	
+    extend(this,
+        {left_delimiter: 	left +'%',
+         right_delimiter: 	'%'+right,
+         double_left: 		left+'%%',
+         double_right:  	'%%'+right,
+         left_equal: 		left+'%=',
+         left_comment: 	left+'%#'})
+
+	this.SplitRegexp = left=='[' ? /(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/ : new RegExp('('+this.double_left+')|(%%'+this.double_right+')|('+this.left_equal+')|('+this.left_comment+')|('+this.left_delimiter+')|('+this.right_delimiter+'\n)|('+this.right_delimiter+')|(\n)') ;
+	
+	this.source = source;
+	this.stag = null;
+	this.lines = 0;
+};
+
+EJS.Scanner.to_text = function(input){
+	if(input == null || input === undefined)
+        return '';
+    if(input instanceof Date)
+		return input.toDateString();
+	if(input.toString) 
+        return input.toString();
+	return '';
+};
+
+EJS.Scanner.prototype = {
+  scan: function(block) {
+     scanline = this.scanline;
+	 regex = this.SplitRegexp;
+	 if (! this.source == '')
+	 {
+	 	 var source_split = rsplit(this.source, /\n/);
+	 	 for(var i=0; i<source_split.length; i++) {
+		 	 var item = source_split[i];
+			 this.scanline(item, regex, block);
+		 }
+	 }
+  },
+  scanline: function(line, regex, block) {
+	 this.lines++;
+	 var line_split = rsplit(line, regex);
+ 	 for(var i=0; i<line_split.length; i++) {
+	   var token = line_split[i];
+       if (token != null) {
+		   	try{
+	         	block(token, this);
+		 	}catch(e){
+				throw {type: 'EJS.Scanner', line: this.lines};
+			}
+       }
+	 }
+  }
+};
+
+
+EJS.Buffer = function(pre_cmd, post_cmd) {
+	this.line = new Array();
+	this.script = "";
+	this.pre_cmd = pre_cmd;
+	this.post_cmd = post_cmd;
+	for (var i=0; i<this.pre_cmd.length; i++)
+	{
+		this.push(pre_cmd[i]);
+	}
+};
+EJS.Buffer.prototype = {
+	
+  push: function(cmd) {
+	this.line.push(cmd);
+  },
+
+  cr: function() {
+	this.script = this.script + this.line.join('; ');
+	this.line = new Array();
+	this.script = this.script + "\n";
+  },
+
+  close: function() {
+	if (this.line.length > 0)
+	{
+		for (var i=0; i<this.post_cmd.length; i++){
+			this.push(pre_cmd[i]);
+		}
+		this.script = this.script + this.line.join('; ');
+		line = null;
+	}
+  }
+ 	
+};
+
+
+EJS.Compiler = function(source, left) {
+    this.pre_cmd = ['var ___ViewO = [];'];
+	this.post_cmd = new Array();
+	this.source = ' ';	
+	if (source != null)
+	{
+		if (typeof source == 'string')
+		{
+		    source = source.replace(/\r\n/g, "\n");
+            source = source.replace(/\r/g,   "\n");
+			this.source = source;
+		}else if (source.innerHTML){
+			this.source = source.innerHTML;
+		} 
+		if (typeof this.source != 'string'){
+			this.source = "";
+		}
+	}
+	left = left || '<';
+	var right = '>';
+	switch(left) {
+		case '[':
+			right = ']';
+			break;
+		case '<':
+			break;
+		default:
+			throw left+' is not a supported deliminator';
+			break;
+	}
+	this.scanner = new EJS.Scanner(this.source, left, right);
+	this.out = '';
+};
+EJS.Compiler.prototype = {
+  compile: function(options, name) {
+  	options = options || {};
+	this.out = '';
+	var put_cmd = "___ViewO.push(";
+	var insert_cmd = put_cmd;
+	var buff = new EJS.Buffer(this.pre_cmd, this.post_cmd);		
+	var content = '';
+	var clean = function(content)
+	{
+	    content = content.replace(/\\/g, '\\\\');
+        content = content.replace(/\n/g, '\\n');
+        content = content.replace(/"/g,  '\\"');
+        return content;
+	};
+	this.scanner.scan(function(token, scanner) {
+		if (scanner.stag == null)
+		{
+			switch(token) {
+				case '\n':
+					content = content + "\n";
+					buff.push(put_cmd + '"' + clean(content) + '");');
+					buff.cr();
+					content = '';
+					break;
+				case scanner.left_delimiter:
+				case scanner.left_equal:
+				case scanner.left_comment:
+					scanner.stag = token;
+					if (content.length > 0)
+					{
+						buff.push(put_cmd + '"' + clean(content) + '")');
+					}
+					content = '';
+					break;
+				case scanner.double_left:
+					content = content + scanner.left_delimiter;
+					break;
+				default:
+					content = content + token;
+					break;
+			}
+		}
+		else {
+			switch(token) {
+				case scanner.right_delimiter:
+					switch(scanner.stag) {
+						case scanner.left_delimiter:
+							if (content[content.length - 1] == '\n')
+							{
+								content = chop(content);
+								buff.push(content);
+								buff.cr();
+							}
+							else {
+								buff.push(content);
+							}
+							break;
+						case scanner.left_equal:
+							buff.push(insert_cmd + "(EJS.Scanner.to_text(" + content + ")))");
+							break;
+					}
+					scanner.stag = null;
+					content = '';
+					break;
+				case scanner.double_right:
+					content = content + scanner.right_delimiter;
+					break;
+				default:
+					content = content + token;
+					break;
+			}
+		}
+	});
+	if (content.length > 0)
+	{
+		// Chould be content.dump in Ruby
+		buff.push(put_cmd + '"' + clean(content) + '")');
+	}
+	buff.close();
+	this.out = buff.script + ";";
+	var to_be_evaled = '/*'+name+'*/this.process = function(_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) {'+this.out+" return ___ViewO.join('');}}}catch(e){e.lineNumber=null;throw e;}};";
+	
+	try{
+		eval(to_be_evaled);
+	}catch(e){
+		if(typeof JSLINT != 'undefined'){
+			JSLINT(this.out);
+			for(var i = 0; i < JSLINT.errors.length; i++){
+				var error = JSLINT.errors[i];
+				if(error.reason != "Unnecessary semicolon."){
+					error.line++;
+					var e = new Error();
+					e.lineNumber = error.line;
+					e.message = error.reason;
+					if(options.view)
+						e.fileName = options.view;
+					throw e;
+				}
+			}
+		}else{
+			throw e;
+		}
+	}
+  }
+};
+
+
+//type, cache, folder
+/**
+ * Sets default options for all views
+ * @param {Object} options Set view with the following options
+ * <table class="options">
+				<tbody><tr><th>Option</th><th>Default</th><th>Description</th></tr>
+				<tr>
+					<td>type</td>
+					<td>'<'</td>
+					<td>type of magic tags.  Options are '&lt;' or '['
+					</td>
+				</tr>
+				<tr>
+					<td>cache</td>
+					<td>true in production mode, false in other modes</td>
+					<td>true to cache template.
+					</td>
+				</tr>
+	</tbody></table>
+ * 
+ */
+EJS.config = function(options){
+	EJS.cache = options.cache != null ? options.cache : EJS.cache;
+	EJS.type = options.type != null ? options.type : EJS.type;
+	EJS.ext = options.ext != null ? options.ext : EJS.ext;
+	
+	var templates_directory = EJS.templates_directory || {}; //nice and private container
+	EJS.templates_directory = templates_directory;
+	EJS.get = function(path, cache){
+		if(cache == false) return null;
+		if(templates_directory[path]) return templates_directory[path];
+  		return null;
+	};
+	
+	EJS.update = function(path, template) { 
+		if(path == null) return;
+		templates_directory[path] = template ;
+	};
+	
+	EJS.INVALID_PATH =  -1;
+};
+EJS.config( {cache: true, type: '<', ext: '.ejs' } );
+
+
+
+/**
+ * @constructor
+ * By adding functions to EJS.Helpers.prototype, those functions will be available in the 
+ * views.
+ * @init Creates a view helper.  This function is called internally.  You should never call it.
+ * @param {Object} data The data passed to the view.  Helpers have access to it through this._data
+ */
+EJS.Helpers = function(data, extras){
+	this._data = data;
+    this._extras = extras;
+    extend(this, extras );
+};
+/* @prototype*/
+EJS.Helpers.prototype = {
+    /**
+     * Renders a new view.  If data is passed in, uses that to render the view.
+     * @param {Object} options standard options passed to a new view.
+     * @param {optional:Object} data
+     * @return {String}
+     */
+	view: function(options, data, helpers){
+        if(!helpers) helpers = this._extras
+		if(!data) data = this._data;
+		return new EJS(options).render(data, helpers);
+	},
+    /**
+     * For a given value, tries to create a human representation.
+     * @param {Object} input the value being converted.
+     * @param {Object} null_text what text should be present if input == null or undefined, defaults to ''
+     * @return {String} 
+     */
+	to_text: function(input, null_text) {
+	    if(input == null || input === undefined) return null_text || '';
+	    if(input instanceof Date) return input.toDateString();
+		if(input.toString) return input.toString().replace(/\n/g, '<br />').replace(/''/g, "'");
+		return '';
+	}
+};
+    EJS.newRequest = function(){
+	   var factories = [function() { return new ActiveXObject("Msxml2.XMLHTTP"); },function() { return new XMLHttpRequest(); },function() { return new ActiveXObject("Microsoft.XMLHTTP"); }];
+	   for(var i = 0; i < factories.length; i++) {
+	        try {
+	            var request = factories[i]();
+	            if (request != null)  return request;
+	        }
+	        catch(e) { continue;}
+	   }
+	}
+	
+	EJS.request = function(path){
+	   var request = new EJS.newRequest()
+	   request.open("GET", path, false);
+	   
+	   try{request.send(null);}
+	   catch(e){return null;}
+	   
+	   if ( request.status == 404 || request.status == 2 ||(request.status == 0 && request.responseText == '') ) return null;
+	   
+	   return request.responseText
+	}
+	EJS.ajax_request = function(params){
+		params.method = ( params.method ? params.method : 'GET')
+		
+		var request = new EJS.newRequest();
+		request.onreadystatechange = function(){
+			if(request.readyState == 4){
+				if(request.status == 200){
+					params.onComplete(request)
+				}else
+				{
+					params.onComplete(request)
+				}
+			}
+		}
+		request.open(params.method, params.url)
+		request.send(null)
+	}
+
+
+})();
Index: /trunk/prototype/app/plugins/json2/json2.js
===================================================================
--- /trunk/prototype/app/plugins/json2/json2.js	(revision 5283)
+++ /trunk/prototype/app/plugins/json2/json2.js	(revision 5283)
@@ -0,0 +1,326 @@
+// origin: http://www.json.org/json2.js
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+    this.JSON = {};
+}
+
+(function () {
+
+    function f(n) {
+        // Format integers to have at least two digits.
+        return n < 10 ? '0' + n : n;
+    }
+
+    if (typeof Date.prototype.toJSON !== 'function') {
+
+        Date.prototype.toJSON = function (key) {
+
+            return isFinite(this.valueOf()) ?
+                   this.getUTCFullYear()   + '-' +
+                 f(this.getUTCMonth() + 1) + '-' +
+                 f(this.getUTCDate())      + 'T' +
+                 f(this.getUTCHours())     + ':' +
+                 f(this.getUTCMinutes())   + ':' +
+                 f(this.getUTCSeconds())   + 'Z' : null;
+        };
+
+        String.prototype.toJSON =
+        Number.prototype.toJSON =
+        Boolean.prototype.toJSON = function (key) {
+            return this.valueOf();
+        };
+    }
+
+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        gap,
+        indent,
+        meta = {    // table of character substitutions
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '\\': '\\\\'
+        },
+        rep;
+
+
+    function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+        escapable.lastIndex = 0;
+        return escapable.test(string) ?
+            '"' + string.replace(escapable, function (a) {
+                var c = meta[a];
+                return typeof c === 'string' ? c :
+                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+            }) + '"' :
+            '"' + string + '"';
+    }
+
+
+    function str(key, holder) {
+
+// Produce a string from holder[key].
+
+        var i,          // The loop counter.
+            k,          // The member key.
+            v,          // The member value.
+            length,
+            mind = gap,
+            partial,
+            value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+        if (value && typeof value === 'object' &&
+                typeof value.toJSON === 'function') {
+            value = value.toJSON(key);
+        }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+        if (typeof rep === 'function') {
+            value = rep.call(holder, key, value);
+        }
+
+// What happens next depends on the value's type.
+
+        switch (typeof value) {
+        case 'string':
+            return quote(value);
+
+        case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+            return isFinite(value) ? String(value) : 'null';
+
+        case 'boolean':
+        case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+            return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+        case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+            if (!value) {
+                return 'null';
+            }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+            gap += indent;
+            partial = [];
+
+// Is the value an array?
+
+            if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+                length = value.length;
+                for (i = 0; i < length; i += 1) {
+                    partial[i] = str(i, value) || 'null';
+                }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+                v = partial.length === 0 ? '[]' :
+                    gap ? '[\n' + gap +
+                            partial.join(',\n' + gap) + '\n' +
+                                mind + ']' :
+                          '[' + partial.join(',') + ']';
+                gap = mind;
+                return v;
+            }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+            if (rep && typeof rep === 'object') {
+                length = rep.length;
+                for (i = 0; i < length; i += 1) {
+                    k = rep[i];
+                    if (typeof k === 'string') {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+                for (k in value) {
+                    if (Object.hasOwnProperty.call(value, k)) {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+            v = partial.length === 0 ? '{}' :
+                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+                        mind + '}' : '{' + partial.join(',') + '}';
+            gap = mind;
+            return v;
+        }
+    }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+    if (typeof JSON.stringify !== 'function') {
+        JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+            var i;
+            gap = '';
+            indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+            if (typeof space === 'number') {
+                for (i = 0; i < space; i += 1) {
+                    indent += ' ';
+                }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+            } else if (typeof space === 'string') {
+                indent = space;
+            }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+            rep = replacer;
+            if (replacer && typeof replacer !== 'function' &&
+                    (typeof replacer !== 'object' ||
+                     typeof replacer.length !== 'number')) {
+                throw new Error('JSON.stringify');
+            }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+            return str('', {'': value});
+        };
+    }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+    if (typeof JSON.parse !== 'function') {
+        JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+            var j;
+
+            function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+                var k, v, value = holder[key];
+                if (value && typeof value === 'object') {
+                    for (k in value) {
+                        if (Object.hasOwnProperty.call(value, k)) {
+                            v = walk(value, k);
+                            if (v !== undefined) {
+                                value[k] = v;
+                            } else {
+                                delete value[k];
+                            }
+                        }
+                    }
+                }
+                return reviver.call(holder, key, value);
+            }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+            text = String(text);
+            cx.lastIndex = 0;
+            if (cx.test(text)) {
+                text = text.replace(cx, function (a) {
+                    return '\\u' +
+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+                });
+            }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+            if (/^[\],:{}\s]*$/
+.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
+.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+                j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+                return typeof reviver === 'function' ?
+                    walk({'': j}, '') : j;
+            }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+            throw new SyntaxError('JSON.parse');
+        };
+    }
+}());
Index: /trunk/prototype/app/plugins/store/store.js
===================================================================
--- /trunk/prototype/app/plugins/store/store.js	(revision 5283)
+++ /trunk/prototype/app/plugins/store/store.js	(revision 5283)
@@ -0,0 +1,424 @@
+/*
+ * jQuery store - Plugin for persistent data storage using localStorage, userData (and window.name)
+ * 
+ * Authors: Rodney Rehm
+ * Web: http://medialize.github.com/jQuery-store/
+ * 
+ * Licensed under the MIT License:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *
+ */
+
+/**********************************************************************************
+ * INITIALIZE EXAMPLES:
+ **********************************************************************************
+ * 	// automatically detect best suited storage driver and use default serializers
+ *	$.storage = new $.store();
+ *	// optionally initialize with specific driver and or serializers
+ *	$.storage = new $.store( [driver] [, serializers] );
+ *		driver		can be the key (e.g. "windowName") or the driver-object itself
+ *		serializers	can be a list of named serializers like $.store.serializers
+ **********************************************************************************
+ * USAGE EXAMPLES:
+ **********************************************************************************
+ *	$.storage.get( key );			// retrieves a value
+ *	$.storage.set( key, value );	// saves a value
+ *	$.storage.del( key );			// deletes a value
+ *	$.storage.flush();				// deletes aall values
+ **********************************************************************************
+ */
+
+(function($,undefined){
+
+/**********************************************************************************
+ * $.store base and convinience accessor
+ **********************************************************************************/
+
+$.store = function( driver, serializers )
+{
+	var that = this;
+	
+	if( typeof driver == 'string' )
+	{
+		if( $.store.drivers[ driver ] )
+			this.driver = $.store.drivers[ driver ];
+		else
+			throw new Error( 'Unknown driver '+ driver );
+	}
+	else if( typeof driver == 'object' )
+	{
+		var invalidAPI = !$.isFunction( driver.init )
+			|| !$.isFunction( driver.get )
+			|| !$.isFunction( driver.set )
+			|| !$.isFunction( driver.del )
+			|| !$.isFunction( driver.flush );
+			
+		if( invalidAPI )
+			throw new Error( 'The specified driver does not fulfill the API requirements' );
+		
+		this.driver = driver;
+	}
+	else
+	{
+		// detect and initialize storage driver
+		$.each( $.store.drivers, function()
+		{
+			// skip unavailable drivers
+			if( !$.isFunction( this.available ) || !this.available() )
+				return true; // continue;
+			
+			that.driver = this;
+			if( that.driver.init() === false )
+			{
+				that.driver = null;
+				return true; // continue;
+			}
+			
+			return false; // break;
+		});
+	}
+	
+	// use default serializers if not told otherwise
+	if( !serializers )
+		serializers = $.store.serializers;
+	
+	// intialize serializers
+	this.serializers = {};
+	$.each( serializers, function( key, serializer )
+	{
+		// skip invalid processors
+		if( !$.isFunction( this.init ) )
+			return true; // continue;
+		
+		that.serializers[ key ] = this;
+		that.serializers[ key ].init( that.encoders, that.decoders );
+	});
+};
+
+
+/**********************************************************************************
+ * $.store API
+ **********************************************************************************/
+
+$.extend( $.store.prototype, {
+	get: function( key )
+	{
+		var value = this.driver.get( key );
+		return this.driver.encodes ? value : this.unserialize( value );
+	},
+	set: function( key, value )
+	{
+		this.driver.set( key, this.driver.encodes ? value : this.serialize( value ) );
+	},
+	del: function( key )
+	{
+		this.driver.del( key );
+	},
+	flush: function()
+	{
+		this.driver.flush();
+	},
+	driver : undefined,
+	encoders : [],
+	decoders : [],
+	serialize: function( value )
+	{
+		var that = this;
+		
+		$.each( this.encoders, function()
+		{
+			var serializer = that.serializers[ this + "" ];
+			if( !serializer || !serializer.encode )
+				return true; // continue;
+			try
+			{
+				value = serializer.encode( value );
+			}
+			catch( e ){}
+		});
+
+		return value;
+	},
+	unserialize: function( value )
+	{
+		var that = this;
+		if( !value )
+			return value;
+		
+		$.each( this.decoders, function()
+		{
+			var serializer = that.serializers[ this + "" ];
+			if( !serializer || !serializer.decode )
+				return true; // continue;
+
+			value = serializer.decode( value );
+		});
+
+		return value;
+	}
+});
+
+
+/**********************************************************************************
+ * $.store drivers
+ **********************************************************************************/
+
+$.store.drivers = {
+	// Firefox 3.5, Safari 4.0, Chrome 5, Opera 10.5, IE8
+	'localStorage': {
+		// see https://developer.mozilla.org/en/dom/storage#localStorage
+		ident: "$.store.drivers.localStorage",
+		scope: 'browser',
+		available: function()
+		{
+			try
+			{
+				return !!window.localStorage;
+			}
+			catch(e)
+			{
+				// Firefox won't allow localStorage if cookies are disabled
+				return false;
+			}
+		},
+		init: $.noop,
+		get: function( key )
+		{
+			return window.localStorage.getItem( key );
+		},
+		set: function( key, value )
+		{
+			window.localStorage.setItem( key, value );
+		},
+		del: function( key )
+		{
+			window.localStorage.removeItem( key );
+		},
+		flush: function()
+		{
+			window.localStorage.clear();
+		}
+	},
+	
+	// IE6, IE7
+	'userData': {
+		// see http://msdn.microsoft.com/en-us/library/ms531424.aspx
+		ident: "$.store.drivers.userData",
+		element: null,
+		nodeName: 'userdatadriver',
+		scope: 'browser',
+		initialized: false,
+		available: function()
+		{
+			try
+			{
+				return !!( document.documentElement && document.documentElement.addBehavior );
+			}
+			catch(e)
+			{
+				return false;
+			}
+		},
+		init: function()
+		{
+			// $.store can only utilize one userData store at a time, thus avoid duplicate initialization
+			if( this.initialized )
+				return;
+			
+			try
+			{
+				// Create a non-existing element and append it to the root element (html)
+				this.element = document.createElement( this.nodeName );
+				document.documentElement.insertBefore( this.element, document.getElementsByTagName('title')[0] );
+				// Apply userData behavior
+				this.element.addBehavior( "#default#userData" );
+				this.initialized = true;
+			}
+			catch( e )
+			{
+				return false; 
+			}
+		},
+		get: function( key )
+		{
+			this.element.load( this.nodeName );
+			return this.element.getAttribute( key );
+		},
+		set: function( key, value )
+		{
+			this.element.setAttribute( key, value );
+			this.element.save( this.nodeName );
+		},
+		del: function( key )
+		{
+			this.element.removeAttribute( key );
+			this.element.save( this.nodeName );
+			
+		},
+		flush: function()
+		{
+			// flush by expiration
+			this.element.expires = (new Date).toUTCString();
+			this.element.save( this.nodeName );
+		}
+	},
+	
+	// most other browsers
+	'windowName': {
+		ident: "$.store.drivers.windowName",
+		scope: 'window',
+		cache: {},
+		encodes: true,
+		available: function()
+		{
+			return true;
+		},
+		init: function()
+		{
+			this.load();
+		},
+		save: function()
+		{
+			window.name = $.store.serializers.json.encode( this.cache );
+		},
+		load: function()
+		{
+			try
+			{
+				this.cache = $.store.serializers.json.decode( window.name + "" );
+				if( typeof this.cache != "object" )
+					this.cache = {};
+			}
+			catch(e)
+			{
+				this.cache = {};
+				window.name = "{}";
+			}
+		},
+		get: function( key )
+		{
+			return this.cache[ key ];
+		},
+		set: function( key, value )
+		{
+			this.cache[ key ] = value;
+			this.save();
+		},
+		del: function( key )
+		{
+			try
+			{
+				delete this.cache[ key ];
+			}
+			catch(e)
+			{
+				this.cache[ key ] = undefined;
+			}
+			
+			this.save();
+		},
+		flush: function()
+		{
+			window.name = "{}";
+		}
+	}
+};
+
+/**********************************************************************************
+ * $.store serializers
+ **********************************************************************************/
+
+$.store.serializers = {
+	
+	'json': {
+		ident: "$.store.serializers.json",
+		init: function( encoders, decoders )
+		{
+			encoders.push( "json" );
+			decoders.push( "json" );
+		},
+		encode: JSON.stringify,
+		decode: JSON.parse
+	},
+	
+	// TODO: html serializer
+	// 'html' : {},
+	
+	'xml': {
+		ident: "$.store.serializers.xml",
+		init: function( encoders, decoders )
+		{
+			encoders.unshift( "xml" );
+			decoders.push( "xml" );
+		},
+		
+		// wouldn't be necessary if jQuery exposed this function
+		isXML: function( value )
+		{
+			var documentElement = ( value ? value.ownerDocument || value : 0 ).documentElement;
+			return documentElement ? documentElement.nodeName.toLowerCase() !== "html" : false;
+		},
+
+		// encodes a XML node to string (taken from $.jStorage, MIT License)
+		encode: function( value )
+		{
+			if( !value || value._serialized || !this.isXML( value ) )
+				return value;
+
+			var _value = { _serialized: this.ident, value: value };
+			
+			try
+			{
+				// Mozilla, Webkit, Opera
+				_value.value = new XMLSerializer().serializeToString( value );
+				return _value;
+			}
+			catch(E1)
+			{
+				try
+				{
+					// Internet Explorer
+					_value.value = value.xml;
+					return _value;
+				}
+				catch(E2){}
+			}
+			
+			return value;
+		},
+		
+		// decodes a XML node from string (taken from $.jStorage, MIT License)
+		decode: function( value )
+		{
+			if( !value || !value._serialized || value._serialized != this.ident )
+				return value;
+
+			var dom_parser = ( "DOMParser" in window && (new DOMParser()).parseFromString );
+			if( !dom_parser && window.ActiveXObject )
+			{
+				dom_parser = function( _xmlString )
+				{
+					var xml_doc = new ActiveXObject( 'Microsoft.XMLDOM' );
+					xml_doc.async = 'false';
+					xml_doc.loadXML( _xmlString );
+					return xml_doc;
+				}
+			}
+
+			if( !dom_parser )
+			{
+				return undefined;
+			}
+			
+			value.value = dom_parser.call(
+				"DOMParser" in window && (new DOMParser()) || window, 
+				value.value, 
+				'text/xml'
+			);
+			
+			return this.isXML( value.value ) ? value.value : undefined;
+		}
+	}
+};
+
+})(jQuery);
Index: /trunk/prototype/app/plugins/store/index.html
===================================================================
--- /trunk/prototype/app/plugins/store/index.html	(revision 5283)
+++ /trunk/prototype/app/plugins/store/index.html	(revision 5283)
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+	<meta charset="utf-8" />
+	<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+
+	<title>$.store jQuery plugin</title>
+	<meta name="title" content="$.store() jQuery plugin" />
+	<meta name="description" content="Persistently storing data on the client side" />
+
+	<script type="text/javascript" src="json2.js"></script>
+	<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+	<script type="text/javascript" src="jquery.store.js"></script>
+	<script type="text/javascript">
+		(function($,undefined){
+			
+			// initialize storage API
+			$.storage = new $.store();
+			
+			// build some demo interface
+			var $buttons;
+
+			var log = function( m )
+			{
+				$('<p>'+ m +'</p>').insertAfter( $buttons );
+			}
+
+			var poll = function()
+			{
+				log( $.storage.get( 'event' ) );
+			}
+
+			var push = function()
+			{
+				$.storage.set( 'event', new Date() );
+			}
+
+			$(function()
+			{
+				$buttons = $( '<div></div>' ).prependTo( $( document.body ) );
+
+				$( '<button type="button">start writing</button>' ).appendTo( $buttons ).bind( 'click', function()
+				{
+					window.setInterval( push, 500 );
+				});
+
+				$( '<button type="button">start reading</button>' ).appendTo( $buttons ).bind( 'click', function()
+				{
+					window.setInterval( poll, 1000 );
+				});	
+			});
+
+		})(jQuery);
+	</script>
+	<script type="text/javascript" src="jquery.windowSync.js"></script>
+</head>
+<body>
+
+	<script type="text/javascript">
+		document.write( '<p>Initialized $.storage with driver ' + $.storage.driver.ident + '</p>' );
+	</script>
+	
+	<h1>$.store</h1>
+	
+	<p>
+		<code>$.store</code> is a simple, yet easily extensible, plugin to persistently store data on the client side of things.
+		It uses <code>window.localStore</code> where available. Older Internet Explorers will use <code>userData</code>.
+		If all fails <code>$.store</code> will save your data to <code>window.name</code>.
+	</p>
+	
+	<p>
+		Note: The <code>windowName</code> will only do JSON serialization. <code>windowName</code> 
+		is not persistent in the sense of making it accross a closed browser window. If you need
+		that ability you should check <code>$.storage.driver.scope == "browser"</code>.
+	</p>
+	
+	<h2>Usage</h2>
+	
+<pre>//initialize
+$.storage = new $.store();
+
+// save a value
+$.storage.set( key, value );
+
+// read a value
+$.storage.get( key );
+
+// deletes a value
+$.storage.del( key );
+
+// delete all values
+$.storage.flush();</pre>
+	
+	<h2>Adding Serializers</h2>
+	
+	<p>
+		You can easily add your own serializers to the stack.
+		Make sure to add them before initializing the <code>$.store</code>.
+		Note: Serializers do not apply to the <code>windowName</code> storage driver.
+	</p>
+<pre>$.store.serializers.yaddayadda = {
+  ident: "$.store.serializers.yaddayadda",
+  init: function( encoders, decoders )
+  {
+    // register your serializer with en/decoder stack
+    encoders.unshift( "yaddayadda" );
+    decoders.push( "yaddayadda" );
+  },
+  
+  isYaddaYadda: function( value )
+  {
+    // determine if value should be processed by this serializer
+    return true;
+  },
+
+  encode: function( value )
+  {
+    // check if the value can be encoded
+    if( !value || value._serialized || !this.isYaddaYadda( value ) )
+      return value;
+
+    // prepare serialized-data-wrapper
+    var _value = { _serialized: this.ident, value: value };
+    
+    // work your magic
+    _value.value = "serializedVersionOf data";
+    
+    return value;
+  },
+  
+  decode: function( value )
+  {
+    // check if the value can be decoded
+    if( !value || !value._serialized || value._serialized != this.ident )
+      return value;
+
+    // work your magic
+    value.value = "unserializedVersionOf data";
+    
+    return this.isYaddaYadda( value.value ) ? value.value : undefined;
+  }
+};</pre>
+
+
+</body>
+</html>
Index: /trunk/prototype/app/plugins/store/README.md
===================================================================
--- /trunk/prototype/app/plugins/store/README.md	(revision 5283)
+++ /trunk/prototype/app/plugins/store/README.md	(revision 5283)
@@ -0,0 +1,28 @@
+# $.store jQuery plugin #
+
+<code>$.store</code> is a simple, yet easily extensible, plugin to persistently store data on the client side of things. It uses <code>window.localStore</code> where available. Older Internet Explorers will use <code>userData</code>. If all fails <code>$.store</code> will save your data to <code>window.name</code>.
+
+*Note*: The <code>windowName</code> will only do JSON serialization. <code>windowName</code> is not persistent in the sense of making it accross a closed browser window. If you need that ability you should check <code>$.storage.driver.scope == "browser"</code>.
+
+## Usage ##
+
+<pre><code>
+//initialize
+$.storage = new $.store();
+
+// save a value
+$.storage.set( key, value );
+
+// read a value
+$.storage.get( key );
+
+// deletes a value
+$.storage.del( key );
+
+// delete all values
+$.storage.flush();
+</code></pre>
+
+## License ##
+
+$.store is published under the [MIT license](http://www.opensource.org/licenses/mit-license.php).
Index: /trunk/prototype/app/templates/foldertree.ejs
===================================================================
--- /trunk/prototype/app/templates/foldertree.ejs	(revision 5136)
+++ /trunk/prototype/app/templates/foldertree.ejs	(revision 5283)
@@ -36,14 +36,14 @@
 <ul id="foldertree" class="filetree foldertree-skin-prognus">
   <li><span class="folder">Minhas Pastas</span>
-      <% recursive_forest(folders[0]); %>
+      <% recursive_forest(data.folders[0]); %>
   </li>
-  <% if(folders[1].length > 0) { %>
+  <% if(data.folders[1].length > 0) { %>
   <li><span class="folder">Pastas compartilhadas</span>
-      <% recursive_forest(folders[1]); %>
+      <% recursive_forest(data.folders[1]); %>
   </li> 
   <%}%>
   <% if(preferences.use_local_messages == 1) { %>
   <li><span class="folder">Pastas locais</span>
-      <% recursive_forest(folders[2]); %>
+      <% recursive_forest(data.folders[2]); %>
   </li>
   <%}%>
Index: /trunk/prototype/modules/attach_message/attach_message.css
===================================================================
--- /trunk/prototype/modules/attach_message/attach_message.css	(revision 5278)
+++ /trunk/prototype/modules/attach_message/attach_message.css	(revision 5283)
@@ -32,10 +32,10 @@
 
 /* especifico */
-#foldertree-container { width:220px;height:240px; display:inline-block;margin:5px 0px 5px 5px;overflow:auto;}
-#mailgrid-container {width:650px;/*height:240px;*/ margin:5px 5px 5px 0px; float:right; overflow:auto;}
-#mailpreview_container {margin:5px;padding:0px;height:200px;}
-#mailpreview_container .ui-tabs-panel {height:145px;overflow:auto;padding:0px;}
+#foldertree-container { width:220px;height:240px; display:inline-block;margin:5px 5px 5px 5px;overflow:auto;float:left;}
+#mailgrid-container {width:640px;/*height:240px;*/ margin:5px 0px 5px 0px; float:right; overflow:auto;}
+#mailpreview_container {margin:5px;padding:0px;height:220px; width:888; float:left; margin:5px 5px 5px 5px;}
+#mailpreview_container .ui-tabs-panel {height:194px;overflow:auto;padding:0px; width:886px;}
 /*#mailpreview_tabs { max-height: 20px; }*/
-#buttons-container {text-align: right;margin:5px;}
+#buttons-container {text-align: right;margin:5px 0px 5px 5px;float:right;}
 #selected_messages_counter {text-align:right;padding:0;}
 #selected_messages_number { }
@@ -66,2 +66,4 @@
 .mailpreview-message-body {padding:5px;}
 
+.expresso-window-container{height:520px;}
+#message-attach-dialog {overflow:visible !important;}
Index: /trunk/prototype/modules/attach_message/attach_message.js
===================================================================
--- /trunk/prototype/modules/attach_message/attach_message.js	(revision 5278)
+++ /trunk/prototype/modules/attach_message/attach_message.js	(revision 5283)
@@ -119,4 +119,33 @@
 }
 
+function numberMonths (months){
+	switch(months){
+		case 'Jan':
+			return 1;
+		case 'Feb':
+			return 2;
+		case 'Mar':
+			return 3;
+		case 'Apr':
+			return 4;
+		case 'May':
+			return 5;
+		case 'June':
+			return 6;
+		case 'July':
+			return 7;
+		case 'Aug':
+			return 8;
+		case 'Sept':
+			return 9;
+		case 'Oct':
+			return 10;
+		case 'Nov':
+			return 11;
+		case 'Dec':
+			return 12;
+	}	
+}
+
 NormaliseFrom = function(cellvalue, options, rowObject) {
 	//alert(dump(rowObject));
@@ -132,7 +161,13 @@
 	} else {
 		date = new Date(timestamp);
-		var b = date.toISOString().split("T")[0].split("-");
-		var c = b[2] + "/" + b[1] + "/" + b[0];
-		return '<span class="datable">' + c + '</span>';
+		if(is_ie){
+			var b = date.toString().split(' ');
+			var c = b[2] + "/" + numberMonths(b[1]) + "/" + b[5];
+			return '<span class="datable">' + c + '</span>';
+		}else{
+			var b = date.toISOString().split("T")[0].split("-");
+			var c = b[2] + "/" + b[1] + "/" + b[0];
+			return '<span class="datable">' + c + '</span>';
+		}
 	}
 }
@@ -231,5 +266,5 @@
 		var firstFolder = jQuery("#foldertree-container")
 		.removeClass('empty-container')
-		.html(BASE_PATH + 'app/templates/foldertree.ejs', {folders: [tree1, tree2, tree3]})
+		.html(DataLayer.render(BASE_PATH + 'app/templates/foldertree.ejs', {folders: [tree1, tree2, tree3]}))
 		.find("#foldertree").treeview()
 		.click(function(event){
@@ -255,5 +290,5 @@
 			    id: targetId, 
 			    name: child.attr('title'),
-			    class: child.attr('class')
+			    'class': child.attr('class')
 			};
 
@@ -273,5 +308,5 @@
 			id: firstFolder.parent().attr('id'), 
 			name: firstFolder.attr('title'),
-			class: firstFolder.attr('class')
+			'class': firstFolder.attr('class')
 		};
 
@@ -279,5 +314,5 @@
 		jQuery("#mailgrid-container")
 		.removeClass('empty-container')
-		.html(BASE_PATH + 'app/templates/messagegrid.ejs', {data: {}})
+		.html(DataLayer.render(BASE_PATH + 'app/templates/messagegrid.ejs', {}))
 		.find("#message_attacher_grid")
 		.jqGrid({
@@ -444,5 +479,5 @@
 				}
 			},
-			caption: '<span class="'+selectedFolder.class+'">'+selectedFolder.name+'</span>'
+			caption: '<span class="'+selectedFolder['class']+'">'+selectedFolder.name+'</span>'
 		});
 		//.jqGrid('navGrid','#message_attacher_grid_pager',{edit:false,add:false,del:false});
Index: /trunk/expressoMail1_2/index.php
===================================================================
--- /trunk/expressoMail1_2/index.php	(revision 5229)
+++ /trunk/expressoMail1_2/index.php	(revision 5283)
@@ -18,4 +18,8 @@
 	//jquery and Editor 
  	        echo "  <script src='../library/jquery/jquery.js' language='javascript'></script> 
+					<script src='../prototype/app/plugins/json2/json2.js' language='javascript'></script>
+					<script src='../prototype/app/plugins/ejs/ejs.js' language='javascript'></script>
+					<script src='../prototype/app/plugins/store/store.js' language='javascript'></script>
+					<script src='../prototype/app/datalayer.js' language='javascript'></script>					
  	                <script src='../library/ckeditor/ckeditor.js' language='javascript'></script> 
  	                <script src='../library/ckeditor/adapters/jquery.js' language='javascript'></script> 
@@ -36,5 +40,5 @@
 
 	echo '
-	<script type="text/javascript" src="../prototype/app/plugins/view/jquerymx-1.0.custom.min.js"></script>
+	
 	<script type="text/javascript" src="../prototype/app/plugins/treeview/jquery.treeview.js"></script>
 	<script type="text/javascript" src="../prototype/app/plugins/block/jquery.blockUI.js"></script>
@@ -298,4 +302,7 @@
 </head>
 <body scroll="no" style="overflow:hidden">
+
+<link rel="stylesheet" href="../prototype/modules/attach_message/attach_message.css" type="text/css" />
+
 </body>
 </html>
Index: /trunk/expressoMail1_2/js/draw_api.js
===================================================================
--- /trunk/expressoMail1_2/js/draw_api.js	(revision 5280)
+++ /trunk/expressoMail1_2/js/draw_api.js	(revision 5283)
@@ -3538,5 +3538,6 @@
 
 	jQuery(add_msgs).click(function(event){
-		jQuery('#message-attach-dialog').html("../prototype/modules/attach_message/attach_message.ejs", {data:{}}).dialog({
+		jQuery('#message-attach-dialog').html(DataLayer.render("../prototype/modules/attach_message/attach_message.ejs", {}));
+		jQuery('#message-attach-dialog').dialog({
 			width:920,
 			height:550,
