A design philosophy
Inhoud
  1. Gestructureerde aanpak
  2. Lagen en interfaces
  3. DOM interface
  4. Concrete code voorbeelden
  5. Singleton classes deel 1
  6. De status persitence module
  7. Een model van de server event handling
  8. Algemene opbouw en werking van een javascript singleton class
  9. Singleton class als Factory class


Gestructureerde aanpak

Om een webapplicatie te ontwerpen die goed werkt en waarvan de code makkelijk is aan te passen en te onderhouden moet de onderliggende structuur eenduidig zijn, vandaar dat design patterns steeds meer gebruikt worden in javascript/PHP setups.

Een codebase is als een fundering : als de basis niet deugt, wordt het ook schipperen met alles wat je erop bouwt, en echt solide zal het nooit worden.
We willen clientside en serverside objecten ontwerpen die op de juiste plek doen wat ze moeten doen :

  1. data verificeren
  2. data routen
  3. data verwerken
  4. data registreren
  5. data opslaan
  6. acties verificeren
  7. acties routen
  8. acties verwerken
  9. acties registreren
  10. acties opslaan

Er is ook een statusobject nodig dat de acties van de user en de status van de applicatie vastlegt. Dit object houdt op de client en op de server de stand van zaken bij en gebruikt als dataopslag :

  1. server tijdelijke opslag : sessions
  2. server permanente opslag : database of flat files
  3. client tijdelijke opslag : javascript objecten of URI-component
  4. client permanente opslag : cookies

Het statusobject verwerkt en registreert acties en synchroniseert de actie-opslag op server en client. Useracties kunnen ook tijdens een sessie in een stack worden opgeslagen. Op die manier is het ook mogelijk useracties te manipuleren (undo/redo functies).
Dit object past in het commander design pattern.


Lagen en interfaces

Een verder uitgewerkt diagram hieronder laat de samenhang zien tussen de verschillende lagen en interfaces in een uitgebreide codebase.
Wat betreft het document kun je vier lagen benoemen :

  1. structuur layer : css, hier wordt de paginastructuur vastgelegd, alleen afmetingen en posities
  2. design layer : css, hier wordt het uiterlijk vastgelegd, meest classes
  3. content layer : alleen text, de inhoud van de structuur elementen
  4. application layer : javascript, hier de code die communiceert met de pagina en de server

Met deze filosofie in gedachten wordt met veel eenvoudiger om met een team van designers en programmeurs parallel aan één project te werken. Zie wat dit betreft ook Native PHP Templating

Deze opbouw past ook in het populaire Model View Presenter design pattern.
De interfaces passen in het facade-pattern, en de abstraction layer past in het adapter pattern.

Meer informatie Design patterns zijn natuurlijk geen wondermiddel, het gaat erom ze goed toe te passen. Vaak zijn ze de resultante van een refactoringsproces en ze kunnen behulpzaam zijn bij het in kaart brengen van data- en actiestromen.

Er kunnen meerdere application layers zijn, elke layer of module communniceert via custom events. Dit zorgt voor een "loose coupling" waardoor code beter te onderhouden en uit te breiden is, zie ook Message Broker



DOM interface Als we de DOM interface nader beschouwen zijn er verschillende delen te onderscheiden :

  1. onload inline script : De server schrijft inline javascript in de header om data en acties door te geven
  2. onload init : de javascript module (singleton class) initieert hier, met informatie uit de vorige interface
  3. de DOM abstraction layer, een facade-pattern dat browserverschillen elimineert (jquery, prototype, x library)
  4. namespaced events : een connector tussen DOM interface en document. De applicatie is ook ge-namespaced zodat er geen conflicten ontstaan in de global namespace






Concrete code voorbeelden :

De codevoorbeelden zijn niet compleet, ze dienen ter illustratie.

De modules (of singleton classes)zijn elk in een aparte .js-file ondergebracht.
De class "index.js" initieert ONLOAD de DOM abstraction layer en de custom event interface. Er is ook een globale class die onderdeel uitmaakt van de ONLOAD inline script interface, en een globale helperclass waarin algemene assisterende functies een plaats krijgen


vb1 : Onload initiatie van modules en interfaces

       
//INIT ON LOAD===========================================================
/*CLASS : INDEX.JS*/

		$(document).ready(function(){
		
			$.extend(Util);//tools object
			$.extend(RequestManager);//xhr object
			$.logInit();//logger
			
			application.init();
			
			//CUSTOM EVENT INTERFACE
			application.addListener('application_submit', DataProcessor.handler);
			application.addListener('application_submit', Dummy.handler);
			dummy.addListener('applicationfield_save', Updater.handler);
			
			//CONNECTOR  : APPLICATION <==> SYNC HTTPREQUEST SERVER VIA INLINE SCRIPT
			if(!GL_firstload){
			 application.startPolling(4000);
			}
			
		});
		
//================================================================
		
//GLOBAL ONLOAD INTERFACE
var GL = (function(){

    this.load = true;
    this.status=''; 
})();

//HELPERCLASS
var HL = (function(){

    var customAlert = function(event){
            var msg = '';
            
            msg+=   'receiver : '+event.receiver +', \ntype: ' + event.type;
            msg+=   ',  \nresults: ' + event.results.data + ', \nsender : ';
            msg+=   event.sender + ', \nidentifier : '+ event.target.identifier;
            
            alert(msg );
    	
    };
    
})();
        


Singleton classes deel 1

Alle modules (of singleton classes) hebben dezelfde opbouw :

  1. - private static vars en constants declaratie
  2. - private static methods, en wel een event receiver (custom en namespaced), een xhr-interface met event sender, meerdere interne methods
  3. - public static methods als interface met andere modules

vb 2 : Verschillende classes die deel uitmaken van de totale codebase :

/*APPLICATION.JS*/        
        
var application = (function(){
	        //BEGIN CLASS APPLICATION
			//PRIVATE METHODS EN VARS
			
			//private constants
			var CNT_DISPLAY = 'melding',
				CNT_ALERT = 'alert',
				MSG_ERROR = 'Dit is een custom foutmelding',
				MSG_SUCCESS = 'Dit is een custom succes-melding',
                
			var bRequestActive=false,//true als er nog XHR-request gaande is
				oForm,//referentie naar het  formulier, zonder declaratie 
                wordt oForm global
				oConnect = new objEventTarget();//event-interface object
			
			var handler =  {//EVENT RECEIVER
			
				process : function(event) {
					var t=event.target;
					switch(event.type)
					{
						case 'click' :
							//ACTIONS
						break;
					}
				}
			};
			
			var output = { //XHR-adapter to VIEW
			
				setResult : function(oResponse, sStatus) {
					//INTERFACE TO VIEW
				},
				
				setNotify : function(oResponse, sStatus) {
					//INTERFACE TO VIEW
				}
			};
			
			//XHR INTERFACE  APPLICATION <==> SERVER
			var XHR = {
				
				//req 01 ------------------------------------------
				doSave:function(t) {
					
					var elemId=t.getAttribute('rel');
					bRequestActive=true;
					$.getForm(oForm,elemId);//sla element op voor submit
					$.submit({
						type 			: "post",
						url 			: 'xhr/element_save.php',
						onsuccess 		: this.saveSuccessResult,
						onfailure 		: this.saveFailureResult,
						scope			: o //callbackscope
				
					});
				},
				
				saveSuccessResult : function(oResponse) {
					this.trigger( {//CUSTOM EVENT SENDER
						type		: 'applicationfield_save',
						sender		: o.identifier,
						results		: oResponse					
					});
					output.setResult(oResponse, MSG_SUCCESS);
                    //logger
					$.echo(oResponse, true,'form.js saveSuccessResult regel 105',10);
				},
			
				saveFailureResult : function(oResponse) {
					//doError(); //acties onerror
					output.setResult(oResponse, MSG_ERROR);
				},
				
				//req 02 ----------------------------------------------
				addPollUpdater:function() {
					
					$.getForm(oForm);//sla element op voor submit
					$.addPoll({
						type 			: "post",
						url 			: 'xhr/getpolldata.php',
						onsuccess 		: this.pollSuccessResult,
						onfailure 		: this.pollFailureResult,
						scope			: o //callbackscope
				
					});
				},
				
				pollSuccessResult : function(oResponse) {
					this.trigger( {//CUSTOM EVENT SENDER
						type		: 'poll_update',
						sender		: o.identifier,
						results		: oResponse,
						success		: true
					});
				},
			
				pollFailureResult : function(oResponse) {
					this.trigger( {//CUSTOM EVENT SENDER
						type		: 'poll_update',
						sender		: o.identifier,
						results		: oResponse,
						success		: false
					});
				}
			};
			
			//PUBLIC INTERFACE-----------------------------------
			var o={
				identifier : 'application',
				
				init : function(){
				
					oForm=$.getRefForm(0);//haal referentie naar formulier op
					
					//NAMESPACING CLICK EVENT
					$(oForm).bind('click.application', handler.process);
				},
				
				submit:function(obj){
					//ACTIONS
				},
				
				startPolling:function(interval){
					//ACTIONS
				}
				
			};
			$.extend(o,oConnect);//custom events connector
			return o;
			//END CLASS APPLICATION--------------------------------------------------
		})();

        



Overige modules :

/*CLASS DATAPROCESSOR.JS*/        
        
var DataProcessor = (function(){
	var o = {
		receiver : 'DATAPROCESSOR',
		handler	: function(event){
			//pickup event
			event.receiver=o.receiver;
			HL.customAlert(event);//test		
		}	
	};
	return o;
})();

/*CLASS UPDATER.JS*/

var Updater = (function(){
	var o = {
		receiver : 'UPDATER',
		handler	: function(event){
			event.receiver=o.receiver;
			HL.customAlert(event);//test	
		}	
	};
	return o;
})();

/*CLASS DUMMY.JS*/

var Dummy = (function(){
	//PRIVATE CONSTANTS
	
	//PRIVATE VARS
	var oConnect = new objEventTarget();//event-interface object
	
	//PRIVATE METHODS
	var _handler = {
	    
	    process : function(event){
	        
	       //HANDLE CUSTOM OR NAMESPACES EVENTS   
	    }
	};
	
	
	
	var _showMSG = function(elem){
	    //INTERFACE MET VIEW VIA DOM ABSTRACTION LAYER JQUERY
		$('#test').css(oCss).slideDown('slow').click(function(){
			$(this).fadeOut('slow',function(){
				$(this).detach();
			});
				
		});
	};
	
	//XHR INTERFACE  DUMMY <==> SERVER ASYNC HTTP REQUEST  (AJAX)
	var _XHR = {
	
		init : function(){
			var CONTAINER='#output_result',
				LOADING  = '#loaderbar';
                
			//VIEW ELEMENTS	
			uitvoer=$(CONTAINER);
			loading=$(LOADING);
		},
		
		send:function(t) {
					
			//XHR INTERFACE WITH CALLBACK TO VIEW
		
	};
	
	
	//STATUSINTERFACE DMV URI-HASH , COOKIE EN XHR-REQUEST :  DUMMY <==> BROWSER DATASTORAGE
	var _hash = {
	
		load : function(forceload,sender){
		    
		    //CONNECTOR SYNC HTTP REQUEST SERVER ONLOAD  DUMMY <==> INLINE SCRIPT 
			if( RELOAD[GL.status] ){
				//page is allowed for caching
				//on reload haal hash op uit cookie
				if(GL.load && PERSIST){
					if(Util.getCookie('hash_'+GL.status)){
						window.location.hash=Util.getCookie('hash_'+GL.status);
						GL.load=false;
					}
				}
				//get sender
				if(sender){
					_source.set(sender);
				}
				else if(_source.get()){
					sender=_source.get();
				}
				else {
					sender='load';
				}
				//load page if active hash changes
				if(uitvoer){
					var uri,value;
                    
					//PERMANENT CLIENT MEMORY
					value=window.location.hash;
					if(value != '' && typeof(value)!='undefined'){
						if(Util.getCookie('hash_'+GL.status)){
							Util.unsetCookie('hash_'+GL.status);
						}
						Util.setCookie('hash_'+GL.status,value);//cache
					}
					//STATUSINTERFACE  DUMMY <==> SERVER  AJAX
					_XHR.send("&sender="+sender+uri.value);		
					
					oHash=uri.active;//cache alleen deel met cut=...
				}
				setTimeout(function(){
						XHRhistory.loadHash();
					}, INTERVAL);
			}
		},
		
		init : function(){
			//INIT
			
            //ONLOAD init interface
			if(typeof(GL.status)==='undefined'){
				GL.status='init';
			}
		},
		
		//MORE PUBLIC METHODS HERE
	
	};
	
	
	
	//PUBLIC INTERFACE--------------------------------
	var o = {
		identifier : 'dummy',
		receiver : 'dummy',
		
		//CUSTOM EVENT RECEIVER
		handler	: function(event){
			//pickup event
			event.receiver=o.receiver;
			_handler.process(event);		
		},	
		
		//CUSTOM EVENT SENDER
		shoot : function(elem){
		
			this.trigger({//trigger event met custom object
						type		: 'applicationfield_save',
						sender		: 'shoot '+elem.value,
						results		: {data : 'aaldert'},
						success		: true
					});
					
			//ACTION TO VIEW	
			_showMSG(elem);
			
		}
	
	};
	$.extend(o,oConnect);//CUSTOM EVENT CONNECTOR OBJECT KOPPELEN
	return o;

})();

        


De status persitence module Hieronder een verdere uitwerking van de status persistence module.
De module heeft een client gedeelte en een server gedeelte, die met elkaar communiceren via XHR, URI-component en cookies.
Tijdens de sessie wordt de status opgeslagen in de URI component, cookies en PHP $_SESSION. Statuspersistentie op de langere termijn kan via cookies en de database. Op die manier kan eenvoudig usergericht statusbeheer geimplementeerd worden.

>> Zie ook : AJAX Status Persistentie





Een model van de server event handling

Event-driven programmeren op de server heeft voordelen : loose coupling van objecten is er één van. De data en actions van de client moeten gevalideerd worden door het Secure Transfer object (facade pattern), daarna moeten data en actie gerout worden naar de juiste receiver. Dit past in het observer pattern.

De event handler verwerkt de events (observer pattern) en zorgt ervoor dat de juiste handelingen uitgevoerd worden, (factory pattern, lazy initialization).
>> Zie ook : safeURI -> PHP eventhandler >> Zie ook : Native PHP Template






Algemene opbouw en werking van een javascript singleton class :

Introductie

Een singleton class heeft als kenmerk dat er maar één instantie van bestaat in de global namespace. Door gebruik te maken van een closure die zichzelf aanroept wordt er een omgeving gecreëerd die verborgen blijft vanuit de global scope. Door het returnen van een interface-object kunnen andere scripts met de singletonclass (of module) communiceren.

Door de opbouw zoals hieronder geschetst wordt kan er eenvoudig een codebase gebouwd worden bestaande uit verschillende modules die via public interfaces en custom event interfaces loosly coupled zijn. De kans op namespace conflicten wordt aanzienlijk kleiner.

Binnen de modules kunnen andere applicatie-objecten op een veilige controleerbare manier worden geïnstantieërd en worden gereturned. Door het statische karakter van een singleton class kan de status van de verschillende applicatie-objecten eenvoudig worden bijgehouden.



var SingletonModule = (function(){

            //constants
            var INIT_TAB         = 1;
                ANOTHER_CONSTANT = 'value';
            
            //private vars
            var doc,
                root = $.getLocation().root,
                $form = $('#form');
            
            //DEFAULT OPTIONS INTERFACE  FOR CONFIGURATION AND CALLBACK FUNCTIONS DECLARATION       
            var oOptions = {
                option01        : {},
                onComplete      : function(){},//CUSTOM EVENT INTERFACE:RECEIVER
                option03        : true,
                option04        : false   
            };
            
            //APPLICATIE STATUS
            var rank = 0;
                aApplicaties = [];
            
            //dynamic dom interface constructor : MODEL->VIEW
            function Nodes(tab){
                this.tab=parseInt(tab);
                this.input=$('#txt'+this.tab+'Input');
                this.output=$('#txt'+this.tab+'Output');
                this.clear=$('#cmd'+this.tab+'Clear');
                this.fill=$('#cmd'+this.tab+'Fill');
                this.copy=$('#cmd'+this.tab+'Copy');  
            };
            
            //APPLICATION FACTORY
            function ApplicatieObject(obj){
                this.init=obj;
                this.method=function(){//more code here};
                //more methods an properties here
                aApplicaties.push(this);
                rank++; 
            }
            
            //XHR-INTERFACE CLIENT <--> SERVER  (MODEL SERVER <--> MODEL CLIENT)
            var XHR = {
			
                submit : function(obj){
                    obj= typeof(obj)==='undefined' ? oData.obj:obj;
                    $.submit({
                        type 			: obj.type,
                        url 			: obj.url,
                        data			: 'd='+obj.data,
                        onsuccess 		: this.submitSuccessResult,//200
                        onfailure 		: this.submitFailureResult,//404 ea
                        scope           : self       
                    });
                },
                        
                submitSuccessResult : function(oResponse){
                    //xhr-interface server->client
                    //CUSTOM EVENT INTERFACE:SENDER
                    this.trigger('onComplete', oResponse);
                    return true;
                },
                        
                submitFailureResult : function(oResponse){
                    //xhr-interface server->client
                    return false;
                }
            };

            //private internal helper methods
            var _hlp ={
            
                base64encode : function(safe){
                    //more code here
                    doc.output.val(result);
                },
                base64decode : function(safe){
                    var value=doc.output.val();
                    //more code here
                },
                clear : function(){
                    //more code here
                }
            };
            
            var _unloadDomElements = function(){
                //actions here
            };
            var _setValue = function(){
                //actions here
            };
            var _getValue = function(){
                //actions here
            };
            
            
            //INTERFACE CONTROLLER <--> MODEL
            $form.bind('tabsselect', function(event, ui) {

                doc= new Nodes(tab);//load dom-interface for this tab

                switch(tab){
                    case 1 :
                        //MODEL ACTIONS
                        break;
                    case 2 :
                        //MODEL ACTIONS
                        break;
                    
                }//end switch
                
                //clear button
                doc.clear.click(function(event){
                    _hlp.clear();   
                });
                //load testsample button
                doc.fill.click(function(event){
                    doc.input.val(doc.input.sample);
                });
                //copy button
                doc.copy.click(function(event){
                    doc.input.val( doc.output.val() );
                });
                
            });//end tabselect event handler
            
            //INTERFACE OBJECT, COMMUNICATIE MET ANDERE MODULES 
            //public methods
            var self = {
            
                setValue : function(value){
                    if(_check(value)){
                        _setValue();
                    }
                },
                getValue : function(){
                    _getValue();
                },
                setOptions : function(options){
                    $.extend(oOptions, options);
                },
                initApplication : function(obj){
                    return new Application(obj);
                },
                destroy :function(){
                    _unloadDomElements();
                }
            };
            return self;

            //ONLOAD INTERFACE MODEL -> VIEW
            doc= new Nodes(INIT_TAB);//load dom-interface for INIT_TAB
            $form.trigger('tabsselect',INIT_TAB);
 
})();
        


Singleton class als Factory class Voorbeeld van een toepassing van het factory design pattern via een singleton class

De WidgetFactory class is een voorbeeld van een factory class met een private constructor, geschikt voor het initiëren van objecten in een secure production environment. De omgeving bepaalt welk object geïnstantieë wordt, maar kan de methods en properties van de factory niet overschrijven. De status van elke widget is intern binnen de factory eenvoudig te monitoren.

Onderstaande code is alleen bedoeld ter illustratie.

Meer informatie

Widget container
De geproduceerde widgets worden hier aan het document gekoppeld. Deze widget heeft twee methods :

public Widget methods

  1. setOptions : widget-options aanpassen / toevoegen
  2. getOptions : Het options-object tonen via de logger

public Factory methods:

  1. Init(container) : Initieert factory en koppelt widgets aan view
  2. createWidget(options) : Creëren van een niewe widget-instance
  3. getCount() : returns het aantal widgets
  4. destroyWidget(id) :Een widget destroyen en loskoppelen van de DOM-view


Factory controller
Create Widget Show all Widgets Delete Widget no 2

De widgets :




De code :

var WidgetFactory = (function(){
    
    //PRIVATE CONSTANTS
    
    //DOM controller/view constants
    var BTN          = '#create',
        SHOW         = '#show',
        DELETE       = '#delete',
        MESSAGE      = '#message';
        
    //widget constants   
    var WIDGETNAME   = 'widget_',
        WRAP         = 'wrap_',
        BTN01PREFIX  = 'btnSetEngine_',
        BTN02PREFIX  = 'btnOptions_',
        BTN03PREFIX  = 'btnDestroy_',
        BTN01CAPTION = 'Set SearchEngine',
        BTN02CAPTION = 'Show options',
        BTN03CAPTION = 'Destroy this Widget';
        
    //PRIVATE PROPERTIES
    var _rank        = 0,
        _doc        = {},
        _widgets     = {};
    
    //PRIVATE DOM INTERFACE CONSTRUCTOR
    function Nodes(){
        this.btn_create  = $(BTN);
        this.btn_show    = $(SHOW);
        this.btn_delete  = $(DELETE);
        this.cnt_message = $(MESSAGE);
    }
    
    //PRIVATE WIDGET CONSTRUCTOR
    function Widget(options){
    
        //private properties
        var  _options = {
                test         : true,
                searchengine : 'google',
                query        : 'factory pattern',
                onCreate     : function(event){},
                onDestroy    : function(event){},
                btn01Prefix  : BTN01PREFIX,
                btn02Prefix  : BTN02PREFIX,
                btn03Prefix  : BTN03PREFIX,
                btn01Caption : BTN01CAPTION,
                btn02Caption : BTN02CAPTION,
                btn03Caption : BTN03CAPTION
            };
        
        //public properties
        this.constructor = 'WidgetFactory';
        this.identifier = _rank;
        
        $.extend(_options, options);
        //widget methods
        this.getID = function(){
            return this.identifier;
        };
        this.getOptions = function(){
            return _options;
        };
        this.setOptions = function(options){
            $.extend(_options, options);
        };
        
        //create controller-----------------------------
        var $controller,
            $btn01,
            $btn02,
            $btn03,
            $input;
            
        //elements
        $controller=$(
            '<div class="app" style="margin-bottom:10px;" id='+WRAP+this.identifier+
            '><span>widget_'+this.identifier+'&nbsp;:&nbsp;</span></div>'
        );        
        $btn01=$('<a href="" class="button m10" id="'+_options.btn01Prefix+this.identifier+
            '">'+_options.btn01Caption+'</a>');
        $btn02=$('<a href="" class="button m10" id="'+_options.btn02Prefix+this.identifier+
            '">'+_options.btn02Caption+'</a>');
        $btn03=$('<a href="" class="button m10" id="'+_options.btn03Prefix+this.identifier+
            '">'+_options.btn03Caption+'</a>');
        $input=$('<input type="text" name="searchengine_'+this.identifier+'" value="'+
            _options.searchengine+'"/>');
        
        //events
        $btn01.click(function(event){
            event.preventDefault();
            var thisWidget = _factory.getWidget(this.id),
                value      = thisWidget.controller.input.val();
                
            thisWidget.setOptions({
               searchengine : value 
            });   
        });
        $btn02.click(function(event){
            event.preventDefault();
            var thisWidget = _factory.getWidget(this.id);
            $.echo(thisWidget.getOptions(),false,'widget_'+thisWidget.identifier+' options');   
        });
        $btn03.click(function(event){
            event.preventDefault();
            _factory.destroyWidget(this.id); 
        });
        
        //attach
        $controller .append($input)
                    .append($btn01)
                    .append($btn02)
                    .append($btn03);
        $controller.selector='id_'+this.identifier
        
        //store controller reference to widget
        $controller.input=$input;
        this.controller=$controller;
        
        //store reference to widget
        this.self=$(this);
        
        //events
        this.self.bind('onCreate', _options.onCreate)
                 .bind('onDestroy', _options.onDestroy);
        //end controller--------------------------------
        
        //store widget for reference and tracking
        _widgets[WIDGETNAME+this.identifier] = this;
        _rank++;        
        //actions after widget is created
        this.self.trigger('onCreate');
    }
    
    //PRIVATE METHODS
    var _factory = {
    
        getID : function(id){
        
            if(typeof(id)==='string'  && $.in_str('_', id)){
                id=id.split('_')[1];
            }
            return id;
        },
        
        getWidget : function(id){
        
            if( typeof(id)!=='undefined' ){
               id=this.getID(id);
                return _widgets[WIDGETNAME+id];
            }
            else{
                return _widgets;
            }         
        },
        
        countWidgets : function(){
        
            return this.length;
        },
        
        createWidget : function(options){
        
            var thisWidget = new Widget(options);
            //connect widgetcontroller to view
            _doc.container.append(thisWidget.controller);
            return thisWidget;
                
        },
        
        destroyWidget : function(id){
        
            id=this.getID(id);
            var thisWidget=_widgets[WIDGETNAME+id];
            thisWidget.self.trigger('onDestroy');
            //remove obj from widgets-object and from DOM
            thisWidget.controller.detach();
            delete _widgets[WIDGETNAME + thisWidget.identifier];
            return true;
        }
    };

    //PUBLIC METHODS
    var self = {

        init : function(container){
        
            //dom interface
            _doc = new Nodes();
            _doc.container=$(container);
            
            //factory controller
            _doc.btn_create.click(function(event){
            
                event.preventDefault();
                _factory.createWidget({
                
                    searchengine     : 'yahoo',
                    query            : 'design patterns',
                    onCreate         : function(event){
                    
                        _doc.cnt_message.html('widget_'+this.identifier+' is created')
                        $.doShow(_doc.cnt_message[0],2000);
                    },
                    onDestroy        : function(event){
                    
                        alert('Widget_'+this.identifier+
                        ' famous last words : I\'m about to be destroyed');//test
                    }
                });   
            });
            
            _doc.btn_show.click(function(event){
            
                event.preventDefault();
                $.echo(_factory.getWidget(),false,'show all widgets line 192',3,
                        ['self','controller','input']);//test
            });
            
            _doc.btn_delete.click(function(event){
            
                event.preventDefault();
                try{
                    _factory.destroyWidget(_widgets.widget_2.identifier);
                    _doc.btn_delete.html('Widget_2 deleted');//test
                }
                catch(e){}
            });
        },
        createWidget : function(options){
        
            return _factory.createWidget(options);
        },
        destroyWidget : function(id){
        
            return _factory.destroyWidget(id); 
        },
        getCount : function(){
        
            return _factory.countWidgets();
        }  
    };
    return self;

})();