1  /*
  2   * Author: Sierk Hoeksma. WebBlocks.eu
  3   * Copyright 2007-2008, WebBlocks.  All rights reserved.
  4   *
  5   * This plugin used to edit a panel
  6   ************************************************************************************
  7   *   This file is distributed on an AS IS BASIS WITHOUT ANY WARRANTY;
  8   *   without even the implied warranty of MERCHANTABILITY or
  9   *   FITNESS FOR A PARTICULAR PURPOSE.
 10   ************************************************************************************
 11 
 12   License: This source is licensed under the terms of the Open Source LGPL 3.0 license.
 13   Commercial use is permitted to the extent that the code/component(s) do NOT become
 14   part of another Open Source or Commercially licensed development library or toolkit
 15   without explicit permission.Full text: http://www.opensource.org/licenses/lgpl-3.0.html
 16 
 17   * Donations are welcomed: http://donate.webblocks.eu
 18   */
 19 
 20 Ext.override(Ext.form.Label, {   
 21     onRender : function(ct, position){
 22         if(!this.el){
 23             this.el = document.createElement('label');
 24             this.el.innerHTML = this.text ? Ext.util.Format.htmlEncode(this.text) : (this.html || '');
 25             if(this.forId){
 26                 this.el.setAttribute('htmlFor', this.forId);
 27             }
 28             //Swap the ids, so it becomes selectable in designer
 29             this.el.id = this.id;
 30             this.id = this.id + '-';
 31         }
 32         Ext.form.Label.superclass.onRender.call(this, ct, position);
 33     }
 34 });
 35 
 36 Ext.ux.IFrameComponent = Ext.extend(Ext.BoxComponent, {
 37     onRender : function(ct, position){
 38         var url = this.url;
 39         url += (url.indexOf('?') != -1 ? '&' : '?') + '_dc=' + (new Date().getTime());
 40         this.el = ct.createChild({tag: 'iframe', id: 'iframe-'+ this.id, frameBorder: 0, src: url});
 41     }
 42 });
 43 Ext.reg('iframe', Ext.ux.IFrameComponent);
 44 
 45 Ext.namespace('Ext.ux.tree');
 46 Ext.ux.tree.JsonTreeLoader = Ext.extend(Ext.tree.TreeLoader,{
 47  /**
 48   * Create node but enabling childeren from Json
 49   */
 50   createNode : function(attr){
 51     var childeren = attr.childeren;
 52     delete attr.childeren;
 53     if(this.baseAttrs){
 54         Ext.applyIf(attr, this.baseAttrs);
 55     }
 56     if(this.applyLoader !== false){
 57         attr.loader = this;
 58     }
 59     if(typeof attr.uiProvider == 'string'){
 60        attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
 61     }
 62     if (!childeren) {
 63       return(attr.leaf===false ? 
 64             new Ext.tree.AsyncTreeNode(attr) : 
 65             new Ext.tree.TreeNode(attr) );
 66     } else {
 67       var node = new Ext.tree.TreeNode(Ext.applyIf(attr,{draggable:false}));
 68       for(var i = 0, len = childeren.length; i < len; i++){
 69        var n = this.createNode(childeren[i]);
 70        if(n) node.appendChild(n);
 71       }
 72       return node;
 73     }
 74   }
 75 });
 76 
 77 Ext.namespace('Ext.ux.plugin');
 78 
 79 /*
 80 Ext.ux.plugin.DesignerWizard = function(json){
 81   var cache = {};
 82   return function(callback) {
 83      if (!cache[json]) {  
 84       cache[json] = new Ext.ux.JsonPanel({autoLoad:json,updateOwner:true});
 85      }
 86      cache[json].callback = callback;
 87      (new Ext.Window(cache[json])).show();
 88   }
 89 }
 90 */
 91 /**
 92  * FileControl
 93  */
 94 Ext.ux.plugin.FileControl = function(config) {
 95   Ext.apply(this,config);
 96   Ext.ux.plugin.FileControl.superclass.constructor.call(this);
 97   this.init();
 98 }
 99 
100 Ext.extend(Ext.ux.plugin.FileControl,Ext.util.Observable,{
101   files : {},
102   last  : null,
103   activeNode : null,
104   
105   init : function(){
106     this.refreshFiles();
107   },
108   
109   refreshFiles : function (callback) {
110     this.files = this.files || {};
111     if(typeof callback == "function") callback(true);
112   },
113   
114   saveChanges : function(id,action,callback,content) {
115     this.files[id] = id;
116     if (action=='delete') {
117       delete this.files[id];
118       if (id==this.last) this.last = null;
119     } else {
120       this.last = id;
121     }
122     if(typeof callback == "function") callback(true);
123   },
124 
125   openFile : function(id,callback,content) {
126     this.last = id;
127     if(typeof callback == "function") callback(true,content);
128   },
129   
130   
131   deleteFile : function(id,callback){
132     this.saveChanges(id,'delete',callback);
133   },
134   
135   renameFile : function(fileFrom,fileTo,callback){
136     var last = this.last;
137     this.openFile(fileFrom,function(success,content) {
138       if (success) {
139          this.saveChanges(fileTo,'save',function(success){          
140            if (success) {              
141               this.deleteFile(fileFrom,function(success){
142                 if (success && last==fileFrom) this.last=fileTo;
143                 if(typeof callback == "function") callback(success);
144               }.createDelegate(this));
145            } else if(typeof callback == "function") callback(success);
146          }.createDelegate(this),content);
147       } else if(typeof callback == "function") callback(success);
148     }.createDelegate(this));
149   },
150   
151   saveFile : function(id,content,callback){
152     this.saveChanges(id,'save',callback,content);
153   },
154   
155   newFile  : function(id,content,callback){
156     this.saveChanges(id,'new',callback,content);
157   },
158   
159   load : function(node, callback,refresh){ 
160    if (refresh) {
161      this.refreshFiles(function(){
162         this.loadNodes(node,false,callback);
163       }.createDelegate(this));
164    } else {
165      this.loadNodes(node,false,callback);
166    }
167   },
168   
169   loadNodes : function(node,append,callback){
170     this.activeNode = null;
171     if (!append) while(node.firstChild) node.removeChild(node.firstChild);
172     node.beginUpdate();
173     for (var f in this.files){
174         var file = this.files[f];
175         var path = f.split('/');
176         var name = '';
177         var cnode = node;
178         var n;
179         for (var i=0;i<path.length;i++) {
180            name += path[i];
181            n=null;
182            for (var j=0,c=cnode.childNodes;j<c.length && !n;j++) {
183              if (c[j].attributes.text==path[i]) n = c[j];
184            }
185            if (!n) {
186              var leaf = (i==path.length-1);             
187              n = new Ext.tree.TreeNode({
188                      text: (name==this.last ? '<B>' + path[i] + '</B>' : path[i]),
189                      cls:  leaf ? 'file' : 'folder' , 
190                      leaf : leaf,
191                      id  : name
192              }); 
193              cnode.appendChild(n);
194              if (name==this.last) this.activeNode = n;
195            }
196            cnode = n;
197            name += '/' 
198         }
199     }
200     node.endUpdate();
201     if(typeof callback == "function")  callback(this.activeNode);   
202     return this.activeNode;      
203   }
204 
205 });
206 
207 
208 /*
209  * CookieFiles
210  */
211 Ext.ux.plugin.CookieFiles = Ext.extend(Ext.ux.plugin.FileControl,{
212   
213   init : function(){
214     this.cookies = new Ext.state.CookieProvider();     
215     Ext.ux.plugin.CookieFiles.superclass.init.call(this);
216   },
217   
218   refreshFiles : function (callback) {
219     this.files = this.cookies.get('Designer.files');
220     Ext.ux.plugin.CookieFiles.superclass.refreshFiles.call(this,callback);
221   },
222 
223   saveChanges : function(id,action,callback,content) {  
224     if (content) this.cookies.set('Designer/' + id,escape(content));
225     if (action=='delete') this.cookies.clear('Designer.'+id);
226     Ext.ux.plugin.CookieFiles.superclass.saveChanges.call(this,id,action,callback,content);
227     this.cookies.set('Designer.files',this.files);
228   },
229 
230   openFile : function(id,callback,content) {
231     content = unescape(this.cookies.get('Designer/' + id));
232     Ext.ux.plugin.CookieFiles.superclass.openFile.call(this,id,callback,content)
233   }
234     
235 });
236 
237 /*
238  * PHPFiles
239  */
240 Ext.ux.plugin.PHPFiles = Ext.extend(Ext.ux.plugin.FileControl,{
241   url : "phpFiles.php",  
242   baseDir : "json",
243     
244   refreshFiles : function (callback) {
245     Ext.Ajax.request({
246       url: this.url,
247       params: {
248          cmd: 'get_files',
249          baseDir: this.baseDir
250       },            
251       callback: function(options, success, response) {
252         this.files= success ? Ext.util.JSON.decode(response.responseText) : {};
253         if(typeof callback == "function") callback(success);
254       },            
255       scope: this        
256     });    
257   },
258 
259   saveChanges : function(id,action,callback,content) {  
260     Ext.Ajax.request({
261        url: this.url,
262        params: {
263          cmd: 'save_changes',
264          baseDir: this.baseDir,
265          filename: id,
266          action: action,
267          content: content
268        },
269        callback: function(options, success, response) {
270          if(success && response.responseText=='1') { 
271            if(action=='delete') {
272              delete this.files[id];
273              if (id==this.last) this.last = null;
274            } else {
275              this.last = id;
276            } 
277          }
278          if(typeof callback == "function") callback(response.responseText=='1');
279        },
280        scope: this        
281     }); 
282   },
283 
284   openFile : function(id,callback,content) {
285     Ext.Ajax.request({
286       url: this.url,
287       params: {
288         cmd: 'get_content',
289         baseDir: this.baseDir,
290         filename: id 
291       },
292       callback: function(options, success, response) {
293         if (success) this.last = id;
294         if(typeof callback == "function") callback(success,response.responseText);
295       },
296       scope: this        
297     }); 
298   }    
299 });
300 
301 
302 /** Create a desginer */
303 Ext.ux.plugin.Designer = function(config){
304   Ext.apply(this, config);
305   Ext.ux.plugin.Designer.superclass.constructor.call(this);
306   this.initialConfig = config;
307 };
308 
309 Ext.extend(Ext.ux.plugin.Designer, Ext.util.Observable, Ext.applyIf({  
310   
311   /**
312    * When true the toolbox is show on init
313    * @type {Boolean}
314    @cfg */
315   autoShow : true,
316   
317   /** 
318    * Should caching be disabled when JSON are loaded (defaults false).   
319    * @type {Boolean} 
320    @cfg */
321   disableCaching: false, 
322   
323   /**
324    * When toolboxTarget is set, this will be used to render toolbox to not window
325    * @type {String/Element}
326    @cfg */
327   toolboxTarget : false, 
328   
329   /**
330    * Url used to load toolbox json from defaults to <this.file>/Ext.ux.plugin.Designer.json
331    * @type {String} 
332    @cfg */
333   toolboxJson   : false,
334 
335   /**
336    * Enable or disable the usage of customProperties (defaults false). 
337    * When disabled only properties which are defined within Ext.ux.Designer.ComponentsDoc.json are available.
338    * @type {Boolean}
339    @cfg */    
340   customProperties  : false, 
341   
342   
343  //Menu buttons
344   /**
345    * Enable or disable the Copy menu button (defaults true).
346    * @type {Boolean}
347    @cfg */
348   enableCopy : true,
349   /**
350    * Enable or disable the Show menu button (defaults true).
351    * @type {Boolean}
352    @cfg */
353   enableShow : true,
354   /**
355    * Enable or disable the Edit Json menu button (defaults true).
356    * @type {Boolean}
357    @cfg */    
358   enableEdit : true,
359   /**
360    * Enable or disable the Help/Version information menu button (defaults true).
361    * @type {Boolean}
362    @cfg */    
363   enableVersion : true,
364   
365   /**
366    * An url specifing the json to load
367    * @type {Url}
368    @cfg */
369   autoLoad :  false,
370   
371   //@private Whe tag each json object with a id
372   jsonId :  '__JSON__',
373   
374   licenseText  :  "/* This file is created with Ext.ux.plugin.GuiDesigner */",
375    
376   //@private The version of the designer
377   version : '2.0.6',
378   
379   //@private The id for button undo
380   undoBtnId  : Ext.id(),
381 
382   //@private The id for button undo
383   redoBtnId  : Ext.id(),
384   
385   //@private The maximum number of undo histories to keep
386   undoHistoryMax : 20,
387   //@private The history for undo
388   undoHistory : [],  
389   //@private The marker for active undo
390   undoHistoryMark : 0,
391   
392   /**
393    * A file control config item
394    */
395   fileControl : null,
396    
397   /**
398    * Init the plugin ad assoiate it to a field
399    * @param {Component} field The component to connect this plugin to
400    */
401   init: function(field) {
402     Ext.QuickTips.init();
403     this.container = field;
404     this.jsonScope = this.scope || this.container;
405     
406     this.addEvents({
407       /**
408        * Fires before the toolbox is shown, returning false will disable show
409        * @event beforeshow
410        * @param {Object} toolbox The toolbox window
411        */
412       'beforeshow' : true,
413       /**
414        * Fires before the toolbox is hidden, returning false will cancel hide
415        * @event beforehide
416        * @param {Object} toolbox The toolbox window
417        */
418       'beforehide' : true,
419       
420       'add' : true,
421       
422       'remove' : true,
423       
424       'change' : true,
425       
426       'newconfig': true,
427       
428       'select'   : true,
429 
430       /**
431        * Fires after loadConfig fails
432        * @event loadfailed
433        * @param {Url} url The url tried to load
434        * @param {Object} response Response object
435        */      
436       'loadfailed' : false
437     });
438         
439     //Init the components drag & drop and toolbox when it is rendered
440     this.container.on('render', function() {    
441       this.drag = new Ext.dd.DragZone(this.container.el, {
442         ddGroup:'designerddgroup',
443         getDragData     : this.getDragData.createDelegate(this)
444       });
445       this.drop = new Ext.dd.DropZone(this.container.el, {
446         ddGroup:'designerddgroup',
447         notifyOver : this.notifyOver.createDelegate(this),
448         notifyDrop : this.notifyDrop.createDelegate(this)
449       });
450       this.container.el.on('click', function(e,el) {
451          var cmp = this.selectElement(el);
452          if (el.focus) el.focus();
453       }, this);
454       this.toolbox(this.autoShow);
455       this.createConfig();
456       this.initContextMenu();
457       // Check if whe have to load a external file
458       if (this.autoLoad) {
459        if (typeof this.autoLoad !== 'object')  this.autoLoad = {url: this.autoLoad};
460        if (typeof this.autoLoad['nocache'] == 'undefined') this.autoLoad['nocache'] = this.disableCaching;
461        this.loadConfig(this.autoLoad.url);
462       } 
463     }, this);
464   },
465     
466   initContextMenu : function () {
467     var contextMenu = new Ext.menu.Menu({items:[{
468           text    : 'Delete this element',
469           iconCls : 'icon-deleteEl',
470           scope   : this,
471           handler : function(item) {
472               this.removeElement(contextMenu.element);
473             }
474         }]});
475       this.container.el.on('contextmenu', function(e) {
476           e.preventDefault();
477           var el = this.getDesignElement(this.getTarget(e));
478           if (el) {
479             contextMenu.element = el;
480             contextMenu.showAt(e.getXY());
481           }
482       }, this);
483   },
484   
485   removeElement : function(source,internal) {
486     if (!source) return false;
487     var own = this.getContainer(source.ownerCt);
488     if (!internal) this.markUndo();
489     for (var i=0;i<own.items.length;i++) {
490       if (own.items.items[i]==source) {
491         if (!own.codeConfig) own.codeConfig = this.getConfig(own);
492         own.codeConfig.items.splice(i,1);
493         if (own.codeConfig.items.length==0) delete own.codeConfig.items;
494         if (!internal) {
495           this.fireEvent('remove');
496           this.redrawElement(own);
497         } else {
498           this.redrawContainer = true;
499         }
500         return true;
501       }
502     }    
503     return false;
504   },
505   
506   menuUpdate : function(){
507     var menu = Ext.getCmp(this.undoBtnId);
508     if (menu) if (this.undoHistoryMark>0) menu.enable(); else menu.disable();
509     menu = Ext.getCmp(this.redoBtnId);
510     if (menu) if (this.undoHistory.length>this.undoHistoryMark+1) menu.enable(); else menu.disable();
511   },
512   
513   markUndo : function() {
514     while (this.undoHistory.length>this.undoHistoryMark) this.undoHistory.pop();
515     this.undoHistory.push(this.encode(this.getConfig(),0,true));
516     while (this.undoHistory.length > this.undoHistoryMax) this.undoHistory.shift();
517     this.undoHistoryMark = this.undoHistory.length;
518     this.menuUpdate();
519   },
520   
521   undo : function(){
522     if (this.undoHistoryMark>0) {
523       if (this.undoHistoryMark==this.undoHistory.length) {
524         //Make sure whe have point to recover incase of redo
525         this.undoHistory.push(this.encode(this.getConfig(),0,true));
526         this.undoHistoryMark = this.undoHistory.length-1;
527       }
528       this.undoHistoryMark--;
529       this.setConfig(this.undoHistory[this.undoHistoryMark]);
530       this.menuUpdate();
531     }
532   },
533   
534   redo : function(){
535     if (this.undoHistory.length>this.undoHistoryMark+1) {
536       this.undoHistoryMark++;
537       this.setConfig(this.undoHistory[this.undoHistoryMark]);
538       this.menuUpdate();
539     }
540   },
541   
542   /**
543    * Append the config to the element
544    * @param {Element} el The element to which the config would be added
545    * @param {Object} config The config object to be added
546    * @return {Component} The component added
547    */
548   appendConfig : function (el,config,select,dropLocation,source){
549     if (!el) return false;
550     this.markUndo();
551     
552     //Custom function for adding stuff to a container
553     var add =  function(src,comp,at,before){
554       if(!src.items) src.initItems();
555       var pos = src.items.length;
556       for (var i=0;i<src.items.length;i++) {
557         if (src.items.items[i]==at) {
558           pos = (before) ? i : i+1;
559           i=src.items.length;
560         }
561       }
562       if (!src.codeConfig) src.codeConfig = this.getConfig(src);
563       if (!src.codeConfig.items || !(src.codeConfig.items instanceof Array)) 
564           src.codeConfig.items =  [];
565       delete src.codeConfig.html; //items and html go not together in IE
566       if (pos>src.codeConfig.items.length) 
567         src.codeConfig.items.push(comp)
568       else 
569         src.codeConfig.items.splice(pos, 0, comp);      
570     }.createDelegate(this);
571     
572 
573     if (typeof config == 'function') {
574       config.call(this,function(config) {
575         this.appendConfig(el,config,true);
576       }.createDelegate(this),this);
577     } else {
578      //Get the config of the items
579      var ccmp,cmp= this.getDesignElement(el,true);
580      var items = this.editableJson(this.deleteJsonNull(this.clone(config)));
581      //Find the container that should be changed
582      ccmp = this.getContainer(cmp); 
583      if (dropLocation == 'appendafter') {
584        add(ccmp,items,this.activeElement,false);      
585      } else if (dropLocation == 'appendbefore') { 
586        add(ccmp,items,this.activeElement,true);
587      } else if (dropLocation == 'moveafter') {
588        this.removeElement(source,true);
589        add(ccmp,items,this.activeElement,false);      
590      } else if (dropLocation == 'movebefore') { 
591        this.removeElement(source,true);
592        add(ccmp,items,this.activeElement,true);
593      } else if (dropLocation == 'move') {
594        this.removeElement(source,true);
595        add(ccmp,items);
596      } else // Append default behavior
597        add(ccmp,items);
598      this.modified = true;
599      this.fireEvent('add');
600      this.redrawElement(ccmp,items[this.jsonId]); 
601     } 
602     return false;
603   },
604 
605   /**
606    * Create the codeConfig object and apply it to the field
607    */
608   createConfig : function() {
609     if (this.container.items && this.container.items.first()) {
610        var items = [];
611        while (this.container.items.first()) {
612           items.push(this.container.items.first());
613           this.container.items.remove(this.container.items.first());
614        }       
615        //Re create a panel with items from config editable root
616        var config = { 'border' : false, 'layout' : this.container.getLayout(),'items' : this.editableJson(items)};
617        config[this.jsonId]=Ext.id();
618        var el = this.container.add(config);
619        el.codeConfig = config;
620     }
621   },
622   
623   /**
624    * Load a config from URL
625    * @param {Element} el The url to load
626    */
627   loadConfig : function (url) {
628     if (this.loadMask && this.container.ownerCt) 
629       this.container.ownerCt.el.mask(this.loadMsg, this.msgCls);
630      Ext.Ajax.request({
631       url: url,
632       method : 'GET',
633       callback: function(options, success, response){
634         if (success) {
635          this.setConfig(response.responseText);
636          this.modified = false;
637         } else {
638          if (!this.fireEvent('loadfailed',url,response))
639             Ext.Msg.alert('Failure','Failed to load url :' + url);
640         }
641         if (this.loadMask && this.container.ownerCt) 
642            this.container.ownerCt.el.unmask();
643       },
644       scope: this
645      });
646   },
647  
648   /** 
649     * Get the config as string of the specified element
650     * @param {Element} el The element for which to get the config object
651     * @return {String} The config string 
652     */
653   getCode : function(el) {
654    return this.encode(this.getConfig(el));
655   },
656   
657   /** 
658    * Get the config of the specified element
659    * @param {Element} el The element for which to get the config object
660    * @return {Object} The config object 
661    */
662   getConfig : function (el) {
663     el = el || this.container.items.first();
664     if (!el) return {};
665     if (!el.codeConfig && el[this.jsonId]) {
666       var findIn = function(o) {
667         if (!o) return null;
668         if (o[this.jsonId]==el[this.jsonId]) return o;
669         if (o.items) {
670           for (var i=0;i<o.items.length;i++) {
671             var r = findIn(o.items[i]);
672             if (r) return r;
673           }
674         }
675         return null;
676       }.createDelegate(this);
677       el.codeConfig = findIn(this.codeConfig)
678     } 
679     return el.codeConfig || el.initialConfig;
680   },
681   
682   /**
683    * Set the config to the design element
684    * @param {String/Object} json The json to be applied
685    * @return {Boolean} true when succesfull applied
686    */
687   setConfig : function (json) {
688     var id = this.activeElement ? this.activeElement[this.jsonId] : null;
689     var items = (typeof(json)=='object' ? json : this.decode(json)) || {};
690     if (!this.container.codeConfig) this.container.codeConfig = this.getConfig(this.container);
691     items = this.deleteJsonNull(items);
692     this.container.codeConfig.items=[this.editableJson(items)];
693     this.applyJson(items,this.container); //Recreate childs
694     this.redrawContainer=false;
695     this.modified = true;
696     this.fireEvent('newconfig');
697     this.selectElement(this.findByJsonId(id));
698     return true;
699   },
700   
701   /**
702    * Refresh the content of the designer
703    */
704   refresh : function() {
705     this.setConfig(this.getConfig());
706   },
707 
708   //Find parent which is of type container  
709   getContainer : function(el) {
710     var p = el;
711     while (p && p!=this.container && !this.isContainer(p)) p = p.ownerCt;
712     return p;
713   },
714   
715   /**
716    * redraw an element with the changed config
717    * @param {Element} element The elmenent to update
718    * @param {Object} config The config 
719    * @return {Boolean} Indicator that update was applied
720    */
721   redrawElement : function (element,selectId) {
722     var el = element || this.activeElement;
723     if (el) {
724       try {
725         var id = selectId || (this.activeElement ? this.activeElement[this.jsonId] : null);
726         var p = this.container; //Redraw whole canvas          
727         if (!this.redrawContainer && el!=p) {
728            //Check if whe can find parent which can be redraw
729            var c = '';
730            p = this.getContainer(el);
731            //Search if whe find a layout capeble contianer
732            while (p!=this.container && !c) {
733              if (!p.codeConfig) p.codeConfig = this.getConfig(p);
734              c = p.codeConfig.layout;
735              if (!c || (p==el && c)) 
736                 p = this.getContainer(p.ownerCt);
737            }
738            p = c ? p : this.getContainer(el.ownerCt);
739         }
740         this.applyJson(this.getConfig(p).items,p);
741         this.redrawContainer=false;
742         this.selectElement(id);
743       } catch (e) { Ext.Msg.alert('Failure', 'Failed to redraw element ' + e); }
744       this.fireEvent('change',el);
745       this.modified = true;
746       return true;
747     }
748     return  false;
749   },
750   
751   /**
752    * Select a designElement
753    * @param {Element} el The element of the item to select
754    * @param {Boolean} fieldOnNull Use the designer field when element not found
755    * @return {Component} The selected component
756    */
757   selectElement : function (el) {
758     if (typeof(el)=='string') el = this.findByJsonId(el);
759     var cmp = this.getDesignElement(el);
760     this.highlightElement(cmp);
761     this.activeElement = cmp;
762     if (cmp) {
763       if (this.propertyGrid) {
764         this.propertyFilter();
765         this.propertyGrid.enable();
766         this.propertyGrid.setSource(this.getConfig(this.activeElement));
767       }
768     } else {
769       if (this.propertyGrid) {
770         this.propertyGrid.disable();
771         this.propertyGrid.setSource({});
772       }
773     }
774     this.fireEvent('select',cmp);
775     return cmp;
776   },
777 
778   /**
779    * Highlight a element within the component, removing old highlight
780    * @param {Element} el The element to highlight
781    * @return {Boolean} True when element highlighted
782    */
783   highlightElement : function (el) {
784     //Remove old highlight and drag support
785     this.container.el.removeClass('selectedElement');
786     this.container.el.select('.selectedElement').removeClass('selectedElement');
787     this.container.el.select('.designerddgroup').removeClass('designerddgroup');
788     if (el) {
789       el.addClass("selectedElement");
790       if (el.id != this.container.id) el.addClass("designerddgroup");
791       return true;
792     }
793     return false;
794   },
795 
796   /**
797    * Check if a element is contained within a other element
798    * @param {Element} cmp The component to search
799    * @param {Element} container The component to search within
800    * @return {Component} The ExtJs component found, false when not valid
801    */  
802   isElementOf : function(cmp,container) {
803     container = container || this.container;
804     var loops = 50,c = cmp,id = container.getId();
805     while (loops && c) {
806       if (c.id == id) return cmp;
807       c = c.ownerCt;
808       loops--;
809     }
810     return false;
811   },
812     
813   /**
814    * Find a designElement, this is a ExtJs component which is embedded within this.container
815    * @param {Element} el The element to search the designelement for
816    * @return {Component} The ExtJs component found, false when not valid
817    */
818   getDesignElement : function(el,allowField) {
819     var cmp,loops = 10;
820     while (loops && el) {
821       cmp = Ext.getCmp(el.id);
822       if (cmp) {
823         if (!allowField && cmp == this.container) return false;
824         return this.isElementOf(cmp,this.container) ? cmp : (allowField ? this.container : false);
825       }
826       el = el.parentNode;
827       loops--;
828     }
829     return allowField ? this.container : false;
830   },
831   
832   findByJsonId : function(id) {
833      return this.container.findBy(function (c,p) {return (c[this.jsonId]==id ? true : false);},this)[0];
834   },
835   
836   /**
837    * Create the drag data for a element on designerpanel
838    * @param {Event} e The drag event
839    * @return {Object} the drag data
840    */
841   getDragData : function(e) {
842      var cmp = this.selectElement(this.getTarget(e));
843      var el = e.getTarget('.designerddgroup');
844      if (el && cmp) { 
845        return {
846          ddel:el,
847          config : cmp.initialConfig,
848          internal : true,
849          source   : cmp
850        }; 
851      }
852   },
853   
854   /**
855    * Check if the given component is a container which can contain other xtypes
856    * @param {Component} cmp The component to validate if it is in the list
857    * @return {Boolean} True indicates the xtype is a container capable of contain other elements
858    */
859   isContainer : function (cmp) {
860     return cmp instanceof Ext.Container;
861     /*var xtype = cmp ? cmp.xtype : null;
862     return  (xtype && ['jsonpanel','panel','viewport','form','window','tabpanel','toolbar','fieldset'].indexOf(xtype) !== -1);*/
863   },
864   
865   /**
866    * @private Fix a problem in firefox with drop getTarget by finding a component 
867    * using xy coordinates. 
868    * @param {Event} event The event for which a node should be searched
869    * @return {Node} The node that is located by xy coordinates or null when none.
870    */
871   getTarget : function (event) {
872     if (!event) return;
873     if (!Ext.isGecko) event.getTarget();
874     var n,findNode = function(c) {
875       if (c && c.getPosition && c.getSize) {
876        var pos = c.getPosition();
877        var size = c.getSize();
878        if (event.xy[0] >= pos[0] && event.xy[0]<=pos[0] + size.width &&
879            event.xy[1] >= pos[1] && event.xy[1]<=pos[1] + size.height) {
880          n = c
881          if(c.items){
882             var cs = c.items.items;
883             for(var i = 0, len = cs.length; i < len  && !findNode(cs[i]); i++) {}
884          }
885          return true;
886        }
887       }
888       return false;
889     };
890     findNode(this.container);
891     return n;
892   },
893   
894   /**
895    * Called when a element is dragged over the component
896    * @param {Object} src The source element
897    * @param {Event} e The drag event 
898    * @param {Object} data The dataobject of event
899    * @return {Boolean} return true to accept or false to reject 
900    */
901   notifyOver : function (src,e,data) {
902     if (data.config) {
903       var cmp = this.getDesignElement(this.getTarget(e),true);
904       this.selectElement(cmp);
905       var el=cmp.getEl();
906       if (data.internal && !e.shiftKey) {
907         //Only allow move if not within same container
908         if (this.isElementOf(cmp,data.source,true)) return false;
909         data.drop = this.isContainer(cmp) ? "move" : 
910          (el.getX()+(el.getWidth()/2)>Ext.lib.Event.getPageX(e) ? "movebefore" : "moveafter");
911         return (data.drop=='movebefore' ?  "icon-element-move-before" :
912           (data.drop=='moveafter'  ? "icon-element-move-after"  : "icon-element-move"));
913       } else { //Clone
914         data.drop = this.isContainer(cmp) ? "append" : 
915          (el.getX()+(el.getWidth()/2)>Ext.lib.Event.getPageX(e) ? "appendbefore" : "appendafter");
916         return (data.drop=='appendbefore' ?  "icon-element-add-before" :
917           (data.drop=='appendafter'  ? "icon-element-add-after"  : "icon-element-add"));
918       }
919     }
920     data.drop = null;
921     return false;
922   },
923   
924   /**
925    * Called when a element is dropped on the component
926    * @param {Object} src The source element
927    * @param {Event} e The drag event 
928    * @param {Object} data The dataobject of event
929    * @return {Boolean} return true to accept or false to reject 
930    */
931   notifyDrop : function (src,e,data) {
932     var el=this.getTarget(e);
933     if (data.config && !data.processed && data.drop) {
934       this.appendConfig(el,data.config,true,data.drop,data.source);
935       data.processed = true;
936     }
937     return true;  
938   },
939   
940   /**
941    * @private Function called to initalize the property editor which can be used to edit properties
942    * @param {PropertyGrid} propertyGrid The property grid which is used to edit
943    */
944   setPropertyGrid : function(propertyGrid) {
945     this.propertyGrid = propertyGrid;
946     this.propertyGrid.jsonScope = this.getJsonScope();
947     propertyGrid.on('beforepropertychange', function(source,id,value,oldvalue) {
948         this.markUndo();
949     },this);
950     propertyGrid.on('propertychange', function(source,id,value,oldvalue) {
951         if (id=='json') this.jsonInit(this.decode(value));
952         this.redrawElement(this.activeElement);
953     }, this);
954   },
955   
956   /**
957    * Show or hide the toolbox
958    * @param {Boolean} visible Should toolbox be hidden or shown (defaults true)
959    */
960   toolbox : function(visible){
961     if (!this._toolbox) {
962       if (!this.toolboxJson) {
963         //Locate the designer javascript file
964         var elements = document.getElementsByTagName("script");
965         var path ='';
966         for (var i=0;i<elements.length;i++) {
967           var s = elements[i].src ? elements[i].src : elements[i].id;
968           if (s.match(/Ext\.ux\.plugin\.Designer\.js(\?.*)?$/)) {
969             path = s.replace(/Ext\.ux\.plugin\.Designer\.js(\?.*)?$/,'');
970           }
971         }
972         this.toolboxPath = path;
973         this.toolboxJson = path + 'Ext.ux.plugin.Designer.json';
974         this.properties = new Ext.data.JsonStore({
975             url: this.toolboxPath + 'Ext.ux.plugin.Designer.Properties.json',
976             sortInfo : {field:'name',order:'ASC'},
977             root: 'properties',
978             fields: ['name', 'type','defaults','desc','instance','editable','values']
979         });
980         this.properties.load();
981         //Add Filter function based on instance
982         var filterByFn = function(rec,id) {
983           var i = rec.get('instance');
984           if (i) return eval('this.activeElement instanceof ' +i);
985           return true;
986         }.createDelegate(this);
987         this.propertyFilter = function (){
988           this.properties.filterBy(filterByFn,this);
989         };
990       }
991       var tools = 
992       this.toolboxTarget = Ext.getCmp(this.toolboxTarget);
993       if (this.toolboxTarget){
994         this._toolbox = this.toolboxTarget;
995         this._toolbox.add(new Ext.ux.JsonPanel({
996             autoLoad:this.toolboxJson,
997             disableCaching :this.disableCaching,
998             scope   : this })
999         );
1000       } else {
1001         this._toolbox = new Ext.ux.JsonWindow({
1002             autoLoad:this.toolboxJson,
1003             disableCaching :this.disableCaching,
1004             scope   : this,
1005             closable: false
1006         });
1007       }
1008     }
1009     //Now show or hide the toolbox
1010     if (visible || visible === true) {
1011       if (this.fireEvent('beforeshow',this._toolbox)) this._toolbox.show();
1012     } else {
1013       if (this.fireEvent('beforehide',this._toolbox)) this._toolbox.hide();
1014     }
1015   }
1016 
1017 },Ext.ux.JSON));
1018