source: Dev/trunk/src/client/dojox/mobile/_compat.js @ 532

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

Added Dojo 1.9.3 release.

File size: 19.7 KB
Line 
1define([
2        "dojo/_base/array",     // array.forEach
3        "dojo/_base/config",
4        "dojo/_base/connect",   // connect.connect
5        "dojo/_base/fx",        // fx.fadeOut, fx.fadeIn
6        "dojo/_base/lang",      // lang.extend, lang.isArray
7        "dojo/sniff",           // has("webkit"), has("ie")
8        "dojo/_base/window",    // win.doc, win.body
9        "dojo/dom-class",
10        "dojo/dom-construct",
11        "dojo/dom-geometry",
12        "dojo/dom-style",
13        "dojo/dom-attr",
14        "dojo/fx",
15        "dojo/fx/easing",
16        "dojo/ready",
17        "dojo/uacss",
18        "dijit/registry",       // registry.byNode
19        "dojox/fx",
20        "dojox/fx/flip",
21        "./EdgeToEdgeList",
22        "./IconContainer",
23        "./ProgressIndicator",
24        "./RoundRect",
25        "./RoundRectList",
26        "./ScrollableView",
27        "./Switch",
28        "./View",
29        "./Heading",
30        "require"
31], function(array, config, connect, bfx, lang, has, win, domClass, domConstruct, domGeometry, domStyle, domAttr, fx, easing, ready, uacss, registry, xfx, flip, EdgeToEdgeList, IconContainer, ProgressIndicator, RoundRect, RoundRectList, ScrollableView, Switch, View, Heading, require){
32
33        // module:
34        //              dojox/mobile/compat
35
36/*=====
37return {
38        // summary:
39        //              CSS3 compatibility module.
40        // description:
41        //              This module provides to dojox/mobile support for some of the CSS3 features
42        //              in non-CSS3 browsers, such as IE or Firefox.
43        //              If you require this module, when running in a non-CSS3 browser it directly
44        //              replaces some of the methods of dojox/mobile classes, without any subclassing.
45        //              This way, HTML pages remain the same regardless of whether this compatibility
46        //              module is used or not.
47        //
48        //              Example of usage:
49        //              |       require([
50        //              |               "dojox/mobile",
51        //              |               "dojox/mobile/compat",
52        //              |               ...
53        //              |       ], function(...){
54        //              |               ...
55        //              |       });
56        //
57        //              This module also loads compatibility CSS files, which have a -compat.css
58        //              suffix. You can use either the `<link>` tag or `@import` to load theme
59        //              CSS files. Then, this module searches for the loaded CSS files and loads
60        //              compatibility CSS files. For example, if you load dojox/mobile/themes/iphone/iphone.css
61        //              in a page, this module automatically loads dojox/mobile/themes/iphone/iphone-compat.css.
62        //              If you explicitly load iphone-compat.css with `<link>` or `@import`,
63        //              this module will not load again the already loaded file.
64        //
65        //              Note that, by default, compatibility CSS files are only loaded for CSS files located
66        //              in a directory containing a "mobile/themes" path. For that, a matching is done using
67        //              the default pattern     "/\/mobile\/themes\/.*\.css$/". If a custom theme is not located
68        //              in a directory containing this path, the data-dojo-config needs to specify a custom
69        //              pattern using the "mblLoadCompatPattern" configuration parameter, for instance:
70        //              |       data-dojo-config="mblLoadCompatPattern: /\/mycustomtheme\/.*\.css$/"
71};
72=====*/
73
74        var dm = lang.getObject("dojox.mobile", true);
75
76        if(!(has("webkit") || has("ie") === 10 || (!has("ie") && has("trident") > 6))){
77                lang.extend(View, {
78                        _doTransition: function(fromNode, toNode, transition, dir){
79                                var anim;
80                                this.wakeUp(toNode);
81                                var s1, s2;
82                                if(!transition || transition == "none"){
83                                        toNode.style.display = "";
84                                        fromNode.style.display = "none";
85                                        toNode.style.left = "0px";
86                                        this.invokeCallback();
87                                }else if(transition == "slide" || transition == "cover" || transition == "reveal"){
88                                        var w = fromNode.offsetWidth;
89                                        s1 = fx.slideTo({
90                                                node: fromNode,
91                                                duration: 400,
92                                                left: -w*dir,
93                                                top: domStyle.get(fromNode, "top")
94                                        });
95                                        s2 = fx.slideTo({
96                                                node: toNode,
97                                                duration: 400,
98                                                left: 0,
99                                                top: domStyle.get(toNode, "top")
100                                        });
101                                        toNode.style.position = "absolute";
102                                        toNode.style.left = w*dir + "px";
103                                        toNode.style.display = "";
104                                        anim = fx.combine([s1,s2]);
105                                        connect.connect(anim, "onEnd", this, function(){
106                                                if(!this._inProgress){ return; } // transition has been aborted
107                                                fromNode.style.display = "none";
108                                                fromNode.style.left = "0px";
109                                                toNode.style.position = "relative";
110                                                var toWidget = registry.byNode(toNode);
111                                                if(toWidget && !domClass.contains(toWidget.domNode, "out")){
112                                                        // Reset the temporary padding
113                                                        toWidget.containerNode.style.paddingTop = "";
114                                                }
115                                                this.invokeCallback();
116                                        });
117                                        anim.play();
118                                }else if(transition == "slidev" || transition == "coverv" || transition == "reavealv"){
119                                        var h = fromNode.offsetHeight;
120                                        s1 = fx.slideTo({
121                                                node: fromNode,
122                                                duration: 400,
123                                                left: 0,
124                                                top: -h*dir
125                                        });
126                                        s2 = fx.slideTo({
127                                                node: toNode,
128                                                duration: 400,
129                                                left: 0,
130                                                top: 0
131                                        });
132                                        toNode.style.position = "absolute";
133                                        toNode.style.top = h*dir + "px";
134                                        toNode.style.left = "0px";
135                                        toNode.style.display = "";
136                                        anim = fx.combine([s1,s2]);
137                                        connect.connect(anim, "onEnd", this, function(){
138                                                if(!this._inProgress){ return; } // transition has been aborted
139                                                fromNode.style.display = "none";
140                                                toNode.style.position = "relative";
141                                                this.invokeCallback();
142                                        });
143                                        anim.play();
144                                }else if(transition == "flip"){
145                                        anim = xfx.flip({
146                                                node: fromNode,
147                                                dir: "right",
148                                                depth: 0.5,
149                                                duration: 400
150                                        });
151                                        toNode.style.position = "absolute";
152                                        toNode.style.left = "0px";
153                                        connect.connect(anim, "onEnd", this, function(){
154                                                if(!this._inProgress){ return; } // transition has been aborted
155                                                fromNode.style.display = "none";
156                                                toNode.style.position = "relative";
157                                                toNode.style.display = "";
158                                                this.invokeCallback();
159                                        });
160                                        anim.play();
161                                }else {
162                                        // other transitions - "fade", "dissolve", "swirl"
163                                        anim = fx.chain([
164                                                bfx.fadeOut({
165                                                        node: fromNode,
166                                                        duration: 600
167                                                }),
168                                                bfx.fadeIn({
169                                                        node: toNode,
170                                                        duration: 600
171                                                })
172                                        ]);
173                                        toNode.style.position = "absolute";
174                                        toNode.style.left = "0px";
175                                        toNode.style.display = "";
176                                        domStyle.set(toNode, "opacity", 0);
177                                        connect.connect(anim, "onEnd", this, function(){
178                                                if(!this._inProgress){ return; } // transition has been aborted
179                                                fromNode.style.display = "none";
180                                                toNode.style.position = "relative";
181                                                domStyle.set(fromNode, "opacity", 1);
182                                                this.invokeCallback();
183                                        });
184                                        anim.play();
185                                }
186                        },
187
188                        wakeUp: function(/*DomNode*/node){
189                                // summary:
190                                //              Function to force IE to redraw a node since its layout
191                                //              code tends to misrender in partial draws.
192                                // node: DomNode
193                                //              The node to forcibly redraw.
194                                // tags:
195                                //              public
196                                if(has("ie") && !node._wokeup){
197                                        node._wokeup = true;
198                                        var disp = node.style.display;
199                                        node.style.display = "";
200                                        var nodes = node.getElementsByTagName("*");
201                                        for(var i = 0, len = nodes.length; i < len; i++){
202                                                var val = nodes[i].style.display;
203                                                nodes[i].style.display = "none";
204                                                nodes[i].style.display = "";
205                                                nodes[i].style.display = val;
206                                        }
207                                        node.style.display = disp;
208                                }
209                        }
210                });     
211
212
213                lang.extend(Switch, {
214                        _changeState: function(/*String*/state, /*Boolean*/anim){
215                                // summary:
216                                //              Function to toggle the switch state on the switch
217                                // state:
218                                //              The state to toggle, switch 'on' or 'off'
219                                // anim:
220                                //              Whether to use animation or not
221                                // tags:
222                                //              private
223                                var on = (state === "on");
224
225                                var pos;
226                                if(!on){
227                                        pos = this.isLeftToRight() ? -domStyle.get(this.right,"left") : 0;
228                                }else{
229                                        pos = this.isLeftToRight() ? 0 : -domStyle.get(this.right,"left");
230                                }
231
232                                this.left.style.display = "";
233                                this.right.style.display = "";
234
235                                var _this = this;
236                                var f = function(){
237                                        domClass.remove(_this.domNode, on ? "mblSwitchOff" : "mblSwitchOn");
238                                        domClass.add(_this.domNode, on ? "mblSwitchOn" : "mblSwitchOff");
239                                        _this.left.style.display = on ? "" : "none";
240                                        _this.right.style.display = !on ? "" : "none";
241                                        domAttr.set(_this.domNode, "aria-checked", on ? "true" : "false"); //a11y
242                                };
243
244                                if(anim){
245                                        var a = fx.slideTo({
246                                                node: this.inner,
247                                                duration: 300,
248                                                left: pos,
249                                                onEnd: f
250                                        });
251                                        a.play();
252                                }else{
253                                        if((this.isLeftToRight() ? on : !on) || pos){
254                                                this.inner.style.left = pos + "px";
255                                        }
256                                        f();
257                                }
258                        }
259                });     
260
261
262                lang.extend(ProgressIndicator, {
263                        scale: function(/*Number*/size){
264                                if(has("ie")){
265                                        var dim = {w:size, h:size};
266                                        domGeometry.setMarginBox(this.domNode, dim);
267                                        domGeometry.setMarginBox(this.containerNode, dim);
268                                }else if(has("ff")){
269                                        var scale = size / 40;
270                                        domStyle.set(this.containerNode, {
271                                                MozTransform: "scale(" + scale + ")",
272                                                MozTransformOrigin: "0 0"
273                                        });
274
275                                        domGeometry.setMarginBox(this.domNode, {w:size, h:size});
276                                        domGeometry.setMarginBox(this.containerNode, {w:size / scale, h:size / scale});
277                                }
278                        }
279                });     
280
281
282                if(has("ie")){
283                        lang.extend(RoundRect, {
284                                buildRendering: function(){
285                                        // summary:
286                                        //              Function to simulate the borderRadius appearance on
287                                        //              IE, since IE does not support this CSS style.
288                                        // tags:
289                                        //              protected
290                                        dm.createRoundRect(this);
291                                        this.domNode.className = "mblRoundRect";
292                                }
293                        });
294
295
296                        RoundRectList._addChild = RoundRectList.prototype.addChild;
297                        RoundRectList._postCreate = RoundRectList.prototype.postCreate;
298                        lang.extend(RoundRectList, {
299                                buildRendering: function(){
300                                        // summary:
301                                        //              Function to simulate the borderRadius appearance on
302                                        //              IE, since IE does not support this CSS style.
303                                        // tags:
304                                        //              protected
305                                        dm.createRoundRect(this, true);
306                                        this.domNode.className = "mblRoundRectList";
307                                        if(has("ie") && has("dojo-bidi") && !this.isLeftToRight()){
308                                                this.domNode.className = "mblRoundRectList mblRoundRectListRtl"
309                                        }
310                                },
311
312                                postCreate: function(){
313                                        RoundRectList._postCreate.apply(this, arguments);
314                                        this.redrawBorders();
315                                },
316
317                                addChild: function(widget, /*Number?*/insertIndex){
318                                        RoundRectList._addChild.apply(this, arguments);
319                                        this.redrawBorders();
320                                        if(dm.applyPngFilter){
321                                                dm.applyPngFilter(widget.domNode);
322                                        }
323                                },
324
325                                redrawBorders: function(){
326                                        // summary:
327                                        //              Function to adjust the creation of RoundRectLists on IE.
328                                        //              Removed undesired styles.
329                                        // tags:
330                                        //              public
331
332                                        // Remove a border of the last ListItem.
333                                        // This is for browsers that do not support the last-child CSS pseudo-class.
334
335                                        if(this instanceof EdgeToEdgeList){ return; }
336                                        var lastChildFound = false;
337                                        for(var i = this.containerNode.childNodes.length - 1; i >= 0; i--){
338                                                var c = this.containerNode.childNodes[i];
339                                                if(c.tagName == "LI"){
340                                                        c.style.borderBottomStyle = lastChildFound ? "solid" : "none";
341                                                        lastChildFound = true;
342                                                }
343                                        }
344                                }
345                        });     
346
347
348                        lang.extend(EdgeToEdgeList, {
349                                buildRendering: function(){
350                                this.domNode = this.containerNode = this.srcNodeRef || win.doc.createElement("ul");
351                                        this.domNode.className = "mblEdgeToEdgeList";
352                                }
353                        });
354
355
356                        IconContainer._addChild = IconContainer.prototype.addChild;
357                        lang.extend(IconContainer, {
358                                addChild: function(widget, /*Number?*/insertIndex){
359                                        IconContainer._addChild.apply(this, arguments);
360                                        if(dm.applyPngFilter){
361                                                dm.applyPngFilter(widget.domNode);
362                                        }
363                                }
364                        });
365
366
367                        lang.mixin(dm, {
368                                createRoundRect: function(_this, isList){
369                                        // summary:
370                                        //              Function to adjust the creation of rounded rectangles on IE.
371                                        //              Deals with IE's lack of borderRadius support
372                                        // tags:
373                                        //              public
374                                        var i, len;
375                                        _this.domNode = win.doc.createElement("div");
376                                        _this.domNode.style.padding = "0px";
377                                        _this.domNode.style.backgroundColor = "transparent";
378                                        _this.domNode.style.border = "none"; // borderStyle = "none"; doesn't work on IE9
379                                        _this.containerNode = win.doc.createElement(isList?"ul":"div");
380                                        _this.containerNode.className = "mblRoundRectContainer";
381                                        if(_this.srcNodeRef){
382                                                _this.srcNodeRef.parentNode.replaceChild(_this.domNode, _this.srcNodeRef);
383                                                for(i = 0, len = _this.srcNodeRef.childNodes.length; i < len; i++){
384                                                        _this.containerNode.appendChild(_this.srcNodeRef.removeChild(_this.srcNodeRef.firstChild));
385                                                }
386                                                _this.srcNodeRef = null;
387                                        }
388                                        _this.domNode.appendChild(_this.containerNode);
389
390                                        for(i = 0; i <= 5; i++){
391                                                var top = domConstruct.create("div");
392                                                top.className = "mblRoundCorner mblRoundCorner"+i+"T";
393                                                _this.domNode.insertBefore(top, _this.containerNode);
394
395                                                var bottom = domConstruct.create("div");
396                                                bottom.className = "mblRoundCorner mblRoundCorner"+i+"B";
397                                                _this.domNode.appendChild(bottom);
398                                        }
399                                }
400                        });
401
402
403                        lang.extend(ScrollableView, {
404                                postCreate: function(){
405                                        // On IE, margin-top of the first child does not seem to be effective,
406                                        // probably because padding-top is specified for containerNode
407                                        // to make room for a fixed header. This dummy node is a workaround for that.
408                                        var dummy = domConstruct.create("div", {className:"mblDummyForIE", innerHTML:"&nbsp;"}, this.containerNode, "first");
409                                        domStyle.set(dummy, {
410                                                position: "relative",
411                                                marginBottom: "-2px",
412                                                fontSize: "1px"
413                                        });
414                                }
415                        });
416
417                        // #13846: on IE<10, setSelectable(false) sets unselectable="on" on all children,
418                        // which makes INPUT elements uneditable.
419                        Heading._buildRendering = Heading.prototype.buildRendering;
420                        lang.extend(Heading, {
421                                buildRendering: function(){
422                                        Heading._buildRendering.apply(this);
423                                        var nodes = this.domNode.getElementsByTagName("INPUT"),
424                                                i = nodes.length;
425                                        while(i--){
426                                                nodes[i].removeAttribute("unselectable");
427                                        }
428                                }
429                        });
430                } // if (has("ie"))
431
432
433                if(has("ie") <= 6){
434                        dm.applyPngFilter = function(root){
435                                root = root || win.body();
436                                var nodes = root.getElementsByTagName("IMG");
437                                var blank = require.toUrl("dojo/resources/blank.gif");
438                                for(var i = 0, len = nodes.length; i < len; i++){
439                                        var img = nodes[i];
440                                        var w = img.offsetWidth;
441                                        var h = img.offsetHeight;
442                                        if(w === 0 || h === 0){
443                                                // The reason why the image has no width/height may be because
444                                                // display is "none". If that is the case, let's change the
445                                                // display to "" temporarily and see if the image returns them.
446                                                if(domStyle.get(img, "display") != "none"){ continue; }
447                                                img.style.display = "";
448                                                w = img.offsetWidth;
449                                                h = img.offsetHeight;
450                                                img.style.display = "none";
451                                                if(w === 0 || h === 0){ continue; }
452                                        }
453                                        var src = img.src;
454                                        if(src.indexOf("resources/blank.gif") != -1){ continue; }
455                                        img.src = blank;
456                                        img.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src+"')";
457                                        img.style.width = w + "px";
458                                        img.style.height = h + "px";
459                                }
460                        };
461
462                        if(!dm._disableBgFilter && dm.createDomButton){
463                                dm._createDomButton_orig = dm.createDomButton;
464                                dm.createDomButton = function(/*DomNode*/refNode, /*Object?*/style, /*DomNode?*/toNode){
465                                        var node = dm._createDomButton_orig.apply(this, arguments);
466                                        if(node && node.className && node.className.indexOf("mblDomButton") !== -1){
467                                                var f = function(){
468                                                        if(node.currentStyle && node.currentStyle.backgroundImage.match(/url.*(mblDomButton.*\.png)/)){
469                                                                var img = RegExp.$1;
470                                                                var src = require.toUrl("dojox/mobile/themes/common/domButtons/compat/") + img;
471                                                                node.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src+"',sizingMethod='crop')";
472                                                                node.style.background = "none";
473                                                        }
474                                                };
475                                                setTimeout(f, 1000);
476                                                setTimeout(f, 5000);
477                                        }
478                                        return node;
479                                };
480                        }
481                } // if(has("ie") <= 6)
482
483                dm.loadCssFile = function(/*String*/file){
484                        // summary:
485                        //              Overrides dojox/mobile.loadCssFile() defined in
486                        //              deviceTheme.js.
487                        if(!dm.loadedCssFiles){ dm.loadedCssFiles = []; }
488                        if(win.doc.createStyleSheet){
489                                // for some reason, IE hangs when you try to load
490                                // multiple css files almost at once.
491                                setTimeout(function(file){
492                                        return function(){
493                                                var ss = win.doc.createStyleSheet(file);
494                                                ss && dm.loadedCssFiles.push(ss.owningElement);
495                                        };
496                                }(file), 0);
497                        }else{
498                                dm.loadedCssFiles.push(domConstruct.create("link", {
499                                        href: file,
500                                        type: "text/css",
501                                        rel: "stylesheet"
502                                }, win.doc.getElementsByTagName('head')[0]));
503                        }
504                };
505
506                dm.loadCss = function(/*String|Array*/files){
507                        // summary:
508                        //              Function to load and register CSS files with the page
509                        // files: String|Array
510                        //              The CSS files to load and register with the page.
511                        // tags:
512                        //              private
513                        if(!dm._loadedCss){
514                                var obj = {};
515                                array.forEach(dm.getCssPaths(), function(path){
516                                        obj[path] = true;
517                                });
518                                dm._loadedCss = obj;
519                        }
520                        if(!lang.isArray(files)){ files = [files]; }
521                        for(var i = 0; i < files.length; i++){
522                                var file = files[i];
523                                if(!dm._loadedCss[file]){
524                                        dm._loadedCss[file] = true;
525                                        dm.loadCssFile(file);
526                                }
527                        }
528                };
529
530                dm.getCssPaths = function(){
531                        var paths = [];
532                        var i, j, len;
533
534                        // find @import
535                        var s = win.doc.styleSheets;
536                        for(i = 0; i < s.length; i++){
537                                if(s[i].href){ continue; }
538                                var r = s[i].cssRules || s[i].imports;
539                                if(!r){ continue; }
540                                for(j = 0; j < r.length; j++){
541                                        if(r[j].href){
542                                                paths.push(r[j].href);
543                                        }
544                                }
545                        }
546
547                        // find <link>
548                        var elems = win.doc.getElementsByTagName("link");
549                        for(i = 0, len = elems.length; i < len; i++){
550                                if(elems[i].href){
551                                        paths.push(elems[i].href);
552                                }
553                        }
554                        return paths;
555                };
556
557                dm.loadCompatPattern = /\/mobile\/themes\/.*\.css$/;
558
559                dm.loadCompatCssFiles = function(/*Boolean?*/force){
560                        // summary:
561                        //              Function to perform page-level adjustments on browsers such as
562                        //              IE and firefox.  It loads compat specific css files into the
563                        //              page header.
564                        if(has("ie") && !force){
565                                setTimeout(function(){ // IE needs setTimeout
566                                        dm.loadCompatCssFiles(true);
567                                }, 0);
568                                return;
569                        }
570                        dm._loadedCss = undefined;
571                        var paths = dm.getCssPaths();
572                        // dojox.mobile mirroring support
573                        if(has("dojo-bidi")){
574                                paths = dm.loadRtlCssFiles(paths);
575                        }
576                        for(var i = 0; i < paths.length; i++){
577                                var href = paths[i];
578                                // Load the -compat.css only for css files that belong to a theme. For that, by default
579                                // we match on directories containing "mobile/themes". If a custom theme is located
580                                // outside a "mobile/themes" directory, the dojoConfig needs to specify a custom
581                                // pattern using the "mblLoadCompatPattern" configuration parameter, for instance:
582                                // data-dojo-config="mblLoadCompatPattern: /\/mycustom\/.*\.css$/"
583                                // Additionally, compat css files are loaded for css in the mobile/tests directory.
584                                if((href.match(config.mblLoadCompatPattern || dm.loadCompatPattern) ||
585                                        location.href.indexOf("mobile/tests/") !== -1) && href.indexOf("-compat.css") === -1){
586                                        var compatCss = href.substring(0, href.length-4)+"-compat.css";
587                                        dm.loadCss(compatCss);
588                                }
589                        }
590                };
591                if(has("dojo-bidi")){
592                        dm.loadRtlCssFiles = function(/*Array*/paths){
593                                // summary:
594                                //              Function to load the corresponding *_rtl.css file for every *.css file.
595                                //              Enable mobile mirroring support
596                                // paths: Array
597                                //              Array of css files within the page.
598                                for(var i = 0; i < paths.length; i++){
599                                        var href = paths[i];
600                                        if(href.indexOf("_rtl") == -1){
601                                                var rtlCssList = "android.css blackberry.css custom.css iphone.css holodark.css base.css Carousel.css ComboBox.css IconContainer.css IconMenu.css ListItem.css RoundRectCategory.css SpinWheel.css Switch.css TabBar.css ToggleButton.css ToolBarButton.css";
602                                                var cssName = href.substr(href.lastIndexOf('/') + 1);
603                                                if(rtlCssList.indexOf(cssName) != -1){
604                                                        var rtlPath = href.replace(".css", "_rtl.css");
605                                                        paths.push(rtlPath);
606                                                        dm.loadCss(rtlPath);
607                                                }
608                                        }
609                                }
610                                return paths;
611                        };
612                }
613                dm.hideAddressBar = function(/*Event?*/evt, /*Boolean?*/doResize){
614                        if(doResize !== false){ dm.resizeAll(); }
615                };
616
617                ready(function(){
618                        if(config["mblLoadCompatCssFiles"] !== false){
619                                dm.loadCompatCssFiles();
620                        }
621                        if(dm.applyPngFilter){
622                                dm.applyPngFilter();
623                        }
624                });
625
626        } // end of if(!has("webkit")){
627
628        return dm;
629});
Note: See TracBrowser for help on using the repository browser.