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