/* * Original code for Roojs - LGPL * <script type="text/javascript"> */ /** * @class Roo.XComponent * A delayed Element creator... * Or a way to group chunks of interface together. * technically this is a wrapper around a tree of Roo elements (which defines a 'module'), * used in conjunction with XComponent.build() it will create an instance of each element, * then call addxtype() to build the User interface. * * Mypart.xyx = new Roo.XComponent({ parent : 'Mypart.xyz', // empty == document.element.!! order : '001', name : 'xxxx' region : 'xxxx' disabled : function() {} tree : function() { // return an tree of xtype declared components var MODULE = this; return { xtype : 'NestedLayoutPanel', // technicall } ] *}) * * * It can be used to build a big heiracy, with parent etc. * or you can just use this to render a single compoent to a dom element * MYPART.render(Roo.Element | String(id) | dom_element ) * * * Usage patterns. * * Classic Roo * * Roo is designed primarily as a single page application, so the UI build for a standard interface will * expect a single 'TOP' level module normally indicated by the 'parent' of the XComponent definition being defined as false. * * Each sub module is expected to have a parent pointing to the class name of it's parent module. * * When the top level is false, a 'Roo.BorderLayout' is created and the element is flagged as 'topModule' * - if mulitple topModules exist, the last one is defined as the top module. * * Embeded Roo * * When the top level or multiple modules are to embedded into a existing HTML page, * the parent element can container '#id' of the element where the module will be drawn. * * Bootstrap Roo * * Unlike classic Roo, the bootstrap tends not to be used as a single page. * it relies more on a include mechanism, where sub modules are included into an outer page. * This is normally managed by the builder tools using Roo.apply( options, Included.Sub.Module ) * * Bootstrap Roo Included elements * * Our builder application needs the ability to preview these sub compoennts. They will normally have parent=false set, * hence confusing the component builder as it thinks there are multiple top level elements. * * String Over-ride & Translations * * Our builder application writes all the strings as _strings and _named_strings. This is to enable the translation of elements, * and also the 'overlaying of string values - needed when different versions of the same application with different text content * are needed. @see Roo.XComponent.overlayString * * * * @extends Roo.util.Observable * @constructor * @param cfg {Object} configuration of component * */ Roo.XComponent = function(cfg) { Roo.apply(this, cfg); this.addEvents({ /** * @event built * Fires when this the componnt is built * @param {Roo.XComponent} c the component */ 'built' : true }); this.region = this.region || 'center'; // default.. Roo.XComponent.register(this); this.modules = false; this.el = false; // where the layout goes.. } Roo.extend(Roo.XComponent, Roo.util.Observable, { /** * @property el * The created element (with Roo.factory()) * @type {Roo.Layout} */ el : false, /** * @property el * for BC - use el in new code * @type {Roo.Layout} */ panel : false, /** * @property layout * for BC - use el in new code * @type {Roo.Layout} */ layout : false, /** * @cfg {Function|boolean} disabled * If this module is disabled by some rule, return true from the funtion */ disabled : false, /** * @cfg {String} parent * Name of parent element which it get xtype added to.. */ parent: false, /** * @cfg {String} order * Used to set the order in which elements are created (usefull for multiple tabs) */ order : false, /** * @cfg {String} name * String to display while loading. */ name : false, /** * @cfg {String} region * Region to render component to (defaults to center) */ region : 'center', /** * @cfg {Array} items * A single item array - the first element is the root of the tree.. * It's done this way to stay compatible with the Xtype system... */ items : false, /** * @property _tree * The method that retuns the tree of parts that make up this compoennt * @type {function} */ _tree : false, /** * render * render element to dom or tree * @param {Roo.Element|String|DomElement} optional render to if parent is not set. */ render : function(el) { el = el || false; var hp = this.parent ? 1 : 0; Roo.debug && Roo.log(this); var tree = this._tree ? this._tree() : this.tree(); if (!el && typeof(this.parent) == 'string' && this.parent.substring(0,1) == '#') { // if parent is a '#.....' string, then let's use that.. var ename = this.parent.substr(1); this.parent = false; Roo.debug && Roo.log(ename); switch (ename) { case 'bootstrap-body': if (typeof(tree.el) != 'undefined' && tree.el == document.body) { // this is the BorderLayout standard? this.parent = { el : true }; break; } if (["Nest", "Content", "Grid", "Tree"].indexOf(tree.xtype) > -1) { // need to insert stuff... this.parent = { el : new Roo.bootstrap.layout.Border({ el : document.body, center: { titlebar: false, autoScroll:false, closeOnTab: true, tabPosition: 'top', //resizeTabs: true, alwaysShowTabs: true, hideTabs: false //minTabWidth: 140 } }) }; break; } if (typeof(Roo.bootstrap.Body) != 'undefined' ) { this.parent = { el : new Roo.bootstrap.Body() }; Roo.debug && Roo.log("setting el to doc body"); } else { throw "Container is bootstrap body, but Roo.bootstrap.Body is not defined"; } break; case 'bootstrap': this.parent = { el : true}; // fall through default: el = Roo.get(ename); if (typeof(Roo.bootstrap) != 'undefined' && tree['|xns'] == 'Roo.bootstrap') { this.parent = { el : true}; } break; } if (!el && !this.parent) { Roo.debug && Roo.log("Warning - element can not be found :#" + ename ); return; } } Roo.debug && Roo.log("EL:"); Roo.debug && Roo.log(el); Roo.debug && Roo.log("this.parent.el:"); Roo.debug && Roo.log(this.parent.el); // altertive root elements ??? - we need a better way to indicate these. var is_alt = Roo.XComponent.is_alt || (typeof(tree.el) != 'undefined' && tree.el == document.body) || (typeof(Roo.bootstrap) != 'undefined' && tree.xns == Roo.bootstrap) || (typeof(Roo.mailer) != 'undefined' && tree.xns == Roo.mailer) ; if (!this.parent && is_alt) { //el = Roo.get(document.body); this.parent = { el : true }; } if (!this.parent) { Roo.debug && Roo.log("no parent - creating one"); el = el ? Roo.get(el) : false; if (typeof(Roo.BorderLayout) == 'undefined' ) { this.parent = { el : new Roo.bootstrap.layout.Border({ el: el || document.body, center: { titlebar: false, autoScroll:false, closeOnTab: true, tabPosition: 'top', //resizeTabs: true, alwaysShowTabs: false, hideTabs: true, minTabWidth: 140, overflow: 'visible' } }) }; } else { // it's a top level one.. this.parent = { el : new Roo.BorderLayout(el || document.body, { center: { titlebar: false, autoScroll:false, closeOnTab: true, tabPosition: 'top', //resizeTabs: true, alwaysShowTabs: el && hp? false : true, hideTabs: el || !hp ? true : false, minTabWidth: 140 } }) }; } } if (!this.parent.el) { // probably an old style ctor, which has been disabled. return; } // The 'tree' method is '_tree now' tree.region = tree.region || this.region; var is_body = false; if (this.parent.el === true) { // bootstrap... - body.. if (el) { tree.el = el; } this.parent.el = Roo.factory(tree); is_body = true; } this.el = this.parent.el.addxtype(tree, undefined, is_body); this.fireEvent('built', this); this.panel = this.el; this.layout = this.panel.layout; this.parentLayout = this.parent.layout || false; } }); Roo.apply(Roo.XComponent, { /** * @property hideProgress * true to disable the building progress bar.. usefull on single page renders. * @type Boolean */ hideProgress : false, /** * @property buildCompleted * True when the builder has completed building the interface. * @type Boolean */ buildCompleted : false, /** * @property topModule * the upper most module - uses document.element as it's constructor. * @type Object */ topModule : false, /** * @property modules * array of modules to be created by registration system. * @type {Array} of Roo.XComponent */ modules : [], /** * @property elmodules * array of modules to be created by which use #ID * @type {Array} of Roo.XComponent */ elmodules : [], /** * @property is_alt * Is an alternative Root - normally used by bootstrap or other systems, * where the top element in the tree can wrap 'body' * @type {boolean} (default false) */ is_alt : false, /** * @property build_from_html * Build elements from html - used by bootstrap HTML stuff * - this is cleared after build is completed * @type {boolean} (default false) */ build_from_html : false, /** * Register components to be built later. * * This solves the following issues * - Building is not done on page load, but after an authentication process has occured. * - Interface elements are registered on page load * - Parent Interface elements may not be loaded before child, so this handles that.. * * * example: * * MyApp.register({ order : '000001', module : 'Pman.Tab.projectMgr', region : 'center', parent : 'Pman.layout', disabled : false, // or use a function.. }) * * @param {Object} details about module */ register : function(obj) { Roo.XComponent.event.fireEvent('register', obj); switch(typeof(obj.disabled) ) { case 'undefined': break; case 'function': if ( obj.disabled() ) { return; } break; default: if (obj.disabled || obj.region == '#disabled') { return; } break; } this.modules.push(obj); }, /** * convert a string to an object.. * eg. 'AAA.BBB' -> finds AAA.BBB */ toObject : function(str) { if (!str || typeof(str) == 'object') { return str; } if (str.substring(0,1) == '#') { return str; } var ar = str.split('.'); var rt, o; rt = ar.shift(); /** eval:var:o */ try { eval('if (typeof ' + rt + ' == "undefined"){ o = false;} o = ' + rt + ';'); } catch (e) { throw "Module not found : " + str; } if (o === false) { throw "Module not found : " + str; } Roo.each(ar, function(e) { if (typeof(o[e]) == 'undefined') { throw "Module not found : " + str; } o = o[e]; }); return o; }, /** * move modules into their correct place in the tree.. * */ preBuild : function () { var _t = this; Roo.each(this.modules , function (obj) { Roo.XComponent.event.fireEvent('beforebuild', obj); var opar = obj.parent; try { obj.parent = this.toObject(opar); } catch(e) { Roo.debug && Roo.log("parent:toObject failed: " + e.toString()); return; } if (!obj.parent) { Roo.debug && Roo.log("GOT top level module"); Roo.debug && Roo.log(obj); obj.modules = new Roo.util.MixedCollection(false, function(o) { return o.order + '' } ); this.topModule = obj; return; } // parent is a string (usually a dom element name..) if (typeof(obj.parent) == 'string') { this.elmodules.push(obj); return; } if (obj.parent.constructor != Roo.XComponent) { Roo.debug && Roo.log("Warning : Object Parent is not instance of XComponent:" + obj.name) } if (!obj.parent.modules) { obj.parent.modules = new Roo.util.MixedCollection(false, function(o) { return o.order + '' } ); } if (obj.parent.disabled) { obj.disabled = true; } obj.parent.modules.add(obj); }, this); }, /** * make a list of modules to build. * @return {Array} list of modules. */ buildOrder : function() { var _this = this; var cmp = function(a,b) { return String(a).toUpperCase() > String(b).toUpperCase() ? 1 : -1; }; if ((!this.topModule || !this.topModule.modules) && !this.elmodules.length) { throw "No top level modules to build"; } // make a flat list in order of modules to build. var mods = this.topModule ? [ this.topModule ] : []; // elmodules (is a list of DOM based modules ) Roo.each(this.elmodules, function(e) { mods.push(e); if (!this.topModule && typeof(e.parent) == 'string' && e.parent.substring(0,1) == '#' && Roo.get(e.parent.substr(1)) ) { _this.topModule = e; } }); // add modules to their parents.. var addMod = function(m) { Roo.debug && Roo.log("build Order: add: " + m.name); mods.push(m); if (m.modules && !m.disabled) { Roo.debug && Roo.log("build Order: " + m.modules.length + " child modules"); m.modules.keySort('ASC', cmp ); Roo.debug && Roo.log("build Order: " + m.modules.length + " child modules (after sort)"); m.modules.each(addMod); } else { Roo.debug && Roo.log("build Order: no child modules"); } // not sure if this is used any more.. if (m.finalize) { m.finalize.name = m.name + " (clean up) "; mods.push(m.finalize); } } if (this.topModule && this.topModule.modules) { this.topModule.modules.keySort('ASC', cmp ); this.topModule.modules.each(addMod); } return mods; }, /** * Build the registered modules. * @param {Object} parent element. * @param {Function} optional method to call after module has been added. * */ build : function(opts) { if (typeof(opts) != 'undefined') { Roo.apply(this,opts); } this.preBuild(); var mods = this.buildOrder(); //this.allmods = mods; //Roo.debug && Roo.log(mods); //return; if (!mods.length) { // should not happen throw "NO modules!!!"; } var msg = "Building Interface..."; // flash it up as modal - so we store the mask!? if (!this.hideProgress && Roo.MessageBox) { Roo.MessageBox.show({ title: 'loading' }); Roo.MessageBox.show({ title: "Please wait...", msg: msg, width:450, progress:true, buttons : false, closable:false, modal: false }); } var total = mods.length; var _this = this; var progressRun = function() { if (!mods.length) { Roo.debug && Roo.log('hide?'); if (!this.hideProgress && Roo.MessageBox) { Roo.MessageBox.hide(); } Roo.XComponent.build_from_html = false; // reset, so dialogs will be build from javascript Roo.XComponent.event.fireEvent('buildcomplete', _this.topModule); // THE END... return false; } var m = mods.shift(); Roo.debug && Roo.log(m); // not sure if this is supported any more.. - modules that are are just function if (typeof(m) == 'function') { m.call(this); return progressRun.defer(10, _this); } msg = "Building Interface " + (total - mods.length) + " of " + total + (m.name ? (' - ' + m.name) : ''); Roo.debug && Roo.log(msg); if (!_this.hideProgress && Roo.MessageBox) { Roo.MessageBox.updateProgress( (total - mods.length)/total, msg ); } // is the module disabled? var disabled = (typeof(m.disabled) == 'function') ? m.disabled.call(m.module.disabled) : m.disabled; if (disabled) { return progressRun(); // we do not update the display! } // now build m.render(); // it's 10 on top level, and 1 on others??? why... return progressRun.defer(10, _this); } progressRun.defer(1, _this); }, /** * Overlay a set of modified strings onto a component * This is dependant on our builder exporting the strings and 'named strings' elements. * * @param {Object} element to overlay on - eg. Pman.Dialog.Login * @param {Object} associative array of 'named' string and it's new value. * */ overlayStrings : function( component, strings ) { if (typeof(component['_named_strings']) == 'undefined') { throw "ERROR: component does not have _named_strings"; } for ( var k in strings ) { var md = typeof(component['_named_strings'][k]) == 'undefined' ? false : component['_named_strings'][k]; if (md !== false) { component['_strings'][md] = strings[k]; } else { Roo.log('could not find named string: ' + k + ' in'); Roo.log(component); } } }, /** * Event Object. * * */ event: false, /** * wrapper for event.on - aliased later.. * Typically use to register a event handler for register: * * eg. Roo.XComponent.on('register', function(comp) { comp.disable = true } ); * */ on : false }); Roo.XComponent.event = new Roo.util.Observable({ events : { /** * @event register * Fires when an Component is registered, * set the disable property on the Component to stop registration. * @param {Roo.XComponent} c the component being registerd. * */ 'register' : true, /** * @event beforebuild * Fires before each Component is built * can be used to apply permissions. * @param {Roo.XComponent} c the component being registerd. * */ 'beforebuild' : true, /** * @event buildcomplete * Fires on the top level element when all elements have been built * @param {Roo.XComponent} the top level component. */ 'buildcomplete' : true } }); Roo.XComponent.on = Roo.XComponent.event.on.createDelegate(Roo.XComponent.event);