/* Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ /* This is an optimized version of Dojo, built for deployment and not for development. To get sources and documentation, please visit: http://dojotoolkit.org */ if(!dojo._hasResource["dojo.back"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dojo.back"] = true; dojo.provide("dojo.back"); dojo.getObject("back", true, dojo); /*===== dojo.back = { // summary: Browser history management resources } =====*/ (function(){ var back = dojo.back, // everyone deals with encoding the hash slightly differently getHash= back.getHash= function(){ var h = window.location.hash; if(h.charAt(0) == "#"){ h = h.substring(1); } return dojo.isMozilla ? h : decodeURIComponent(h); }, setHash= back.setHash= function(h){ if(!h){ h = ""; } window.location.hash = encodeURIComponent(h); historyCounter = history.length; }; var initialHref = (typeof(window) !== "undefined") ? window.location.href : ""; var initialHash = (typeof(window) !== "undefined") ? getHash() : ""; var initialState = null; var locationTimer = null; var bookmarkAnchor = null; var historyIframe = null; var forwardStack = []; var historyStack = []; var moveForward = false; var changingUrl = false; var historyCounter; function handleBackButton(){ //summary: private method. Do not call this directly. //The "current" page is always at the top of the history stack. var current = historyStack.pop(); if(!current){ return; } var last = historyStack[historyStack.length-1]; if(!last && historyStack.length == 0){ last = initialState; } if(last){ if(last.kwArgs["back"]){ last.kwArgs["back"](); }else if(last.kwArgs["backButton"]){ last.kwArgs["backButton"](); }else if(last.kwArgs["handle"]){ last.kwArgs.handle("back"); } } forwardStack.push(current); } back.goBack = handleBackButton; function handleForwardButton(){ //summary: private method. Do not call this directly. var last = forwardStack.pop(); if(!last){ return; } if(last.kwArgs["forward"]){ last.kwArgs.forward(); }else if(last.kwArgs["forwardButton"]){ last.kwArgs.forwardButton(); }else if(last.kwArgs["handle"]){ last.kwArgs.handle("forward"); } historyStack.push(last); } back.goForward = handleForwardButton; function createState(url, args, hash){ //summary: private method. Do not call this directly. return {"url": url, "kwArgs": args, "urlHash": hash}; //Object } function getUrlQuery(url){ //summary: private method. Do not call this directly. var segments = url.split("?"); if(segments.length < 2){ return null; //null } else{ return segments[1]; //String } } function loadIframeHistory(){ //summary: private method. Do not call this directly. var url = (dojo.config["dojoIframeHistoryUrl"] || dojo.moduleUrl("dojo", "resources/iframe_history.html")) + "?" + (new Date()).getTime(); moveForward = true; if(historyIframe){ dojo.isWebKit ? historyIframe.location = url : window.frames[historyIframe.name].location = url; }else{ //console.warn("dojo.back: Not initialised. You need to call dojo.back.init() from a // into a function // script: DOMNode // The // }; =====*/ // All the stuff in _base (these are the function that are guaranteed available without an explicit dojo.require) // And some other stuff that we tend to pull in all the time anyway } if(!dojo._hasResource["dijit._editor.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit._editor.html"] = true; dojo.provide("dijit._editor.html"); var exports = dojo.getObject("_editor", true, dijit); var escape = exports.escapeXml=function(/*String*/str, /*Boolean?*/noSingleQuotes){ // summary: // Adds escape sequences for special characters in XML: &<>"' // Optionally skips escapes for single quotes str = str.replace(/&/gm, "&").replace(//gm, ">").replace(/"/gm, """); if(!noSingleQuotes){ str = str.replace(/'/gm, "'"); } return str; // string }; exports.getNodeHtml = function(/*DomNode*/ node){ // summary: // Return string representing HTML for node and it's children var output = []; exports.getNodeHtmlHelper(node, output); return output.join(""); }; exports.getNodeHtmlHelper = function(/*DomNode*/ node, /*String[]*/ output){ // summary: // Pushes array of strings into output[] which represent HTML for node and it's children switch(node.nodeType){ case 1: //element node var lName = node.nodeName.toLowerCase(); if(!lName || lName.charAt(0) == "/"){ // IE does some strange things with malformed HTML input, like // treating a close tag without an open tag , as // a new tag with tagName of /span. Corrupts output HTML, remove // them. Other browsers don't prefix tags that way, so will // never show up. return ""; } output.push('<', lName); //store the list of attributes and sort it to have the //attributes appear in the dictionary order var attrarray = []; var attr; if(dojo.isIE < 9){ var clone = /^input$|^img$/i.test(node.nodeName) ? node : node.cloneNode(false); var s = clone.outerHTML; s = s.substr(0, s.indexOf('>')) .replace(/(['"])[^"']*\1/g, ''); //to make the following regexp safe var reg = /(\b\w+)\s?=/g; var m, key; while((m = reg.exec(s))){ key = m[1]; if(key.substr(0,3) != '_dj'){ if(key == 'src' || key == 'href'){ if(node.getAttribute('_djrealurl')){ attrarray.push([key,node.getAttribute('_djrealurl')]); continue; } } var val, match; switch(key){ case 'style': val = node.style.cssText.toLowerCase(); break; case 'class': val = node.className; break; case 'width': if(lName === "img"){ // This somehow gets lost on IE for IMG tags and the like // and we have to find it in outerHTML, known IE oddity. match=/width=(\S+)/i.exec(s); if(match){ val = match[1]; } break; } case 'height': if(lName === "img"){ // This somehow gets lost on IE for IMG tags and the like // and we have to find it in outerHTML, known IE oddity. match=/height=(\S+)/i.exec(s); if(match){ val = match[1]; } break; } default: val = node.getAttribute(key); } if(val != null){ attrarray.push([key, val.toString()]); } } } }else{ var i = 0; while((attr = node.attributes[i++])){ //ignore all attributes starting with _dj which are //internal temporary attributes used by the editor var n = attr.name; if(n.substr(0,3) != '_dj' /*&& (attr.specified == undefined || attr.specified)*/){ var v = attr.value; if(n == 'src' || n == 'href'){ if(node.getAttribute('_djrealurl')){ v = node.getAttribute('_djrealurl'); } } attrarray.push([n,v]); } } } attrarray.sort(function(a,b){ return a[0] < b[0] ? -1 : (a[0] == b[0] ? 0 : 1); }); var j = 0; while((attr = attrarray[j++])){ output.push(' ', attr[0], '="', (dojo.isString(attr[1]) ? escape(attr[1], true) : attr[1]), '"'); } switch(lName){ case 'br': case 'hr': case 'img': case 'input': case 'base': case 'meta': case 'area': case 'basefont': // These should all be singly closed output.push(' />'); break; case 'script': // Browsers handle script tags differently in how you get content, // but innerHTML always seems to work, so insert its content that way // Yes, it's bad to allow script tags in the editor code, but some people // seem to want to do it, so we need to at least return them right. // other plugins/filters can strip them. output.push('>', node.innerHTML, ''); break; default: output.push('>'); if(node.hasChildNodes()){ exports.getChildrenHtmlHelper(node, output); } output.push(''); } break; case 4: // cdata case 3: // text // FIXME: output.push(escape(node.nodeValue, true)); break; case 8: //comment // FIXME: output.push(''); break; default: output.push(""); } }; exports.getChildrenHtml = function(/*DomNode*/ node){ // summary: // Returns the html content of a DomNode's children var output = []; exports.getChildrenHtmlHelper(node, output); return output.join(""); }; exports.getChildrenHtmlHelper = function(/*DomNode*/ dom, /*String[]*/ output){ // summary: // Pushes the html content of a DomNode's children into out[] if(!dom){ return; } var nodes = dom["childNodes"] || dom; //IE issue. //If we have an actual node we can check parent relationships on for IE, //We should check, as IE sometimes builds invalid DOMS. If no parent, we can't check //And should just process it and hope for the best. var checkParent = !dojo.isIE || nodes !== dom; var node, i = 0; while((node = nodes[i++])){ //IE is broken. DOMs are supposed to be a tree. But in the case of malformed HTML, IE generates a graph //meaning one node ends up with multiple references (multiple parents). This is totally wrong and invalid, but //such is what it is. We have to keep track and check for this because otherwise the source output HTML will have dups. //No other browser generates a graph. Leave it to IE to break a fundamental DOM rule. So, we check the parent if we can //If we can't, nothing more we can do other than walk it. if(!checkParent || node.parentNode == dom){ exports.getNodeHtmlHelper(node, output); } } }; } if(!dojo._hasResource["dijit._editor.RichText"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit._editor.RichText"] = true; dojo.provide("dijit._editor.RichText"); // used to restore content when user leaves this page then comes back // but do not try doing dojo.doc.write if we are using xd loading. // dojo.doc.write will only work if RichText.js is included in the dojo.js // file. If it is included in dojo.js and you want to allow rich text saving // for back/forward actions, then set dojo.config.allowXdRichTextSave = true. if(!dojo.config["useXDomain"] || dojo.config["allowXdRichTextSave"]){ if(dojo._postLoad){ (function(){ var savetextarea = dojo.doc.createElement('textarea'); savetextarea.id = dijit._scopeName + "._editor.RichText.value"; dojo.style(savetextarea, { display:'none', position:'absolute', top:"-100px", height:"3px", width:"3px" }); dojo.body().appendChild(savetextarea); })(); }else{ //dojo.body() is not available before onLoad is fired try{ dojo.doc.write(''); }catch(e){ } } } dojo.declare("dijit._editor.RichText", [dijit._Widget, dijit._CssStateMixin], { constructor: function(params){ // summary: // dijit._editor.RichText is the core of dijit.Editor, which provides basic // WYSIWYG editing features. // // description: // dijit._editor.RichText is the core of dijit.Editor, which provides basic // WYSIWYG editing features. It also encapsulates the differences // of different js engines for various browsers. Do not use this widget // with an HTML <TEXTAREA> tag, since the browser unescapes XML escape characters, // like <. This can have unexpected behavior and lead to security issues // such as scripting attacks. // // tags: // private // contentPreFilters: Function(String)[] // Pre content filter function register array. // these filters will be executed before the actual // editing area gets the html content. this.contentPreFilters = []; // contentPostFilters: Function(String)[] // post content filter function register array. // These will be used on the resulting html // from contentDomPostFilters. The resulting // content is the final html (returned by getValue()). this.contentPostFilters = []; // contentDomPreFilters: Function(DomNode)[] // Pre content dom filter function register array. // These filters are applied after the result from // contentPreFilters are set to the editing area. this.contentDomPreFilters = []; // contentDomPostFilters: Function(DomNode)[] // Post content dom filter function register array. // These filters are executed on the editing area dom. // The result from these will be passed to contentPostFilters. this.contentDomPostFilters = []; // editingAreaStyleSheets: dojo._URL[] // array to store all the stylesheets applied to the editing area this.editingAreaStyleSheets = []; // Make a copy of this.events before we start writing into it, otherwise we // will modify the prototype which leads to bad things on pages w/multiple editors this.events = [].concat(this.events); this._keyHandlers = {}; if(params && dojo.isString(params.value)){ this.value = params.value; } this.onLoadDeferred = new dojo.Deferred(); }, baseClass: "dijitEditor", // inheritWidth: Boolean // whether to inherit the parent's width or simply use 100% inheritWidth: false, // focusOnLoad: [deprecated] Boolean // Focus into this widget when the page is loaded focusOnLoad: false, // name: String? // Specifies the name of a (hidden) // // example: // | new dijit.form.SimpleTextarea({ rows:20, cols:30 }, "foo"); baseClass: "dijitTextBox dijitTextArea", attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, { rows:"textbox", cols: "textbox" }), // rows: Number // The number of rows of text. rows: "3", // rows: Number // The number of characters per line. cols: "20", templateString: "", postMixInProperties: function(){ // Copy value from srcNodeRef, unless user specified a value explicitly (or there is no srcNodeRef) // TODO: parser will handle this in 2.0 if(!this.value && this.srcNodeRef){ this.value = this.srcNodeRef.value; } this.inherited(arguments); }, buildRendering: function(){ this.inherited(arguments); if(dojo.isIE && this.cols){ // attribute selectors is not supported in IE6 dojo.addClass(this.textbox, "dijitTextAreaCols"); } }, filter: function(/*String*/ value){ // Override TextBox.filter to deal with newlines... specifically (IIRC) this is for IE which writes newlines // as \r\n instead of just \n if(value){ value = value.replace(/\r/g,""); } return this.inherited(arguments); }, _previousValue: "", _onInput: function(/*Event?*/ e){ // Override TextBox._onInput() to enforce maxLength restriction if(this.maxLength){ var maxLength = parseInt(this.maxLength); var value = this.textbox.value.replace(/\r/g,''); var overflow = value.length - maxLength; if(overflow > 0){ if(e){ dojo.stopEvent(e); } var textarea = this.textbox; if(textarea.selectionStart){ var pos = textarea.selectionStart; var cr = 0; if(dojo.isOpera){ cr = (this.textbox.value.substring(0,pos).match(/\r/g) || []).length; } this.textbox.value = value.substring(0,pos-overflow-cr)+value.substring(pos-cr); textarea.setSelectionRange(pos-overflow, pos-overflow); }else if(dojo.doc.selection){ //IE textarea.focus(); var range = dojo.doc.selection.createRange(); // delete overflow characters range.moveStart("character", -overflow); range.text = ''; // show cursor range.select(); } } this._previousValue = this.textbox.value; } this.inherited(arguments); } }); } if(!dojo._hasResource["dijit.form._ExpandingTextAreaMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit.form._ExpandingTextAreaMixin"] = true; dojo.provide("dijit.form._ExpandingTextAreaMixin"); // module: // dijit/form/_ExpandingTextAreaMixin // summary: // Mixin for textarea widgets to add auto-expanding capability // feature detection var needsHelpShrinking; dojo.declare("dijit.form._ExpandingTextAreaMixin", null, { // summary: // Mixin for textarea widgets to add auto-expanding capability _setValueAttr: function(){ this.inherited(arguments); this.resize(); }, postCreate: function(){ this.inherited(arguments); var textarea = this.textbox; if(needsHelpShrinking == undefined){ var te = dojo.create('textarea', {rows:"5", cols:"20", value: ' ', style: {zoom:1, fontSize:"12px", height:"96px", overflow:'hidden', visibility:'hidden', position:'absolute', border:"5px solid white", margin:"0", padding:"0", boxSizing: 'border-box', MsBoxSizing: 'border-box', WebkitBoxSizing: 'border-box', MozBoxSizing: 'border-box' }}, dojo.body(), "last"); needsHelpShrinking = te.scrollHeight >= te.clientHeight; dojo.body().removeChild(te); } this.connect(textarea, "onresize", "_resizeLater"); this.connect(textarea, "onfocus", "_resizeLater"); textarea.style.overflowY = "hidden"; }, startup: function(){ this.inherited(arguments); this._resizeLater(); }, _onInput: function(e){ this.inherited(arguments); this.resize(); }, _estimateHeight: function(){ // summary: // Approximate the height when the textarea is invisible with the number of lines in the text. // Fails when someone calls setValue with a long wrapping line, but the layout fixes itself when the user clicks inside so . . . // In IE, the resize event is supposed to fire when the textarea becomes visible again and that will correct the size automatically. // var textarea = this.textbox; // #rows = #newlines+1 textarea.rows = (textarea.value.match(/\n/g) || []).length + 1; }, _resizeLater: function(){ this.defer("resize"); }, resize: function(){ // summary: // Resizes the textarea vertically (should be called after a style/value change) var textarea = this.textbox; function textareaScrollHeight(){ var empty = false; if(textarea.value === ''){ textarea.value = ' '; empty = true; } var sh = textarea.scrollHeight; if(empty){ textarea.value = ''; } return sh; } if(textarea.style.overflowY == "hidden"){ textarea.scrollTop = 0; } if(this.busyResizing){ return; } this.busyResizing = true; if(textareaScrollHeight() || textarea.offsetHeight){ var newH = textareaScrollHeight() + Math.max(textarea.offsetHeight - textarea.clientHeight, 0); var newHpx = newH + "px"; if(newHpx != textarea.style.height){ textarea.style.height = newHpx; textarea.rows = 1; // rows can act like a minHeight if not cleared } if(needsHelpShrinking){ var origScrollHeight = textareaScrollHeight(), newScrollHeight = origScrollHeight, origMinHeight = textarea.style.minHeight, decrement = 4, // not too fast, not too slow thisScrollHeight, origScrollTop = textarea.scrollTop; textarea.style.minHeight = newHpx; // maintain current height textarea.style.height = "auto"; // allow scrollHeight to change while(newH > 0){ textarea.style.minHeight = Math.max(newH - decrement, 4) + "px"; thisScrollHeight = textareaScrollHeight(); var change = newScrollHeight - thisScrollHeight; newH -= change; if(change < decrement){ break; // scrollHeight didn't shrink } newScrollHeight = thisScrollHeight; decrement <<= 1; } textarea.style.height = newH + "px"; textarea.style.minHeight = origMinHeight; textarea.scrollTop = origScrollTop; } textarea.style.overflowY = textareaScrollHeight() > textarea.clientHeight ? "auto" : "hidden"; if(textarea.style.overflowY == "hidden"){ textarea.scrollTop = 0; } }else{ // hidden content of unknown size this._estimateHeight(); } this.busyResizing = false; } }); } if(!dojo._hasResource["dijit.form.Textarea"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit.form.Textarea"] = true; dojo.provide("dijit.form.Textarea"); /*===== var _ExpandingTextAreaMixin = dijit.form._ExpandingTextAreaMixin; var SimpleTextarea = dijit.form.SimpleTextarea; =====*/ // module: // dijit/form/Textarea // summary: // A textarea widget that adjusts it's height according to the amount of data. dojo.declare("dijit.form.Textarea", [dijit.form.SimpleTextarea, dijit.form._ExpandingTextAreaMixin], { // summary: // A textarea widget that adjusts it's height according to the amount of data. // // description: // A textarea that dynamically expands/contracts (changing it's height) as // the user types, to display all the text without requiring a scroll bar. // // Takes nearly all the parameters (name, value, etc.) that a vanilla textarea takes. // Rows is not supported since this widget adjusts the height. // // example: // | // TODO: for 2.0, rename this to ExpandingTextArea, and rename SimpleTextarea to TextArea baseClass: "dijitTextBox dijitTextArea dijitExpandingTextArea", // Override SimpleTextArea.cols to default to width:100%, for backward compatibility cols: "", buildRendering: function(){ this.inherited(arguments); // tweak textarea style to reduce browser differences dojo.style(this.textbox, { overflowY: 'hidden', overflowX: 'auto', boxSizing: 'border-box', MsBoxSizing: 'border-box', WebkitBoxSizing: 'border-box', MozBoxSizing: 'border-box' }); } }); } if(!dojo._hasResource["dijit.layout.BorderContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit.layout.BorderContainer"] = true; dojo.provide("dijit.layout.BorderContainer"); dojo.declare( "dijit.layout.BorderContainer", dijit.layout._LayoutWidget, { // summary: // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides. // // description: // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;", // that contains a child widget marked region="center" and optionally children widgets marked // region equal to "top", "bottom", "leading", "trailing", "left" or "right". // Children along the edges will be laid out according to width or height dimensions and may // include optional splitters (splitter="true") to make them resizable by the user. The remaining // space is designated for the center region. // // The outer size must be specified on the BorderContainer node. Width must be specified for the sides // and height for the top and bottom, respectively. No dimensions should be specified on the center; // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like // "left" and "right" except that they will be reversed in right-to-left environments. // // For complex layouts, multiple children can be specified for a single region. In this case, the // layoutPriority flag on the children determines which child is closer to the edge (low layoutPriority) // and which child is closer to the center (high layoutPriority). layoutPriority can also be used // instead of the design attribute to conrol layout precedence of horizontal vs. vertical panes. // example: // |
// |
header text
// |
table of contents
// |
client area
// |
// design: String // Which design is used for the layout: // - "headline" (default) where the top and bottom extend // the full width of the container // - "sidebar" where the left and right sides extend from top to bottom. design: "headline", // gutters: [const] Boolean // Give each pane a border and margin. // Margin determined by domNode.paddingLeft. // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing. gutters: true, // liveSplitters: [const] Boolean // Specifies whether splitters resize as you drag (true) or only upon mouseup (false) liveSplitters: true, // persist: Boolean // Save splitter positions in a cookie. persist: false, baseClass: "dijitBorderContainer", // _splitterClass: String // Optional hook to override the default Splitter widget used by BorderContainer _splitterClass: "dijit.layout._Splitter", postMixInProperties: function(){ // change class name to indicate that BorderContainer is being used purely for // layout (like LayoutContainer) rather than for pretty formatting. if(!this.gutters){ this.baseClass += "NoGutter"; } this.inherited(arguments); }, startup: function(){ if(this._started){ return; } dojo.forEach(this.getChildren(), this._setupChild, this); this.inherited(arguments); }, _setupChild: function(/*dijit._Widget*/ child){ // Override _LayoutWidget._setupChild(). var region = child.region; if(region){ this.inherited(arguments); dojo.addClass(child.domNode, this.baseClass+"Pane"); var ltr = this.isLeftToRight(); if(region == "leading"){ region = ltr ? "left" : "right"; } if(region == "trailing"){ region = ltr ? "right" : "left"; } // Create draggable splitter for resizing pane, // or alternately if splitter=false but BorderContainer.gutters=true then // insert dummy div just for spacing if(region != "center" && (child.splitter || this.gutters) && !child._splitterWidget){ var _Splitter = dojo.getObject(child.splitter ? this._splitterClass : "dijit.layout._Gutter"); var splitter = new _Splitter({ id: child.id + "_splitter", container: this, child: child, region: region, live: this.liveSplitters }); splitter.isSplitter = true; child._splitterWidget = splitter; dojo.place(splitter.domNode, child.domNode, "after"); // Splitters aren't added as Contained children, so we need to call startup explicitly splitter.startup(); } child.region = region; // TODO: technically wrong since it overwrites "trailing" with "left" etc. } }, layout: function(){ // Implement _LayoutWidget.layout() virtual method. this._layoutChildren(); }, addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ // Override _LayoutWidget.addChild(). this.inherited(arguments); if(this._started){ this.layout(); //OPT } }, removeChild: function(/*dijit._Widget*/ child){ // Override _LayoutWidget.removeChild(). var region = child.region; var splitter = child._splitterWidget if(splitter){ splitter.destroy(); delete child._splitterWidget; } this.inherited(arguments); if(this._started){ this._layoutChildren(); } // Clean up whatever style changes we made to the child pane. // Unclear how height and width should be handled. dojo.removeClass(child.domNode, this.baseClass+"Pane"); dojo.style(child.domNode, { top: "auto", bottom: "auto", left: "auto", right: "auto", position: "static" }); dojo.style(child.domNode, region == "top" || region == "bottom" ? "width" : "height", "auto"); }, getChildren: function(){ // Override _LayoutWidget.getChildren() to only return real children, not the splitters. return dojo.filter(this.inherited(arguments), function(widget){ return !widget.isSplitter; }); }, // TODO: remove in 2.0 getSplitter: function(/*String*/region){ // summary: // Returns the widget responsible for rendering the splitter associated with region // tags: // deprecated return dojo.filter(this.getChildren(), function(child){ return child.region == region; })[0]._splitterWidget; }, resize: function(newSize, currentSize){ // Overrides _LayoutWidget.resize(). // resetting potential padding to 0px to provide support for 100% width/height + padding // TODO: this hack doesn't respect the box model and is a temporary fix if(!this.cs || !this.pe){ var node = this.domNode; this.cs = dojo.getComputedStyle(node); this.pe = dojo._getPadExtents(node, this.cs); this.pe.r = dojo._toPixelValue(node, this.cs.paddingRight); this.pe.b = dojo._toPixelValue(node, this.cs.paddingBottom); dojo.style(node, "padding", "0px"); } this.inherited(arguments); }, _layoutChildren: function(/*String?*/ changedChildId, /*Number?*/ changedChildSize){ // summary: // This is the main routine for setting size/position of each child. // description: // With no arguments, measures the height of top/bottom panes, the width // of left/right panes, and then sizes all panes accordingly. // // With changedRegion specified (as "left", "top", "bottom", or "right"), // it changes that region's width/height to changedRegionSize and // then resizes other regions that were affected. // changedChildId: // Id of the child which should be resized because splitter was dragged. // changedChildSize: // The new width/height (in pixels) to make specified child if(!this._borderBox || !this._borderBox.h){ // We are currently hidden, or we haven't been sized by our parent yet. // Abort. Someone will resize us later. return; } // Generate list of wrappers of my children in the order that I want layoutChildren() // to process them (i.e. from the outside to the inside) var wrappers = dojo.map(this.getChildren(), function(child, idx){ return { pane: child, weight: [ child.region == "center" ? Infinity : 0, child.layoutPriority, (this.design == "sidebar" ? 1 : -1) * (/top|bottom/.test(child.region) ? 1 : -1), idx ] }; }, this); wrappers.sort(function(a, b){ var aw = a.weight, bw = b.weight; for(var i=0; i
', postMixInProperties: function(){ this.inherited(arguments); this.horizontal = /top|bottom/.test(this.region); this._factor = /top|left/.test(this.region) ? 1 : -1; this._cookieName = this.container.id + "_" + this.region; }, buildRendering: function(){ this.inherited(arguments); dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V")); if(this.container.persist){ // restore old size var persistSize = dojo.cookie(this._cookieName); if(persistSize){ this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize; } } }, _computeMaxSize: function(){ // summary: // Return the maximum size that my corresponding pane can be set to var dim = this.horizontal ? 'h' : 'w', childSize = dojo.marginBox(this.child.domNode)[dim], center = dojo.filter(this.container.getChildren(), function(child){ return child.region == "center";})[0], spaceAvailable = dojo.marginBox(center.domNode)[dim]; // can expand until center is crushed to 0 return Math.min(this.child.maxSize, childSize + spaceAvailable); }, _startDrag: function(e){ if(!this.cover){ this.cover = dojo.doc.createElement('div'); dojo.addClass(this.cover, "dijitSplitterCover"); dojo.place(this.cover, this.child.domNode, "after"); } dojo.addClass(this.cover, "dijitSplitterCoverActive"); // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up. if(this.fake){ dojo.destroy(this.fake); } if(!(this._resize = this.live)){ //TODO: disable live for IE6? // create fake splitter to display at old position while we drag (this.fake = this.domNode.cloneNode(true)).removeAttribute("id"); dojo.addClass(this.domNode, "dijitSplitterShadow"); dojo.place(this.fake, this.domNode, "after"); } dojo.addClass(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active"); if(this.fake){ dojo.removeClass(this.fake, "dijitSplitterHover dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover"); } //Performance: load data info local vars for onmousevent function closure var factor = this._factor, isHorizontal = this.horizontal, axis = isHorizontal ? "pageY" : "pageX", pageStart = e[axis], splitterStyle = this.domNode.style, dim = isHorizontal ? 'h' : 'w', childStart = dojo.marginBox(this.child.domNode)[dim], max = this._computeMaxSize(), min = this.child.minSize || 20, region = this.region, splitterAttr = region == "top" || region == "bottom" ? "top" : "left", // style attribute of splitter to adjust splitterStart = parseInt(splitterStyle[splitterAttr], 10), resize = this._resize, layoutFunc = dojo.hitch(this.container, "_layoutChildren", this.child.id), de = dojo.doc; this._handlers = (this._handlers || []).concat([ dojo.connect(de, "onmousemove", this._drag = function(e, forceResize){ var delta = e[axis] - pageStart, childSize = factor * delta + childStart, boundChildSize = Math.max(Math.min(childSize, max), min); if(resize || forceResize){ layoutFunc(boundChildSize); } // TODO: setting style directly (usually) sets content box size, need to set margin box size splitterStyle[splitterAttr] = delta + splitterStart + factor*(boundChildSize - childSize) + "px"; }), dojo.connect(de, "ondragstart", dojo.stopEvent), dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent), dojo.connect(de, "onmouseup", this, "_stopDrag") ]); dojo.stopEvent(e); }, _onMouse: function(e){ var o = (e.type == "mouseover" || e.type == "mouseenter"); dojo.toggleClass(this.domNode, "dijitSplitterHover", o); dojo.toggleClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o); }, _stopDrag: function(e){ try{ if(this.cover){ dojo.removeClass(this.cover, "dijitSplitterCoverActive"); } if(this.fake){ dojo.destroy(this.fake); } dojo.removeClass(this.domNode, "dijitSplitterActive dijitSplitter" + (this.horizontal ? "H" : "V") + "Active dijitSplitterShadow"); this._drag(e); //TODO: redundant with onmousemove? this._drag(e, true); }finally{ this._cleanupHandlers(); delete this._drag; } if(this.container.persist){ dojo.cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365}); } }, _cleanupHandlers: function(){ dojo.forEach(this._handlers, dojo.disconnect); delete this._handlers; }, _onKeyPress: function(/*Event*/ e){ // should we apply typematic to this? this._resize = true; var horizontal = this.horizontal; var tick = 1; var dk = dojo.keys; switch(e.charOrCode){ case horizontal ? dk.UP_ARROW : dk.LEFT_ARROW: tick *= -1; // break; case horizontal ? dk.DOWN_ARROW : dk.RIGHT_ARROW: break; default: // this.inherited(arguments); return; } var childSize = dojo._getMarginSize(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick; this.container._layoutChildren(this.child.id, Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize)); dojo.stopEvent(e); }, destroy: function(){ this._cleanupHandlers(); delete this.child; delete this.container; delete this.cover; delete this.fake; this.inherited(arguments); } }); dojo.declare("dijit.layout._Gutter", [dijit._Widget, dijit._Templated], { // summary: // Just a spacer div to separate side pane from center pane. // Basically a trick to lookup the gutter/splitter width from the theme. // description: // Instantiated by `dijit.layout.BorderContainer`. Users should not // create directly. // tags: // private templateString: '', postMixInProperties: function(){ this.inherited(arguments); this.horizontal = /top|bottom/.test(this.region); }, buildRendering: function(){ this.inherited(arguments); dojo.addClass(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V")); } }); } if(!dojo._hasResource["dijit.layout.StackController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit.layout.StackController"] = true; dojo.provide("dijit.layout.StackController"); dojo.declare( "dijit.layout.StackController", [dijit._Widget, dijit._Templated, dijit._Container], { // summary: // Set of buttons to select a page in a page list. // description: // Monitors the specified StackContainer, and whenever a page is // added, deleted, or selected, updates itself accordingly. templateString: "", // containerId: [const] String // The id of the page container that I point to containerId: "", // buttonWidget: [const] String // The name of the button widget to create to correspond to each page buttonWidget: "dijit.layout._StackButton", constructor: function(){ this.pane2button = {}; // mapping from pane id to buttons this.pane2connects = {}; // mapping from pane id to this.connect() handles this.pane2watches = {}; // mapping from pane id to watch() handles }, buildRendering: function(){ this.inherited(arguments); dijit.setWaiRole(this.domNode, "tablist"); // TODO: unneeded? it's in template above. }, postCreate: function(){ this.inherited(arguments); // Listen to notifications from StackContainer this.subscribe(this.containerId+"-startup", "onStartup"); this.subscribe(this.containerId+"-addChild", "onAddChild"); this.subscribe(this.containerId+"-removeChild", "onRemoveChild"); this.subscribe(this.containerId+"-selectChild", "onSelectChild"); this.subscribe(this.containerId+"-containerKeyPress", "onContainerKeyPress"); }, onStartup: function(/*Object*/ info){ // summary: // Called after StackContainer has finished initializing // tags: // private dojo.forEach(info.children, this.onAddChild, this); if(info.selected){ // Show button corresponding to selected pane (unless selected // is null because there are no panes) this.onSelectChild(info.selected); } }, destroy: function(){ for(var pane in this.pane2button){ this.onRemoveChild(dijit.byId(pane)); } this.inherited(arguments); }, onAddChild: function(/*dijit._Widget*/ page, /*Integer?*/ insertIndex){ // summary: // Called whenever a page is added to the container. // Create button corresponding to the page. // tags: // private // create an instance of the button widget var cls = dojo.getObject(this.buttonWidget); var button = new cls({ id: this.id + "_" + page.id, label: page.title, dir: page.dir, lang: page.lang, showLabel: page.showTitle, iconClass: page.iconClass, closeButton: page.closable, title: page.tooltip }); dijit.setWaiState(button.focusNode,"selected", "false"); // map from page attribute to corresponding tab button attribute var pageAttrList = ["title", "showTitle", "iconClass", "closable", "tooltip"], buttonAttrList = ["label", "showLabel", "iconClass", "closeButton", "title"]; // watch() so events like page title changes are reflected in tab button this.pane2watches[page.id] = dojo.map(pageAttrList, function(pageAttr, idx){ return page.watch(pageAttr, function(name, oldVal, newVal){ button.set(buttonAttrList[idx], newVal); }); }); // connections so that clicking a tab button selects the corresponding page this.pane2connects[page.id] = [ this.connect(button, 'onClick', dojo.hitch(this,"onButtonClick", page)), this.connect(button, 'onClickCloseButton', dojo.hitch(this,"onCloseButtonClick", page)) ]; this.addChild(button, insertIndex); this.pane2button[page.id] = button; page.controlButton = button; // this value might be overwritten if two tabs point to same container if(!this._currentChild){ // put the first child into the tab order button.focusNode.setAttribute("tabIndex", "0"); dijit.setWaiState(button.focusNode, "selected", "true"); this._currentChild = page; } // make sure all tabs have the same length if(!this.isLeftToRight() && dojo.isIE && this._rectifyRtlTabList){ this._rectifyRtlTabList(); } }, onRemoveChild: function(/*dijit._Widget*/ page){ // summary: // Called whenever a page is removed from the container. // Remove the button corresponding to the page. // tags: // private if(this._currentChild === page){ this._currentChild = null; } // disconnect/unwatch connections/watches related to page being removed dojo.forEach(this.pane2connects[page.id], dojo.hitch(this, "disconnect")); delete this.pane2connects[page.id]; dojo.forEach(this.pane2watches[page.id], function(w){ w.unwatch(); }); delete this.pane2watches[page.id]; var button = this.pane2button[page.id]; if(button){ this.removeChild(button); delete this.pane2button[page.id]; button.destroy(); } delete page.controlButton; }, onSelectChild: function(/*dijit._Widget*/ page){ // summary: // Called when a page has been selected in the StackContainer, either by me or by another StackController // tags: // private if(!page){ return; } if(this._currentChild){ var oldButton=this.pane2button[this._currentChild.id]; oldButton.set('checked', false); dijit.setWaiState(oldButton.focusNode, "selected", "false"); oldButton.focusNode.setAttribute("tabIndex", "-1"); } var newButton=this.pane2button[page.id]; newButton.set('checked', true); dijit.setWaiState(newButton.focusNode, "selected", "true"); this._currentChild = page; newButton.focusNode.setAttribute("tabIndex", "0"); var container = dijit.byId(this.containerId); dijit.setWaiState(container.containerNode, "labelledby", newButton.id); }, onButtonClick: function(/*dijit._Widget*/ page){ // summary: // Called whenever one of my child buttons is pressed in an attempt to select a page // tags: // private var container = dijit.byId(this.containerId); container.selectChild(page); }, onCloseButtonClick: function(/*dijit._Widget*/ page){ // summary: // Called whenever one of my child buttons [X] is pressed in an attempt to close a page // tags: // private var container = dijit.byId(this.containerId); container.closeChild(page); if(this._currentChild){ var b = this.pane2button[this._currentChild.id]; if(b){ dijit.focus(b.focusNode || b.domNode); } } }, // TODO: this is a bit redundant with forward, back api in StackContainer adjacent: function(/*Boolean*/ forward){ // summary: // Helper for onkeypress to find next/previous button // tags: // private if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !forward; } // find currently focused button in children array var children = this.getChildren(); var current = dojo.indexOf(children, this.pane2button[this._currentChild.id]); // pick next button to focus on var offset = forward ? 1 : children.length - 1; return children[ (current + offset) % children.length ]; // dijit._Widget }, onkeypress: function(/*Event*/ e){ // summary: // Handle keystrokes on the page list, for advancing to next/previous button // and closing the current page if the page is closable. // tags: // private if(this.disabled || e.altKey ){ return; } var forward = null; if(e.ctrlKey || !e._djpage){ var k = dojo.keys; switch(e.charOrCode){ case k.LEFT_ARROW: case k.UP_ARROW: if(!e._djpage){ forward = false; } break; case k.PAGE_UP: if(e.ctrlKey){ forward = false; } break; case k.RIGHT_ARROW: case k.DOWN_ARROW: if(!e._djpage){ forward = true; } break; case k.PAGE_DOWN: if(e.ctrlKey){ forward = true; } break; case k.HOME: case k.END: var children = this.getChildren(); if(children && children.length){ children[e.charOrCode == k.HOME ? 0 : children.length-1].onClick(); } dojo.stopEvent(e); break; case k.DELETE: if(this._currentChild.closable){ this.onCloseButtonClick(this._currentChild); } dojo.stopEvent(e); break; default: if(e.ctrlKey){ if(e.charOrCode === k.TAB){ this.adjacent(!e.shiftKey).onClick(); dojo.stopEvent(e); }else if(e.charOrCode == "w"){ if(this._currentChild.closable){ this.onCloseButtonClick(this._currentChild); } dojo.stopEvent(e); // avoid browser tab closing. } } } // handle next/previous page navigation (left/right arrow, etc.) if(forward !== null){ this.adjacent(forward).onClick(); dojo.stopEvent(e); } } }, onContainerKeyPress: function(/*Object*/ info){ // summary: // Called when there was a keypress on the container // tags: // private info.e._djpage = info.page; this.onkeypress(info.e); } }); dojo.declare("dijit.layout._StackButton", dijit.form.ToggleButton, { // summary: // Internal widget used by StackContainer. // description: // The button-like or tab-like object you click to select or delete a page // tags: // private // Override _FormWidget.tabIndex. // StackContainer buttons are not in the tab order by default. // Probably we should be calling this.startupKeyNavChildren() instead. tabIndex: "-1", buildRendering: function(/*Event*/ evt){ this.inherited(arguments); dijit.setWaiRole((this.focusNode || this.domNode), "tab"); }, onClick: function(/*Event*/ evt){ // summary: // This is for TabContainer where the tabs are rather than button, // so need to set focus explicitly (on some browsers) // Note that you shouldn't override this method, but you can connect to it. dijit.focus(this.focusNode); // ... now let StackController catch the event and tell me what to do }, onClickCloseButton: function(/*Event*/ evt){ // summary: // StackContainer connects to this function; if your widget contains a close button // then clicking it should call this function. // Note that you shouldn't override this method, but you can connect to it. evt.stopPropagation(); } }); } if(!dojo._hasResource["dijit.layout.StackContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit.layout.StackContainer"] = true; dojo.provide("dijit.layout.StackContainer"); dojo.declare( "dijit.layout.StackContainer", dijit.layout._LayoutWidget, { // summary: // A container that has multiple children, but shows only // one child at a time // // description: // A container for widgets (ContentPanes, for example) That displays // only one Widget at a time. // // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild // // Can be base class for container, Wizard, Show, etc. // doLayout: Boolean // If true, change the size of my currently displayed child to match my size doLayout: true, // persist: Boolean // Remembers the selected child across sessions persist: false, baseClass: "dijitStackContainer", /*===== // selectedChildWidget: [readonly] dijit._Widget // References the currently selected child widget, if any. // Adjust selected child with selectChild() method. selectedChildWidget: null, =====*/ buildRendering: function(){ this.inherited(arguments); dojo.addClass(this.domNode, "dijitLayoutContainer"); dijit.setWaiRole(this.containerNode, "tabpanel"); }, postCreate: function(){ this.inherited(arguments); this.connect(this.domNode, "onkeypress", this._onKeyPress); }, startup: function(){ if(this._started){ return; } var children = this.getChildren(); // Setup each page panel to be initially hidden dojo.forEach(children, this._setupChild, this); // Figure out which child to initially display, defaulting to first one if(this.persist){ this.selectedChildWidget = dijit.byId(dojo.cookie(this.id + "_selectedChild")); }else{ dojo.some(children, function(child){ if(child.selected){ this.selectedChildWidget = child; } return child.selected; }, this); } var selected = this.selectedChildWidget; if(!selected && children[0]){ selected = this.selectedChildWidget = children[0]; selected.selected = true; } // Publish information about myself so any StackControllers can initialize. // This needs to happen before this.inherited(arguments) so that for // TabContainer, this._contentBox doesn't include the space for the tab labels. dojo.publish(this.id+"-startup", [{children: children, selected: selected}]); // Startup each child widget, and do initial layout like setting this._contentBox, // then calls this.resize() which does the initial sizing on the selected child. this.inherited(arguments); }, resize: function(){ // Resize is called when we are first made visible (it's called from startup() // if we are initially visible). If this is the first time we've been made // visible then show our first child. if(!this._hasBeenShown){ this._hasBeenShown = true; var selected = this.selectedChildWidget; if(selected){ this._showChild(selected); } } this.inherited(arguments); }, _setupChild: function(/*dijit._Widget*/ child){ // Overrides _LayoutWidget._setupChild() this.inherited(arguments); dojo.replaceClass(child.domNode, "dijitHidden", "dijitVisible"); // remove the title attribute so it doesn't show up when i hover // over a node child.domNode.title = ""; }, addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ // Overrides _Container.addChild() to do layout and publish events this.inherited(arguments); if(this._started){ dojo.publish(this.id+"-addChild", [child, insertIndex]); // in case the tab titles have overflowed from one line to two lines // (or, if this if first child, from zero lines to one line) // TODO: w/ScrollingTabController this is no longer necessary, although // ScrollTabController.resize() does need to get called to show/hide // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild() this.layout(); // if this is the first child, then select it if(!this.selectedChildWidget){ this.selectChild(child); } } }, removeChild: function(/*dijit._Widget*/ page){ // Overrides _Container.removeChild() to do layout and publish events this.inherited(arguments); if(this._started){ // this will notify any tablists to remove a button; do this first because it may affect sizing dojo.publish(this.id + "-removeChild", [page]); } // If we are being destroyed than don't run the code below (to select another page), because we are deleting // every page one by one if(this._beingDestroyed){ return; } // Select new page to display, also updating TabController to show the respective tab. // Do this before layout call because it can affect the height of the TabController. if(this.selectedChildWidget === page){ this.selectedChildWidget = undefined; if(this._started){ var children = this.getChildren(); if(children.length){ this.selectChild(children[0]); } } } if(this._started){ // In case the tab titles now take up one line instead of two lines // (note though that ScrollingTabController never overflows to multiple lines), // or the height has changed slightly because of addition/removal of tab which close icon this.layout(); } }, selectChild: function(/*dijit._Widget|String*/ page, /*Boolean*/ animate){ // summary: // Show the given widget (which must be one of my children) // page: // Reference to child widget or id of child widget page = dijit.byId(page); if(this.selectedChildWidget != page){ // Deselect old page and select new one var d = this._transition(page, this.selectedChildWidget, animate); this._set("selectedChildWidget", page); dojo.publish(this.id+"-selectChild", [page]); if(this.persist){ dojo.cookie(this.id + "_selectedChild", this.selectedChildWidget.id); } } return d; // If child has an href, promise that fires when the child's href finishes loading }, _transition: function(/*dijit._Widget*/ newWidget, /*dijit._Widget*/ oldWidget, /*Boolean*/ animate){ // summary: // Hide the old widget and display the new widget. // Subclasses should override this. // tags: // protected extension if(oldWidget){ this._hideChild(oldWidget); } var d = this._showChild(newWidget); // Size the new widget, in case this is the first time it's being shown, // or I have been resized since the last time it was shown. // Note that page must be visible for resizing to work. if(newWidget.resize){ if(this.doLayout){ newWidget.resize(this._containerContentBox || this._contentBox); }else{ // the child should pick it's own size but we still need to call resize() // (with no arguments) to let the widget lay itself out newWidget.resize(); } } return d; // If child has an href, promise that fires when the child's href finishes loading }, _adjacent: function(/*Boolean*/ forward){ // summary: // Gets the next/previous child widget in this container from the current selection. var children = this.getChildren(); var index = dojo.indexOf(children, this.selectedChildWidget); index += forward ? 1 : children.length - 1; return children[ index % children.length ]; // dijit._Widget }, forward: function(){ // summary: // Advance to next page. return this.selectChild(this._adjacent(true), true); }, back: function(){ // summary: // Go back to previous page. return this.selectChild(this._adjacent(false), true); }, _onKeyPress: function(e){ dojo.publish(this.id+"-containerKeyPress", [{ e: e, page: this}]); }, layout: function(){ // Implement _LayoutWidget.layout() virtual method. if(this.doLayout && this.selectedChildWidget && this.selectedChildWidget.resize){ this.selectedChildWidget.resize(this._containerContentBox || this._contentBox); } }, _showChild: function(/*dijit._Widget*/ page){ // summary: // Show the specified child by changing it's CSS, and call _onShow()/onShow() so // it can do any updates it needs regarding loading href's etc. // returns: // Promise that fires when page has finished showing, or true if there's no href var children = this.getChildren(); page.isFirstChild = (page == children[0]); page.isLastChild = (page == children[children.length-1]); page._set("selected", true); dojo.replaceClass(page.domNode, "dijitVisible", "dijitHidden"); return page._onShow() || true; }, _hideChild: function(/*dijit._Widget*/ page){ // summary: // Hide the specified child by changing it's CSS, and call _onHide() so // it's notified. page._set("selected", false); dojo.replaceClass(page.domNode, "dijitHidden", "dijitVisible"); page.onHide(); }, closeChild: function(/*dijit._Widget*/ page){ // summary: // Callback when user clicks the [X] to remove a page. // If onClose() returns true then remove and destroy the child. // tags: // private var remove = page.onClose(this, page); if(remove){ this.removeChild(page); // makes sure we can clean up executeScripts in ContentPane onUnLoad page.destroyRecursive(); } }, destroyDescendants: function(/*Boolean*/ preserveDom){ dojo.forEach(this.getChildren(), function(child){ this.removeChild(child); child.destroyRecursive(preserveDom); }, this); } }); // For back-compat, remove for 2.0 // These arguments can be specified for the children of a StackContainer. // Since any widget can be specified as a StackContainer child, mix them // into the base widget class. (This is a hack, but it's effective.) dojo.extend(dijit._Widget, { // selected: Boolean // Parameter for children of `dijit.layout.StackContainer` or subclasses. // Specifies that this widget should be the initially displayed pane. // Note: to change the selected child use `dijit.layout.StackContainer.selectChild` selected: false, // closable: Boolean // Parameter for children of `dijit.layout.StackContainer` or subclasses. // True if user can close (destroy) this child, such as (for example) clicking the X on the tab. closable: false, // iconClass: String // Parameter for children of `dijit.layout.StackContainer` or subclasses. // CSS Class specifying icon to use in label associated with this pane. iconClass: "", // showTitle: Boolean // Parameter for children of `dijit.layout.StackContainer` or subclasses. // When true, display title of this widget as tab label etc., rather than just using // icon specified in iconClass showTitle: true }); } if(!dojo._hasResource["dijit.layout._TabContainerBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit.layout._TabContainerBase"] = true; dojo.provide("dijit.layout._TabContainerBase"); dojo.declare("dijit.layout._TabContainerBase", [dijit.layout.StackContainer, dijit._Templated], { // summary: // Abstract base class for TabContainer. Must define _makeController() to instantiate // and return the widget that displays the tab labels // description: // A TabContainer is a container that has multiple panes, but shows only // one pane at a time. There are a set of tabs corresponding to each pane, // where each tab has the name (aka title) of the pane, and optionally a close button. // tabPosition: String // Defines where tabs go relative to tab content. // "top", "bottom", "left-h", "right-h" tabPosition: "top", baseClass: "dijitTabContainer", // tabStrip: [const] Boolean // Defines whether the tablist gets an extra class for layouting, putting a border/shading // around the set of tabs. Not supported by claro theme. tabStrip: false, // nested: [const] Boolean // If true, use styling for a TabContainer nested inside another TabContainer. // For tundra etc., makes tabs look like links, and hides the outer // border since the outer TabContainer already has a border. nested: false, templateString: dojo.cache("dijit.layout", "templates/TabContainer.html", "
\n\t
\n\t
\n\t
\n
\n"), postMixInProperties: function(){ // set class name according to tab position, ex: dijitTabContainerTop this.baseClass += this.tabPosition.charAt(0).toUpperCase() + this.tabPosition.substr(1).replace(/-.*/, ""); this.srcNodeRef && dojo.style(this.srcNodeRef, "visibility", "hidden"); this.inherited(arguments); }, buildRendering: function(){ this.inherited(arguments); // Create the tab list that will have a tab (a.k.a. tab button) for each tab panel this.tablist = this._makeController(this.tablistNode); if(!this.doLayout){ dojo.addClass(this.domNode, "dijitTabContainerNoLayout"); } if(this.nested){ /* workaround IE's lack of support for "a > b" selectors by * tagging each node in the template. */ dojo.addClass(this.domNode, "dijitTabContainerNested"); dojo.addClass(this.tablist.containerNode, "dijitTabContainerTabListNested"); dojo.addClass(this.tablistSpacer, "dijitTabContainerSpacerNested"); dojo.addClass(this.containerNode, "dijitTabPaneWrapperNested"); }else{ dojo.addClass(this.domNode, "tabStrip-" + (this.tabStrip ? "enabled" : "disabled")); } }, _setupChild: function(/*dijit._Widget*/ tab){ // Overrides StackContainer._setupChild(). dojo.addClass(tab.domNode, "dijitTabPane"); this.inherited(arguments); }, startup: function(){ if(this._started){ return; } // wire up the tablist and its tabs this.tablist.startup(); this.inherited(arguments); }, layout: function(){ // Overrides StackContainer.layout(). // Configure the content pane to take up all the space except for where the tabs are if(!this._contentBox || typeof(this._contentBox.l) == "undefined"){return;} var sc = this.selectedChildWidget; if(this.doLayout){ // position and size the titles and the container node var titleAlign = this.tabPosition.replace(/-h/, ""); this.tablist.layoutAlign = titleAlign; var children = [this.tablist, { domNode: this.tablistSpacer, layoutAlign: titleAlign }, { domNode: this.containerNode, layoutAlign: "client" }]; dijit.layout.layoutChildren(this.domNode, this._contentBox, children); // Compute size to make each of my children. // children[2] is the margin-box size of this.containerNode, set by layoutChildren() call above this._containerContentBox = dijit.layout.marginBox2contentBox(this.containerNode, children[2]); if(sc && sc.resize){ sc.resize(this._containerContentBox); } }else{ // just layout the tab controller, so it can position left/right buttons etc. if(this.tablist.resize){ //make the tabs zero width so that they don't interfere with width calc, then reset var s = this.tablist.domNode.style; s.width="0"; var width = dojo.contentBox(this.domNode).w; s.width=""; this.tablist.resize({w: width}); } // and call resize() on the selected pane just to tell it that it's been made visible if(sc && sc.resize){ sc.resize(); } } }, destroy: function(){ if(this.tablist){ this.tablist.destroy(); } this.inherited(arguments); } }); } if(!dojo._hasResource["dijit.layout.TabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit.layout.TabController"] = true; dojo.provide("dijit.layout.TabController"); // Menu is used for an accessible close button, would be nice to have a lighter-weight solution dojo.declare("dijit.layout.TabController", dijit.layout.StackController, { // summary: // Set of tabs (the things with titles and a close button, that you click to show a tab panel). // Used internally by `dijit.layout.TabContainer`. // description: // Lets the user select the currently shown pane in a TabContainer or StackContainer. // TabController also monitors the TabContainer, and whenever a pane is // added or deleted updates itself accordingly. // tags: // private templateString: "
", // tabPosition: String // Defines where tabs go relative to the content. // "top", "bottom", "left-h", "right-h" tabPosition: "top", // buttonWidget: String // The name of the tab widget to create to correspond to each page buttonWidget: "dijit.layout._TabButton", _rectifyRtlTabList: function(){ // summary: // For left/right TabContainer when page is RTL mode, rectify the width of all tabs to be equal, otherwise the tab widths are different in IE if(0 >= this.tabPosition.indexOf('-h')){ return; } if(!this.pane2button){ return; } var maxWidth = 0; for(var pane in this.pane2button){ var ow = this.pane2button[pane].innerDiv.scrollWidth; maxWidth = Math.max(maxWidth, ow); } //unify the length of all the tabs for(pane in this.pane2button){ this.pane2button[pane].innerDiv.style.width = maxWidth + 'px'; } } }); dojo.declare("dijit.layout._TabButton", dijit.layout._StackButton, { // summary: // A tab (the thing you click to select a pane). // description: // Contains the title of the pane, and optionally a close-button to destroy the pane. // This is an internal widget and should not be instantiated directly. // tags: // private // baseClass: String // The CSS class applied to the domNode. baseClass: "dijitTab", // Apply dijitTabCloseButtonHover when close button is hovered cssStateNodes: { closeNode: "dijitTabCloseButton" }, templateString: dojo.cache("dijit.layout", "templates/_TabButton.html", "
\n
\n
\n \t
\n\t\t \"\"\n\t\t \n\t\t \n\t\t [x]\n\t\t\t
\n
\n
\n
\n"), // Override _FormWidget.scrollOnFocus. // Don't scroll the whole tab container into view when the button is focused. scrollOnFocus: false, buildRendering: function(){ this.inherited(arguments); dojo.setSelectable(this.containerNode, false); }, startup: function(){ this.inherited(arguments); var n = this.domNode; // Required to give IE6 a kick, as it initially hides the // tabs until they are focused on. setTimeout(function(){ n.className = n.className; }, 1); }, _setCloseButtonAttr: function(/*Boolean*/ disp){ // summary: // Hide/show close button this._set("closeButton", disp); dojo.toggleClass(this.innerDiv, "dijitClosable", disp); this.closeNode.style.display = disp ? "" : "none"; if(disp){ var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); if(this.closeNode){ dojo.attr(this.closeNode,"title", _nlsResources.itemClose); } // add context menu onto title button var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); this._closeMenu = new dijit.Menu({ id: this.id+"_Menu", dir: this.dir, lang: this.lang, targetNodeIds: [this.domNode] }); this._closeMenu.addChild(new dijit.MenuItem({ label: _nlsResources.itemClose, dir: this.dir, lang: this.lang, onClick: dojo.hitch(this, "onClickCloseButton") })); }else{ if(this._closeMenu){ this._closeMenu.destroyRecursive(); delete this._closeMenu; } } }, _setLabelAttr: function(/*String*/ content){ // summary: // Hook for set('label', ...) to work. // description: // takes an HTML string. // Inherited ToggleButton implementation will Set the label (text) of the button; // Need to set the alt attribute of icon on tab buttons if no label displayed this.inherited(arguments); if(this.showLabel == false && !this.params.title){ this.iconNode.alt = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || ''); } }, destroy: function(){ if(this._closeMenu){ this._closeMenu.destroyRecursive(); delete this._closeMenu; } this.inherited(arguments); } }); } if(!dojo._hasResource["dijit.layout.ScrollingTabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit.layout.ScrollingTabController"] = true; dojo.provide("dijit.layout.ScrollingTabController"); dojo.declare("dijit.layout.ScrollingTabController", dijit.layout.TabController, { // summary: // Set of tabs with left/right arrow keys and a menu to switch between tabs not // all fitting on a single row. // Works only for horizontal tabs (either above or below the content, not to the left // or right). // tags: // private templateString: dojo.cache("dijit.layout", "templates/ScrollingTabController.html", "
\n\t
\n\t
\n\t
\n\t
\n\t\t
\n\t
\n
\n"), // useMenu: [const] Boolean // True if a menu should be used to select tabs when they are too // wide to fit the TabContainer, false otherwise. useMenu: true, // useSlider: [const] Boolean // True if a slider should be used to select tabs when they are too // wide to fit the TabContainer, false otherwise. useSlider: true, // tabStripClass: [const] String // The css class to apply to the tab strip, if it is visible. tabStripClass: "", widgetsInTemplate: true, // _minScroll: Number // The distance in pixels from the edge of the tab strip which, // if a scroll animation is less than, forces the scroll to // go all the way to the left/right. _minScroll: 5, attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { "class": "containerNode" }), buildRendering: function(){ this.inherited(arguments); var n = this.domNode; this.scrollNode = this.tablistWrapper; this._initButtons(); if(!this.tabStripClass){ this.tabStripClass = "dijitTabContainer" + this.tabPosition.charAt(0).toUpperCase() + this.tabPosition.substr(1).replace(/-.*/, "") + "None"; dojo.addClass(n, "tabStrip-disabled") } dojo.addClass(this.tablistWrapper, this.tabStripClass); }, onStartup: function(){ this.inherited(arguments); // Do not show the TabController until the related // StackController has added it's children. This gives // a less visually jumpy instantiation. dojo.style(this.domNode, "visibility", "visible"); this._postStartup = true; }, onAddChild: function(page, insertIndex){ this.inherited(arguments); // changes to the tab button label or iconClass will have changed the width of the // buttons, so do a resize dojo.forEach(["label", "iconClass"], function(attr){ this.pane2watches[page.id].push( this.pane2button[page.id].watch(attr, dojo.hitch(this, function(name, oldValue, newValue){ if(this._postStartup && this._dim){ this.resize(this._dim); } })) ); }, this); // Increment the width of the wrapper when a tab is added // This makes sure that the buttons never wrap. // The value 200 is chosen as it should be bigger than most // Tab button widths. dojo.style(this.containerNode, "width", (dojo.style(this.containerNode, "width") + 200) + "px"); }, onRemoveChild: function(page, insertIndex){ // null out _selectedTab because we are about to delete that dom node var button = this.pane2button[page.id]; if(this._selectedTab === button.domNode){ this._selectedTab = null; } this.inherited(arguments); }, _initButtons: function(){ // summary: // Creates the buttons used to scroll to view tabs that // may not be visible if the TabContainer is too narrow. // Make a list of the buttons to display when the tab labels become // wider than the TabContainer, and hide the other buttons. // Also gets the total width of the displayed buttons. this._btnWidth = 0; this._buttons = dojo.query("> .tabStripButton", this.domNode).filter(function(btn){ if((this.useMenu && btn == this._menuBtn.domNode) || (this.useSlider && (btn == this._rightBtn.domNode || btn == this._leftBtn.domNode))){ this._btnWidth += dojo._getMarginSize(btn).w; return true; }else{ dojo.style(btn, "display", "none"); return false; } }, this); }, _getTabsWidth: function(){ var children = this.getChildren(); if(children.length){ var leftTab = children[this.isLeftToRight() ? 0 : children.length - 1].domNode, rightTab = children[this.isLeftToRight() ? children.length - 1 : 0].domNode; return rightTab.offsetLeft + dojo.style(rightTab, "width") - leftTab.offsetLeft; }else{ return 0; } }, _enableBtn: function(width){ // summary: // Determines if the tabs are wider than the width of the TabContainer, and // thus that we need to display left/right/menu navigation buttons. var tabsWidth = this._getTabsWidth(); width = width || dojo.style(this.scrollNode, "width"); return tabsWidth > 0 && width < tabsWidth; }, resize: function(dim){ // summary: // Hides or displays the buttons used to scroll the tab list and launch the menu // that selects tabs. if(this.domNode.offsetWidth == 0){ return; } // Save the dimensions to be used when a child is renamed. this._dim = dim; // Set my height to be my natural height (tall enough for one row of tab labels), // and my content-box width based on margin-box width specified in dim parameter. // But first reset scrollNode.height in case it was set by layoutChildren() call // in a previous run of this method. this.scrollNode.style.height = "auto"; this._contentBox = dijit.layout.marginBox2contentBox(this.domNode, {h: 0, w: dim.w}); this._contentBox.h = this.scrollNode.offsetHeight; dojo.contentBox(this.domNode, this._contentBox); // Show/hide the left/right/menu navigation buttons depending on whether or not they // are needed. var enable = this._enableBtn(this._contentBox.w); this._buttons.style("display", enable ? "" : "none"); // Position and size the navigation buttons and the tablist this._leftBtn.layoutAlign = "left"; this._rightBtn.layoutAlign = "right"; this._menuBtn.layoutAlign = this.isLeftToRight() ? "right" : "left"; dijit.layout.layoutChildren(this.domNode, this._contentBox, [this._menuBtn, this._leftBtn, this._rightBtn, {domNode: this.scrollNode, layoutAlign: "client"}]); // set proper scroll so that selected tab is visible if(this._selectedTab){ if(this._anim && this._anim.status() == "playing"){ this._anim.stop(); } var w = this.scrollNode, sl = this._convertToScrollLeft(this._getScrollForSelectedTab()); w.scrollLeft = sl; } // Enable/disabled left right buttons depending on whether or not user can scroll to left or right this._setButtonClass(this._getScroll()); this._postResize = true; // Return my size so layoutChildren() can use it. // Also avoids IE9 layout glitch on browser resize when scroll buttons present return {h: this._contentBox.h, w: dim.w}; }, _getScroll: function(){ // summary: // Returns the current scroll of the tabs where 0 means // "scrolled all the way to the left" and some positive number, based on # // of pixels of possible scroll (ex: 1000) means "scrolled all the way to the right" var sl = (this.isLeftToRight() || dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.isWebKit) ? this.scrollNode.scrollLeft : dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width") + (dojo.isIE == 8 ? -1 : 1) * this.scrollNode.scrollLeft; return sl; }, _convertToScrollLeft: function(val){ // summary: // Given a scroll value where 0 means "scrolled all the way to the left" // and some positive number, based on # of pixels of possible scroll (ex: 1000) // means "scrolled all the way to the right", return value to set this.scrollNode.scrollLeft // to achieve that scroll. // // This method is to adjust for RTL funniness in various browsers and versions. if(this.isLeftToRight() || dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.isWebKit){ return val; }else{ var maxScroll = dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width"); return (dojo.isIE == 8 ? -1 : 1) * (val - maxScroll); } }, onSelectChild: function(/*dijit._Widget*/ page){ // summary: // Smoothly scrolls to a tab when it is selected. var tab = this.pane2button[page.id]; if(!tab || !page){return;} // Scroll to the selected tab, except on startup, when scrolling is handled in resize() var node = tab.domNode; if(this._postResize && node != this._selectedTab){ this._selectedTab = node; var sl = this._getScroll(); if(sl > node.offsetLeft || sl + dojo.style(this.scrollNode, "width") < node.offsetLeft + dojo.style(node, "width")){ this.createSmoothScroll().play(); } } this.inherited(arguments); }, _getScrollBounds: function(){ // summary: // Returns the minimum and maximum scroll setting to show the leftmost and rightmost // tabs (respectively) var children = this.getChildren(), scrollNodeWidth = dojo.style(this.scrollNode, "width"), // about 500px containerWidth = dojo.style(this.containerNode, "width"), // 50,000px maxPossibleScroll = containerWidth - scrollNodeWidth, // scrolling until right edge of containerNode visible tabsWidth = this._getTabsWidth(); if(children.length && tabsWidth > scrollNodeWidth){ // Scrolling should happen return { min: this.isLeftToRight() ? 0 : children[children.length-1].domNode.offsetLeft, max: this.isLeftToRight() ? (children[children.length-1].domNode.offsetLeft + dojo.style(children[children.length-1].domNode, "width")) - scrollNodeWidth : maxPossibleScroll }; }else{ // No scrolling needed, all tabs visible, we stay either scrolled to far left or far right (depending on dir) var onlyScrollPosition = this.isLeftToRight() ? 0 : maxPossibleScroll; return { min: onlyScrollPosition, max: onlyScrollPosition }; } }, _getScrollForSelectedTab: function(){ // summary: // Returns the scroll value setting so that the selected tab // will appear in the center var w = this.scrollNode, n = this._selectedTab, scrollNodeWidth = dojo.style(this.scrollNode, "width"), scrollBounds = this._getScrollBounds(); // TODO: scroll minimal amount (to either right or left) so that // selected tab is fully visible, and just return if it's already visible? var pos = (n.offsetLeft + dojo.style(n, "width")/2) - scrollNodeWidth/2; pos = Math.min(Math.max(pos, scrollBounds.min), scrollBounds.max); // TODO: // If scrolling close to the left side or right side, scroll // all the way to the left or right. See this._minScroll. // (But need to make sure that doesn't scroll the tab out of view...) return pos; }, createSmoothScroll: function(x){ // summary: // Creates a dojo._Animation object that smoothly scrolls the tab list // either to a fixed horizontal pixel value, or to the selected tab. // description: // If an number argument is passed to the function, that horizontal // pixel position is scrolled to. Otherwise the currently selected // tab is scrolled to. // x: Integer? // An optional pixel value to scroll to, indicating distance from left. // Calculate position to scroll to if(arguments.length > 0){ // position specified by caller, just make sure it's within bounds var scrollBounds = this._getScrollBounds(); x = Math.min(Math.max(x, scrollBounds.min), scrollBounds.max); }else{ // scroll to center the current tab x = this._getScrollForSelectedTab(); } if(this._anim && this._anim.status() == "playing"){ this._anim.stop(); } var self = this, w = this.scrollNode, anim = new dojo._Animation({ beforeBegin: function(){ if(this.curve){ delete this.curve; } var oldS = w.scrollLeft, newS = self._convertToScrollLeft(x); anim.curve = new dojo._Line(oldS, newS); }, onAnimate: function(val){ w.scrollLeft = val; } }); this._anim = anim; // Disable/enable left/right buttons according to new scroll position this._setButtonClass(x); return anim; // dojo._Animation }, _getBtnNode: function(/*Event*/ e){ // summary: // Gets a button DOM node from a mouse click event. // e: // The mouse click event. var n = e.target; while(n && !dojo.hasClass(n, "tabStripButton")){ n = n.parentNode; } return n; }, doSlideRight: function(/*Event*/ e){ // summary: // Scrolls the menu to the right. // e: // The mouse click event. this.doSlide(1, this._getBtnNode(e)); }, doSlideLeft: function(/*Event*/ e){ // summary: // Scrolls the menu to the left. // e: // The mouse click event. this.doSlide(-1,this._getBtnNode(e)); }, doSlide: function(/*Number*/ direction, /*DomNode*/ node){ // summary: // Scrolls the tab list to the left or right by 75% of the widget width. // direction: // If the direction is 1, the widget scrolls to the right, if it is // -1, it scrolls to the left. if(node && dojo.hasClass(node, "dijitTabDisabled")){return;} var sWidth = dojo.style(this.scrollNode, "width"); var d = (sWidth * 0.75) * direction; var to = this._getScroll() + d; this._setButtonClass(to); this.createSmoothScroll(to).play(); }, _setButtonClass: function(/*Number*/ scroll){ // summary: // Disables the left scroll button if the tabs are scrolled all the way to the left, // or the right scroll button in the opposite case. // scroll: Integer // amount of horizontal scroll var scrollBounds = this._getScrollBounds(); this._leftBtn.set("disabled", scroll <= scrollBounds.min); this._rightBtn.set("disabled", scroll >= scrollBounds.max); } }); dojo.declare("dijit.layout._ScrollingTabControllerButtonMixin", null, { baseClass: "dijitTab tabStripButton", templateString: dojo.cache("dijit.layout", "templates/_ScrollingTabControllerButton.html", "
\n\t
\n\t\t
\n\t\t\t\"\"\n\t\t\t\n\t\t
\n\t
\n
\n"), // Override inherited tabIndex: 0 from dijit.form.Button, because user shouldn't be // able to tab to the left/right/menu buttons tabIndex: "", // Similarly, override FormWidget.isFocusable() because clicking a button shouldn't focus it // either (this override avoids focus() call in FormWidget.js) isFocusable: function(){ return false; } }); dojo.declare("dijit.layout._ScrollingTabControllerButton", [dijit.form.Button, dijit.layout._ScrollingTabControllerButtonMixin]); dojo.declare( "dijit.layout._ScrollingTabControllerMenuButton", [dijit.form.Button, dijit._HasDropDown, dijit.layout._ScrollingTabControllerButtonMixin], { // id of the TabContainer itself containerId: "", // -1 so user can't tab into the button, but so that button can still be focused programatically. // Because need to move focus to the button (or somewhere) before the menu is hidden or IE6 will crash. tabIndex: "-1", isLoaded: function(){ // recreate menu every time, in case the TabContainer's list of children (or their icons/labels) have changed return false; }, loadDropDown: function(callback){ this.dropDown = new dijit.Menu({ id: this.containerId + "_menu", dir: this.dir, lang: this.lang }); var container = dijit.byId(this.containerId); dojo.forEach(container.getChildren(), function(page){ var menuItem = new dijit.MenuItem({ id: page.id + "_stcMi", label: page.title, iconClass: page.iconClass, dir: page.dir, lang: page.lang, onClick: function(){ container.selectChild(page); } }); this.dropDown.addChild(menuItem); }, this); callback(); }, closeDropDown: function(/*Boolean*/ focus){ this.inherited(arguments); if(this.dropDown){ this.dropDown.destroyRecursive(); delete this.dropDown; } } }); } if(!dojo._hasResource["dijit.layout.TabContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit.layout.TabContainer"] = true; dojo.provide("dijit.layout.TabContainer"); dojo.declare("dijit.layout.TabContainer", dijit.layout._TabContainerBase, { // summary: // A Container with tabs to select each child (only one of which is displayed at a time). // description: // A TabContainer is a container that has multiple panes, but shows only // one pane at a time. There are a set of tabs corresponding to each pane, // where each tab has the name (aka title) of the pane, and optionally a close button. // useMenu: [const] Boolean // True if a menu should be used to select tabs when they are too // wide to fit the TabContainer, false otherwise. useMenu: true, // useSlider: [const] Boolean // True if a slider should be used to select tabs when they are too // wide to fit the TabContainer, false otherwise. useSlider: true, // controllerWidget: String // An optional parameter to override the widget used to display the tab labels controllerWidget: "", _makeController: function(/*DomNode*/ srcNode){ // summary: // Instantiate tablist controller widget and return reference to it. // Callback from _TabContainerBase.postCreate(). // tags: // protected extension var cls = this.baseClass + "-tabs" + (this.doLayout ? "" : " dijitTabNoLayout"), TabController = dojo.getObject(this.controllerWidget); return new TabController({ id: this.id + "_tablist", dir: this.dir, lang: this.lang, tabPosition: this.tabPosition, doLayout: this.doLayout, containerId: this.id, "class": cls, nested: this.nested, useMenu: this.useMenu, useSlider: this.useSlider, tabStripClass: this.tabStrip ? this.baseClass + (this.tabStrip ? "":"No") + "Strip": null }, srcNode); }, postMixInProperties: function(){ this.inherited(arguments); // Scrolling controller only works for horizontal non-nested tabs if(!this.controllerWidget){ this.controllerWidget = (this.tabPosition == "top" || this.tabPosition == "bottom") && !this.nested ? "dijit.layout.ScrollingTabController" : "dijit.layout.TabController"; } } }); } if(!dojo._hasResource["dijit.TitlePane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit.TitlePane"] = true; dojo.provide("dijit.TitlePane"); dojo.declare( "dijit.TitlePane", [dijit.layout.ContentPane, dijit._Templated, dijit._CssStateMixin], { // summary: // A pane with a title on top, that can be expanded or collapsed. // // description: // An accessible container with a title Heading, and a content // section that slides open and closed. TitlePane is an extension to // `dijit.layout.ContentPane`, providing all the useful content-control aspects from it. // // example: // | // load a TitlePane from remote file: // | var foo = new dijit.TitlePane({ href: "foobar.html", title:"Title" }); // | foo.startup(); // // example: // | // |
// // example: // | // |
// |

I am content

// |
// title: String // Title of the pane title: "", // open: Boolean // Whether pane is opened or closed. open: true, // toggleable: Boolean // Whether pane can be opened or closed by clicking the title bar. toggleable: true, // tabIndex: String // Tabindex setting for the title (so users can tab to the title then // use space/enter to open/close the title pane) tabIndex: "0", // duration: Integer // Time in milliseconds to fade in/fade out duration: dijit.defaultDuration, // baseClass: [protected] String // The root className to be placed on this widget's domNode. baseClass: "dijitTitlePane", templateString: dojo.cache("dijit", "templates/TitlePane.html", "
\n\t
\n\t\t
\n\t\t\t\"\"\n\t\t
\n\t
\n\t
\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t
\n
\n"), attributeMap: dojo.delegate(dijit.layout.ContentPane.prototype.attributeMap, { title: { node: "titleNode", type: "innerHTML" }, tooltip: {node: "focusNode", type: "attribute", attribute: "title"}, // focusNode spans the entire width, titleNode doesn't id:"" }), buildRendering: function(){ this.inherited(arguments); dojo.setSelectable(this.titleNode, false); }, postCreate: function(){ this.inherited(arguments); // Hover and focus effect on title bar, except for non-toggleable TitlePanes // This should really be controlled from _setToggleableAttr() but _CssStateMixin // doesn't provide a way to disconnect a previous _trackMouseState() call if(this.toggleable){ this._trackMouseState(this.titleBarNode, "dijitTitlePaneTitle"); } // setup open/close animations var hideNode = this.hideNode, wipeNode = this.wipeNode; this._wipeIn = dojo.fx.wipeIn({ node: this.wipeNode, duration: this.duration, beforeBegin: function(){ hideNode.style.display=""; } }); this._wipeOut = dojo.fx.wipeOut({ node: this.wipeNode, duration: this.duration, onEnd: function(){ hideNode.style.display="none"; } }); }, _setOpenAttr: function(/*Boolean*/ open, /*Boolean*/ animate){ // summary: // Hook to make set("open", boolean) control the open/closed state of the pane. // open: Boolean // True if you want to open the pane, false if you want to close it. dojo.forEach([this._wipeIn, this._wipeOut], function(animation){ if(animation && animation.status() == "playing"){ animation.stop(); } }); if(animate){ var anim = this[open ? "_wipeIn" : "_wipeOut"]; anim.play(); }else{ this.hideNode.style.display = this.wipeNode.style.display = open ? "" : "none"; } // load content (if this is the first time we are opening the TitlePane // and content is specified as an href, or href was set when hidden) if(this._started){ if(open){ this._onShow(); }else{ this.onHide(); } } this.arrowNodeInner.innerHTML = open ? "-" : "+"; dijit.setWaiState(this.containerNode,"hidden", open ? "false" : "true"); dijit.setWaiState(this.focusNode, "pressed", open ? "true" : "false"); this._set("open", open); this._setCss(); }, _setToggleableAttr: function(/*Boolean*/ canToggle){ // summary: // Hook to make set("toggleable", boolean) work. // canToggle: Boolean // True to allow user to open/close pane by clicking title bar. dijit.setWaiRole(this.focusNode, canToggle ? "button" : "heading"); if(canToggle){ // TODO: if canToggle is switched from true to false shouldn't we remove this setting? dijit.setWaiState(this.focusNode, "controls", this.id+"_pane"); dojo.attr(this.focusNode, "tabIndex", this.tabIndex); }else{ dojo.removeAttr(this.focusNode, "tabIndex"); } this._set("toggleable", canToggle); this._setCss(); }, _setContentAttr: function(/*String|DomNode|Nodelist*/ content){ // summary: // Hook to make set("content", ...) work. // Typically called when an href is loaded. Our job is to make the animation smooth. if(!this.open || !this._wipeOut || this._wipeOut.status() == "playing"){ // we are currently *closing* the pane (or the pane is closed), so just let that continue this.inherited(arguments); }else{ if(this._wipeIn && this._wipeIn.status() == "playing"){ this._wipeIn.stop(); } // freeze container at current height so that adding new content doesn't make it jump dojo.marginBox(this.wipeNode, { h: dojo.marginBox(this.wipeNode).h }); // add the new content (erasing the old content, if any) this.inherited(arguments); // call _wipeIn.play() to animate from current height to new height if(this._wipeIn){ this._wipeIn.play(); }else{ this.hideNode.style.display = ""; } } }, toggle: function(){ // summary: // Switches between opened and closed state // tags: // private this._setOpenAttr(!this.open, true); }, _setCss: function(){ // summary: // Set the open/close css state for the TitlePane // tags: // private var node = this.titleBarNode || this.focusNode; var oldCls = this._titleBarClass; this._titleBarClass = "dijit" + (this.toggleable ? "" : "Fixed") + (this.open ? "Open" : "Closed"); dojo.replaceClass(node, this._titleBarClass, oldCls || ""); this.arrowNodeInner.innerHTML = this.open ? "-" : "+"; }, _onTitleKey: function(/*Event*/ e){ // summary: // Handler for when user hits a key // tags: // private if(e.charOrCode == dojo.keys.ENTER || e.charOrCode == ' '){ if(this.toggleable){ this.toggle(); } dojo.stopEvent(e); }else if(e.charOrCode == dojo.keys.DOWN_ARROW && this.open){ this.containerNode.focus(); e.preventDefault(); } }, _onTitleClick: function(){ // summary: // Handler when user clicks the title bar // tags: // private if(this.toggleable){ this.toggle(); } }, setTitle: function(/*String*/ title){ // summary: // Deprecated. Use set('title', ...) instead. // tags: // deprecated dojo.deprecated("dijit.TitlePane.setTitle() is deprecated. Use set('title', ...) instead.", "", "2.0"); this.set("title", title); } }); } if(!dojo._hasResource["dojo.DeferredList"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dojo.DeferredList"] = true; dojo.provide("dojo.DeferredList"); dojo.DeferredList = function(/*Array*/ list, /*Boolean?*/ fireOnOneCallback, /*Boolean?*/ fireOnOneErrback, /*Boolean?*/ consumeErrors, /*Function?*/ canceller){ // summary: // Provides event handling for a group of Deferred objects. // description: // DeferredList takes an array of existing deferreds and returns a new deferred of its own // this new deferred will typically have its callback fired when all of the deferreds in // the given list have fired their own deferreds. The parameters `fireOnOneCallback` and // fireOnOneErrback, will fire before all the deferreds as appropriate // // list: // The list of deferreds to be synchronizied with this DeferredList // fireOnOneCallback: // Will cause the DeferredLists callback to be fired as soon as any // of the deferreds in its list have been fired instead of waiting until // the entire list has finished // fireonOneErrback: // Will cause the errback to fire upon any of the deferreds errback // canceller: // A deferred canceller function, see dojo.Deferred var resultList = []; dojo.Deferred.call(this); var self = this; if(list.length === 0 && !fireOnOneCallback){ this.resolve([0, []]); } var finished = 0; dojo.forEach(list, function(item, i){ item.then(function(result){ if(fireOnOneCallback){ self.resolve([i, result]); }else{ addResult(true, result); } },function(error){ if(fireOnOneErrback){ self.reject(error); }else{ addResult(false, error); } if(consumeErrors){ return null; } throw error; }); function addResult(succeeded, result){ resultList[i] = [succeeded, result]; finished++; if(finished === list.length){ self.resolve(resultList); } } }); }; dojo.DeferredList.prototype = new dojo.Deferred(); dojo.DeferredList.prototype.gatherResults= function(deferredList){ // summary: // Gathers the results of the deferreds for packaging // as the parameters to the Deferred Lists' callback var d = new dojo.DeferredList(deferredList, false, true, false); d.addCallback(function(results){ var ret = []; dojo.forEach(results, function(result){ ret.push(result[1]); }); return ret; }); return d; }; } if(!dojo._hasResource["dijit.tree.TreeStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. dojo._hasResource["dijit.tree.TreeStoreModel"] = true; dojo.provide("dijit.tree.TreeStoreModel"); dojo.declare( "dijit.tree.TreeStoreModel", null, { // summary: // Implements dijit.Tree.model connecting to a store with a single // root item. Any methods passed into the constructor will override // the ones defined here. // store: dojo.data.Store // Underlying store store: null, // childrenAttrs: String[] // One or more attribute names (attributes in the dojo.data item) that specify that item's children childrenAttrs: ["children"], // newItemIdAttr: String // Name of attribute in the Object passed to newItem() that specifies the id. // // If newItemIdAttr is set then it's used when newItem() is called to see if an // item with the same id already exists, and if so just links to the old item // (so that the old item ends up with two parents). // // Setting this to null or "" will make every drop create a new item. newItemIdAttr: "id", // labelAttr: String // If specified, get label for tree node from this attribute, rather // than by calling store.getLabel() labelAttr: "", // root: [readonly] dojo.data.Item // Pointer to the root item (read only, not a parameter) root: null, // query: anything // Specifies datastore query to return the root item for the tree. // Must only return a single item. Alternately can just pass in pointer // to root item. // example: // | {id:'ROOT'} query: null, // deferItemLoadingUntilExpand: Boolean // Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes // until they are expanded. This allows for lazying loading where only one // loadItem (and generally one network call, consequently) per expansion // (rather than one for each child). // This relies on partial loading of the children items; each children item of a // fully loaded item should contain the label and info about having children. deferItemLoadingUntilExpand: false, constructor: function(/* Object */ args){ // summary: // Passed the arguments listed above (store, etc) // tags: // private dojo.mixin(this, args); this.connects = []; var store = this.store; if(!store.getFeatures()['dojo.data.api.Identity']){ throw new Error("dijit.Tree: store must support dojo.data.Identity"); } // if the store supports Notification, subscribe to the notification events if(store.getFeatures()['dojo.data.api.Notification']){ this.connects = this.connects.concat([ dojo.connect(store, "onNew", this, "onNewItem"), dojo.connect(store, "onDelete", this, "onDeleteItem"), dojo.connect(store, "onSet", this, "onSetItem") ]); } }, destroy: function(){ dojo.forEach(this.connects, dojo.disconnect); // TODO: should cancel any in-progress processing of getRoot(), getChildren() }, // ======================================================================= // Methods for traversing hierarchy getRoot: function(onItem, onError){ // summary: // Calls onItem with the root item for the tree, possibly a fabricated item. // Calls onError on error. if(this.root){ onItem(this.root); }else{ this.store.fetch({ query: this.query, onComplete: dojo.hitch(this, function(items){ if(items.length != 1){ throw new Error(this.declaredClass + ": query " + dojo.toJson(this.query) + " returned " + items.length + " items, but must return exactly one item"); } this.root = items[0]; onItem(this.root); }), onError: onError }); } }, mayHaveChildren: function(/*dojo.data.Item*/ item){ // summary: // Tells if an item has or may have children. Implementing logic here // avoids showing +/- expando icon for nodes that we know don't have children. // (For efficiency reasons we may not want to check if an element actually // has children until user clicks the expando node) return dojo.some(this.childrenAttrs, function(attr){ return this.store.hasAttribute(item, attr); }, this); }, getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){ // summary: // Calls onComplete() with array of child items of given parent item, all loaded. var store = this.store; if(!store.isItemLoaded(parentItem)){ // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand // mode, so we will load it and just return the children (without loading each // child item) var getChildren = dojo.hitch(this, arguments.callee); store.loadItem({ item: parentItem, onItem: function(parentItem){ getChildren(parentItem, onComplete, onError); }, onError: onError }); return; } // get children of specified item var childItems = []; for(var i=0; i
\"\"\n\t\t\t\"\"\n\t\t
\n\t
\n\n"), baseClass: "dijitTreeNode", // For hover effect for tree node, and focus effect for label cssStateNodes: { rowNode: "dijitTreeRow", labelNode: "dijitTreeLabel" }, attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { label: {node: "labelNode", type: "innerText"}, tooltip: {node: "rowNode", type: "attribute", attribute: "title"} }), buildRendering: function(){ this.inherited(arguments); // set expand icon for leaf this._setExpando(); // set icon and label class based on item this._updateItemClasses(this.item); if(this.isExpandable){ dijit.setWaiState(this.labelNode, "expanded", this.isExpanded); } //aria-selected should be false on all selectable elements. this.setSelected(false); }, _setIndentAttr: function(indent){ // summary: // Tell this node how many levels it should be indented // description: // 0 for top level nodes, 1 for their children, 2 for their // grandchildren, etc. // Math.max() is to prevent negative padding on hidden root node (when indent == -1) var pixels = (Math.max(indent, 0) * this.tree._nodePixelIndent) + "px"; dojo.style(this.domNode, "backgroundPosition", pixels + " 0px"); dojo.style(this.rowNode, this.isLeftToRight() ? "paddingLeft" : "paddingRight", pixels); dojo.forEach(this.getChildren(), function(child){ child.set("indent", indent+1); }); this._set("indent", indent); }, markProcessing: function(){ // summary: // Visually denote that tree is loading data, etc. // tags: // private this.state = "LOADING"; this._setExpando(true); }, unmarkProcessing: function(){ // summary: // Clear markup from markProcessing() call // tags: // private this._setExpando(false); }, _updateItemClasses: function(item){ // summary: // Set appropriate CSS classes for icon and label dom node // (used to allow for item updates to change respective CSS) // tags: // private var tree = this.tree, model = tree.model; if(tree._v10Compat && item === model.root){ // For back-compat with 1.0, need to use null to specify root item (TODO: remove in 2.0) item = null; } this._applyClassAndStyle(item, "icon", "Icon"); this._applyClassAndStyle(item, "label", "Label"); this._applyClassAndStyle(item, "row", "Row"); }, _applyClassAndStyle: function(item, lower, upper){ // summary: // Set the appropriate CSS classes and styles for labels, icons and rows. // // item: // The data item. // // lower: // The lower case attribute to use, e.g. 'icon', 'label' or 'row'. // // upper: // The upper case attribute to use, e.g. 'Icon', 'Label' or 'Row'. // // tags: // private var clsName = "_" + lower + "Class"; var nodeName = lower + "Node"; var oldCls = this[clsName]; this[clsName] = this.tree["get" + upper + "Class"](item, this.isExpanded); dojo.replaceClass(this[nodeName], this[clsName] || "", oldCls || ""); dojo.style(this[nodeName], this.tree["get" + upper + "Style"](item, this.isExpanded) || {}); }, _updateLayout: function(){ // summary: // Set appropriate CSS classes for this.domNode // tags: // private var parent = this.getParent(); if(!parent || parent.rowNode.style.display == "none"){ /* if we are hiding the root node then make every first level child look like a root node */ dojo.addClass(this.domNode, "dijitTreeIsRoot"); }else{ dojo.toggleClass(this.domNode, "dijitTreeIsLast", !this.getNextSibling()); } }, _setExpando: function(/*Boolean*/ processing){ // summary: // Set the right image for the expando node // tags: // private var styles = ["dijitTreeExpandoLoading", "dijitTreeExpandoOpened", "dijitTreeExpandoClosed", "dijitTreeExpandoLeaf"], _a11yStates = ["*","-","+","*"], idx = processing ? 0 : (this.isExpandable ? (this.isExpanded ? 1 : 2) : 3); // apply the appropriate class to the expando node dojo.replaceClass(this.expandoNode, styles[idx], styles); // provide a non-image based indicator for images-off mode this.expandoNodeText.innerHTML = _a11yStates[idx]; }, expand: function(){ // summary: // Show my children // returns: // Deferred that fires when expansion is complete // If there's already an expand in progress or we are already expanded, just return if(this._expandDeferred){ return this._expandDeferred; // dojo.Deferred } // cancel in progress collapse operation this._wipeOut && this._wipeOut.stop(); // All the state information for when a node is expanded, maybe this should be // set when the animation completes instead this.isExpanded = true; dijit.setWaiState(this.labelNode, "expanded", "true"); if(this.tree.showRoot || this !== this.tree.rootNode){ dijit.setWaiRole(this.containerNode, "group"); } dojo.addClass(this.contentNode,'dijitTreeContentExpanded'); this._setExpando(); this._updateItemClasses(this.item); if(this == this.tree.rootNode){ dijit.setWaiState(this.tree.domNode, "expanded", "true"); } var def, wipeIn = dojo.fx.wipeIn({ node: this.containerNode, duration: dijit.defaultDuration, onEnd: function(){ def.callback(true); } }); // Deferred that fires when expand is complete def = (this._expandDeferred = new dojo.Deferred(function(){ // Canceller wipeIn.stop(); })); wipeIn.play(); return def; // dojo.Deferred }, collapse: function(){ // summary: // Collapse this node (if it's expanded) if(!this.isExpanded){ return; } // cancel in progress expand operation if(this._expandDeferred){ this._expandDeferred.cancel(); delete this._expandDeferred; } this.isExpanded = false; dijit.setWaiState(this.labelNode, "expanded", "false"); if(this == this.tree.rootNode){ dijit.setWaiState(this.tree.domNode, "expanded", "false"); } dojo.removeClass(this.contentNode,'dijitTreeContentExpanded'); this._setExpando(); this._updateItemClasses(this.item); if(!this._wipeOut){ this._wipeOut = dojo.fx.wipeOut({ node: this.containerNode, duration: dijit.defaultDuration }); } this._wipeOut.play(); }, // indent: Integer // Levels from this node to the root node indent: 0, setChildItems: function(/* Object[] */ items){ // summary: // Sets the child items of this node, removing/adding nodes // from current children to match specified items[] array. // Also, if this.persist == true, expands any children that were previously // opened. // returns: // Deferred object that fires after all previously opened children // have been expanded again (or fires instantly if there are no such children). var tree = this.tree, model = tree.model, defs = []; // list of deferreds that need to fire before I am complete // Orphan all my existing children. // If items contains some of the same items as before then we will reattach them. // Don't call this.removeChild() because that will collapse the tree etc. dojo.forEach(this.getChildren(), function(child){ dijit._Container.prototype.removeChild.call(this, child); }, this); this.state = "LOADED"; if(items && items.length > 0){ this.isExpandable = true; // Create _TreeNode widget for each specified tree node, unless one already // exists and isn't being used (presumably it's from a DnD move and was recently // released dojo.forEach(items, function(item){ var id = model.getIdentity(item), existingNodes = tree._itemNodesMap[id], node; if(existingNodes){ for(var i=0;i