source: Dev/branches/rest-dojo-ui/client/dijit/layout/ContentPane.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 19.9 KB
Line 
1define([
2        "dojo/_base/kernel", // kernel.deprecated
3        "dojo/_base/lang", // lang.mixin lang.delegate lang.hitch lang.isFunction lang.isObject
4        "../_Widget",
5        "./_ContentPaneResizeMixin",
6        "dojo/string", // string.substitute
7        "dojo/html", // html._ContentSetter html._emptyNode
8        "dojo/i18n!../nls/loading",
9        "dojo/_base/array", // array.forEach
10        "dojo/_base/declare", // declare
11        "dojo/_base/Deferred", // Deferred
12        "dojo/dom", // dom.byId
13        "dojo/dom-attr", // domAttr.attr
14        "dojo/_base/window", // win.body win.doc.createDocumentFragment
15        "dojo/_base/xhr", // xhr.get
16        "dojo/i18n" // i18n.getLocalization
17], function(kernel, lang, _Widget, _ContentPaneResizeMixin, string, html, nlsLoading,
18        array, declare, Deferred, dom, domAttr, win, xhr, i18n){
19
20/*=====
21        var _Widget = dijit._Widget;
22        var _ContentPaneResizeMixin = dijit.layout._ContentPaneResizeMixin;
23=====*/
24
25// module:
26//              dijit/layout/ContentPane
27// summary:
28//              A widget containing an HTML fragment, specified inline
29//              or by uri.  Fragment may include widgets.
30
31
32return declare("dijit.layout.ContentPane", [_Widget, _ContentPaneResizeMixin], {
33        // summary:
34        //              A widget containing an HTML fragment, specified inline
35        //              or by uri.  Fragment may include widgets.
36        //
37        // description:
38        //              This widget embeds a document fragment in the page, specified
39        //              either by uri, javascript generated markup or DOM reference.
40        //              Any widgets within this content are instantiated and managed,
41        //              but laid out according to the HTML structure.  Unlike IFRAME,
42        //              ContentPane embeds a document fragment as would be found
43        //              inside the BODY tag of a full HTML document.  It should not
44        //              contain the HTML, HEAD, or BODY tags.
45        //              For more advanced functionality with scripts and
46        //              stylesheets, see dojox.layout.ContentPane.  This widget may be
47        //              used stand alone or as a base class for other widgets.
48        //              ContentPane is useful as a child of other layout containers
49        //              such as BorderContainer or TabContainer, but note that those
50        //              widgets can contain any widget as a child.
51        //
52        // example:
53        //              Some quick samples:
54        //              To change the innerHTML: cp.set('content', '<b>new content</b>')
55        //
56        //              Or you can send it a NodeList: cp.set('content', dojo.query('div [class=selected]', userSelection))
57        //
58        //              To do an ajax update: cp.set('href', url)
59
60        // href: String
61        //              The href of the content that displays now.
62        //              Set this at construction if you want to load data externally when the
63        //              pane is shown.  (Set preload=true to load it immediately.)
64        //              Changing href after creation doesn't have any effect; Use set('href', ...);
65        href: "",
66
67        // content: String || DomNode || NodeList || dijit._Widget
68        //              The innerHTML of the ContentPane.
69        //              Note that the initialization parameter / argument to set("content", ...)
70        //              can be a String, DomNode, Nodelist, or _Widget.
71        content: "",
72
73        // extractContent: Boolean
74        //              Extract visible content from inside of <body> .... </body>.
75        //              I.e., strip <html> and <head> (and it's contents) from the href
76        extractContent: false,
77
78        // parseOnLoad: Boolean
79        //              Parse content and create the widgets, if any.
80        parseOnLoad: true,
81
82        // parserScope: String
83        //              Flag passed to parser.  Root for attribute names to search for.   If scopeName is dojo,
84        //              will search for data-dojo-type (or dojoType).  For backwards compatibility
85        //              reasons defaults to dojo._scopeName (which is "dojo" except when
86        //              multi-version support is used, when it will be something like dojo16, dojo20, etc.)
87        parserScope: kernel._scopeName,
88
89        // preventCache: Boolean
90        //              Prevent caching of data from href's by appending a timestamp to the href.
91        preventCache: false,
92
93        // preload: Boolean
94        //              Force load of data on initialization even if pane is hidden.
95        preload: false,
96
97        // refreshOnShow: Boolean
98        //              Refresh (re-download) content when pane goes from hidden to shown
99        refreshOnShow: false,
100
101        // loadingMessage: String
102        //              Message that shows while downloading
103        loadingMessage: "<span class='dijitContentPaneLoading'><span class='dijitInline dijitIconLoading'></span>${loadingState}</span>",
104
105        // errorMessage: String
106        //              Message that shows if an error occurs
107        errorMessage: "<span class='dijitContentPaneError'><span class='dijitInline dijitIconError'></span>${errorState}</span>",
108
109        // isLoaded: [readonly] Boolean
110        //              True if the ContentPane has data in it, either specified
111        //              during initialization (via href or inline content), or set
112        //              via set('content', ...) / set('href', ...)
113        //
114        //              False if it doesn't have any content, or if ContentPane is
115        //              still in the process of downloading href.
116        isLoaded: false,
117
118        baseClass: "dijitContentPane",
119
120        /*======
121        // ioMethod: dojo.xhrGet|dojo.xhrPost
122        //              Function that should grab the content specified via href.
123        ioMethod: dojo.xhrGet,
124        ======*/
125
126        // ioArgs: Object
127        //              Parameters to pass to xhrGet() request, for example:
128        // |    <div data-dojo-type="dijit.layout.ContentPane" data-dojo-props="href: './bar', ioArgs: {timeout: 500}">
129        ioArgs: {},
130
131        // onLoadDeferred: [readonly] dojo.Deferred
132        //              This is the `dojo.Deferred` returned by set('href', ...) and refresh().
133        //              Calling onLoadDeferred.addCallback() or addErrback() registers your
134        //              callback to be called only once, when the prior set('href', ...) call or
135        //              the initial href parameter to the constructor finishes loading.
136        //
137        //              This is different than an onLoad() handler which gets called any time any href
138        //              or content is loaded.
139        onLoadDeferred: null,
140
141        // Cancel _WidgetBase's _setTitleAttr because we don't want the title attribute (used to specify
142        // tab labels) to be copied to ContentPane.domNode... otherwise a tooltip shows up over the
143        // entire pane.
144        _setTitleAttr: null,
145
146        // Flag to parser that I'll parse my contents, so it shouldn't.
147        stopParser: true,
148
149        // template: [private] Boolean
150        //              Flag from the parser that this ContentPane is inside a template
151        //              so the contents are pre-parsed.
152        // (TODO: this declaration can be commented out in 2.0)
153        template: false,
154
155        create: function(params, srcNodeRef){
156                // Convert a srcNodeRef argument into a content parameter, so that the original contents are
157                // processed in the same way as contents set via set("content", ...), calling the parser etc.
158                // Avoid modifying original params object since that breaks NodeList instantiation, see #11906.
159                if((!params || !params.template) && srcNodeRef && !("href" in params) && !("content" in params)){
160                        var df = win.doc.createDocumentFragment();
161                        srcNodeRef = dom.byId(srcNodeRef);
162                        while(srcNodeRef.firstChild){
163                                df.appendChild(srcNodeRef.firstChild);
164                        }
165                        params = lang.delegate(params, {content: df});
166                }
167                this.inherited(arguments, [params, srcNodeRef]);
168        },
169
170        postMixInProperties: function(){
171                this.inherited(arguments);
172                var messages = i18n.getLocalization("dijit", "loading", this.lang);
173                this.loadingMessage = string.substitute(this.loadingMessage, messages);
174                this.errorMessage = string.substitute(this.errorMessage, messages);
175        },
176
177        buildRendering: function(){
178                this.inherited(arguments);
179
180                // Since we have no template we need to set this.containerNode ourselves, to make getChildren() work.
181                // For subclasses of ContentPane that do have a template, does nothing.
182                if(!this.containerNode){
183                        this.containerNode = this.domNode;
184                }
185
186                // remove the title attribute so it doesn't show up when hovering
187                // over a node  (TODO: remove in 2.0, no longer needed after #11490)
188                this.domNode.title = "";
189
190                if(!domAttr.get(this.domNode,"role")){
191                        this.domNode.setAttribute("role", "group");
192                }
193        },
194
195        startup: function(){
196                // summary:
197                //              Call startup() on all children including non _Widget ones like dojo.dnd.Source objects
198
199                // This starts all the widgets
200                this.inherited(arguments);
201
202                // And this catches stuff like dojo.dnd.Source
203                if(this._contentSetter){
204                        array.forEach(this._contentSetter.parseResults, function(obj){
205                                if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){
206                                        obj.startup();
207                                        obj._started = true;
208                                }
209                        }, this);
210                }
211        },
212
213        setHref: function(/*String|Uri*/ href){
214                // summary:
215                //              Deprecated.   Use set('href', ...) instead.
216                kernel.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use set('href', ...) instead.", "", "2.0");
217                return this.set("href", href);
218        },
219        _setHrefAttr: function(/*String|Uri*/ href){
220                // summary:
221                //              Hook so set("href", ...) works.
222                // description:
223                //              Reset the (external defined) content of this pane and replace with new url
224                //              Note: It delays the download until widget is shown if preload is false.
225                //      href:
226                //              url to the page you want to get, must be within the same domain as your mainpage
227
228                // Cancel any in-flight requests (a set('href', ...) will cancel any in-flight set('href', ...))
229                this.cancel();
230
231                this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel"));
232                this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad"));
233
234                this._set("href", href);
235
236                // _setHrefAttr() is called during creation and by the user, after creation.
237                // Assuming preload == false, only in the second case do we actually load the URL;
238                // otherwise it's done in startup(), and only if this widget is shown.
239                if(this.preload || (this._created && this._isShown())){
240                        this._load();
241                }else{
242                        // Set flag to indicate that href needs to be loaded the next time the
243                        // ContentPane is made visible
244                        this._hrefChanged = true;
245                }
246
247                return this.onLoadDeferred;             // Deferred
248        },
249
250        setContent: function(/*String|DomNode|Nodelist*/data){
251                // summary:
252                //              Deprecated.   Use set('content', ...) instead.
253                kernel.deprecated("dijit.layout.ContentPane.setContent() is deprecated.  Use set('content', ...) instead.", "", "2.0");
254                this.set("content", data);
255        },
256        _setContentAttr: function(/*String|DomNode|Nodelist*/data){
257                // summary:
258                //              Hook to make set("content", ...) work.
259                //              Replaces old content with data content, include style classes from old content
260                //      data:
261                //              the new Content may be String, DomNode or NodeList
262                //
263                //              if data is a NodeList (or an array of nodes) nodes are copied
264                //              so you can import nodes from another document implicitly
265
266                // clear href so we can't run refresh and clear content
267                // refresh should only work if we downloaded the content
268                this._set("href", "");
269
270                // Cancel any in-flight requests (a set('content', ...) will cancel any in-flight set('href', ...))
271                this.cancel();
272
273                // Even though user is just setting content directly, still need to define an onLoadDeferred
274                // because the _onLoadHandler() handler is still getting called from setContent()
275                this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel"));
276                if(this._created){
277                        // For back-compat reasons, call onLoad() for set('content', ...)
278                        // calls but not for content specified in srcNodeRef (ie: <div data-dojo-type=ContentPane>...</div>)
279                        // or as initialization parameter (ie: new ContentPane({content: ...})
280                        this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad"));
281                }
282
283                this._setContent(data || "");
284
285                this._isDownloaded = false; // mark that content is from a set('content') not a set('href')
286
287                return this.onLoadDeferred;     // Deferred
288        },
289        _getContentAttr: function(){
290                // summary:
291                //              Hook to make get("content") work
292                return this.containerNode.innerHTML;
293        },
294
295        cancel: function(){
296                // summary:
297                //              Cancels an in-flight download of content
298                if(this._xhrDfd && (this._xhrDfd.fired == -1)){
299                        this._xhrDfd.cancel();
300                }
301                delete this._xhrDfd; // garbage collect
302
303                this.onLoadDeferred = null;
304        },
305
306        uninitialize: function(){
307                if(this._beingDestroyed){
308                        this.cancel();
309                }
310                this.inherited(arguments);
311        },
312
313        destroyRecursive: function(/*Boolean*/ preserveDom){
314                // summary:
315                //              Destroy the ContentPane and its contents
316
317                // if we have multiple controllers destroying us, bail after the first
318                if(this._beingDestroyed){
319                        return;
320                }
321                this.inherited(arguments);
322        },
323
324        _onShow: function(){
325                // summary:
326                //              Called when the ContentPane is made visible
327                // description:
328                //              For a plain ContentPane, this is called on initialization, from startup().
329                //              If the ContentPane is a hidden pane of a TabContainer etc., then it's
330                //              called whenever the pane is made visible.
331                //
332                //              Does necessary processing, including href download and layout/resize of
333                //              child widget(s)
334
335                this.inherited(arguments);
336
337                if(this.href){
338                        if(!this._xhrDfd && // if there's an href that isn't already being loaded
339                                (!this.isLoaded || this._hrefChanged || this.refreshOnShow)
340                        ){
341                                return this.refresh();  // If child has an href, promise that fires when the load is complete
342                        }
343                }
344        },
345
346        refresh: function(){
347                // summary:
348                //              [Re]download contents of href and display
349                // description:
350                //              1. cancels any currently in-flight requests
351                //              2. posts "loading..." message
352                //              3. sends XHR to download new data
353
354                // Cancel possible prior in-flight request
355                this.cancel();
356
357                this.onLoadDeferred = new Deferred(lang.hitch(this, "cancel"));
358                this.onLoadDeferred.addCallback(lang.hitch(this, "onLoad"));
359                this._load();
360                return this.onLoadDeferred;             // If child has an href, promise that fires when refresh is complete
361        },
362
363        _load: function(){
364                // summary:
365                //              Load/reload the href specified in this.href
366
367                // display loading message
368                this._setContent(this.onDownloadStart(), true);
369
370                var self = this;
371                var getArgs = {
372                        preventCache: (this.preventCache || this.refreshOnShow),
373                        url: this.href,
374                        handleAs: "text"
375                };
376                if(lang.isObject(this.ioArgs)){
377                        lang.mixin(getArgs, this.ioArgs);
378                }
379
380                var hand = (this._xhrDfd = (this.ioMethod || xhr.get)(getArgs));
381
382                hand.addCallback(function(html){
383                        try{
384                                self._isDownloaded = true;
385                                self._setContent(html, false);
386                                self.onDownloadEnd();
387                        }catch(err){
388                                self._onError('Content', err); // onContentError
389                        }
390                        delete self._xhrDfd;
391                        return html;
392                });
393
394                hand.addErrback(function(err){
395                        if(!hand.canceled){
396                                // show error message in the pane
397                                self._onError('Download', err); // onDownloadError
398                        }
399                        delete self._xhrDfd;
400                        return err;
401                });
402
403                // Remove flag saying that a load is needed
404                delete this._hrefChanged;
405        },
406
407        _onLoadHandler: function(data){
408                // summary:
409                //              This is called whenever new content is being loaded
410                this._set("isLoaded", true);
411                try{
412                        this.onLoadDeferred.callback(data);
413                }catch(e){
414                        console.error('Error '+this.widgetId+' running custom onLoad code: ' + e.message);
415                }
416        },
417
418        _onUnloadHandler: function(){
419                // summary:
420                //              This is called whenever the content is being unloaded
421                this._set("isLoaded", false);
422                try{
423                        this.onUnload();
424                }catch(e){
425                        console.error('Error '+this.widgetId+' running custom onUnload code: ' + e.message);
426                }
427        },
428
429        destroyDescendants: function(/*Boolean*/ preserveDom){
430                // summary:
431                //              Destroy all the widgets inside the ContentPane and empty containerNode
432
433                // Make sure we call onUnload (but only when the ContentPane has real content)
434                if(this.isLoaded){
435                        this._onUnloadHandler();
436                }
437
438                // Even if this.isLoaded == false there might still be a "Loading..." message
439                // to erase, so continue...
440
441                // For historical reasons we need to delete all widgets under this.containerNode,
442                // even ones that the user has created manually.
443                var setter = this._contentSetter;
444                array.forEach(this.getChildren(), function(widget){
445                        if(widget.destroyRecursive){
446                                widget.destroyRecursive(preserveDom);
447                        }
448                });
449                if(setter){
450                        // Most of the widgets in setter.parseResults have already been destroyed, but
451                        // things like Menu that have been moved to <body> haven't yet
452                        array.forEach(setter.parseResults, function(widget){
453                                if(widget.destroyRecursive && widget.domNode && widget.domNode.parentNode == win.body()){
454                                        widget.destroyRecursive(preserveDom);
455                                }
456                        });
457                        delete setter.parseResults;
458                }
459
460                // And then clear away all the DOM nodes
461                if(!preserveDom){
462                        html._emptyNode(this.containerNode);
463                }
464
465                // Delete any state information we have about current contents
466                delete this._singleChild;
467        },
468
469        _setContent: function(/*String|DocumentFragment*/ cont, /*Boolean*/ isFakeContent){
470                // summary:
471                //              Insert the content into the container node
472
473                // first get rid of child widgets
474                this.destroyDescendants();
475
476                // html.set will take care of the rest of the details
477                // we provide an override for the error handling to ensure the widget gets the errors
478                // configure the setter instance with only the relevant widget instance properties
479                // NOTE: unless we hook into attr, or provide property setters for each property,
480                // we need to re-configure the ContentSetter with each use
481                var setter = this._contentSetter;
482                if(! (setter && setter instanceof html._ContentSetter)){
483                        setter = this._contentSetter = new html._ContentSetter({
484                                node: this.containerNode,
485                                _onError: lang.hitch(this, this._onError),
486                                onContentError: lang.hitch(this, function(e){
487                                        // fires if a domfault occurs when we are appending this.errorMessage
488                                        // like for instance if domNode is a UL and we try append a DIV
489                                        var errMess = this.onContentError(e);
490                                        try{
491                                                this.containerNode.innerHTML = errMess;
492                                        }catch(e){
493                                                console.error('Fatal '+this.id+' could not change content due to '+e.message, e);
494                                        }
495                                })/*,
496                                _onError */
497                        });
498                }
499
500                var setterParams = lang.mixin({
501                        cleanContent: this.cleanContent,
502                        extractContent: this.extractContent,
503                        parseContent: !cont.domNode && this.parseOnLoad,
504                        parserScope: this.parserScope,
505                        startup: false,
506                        dir: this.dir,
507                        lang: this.lang,
508                        textDir: this.textDir
509                }, this._contentSetterParams || {});
510
511                setter.set( (lang.isObject(cont) && cont.domNode) ? cont.domNode : cont, setterParams );
512
513                // setter params must be pulled afresh from the ContentPane each time
514                delete this._contentSetterParams;
515
516                if(this.doLayout){
517                        this._checkIfSingleChild();
518                }
519
520                if(!isFakeContent){
521                        if(this._started){
522                                // Startup each top level child widget (and they will start their children, recursively)
523                                delete this._started;
524                                this.startup();
525
526                                // Call resize() on each of my child layout widgets,
527                                // or resize() on my single child layout widget...
528                                // either now (if I'm currently visible) or when I become visible
529                                this._scheduleLayout();
530                        }
531
532                        this._onLoadHandler(cont);
533                }
534        },
535
536        _onError: function(type, err, consoleText){
537                this.onLoadDeferred.errback(err);
538
539                // shows user the string that is returned by on[type]Error
540                // override on[type]Error and return your own string to customize
541                var errText = this['on' + type + 'Error'].call(this, err);
542                if(consoleText){
543                        console.error(consoleText, err);
544                }else if(errText){// a empty string won't change current content
545                        this._setContent(errText, true);
546                }
547        },
548
549        // EVENT's, should be overide-able
550        onLoad: function(/*===== data =====*/){
551                // summary:
552                //              Event hook, is called after everything is loaded and widgetified
553                // tags:
554                //              callback
555        },
556
557        onUnload: function(){
558                // summary:
559                //              Event hook, is called before old content is cleared
560                // tags:
561                //              callback
562        },
563
564        onDownloadStart: function(){
565                // summary:
566                //              Called before download starts.
567                // description:
568                //              The string returned by this function will be the html
569                //              that tells the user we are loading something.
570                //              Override with your own function if you want to change text.
571                // tags:
572                //              extension
573                return this.loadingMessage;
574        },
575
576        onContentError: function(/*Error*/ /*===== error =====*/){
577                // summary:
578                //              Called on DOM faults, require faults etc. in content.
579                //
580                //              In order to display an error message in the pane, return
581                //              the error message from this method, as an HTML string.
582                //
583                //              By default (if this method is not overriden), it returns
584                //              nothing, so the error message is just printed to the console.
585                // tags:
586                //              extension
587        },
588
589        onDownloadError: function(/*Error*/ /*===== error =====*/){
590                // summary:
591                //              Called when download error occurs.
592                //
593                //              In order to display an error message in the pane, return
594                //              the error message from this method, as an HTML string.
595                //
596                //              Default behavior (if this method is not overriden) is to display
597                //              the error message inside the pane.
598                // tags:
599                //              extension
600                return this.errorMessage;
601        },
602
603        onDownloadEnd: function(){
604                // summary:
605                //              Called when download is finished.
606                // tags:
607                //              callback
608        }
609});
610
611});
Note: See TracBrowser for help on using the repository browser.