source: Dev/trunk/src/client/dijit/layout/ContentPane.js @ 483

Last change on this file since 483 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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