| /* |
| Copyright (c) 2004-2009, The Dojo Foundation All Rights Reserved. |
| Available via Academic Free License >= 2.1 OR the modified BSD license. |
| see: http://dojotoolkit.org/license for details |
| */ |
| |
| /* |
| This is a compiled version of Dojo, built for deployment and not for |
| development. To get an editable version, please visit: |
| |
| http://dojotoolkit.org |
| |
| for documentation and information on getting the source. |
| */ |
| |
| if(!dojo._hasResource["dojo.colors"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.colors"] = true; |
| dojo.provide("dojo.colors"); |
| |
| //TODO: this module appears to break naming conventions |
| |
| /*===== |
| dojo.colors = { |
| // summary: Color utilities |
| } |
| =====*/ |
| |
| (function(){ |
| // this is a standard conversion prescribed by the CSS3 Color Module |
| var hue2rgb = function(m1, m2, h){ |
| if(h < 0){ ++h; } |
| if(h > 1){ --h; } |
| var h6 = 6 * h; |
| if(h6 < 1){ return m1 + (m2 - m1) * h6; } |
| if(2 * h < 1){ return m2; } |
| if(3 * h < 2){ return m1 + (m2 - m1) * (2 / 3 - h) * 6; } |
| return m1; |
| }; |
| |
| dojo.colorFromRgb = function(/*String*/ color, /*dojo.Color?*/ obj){ |
| // summary: |
| // get rgb(a) array from css-style color declarations |
| // description: |
| // this function can handle all 4 CSS3 Color Module formats: rgb, |
| // rgba, hsl, hsla, including rgb(a) with percentage values. |
| var m = color.toLowerCase().match(/^(rgba?|hsla?)\(([\s\.\-,%0-9]+)\)/); |
| if(m){ |
| var c = m[2].split(/\s*,\s*/), l = c.length, t = m[1], a; |
| if((t == "rgb" && l == 3) || (t == "rgba" && l == 4)){ |
| var r = c[0]; |
| if(r.charAt(r.length - 1) == "%"){ |
| // 3 rgb percentage values |
| a = dojo.map(c, function(x){ |
| return parseFloat(x) * 2.56; |
| }); |
| if(l == 4){ a[3] = c[3]; } |
| return dojo.colorFromArray(a, obj); // dojo.Color |
| } |
| return dojo.colorFromArray(c, obj); // dojo.Color |
| } |
| if((t == "hsl" && l == 3) || (t == "hsla" && l == 4)){ |
| // normalize hsl values |
| var H = ((parseFloat(c[0]) % 360) + 360) % 360 / 360, |
| S = parseFloat(c[1]) / 100, |
| L = parseFloat(c[2]) / 100, |
| // calculate rgb according to the algorithm |
| // recommended by the CSS3 Color Module |
| m2 = L <= 0.5 ? L * (S + 1) : L + S - L * S, |
| m1 = 2 * L - m2; |
| a = [ |
| hue2rgb(m1, m2, H + 1 / 3) * 256, |
| hue2rgb(m1, m2, H) * 256, |
| hue2rgb(m1, m2, H - 1 / 3) * 256, |
| 1 |
| ]; |
| if(l == 4){ a[3] = c[3]; } |
| return dojo.colorFromArray(a, obj); // dojo.Color |
| } |
| } |
| return null; // dojo.Color |
| }; |
| |
| var confine = function(c, low, high){ |
| // summary: |
| // sanitize a color component by making sure it is a number, |
| // and clamping it to valid values |
| c = Number(c); |
| return isNaN(c) ? high : c < low ? low : c > high ? high : c; // Number |
| }; |
| |
| dojo.Color.prototype.sanitize = function(){ |
| // summary: makes sure that the object has correct attributes |
| var t = this; |
| t.r = Math.round(confine(t.r, 0, 255)); |
| t.g = Math.round(confine(t.g, 0, 255)); |
| t.b = Math.round(confine(t.b, 0, 255)); |
| t.a = confine(t.a, 0, 1); |
| return this; // dojo.Color |
| }; |
| })(); |
| |
| |
| dojo.colors.makeGrey = function(/*Number*/ g, /*Number?*/ a){ |
| // summary: creates a greyscale color with an optional alpha |
| return dojo.colorFromArray([g, g, g, a]); |
| }; |
| |
| // mixin all CSS3 named colors not already in _base, along with SVG 1.0 variant spellings |
| dojo.mixin(dojo.Color.named, { |
| aliceblue: [240,248,255], |
| antiquewhite: [250,235,215], |
| aquamarine: [127,255,212], |
| azure: [240,255,255], |
| beige: [245,245,220], |
| bisque: [255,228,196], |
| blanchedalmond: [255,235,205], |
| blueviolet: [138,43,226], |
| brown: [165,42,42], |
| burlywood: [222,184,135], |
| cadetblue: [95,158,160], |
| chartreuse: [127,255,0], |
| chocolate: [210,105,30], |
| coral: [255,127,80], |
| cornflowerblue: [100,149,237], |
| cornsilk: [255,248,220], |
| crimson: [220,20,60], |
| cyan: [0,255,255], |
| darkblue: [0,0,139], |
| darkcyan: [0,139,139], |
| darkgoldenrod: [184,134,11], |
| darkgray: [169,169,169], |
| darkgreen: [0,100,0], |
| darkgrey: [169,169,169], |
| darkkhaki: [189,183,107], |
| darkmagenta: [139,0,139], |
| darkolivegreen: [85,107,47], |
| darkorange: [255,140,0], |
| darkorchid: [153,50,204], |
| darkred: [139,0,0], |
| darksalmon: [233,150,122], |
| darkseagreen: [143,188,143], |
| darkslateblue: [72,61,139], |
| darkslategray: [47,79,79], |
| darkslategrey: [47,79,79], |
| darkturquoise: [0,206,209], |
| darkviolet: [148,0,211], |
| deeppink: [255,20,147], |
| deepskyblue: [0,191,255], |
| dimgray: [105,105,105], |
| dimgrey: [105,105,105], |
| dodgerblue: [30,144,255], |
| firebrick: [178,34,34], |
| floralwhite: [255,250,240], |
| forestgreen: [34,139,34], |
| gainsboro: [220,220,220], |
| ghostwhite: [248,248,255], |
| gold: [255,215,0], |
| goldenrod: [218,165,32], |
| greenyellow: [173,255,47], |
| grey: [128,128,128], |
| honeydew: [240,255,240], |
| hotpink: [255,105,180], |
| indianred: [205,92,92], |
| indigo: [75,0,130], |
| ivory: [255,255,240], |
| khaki: [240,230,140], |
| lavender: [230,230,250], |
| lavenderblush: [255,240,245], |
| lawngreen: [124,252,0], |
| lemonchiffon: [255,250,205], |
| lightblue: [173,216,230], |
| lightcoral: [240,128,128], |
| lightcyan: [224,255,255], |
| lightgoldenrodyellow: [250,250,210], |
| lightgray: [211,211,211], |
| lightgreen: [144,238,144], |
| lightgrey: [211,211,211], |
| lightpink: [255,182,193], |
| lightsalmon: [255,160,122], |
| lightseagreen: [32,178,170], |
| lightskyblue: [135,206,250], |
| lightslategray: [119,136,153], |
| lightslategrey: [119,136,153], |
| lightsteelblue: [176,196,222], |
| lightyellow: [255,255,224], |
| limegreen: [50,205,50], |
| linen: [250,240,230], |
| magenta: [255,0,255], |
| mediumaquamarine: [102,205,170], |
| mediumblue: [0,0,205], |
| mediumorchid: [186,85,211], |
| mediumpurple: [147,112,219], |
| mediumseagreen: [60,179,113], |
| mediumslateblue: [123,104,238], |
| mediumspringgreen: [0,250,154], |
| mediumturquoise: [72,209,204], |
| mediumvioletred: [199,21,133], |
| midnightblue: [25,25,112], |
| mintcream: [245,255,250], |
| mistyrose: [255,228,225], |
| moccasin: [255,228,181], |
| navajowhite: [255,222,173], |
| oldlace: [253,245,230], |
| olivedrab: [107,142,35], |
| orange: [255,165,0], |
| orangered: [255,69,0], |
| orchid: [218,112,214], |
| palegoldenrod: [238,232,170], |
| palegreen: [152,251,152], |
| paleturquoise: [175,238,238], |
| palevioletred: [219,112,147], |
| papayawhip: [255,239,213], |
| peachpuff: [255,218,185], |
| peru: [205,133,63], |
| pink: [255,192,203], |
| plum: [221,160,221], |
| powderblue: [176,224,230], |
| rosybrown: [188,143,143], |
| royalblue: [65,105,225], |
| saddlebrown: [139,69,19], |
| salmon: [250,128,114], |
| sandybrown: [244,164,96], |
| seagreen: [46,139,87], |
| seashell: [255,245,238], |
| sienna: [160,82,45], |
| skyblue: [135,206,235], |
| slateblue: [106,90,205], |
| slategray: [112,128,144], |
| slategrey: [112,128,144], |
| snow: [255,250,250], |
| springgreen: [0,255,127], |
| steelblue: [70,130,180], |
| tan: [210,180,140], |
| thistle: [216,191,216], |
| tomato: [255,99,71], |
| transparent: [0, 0, 0, 0], |
| turquoise: [64,224,208], |
| violet: [238,130,238], |
| wheat: [245,222,179], |
| whitesmoke: [245,245,245], |
| yellowgreen: [154,205,50] |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dojo.i18n"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.i18n"] = true; |
| dojo.provide("dojo.i18n"); |
| |
| /*===== |
| dojo.i18n = { |
| // summary: Utility classes to enable loading of resources for internationalization (i18n) |
| }; |
| =====*/ |
| |
| dojo.i18n.getLocalization = function(/*String*/packageName, /*String*/bundleName, /*String?*/locale){ |
| // summary: |
| // Returns an Object containing the localization for a given resource |
| // bundle in a package, matching the specified locale. |
| // description: |
| // Returns a hash containing name/value pairs in its prototypesuch |
| // that values can be easily overridden. Throws an exception if the |
| // bundle is not found. Bundle must have already been loaded by |
| // `dojo.requireLocalization()` or by a build optimization step. NOTE: |
| // try not to call this method as part of an object property |
| // definition (`var foo = { bar: dojo.i18n.getLocalization() }`). In |
| // some loading situations, the bundle may not be available in time |
| // for the object definition. Instead, call this method inside a |
| // function that is run after all modules load or the page loads (like |
| // in `dojo.addOnLoad()`), or in a widget lifecycle method. |
| // packageName: |
| // package which is associated with this resource |
| // bundleName: |
| // the base filename of the resource bundle (without the ".js" suffix) |
| // locale: |
| // the variant to load (optional). By default, the locale defined by |
| // the host environment: dojo.locale |
| |
| locale = dojo.i18n.normalizeLocale(locale); |
| |
| // look for nearest locale match |
| var elements = locale.split('-'); |
| var module = [packageName,"nls",bundleName].join('.'); |
| var bundle = dojo._loadedModules[module]; |
| if(bundle){ |
| var localization; |
| for(var i = elements.length; i > 0; i--){ |
| var loc = elements.slice(0, i).join('_'); |
| if(bundle[loc]){ |
| localization = bundle[loc]; |
| break; |
| } |
| } |
| if(!localization){ |
| localization = bundle.ROOT; |
| } |
| |
| // make a singleton prototype so that the caller won't accidentally change the values globally |
| if(localization){ |
| var clazz = function(){}; |
| clazz.prototype = localization; |
| return new clazz(); // Object |
| } |
| } |
| |
| throw new Error("Bundle not found: " + bundleName + " in " + packageName+" , locale=" + locale); |
| }; |
| |
| dojo.i18n.normalizeLocale = function(/*String?*/locale){ |
| // summary: |
| // Returns canonical form of locale, as used by Dojo. |
| // |
| // description: |
| // All variants are case-insensitive and are separated by '-' as specified in [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt). |
| // If no locale is specified, the dojo.locale is returned. dojo.locale is defined by |
| // the user agent's locale unless overridden by djConfig. |
| |
| var result = locale ? locale.toLowerCase() : dojo.locale; |
| if(result == "root"){ |
| result = "ROOT"; |
| } |
| return result; // String |
| }; |
| |
| dojo.i18n._requireLocalization = function(/*String*/moduleName, /*String*/bundleName, /*String?*/locale, /*String?*/availableFlatLocales){ |
| // summary: |
| // See dojo.requireLocalization() |
| // description: |
| // Called by the bootstrap, but factored out so that it is only |
| // included in the build when needed. |
| |
| var targetLocale = dojo.i18n.normalizeLocale(locale); |
| var bundlePackage = [moduleName, "nls", bundleName].join("."); |
| // NOTE: |
| // When loading these resources, the packaging does not match what is |
| // on disk. This is an implementation detail, as this is just a |
| // private data structure to hold the loaded resources. e.g. |
| // `tests/hello/nls/en-us/salutations.js` is loaded as the object |
| // `tests.hello.nls.salutations.en_us={...}` The structure on disk is |
| // intended to be most convenient for developers and translators, but |
| // in memory it is more logical and efficient to store in a different |
| // order. Locales cannot use dashes, since the resulting path will |
| // not evaluate as valid JS, so we translate them to underscores. |
| |
| //Find the best-match locale to load if we have available flat locales. |
| var bestLocale = ""; |
| if(availableFlatLocales){ |
| var flatLocales = availableFlatLocales.split(","); |
| for(var i = 0; i < flatLocales.length; i++){ |
| //Locale must match from start of string. |
| //Using ["indexOf"] so customBase builds do not see |
| //this as a dojo._base.array dependency. |
| if(targetLocale["indexOf"](flatLocales[i]) == 0){ |
| if(flatLocales[i].length > bestLocale.length){ |
| bestLocale = flatLocales[i]; |
| } |
| } |
| } |
| if(!bestLocale){ |
| bestLocale = "ROOT"; |
| } |
| } |
| |
| //See if the desired locale is already loaded. |
| var tempLocale = availableFlatLocales ? bestLocale : targetLocale; |
| var bundle = dojo._loadedModules[bundlePackage]; |
| var localizedBundle = null; |
| if(bundle){ |
| if(dojo.config.localizationComplete && bundle._built){return;} |
| var jsLoc = tempLocale.replace(/-/g, '_'); |
| var translationPackage = bundlePackage+"."+jsLoc; |
| localizedBundle = dojo._loadedModules[translationPackage]; |
| } |
| |
| if(!localizedBundle){ |
| bundle = dojo["provide"](bundlePackage); |
| var syms = dojo._getModuleSymbols(moduleName); |
| var modpath = syms.concat("nls").join("/"); |
| var parent; |
| |
| dojo.i18n._searchLocalePath(tempLocale, availableFlatLocales, function(loc){ |
| var jsLoc = loc.replace(/-/g, '_'); |
| var translationPackage = bundlePackage + "." + jsLoc; |
| var loaded = false; |
| if(!dojo._loadedModules[translationPackage]){ |
| // Mark loaded whether it's found or not, so that further load attempts will not be made |
| dojo["provide"](translationPackage); |
| var module = [modpath]; |
| if(loc != "ROOT"){module.push(loc);} |
| module.push(bundleName); |
| var filespec = module.join("/") + '.js'; |
| loaded = dojo._loadPath(filespec, null, function(hash){ |
| // Use singleton with prototype to point to parent bundle, then mix-in result from loadPath |
| var clazz = function(){}; |
| clazz.prototype = parent; |
| bundle[jsLoc] = new clazz(); |
| for(var j in hash){ bundle[jsLoc][j] = hash[j]; } |
| }); |
| }else{ |
| loaded = true; |
| } |
| if(loaded && bundle[jsLoc]){ |
| parent = bundle[jsLoc]; |
| }else{ |
| bundle[jsLoc] = parent; |
| } |
| |
| if(availableFlatLocales){ |
| //Stop the locale path searching if we know the availableFlatLocales, since |
| //the first call to this function will load the only bundle that is needed. |
| return true; |
| } |
| }); |
| } |
| |
| //Save the best locale bundle as the target locale bundle when we know the |
| //the available bundles. |
| if(availableFlatLocales && targetLocale != bestLocale){ |
| bundle[targetLocale.replace(/-/g, '_')] = bundle[bestLocale.replace(/-/g, '_')]; |
| } |
| }; |
| |
| (function(){ |
| // If other locales are used, dojo.requireLocalization should load them as |
| // well, by default. |
| // |
| // Override dojo.requireLocalization to do load the default bundle, then |
| // iterate through the extraLocale list and load those translations as |
| // well, unless a particular locale was requested. |
| |
| var extra = dojo.config.extraLocale; |
| if(extra){ |
| if(!extra instanceof Array){ |
| extra = [extra]; |
| } |
| |
| var req = dojo.i18n._requireLocalization; |
| dojo.i18n._requireLocalization = function(m, b, locale, availableFlatLocales){ |
| req(m,b,locale, availableFlatLocales); |
| if(locale){return;} |
| for(var i=0; i<extra.length; i++){ |
| req(m,b,extra[i], availableFlatLocales); |
| } |
| }; |
| } |
| })(); |
| |
| dojo.i18n._searchLocalePath = function(/*String*/locale, /*Boolean*/down, /*Function*/searchFunc){ |
| // summary: |
| // A helper method to assist in searching for locale-based resources. |
| // Will iterate through the variants of a particular locale, either up |
| // or down, executing a callback function. For example, "en-us" and |
| // true will try "en-us" followed by "en" and finally "ROOT". |
| |
| locale = dojo.i18n.normalizeLocale(locale); |
| |
| var elements = locale.split('-'); |
| var searchlist = []; |
| for(var i = elements.length; i > 0; i--){ |
| searchlist.push(elements.slice(0, i).join('-')); |
| } |
| searchlist.push(false); |
| if(down){searchlist.reverse();} |
| |
| for(var j = searchlist.length - 1; j >= 0; j--){ |
| var loc = searchlist[j] || "ROOT"; |
| var stop = searchFunc(loc); |
| if(stop){ break; } |
| } |
| }; |
| |
| dojo.i18n._preloadLocalizations = function(/*String*/bundlePrefix, /*Array*/localesGenerated){ |
| // summary: |
| // Load built, flattened resource bundles, if available for all |
| // locales used in the page. Only called by built layer files. |
| |
| function preload(locale){ |
| locale = dojo.i18n.normalizeLocale(locale); |
| dojo.i18n._searchLocalePath(locale, true, function(loc){ |
| for(var i=0; i<localesGenerated.length;i++){ |
| if(localesGenerated[i] == loc){ |
| dojo["require"](bundlePrefix+"_"+loc); |
| return true; // Boolean |
| } |
| } |
| return false; // Boolean |
| }); |
| } |
| preload(); |
| var extra = dojo.config.extraLocale||[]; |
| for(var i=0; i<extra.length; i++){ |
| preload(extra[i]); |
| } |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dijit.ColorPalette"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.ColorPalette"] = true; |
| dojo.provide("dijit.ColorPalette"); |
| |
| |
| |
| |
| |
| |
| |
| dojo.declare("dijit.ColorPalette", |
| [dijit._Widget, dijit._Templated], |
| { |
| // summary: |
| // A keyboard accessible color-picking widget |
| // description: |
| // Grid showing various colors, so the user can pick a certain color. |
| // Can be used standalone, or as a popup. |
| // |
| // example: |
| // | <div dojoType="dijit.ColorPalette"></div> |
| // |
| // example: |
| // | var picker = new dijit.ColorPalette({ },srcNode); |
| // | picker.startup(); |
| |
| // defaultTimeout: Number |
| // Number of milliseconds before a held key or button becomes typematic |
| defaultTimeout: 500, |
| |
| // timeoutChangeRate: Number |
| // Fraction of time used to change the typematic timer between events |
| // 1.0 means that each typematic event fires at defaultTimeout intervals |
| // < 1.0 means that each typematic event fires at an increasing faster rate |
| timeoutChangeRate: 0.90, |
| |
| // palette: String |
| // Size of grid, either "7x10" or "3x4". |
| palette: "7x10", |
| |
| // value: String |
| // The value of the selected color. |
| value: null, |
| |
| // _currentFocus: [private] DomNode |
| // The currently focused or hovered color. |
| // Different from value, which represents the selected (i.e. clicked) color. |
| _currentFocus: 0, |
| |
| // _xDim: [protected] Integer |
| // This is the number of colors horizontally across. |
| _xDim: null, |
| |
| // _yDim: [protected] Integer |
| /// This is the number of colors vertically down. |
| _yDim: null, |
| |
| // _palettes: [protected] Map |
| // This represents the value of the colors. |
| // The first level is a hashmap of the different arrays available |
| // The next two dimensions represent the columns and rows of colors. |
| _palettes: { |
| "7x10": [["white", "seashell", "cornsilk", "lemonchiffon","lightyellow", "palegreen", "paleturquoise", "lightcyan", "lavender", "plum"], |
| ["lightgray", "pink", "bisque", "moccasin", "khaki", "lightgreen", "lightseagreen", "lightskyblue", "cornflowerblue", "violet"], |
| ["silver", "lightcoral", "sandybrown", "orange", "palegoldenrod", "chartreuse", "mediumturquoise", "skyblue", "mediumslateblue","orchid"], |
| ["gray", "red", "orangered", "darkorange", "yellow", "limegreen", "darkseagreen", "royalblue", "slateblue", "mediumorchid"], |
| ["dimgray", "crimson", "chocolate", "coral", "gold", "forestgreen", "seagreen", "blue", "blueviolet", "darkorchid"], |
| ["darkslategray","firebrick","saddlebrown", "sienna", "olive", "green", "darkcyan", "mediumblue","darkslateblue", "darkmagenta" ], |
| ["black", "darkred", "maroon", "brown", "darkolivegreen", "darkgreen", "midnightblue", "navy", "indigo", "purple"]], |
| |
| "3x4": [["white", "lime", "green", "blue"], |
| ["silver", "yellow", "fuchsia", "navy"], |
| ["gray", "red", "purple", "black"]] |
| }, |
| |
| // _imagePaths: [protected] Map |
| // This is stores the path to the palette images |
| _imagePaths: { |
| "7x10": dojo.moduleUrl("dijit.themes", "a11y/colors7x10.png"), |
| "3x4": dojo.moduleUrl("dijit.themes", "a11y/colors3x4.png") |
| }, |
| |
| // _paletteCoords: [protected] Map |
| // This is a map that is used to calculate the coordinates of the |
| // images that make up the palette. |
| _paletteCoords: { |
| "leftOffset": 3, "topOffset": 3, |
| "cWidth": 20, "cHeight": 20 |
| }, |
| |
| // templateString: String |
| // The template of this widget. |
| templateString: dojo.cache("dijit", "templates/ColorPalette.html", "<div class=\"dijitInline dijitColorPalette\">\n\t<div class=\"dijitColorPaletteInner\" dojoAttachPoint=\"divNode\" waiRole=\"grid\"\">\n\t\t<img class=\"dijitColorPaletteUnder\" dojoAttachPoint=\"imageNode\" waiRole=\"presentation\" alt=\"\">\n\t</div>\n</div>\n"), |
| |
| // _paletteDims: [protected] Object |
| // Size of the supported palettes for alignment purposes. |
| _paletteDims: { |
| "7x10": {"width": "206px", "height": "145px"}, |
| "3x4": {"width": "86px", "height": "64px"} |
| }, |
| |
| // tabIndex: String |
| // Widget tab index. |
| tabIndex: "0", |
| |
| buildRendering: function(){ |
| // Instantiate the template, which makes a skeleton into which we'll insert a bunch of |
| // <img> nodes |
| this.inherited(arguments); |
| |
| // A name has to be given to the colorMap, this needs to be unique per Palette. |
| dojo.mixin(this.divNode.style, this._paletteDims[this.palette]); |
| this.imageNode.setAttribute("src", this._imagePaths[this.palette].toString()); |
| var choices = this._palettes[this.palette]; |
| this.domNode.style.position = "relative"; |
| this._cellNodes = []; |
| this.colorNames = dojo.i18n.getLocalization("dojo", "colors", this.lang); |
| var url = this._blankGif, |
| colorObject = new dojo.Color(), |
| coords = this._paletteCoords; |
| for(var row=0; row < choices.length; row++){ |
| var rowNode = dojo.create("div", { |
| role: "row" |
| }, this.divNode); |
| for(var col=0; col < choices[row].length; col++){ |
| |
| var color = choices[row][col], |
| colorValue = colorObject.setColor(dojo.Color.named[color]); |
| |
| var cellNode = dojo.create("span", { |
| "class": "dijitPaletteCell", |
| tabIndex: "-1", |
| title: this.colorNames[color], |
| style: { |
| top: coords.topOffset + (row * coords.cHeight) + "px", |
| left: coords.leftOffset + (col * coords.cWidth) + "px" |
| } |
| }); |
| |
| var imgNode = dojo.create("img",{ |
| src: url, |
| "class":"dijitPaletteImg", |
| alt: this.colorNames[color] |
| }, cellNode); |
| |
| // FIXME: color is an attribute of img? |
| imgNode.color = colorValue.toHex(); |
| var imgStyle = imgNode.style; |
| imgStyle.color = imgStyle.backgroundColor = imgNode.color; |
| |
| dojo.forEach(["Dijitclick", "MouseEnter", "MouseLeave", "Focus"], function(handler){ |
| this.connect(cellNode, "on" + handler.toLowerCase(), "_onCell" + handler); |
| }, this); |
| |
| dojo.place(cellNode, rowNode); |
| |
| dijit.setWaiRole(cellNode, "gridcell"); |
| cellNode.index = this._cellNodes.length; |
| this._cellNodes.push(cellNode); |
| } |
| } |
| this._xDim = choices[0].length; |
| this._yDim = choices.length; |
| |
| // Now set all events |
| // The palette itself is navigated to with the tab key on the keyboard |
| // Keyboard navigation within the Palette is with the arrow keys |
| // Spacebar selects the color. |
| // For the up key the index is changed by negative the x dimension. |
| |
| var keyIncrementMap = { |
| UP_ARROW: -this._xDim, |
| // The down key the index is increase by the x dimension. |
| DOWN_ARROW: this._xDim, |
| // Right and left move the index by 1. |
| RIGHT_ARROW: 1, |
| LEFT_ARROW: -1 |
| }; |
| for(var key in keyIncrementMap){ |
| this._connects.push(dijit.typematic.addKeyListener(this.domNode, |
| {charOrCode:dojo.keys[key], ctrlKey:false, altKey:false, shiftKey:false}, |
| this, |
| function(){ |
| var increment = keyIncrementMap[key]; |
| return function(count){ this._navigateByKey(increment, count); }; |
| }(), |
| this.timeoutChangeRate, this.defaultTimeout)); |
| } |
| }, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| |
| // Set initial navigable node. At any point in time there's exactly one |
| // cell with tabIndex != -1. If focus is inside the ColorPalette then |
| // focus is on that cell. |
| // TODO: if we set aria info (for the current value) on the ColorPalette itself then can we avoid |
| // having to focus each individual cell? |
| this._currentFocus = this._cellNodes[0]; |
| dojo.attr(this._currentFocus, "tabIndex", this.tabIndex); |
| }, |
| |
| focus: function(){ |
| // summary: |
| // Focus this ColorPalette. Puts focus on the most recently focused cell. |
| |
| // The cell already has tabIndex set, just need to set CSS and focus it |
| dojo.addClass(this._currentFocus, "dijitPaletteCellHighlight"); |
| dijit.focus(this._currentFocus); |
| }, |
| |
| onChange: function(color){ |
| // summary: |
| // Callback when a color is selected. |
| // color: String |
| // Hex value corresponding to color. |
| // console.debug("Color selected is: "+color); |
| }, |
| |
| _onFocus: function(){ |
| // summary: |
| // Handler for when the ColorPalette gets focus (because a cell inside |
| // the ColorPalette got focus) |
| // tags: |
| // protected |
| |
| dojo.addClass(this._currentFocus, "dijitPaletteCellHighlight"); |
| this.inherited(arguments); |
| }, |
| |
| _onBlur: function(){ |
| // summary: |
| // Handler for when the ColorPalette loses focus |
| // tags: |
| // protected |
| |
| // Just to be the same as 1.3, when I am focused again go to first (0,0) cell rather than |
| // currently focused node. |
| dojo.attr(this._currentFocus, "tabIndex", "-1"); |
| dojo.removeClass(this._currentFocus, "dijitPaletteCellHighlight"); |
| this._currentFocus = this._cellNodes[0]; |
| dojo.attr(this._currentFocus, "tabIndex", this.tabIndex); |
| |
| this.inherited(arguments); |
| }, |
| |
| _onCellDijitclick: function(/*Event*/ evt){ |
| // summary: |
| // Handler for click, enter key & space key. Selects the color. |
| // evt: |
| // The event. |
| // tags: |
| // private |
| |
| var target = evt.currentTarget; |
| this._selectColor(target); |
| dojo.stopEvent(evt); |
| }, |
| |
| _onCellMouseEnter: function(/*Event*/ evt){ |
| // summary: |
| // Handler for onMouseEnter event on a cell. Put highlight on the color under the mouse. |
| // evt: |
| // The mouse event. |
| // tags: |
| // private |
| |
| var target = evt.currentTarget; |
| this._setCurrent(target); |
| }, |
| |
| _onCellMouseLeave: function(/*Event*/ evt){ |
| // summary: |
| // Handler for onMouseLeave event on a cell. Remove highlight on the color under the mouse. |
| // evt: |
| // The mouse event. |
| // tags: |
| // private |
| |
| dojo.removeClass(this._currentFocus, "dijitPaletteCellHighlight"); |
| }, |
| |
| _onCellFocus: function(/*Event*/ evt){ |
| // summary: |
| // Handler for onFocus of a cell. |
| // description: |
| // Removes highlight of the color that just lost focus, and highlights |
| // the new color. Also moves the tabIndex setting to the new cell. |
| // |
| // evt: |
| // The focus event. |
| // tags: |
| // private |
| |
| this._setCurrent(evt.currentTarget); |
| }, |
| |
| _setCurrent: function(/*Node*/ node){ |
| // summary: |
| // Called when a color is hovered or focused. |
| // description: |
| // Removes highlight of the old color, and highlights |
| // the new color. Also moves the tabIndex setting to the new cell. |
| // tags: |
| // protected |
| if("_currentFocus" in this){ |
| // Remove highlight and tabIndex on old cell |
| dojo.attr(this._currentFocus, "tabIndex", "-1"); |
| dojo.removeClass(this._currentFocus, "dijitPaletteCellHighlight"); |
| } |
| |
| // Set highlight and tabIndex of new cell |
| this._currentFocus = node; |
| if(node){ |
| dojo.attr(node, "tabIndex", this.tabIndex); |
| dojo.addClass(node, "dijitPaletteCellHighlight"); |
| } |
| }, |
| |
| _selectColor: function(selectNode){ |
| // summary: |
| // This selects a color. It triggers the onChange event |
| // area: |
| // The area node that covers the color being selected. |
| // tags: |
| // private |
| var img = selectNode.getElementsByTagName("img")[0]; |
| this.onChange(this.value = img.color); |
| }, |
| |
| _navigateByKey: function(increment, typeCount){ |
| // summary: |
| // This is the callback for typematic. |
| // It changes the focus and the highlighed color. |
| // increment: |
| // How much the key is navigated. |
| // typeCount: |
| // How many times typematic has fired. |
| // tags: |
| // private |
| |
| // typecount == -1 means the key is released. |
| if(typeCount == -1){ return; } |
| |
| var newFocusIndex = this._currentFocus.index + increment; |
| if(newFocusIndex < this._cellNodes.length && newFocusIndex > -1) |
| { |
| var focusNode = this._cellNodes[newFocusIndex]; |
| this._setCurrent(focusNode); |
| |
| // Actually focus the node, for the benefit of screen readers. |
| // Use setTimeout because IE doesn't like changing focus inside of an event handler |
| setTimeout(dojo.hitch(dijit, "focus", focusNode), 0); |
| } |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.Declaration"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.Declaration"] = true; |
| dojo.provide("dijit.Declaration"); |
| |
| |
| |
| dojo.declare( |
| "dijit.Declaration", |
| dijit._Widget, |
| { |
| // summary: |
| // The Declaration widget allows a developer to declare new widget |
| // classes directly from a snippet of markup. |
| |
| // _noScript: [private] Boolean |
| // Flag to parser to leave alone the script tags contained inside of me |
| _noScript: true, |
| |
| // widgetClass: String |
| // Name of class being declared, ex: "acme.myWidget" |
| widgetClass: "", |
| |
| // propList: Object |
| // Set of attributes for this widget along with default values, ex: |
| // {delay: 100, title: "hello world"} |
| defaults: null, |
| |
| // mixins: String[] |
| // List containing the prototype for this widget, and also any mixins, |
| // ex: ["dijit._Widget", "dijit._Container"] |
| mixins: [], |
| |
| buildRendering: function(){ |
| var src = this.srcNodeRef.parentNode.removeChild(this.srcNodeRef), |
| methods = dojo.query("> script[type^='dojo/method'][event]", src).orphan(), |
| postscriptConnects = dojo.query("> script[type^='dojo/method']", src).orphan(), |
| regularConnects = dojo.query("> script[type^='dojo/connect']", src).orphan(), |
| srcType = src.nodeName; |
| |
| var propList = this.defaults || {}; |
| |
| // For all methods defined like <script type="dojo/method" event="foo">, |
| // add that method to prototype |
| dojo.forEach(methods, function(s){ |
| var evt = s.getAttribute("event"), |
| func = dojo.parser._functionFromScript(s); |
| propList[evt] = func; |
| }); |
| |
| // map array of strings like [ "dijit.form.Button" ] to array of mixin objects |
| // (note that dojo.map(this.mixins, dojo.getObject) doesn't work because it passes |
| // a bogus third argument to getObject(), confusing it) |
| this.mixins = this.mixins.length ? |
| dojo.map(this.mixins, function(name){ return dojo.getObject(name); } ) : |
| [ dijit._Widget, dijit._Templated ]; |
| |
| propList.widgetsInTemplate = true; |
| propList._skipNodeCache = true; |
| propList.templateString = "<"+srcType+" class='"+src.className+"' dojoAttachPoint='"+(src.getAttribute("dojoAttachPoint") || '')+"' dojoAttachEvent='"+(src.getAttribute("dojoAttachEvent") || '')+"' >"+src.innerHTML.replace(/\%7B/g,"{").replace(/\%7D/g,"}")+"</"+srcType+">"; |
| |
| // strip things so we don't create stuff under us in the initial setup phase |
| dojo.query("[dojoType]", src).forEach(function(node){ |
| node.removeAttribute("dojoType"); |
| }); |
| |
| // create the new widget class |
| var wc = dojo.declare( |
| this.widgetClass, |
| this.mixins, |
| propList |
| ); |
| |
| // Handle <script> blocks of form: |
| // <script type="dojo/connect" event="foo"> |
| // and |
| // <script type="dojo/method"> |
| // (Note that the second one is just shorthand for a dojo/connect to postscript) |
| // Since this is a connect in the declaration, we are actually connection to the method |
| // in the _prototype_. |
| var connects = regularConnects.concat(postscriptConnects); |
| dojo.forEach(connects, function(s){ |
| var evt = s.getAttribute("event") || "postscript", |
| func = dojo.parser._functionFromScript(s); |
| dojo.connect(wc.prototype, evt, func); |
| }); |
| } |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dojo.dnd.common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.dnd.common"] = true; |
| dojo.provide("dojo.dnd.common"); |
| |
| dojo.dnd.getCopyKeyState = dojo.isCopyKeyPressed; |
| |
| dojo.dnd._uniqueId = 0; |
| dojo.dnd.getUniqueId = function(){ |
| // summary: |
| // returns a unique string for use with any DOM element |
| var id; |
| do{ |
| id = dojo._scopeName + "Unique" + (++dojo.dnd._uniqueId); |
| }while(dojo.byId(id)); |
| return id; |
| }; |
| |
| dojo.dnd._empty = {}; |
| |
| dojo.dnd.isFormElement = function(/*Event*/ e){ |
| // summary: |
| // returns true if user clicked on a form element |
| var t = e.target; |
| if(t.nodeType == 3 /*TEXT_NODE*/){ |
| t = t.parentNode; |
| } |
| return " button textarea input select option ".indexOf(" " + t.tagName.toLowerCase() + " ") >= 0; // Boolean |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dojo.dnd.autoscroll"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.dnd.autoscroll"] = true; |
| dojo.provide("dojo.dnd.autoscroll"); |
| |
| dojo.dnd.getViewport = function(){ |
| // summary: |
| // Returns a viewport size (visible part of the window) |
| |
| // TODO: remove this when getViewport() moved to dojo core, see #7028 |
| |
| // FIXME: need more docs!! |
| var d = dojo.doc, dd = d.documentElement, w = window, b = dojo.body(); |
| if(dojo.isMozilla){ |
| return {w: dd.clientWidth, h: w.innerHeight}; // Object |
| }else if(!dojo.isOpera && w.innerWidth){ |
| return {w: w.innerWidth, h: w.innerHeight}; // Object |
| }else if (!dojo.isOpera && dd && dd.clientWidth){ |
| return {w: dd.clientWidth, h: dd.clientHeight}; // Object |
| }else if (b.clientWidth){ |
| return {w: b.clientWidth, h: b.clientHeight}; // Object |
| } |
| return null; // Object |
| }; |
| |
| dojo.dnd.V_TRIGGER_AUTOSCROLL = 32; |
| dojo.dnd.H_TRIGGER_AUTOSCROLL = 32; |
| |
| dojo.dnd.V_AUTOSCROLL_VALUE = 16; |
| dojo.dnd.H_AUTOSCROLL_VALUE = 16; |
| |
| dojo.dnd.autoScroll = function(e){ |
| // summary: |
| // a handler for onmousemove event, which scrolls the window, if |
| // necesary |
| // e: Event |
| // onmousemove event |
| |
| // FIXME: needs more docs! |
| var v = dojo.dnd.getViewport(), dx = 0, dy = 0; |
| if(e.clientX < dojo.dnd.H_TRIGGER_AUTOSCROLL){ |
| dx = -dojo.dnd.H_AUTOSCROLL_VALUE; |
| }else if(e.clientX > v.w - dojo.dnd.H_TRIGGER_AUTOSCROLL){ |
| dx = dojo.dnd.H_AUTOSCROLL_VALUE; |
| } |
| if(e.clientY < dojo.dnd.V_TRIGGER_AUTOSCROLL){ |
| dy = -dojo.dnd.V_AUTOSCROLL_VALUE; |
| }else if(e.clientY > v.h - dojo.dnd.V_TRIGGER_AUTOSCROLL){ |
| dy = dojo.dnd.V_AUTOSCROLL_VALUE; |
| } |
| window.scrollBy(dx, dy); |
| }; |
| |
| dojo.dnd._validNodes = {"div": 1, "p": 1, "td": 1}; |
| dojo.dnd._validOverflow = {"auto": 1, "scroll": 1}; |
| |
| dojo.dnd.autoScrollNodes = function(e){ |
| // summary: |
| // a handler for onmousemove event, which scrolls the first avaialble |
| // Dom element, it falls back to dojo.dnd.autoScroll() |
| // e: Event |
| // onmousemove event |
| |
| // FIXME: needs more docs! |
| for(var n = e.target; n;){ |
| if(n.nodeType == 1 && (n.tagName.toLowerCase() in dojo.dnd._validNodes)){ |
| var s = dojo.getComputedStyle(n); |
| if(s.overflow.toLowerCase() in dojo.dnd._validOverflow){ |
| var b = dojo._getContentBox(n, s), t = dojo.position(n, true); |
| //console.log(b.l, b.t, t.x, t.y, n.scrollLeft, n.scrollTop); |
| var w = Math.min(dojo.dnd.H_TRIGGER_AUTOSCROLL, b.w / 2), |
| h = Math.min(dojo.dnd.V_TRIGGER_AUTOSCROLL, b.h / 2), |
| rx = e.pageX - t.x, ry = e.pageY - t.y, dx = 0, dy = 0; |
| if(dojo.isWebKit || dojo.isOpera){ |
| // FIXME: this code should not be here, it should be taken into account |
| // either by the event fixing code, or the dojo.position() |
| // FIXME: this code doesn't work on Opera 9.5 Beta |
| rx += dojo.body().scrollLeft, ry += dojo.body().scrollTop; |
| } |
| if(rx > 0 && rx < b.w){ |
| if(rx < w){ |
| dx = -w; |
| }else if(rx > b.w - w){ |
| dx = w; |
| } |
| } |
| //console.log("ry =", ry, "b.h =", b.h, "h =", h); |
| if(ry > 0 && ry < b.h){ |
| if(ry < h){ |
| dy = -h; |
| }else if(ry > b.h - h){ |
| dy = h; |
| } |
| } |
| var oldLeft = n.scrollLeft, oldTop = n.scrollTop; |
| n.scrollLeft = n.scrollLeft + dx; |
| n.scrollTop = n.scrollTop + dy; |
| if(oldLeft != n.scrollLeft || oldTop != n.scrollTop){ return; } |
| } |
| } |
| try{ |
| n = n.parentNode; |
| }catch(x){ |
| n = null; |
| } |
| } |
| dojo.dnd.autoScroll(e); |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dojo.dnd.Mover"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.dnd.Mover"] = true; |
| dojo.provide("dojo.dnd.Mover"); |
| |
| |
| |
| |
| dojo.declare("dojo.dnd.Mover", null, { |
| constructor: function(node, e, host){ |
| // summary: |
| // an object, which makes a node follow the mouse. |
| // Used as a default mover, and as a base class for custom movers. |
| // node: Node |
| // a node (or node's id) to be moved |
| // e: Event |
| // a mouse event, which started the move; |
| // only pageX and pageY properties are used |
| // host: Object? |
| // object which implements the functionality of the move, |
| // and defines proper events (onMoveStart and onMoveStop) |
| this.node = dojo.byId(node); |
| this.marginBox = {l: e.pageX, t: e.pageY}; |
| this.mouseButton = e.button; |
| var h = this.host = host, d = node.ownerDocument, |
| firstEvent = dojo.connect(d, "onmousemove", this, "onFirstMove"); |
| this.events = [ |
| dojo.connect(d, "onmousemove", this, "onMouseMove"), |
| dojo.connect(d, "onmouseup", this, "onMouseUp"), |
| // cancel text selection and text dragging |
| dojo.connect(d, "ondragstart", dojo.stopEvent), |
| dojo.connect(d.body, "onselectstart", dojo.stopEvent), |
| firstEvent |
| ]; |
| // notify that the move has started |
| if(h && h.onMoveStart){ |
| h.onMoveStart(this); |
| } |
| }, |
| // mouse event processors |
| onMouseMove: function(e){ |
| // summary: |
| // event processor for onmousemove |
| // e: Event |
| // mouse event |
| dojo.dnd.autoScroll(e); |
| var m = this.marginBox; |
| this.host.onMove(this, {l: m.l + e.pageX, t: m.t + e.pageY}); |
| dojo.stopEvent(e); |
| }, |
| onMouseUp: function(e){ |
| if(dojo.isWebKit && dojo.isMac && this.mouseButton == 2 ? |
| e.button == 0 : this.mouseButton == e.button){ |
| this.destroy(); |
| } |
| dojo.stopEvent(e); |
| }, |
| // utilities |
| onFirstMove: function(){ |
| // summary: |
| // makes the node absolute; it is meant to be called only once. |
| // relative and absolutely positioned nodes are assumed to use pixel units |
| var s = this.node.style, l, t, h = this.host; |
| switch(s.position){ |
| case "relative": |
| case "absolute": |
| // assume that left and top values are in pixels already |
| l = Math.round(parseFloat(s.left)); |
| t = Math.round(parseFloat(s.top)); |
| break; |
| default: |
| s.position = "absolute"; // enforcing the absolute mode |
| var m = dojo.marginBox(this.node); |
| // event.pageX/pageY (which we used to generate the initial |
| // margin box) includes padding and margin set on the body. |
| // However, setting the node's position to absolute and then |
| // doing dojo.marginBox on it *doesn't* take that additional |
| // space into account - so we need to subtract the combined |
| // padding and margin. We use getComputedStyle and |
| // _getMarginBox/_getContentBox to avoid the extra lookup of |
| // the computed style. |
| var b = dojo.doc.body; |
| var bs = dojo.getComputedStyle(b); |
| var bm = dojo._getMarginBox(b, bs); |
| var bc = dojo._getContentBox(b, bs); |
| l = m.l - (bc.l - bm.l); |
| t = m.t - (bc.t - bm.t); |
| break; |
| } |
| this.marginBox.l = l - this.marginBox.l; |
| this.marginBox.t = t - this.marginBox.t; |
| if(h && h.onFirstMove){ |
| h.onFirstMove(this); |
| } |
| dojo.disconnect(this.events.pop()); |
| }, |
| destroy: function(){ |
| // summary: |
| // stops the move, deletes all references, so the object can be garbage-collected |
| dojo.forEach(this.events, dojo.disconnect); |
| // undo global settings |
| var h = this.host; |
| if(h && h.onMoveStop){ |
| h.onMoveStop(this); |
| } |
| // destroy objects |
| this.events = this.node = this.host = null; |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dojo.dnd.Moveable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.dnd.Moveable"] = true; |
| dojo.provide("dojo.dnd.Moveable"); |
| |
| |
| |
| /*===== |
| dojo.declare("dojo.dnd.__MoveableArgs", [], { |
| // handle: Node||String |
| // A node (or node's id), which is used as a mouse handle. |
| // If omitted, the node itself is used as a handle. |
| handle: null, |
| |
| // delay: Number |
| // delay move by this number of pixels |
| delay: 0, |
| |
| // skip: Boolean |
| // skip move of form elements |
| skip: false, |
| |
| // mover: Object |
| // a constructor of custom Mover |
| mover: dojo.dnd.Mover |
| }); |
| =====*/ |
| |
| dojo.declare("dojo.dnd.Moveable", null, { |
| // object attributes (for markup) |
| handle: "", |
| delay: 0, |
| skip: false, |
| |
| constructor: function(node, params){ |
| // summary: |
| // an object, which makes a node moveable |
| // node: Node |
| // a node (or node's id) to be moved |
| // params: dojo.dnd.__MoveableArgs? |
| // optional parameters |
| this.node = dojo.byId(node); |
| if(!params){ params = {}; } |
| this.handle = params.handle ? dojo.byId(params.handle) : null; |
| if(!this.handle){ this.handle = this.node; } |
| this.delay = params.delay > 0 ? params.delay : 0; |
| this.skip = params.skip; |
| this.mover = params.mover ? params.mover : dojo.dnd.Mover; |
| this.events = [ |
| dojo.connect(this.handle, "onmousedown", this, "onMouseDown"), |
| // cancel text selection and text dragging |
| dojo.connect(this.handle, "ondragstart", this, "onSelectStart"), |
| dojo.connect(this.handle, "onselectstart", this, "onSelectStart") |
| ]; |
| }, |
| |
| // markup methods |
| markupFactory: function(params, node){ |
| return new dojo.dnd.Moveable(node, params); |
| }, |
| |
| // methods |
| destroy: function(){ |
| // summary: |
| // stops watching for possible move, deletes all references, so the object can be garbage-collected |
| dojo.forEach(this.events, dojo.disconnect); |
| this.events = this.node = this.handle = null; |
| }, |
| |
| // mouse event processors |
| onMouseDown: function(e){ |
| // summary: |
| // event processor for onmousedown, creates a Mover for the node |
| // e: Event |
| // mouse event |
| if(this.skip && dojo.dnd.isFormElement(e)){ return; } |
| if(this.delay){ |
| this.events.push( |
| dojo.connect(this.handle, "onmousemove", this, "onMouseMove"), |
| dojo.connect(this.handle, "onmouseup", this, "onMouseUp") |
| ); |
| this._lastX = e.pageX; |
| this._lastY = e.pageY; |
| }else{ |
| this.onDragDetected(e); |
| } |
| dojo.stopEvent(e); |
| }, |
| onMouseMove: function(e){ |
| // summary: |
| // event processor for onmousemove, used only for delayed drags |
| // e: Event |
| // mouse event |
| if(Math.abs(e.pageX - this._lastX) > this.delay || Math.abs(e.pageY - this._lastY) > this.delay){ |
| this.onMouseUp(e); |
| this.onDragDetected(e); |
| } |
| dojo.stopEvent(e); |
| }, |
| onMouseUp: function(e){ |
| // summary: |
| // event processor for onmouseup, used only for delayed drags |
| // e: Event |
| // mouse event |
| for(var i = 0; i < 2; ++i){ |
| dojo.disconnect(this.events.pop()); |
| } |
| dojo.stopEvent(e); |
| }, |
| onSelectStart: function(e){ |
| // summary: |
| // event processor for onselectevent and ondragevent |
| // e: Event |
| // mouse event |
| if(!this.skip || !dojo.dnd.isFormElement(e)){ |
| dojo.stopEvent(e); |
| } |
| }, |
| |
| // local events |
| onDragDetected: function(/* Event */ e){ |
| // summary: |
| // called when the drag is detected; |
| // responsible for creation of the mover |
| new this.mover(this.node, e, this); |
| }, |
| onMoveStart: function(/* dojo.dnd.Mover */ mover){ |
| // summary: |
| // called before every move operation |
| dojo.publish("/dnd/move/start", [mover]); |
| dojo.addClass(dojo.body(), "dojoMove"); |
| dojo.addClass(this.node, "dojoMoveItem"); |
| }, |
| onMoveStop: function(/* dojo.dnd.Mover */ mover){ |
| // summary: |
| // called after every move operation |
| dojo.publish("/dnd/move/stop", [mover]); |
| dojo.removeClass(dojo.body(), "dojoMove"); |
| dojo.removeClass(this.node, "dojoMoveItem"); |
| }, |
| onFirstMove: function(/* dojo.dnd.Mover */ mover){ |
| // summary: |
| // called during the very first move notification; |
| // can be used to initialize coordinates, can be overwritten. |
| |
| // default implementation does nothing |
| }, |
| onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ |
| // summary: |
| // called during every move notification; |
| // should actually move the node; can be overwritten. |
| this.onMoving(mover, leftTop); |
| var s = mover.node.style; |
| s.left = leftTop.l + "px"; |
| s.top = leftTop.t + "px"; |
| this.onMoved(mover, leftTop); |
| }, |
| onMoving: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ |
| // summary: |
| // called before every incremental move; can be overwritten. |
| |
| // default implementation does nothing |
| }, |
| onMoved: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ |
| // summary: |
| // called after every incremental move; can be overwritten. |
| |
| // default implementation does nothing |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dojo.dnd.move"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.dnd.move"] = true; |
| dojo.provide("dojo.dnd.move"); |
| |
| |
| |
| |
| /*===== |
| dojo.declare("dojo.dnd.move.__constrainedMoveableArgs", [dojo.dnd.__MoveableArgs], { |
| // constraints: Function |
| // Calculates a constraint box. |
| // It is called in a context of the moveable object. |
| constraints: function(){}, |
| |
| // within: Boolean |
| // restrict move within boundaries. |
| within: false |
| }); |
| =====*/ |
| |
| dojo.declare("dojo.dnd.move.constrainedMoveable", dojo.dnd.Moveable, { |
| // object attributes (for markup) |
| constraints: function(){}, |
| within: false, |
| |
| // markup methods |
| markupFactory: function(params, node){ |
| return new dojo.dnd.move.constrainedMoveable(node, params); |
| }, |
| |
| constructor: function(node, params){ |
| // summary: |
| // an object that makes a node moveable |
| // node: Node |
| // a node (or node's id) to be moved |
| // params: dojo.dnd.move.__constrainedMoveableArgs? |
| // an optional object with additional parameters; |
| // the rest is passed to the base class |
| if(!params){ params = {}; } |
| this.constraints = params.constraints; |
| this.within = params.within; |
| }, |
| onFirstMove: function(/* dojo.dnd.Mover */ mover){ |
| // summary: |
| // called during the very first move notification; |
| // can be used to initialize coordinates, can be overwritten. |
| var c = this.constraintBox = this.constraints.call(this, mover); |
| c.r = c.l + c.w; |
| c.b = c.t + c.h; |
| if(this.within){ |
| var mb = dojo.marginBox(mover.node); |
| c.r -= mb.w; |
| c.b -= mb.h; |
| } |
| }, |
| onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ |
| // summary: |
| // called during every move notification; |
| // should actually move the node; can be overwritten. |
| var c = this.constraintBox, s = mover.node.style; |
| s.left = (leftTop.l < c.l ? c.l : c.r < leftTop.l ? c.r : leftTop.l) + "px"; |
| s.top = (leftTop.t < c.t ? c.t : c.b < leftTop.t ? c.b : leftTop.t) + "px"; |
| } |
| }); |
| |
| /*===== |
| dojo.declare("dojo.dnd.move.__boxConstrainedMoveableArgs", [dojo.dnd.move.__constrainedMoveableArgs], { |
| // box: Object |
| // a constraint box |
| box: {} |
| }); |
| =====*/ |
| |
| dojo.declare("dojo.dnd.move.boxConstrainedMoveable", dojo.dnd.move.constrainedMoveable, { |
| // box: |
| // object attributes (for markup) |
| box: {}, |
| |
| // markup methods |
| markupFactory: function(params, node){ |
| return new dojo.dnd.move.boxConstrainedMoveable(node, params); |
| }, |
| |
| constructor: function(node, params){ |
| // summary: |
| // an object, which makes a node moveable |
| // node: Node |
| // a node (or node's id) to be moved |
| // params: dojo.dnd.move.__boxConstrainedMoveableArgs? |
| // an optional object with parameters |
| var box = params && params.box; |
| this.constraints = function(){ return box; }; |
| } |
| }); |
| |
| /*===== |
| dojo.declare("dojo.dnd.move.__parentConstrainedMoveableArgs", [dojo.dnd.move.__constrainedMoveableArgs], { |
| // area: String |
| // A parent's area to restrict the move. |
| // Can be "margin", "border", "padding", or "content". |
| area: "" |
| }); |
| =====*/ |
| |
| dojo.declare("dojo.dnd.move.parentConstrainedMoveable", dojo.dnd.move.constrainedMoveable, { |
| // area: |
| // object attributes (for markup) |
| area: "content", |
| |
| // markup methods |
| markupFactory: function(params, node){ |
| return new dojo.dnd.move.parentConstrainedMoveable(node, params); |
| }, |
| |
| constructor: function(node, params){ |
| // summary: |
| // an object, which makes a node moveable |
| // node: Node |
| // a node (or node's id) to be moved |
| // params: dojo.dnd.move.__parentConstrainedMoveableArgs? |
| // an optional object with parameters |
| var area = params && params.area; |
| this.constraints = function(){ |
| var n = this.node.parentNode, |
| s = dojo.getComputedStyle(n), |
| mb = dojo._getMarginBox(n, s); |
| if(area == "margin"){ |
| return mb; // Object |
| } |
| var t = dojo._getMarginExtents(n, s); |
| mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; |
| if(area == "border"){ |
| return mb; // Object |
| } |
| t = dojo._getBorderExtents(n, s); |
| mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; |
| if(area == "padding"){ |
| return mb; // Object |
| } |
| t = dojo._getPadExtents(n, s); |
| mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; |
| return mb; // Object |
| }; |
| } |
| }); |
| |
| // WARNING: below are obsolete objects, instead of custom movers use custom moveables (above) |
| |
| dojo.dnd.move.constrainedMover = function(fun, within){ |
| // summary: |
| // returns a constrained version of dojo.dnd.Mover |
| // description: |
| // this function produces n object, which will put a constraint on |
| // the margin box of dragged object in absolute coordinates |
| // fun: Function |
| // called on drag, and returns a constraint box |
| // within: Boolean |
| // if true, constraints the whole dragged object withtin the rectangle, |
| // otherwise the constraint is applied to the left-top corner |
| |
| dojo.deprecated("dojo.dnd.move.constrainedMover, use dojo.dnd.move.constrainedMoveable instead"); |
| var mover = function(node, e, notifier){ |
| dojo.dnd.Mover.call(this, node, e, notifier); |
| }; |
| dojo.extend(mover, dojo.dnd.Mover.prototype); |
| dojo.extend(mover, { |
| onMouseMove: function(e){ |
| // summary: event processor for onmousemove |
| // e: Event: mouse event |
| dojo.dnd.autoScroll(e); |
| var m = this.marginBox, c = this.constraintBox, |
| l = m.l + e.pageX, t = m.t + e.pageY; |
| l = l < c.l ? c.l : c.r < l ? c.r : l; |
| t = t < c.t ? c.t : c.b < t ? c.b : t; |
| this.host.onMove(this, {l: l, t: t}); |
| }, |
| onFirstMove: function(){ |
| // summary: called once to initialize things; it is meant to be called only once |
| dojo.dnd.Mover.prototype.onFirstMove.call(this); |
| var c = this.constraintBox = fun.call(this); |
| c.r = c.l + c.w; |
| c.b = c.t + c.h; |
| if(within){ |
| var mb = dojo.marginBox(this.node); |
| c.r -= mb.w; |
| c.b -= mb.h; |
| } |
| } |
| }); |
| return mover; // Object |
| }; |
| |
| dojo.dnd.move.boxConstrainedMover = function(box, within){ |
| // summary: |
| // a specialization of dojo.dnd.constrainedMover, which constrains to the specified box |
| // box: Object |
| // a constraint box (l, t, w, h) |
| // within: Boolean |
| // if true, constraints the whole dragged object withtin the rectangle, |
| // otherwise the constraint is applied to the left-top corner |
| |
| dojo.deprecated("dojo.dnd.move.boxConstrainedMover, use dojo.dnd.move.boxConstrainedMoveable instead"); |
| return dojo.dnd.move.constrainedMover(function(){ return box; }, within); // Object |
| }; |
| |
| dojo.dnd.move.parentConstrainedMover = function(area, within){ |
| // summary: |
| // a specialization of dojo.dnd.constrainedMover, which constrains to the parent node |
| // area: String |
| // "margin" to constrain within the parent's margin box, "border" for the border box, |
| // "padding" for the padding box, and "content" for the content box; "content" is the default value. |
| // within: Boolean |
| // if true, constraints the whole dragged object within the rectangle, |
| // otherwise the constraint is applied to the left-top corner |
| |
| dojo.deprecated("dojo.dnd.move.parentConstrainedMover, use dojo.dnd.move.parentConstrainedMoveable instead"); |
| var fun = function(){ |
| var n = this.node.parentNode, |
| s = dojo.getComputedStyle(n), |
| mb = dojo._getMarginBox(n, s); |
| if(area == "margin"){ |
| return mb; // Object |
| } |
| var t = dojo._getMarginExtents(n, s); |
| mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; |
| if(area == "border"){ |
| return mb; // Object |
| } |
| t = dojo._getBorderExtents(n, s); |
| mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; |
| if(area == "padding"){ |
| return mb; // Object |
| } |
| t = dojo._getPadExtents(n, s); |
| mb.l += t.l, mb.t += t.t, mb.w -= t.w, mb.h -= t.h; |
| return mb; // Object |
| }; |
| return dojo.dnd.move.constrainedMover(fun, within); // Object |
| }; |
| |
| // patching functions one level up for compatibility |
| |
| dojo.dnd.constrainedMover = dojo.dnd.move.constrainedMover; |
| dojo.dnd.boxConstrainedMover = dojo.dnd.move.boxConstrainedMover; |
| dojo.dnd.parentConstrainedMover = dojo.dnd.move.parentConstrainedMover; |
| |
| } |
| |
| if(!dojo._hasResource["dojo.dnd.TimedMoveable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.dnd.TimedMoveable"] = true; |
| dojo.provide("dojo.dnd.TimedMoveable"); |
| |
| |
| |
| /*===== |
| dojo.declare("dojo.dnd.__TimedMoveableArgs", [dojo.dnd.__MoveableArgs], { |
| // timeout: Number |
| // delay move by this number of ms, |
| // accumulating position changes during the timeout |
| timeout: 0 |
| }); |
| =====*/ |
| |
| (function(){ |
| // precalculate long expressions |
| var oldOnMove = dojo.dnd.Moveable.prototype.onMove; |
| |
| dojo.declare("dojo.dnd.TimedMoveable", dojo.dnd.Moveable, { |
| // summary: |
| // A specialized version of Moveable to support an FPS throttling. |
| // This class puts an upper restriction on FPS, which may reduce |
| // the CPU load. The additional parameter "timeout" regulates |
| // the delay before actually moving the moveable object. |
| |
| // object attributes (for markup) |
| timeout: 40, // in ms, 40ms corresponds to 25 fps |
| |
| constructor: function(node, params){ |
| // summary: |
| // an object that makes a node moveable with a timer |
| // node: Node||String |
| // a node (or node's id) to be moved |
| // params: dojo.dnd.__TimedMoveableArgs |
| // object with additional parameters. |
| |
| // sanitize parameters |
| if(!params){ params = {}; } |
| if(params.timeout && typeof params.timeout == "number" && params.timeout >= 0){ |
| this.timeout = params.timeout; |
| } |
| }, |
| |
| // markup methods |
| markupFactory: function(params, node){ |
| return new dojo.dnd.TimedMoveable(node, params); |
| }, |
| |
| onMoveStop: function(/* dojo.dnd.Mover */ mover){ |
| if(mover._timer){ |
| // stop timer |
| clearTimeout(mover._timer) |
| // reflect the last received position |
| oldOnMove.call(this, mover, mover._leftTop) |
| } |
| dojo.dnd.Moveable.prototype.onMoveStop.apply(this, arguments); |
| }, |
| onMove: function(/* dojo.dnd.Mover */ mover, /* Object */ leftTop){ |
| mover._leftTop = leftTop; |
| if(!mover._timer){ |
| var _t = this; // to avoid using dojo.hitch() |
| mover._timer = setTimeout(function(){ |
| // we don't have any pending requests |
| mover._timer = null; |
| // reflect the last received position |
| oldOnMove.call(_t, mover, mover._leftTop); |
| }, this.timeout); |
| } |
| } |
| }); |
| })(); |
| |
| } |
| |
| if(!dojo._hasResource["dojo.fx.Toggler"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.fx.Toggler"] = true; |
| dojo.provide("dojo.fx.Toggler"); |
| |
| dojo.declare("dojo.fx.Toggler", null, { |
| // summary: |
| // A simple `dojo.Animation` toggler API. |
| // |
| // description: |
| // class constructor for an animation toggler. It accepts a packed |
| // set of arguments about what type of animation to use in each |
| // direction, duration, etc. All available members are mixed into |
| // these animations from the constructor (for example, `node`, |
| // `showDuration`, `hideDuration`). |
| // |
| // example: |
| // | var t = new dojo.fx.Toggler({ |
| // | node: "nodeId", |
| // | showDuration: 500, |
| // | // hideDuration will default to "200" |
| // | showFunc: dojo.fx.wipeIn, |
| // | // hideFunc will default to "fadeOut" |
| // | }); |
| // | t.show(100); // delay showing for 100ms |
| // | // ...time passes... |
| // | t.hide(); |
| |
| // node: DomNode |
| // the node to target for the showing and hiding animations |
| node: null, |
| |
| // showFunc: Function |
| // The function that returns the `dojo.Animation` to show the node |
| showFunc: dojo.fadeIn, |
| |
| // hideFunc: Function |
| // The function that returns the `dojo.Animation` to hide the node |
| hideFunc: dojo.fadeOut, |
| |
| // showDuration: |
| // Time in milliseconds to run the show Animation |
| showDuration: 200, |
| |
| // hideDuration: |
| // Time in milliseconds to run the hide Animation |
| hideDuration: 200, |
| |
| // FIXME: need a policy for where the toggler should "be" the next |
| // time show/hide are called if we're stopped somewhere in the |
| // middle. |
| // FIXME: also would be nice to specify individual showArgs/hideArgs mixed into |
| // each animation individually. |
| // FIXME: also would be nice to have events from the animations exposed/bridged |
| |
| /*===== |
| _showArgs: null, |
| _showAnim: null, |
| |
| _hideArgs: null, |
| _hideAnim: null, |
| |
| _isShowing: false, |
| _isHiding: false, |
| =====*/ |
| |
| constructor: function(args){ |
| var _t = this; |
| |
| dojo.mixin(_t, args); |
| _t.node = args.node; |
| _t._showArgs = dojo.mixin({}, args); |
| _t._showArgs.node = _t.node; |
| _t._showArgs.duration = _t.showDuration; |
| _t.showAnim = _t.showFunc(_t._showArgs); |
| |
| _t._hideArgs = dojo.mixin({}, args); |
| _t._hideArgs.node = _t.node; |
| _t._hideArgs.duration = _t.hideDuration; |
| _t.hideAnim = _t.hideFunc(_t._hideArgs); |
| |
| dojo.connect(_t.showAnim, "beforeBegin", dojo.hitch(_t.hideAnim, "stop", true)); |
| dojo.connect(_t.hideAnim, "beforeBegin", dojo.hitch(_t.showAnim, "stop", true)); |
| }, |
| |
| show: function(delay){ |
| // summary: Toggle the node to showing |
| // delay: Integer? |
| // Ammount of time to stall playing the show animation |
| return this.showAnim.play(delay || 0); |
| }, |
| |
| hide: function(delay){ |
| // summary: Toggle the node to hidden |
| // delay: Integer? |
| // Ammount of time to stall playing the hide animation |
| return this.hideAnim.play(delay || 0); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dojo.fx"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.fx"] = true; |
| dojo.provide("dojo.fx"); |
| // FIXME: remove this back-compat require in 2.0 |
| /*===== |
| dojo.fx = { |
| // summary: Effects library on top of Base animations |
| }; |
| =====*/ |
| (function(){ |
| |
| var d = dojo, |
| _baseObj = { |
| _fire: function(evt, args){ |
| if(this[evt]){ |
| this[evt].apply(this, args||[]); |
| } |
| return this; |
| } |
| }; |
| |
| var _chain = function(animations){ |
| this._index = -1; |
| this._animations = animations||[]; |
| this._current = this._onAnimateCtx = this._onEndCtx = null; |
| |
| this.duration = 0; |
| d.forEach(this._animations, function(a){ |
| this.duration += a.duration; |
| if(a.delay){ this.duration += a.delay; } |
| }, this); |
| }; |
| d.extend(_chain, { |
| _onAnimate: function(){ |
| this._fire("onAnimate", arguments); |
| }, |
| _onEnd: function(){ |
| d.disconnect(this._onAnimateCtx); |
| d.disconnect(this._onEndCtx); |
| this._onAnimateCtx = this._onEndCtx = null; |
| if(this._index + 1 == this._animations.length){ |
| this._fire("onEnd"); |
| }else{ |
| // switch animations |
| this._current = this._animations[++this._index]; |
| this._onAnimateCtx = d.connect(this._current, "onAnimate", this, "_onAnimate"); |
| this._onEndCtx = d.connect(this._current, "onEnd", this, "_onEnd"); |
| this._current.play(0, true); |
| } |
| }, |
| play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){ |
| if(!this._current){ this._current = this._animations[this._index = 0]; } |
| if(!gotoStart && this._current.status() == "playing"){ return this; } |
| var beforeBegin = d.connect(this._current, "beforeBegin", this, function(){ |
| this._fire("beforeBegin"); |
| }), |
| onBegin = d.connect(this._current, "onBegin", this, function(arg){ |
| this._fire("onBegin", arguments); |
| }), |
| onPlay = d.connect(this._current, "onPlay", this, function(arg){ |
| this._fire("onPlay", arguments); |
| d.disconnect(beforeBegin); |
| d.disconnect(onBegin); |
| d.disconnect(onPlay); |
| }); |
| if(this._onAnimateCtx){ |
| d.disconnect(this._onAnimateCtx); |
| } |
| this._onAnimateCtx = d.connect(this._current, "onAnimate", this, "_onAnimate"); |
| if(this._onEndCtx){ |
| d.disconnect(this._onEndCtx); |
| } |
| this._onEndCtx = d.connect(this._current, "onEnd", this, "_onEnd"); |
| this._current.play.apply(this._current, arguments); |
| return this; |
| }, |
| pause: function(){ |
| if(this._current){ |
| var e = d.connect(this._current, "onPause", this, function(arg){ |
| this._fire("onPause", arguments); |
| d.disconnect(e); |
| }); |
| this._current.pause(); |
| } |
| return this; |
| }, |
| gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){ |
| this.pause(); |
| var offset = this.duration * percent; |
| this._current = null; |
| d.some(this._animations, function(a){ |
| if(a.duration <= offset){ |
| this._current = a; |
| return true; |
| } |
| offset -= a.duration; |
| return false; |
| }); |
| if(this._current){ |
| this._current.gotoPercent(offset / this._current.duration, andPlay); |
| } |
| return this; |
| }, |
| stop: function(/*boolean?*/ gotoEnd){ |
| if(this._current){ |
| if(gotoEnd){ |
| for(; this._index + 1 < this._animations.length; ++this._index){ |
| this._animations[this._index].stop(true); |
| } |
| this._current = this._animations[this._index]; |
| } |
| var e = d.connect(this._current, "onStop", this, function(arg){ |
| this._fire("onStop", arguments); |
| d.disconnect(e); |
| }); |
| this._current.stop(); |
| } |
| return this; |
| }, |
| status: function(){ |
| return this._current ? this._current.status() : "stopped"; |
| }, |
| destroy: function(){ |
| if(this._onAnimateCtx){ d.disconnect(this._onAnimateCtx); } |
| if(this._onEndCtx){ d.disconnect(this._onEndCtx); } |
| } |
| }); |
| d.extend(_chain, _baseObj); |
| |
| dojo.fx.chain = function(/*dojo.Animation[]*/ animations){ |
| // summary: |
| // Chain a list of `dojo.Animation`s to run in sequence |
| // |
| // description: |
| // Return a `dojo.Animation` which will play all passed |
| // `dojo.Animation` instances in sequence, firing its own |
| // synthesized events simulating a single animation. (eg: |
| // onEnd of this animation means the end of the chain, |
| // not the individual animations within) |
| // |
| // example: |
| // Once `node` is faded out, fade in `otherNode` |
| // | dojo.fx.chain([ |
| // | dojo.fadeIn({ node:node }), |
| // | dojo.fadeOut({ node:otherNode }) |
| // | ]).play(); |
| // |
| return new _chain(animations) // dojo.Animation |
| }; |
| |
| var _combine = function(animations){ |
| this._animations = animations||[]; |
| this._connects = []; |
| this._finished = 0; |
| |
| this.duration = 0; |
| d.forEach(animations, function(a){ |
| var duration = a.duration; |
| if(a.delay){ duration += a.delay; } |
| if(this.duration < duration){ this.duration = duration; } |
| this._connects.push(d.connect(a, "onEnd", this, "_onEnd")); |
| }, this); |
| |
| this._pseudoAnimation = new d.Animation({curve: [0, 1], duration: this.duration}); |
| var self = this; |
| d.forEach(["beforeBegin", "onBegin", "onPlay", "onAnimate", "onPause", "onStop", "onEnd"], |
| function(evt){ |
| self._connects.push(d.connect(self._pseudoAnimation, evt, |
| function(){ self._fire(evt, arguments); } |
| )); |
| } |
| ); |
| }; |
| d.extend(_combine, { |
| _doAction: function(action, args){ |
| d.forEach(this._animations, function(a){ |
| a[action].apply(a, args); |
| }); |
| return this; |
| }, |
| _onEnd: function(){ |
| if(++this._finished > this._animations.length){ |
| this._fire("onEnd"); |
| } |
| }, |
| _call: function(action, args){ |
| var t = this._pseudoAnimation; |
| t[action].apply(t, args); |
| }, |
| play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){ |
| this._finished = 0; |
| this._doAction("play", arguments); |
| this._call("play", arguments); |
| return this; |
| }, |
| pause: function(){ |
| this._doAction("pause", arguments); |
| this._call("pause", arguments); |
| return this; |
| }, |
| gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){ |
| var ms = this.duration * percent; |
| d.forEach(this._animations, function(a){ |
| a.gotoPercent(a.duration < ms ? 1 : (ms / a.duration), andPlay); |
| }); |
| this._call("gotoPercent", arguments); |
| return this; |
| }, |
| stop: function(/*boolean?*/ gotoEnd){ |
| this._doAction("stop", arguments); |
| this._call("stop", arguments); |
| return this; |
| }, |
| status: function(){ |
| return this._pseudoAnimation.status(); |
| }, |
| destroy: function(){ |
| d.forEach(this._connects, dojo.disconnect); |
| } |
| }); |
| d.extend(_combine, _baseObj); |
| |
| dojo.fx.combine = function(/*dojo.Animation[]*/ animations){ |
| // summary: |
| // Combine a list of `dojo.Animation`s to run in parallel |
| // |
| // description: |
| // Combine an array of `dojo.Animation`s to run in parallel, |
| // providing a new `dojo.Animation` instance encompasing each |
| // animation, firing standard animation events. |
| // |
| // example: |
| // Fade out `node` while fading in `otherNode` simultaneously |
| // | dojo.fx.combine([ |
| // | dojo.fadeIn({ node:node }), |
| // | dojo.fadeOut({ node:otherNode }) |
| // | ]).play(); |
| // |
| // example: |
| // When the longest animation ends, execute a function: |
| // | var anim = dojo.fx.combine([ |
| // | dojo.fadeIn({ node: n, duration:700 }), |
| // | dojo.fadeOut({ node: otherNode, duration: 300 }) |
| // | ]); |
| // | dojo.connect(anim, "onEnd", function(){ |
| // | // overall animation is done. |
| // | }); |
| // | anim.play(); // play the animation |
| // |
| return new _combine(animations); // dojo.Animation |
| }; |
| |
| dojo.fx.wipeIn = function(/*Object*/ args){ |
| // summary: |
| // Expand a node to it's natural height. |
| // |
| // description: |
| // Returns an animation that will expand the |
| // node defined in 'args' object from it's current height to |
| // it's natural height (with no scrollbar). |
| // Node must have no margin/border/padding. |
| // |
| // args: Object |
| // A hash-map of standard `dojo.Animation` constructor properties |
| // (such as easing: node: duration: and so on) |
| // |
| // example: |
| // | dojo.fx.wipeIn({ |
| // | node:"someId" |
| // | }).play() |
| var node = args.node = d.byId(args.node), s = node.style, o; |
| |
| var anim = d.animateProperty(d.mixin({ |
| properties: { |
| height: { |
| // wrapped in functions so we wait till the last second to query (in case value has changed) |
| start: function(){ |
| // start at current [computed] height, but use 1px rather than 0 |
| // because 0 causes IE to display the whole panel |
| o = s.overflow; |
| s.overflow = "hidden"; |
| if(s.visibility == "hidden" || s.display == "none"){ |
| s.height = "1px"; |
| s.display = ""; |
| s.visibility = ""; |
| return 1; |
| }else{ |
| var height = d.style(node, "height"); |
| return Math.max(height, 1); |
| } |
| }, |
| end: function(){ |
| return node.scrollHeight; |
| } |
| } |
| } |
| }, args)); |
| |
| d.connect(anim, "onEnd", function(){ |
| s.height = "auto"; |
| s.overflow = o; |
| }); |
| |
| return anim; // dojo.Animation |
| } |
| |
| dojo.fx.wipeOut = function(/*Object*/ args){ |
| // summary: |
| // Shrink a node to nothing and hide it. |
| // |
| // description: |
| // Returns an animation that will shrink node defined in "args" |
| // from it's current height to 1px, and then hide it. |
| // |
| // args: Object |
| // A hash-map of standard `dojo.Animation` constructor properties |
| // (such as easing: node: duration: and so on) |
| // |
| // example: |
| // | dojo.fx.wipeOut({ node:"someId" }).play() |
| |
| var node = args.node = d.byId(args.node), s = node.style, o; |
| |
| var anim = d.animateProperty(d.mixin({ |
| properties: { |
| height: { |
| end: 1 // 0 causes IE to display the whole panel |
| } |
| } |
| }, args)); |
| |
| d.connect(anim, "beforeBegin", function(){ |
| o = s.overflow; |
| s.overflow = "hidden"; |
| s.display = ""; |
| }); |
| d.connect(anim, "onEnd", function(){ |
| s.overflow = o; |
| s.height = "auto"; |
| s.display = "none"; |
| }); |
| |
| return anim; // dojo.Animation |
| } |
| |
| dojo.fx.slideTo = function(/*Object*/ args){ |
| // summary: |
| // Slide a node to a new top/left position |
| // |
| // description: |
| // Returns an animation that will slide "node" |
| // defined in args Object from its current position to |
| // the position defined by (args.left, args.top). |
| // |
| // args: Object |
| // A hash-map of standard `dojo.Animation` constructor properties |
| // (such as easing: node: duration: and so on). Special args members |
| // are `top` and `left`, which indicate the new position to slide to. |
| // |
| // example: |
| // | dojo.fx.slideTo({ node: node, left:"40", top:"50", units:"px" }).play() |
| |
| var node = args.node = d.byId(args.node), |
| top = null, left = null; |
| |
| var init = (function(n){ |
| return function(){ |
| var cs = d.getComputedStyle(n); |
| var pos = cs.position; |
| top = (pos == 'absolute' ? n.offsetTop : parseInt(cs.top) || 0); |
| left = (pos == 'absolute' ? n.offsetLeft : parseInt(cs.left) || 0); |
| if(pos != 'absolute' && pos != 'relative'){ |
| var ret = d.position(n, true); |
| top = ret.y; |
| left = ret.x; |
| n.style.position="absolute"; |
| n.style.top=top+"px"; |
| n.style.left=left+"px"; |
| } |
| }; |
| })(node); |
| init(); |
| |
| var anim = d.animateProperty(d.mixin({ |
| properties: { |
| top: args.top || 0, |
| left: args.left || 0 |
| } |
| }, args)); |
| d.connect(anim, "beforeBegin", anim, init); |
| |
| return anim; // dojo.Animation |
| } |
| |
| })(); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form._FormMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form._FormMixin"] = true; |
| dojo.provide("dijit.form._FormMixin"); |
| |
| dojo.declare("dijit.form._FormMixin", null, |
| { |
| // summary: |
| // Mixin for containers of form widgets (i.e. widgets that represent a single value |
| // and can be children of a <form> node or dijit.form.Form widget) |
| // description: |
| // Can extract all the form widgets |
| // values and combine them into a single javascript object, or alternately |
| // take such an object and set the values for all the contained |
| // form widgets |
| |
| /*===== |
| // value: Object |
| // Name/value hash for each form element. |
| // If there are multiple elements w/the same name, value is an array, |
| // unless they are radio buttons in which case value is a scalar since only |
| // one can be checked at a time. |
| // |
| // If the name is a dot separated list (like a.b.c.d), it's a nested structure. |
| // Only works on widget form elements. |
| // example: |
| // | { name: "John Smith", interests: ["sports", "movies"] } |
| =====*/ |
| |
| // TODO: |
| // * Repeater |
| // * better handling for arrays. Often form elements have names with [] like |
| // * people[3].sex (for a list of people [{name: Bill, sex: M}, ...]) |
| // |
| // |
| |
| reset: function(){ |
| dojo.forEach(this.getDescendants(), function(widget){ |
| if(widget.reset){ |
| widget.reset(); |
| } |
| }); |
| }, |
| |
| validate: function(){ |
| // summary: |
| // returns if the form is valid - same as isValid - but |
| // provides a few additional (ui-specific) features. |
| // 1 - it will highlight any sub-widgets that are not |
| // valid |
| // 2 - it will call focus() on the first invalid |
| // sub-widget |
| var didFocus = false; |
| return dojo.every(dojo.map(this.getDescendants(), function(widget){ |
| // Need to set this so that "required" widgets get their |
| // state set. |
| widget._hasBeenBlurred = true; |
| var valid = widget.disabled || !widget.validate || widget.validate(); |
| if(!valid && !didFocus){ |
| // Set focus of the first non-valid widget |
| dijit.scrollIntoView(widget.containerNode || widget.domNode); |
| widget.focus(); |
| didFocus = true; |
| } |
| return valid; |
| }), function(item){ return item; }); |
| }, |
| |
| setValues: function(val){ |
| dojo.deprecated(this.declaredClass+"::setValues() is deprecated. Use attr('value', val) instead.", "", "2.0"); |
| return this.attr('value', val); |
| }, |
| _setValueAttr: function(/*object*/obj){ |
| // summary: |
| // Fill in form values from according to an Object (in the format returned by attr('value')) |
| |
| // generate map from name --> [list of widgets with that name] |
| var map = { }; |
| dojo.forEach(this.getDescendants(), function(widget){ |
| if(!widget.name){ return; } |
| var entry = map[widget.name] || (map[widget.name] = [] ); |
| entry.push(widget); |
| }); |
| |
| for(var name in map){ |
| if(!map.hasOwnProperty(name)){ |
| continue; |
| } |
| var widgets = map[name], // array of widgets w/this name |
| values = dojo.getObject(name, false, obj); // list of values for those widgets |
| |
| if(values === undefined){ |
| continue; |
| } |
| if(!dojo.isArray(values)){ |
| values = [ values ]; |
| } |
| if(typeof widgets[0].checked == 'boolean'){ |
| // for checkbox/radio, values is a list of which widgets should be checked |
| dojo.forEach(widgets, function(w, i){ |
| w.attr('value', dojo.indexOf(values, w.value) != -1); |
| }); |
| }else if(widgets[0].multiple){ |
| // it takes an array (e.g. multi-select) |
| widgets[0].attr('value', values); |
| }else{ |
| // otherwise, values is a list of values to be assigned sequentially to each widget |
| dojo.forEach(widgets, function(w, i){ |
| w.attr('value', values[i]); |
| }); |
| } |
| } |
| |
| /*** |
| * TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets) |
| |
| dojo.forEach(this.containerNode.elements, function(element){ |
| if(element.name == ''){return}; // like "continue" |
| var namePath = element.name.split("."); |
| var myObj=obj; |
| var name=namePath[namePath.length-1]; |
| for(var j=1,len2=namePath.length;j<len2;++j){ |
| var p=namePath[j - 1]; |
| // repeater support block |
| var nameA=p.split("["); |
| if(nameA.length > 1){ |
| if(typeof(myObj[nameA[0]]) == "undefined"){ |
| myObj[nameA[0]]=[ ]; |
| } // if |
| |
| nameIndex=parseInt(nameA[1]); |
| if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){ |
| myObj[nameA[0]][nameIndex] = { }; |
| } |
| myObj=myObj[nameA[0]][nameIndex]; |
| continue; |
| } // repeater support ends |
| |
| if(typeof(myObj[p]) == "undefined"){ |
| myObj=undefined; |
| break; |
| }; |
| myObj=myObj[p]; |
| } |
| |
| if(typeof(myObj) == "undefined"){ |
| return; // like "continue" |
| } |
| if(typeof(myObj[name]) == "undefined" && this.ignoreNullValues){ |
| return; // like "continue" |
| } |
| |
| // TODO: widget values (just call attr('value', ...) on the widget) |
| |
| // TODO: maybe should call dojo.getNodeProp() instead |
| switch(element.type){ |
| case "checkbox": |
| element.checked = (name in myObj) && |
| dojo.some(myObj[name], function(val){ return val == element.value; }); |
| break; |
| case "radio": |
| element.checked = (name in myObj) && myObj[name] == element.value; |
| break; |
| case "select-multiple": |
| element.selectedIndex=-1; |
| dojo.forEach(element.options, function(option){ |
| option.selected = dojo.some(myObj[name], function(val){ return option.value == val; }); |
| }); |
| break; |
| case "select-one": |
| element.selectedIndex="0"; |
| dojo.forEach(element.options, function(option){ |
| option.selected = option.value == myObj[name]; |
| }); |
| break; |
| case "hidden": |
| case "text": |
| case "textarea": |
| case "password": |
| element.value = myObj[name] || ""; |
| break; |
| } |
| }); |
| */ |
| }, |
| |
| getValues: function(){ |
| dojo.deprecated(this.declaredClass+"::getValues() is deprecated. Use attr('value') instead.", "", "2.0"); |
| return this.attr('value'); |
| }, |
| _getValueAttr: function(){ |
| // summary: |
| // Returns Object representing form values. |
| // description: |
| // Returns name/value hash for each form element. |
| // If there are multiple elements w/the same name, value is an array, |
| // unless they are radio buttons in which case value is a scalar since only |
| // one can be checked at a time. |
| // |
| // If the name is a dot separated list (like a.b.c.d), creates a nested structure. |
| // Only works on widget form elements. |
| // example: |
| // | { name: "John Smith", interests: ["sports", "movies"] } |
| |
| // get widget values |
| var obj = { }; |
| dojo.forEach(this.getDescendants(), function(widget){ |
| var name = widget.name; |
| if(!name || widget.disabled){ return; } |
| |
| // Single value widget (checkbox, radio, or plain <input> type widget |
| var value = widget.attr('value'); |
| |
| // Store widget's value(s) as a scalar, except for checkboxes which are automatically arrays |
| if(typeof widget.checked == 'boolean'){ |
| if(/Radio/.test(widget.declaredClass)){ |
| // radio button |
| if(value !== false){ |
| dojo.setObject(name, value, obj); |
| }else{ |
| // give radio widgets a default of null |
| value = dojo.getObject(name, false, obj); |
| if(value === undefined){ |
| dojo.setObject(name, null, obj); |
| } |
| } |
| }else{ |
| // checkbox/toggle button |
| var ary=dojo.getObject(name, false, obj); |
| if(!ary){ |
| ary=[]; |
| dojo.setObject(name, ary, obj); |
| } |
| if(value !== false){ |
| ary.push(value); |
| } |
| } |
| }else{ |
| var prev=dojo.getObject(name, false, obj); |
| if(typeof prev != "undefined"){ |
| if(dojo.isArray(prev)){ |
| prev.push(value); |
| }else{ |
| dojo.setObject(name, [prev, value], obj); |
| } |
| }else{ |
| // unique name |
| dojo.setObject(name, value, obj); |
| } |
| } |
| }); |
| |
| /*** |
| * code for plain input boxes (see also dojo.formToObject, can we use that instead of this code? |
| * but it doesn't understand [] notation, presumably) |
| var obj = { }; |
| dojo.forEach(this.containerNode.elements, function(elm){ |
| if(!elm.name) { |
| return; // like "continue" |
| } |
| var namePath = elm.name.split("."); |
| var myObj=obj; |
| var name=namePath[namePath.length-1]; |
| for(var j=1,len2=namePath.length;j<len2;++j){ |
| var nameIndex = null; |
| var p=namePath[j - 1]; |
| var nameA=p.split("["); |
| if(nameA.length > 1){ |
| if(typeof(myObj[nameA[0]]) == "undefined"){ |
| myObj[nameA[0]]=[ ]; |
| } // if |
| nameIndex=parseInt(nameA[1]); |
| if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){ |
| myObj[nameA[0]][nameIndex] = { }; |
| } |
| } else if(typeof(myObj[nameA[0]]) == "undefined"){ |
| myObj[nameA[0]] = { } |
| } // if |
| |
| if(nameA.length == 1){ |
| myObj=myObj[nameA[0]]; |
| } else{ |
| myObj=myObj[nameA[0]][nameIndex]; |
| } // if |
| } // for |
| |
| if((elm.type != "select-multiple" && elm.type != "checkbox" && elm.type != "radio") || (elm.type == "radio" && elm.checked)){ |
| if(name == name.split("[")[0]){ |
| myObj[name]=elm.value; |
| } else{ |
| // can not set value when there is no name |
| } |
| } else if(elm.type == "checkbox" && elm.checked){ |
| if(typeof(myObj[name]) == 'undefined'){ |
| myObj[name]=[ ]; |
| } |
| myObj[name].push(elm.value); |
| } else if(elm.type == "select-multiple"){ |
| if(typeof(myObj[name]) == 'undefined'){ |
| myObj[name]=[ ]; |
| } |
| for(var jdx=0,len3=elm.options.length; jdx<len3; ++jdx){ |
| if(elm.options[jdx].selected){ |
| myObj[name].push(elm.options[jdx].value); |
| } |
| } |
| } // if |
| name=undefined; |
| }); // forEach |
| ***/ |
| return obj; |
| }, |
| |
| // TODO: ComboBox might need time to process a recently input value. This should be async? |
| isValid: function(){ |
| // summary: |
| // Returns true if all of the widgets are valid |
| |
| // This also populate this._invalidWidgets[] array with list of invalid widgets... |
| // TODO: put that into separate function? It's confusing to have that as a side effect |
| // of a method named isValid(). |
| |
| this._invalidWidgets = dojo.filter(this.getDescendants(), function(widget){ |
| return !widget.disabled && widget.isValid && !widget.isValid(); |
| }); |
| return !this._invalidWidgets.length; |
| }, |
| |
| |
| onValidStateChange: function(isValid){ |
| // summary: |
| // Stub function to connect to if you want to do something |
| // (like disable/enable a submit button) when the valid |
| // state changes on the form as a whole. |
| }, |
| |
| _widgetChange: function(widget){ |
| // summary: |
| // Connected to a widget's onChange function - update our |
| // valid state, if needed. |
| var isValid = this._lastValidState; |
| if(!widget || this._lastValidState === undefined){ |
| // We have passed a null widget, or we haven't been validated |
| // yet - let's re-check all our children |
| // This happens when we connect (or reconnect) our children |
| isValid = this.isValid(); |
| if(this._lastValidState === undefined){ |
| // Set this so that we don't fire an onValidStateChange |
| // the first time |
| this._lastValidState = isValid; |
| } |
| }else if(widget.isValid){ |
| this._invalidWidgets = dojo.filter(this._invalidWidgets || [], function(w){ |
| return (w != widget); |
| }, this); |
| if(!widget.isValid() && !widget.attr("disabled")){ |
| this._invalidWidgets.push(widget); |
| } |
| isValid = (this._invalidWidgets.length === 0); |
| } |
| if(isValid !== this._lastValidState){ |
| this._lastValidState = isValid; |
| this.onValidStateChange(isValid); |
| } |
| }, |
| |
| connectChildren: function(){ |
| // summary: |
| // Connects to the onChange function of all children to |
| // track valid state changes. You can call this function |
| // directly, ex. in the event that you programmatically |
| // add a widget to the form *after* the form has been |
| // initialized. |
| dojo.forEach(this._changeConnections, dojo.hitch(this, "disconnect")); |
| var _this = this; |
| |
| // we connect to validate - so that it better reflects the states |
| // of the widgets - also, we only connect if it has a validate |
| // function (to avoid too many unneeded connections) |
| var conns = this._changeConnections = []; |
| dojo.forEach(dojo.filter(this.getDescendants(), |
| function(item){ return item.validate; } |
| ), |
| function(widget){ |
| // We are interested in whenever the widget is validated - or |
| // whenever the disabled attribute on that widget is changed |
| conns.push(_this.connect(widget, "validate", |
| dojo.hitch(_this, "_widgetChange", widget))); |
| conns.push(_this.connect(widget, "_setDisabledAttr", |
| dojo.hitch(_this, "_widgetChange", widget))); |
| }); |
| |
| // Call the widget change function to update the valid state, in |
| // case something is different now. |
| this._widgetChange(null); |
| }, |
| |
| startup: function(){ |
| this.inherited(arguments); |
| // Initialize our valid state tracking. Needs to be done in startup |
| // because it's not guaranteed that our children are initialized |
| // yet. |
| this._changeConnections = []; |
| this.connectChildren(); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit._DialogMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit._DialogMixin"] = true; |
| dojo.provide("dijit._DialogMixin"); |
| |
| dojo.declare("dijit._DialogMixin", null, |
| { |
| // summary: |
| // This provides functions useful to Dialog and TooltipDialog |
| |
| attributeMap: dijit._Widget.prototype.attributeMap, |
| |
| execute: function(/*Object*/ formContents){ |
| // summary: |
| // Callback when the user hits the submit button. |
| // Override this method to handle Dialog execution. |
| // description: |
| // After the user has pressed the submit button, the Dialog |
| // first calls onExecute() to notify the container to hide the |
| // dialog and restore focus to wherever it used to be. |
| // |
| // *Then* this method is called. |
| // type: |
| // callback |
| }, |
| |
| onCancel: function(){ |
| // summary: |
| // Called when user has pressed the Dialog's cancel button, to notify container. |
| // description: |
| // Developer shouldn't override or connect to this method; |
| // it's a private communication device between the TooltipDialog |
| // and the thing that opened it (ex: `dijit.form.DropDownButton`) |
| // type: |
| // protected |
| }, |
| |
| onExecute: function(){ |
| // summary: |
| // Called when user has pressed the dialog's OK button, to notify container. |
| // description: |
| // Developer shouldn't override or connect to this method; |
| // it's a private communication device between the TooltipDialog |
| // and the thing that opened it (ex: `dijit.form.DropDownButton`) |
| // type: |
| // protected |
| }, |
| |
| _onSubmit: function(){ |
| // summary: |
| // Callback when user hits submit button |
| // type: |
| // protected |
| this.onExecute(); // notify container that we are about to execute |
| this.execute(this.attr('value')); |
| }, |
| |
| _getFocusItems: function(/*Node*/ dialogNode){ |
| // summary: |
| // Find focusable Items each time a dialog is opened, |
| // setting _firstFocusItem and _lastFocusItem |
| // tags: |
| // protected |
| |
| var elems = dijit._getTabNavigable(dojo.byId(dialogNode)); |
| this._firstFocusItem = elems.lowest || elems.first || dialogNode; |
| this._lastFocusItem = elems.last || elems.highest || this._firstFocusItem; |
| if(dojo.isMoz && this._firstFocusItem.tagName.toLowerCase() == "input" && |
| dojo.getNodeProp(this._firstFocusItem, "type").toLowerCase() == "file"){ |
| // FF doesn't behave well when first element is input type=file, set first focusable to dialog container |
| dojo.attr(dialogNode, "tabIndex", "0"); |
| this._firstFocusItem = dialogNode; |
| } |
| } |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.DialogUnderlay"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.DialogUnderlay"] = true; |
| dojo.provide("dijit.DialogUnderlay"); |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.DialogUnderlay", |
| [dijit._Widget, dijit._Templated], |
| { |
| // summary: |
| // The component that blocks the screen behind a `dijit.Dialog` |
| // |
| // description: |
| // A component used to block input behind a `dijit.Dialog`. Only a single |
| // instance of this widget is created by `dijit.Dialog`, and saved as |
| // a reference to be shared between all Dialogs as `dijit._underlay` |
| // |
| // The underlay itself can be styled based on and id: |
| // | #myDialog_underlay { background-color:red; } |
| // |
| // In the case of `dijit.Dialog`, this id is based on the id of the Dialog, |
| // suffixed with _underlay. |
| |
| // Template has two divs; outer div is used for fade-in/fade-out, and also to hold background iframe. |
| // Inner div has opacity specified in CSS file. |
| templateString: "<div class='dijitDialogUnderlayWrapper'><div class='dijitDialogUnderlay' dojoAttachPoint='node'></div></div>", |
| |
| // Parameters on creation or updatable later |
| |
| // dialogId: String |
| // Id of the dialog.... DialogUnderlay's id is based on this id |
| dialogId: "", |
| |
| // class: String |
| // This class name is used on the DialogUnderlay node, in addition to dijitDialogUnderlay |
| "class": "", |
| |
| attributeMap: { id: "domNode" }, |
| |
| _setDialogIdAttr: function(id){ |
| dojo.attr(this.node, "id", id + "_underlay"); |
| }, |
| |
| _setClassAttr: function(clazz){ |
| this.node.className = "dijitDialogUnderlay " + clazz; |
| }, |
| |
| postCreate: function(){ |
| // summary: |
| // Append the underlay to the body |
| dojo.body().appendChild(this.domNode); |
| }, |
| |
| layout: function(){ |
| // summary: |
| // Sets the background to the size of the viewport |
| // |
| // description: |
| // Sets the background to the size of the viewport (rather than the size |
| // of the document) since we need to cover the whole browser window, even |
| // if the document is only a few lines long. |
| // tags: |
| // private |
| |
| var is = this.node.style, |
| os = this.domNode.style; |
| |
| // hide the background temporarily, so that the background itself isn't |
| // causing scrollbars to appear (might happen when user shrinks browser |
| // window and then we are called to resize) |
| os.display = "none"; |
| |
| // then resize and show |
| var viewport = dijit.getViewport(); |
| os.top = viewport.t + "px"; |
| os.left = viewport.l + "px"; |
| is.width = viewport.w + "px"; |
| is.height = viewport.h + "px"; |
| os.display = "block"; |
| }, |
| |
| show: function(){ |
| // summary: |
| // Show the dialog underlay |
| this.domNode.style.display = "block"; |
| this.layout(); |
| this.bgIframe = new dijit.BackgroundIframe(this.domNode); |
| }, |
| |
| hide: function(){ |
| // summary: |
| // Hides the dialog underlay |
| this.bgIframe.destroy(); |
| this.domNode.style.display = "none"; |
| }, |
| |
| uninitialize: function(){ |
| if(this.bgIframe){ |
| this.bgIframe.destroy(); |
| } |
| this.inherited(arguments); |
| } |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dojo.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.html"] = true; |
| dojo.provide("dojo.html"); |
| |
| // the parser might be needed.. |
| |
| |
| (function(){ // private scope, sort of a namespace |
| |
| // idCounter is incremented with each instantiation to allow asignment of a unique id for tracking, logging purposes |
| var idCounter = 0, |
| d = dojo; |
| |
| dojo.html._secureForInnerHtml = function(/*String*/ cont){ |
| // summary: |
| // removes !DOCTYPE and title elements from the html string. |
| // |
| // khtml is picky about dom faults, you can't attach a style or <title> node as child of body |
| // must go into head, so we need to cut out those tags |
| // cont: |
| // An html string for insertion into the dom |
| // |
| return cont.replace(/(?:\s*<!DOCTYPE\s[^>]+>|<title[^>]*>[\s\S]*?<\/title>)/ig, ""); // String |
| }; |
| |
| /*==== |
| dojo.html._emptyNode = function(node){ |
| // summary: |
| // removes all child nodes from the given node |
| // node: DOMNode |
| // the parent element |
| }; |
| =====*/ |
| dojo.html._emptyNode = dojo.empty; |
| |
| dojo.html._setNodeContent = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont){ |
| // summary: |
| // inserts the given content into the given node |
| // node: |
| // the parent element |
| // content: |
| // the content to be set on the parent element. |
| // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes |
| |
| // always empty |
| d.empty(node); |
| |
| if(cont) { |
| if(typeof cont == "string") { |
| cont = d._toDom(cont, node.ownerDocument); |
| } |
| if(!cont.nodeType && d.isArrayLike(cont)) { |
| // handle as enumerable, but it may shrink as we enumerate it |
| for(var startlen=cont.length, i=0; i<cont.length; i=startlen==cont.length ? i+1 : 0) { |
| d.place( cont[i], node, "last"); |
| } |
| } else { |
| // pass nodes, documentFragments and unknowns through to dojo.place |
| d.place(cont, node, "last"); |
| } |
| } |
| |
| // return DomNode |
| return node; |
| }; |
| |
| // we wrap up the content-setting operation in a object |
| dojo.declare("dojo.html._ContentSetter", null, |
| { |
| // node: DomNode|String |
| // An node which will be the parent element that we set content into |
| node: "", |
| |
| // content: String|DomNode|DomNode[] |
| // The content to be placed in the node. Can be an HTML string, a node reference, or a enumerable list of nodes |
| content: "", |
| |
| // id: String? |
| // Usually only used internally, and auto-generated with each instance |
| id: "", |
| |
| // cleanContent: Boolean |
| // Should the content be treated as a full html document, |
| // and the real content stripped of <html>, <body> wrapper before injection |
| cleanContent: false, |
| |
| // extractContent: Boolean |
| // Should the content be treated as a full html document, and the real content stripped of <html>, <body> wrapper before injection |
| extractContent: false, |
| |
| // parseContent: Boolean |
| // Should the node by passed to the parser after the new content is set |
| parseContent: false, |
| |
| // lifecyle methods |
| constructor: function(/* Object */params, /* String|DomNode */node){ |
| // summary: |
| // Provides a configurable, extensible object to wrap the setting on content on a node |
| // call the set() method to actually set the content.. |
| |
| // the original params are mixed directly into the instance "this" |
| dojo.mixin(this, params || {}); |
| |
| // give precedence to params.node vs. the node argument |
| // and ensure its a node, not an id string |
| node = this.node = dojo.byId( this.node || node ); |
| |
| if(!this.id){ |
| this.id = [ |
| "Setter", |
| (node) ? node.id || node.tagName : "", |
| idCounter++ |
| ].join("_"); |
| } |
| |
| if(! (this.node || node)){ |
| new Error(this.declaredClass + ": no node provided to " + this.id); |
| } |
| }, |
| set: function(/* String|DomNode|NodeList? */ cont, /* Object? */ params){ |
| // summary: |
| // front-end to the set-content sequence |
| // cont: |
| // An html string, node or enumerable list of nodes for insertion into the dom |
| // If not provided, the object's content property will be used |
| if(undefined !== cont){ |
| this.content = cont; |
| } |
| // in the re-use scenario, set needs to be able to mixin new configuration |
| if(params){ |
| this._mixin(params); |
| } |
| |
| this.onBegin(); |
| this.setContent(); |
| this.onEnd(); |
| |
| return this.node; |
| }, |
| setContent: function(){ |
| // summary: |
| // sets the content on the node |
| |
| var node = this.node; |
| if(!node) { |
| console.error("setContent given no node"); |
| } |
| try{ |
| node = dojo.html._setNodeContent(node, this.content); |
| }catch(e){ |
| // check if a domfault occurs when we are appending this.errorMessage |
| // like for instance if domNode is a UL and we try append a DIV |
| |
| // FIXME: need to allow the user to provide a content error message string |
| var errMess = this.onContentError(e); |
| try{ |
| node.innerHTML = errMess; |
| }catch(e){ |
| console.error('Fatal ' + this.declaredClass + '.setContent could not change content due to '+e.message, e); |
| } |
| } |
| // always put back the node for the next method |
| this.node = node; // DomNode |
| }, |
| |
| empty: function() { |
| // summary |
| // cleanly empty out existing content |
| |
| // destroy any widgets from a previous run |
| // NOTE: if you dont want this you'll need to empty |
| // the parseResults array property yourself to avoid bad things happenning |
| if(this.parseResults && this.parseResults.length) { |
| dojo.forEach(this.parseResults, function(w) { |
| if(w.destroy){ |
| w.destroy(); |
| } |
| }); |
| delete this.parseResults; |
| } |
| // this is fast, but if you know its already empty or safe, you could |
| // override empty to skip this step |
| dojo.html._emptyNode(this.node); |
| }, |
| |
| onBegin: function(){ |
| // summary |
| // Called after instantiation, but before set(); |
| // It allows modification of any of the object properties |
| // - including the node and content provided - before the set operation actually takes place |
| // This default implementation checks for cleanContent and extractContent flags to |
| // optionally pre-process html string content |
| var cont = this.content; |
| |
| if(dojo.isString(cont)){ |
| if(this.cleanContent){ |
| cont = dojo.html._secureForInnerHtml(cont); |
| } |
| |
| if(this.extractContent){ |
| var match = cont.match(/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im); |
| if(match){ cont = match[1]; } |
| } |
| } |
| |
| // clean out the node and any cruft associated with it - like widgets |
| this.empty(); |
| |
| this.content = cont; |
| return this.node; /* DomNode */ |
| }, |
| |
| onEnd: function(){ |
| // summary |
| // Called after set(), when the new content has been pushed into the node |
| // It provides an opportunity for post-processing before handing back the node to the caller |
| // This default implementation checks a parseContent flag to optionally run the dojo parser over the new content |
| if(this.parseContent){ |
| // populates this.parseResults if you need those.. |
| this._parse(); |
| } |
| return this.node; /* DomNode */ |
| }, |
| |
| tearDown: function(){ |
| // summary |
| // manually reset the Setter instance if its being re-used for example for another set() |
| // description |
| // tearDown() is not called automatically. |
| // In normal use, the Setter instance properties are simply allowed to fall out of scope |
| // but the tearDown method can be called to explicitly reset this instance. |
| delete this.parseResults; |
| delete this.node; |
| delete this.content; |
| }, |
| |
| onContentError: function(err){ |
| return "Error occured setting content: " + err; |
| }, |
| |
| _mixin: function(params){ |
| // mix properties/methods into the instance |
| // TODO: the intention with tearDown is to put the Setter's state |
| // back to that of the original constructor (vs. deleting/resetting everything regardless of ctor params) |
| // so we could do something here to move the original properties aside for later restoration |
| var empty = {}, key; |
| for(key in params){ |
| if(key in empty){ continue; } |
| // TODO: here's our opportunity to mask the properties we dont consider configurable/overridable |
| // .. but history shows we'll almost always guess wrong |
| this[key] = params[key]; |
| } |
| }, |
| _parse: function(){ |
| // summary: |
| // runs the dojo parser over the node contents, storing any results in this.parseResults |
| // Any errors resulting from parsing are passed to _onError for handling |
| |
| var rootNode = this.node; |
| try{ |
| // store the results (widgets, whatever) for potential retrieval |
| this.parseResults = dojo.parser.parse(rootNode, true); |
| }catch(e){ |
| this._onError('Content', e, "Error parsing in _ContentSetter#"+this.id); |
| } |
| }, |
| |
| _onError: function(type, err, consoleText){ |
| // summary: |
| // shows user the string that is returned by on[type]Error |
| // overide/implement on[type]Error and return your own string to customize |
| var errText = this['on' + type + 'Error'].call(this, err); |
| if(consoleText){ |
| console.error(consoleText, err); |
| }else if(errText){ // a empty string won't change current content |
| dojo.html._setNodeContent(this.node, errText, true); |
| } |
| } |
| }); // end dojo.declare() |
| |
| dojo.html.set = function(/* DomNode */ node, /* String|DomNode|NodeList */ cont, /* Object? */ params){ |
| // summary: |
| // inserts (replaces) the given content into the given node. dojo.place(cont, node, "only") |
| // may be a better choice for simple HTML insertion. |
| // description: |
| // Unless you need to use the params capabilities of this method, you should use |
| // dojo.place(cont, node, "only"). dojo.place() has more robust support for injecting |
| // an HTML string into the DOM, but it only handles inserting an HTML string as DOM |
| // elements, or inserting a DOM node. dojo.place does not handle NodeList insertions |
| // or the other capabilities as defined by the params object for this method. |
| // node: |
| // the parent element that will receive the content |
| // cont: |
| // the content to be set on the parent element. |
| // This can be an html string, a node reference or a NodeList, dojo.NodeList, Array or other enumerable list of nodes |
| // params: |
| // Optional flags/properties to configure the content-setting. See dojo.html._ContentSetter |
| // example: |
| // A safe string/node/nodelist content replacement/injection with hooks for extension |
| // Example Usage: |
| // dojo.html.set(node, "some string"); |
| // dojo.html.set(node, contentNode, {options}); |
| // dojo.html.set(node, myNode.childNodes, {options}); |
| if(undefined == cont){ |
| console.warn("dojo.html.set: no cont argument provided, using empty string"); |
| cont = ""; |
| } |
| if(!params){ |
| // simple and fast |
| return dojo.html._setNodeContent(node, cont, true); |
| }else{ |
| // more options but slower |
| // note the arguments are reversed in order, to match the convention for instantiation via the parser |
| var op = new dojo.html._ContentSetter(dojo.mixin( |
| params, |
| { content: cont, node: node } |
| )); |
| return op.set(); |
| } |
| }; |
| })(); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout.ContentPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout.ContentPane"] = true; |
| dojo.provide("dijit.layout.ContentPane"); |
| |
| |
| |
| // for dijit.layout.marginBox2contentBox() |
| |
| |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.layout.ContentPane", dijit._Widget, |
| { |
| // summary: |
| // A widget that acts as a container for mixed HTML and widgets, and includes an Ajax interface |
| // description: |
| // A widget that can be used as a stand alone widget |
| // or as a base class for other widgets. |
| // |
| // Handles replacement of document fragment using either external uri or javascript |
| // generated markup or DOM content, instantiating widgets within that content. |
| // Don't confuse it with an iframe, it only needs/wants document fragments. |
| // It's useful as a child of LayoutContainer, SplitContainer, or TabContainer. |
| // But note that those classes can contain any widget as a child. |
| // example: |
| // Some quick samples: |
| // To change the innerHTML use .attr('content', '<b>new content</b>') |
| // |
| // Or you can send it a NodeList, .attr('content', dojo.query('div [class=selected]', userSelection)) |
| // please note that the nodes in NodeList will copied, not moved |
| // |
| // To do a ajax update use .attr('href', url) |
| |
| // href: String |
| // The href of the content that displays now. |
| // Set this at construction if you want to load data externally when the |
| // pane is shown. (Set preload=true to load it immediately.) |
| // Changing href after creation doesn't have any effect; use attr('href', ...); |
| href: "", |
| |
| /*===== |
| // content: String || DomNode || NodeList || dijit._Widget |
| // The innerHTML of the ContentPane. |
| // Note that the initialization parameter / argument to attr("content", ...) |
| // can be a String, DomNode, Nodelist, or _Widget. |
| content: "", |
| =====*/ |
| |
| // extractContent: Boolean |
| // Extract visible content from inside of <body> .... </body>. |
| // I.e., strip <html> and <head> (and it's contents) from the href |
| extractContent: false, |
| |
| // parseOnLoad: Boolean |
| // Parse content and create the widgets, if any. |
| parseOnLoad: true, |
| |
| // preventCache: Boolean |
| // Prevent caching of data from href's by appending a timestamp to the href. |
| preventCache: false, |
| |
| // preload: Boolean |
| // Force load of data on initialization even if pane is hidden. |
| preload: false, |
| |
| // refreshOnShow: Boolean |
| // Refresh (re-download) content when pane goes from hidden to shown |
| refreshOnShow: false, |
| |
| // loadingMessage: String |
| // Message that shows while downloading |
| loadingMessage: "<span class='dijitContentPaneLoading'>${loadingState}</span>", |
| |
| // errorMessage: String |
| // Message that shows if an error occurs |
| errorMessage: "<span class='dijitContentPaneError'>${errorState}</span>", |
| |
| // isLoaded: [readonly] Boolean |
| // True if the ContentPane has data in it, either specified |
| // during initialization (via href or inline content), or set |
| // via attr('content', ...) / attr('href', ...) |
| // |
| // False if it doesn't have any content, or if ContentPane is |
| // still in the process of downloading href. |
| isLoaded: false, |
| |
| baseClass: "dijitContentPane", |
| |
| // doLayout: Boolean |
| // - false - don't adjust size of children |
| // - true - if there is a single visible child widget, set it's size to |
| // however big the ContentPane is |
| doLayout: true, |
| |
| // ioArgs: Object |
| // Parameters to pass to xhrGet() request, for example: |
| // | <div dojoType="dijit.layout.ContentPane" href="./bar" ioArgs="{timeout: 500}"> |
| ioArgs: {}, |
| |
| // isContainer: [protected] Boolean |
| // Indicates that this widget acts as a "parent" to the descendant widgets. |
| // When the parent is started it will call startup() on the child widgets. |
| // See also `isLayoutContainer`. |
| isContainer: true, |
| |
| // isLayoutContainer: [protected] Boolean |
| // Indicates that this widget will call resize() on it's child widgets |
| // when they become visible. |
| isLayoutContainer: true, |
| |
| // onLoadDeferred: [readonly] dojo.Deferred |
| // This is the `dojo.Deferred` returned by attr('href', ...) and refresh(). |
| // Calling onLoadDeferred.addCallback() or addErrback() registers your |
| // callback to be called only once, when the prior attr('href', ...) call or |
| // the initial href parameter to the constructor finishes loading. |
| // |
| // This is different than an onLoad() handler which gets called any time any href is loaded. |
| onLoadDeferred: null, |
| |
| // Override _Widget's attributeMap because we don't want the title attribute (used to specify |
| // tab labels) to be copied to ContentPane.domNode... otherwise a tooltip shows up over the |
| // entire pane. |
| attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { |
| title: [] |
| }), |
| |
| postMixInProperties: function(){ |
| this.inherited(arguments); |
| var messages = dojo.i18n.getLocalization("dijit", "loading", this.lang); |
| this.loadingMessage = dojo.string.substitute(this.loadingMessage, messages); |
| this.errorMessage = dojo.string.substitute(this.errorMessage, messages); |
| |
| // Detect if we were initialized with data |
| if(!this.href && this.srcNodeRef && this.srcNodeRef.innerHTML){ |
| this.isLoaded = true; |
| } |
| }, |
| |
| buildRendering: function(){ |
| // Overrides Widget.buildRendering(). |
| // Since we have no template we need to set this.containerNode ourselves. |
| // For subclasses of ContentPane do have a template, does nothing. |
| this.inherited(arguments); |
| if(!this.containerNode){ |
| // make getDescendants() work |
| this.containerNode = this.domNode; |
| } |
| }, |
| |
| postCreate: function(){ |
| // remove the title attribute so it doesn't show up when hovering |
| // over a node |
| this.domNode.title = ""; |
| |
| if(!dojo.attr(this.domNode,"role")){ |
| dijit.setWaiRole(this.domNode, "group"); |
| } |
| |
| dojo.addClass(this.domNode, this.baseClass); |
| }, |
| |
| startup: function(){ |
| // summary: |
| // See `dijit.layout._LayoutWidget.startup` for description. |
| // Although ContentPane doesn't extend _LayoutWidget, it does implement |
| // the same API. |
| if(this._started){ return; } |
| |
| var parent = dijit._Contained.prototype.getParent.call(this); |
| this._childOfLayoutWidget = parent && parent.isLayoutContainer; |
| |
| // I need to call resize() on my child/children (when I become visible), unless |
| // I'm the child of a layout widget in which case my parent will call resize() on me and I'll do it then. |
| this._needLayout = !this._childOfLayoutWidget; |
| |
| if(this.isLoaded){ |
| dojo.forEach(this.getChildren(), function(child){ |
| child.startup(); |
| }); |
| } |
| |
| if(this._isShown() || this.preload){ |
| this._onShow(); |
| } |
| |
| this.inherited(arguments); |
| }, |
| |
| _checkIfSingleChild: function(){ |
| // summary: |
| // Test if we have exactly one visible widget as a child, |
| // and if so assume that we are a container for that widget, |
| // and should propogate startup() and resize() calls to it. |
| // Skips over things like data stores since they aren't visible. |
| |
| var childNodes = dojo.query("> *", this.containerNode).filter(function(node){ |
| return node.tagName !== "SCRIPT"; // or a regexp for hidden elements like script|area|map|etc.. |
| }), |
| childWidgetNodes = childNodes.filter(function(node){ |
| return dojo.hasAttr(node, "dojoType") || dojo.hasAttr(node, "widgetId"); |
| }), |
| candidateWidgets = dojo.filter(childWidgetNodes.map(dijit.byNode), function(widget){ |
| return widget && widget.domNode && widget.resize; |
| }); |
| |
| if( |
| // all child nodes are widgets |
| childNodes.length == childWidgetNodes.length && |
| |
| // all but one are invisible (like dojo.data) |
| candidateWidgets.length == 1 |
| ){ |
| this._singleChild = candidateWidgets[0]; |
| }else{ |
| delete this._singleChild; |
| } |
| |
| // So we can set overflow: hidden to avoid a safari bug w/scrollbars showing up (#9449) |
| dojo.toggleClass(this.containerNode, this.baseClass + "SingleChild", !!this._singleChild); |
| }, |
| |
| setHref: function(/*String|Uri*/ href){ |
| // summary: |
| // Deprecated. Use attr('href', ...) instead. |
| dojo.deprecated("dijit.layout.ContentPane.setHref() is deprecated. Use attr('href', ...) instead.", "", "2.0"); |
| return this.attr("href", href); |
| }, |
| _setHrefAttr: function(/*String|Uri*/ href){ |
| // summary: |
| // Hook so attr("href", ...) works. |
| // description: |
| // Reset the (external defined) content of this pane and replace with new url |
| // Note: It delays the download until widget is shown if preload is false. |
| // href: |
| // url to the page you want to get, must be within the same domain as your mainpage |
| |
| // Cancel any in-flight requests (an attr('href') will cancel any in-flight attr('href', ...)) |
| this.cancel(); |
| |
| this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel")); |
| |
| this.href = href; |
| |
| // _setHrefAttr() is called during creation and by the user, after creation. |
| // only in the second case do we actually load the URL; otherwise it's done in startup() |
| if(this._created && (this.preload || this._isShown())){ |
| this._load(); |
| }else{ |
| // Set flag to indicate that href needs to be loaded the next time the |
| // ContentPane is made visible |
| this._hrefChanged = true; |
| } |
| |
| return this.onLoadDeferred; // dojo.Deferred |
| }, |
| |
| setContent: function(/*String|DomNode|Nodelist*/data){ |
| // summary: |
| // Deprecated. Use attr('content', ...) instead. |
| dojo.deprecated("dijit.layout.ContentPane.setContent() is deprecated. Use attr('content', ...) instead.", "", "2.0"); |
| this.attr("content", data); |
| }, |
| _setContentAttr: function(/*String|DomNode|Nodelist*/data){ |
| // summary: |
| // Hook to make attr("content", ...) work. |
| // Replaces old content with data content, include style classes from old content |
| // data: |
| // the new Content may be String, DomNode or NodeList |
| // |
| // if data is a NodeList (or an array of nodes) nodes are copied |
| // so you can import nodes from another document implicitly |
| |
| // clear href so we can't run refresh and clear content |
| // refresh should only work if we downloaded the content |
| this.href = ""; |
| |
| // Cancel any in-flight requests (an attr('content') will cancel any in-flight attr('href', ...)) |
| this.cancel(); |
| |
| // Even though user is just setting content directly, still need to define an onLoadDeferred |
| // because the _onLoadHandler() handler is still getting called from setContent() |
| this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel")); |
| |
| this._setContent(data || ""); |
| |
| this._isDownloaded = false; // mark that content is from a attr('content') not an attr('href') |
| |
| return this.onLoadDeferred; // dojo.Deferred |
| }, |
| _getContentAttr: function(){ |
| // summary: |
| // Hook to make attr("content") work |
| return this.containerNode.innerHTML; |
| }, |
| |
| cancel: function(){ |
| // summary: |
| // Cancels an in-flight download of content |
| if(this._xhrDfd && (this._xhrDfd.fired == -1)){ |
| this._xhrDfd.cancel(); |
| } |
| delete this._xhrDfd; // garbage collect |
| |
| this.onLoadDeferred = null; |
| }, |
| |
| uninitialize: function(){ |
| if(this._beingDestroyed){ |
| this.cancel(); |
| } |
| this.inherited(arguments); |
| }, |
| |
| destroyRecursive: function(/*Boolean*/ preserveDom){ |
| // summary: |
| // Destroy the ContentPane and its contents |
| |
| // if we have multiple controllers destroying us, bail after the first |
| if(this._beingDestroyed){ |
| return; |
| } |
| this.inherited(arguments); |
| }, |
| |
| resize: function(changeSize, resultSize){ |
| // summary: |
| // See `dijit.layout._LayoutWidget.resize` for description. |
| // Although ContentPane doesn't extend _LayoutWidget, it does implement |
| // the same API. |
| |
| // For the TabContainer --> BorderContainer --> ContentPane case, _onShow() is |
| // never called, so resize() is our trigger to do the initial href download. |
| if(!this._wasShown){ |
| this._onShow(); |
| } |
| |
| this._resizeCalled = true; |
| |
| // Set margin box size, unless it wasn't specified, in which case use current size. |
| if(changeSize){ |
| dojo.marginBox(this.domNode, changeSize); |
| } |
| |
| // Compute content box size of containerNode in case we [later] need to size our single child. |
| var cn = this.containerNode; |
| if(cn === this.domNode){ |
| // If changeSize or resultSize was passed to this method and this.containerNode == |
| // this.domNode then we can compute the content-box size without querying the node, |
| // which is more reliable (similar to LayoutWidget.resize) (see for example #9449). |
| var mb = resultSize || {}; |
| dojo.mixin(mb, changeSize || {}); // changeSize overrides resultSize |
| if(!("h" in mb) || !("w" in mb)){ |
| mb = dojo.mixin(dojo.marginBox(cn), mb); // just use dojo.marginBox() to fill in missing values |
| } |
| this._contentBox = dijit.layout.marginBox2contentBox(cn, mb); |
| }else{ |
| this._contentBox = dojo.contentBox(cn); |
| } |
| |
| // Make my children layout, or size my single child widget |
| this._layoutChildren(); |
| }, |
| |
| _isShown: function(){ |
| // summary: |
| // Returns true if the content is currently shown. |
| // description: |
| // If I am a child of a layout widget then it actually returns true if I've ever been visible, |
| // not whether I'm currently visible, since that's much faster than tracing up the DOM/widget |
| // tree every call, and at least solves the performance problem on page load by deferring loading |
| // hidden ContentPanes until they are first shown |
| |
| if(this._childOfLayoutWidget){ |
| // If we are TitlePane, etc - we return that only *IF* we've been resized |
| if(this._resizeCalled && "open" in this){ |
| return this.open; |
| } |
| return this._resizeCalled; |
| }else if("open" in this){ |
| return this.open; // for TitlePane, etc. |
| }else{ |
| // TODO: with _childOfLayoutWidget check maybe this branch no longer necessary? |
| var node = this.domNode; |
| return (node.style.display != 'none') && (node.style.visibility != 'hidden') && !dojo.hasClass(node, "dijitHidden"); |
| } |
| }, |
| |
| _onShow: function(){ |
| // summary: |
| // Called when the ContentPane is made visible |
| // description: |
| // For a plain ContentPane, this is called on initialization, from startup(). |
| // If the ContentPane is a hidden pane of a TabContainer etc., then it's |
| // called whenever the pane is made visible. |
| // |
| // Does necessary processing, including href download and layout/resize of |
| // child widget(s) |
| |
| if(this.href){ |
| if(!this._xhrDfd && // if there's an href that isn't already being loaded |
| (!this.isLoaded || this._hrefChanged || this.refreshOnShow) |
| ){ |
| this.refresh(); |
| } |
| }else{ |
| // If we are the child of a layout widget then the layout widget will call resize() on |
| // us, and then we will size our child/children. Otherwise, we need to do it now. |
| if(!this._childOfLayoutWidget && this._needLayout){ |
| // If a layout has been scheduled for when we become visible, do it now |
| this._layoutChildren(); |
| } |
| } |
| |
| this.inherited(arguments); |
| |
| // Need to keep track of whether ContentPane has been shown (which is different than |
| // whether or not it's currently visible). |
| this._wasShown = true; |
| }, |
| |
| refresh: function(){ |
| // summary: |
| // [Re]download contents of href and display |
| // description: |
| // 1. cancels any currently in-flight requests |
| // 2. posts "loading..." message |
| // 3. sends XHR to download new data |
| |
| // Cancel possible prior in-flight request |
| this.cancel(); |
| |
| this.onLoadDeferred = new dojo.Deferred(dojo.hitch(this, "cancel")); |
| this._load(); |
| return this.onLoadDeferred; |
| }, |
| |
| _load: function(){ |
| // summary: |
| // Load/reload the href specified in this.href |
| |
| // display loading message |
| this._setContent(this.onDownloadStart(), true); |
| |
| var self = this; |
| var getArgs = { |
| preventCache: (this.preventCache || this.refreshOnShow), |
| url: this.href, |
| handleAs: "text" |
| }; |
| if(dojo.isObject(this.ioArgs)){ |
| dojo.mixin(getArgs, this.ioArgs); |
| } |
| |
| var hand = (this._xhrDfd = (this.ioMethod || dojo.xhrGet)(getArgs)); |
| |
| hand.addCallback(function(html){ |
| try{ |
| self._isDownloaded = true; |
| self._setContent(html, false); |
| self.onDownloadEnd(); |
| }catch(err){ |
| self._onError('Content', err); // onContentError |
| } |
| delete self._xhrDfd; |
| return html; |
| }); |
| |
| hand.addErrback(function(err){ |
| if(!hand.canceled){ |
| // show error message in the pane |
| self._onError('Download', err); // onDownloadError |
| } |
| delete self._xhrDfd; |
| return err; |
| }); |
| |
| // Remove flag saying that a load is needed |
| delete this._hrefChanged; |
| }, |
| |
| _onLoadHandler: function(data){ |
| // summary: |
| // This is called whenever new content is being loaded |
| this.isLoaded = true; |
| try{ |
| this.onLoadDeferred.callback(data); |
| this.onLoad(data); |
| }catch(e){ |
| console.error('Error '+this.widgetId+' running custom onLoad code: ' + e.message); |
| } |
| }, |
| |
| _onUnloadHandler: function(){ |
| // summary: |
| // This is called whenever the content is being unloaded |
| this.isLoaded = false; |
| try{ |
| this.onUnload(); |
| }catch(e){ |
| console.error('Error '+this.widgetId+' running custom onUnload code: ' + e.message); |
| } |
| }, |
| |
| destroyDescendants: function(){ |
| // summary: |
| // Destroy all the widgets inside the ContentPane and empty containerNode |
| |
| // Make sure we call onUnload (but only when the ContentPane has real content) |
| if(this.isLoaded){ |
| this._onUnloadHandler(); |
| } |
| |
| // Even if this.isLoaded == false there might still be a "Loading..." message |
| // to erase, so continue... |
| |
| // For historical reasons we need to delete all widgets under this.containerNode, |
| // even ones that the user has created manually. |
| var setter = this._contentSetter; |
| dojo.forEach(this.getChildren(), function(widget){ |
| if(widget.destroyRecursive){ |
| widget.destroyRecursive(); |
| } |
| }); |
| if(setter){ |
| // Most of the widgets in setter.parseResults have already been destroyed, but |
| // things like Menu that have been moved to <body> haven't yet |
| dojo.forEach(setter.parseResults, function(widget){ |
| if(widget.destroyRecursive && widget.domNode && widget.domNode.parentNode == dojo.body()){ |
| widget.destroyRecursive(); |
| } |
| }); |
| delete setter.parseResults; |
| } |
| |
| // And then clear away all the DOM nodes |
| dojo.html._emptyNode(this.containerNode); |
| |
| // Delete any state information we have about current contents |
| delete this._singleChild; |
| }, |
| |
| _setContent: function(cont, isFakeContent){ |
| // summary: |
| // Insert the content into the container node |
| |
| // first get rid of child widgets |
| this.destroyDescendants(); |
| |
| // dojo.html.set will take care of the rest of the details |
| // we provide an override for the error handling to ensure the widget gets the errors |
| // configure the setter instance with only the relevant widget instance properties |
| // NOTE: unless we hook into attr, or provide property setters for each property, |
| // we need to re-configure the ContentSetter with each use |
| var setter = this._contentSetter; |
| if(! (setter && setter instanceof dojo.html._ContentSetter)){ |
| setter = this._contentSetter = new dojo.html._ContentSetter({ |
| node: this.containerNode, |
| _onError: dojo.hitch(this, this._onError), |
| onContentError: dojo.hitch(this, function(e){ |
| // fires if a domfault occurs when we are appending this.errorMessage |
| // like for instance if domNode is a UL and we try append a DIV |
| var errMess = this.onContentError(e); |
| try{ |
| this.containerNode.innerHTML = errMess; |
| }catch(e){ |
| console.error('Fatal '+this.id+' could not change content due to '+e.message, e); |
| } |
| })/*, |
| _onError */ |
| }); |
| }; |
| |
| var setterParams = dojo.mixin({ |
| cleanContent: this.cleanContent, |
| extractContent: this.extractContent, |
| parseContent: this.parseOnLoad |
| }, this._contentSetterParams || {}); |
| |
| dojo.mixin(setter, setterParams); |
| |
| setter.set( (dojo.isObject(cont) && cont.domNode) ? cont.domNode : cont ); |
| |
| // setter params must be pulled afresh from the ContentPane each time |
| delete this._contentSetterParams; |
| |
| if(!isFakeContent){ |
| // Startup each top level child widget (and they will start their children, recursively) |
| dojo.forEach(this.getChildren(), function(child){ |
| // The parser has already called startup on all widgets *without* a getParent() method |
| if(!this.parseOnLoad || child.getParent){ |
| child.startup(); |
| } |
| }, this); |
| |
| // Call resize() on each of my child layout widgets, |
| // or resize() on my single child layout widget... |
| // either now (if I'm currently visible) |
| // or when I become visible |
| this._scheduleLayout(); |
| |
| this._onLoadHandler(cont); |
| } |
| }, |
| |
| _onError: function(type, err, consoleText){ |
| this.onLoadDeferred.errback(err); |
| |
| // shows user the string that is returned by on[type]Error |
| // overide on[type]Error and return your own string to customize |
| var errText = this['on' + type + 'Error'].call(this, err); |
| if(consoleText){ |
| console.error(consoleText, err); |
| }else if(errText){// a empty string won't change current content |
| this._setContent(errText, true); |
| } |
| }, |
| |
| _scheduleLayout: function(){ |
| // summary: |
| // Call resize() on each of my child layout widgets, either now |
| // (if I'm currently visible) or when I become visible |
| if(this._isShown()){ |
| this._layoutChildren(); |
| }else{ |
| this._needLayout = true; |
| } |
| }, |
| |
| _layoutChildren: function(){ |
| // summary: |
| // Since I am a Container widget, each of my children expects me to |
| // call resize() or layout() on them. |
| // description: |
| // Should be called on initialization and also whenever we get new content |
| // (from an href, or from attr('content', ...))... but deferred until |
| // the ContentPane is visible |
| |
| if(this.doLayout){ |
| this._checkIfSingleChild(); |
| } |
| |
| if(this._singleChild && this._singleChild.resize){ |
| var cb = this._contentBox || dojo.contentBox(this.containerNode); |
| |
| // note: if widget has padding this._contentBox will have l and t set, |
| // but don't pass them to resize() or it will doubly-offset the child |
| this._singleChild.resize({w: cb.w, h: cb.h}); |
| }else{ |
| // All my child widgets are independently sized (rather than matching my size), |
| // but I still need to call resize() on each child to make it layout. |
| dojo.forEach(this.getChildren(), function(widget){ |
| if(widget.resize){ |
| widget.resize(); |
| } |
| }); |
| } |
| delete this._needLayout; |
| }, |
| |
| // EVENT's, should be overide-able |
| onLoad: function(data){ |
| // summary: |
| // Event hook, is called after everything is loaded and widgetified |
| // tags: |
| // callback |
| }, |
| |
| onUnload: function(){ |
| // summary: |
| // Event hook, is called before old content is cleared |
| // tags: |
| // callback |
| }, |
| |
| onDownloadStart: function(){ |
| // summary: |
| // Called before download starts. |
| // description: |
| // The string returned by this function will be the html |
| // that tells the user we are loading something. |
| // Override with your own function if you want to change text. |
| // tags: |
| // extension |
| return this.loadingMessage; |
| }, |
| |
| onContentError: function(/*Error*/ error){ |
| // summary: |
| // Called on DOM faults, require faults etc. in content. |
| // |
| // In order to display an error message in the pane, return |
| // the error message from this method, as an HTML string. |
| // |
| // By default (if this method is not overriden), it returns |
| // nothing, so the error message is just printed to the console. |
| // tags: |
| // extension |
| }, |
| |
| onDownloadError: function(/*Error*/ error){ |
| // summary: |
| // Called when download error occurs. |
| // |
| // In order to display an error message in the pane, return |
| // the error message from this method, as an HTML string. |
| // |
| // Default behavior (if this method is not overriden) is to display |
| // the error message inside the pane. |
| // tags: |
| // extension |
| return this.errorMessage; |
| }, |
| |
| onDownloadEnd: function(){ |
| // summary: |
| // Called when download is finished. |
| // tags: |
| // callback |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.TooltipDialog"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.TooltipDialog"] = true; |
| dojo.provide("dijit.TooltipDialog"); |
| |
| |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.TooltipDialog", |
| [dijit.layout.ContentPane, dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin], |
| { |
| // summary: |
| // Pops up a dialog that appears like a Tooltip |
| |
| // title: String |
| // Description of tooltip dialog (required for a11y) |
| title: "", |
| |
| // doLayout: [protected] Boolean |
| // Don't change this parameter from the default value. |
| // This ContentPane parameter doesn't make sense for TooltipDialog, since TooltipDialog |
| // is never a child of a layout container, nor can you specify the size of |
| // TooltipDialog in order to control the size of an inner widget. |
| doLayout: false, |
| |
| // autofocus: Boolean |
| // A Toggle to modify the default focus behavior of a Dialog, which |
| // is to focus on the first dialog element after opening the dialog. |
| // False will disable autofocusing. Default: true |
| autofocus: true, |
| |
| // baseClass: [protected] String |
| // The root className to use for the various states of this widget |
| baseClass: "dijitTooltipDialog", |
| |
| // _firstFocusItem: [private] [readonly] DomNode |
| // The pointer to the first focusable node in the dialog. |
| // Set by `dijit._DialogMixin._getFocusItems`. |
| _firstFocusItem: null, |
| |
| // _lastFocusItem: [private] [readonly] DomNode |
| // The pointer to which node has focus prior to our dialog. |
| // Set by `dijit._DialogMixin._getFocusItems`. |
| _lastFocusItem: null, |
| |
| templateString: dojo.cache("dijit", "templates/TooltipDialog.html", "<div waiRole=\"presentation\">\n\t<div class=\"dijitTooltipContainer\" waiRole=\"presentation\">\n\t\t<div class =\"dijitTooltipContents dijitTooltipFocusNode\" dojoAttachPoint=\"containerNode\" tabindex=\"-1\" waiRole=\"dialog\"></div>\n\t</div>\n\t<div class=\"dijitTooltipConnector\" waiRole=\"presentation\"></div>\n</div>\n"), |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| this.connect(this.containerNode, "onkeypress", "_onKey"); |
| this.containerNode.title = this.title; |
| }, |
| |
| orient: function(/*DomNode*/ node, /*String*/ aroundCorner, /*String*/ corner){ |
| // summary: |
| // Configure widget to be displayed in given position relative to the button. |
| // This is called from the dijit.popup code, and should not be called |
| // directly. |
| // tags: |
| // protected |
| var c = this._currentOrientClass; |
| if(c){ |
| dojo.removeClass(this.domNode, c); |
| } |
| c = "dijitTooltipAB"+(corner.charAt(1) == 'L'?"Left":"Right")+" dijitTooltip"+(corner.charAt(0) == 'T' ? "Below" : "Above"); |
| dojo.addClass(this.domNode, c); |
| this._currentOrientClass = c; |
| }, |
| |
| onOpen: function(/*Object*/ pos){ |
| // summary: |
| // Called when dialog is displayed. |
| // This is called from the dijit.popup code, and should not be called directly. |
| // tags: |
| // protected |
| |
| this.orient(this.domNode,pos.aroundCorner, pos.corner); |
| this._onShow(); // lazy load trigger |
| |
| if(this.autofocus){ |
| this._getFocusItems(this.containerNode); |
| dijit.focus(this._firstFocusItem); |
| } |
| }, |
| |
| onClose: function(){ |
| // summary: |
| // Called when dialog is hidden. |
| // This is called from the dijit.popup code, and should not be called directly. |
| // tags: |
| // protected |
| this.onHide(); |
| }, |
| |
| _onKey: function(/*Event*/ evt){ |
| // summary: |
| // Handler for keyboard events |
| // description: |
| // Keep keyboard focus in dialog; close dialog on escape key |
| // tags: |
| // private |
| |
| var node = evt.target; |
| var dk = dojo.keys; |
| if(evt.charOrCode === dk.TAB){ |
| this._getFocusItems(this.containerNode); |
| } |
| var singleFocusItem = (this._firstFocusItem == this._lastFocusItem); |
| if(evt.charOrCode == dk.ESCAPE){ |
| // Use setTimeout to avoid crash on IE, see #10396. |
| setTimeout(dojo.hitch(this, "onCancel"), 0); |
| dojo.stopEvent(evt); |
| }else if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){ |
| if(!singleFocusItem){ |
| dijit.focus(this._lastFocusItem); // send focus to last item in dialog |
| } |
| dojo.stopEvent(evt); |
| }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){ |
| if(!singleFocusItem){ |
| dijit.focus(this._firstFocusItem); // send focus to first item in dialog |
| } |
| dojo.stopEvent(evt); |
| }else if(evt.charOrCode === dk.TAB){ |
| // we want the browser's default tab handling to move focus |
| // but we don't want the tab to propagate upwards |
| evt.stopPropagation(); |
| } |
| } |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.Dialog"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.Dialog"] = true; |
| dojo.provide("dijit.Dialog"); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| /*===== |
| dijit._underlay = function(kwArgs){ |
| // summary: |
| // A shared instance of a `dijit.DialogUnderlay` |
| // |
| // description: |
| // A shared instance of a `dijit.DialogUnderlay` created and |
| // used by `dijit.Dialog`, though never created until some Dialog |
| // or subclass thereof is shown. |
| }; |
| =====*/ |
| |
| dojo.declare( |
| "dijit._DialogBase", |
| [dijit._Templated, dijit.form._FormMixin, dijit._DialogMixin], |
| { |
| // summary: |
| // A modal dialog Widget |
| // |
| // description: |
| // Pops up a modal dialog window, blocking access to the screen |
| // and also graying out the screen Dialog is extended from |
| // ContentPane so it supports all the same parameters (href, etc.) |
| // |
| // example: |
| // | <div dojoType="dijit.Dialog" href="test.html"></div> |
| // |
| // example: |
| // | var foo = new dijit.Dialog({ title: "test dialog", content: "test content" }; |
| // | dojo.body().appendChild(foo.domNode); |
| // | foo.startup(); |
| |
| templateString: dojo.cache("dijit", "templates/Dialog.html", "<div class=\"dijitDialog\" tabindex=\"-1\" waiRole=\"dialog\" waiState=\"labelledby-${id}_title\">\n\t<div dojoAttachPoint=\"titleBar\" class=\"dijitDialogTitleBar\">\n\t<span dojoAttachPoint=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"></span>\n\t<span dojoAttachPoint=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" dojoAttachEvent=\"onclick: onCancel, onmouseenter: _onCloseEnter, onmouseleave: _onCloseLeave\" title=\"${buttonCancel}\">\n\t\t<span dojoAttachPoint=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t</span>\n\t</div>\n\t\t<div dojoAttachPoint=\"containerNode\" class=\"dijitDialogPaneContent\"></div>\n</div>\n"), |
| |
| attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { |
| title: [ |
| { node: "titleNode", type: "innerHTML" }, |
| { node: "titleBar", type: "attribute" } |
| ], |
| "aria-describedby":"" |
| }), |
| |
| // open: Boolean |
| // True if Dialog is currently displayed on screen. |
| open: false, |
| |
| // duration: Integer |
| // The time in milliseconds it takes the dialog to fade in and out |
| duration: dijit.defaultDuration, |
| |
| // refocus: Boolean |
| // A Toggle to modify the default focus behavior of a Dialog, which |
| // is to re-focus the element which had focus before being opened. |
| // False will disable refocusing. Default: true |
| refocus: true, |
| |
| // autofocus: Boolean |
| // A Toggle to modify the default focus behavior of a Dialog, which |
| // is to focus on the first dialog element after opening the dialog. |
| // False will disable autofocusing. Default: true |
| autofocus: true, |
| |
| // _firstFocusItem: [private] [readonly] DomNode |
| // The pointer to the first focusable node in the dialog. |
| // Set by `dijit._DialogMixin._getFocusItems`. |
| _firstFocusItem: null, |
| |
| // _lastFocusItem: [private] [readonly] DomNode |
| // The pointer to which node has focus prior to our dialog. |
| // Set by `dijit._DialogMixin._getFocusItems`. |
| _lastFocusItem: null, |
| |
| // doLayout: [protected] Boolean |
| // Don't change this parameter from the default value. |
| // This ContentPane parameter doesn't make sense for Dialog, since Dialog |
| // is never a child of a layout container, nor can you specify the size of |
| // Dialog in order to control the size of an inner widget. |
| doLayout: false, |
| |
| // draggable: Boolean |
| // Toggles the moveable aspect of the Dialog. If true, Dialog |
| // can be dragged by it's title. If false it will remain centered |
| // in the viewport. |
| draggable: true, |
| |
| //aria-describedby: String |
| // Allows the user to add an aria-describedby attribute onto the dialog. The value should |
| // be the id of the container element of text that describes the dialog purpose (usually |
| // the first text in the dialog). |
| // <div dojoType="dijit.Dialog" aria-describedby="intro" .....> |
| // <div id="intro">Introductory text</div> |
| // <div>rest of dialog contents</div> |
| // </div> |
| "aria-describedby":"", |
| |
| postMixInProperties: function(){ |
| var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); |
| dojo.mixin(this, _nlsResources); |
| this.inherited(arguments); |
| }, |
| |
| postCreate: function(){ |
| dojo.style(this.domNode, { |
| display: "none", |
| position:"absolute" |
| }); |
| dojo.body().appendChild(this.domNode); |
| |
| this.inherited(arguments); |
| |
| this.connect(this, "onExecute", "hide"); |
| this.connect(this, "onCancel", "hide"); |
| this._modalconnects = []; |
| }, |
| |
| onLoad: function(){ |
| // summary: |
| // Called when data has been loaded from an href. |
| // Unlike most other callbacks, this function can be connected to (via `dojo.connect`) |
| // but should *not* be overriden. |
| // tags: |
| // callback |
| |
| // when href is specified we need to reposition the dialog after the data is loaded |
| this._position(); |
| this.inherited(arguments); |
| }, |
| |
| _endDrag: function(e){ |
| // summary: |
| // Called after dragging the Dialog. Saves the position of the dialog in the viewport. |
| // tags: |
| // private |
| if(e && e.node && e.node === this.domNode){ |
| this._relativePosition = dojo.position(e.node); |
| } |
| }, |
| |
| _setup: function(){ |
| // summary: |
| // Stuff we need to do before showing the Dialog for the first |
| // time (but we defer it until right beforehand, for |
| // performance reasons). |
| // tags: |
| // private |
| |
| var node = this.domNode; |
| |
| if(this.titleBar && this.draggable){ |
| this._moveable = (dojo.isIE == 6) ? |
| new dojo.dnd.TimedMoveable(node, { handle: this.titleBar }) : // prevent overload, see #5285 |
| new dojo.dnd.Moveable(node, { handle: this.titleBar, timeout: 0 }); |
| dojo.subscribe("/dnd/move/stop",this,"_endDrag"); |
| }else{ |
| dojo.addClass(node,"dijitDialogFixed"); |
| } |
| |
| this.underlayAttrs = { |
| dialogId: this.id, |
| "class": dojo.map(this["class"].split(/\s/), function(s){ return s+"_underlay"; }).join(" ") |
| }; |
| |
| this._fadeIn = dojo.fadeIn({ |
| node: node, |
| duration: this.duration, |
| beforeBegin: dojo.hitch(this, function(){ |
| var underlay = dijit._underlay; |
| if(!underlay){ |
| underlay = dijit._underlay = new dijit.DialogUnderlay(this.underlayAttrs); |
| }else{ |
| underlay.attr(this.underlayAttrs); |
| } |
| |
| var zIndex = 948 + dijit._dialogStack.length*2; |
| dojo.style(dijit._underlay.domNode, 'zIndex', zIndex); |
| dojo.style(this.domNode, 'zIndex', zIndex + 1); |
| underlay.show(); |
| }), |
| onEnd: dojo.hitch(this, function(){ |
| if(this.autofocus){ |
| // find focusable Items each time dialog is shown since if dialog contains a widget the |
| // first focusable items can change |
| this._getFocusItems(this.domNode); |
| dijit.focus(this._firstFocusItem); |
| } |
| }) |
| }); |
| |
| this._fadeOut = dojo.fadeOut({ |
| node: node, |
| duration: this.duration, |
| onEnd: dojo.hitch(this, function(){ |
| node.style.display = "none"; |
| |
| // Restore the previous dialog in the stack, or if this is the only dialog |
| // then restore to original page |
| var ds = dijit._dialogStack; |
| if(ds.length == 0){ |
| dijit._underlay.hide(); |
| }else{ |
| dojo.style(dijit._underlay.domNode, 'zIndex', 948 + ds.length*2); |
| dijit._underlay.attr(ds[ds.length-1].underlayAttrs); |
| } |
| |
| // Restore focus to wherever it was before this dialog was displayed |
| if(this.refocus){ |
| var focus = this._savedFocus; |
| |
| // If we are returning control to a previous dialog but for some reason |
| // that dialog didn't have a focused field, set focus to first focusable item. |
| // This situation could happen if two dialogs appeared at nearly the same time, |
| // since a dialog doesn't set it's focus until the fade-in is finished. |
| if(ds.length > 0){ |
| var pd = ds[ds.length-1]; |
| if(!dojo.isDescendant(focus.node, pd.domNode)){ |
| pd._getFocusItems(pd.domNode); |
| focus = pd._firstFocusItem; |
| } |
| } |
| |
| dijit.focus(focus); |
| } |
| }) |
| }); |
| }, |
| |
| uninitialize: function(){ |
| var wasPlaying = false; |
| if(this._fadeIn && this._fadeIn.status() == "playing"){ |
| wasPlaying = true; |
| this._fadeIn.stop(); |
| } |
| if(this._fadeOut && this._fadeOut.status() == "playing"){ |
| wasPlaying = true; |
| this._fadeOut.stop(); |
| } |
| |
| // Hide the underlay, unless the underlay widget has already been destroyed |
| // because we are being called during page unload (when all widgets are destroyed) |
| if((this.open || wasPlaying) && !dijit._underlay._destroyed){ |
| dijit._underlay.hide(); |
| } |
| if(this._moveable){ |
| this._moveable.destroy(); |
| } |
| this.inherited(arguments); |
| }, |
| |
| _size: function(){ |
| // summary: |
| // If necessary, shrink dialog contents so dialog fits in viewport |
| // tags: |
| // private |
| |
| this._checkIfSingleChild(); |
| |
| // If we resized the dialog contents earlier, reset them back to original size, so |
| // that if the user later increases the viewport size, the dialog can display w/out a scrollbar. |
| // Need to do this before the dojo.marginBox(this.domNode) call below. |
| if(this._singleChild){ |
| if(this._singleChildOriginalStyle){ |
| this._singleChild.domNode.style.cssText = this._singleChildOriginalStyle; |
| } |
| delete this._singleChildOriginalStyle; |
| }else{ |
| dojo.style(this.containerNode, { |
| width:"auto", |
| height:"auto" |
| }); |
| } |
| |
| var mb = dojo.marginBox(this.domNode); |
| var viewport = dijit.getViewport(); |
| if(mb.w >= viewport.w || mb.h >= viewport.h){ |
| // Reduce size of dialog contents so that dialog fits in viewport |
| |
| var w = Math.min(mb.w, Math.floor(viewport.w * 0.75)), |
| h = Math.min(mb.h, Math.floor(viewport.h * 0.75)); |
| |
| if(this._singleChild && this._singleChild.resize){ |
| this._singleChildOriginalStyle = this._singleChild.domNode.style.cssText; |
| this._singleChild.resize({w: w, h: h}); |
| }else{ |
| dojo.style(this.containerNode, { |
| width: w + "px", |
| height: h + "px", |
| overflow: "auto", |
| position: "relative" // workaround IE bug moving scrollbar or dragging dialog |
| }); |
| } |
| }else{ |
| if(this._singleChild && this._singleChild.resize){ |
| this._singleChild.resize(); |
| } |
| } |
| }, |
| |
| _position: function(){ |
| // summary: |
| // Position modal dialog in the viewport. If no relative offset |
| // in the viewport has been determined (by dragging, for instance), |
| // center the node. Otherwise, use the Dialog's stored relative offset, |
| // and position the node to top: left: values based on the viewport. |
| // tags: |
| // private |
| if(!dojo.hasClass(dojo.body(),"dojoMove")){ |
| var node = this.domNode, |
| viewport = dijit.getViewport(), |
| p = this._relativePosition, |
| bb = p ? null : dojo._getBorderBox(node), |
| l = Math.floor(viewport.l + (p ? p.x : (viewport.w - bb.w) / 2)), |
| t = Math.floor(viewport.t + (p ? p.y : (viewport.h - bb.h) / 2)) |
| ; |
| dojo.style(node,{ |
| left: l + "px", |
| top: t + "px" |
| }); |
| } |
| }, |
| |
| _onKey: function(/*Event*/ evt){ |
| // summary: |
| // Handles the keyboard events for accessibility reasons |
| // tags: |
| // private |
| |
| var ds = dijit._dialogStack; |
| if(ds[ds.length-1] != this){ |
| // console.debug(this.id + ': skipping because', this, 'is not the active dialog'); |
| return; |
| } |
| |
| if(evt.charOrCode){ |
| var dk = dojo.keys; |
| var node = evt.target; |
| if(evt.charOrCode === dk.TAB){ |
| this._getFocusItems(this.domNode); |
| } |
| var singleFocusItem = (this._firstFocusItem == this._lastFocusItem); |
| // see if we are shift-tabbing from first focusable item on dialog |
| if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === dk.TAB){ |
| if(!singleFocusItem){ |
| dijit.focus(this._lastFocusItem); // send focus to last item in dialog |
| } |
| dojo.stopEvent(evt); |
| }else if(node == this._lastFocusItem && evt.charOrCode === dk.TAB && !evt.shiftKey){ |
| if(!singleFocusItem){ |
| dijit.focus(this._firstFocusItem); // send focus to first item in dialog |
| } |
| dojo.stopEvent(evt); |
| }else{ |
| // see if the key is for the dialog |
| while(node){ |
| if(node == this.domNode || dojo.hasClass(node, "dijitPopup")){ |
| if(evt.charOrCode == dk.ESCAPE){ |
| this.onCancel(); |
| }else{ |
| return; // just let it go |
| } |
| } |
| node = node.parentNode; |
| } |
| // this key is for the disabled document window |
| if(evt.charOrCode !== dk.TAB){ // allow tabbing into the dialog for a11y |
| dojo.stopEvent(evt); |
| // opera won't tab to a div |
| }else if(!dojo.isOpera){ |
| try{ |
| this._firstFocusItem.focus(); |
| }catch(e){ /*squelch*/ } |
| } |
| } |
| } |
| }, |
| |
| show: function(){ |
| // summary: |
| // Display the dialog |
| if(this.open){ return; } |
| |
| // first time we show the dialog, there's some initialization stuff to do |
| if(!this._alreadyInitialized){ |
| this._setup(); |
| this._alreadyInitialized=true; |
| } |
| |
| if(this._fadeOut.status() == "playing"){ |
| this._fadeOut.stop(); |
| } |
| |
| this._modalconnects.push(dojo.connect(window, "onscroll", this, "layout")); |
| this._modalconnects.push(dojo.connect(window, "onresize", this, function(){ |
| // IE gives spurious resize events and can actually get stuck |
| // in an infinite loop if we don't ignore them |
| var viewport = dijit.getViewport(); |
| if(!this._oldViewport || |
| viewport.h != this._oldViewport.h || |
| viewport.w != this._oldViewport.w){ |
| this.layout(); |
| this._oldViewport = viewport; |
| } |
| })); |
| this._modalconnects.push(dojo.connect(dojo.doc.documentElement, "onkeypress", this, "_onKey")); |
| |
| dojo.style(this.domNode, { |
| opacity:0, |
| display:"" |
| }); |
| |
| this.open = true; |
| this._onShow(); // lazy load trigger |
| |
| this._size(); |
| this._position(); |
| dijit._dialogStack.push(this); |
| this._fadeIn.play(); |
| |
| this._savedFocus = dijit.getFocus(this); |
| }, |
| |
| hide: function(){ |
| // summary: |
| // Hide the dialog |
| |
| // if we haven't been initialized yet then we aren't showing and we can just return |
| // or if we aren't the active dialog, don't allow us to close yet |
| var ds = dijit._dialogStack; |
| if(!this._alreadyInitialized || this != ds[ds.length-1]){ |
| return; |
| } |
| |
| if(this._fadeIn.status() == "playing"){ |
| this._fadeIn.stop(); |
| } |
| |
| // throw away current active dialog from stack -- making the previous dialog or the node on the original page active |
| ds.pop(); |
| |
| this._fadeOut.play(); |
| |
| if(this._scrollConnected){ |
| this._scrollConnected = false; |
| } |
| dojo.forEach(this._modalconnects, dojo.disconnect); |
| this._modalconnects = []; |
| |
| if(this._relativePosition){ |
| delete this._relativePosition; |
| } |
| this.open = false; |
| |
| this.onHide(); |
| }, |
| |
| layout: function(){ |
| // summary: |
| // Position the Dialog and the underlay |
| // tags: |
| // private |
| if(this.domNode.style.display != "none"){ |
| if(dijit._underlay){ // avoid race condition during show() |
| dijit._underlay.layout(); |
| } |
| this._position(); |
| } |
| }, |
| |
| destroy: function(){ |
| dojo.forEach(this._modalconnects, dojo.disconnect); |
| if(this.refocus && this.open){ |
| setTimeout(dojo.hitch(dijit,"focus",this._savedFocus), 25); |
| } |
| this.inherited(arguments); |
| }, |
| |
| _onCloseEnter: function(){ |
| // summary: |
| // Called when user hovers over close icon |
| // tags: |
| // private |
| dojo.addClass(this.closeButtonNode, "dijitDialogCloseIcon-hover"); |
| }, |
| |
| _onCloseLeave: function(){ |
| // summary: |
| // Called when user stops hovering over close icon |
| // tags: |
| // private |
| dojo.removeClass(this.closeButtonNode, "dijitDialogCloseIcon-hover"); |
| } |
| } |
| ); |
| |
| dojo.declare( |
| "dijit.Dialog", |
| [dijit.layout.ContentPane, dijit._DialogBase], |
| {} |
| ); |
| |
| // Stack of currenctly displayed dialogs, layered on top of each other |
| dijit._dialogStack = []; |
| |
| // For back-compat. TODO: remove in 2.0 |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit._editor.selection"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit._editor.selection"] = true; |
| dojo.provide("dijit._editor.selection"); |
| |
| // FIXME: |
| // all of these methods branch internally for IE. This is probably |
| // sub-optimal in terms of runtime performance. We should investigate the |
| // size difference for differentiating at definition time. |
| |
| dojo.mixin(dijit._editor.selection, { |
| getType: function(){ |
| // summary: |
| // Get the selection type (like dojo.doc.select.type in IE). |
| if(dojo.isIE){ |
| return dojo.doc.selection.type.toLowerCase(); |
| }else{ |
| var stype = "text"; |
| |
| // Check if the actual selection is a CONTROL (IMG, TABLE, HR, etc...). |
| var oSel; |
| try{ |
| oSel = dojo.global.getSelection(); |
| }catch(e){ /*squelch*/ } |
| |
| if(oSel && oSel.rangeCount == 1){ |
| var oRange = oSel.getRangeAt(0); |
| if( (oRange.startContainer == oRange.endContainer) && |
| ((oRange.endOffset - oRange.startOffset) == 1) && |
| (oRange.startContainer.nodeType != 3 /* text node*/) |
| ){ |
| stype = "control"; |
| } |
| } |
| return stype; //String |
| } |
| }, |
| |
| getSelectedText: function(){ |
| // summary: |
| // Return the text (no html tags) included in the current selection or null if no text is selected |
| if(dojo.isIE){ |
| if(dijit._editor.selection.getType() == 'control'){ |
| return null; |
| } |
| return dojo.doc.selection.createRange().text; |
| }else{ |
| var selection = dojo.global.getSelection(); |
| if(selection){ |
| return selection.toString(); //String |
| } |
| } |
| return ''; |
| }, |
| |
| getSelectedHtml: function(){ |
| // summary: |
| // Return the html text of the current selection or null if unavailable |
| if(dojo.isIE){ |
| if(dijit._editor.selection.getType() == 'control'){ |
| return null; |
| } |
| return dojo.doc.selection.createRange().htmlText; |
| }else{ |
| var selection = dojo.global.getSelection(); |
| if(selection && selection.rangeCount){ |
| var i; |
| var html = ""; |
| for(i = 0; i < selection.rangeCount; i++){ |
| //Handle selections spanning ranges, such as Opera |
| var frag = selection.getRangeAt(i).cloneContents(); |
| var div = dojo.doc.createElement("div"); |
| div.appendChild(frag); |
| html += div.innerHTML; |
| } |
| return html; //String |
| } |
| return null; |
| } |
| }, |
| |
| getSelectedElement: function(){ |
| // summary: |
| // Retrieves the selected element (if any), just in the case that |
| // a single element (object like and image or a table) is |
| // selected. |
| if(dijit._editor.selection.getType() == "control"){ |
| if(dojo.isIE){ |
| var range = dojo.doc.selection.createRange(); |
| if(range && range.item){ |
| return dojo.doc.selection.createRange().item(0); |
| } |
| }else{ |
| var selection = dojo.global.getSelection(); |
| return selection.anchorNode.childNodes[ selection.anchorOffset ]; |
| } |
| } |
| return null; |
| }, |
| |
| getParentElement: function(){ |
| // summary: |
| // Get the parent element of the current selection |
| if(dijit._editor.selection.getType() == "control"){ |
| var p = this.getSelectedElement(); |
| if(p){ return p.parentNode; } |
| }else{ |
| if(dojo.isIE){ |
| var r = dojo.doc.selection.createRange(); |
| r.collapse(true); |
| return r.parentElement(); |
| }else{ |
| var selection = dojo.global.getSelection(); |
| if(selection){ |
| var node = selection.anchorNode; |
| while(node && (node.nodeType != 1)){ // not an element |
| node = node.parentNode; |
| } |
| return node; |
| } |
| } |
| } |
| return null; |
| }, |
| |
| hasAncestorElement: function(/*String*/tagName /* ... */){ |
| // summary: |
| // Check whether current selection has a parent element which is |
| // of type tagName (or one of the other specified tagName) |
| // tagName: String |
| // The tag name to determine if it has an ancestor of. |
| return this.getAncestorElement.apply(this, arguments) != null; //Boolean |
| }, |
| |
| getAncestorElement: function(/*String*/tagName /* ... */){ |
| // summary: |
| // Return the parent element of the current selection which is of |
| // type tagName (or one of the other specified tagName) |
| // tagName: String |
| // The tag name to determine if it has an ancestor of. |
| var node = this.getSelectedElement() || this.getParentElement(); |
| return this.getParentOfType(node, arguments); //DOMNode |
| }, |
| |
| isTag: function(/*DomNode*/ node, /*String[]*/ tags){ |
| // summary: |
| // Function to determine if a node is one of an array of tags. |
| // node: |
| // The node to inspect. |
| // tags: |
| // An array of tag name strings to check to see if the node matches. |
| if(node && node.tagName){ |
| var _nlc = node.tagName.toLowerCase(); |
| for(var i=0; i<tags.length; i++){ |
| var _tlc = String(tags[i]).toLowerCase(); |
| if(_nlc == _tlc){ |
| return _tlc; // String |
| } |
| } |
| } |
| return ""; |
| }, |
| |
| getParentOfType: function(/*DomNode*/ node, /*String[]*/ tags){ |
| // summary: |
| // Function to locate a parent node that matches one of a set of tags |
| // node: |
| // The node to inspect. |
| // tags: |
| // An array of tag name strings to check to see if the node matches. |
| while(node){ |
| if(this.isTag(node, tags).length){ |
| return node; // DOMNode |
| } |
| node = node.parentNode; |
| } |
| return null; |
| }, |
| |
| collapse: function(/*Boolean*/beginning){ |
| // summary: |
| // Function to collapse (clear), the current selection |
| // beginning: Boolean |
| // Boolean to indicate whether to collapse the cursor to the beginning of the selection or end. |
| if(window.getSelection){ |
| var selection = dojo.global.getSelection(); |
| if(selection.removeAllRanges){ // Mozilla |
| if(beginning){ |
| selection.collapseToStart(); |
| }else{ |
| selection.collapseToEnd(); |
| } |
| }else{ // Safari |
| // pulled from WebCore/ecma/kjs_window.cpp, line 2536 |
| selection.collapse(beginning); |
| } |
| }else if(dojo.isIE){ // IE |
| var range = dojo.doc.selection.createRange(); |
| range.collapse(beginning); |
| range.select(); |
| } |
| }, |
| |
| remove: function(){ |
| // summary: |
| // Function to delete the currently selected content from the document. |
| var sel = dojo.doc.selection; |
| if(dojo.isIE){ |
| if(sel.type.toLowerCase() != "none"){ |
| sel.clear(); |
| } |
| return sel; //Selection |
| }else{ |
| sel = dojo.global.getSelection(); |
| sel.deleteFromDocument(); |
| return sel; //Selection |
| } |
| }, |
| |
| selectElementChildren: function(/*DomNode*/element,/*Boolean?*/nochangefocus){ |
| // summary: |
| // clear previous selection and select the content of the node |
| // (excluding the node itself) |
| // element: DOMNode |
| // The element you wish to select the children content of. |
| // nochangefocus: Boolean |
| // Boolean to indicate if the foxus should change or not. |
| var win = dojo.global; |
| var doc = dojo.doc; |
| var range; |
| element = dojo.byId(element); |
| if(doc.selection && dojo.isIE && dojo.body().createTextRange){ // IE |
| range = element.ownerDocument.body.createTextRange(); |
| range.moveToElementText(element); |
| if(!nochangefocus){ |
| try{ |
| range.select(); // IE throws an exception here if the widget is hidden. See #5439 |
| }catch(e){ /* squelch */} |
| } |
| }else if(win.getSelection){ |
| var selection = dojo.global.getSelection(); |
| if(selection.setBaseAndExtent){ // Safari |
| selection.setBaseAndExtent(element, 0, element, element.innerText.length - 1); |
| }else if(dojo.isOpera){ |
| //Opera's selectAllChildren doesn't seem to work right |
| //against <body> nodes and possibly others ... so |
| //we use the W3C range API |
| if(selection.rangeCount){ |
| range = selection.getRangeAt(0); |
| }else{ |
| range = doc.createRange(); |
| } |
| range.setStart(element, 0); |
| range.setEnd(element,(element.nodeType == 3)?element.length:element.childNodes.length); |
| selection.addRange(range); |
| }else if(selection.selectAllChildren){ // Mozilla |
| selection.selectAllChildren(element); |
| } |
| } |
| }, |
| |
| selectElement: function(/*DomNode*/element,/*Boolean?*/nochangefocus){ |
| // summary: |
| // clear previous selection and select element (including all its children) |
| // element: DOMNode |
| // The element to select. |
| // nochangefocus: Boolean |
| // Boolean indicating if the focus should be changed. IE only. |
| var range; |
| var doc = dojo.doc; |
| var win = dojo.global; |
| element = dojo.byId(element); |
| if(dojo.isIE && dojo.body().createTextRange){ |
| try{ |
| range = dojo.body().createControlRange(); |
| range.addElement(element); |
| if(!nochangefocus){ |
| range.select(); |
| } |
| }catch(e){ |
| this.selectElementChildren(element,nochangefocus); |
| } |
| }else if(dojo.global.getSelection){ |
| var selection = win.getSelection(); |
| range = doc.createRange(); |
| if(selection.removeAllRanges){ // Mozilla |
| // FIXME: does this work on Safari? |
| if(dojo.isOpera){ |
| //Opera works if you use the current range on |
| //the selection if present. |
| if(selection.getRangeAt(0)){ |
| range = selection.getRangeAt(0); |
| } |
| } |
| range.selectNode(element); |
| selection.removeAllRanges(); |
| selection.addRange(range); |
| } |
| } |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit._editor.range"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit._editor.range"] = true; |
| dojo.provide("dijit._editor.range"); |
| |
| dijit.range={}; |
| |
| dijit.range.getIndex=function(/*DomNode*/node, /*DomNode*/parent){ |
| // dojo.profile.start("dijit.range.getIndex"); |
| var ret=[], retR=[]; |
| var stop = parent; |
| var onode = node; |
| |
| var pnode, n; |
| while(node != stop){ |
| var i = 0; |
| pnode = node.parentNode; |
| while((n=pnode.childNodes[i++])){ |
| if(n === node){ |
| --i; |
| break; |
| } |
| } |
| if(i>=pnode.childNodes.length){ |
| dojo.debug("Error finding index of a node in dijit.range.getIndex"); |
| } |
| ret.unshift(i); |
| retR.unshift(i-pnode.childNodes.length); |
| node = pnode; |
| } |
| |
| //normalized() can not be called so often to prevent |
| //invalidating selection/range, so we have to detect |
| //here that any text nodes in a row |
| if(ret.length > 0 && onode.nodeType == 3){ |
| n = onode.previousSibling; |
| while(n && n.nodeType == 3){ |
| ret[ret.length-1]--; |
| n = n.previousSibling; |
| } |
| n = onode.nextSibling; |
| while(n && n.nodeType == 3){ |
| retR[retR.length-1]++; |
| n = n.nextSibling; |
| } |
| } |
| // dojo.profile.end("dijit.range.getIndex"); |
| return {o: ret, r:retR}; |
| } |
| |
| dijit.range.getNode = function(/*Array*/index, /*DomNode*/parent){ |
| if(!dojo.isArray(index) || index.length == 0){ |
| return parent; |
| } |
| var node = parent; |
| // if(!node)debugger |
| dojo.every(index, function(i){ |
| if(i >= 0 && i < node.childNodes.length){ |
| node = node.childNodes[i]; |
| }else{ |
| node = null; |
| console.debug('Error: can not find node with index',index,'under parent node',parent ); |
| return false; //terminate dojo.every |
| } |
| return true; //carry on the every loop |
| }); |
| |
| return node; |
| } |
| |
| dijit.range.getCommonAncestor = function(n1,n2){ |
| var getAncestors = function(n){ |
| var as=[]; |
| while(n){ |
| as.unshift(n); |
| if(n.nodeName!='BODY'){ |
| n = n.parentNode; |
| }else{ |
| break; |
| } |
| } |
| return as; |
| }; |
| var n1as = getAncestors(n1); |
| var n2as = getAncestors(n2); |
| |
| var m = Math.min(n1as.length,n2as.length); |
| var com = n1as[0]; //at least, one element should be in the array: the root (BODY by default) |
| for(var i=1;i<m;i++){ |
| if(n1as[i] === n2as[i]){ |
| com = n1as[i] |
| }else{ |
| break; |
| } |
| } |
| return com; |
| } |
| |
| dijit.range.getAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){ |
| root = root || node.ownerDocument.body; |
| while(node && node !== root){ |
| var name = node.nodeName.toUpperCase() ; |
| if(regex.test(name)){ |
| return node; |
| } |
| |
| node = node.parentNode; |
| } |
| return null; |
| } |
| |
| dijit.range.BlockTagNames = /^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/; |
| dijit.range.getBlockAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){ |
| root = root || node.ownerDocument.body; |
| regex = regex || dijit.range.BlockTagNames; |
| var block=null, blockContainer; |
| while(node && node !== root){ |
| var name = node.nodeName.toUpperCase() ; |
| if(!block && regex.test(name)){ |
| block = node; |
| } |
| if(!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)){ |
| blockContainer = node; |
| } |
| |
| node = node.parentNode; |
| } |
| return {blockNode:block, blockContainer:blockContainer || node.ownerDocument.body}; |
| } |
| |
| dijit.range.atBeginningOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){ |
| var atBeginning = false; |
| var offsetAtBeginning = (offset == 0); |
| if(!offsetAtBeginning && node.nodeType == 3){ //if this is a text node, check whether the left part is all space |
| if(/^[\s\xA0]+$/.test(node.nodeValue.substr(0,offset))){ |
| offsetAtBeginning = true; |
| } |
| } |
| if(offsetAtBeginning){ |
| var cnode = node; |
| atBeginning = true; |
| while(cnode && cnode !== container){ |
| if(cnode.previousSibling){ |
| atBeginning = false; |
| break; |
| } |
| cnode = cnode.parentNode; |
| } |
| } |
| return atBeginning; |
| } |
| |
| dijit.range.atEndOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){ |
| var atEnd = false; |
| var offsetAtEnd = (offset == (node.length || node.childNodes.length)); |
| if(!offsetAtEnd && node.nodeType == 3){ //if this is a text node, check whether the right part is all space |
| if(/^[\s\xA0]+$/.test(node.nodeValue.substr(offset))){ |
| offsetAtEnd = true; |
| } |
| } |
| if(offsetAtEnd){ |
| var cnode = node; |
| atEnd = true; |
| while(cnode && cnode !== container){ |
| if(cnode.nextSibling){ |
| atEnd = false; |
| break; |
| } |
| cnode = cnode.parentNode; |
| } |
| } |
| return atEnd; |
| } |
| |
| dijit.range.adjacentNoneTextNode=function(startnode, next){ |
| var node = startnode; |
| var len = (0-startnode.length) || 0; |
| var prop = next?'nextSibling':'previousSibling'; |
| while(node){ |
| if(node.nodeType!=3){ |
| break; |
| } |
| len += node.length |
| node = node[prop]; |
| } |
| return [node,len]; |
| } |
| |
| dijit.range._w3c = Boolean(window['getSelection']); |
| dijit.range.create = function(/*Window?*/win){ |
| if(dijit.range._w3c){ |
| return (win || dojo.global).document.createRange(); |
| }else{//IE |
| return new dijit.range.W3CRange; |
| } |
| } |
| |
| dijit.range.getSelection = function(/*Window*/win, /*Boolean?*/ignoreUpdate){ |
| if(dijit.range._w3c){ |
| return win.getSelection(); |
| }else{//IE |
| var s = new dijit.range.ie.selection(win); |
| if(!ignoreUpdate){ |
| s._getCurrentSelection(); |
| } |
| return s; |
| } |
| } |
| |
| if(!dijit.range._w3c){ |
| dijit.range.ie={ |
| cachedSelection: {}, |
| selection: function(win){ |
| this._ranges = []; |
| this.addRange = function(r, /*boolean*/internal){ |
| this._ranges.push(r); |
| if(!internal){ |
| r._select(); |
| } |
| this.rangeCount = this._ranges.length; |
| }; |
| this.removeAllRanges = function(){ |
| //don't detach, the range may be used later |
| // for(var i=0;i<this._ranges.length;i++){ |
| // this._ranges[i].detach(); |
| // } |
| this._ranges = []; |
| this.rangeCount = 0; |
| }; |
| var _initCurrentRange = function(){ |
| var r = win.document.selection.createRange(); |
| var type=win.document.selection.type.toUpperCase(); |
| if(type == "CONTROL"){ |
| //TODO: multiple range selection(?) |
| return new dijit.range.W3CRange(dijit.range.ie.decomposeControlRange(r)); |
| }else{ |
| return new dijit.range.W3CRange(dijit.range.ie.decomposeTextRange(r)); |
| } |
| }; |
| this.getRangeAt = function(i){ |
| return this._ranges[i]; |
| }; |
| this._getCurrentSelection = function(){ |
| this.removeAllRanges(); |
| var r=_initCurrentRange(); |
| if(r){ |
| this.addRange(r, true); |
| } |
| }; |
| }, |
| decomposeControlRange: function(range){ |
| var firstnode = range.item(0), lastnode = range.item(range.length-1); |
| var startContainer = firstnode.parentNode, endContainer = lastnode.parentNode; |
| var startOffset = dijit.range.getIndex(firstnode, startContainer).o; |
| var endOffset = dijit.range.getIndex(lastnode, endContainer).o+1; |
| return [startContainer, startOffset,endContainer, endOffset]; |
| }, |
| getEndPoint: function(range, end){ |
| var atmrange = range.duplicate(); |
| atmrange.collapse(!end); |
| var cmpstr = 'EndTo' + (end?'End':'Start'); |
| var parentNode = atmrange.parentElement(); |
| |
| var startnode, startOffset, lastNode; |
| if(parentNode.childNodes.length>0){ |
| dojo.every(parentNode.childNodes, function(node,i){ |
| var calOffset; |
| if(node.nodeType != 3){ |
| atmrange.moveToElementText(node); |
| |
| if(atmrange.compareEndPoints(cmpstr,range) > 0){ |
| //startnode = node.previousSibling; |
| if(lastNode && lastNode.nodeType == 3){ |
| //where shall we put the start? in the text node or after? |
| startnode = lastNode; |
| calOffset = true; |
| }else{ |
| startnode = parentNode; |
| startOffset = i; |
| return false; |
| } |
| }else{ |
| if(i == parentNode.childNodes.length-1){ |
| startnode = parentNode; |
| startOffset = parentNode.childNodes.length; |
| return false; |
| } |
| } |
| }else{ |
| if(i == parentNode.childNodes.length-1){//at the end of this node |
| startnode = node; |
| calOffset = true; |
| } |
| } |
| // try{ |
| if(calOffset && startnode){ |
| var prevnode = dijit.range.adjacentNoneTextNode(startnode)[0]; |
| if(prevnode){ |
| startnode = prevnode.nextSibling; |
| }else{ |
| startnode = parentNode.firstChild; //firstChild must be a text node |
| } |
| var prevnodeobj = dijit.range.adjacentNoneTextNode(startnode); |
| prevnode = prevnodeobj[0]; |
| var lenoffset = prevnodeobj[1]; |
| if(prevnode){ |
| atmrange.moveToElementText(prevnode); |
| atmrange.collapse(false); |
| }else{ |
| atmrange.moveToElementText(parentNode); |
| } |
| atmrange.setEndPoint(cmpstr, range); |
| startOffset = atmrange.text.length-lenoffset; |
| |
| return false; |
| } |
| // }catch(e){ debugger } |
| lastNode = node; |
| return true; |
| }); |
| }else{ |
| startnode = parentNode; |
| startOffset = 0; |
| } |
| |
| //if at the end of startnode and we are dealing with start container, then |
| //move the startnode to nextSibling if it is a text node |
| //TODO: do this for end container? |
| if(!end && startnode.nodeType == 1 && startOffset == startnode.childNodes.length){ |
| var nextnode=startnode.nextSibling; |
| if(nextnode && nextnode.nodeType == 3){ |
| startnode = nextnode; |
| startOffset = 0; |
| } |
| } |
| return [startnode, startOffset]; |
| }, |
| setEndPoint: function(range, container, offset){ |
| //text node |
| var atmrange = range.duplicate(), node, len; |
| if(container.nodeType!=3){ //normal node |
| if(offset > 0){ |
| node = container.childNodes[offset-1]; |
| if(node.nodeType == 3){ |
| container = node; |
| offset = node.length; |
| //pass through |
| }else{ |
| if(node.nextSibling && node.nextSibling.nodeType == 3){ |
| container=node.nextSibling; |
| offset=0; |
| //pass through |
| }else{ |
| atmrange.moveToElementText(node.nextSibling?node:container); |
| var parent = node.parentNode; |
| var tempNode = parent.insertBefore(node.ownerDocument.createTextNode(' '), node.nextSibling); |
| atmrange.collapse(false); |
| parent.removeChild(tempNode); |
| } |
| } |
| }else{ |
| atmrange.moveToElementText(container); |
| atmrange.collapse(true); |
| } |
| } |
| if(container.nodeType == 3){ |
| var prevnodeobj = dijit.range.adjacentNoneTextNode(container); |
| var prevnode = prevnodeobj[0]; |
| len = prevnodeobj[1]; |
| if(prevnode){ |
| atmrange.moveToElementText(prevnode); |
| atmrange.collapse(false); |
| //if contentEditable is not inherit, the above collapse won't make the end point |
| //in the correctly position: it always has a -1 offset, so compensate it |
| if(prevnode.contentEditable!='inherit'){ |
| len++; |
| } |
| }else{ |
| atmrange.moveToElementText(container.parentNode); |
| atmrange.collapse(true); |
| } |
| |
| offset += len; |
| if(offset>0){ |
| if(atmrange.move('character',offset) != offset){ |
| console.error('Error when moving!'); |
| } |
| } |
| } |
| |
| return atmrange; |
| }, |
| decomposeTextRange: function(range){ |
| var tmpary = dijit.range.ie.getEndPoint(range); |
| var startContainer = tmpary[0], startOffset = tmpary[1]; |
| var endContainer = tmpary[0], endOffset = tmpary[1]; |
| |
| if(range.htmlText.length){ |
| if(range.htmlText == range.text){ //in the same text node |
| endOffset = startOffset+range.text.length; |
| }else{ |
| tmpary = dijit.range.ie.getEndPoint(range,true); |
| endContainer = tmpary[0], endOffset = tmpary[1]; |
| // if(startContainer.tagName == "BODY"){ |
| // startContainer = startContainer.firstChild; |
| // } |
| } |
| } |
| return [startContainer, startOffset, endContainer, endOffset]; |
| }, |
| setRange: function(range, startContainer, |
| startOffset, endContainer, endOffset, collapsed){ |
| var start=dijit.range.ie.setEndPoint(range, startContainer, startOffset); |
| |
| range.setEndPoint('StartToStart',start); |
| if(!collapsed){ |
| var end=dijit.range.ie.setEndPoint(range, endContainer, endOffset); |
| } |
| range.setEndPoint('EndToEnd',end || start); |
| |
| return range; |
| } |
| } |
| |
| dojo.declare("dijit.range.W3CRange",null, { |
| constructor: function(){ |
| if(arguments.length>0){ |
| this.setStart(arguments[0][0],arguments[0][1]); |
| this.setEnd(arguments[0][2],arguments[0][3]); |
| }else{ |
| this.commonAncestorContainer = null; |
| this.startContainer = null; |
| this.startOffset = 0; |
| this.endContainer = null; |
| this.endOffset = 0; |
| this.collapsed = true; |
| } |
| }, |
| _updateInternal: function(){ |
| if(this.startContainer !== this.endContainer){ |
| this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer); |
| }else{ |
| this.commonAncestorContainer = this.startContainer; |
| } |
| this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset); |
| }, |
| setStart: function(node, offset){ |
| offset=parseInt(offset); |
| if(this.startContainer === node && this.startOffset == offset){ |
| return; |
| } |
| delete this._cachedBookmark; |
| |
| this.startContainer = node; |
| this.startOffset = offset; |
| if(!this.endContainer){ |
| this.setEnd(node, offset); |
| }else{ |
| this._updateInternal(); |
| } |
| }, |
| setEnd: function(node, offset){ |
| offset=parseInt(offset); |
| if(this.endContainer === node && this.endOffset == offset){ |
| return; |
| } |
| delete this._cachedBookmark; |
| |
| this.endContainer = node; |
| this.endOffset = offset; |
| if(!this.startContainer){ |
| this.setStart(node, offset); |
| }else{ |
| this._updateInternal(); |
| } |
| }, |
| setStartAfter: function(node, offset){ |
| this._setPoint('setStart', node, offset, 1); |
| }, |
| setStartBefore: function(node, offset){ |
| this._setPoint('setStart', node, offset, 0); |
| }, |
| setEndAfter: function(node, offset){ |
| this._setPoint('setEnd', node, offset, 1); |
| }, |
| setEndBefore: function(node, offset){ |
| this._setPoint('setEnd', node, offset, 0); |
| }, |
| _setPoint: function(what, node, offset, ext){ |
| var index = dijit.range.getIndex(node, node.parentNode).o; |
| this[what](node.parentNode, index.pop()+ext); |
| }, |
| _getIERange: function(){ |
| var r = (this._body || this.endContainer.ownerDocument.body).createTextRange(); |
| dijit.range.ie.setRange(r, this.startContainer, this.startOffset, this.endContainer, this.endOffset, this.collapsed); |
| return r; |
| }, |
| getBookmark: function(body){ |
| this._getIERange(); |
| return this._cachedBookmark; |
| }, |
| _select: function(){ |
| var r = this._getIERange(); |
| r.select(); |
| }, |
| deleteContents: function(){ |
| var r = this._getIERange(); |
| r.pasteHTML(''); |
| this.endContainer = this.startContainer; |
| this.endOffset = this.startOffset; |
| this.collapsed = true; |
| }, |
| cloneRange: function(){ |
| var r = new dijit.range.W3CRange([this.startContainer,this.startOffset, |
| this.endContainer,this.endOffset]); |
| r._body = this._body; |
| return r; |
| }, |
| detach: function(){ |
| this._body = null; |
| this.commonAncestorContainer = null; |
| this.startContainer = null; |
| this.startOffset = 0; |
| this.endContainer = null; |
| this.endOffset = 0; |
| this.collapsed = true; |
| } |
| }); |
| } //if(!dijit.range._w3c) |
| |
| } |
| |
| if(!dojo._hasResource["dijit._editor.html"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit._editor.html"] = true; |
| dojo.provide("dijit._editor.html"); |
| |
| dijit._editor.escapeXml=function(/*String*/str, /*Boolean?*/noSingleQuotes){ |
| // summary: |
| // Adds escape sequences for special characters in XML: &<>"' |
| // Optionally skips escapes for single quotes |
| str = str.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """); |
| if(!noSingleQuotes){ |
| str = str.replace(/'/gm, "'"); |
| } |
| return str; // string |
| }; |
| |
| dijit._editor.getNodeHtml=function(/* DomNode */node){ |
| var output; |
| switch(node.nodeType){ |
| case 1: //element node |
| var lName = node.nodeName.toLowerCase(); |
| if(lName.charAt(0) == "/"){ |
| // IE does some strange things with malformed HTML input, like |
| // treating a close tag </span> without an open tag <span>, as |
| // a new tag with tagName of /span. Corrupts output HTML, remove |
| // them. Other browsers don't prefix tags that way, so will |
| // never show up. |
| return ""; |
| } |
| output = '<' + lName; |
| |
| //store the list of attributes and sort it to have the |
| //attributes appear in the dictionary order |
| var attrarray = []; |
| var attr; |
| if(dojo.isIE && node.outerHTML){ |
| var s = node.outerHTML; |
| s = s.substr(0, s.indexOf('>')) |
| .replace(/(['"])[^"']*\1/g, ''); //to make the following regexp safe |
| var reg = /(\b\w+)\s?=/g; |
| var m, key; |
| while((m = reg.exec(s))){ |
| key = m[1]; |
| if(key.substr(0,3) != '_dj'){ |
| if(key == 'src' || key == 'href'){ |
| if(node.getAttribute('_djrealurl')){ |
| attrarray.push([key,node.getAttribute('_djrealurl')]); |
| continue; |
| } |
| } |
| var val, match; |
| switch(key){ |
| case 'style': |
| val = node.style.cssText.toLowerCase(); |
| break; |
| case 'class': |
| val = node.className; |
| break; |
| case 'width': |
| if(lName === "img"){ |
| // This somehow gets lost on IE for IMG tags and the like |
| // and we have to find it in outerHTML, known IE oddity. |
| match=/width=(\S+)/i.exec(s); |
| if(match){ |
| val = match[1]; |
| } |
| break; |
| } |
| case 'height': |
| if(lName === "img"){ |
| // This somehow gets lost on IE for IMG tags and the like |
| // and we have to find it in outerHTML, known IE oddity. |
| match=/height=(\S+)/i.exec(s); |
| if(match){ |
| val = match[1]; |
| } |
| break; |
| } |
| default: |
| val = node.getAttribute(key); |
| } |
| if(val != null){ |
| attrarray.push([key, val.toString()]); |
| } |
| } |
| } |
| }else{ |
| var i = 0; |
| while((attr = node.attributes[i++])){ |
| //ignore all attributes starting with _dj which are |
| //internal temporary attributes used by the editor |
| var n = attr.name; |
| if(n.substr(0,3) != '_dj' /*&& |
| (attr.specified == undefined || attr.specified)*/){ |
| var v = attr.value; |
| if(n == 'src' || n == 'href'){ |
| if(node.getAttribute('_djrealurl')){ |
| v = node.getAttribute('_djrealurl'); |
| } |
| } |
| attrarray.push([n,v]); |
| } |
| } |
| } |
| attrarray.sort(function(a,b){ |
| return a[0] < b[0] ? -1 : (a[0] == b[0] ? 0 : 1); |
| }); |
| var j = 0; |
| while((attr = attrarray[j++])){ |
| output += ' ' + attr[0] + '="' + |
| (dojo.isString(attr[1]) ? dijit._editor.escapeXml(attr[1], true) : attr[1]) + '"'; |
| } |
| if(lName === "script"){ |
| // Browsers handle script tags differently in how you get content, |
| // but innerHTML always seems to work, so insert its content that way |
| // Yes, it's bad to allow script tags in the editor code, but some people |
| // seem to want to do it, so we need to at least return them right. |
| // other plugins/filters can strip them. |
| output += '>' + node.innerHTML +'</' + lName + '>'; |
| }else{ |
| if(node.childNodes.length){ |
| output += '>' + dijit._editor.getChildrenHtml(node)+'</' + lName +'>'; |
| }else{ |
| switch(lName){ |
| case 'br': |
| case 'hr': |
| case 'img': |
| case 'input': |
| case 'base': |
| case 'meta': |
| case 'area': |
| case 'basefont': |
| // These should all be singly closed |
| output += ' />'; |
| break; |
| default: |
| // Assume XML style separate closure for everything else. |
| output += '></' + lName + '>'; |
| } |
| } |
| } |
| break; |
| case 4: // cdata |
| case 3: // text |
| // FIXME: |
| output = dijit._editor.escapeXml(node.nodeValue, true); |
| break; |
| case 8: //comment |
| // FIXME: |
| output = '<!--' + dijit._editor.escapeXml(node.nodeValue, true) + '-->'; |
| break; |
| default: |
| output = "<!-- Element not recognized - Type: " + node.nodeType + " Name: " + node.nodeName + "-->"; |
| } |
| return output; |
| }; |
| |
| dijit._editor.getChildrenHtml = function(/* DomNode */dom){ |
| // summary: |
| // Returns the html content of a DomNode and children |
| var out = ""; |
| if(!dom){ return out; } |
| var nodes = dom["childNodes"] || dom; |
| |
| //IE issue. |
| //If we have an actual node we can check parent relationships on for IE, |
| //We should check, as IE sometimes builds invalid DOMS. If no parent, we can't check |
| //And should just process it and hope for the best. |
| var checkParent = !dojo.isIE || nodes !== dom; |
| |
| var node, i = 0; |
| while((node = nodes[i++])){ |
| //IE is broken. DOMs are supposed to be a tree. But in the case of malformed HTML, IE generates a graph |
| //meaning one node ends up with multiple references (multiple parents). This is totally wrong and invalid, but |
| //such is what it is. We have to keep track and check for this because otherise the source output HTML will have dups. |
| //No other browser generates a graph. Leave it to IE to break a fundamental DOM rule. So, we check the parent if we can |
| //If we can't, nothing more we can do other than walk it. |
| if(!checkParent || node.parentNode == dom){ |
| out += dijit._editor.getNodeHtml(node); |
| } |
| } |
| return out; // String |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dijit._editor.RichText"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit._editor.RichText"] = true; |
| dojo.provide("dijit._editor.RichText"); |
| |
| |
| |
| |
| |
| |
| // used to restore content when user leaves this page then comes back |
| // but do not try doing dojo.doc.write if we are using xd loading. |
| // dojo.doc.write will only work if RichText.js is included in the dojo.js |
| // file. If it is included in dojo.js and you want to allow rich text saving |
| // for back/forward actions, then set dojo.config.allowXdRichTextSave = true. |
| if(!dojo.config["useXDomain"] || dojo.config["allowXdRichTextSave"]){ |
| if(dojo._postLoad){ |
| (function(){ |
| var savetextarea = dojo.doc.createElement('textarea'); |
| savetextarea.id = dijit._scopeName + "._editor.RichText.savedContent"; |
| dojo.style(savetextarea, { |
| display:'none', |
| position:'absolute', |
| top:"-100px", |
| height:"3px", |
| width:"3px" |
| }); |
| dojo.body().appendChild(savetextarea); |
| })(); |
| }else{ |
| //dojo.body() is not available before onLoad is fired |
| try{ |
| dojo.doc.write('<textarea id="' + dijit._scopeName + '._editor.RichText.savedContent" ' + |
| 'style="display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"></textarea>'); |
| }catch(e){ } |
| } |
| } |
| |
| dojo.declare("dijit._editor.RichText", dijit._Widget, { |
| constructor: function(params){ |
| // summary: |
| // dijit._editor.RichText is the core of dijit.Editor, which provides basic |
| // WYSIWYG editing features. |
| // |
| // description: |
| // dijit._editor.RichText is the core of dijit.Editor, which provides basic |
| // WYSIWYG editing features. It also encapsulates the differences |
| // of different js engines for various browsers. Do not use this widget |
| // with an HTML <TEXTAREA> tag, since the browser unescapes XML escape characters, |
| // like <. This can have unexpected behavior and lead to security issues |
| // such as scripting attacks. |
| // |
| // tags: |
| // private |
| |
| // contentPreFilters: Function(String)[] |
| // Pre content filter function register array. |
| // these filters will be executed before the actual |
| // editing area gets the html content. |
| this.contentPreFilters = []; |
| |
| // contentPostFilters: Function(String)[] |
| // post content filter function register array. |
| // These will be used on the resulting html |
| // from contentDomPostFilters. The resulting |
| // content is the final html (returned by getValue()). |
| this.contentPostFilters = []; |
| |
| // contentDomPreFilters: Function(DomNode)[] |
| // Pre content dom filter function register array. |
| // These filters are applied after the result from |
| // contentPreFilters are set to the editing area. |
| this.contentDomPreFilters = []; |
| |
| // contentDomPostFilters: Function(DomNode)[] |
| // Post content dom filter function register array. |
| // These filters are executed on the editing area dom. |
| // The result from these will be passed to contentPostFilters. |
| this.contentDomPostFilters = []; |
| |
| // editingAreaStyleSheets: dojo._URL[] |
| // array to store all the stylesheets applied to the editing area |
| this.editingAreaStyleSheets = []; |
| |
| // Make a copy of this.events before we start writing into it, otherwise we |
| // will modify the prototype which leads to bad things on pages w/multiple editors |
| this.events = [].concat(this.events); |
| |
| this._keyHandlers = {}; |
| this.contentPreFilters.push(dojo.hitch(this, "_preFixUrlAttributes")); |
| if(dojo.isMoz){ |
| this.contentPreFilters.push(this._normalizeFontStyle); |
| this.contentPostFilters.push(this._removeMozBogus); |
| } |
| if(dojo.isWebKit){ |
| // Try to clean up WebKit bogus artifacts. The inserted classes |
| // made by WebKit sometimes messes things up. |
| this.contentPreFilters.push(this._removeWebkitBogus); |
| this.contentPostFilters.push(this._removeWebkitBogus); |
| } |
| if(dojo.isIE){ |
| // IE generates <strong> and <em> but we want to normalize to <b> and <i> |
| this.contentPostFilters.push(this._normalizeFontStyle); |
| } |
| //this.contentDomPostFilters.push(this._postDomFixUrlAttributes); |
| |
| this.onLoadDeferred = new dojo.Deferred(); |
| }, |
| |
| // inheritWidth: Boolean |
| // whether to inherit the parent's width or simply use 100% |
| inheritWidth: false, |
| |
| // focusOnLoad: [deprecated] Boolean |
| // Focus into this widget when the page is loaded |
| focusOnLoad: false, |
| |
| // name: String? |
| // Specifies the name of a (hidden) <textarea> node on the page that's used to save |
| // the editor content on page leave. Used to restore editor contents after navigating |
| // to a new page and then hitting the back button. |
| name: "", |
| |
| // styleSheets: [const] String |
| // semicolon (";") separated list of css files for the editing area |
| styleSheets: "", |
| |
| // _content: [private] String |
| // temporary content storage |
| _content: "", |
| |
| // height: String |
| // Set height to fix the editor at a specific height, with scrolling. |
| // By default, this is 300px. If you want to have the editor always |
| // resizes to accommodate the content, use AlwaysShowToolbar plugin |
| // and set height="". If this editor is used within a layout widget, |
| // set height="100%". |
| height: "300px", |
| |
| // minHeight: String |
| // The minimum height that the editor should have. |
| minHeight: "1em", |
| |
| // isClosed: [private] Boolean |
| isClosed: true, |
| |
| // isLoaded: [private] Boolean |
| isLoaded: false, |
| |
| // _SEPARATOR: [private] String |
| // Used to concat contents from multiple editors into a single string, |
| // so they can be saved into a single <textarea> node. See "name" attribute. |
| _SEPARATOR: "@@**%%__RICHTEXTBOUNDRY__%%**@@", |
| |
| // onLoadDeferred: [protected] dojo.Deferred |
| // Deferred which is fired when the editor finishes loading |
| onLoadDeferred: null, |
| |
| // isTabIndent: Boolean |
| // Make tab key and shift-tab indent and outdent rather than navigating. |
| // Caution: sing this makes web pages inaccessible to users unable to use a mouse. |
| isTabIndent: false, |
| |
| // disableSpellCheck: [const] Boolean |
| // When true, disables the browser's native spell checking, if supported. |
| // Works only in Firefox. |
| disableSpellCheck: false, |
| |
| postCreate: function(){ |
| if("textarea" == this.domNode.tagName.toLowerCase()){ |
| console.warn("RichText should not be used with the TEXTAREA tag. See dijit._editor.RichText docs."); |
| } |
| dojo.publish(dijit._scopeName + "._editor.RichText::init", [this]); |
| this.open(); |
| this.setupDefaultShortcuts(); |
| }, |
| |
| setupDefaultShortcuts: function(){ |
| // summary: |
| // Add some default key handlers |
| // description: |
| // Overwrite this to setup your own handlers. The default |
| // implementation does not use Editor commands, but directly |
| // executes the builtin commands within the underlying browser |
| // support. |
| // tags: |
| // protected |
| var exec = dojo.hitch(this, function(cmd, arg){ |
| return function(){ |
| return !this.execCommand(cmd,arg); |
| }; |
| }); |
| |
| var ctrlKeyHandlers = { |
| b: exec("bold"), |
| i: exec("italic"), |
| u: exec("underline"), |
| a: exec("selectall"), |
| s: function(){ this.save(true); }, |
| m: function(){ this.isTabIndent = !this.isTabIndent; }, |
| |
| "1": exec("formatblock", "h1"), |
| "2": exec("formatblock", "h2"), |
| "3": exec("formatblock", "h3"), |
| "4": exec("formatblock", "h4"), |
| |
| "\\": exec("insertunorderedlist") |
| }; |
| |
| if(!dojo.isIE){ |
| ctrlKeyHandlers.Z = exec("redo"); //FIXME: undo? |
| } |
| |
| for(var key in ctrlKeyHandlers){ |
| this.addKeyHandler(key, true, false, ctrlKeyHandlers[key]); |
| } |
| }, |
| |
| // events: [private] String[] |
| // events which should be connected to the underlying editing area |
| events: ["onKeyPress", "onKeyDown", "onKeyUp", "onClick"], |
| |
| // captureEvents: [deprecated] String[] |
| // Events which should be connected to the underlying editing |
| // area, events in this array will be addListener with |
| // capture=true. |
| // TODO: looking at the code I don't see any distinction between events and captureEvents, |
| // so get rid of this for 2.0 if not sooner |
| captureEvents: [], |
| |
| _editorCommandsLocalized: false, |
| _localizeEditorCommands: function(){ |
| // summary: |
| // When IE is running in a non-English locale, the API actually changes, |
| // so that we have to say (for example) danraku instead of p (for paragraph). |
| // Handle that here. |
| // tags: |
| // private |
| if(this._editorCommandsLocalized){ |
| return; |
| } |
| this._editorCommandsLocalized = true; |
| |
| //in IE, names for blockformat is locale dependent, so we cache the values here |
| |
| //if the normal way fails, we try the hard way to get the list |
| |
| //do not use _cacheLocalBlockFormatNames here, as it will |
| //trigger security warning in IE7 |
| |
| //put p after div, so if IE returns Normal, we show it as paragraph |
| //We can distinguish p and div if IE returns Normal, however, in order to detect that, |
| //we have to call this.document.selection.createRange().parentElement() or such, which |
| //could slow things down. Leave it as it is for now |
| var formats = ['div', 'p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'address']; |
| var localhtml = "", format, i=0; |
| while((format=formats[i++])){ |
| //append a <br> after each element to separate the elements more reliably |
| if(format.charAt(1) != 'l'){ |
| localhtml += "<"+format+"><span>content</span></"+format+"><br/>"; |
| }else{ |
| localhtml += "<"+format+"><li>content</li></"+format+"><br/>"; |
| } |
| } |
| //queryCommandValue returns empty if we hide editNode, so move it out of screen temporary |
| var div = dojo.doc.createElement('div'); |
| dojo.style(div, { |
| position: "absolute", |
| top: "-2000px" |
| }); |
| dojo.doc.body.appendChild(div); |
| div.innerHTML = localhtml; |
| var node = div.firstChild; |
| while(node){ |
| dijit._editor.selection.selectElement(node.firstChild); |
| dojo.withGlobal(this.window, "selectElement", dijit._editor.selection, [node.firstChild]); |
| var nativename = node.tagName.toLowerCase(); |
| this._local2NativeFormatNames[nativename] = document.queryCommandValue("formatblock"); |
| //this.queryCommandValue("formatblock"); |
| this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename; |
| node = node.nextSibling.nextSibling; |
| } |
| dojo.body().removeChild(div); |
| }, |
| |
| open: function(/*DomNode?*/ element){ |
| // summary: |
| // Transforms the node referenced in this.domNode into a rich text editing |
| // node. |
| // description: |
| // Sets up the editing area asynchronously. This will result in |
| // the creation and replacement with an <iframe>. |
| // |
| // A dojo.Deferred object is created at this.onLoadDeferred, and |
| // users may attach to it to be informed when the rich-text area |
| // initialization is finalized. |
| // tags: |
| // private |
| |
| if(!this.onLoadDeferred || this.onLoadDeferred.fired >= 0){ |
| this.onLoadDeferred = new dojo.Deferred(); |
| } |
| |
| if(!this.isClosed){ this.close(); } |
| dojo.publish(dijit._scopeName + "._editor.RichText::open", [ this ]); |
| |
| this._content = ""; |
| if(arguments.length == 1 && element.nodeName){ // else unchanged |
| this.domNode = element; |
| } |
| |
| var dn = this.domNode; |
| |
| // "html" will hold the innerHTML of the srcNodeRef and will be used to |
| // initialize the editor. |
| var html; |
| |
| if(dn.nodeName && dn.nodeName.toLowerCase() == "textarea"){ |
| // if we were created from a textarea, then we need to create a |
| // new editing harness node. |
| var ta = (this.textarea = dn); |
| this.name = ta.name; |
| html = ta.value; |
| dn = this.domNode = dojo.doc.createElement("div"); |
| dn.setAttribute('widgetId', this.id); |
| ta.removeAttribute('widgetId'); |
| dn.cssText = ta.cssText; |
| dn.className += " " + ta.className; |
| dojo.place(dn, ta, "before"); |
| var tmpFunc = dojo.hitch(this, function(){ |
| //some browsers refuse to submit display=none textarea, so |
| //move the textarea off screen instead |
| dojo.style(ta, { |
| display: "block", |
| position: "absolute", |
| top: "-1000px" |
| }); |
| |
| if(dojo.isIE){ //nasty IE bug: abnormal formatting if overflow is not hidden |
| var s = ta.style; |
| this.__overflow = s.overflow; |
| s.overflow = "hidden"; |
| } |
| }); |
| if(dojo.isIE){ |
| setTimeout(tmpFunc, 10); |
| }else{ |
| tmpFunc(); |
| } |
| |
| if(ta.form){ |
| dojo.connect(ta.form, "onsubmit", this, function(){ |
| // FIXME: should we be calling close() here instead? |
| ta.value = this.getValue(); |
| }); |
| } |
| }else{ |
| html = dijit._editor.getChildrenHtml(dn); |
| dn.innerHTML = ""; |
| } |
| |
| var content = dojo.contentBox(dn); |
| this._oldHeight = content.h; |
| this._oldWidth = content.w; |
| |
| this.savedContent = html; |
| |
| // If we're a list item we have to put in a blank line to force the |
| // bullet to nicely align at the top of text |
| if(dn.nodeName && dn.nodeName == "LI"){ |
| dn.innerHTML = " <br>"; |
| } |
| |
| this.editingArea = dn.ownerDocument.createElement("div"); |
| dn.appendChild(this.editingArea); |
| |
| // User has pressed back/forward button so we lost the text in the editor, but it's saved |
| // in a hidden <textarea> (which contains the data for all the editors on this page), |
| // so get editor value from there |
| if(this.name !== "" && (!dojo.config["useXDomain"] || dojo.config["allowXdRichTextSave"])){ |
| var saveTextarea = dojo.byId(dijit._scopeName + "._editor.RichText.savedContent"); |
| if(saveTextarea.value !== ""){ |
| var datas = saveTextarea.value.split(this._SEPARATOR), i=0, dat; |
| while((dat=datas[i++])){ |
| var data = dat.split(":"); |
| if(data[0] == this.name){ |
| html = data[1]; |
| datas.splice(i, 1); // TODO: this has no effect |
| break; |
| } |
| } |
| } |
| |
| // TODO: this is troublesome if this editor has been destroyed, should have global handler. |
| // TODO: need to clear <textarea> in global handler |
| dojo.addOnUnload(dojo.hitch(this, "_saveContent")); |
| } |
| |
| this.isClosed = false; |
| |
| var ifr = (this.editorObject = this.iframe = dojo.doc.createElement('iframe')); |
| ifr.id = this.id+"_iframe"; |
| this._iframeSrc = this._getIframeDocTxt(); |
| ifr.style.border = "none"; |
| ifr.style.width = "100%"; |
| if(this._layoutMode){ |
| // iframe should be 100% height, thus getting it's height from surrounding |
| // <div> (which has the correct height set by Editor) |
| ifr.style.height = "100%"; |
| }else{ |
| if(dojo.isIE >= 7){ |
| if(this.height){ |
| ifr.style.height = this.height; |
| } |
| if(this.minHeight){ |
| ifr.style.minHeight = this.minHeight; |
| } |
| }else{ |
| ifr.style.height = this.height ? this.height : this.minHeight; |
| } |
| } |
| ifr.frameBorder = 0; |
| ifr._loadFunc = dojo.hitch( this, function(win){ |
| this.window = win; |
| this.document = this.window.document; |
| |
| if(dojo.isIE){ |
| this._localizeEditorCommands(); |
| } |
| |
| // Do final setup and set initial contents of editor |
| this.onLoad(html); |
| |
| this.savedContent = this.getValue(true); |
| }); |
| |
| // Set the iframe's initial (blank) content. |
| var s = 'javascript:parent.' + dijit._scopeName + '.byId("'+this.id+'")._iframeSrc'; |
| ifr.setAttribute('src', s); |
| this.editingArea.appendChild(ifr); |
| |
| if(dojo.isSafari){ // Safari seems to always append iframe with src=about:blank |
| setTimeout(function(){ifr.setAttribute('src', s);},0); |
| } |
| |
| // TODO: this is a guess at the default line-height, kinda works |
| if(dn.nodeName == "LI"){ |
| dn.lastChild.style.marginTop = "-1.2em"; |
| } |
| |
| dojo.addClass(this.domNode, "RichTextEditable"); |
| }, |
| |
| //static cache variables shared among all instance of this class |
| _local2NativeFormatNames: {}, |
| _native2LocalFormatNames: {}, |
| |
| _getIframeDocTxt: function(){ |
| // summary: |
| // Generates the boilerplate text of the document inside the iframe (ie, <html><head>...</head><body/></html>). |
| // Editor content (if not blank) should be added afterwards. |
| // tags: |
| // private |
| var _cs = dojo.getComputedStyle(this.domNode); |
| |
| // The contents inside of <body>. The real contents are set later via a call to setValue(). |
| var html = ""; |
| if(dojo.isIE || (!this.height && !dojo.isMoz)){ |
| // In auto-expand mode, need a wrapper div for AlwaysShowToolbar plugin to correctly |
| // expand/contract the editor as the content changes. |
| html = "<div></div>"; |
| }else if(dojo.isMoz){ |
| // workaround bug where can't select then delete text (until user types something |
| // into the editor)... and/or issue where typing doesn't erase selected text |
| this._cursorToStart = true; |
| html = " "; |
| } |
| |
| var font = [ _cs.fontWeight, _cs.fontSize, _cs.fontFamily ].join(" "); |
| |
| // line height is tricky - applying a units value will mess things up. |
| // if we can't get a non-units value, bail out. |
| var lineHeight = _cs.lineHeight; |
| if(lineHeight.indexOf("px") >= 0){ |
| lineHeight = parseFloat(lineHeight)/parseFloat(_cs.fontSize); |
| // console.debug(lineHeight); |
| }else if(lineHeight.indexOf("em")>=0){ |
| lineHeight = parseFloat(lineHeight); |
| }else{ |
| // If we can't get a non-units value, just default |
| // it to the CSS spec default of 'normal'. Seems to |
| // work better, esp on IE, than '1.0' |
| lineHeight = "normal"; |
| } |
| var userStyle = ""; |
| this.style.replace(/(^|;)(line-|font-?)[^;]+/g, function(match){ userStyle += match.replace(/^;/g,"") + ';'; }); |
| |
| // need to find any associated label element and update iframe document title |
| var label=dojo.query('label[for="'+this.id+'"]'); |
| |
| return [ |
| this.isLeftToRight() ? "<html><head>" : "<html dir='rtl'><head>", |
| (dojo.isMoz && label.length ? "<title>" + label[0].innerHTML + "</title>" : ""), |
| "<meta http-equiv='Content-Type' content='text/html'>", |
| "<style>", |
| "body,html {", |
| "\tbackground:transparent;", |
| "\tpadding: 1px 0 0 0;", |
| "\tmargin: -1px 0 0 0;", // remove extraneous vertical scrollbar on safari and firefox |
| (dojo.isWebKit?"\twidth: 100%;":""), |
| (dojo.isWebKit?"\theight: 100%;":""), |
| "}", |
| // TODO: left positioning will cause contents to disappear out of view |
| // if it gets too wide for the visible area |
| "body{", |
| "\ttop:0px; left:0px; right:0px;", |
| "\tfont:", font, ";", |
| ((this.height||dojo.isOpera) ? "" : "position: fixed;"), |
| // FIXME: IE 6 won't understand min-height? |
| "\tmin-height:", this.minHeight, ";", |
| "\tline-height:", lineHeight, |
| "}", |
| "p{ margin: 1em 0; }", |
| (this.height ? // height:auto undoes the height:100% |
| "" : "body,html{overflow-y:hidden;/*for IE*/} body > div {overflow-x:auto;/*FF:horizontal scrollbar*/ overflow-y:hidden;/*safari*/ min-height:"+this.minHeight+";/*safari*/}" |
| ), |
| "li > ul:-moz-first-node, li > ol:-moz-first-node{ padding-top: 1.2em; } ", |
| "li{ min-height:1.2em; }", |
| "</style>", |
| this._applyEditingAreaStyleSheets(), |
| "</head><body onload='frameElement._loadFunc(window,document)' style='"+userStyle+"'>", html, "</body></html>" |
| ].join(""); // String |
| }, |
| |
| _applyEditingAreaStyleSheets: function(){ |
| // summary: |
| // apply the specified css files in styleSheets |
| // tags: |
| // private |
| var files = []; |
| if(this.styleSheets){ |
| files = this.styleSheets.split(';'); |
| this.styleSheets = ''; |
| } |
| |
| //empty this.editingAreaStyleSheets here, as it will be filled in addStyleSheet |
| files = files.concat(this.editingAreaStyleSheets); |
| this.editingAreaStyleSheets = []; |
| |
| var text='', i=0, url; |
| while((url=files[i++])){ |
| var abstring = (new dojo._Url(dojo.global.location, url)).toString(); |
| this.editingAreaStyleSheets.push(abstring); |
| text += '<link rel="stylesheet" type="text/css" href="'+abstring+'"/>'; |
| } |
| return text; |
| }, |
| |
| addStyleSheet: function(/*dojo._Url*/ uri){ |
| // summary: |
| // add an external stylesheet for the editing area |
| // uri: |
| // A dojo.uri.Uri pointing to the url of the external css file |
| var url=uri.toString(); |
| |
| //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe |
| if(url.charAt(0) == '.' || (url.charAt(0) != '/' && !uri.host)){ |
| url = (new dojo._Url(dojo.global.location, url)).toString(); |
| } |
| |
| if(dojo.indexOf(this.editingAreaStyleSheets, url) > -1){ |
| // console.debug("dijit._editor.RichText.addStyleSheet: Style sheet "+url+" is already applied"); |
| return; |
| } |
| |
| this.editingAreaStyleSheets.push(url); |
| this.onLoadDeferred.addCallback(dojo.hitch(function(){ |
| if(this.document.createStyleSheet){ //IE |
| this.document.createStyleSheet(url); |
| }else{ //other browser |
| var head = this.document.getElementsByTagName("head")[0]; |
| var stylesheet = this.document.createElement("link"); |
| stylesheet.rel="stylesheet"; |
| stylesheet.type="text/css"; |
| stylesheet.href=url; |
| head.appendChild(stylesheet); |
| } |
| })); |
| }, |
| |
| removeStyleSheet: function(/*dojo._Url*/ uri){ |
| // summary: |
| // remove an external stylesheet for the editing area |
| var url=uri.toString(); |
| //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe |
| if(url.charAt(0) == '.' || (url.charAt(0) != '/' && !uri.host)){ |
| url = (new dojo._Url(dojo.global.location, url)).toString(); |
| } |
| var index = dojo.indexOf(this.editingAreaStyleSheets, url); |
| if(index == -1){ |
| // console.debug("dijit._editor.RichText.removeStyleSheet: Style sheet "+url+" has not been applied"); |
| return; |
| } |
| delete this.editingAreaStyleSheets[index]; |
| dojo.withGlobal(this.window,'query', dojo, ['link:[href="'+url+'"]']).orphan(); |
| }, |
| |
| // disabled: Boolean |
| // The editor is disabled; the text cannot be changed. |
| disabled: false, |
| |
| _mozSettingProps: {'styleWithCSS':false}, |
| _setDisabledAttr: function(/*Boolean*/ value){ |
| this.disabled = value; |
| if(!this.isLoaded){ return; } // this method requires init to be complete |
| value = !!value; |
| if(dojo.isIE || dojo.isWebKit || dojo.isOpera){ |
| var preventIEfocus = dojo.isIE && (this.isLoaded || !this.focusOnLoad); |
| if(preventIEfocus){ this.editNode.unselectable = "on"; } |
| this.editNode.contentEditable = !value; |
| if(preventIEfocus){ |
| var _this = this; |
| setTimeout(function(){ _this.editNode.unselectable = "off"; }, 0); |
| } |
| }else{ //moz |
| try{ |
| this.document.designMode=(value?'off':'on'); |
| }catch(e){ return; } // ! _disabledOK |
| if(!value && this._mozSettingProps){ |
| var ps = this._mozSettingProps; |
| for(var n in ps){ |
| if(ps.hasOwnProperty(n)){ |
| try{ |
| this.document.execCommand(n,false,ps[n]); |
| }catch(e2){} |
| } |
| } |
| } |
| // this.document.execCommand('contentReadOnly', false, value); |
| // if(value){ |
| // this.blur(); //to remove the blinking caret |
| // } |
| } |
| this._disabledOK = true; |
| }, |
| |
| /* Event handlers |
| *****************/ |
| |
| onLoad: function(/*String*/ html){ |
| // summary: |
| // Handler after the iframe finishes loading. |
| // html: String |
| // Editor contents should be set to this value |
| // tags: |
| // protected |
| |
| // TODO: rename this to _onLoad, make empty public onLoad() method, deprecate/make protected onLoadDeferred handler? |
| |
| if(!this.window.__registeredWindow){ |
| this.window.__registeredWindow = true; |
| this._iframeRegHandle = dijit.registerIframe(this.iframe); |
| } |
| if(!dojo.isIE && (this.height || dojo.isMoz)){ |
| this.editNode=this.document.body; |
| }else{ |
| // there's a wrapper div around the content, see _getIframeDocTxt(). |
| this.editNode=this.document.body.firstChild; |
| var _this = this; |
| if(dojo.isIE){ // #4996 IE wants to focus the BODY tag |
| var tabStop = (this.tabStop = dojo.doc.createElement('<div tabIndex=-1>')); |
| this.editingArea.appendChild(tabStop); |
| this.iframe.onfocus = function(){ _this.editNode.setActive(); }; |
| } |
| } |
| this.focusNode = this.editNode; // for InlineEditBox |
| |
| |
| var events = this.events.concat(this.captureEvents); |
| var ap = this.iframe ? this.document : this.editNode; |
| dojo.forEach(events, function(item){ |
| this.connect(ap, item.toLowerCase(), item); |
| }, this); |
| |
| if(dojo.isIE){ // IE contentEditable |
| this.connect(this.document, "onmousedown", "_onIEMouseDown"); // #4996 fix focus |
| |
| // give the node Layout on IE |
| // TODO: this may no longer be needed, since we've reverted IE to using an iframe, |
| // not contentEditable. Removing it would also probably remove the need for creating |
| // the extra <div> in _getIframeDocTxt() |
| this.editNode.style.zoom = 1.0; |
| } |
| |
| if(dojo.isWebKit){ |
| //WebKit sometimes doesn't fire right on selections, so the toolbar |
| //doesn't update right. Therefore, help it out a bit with an additional |
| //listener. A mouse up will typically indicate a display change, so fire this |
| //and get the toolbar to adapt. Reference: #9532 |
| this._webkitListener = this.connect(this.document, "onmouseup", "onDisplayChanged"); |
| } |
| |
| if(dojo.isIE){ |
| // Try to make sure 'hidden' elements aren't visible in edit mode (like browsers other than IE |
| // do). See #9103 |
| try{ |
| this.document.execCommand('RespectVisibilityInDesign', true, null); |
| }catch(e){/* squelch */} |
| } |
| |
| this.isLoaded = true; |
| |
| this.attr('disabled', this.disabled); // initialize content to editable (or not) |
| |
| // Note that setValue() call will only work after isLoaded is set to true (above) |
| this.setValue(html); |
| |
| if(this.onLoadDeferred){ |
| this.onLoadDeferred.callback(true); |
| } |
| |
| this.onDisplayChanged(); |
| |
| if(this.focusOnLoad){ |
| // after the document loads, then set focus after updateInterval expires so that |
| // onNormalizedDisplayChanged has run to avoid input caret issues |
| dojo.addOnLoad(dojo.hitch(this, function(){ setTimeout(dojo.hitch(this, "focus"), this.updateInterval); })); |
| } |
| }, |
| |
| onKeyDown: function(/* Event */ e){ |
| // summary: |
| // Handler for onkeydown event |
| // tags: |
| // protected |
| |
| // we need this event at the moment to get the events from control keys |
| // such as the backspace. It might be possible to add this to Dojo, so that |
| // keyPress events can be emulated by the keyDown and keyUp detection. |
| |
| if(e.keyCode === dojo.keys.TAB && this.isTabIndent ){ |
| dojo.stopEvent(e); //prevent tab from moving focus out of editor |
| |
| // FIXME: this is a poor-man's indent/outdent. It would be |
| // better if it added 4 " " chars in an undoable way. |
| // Unfortunately pasteHTML does not prove to be undoable |
| if(this.queryCommandEnabled((e.shiftKey ? "outdent" : "indent"))){ |
| this.execCommand((e.shiftKey ? "outdent" : "indent")); |
| } |
| } |
| if(dojo.isIE){ |
| if(e.keyCode == dojo.keys.TAB && !this.isTabIndent){ |
| if(e.shiftKey && !e.ctrlKey && !e.altKey){ |
| // focus the BODY so the browser will tab away from it instead |
| this.iframe.focus(); |
| }else if(!e.shiftKey && !e.ctrlKey && !e.altKey){ |
| // focus the BODY so the browser will tab away from it instead |
| this.tabStop.focus(); |
| } |
| }else if(e.keyCode === dojo.keys.BACKSPACE && this.document.selection.type === "Control"){ |
| // IE has a bug where if a non-text object is selected in the editor, |
| // hitting backspace would act as if the browser's back button was |
| // clicked instead of deleting the object. see #1069 |
| dojo.stopEvent(e); |
| this.execCommand("delete"); |
| }else if((65 <= e.keyCode && e.keyCode <= 90) || |
| (e.keyCode>=37 && e.keyCode<=40) // FIXME: get this from connect() instead! |
| ){ //arrow keys |
| e.charCode = e.keyCode; |
| this.onKeyPress(e); |
| } |
| } |
| return true; |
| }, |
| |
| onKeyUp: function(e){ |
| // summary: |
| // Handler for onkeyup event |
| // tags: |
| // callback |
| return; |
| }, |
| |
| setDisabled: function(/*Boolean*/ disabled){ |
| // summary: |
| // Deprecated, use attr('disabled', ...) instead. |
| // tags: |
| // deprecated |
| dojo.deprecated('dijit.Editor::setDisabled is deprecated','use dijit.Editor::attr("disabled",boolean) instead', 2.0); |
| this.attr('disabled',disabled); |
| }, |
| _setValueAttr: function(/*String*/ value){ |
| // summary: |
| // Registers that attr("value", foo) should call setValue(foo) |
| this.setValue(value); |
| }, |
| _setDisableSpellCheckAttr: function(/*Boolean*/ disabled){ |
| if(this.document){ |
| dojo.attr(this.document.body, "spellcheck", !disabled); |
| }else{ |
| // try again after the editor is finished loading |
| this.onLoadDeferred.addCallback(dojo.hitch(this, function(){ |
| dojo.attr(this.document.body, "spellcheck", !disabled); |
| })); |
| } |
| this.disableSpellCheck = disabled; |
| }, |
| |
| onKeyPress: function(e){ |
| // summary: |
| // Handle the various key events |
| // tags: |
| // protected |
| |
| var c = (e.keyChar && e.keyChar.toLowerCase()) || e.keyCode, |
| handlers = this._keyHandlers[c], |
| args = arguments; |
| |
| if(handlers && !e.altKey){ |
| dojo.some(handlers, function(h){ |
| if(!(h.shift ^ e.shiftKey) && !(h.ctrl ^ e.ctrlKey)){ |
| if(!h.handler.apply(this, args)){ |
| e.preventDefault(); |
| } |
| return true; |
| } |
| }, this); |
| } |
| |
| // function call after the character has been inserted |
| if(!this._onKeyHitch){ |
| this._onKeyHitch = dojo.hitch(this, "onKeyPressed"); |
| } |
| setTimeout(this._onKeyHitch, 1); |
| return true; |
| }, |
| |
| addKeyHandler: function(/*String*/ key, /*Boolean*/ ctrl, /*Boolean*/ shift, /*Function*/ handler){ |
| // summary: |
| // Add a handler for a keyboard shortcut |
| // description: |
| // The key argument should be in lowercase if it is a letter character |
| // tags: |
| // protected |
| if(!dojo.isArray(this._keyHandlers[key])){ |
| this._keyHandlers[key] = []; |
| } |
| //TODO: would be nice to make this a hash instead of an array for quick lookups |
| this._keyHandlers[key].push({ |
| shift: shift || false, |
| ctrl: ctrl || false, |
| handler: handler |
| }); |
| }, |
| |
| onKeyPressed: function(){ |
| // summary: |
| // Handler for after the user has pressed a key, and the display has been updated. |
| // (Runs on a timer so that it runs after the display is updated) |
| // tags: |
| // private |
| this.onDisplayChanged(/*e*/); // can't pass in e |
| }, |
| |
| onClick: function(/*Event*/ e){ |
| // summary: |
| // Handler for when the user clicks. |
| // tags: |
| // private |
| |
| // console.info('onClick',this._tryDesignModeOn); |
| this.onDisplayChanged(e); |
| }, |
| |
| _onIEMouseDown: function(/*Event*/ e){ |
| // summary: |
| // IE only to prevent 2 clicks to focus |
| // tags: |
| // protected |
| |
| if(!this._focused && !this.disabled){ |
| this.focus(); |
| } |
| }, |
| |
| _onBlur: function(e){ |
| // summary: |
| // Called from focus manager when focus has moved away from this editor |
| // tags: |
| // protected |
| |
| // console.info('_onBlur') |
| |
| this.inherited(arguments); |
| var _c=this.getValue(true); |
| |
| if(_c!=this.savedContent){ |
| this.onChange(_c); |
| this.savedContent=_c; |
| } |
| }, |
| _onFocus: function(/*Event*/ e){ |
| // summary: |
| // Called from focus manager when focus has moved into this editor |
| // tags: |
| // protected |
| |
| // console.info('_onFocus') |
| if(!this.disabled){ |
| if(!this._disabledOK){ |
| this.attr('disabled', false); |
| } |
| this.inherited(arguments); |
| } |
| }, |
| |
| // TODO: why is this needed - should we deprecate this ? |
| blur: function(){ |
| // summary: |
| // Remove focus from this instance. |
| // tags: |
| // deprecated |
| if(!dojo.isIE && this.window.document.documentElement && this.window.document.documentElement.focus){ |
| this.window.document.documentElement.focus(); |
| }else if(dojo.doc.body.focus){ |
| dojo.doc.body.focus(); |
| } |
| }, |
| |
| focus: function(){ |
| // summary: |
| // Move focus to this editor |
| if(!dojo.isIE){ |
| dijit.focus(this.iframe); |
| if(this._cursorToStart){ |
| delete this._cursorToStart; |
| if(this.editNode.childNodes && |
| this.editNode.childNodes.length === 1 && |
| this.editNode.innerHTML === " "){ |
| this.placeCursorAtStart(); |
| } |
| } |
| }else if(this.editNode && this.editNode.focus){ |
| // editNode may be hidden in display:none div, lets just punt in this case |
| //this.editNode.focus(); -> causes IE to scroll always (strict and quirks mode) to the top the Iframe |
| // if we fire the event manually and let the browser handle the focusing, the latest |
| // cursor position is focused like in FF |
| this.iframe.fireEvent('onfocus', document.createEventObject()); // createEventObject only in IE |
| // }else{ |
| // TODO: should we throw here? |
| // console.debug("Have no idea how to focus into the editor!"); |
| } |
| }, |
| |
| // _lastUpdate: 0, |
| updateInterval: 200, |
| _updateTimer: null, |
| onDisplayChanged: function(/*Event*/ e){ |
| // summary: |
| // This event will be fired everytime the display context |
| // changes and the result needs to be reflected in the UI. |
| // description: |
| // If you don't want to have update too often, |
| // onNormalizedDisplayChanged should be used instead |
| // tags: |
| // private |
| |
| // var _t=new Date(); |
| if(this._updateTimer){ |
| clearTimeout(this._updateTimer); |
| } |
| if(!this._updateHandler){ |
| this._updateHandler = dojo.hitch(this,"onNormalizedDisplayChanged"); |
| } |
| this._updateTimer = setTimeout(this._updateHandler, this.updateInterval); |
| }, |
| onNormalizedDisplayChanged: function(){ |
| // summary: |
| // This event is fired every updateInterval ms or more |
| // description: |
| // If something needs to happen immediately after a |
| // user change, please use onDisplayChanged instead. |
| // tags: |
| // private |
| delete this._updateTimer; |
| }, |
| onChange: function(newContent){ |
| // summary: |
| // This is fired if and only if the editor loses focus and |
| // the content is changed. |
| }, |
| _normalizeCommand: function(/*String*/ cmd, /*Anything?*/argument){ |
| // summary: |
| // Used as the advice function by dojo.connect to map our |
| // normalized set of commands to those supported by the target |
| // browser. |
| // tags: |
| // private |
| |
| var command = cmd.toLowerCase(); |
| if(command == "formatblock"){ |
| if(dojo.isSafari && argument === undefined){ command = "heading"; } |
| }else if(command == "hilitecolor" && !dojo.isMoz){ |
| command = "backcolor"; |
| } |
| |
| return command; |
| }, |
| |
| _qcaCache: {}, |
| queryCommandAvailable: function(/*String*/ command){ |
| // summary: |
| // Tests whether a command is supported by the host. Clients |
| // SHOULD check whether a command is supported before attempting |
| // to use it, behaviour for unsupported commands is undefined. |
| // command: |
| // The command to test for |
| // tags: |
| // private |
| |
| // memoizing version. See _queryCommandAvailable for computing version |
| var ca = this._qcaCache[command]; |
| if(ca !== undefined){ return ca; } |
| return (this._qcaCache[command] = this._queryCommandAvailable(command)); |
| }, |
| |
| _queryCommandAvailable: function(/*String*/ command){ |
| // summary: |
| // See queryCommandAvailable(). |
| // tags: |
| // private |
| |
| var ie = 1; |
| var mozilla = 1 << 1; |
| var webkit = 1 << 2; |
| var opera = 1 << 3; |
| var webkit420 = 1 << 4; |
| |
| function isSupportedBy(browsers){ |
| return { |
| ie: Boolean(browsers & ie), |
| mozilla: Boolean(browsers & mozilla), |
| webkit: Boolean(browsers & webkit), |
| webkit420: Boolean(browsers & webkit420), |
| opera: Boolean(browsers & opera) |
| }; |
| } |
| |
| var supportedBy = null; |
| |
| switch(command.toLowerCase()){ |
| case "bold": case "italic": case "underline": |
| case "subscript": case "superscript": |
| case "fontname": case "fontsize": |
| case "forecolor": case "hilitecolor": |
| case "justifycenter": case "justifyfull": case "justifyleft": |
| case "justifyright": case "delete": case "selectall": case "toggledir": |
| supportedBy = isSupportedBy(mozilla | ie | webkit | opera); |
| break; |
| |
| case "createlink": case "unlink": case "removeformat": |
| case "inserthorizontalrule": case "insertimage": |
| case "insertorderedlist": case "insertunorderedlist": |
| case "indent": case "outdent": case "formatblock": |
| case "inserthtml": case "undo": case "redo": case "strikethrough": case "tabindent": |
| supportedBy = isSupportedBy(mozilla | ie | opera | webkit420); |
| break; |
| |
| case "blockdirltr": case "blockdirrtl": |
| case "dirltr": case "dirrtl": |
| case "inlinedirltr": case "inlinedirrtl": |
| supportedBy = isSupportedBy(ie); |
| break; |
| case "cut": case "copy": case "paste": |
| supportedBy = isSupportedBy( ie | mozilla | webkit420); |
| break; |
| |
| case "inserttable": |
| supportedBy = isSupportedBy(mozilla | ie); |
| break; |
| |
| case "insertcell": case "insertcol": case "insertrow": |
| case "deletecells": case "deletecols": case "deleterows": |
| case "mergecells": case "splitcell": |
| supportedBy = isSupportedBy(ie | mozilla); |
| break; |
| |
| default: return false; |
| } |
| |
| return (dojo.isIE && supportedBy.ie) || |
| (dojo.isMoz && supportedBy.mozilla) || |
| (dojo.isWebKit && supportedBy.webkit) || |
| (dojo.isWebKit > 420 && supportedBy.webkit420) || |
| (dojo.isOpera && supportedBy.opera); // Boolean return true if the command is supported, false otherwise |
| }, |
| |
| execCommand: function(/*String*/ command, argument){ |
| // summary: |
| // Executes a command in the Rich Text area |
| // command: |
| // The command to execute |
| // argument: |
| // An optional argument to the command |
| // tags: |
| // protected |
| var returnValue; |
| |
| //focus() is required for IE to work |
| //In addition, focus() makes sure after the execution of |
| //the command, the editor receives the focus as expected |
| this.focus(); |
| |
| command = this._normalizeCommand(command, argument); |
| |
| |
| if(argument !== undefined){ |
| if(command == "heading"){ |
| throw new Error("unimplemented"); |
| }else if((command == "formatblock") && dojo.isIE){ |
| argument = '<'+argument+'>'; |
| } |
| } |
| |
| //Check to see if we have any over-rides for commands, they will be functions on this |
| //widget of the form _commandImpl. If we don't, fall through to the basic native |
| //exec command of the browser. |
| var implFunc = "_" + command + "Impl"; |
| if(this[implFunc]){ |
| returnValue = this[implFunc](argument); |
| }else{ |
| argument = arguments.length > 1 ? argument : null; |
| if(argument || command!="createlink"){ |
| returnValue = this.document.execCommand(command, false, argument); |
| } |
| } |
| |
| this.onDisplayChanged(); |
| return returnValue; |
| }, |
| |
| queryCommandEnabled: function(/*String*/ command){ |
| // summary: |
| // Check whether a command is enabled or not. |
| // tags: |
| // protected |
| if(this.disabled || !this._disabledOK){ return false; } |
| command = this._normalizeCommand(command); |
| if(dojo.isMoz || dojo.isWebKit){ |
| if(command == "unlink"){ // mozilla returns true always |
| // console.debug(this._sCall("hasAncestorElement", ['a'])); |
| return this._sCall("hasAncestorElement", ["a"]); |
| }else if(command == "inserttable"){ |
| return true; |
| } |
| } |
| //see #4109 |
| if(dojo.isWebKit){ |
| if(command == "copy"){ |
| command = "cut"; |
| }else if(command == "paste"){ |
| return true; |
| } |
| } |
| |
| var elem = dojo.isIE ? this.document.selection.createRange() : this.document; |
| try{ |
| return elem.queryCommandEnabled(command); |
| }catch(e){ |
| //Squelch, occurs if editor is hidden on FF 3 (and maybe others.) |
| return false; |
| } |
| |
| }, |
| |
| queryCommandState: function(command){ |
| // summary: |
| // Check the state of a given command and returns true or false. |
| // tags: |
| // protected |
| |
| if(this.disabled || !this._disabledOK){ return false; } |
| command = this._normalizeCommand(command); |
| try{ |
| return this.document.queryCommandState(command); |
| }catch(e){ |
| //Squelch, occurs if editor is hidden on FF 3 (and maybe others.) |
| return false; |
| } |
| }, |
| |
| queryCommandValue: function(command){ |
| // summary: |
| // Check the value of a given command. This matters most for |
| // custom selections and complex values like font value setting. |
| // tags: |
| // protected |
| |
| if(this.disabled || !this._disabledOK){ return false; } |
| var r; |
| command = this._normalizeCommand(command); |
| if(dojo.isIE && command == "formatblock"){ |
| r = this._native2LocalFormatNames[this.document.queryCommandValue(command)]; |
| }else if(dojo.isMoz && command === "hilitecolor"){ |
| var oldValue; |
| try{ |
| oldValue = this.document.queryCommandValue("styleWithCSS"); |
| }catch(e){ |
| oldValue = false; |
| } |
| this.document.execCommand("styleWithCSS", false, true); |
| r = this.document.queryCommandValue(command); |
| this.document.execCommand("styleWithCSS", false, oldValue); |
| }else{ |
| r = this.document.queryCommandValue(command); |
| } |
| return r; |
| }, |
| |
| // Misc. |
| |
| _sCall: function(name, args){ |
| // summary: |
| // Run the named method of dijit._editor.selection over the |
| // current editor instance's window, with the passed args. |
| // tags: |
| // private |
| return dojo.withGlobal(this.window, name, dijit._editor.selection, args); |
| }, |
| |
| // FIXME: this is a TON of code duplication. Why? |
| |
| placeCursorAtStart: function(){ |
| // summary: |
| // Place the cursor at the start of the editing area. |
| // tags: |
| // private |
| |
| this.focus(); |
| |
| //see comments in placeCursorAtEnd |
| var isvalid=false; |
| if(dojo.isMoz){ |
| // TODO: Is this branch even necessary? |
| var first=this.editNode.firstChild; |
| while(first){ |
| if(first.nodeType == 3){ |
| if(first.nodeValue.replace(/^\s+|\s+$/g, "").length>0){ |
| isvalid=true; |
| this._sCall("selectElement", [ first ]); |
| break; |
| } |
| }else if(first.nodeType == 1){ |
| isvalid=true; |
| var tg = first.tagName ? first.tagName.toLowerCase() : ""; |
| // Collapse before childless tags. |
| if(/br|input|img|base|meta|area|basefont|hr|link/.test(tg)){ |
| this._sCall("selectElement", [ first ]); |
| }else{ |
| // Collapse inside tags with children. |
| this._sCall("selectElementChildren", [ first ]); |
| } |
| break; |
| } |
| first = first.nextSibling; |
| } |
| }else{ |
| isvalid=true; |
| this._sCall("selectElementChildren", [ this.editNode ]); |
| } |
| if(isvalid){ |
| this._sCall("collapse", [ true ]); |
| } |
| }, |
| |
| placeCursorAtEnd: function(){ |
| // summary: |
| // Place the cursor at the end of the editing area. |
| // tags: |
| // private |
| |
| this.focus(); |
| |
| //In mozilla, if last child is not a text node, we have to use |
| // selectElementChildren on this.editNode.lastChild otherwise the |
| // cursor would be placed at the end of the closing tag of |
| //this.editNode.lastChild |
| var isvalid=false; |
| if(dojo.isMoz){ |
| var last=this.editNode.lastChild; |
| while(last){ |
| if(last.nodeType == 3){ |
| if(last.nodeValue.replace(/^\s+|\s+$/g, "").length>0){ |
| isvalid=true; |
| this._sCall("selectElement", [ last ]); |
| break; |
| } |
| }else if(last.nodeType == 1){ |
| isvalid=true; |
| if(last.lastChild){ |
| this._sCall("selectElement", [ last.lastChild ]); |
| }else{ |
| this._sCall("selectElement", [ last ]); |
| } |
| break; |
| } |
| last = last.previousSibling; |
| } |
| }else{ |
| isvalid=true; |
| this._sCall("selectElementChildren", [ this.editNode ]); |
| } |
| if(isvalid){ |
| this._sCall("collapse", [ false ]); |
| } |
| }, |
| |
| getValue: function(/*Boolean?*/ nonDestructive){ |
| // summary: |
| // Return the current content of the editing area (post filters |
| // are applied). Users should call attr('value') instead. |
| // nonDestructive: |
| // defaults to false. Should the post-filtering be run over a copy |
| // of the live DOM? Most users should pass "true" here unless they |
| // *really* know that none of the installed filters are going to |
| // mess up the editing session. |
| // tags: |
| // private |
| if(this.textarea){ |
| if(this.isClosed || !this.isLoaded){ |
| return this.textarea.value; |
| } |
| } |
| |
| return this._postFilterContent(null, nonDestructive); |
| }, |
| _getValueAttr: function(){ |
| // summary: |
| // Hook to make attr("value") work |
| return this.getValue(true); |
| }, |
| |
| setValue: function(/*String*/ html){ |
| // summary: |
| // This function sets the content. No undo history is preserved. |
| // Users should use attr('value', ...) instead. |
| // tags: |
| // deprecated |
| |
| // TODO: remove this and getValue() for 2.0, and move code to _setValueAttr() |
| |
| if(!this.isLoaded){ |
| // try again after the editor is finished loading |
| this.onLoadDeferred.addCallback(dojo.hitch(this, function(){ |
| this.setValue(html); |
| })); |
| return; |
| } |
| if(this.textarea && (this.isClosed || !this.isLoaded)){ |
| this.textarea.value=html; |
| }else{ |
| html = this._preFilterContent(html); |
| var node = this.isClosed ? this.domNode : this.editNode; |
| |
| // Use to avoid webkit problems where editor is disabled until the user clicks it |
| if(!html && dojo.isWebKit){ |
| this._cursorToStart = true; |
| html = " "; |
| } |
| node.innerHTML = html; |
| this._preDomFilterContent(node); |
| } |
| this.onDisplayChanged(); |
| }, |
| |
| replaceValue: function(/*String*/ html){ |
| // summary: |
| // This function set the content while trying to maintain the undo stack |
| // (now only works fine with Moz, this is identical to setValue in all |
| // other browsers) |
| // tags: |
| // protected |
| |
| if(this.isClosed){ |
| this.setValue(html); |
| }else if(this.window && this.window.getSelection && !dojo.isMoz){ // Safari |
| // look ma! it's a totally f'd browser! |
| this.setValue(html); |
| }else if(this.window && this.window.getSelection){ // Moz |
| html = this._preFilterContent(html); |
| this.execCommand("selectall"); |
| if(!html){ |
| this._cursorToStart = true; |
| html = " "; |
| } |
| this.execCommand("inserthtml", html); |
| this._preDomFilterContent(this.editNode); |
| }else if(this.document && this.document.selection){//IE |
| //In IE, when the first element is not a text node, say |
| //an <a> tag, when replacing the content of the editing |
| //area, the <a> tag will be around all the content |
| //so for now, use setValue for IE too |
| this.setValue(html); |
| } |
| }, |
| |
| _preFilterContent: function(/*String*/ html){ |
| // summary: |
| // Filter the input before setting the content of the editing |
| // area. DOM pre-filtering may happen after this |
| // string-based filtering takes place but as of 1.2, this is not |
| // guaranteed for operations such as the inserthtml command. |
| // tags: |
| // private |
| |
| var ec = html; |
| dojo.forEach(this.contentPreFilters, function(ef){ if(ef){ ec = ef(ec); } }); |
| return ec; |
| }, |
| _preDomFilterContent: function(/*DomNode*/ dom){ |
| // summary: |
| // filter the input's live DOM. All filter operations should be |
| // considered to be "live" and operating on the DOM that the user |
| // will be interacting with in their editing session. |
| // tags: |
| // private |
| dom = dom || this.editNode; |
| dojo.forEach(this.contentDomPreFilters, function(ef){ |
| if(ef && dojo.isFunction(ef)){ |
| ef(dom); |
| } |
| }, this); |
| }, |
| |
| _postFilterContent: function( |
| /*DomNode|DomNode[]|String?*/ dom, |
| /*Boolean?*/ nonDestructive){ |
| // summary: |
| // filter the output after getting the content of the editing area |
| // |
| // description: |
| // post-filtering allows plug-ins and users to specify any number |
| // of transforms over the editor's content, enabling many common |
| // use-cases such as transforming absolute to relative URLs (and |
| // vice-versa), ensuring conformance with a particular DTD, etc. |
| // The filters are registered in the contentDomPostFilters and |
| // contentPostFilters arrays. Each item in the |
| // contentDomPostFilters array is a function which takes a DOM |
| // Node or array of nodes as its only argument and returns the |
| // same. It is then passed down the chain for further filtering. |
| // The contentPostFilters array behaves the same way, except each |
| // member operates on strings. Together, the DOM and string-based |
| // filtering allow the full range of post-processing that should |
| // be necessaray to enable even the most agressive of post-editing |
| // conversions to take place. |
| // |
| // If nonDestructive is set to "true", the nodes are cloned before |
| // filtering proceeds to avoid potentially destructive transforms |
| // to the content which may still needed to be edited further. |
| // Once DOM filtering has taken place, the serialized version of |
| // the DOM which is passed is run through each of the |
| // contentPostFilters functions. |
| // |
| // dom: |
| // a node, set of nodes, which to filter using each of the current |
| // members of the contentDomPostFilters and contentPostFilters arrays. |
| // |
| // nonDestructive: |
| // defaults to "false". If true, ensures that filtering happens on |
| // a clone of the passed-in content and not the actual node |
| // itself. |
| // |
| // tags: |
| // private |
| |
| var ec; |
| if(!dojo.isString(dom)){ |
| dom = dom || this.editNode; |
| if(this.contentDomPostFilters.length){ |
| if(nonDestructive){ |
| dom = dojo.clone(dom); |
| } |
| dojo.forEach(this.contentDomPostFilters, function(ef){ |
| dom = ef(dom); |
| }); |
| } |
| ec = dijit._editor.getChildrenHtml(dom); |
| }else{ |
| ec = dom; |
| } |
| |
| if(!dojo.trim(ec.replace(/^\xA0\xA0*/, '').replace(/\xA0\xA0*$/, '')).length){ |
| ec = ""; |
| } |
| |
| // if(dojo.isIE){ |
| // //removing appended <P> </P> for IE |
| // ec = ec.replace(/(?:<p> </p>[\n\r]*)+$/i,""); |
| // } |
| dojo.forEach(this.contentPostFilters, function(ef){ |
| ec = ef(ec); |
| }); |
| |
| return ec; |
| }, |
| |
| _saveContent: function(/*Event*/ e){ |
| // summary: |
| // Saves the content in an onunload event if the editor has not been closed |
| // tags: |
| // private |
| |
| var saveTextarea = dojo.byId(dijit._scopeName + "._editor.RichText.savedContent"); |
| if(saveTextarea.value){ |
| saveTextarea.value += this._SEPARATOR; |
| } |
| saveTextarea.value += this.name + ":" + this.getValue(true); |
| }, |
| |
| |
| escapeXml: function(/*String*/ str, /*Boolean*/ noSingleQuotes){ |
| // summary: |
| // Adds escape sequences for special characters in XML: &<>"' |
| // Optionally skips escapes for single quotes |
| // tags: |
| // private |
| |
| str = str.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """); |
| if(!noSingleQuotes){ |
| str = str.replace(/'/gm, "'"); |
| } |
| return str; // string |
| }, |
| |
| getNodeHtml: function(/* DomNode */ node){ |
| // summary: |
| // Deprecated. Use dijit._editor._getNodeHtml() instead. |
| // tags: |
| // deprecated |
| dojo.deprecated('dijit.Editor::getNodeHtml is deprecated','use dijit._editor.getNodeHtml instead', 2); |
| return dijit._editor.getNodeHtml(node); // String |
| }, |
| |
| getNodeChildrenHtml: function(/* DomNode */ dom){ |
| // summary: |
| // Deprecated. Use dijit._editor.getChildrenHtml() instead. |
| // tags: |
| // deprecated |
| dojo.deprecated('dijit.Editor::getNodeChildrenHtml is deprecated','use dijit._editor.getChildrenHtml instead', 2); |
| return dijit._editor.getChildrenHtml(dom); |
| }, |
| |
| close: function(/*Boolean*/ save){ |
| // summary: |
| // Kills the editor and optionally writes back the modified contents to the |
| // element from which it originated. |
| // save: |
| // Whether or not to save the changes. If false, the changes are discarded. |
| // tags: |
| // private |
| |
| if(this.isClosed){return false; } |
| |
| if(!arguments.length){ save = true; } |
| this._content = this.getValue(); |
| var changed = (this.savedContent != this._content); |
| |
| // line height is squashed for iframes |
| // FIXME: why was this here? if (this.iframe){ this.domNode.style.lineHeight = null; } |
| |
| if(this.interval){ clearInterval(this.interval); } |
| |
| if(this._webkitListener){ |
| //Cleaup of WebKit fix: #9532 |
| this.disconnect(this._webkitListener); |
| delete this._webkitListener; |
| } |
| |
| // Guard against memory leaks on IE (see #9268) |
| if(dojo.isIE){ |
| this.iframe.onfocus = null; |
| } |
| this.iframe._loadFunc = null; |
| |
| if(this._iframeRegHandle){ |
| dijit.unregisterIframe(this._iframeRegHandle); |
| delete this._iframeRegHandle; |
| } |
| |
| if(this.textarea){ |
| var s = this.textarea.style; |
| s.position = ""; |
| s.left = s.top = ""; |
| if(dojo.isIE){ |
| s.overflow = this.__overflow; |
| this.__overflow = null; |
| } |
| this.textarea.value = save ? this._content : this.savedContent; |
| dojo.destroy(this.domNode); |
| this.domNode = this.textarea; |
| }else{ |
| // if(save){ |
| // why we treat moz differently? comment out to fix #1061 |
| // if(dojo.isMoz){ |
| // var nc = dojo.doc.createElement("span"); |
| // this.domNode.appendChild(nc); |
| // nc.innerHTML = this.editNode.innerHTML; |
| // }else{ |
| // this.domNode.innerHTML = this._content; |
| // } |
| // } |
| |
| // Note that this destroys the iframe |
| this.domNode.innerHTML = save ? this._content : this.savedContent; |
| } |
| delete this.iframe; |
| |
| dojo.removeClass(this.domNode, "RichTextEditable"); |
| this.isClosed = true; |
| this.isLoaded = false; |
| |
| delete this.editNode; |
| delete this.focusNode; |
| |
| if(this.window && this.window._frameElement){ |
| this.window._frameElement = null; |
| } |
| |
| this.window = null; |
| this.document = null; |
| this.editingArea = null; |
| this.editorObject = null; |
| |
| return changed; // Boolean: whether the content has been modified |
| }, |
| |
| destroy: function(){ |
| if(!this.isClosed){ this.close(false); } |
| this.inherited(arguments); |
| }, |
| |
| _removeMozBogus: function(/* String */ html){ |
| // summary: |
| // Post filter to remove unwanted HTML attributes generated by mozilla |
| // tags: |
| // private |
| return html.replace(/\stype="_moz"/gi, '').replace(/\s_moz_dirty=""/gi, '').replace(/_moz_resizing="(true|false)"/gi,''); // String |
| }, |
| _removeWebkitBogus: function(/* String */ html){ |
| // summary: |
| // Post filter to remove unwanted HTML attributes generated by webkit |
| // tags: |
| // private |
| html = html.replace(/\sclass="webkit-block-placeholder"/gi, ''); |
| html = html.replace(/\sclass="apple-style-span"/gi, ''); |
| return html; // String |
| }, |
| _normalizeFontStyle: function(/* String */ html){ |
| // summary: |
| // Convert <strong> and <em> to <b> and <i>. |
| // description: |
| // Moz can not handle strong/em tags correctly, so to help |
| // mozilla and also to normalize output, convert them to <b> and <i>. |
| // |
| // Note the IE generates <strong> and <em> rather than <b> and <i> |
| // tags: |
| // private |
| return html.replace(/<(\/)?strong([ \>])/gi, '<$1b$2') |
| .replace(/<(\/)?em([ \>])/gi, '<$1i$2' ); // String |
| }, |
| |
| _preFixUrlAttributes: function(/* String */ html){ |
| // summary: |
| // Pre-filter to do fixing to href attributes on <a> and <img> tags |
| // tags: |
| // private |
| return html.replace(/(?:(<a(?=\s).*?\shref=)("|')(.*?)\2)|(?:(<a\s.*?href=)([^"'][^ >]+))/gi, |
| '$1$4$2$3$5$2 _djrealurl=$2$3$5$2') |
| .replace(/(?:(<img(?=\s).*?\ssrc=)("|')(.*?)\2)|(?:(<img\s.*?src=)([^"'][^ >]+))/gi, |
| '$1$4$2$3$5$2 _djrealurl=$2$3$5$2'); // String |
| }, |
| |
| /***************************************************************************** |
| The following functions implement HTML manipulation commands for various |
| browser/contentEditable implementations. The goal of them is to enforce |
| standard behaviors of them. |
| ******************************************************************************/ |
| |
| _inserthorizontalruleImpl: function(argument){ |
| // summary: |
| // This function implements the insertion of HTML <HR> tags. |
| // into a point on the page. IE doesn't to it right, so |
| // we have to use an alternate form |
| // argument: |
| // arguments to the exec command, if any. |
| // tags: |
| // protected |
| if(dojo.isIE){ |
| return this._inserthtmlImpl("<hr>"); |
| } |
| return this.document.execCommand("inserthorizontalrule", false, argument); |
| }, |
| |
| _unlinkImpl: function(argument){ |
| // summary: |
| // This function implements the unlink of an <a> tag. |
| // argument: |
| // arguments to the exec command, if any. |
| // tags: |
| // protected |
| if((this.queryCommandEnabled("unlink")) && (dojo.isMoz || dojo.isWebKit)){ |
| var a = this._sCall("getAncestorElement", [ "a" ]); |
| this._sCall("selectElement", [ a ]); |
| return this.document.execCommand("unlink", false, null); |
| } |
| return this.document.execCommand("unlink", false, argument); |
| }, |
| |
| _hilitecolorImpl: function(argument){ |
| // summary: |
| // This function implements the hilitecolor command |
| // argument: |
| // arguments to the exec command, if any. |
| // tags: |
| // protected |
| var returnValue; |
| if(dojo.isMoz){ |
| // mozilla doesn't support hilitecolor properly when useCSS is |
| // set to false (bugzilla #279330) |
| this.document.execCommand("styleWithCSS", false, true); |
| returnValue = this.document.execCommand("hilitecolor", false, argument); |
| this.document.execCommand("styleWithCSS", false, false); |
| }else{ |
| returnValue = this.document.execCommand("hilitecolor", false, argument); |
| } |
| return returnValue; |
| }, |
| |
| _backcolorImpl: function(argument){ |
| // summary: |
| // This function implements the backcolor command |
| // argument: |
| // arguments to the exec command, if any. |
| // tags: |
| // protected |
| if(dojo.isIE){ |
| // Tested under IE 6 XP2, no problem here, comment out |
| // IE weirdly collapses ranges when we exec these commands, so prevent it |
| // var tr = this.document.selection.createRange(); |
| argument = argument ? argument : null; |
| } |
| return this.document.execCommand("backcolor", false, argument); |
| }, |
| |
| _forecolorImpl: function(argument){ |
| // summary: |
| // This function implements the forecolor command |
| // argument: |
| // arguments to the exec command, if any. |
| // tags: |
| // protected |
| if(dojo.isIE){ |
| // Tested under IE 6 XP2, no problem here, comment out |
| // IE weirdly collapses ranges when we exec these commands, so prevent it |
| // var tr = this.document.selection.createRange(); |
| argument = argument? argument : null; |
| } |
| return this.document.execCommand("forecolor", false, argument); |
| }, |
| |
| _inserthtmlImpl: function(argument){ |
| // summary: |
| // This function implements the insertion of HTML content into |
| // a point on the page. |
| // argument: |
| // The content to insert, if any. |
| // tags: |
| // protected |
| argument = this._preFilterContent(argument); |
| var rv = true; |
| if(dojo.isIE){ |
| var insertRange = this.document.selection.createRange(); |
| if(this.document.selection.type.toUpperCase() == 'CONTROL'){ |
| var n=insertRange.item(0); |
| while(insertRange.length){ |
| insertRange.remove(insertRange.item(0)); |
| } |
| n.outerHTML=argument; |
| }else{ |
| insertRange.pasteHTML(argument); |
| } |
| insertRange.select(); |
| //insertRange.collapse(true); |
| }else if(dojo.isMoz && !argument.length){ |
| //mozilla can not inserthtml an empty html to delete current selection |
| //so we delete the selection instead in this case |
| this._sCall("remove"); // FIXME |
| }else{ |
| rv = this.document.execCommand("inserthtml", false, argument); |
| } |
| return rv; |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit._KeyNavContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit._KeyNavContainer"] = true; |
| dojo.provide("dijit._KeyNavContainer"); |
| |
| |
| dojo.declare("dijit._KeyNavContainer", |
| dijit._Container, |
| { |
| |
| // summary: |
| // A _Container with keyboard navigation of its children. |
| // description: |
| // To use this mixin, call connectKeyNavHandlers() in |
| // postCreate() and call startupKeyNavChildren() in startup(). |
| // It provides normalized keyboard and focusing code for Container |
| // widgets. |
| /*===== |
| // focusedChild: [protected] Widget |
| // The currently focused child widget, or null if there isn't one |
| focusedChild: null, |
| =====*/ |
| |
| // tabIndex: Integer |
| // Tab index of the container; same as HTML tabIndex attribute. |
| // Note then when user tabs into the container, focus is immediately |
| // moved to the first item in the container. |
| tabIndex: "0", |
| |
| _keyNavCodes: {}, |
| |
| connectKeyNavHandlers: function(/*dojo.keys[]*/ prevKeyCodes, /*dojo.keys[]*/ nextKeyCodes){ |
| // summary: |
| // Call in postCreate() to attach the keyboard handlers |
| // to the container. |
| // preKeyCodes: dojo.keys[] |
| // Key codes for navigating to the previous child. |
| // nextKeyCodes: dojo.keys[] |
| // Key codes for navigating to the next child. |
| // tags: |
| // protected |
| |
| var keyCodes = (this._keyNavCodes = {}); |
| var prev = dojo.hitch(this, this.focusPrev); |
| var next = dojo.hitch(this, this.focusNext); |
| dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; }); |
| dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; }); |
| this.connect(this.domNode, "onkeypress", "_onContainerKeypress"); |
| this.connect(this.domNode, "onfocus", "_onContainerFocus"); |
| }, |
| |
| startupKeyNavChildren: function(){ |
| // summary: |
| // Call in startup() to set child tabindexes to -1 |
| // tags: |
| // protected |
| dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild")); |
| }, |
| |
| addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){ |
| // summary: |
| // Add a child to our _Container |
| dijit._KeyNavContainer.superclass.addChild.apply(this, arguments); |
| this._startupChild(widget); |
| }, |
| |
| focus: function(){ |
| // summary: |
| // Default focus() implementation: focus the first child. |
| this.focusFirstChild(); |
| }, |
| |
| focusFirstChild: function(){ |
| // summary: |
| // Focus the first focusable child in the container. |
| // tags: |
| // protected |
| var child = this._getFirstFocusableChild(); |
| if(child){ // edge case: Menu could be empty or hidden |
| this.focusChild(child); |
| } |
| }, |
| |
| focusNext: function(){ |
| // summary: |
| // Focus the next widget |
| // tags: |
| // protected |
| var child = this._getNextFocusableChild(this.focusedChild, 1); |
| this.focusChild(child); |
| }, |
| |
| focusPrev: function(){ |
| // summary: |
| // Focus the last focusable node in the previous widget |
| // (ex: go to the ComboButton icon section rather than button section) |
| // tags: |
| // protected |
| var child = this._getNextFocusableChild(this.focusedChild, -1); |
| this.focusChild(child, true); |
| }, |
| |
| focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){ |
| // summary: |
| // Focus widget. |
| // widget: |
| // Reference to container's child widget |
| // last: |
| // If true and if widget has multiple focusable nodes, focus the |
| // last one instead of the first one |
| // tags: |
| // protected |
| |
| if(this.focusedChild && widget !== this.focusedChild){ |
| this._onChildBlur(this.focusedChild); |
| } |
| widget.focus(last ? "end" : "start"); |
| this.focusedChild = widget; |
| }, |
| |
| _startupChild: function(/*dijit._Widget*/ widget){ |
| // summary: |
| // Setup for each child widget |
| // description: |
| // Sets tabIndex=-1 on each child, so that the tab key will |
| // leave the container rather than visiting each child. |
| // tags: |
| // private |
| |
| widget.attr("tabIndex", "-1"); |
| |
| this.connect(widget, "_onFocus", function(){ |
| // Set valid tabIndex so tabbing away from widget goes to right place, see #10272 |
| widget.attr("tabIndex", this.tabIndex); |
| }); |
| this.connect(widget, "_onBlur", function(){ |
| widget.attr("tabIndex", "-1"); |
| }); |
| }, |
| |
| _onContainerFocus: function(evt){ |
| // summary: |
| // Handler for when the container gets focus |
| // description: |
| // Initially the container itself has a tabIndex, but when it gets |
| // focus, switch focus to first child... |
| // tags: |
| // private |
| |
| // Note that we can't use _onFocus() because switching focus from the |
| // _onFocus() handler confuses the focus.js code |
| // (because it causes _onFocusNode() to be called recursively) |
| |
| // focus bubbles on Firefox, |
| // so just make sure that focus has really gone to the container |
| if(evt.target !== this.domNode){ return; } |
| |
| this.focusFirstChild(); |
| |
| // and then set the container's tabIndex to -1, |
| // (don't remove as that breaks Safari 4) |
| // so that tab or shift-tab will go to the fields after/before |
| // the container, rather than the container itself |
| dojo.attr(this.domNode, "tabIndex", "-1"); |
| }, |
| |
| _onBlur: function(evt){ |
| // When focus is moved away the container, and it's descendant (popup) widgets, |
| // then restore the container's tabIndex so that user can tab to it again. |
| // Note that using _onBlur() so that this doesn't happen when focus is shifted |
| // to one of my child widgets (typically a popup) |
| if(this.tabIndex){ |
| dojo.attr(this.domNode, "tabIndex", this.tabIndex); |
| } |
| this.inherited(arguments); |
| }, |
| |
| _onContainerKeypress: function(evt){ |
| // summary: |
| // When a key is pressed, if it's an arrow key etc. then |
| // it's handled here. |
| // tags: |
| // private |
| if(evt.ctrlKey || evt.altKey){ return; } |
| var func = this._keyNavCodes[evt.charOrCode]; |
| if(func){ |
| func(); |
| dojo.stopEvent(evt); |
| } |
| }, |
| |
| _onChildBlur: function(/*dijit._Widget*/ widget){ |
| // summary: |
| // Called when focus leaves a child widget to go |
| // to a sibling widget. |
| // tags: |
| // protected |
| }, |
| |
| _getFirstFocusableChild: function(){ |
| // summary: |
| // Returns first child that can be focused |
| return this._getNextFocusableChild(null, 1); // dijit._Widget |
| }, |
| |
| _getNextFocusableChild: function(child, dir){ |
| // summary: |
| // Returns the next or previous focusable child, compared |
| // to "child" |
| // child: Widget |
| // The current widget |
| // dir: Integer |
| // * 1 = after |
| // * -1 = before |
| if(child){ |
| child = this._getSiblingOfChild(child, dir); |
| } |
| var children = this.getChildren(); |
| for(var i=0; i < children.length; i++){ |
| if(!child){ |
| child = children[(dir>0) ? 0 : (children.length-1)]; |
| } |
| if(child.isFocusable()){ |
| return child; // dijit._Widget |
| } |
| child = this._getSiblingOfChild(child, dir); |
| } |
| // no focusable child found |
| return null; // dijit._Widget |
| } |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.ToolbarSeparator"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.ToolbarSeparator"] = true; |
| dojo.provide("dijit.ToolbarSeparator"); |
| |
| |
| |
| |
| dojo.declare("dijit.ToolbarSeparator", |
| [ dijit._Widget, dijit._Templated ], |
| { |
| // summary: |
| // A spacer between two `dijit.Toolbar` items |
| templateString: '<div class="dijitToolbarSeparator dijitInline"></div>', |
| postCreate: function(){ dojo.setSelectable(this.domNode, false); }, |
| isFocusable: function(){ |
| // summary: |
| // This widget isn't focusable, so pass along that fact. |
| // tags: |
| // protected |
| return false; |
| } |
| |
| }); |
| |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.Toolbar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.Toolbar"] = true; |
| dojo.provide("dijit.Toolbar"); |
| |
| |
| |
| |
| |
| dojo.declare("dijit.Toolbar", |
| [dijit._Widget, dijit._Templated, dijit._KeyNavContainer], |
| { |
| // summary: |
| // A Toolbar widget, used to hold things like `dijit.Editor` buttons |
| |
| templateString: |
| '<div class="dijit dijitToolbar" waiRole="toolbar" tabIndex="${tabIndex}" dojoAttachPoint="containerNode">' + |
| // '<table style="table-layout: fixed" class="dijitReset dijitToolbarTable">' + // factor out style |
| // '<tr class="dijitReset" dojoAttachPoint="containerNode"></tr>'+ |
| // '</table>' + |
| '</div>', |
| |
| postCreate: function(){ |
| this.connectKeyNavHandlers( |
| this.isLeftToRight() ? [dojo.keys.LEFT_ARROW] : [dojo.keys.RIGHT_ARROW], |
| this.isLeftToRight() ? [dojo.keys.RIGHT_ARROW] : [dojo.keys.LEFT_ARROW] |
| ); |
| }, |
| |
| startup: function(){ |
| if(this._started){ return; } |
| |
| this.startupKeyNavChildren(); |
| |
| this.inherited(arguments); |
| } |
| } |
| ); |
| |
| // For back-compat, remove for 2.0 |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit._HasDropDown"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit._HasDropDown"] = true; |
| dojo.provide("dijit._HasDropDown"); |
| |
| |
| |
| |
| dojo.declare("dijit._HasDropDown", |
| null, |
| { |
| // summary: |
| // Mixin for widgets that need drop down ability. |
| |
| // _buttonNode: [protected] DomNode |
| // The button/icon/node to click to display the drop down. |
| // Can be set via a dojoAttachPoint assignment. |
| // If missing, then either focusNode or domNode (if focusNode is also missing) will be used. |
| _buttonNode: null, |
| |
| // _arrowWrapperNode: [protected] DomNode |
| // Will set CSS class dijitUpArrow, dijitDownArrow, dijitRightArrow etc. on this node depending |
| // on where the drop down is set to be positioned. |
| // Can be set via a dojoAttachPoint assignment. |
| // If missing, then _buttonNode will be used. |
| _arrowWrapperNode: null, |
| |
| // _popupStateNode: [protected] DomNode |
| // The node to set the popupActive class on. |
| // Can be set via a dojoAttachPoint assignment. |
| // If missing, then focusNode or _buttonNode (if focusNode is missing) will be used. |
| _popupStateNode: null, |
| |
| // _aroundNode: [protected] DomNode |
| // The node to display the popup around. |
| // Can be set via a dojoAttachPoint assignment. |
| // If missing, then domNode will be used. |
| _aroundNode: null, |
| |
| // dropDown: [protected] Widget |
| // The widget to display as a popup. This widget *must* be |
| // defined before the startup function is called. |
| dropDown: null, |
| |
| // autoWidth: [protected] Boolean |
| // Set to true to make the drop down at least as wide as this |
| // widget. Set to false if the drop down should just be its |
| // default width |
| autoWidth: true, |
| |
| // forceWidth: [protected] Boolean |
| // Set to true to make the drop down exactly as wide as this |
| // widget. Overrides autoWidth. |
| forceWidth: false, |
| |
| // maxHeight: [protected] Integer |
| // The max height for our dropdown. Set to 0 for no max height. |
| // any dropdown taller than this will have scrollbars |
| maxHeight: 0, |
| |
| // dropDownPosition: [const] String[] |
| // This variable controls the position of the drop down. |
| // It's an array of strings with the following values: |
| // |
| // * before: places drop down to the left of the target node/widget, or to the right in |
| // the case of RTL scripts like Hebrew and Arabic |
| // * after: places drop down to the right of the target node/widget, or to the left in |
| // the case of RTL scripts like Hebrew and Arabic |
| // * above: drop down goes above target node |
| // * below: drop down goes below target node |
| // |
| // The list is positions is tried, in order, until a position is found where the drop down fits |
| // within the viewport. |
| // |
| dropDownPosition: ["below","above"], |
| |
| // _stopClickEvents: Boolean |
| // When set to false, the click events will not be stopped, in |
| // case you want to use them in your subwidget |
| _stopClickEvents: true, |
| |
| _onDropDownMouse: function(/*Event*/ e){ |
| // summary: |
| // Callback when the user mouse clicks on the arrow icon, or presses the down |
| // arrow key, to open the drop down. |
| |
| // We handle mouse events using onmousedown in order to allow for selecting via |
| // a mouseDown --> mouseMove --> mouseUp. So, our click is already handled, unless |
| // we are executed via keypress - in which case, this._seenKeydown |
| // will be set to true. |
| if(e.type == "click" && !this._seenKeydown){ return; } |
| this._seenKeydown = false; |
| |
| // If we are a mouse event, set up the mouseup handler. See _onDropDownMouse() for |
| // details on this handler. |
| if(e.type == "mousedown"){ |
| this._docHandler = this.connect(dojo.doc, "onmouseup", "_onDropDownMouseup"); |
| } |
| if(this.disabled || this.readOnly){ return; } |
| if(this._stopClickEvents){ |
| dojo.stopEvent(e); |
| } |
| this.toggleDropDown(); |
| |
| // If we are a click, then we'll pretend we did a mouse up |
| if(e.type == "click" || e.type == "keypress"){ |
| this._onDropDownMouseup(); |
| } |
| }, |
| |
| _onDropDownMouseup: function(/*Event?*/ e){ |
| // summary: |
| // Callback when the user lifts their mouse after mouse down on the arrow icon. |
| // If the drop is a simple menu and the mouse is over the menu, we execute it, otherwise, we focus our |
| // dropDown node. If the event is missing, then we are not |
| // a mouseup event. |
| // |
| // This is useful for the common mouse movement pattern |
| // with native browser <select> nodes: |
| // 1. mouse down on the select node (probably on the arrow) |
| // 2. move mouse to a menu item while holding down the mouse button |
| // 3. mouse up. this selects the menu item as though the user had clicked it. |
| |
| if(e && this._docHandler){ |
| this.disconnect(this._docHandler); |
| } |
| var dropDown = this.dropDown, overMenu = false; |
| |
| if(e && this._opened){ |
| // This code deals with the corner-case when the drop down covers the original widget, |
| // because it's so large. In that case mouse-up shouldn't select a value from the menu. |
| // Find out if our target is somewhere in our dropdown widget, |
| // but not over our _buttonNode (the clickable node) |
| var c = dojo.position(this._buttonNode, true); |
| if(!(e.pageX >= c.x && e.pageX <= c.x + c.w) || |
| !(e.pageY >= c.y && e.pageY <= c.y + c.h)){ |
| var t = e.target; |
| while(t && !overMenu){ |
| if(dojo.hasClass(t, "dijitPopup")){ |
| overMenu = true; |
| }else{ |
| t = t.parentNode; |
| } |
| } |
| if(overMenu){ |
| t = e.target; |
| if(dropDown.onItemClick){ |
| var menuItem; |
| while(t && !(menuItem = dijit.byNode(t))){ |
| t = t.parentNode; |
| } |
| if(menuItem && menuItem.onClick && menuItem.getParent){ |
| menuItem.getParent().onItemClick(menuItem, e); |
| } |
| } |
| return; |
| } |
| } |
| } |
| if(this._opened && dropDown.focus){ |
| // Focus the dropdown widget - do it on a delay so that we |
| // don't steal our own focus. |
| window.setTimeout(dojo.hitch(dropDown, "focus"), 1); |
| } |
| }, |
| |
| _setupDropdown: function(){ |
| // summary: |
| // set up nodes and connect our mouse and keypress events |
| this._buttonNode = this._buttonNode || this.focusNode || this.domNode; |
| this._popupStateNode = this._popupStateNode || this.focusNode || this._buttonNode; |
| this._aroundNode = this._aroundNode || this.domNode; |
| this.connect(this._buttonNode, "onmousedown", "_onDropDownMouse"); |
| this.connect(this._buttonNode, "onclick", "_onDropDownMouse"); |
| this.connect(this._buttonNode, "onkeydown", "_onDropDownKeydown"); |
| this.connect(this._buttonNode, "onblur", "_onDropDownBlur"); |
| this.connect(this._buttonNode, "onkeypress", "_onKey"); |
| |
| // If we have a _setStateClass function (which happens when |
| // we are a form widget), then we need to connect our open/close |
| // functions to it |
| if(this._setStateClass){ |
| this.connect(this, "openDropDown", "_setStateClass"); |
| this.connect(this, "closeDropDown", "_setStateClass"); |
| } |
| |
| // Add a class to the "dijitDownArrowButton" type class to _buttonNode so theme can set direction of arrow |
| // based on where drop down will normally appear |
| var defaultPos = { |
| "after" : this.isLeftToRight() ? "Right" : "Left", |
| "before" : this.isLeftToRight() ? "Left" : "Right", |
| "above" : "Up", |
| "below" : "Down", |
| "left" : "Left", |
| "right" : "Right" |
| }[this.dropDownPosition[0]] || this.dropDownPosition[0] || "Down"; |
| dojo.addClass(this._arrowWrapperNode || this._buttonNode, "dijit" + defaultPos + "ArrowButton"); |
| }, |
| |
| postCreate: function(){ |
| this._setupDropdown(); |
| this.inherited(arguments); |
| }, |
| |
| destroyDescendants: function(){ |
| if(this.dropDown){ |
| // Destroy the drop down, unless it's already been destroyed. This can happen because |
| // the drop down is a direct child of <body> even though it's logically my child. |
| if(!this.dropDown._destroyed){ |
| this.dropDown.destroyRecursive(); |
| } |
| delete this.dropDown; |
| } |
| this.inherited(arguments); |
| }, |
| |
| _onDropDownKeydown: function(/*Event*/ e){ |
| this._seenKeydown = true; |
| }, |
| |
| _onKeyPress: function(/*Event*/ e){ |
| if(this._opened && e.charOrCode == dojo.keys.ESCAPE && !e.shiftKey && !e.ctrlKey && !e.altKey){ |
| this.toggleDropDown(); |
| dojo.stopEvent(e); |
| return; |
| } |
| this.inherited(arguments); |
| }, |
| |
| _onDropDownBlur: function(/*Event*/ e){ |
| this._seenKeydown = false; |
| }, |
| |
| _onKey: function(/*Event*/ e){ |
| // summary: |
| // Callback when the user presses a key on menu popup node |
| |
| if(this.disabled || this.readOnly){ return; } |
| var d = this.dropDown; |
| if(d && this._opened && d.handleKey){ |
| if(d.handleKey(e) === false){ return; } |
| } |
| if(d && this._opened && e.keyCode == dojo.keys.ESCAPE){ |
| this.toggleDropDown(); |
| return; |
| } |
| if(e.keyCode == dojo.keys.DOWN_ARROW || e.keyCode == dojo.keys.ENTER || e.charOrCode == " "){ |
| this._onDropDownMouse(e); |
| } |
| }, |
| |
| _onBlur: function(){ |
| // summary: |
| // Called magically when focus has shifted away from this widget and it's dropdown |
| |
| this.closeDropDown(); |
| // don't focus on button. the user has explicitly focused on something else. |
| this.inherited(arguments); |
| }, |
| |
| isLoaded: function(){ |
| // summary: |
| // Returns whether or not the dropdown is loaded. This can |
| // be overridden in order to force a call to loadDropDown(). |
| // tags: |
| // protected |
| |
| return true; |
| }, |
| |
| loadDropDown: function(/* Function */ loadCallback){ |
| // summary: |
| // Loads the data for the dropdown, and at some point, calls |
| // the given callback |
| // tags: |
| // protected |
| |
| loadCallback(); |
| }, |
| |
| toggleDropDown: function(){ |
| // summary: |
| // Toggle the drop-down widget; if it is up, close it, if not, open it |
| // tags: |
| // protected |
| |
| if(this.disabled || this.readOnly){ return; } |
| this.focus(); |
| var dropDown = this.dropDown; |
| if(!dropDown){ return; } |
| if(!this._opened){ |
| // If we aren't loaded, load it first so there isn't a flicker |
| if(!this.isLoaded()){ |
| this.loadDropDown(dojo.hitch(this, "openDropDown")); |
| return; |
| }else{ |
| this.openDropDown(); |
| } |
| }else{ |
| this.closeDropDown(); |
| } |
| }, |
| |
| openDropDown: function(){ |
| // summary: |
| // Opens the dropdown for this widget - it returns the |
| // return value of dijit.popup.open |
| // tags: |
| // protected |
| |
| var dropDown = this.dropDown; |
| var ddNode = dropDown.domNode; |
| var self = this; |
| |
| // Prepare our popup's height and honor maxHeight if it exists. |
| |
| // TODO: isn't maxHeight dependent on the return value from dijit.popup.open(), |
| // ie, dependent on how much space is available (BK) |
| |
| if(!this._preparedNode){ |
| dijit.popup.moveOffScreen(ddNode); |
| this._preparedNode = true; |
| // Check if we have explicitly set width and height on the dropdown widget dom node |
| if(ddNode.style.width){ |
| this._explicitDDWidth = true; |
| } |
| if(ddNode.style.height){ |
| this._explicitDDHeight = true; |
| } |
| } |
| if(this.maxHeight || this.forceWidth || this.autoWidth){ |
| var myStyle = { |
| display: "", |
| visibility: "hidden" |
| }; |
| if(!this._explicitDDWidth){ |
| myStyle.width = ""; |
| } |
| if(!this._explicitDDHeight){ |
| myStyle.height = ""; |
| } |
| dojo.style(ddNode, myStyle); |
| var mb = dojo.marginBox(ddNode); |
| var overHeight = (this.maxHeight && mb.h > this.maxHeight); |
| dojo.style(ddNode, {overflow: overHeight ? "auto" : "hidden"}); |
| if(this.forceWidth){ |
| mb.w = this.domNode.offsetWidth; |
| }else if(this.autoWidth){ |
| mb.w = Math.max(mb.w, this.domNode.offsetWidth); |
| }else{ |
| delete mb.w; |
| } |
| if(overHeight){ |
| mb.h = this.maxHeight; |
| if("w" in mb){ |
| mb.w += 16; |
| } |
| }else{ |
| delete mb.h; |
| } |
| delete mb.t; |
| delete mb.l; |
| if(dojo.isFunction(dropDown.resize)){ |
| dropDown.resize(mb); |
| }else{ |
| dojo.marginBox(ddNode, mb); |
| } |
| } |
| var retVal = dijit.popup.open({ |
| parent: this, |
| popup: dropDown, |
| around: this._aroundNode, |
| orient: dijit.getPopupAroundAlignment((this.dropDownPosition && this.dropDownPosition.length) ? this.dropDownPosition : ["below"],this.isLeftToRight()), |
| onExecute: function(){ |
| self.closeDropDown(true); |
| }, |
| onCancel: function(){ |
| self.closeDropDown(true); |
| }, |
| onClose: function(){ |
| dojo.attr(self._popupStateNode, "popupActive", false); |
| dojo.removeClass(self._popupStateNode, "dijitHasDropDownOpen"); |
| self._opened = false; |
| self.state = ""; |
| } |
| }); |
| dojo.attr(this._popupStateNode, "popupActive", "true"); |
| dojo.addClass(self._popupStateNode, "dijitHasDropDownOpen"); |
| this._opened=true; |
| this.state="Opened"; |
| // TODO: set this.checked and call setStateClass(), to affect button look while drop down is shown |
| return retVal; |
| }, |
| |
| closeDropDown: function(/*Boolean*/ focus){ |
| // summary: |
| // Closes the drop down on this widget |
| // tags: |
| // protected |
| |
| if(this._opened){ |
| dijit.popup.close(this.dropDown); |
| if(focus){ this.focus(); } |
| this._opened = false; |
| this.state = ""; |
| } |
| } |
| |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.Button"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.Button"] = true; |
| dojo.provide("dijit.form.Button"); |
| |
| |
| |
| |
| |
| dojo.declare("dijit.form.Button", |
| dijit.form._FormWidget, |
| { |
| // summary: |
| // Basically the same thing as a normal HTML button, but with special styling. |
| // description: |
| // Buttons can display a label, an icon, or both. |
| // A label should always be specified (through innerHTML) or the label |
| // attribute. It can be hidden via showLabel=false. |
| // example: |
| // | <button dojoType="dijit.form.Button" onClick="...">Hello world</button> |
| // |
| // example: |
| // | var button1 = new dijit.form.Button({label: "hello world", onClick: foo}); |
| // | dojo.body().appendChild(button1.domNode); |
| |
| // label: HTML String |
| // Text to display in button. |
| // If the label is hidden (showLabel=false) then and no title has |
| // been specified, then label is also set as title attribute of icon. |
| label: "", |
| |
| // showLabel: Boolean |
| // Set this to true to hide the label text and display only the icon. |
| // (If showLabel=false then iconClass must be specified.) |
| // Especially useful for toolbars. |
| // If showLabel=true, the label will become the title (a.k.a. tooltip/hint) of the icon. |
| // |
| // The exception case is for computers in high-contrast mode, where the label |
| // will still be displayed, since the icon doesn't appear. |
| showLabel: true, |
| |
| // iconClass: String |
| // Class to apply to DOMNode in button to make it display an icon |
| iconClass: "", |
| |
| // type: String |
| // Defines the type of button. "button", "submit", or "reset". |
| type: "button", |
| |
| baseClass: "dijitButton", |
| |
| templateString: dojo.cache("dijit.form", "templates/Button.html", "<span class=\"dijit dijitReset dijitLeft dijitInline\"\n\tdojoAttachEvent=\"onclick:_onButtonClick,onmouseenter:_onMouse,onmouseleave:_onMouse,onmousedown:_onMouse\"\n\t><span class=\"dijitReset dijitRight dijitInline\"\n\t\t><span class=\"dijitReset dijitInline dijitButtonNode\"\n\t\t\t><button class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\t\tdojoAttachPoint=\"titleNode,focusNode\"\n\t\t\t\t${nameAttrSetting} type=\"${type}\" value=\"${value}\" waiRole=\"button\" waiState=\"labelledby-${id}_label\"\n\t\t\t\t><span class=\"dijitReset dijitInline\" dojoAttachPoint=\"iconNode\"\n\t\t\t\t\t><span class=\"dijitReset dijitToggleButtonIconChar\">✓</span\n\t\t\t\t></span\n\t\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\t\tid=\"${id}_label\"\n\t\t\t\t\tdojoAttachPoint=\"containerNode\"\n\t\t\t\t></span\n\t\t\t></button\n\t\t></span\n\t></span\n></span>\n"), |
| |
| attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { |
| label: { node: "containerNode", type: "innerHTML" }, |
| iconClass: { node: "iconNode", type: "class" } |
| }), |
| |
| |
| _onClick: function(/*Event*/ e){ |
| // summary: |
| // Internal function to handle click actions |
| if(this.disabled){ |
| return false; |
| } |
| this._clicked(); // widget click actions |
| return this.onClick(e); // user click actions |
| }, |
| |
| _onButtonClick: function(/*Event*/ e){ |
| // summary: |
| // Handler when the user activates the button portion. |
| if(this._onClick(e) === false){ // returning nothing is same as true |
| e.preventDefault(); // needed for checkbox |
| }else if(this.type == "submit" && !this.focusNode.form){ // see if a nonform widget needs to be signalled |
| for(var node=this.domNode; node.parentNode/*#5935*/; node=node.parentNode){ |
| var widget=dijit.byNode(node); |
| if(widget && typeof widget._onSubmit == "function"){ |
| widget._onSubmit(e); |
| break; |
| } |
| } |
| } |
| }, |
| |
| _setValueAttr: function(/*String*/ value){ |
| // Verify that value cannot be set for BUTTON elements. |
| var attr = this.attributeMap.value || ''; |
| if(this[attr.node || attr || 'domNode'].tagName == 'BUTTON'){ |
| // On IE, setting value actually overrides innerHTML, so disallow for everyone for consistency |
| if(value != this.value){ |
| console.debug('Cannot change the value attribute on a Button widget.'); |
| } |
| } |
| }, |
| |
| _fillContent: function(/*DomNode*/ source){ |
| // Overrides _Templated._fillContent(). |
| // If button label is specified as srcNodeRef.innerHTML rather than |
| // this.params.label, handle it here. |
| if(source && (!this.params || !("label" in this.params))){ |
| this.attr('label', source.innerHTML); |
| } |
| }, |
| |
| postCreate: function(){ |
| dojo.setSelectable(this.focusNode, false); |
| this.inherited(arguments); |
| }, |
| |
| _setShowLabelAttr: function(val){ |
| if(this.containerNode){ |
| dojo.toggleClass(this.containerNode, "dijitDisplayNone", !val); |
| } |
| this.showLabel = val; |
| }, |
| |
| onClick: function(/*Event*/ e){ |
| // summary: |
| // Callback for when button is clicked. |
| // If type="submit", return true to perform submit, or false to cancel it. |
| // type: |
| // callback |
| return true; // Boolean |
| }, |
| |
| _clicked: function(/*Event*/ e){ |
| // summary: |
| // Internal overridable function for when the button is clicked |
| }, |
| |
| setLabel: function(/*String*/ content){ |
| // summary: |
| // Deprecated. Use attr('label', ...) instead. |
| dojo.deprecated("dijit.form.Button.setLabel() is deprecated. Use attr('label', ...) instead.", "", "2.0"); |
| this.attr("label", content); |
| }, |
| _setLabelAttr: function(/*String*/ content){ |
| // summary: |
| // Hook for attr('label', ...) to work. |
| // description: |
| // Set the label (text) of the button; takes an HTML string. |
| this.containerNode.innerHTML = this.label = content; |
| if(this.showLabel == false && !this.params.title){ |
| this.titleNode.title = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || ''); |
| } |
| } |
| }); |
| |
| |
| dojo.declare("dijit.form.DropDownButton", [dijit.form.Button, dijit._Container, dijit._HasDropDown], { |
| // summary: |
| // A button with a drop down |
| // |
| // example: |
| // | <button dojoType="dijit.form.DropDownButton" label="Hello world"> |
| // | <div dojotype="dijit.Menu">...</div> |
| // | </button> |
| // |
| // example: |
| // | var button1 = new dijit.form.DropDownButton({ label: "hi", dropDown: new dijit.Menu(...) }); |
| // | dojo.body().appendChild(button1); |
| // |
| |
| baseClass : "dijitDropDownButton", |
| |
| templateString: dojo.cache("dijit.form", "templates/DropDownButton.html", "<span class=\"dijit dijitReset dijitLeft dijitInline\"\n\tdojoAttachPoint=\"_buttonNode\"\n\tdojoAttachEvent=\"onmouseenter:_onMouse,onmouseleave:_onMouse,onmousedown:_onMouse\"\n\t><span class='dijitReset dijitRight dijitInline'\n\t\t><span class='dijitReset dijitInline dijitButtonNode'\n\t\t\t><button class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\t\t${nameAttrSetting} type=\"${type}\" value=\"${value}\"\n\t\t\t\tdojoAttachPoint=\"focusNode,titleNode,_arrowWrapperNode\"\n\t\t\t\twaiRole=\"button\" waiState=\"haspopup-true,labelledby-${id}_label\"\n\t\t\t\t><span class=\"dijitReset dijitInline\"\n\t\t\t\t\tdojoAttachPoint=\"iconNode\"\n\t\t\t\t></span\n\t\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\t\tdojoAttachPoint=\"containerNode,_popupStateNode\"\n\t\t\t\t\tid=\"${id}_label\"\n\t\t\t\t></span\n\t\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonInner\"> </span\n\t\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonChar\">▼</span\n\t\t\t></button\n\t\t></span\n\t></span\n></span>\n"), |
| |
| _fillContent: function(){ |
| // Overrides Button._fillContent(). |
| // |
| // My inner HTML contains both the button contents and a drop down widget, like |
| // <DropDownButton> <span>push me</span> <Menu> ... </Menu> </DropDownButton> |
| // The first node is assumed to be the button content. The widget is the popup. |
| |
| if(this.srcNodeRef){ // programatically created buttons might not define srcNodeRef |
| //FIXME: figure out how to filter out the widget and use all remaining nodes as button |
| // content, not just nodes[0] |
| var nodes = dojo.query("*", this.srcNodeRef); |
| dijit.form.DropDownButton.superclass._fillContent.call(this, nodes[0]); |
| |
| // save pointer to srcNode so we can grab the drop down widget after it's instantiated |
| this.dropDownContainer = this.srcNodeRef; |
| } |
| }, |
| |
| startup: function(){ |
| if(this._started){ return; } |
| |
| // the child widget from srcNodeRef is the dropdown widget. Insert it in the page DOM, |
| // make it invisible, and store a reference to pass to the popup code. |
| if(!this.dropDown){ |
| var dropDownNode = dojo.query("[widgetId]", this.dropDownContainer)[0]; |
| this.dropDown = dijit.byNode(dropDownNode); |
| delete this.dropDownContainer; |
| } |
| dijit.popup.moveOffScreen(this.dropDown.domNode); |
| |
| this.inherited(arguments); |
| }, |
| |
| isLoaded: function(){ |
| // Returns whether or not we are loaded - if our dropdown has an href, |
| // then we want to check that. |
| var dropDown = this.dropDown; |
| return (!dropDown.href || dropDown.isLoaded); |
| }, |
| |
| loadDropDown: function(){ |
| // Loads our dropdown |
| var dropDown = this.dropDown; |
| if(!dropDown){ return; } |
| if(!this.isLoaded()){ |
| var handler = dojo.connect(dropDown, "onLoad", this, function(){ |
| dojo.disconnect(handler); |
| this.openDropDown(); |
| }); |
| dropDown.refresh(); |
| }else{ |
| this.openDropDown(); |
| } |
| }, |
| |
| isFocusable: function(){ |
| // Overridden so that focus is handled by the _HasDropDown mixin, not by |
| // the _FormWidget mixin. |
| return this.inherited(arguments) && !this._mouseDown; |
| } |
| }); |
| |
| dojo.declare("dijit.form.ComboButton", dijit.form.DropDownButton, { |
| // summary: |
| // A combination button and drop-down button. |
| // Users can click one side to "press" the button, or click an arrow |
| // icon to display the drop down. |
| // |
| // example: |
| // | <button dojoType="dijit.form.ComboButton" onClick="..."> |
| // | <span>Hello world</span> |
| // | <div dojoType="dijit.Menu">...</div> |
| // | </button> |
| // |
| // example: |
| // | var button1 = new dijit.form.ComboButton({label: "hello world", onClick: foo, dropDown: "myMenu"}); |
| // | dojo.body().appendChild(button1.domNode); |
| // |
| |
| templateString: dojo.cache("dijit.form", "templates/ComboButton.html", "<table class='dijit dijitReset dijitInline dijitLeft'\n\tcellspacing='0' cellpadding='0' waiRole=\"presentation\"\n\t><tbody waiRole=\"presentation\"><tr waiRole=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonNode\"><button id=\"${id}_button\" class=\"dijitReset dijitButtonContents\"\n\t\t\tdojoAttachEvent=\"onclick:_onButtonClick,onmouseenter:_onMouse,onmouseleave:_onMouse,onmousedown:_onMouse,onkeypress:_onButtonKeyPress\" dojoAttachPoint=\"titleNode\"\n\t\t\twaiRole=\"button\" waiState=\"labelledby-${id}_label\"\n\t\t\t><div class=\"dijitReset dijitInline\" dojoAttachPoint=\"iconNode\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitInline dijitButtonText\" id=\"${id}_label\" dojoAttachPoint=\"containerNode\" waiRole=\"presentation\"></div\n\t\t></button></td\n\t\t><td id=\"${id}_arrow\" class='dijitReset dijitRight dijitButtonNode dijitArrowButton'\n\t\t\tdojoAttachPoint=\"_popupStateNode,focusNode,_buttonNode\"\n\t\t\tdojoAttachEvent=\"onmouseenter:_onMouse,onmouseleave:_onMouse,onkeypress:_onArrowKeyPress\"\n\t\t\tstateModifier=\"DownArrow\"\n\t\t\ttitle=\"${optionsTitle}\" ${nameAttrSetting}\n\t\t\twaiRole=\"button\" waiState=\"haspopup-true\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" waiRole=\"presentation\"> </div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" waiRole=\"presentation\">▼</div\n\t\t></td\n\t></tr></tbody\n></table>\n"), |
| |
| attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), { |
| id: "", |
| tabIndex: ["focusNode", "titleNode"], |
| title: "titleNode" |
| }), |
| |
| // optionsTitle: String |
| // Text that describes the options menu (accessibility) |
| optionsTitle: "", |
| |
| baseClass: "dijitComboButton", |
| |
| _focusedNode: null, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| this._focalNodes = [this.titleNode, this._popupStateNode]; |
| var isIE = dojo.isIE; |
| dojo.forEach(this._focalNodes, dojo.hitch(this, function(node){ |
| this.connect(node, isIE? "onactivate" : "onfocus", this._onNodeFocus); |
| this.connect(node, isIE? "ondeactivate" : "onblur", this._onNodeBlur); |
| })); |
| if(isIE && (isIE < 8 || dojo.isQuirks)){ // fixed in IE8/strict |
| with(this.titleNode){ // resize BUTTON tag so parent TD won't inherit extra padding |
| style.width = scrollWidth + "px"; |
| this.connect(this.titleNode, "onresize", function(){ |
| setTimeout( function(){ style.width = scrollWidth + "px"; }, 0); |
| }); |
| } |
| } |
| }, |
| |
| _onNodeFocus: function(evt){ |
| this._focusedNode = evt.currentTarget; |
| var fnc = this._focusedNode == this.focusNode ? "dijitDownArrowButtonFocused" : "dijitButtonContentsFocused"; |
| dojo.addClass(this._focusedNode, fnc); |
| }, |
| |
| _onNodeBlur: function(evt){ |
| var fnc = evt.currentTarget == this.focusNode ? "dijitDownArrowButtonFocused" : "dijitButtonContentsFocused"; |
| dojo.removeClass(evt.currentTarget, fnc); |
| }, |
| |
| _onBlur: function(){ |
| this.inherited(arguments); |
| this._focusedNode = null; |
| }, |
| |
| _onButtonKeyPress: function(/*Event*/ evt){ |
| // summary: |
| // Handler for right arrow key when focus is on left part of button |
| if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "RIGHT_ARROW" : "LEFT_ARROW"]){ |
| dijit.focus(this._popupStateNode); |
| dojo.stopEvent(evt); |
| } |
| }, |
| |
| _onArrowKeyPress: function(/*Event*/ evt){ |
| // summary: |
| // Handler for left arrow key when focus is on right part of button |
| if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "LEFT_ARROW" : "RIGHT_ARROW"]){ |
| dijit.focus(this.titleNode); |
| dojo.stopEvent(evt); |
| } |
| }, |
| |
| focus: function(/*String*/ position){ |
| // summary: |
| // Focuses this widget to according to position, if specified, |
| // otherwise on arrow node |
| // position: |
| // "start" or "end" |
| |
| dijit.focus(position == "start" ? this.titleNode : this._popupStateNode); |
| } |
| }); |
| |
| dojo.declare("dijit.form.ToggleButton", dijit.form.Button, { |
| // summary: |
| // A button that can be in two states (checked or not). |
| // Can be base class for things like tabs or checkbox or radio buttons |
| |
| baseClass: "dijitToggleButton", |
| |
| // checked: Boolean |
| // Corresponds to the native HTML <input> element's attribute. |
| // In markup, specified as "checked='checked'" or just "checked". |
| // True if the button is depressed, or the checkbox is checked, |
| // or the radio button is selected, etc. |
| checked: false, |
| |
| attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), { |
| checked:"focusNode" |
| }), |
| |
| _clicked: function(/*Event*/ evt){ |
| this.attr('checked', !this.checked); |
| }, |
| |
| _setCheckedAttr: function(/*Boolean*/ value){ |
| this.checked = value; |
| dojo.attr(this.focusNode || this.domNode, "checked", value); |
| dijit.setWaiState(this.focusNode || this.domNode, "pressed", value); |
| this._setStateClass(); |
| this._handleOnChange(value, true); |
| }, |
| |
| setChecked: function(/*Boolean*/ checked){ |
| // summary: |
| // Deprecated. Use attr('checked', true/false) instead. |
| dojo.deprecated("setChecked("+checked+") is deprecated. Use attr('checked',"+checked+") instead.", "", "2.0"); |
| this.attr('checked', checked); |
| }, |
| |
| reset: function(){ |
| // summary: |
| // Reset the widget's value to what it was at initialization time |
| |
| this._hasBeenBlurred = false; |
| |
| // set checked state to original setting |
| this.attr('checked', this.params.checked || false); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit._editor._Plugin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit._editor._Plugin"] = true; |
| dojo.provide("dijit._editor._Plugin"); |
| |
| |
| |
| |
| dojo.declare("dijit._editor._Plugin", null, { |
| // summary |
| // Base class for a "plugin" to the editor, which is usually |
| // a single button on the Toolbar and some associated code |
| |
| constructor: function(/*Object?*/args, /*DomNode?*/node){ |
| this.params = args || {}; |
| dojo.mixin(this, this.params); |
| this._connects=[]; |
| }, |
| |
| // editor: [const] dijit.Editor |
| // Points to the parent editor |
| editor: null, |
| |
| // iconClassPrefix: [const] String |
| // The CSS class name for the button node is formed from `iconClassPrefix` and `command` |
| iconClassPrefix: "dijitEditorIcon", |
| |
| // button: dijit._Widget? |
| // Pointer to `dijit.form.Button` or other widget (ex: `dijit.form.FilteringSelect`) |
| // that is added to the toolbar to control this plugin. |
| // If not specified, will be created on initialization according to `buttonClass` |
| button: null, |
| |
| // command: String |
| // String like "insertUnorderedList", "outdent", "justifyCenter", etc. that represents an editor command. |
| // Passed to editor.execCommand() if `useDefaultCommand` is true. |
| command: "", |
| |
| // useDefaultCommand: Boolean |
| // If true, this plugin executes by calling Editor.execCommand() with the argument specified in `command`. |
| useDefaultCommand: true, |
| |
| // buttonClass: Widget Class |
| // Class of widget (ex: dijit.form.Button or dijit.form.FilteringSelect) |
| // that is added to the toolbar to control this plugin. |
| // This is used to instantiate the button, unless `button` itself is specified directly. |
| buttonClass: dijit.form.Button, |
| |
| getLabel: function(/*String*/key){ |
| // summary: |
| // Returns the label to use for the button |
| // tags: |
| // private |
| return this.editor.commands[key]; // String |
| }, |
| |
| _initButton: function(){ |
| // summary: |
| // Initialize the button or other widget that will control this plugin. |
| // This code only works for plugins controlling built-in commands in the editor. |
| // tags: |
| // protected extension |
| if(this.command.length){ |
| var label = this.getLabel(this.command); |
| var className = this.iconClassPrefix+" "+this.iconClassPrefix + this.command.charAt(0).toUpperCase() + this.command.substr(1); |
| if(!this.button){ |
| var props = dojo.mixin({ |
| label: label, |
| showLabel: false, |
| iconClass: className, |
| dropDown: this.dropDown, |
| tabIndex: "-1" |
| }, this.params || {}); |
| this.button = new this.buttonClass(props); |
| } |
| } |
| }, |
| |
| destroy: function(){ |
| // summary: |
| // Destroy this plugin |
| |
| dojo.forEach(this._connects, dojo.disconnect); |
| if(this.dropDown){ |
| this.dropDown.destroyRecursive(); |
| } |
| }, |
| |
| connect: function(o, f, tf){ |
| // summary: |
| // Make a dojo.connect() that is automatically disconnected when this plugin is destroyed. |
| // Similar to `dijit._Widget.connect`. |
| // tags: |
| // protected |
| this._connects.push(dojo.connect(o, f, this, tf)); |
| }, |
| |
| updateState: function(){ |
| // summary: |
| // Change state of the plugin to respond to events in the editor. |
| // description: |
| // This is called on meaningful events in the editor, such as change of selection |
| // or caret position (but not simple typing of alphanumeric keys). It gives the |
| // plugin a chance to update the CSS of its button. |
| // |
| // For example, the "bold" plugin will highlight/unhighlight the bold button depending on whether the |
| // characters next to the caret are bold or not. |
| // |
| // Only makes sense when `useDefaultCommand` is true, as it calls Editor.queryCommandEnabled(`command`). |
| var e = this.editor, |
| c = this.command, |
| checked, enabled; |
| if(!e || !e.isLoaded || !c.length){ return; } |
| if(this.button){ |
| try{ |
| enabled = e.queryCommandEnabled(c); |
| if(this.enabled !== enabled){ |
| this.enabled = enabled; |
| this.button.attr('disabled', !enabled); |
| } |
| if(typeof this.button.checked == 'boolean'){ |
| checked = e.queryCommandState(c); |
| if(this.checked !== checked){ |
| this.checked = checked; |
| this.button.attr('checked', e.queryCommandState(c)); |
| } |
| } |
| }catch(e){ |
| console.log(e); // FIXME: we shouldn't have debug statements in our code. Log as an error? |
| } |
| } |
| }, |
| |
| setEditor: function(/*dijit.Editor*/ editor){ |
| // summary: |
| // Tell the plugin which Editor it is associated with. |
| |
| // TODO: refactor code to just pass editor to constructor. |
| |
| // FIXME: detach from previous editor!! |
| this.editor = editor; |
| |
| // FIXME: prevent creating this if we don't need to (i.e., editor can't handle our command) |
| this._initButton(); |
| |
| // FIXME: wire up editor to button here! |
| if(this.command.length && |
| !this.editor.queryCommandAvailable(this.command)){ |
| // console.debug("hiding:", this.command); |
| if(this.button){ |
| this.button.domNode.style.display = "none"; |
| } |
| } |
| if(this.button && this.useDefaultCommand){ |
| this.connect(this.button, "onClick", |
| dojo.hitch(this.editor, "execCommand", this.command, this.commandArg) |
| ); |
| } |
| this.connect(this.editor, "onNormalizedDisplayChanged", "updateState"); |
| }, |
| |
| setToolbar: function(/*dijit.Toolbar*/ toolbar){ |
| // summary: |
| // Tell the plugin to add it's controller widget (often a button) |
| // to the toolbar. Does nothing if there is no controller widget. |
| |
| // TODO: refactor code to just pass toolbar to constructor. |
| |
| if(this.button){ |
| toolbar.addChild(this.button); |
| } |
| // console.debug("adding", this.button, "to:", toolbar); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit._editor.plugins.EnterKeyHandling"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit._editor.plugins.EnterKeyHandling"] = true; |
| dojo.provide("dijit._editor.plugins.EnterKeyHandling"); |
| |
| |
| |
| dojo.declare("dijit._editor.plugins.EnterKeyHandling", dijit._editor._Plugin, { |
| // summary: |
| // This plugin tries to make all browsers behave consistently w.r.t |
| // displaying paragraphs, specifically dealing with when the user presses |
| // the ENTER key. |
| // |
| // It deals mainly with how the text appears on the screen (specifically |
| // address the double-spaced line problem on IE), but also has some code |
| // to normalize what attr('value') returns. |
| // |
| // description: |
| // This plugin has three modes: |
| // |
| // * blockModeForEnter=BR |
| // * blockModeForEnter=DIV |
| // * blockModeForEnter=P |
| // |
| // In blockModeForEnter=P, the ENTER key semantically means "start a new |
| // paragraph", whereas shift-ENTER means "new line in the current paragraph". |
| // For example: |
| // |
| // | first paragraph <shift-ENTER> |
| // | second line of first paragraph <ENTER> |
| // | |
| // | second paragraph |
| // |
| // In the other two modes, the ENTER key means to go to a new line in the |
| // current paragraph, and users [visually] create a new paragraph by pressing ENTER twice. |
| // For example, if the user enters text into an editor like this: |
| // |
| // | one <ENTER> |
| // | two <ENTER> |
| // | three <ENTER> |
| // | <ENTER> |
| // | four <ENTER> |
| // | five <ENTER> |
| // | six <ENTER> |
| // |
| // It will appear on the screen as two paragraphs of three lines each. |
| // |
| // blockNodeForEnter=BR |
| // -------------------- |
| // On IE, typing the above keystrokes in the editor will internally produce DOM of: |
| // |
| // | <p>one</p> |
| // | <p>two</p> |
| // | <p>three</p> |
| // | <p></p> |
| // | <p>four</p> |
| // | <p>five</p> |
| // | <p>six</p> |
| // |
| // However, blockNodeForEnter=BR makes the Editor on IE display like other browsers, by |
| // changing the CSS for the <p> node to not have top/bottom margins, |
| // thus eliminating the double-spaced appearance. |
| // |
| // Also, attr('value') when used w/blockNodeForEnter=br on IE will return: |
| // |
| // | <p> one <br> two <br> three </p> |
| // | <p> four <br> five <br> six </p> |
| // |
| // This output normalization implemented by a filter when the |
| // editor writes out it's data, to convert consecutive <p> |
| // nodes into a single <p> node with internal <br> separators. |
| // |
| // There's also a pre-filter to mirror the post-filter. |
| // It converts a single <p> with <br> line breaks |
| // into separate <p> nodes, and creates empty <p> nodes for spacing |
| // between paragraphs. |
| // |
| // On FF typing the above keystrokes will internally generate: |
| // |
| // | one <br> two <br> three <br> <br> four <br> five <br> six <br> |
| // |
| // And on Safari it will generate: |
| // |
| // | "one" |
| // | <div>two</div> |
| // | <div>three</div> |
| // | <div><br></div> |
| // | <div>four</div> |
| // | <div>five</div> |
| // | <div>six</div> |
| // |
| // Thus, Safari and FF already look correct although semantically their content is a bit strange. |
| // On Safari or Firefox blockNodeForEnter=BR uses the builtin editor command "insertBrOnReturn", |
| // but that doesn't seem to do anything. |
| // Thus, attr('value') on safari/FF returns the browser-specific HTML listed above, |
| // rather than the semantically meaningful value that IE returns: <p>one<br>two</p> <p>three<br>four</p>. |
| // |
| // (Note: originally based on http://bugs.dojotoolkit.org/ticket/2859) |
| // |
| // blockNodeForEnter=P |
| // ------------------- |
| // Plugin will monitor keystrokes and update the editor's content on the fly, |
| // so that the ENTER key will create a new <p> on FF and Safari (it already |
| // works that way by default on IE). |
| // |
| // blockNodeForEnter=DIV |
| // --------------------- |
| // Follows the same code path as blockNodeForEnter=P but inserting a <div> |
| // on ENTER key. Although it produces strange internal DOM, like this: |
| // |
| // | <div>paragraph one</div> |
| // | <div>paragraph one, line 2</div> |
| // | <div> </div> |
| // | <div>paragraph two</div> |
| // |
| // it does provide a consistent look on all browsers, and the on-the-fly DOM updating |
| // can be useful for collaborative editing. |
| |
| // blockNodeForEnter: String |
| // This property decides the behavior of Enter key. It can be either P, |
| // DIV, BR, or empty (which means disable this feature). Anything else |
| // will trigger errors. |
| // |
| // See class description for more details. |
| blockNodeForEnter: 'BR', |
| |
| constructor: function(args){ |
| if(args){ |
| dojo.mixin(this,args); |
| } |
| }, |
| |
| setEditor: function(editor){ |
| // Overrides _Plugin.setEditor(). |
| this.editor = editor; |
| if(this.blockNodeForEnter == 'BR'){ |
| if(dojo.isIE){ |
| editor.contentDomPreFilters.push(dojo.hitch(this, "regularPsToSingleLinePs")); |
| editor.contentDomPostFilters.push(dojo.hitch(this, "singleLinePsToRegularPs")); |
| editor.onLoadDeferred.addCallback(dojo.hitch(this, "_fixNewLineBehaviorForIE")); |
| }else{ |
| editor.onLoadDeferred.addCallback(dojo.hitch(this,function(d){ |
| try{ |
| this.editor.document.execCommand("insertBrOnReturn", false, true); |
| }catch(e){} |
| return d; |
| })); |
| } |
| }else if(this.blockNodeForEnter){ |
| // add enter key handler |
| // FIXME: need to port to the new event code!! |
| dojo['require']('dijit._editor.range'); |
| var h = dojo.hitch(this,this.handleEnterKey); |
| editor.addKeyHandler(13, 0, 0, h); //enter |
| editor.addKeyHandler(13, 0, 1, h); //shift+enter |
| this.connect(this.editor,'onKeyPressed','onKeyPressed'); |
| } |
| }, |
| onKeyPressed: function(e){ |
| // summary: |
| // Handler for keypress events. |
| // tags: |
| // private |
| if(this._checkListLater){ |
| if(dojo.withGlobal(this.editor.window, 'isCollapsed', dijit)){ |
| var liparent=dojo.withGlobal(this.editor.window, 'getAncestorElement', dijit._editor.selection, ['LI']); |
| if(!liparent){ |
| // circulate the undo detection code by calling RichText::execCommand directly |
| dijit._editor.RichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter); |
| // set the innerHTML of the new block node |
| var block = dojo.withGlobal(this.editor.window, 'getAncestorElement', dijit._editor.selection, [this.blockNodeForEnter]); |
| if(block){ |
| block.innerHTML=this.bogusHtmlContent; |
| if(dojo.isIE){ |
| // move to the start by moving backwards one char |
| var r = this.editor.document.selection.createRange(); |
| r.move('character',-1); |
| r.select(); |
| } |
| }else{ |
| console.error('onKeyPressed: Cannot find the new block node'); // FIXME |
| } |
| }else{ |
| if(dojo.isMoz){ |
| if(liparent.parentNode.parentNode.nodeName == 'LI'){ |
| liparent=liparent.parentNode.parentNode; |
| } |
| } |
| var fc=liparent.firstChild; |
| if(fc && fc.nodeType == 1 && (fc.nodeName == 'UL' || fc.nodeName == 'OL')){ |
| liparent.insertBefore(fc.ownerDocument.createTextNode('\xA0'),fc); |
| var newrange = dijit.range.create(this.editor.window); |
| newrange.setStart(liparent.firstChild,0); |
| var selection = dijit.range.getSelection(this.editor.window, true); |
| selection.removeAllRanges(); |
| selection.addRange(newrange); |
| } |
| } |
| } |
| this._checkListLater = false; |
| } |
| if(this._pressedEnterInBlock){ |
| // the new created is the original current P, so we have previousSibling below |
| if(this._pressedEnterInBlock.previousSibling){ |
| this.removeTrailingBr(this._pressedEnterInBlock.previousSibling); |
| } |
| delete this._pressedEnterInBlock; |
| } |
| }, |
| |
| // bogusHtmlContent: [private] String |
| // HTML to stick into a new empty block |
| bogusHtmlContent: ' ', |
| |
| // blockNodes: [private] Regex |
| // Regex for testing if a given tag is a block level (display:block) tag |
| blockNodes: /^(?:P|H1|H2|H3|H4|H5|H6|LI)$/, |
| |
| handleEnterKey: function(e){ |
| // summary: |
| // Handler for enter key events when blockModeForEnter is DIV or P. |
| // description: |
| // Manually handle enter key event to make the behavior consistent across |
| // all supported browsers. See class description for details. |
| // tags: |
| // private |
| |
| var selection, range, newrange, doc=this.editor.document,br; |
| if(e.shiftKey){ // shift+enter always generates <br> |
| var parent = dojo.withGlobal(this.editor.window, "getParentElement", dijit._editor.selection); |
| var header = dijit.range.getAncestor(parent,this.blockNodes); |
| if(header){ |
| if(!e.shiftKey && header.tagName == 'LI'){ |
| return true; // let browser handle |
| } |
| selection = dijit.range.getSelection(this.editor.window); |
| range = selection.getRangeAt(0); |
| if(!range.collapsed){ |
| range.deleteContents(); |
| selection = dijit.range.getSelection(this.editor.window); |
| range = selection.getRangeAt(0); |
| } |
| if(dijit.range.atBeginningOfContainer(header, range.startContainer, range.startOffset)){ |
| if(e.shiftKey){ |
| br=doc.createElement('br'); |
| newrange = dijit.range.create(this.editor.window); |
| header.insertBefore(br,header.firstChild); |
| newrange.setStartBefore(br.nextSibling); |
| selection.removeAllRanges(); |
| selection.addRange(newrange); |
| }else{ |
| dojo.place(br, header, "before"); |
| } |
| }else if(dijit.range.atEndOfContainer(header, range.startContainer, range.startOffset)){ |
| newrange = dijit.range.create(this.editor.window); |
| br=doc.createElement('br'); |
| if(e.shiftKey){ |
| header.appendChild(br); |
| header.appendChild(doc.createTextNode('\xA0')); |
| newrange.setStart(header.lastChild,0); |
| }else{ |
| dojo.place(br, header, "after"); |
| newrange.setStartAfter(header); |
| } |
| |
| selection.removeAllRanges(); |
| selection.addRange(newrange); |
| }else{ |
| return true; // let browser handle |
| } |
| }else{ |
| // don't change this: do not call this.execCommand, as that may have other logic in subclass |
| dijit._editor.RichText.prototype.execCommand.call(this.editor, 'inserthtml', '<br>'); |
| } |
| return false; |
| } |
| var _letBrowserHandle = true; |
| |
| // first remove selection |
| selection = dijit.range.getSelection(this.editor.window); |
| range = selection.getRangeAt(0); |
| if(!range.collapsed){ |
| range.deleteContents(); |
| selection = dijit.range.getSelection(this.editor.window); |
| range = selection.getRangeAt(0); |
| } |
| |
| var block = dijit.range.getBlockAncestor(range.endContainer, null, this.editor.editNode); |
| var blockNode = block.blockNode; |
| |
| // if this is under a LI or the parent of the blockNode is LI, just let browser to handle it |
| if((this._checkListLater = (blockNode && (blockNode.nodeName == 'LI' || blockNode.parentNode.nodeName == 'LI')))){ |
| if(dojo.isMoz){ |
| // press enter in middle of P may leave a trailing <br/>, let's remove it later |
| this._pressedEnterInBlock = blockNode; |
| } |
| // if this li only contains spaces, set the content to empty so the browser will outdent this item |
| if(/^(\s| |\xA0|<span\b[^>]*\bclass=['"]Apple-style-span['"][^>]*>(\s| |\xA0)<\/span>)?(<br>)?$/.test(blockNode.innerHTML)){ |
| // empty LI node |
| blockNode.innerHTML = ''; |
| if(dojo.isWebKit){ // WebKit tosses the range when innerHTML is reset |
| newrange = dijit.range.create(this.editor.window); |
| newrange.setStart(blockNode, 0); |
| selection.removeAllRanges(); |
| selection.addRange(newrange); |
| } |
| this._checkListLater = false; // nothing to check since the browser handles outdent |
| } |
| return true; |
| } |
| |
| // text node directly under body, let's wrap them in a node |
| if(!block.blockNode || block.blockNode===this.editor.editNode){ |
| try{ |
| dijit._editor.RichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter); |
| }catch(e2){ /*squelch FF3 exception bug when editor content is a single BR*/ } |
| // get the newly created block node |
| // FIXME |
| block = {blockNode:dojo.withGlobal(this.editor.window, "getAncestorElement", dijit._editor.selection, [this.blockNodeForEnter]), |
| blockContainer: this.editor.editNode}; |
| if(block.blockNode){ |
| if(block.blockNode != this.editor.editNode && |
| (!(block.blockNode.textContent || block.blockNode.innerHTML).replace(/^\s+|\s+$/g, "").length)){ |
| this.removeTrailingBr(block.blockNode); |
| return false; |
| } |
| }else{ // we shouldn't be here if formatblock worked |
| block.blockNode = this.editor.editNode; |
| } |
| selection = dijit.range.getSelection(this.editor.window); |
| range = selection.getRangeAt(0); |
| } |
| |
| var newblock = doc.createElement(this.blockNodeForEnter); |
| newblock.innerHTML=this.bogusHtmlContent; |
| this.removeTrailingBr(block.blockNode); |
| if(dijit.range.atEndOfContainer(block.blockNode, range.endContainer, range.endOffset)){ |
| if(block.blockNode === block.blockContainer){ |
| block.blockNode.appendChild(newblock); |
| }else{ |
| dojo.place(newblock, block.blockNode, "after"); |
| } |
| _letBrowserHandle = false; |
| // lets move caret to the newly created block |
| newrange = dijit.range.create(this.editor.window); |
| newrange.setStart(newblock, 0); |
| selection.removeAllRanges(); |
| selection.addRange(newrange); |
| if(this.editor.height){ |
| dijit.scrollIntoView(newblock); |
| } |
| }else if(dijit.range.atBeginningOfContainer(block.blockNode, |
| range.startContainer, range.startOffset)){ |
| dojo.place(newblock, block.blockNode, block.blockNode === block.blockContainer ? "first" : "before"); |
| if(newblock.nextSibling && this.editor.height){ |
| // position input caret - mostly WebKit needs this |
| newrange = dijit.range.create(this.editor.window); |
| newrange.setStart(newblock.nextSibling, 0); |
| selection.removeAllRanges(); |
| selection.addRange(newrange); |
| // browser does not scroll the caret position into view, do it manually |
| dijit.scrollIntoView(newblock.nextSibling); |
| } |
| _letBrowserHandle = false; |
| }else{ // press enter in the middle of P |
| if(dojo.isMoz){ |
| // press enter in middle of P may leave a trailing <br/>, let's remove it later |
| this._pressedEnterInBlock = block.blockNode; |
| } |
| } |
| return _letBrowserHandle; |
| }, |
| |
| removeTrailingBr: function(container){ |
| // summary: |
| // If last child of container is a <br>, then remove it. |
| // tags: |
| // private |
| var para = /P|DIV|LI/i.test(container.tagName) ? |
| container : dijit._editor.selection.getParentOfType(container,['P','DIV','LI']); |
| |
| if(!para){ return; } |
| if(para.lastChild){ |
| if((para.childNodes.length > 1 && para.lastChild.nodeType == 3 && /^[\s\xAD]*$/.test(para.lastChild.nodeValue)) || |
| para.lastChild.tagName=='BR'){ |
| |
| dojo.destroy(para.lastChild); |
| } |
| } |
| if(!para.childNodes.length){ |
| para.innerHTML=this.bogusHtmlContent; |
| } |
| }, |
| _fixNewLineBehaviorForIE: function(d){ |
| // summary: |
| // Insert CSS so <p> nodes don't have spacing around them, |
| // thus hiding the fact that ENTER key on IE is creating new |
| // paragraphs |
| |
| // cannot use !important since there may be custom user styling; |
| var doc = this.editor.document; |
| if(doc.__INSERTED_EDITIOR_NEWLINE_CSS === undefined){ |
| var style = dojo.create("style", {type: "text/css"}, doc.getElementsByTagName("head")[0]); |
| style.styleSheet.cssText = "p{margin:0;}"; // cannot use !important since there may be custom user styling; |
| this.editor.document.__INSERTED_EDITIOR_NEWLINE_CSS = true; |
| } |
| return d; |
| }, |
| regularPsToSingleLinePs: function(element, noWhiteSpaceInEmptyP){ |
| // summary: |
| // Converts a <p> node containing <br>'s into multiple <p> nodes. |
| // description: |
| // See singleLinePsToRegularPs(). This method does the |
| // opposite thing, and is used as a pre-filter when loading the |
| // editor, to mirror the effects of the post-filter at end of edit. |
| // tags: |
| // private |
| function wrapLinesInPs(el){ |
| // move "lines" of top-level text nodes into ps |
| function wrapNodes(nodes){ |
| // nodes are assumed to all be siblings |
| var newP = nodes[0].ownerDocument.createElement('p'); // FIXME: not very idiomatic |
| nodes[0].parentNode.insertBefore(newP, nodes[0]); |
| dojo.forEach(nodes, function(node){ |
| newP.appendChild(node); |
| }); |
| } |
| |
| var currentNodeIndex = 0; |
| var nodesInLine = []; |
| var currentNode; |
| while(currentNodeIndex < el.childNodes.length){ |
| currentNode = el.childNodes[currentNodeIndex]; |
| if( currentNode.nodeType==3 || // text node |
| (currentNode.nodeType==1 && currentNode.nodeName!='BR' && dojo.style(currentNode, "display")!="block") |
| ){ |
| nodesInLine.push(currentNode); |
| }else{ |
| // hit line delimiter; process nodesInLine if there are any |
| var nextCurrentNode = currentNode.nextSibling; |
| if(nodesInLine.length){ |
| wrapNodes(nodesInLine); |
| currentNodeIndex = (currentNodeIndex+1)-nodesInLine.length; |
| if(currentNode.nodeName=="BR"){ |
| dojo.destroy(currentNode); |
| } |
| } |
| nodesInLine = []; |
| } |
| currentNodeIndex++; |
| } |
| if(nodesInLine.length){ wrapNodes(nodesInLine); } |
| } |
| |
| function splitP(el){ |
| // split a paragraph into seperate paragraphs at BRs |
| var currentNode = null; |
| var trailingNodes = []; |
| var lastNodeIndex = el.childNodes.length-1; |
| for(var i=lastNodeIndex; i>=0; i--){ |
| currentNode = el.childNodes[i]; |
| if(currentNode.nodeName=="BR"){ |
| var newP = currentNode.ownerDocument.createElement('p'); |
| dojo.place(newP, el, "after"); |
| if(trailingNodes.length==0 && i != lastNodeIndex){ |
| newP.innerHTML = " " |
| } |
| dojo.forEach(trailingNodes, function(node){ |
| newP.appendChild(node); |
| }); |
| dojo.destroy(currentNode); |
| trailingNodes = []; |
| }else{ |
| trailingNodes.unshift(currentNode); |
| } |
| } |
| } |
| |
| var pList = []; |
| var ps = element.getElementsByTagName('p'); |
| dojo.forEach(ps, function(p){ pList.push(p); }); |
| dojo.forEach(pList, function(p){ |
| var prevSib = p.previousSibling; |
| if( (prevSib) && (prevSib.nodeType == 1) && |
| (prevSib.nodeName == 'P' || dojo.style(prevSib, 'display') != 'block') |
| ){ |
| var newP = p.parentNode.insertBefore(this.document.createElement('p'), p); |
| // this is essential to prevent IE from losing the P. |
| // if it's going to be innerHTML'd later we need |
| // to add the to _really_ force the issue |
| newP.innerHTML = noWhiteSpaceInEmptyP ? "" : " "; |
| } |
| splitP(p); |
| },this.editor); |
| wrapLinesInPs(element); |
| return element; |
| }, |
| |
| singleLinePsToRegularPs: function(element){ |
| // summary: |
| // Called as post-filter. |
| // Apparently collapses adjacent <p> nodes into a single <p> |
| // nodes with <br> separating each line. |
| // |
| // example: |
| // Given this input: |
| // | <p>line 1</p> |
| // | <p>line 2</p> |
| // | <ol> |
| // | <li>item 1 |
| // | <li>item 2 |
| // | </ol> |
| // | <p>line 3</p> |
| // | <p>line 4</p> |
| // |
| // Will convert to: |
| // | <p>line 1<br>line 2</p> |
| // | <ol> |
| // | <li>item 1 |
| // | <li>item 2 |
| // | </ol> |
| // | <p>line 3<br>line 4</p> |
| // |
| // Not sure why this situation would even come up after the pre-filter and |
| // the enter-key-handling code. |
| // |
| // tags: |
| // private |
| |
| function getParagraphParents(node){ |
| // summary: |
| // Used to get list of all nodes that contain paragraphs. |
| // Seems like that would just be the very top node itself, but apparently not. |
| var ps = node.getElementsByTagName('p'); |
| var parents = []; |
| for(var i=0; i<ps.length; i++){ |
| var p = ps[i]; |
| var knownParent = false; |
| for(var k=0; k < parents.length; k++){ |
| if(parents[k] === p.parentNode){ |
| knownParent = true; |
| break; |
| } |
| } |
| if(!knownParent){ |
| parents.push(p.parentNode); |
| } |
| } |
| return parents; |
| } |
| |
| function isParagraphDelimiter(node){ |
| return (!node.childNodes.length || node.innerHTML==" "); |
| } |
| |
| var paragraphContainers = getParagraphParents(element); |
| for(var i=0; i<paragraphContainers.length; i++){ |
| var container = paragraphContainers[i]; |
| var firstPInBlock = null; |
| var node = container.firstChild; |
| var deleteNode = null; |
| while(node){ |
| if(node.nodeType != 1 || node.tagName != 'P' || |
| (node.getAttributeNode('style') || {/*no style*/}).specified){ |
| firstPInBlock = null; |
| }else if(isParagraphDelimiter(node)){ |
| deleteNode = node; |
| firstPInBlock = null; |
| }else{ |
| if(firstPInBlock == null){ |
| firstPInBlock = node; |
| }else{ |
| if( (!firstPInBlock.lastChild || firstPInBlock.lastChild.nodeName != 'BR') && |
| (node.firstChild) && |
| (node.firstChild.nodeName != 'BR') |
| ){ |
| firstPInBlock.appendChild(this.editor.document.createElement('br')); |
| } |
| while(node.firstChild){ |
| firstPInBlock.appendChild(node.firstChild); |
| } |
| deleteNode = node; |
| } |
| } |
| node = node.nextSibling; |
| if(deleteNode){ |
| dojo.destroy(deleteNode); |
| deleteNode = null; |
| } |
| } |
| } |
| return element; |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.Editor"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.Editor"] = true; |
| dojo.provide("dijit.Editor"); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.Editor", |
| dijit._editor.RichText, |
| { |
| // summary: |
| // A rich text Editing widget |
| // |
| // description: |
| // This widget provides basic WYSIWYG editing features, based on the browser's |
| // underlying rich text editing capability, accompanied by a toolbar (`dijit.Toolbar`). |
| // A plugin model is available to extend the editor's capabilities as well as the |
| // the options available in the toolbar. Content generation may vary across |
| // browsers, and clipboard operations may have different results, to name |
| // a few limitations. Note: this widget should not be used with the HTML |
| // <TEXTAREA> tag -- see dijit._editor.RichText for details. |
| |
| // plugins: Object[] |
| // A list of plugin names (as strings) or instances (as objects) |
| // for this widget. |
| // |
| // When declared in markup, it might look like: |
| // | plugins="['bold',{name:'dijit._editor.plugins.FontChoice', command:'fontName', generic:true}]" |
| plugins: null, |
| |
| // extraPlugins: Object[] |
| // A list of extra plugin names which will be appended to plugins array |
| extraPlugins: null, |
| |
| constructor: function(){ |
| // summary: |
| // Runs on widget initialization to setup arrays etc. |
| // tags: |
| // private |
| |
| if(!dojo.isArray(this.plugins)){ |
| this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|", |
| "insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull", |
| "dijit._editor.plugins.EnterKeyHandling" /*, "createLink"*/]; |
| } |
| |
| this._plugins=[]; |
| this._editInterval = this.editActionInterval * 1000; |
| |
| //IE will always lose focus when other element gets focus, while for FF and safari, |
| //when no iframe is used, focus will be lost whenever another element gets focus. |
| //For IE, we can connect to onBeforeDeactivate, which will be called right before |
| //the focus is lost, so we can obtain the selected range. For other browsers, |
| //no equivelent of onBeforeDeactivate, so we need to do two things to make sure |
| //selection is properly saved before focus is lost: 1) when user clicks another |
| //element in the page, in which case we listen to mousedown on the entire page and |
| //see whether user clicks out of a focus editor, if so, save selection (focus will |
| //only lost after onmousedown event is fired, so we can obtain correct caret pos.) |
| //2) when user tabs away from the editor, which is handled in onKeyDown below. |
| if(dojo.isIE){ |
| this.events.push("onBeforeDeactivate"); |
| this.events.push("onBeforeActivate"); |
| } |
| }, |
| |
| postCreate: function(){ |
| //for custom undo/redo |
| if(this.customUndo){ |
| dojo['require']("dijit._editor.range"); |
| this._steps=this._steps.slice(0); |
| this._undoedSteps=this._undoedSteps.slice(0); |
| // this.addKeyHandler('z',this.KEY_CTRL,this.undo); |
| // this.addKeyHandler('y',this.KEY_CTRL,this.redo); |
| } |
| if(dojo.isArray(this.extraPlugins)){ |
| this.plugins=this.plugins.concat(this.extraPlugins); |
| } |
| |
| // try{ |
| this.inherited(arguments); |
| // dijit.Editor.superclass.postCreate.apply(this, arguments); |
| |
| this.commands = dojo.i18n.getLocalization("dijit._editor", "commands", this.lang); |
| |
| if(!this.toolbar){ |
| // if we haven't been assigned a toolbar, create one |
| this.toolbar = new dijit.Toolbar({}); |
| dojo.place(this.toolbar.domNode, this.editingArea, "before"); |
| } |
| |
| dojo.forEach(this.plugins, this.addPlugin, this); |
| this.onNormalizedDisplayChanged(); //update toolbar button status |
| // }catch(e){ console.debug(e); } |
| |
| this.toolbar.startup(); |
| }, |
| destroy: function(){ |
| dojo.forEach(this._plugins, function(p){ |
| if(p && p.destroy){ |
| p.destroy(); |
| } |
| }); |
| this._plugins=[]; |
| this.toolbar.destroyRecursive(); |
| delete this.toolbar; |
| this.inherited(arguments); |
| }, |
| addPlugin: function(/*String||Object*/plugin, /*Integer?*/index){ |
| // summary: |
| // takes a plugin name as a string or a plugin instance and |
| // adds it to the toolbar and associates it with this editor |
| // instance. The resulting plugin is added to the Editor's |
| // plugins array. If index is passed, it's placed in the plugins |
| // array at that index. No big magic, but a nice helper for |
| // passing in plugin names via markup. |
| // |
| // plugin: String, args object or plugin instance |
| // |
| // args: |
| // This object will be passed to the plugin constructor |
| // |
| // index: Integer |
| // Used when creating an instance from |
| // something already in this.plugins. Ensures that the new |
| // instance is assigned to this.plugins at that index. |
| var args=dojo.isString(plugin)?{name:plugin}:plugin; |
| if(!args.setEditor){ |
| var o={"args":args,"plugin":null,"editor":this}; |
| dojo.publish(dijit._scopeName + ".Editor.getPlugin",[o]); |
| if(!o.plugin){ |
| var pc = dojo.getObject(args.name); |
| if(pc){ |
| o.plugin=new pc(args); |
| } |
| } |
| if(!o.plugin){ |
| console.warn('Cannot find plugin',plugin); |
| return; |
| } |
| plugin=o.plugin; |
| } |
| if(arguments.length > 1){ |
| this._plugins[index] = plugin; |
| }else{ |
| this._plugins.push(plugin); |
| } |
| plugin.setEditor(this); |
| if(dojo.isFunction(plugin.setToolbar)){ |
| plugin.setToolbar(this.toolbar); |
| } |
| }, |
| //the following 3 functions are required to make the editor play nice under a layout widget, see #4070 |
| startup: function(){ |
| // summary: |
| // Exists to make Editor work as a child of a layout widget. |
| // Developers don't need to call this method. |
| // tags: |
| // protected |
| //console.log('startup',arguments); |
| }, |
| resize: function(size){ |
| // summary: |
| // Resize the editor to the specified size, see `dijit.layout._LayoutWidget.resize` |
| if(size){ |
| // we've been given a height/width for the entire editor (toolbar + contents), calls layout() |
| // to split the allocated size between the toolbar and the contents |
| dijit.layout._LayoutWidget.prototype.resize.apply(this, arguments); |
| } |
| /* |
| else{ |
| // do nothing, the editor is already laid out correctly. The user has probably specified |
| // the height parameter, which was used to set a size on the iframe |
| } |
| */ |
| }, |
| layout: function(){ |
| // summary: |
| // Called from `dijit.layout._LayoutWidget.resize`. This shouldn't be called directly |
| // tags: |
| // protected |
| |
| // Converts the iframe (or rather the <div> surrounding it) to take all the available space |
| // except what's needed for the toolbar |
| this.editingArea.style.height = (this._contentBox.h - dojo.marginBox(this.toolbar.domNode).h)+"px"; |
| if(this.iframe){ |
| this.iframe.style.height="100%"; |
| } |
| this._layoutMode = true; |
| }, |
| _onIEMouseDown: function(/*Event*/ e){ |
| // summary: |
| // IE only to prevent 2 clicks to focus |
| // tags: |
| // private |
| |
| var outsideClientArea = this.document.body.componentFromPoint(e.x, e.y); |
| if(!outsideClientArea){ |
| delete this._savedSelection; // new mouse position overrides old selection |
| if(e.target.tagName == "BODY"){ |
| setTimeout(dojo.hitch(this, "placeCursorAtEnd"), 0); |
| } |
| this.inherited(arguments); |
| } |
| }, |
| onBeforeActivate: function(e){ |
| this._restoreSelection(); |
| }, |
| onBeforeDeactivate: function(e){ |
| // summary: |
| // Called on IE right before focus is lost. Saves the selected range. |
| // tags: |
| // private |
| if(this.customUndo){ |
| this.endEditing(true); |
| } |
| //in IE, the selection will be lost when other elements get focus, |
| //let's save focus before the editor is deactivated |
| if(e.target.tagName != "BODY"){ |
| this._saveSelection(); |
| } |
| //console.log('onBeforeDeactivate',this); |
| }, |
| |
| /* beginning of custom undo/redo support */ |
| |
| // customUndo: Boolean |
| // Whether we shall use custom undo/redo support instead of the native |
| // browser support. By default, we only enable customUndo for IE, as it |
| // has broken native undo/redo support. Note: the implementation does |
| // support other browsers which have W3C DOM2 Range API implemented. |
| customUndo: dojo.isIE, |
| |
| // editActionInterval: Integer |
| // When using customUndo, not every keystroke will be saved as a step. |
| // Instead typing (including delete) will be grouped together: after |
| // a user stops typing for editActionInterval seconds, a step will be |
| // saved; if a user resume typing within editActionInterval seconds, |
| // the timeout will be restarted. By default, editActionInterval is 3 |
| // seconds. |
| editActionInterval: 3, |
| |
| beginEditing: function(cmd){ |
| // summary: |
| // Called to note that the user has started typing alphanumeric characters, if it's not already noted. |
| // Deals with saving undo; see editActionInterval parameter. |
| // tags: |
| // private |
| if(!this._inEditing){ |
| this._inEditing=true; |
| this._beginEditing(cmd); |
| } |
| if(this.editActionInterval>0){ |
| if(this._editTimer){ |
| clearTimeout(this._editTimer); |
| } |
| this._editTimer = setTimeout(dojo.hitch(this, this.endEditing), this._editInterval); |
| } |
| }, |
| _steps:[], |
| _undoedSteps:[], |
| execCommand: function(cmd){ |
| // summary: |
| // Main handler for executing any commands to the editor, like paste, bold, etc. |
| // Called by plugins, but not meant to be called by end users. |
| // tags: |
| // protected |
| if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){ |
| return this[cmd](); |
| }else{ |
| if(this.customUndo){ |
| this.endEditing(); |
| this._beginEditing(); |
| } |
| var r; |
| try{ |
| r = this.inherited('execCommand', arguments); |
| if(dojo.isWebKit && cmd == 'paste' && !r){ //see #4598: safari does not support invoking paste from js |
| throw { code: 1011 }; // throw an object like Mozilla's error |
| } |
| }catch(e){ |
| //TODO: when else might we get an exception? Do we need the Mozilla test below? |
| if(e.code == 1011 /* Mozilla: service denied */ && /copy|cut|paste/.test(cmd)){ |
| // Warn user of platform limitation. Cannot programmatically access clipboard. See ticket #4136 |
| var sub = dojo.string.substitute, |
| accel = {cut:'X', copy:'C', paste:'V'}; |
| alert(sub(this.commands.systemShortcut, |
| [this.commands[cmd], sub(this.commands[dojo.isMac ? 'appleKey' : 'ctrlKey'], [accel[cmd]])])); |
| } |
| r = false; |
| } |
| if(this.customUndo){ |
| this._endEditing(); |
| } |
| return r; |
| } |
| }, |
| queryCommandEnabled: function(cmd){ |
| // summary: |
| // Returns true if specified editor command is enabled. |
| // Used by the plugins to know when to highlight/not highlight buttons. |
| // tags: |
| // protected |
| if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){ |
| return cmd == 'undo' ? (this._steps.length > 1) : (this._undoedSteps.length > 0); |
| }else{ |
| return this.inherited('queryCommandEnabled',arguments); |
| } |
| }, |
| |
| _moveToBookmark: function(b){ |
| // summary: |
| // Selects the text specified in bookmark b |
| // tags: |
| // private |
| var bookmark = b.mark; |
| var mark = b.mark; |
| var col = b.isCollapsed; |
| if(dojo.isIE){ |
| if(dojo.isArray(mark)){//IE CONTROL |
| bookmark = []; |
| dojo.forEach(mark,function(n){ |
| bookmark.push(dijit.range.getNode(n,this.editNode)); |
| },this); |
| } |
| }else{//w3c range |
| var r=dijit.range.create(this.window); |
| r.setStart(dijit.range.getNode(b.startContainer,this.editNode),b.startOffset); |
| r.setEnd(dijit.range.getNode(b.endContainer,this.editNode),b.endOffset); |
| bookmark=r; |
| } |
| dojo.withGlobal(this.window,'moveToBookmark',dijit,[{mark: bookmark, isCollapsed: col}]); |
| }, |
| |
| _changeToStep: function(from, to){ |
| // summary: |
| // Reverts editor to "to" setting, from the undo stack. |
| // tags: |
| // private |
| this.setValue(to.text); |
| var b=to.bookmark; |
| if(!b){ return; } |
| this._moveToBookmark(b); |
| }, |
| undo: function(){ |
| // summary: |
| // Handler for editor undo (ex: ctrl-z) operation |
| // tags: |
| // private |
| // console.log('undo'); |
| this.endEditing(true); |
| var s=this._steps.pop(); |
| if(this._steps.length>0){ |
| this.focus(); |
| this._changeToStep(s,this._steps[this._steps.length-1]); |
| this._undoedSteps.push(s); |
| this.onDisplayChanged(); |
| return true; |
| } |
| return false; |
| }, |
| redo: function(){ |
| // summary: |
| // Handler for editor redo (ex: ctrl-y) operation |
| // tags: |
| // private |
| |
| // console.log('redo'); |
| this.endEditing(true); |
| var s=this._undoedSteps.pop(); |
| if(s && this._steps.length>0){ |
| this.focus(); |
| this._changeToStep(this._steps[this._steps.length-1],s); |
| this._steps.push(s); |
| this.onDisplayChanged(); |
| return true; |
| } |
| return false; |
| }, |
| endEditing: function(ignore_caret){ |
| // summary: |
| // Called to note that the user has stopped typing alphanumeric characters, if it's not already noted. |
| // Deals with saving undo; see editActionInterval parameter. |
| // tags: |
| // private |
| if(this._editTimer){ |
| clearTimeout(this._editTimer); |
| } |
| if(this._inEditing){ |
| this._endEditing(ignore_caret); |
| this._inEditing=false; |
| } |
| }, |
| _getBookmark: function(){ |
| // summary: |
| // Get the currently selected text |
| // tags: |
| // protected |
| var b=dojo.withGlobal(this.window,dijit.getBookmark); |
| var tmp=[]; |
| if(b.mark){ |
| var mark = b.mark; |
| if(dojo.isIE){ |
| if(dojo.isArray(mark)){//CONTROL |
| dojo.forEach(mark,function(n){ |
| tmp.push(dijit.range.getIndex(n,this.editNode).o); |
| },this); |
| b.mark = tmp; |
| } |
| }else{//w3c range |
| tmp=dijit.range.getIndex(mark.startContainer,this.editNode).o; |
| b.mark ={startContainer:tmp, |
| startOffset:mark.startOffset, |
| endContainer:mark.endContainer === mark.startContainer?tmp:dijit.range.getIndex(mark.endContainer,this.editNode).o, |
| endOffset:mark.endOffset}; |
| } |
| } |
| return b; |
| }, |
| _beginEditing: function(cmd){ |
| // summary: |
| // Called when the user starts typing alphanumeric characters. |
| // Deals with saving undo; see editActionInterval parameter. |
| // tags: |
| // private |
| if(this._steps.length === 0){ |
| this._steps.push({'text':this.savedContent,'bookmark':this._getBookmark()}); |
| } |
| }, |
| _endEditing: function(ignore_caret){ |
| // summary: |
| // Called when the user stops typing alphanumeric characters. |
| // Deals with saving undo; see editActionInterval parameter. |
| // tags: |
| // private |
| var v=this.getValue(true); |
| |
| this._undoedSteps=[];//clear undoed steps |
| this._steps.push({text: v, bookmark: this._getBookmark()}); |
| }, |
| onKeyDown: function(e){ |
| // summary: |
| // Handler for onkeydown event. |
| // tags: |
| // private |
| |
| //We need to save selection if the user TAB away from this editor |
| //no need to call _saveSelection for IE, as that will be taken care of in onBeforeDeactivate |
| if(!dojo.isIE && !this.iframe && e.keyCode == dojo.keys.TAB && !this.tabIndent){ |
| this._saveSelection(); |
| } |
| if(!this.customUndo){ |
| this.inherited(arguments); |
| return; |
| } |
| var k = e.keyCode, ks = dojo.keys; |
| if(e.ctrlKey && !e.altKey){//undo and redo only if the special right Alt + z/y are not pressed #5892 |
| if(k == 90 || k == 122){ //z |
| dojo.stopEvent(e); |
| this.undo(); |
| return; |
| }else if(k == 89 || k == 121){ //y |
| dojo.stopEvent(e); |
| this.redo(); |
| return; |
| } |
| } |
| this.inherited(arguments); |
| |
| switch(k){ |
| case ks.ENTER: |
| case ks.BACKSPACE: |
| case ks.DELETE: |
| this.beginEditing(); |
| break; |
| case 88: //x |
| case 86: //v |
| if(e.ctrlKey && !e.altKey && !e.metaKey){ |
| this.endEditing();//end current typing step if any |
| if(e.keyCode == 88){ |
| this.beginEditing('cut'); |
| //use timeout to trigger after the cut is complete |
| setTimeout(dojo.hitch(this, this.endEditing), 1); |
| }else{ |
| this.beginEditing('paste'); |
| //use timeout to trigger after the paste is complete |
| setTimeout(dojo.hitch(this, this.endEditing), 1); |
| } |
| break; |
| } |
| //pass through |
| default: |
| if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCode<dojo.keys.F1 || e.keyCode>dojo.keys.F15)){ |
| this.beginEditing(); |
| break; |
| } |
| //pass through |
| case ks.ALT: |
| this.endEditing(); |
| break; |
| case ks.UP_ARROW: |
| case ks.DOWN_ARROW: |
| case ks.LEFT_ARROW: |
| case ks.RIGHT_ARROW: |
| case ks.HOME: |
| case ks.END: |
| case ks.PAGE_UP: |
| case ks.PAGE_DOWN: |
| this.endEditing(true); |
| break; |
| //maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed |
| case ks.CTRL: |
| case ks.SHIFT: |
| case ks.TAB: |
| break; |
| } |
| }, |
| _onBlur: function(){ |
| // summary: |
| // Called from focus manager when focus has moved away from this editor |
| // tags: |
| // protected |
| |
| //this._saveSelection(); |
| this.inherited('_onBlur',arguments); |
| this.endEditing(true); |
| }, |
| _saveSelection: function(){ |
| // summary: |
| // Save the currently selected text in _savedSelection attribute |
| // tags: |
| // private |
| this._savedSelection=this._getBookmark(); |
| //console.log('save selection',this._savedSelection,this); |
| }, |
| _restoreSelection: function(){ |
| // summary: |
| // Re-select the text specified in _savedSelection attribute; |
| // see _saveSelection(). |
| // tags: |
| // private |
| if(this._savedSelection){ |
| // only restore the selection if the current range is collapsed |
| // if not collapsed, then it means the editor does not lose |
| // selection and there is no need to restore it |
| if(dojo.withGlobal(this.window,'isCollapsed',dijit)){ |
| this._moveToBookmark(this._savedSelection); |
| } |
| delete this._savedSelection; |
| } |
| }, |
| |
| onClick: function(){ |
| // summary: |
| // Handler for when editor is clicked |
| // tags: |
| // protected |
| this.endEditing(true); |
| this.inherited(arguments); |
| } |
| /* end of custom undo/redo support */ |
| } |
| ); |
| |
| // Register the "default plugins", ie, the built-in editor commands |
| dojo.subscribe(dijit._scopeName + ".Editor.getPlugin",null,function(o){ |
| if(o.plugin){ return; } |
| var args = o.args, p; |
| var _p = dijit._editor._Plugin; |
| var name = args.name; |
| switch(name){ |
| case "undo": case "redo": case "cut": case "copy": case "paste": case "insertOrderedList": |
| case "insertUnorderedList": case "indent": case "outdent": case "justifyCenter": |
| case "justifyFull": case "justifyLeft": case "justifyRight": case "delete": |
| case "selectAll": case "removeFormat": case "unlink": |
| case "insertHorizontalRule": |
| p = new _p({ command: name }); |
| break; |
| |
| case "bold": case "italic": case "underline": case "strikethrough": |
| case "subscript": case "superscript": |
| p = new _p({ buttonClass: dijit.form.ToggleButton, command: name }); |
| break; |
| case "|": |
| p = new _p({ button: new dijit.ToolbarSeparator() }); |
| } |
| // console.log('name',name,p); |
| o.plugin=p; |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.MenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.MenuItem"] = true; |
| dojo.provide("dijit.MenuItem"); |
| |
| |
| |
| |
| |
| dojo.declare("dijit.MenuItem", |
| [dijit._Widget, dijit._Templated, dijit._Contained], |
| { |
| // summary: |
| // A line item in a Menu Widget |
| |
| // Make 3 columns |
| // icon, label, and expand arrow (BiDi-dependent) indicating sub-menu |
| templateString: dojo.cache("dijit", "templates/MenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" waiRole=\"menuitem\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset\" waiRole=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuItemIcon\" dojoAttachPoint=\"iconNode\">\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" waiRole=\"presentation\">\n\t\t<div dojoAttachPoint=\"arrowWrapper\" style=\"visibility: hidden\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuExpand\">\n\t\t\t<span class=\"dijitMenuExpandA11y\">+</span>\n\t\t</div>\n\t</td>\n</tr>\n"), |
| |
| attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { |
| label: { node: "containerNode", type: "innerHTML" }, |
| iconClass: { node: "iconNode", type: "class" } |
| }), |
| |
| // label: String |
| // Menu text |
| label: '', |
| |
| // iconClass: String |
| // Class to apply to DOMNode to make it display an icon. |
| iconClass: "", |
| |
| // accelKey: String |
| // Text for the accelerator (shortcut) key combination. |
| // Note that although Menu can display accelerator keys there |
| // is no infrastructure to actually catch and execute these |
| // accelerators. |
| accelKey: "", |
| |
| // disabled: Boolean |
| // If true, the menu item is disabled. |
| // If false, the menu item is enabled. |
| disabled: false, |
| |
| _fillContent: function(/*DomNode*/ source){ |
| // If button label is specified as srcNodeRef.innerHTML rather than |
| // this.params.label, handle it here. |
| if(source && !("label" in this.params)){ |
| this.attr('label', source.innerHTML); |
| } |
| }, |
| |
| postCreate: function(){ |
| dojo.setSelectable(this.domNode, false); |
| var label = this.id+"_text"; |
| dojo.attr(this.containerNode, "id", label); |
| if(this.accelKeyNode){ |
| dojo.attr(this.accelKeyNode, "id", this.id + "_accel"); |
| label += " " + this.id + "_accel"; |
| } |
| dijit.setWaiState(this.domNode, "labelledby", label); |
| }, |
| |
| _onHover: function(){ |
| // summary: |
| // Handler when mouse is moved onto menu item |
| // tags: |
| // protected |
| dojo.addClass(this.domNode, 'dijitMenuItemHover'); |
| this.getParent().onItemHover(this); |
| }, |
| |
| _onUnhover: function(){ |
| // summary: |
| // Handler when mouse is moved off of menu item, |
| // possibly to a child menu, or maybe to a sibling |
| // menuitem or somewhere else entirely. |
| // tags: |
| // protected |
| |
| // if we are unhovering the currently selected item |
| // then unselect it |
| dojo.removeClass(this.domNode, 'dijitMenuItemHover'); |
| this.getParent().onItemUnhover(this); |
| }, |
| |
| _onClick: function(evt){ |
| // summary: |
| // Internal handler for click events on MenuItem. |
| // tags: |
| // private |
| this.getParent().onItemClick(this, evt); |
| dojo.stopEvent(evt); |
| }, |
| |
| onClick: function(/*Event*/ evt){ |
| // summary: |
| // User defined function to handle clicks |
| // tags: |
| // callback |
| }, |
| |
| focus: function(){ |
| // summary: |
| // Focus on this MenuItem |
| try{ |
| if(dojo.isIE == 8){ |
| // needed for IE8 which won't scroll TR tags into view on focus yet calling scrollIntoView creates flicker (#10275) |
| this.containerNode.focus(); |
| } |
| dijit.focus(this.focusNode); |
| }catch(e){ |
| // this throws on IE (at least) in some scenarios |
| } |
| }, |
| |
| _onFocus: function(){ |
| // summary: |
| // This is called by the focus manager when focus |
| // goes to this MenuItem or a child menu. |
| // tags: |
| // protected |
| this._setSelected(true); |
| this.getParent()._onItemFocus(this); |
| |
| this.inherited(arguments); |
| }, |
| |
| _setSelected: function(selected){ |
| // summary: |
| // Indicate that this node is the currently selected one |
| // tags: |
| // private |
| |
| /*** |
| * TODO: remove this method and calls to it, when _onBlur() is working for MenuItem. |
| * Currently _onBlur() gets called when focus is moved from the MenuItem to a child menu. |
| * That's not supposed to happen, but the problem is: |
| * In order to allow dijit.popup's getTopPopup() to work,a sub menu's popupParent |
| * points to the parent Menu, bypassing the parent MenuItem... thus the |
| * MenuItem is not in the chain of active widgets and gets a premature call to |
| * _onBlur() |
| */ |
| |
| dojo.toggleClass(this.domNode, "dijitMenuItemSelected", selected); |
| }, |
| |
| setLabel: function(/*String*/ content){ |
| // summary: |
| // Deprecated. Use attr('label', ...) instead. |
| // tags: |
| // deprecated |
| dojo.deprecated("dijit.MenuItem.setLabel() is deprecated. Use attr('label', ...) instead.", "", "2.0"); |
| this.attr("label", content); |
| }, |
| |
| setDisabled: function(/*Boolean*/ disabled){ |
| // summary: |
| // Deprecated. Use attr('disabled', bool) instead. |
| // tags: |
| // deprecated |
| dojo.deprecated("dijit.Menu.setDisabled() is deprecated. Use attr('disabled', bool) instead.", "", "2.0"); |
| this.attr('disabled', disabled); |
| }, |
| _setDisabledAttr: function(/*Boolean*/ value){ |
| // summary: |
| // Hook for attr('disabled', ...) to work. |
| // Enable or disable this menu item. |
| this.disabled = value; |
| dojo[value ? "addClass" : "removeClass"](this.domNode, 'dijitMenuItemDisabled'); |
| dijit.setWaiState(this.focusNode, 'disabled', value ? 'true' : 'false'); |
| }, |
| _setAccelKeyAttr: function(/*String*/ value){ |
| // summary: |
| // Hook for attr('accelKey', ...) to work. |
| // Set accelKey on this menu item. |
| this.accelKey=value; |
| |
| this.accelKeyNode.style.display=value?"":"none"; |
| this.accelKeyNode.innerHTML=value; |
| //have to use colSpan to make it work in IE |
| dojo.attr(this.containerNode,'colSpan',value?"1":"2"); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.PopupMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.PopupMenuItem"] = true; |
| dojo.provide("dijit.PopupMenuItem"); |
| |
| |
| |
| dojo.declare("dijit.PopupMenuItem", |
| dijit.MenuItem, |
| { |
| _fillContent: function(){ |
| // summary: |
| // When Menu is declared in markup, this code gets the menu label and |
| // the popup widget from the srcNodeRef. |
| // description: |
| // srcNodeRefinnerHTML contains both the menu item text and a popup widget |
| // The first part holds the menu item text and the second part is the popup |
| // example: |
| // | <div dojoType="dijit.PopupMenuItem"> |
| // | <span>pick me</span> |
| // | <popup> ... </popup> |
| // | </div> |
| // tags: |
| // protected |
| |
| if(this.srcNodeRef){ |
| var nodes = dojo.query("*", this.srcNodeRef); |
| dijit.PopupMenuItem.superclass._fillContent.call(this, nodes[0]); |
| |
| // save pointer to srcNode so we can grab the drop down widget after it's instantiated |
| this.dropDownContainer = this.srcNodeRef; |
| } |
| }, |
| |
| startup: function(){ |
| if(this._started){ return; } |
| this.inherited(arguments); |
| |
| // we didn't copy the dropdown widget from the this.srcNodeRef, so it's in no-man's |
| // land now. move it to dojo.doc.body. |
| if(!this.popup){ |
| var node = dojo.query("[widgetId]", this.dropDownContainer)[0]; |
| this.popup = dijit.byNode(node); |
| } |
| dojo.body().appendChild(this.popup.domNode); |
| |
| this.popup.domNode.style.display="none"; |
| if(this.arrowWrapper){ |
| dojo.style(this.arrowWrapper, "visibility", ""); |
| } |
| dijit.setWaiState(this.focusNode, "haspopup", "true"); |
| }, |
| |
| destroyDescendants: function(){ |
| if(this.popup){ |
| // Destroy the popup, unless it's already been destroyed. This can happen because |
| // the popup is a direct child of <body> even though it's logically my child. |
| if(!this.popup._destroyed){ |
| this.popup.destroyRecursive(); |
| } |
| delete this.popup; |
| } |
| this.inherited(arguments); |
| } |
| }); |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.CheckedMenuItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.CheckedMenuItem"] = true; |
| dojo.provide("dijit.CheckedMenuItem"); |
| |
| |
| |
| dojo.declare("dijit.CheckedMenuItem", |
| dijit.MenuItem, |
| { |
| // summary: |
| // A checkbox-like menu item for toggling on and off |
| |
| templateString: dojo.cache("dijit", "templates/CheckedMenuItem.html", "<tr class=\"dijitReset dijitMenuItem\" dojoAttachPoint=\"focusNode\" waiRole=\"menuitemcheckbox\" tabIndex=\"-1\"\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<td class=\"dijitReset\" waiRole=\"presentation\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitMenuItemIcon dijitCheckedMenuItemIcon\" dojoAttachPoint=\"iconNode\">\n\t\t<span class=\"dijitCheckedMenuItemIconChar\">✓</span>\n\t</td>\n\t<td class=\"dijitReset dijitMenuItemLabel\" colspan=\"2\" dojoAttachPoint=\"containerNode,labelNode\"></td>\n\t<td class=\"dijitReset dijitMenuItemAccelKey\" style=\"display: none\" dojoAttachPoint=\"accelKeyNode\"></td>\n\t<td class=\"dijitReset dijitMenuArrowCell\" waiRole=\"presentation\">\n\t</td>\n</tr>\n"), |
| |
| // checked: Boolean |
| // Our checked state |
| checked: false, |
| _setCheckedAttr: function(/*Boolean*/ checked){ |
| // summary: |
| // Hook so attr('checked', bool) works. |
| // Sets the class and state for the check box. |
| dojo.toggleClass(this.domNode, "dijitCheckedMenuItemChecked", checked); |
| dijit.setWaiState(this.domNode, "checked", checked); |
| this.checked = checked; |
| }, |
| |
| onChange: function(/*Boolean*/ checked){ |
| // summary: |
| // User defined function to handle check/uncheck events |
| // tags: |
| // callback |
| }, |
| |
| _onClick: function(/*Event*/ e){ |
| // summary: |
| // Clicking this item just toggles its state |
| // tags: |
| // private |
| if(!this.disabled){ |
| this.attr("checked", !this.checked); |
| this.onChange(this.checked); |
| } |
| this.inherited(arguments); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.MenuSeparator"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.MenuSeparator"] = true; |
| dojo.provide("dijit.MenuSeparator"); |
| |
| |
| |
| |
| |
| dojo.declare("dijit.MenuSeparator", |
| [dijit._Widget, dijit._Templated, dijit._Contained], |
| { |
| // summary: |
| // A line between two menu items |
| |
| templateString: dojo.cache("dijit", "templates/MenuSeparator.html", "<tr class=\"dijitMenuSeparator\">\n\t<td colspan=\"4\">\n\t\t<div class=\"dijitMenuSeparatorTop\"></div>\n\t\t<div class=\"dijitMenuSeparatorBottom\"></div>\n\t</td>\n</tr>\n"), |
| |
| postCreate: function(){ |
| dojo.setSelectable(this.domNode, false); |
| }, |
| |
| isFocusable: function(){ |
| // summary: |
| // Override to always return false |
| // tags: |
| // protected |
| |
| return false; // Boolean |
| } |
| }); |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.Menu"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.Menu"] = true; |
| dojo.provide("dijit.Menu"); |
| |
| |
| |
| |
| |
| dojo.declare("dijit._MenuBase", |
| [dijit._Widget, dijit._Templated, dijit._KeyNavContainer], |
| { |
| // summary: |
| // Base class for Menu and MenuBar |
| |
| // parentMenu: [readonly] Widget |
| // pointer to menu that displayed me |
| parentMenu: null, |
| |
| // popupDelay: Integer |
| // number of milliseconds before hovering (without clicking) causes the popup to automatically open. |
| popupDelay: 500, |
| |
| startup: function(){ |
| if(this._started){ return; } |
| |
| dojo.forEach(this.getChildren(), function(child){ child.startup(); }); |
| this.startupKeyNavChildren(); |
| |
| this.inherited(arguments); |
| }, |
| |
| onExecute: function(){ |
| // summary: |
| // Attach point for notification about when a menu item has been executed. |
| // This is an internal mechanism used for Menus to signal to their parent to |
| // close them, because they are about to execute the onClick handler. In |
| // general developers should not attach to or override this method. |
| // tags: |
| // protected |
| }, |
| |
| onCancel: function(/*Boolean*/ closeAll){ |
| // summary: |
| // Attach point for notification about when the user cancels the current menu |
| // This is an internal mechanism used for Menus to signal to their parent to |
| // close them. In general developers should not attach to or override this method. |
| // tags: |
| // protected |
| }, |
| |
| _moveToPopup: function(/*Event*/ evt){ |
| // summary: |
| // This handles the right arrow key (left arrow key on RTL systems), |
| // which will either open a submenu, or move to the next item in the |
| // ancestor MenuBar |
| // tags: |
| // private |
| |
| if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){ |
| this.focusedChild._onClick(evt); |
| }else{ |
| var topMenu = this._getTopMenu(); |
| if(topMenu && topMenu._isMenuBar){ |
| topMenu.focusNext(); |
| } |
| } |
| }, |
| |
| _onPopupHover: function(/*Event*/ evt){ |
| // summary: |
| // This handler is called when the mouse moves over the popup. |
| // tags: |
| // private |
| |
| // if the mouse hovers over a menu popup that is in pending-close state, |
| // then stop the close operation. |
| // This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker) |
| if(this.currentPopup && this.currentPopup._pendingClose_timer){ |
| var parentMenu = this.currentPopup.parentMenu; |
| // highlight the parent menu item pointing to this popup |
| if(parentMenu.focusedChild){ |
| parentMenu.focusedChild._setSelected(false); |
| } |
| parentMenu.focusedChild = this.currentPopup.from_item; |
| parentMenu.focusedChild._setSelected(true); |
| // cancel the pending close |
| this._stopPendingCloseTimer(this.currentPopup); |
| } |
| }, |
| |
| onItemHover: function(/*MenuItem*/ item){ |
| // summary: |
| // Called when cursor is over a MenuItem. |
| // tags: |
| // protected |
| |
| // Don't do anything unless user has "activated" the menu by: |
| // 1) clicking it |
| // 2) opening it from a parent menu (which automatically focuses it) |
| if(this.isActive){ |
| this.focusChild(item); |
| if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){ |
| this.hover_timer = setTimeout(dojo.hitch(this, "_openPopup"), this.popupDelay); |
| } |
| } |
| // if the user is mixing mouse and keyboard navigation, |
| // then the menu may not be active but a menu item has focus, |
| // but it's not the item that the mouse just hovered over. |
| // To avoid both keyboard and mouse selections, use the latest. |
| if(this.focusedChild){ |
| this.focusChild(item); |
| } |
| this._hoveredChild = item; |
| }, |
| |
| _onChildBlur: function(item){ |
| // summary: |
| // Called when a child MenuItem becomes inactive because focus |
| // has been removed from the MenuItem *and* it's descendant menus. |
| // tags: |
| // private |
| this._stopPopupTimer(); |
| item._setSelected(false); |
| // Close all popups that are open and descendants of this menu |
| var itemPopup = item.popup; |
| if(itemPopup){ |
| this._stopPendingCloseTimer(itemPopup); |
| itemPopup._pendingClose_timer = setTimeout(function(){ |
| itemPopup._pendingClose_timer = null; |
| if(itemPopup.parentMenu){ |
| itemPopup.parentMenu.currentPopup = null; |
| } |
| dijit.popup.close(itemPopup); // this calls onClose |
| }, this.popupDelay); |
| } |
| }, |
| |
| onItemUnhover: function(/*MenuItem*/ item){ |
| // summary: |
| // Callback fires when mouse exits a MenuItem |
| // tags: |
| // protected |
| |
| if(this.isActive){ |
| this._stopPopupTimer(); |
| } |
| if(this._hoveredChild == item){ this._hoveredChild = null; } |
| }, |
| |
| _stopPopupTimer: function(){ |
| // summary: |
| // Cancels the popup timer because the user has stop hovering |
| // on the MenuItem, etc. |
| // tags: |
| // private |
| if(this.hover_timer){ |
| clearTimeout(this.hover_timer); |
| this.hover_timer = null; |
| } |
| }, |
| |
| _stopPendingCloseTimer: function(/*dijit._Widget*/ popup){ |
| // summary: |
| // Cancels the pending-close timer because the close has been preempted |
| // tags: |
| // private |
| if(popup._pendingClose_timer){ |
| clearTimeout(popup._pendingClose_timer); |
| popup._pendingClose_timer = null; |
| } |
| }, |
| |
| _stopFocusTimer: function(){ |
| // summary: |
| // Cancels the pending-focus timer because the menu was closed before focus occured |
| // tags: |
| // private |
| if(this._focus_timer){ |
| clearTimeout(this._focus_timer); |
| this._focus_timer = null; |
| } |
| }, |
| |
| _getTopMenu: function(){ |
| // summary: |
| // Returns the top menu in this chain of Menus |
| // tags: |
| // private |
| for(var top=this; top.parentMenu; top=top.parentMenu); |
| return top; |
| }, |
| |
| onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){ |
| // summary: |
| // Handle clicks on an item. |
| // tags: |
| // private |
| if(item.disabled){ return false; } |
| |
| // this can't be done in _onFocus since the _onFocus events occurs asynchronously |
| if(typeof this.isShowingNow == 'undefined'){ // non-popup menu |
| this._markActive(); |
| } |
| |
| this.focusChild(item); |
| |
| if(item.popup){ |
| this._openPopup(); |
| }else{ |
| // before calling user defined handler, close hierarchy of menus |
| // and restore focus to place it was when menu was opened |
| this.onExecute(); |
| |
| // user defined handler for click |
| item.onClick(evt); |
| } |
| }, |
| |
| _openPopup: function(){ |
| // summary: |
| // Open the popup to the side of/underneath the current menu item |
| // tags: |
| // protected |
| |
| this._stopPopupTimer(); |
| var from_item = this.focusedChild; |
| if(!from_item){ return; } // the focused child lost focus since the timer was started |
| var popup = from_item.popup; |
| if(popup.isShowingNow){ return; } |
| if(this.currentPopup){ |
| this._stopPendingCloseTimer(this.currentPopup); |
| dijit.popup.close(this.currentPopup); |
| } |
| popup.parentMenu = this; |
| popup.from_item = from_item; // helps finding the parent item that should be focused for this popup |
| var self = this; |
| dijit.popup.open({ |
| parent: this, |
| popup: popup, |
| around: from_item.domNode, |
| orient: this._orient || (this.isLeftToRight() ? |
| {'TR': 'TL', 'TL': 'TR', 'BR': 'BL', 'BL': 'BR'} : |
| {'TL': 'TR', 'TR': 'TL', 'BL': 'BR', 'BR': 'BL'}), |
| onCancel: function(){ // called when the child menu is canceled |
| // set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus |
| // which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro) |
| self.focusChild(from_item); // put focus back on my node |
| self._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved) |
| from_item._setSelected(true); // oops, _cleanUp() deselected the item |
| self.focusedChild = from_item; // and unset focusedChild |
| }, |
| onExecute: dojo.hitch(this, "_cleanUp") |
| }); |
| |
| this.currentPopup = popup; |
| // detect mouseovers to handle lazy mouse movements that temporarily focus other menu items |
| popup.connect(popup.domNode, "onmouseenter", dojo.hitch(self, "_onPopupHover")); // cleaned up when the popped-up widget is destroyed on close |
| |
| if(popup.focus){ |
| // If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar), |
| // if the cursor happens to collide with the popup, it will generate an onmouseover event |
| // even though the mouse wasn't moved. Use a setTimeout() to call popup.focus so that |
| // our focus() call overrides the onmouseover event, rather than vice-versa. (#8742) |
| popup._focus_timer = setTimeout(dojo.hitch(popup, function(){ |
| this._focus_timer = null; |
| this.focus(); |
| }), 0); |
| } |
| }, |
| |
| _markActive: function(){ |
| // summary: |
| // Mark this menu's state as active. |
| // Called when this Menu gets focus from: |
| // 1) clicking it (mouse or via space/arrow key) |
| // 2) being opened by a parent menu. |
| // This is not called just from mouse hover. |
| // Focusing a menu via TAB does NOT automatically set isActive |
| // since TAB is a navigation operation and not a selection one. |
| // For Windows apps, pressing the ALT key focuses the menubar |
| // menus (similar to TAB navigation) but the menu is not active |
| // (ie no dropdown) until an item is clicked. |
| this.isActive = true; |
| dojo.addClass(this.domNode, "dijitMenuActive"); |
| dojo.removeClass(this.domNode, "dijitMenuPassive"); |
| }, |
| |
| onOpen: function(/*Event*/ e){ |
| // summary: |
| // Callback when this menu is opened. |
| // This is called by the popup manager as notification that the menu |
| // was opened. |
| // tags: |
| // private |
| |
| this.isShowingNow = true; |
| this._markActive(); |
| }, |
| |
| _markInactive: function(){ |
| // summary: |
| // Mark this menu's state as inactive. |
| this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here |
| dojo.removeClass(this.domNode, "dijitMenuActive"); |
| dojo.addClass(this.domNode, "dijitMenuPassive"); |
| }, |
| |
| onClose: function(){ |
| // summary: |
| // Callback when this menu is closed. |
| // This is called by the popup manager as notification that the menu |
| // was closed. |
| // tags: |
| // private |
| |
| this._stopFocusTimer(); |
| this._markInactive(); |
| this.isShowingNow = false; |
| this.parentMenu = null; |
| }, |
| |
| _closeChild: function(){ |
| // summary: |
| // Called when submenu is clicked or focus is lost. Close hierarchy of menus. |
| // tags: |
| // private |
| this._stopPopupTimer(); |
| if(this.focusedChild){ // unhighlight the focused item |
| this.focusedChild._setSelected(false); |
| this.focusedChild._onUnhover(); |
| this.focusedChild = null; |
| } |
| if(this.currentPopup){ |
| // Close all popups that are open and descendants of this menu |
| dijit.popup.close(this.currentPopup); |
| this.currentPopup = null; |
| } |
| }, |
| |
| _onItemFocus: function(/*MenuItem*/ item){ |
| // summary: |
| // Called when child of this Menu gets focus from: |
| // 1) clicking it |
| // 2) tabbing into it |
| // 3) being opened by a parent menu. |
| // This is not called just from mouse hover. |
| if(this._hoveredChild && this._hoveredChild != item){ |
| this._hoveredChild._onUnhover(); // any previous mouse movement is trumped by focus selection |
| } |
| }, |
| |
| _onBlur: function(){ |
| // summary: |
| // Called when focus is moved away from this Menu and it's submenus. |
| // tags: |
| // protected |
| this._cleanUp(); |
| this.inherited(arguments); |
| }, |
| |
| _cleanUp: function(){ |
| // summary: |
| // Called when the user is done with this menu. Closes hierarchy of menus. |
| // tags: |
| // private |
| |
| this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close |
| if(typeof this.isShowingNow == 'undefined'){ // non-popup menu doesn't call onClose |
| this._markInactive(); |
| } |
| } |
| }); |
| |
| dojo.declare("dijit.Menu", |
| dijit._MenuBase, |
| { |
| // summary |
| // A context menu you can assign to multiple elements |
| |
| // TODO: most of the code in here is just for context menu (right-click menu) |
| // support. In retrospect that should have been a separate class (dijit.ContextMenu). |
| // Split them for 2.0 |
| |
| constructor: function(){ |
| this._bindings = []; |
| }, |
| |
| templateString: dojo.cache("dijit", "templates/Menu.html", "<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" waiRole=\"menu\" tabIndex=\"${tabIndex}\" dojoAttachEvent=\"onkeypress:_onKeyPress\">\n\t<tbody class=\"dijitReset\" dojoAttachPoint=\"containerNode\"></tbody>\n</table>\n"), |
| |
| // targetNodeIds: [const] String[] |
| // Array of dom node ids of nodes to attach to. |
| // Fill this with nodeIds upon widget creation and it becomes context menu for those nodes. |
| targetNodeIds: [], |
| |
| // contextMenuForWindow: [const] Boolean |
| // If true, right clicking anywhere on the window will cause this context menu to open. |
| // If false, must specify targetNodeIds. |
| contextMenuForWindow: false, |
| |
| // leftClickToOpen: [const] Boolean |
| // If true, menu will open on left click instead of right click, similiar to a file menu. |
| leftClickToOpen: false, |
| |
| // refocus: Boolean |
| // When this menu closes, re-focus the element which had focus before it was opened. |
| refocus: true, |
| |
| // _contextMenuWithMouse: [private] Boolean |
| // Used to record mouse and keyboard events to determine if a context |
| // menu is being opened with the keyboard or the mouse. |
| _contextMenuWithMouse: false, |
| |
| postCreate: function(){ |
| if(this.contextMenuForWindow){ |
| this.bindDomNode(dojo.body()); |
| }else{ |
| // TODO: should have _setTargetNodeIds() method to handle initialization and a possible |
| // later attr('targetNodeIds', ...) call. There's also a problem that targetNodeIds[] |
| // gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610) |
| dojo.forEach(this.targetNodeIds, this.bindDomNode, this); |
| } |
| var k = dojo.keys, l = this.isLeftToRight(); |
| this._openSubMenuKey = l ? k.RIGHT_ARROW : k.LEFT_ARROW; |
| this._closeSubMenuKey = l ? k.LEFT_ARROW : k.RIGHT_ARROW; |
| this.connectKeyNavHandlers([k.UP_ARROW], [k.DOWN_ARROW]); |
| }, |
| |
| _onKeyPress: function(/*Event*/ evt){ |
| // summary: |
| // Handle keyboard based menu navigation. |
| // tags: |
| // protected |
| |
| if(evt.ctrlKey || evt.altKey){ return; } |
| |
| switch(evt.charOrCode){ |
| case this._openSubMenuKey: |
| this._moveToPopup(evt); |
| dojo.stopEvent(evt); |
| break; |
| case this._closeSubMenuKey: |
| if(this.parentMenu){ |
| if(this.parentMenu._isMenuBar){ |
| this.parentMenu.focusPrev(); |
| }else{ |
| this.onCancel(false); |
| } |
| }else{ |
| dojo.stopEvent(evt); |
| } |
| break; |
| } |
| }, |
| |
| // thanks burstlib! |
| _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){ |
| // summary: |
| // Returns the window reference of the passed iframe |
| // tags: |
| // private |
| var win = dijit.getDocumentWindow(this._iframeContentDocument(iframe_el)) || |
| // Moz. TODO: is this available when defaultView isn't? |
| this._iframeContentDocument(iframe_el)['__parent__'] || |
| (iframe_el.name && dojo.doc.frames[iframe_el.name]) || null; |
| return win; // Window |
| }, |
| |
| _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){ |
| // summary: |
| // Returns a reference to the document object inside iframe_el |
| // tags: |
| // protected |
| var doc = iframe_el.contentDocument // W3 |
| || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE |
| || (iframe_el.name && dojo.doc.frames[iframe_el.name] && dojo.doc.frames[iframe_el.name].document) |
| || null; |
| return doc; // HTMLDocument |
| }, |
| |
| bindDomNode: function(/*String|DomNode*/ node){ |
| // summary: |
| // Attach menu to given node |
| node = dojo.byId(node); |
| |
| var cn; // Connect node |
| |
| // Support context menus on iframes. Rather than binding to the iframe itself we need |
| // to bind to the <body> node inside the iframe. |
| if(node.tagName.toLowerCase() == "iframe"){ |
| var iframe = node, |
| win = this._iframeContentWindow(iframe); |
| cn = dojo.withGlobal(win, dojo.body); |
| }else{ |
| |
| // To capture these events at the top level, attach to <html>, not <body>. |
| // Otherwise right-click context menu just doesn't work. |
| cn = (node == dojo.body() ? dojo.doc.documentElement : node); |
| } |
| |
| |
| // "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode()) |
| var binding = { |
| node: node, |
| iframe: iframe |
| }; |
| |
| // Save info about binding in _bindings[], and make node itself record index(+1) into |
| // _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may |
| // start with a number, which fails on FF/safari. |
| dojo.attr(node, "_dijitMenu" + this.id, this._bindings.push(binding)); |
| |
| // Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished |
| // loading yet, in which case we need to wait for the onload event first, and then connect |
| var doConnects = dojo.hitch(this, function(cn){ |
| return [ |
| dojo.connect(cn, (this.leftClickToOpen)?"onclick":"oncontextmenu", this, function(evt){ |
| this._openMyself(evt, cn, iframe); |
| }), |
| dojo.connect(cn, "onkeydown", this, "_contextKey"), |
| dojo.connect(cn, "onmousedown", this, "_contextMouse") |
| ]; |
| }); |
| binding.connects = cn ? doConnects(cn) : []; |
| |
| if(iframe){ |
| // Setup handler to [re]bind to the iframe when the contents are initially loaded, |
| // and every time the contents change. |
| // Need to do this b/c we are actually binding to the iframe's <body> node. |
| // Note: can't use dojo.connect(), see #9609. |
| |
| binding.onloadHandler = dojo.hitch(this, function(){ |
| // want to remove old connections, but IE throws exceptions when trying to |
| // access the <body> node because it's already gone, or at least in a state of limbo |
| |
| var win = this._iframeContentWindow(iframe); |
| cn = dojo.withGlobal(win, dojo.body); |
| binding.connects = doConnects(cn); |
| }); |
| if(iframe.addEventListener){ |
| iframe.addEventListener("load", binding.onloadHandler, false); |
| }else{ |
| iframe.attachEvent("onload", binding.onloadHandler); |
| } |
| } |
| }, |
| |
| unBindDomNode: function(/*String|DomNode*/ nodeName){ |
| // summary: |
| // Detach menu from given node |
| |
| var node; |
| try{ |
| node = dojo.byId(nodeName); |
| }catch(e){ |
| // On IE the dojo.byId() call will get an exception if the attach point was |
| // the <body> node of an <iframe> that has since been reloaded (and thus the |
| // <body> node is in a limbo state of destruction. |
| return; |
| } |
| |
| // node["_dijitMenu" + this.id] contains index(+1) into my _bindings[] array |
| var attrName = "_dijitMenu" + this.id; |
| if(node && dojo.hasAttr(node, attrName)){ |
| var bid = dojo.attr(node, attrName)-1, b = this._bindings[bid]; |
| dojo.forEach(b.connects, dojo.disconnect); |
| |
| // Remove listener for iframe onload events |
| var iframe = b.iframe; |
| if(iframe){ |
| if(iframe.removeEventListener){ |
| iframe.removeEventListener("load", b.onloadHandler, false); |
| }else{ |
| iframe.detachEvent("onload", b.onloadHandler); |
| } |
| } |
| |
| dojo.removeAttr(node, attrName); |
| delete this._bindings[bid]; |
| } |
| }, |
| |
| _contextKey: function(e){ |
| // summary: |
| // Code to handle popping up editor using F10 key rather than mouse |
| // tags: |
| // private |
| this._contextMenuWithMouse = false; |
| if(e.keyCode == dojo.keys.F10){ |
| dojo.stopEvent(e); |
| if(e.shiftKey && e.type == "keydown"){ |
| // FF: copying the wrong property from e will cause the system |
| // context menu to appear in spite of stopEvent. Don't know |
| // exactly which properties cause this effect. |
| var _e = { target: e.target, pageX: e.pageX, pageY: e.pageY }; |
| _e.preventDefault = _e.stopPropagation = function(){}; |
| // IE: without the delay, focus work in "open" causes the system |
| // context menu to appear in spite of stopEvent. |
| window.setTimeout(dojo.hitch(this, function(){ this._openMyself(_e); }), 1); |
| } |
| } |
| }, |
| |
| _contextMouse: function(e){ |
| // summary: |
| // Helper to remember when we opened the context menu with the mouse instead |
| // of with the keyboard |
| // tags: |
| // private |
| this._contextMenuWithMouse = true; |
| }, |
| |
| _openMyself: function(/*Event*/ e, /*DomNode?*/ node, /*DomNode?*/ iframe){ |
| // summary: |
| // Internal function for opening myself when the user |
| // does a right-click or something similar. |
| // node: |
| // The node that is being clicked |
| // iframe: |
| // If an <iframe> is being clicked, iframe points to that iframe and node |
| // points to the iframe's body. |
| // tags: |
| // private |
| |
| if(this.leftClickToOpen && e.button>0){ |
| return; |
| } |
| dojo.stopEvent(e); |
| |
| // Get coordinates. |
| // If we are opening the menu with the mouse or on safari open |
| // the menu at the mouse cursor |
| // (Safari does not have a keyboard command to open the context menu |
| // and we don't currently have a reliable way to determine |
| // _contextMenuWithMouse on Safari) |
| var x,y; |
| if(dojo.isSafari || this._contextMenuWithMouse){ |
| x=e.pageX; |
| y=e.pageY; |
| |
| if(iframe){ |
| // Event is on <body> node of an <iframe>, convert coordinates to match main document |
| var od = e.target.ownerDocument, |
| ifc = dojo.position(iframe, true), |
| win = this._iframeContentWindow(iframe), |
| scroll = dojo.withGlobal(win, "_docScroll", dojo); |
| |
| var cs = dojo.getComputedStyle(iframe), |
| tp = dojo._toPixelValue, |
| left = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingLeft)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderLeftWidth) : 0), |
| top = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingTop)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderTopWidth) : 0); |
| |
| x += ifc.x + left - scroll.x; |
| y += ifc.y + top - scroll.y; |
| } |
| }else{ |
| // otherwise open near e.target |
| var coords = dojo.position(e.target, true); |
| x = coords.x + 10; |
| y = coords.y + 10; |
| } |
| |
| var self=this; |
| var savedFocus = dijit.getFocus(this); |
| function closeAndRestoreFocus(){ |
| // user has clicked on a menu or popup |
| if(self.refocus){ |
| dijit.focus(savedFocus); |
| } |
| dijit.popup.close(self); |
| } |
| dijit.popup.open({ |
| popup: this, |
| x: x, |
| y: y, |
| onExecute: closeAndRestoreFocus, |
| onCancel: closeAndRestoreFocus, |
| orient: this.isLeftToRight() ? 'L' : 'R' |
| }); |
| this.focus(); |
| |
| this._onBlur = function(){ |
| this.inherited('_onBlur', arguments); |
| // Usually the parent closes the child widget but if this is a context |
| // menu then there is no parent |
| dijit.popup.close(this); |
| // don't try to restore focus; user has clicked another part of the screen |
| // and set focus there |
| }; |
| }, |
| |
| uninitialize: function(){ |
| dojo.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this); |
| this.inherited(arguments); |
| } |
| } |
| ); |
| |
| // Back-compat (TODO: remove in 2.0) |
| |
| |
| |
| |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.MenuBar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.MenuBar"] = true; |
| dojo.provide("dijit.MenuBar"); |
| |
| |
| |
| dojo.declare("dijit.MenuBar", dijit._MenuBase, { |
| // summary: |
| // A menu bar, listing menu choices horizontally, like the "File" menu in most desktop applications |
| |
| templateString: dojo.cache("dijit", "templates/MenuBar.html", "<div class=\"dijitMenuBar dijitMenuPassive\" dojoAttachPoint=\"containerNode\" waiRole=\"menubar\" tabIndex=\"${tabIndex}\" dojoAttachEvent=\"onkeypress: _onKeyPress\"></div>\n"), |
| |
| // _isMenuBar: [protected] Boolean |
| // This is a MenuBar widget, not a (vertical) Menu widget. |
| _isMenuBar: true, |
| |
| constructor: function(){ |
| // summary: |
| // Sets up local variables etc. |
| // tags: |
| // private |
| |
| // parameter to dijit.popup.open() about where to put popup (relative to this.domNode) |
| this._orient = this.isLeftToRight() ? {BL: 'TL'} : {BR: 'TR'}; |
| }, |
| |
| postCreate: function(){ |
| var k = dojo.keys, l = this.isLeftToRight(); |
| this.connectKeyNavHandlers( |
| l ? [k.LEFT_ARROW] : [k.RIGHT_ARROW], |
| l ? [k.RIGHT_ARROW] : [k.LEFT_ARROW] |
| ); |
| }, |
| |
| focusChild: function(item){ |
| // overload focusChild so that whenever the focus is moved to a new item, |
| // check the previous focused whether it has its popup open, if so, after |
| // focusing the new item, open its submenu immediately |
| var prev_item = this.focusedChild, |
| showpopup = prev_item && prev_item.popup && prev_item.popup.isShowingNow; |
| this.inherited(arguments); |
| if(showpopup && item.popup && !item.disabled){ |
| this._openPopup(); // TODO: on down arrow, _openPopup() is called here and in onItemClick() |
| } |
| }, |
| |
| _onKeyPress: function(/*Event*/ evt){ |
| // summary: |
| // Handle keyboard based menu navigation. |
| // tags: |
| // protected |
| |
| if(evt.ctrlKey || evt.altKey){ return; } |
| |
| switch(evt.charOrCode){ |
| case dojo.keys.DOWN_ARROW: |
| this._moveToPopup(evt); |
| dojo.stopEvent(evt); |
| } |
| }, |
| |
| onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){ |
| // summary: |
| // Handle clicks on an item. Cancels a dropdown if already open. |
| // tags: |
| // private |
| if(item.popup && item.popup.isShowingNow){ |
| item.popup.onCancel(); |
| }else{ |
| this.inherited(arguments); |
| } |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.MenuBarItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.MenuBarItem"] = true; |
| dojo.provide("dijit.MenuBarItem"); |
| |
| |
| |
| dojo.declare("dijit._MenuBarItemMixin", null, { |
| templateString: dojo.cache("dijit", "templates/MenuBarItem.html", "<div class=\"dijitReset dijitInline dijitMenuItem dijitMenuItemLabel\" dojoAttachPoint=\"focusNode\" waiRole=\"menuitem\" tabIndex=\"-1\"'\n\t\tdojoAttachEvent=\"onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick\">\n\t<span dojoAttachPoint=\"containerNode\"></span>\n</div>\n"), |
| |
| // overriding attributeMap because we don't have icon |
| attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { |
| label: { node: "containerNode", type: "innerHTML" } |
| }) |
| }); |
| |
| dojo.declare("dijit.MenuBarItem", [dijit.MenuItem, dijit._MenuBarItemMixin], { |
| // summary: |
| // Item in a MenuBar that's clickable, and doesn't spawn a submenu when pressed (or hovered) |
| |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.PopupMenuBarItem"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.PopupMenuBarItem"] = true; |
| dojo.provide("dijit.PopupMenuBarItem"); |
| |
| |
| |
| |
| dojo.declare("dijit.PopupMenuBarItem", [dijit.PopupMenuItem, dijit._MenuBarItemMixin], { |
| // summary: |
| // Item in a MenuBar like "File" or "Edit", that spawns a submenu when pressed (or hovered) |
| }); |
| |
| |
| } |
| |
| if(!dojo._hasResource["dojo.regexp"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.regexp"] = true; |
| dojo.provide("dojo.regexp"); |
| |
| /*===== |
| dojo.regexp = { |
| // summary: Regular expressions and Builder resources |
| }; |
| =====*/ |
| |
| dojo.regexp.escapeString = function(/*String*/str, /*String?*/except){ |
| // summary: |
| // Adds escape sequences for special characters in regular expressions |
| // except: |
| // a String with special characters to be left unescaped |
| |
| return str.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, function(ch){ |
| if(except && except.indexOf(ch) != -1){ |
| return ch; |
| } |
| return "\\" + ch; |
| }); // String |
| } |
| |
| dojo.regexp.buildGroupRE = function(/*Object|Array*/arr, /*Function*/re, /*Boolean?*/nonCapture){ |
| // summary: |
| // Builds a regular expression that groups subexpressions |
| // description: |
| // A utility function used by some of the RE generators. The |
| // subexpressions are constructed by the function, re, in the second |
| // parameter. re builds one subexpression for each elem in the array |
| // a, in the first parameter. Returns a string for a regular |
| // expression that groups all the subexpressions. |
| // arr: |
| // A single value or an array of values. |
| // re: |
| // A function. Takes one parameter and converts it to a regular |
| // expression. |
| // nonCapture: |
| // If true, uses non-capturing match, otherwise matches are retained |
| // by regular expression. Defaults to false |
| |
| // case 1: a is a single value. |
| if(!(arr instanceof Array)){ |
| return re(arr); // String |
| } |
| |
| // case 2: a is an array |
| var b = []; |
| for(var i = 0; i < arr.length; i++){ |
| // convert each elem to a RE |
| b.push(re(arr[i])); |
| } |
| |
| // join the REs as alternatives in a RE group. |
| return dojo.regexp.group(b.join("|"), nonCapture); // String |
| } |
| |
| dojo.regexp.group = function(/*String*/expression, /*Boolean?*/nonCapture){ |
| // summary: |
| // adds group match to expression |
| // nonCapture: |
| // If true, uses non-capturing match, otherwise matches are retained |
| // by regular expression. |
| return "(" + (nonCapture ? "?:":"") + expression + ")"; // String |
| } |
| |
| } |
| |
| if(!dojo._hasResource["dojo.number"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.number"] = true; |
| dojo.provide("dojo.number"); |
| |
| |
| |
| |
| |
| |
| |
| /*===== |
| dojo.number = { |
| // summary: localized formatting and parsing routines for Number |
| } |
| |
| dojo.number.__FormatOptions = function(){ |
| // pattern: String? |
| // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) |
| // with this string. Default value is based on locale. Overriding this property will defeat |
| // localization. |
| // type: String? |
| // choose a format type based on the locale from the following: |
| // decimal, scientific (not yet supported), percent, currency. decimal by default. |
| // places: Number? |
| // fixed number of decimal places to show. This overrides any |
| // information in the provided pattern. |
| // round: Number? |
| // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1 |
| // means do not round. |
| // locale: String? |
| // override the locale used to determine formatting rules |
| this.pattern = pattern; |
| this.type = type; |
| this.places = places; |
| this.round = round; |
| this.locale = locale; |
| } |
| =====*/ |
| |
| dojo.number.format = function(/*Number*/value, /*dojo.number.__FormatOptions?*/options){ |
| // summary: |
| // Format a Number as a String, using locale-specific settings |
| // description: |
| // Create a string from a Number using a known localized pattern. |
| // Formatting patterns appropriate to the locale are chosen from the |
| // [CLDR](http://unicode.org/cldr) as well as the appropriate symbols and |
| // delimiters. See <http://www.unicode.org/reports/tr35/#Number_Elements> |
| // If value is Infinity, -Infinity, or is not a valid JavaScript number, return null. |
| // value: |
| // the number to be formatted |
| |
| options = dojo.mixin({}, options || {}); |
| var locale = dojo.i18n.normalizeLocale(options.locale); |
| var bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale); |
| options.customs = bundle; |
| var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"]; |
| if(isNaN(value) || Math.abs(value) == Infinity){ return null; } // null |
| return dojo.number._applyPattern(value, pattern, options); // String |
| }; |
| |
| //dojo.number._numberPatternRE = /(?:[#0]*,?)*[#0](?:\.0*#*)?/; // not precise, but good enough |
| dojo.number._numberPatternRE = /[#0,]*[#0](?:\.0*#*)?/; // not precise, but good enough |
| |
| dojo.number._applyPattern = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatOptions?*/options){ |
| // summary: |
| // Apply pattern to format value as a string using options. Gives no |
| // consideration to local customs. |
| // value: |
| // the number to be formatted. |
| // pattern: |
| // a pattern string as described by |
| // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) |
| // options: dojo.number.__FormatOptions? |
| // _applyPattern is usually called via `dojo.number.format()` which |
| // populates an extra property in the options parameter, "customs". |
| // The customs object specifies group and decimal parameters if set. |
| |
| //TODO: support escapes |
| options = options || {}; |
| var group = options.customs.group; |
| var decimal = options.customs.decimal; |
| |
| var patternList = pattern.split(';'); |
| var positivePattern = patternList[0]; |
| pattern = patternList[(value < 0) ? 1 : 0] || ("-" + positivePattern); |
| |
| //TODO: only test against unescaped |
| if(pattern.indexOf('%') != -1){ |
| value *= 100; |
| }else if(pattern.indexOf('\u2030') != -1){ |
| value *= 1000; // per mille |
| }else if(pattern.indexOf('\u00a4') != -1){ |
| group = options.customs.currencyGroup || group;//mixins instead? |
| decimal = options.customs.currencyDecimal || decimal;// Should these be mixins instead? |
| pattern = pattern.replace(/\u00a4{1,3}/, function(match){ |
| var prop = ["symbol", "currency", "displayName"][match.length-1]; |
| return options[prop] || options.currency || ""; |
| }); |
| }else if(pattern.indexOf('E') != -1){ |
| throw new Error("exponential notation not supported"); |
| } |
| |
| //TODO: support @ sig figs? |
| var numberPatternRE = dojo.number._numberPatternRE; |
| var numberPattern = positivePattern.match(numberPatternRE); |
| if(!numberPattern){ |
| throw new Error("unable to find a number expression in pattern: "+pattern); |
| } |
| if(options.fractional === false){ options.places = 0; } |
| return pattern.replace(numberPatternRE, |
| dojo.number._formatAbsolute(value, numberPattern[0], {decimal: decimal, group: group, places: options.places, round: options.round})); |
| } |
| |
| dojo.number.round = function(/*Number*/value, /*Number?*/places, /*Number?*/increment){ |
| // summary: |
| // Rounds to the nearest value with the given number of decimal places, away from zero |
| // description: |
| // Rounds to the nearest value with the given number of decimal places, away from zero if equal. |
| // Similar to Number.toFixed(), but compensates for browser quirks. Rounding can be done by |
| // fractional increments also, such as the nearest quarter. |
| // NOTE: Subject to floating point errors. See dojox.math.round for experimental workaround. |
| // value: |
| // The number to round |
| // places: |
| // The number of decimal places where rounding takes place. Defaults to 0 for whole rounding. |
| // Must be non-negative. |
| // increment: |
| // Rounds next place to nearest value of increment/10. 10 by default. |
| // example: |
| // >>> dojo.number.round(-0.5) |
| // -1 |
| // >>> dojo.number.round(162.295, 2) |
| // 162.29 // note floating point error. Should be 162.3 |
| // >>> dojo.number.round(10.71, 0, 2.5) |
| // 10.75 |
| var factor = 10 / (increment || 10); |
| return (factor * +value).toFixed(places) / factor; // Number |
| } |
| |
| if((0.9).toFixed() == 0){ |
| // (isIE) toFixed() bug workaround: Rounding fails on IE when most significant digit |
| // is just after the rounding place and is >=5 |
| (function(){ |
| var round = dojo.number.round; |
| dojo.number.round = function(v, p, m){ |
| var d = Math.pow(10, -p || 0), a = Math.abs(v); |
| if(!v || a >= d || a * Math.pow(10, p + 1) < 5){ |
| d = 0; |
| } |
| return round(v, p, m) + (v > 0 ? d : -d); |
| } |
| })(); |
| } |
| |
| /*===== |
| dojo.number.__FormatAbsoluteOptions = function(){ |
| // decimal: String? |
| // the decimal separator |
| // group: String? |
| // the group separator |
| // places: Number?|String? |
| // number of decimal places. the range "n,m" will format to m places. |
| // round: Number? |
| // 5 rounds to nearest .5; 0 rounds to nearest whole (default). -1 |
| // means don't round. |
| this.decimal = decimal; |
| this.group = group; |
| this.places = places; |
| this.round = round; |
| } |
| =====*/ |
| |
| dojo.number._formatAbsolute = function(/*Number*/value, /*String*/pattern, /*dojo.number.__FormatAbsoluteOptions?*/options){ |
| // summary: |
| // Apply numeric pattern to absolute value using options. Gives no |
| // consideration to local customs. |
| // value: |
| // the number to be formatted, ignores sign |
| // pattern: |
| // the number portion of a pattern (e.g. `#,##0.00`) |
| options = options || {}; |
| if(options.places === true){options.places=0;} |
| if(options.places === Infinity){options.places=6;} // avoid a loop; pick a limit |
| |
| var patternParts = pattern.split("."); |
| var maxPlaces = (options.places >= 0) ? options.places : (patternParts[1] && patternParts[1].length) || 0; |
| if(!(options.round < 0)){ |
| value = dojo.number.round(value, maxPlaces, options.round); |
| } |
| |
| var valueParts = String(Math.abs(value)).split("."); |
| var fractional = valueParts[1] || ""; |
| if(options.places){ |
| var comma = dojo.isString(options.places) && options.places.indexOf(","); |
| if(comma){ |
| options.places = options.places.substring(comma+1); |
| } |
| valueParts[1] = dojo.string.pad(fractional.substr(0, options.places), options.places, '0', true); |
| }else if(patternParts[1] && options.places !== 0){ |
| // Pad fractional with trailing zeros |
| var pad = patternParts[1].lastIndexOf("0") + 1; |
| if(pad > fractional.length){ |
| valueParts[1] = dojo.string.pad(fractional, pad, '0', true); |
| } |
| |
| // Truncate fractional |
| var places = patternParts[1].length; |
| if(places < fractional.length){ |
| valueParts[1] = fractional.substr(0, places); |
| } |
| }else{ |
| if(valueParts[1]){ valueParts.pop(); } |
| } |
| |
| // Pad whole with leading zeros |
| var patternDigits = patternParts[0].replace(',', ''); |
| pad = patternDigits.indexOf("0"); |
| if(pad != -1){ |
| pad = patternDigits.length - pad; |
| if(pad > valueParts[0].length){ |
| valueParts[0] = dojo.string.pad(valueParts[0], pad); |
| } |
| |
| // Truncate whole |
| if(patternDigits.indexOf("#") == -1){ |
| valueParts[0] = valueParts[0].substr(valueParts[0].length - pad); |
| } |
| } |
| |
| // Add group separators |
| var index = patternParts[0].lastIndexOf(','); |
| var groupSize, groupSize2; |
| if(index != -1){ |
| groupSize = patternParts[0].length - index - 1; |
| var remainder = patternParts[0].substr(0, index); |
| index = remainder.lastIndexOf(','); |
| if(index != -1){ |
| groupSize2 = remainder.length - index - 1; |
| } |
| } |
| var pieces = []; |
| for(var whole = valueParts[0]; whole;){ |
| var off = whole.length - groupSize; |
| pieces.push((off > 0) ? whole.substr(off) : whole); |
| whole = (off > 0) ? whole.slice(0, off) : ""; |
| if(groupSize2){ |
| groupSize = groupSize2; |
| delete groupSize2; |
| } |
| } |
| valueParts[0] = pieces.reverse().join(options.group || ","); |
| |
| return valueParts.join(options.decimal || "."); |
| }; |
| |
| /*===== |
| dojo.number.__RegexpOptions = function(){ |
| // pattern: String? |
| // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) |
| // with this string. Default value is based on locale. Overriding this property will defeat |
| // localization. |
| // type: String? |
| // choose a format type based on the locale from the following: |
| // decimal, scientific (not yet supported), percent, currency. decimal by default. |
| // locale: String? |
| // override the locale used to determine formatting rules |
| // strict: Boolean? |
| // strict parsing, false by default. Strict parsing requires input as produced by the format() method. |
| // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators |
| // places: Number|String? |
| // number of decimal places to accept: Infinity, a positive number, or |
| // a range "n,m". Defined by pattern or Infinity if pattern not provided. |
| this.pattern = pattern; |
| this.type = type; |
| this.locale = locale; |
| this.strict = strict; |
| this.places = places; |
| } |
| =====*/ |
| dojo.number.regexp = function(/*dojo.number.__RegexpOptions?*/options){ |
| // summary: |
| // Builds the regular needed to parse a number |
| // description: |
| // Returns regular expression with positive and negative match, group |
| // and decimal separators |
| return dojo.number._parseInfo(options).regexp; // String |
| } |
| |
| dojo.number._parseInfo = function(/*Object?*/options){ |
| options = options || {}; |
| var locale = dojo.i18n.normalizeLocale(options.locale); |
| var bundle = dojo.i18n.getLocalization("dojo.cldr", "number", locale); |
| var pattern = options.pattern || bundle[(options.type || "decimal") + "Format"]; |
| //TODO: memoize? |
| var group = bundle.group; |
| var decimal = bundle.decimal; |
| var factor = 1; |
| |
| if(pattern.indexOf('%') != -1){ |
| factor /= 100; |
| }else if(pattern.indexOf('\u2030') != -1){ |
| factor /= 1000; // per mille |
| }else{ |
| var isCurrency = pattern.indexOf('\u00a4') != -1; |
| if(isCurrency){ |
| group = bundle.currencyGroup || group; |
| decimal = bundle.currencyDecimal || decimal; |
| } |
| } |
| |
| //TODO: handle quoted escapes |
| var patternList = pattern.split(';'); |
| if(patternList.length == 1){ |
| patternList.push("-" + patternList[0]); |
| } |
| |
| var re = dojo.regexp.buildGroupRE(patternList, function(pattern){ |
| pattern = "(?:"+dojo.regexp.escapeString(pattern, '.')+")"; |
| return pattern.replace(dojo.number._numberPatternRE, function(format){ |
| var flags = { |
| signed: false, |
| separator: options.strict ? group : [group,""], |
| fractional: options.fractional, |
| decimal: decimal, |
| exponent: false}; |
| var parts = format.split('.'); |
| var places = options.places; |
| if(parts.length == 1 || places === 0){flags.fractional = false;} |
| else{ |
| if(places === undefined){ places = options.pattern ? parts[1].lastIndexOf('0')+1 : Infinity; } |
| if(places && options.fractional == undefined){flags.fractional = true;} // required fractional, unless otherwise specified |
| if(!options.places && (places < parts[1].length)){ places += "," + parts[1].length; } |
| flags.places = places; |
| } |
| var groups = parts[0].split(','); |
| if(groups.length>1){ |
| flags.groupSize = groups.pop().length; |
| if(groups.length>1){ |
| flags.groupSize2 = groups.pop().length; |
| } |
| } |
| return "("+dojo.number._realNumberRegexp(flags)+")"; |
| }); |
| }, true); |
| |
| if(isCurrency){ |
| // substitute the currency symbol for the placeholder in the pattern |
| re = re.replace(/([\s\xa0]*)(\u00a4{1,3})([\s\xa0]*)/g, function(match, before, target, after){ |
| var prop = ["symbol", "currency", "displayName"][target.length-1]; |
| var symbol = dojo.regexp.escapeString(options[prop] || options.currency || ""); |
| before = before ? "[\\s\\xa0]" : ""; |
| after = after ? "[\\s\\xa0]" : ""; |
| if(!options.strict){ |
| if(before){before += "*";} |
| if(after){after += "*";} |
| return "(?:"+before+symbol+after+")?"; |
| } |
| return before+symbol+after; |
| }); |
| } |
| |
| //TODO: substitute localized sign/percent/permille/etc.? |
| |
| // normalize whitespace and return |
| return {regexp: re.replace(/[\xa0 ]/g, "[\\s\\xa0]"), group: group, decimal: decimal, factor: factor}; // Object |
| } |
| |
| /*===== |
| dojo.number.__ParseOptions = function(){ |
| // pattern: String? |
| // override [formatting pattern](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) |
| // with this string. Default value is based on locale. Overriding this property will defeat |
| // localization. |
| // type: String? |
| // choose a format type based on the locale from the following: |
| // decimal, scientific (not yet supported), percent, currency. decimal by default. |
| // locale: String? |
| // override the locale used to determine formatting rules |
| // strict: Boolean? |
| // strict parsing, false by default. Strict parsing requires input as produced by the format() method. |
| // Non-strict is more permissive, e.g. flexible on white space, omitting thousands separators |
| this.pattern = pattern; |
| this.type = type; |
| this.locale = locale; |
| this.strict = strict; |
| } |
| =====*/ |
| dojo.number.parse = function(/*String*/expression, /*dojo.number.__ParseOptions?*/options){ |
| // summary: |
| // Convert a properly formatted string to a primitive Number, using |
| // locale-specific settings. |
| // description: |
| // Create a Number from a string using a known localized pattern. |
| // Formatting patterns are chosen appropriate to the locale |
| // and follow the syntax described by |
| // [unicode.org TR35](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) |
| // expression: |
| // A string representation of a Number |
| var info = dojo.number._parseInfo(options); |
| var results = (new RegExp("^"+info.regexp+"$")).exec(expression); |
| if(!results){ |
| return NaN; //NaN |
| } |
| var absoluteMatch = results[1]; // match for the positive expression |
| if(!results[1]){ |
| if(!results[2]){ |
| return NaN; //NaN |
| } |
| // matched the negative pattern |
| absoluteMatch =results[2]; |
| info.factor *= -1; |
| } |
| |
| // Transform it to something Javascript can parse as a number. Normalize |
| // decimal point and strip out group separators or alternate forms of whitespace |
| absoluteMatch = absoluteMatch. |
| replace(new RegExp("["+info.group + "\\s\\xa0"+"]", "g"), ""). |
| replace(info.decimal, "."); |
| // Adjust for negative sign, percent, etc. as necessary |
| return absoluteMatch * info.factor; //Number |
| }; |
| |
| /*===== |
| dojo.number.__RealNumberRegexpFlags = function(){ |
| // places: Number? |
| // The integer number of decimal places or a range given as "n,m". If |
| // not given, the decimal part is optional and the number of places is |
| // unlimited. |
| // decimal: String? |
| // A string for the character used as the decimal point. Default |
| // is ".". |
| // fractional: Boolean?|Array? |
| // Whether decimal places are used. Can be true, false, or [true, |
| // false]. Default is [true, false] which means optional. |
| // exponent: Boolean?|Array? |
| // Express in exponential notation. Can be true, false, or [true, |
| // false]. Default is [true, false], (i.e. will match if the |
| // exponential part is present are not). |
| // eSigned: Boolean?|Array? |
| // The leading plus-or-minus sign on the exponent. Can be true, |
| // false, or [true, false]. Default is [true, false], (i.e. will |
| // match if it is signed or unsigned). flags in regexp.integer can be |
| // applied. |
| this.places = places; |
| this.decimal = decimal; |
| this.fractional = fractional; |
| this.exponent = exponent; |
| this.eSigned = eSigned; |
| } |
| =====*/ |
| |
| dojo.number._realNumberRegexp = function(/*dojo.number.__RealNumberRegexpFlags?*/flags){ |
| // summary: |
| // Builds a regular expression to match a real number in exponential |
| // notation |
| |
| // assign default values to missing paramters |
| flags = flags || {}; |
| //TODO: use mixin instead? |
| if(!("places" in flags)){ flags.places = Infinity; } |
| if(typeof flags.decimal != "string"){ flags.decimal = "."; } |
| if(!("fractional" in flags) || /^0/.test(flags.places)){ flags.fractional = [true, false]; } |
| if(!("exponent" in flags)){ flags.exponent = [true, false]; } |
| if(!("eSigned" in flags)){ flags.eSigned = [true, false]; } |
| |
| // integer RE |
| var integerRE = dojo.number._integerRegexp(flags); |
| |
| // decimal RE |
| var decimalRE = dojo.regexp.buildGroupRE(flags.fractional, |
| function(q){ |
| var re = ""; |
| if(q && (flags.places!==0)){ |
| re = "\\" + flags.decimal; |
| if(flags.places == Infinity){ |
| re = "(?:" + re + "\\d+)?"; |
| }else{ |
| re += "\\d{" + flags.places + "}"; |
| } |
| } |
| return re; |
| }, |
| true |
| ); |
| |
| // exponent RE |
| var exponentRE = dojo.regexp.buildGroupRE(flags.exponent, |
| function(q){ |
| if(q){ return "([eE]" + dojo.number._integerRegexp({ signed: flags.eSigned}) + ")"; } |
| return ""; |
| } |
| ); |
| |
| // real number RE |
| var realRE = integerRE + decimalRE; |
| // allow for decimals without integers, e.g. .25 |
| if(decimalRE){realRE = "(?:(?:"+ realRE + ")|(?:" + decimalRE + "))";} |
| return realRE + exponentRE; // String |
| }; |
| |
| /*===== |
| dojo.number.__IntegerRegexpFlags = function(){ |
| // signed: Boolean? |
| // The leading plus-or-minus sign. Can be true, false, or `[true,false]`. |
| // Default is `[true, false]`, (i.e. will match if it is signed |
| // or unsigned). |
| // separator: String? |
| // The character used as the thousands separator. Default is no |
| // separator. For more than one symbol use an array, e.g. `[",", ""]`, |
| // makes ',' optional. |
| // groupSize: Number? |
| // group size between separators |
| // groupSize2: Number? |
| // second grouping, where separators 2..n have a different interval than the first separator (for India) |
| this.signed = signed; |
| this.separator = separator; |
| this.groupSize = groupSize; |
| this.groupSize2 = groupSize2; |
| } |
| =====*/ |
| |
| dojo.number._integerRegexp = function(/*dojo.number.__IntegerRegexpFlags?*/flags){ |
| // summary: |
| // Builds a regular expression that matches an integer |
| |
| // assign default values to missing paramters |
| flags = flags || {}; |
| if(!("signed" in flags)){ flags.signed = [true, false]; } |
| if(!("separator" in flags)){ |
| flags.separator = ""; |
| }else if(!("groupSize" in flags)){ |
| flags.groupSize = 3; |
| } |
| // build sign RE |
| var signRE = dojo.regexp.buildGroupRE(flags.signed, |
| function(q){ return q ? "[-+]" : ""; }, |
| true |
| ); |
| |
| // number RE |
| var numberRE = dojo.regexp.buildGroupRE(flags.separator, |
| function(sep){ |
| if(!sep){ |
| return "(?:\\d+)"; |
| } |
| |
| sep = dojo.regexp.escapeString(sep); |
| if(sep == " "){ sep = "\\s"; } |
| else if(sep == "\xa0"){ sep = "\\s\\xa0"; } |
| |
| var grp = flags.groupSize, grp2 = flags.groupSize2; |
| //TODO: should we continue to enforce that numbers with separators begin with 1-9? See #6933 |
| if(grp2){ |
| var grp2RE = "(?:0|[1-9]\\d{0," + (grp2-1) + "}(?:[" + sep + "]\\d{" + grp2 + "})*[" + sep + "]\\d{" + grp + "})"; |
| return ((grp-grp2) > 0) ? "(?:" + grp2RE + "|(?:0|[1-9]\\d{0," + (grp-1) + "}))" : grp2RE; |
| } |
| return "(?:0|[1-9]\\d{0," + (grp-1) + "}(?:[" + sep + "]\\d{" + grp + "})*)"; |
| }, |
| true |
| ); |
| |
| // integer RE |
| return signRE + numberRE; // String |
| } |
| |
| } |
| |
| if(!dojo._hasResource["dijit.ProgressBar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.ProgressBar"] = true; |
| dojo.provide("dijit.ProgressBar"); |
| |
| |
| |
| |
| |
| |
| |
| dojo.declare("dijit.ProgressBar", [dijit._Widget, dijit._Templated], { |
| // summary: |
| // A progress indication widget, showing the amount completed |
| // (often the percentage completed) of a task. |
| // |
| // example: |
| // | <div dojoType="ProgressBar" |
| // | places="0" |
| // | progress="..." maximum="..."> |
| // | </div> |
| // |
| // description: |
| // Note that the progress bar is updated via (a non-standard) |
| // update() method, rather than via attr() like other widgets. |
| |
| // progress: [const] String (Percentage or Number) |
| // Number or percentage indicating amount of task completed. |
| // With "%": percentage value, 0% <= progress <= 100%, or |
| // without "%": absolute value, 0 <= progress <= maximum |
| // TODO: rename to value for 2.0 |
| progress: "0", |
| |
| // maximum: [const] Float |
| // Max sample number |
| maximum: 100, |
| |
| // places: [const] Number |
| // Number of places to show in values; 0 by default |
| places: 0, |
| |
| // indeterminate: [const] Boolean |
| // If false: show progress value (number or percentage). |
| // If true: show that a process is underway but that the amount completed is unknown. |
| indeterminate: false, |
| |
| // name: String |
| // this is the field name (for a form) if set. This needs to be set if you want to use |
| // this widget in a dijit.form.Form widget (such as dijit.Dialog) |
| name: '', |
| |
| templateString: dojo.cache("dijit", "templates/ProgressBar.html", "<div class=\"dijitProgressBar dijitProgressBarEmpty\"\n\t><div waiRole=\"progressbar\" dojoAttachPoint=\"internalProgress\" class=\"dijitProgressBarFull\"\n\t\t><div class=\"dijitProgressBarTile\"></div\n\t\t><span style=\"visibility:hidden\"> </span\n\t></div\n\t><div dojoAttachPoint=\"label\" class=\"dijitProgressBarLabel\" id=\"${id}_label\"> </div\n\t><img dojoAttachPoint=\"indeterminateHighContrastImage\" class=\"dijitProgressBarIndeterminateHighContrastImage\" alt=\"\"\n\t></img\n></div>\n"), |
| |
| // _indeterminateHighContrastImagePath: [private] dojo._URL |
| // URL to image to use for indeterminate progress bar when display is in high contrast mode |
| _indeterminateHighContrastImagePath: |
| dojo.moduleUrl("dijit", "themes/a11y/indeterminate_progress.gif"), |
| |
| // public functions |
| postCreate: function(){ |
| this.inherited(arguments); |
| this.indeterminateHighContrastImage.setAttribute("src", |
| this._indeterminateHighContrastImagePath.toString()); |
| this.update(); |
| }, |
| |
| update: function(/*Object?*/attributes){ |
| // summary: |
| // Change attributes of ProgressBar, similar to attr(hash). |
| // |
| // attributes: |
| // May provide progress and/or maximum properties on this parameter; |
| // see attribute specs for details. |
| // |
| // example: |
| // | myProgressBar.update({'indeterminate': true}); |
| // | myProgressBar.update({'progress': 80}); |
| |
| // TODO: deprecate this method and use attr() instead |
| |
| dojo.mixin(this, attributes || {}); |
| var tip = this.internalProgress; |
| var percent = 1, classFunc; |
| if(this.indeterminate){ |
| classFunc = "addClass"; |
| dijit.removeWaiState(tip, "valuenow"); |
| dijit.removeWaiState(tip, "valuemin"); |
| dijit.removeWaiState(tip, "valuemax"); |
| }else{ |
| classFunc = "removeClass"; |
| if(String(this.progress).indexOf("%") != -1){ |
| percent = Math.min(parseFloat(this.progress)/100, 1); |
| this.progress = percent * this.maximum; |
| }else{ |
| this.progress = Math.min(this.progress, this.maximum); |
| percent = this.progress / this.maximum; |
| } |
| var text = this.report(percent); |
| this.label.firstChild.nodeValue = text; |
| dijit.setWaiState(tip, "describedby", this.label.id); |
| dijit.setWaiState(tip, "valuenow", this.progress); |
| dijit.setWaiState(tip, "valuemin", 0); |
| dijit.setWaiState(tip, "valuemax", this.maximum); |
| } |
| dojo[classFunc](this.domNode, "dijitProgressBarIndeterminate"); |
| tip.style.width = (percent * 100) + "%"; |
| this.onChange(); |
| }, |
| |
| _setValueAttr: function(v){ |
| if(v == Infinity){ |
| this.update({indeterminate:true}); |
| }else{ |
| this.update({indeterminate:false, progress:v}); |
| } |
| }, |
| |
| _getValueAttr: function(){ |
| return this.progress; |
| }, |
| |
| report: function(/*float*/percent){ |
| // summary: |
| // Generates message to show inside progress bar (normally indicating amount of task completed). |
| // May be overridden. |
| // tags: |
| // extension |
| |
| return dojo.number.format(percent, { type: "percent", places: this.places, locale: this.lang }); |
| }, |
| |
| onChange: function(){ |
| // summary: |
| // Callback fired when progress updates. |
| // tags: |
| // progress |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.TitlePane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.TitlePane"] = true; |
| dojo.provide("dijit.TitlePane"); |
| |
| |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.TitlePane", |
| [dijit.layout.ContentPane, dijit._Templated], |
| { |
| // summary: |
| // A pane with a title on top, that can be expanded or collapsed. |
| // |
| // description: |
| // An accessible container with a title Heading, and a content |
| // section that slides open and closed. TitlePane is an extension to |
| // `dijit.layout.ContentPane`, providing all the useful content-control aspects from it. |
| // |
| // example: |
| // | // load a TitlePane from remote file: |
| // | var foo = new dijit.TitlePane({ href: "foobar.html", title:"Title" }); |
| // | foo.startup(); |
| // |
| // example: |
| // | <!-- markup href example: --> |
| // | <div dojoType="dijit.TitlePane" href="foobar.html" title="Title"></div> |
| // |
| // example: |
| // | <!-- markup with inline data --> |
| // | <div dojoType="dijit.TitlePane" title="Title"> |
| // | <p>I am content</p> |
| // | </div> |
| |
| // title: String |
| // Title of the pane |
| title: "", |
| |
| // open: Boolean |
| // Whether pane is opened or closed. |
| open: true, |
| |
| // toggleable: Boolean |
| // Whether pane can be opened or closed by clicking the title bar. |
| toggleable: true, |
| |
| // tabIndex: String |
| // Tabindex setting for the title (so users can tab to the title then |
| // use space/enter to open/close the title pane) |
| tabIndex: "0", |
| |
| // duration: Integer |
| // Time in milliseconds to fade in/fade out |
| duration: dijit.defaultDuration, |
| |
| // baseClass: [protected] String |
| // The root className to be placed on this widget's domNode. |
| baseClass: "dijitTitlePane", |
| |
| templateString: dojo.cache("dijit", "templates/TitlePane.html", "<div class=\"${baseClass}\">\n\t<div dojoAttachEvent=\"onclick:_onTitleClick, onkeypress:_onTitleKey, onfocus:_handleFocus, onblur:_handleFocus, onmouseenter:_onTitleEnter, onmouseleave:_onTitleLeave\"\n\t\t\tclass=\"dijitTitlePaneTitle\" dojoAttachPoint=\"titleBarNode,focusNode\">\n\t\t<img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"arrowNode\" class=\"dijitArrowNode\" waiRole=\"presentation\"\n\t\t><span dojoAttachPoint=\"arrowNodeInner\" class=\"dijitArrowNodeInner\"></span\n\t\t><span dojoAttachPoint=\"titleNode\" class=\"dijitTitlePaneTextNode\"></span>\n\t</div>\n\t<div class=\"dijitTitlePaneContentOuter\" dojoAttachPoint=\"hideNode\" waiRole=\"presentation\">\n\t\t<div class=\"dijitReset\" dojoAttachPoint=\"wipeNode\" waiRole=\"presentation\">\n\t\t\t<div class=\"dijitTitlePaneContentInner\" dojoAttachPoint=\"containerNode\" waiRole=\"region\" tabindex=\"-1\" id=\"${id}_pane\">\n\t\t\t\t<!-- nested divs because wipeIn()/wipeOut() doesn't work right on node w/padding etc. Put padding on inner div. -->\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</div>\n"), |
| |
| attributeMap: dojo.delegate(dijit.layout.ContentPane.prototype.attributeMap, { |
| title: { node: "titleNode", type: "innerHTML" }, |
| tooltip: {node: "focusNode", type: "attribute", attribute: "title"}, // focusNode spans the entire width, titleNode doesn't |
| id:"" |
| }), |
| |
| postCreate: function(){ |
| if(!this.open){ |
| this.hideNode.style.display = this.wipeNode.style.display = "none"; |
| } |
| this._setCss(); |
| dojo.setSelectable(this.titleNode, false); |
| dijit.setWaiState(this.containerNode,"hidden", this.open ? "false" : "true"); |
| dijit.setWaiState(this.focusNode, "pressed", this.open ? "true" : "false"); |
| |
| // setup open/close animations |
| var hideNode = this.hideNode, wipeNode = this.wipeNode; |
| this._wipeIn = dojo.fx.wipeIn({ |
| node: this.wipeNode, |
| duration: this.duration, |
| beforeBegin: function(){ |
| hideNode.style.display=""; |
| } |
| }); |
| this._wipeOut = dojo.fx.wipeOut({ |
| node: this.wipeNode, |
| duration: this.duration, |
| onEnd: function(){ |
| hideNode.style.display="none"; |
| } |
| }); |
| this.inherited(arguments); |
| }, |
| |
| _setOpenAttr: function(/* Boolean */ open){ |
| // summary: |
| // Hook to make attr("open", boolean) control the open/closed state of the pane. |
| // open: Boolean |
| // True if you want to open the pane, false if you want to close it. |
| if(this.open !== open){ this.toggle(); } |
| }, |
| |
| _setToggleableAttr: function(/* Boolean */ canToggle){ |
| // summary: |
| // Hook to make attr("canToggle", boolean) work. |
| // canToggle: Boolean |
| // True to allow user to open/close pane by clicking title bar. |
| this.toggleable = canToggle; |
| dijit.setWaiRole(this.focusNode, canToggle ? "button" : "heading"); |
| dojo.attr(this.focusNode, "tabIndex", canToggle ? this.tabIndex : "-1"); |
| if(canToggle){ |
| // TODO: if canToggle is switched from true false shouldn't we remove this setting? |
| dijit.setWaiState(this.focusNode, "controls", this.id+"_pane"); |
| } |
| this._setCss(); |
| }, |
| |
| _setContentAttr: function(content){ |
| // summary: |
| // Hook to make attr("content", ...) work. |
| // Typically called when an href is loaded. Our job is to make the animation smooth. |
| |
| if(!this.open || !this._wipeOut || this._wipeOut.status() == "playing"){ |
| // we are currently *closing* the pane (or the pane is closed), so just let that continue |
| this.inherited(arguments); |
| }else{ |
| if(this._wipeIn && this._wipeIn.status() == "playing"){ |
| this._wipeIn.stop(); |
| } |
| |
| // freeze container at current height so that adding new content doesn't make it jump |
| dojo.marginBox(this.wipeNode, { h: dojo.marginBox(this.wipeNode).h }); |
| |
| // add the new content (erasing the old content, if any) |
| this.inherited(arguments); |
| |
| // call _wipeIn.play() to animate from current height to new height |
| if(this._wipeIn){ |
| this._wipeIn.play(); |
| }else{ |
| this.hideNode.style.display = ""; |
| } |
| } |
| }, |
| |
| toggle: function(){ |
| // summary: |
| // Switches between opened and closed state |
| // tags: |
| // private |
| |
| dojo.forEach([this._wipeIn, this._wipeOut], function(animation){ |
| if(animation && animation.status() == "playing"){ |
| animation.stop(); |
| } |
| }); |
| |
| var anim = this[this.open ? "_wipeOut" : "_wipeIn"] |
| if(anim){ |
| anim.play(); |
| }else{ |
| this.hideNode.style.display = this.open ? "" : "none"; |
| } |
| this.open =! this.open; |
| dijit.setWaiState(this.containerNode, "hidden", this.open ? "false" : "true"); |
| dijit.setWaiState(this.focusNode, "pressed", this.open ? "true" : "false"); |
| |
| // load content (if this is the first time we are opening the TitlePane |
| // and content is specified as an href, or href was set when hidden) |
| if(this.open){ |
| this._onShow(); |
| }else{ |
| this.onHide(); |
| } |
| |
| this._setCss(); |
| }, |
| |
| _setCss: function(){ |
| // summary: |
| // Set the open/close css state for the TitlePane |
| // tags: |
| // private |
| |
| var node = this.titleBarNode || this.focusNode; |
| |
| if(this._titleBarClass){ |
| dojo.removeClass(node, this._titleBarClass); |
| } |
| this._titleBarClass = "dijit" + (this.toggleable ? "" : "Fixed") + (this.open ? "Open" : "Closed"); |
| dojo.addClass(node, this._titleBarClass); |
| this.arrowNodeInner.innerHTML = this.open ? "-" : "+"; |
| }, |
| |
| _onTitleKey: function(/*Event*/ e){ |
| // summary: |
| // Handler for when user hits a key |
| // tags: |
| // private |
| |
| if(e.charOrCode == dojo.keys.ENTER || e.charOrCode == ' '){ |
| if(this.toggleable){ |
| this.toggle(); |
| } |
| dojo.stopEvent(e); |
| }else if(e.charOrCode == dojo.keys.DOWN_ARROW && this.open){ |
| this.containerNode.focus(); |
| e.preventDefault(); |
| } |
| }, |
| |
| _onTitleEnter: function(){ |
| // summary: |
| // Handler for when someone hovers over my title |
| // tags: |
| // private |
| if(this.toggleable){ |
| dojo.addClass(this.focusNode, "dijitTitlePaneTitle-hover"); |
| } |
| }, |
| |
| _onTitleLeave: function(){ |
| // summary: |
| // Handler when someone stops hovering over my title |
| // tags: |
| // private |
| if(this.toggleable){ |
| dojo.removeClass(this.focusNode, "dijitTitlePaneTitle-hover"); |
| } |
| }, |
| |
| _onTitleClick: function(){ |
| // summary: |
| // Handler when user clicks the title bar |
| // tags: |
| // private |
| if(this.toggleable){ |
| this.toggle(); |
| } |
| }, |
| |
| _handleFocus: function(/*Event*/ e){ |
| // summary: |
| // Handle blur and focus events on title bar |
| // tags: |
| // private |
| |
| dojo.toggleClass(this.focusNode, this.baseClass + "Focused", e.type == "focus"); |
| }, |
| |
| setTitle: function(/*String*/ title){ |
| // summary: |
| // Deprecated. Use attr('title', ...) instead. |
| // tags: |
| // deprecated |
| dojo.deprecated("dijit.TitlePane.setTitle() is deprecated. Use attr('title', ...) instead.", "", "2.0"); |
| this.attr("title", title); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.Tooltip"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.Tooltip"] = true; |
| dojo.provide("dijit.Tooltip"); |
| |
| |
| |
| |
| dojo.declare( |
| "dijit._MasterTooltip", |
| [dijit._Widget, dijit._Templated], |
| { |
| // summary: |
| // Internal widget that holds the actual tooltip markup, |
| // which occurs once per page. |
| // Called by Tooltip widgets which are just containers to hold |
| // the markup |
| // tags: |
| // protected |
| |
| // duration: Integer |
| // Milliseconds to fade in/fade out |
| duration: dijit.defaultDuration, |
| |
| templateString: dojo.cache("dijit", "templates/Tooltip.html", "<div class=\"dijitTooltip dijitTooltipLeft\" id=\"dojoTooltip\">\n\t<div class=\"dijitTooltipContainer dijitTooltipContents\" dojoAttachPoint=\"containerNode\" waiRole='alert'></div>\n\t<div class=\"dijitTooltipConnector\"></div>\n</div>\n"), |
| |
| postCreate: function(){ |
| dojo.body().appendChild(this.domNode); |
| |
| this.bgIframe = new dijit.BackgroundIframe(this.domNode); |
| |
| // Setup fade-in and fade-out functions. |
| this.fadeIn = dojo.fadeIn({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onShow") }); |
| this.fadeOut = dojo.fadeOut({ node: this.domNode, duration: this.duration, onEnd: dojo.hitch(this, "_onHide") }); |
| |
| }, |
| |
| show: function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position){ |
| // summary: |
| // Display tooltip w/specified contents to right of specified node |
| // (To left if there's no space on the right, or if LTR==right) |
| |
| if(this.aroundNode && this.aroundNode === aroundNode){ |
| return; |
| } |
| |
| if(this.fadeOut.status() == "playing"){ |
| // previous tooltip is being hidden; wait until the hide completes then show new one |
| this._onDeck=arguments; |
| return; |
| } |
| this.containerNode.innerHTML=innerHTML; |
| |
| // Firefox bug. when innerHTML changes to be shorter than previous |
| // one, the node size will not be updated until it moves. |
| this.domNode.style.top = (this.domNode.offsetTop + 1) + "px"; |
| |
| var pos = dijit.placeOnScreenAroundElement(this.domNode, aroundNode, dijit.getPopupAroundAlignment((position && position.length) ? position : dijit.Tooltip.defaultPosition, this.isLeftToRight()), dojo.hitch(this, "orient")); |
| |
| // show it |
| dojo.style(this.domNode, "opacity", 0); |
| this.fadeIn.play(); |
| this.isShowingNow = true; |
| this.aroundNode = aroundNode; |
| }, |
| |
| orient: function(/* DomNode */ node, /* String */ aroundCorner, /* String */ tooltipCorner){ |
| // summary: |
| // Private function to set CSS for tooltip node based on which position it's in. |
| // This is called by the dijit popup code. |
| // tags: |
| // protected |
| |
| node.className = "dijitTooltip " + |
| { |
| "BL-TL": "dijitTooltipBelow dijitTooltipABLeft", |
| "TL-BL": "dijitTooltipAbove dijitTooltipABLeft", |
| "BR-TR": "dijitTooltipBelow dijitTooltipABRight", |
| "TR-BR": "dijitTooltipAbove dijitTooltipABRight", |
| "BR-BL": "dijitTooltipRight", |
| "BL-BR": "dijitTooltipLeft" |
| }[aroundCorner + "-" + tooltipCorner]; |
| }, |
| |
| _onShow: function(){ |
| // summary: |
| // Called at end of fade-in operation |
| // tags: |
| // protected |
| if(dojo.isIE){ |
| // the arrow won't show up on a node w/an opacity filter |
| this.domNode.style.filter=""; |
| } |
| }, |
| |
| hide: function(aroundNode){ |
| // summary: |
| // Hide the tooltip |
| if(this._onDeck && this._onDeck[1] == aroundNode){ |
| // this hide request is for a show() that hasn't even started yet; |
| // just cancel the pending show() |
| this._onDeck=null; |
| }else if(this.aroundNode === aroundNode){ |
| // this hide request is for the currently displayed tooltip |
| this.fadeIn.stop(); |
| this.isShowingNow = false; |
| this.aroundNode = null; |
| this.fadeOut.play(); |
| }else{ |
| // just ignore the call, it's for a tooltip that has already been erased |
| } |
| }, |
| |
| _onHide: function(){ |
| // summary: |
| // Called at end of fade-out operation |
| // tags: |
| // protected |
| |
| this.domNode.style.cssText=""; // to position offscreen again |
| if(this._onDeck){ |
| // a show request has been queued up; do it now |
| this.show.apply(this, this._onDeck); |
| this._onDeck=null; |
| } |
| } |
| |
| } |
| ); |
| |
| dijit.showTooltip = function(/*String*/ innerHTML, /*DomNode*/ aroundNode, /*String[]?*/ position){ |
| // summary: |
| // Display tooltip w/specified contents in specified position. |
| // See description of dijit.Tooltip.defaultPosition for details on position parameter. |
| // If position is not specified then dijit.Tooltip.defaultPosition is used. |
| if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); } |
| return dijit._masterTT.show(innerHTML, aroundNode, position); |
| }; |
| |
| dijit.hideTooltip = function(aroundNode){ |
| // summary: |
| // Hide the tooltip |
| if(!dijit._masterTT){ dijit._masterTT = new dijit._MasterTooltip(); } |
| return dijit._masterTT.hide(aroundNode); |
| }; |
| |
| dojo.declare( |
| "dijit.Tooltip", |
| dijit._Widget, |
| { |
| // summary: |
| // Pops up a tooltip (a help message) when you hover over a node. |
| |
| // label: String |
| // Text to display in the tooltip. |
| // Specified as innerHTML when creating the widget from markup. |
| label: "", |
| |
| // showDelay: Integer |
| // Number of milliseconds to wait after hovering over/focusing on the object, before |
| // the tooltip is displayed. |
| showDelay: 400, |
| |
| // connectId: [const] String[] |
| // Id's of domNodes to attach the tooltip to. |
| // When user hovers over any of the specified dom nodes, the tooltip will appear. |
| // |
| // Note: Currently connectId can only be specified on initialization, it cannot |
| // be changed via attr('connectId', ...) |
| // |
| // Note: in 2.0 this will be renamed to connectIds for less confusion. |
| connectId: [], |
| |
| // position: String[] |
| // See description of `dijit.Tooltip.defaultPosition` for details on position parameter. |
| position: [], |
| |
| constructor: function(){ |
| // Map id's of nodes I'm connected to to a list of the this.connect() handles |
| this._nodeConnectionsById = {}; |
| }, |
| |
| _setConnectIdAttr: function(newIds){ |
| for(var oldId in this._nodeConnectionsById){ |
| this.removeTarget(oldId); |
| } |
| dojo.forEach(dojo.isArrayLike(newIds) ? newIds : [newIds], this.addTarget, this); |
| }, |
| |
| _getConnectIdAttr: function(){ |
| var ary = []; |
| for(var id in this._nodeConnectionsById){ |
| ary.push(id); |
| } |
| return ary; |
| }, |
| |
| addTarget: function(/*DOMNODE || String*/ id){ |
| // summary: |
| // Attach tooltip to specified node, if it's not already connected |
| var node = dojo.byId(id); |
| if(!node){ return; } |
| if(node.id in this._nodeConnectionsById){ return; }//Already connected |
| |
| this._nodeConnectionsById[node.id] = [ |
| this.connect(node, "onmouseenter", "_onTargetMouseEnter"), |
| this.connect(node, "onmouseleave", "_onTargetMouseLeave"), |
| this.connect(node, "onfocus", "_onTargetFocus"), |
| this.connect(node, "onblur", "_onTargetBlur") |
| ]; |
| if(dojo.isIE && !node.style.zoom){//preserve zoom |
| // BiDi workaround |
| node.style.zoom = 1; |
| } |
| }, |
| |
| removeTarget: function(/*DOMNODE || String*/ node){ |
| // summary: |
| // Detach tooltip from specified node |
| |
| // map from DOMNode back to plain id string |
| var id = node.id || node; |
| |
| if(id in this._nodeConnectionsById){ |
| dojo.forEach(this._nodeConnectionsById[id], this.disconnect, this); |
| delete this._nodeConnectionsById[id]; |
| } |
| }, |
| |
| postCreate: function(){ |
| dojo.addClass(this.domNode,"dijitTooltipData"); |
| }, |
| |
| startup: function(){ |
| this.inherited(arguments); |
| |
| // If this tooltip was created in a template, or for some other reason the specified connectId[s] |
| // didn't exist during the widget's initialization, then connect now. |
| var ids = this.connectId; |
| dojo.forEach(dojo.isArrayLike(ids) ? ids : [ids], this.addTarget, this); |
| }, |
| |
| _onTargetMouseEnter: function(/*Event*/ e){ |
| // summary: |
| // Handler for mouseenter event on the target node |
| // tags: |
| // private |
| this._onHover(e); |
| }, |
| |
| _onTargetMouseLeave: function(/*Event*/ e){ |
| // summary: |
| // Handler for mouseleave event on the target node |
| // tags: |
| // private |
| this._onUnHover(e); |
| }, |
| |
| _onTargetFocus: function(/*Event*/ e){ |
| // summary: |
| // Handler for focus event on the target node |
| // tags: |
| // private |
| |
| this._focus = true; |
| this._onHover(e); |
| }, |
| |
| _onTargetBlur: function(/*Event*/ e){ |
| // summary: |
| // Handler for blur event on the target node |
| // tags: |
| // private |
| |
| this._focus = false; |
| this._onUnHover(e); |
| }, |
| |
| _onHover: function(/*Event*/ e){ |
| // summary: |
| // Despite the name of this method, it actually handles both hover and focus |
| // events on the target node, setting a timer to show the tooltip. |
| // tags: |
| // private |
| if(!this._showTimer){ |
| var target = e.target; |
| this._showTimer = setTimeout(dojo.hitch(this, function(){this.open(target)}), this.showDelay); |
| } |
| }, |
| |
| _onUnHover: function(/*Event*/ e){ |
| // summary: |
| // Despite the name of this method, it actually handles both mouseleave and blur |
| // events on the target node, hiding the tooltip. |
| // tags: |
| // private |
| |
| // keep a tooltip open if the associated element still has focus (even though the |
| // mouse moved away) |
| if(this._focus){ return; } |
| |
| if(this._showTimer){ |
| clearTimeout(this._showTimer); |
| delete this._showTimer; |
| } |
| this.close(); |
| }, |
| |
| open: function(/*DomNode*/ target){ |
| // summary: |
| // Display the tooltip; usually not called directly. |
| // tags: |
| // private |
| |
| if(this._showTimer){ |
| clearTimeout(this._showTimer); |
| delete this._showTimer; |
| } |
| dijit.showTooltip(this.label || this.domNode.innerHTML, target, this.position); |
| |
| this._connectNode = target; |
| this.onShow(target, this.position); |
| }, |
| |
| close: function(){ |
| // summary: |
| // Hide the tooltip or cancel timer for show of tooltip |
| // tags: |
| // private |
| |
| if(this._connectNode){ |
| // if tooltip is currently shown |
| dijit.hideTooltip(this._connectNode); |
| delete this._connectNode; |
| this.onHide(); |
| } |
| if(this._showTimer){ |
| // if tooltip is scheduled to be shown (after a brief delay) |
| clearTimeout(this._showTimer); |
| delete this._showTimer; |
| } |
| }, |
| |
| onShow: function(target, position){ |
| // summary: |
| // Called when the tooltip is shown |
| // tags: |
| // callback |
| }, |
| |
| onHide: function(){ |
| // summary: |
| // Called when the tooltip is hidden |
| // tags: |
| // callback |
| }, |
| |
| uninitialize: function(){ |
| this.close(); |
| this.inherited(arguments); |
| } |
| } |
| ); |
| |
| // dijit.Tooltip.defaultPosition: String[] |
| // This variable controls the position of tooltips, if the position is not specified to |
| // the Tooltip widget or *TextBox widget itself. It's an array of strings with the following values: |
| // |
| // * before: places tooltip to the left of the target node/widget, or to the right in |
| // the case of RTL scripts like Hebrew and Arabic |
| // * after: places tooltip to the right of the target node/widget, or to the left in |
| // the case of RTL scripts like Hebrew and Arabic |
| // * above: tooltip goes above target node |
| // * below: tooltip goes below target node |
| // |
| // The list is positions is tried, in order, until a position is found where the tooltip fits |
| // within the viewport. |
| // |
| // Be careful setting this parameter. A value of "above" may work fine until the user scrolls |
| // the screen so that there's no room above the target node. Nodes with drop downs, like |
| // DropDownButton or FilteringSelect, are especially problematic, in that you need to be sure |
| // that the drop down and tooltip don't overlap, even when the viewport is scrolled so that there |
| // is only room below (or above) the target node, but not both. |
| dijit.Tooltip.defaultPosition = ["after", "before"]; |
| |
| } |
| |
| if(!dojo._hasResource["dojo.DeferredList"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.DeferredList"] = true; |
| dojo.provide("dojo.DeferredList"); |
| dojo.declare("dojo.DeferredList", dojo.Deferred, { |
| constructor: function(/*Array*/ list, /*Boolean?*/ fireOnOneCallback, /*Boolean?*/ fireOnOneErrback, /*Boolean?*/ consumeErrors, /*Function?*/ canceller){ |
| // summary: |
| // Provides event handling for a group of Deferred objects. |
| // description: |
| // DeferredList takes an array of existing deferreds and returns a new deferred of its own |
| // this new deferred will typically have its callback fired when all of the deferreds in |
| // the given list have fired their own deferreds. The parameters `fireOnOneCallback` and |
| // fireOnOneErrback, will fire before all the deferreds as appropriate |
| // |
| // list: |
| // The list of deferreds to be synchronizied with this DeferredList |
| // fireOnOneCallback: |
| // Will cause the DeferredLists callback to be fired as soon as any |
| // of the deferreds in its list have been fired instead of waiting until |
| // the entire list has finished |
| // fireonOneErrback: |
| // Will cause the errback to fire upon any of the deferreds errback |
| // canceller: |
| // A deferred canceller function, see dojo.Deferred |
| this.list = list; |
| this.resultList = new Array(this.list.length); |
| |
| // Deferred init |
| this.chain = []; |
| this.id = this._nextId(); |
| this.fired = -1; |
| this.paused = 0; |
| this.results = [null, null]; |
| this.canceller = canceller; |
| this.silentlyCancelled = false; |
| |
| if(this.list.length === 0 && !fireOnOneCallback){ |
| this.callback(this.resultList); |
| } |
| |
| this.finishedCount = 0; |
| this.fireOnOneCallback = fireOnOneCallback; |
| this.fireOnOneErrback = fireOnOneErrback; |
| this.consumeErrors = consumeErrors; |
| |
| dojo.forEach(this.list, function(d, index){ |
| d.addCallback(this, function(r){ this._cbDeferred(index, true, r); return r; }); |
| d.addErrback(this, function(r){ this._cbDeferred(index, false, r); return r; }); |
| }, this); |
| }, |
| |
| _cbDeferred: function(index, succeeded, result){ |
| // summary: |
| // The DeferredLists' callback handler |
| |
| this.resultList[index] = [succeeded, result]; this.finishedCount += 1; |
| if(this.fired !== 0){ |
| if(succeeded && this.fireOnOneCallback){ |
| this.callback([index, result]); |
| }else if(!succeeded && this.fireOnOneErrback){ |
| this.errback(result); |
| }else if(this.finishedCount == this.list.length){ |
| this.callback(this.resultList); |
| } |
| } |
| if(!succeeded && this.consumeErrors){ |
| result = null; |
| } |
| return result; |
| }, |
| |
| gatherResults: function(deferredList){ |
| // summary: |
| // Gathers the results of the deferreds for packaging |
| // as the parameters to the Deferred Lists' callback |
| |
| var d = new dojo.DeferredList(deferredList, false, true, false); |
| d.addCallback(function(results){ |
| var ret = []; |
| dojo.forEach(results, function(result){ |
| ret.push(result[1]); |
| }); |
| return ret; |
| }); |
| return d; |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dojo.cookie"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.cookie"] = true; |
| dojo.provide("dojo.cookie"); |
| |
| |
| |
| /*===== |
| dojo.__cookieProps = function(){ |
| // expires: Date|String|Number? |
| // If a number, the number of days from today at which the cookie |
| // will expire. If a date, the date past which the cookie will expire. |
| // If expires is in the past, the cookie will be deleted. |
| // If expires is omitted or is 0, the cookie will expire when the browser closes. << FIXME: 0 seems to disappear right away? FF3. |
| // path: String? |
| // The path to use for the cookie. |
| // domain: String? |
| // The domain to use for the cookie. |
| // secure: Boolean? |
| // Whether to only send the cookie on secure connections |
| this.expires = expires; |
| this.path = path; |
| this.domain = domain; |
| this.secure = secure; |
| } |
| =====*/ |
| |
| |
| dojo.cookie = function(/*String*/name, /*String?*/value, /*dojo.__cookieProps?*/props){ |
| // summary: |
| // Get or set a cookie. |
| // description: |
| // If one argument is passed, returns the value of the cookie |
| // For two or more arguments, acts as a setter. |
| // name: |
| // Name of the cookie |
| // value: |
| // Value for the cookie |
| // props: |
| // Properties for the cookie |
| // example: |
| // set a cookie with the JSON-serialized contents of an object which |
| // will expire 5 days from now: |
| // | dojo.cookie("configObj", dojo.toJson(config), { expires: 5 }); |
| // |
| // example: |
| // de-serialize a cookie back into a JavaScript object: |
| // | var config = dojo.fromJson(dojo.cookie("configObj")); |
| // |
| // example: |
| // delete a cookie: |
| // | dojo.cookie("configObj", null, {expires: -1}); |
| var c = document.cookie; |
| if(arguments.length == 1){ |
| var matches = c.match(new RegExp("(?:^|; )" + dojo.regexp.escapeString(name) + "=([^;]*)")); |
| return matches ? decodeURIComponent(matches[1]) : undefined; // String or undefined |
| }else{ |
| props = props || {}; |
| // FIXME: expires=0 seems to disappear right away, not on close? (FF3) Change docs? |
| var exp = props.expires; |
| if(typeof exp == "number"){ |
| var d = new Date(); |
| d.setTime(d.getTime() + exp*24*60*60*1000); |
| exp = props.expires = d; |
| } |
| if(exp && exp.toUTCString){ props.expires = exp.toUTCString(); } |
| |
| value = encodeURIComponent(value); |
| var updatedCookie = name + "=" + value, propName; |
| for(propName in props){ |
| updatedCookie += "; " + propName; |
| var propValue = props[propName]; |
| if(propValue !== true){ updatedCookie += "=" + propValue; } |
| } |
| document.cookie = updatedCookie; |
| } |
| }; |
| |
| dojo.cookie.isSupported = function(){ |
| // summary: |
| // Use to determine if the current browser supports cookies or not. |
| // |
| // Returns true if user allows cookies. |
| // Returns false if user doesn't allow cookies. |
| |
| if(!("cookieEnabled" in navigator)){ |
| this("__djCookieTest__", "CookiesAllowed"); |
| navigator.cookieEnabled = this("__djCookieTest__") == "CookiesAllowed"; |
| if(navigator.cookieEnabled){ |
| this("__djCookieTest__", "", {expires: -1}); |
| } |
| } |
| return navigator.cookieEnabled; |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dijit.tree.TreeStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.tree.TreeStoreModel"] = true; |
| dojo.provide("dijit.tree.TreeStoreModel"); |
| |
| dojo.declare( |
| "dijit.tree.TreeStoreModel", |
| null, |
| { |
| // summary: |
| // Implements dijit.Tree.model connecting to a store with a single |
| // root item. Any methods passed into the constructor will override |
| // the ones defined here. |
| |
| // store: dojo.data.Store |
| // Underlying store |
| store: null, |
| |
| // childrenAttrs: String[] |
| // One or more attribute names (attributes in the dojo.data item) that specify that item's children |
| childrenAttrs: ["children"], |
| |
| // newItemIdAttr: String |
| // Name of attribute in the Object passed to newItem() that specifies the id. |
| // |
| // If newItemIdAttr is set then it's used when newItem() is called to see if an |
| // item with the same id already exists, and if so just links to the old item |
| // (so that the old item ends up with two parents). |
| // |
| // Setting this to null or "" will make every drop create a new item. |
| newItemIdAttr: "id", |
| |
| // labelAttr: String |
| // If specified, get label for tree node from this attribute, rather |
| // than by calling store.getLabel() |
| labelAttr: "", |
| |
| // root: [readonly] dojo.data.Item |
| // Pointer to the root item (read only, not a parameter) |
| root: null, |
| |
| // query: anything |
| // Specifies datastore query to return the root item for the tree. |
| // Must only return a single item. Alternately can just pass in pointer |
| // to root item. |
| // example: |
| // | {id:'ROOT'} |
| query: null, |
| |
| // deferItemLoadingUntilExpand: Boolean |
| // Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes |
| // until they are expanded. This allows for lazying loading where only one |
| // loadItem (and generally one network call, consequently) per expansion |
| // (rather than one for each child). |
| // This relies on partial loading of the children items; each children item of a |
| // fully loaded item should contain the label and info about having children. |
| deferItemLoadingUntilExpand: false, |
| |
| constructor: function(/* Object */ args){ |
| // summary: |
| // Passed the arguments listed above (store, etc) |
| // tags: |
| // private |
| |
| dojo.mixin(this, args); |
| |
| this.connects = []; |
| |
| var store = this.store; |
| if(!store.getFeatures()['dojo.data.api.Identity']){ |
| throw new Error("dijit.Tree: store must support dojo.data.Identity"); |
| } |
| |
| // if the store supports Notification, subscribe to the notification events |
| if(store.getFeatures()['dojo.data.api.Notification']){ |
| this.connects = this.connects.concat([ |
| dojo.connect(store, "onNew", this, "onNewItem"), |
| dojo.connect(store, "onDelete", this, "onDeleteItem"), |
| dojo.connect(store, "onSet", this, "onSetItem") |
| ]); |
| } |
| }, |
| |
| destroy: function(){ |
| dojo.forEach(this.connects, dojo.disconnect); |
| // TODO: should cancel any in-progress processing of getRoot(), getChildren() |
| }, |
| |
| // ======================================================================= |
| // Methods for traversing hierarchy |
| |
| getRoot: function(onItem, onError){ |
| // summary: |
| // Calls onItem with the root item for the tree, possibly a fabricated item. |
| // Calls onError on error. |
| if(this.root){ |
| onItem(this.root); |
| }else{ |
| this.store.fetch({ |
| query: this.query, |
| onComplete: dojo.hitch(this, function(items){ |
| if(items.length != 1){ |
| throw new Error(this.declaredClass + ": query " + dojo.toJson(this.query) + " returned " + items.length + |
| " items, but must return exactly one item"); |
| } |
| this.root = items[0]; |
| onItem(this.root); |
| }), |
| onError: onError |
| }); |
| } |
| }, |
| |
| mayHaveChildren: function(/*dojo.data.Item*/ item){ |
| // summary: |
| // Tells if an item has or may have children. Implementing logic here |
| // avoids showing +/- expando icon for nodes that we know don't have children. |
| // (For efficiency reasons we may not want to check if an element actually |
| // has children until user clicks the expando node) |
| return dojo.some(this.childrenAttrs, function(attr){ |
| return this.store.hasAttribute(item, attr); |
| }, this); |
| }, |
| |
| getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){ |
| // summary: |
| // Calls onComplete() with array of child items of given parent item, all loaded. |
| |
| var store = this.store; |
| if(!store.isItemLoaded(parentItem)){ |
| // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand |
| // mode, so we will load it and just return the children (without loading each |
| // child item) |
| var getChildren = dojo.hitch(this, arguments.callee); |
| store.loadItem({ |
| item: parentItem, |
| onItem: function(parentItem){ |
| getChildren(parentItem, onComplete, onError); |
| }, |
| onError: onError |
| }); |
| return; |
| } |
| // get children of specified item |
| var childItems = []; |
| for(var i=0; i<this.childrenAttrs.length; i++){ |
| var vals = store.getValues(parentItem, this.childrenAttrs[i]); |
| childItems = childItems.concat(vals); |
| } |
| |
| // count how many items need to be loaded |
| var _waitCount = 0; |
| if(!this.deferItemLoadingUntilExpand){ |
| dojo.forEach(childItems, function(item){ if(!store.isItemLoaded(item)){ _waitCount++; } }); |
| } |
| |
| if(_waitCount == 0){ |
| // all items are already loaded (or we aren't loading them). proceed... |
| onComplete(childItems); |
| }else{ |
| // still waiting for some or all of the items to load |
| var onItem = function onItem(item){ |
| if(--_waitCount == 0){ |
| // all nodes have been loaded, send them to the tree |
| onComplete(childItems); |
| } |
| } |
| dojo.forEach(childItems, function(item){ |
| if(!store.isItemLoaded(item)){ |
| store.loadItem({ |
| item: item, |
| onItem: onItem, |
| onError: onError |
| }); |
| } |
| }); |
| } |
| }, |
| |
| // ======================================================================= |
| // Inspecting items |
| |
| isItem: function(/* anything */ something){ |
| return this.store.isItem(something); // Boolean |
| }, |
| |
| fetchItemByIdentity: function(/* object */ keywordArgs){ |
| this.store.fetchItemByIdentity(keywordArgs); |
| }, |
| |
| getIdentity: function(/* item */ item){ |
| return this.store.getIdentity(item); // Object |
| }, |
| |
| getLabel: function(/*dojo.data.Item*/ item){ |
| // summary: |
| // Get the label for an item |
| if(this.labelAttr){ |
| return this.store.getValue(item,this.labelAttr); // String |
| }else{ |
| return this.store.getLabel(item); // String |
| } |
| }, |
| |
| // ======================================================================= |
| // Write interface |
| |
| newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){ |
| // summary: |
| // Creates a new item. See `dojo.data.api.Write` for details on args. |
| // Used in drag & drop when item from external source dropped onto tree. |
| // description: |
| // Developers will need to override this method if new items get added |
| // to parents with multiple children attributes, in order to define which |
| // children attribute points to the new item. |
| |
| var pInfo = {parent: parent, attribute: this.childrenAttrs[0], insertIndex: insertIndex}; |
| |
| if(this.newItemIdAttr && args[this.newItemIdAttr]){ |
| // Maybe there's already a corresponding item in the store; if so, reuse it. |
| this.fetchItemByIdentity({identity: args[this.newItemIdAttr], scope: this, onItem: function(item){ |
| if(item){ |
| // There's already a matching item in store, use it |
| this.pasteItem(item, null, parent, true, insertIndex); |
| }else{ |
| // Create new item in the tree, based on the drag source. |
| this.store.newItem(args, pInfo); |
| } |
| }}); |
| }else{ |
| // [as far as we know] there is no id so we must assume this is a new item |
| this.store.newItem(args, pInfo); |
| } |
| }, |
| |
| pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){ |
| // summary: |
| // Move or copy an item from one parent item to another. |
| // Used in drag & drop |
| var store = this.store, |
| parentAttr = this.childrenAttrs[0]; // name of "children" attr in parent item |
| |
| // remove child from source item, and record the attribute that child occurred in |
| if(oldParentItem){ |
| dojo.forEach(this.childrenAttrs, function(attr){ |
| if(store.containsValue(oldParentItem, attr, childItem)){ |
| if(!bCopy){ |
| var values = dojo.filter(store.getValues(oldParentItem, attr), function(x){ |
| return x != childItem; |
| }); |
| store.setValues(oldParentItem, attr, values); |
| } |
| parentAttr = attr; |
| } |
| }); |
| } |
| |
| // modify target item's children attribute to include this item |
| if(newParentItem){ |
| if(typeof insertIndex == "number"){ |
| var childItems = store.getValues(newParentItem, parentAttr); |
| childItems.splice(insertIndex, 0, childItem); |
| store.setValues(newParentItem, parentAttr, childItems); |
| }else{ |
| store.setValues(newParentItem, parentAttr, |
| store.getValues(newParentItem, parentAttr).concat(childItem)); |
| } |
| } |
| }, |
| |
| // ======================================================================= |
| // Callbacks |
| |
| onChange: function(/*dojo.data.Item*/ item){ |
| // summary: |
| // Callback whenever an item has changed, so that Tree |
| // can update the label, icon, etc. Note that changes |
| // to an item's children or parent(s) will trigger an |
| // onChildrenChange() so you can ignore those changes here. |
| // tags: |
| // callback |
| }, |
| |
| onChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ |
| // summary: |
| // Callback to do notifications about new, updated, or deleted items. |
| // tags: |
| // callback |
| }, |
| |
| onDelete: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ |
| // summary: |
| // Callback when an item has been deleted. |
| // description: |
| // Note that there will also be an onChildrenChange() callback for the parent |
| // of this item. |
| // tags: |
| // callback |
| }, |
| |
| // ======================================================================= |
| // Events from data store |
| |
| onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){ |
| // summary: |
| // Handler for when new items appear in the store, either from a drop operation |
| // or some other way. Updates the tree view (if necessary). |
| // description: |
| // If the new item is a child of an existing item, |
| // calls onChildrenChange() with the new list of children |
| // for that existing item. |
| // |
| // tags: |
| // extension |
| |
| // We only care about the new item if it has a parent that corresponds to a TreeNode |
| // we are currently displaying |
| if(!parentInfo){ |
| return; |
| } |
| |
| // Call onChildrenChange() on parent (ie, existing) item with new list of children |
| // In the common case, the new list of children is simply parentInfo.newValue or |
| // [ parentInfo.newValue ], although if items in the store has multiple |
| // child attributes (see `childrenAttr`), then it's a superset of parentInfo.newValue, |
| // so call getChildren() to be sure to get right answer. |
| this.getChildren(parentInfo.item, dojo.hitch(this, function(children){ |
| this.onChildrenChange(parentInfo.item, children); |
| })); |
| }, |
| |
| onDeleteItem: function(/*Object*/ item){ |
| // summary: |
| // Handler for delete notifications from underlying store |
| this.onDelete(item); |
| }, |
| |
| onSetItem: function(/* item */ item, |
| /* attribute-name-string */ attribute, |
| /* object | array */ oldValue, |
| /* object | array */ newValue){ |
| // summary: |
| // Updates the tree view according to changes in the data store. |
| // description: |
| // Handles updates to an item's children by calling onChildrenChange(), and |
| // other updates to an item by calling onChange(). |
| // |
| // See `onNewItem` for more details on handling updates to an item's children. |
| // tags: |
| // extension |
| |
| if(dojo.indexOf(this.childrenAttrs, attribute) != -1){ |
| // item's children list changed |
| this.getChildren(item, dojo.hitch(this, function(children){ |
| // See comments in onNewItem() about calling getChildren() |
| this.onChildrenChange(item, children); |
| })); |
| }else{ |
| // item's label/icon/etc. changed. |
| this.onChange(item); |
| } |
| } |
| }); |
| |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.tree.ForestStoreModel"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.tree.ForestStoreModel"] = true; |
| dojo.provide("dijit.tree.ForestStoreModel"); |
| |
| |
| |
| dojo.declare("dijit.tree.ForestStoreModel", dijit.tree.TreeStoreModel, { |
| // summary: |
| // Interface between Tree and a dojo.store that doesn't have a root item, |
| // i.e. has multiple "top level" items. |
| // |
| // description |
| // Use this class to wrap a dojo.store, making all the items matching the specified query |
| // appear as children of a fabricated "root item". If no query is specified then all the |
| // items returned by fetch() on the underlying store become children of the root item. |
| // It allows dijit.Tree to assume a single root item, even if the store doesn't have one. |
| |
| // Parameters to constructor |
| |
| // rootId: String |
| // ID of fabricated root item |
| rootId: "$root$", |
| |
| // rootLabel: String |
| // Label of fabricated root item |
| rootLabel: "ROOT", |
| |
| // query: String |
| // Specifies the set of children of the root item. |
| // example: |
| // | {type:'continent'} |
| query: null, |
| |
| // End of parameters to constructor |
| |
| constructor: function(params){ |
| // summary: |
| // Sets up variables, etc. |
| // tags: |
| // private |
| |
| // Make dummy root item |
| this.root = { |
| store: this, |
| root: true, |
| id: params.rootId, |
| label: params.rootLabel, |
| children: params.rootChildren // optional param |
| }; |
| }, |
| |
| // ======================================================================= |
| // Methods for traversing hierarchy |
| |
| mayHaveChildren: function(/*dojo.data.Item*/ item){ |
| // summary: |
| // Tells if an item has or may have children. Implementing logic here |
| // avoids showing +/- expando icon for nodes that we know don't have children. |
| // (For efficiency reasons we may not want to check if an element actually |
| // has children until user clicks the expando node) |
| // tags: |
| // extension |
| return item === this.root || this.inherited(arguments); |
| }, |
| |
| getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ callback, /*function*/ onError){ |
| // summary: |
| // Calls onComplete() with array of child items of given parent item, all loaded. |
| if(parentItem === this.root){ |
| if(this.root.children){ |
| // already loaded, just return |
| callback(this.root.children); |
| }else{ |
| this.store.fetch({ |
| query: this.query, |
| onComplete: dojo.hitch(this, function(items){ |
| this.root.children = items; |
| callback(items); |
| }), |
| onError: onError |
| }); |
| } |
| }else{ |
| this.inherited(arguments); |
| } |
| }, |
| |
| // ======================================================================= |
| // Inspecting items |
| |
| isItem: function(/* anything */ something){ |
| return (something === this.root) ? true : this.inherited(arguments); |
| }, |
| |
| fetchItemByIdentity: function(/* object */ keywordArgs){ |
| if(keywordArgs.identity == this.root.id){ |
| var scope = keywordArgs.scope?keywordArgs.scope:dojo.global; |
| if(keywordArgs.onItem){ |
| keywordArgs.onItem.call(scope, this.root); |
| } |
| }else{ |
| this.inherited(arguments); |
| } |
| }, |
| |
| getIdentity: function(/* item */ item){ |
| return (item === this.root) ? this.root.id : this.inherited(arguments); |
| }, |
| |
| getLabel: function(/* item */ item){ |
| return (item === this.root) ? this.root.label : this.inherited(arguments); |
| }, |
| |
| // ======================================================================= |
| // Write interface |
| |
| newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){ |
| // summary: |
| // Creates a new item. See dojo.data.api.Write for details on args. |
| // Used in drag & drop when item from external source dropped onto tree. |
| if(parent === this.root){ |
| this.onNewRootItem(args); |
| return this.store.newItem(args); |
| }else{ |
| return this.inherited(arguments); |
| } |
| }, |
| |
| onNewRootItem: function(args){ |
| // summary: |
| // User can override this method to modify a new element that's being |
| // added to the root of the tree, for example to add a flag like root=true |
| }, |
| |
| pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){ |
| // summary: |
| // Move or copy an item from one parent item to another. |
| // Used in drag & drop |
| if(oldParentItem === this.root){ |
| if(!bCopy){ |
| // It's onLeaveRoot()'s responsibility to modify the item so it no longer matches |
| // this.query... thus triggering an onChildrenChange() event to notify the Tree |
| // that this element is no longer a child of the root node |
| this.onLeaveRoot(childItem); |
| } |
| } |
| dijit.tree.TreeStoreModel.prototype.pasteItem.call(this, childItem, |
| oldParentItem === this.root ? null : oldParentItem, |
| newParentItem === this.root ? null : newParentItem, |
| bCopy, |
| insertIndex |
| ); |
| if(newParentItem === this.root){ |
| // It's onAddToRoot()'s responsibility to modify the item so it matches |
| // this.query... thus triggering an onChildrenChange() event to notify the Tree |
| // that this element is now a child of the root node |
| this.onAddToRoot(childItem); |
| } |
| }, |
| |
| // ======================================================================= |
| // Handling for top level children |
| |
| onAddToRoot: function(/* item */ item){ |
| // summary: |
| // Called when item added to root of tree; user must override this method |
| // to modify the item so that it matches the query for top level items |
| // example: |
| // | store.setValue(item, "root", true); |
| // tags: |
| // extension |
| console.log(this, ": item ", item, " added to root"); |
| }, |
| |
| onLeaveRoot: function(/* item */ item){ |
| // summary: |
| // Called when item removed from root of tree; user must override this method |
| // to modify the item so it doesn't match the query for top level items |
| // example: |
| // | store.unsetAttribute(item, "root"); |
| // tags: |
| // extension |
| console.log(this, ": item ", item, " removed from root"); |
| }, |
| |
| // ======================================================================= |
| // Events from data store |
| |
| _requeryTop: function(){ |
| // reruns the query for the children of the root node, |
| // sending out an onSet notification if those children have changed |
| var oldChildren = this.root.children || []; |
| this.store.fetch({ |
| query: this.query, |
| onComplete: dojo.hitch(this, function(newChildren){ |
| this.root.children = newChildren; |
| |
| // If the list of children or the order of children has changed... |
| if(oldChildren.length != newChildren.length || |
| dojo.some(oldChildren, function(item, idx){ return newChildren[idx] != item;})){ |
| this.onChildrenChange(this.root, newChildren); |
| } |
| }) |
| }); |
| }, |
| |
| onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){ |
| // summary: |
| // Handler for when new items appear in the store. Developers should override this |
| // method to be more efficient based on their app/data. |
| // description: |
| // Note that the default implementation requeries the top level items every time |
| // a new item is created, since any new item could be a top level item (even in |
| // addition to being a child of another item, since items can have multiple parents). |
| // |
| // Developers can override this function to do something more efficient if they can |
| // detect which items are possible top level items (based on the item and the |
| // parentInfo parameters). Often all top level items have parentInfo==null, but |
| // that will depend on which store you use and what your data is like. |
| // tags: |
| // extension |
| this._requeryTop(); |
| |
| this.inherited(arguments); |
| }, |
| |
| onDeleteItem: function(/*Object*/ item){ |
| // summary: |
| // Handler for delete notifications from underlying store |
| |
| // check if this was a child of root, and if so send notification that root's children |
| // have changed |
| if(dojo.indexOf(this.root.children, item) != -1){ |
| this._requeryTop(); |
| } |
| |
| this.inherited(arguments); |
| } |
| }); |
| |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.Tree"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.Tree"] = true; |
| dojo.provide("dijit.Tree"); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| dojo.declare( |
| "dijit._TreeNode", |
| [dijit._Widget, dijit._Templated, dijit._Container, dijit._Contained], |
| { |
| // summary: |
| // Single node within a tree. This class is used internally |
| // by Tree and should not be accessed directly. |
| // tags: |
| // private |
| |
| // item: dojo.data.Item |
| // the dojo.data entry this tree represents |
| item: null, |
| |
| // isTreeNode: [protected] Boolean |
| // Indicates that this is a TreeNode. Used by `dijit.Tree` only, |
| // should not be accessed directly. |
| isTreeNode: true, |
| |
| // label: String |
| // Text of this tree node |
| label: "", |
| |
| // isExpandable: [private] Boolean |
| // This node has children, so show the expando node (+ sign) |
| isExpandable: null, |
| |
| // isExpanded: [readonly] Boolean |
| // This node is currently expanded (ie, opened) |
| isExpanded: false, |
| |
| // state: [private] String |
| // Dynamic loading-related stuff. |
| // When an empty folder node appears, it is "UNCHECKED" first, |
| // then after dojo.data query it becomes "LOADING" and, finally "LOADED" |
| state: "UNCHECKED", |
| |
| templateString: dojo.cache("dijit", "templates/TreeNode.html", "<div class=\"dijitTreeNode\" waiRole=\"presentation\"\n\t><div dojoAttachPoint=\"rowNode\" class=\"dijitTreeRow\" waiRole=\"presentation\" dojoAttachEvent=\"onmouseenter:_onMouseEnter, onmouseleave:_onMouseLeave, onclick:_onClick, ondblclick:_onDblClick\"\n\t\t><img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"expandoNode\" class=\"dijitTreeExpando\" waiRole=\"presentation\"\n\t\t><span dojoAttachPoint=\"expandoNodeText\" class=\"dijitExpandoText\" waiRole=\"presentation\"\n\t\t></span\n\t\t><span dojoAttachPoint=\"contentNode\"\n\t\t\tclass=\"dijitTreeContent\" waiRole=\"presentation\">\n\t\t\t<img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint=\"iconNode\" class=\"dijitTreeIcon\" waiRole=\"presentation\"\n\t\t\t><span dojoAttachPoint=\"labelNode\" class=\"dijitTreeLabel\" wairole=\"treeitem\" tabindex=\"-1\" waiState=\"selected-false\" dojoAttachEvent=\"onfocus:_onLabelFocus, onblur:_onLabelBlur\"></span>\n\t\t</span\n\t></div>\n\t<div dojoAttachPoint=\"containerNode\" class=\"dijitTreeContainer\" waiRole=\"presentation\" style=\"display: none;\"></div>\n</div>\n"), |
| |
| attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { |
| label: {node: "labelNode", type: "innerText"}, |
| tooltip: {node: "rowNode", type: "attribute", attribute: "title"} |
| }), |
| |
| postCreate: function(){ |
| // set expand icon for leaf |
| this._setExpando(); |
| |
| // set icon and label class based on item |
| this._updateItemClasses(this.item); |
| |
| if(this.isExpandable){ |
| dijit.setWaiState(this.labelNode, "expanded", this.isExpanded); |
| } |
| }, |
| |
| _setIndentAttr: function(indent){ |
| // summary: |
| // Tell this node how many levels it should be indented |
| // description: |
| // 0 for top level nodes, 1 for their children, 2 for their |
| // grandchildren, etc. |
| this.indent = indent; |
| |
| // Math.max() is to prevent negative padding on hidden root node (when indent == -1) |
| var pixels = (Math.max(indent, 0) * this.tree._nodePixelIndent) + "px"; |
| |
| dojo.style(this.domNode, "backgroundPosition", pixels + " 0px"); |
| dojo.style(this.rowNode, dojo._isBodyLtr() ? "paddingLeft" : "paddingRight", pixels); |
| |
| dojo.forEach(this.getChildren(), function(child){ |
| child.attr("indent", indent+1); |
| }); |
| }, |
| |
| markProcessing: function(){ |
| // summary: |
| // Visually denote that tree is loading data, etc. |
| // tags: |
| // private |
| this.state = "LOADING"; |
| this._setExpando(true); |
| }, |
| |
| unmarkProcessing: function(){ |
| // summary: |
| // Clear markup from markProcessing() call |
| // tags: |
| // private |
| this._setExpando(false); |
| }, |
| |
| _updateItemClasses: function(item){ |
| // summary: |
| // Set appropriate CSS classes for icon and label dom node |
| // (used to allow for item updates to change respective CSS) |
| // tags: |
| // private |
| var tree = this.tree, model = tree.model; |
| if(tree._v10Compat && item === model.root){ |
| // For back-compat with 1.0, need to use null to specify root item (TODO: remove in 2.0) |
| item = null; |
| } |
| this._applyClassAndStyle(item, "icon", "Icon"); |
| this._applyClassAndStyle(item, "label", "Label"); |
| this._applyClassAndStyle(item, "row", "Row"); |
| }, |
| |
| _applyClassAndStyle: function(item, lower, upper){ |
| // summary: |
| // Set the appropriate CSS classes and styles for labels, icons and rows. |
| // |
| // item: |
| // The data item. |
| // |
| // lower: |
| // The lower case attribute to use, e.g. 'icon', 'label' or 'row'. |
| // |
| // upper: |
| // The upper case attribute to use, e.g. 'Icon', 'Label' or 'Row'. |
| // |
| // tags: |
| // private |
| |
| var clsName = "_" + lower + "Class"; |
| var nodeName = lower + "Node"; |
| |
| if(this[clsName]){ |
| dojo.removeClass(this[nodeName], this[clsName]); |
| } |
| this[clsName] = this.tree["get" + upper + "Class"](item, this.isExpanded); |
| if(this[clsName]){ |
| dojo.addClass(this[nodeName], this[clsName]); |
| } |
| dojo.style(this[nodeName], this.tree["get" + upper + "Style"](item, this.isExpanded) || {}); |
| }, |
| |
| _updateLayout: function(){ |
| // summary: |
| // Set appropriate CSS classes for this.domNode |
| // tags: |
| // private |
| var parent = this.getParent(); |
| if(!parent || parent.rowNode.style.display == "none"){ |
| /* if we are hiding the root node then make every first level child look like a root node */ |
| dojo.addClass(this.domNode, "dijitTreeIsRoot"); |
| }else{ |
| dojo.toggleClass(this.domNode, "dijitTreeIsLast", !this.getNextSibling()); |
| } |
| }, |
| |
| _setExpando: function(/*Boolean*/ processing){ |
| // summary: |
| // Set the right image for the expando node |
| // tags: |
| // private |
| |
| var styles = ["dijitTreeExpandoLoading", "dijitTreeExpandoOpened", |
| "dijitTreeExpandoClosed", "dijitTreeExpandoLeaf"], |
| _a11yStates = ["*","-","+","*"], |
| idx = processing ? 0 : (this.isExpandable ? (this.isExpanded ? 1 : 2) : 3); |
| |
| // apply the appropriate class to the expando node |
| dojo.removeClass(this.expandoNode, styles); |
| dojo.addClass(this.expandoNode, styles[idx]); |
| |
| // provide a non-image based indicator for images-off mode |
| this.expandoNodeText.innerHTML = _a11yStates[idx]; |
| |
| }, |
| |
| expand: function(){ |
| // summary: |
| // Show my children |
| // returns: |
| // Deferred that fires when expansion is complete |
| |
| // If there's already an expand in progress or we are already expanded, just return |
| if(this._expandDeferred){ |
| return this._expandDeferred; // dojo.Deferred |
| } |
| |
| // cancel in progress collapse operation |
| this._wipeOut && this._wipeOut.stop(); |
| |
| // All the state information for when a node is expanded, maybe this should be |
| // set when the animation completes instead |
| this.isExpanded = true; |
| dijit.setWaiState(this.labelNode, "expanded", "true"); |
| dijit.setWaiRole(this.containerNode, "group"); |
| dojo.addClass(this.contentNode,'dijitTreeContentExpanded'); |
| this._setExpando(); |
| this._updateItemClasses(this.item); |
| if(this == this.tree.rootNode){ |
| dijit.setWaiState(this.tree.domNode, "expanded", "true"); |
| } |
| |
| var def, |
| wipeIn = dojo.fx.wipeIn({ |
| node: this.containerNode, duration: dijit.defaultDuration, |
| onEnd: function(){ |
| def.callback(true); |
| } |
| }); |
| |
| // Deferred that fires when expand is complete |
| def = (this._expandDeferred = new dojo.Deferred(function(){ |
| // Canceller |
| wipeIn.stop(); |
| })); |
| |
| wipeIn.play(); |
| |
| return def; // dojo.Deferred |
| }, |
| |
| collapse: function(){ |
| // summary: |
| // Collapse this node (if it's expanded) |
| |
| if(!this.isExpanded){ return; } |
| |
| // cancel in progress expand operation |
| if(this._expandDeferred){ |
| this._expandDeferred.cancel(); |
| delete this._expandDeferred; |
| } |
| |
| this.isExpanded = false; |
| dijit.setWaiState(this.labelNode, "expanded", "false"); |
| if(this == this.tree.rootNode){ |
| dijit.setWaiState(this.tree.domNode, "expanded", "false"); |
| } |
| dojo.removeClass(this.contentNode,'dijitTreeContentExpanded'); |
| this._setExpando(); |
| this._updateItemClasses(this.item); |
| |
| if(!this._wipeOut){ |
| this._wipeOut = dojo.fx.wipeOut({ |
| node: this.containerNode, duration: dijit.defaultDuration |
| }); |
| } |
| this._wipeOut.play(); |
| }, |
| |
| // indent: Integer |
| // Levels from this node to the root node |
| indent: 0, |
| |
| setChildItems: function(/* Object[] */ items){ |
| // summary: |
| // Sets the child items of this node, removing/adding nodes |
| // from current children to match specified items[] array. |
| // Also, if this.persist == true, expands any children that were previously |
| // opened. |
| // returns: |
| // Deferred object that fires after all previously opened children |
| // have been expanded again (or fires instantly if there are no such children). |
| |
| var tree = this.tree, |
| model = tree.model, |
| defs = []; // list of deferreds that need to fire before I am complete |
| |
| |
| // Orphan all my existing children. |
| // If items contains some of the same items as before then we will reattach them. |
| // Don't call this.removeChild() because that will collapse the tree etc. |
| this.getChildren().forEach(function(child){ |
| dijit._Container.prototype.removeChild.call(this, child); |
| }, this); |
| |
| this.state = "LOADED"; |
| |
| if(items && items.length > 0){ |
| this.isExpandable = true; |
| |
| // Create _TreeNode widget for each specified tree node, unless one already |
| // exists and isn't being used (presumably it's from a DnD move and was recently |
| // released |
| dojo.forEach(items, function(item){ |
| var id = model.getIdentity(item), |
| existingNodes = tree._itemNodesMap[id], |
| node; |
| if(existingNodes){ |
| for(var i=0;i<existingNodes.length;i++){ |
| if(existingNodes[i] && !existingNodes[i].getParent()){ |
| node = existingNodes[i]; |
| node.attr('indent', this.indent+1); |
| break; |
| } |
| } |
| } |
| if(!node){ |
| node = this.tree._createTreeNode({ |
| item: item, |
| tree: tree, |
| isExpandable: model.mayHaveChildren(item), |
| label: tree.getLabel(item), |
| tooltip: tree.getTooltip(item), |
| indent: this.indent + 1 |
| }); |
| if(existingNodes){ |
| existingNodes.push(node); |
| }else{ |
| tree._itemNodesMap[id] = [node]; |
| } |
| } |
| this.addChild(node); |
| |
| // If node was previously opened then open it again now (this may trigger |
| // more data store accesses, recursively) |
| if(this.tree.autoExpand || this.tree._state(item)){ |
| defs.push(tree._expandNode(node)); |
| } |
| }, this); |
| |
| // note that updateLayout() needs to be called on each child after |
| // _all_ the children exist |
| dojo.forEach(this.getChildren(), function(child, idx){ |
| child._updateLayout(); |
| }); |
| }else{ |
| this.isExpandable=false; |
| } |
| |
| if(this._setExpando){ |
| // change expando to/from dot or + icon, as appropriate |
| this._setExpando(false); |
| } |
| |
| // On initial tree show, make the selected TreeNode as either the root node of the tree, |
| // or the first child, if the root node is hidden |
| if(this == tree.rootNode){ |
| var fc = this.tree.showRoot ? this : this.getChildren()[0]; |
| if(fc){ |
| fc.setSelected(true); |
| tree.lastFocused = fc; |
| }else{ |
| // fallback: no nodes in tree so focus on Tree <div> itself |
| tree.domNode.setAttribute("tabIndex", "0"); |
| } |
| } |
| |
| return new dojo.DeferredList(defs); // dojo.Deferred |
| }, |
| |
| removeChild: function(/* treeNode */ node){ |
| this.inherited(arguments); |
| |
| var children = this.getChildren(); |
| if(children.length == 0){ |
| this.isExpandable = false; |
| this.collapse(); |
| } |
| |
| dojo.forEach(children, function(child){ |
| child._updateLayout(); |
| }); |
| }, |
| |
| makeExpandable: function(){ |
| // summary: |
| // if this node wasn't already showing the expando node, |
| // turn it into one and call _setExpando() |
| |
| // TODO: hmm this isn't called from anywhere, maybe should remove it for 2.0 |
| |
| this.isExpandable = true; |
| this._setExpando(false); |
| }, |
| |
| _onLabelFocus: function(evt){ |
| // summary: |
| // Called when this node is focused (possibly programatically) |
| // tags: |
| // private |
| dojo.addClass(this.labelNode, "dijitTreeLabelFocused"); |
| this.tree._onNodeFocus(this); |
| }, |
| |
| _onLabelBlur: function(evt){ |
| // summary: |
| // Called when focus was moved away from this node, either to |
| // another TreeNode or away from the Tree entirely. |
| // Note that we aren't using _onFocus/_onBlur builtin to dijit |
| // because _onBlur() isn't called when focus is moved to my child TreeNode. |
| // tags: |
| // private |
| dojo.removeClass(this.labelNode, "dijitTreeLabelFocused"); |
| }, |
| |
| setSelected: function(/*Boolean*/ selected){ |
| // summary: |
| // A Tree has a (single) currently selected node. |
| // Mark that this node is/isn't that currently selected node. |
| // description: |
| // In particular, setting a node as selected involves setting tabIndex |
| // so that when user tabs to the tree, focus will go to that node (only). |
| var labelNode = this.labelNode; |
| labelNode.setAttribute("tabIndex", selected ? "0" : "-1"); |
| dijit.setWaiState(labelNode, "selected", selected); |
| dojo.toggleClass(this.rowNode, "dijitTreeNodeSelected", selected); |
| }, |
| |
| _onClick: function(evt){ |
| // summary: |
| // Handler for onclick event on a node |
| // tags: |
| // private |
| this.tree._onClick(this, evt); |
| }, |
| _onDblClick: function(evt){ |
| // summary: |
| // Handler for ondblclick event on a node |
| // tags: |
| // private |
| this.tree._onDblClick(this, evt); |
| }, |
| |
| _onMouseEnter: function(evt){ |
| // summary: |
| // Handler for onmouseenter event on a node |
| // tags: |
| // private |
| dojo.addClass(this.rowNode, "dijitTreeNodeHover"); |
| this.tree._onNodeMouseEnter(this, evt); |
| }, |
| |
| _onMouseLeave: function(evt){ |
| // summary: |
| // Handler for onmouseenter event on a node |
| // tags: |
| // private |
| dojo.removeClass(this.rowNode, "dijitTreeNodeHover"); |
| this.tree._onNodeMouseLeave(this, evt); |
| } |
| }); |
| |
| dojo.declare( |
| "dijit.Tree", |
| [dijit._Widget, dijit._Templated], |
| { |
| // summary: |
| // This widget displays hierarchical data from a store. |
| |
| // store: [deprecated] String||dojo.data.Store |
| // Deprecated. Use "model" parameter instead. |
| // The store to get data to display in the tree. |
| store: null, |
| |
| // model: dijit.Tree.model |
| // Interface to read tree data, get notifications of changes to tree data, |
| // and for handling drop operations (i.e drag and drop onto the tree) |
| model: null, |
| |
| // query: [deprecated] anything |
| // Deprecated. User should specify query to the model directly instead. |
| // Specifies datastore query to return the root item or top items for the tree. |
| query: null, |
| |
| // label: [deprecated] String |
| // Deprecated. Use dijit.tree.ForestStoreModel directly instead. |
| // Used in conjunction with query parameter. |
| // If a query is specified (rather than a root node id), and a label is also specified, |
| // then a fake root node is created and displayed, with this label. |
| label: "", |
| |
| // showRoot: [const] Boolean |
| // Should the root node be displayed, or hidden? |
| showRoot: true, |
| |
| // childrenAttr: [deprecated] String[] |
| // Deprecated. This information should be specified in the model. |
| // One ore more attributes that holds children of a tree node |
| childrenAttr: ["children"], |
| |
| // path: String[] or Item[] |
| // Full path from rootNode to selected node expressed as array of items or array of ids. |
| path: [], |
| |
| // selectedItem: [readonly] Item |
| // The currently selected item in this tree. |
| // This property can only be set (via attr('selectedItem', ...)) when that item is already |
| // visible in the tree. (I.e. the tree has already been expanded to show that node.) |
| // Should generally use `path` attribute to set the selected item instead. |
| selectedItem: null, |
| |
| // openOnClick: Boolean |
| // If true, clicking a folder node's label will open it, rather than calling onClick() |
| openOnClick: false, |
| |
| // openOnDblClick: Boolean |
| // If true, double-clicking a folder node's label will open it, rather than calling onDblClick() |
| openOnDblClick: false, |
| |
| templateString: dojo.cache("dijit", "templates/Tree.html", "<div class=\"dijitTree dijitTreeContainer\" waiRole=\"tree\"\n\tdojoAttachEvent=\"onkeypress:_onKeyPress\">\n\t<div class=\"dijitInline dijitTreeIndent\" style=\"position: absolute; top: -9999px\" dojoAttachPoint=\"indentDetector\"></div>\n</div>\n"), |
| |
| // persist: Boolean |
| // Enables/disables use of cookies for state saving. |
| persist: true, |
| |
| // autoExpand: Boolean |
| // Fully expand the tree on load. Overrides `persist` |
| autoExpand: false, |
| |
| // dndController: [protected] String |
| // Class name to use as as the dnd controller. Specifying this class enables DnD. |
| // Generally you should specify this as "dijit.tree.dndSource". |
| dndController: null, |
| |
| // parameters to pull off of the tree and pass on to the dndController as its params |
| dndParams: ["onDndDrop","itemCreator","onDndCancel","checkAcceptance", "checkItemAcceptance", "dragThreshold", "betweenThreshold"], |
| |
| //declare the above items so they can be pulled from the tree's markup |
| |
| // onDndDrop: [protected] Function |
| // Parameter to dndController, see `dijit.tree.dndSource.onDndDrop`. |
| // Generally this doesn't need to be set. |
| onDndDrop: null, |
| |
| /*===== |
| itemCreator: function(nodes, target, source){ |
| // summary: |
| // Returns objects passed to `Tree.model.newItem()` based on DnD nodes |
| // dropped onto the tree. Developer must override this method to enable |
| // dropping from external sources onto this Tree, unless the Tree.model's items |
| // happen to look like {id: 123, name: "Apple" } with no other attributes. |
| // description: |
| // For each node in nodes[], which came from source, create a hash of name/value |
| // pairs to be passed to Tree.model.newItem(). Returns array of those hashes. |
| // nodes: DomNode[] |
| // The DOMNodes dragged from the source container |
| // target: DomNode |
| // The target TreeNode.rowNode |
| // source: dojo.dnd.Source |
| // The source container the nodes were dragged from, perhaps another Tree or a plain dojo.dnd.Source |
| // returns: Object[] |
| // Array of name/value hashes for each new item to be added to the Tree, like: |
| // | [ |
| // | { id: 123, label: "apple", foo: "bar" }, |
| // | { id: 456, label: "pear", zaz: "bam" } |
| // | ] |
| // tags: |
| // extension |
| return [{}]; |
| }, |
| =====*/ |
| itemCreator: null, |
| |
| // onDndCancel: [protected] Function |
| // Parameter to dndController, see `dijit.tree.dndSource.onDndCancel`. |
| // Generally this doesn't need to be set. |
| onDndCancel: null, |
| |
| /*===== |
| checkAcceptance: function(source, nodes){ |
| // summary: |
| // Checks if the Tree itself can accept nodes from this source |
| // source: dijit.tree._dndSource |
| // The source which provides items |
| // nodes: DOMNode[] |
| // Array of DOM nodes corresponding to nodes being dropped, dijitTreeRow nodes if |
| // source is a dijit.Tree. |
| // tags: |
| // extension |
| return true; // Boolean |
| }, |
| =====*/ |
| checkAcceptance: null, |
| |
| /*===== |
| checkItemAcceptance: function(target, source, position){ |
| // summary: |
| // Stub function to be overridden if one wants to check for the ability to drop at the node/item level |
| // description: |
| // In the base case, this is called to check if target can become a child of source. |
| // When betweenThreshold is set, position="before" or "after" means that we |
| // are asking if the source node can be dropped before/after the target node. |
| // target: DOMNode |
| // The dijitTreeRoot DOM node inside of the TreeNode that we are dropping on to |
| // Use dijit.getEnclosingWidget(target) to get the TreeNode. |
| // source: dijit.tree.dndSource |
| // The (set of) nodes we are dropping |
| // position: String |
| // "over", "before", or "after" |
| // tags: |
| // extension |
| return true; // Boolean |
| }, |
| =====*/ |
| checkItemAcceptance: null, |
| |
| // dragThreshold: Integer |
| // Number of pixels mouse moves before it's considered the start of a drag operation |
| dragThreshold: 5, |
| |
| // betweenThreshold: Integer |
| // Set to a positive value to allow drag and drop "between" nodes. |
| // |
| // If during DnD mouse is over a (target) node but less than betweenThreshold |
| // pixels from the bottom edge, dropping the the dragged node will make it |
| // the next sibling of the target node, rather than the child. |
| // |
| // Similarly, if mouse is over a target node but less that betweenThreshold |
| // pixels from the top edge, dropping the dragged node will make it |
| // the target node's previous sibling rather than the target node's child. |
| betweenThreshold: 0, |
| |
| // _nodePixelIndent: Integer |
| // Number of pixels to indent tree nodes (relative to parent node). |
| // Default is 19 but can be overridden by setting CSS class dijitTreeIndent |
| // and calling resize() or startup() on tree after it's in the DOM. |
| _nodePixelIndent: 19, |
| |
| _publish: function(/*String*/ topicName, /*Object*/ message){ |
| // summary: |
| // Publish a message for this widget/topic |
| dojo.publish(this.id, [dojo.mixin({tree: this, event: topicName}, message || {})]); |
| }, |
| |
| postMixInProperties: function(){ |
| this.tree = this; |
| |
| this._itemNodesMap={}; |
| |
| if(!this.cookieName){ |
| this.cookieName = this.id + "SaveStateCookie"; |
| } |
| |
| this._loadDeferred = new dojo.Deferred(); |
| |
| this.inherited(arguments); |
| }, |
| |
| postCreate: function(){ |
| this._initState(); |
| |
| // Create glue between store and Tree, if not specified directly by user |
| if(!this.model){ |
| this._store2model(); |
| } |
| |
| // monitor changes to items |
| this.connect(this.model, "onChange", "_onItemChange"); |
| this.connect(this.model, "onChildrenChange", "_onItemChildrenChange"); |
| this.connect(this.model, "onDelete", "_onItemDelete"); |
| |
| this._load(); |
| |
| this.inherited(arguments); |
| |
| if(this.dndController){ |
| if(dojo.isString(this.dndController)){ |
| this.dndController = dojo.getObject(this.dndController); |
| } |
| var params={}; |
| for(var i=0; i<this.dndParams.length;i++){ |
| if(this[this.dndParams[i]]){ |
| params[this.dndParams[i]] = this[this.dndParams[i]]; |
| } |
| } |
| this.dndController = new this.dndController(this, params); |
| } |
| }, |
| |
| _store2model: function(){ |
| // summary: |
| // User specified a store&query rather than model, so create model from store/query |
| this._v10Compat = true; |
| dojo.deprecated("Tree: from version 2.0, should specify a model object rather than a store/query"); |
| |
| var modelParams = { |
| id: this.id + "_ForestStoreModel", |
| store: this.store, |
| query: this.query, |
| childrenAttrs: this.childrenAttr |
| }; |
| |
| // Only override the model's mayHaveChildren() method if the user has specified an override |
| if(this.params.mayHaveChildren){ |
| modelParams.mayHaveChildren = dojo.hitch(this, "mayHaveChildren"); |
| } |
| |
| if(this.params.getItemChildren){ |
| modelParams.getChildren = dojo.hitch(this, function(item, onComplete, onError){ |
| this.getItemChildren((this._v10Compat && item === this.model.root) ? null : item, onComplete, onError); |
| }); |
| } |
| this.model = new dijit.tree.ForestStoreModel(modelParams); |
| |
| // For backwards compatibility, the visibility of the root node is controlled by |
| // whether or not the user has specified a label |
| this.showRoot = Boolean(this.label); |
| }, |
| |
| onLoad: function(){ |
| // summary: |
| // Called when tree finishes loading and expanding. |
| // description: |
| // If persist == true the loading may encompass many levels of fetches |
| // from the data store, each asynchronous. Waits for all to finish. |
| // tags: |
| // callback |
| }, |
| |
| _load: function(){ |
| // summary: |
| // Initial load of the tree. |
| // Load root node (possibly hidden) and it's children. |
| this.model.getRoot( |
| dojo.hitch(this, function(item){ |
| var rn = (this.rootNode = this.tree._createTreeNode({ |
| item: item, |
| tree: this, |
| isExpandable: true, |
| label: this.label || this.getLabel(item), |
| indent: this.showRoot ? 0 : -1 |
| })); |
| if(!this.showRoot){ |
| rn.rowNode.style.display="none"; |
| } |
| this.domNode.appendChild(rn.domNode); |
| var identity = this.model.getIdentity(item); |
| if(this._itemNodesMap[identity]){ |
| this._itemNodesMap[identity].push(rn); |
| }else{ |
| this._itemNodesMap[identity] = [rn]; |
| } |
| |
| rn._updateLayout(); // sets "dijitTreeIsRoot" CSS classname |
| |
| // load top level children and then fire onLoad() event |
| this._expandNode(rn).addCallback(dojo.hitch(this, function(){ |
| this._loadDeferred.callback(true); |
| this.onLoad(); |
| })); |
| }), |
| function(err){ |
| console.error(this, ": error loading root: ", err); |
| } |
| ); |
| }, |
| |
| getNodesByItem: function(/*dojo.data.Item or id*/ item){ |
| // summary: |
| // Returns all tree nodes that refer to an item |
| // returns: |
| // Array of tree nodes that refer to passed item |
| |
| if(!item){ return []; } |
| var identity = dojo.isString(item) ? item : this.model.getIdentity(item); |
| // return a copy so widget don't get messed up by changes to returned array |
| return [].concat(this._itemNodesMap[identity]); |
| }, |
| |
| _setSelectedItemAttr: function(/*dojo.data.Item or id*/ item){ |
| // summary: |
| // Select a tree node related to passed item. |
| // WARNING: if model use multi-parented items or desired tree node isn't already loaded |
| // behavior is not granted. Use 'path' attr instead for full support. |
| var oldValue = this.attr("selectedItem"); |
| var identity = (!item || dojo.isString(item)) ? item : this.model.getIdentity(item); |
| if(identity == oldValue ? this.model.getIdentity(oldValue) : null){ return; } |
| var nodes = this._itemNodesMap[identity]; |
| if(nodes && nodes.length){ |
| //select the first item |
| this.focusNode(nodes[0]); |
| }else if(this.lastFocused){ |
| // Select none so deselect current |
| this.lastFocused.setSelected(false); |
| this.lastFocused = null; |
| } |
| }, |
| |
| _getSelectedItemAttr: function(){ |
| // summary: |
| // Return item related to selected tree node. |
| return this.lastFocused && this.lastFocused.item; |
| }, |
| |
| _setPathAttr: function(/*Item[] || String[]*/ path){ |
| // summary: |
| // Select the tree node identified by passed path. |
| // path: |
| // Array of items or item id's |
| |
| if(!path || !path.length){ return; } |
| |
| // If this is called during initialization, defer running until Tree has finished loading |
| this._loadDeferred.addCallback(dojo.hitch(this, function(){ |
| if(!this.rootNode){ |
| console.debug("!this.rootNode"); |
| return; |
| } |
| if(path[0] !== this.rootNode.item && (dojo.isString(path[0]) && path[0] != this.model.getIdentity(this.rootNode.item))){ |
| console.error(this, ":path[0] doesn't match this.rootNode.item. Maybe you are using the wrong tree."); |
| return; |
| } |
| path.shift(); |
| |
| var node = this.rootNode; |
| |
| function advance(){ |
| // summary: |
| // Called when "node" has completed loading and expanding. Pop the next item from the path |
| // (which must be a child of "node") and advance to it, and then recurse. |
| |
| // Set item and identity to next item in path (node is pointing to the item that was popped |
| // from the path _last_ time. |
| var item = path.shift(), |
| identity = dojo.isString(item) ? item : this.model.getIdentity(item); |
| |
| // Change "node" from previous item in path to the item we just popped from path |
| dojo.some(this._itemNodesMap[identity], function(n){ |
| if(n.getParent() == node){ |
| node = n; |
| return true; |
| } |
| return false; |
| }); |
| |
| if(path.length){ |
| // Need to do more expanding |
| this._expandNode(node).addCallback(dojo.hitch(this, advance)); |
| }else{ |
| // Final destination node, select it |
| if(this.lastFocused != node){ |
| this.focusNode(node); |
| } |
| } |
| } |
| |
| this._expandNode(node).addCallback(dojo.hitch(this, advance)); |
| })); |
| }, |
| |
| _getPathAttr: function(){ |
| // summary: |
| // Return an array of items that is the path to selected tree node. |
| if(!this.lastFocused){ return; } |
| var res = []; |
| var treeNode = this.lastFocused; |
| while(treeNode && treeNode !== this.rootNode){ |
| res.unshift(treeNode.item); |
| treeNode = treeNode.getParent(); |
| } |
| res.unshift(this.rootNode.item); |
| return res; |
| }, |
| |
| ////////////// Data store related functions ////////////////////// |
| // These just get passed to the model; they are here for back-compat |
| |
| mayHaveChildren: function(/*dojo.data.Item*/ item){ |
| // summary: |
| // Deprecated. This should be specified on the model itself. |
| // |
| // Overridable function to tell if an item has or may have children. |
| // Controls whether or not +/- expando icon is shown. |
| // (For efficiency reasons we may not want to check if an element actually |
| // has children until user clicks the expando node) |
| // tags: |
| // deprecated |
| }, |
| |
| getItemChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete){ |
| // summary: |
| // Deprecated. This should be specified on the model itself. |
| // |
| // Overridable function that return array of child items of given parent item, |
| // or if parentItem==null then return top items in tree |
| // tags: |
| // deprecated |
| }, |
| |
| /////////////////////////////////////////////////////// |
| // Functions for converting an item to a TreeNode |
| getLabel: function(/*dojo.data.Item*/ item){ |
| // summary: |
| // Overridable function to get the label for a tree node (given the item) |
| // tags: |
| // extension |
| return this.model.getLabel(item); // String |
| }, |
| |
| getIconClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ |
| // summary: |
| // Overridable function to return CSS class name to display icon |
| // tags: |
| // extension |
| return (!item || this.model.mayHaveChildren(item)) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf" |
| }, |
| |
| getLabelClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ |
| // summary: |
| // Overridable function to return CSS class name to display label |
| // tags: |
| // extension |
| }, |
| |
| getRowClass: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ |
| // summary: |
| // Overridable function to return CSS class name to display row |
| // tags: |
| // extension |
| }, |
| |
| getIconStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ |
| // summary: |
| // Overridable function to return CSS styles to display icon |
| // returns: |
| // Object suitable for input to dojo.style() like {backgroundImage: "url(...)"} |
| // tags: |
| // extension |
| }, |
| |
| getLabelStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ |
| // summary: |
| // Overridable function to return CSS styles to display label |
| // returns: |
| // Object suitable for input to dojo.style() like {color: "red", background: "green"} |
| // tags: |
| // extension |
| }, |
| |
| getRowStyle: function(/*dojo.data.Item*/ item, /*Boolean*/ opened){ |
| // summary: |
| // Overridable function to return CSS styles to display row |
| // returns: |
| // Object suitable for input to dojo.style() like {background-color: "#bbb"} |
| // tags: |
| // extension |
| }, |
| |
| getTooltip: function(/*dojo.data.Item*/ item){ |
| // summary: |
| // Overridable function to get the tooltip for a tree node (given the item) |
| // tags: |
| // extension |
| return ""; // String |
| }, |
| |
| /////////// Keyboard and Mouse handlers //////////////////// |
| |
| _onKeyPress: function(/*Event*/ e){ |
| // summary: |
| // Translates keypress events into commands for the controller |
| if(e.altKey){ return; } |
| var dk = dojo.keys; |
| var treeNode = dijit.getEnclosingWidget(e.target); |
| if(!treeNode){ return; } |
| |
| var key = e.charOrCode; |
| if(typeof key == "string"){ // handle printables (letter navigation) |
| // Check for key navigation. |
| if(!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey){ |
| this._onLetterKeyNav( { node: treeNode, key: key.toLowerCase() } ); |
| dojo.stopEvent(e); |
| } |
| }else{ // handle non-printables (arrow keys) |
| // clear record of recent printables (being saved for multi-char letter navigation), |
| // because "a", down-arrow, "b" shouldn't search for "ab" |
| if(this._curSearch){ |
| clearTimeout(this._curSearch.timer); |
| delete this._curSearch; |
| } |
| |
| var map = this._keyHandlerMap; |
| if(!map){ |
| // setup table mapping keys to events |
| map = {}; |
| map[dk.ENTER]="_onEnterKey"; |
| map[this.isLeftToRight() ? dk.LEFT_ARROW : dk.RIGHT_ARROW]="_onLeftArrow"; |
| map[this.isLeftToRight() ? dk.RIGHT_ARROW : dk.LEFT_ARROW]="_onRightArrow"; |
| map[dk.UP_ARROW]="_onUpArrow"; |
| map[dk.DOWN_ARROW]="_onDownArrow"; |
| map[dk.HOME]="_onHomeKey"; |
| map[dk.END]="_onEndKey"; |
| this._keyHandlerMap = map; |
| } |
| if(this._keyHandlerMap[key]){ |
| this[this._keyHandlerMap[key]]( { node: treeNode, item: treeNode.item, evt: e } ); |
| dojo.stopEvent(e); |
| } |
| } |
| }, |
| |
| _onEnterKey: function(/*Object*/ message, /*Event*/ evt){ |
| this._publish("execute", { item: message.item, node: message.node } ); |
| this.onClick(message.item, message.node, evt); |
| }, |
| |
| _onDownArrow: function(/*Object*/ message){ |
| // summary: |
| // down arrow pressed; get next visible node, set focus there |
| var node = this._getNextNode(message.node); |
| if(node && node.isTreeNode){ |
| this.focusNode(node); |
| } |
| }, |
| |
| _onUpArrow: function(/*Object*/ message){ |
| // summary: |
| // Up arrow pressed; move to previous visible node |
| |
| var node = message.node; |
| |
| // if younger siblings |
| var previousSibling = node.getPreviousSibling(); |
| if(previousSibling){ |
| node = previousSibling; |
| // if the previous node is expanded, dive in deep |
| while(node.isExpandable && node.isExpanded && node.hasChildren()){ |
| // move to the last child |
| var children = node.getChildren(); |
| node = children[children.length-1]; |
| } |
| }else{ |
| // if this is the first child, return the parent |
| // unless the parent is the root of a tree with a hidden root |
| var parent = node.getParent(); |
| if(!(!this.showRoot && parent === this.rootNode)){ |
| node = parent; |
| } |
| } |
| |
| if(node && node.isTreeNode){ |
| this.focusNode(node); |
| } |
| }, |
| |
| _onRightArrow: function(/*Object*/ message){ |
| // summary: |
| // Right arrow pressed; go to child node |
| var node = message.node; |
| |
| // if not expanded, expand, else move to 1st child |
| if(node.isExpandable && !node.isExpanded){ |
| this._expandNode(node); |
| }else if(node.hasChildren()){ |
| node = node.getChildren()[0]; |
| if(node && node.isTreeNode){ |
| this.focusNode(node); |
| } |
| } |
| }, |
| |
| _onLeftArrow: function(/*Object*/ message){ |
| // summary: |
| // Left arrow pressed. |
| // If not collapsed, collapse, else move to parent. |
| |
| var node = message.node; |
| |
| if(node.isExpandable && node.isExpanded){ |
| this._collapseNode(node); |
| }else{ |
| var parent = node.getParent(); |
| if(parent && parent.isTreeNode && !(!this.showRoot && parent === this.rootNode)){ |
| this.focusNode(parent); |
| } |
| } |
| }, |
| |
| _onHomeKey: function(){ |
| // summary: |
| // Home key pressed; get first visible node, and set focus there |
| var node = this._getRootOrFirstNode(); |
| if(node){ |
| this.focusNode(node); |
| } |
| }, |
| |
| _onEndKey: function(/*Object*/ message){ |
| // summary: |
| // End key pressed; go to last visible node. |
| |
| var node = this.rootNode; |
| while(node.isExpanded){ |
| var c = node.getChildren(); |
| node = c[c.length - 1]; |
| } |
| |
| if(node && node.isTreeNode){ |
| this.focusNode(node); |
| } |
| }, |
| |
| // multiCharSearchDuration: Number |
| // If multiple characters are typed where each keystroke happens within |
| // multiCharSearchDuration of the previous keystroke, |
| // search for nodes matching all the keystrokes. |
| // |
| // For example, typing "ab" will search for entries starting with |
| // "ab" unless the delay between "a" and "b" is greater than multiCharSearchDuration. |
| multiCharSearchDuration: 250, |
| |
| _onLetterKeyNav: function(message){ |
| // summary: |
| // Called when user presses a prinatable key; search for node starting with recently typed letters. |
| // message: Object |
| // Like { node: TreeNode, key: 'a' } where key is the key the user pressed. |
| |
| // Branch depending on whether this key starts a new search, or modifies an existing search |
| var cs = this._curSearch; |
| if(cs){ |
| // We are continuing a search. Ex: user has pressed 'a', and now has pressed |
| // 'b', so we want to search for nodes starting w/"ab". |
| cs.pattern = cs.pattern + message.key; |
| clearTimeout(cs.timer); |
| }else{ |
| // We are starting a new search |
| cs = this._curSearch = { |
| pattern: message.key, |
| startNode: message.node |
| }; |
| } |
| |
| // set/reset timer to forget recent keystrokes |
| var self = this; |
| cs.timer = setTimeout(function(){ |
| delete self._curSearch; |
| }, this.multiCharSearchDuration); |
| |
| // Navigate to TreeNode matching keystrokes [entered so far]. |
| var node = cs.startNode; |
| do{ |
| node = this._getNextNode(node); |
| //check for last node, jump to first node if necessary |
| if(!node){ |
| node = this._getRootOrFirstNode(); |
| } |
| }while(node !== cs.startNode && (node.label.toLowerCase().substr(0, cs.pattern.length) != cs.pattern)); |
| if(node && node.isTreeNode){ |
| // no need to set focus if back where we started |
| if(node !== cs.startNode){ |
| this.focusNode(node); |
| } |
| } |
| }, |
| |
| _onClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){ |
| // summary: |
| // Translates click events into commands for the controller to process |
| |
| var domElement = e.target; |
| |
| if( (this.openOnClick && nodeWidget.isExpandable) || |
| (domElement == nodeWidget.expandoNode || domElement == nodeWidget.expandoNodeText) ){ |
| // expando node was clicked, or label of a folder node was clicked; open it |
| if(nodeWidget.isExpandable){ |
| this._onExpandoClick({node:nodeWidget}); |
| } |
| }else{ |
| this._publish("execute", { item: nodeWidget.item, node: nodeWidget, evt: e } ); |
| this.onClick(nodeWidget.item, nodeWidget, e); |
| this.focusNode(nodeWidget); |
| } |
| dojo.stopEvent(e); |
| }, |
| _onDblClick: function(/*TreeNode*/ nodeWidget, /*Event*/ e){ |
| // summary: |
| // Translates double-click events into commands for the controller to process |
| |
| var domElement = e.target; |
| |
| if( (this.openOnDblClick && nodeWidget.isExpandable) || |
| (domElement == nodeWidget.expandoNode || domElement == nodeWidget.expandoNodeText) ){ |
| // expando node was clicked, or label of a folder node was clicked; open it |
| if(nodeWidget.isExpandable){ |
| this._onExpandoClick({node:nodeWidget}); |
| } |
| }else{ |
| this._publish("execute", { item: nodeWidget.item, node: nodeWidget, evt: e } ); |
| this.onDblClick(nodeWidget.item, nodeWidget, e); |
| this.focusNode(nodeWidget); |
| } |
| dojo.stopEvent(e); |
| }, |
| |
| _onExpandoClick: function(/*Object*/ message){ |
| // summary: |
| // User clicked the +/- icon; expand or collapse my children. |
| var node = message.node; |
| |
| // If we are collapsing, we might be hiding the currently focused node. |
| // Also, clicking the expando node might have erased focus from the current node. |
| // For simplicity's sake just focus on the node with the expando. |
| this.focusNode(node); |
| |
| if(node.isExpanded){ |
| this._collapseNode(node); |
| }else{ |
| this._expandNode(node); |
| } |
| }, |
| |
| onClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){ |
| // summary: |
| // Callback when a tree node is clicked |
| // tags: |
| // callback |
| }, |
| onDblClick: function(/* dojo.data */ item, /*TreeNode*/ node, /*Event*/ evt){ |
| // summary: |
| // Callback when a tree node is double-clicked |
| // tags: |
| // callback |
| }, |
| onOpen: function(/* dojo.data */ item, /*TreeNode*/ node){ |
| // summary: |
| // Callback when a node is opened |
| // tags: |
| // callback |
| }, |
| onClose: function(/* dojo.data */ item, /*TreeNode*/ node){ |
| // summary: |
| // Callback when a node is closed |
| // tags: |
| // callback |
| }, |
| |
| _getNextNode: function(node){ |
| // summary: |
| // Get next visible node |
| |
| if(node.isExpandable && node.isExpanded && node.hasChildren()){ |
| // if this is an expanded node, get the first child |
| return node.getChildren()[0]; // _TreeNode |
| }else{ |
| // find a parent node with a sibling |
| while(node && node.isTreeNode){ |
| var returnNode = node.getNextSibling(); |
| if(returnNode){ |
| return returnNode; // _TreeNode |
| } |
| node = node.getParent(); |
| } |
| return null; |
| } |
| }, |
| |
| _getRootOrFirstNode: function(){ |
| // summary: |
| // Get first visible node |
| return this.showRoot ? this.rootNode : this.rootNode.getChildren()[0]; |
| }, |
| |
| _collapseNode: function(/*_TreeNode*/ node){ |
| // summary: |
| // Called when the user has requested to collapse the node |
| |
| if(node._expandNodeDeferred){ |
| delete node._expandNodeDeferred; |
| } |
| |
| if(node.isExpandable){ |
| if(node.state == "LOADING"){ |
| // ignore clicks while we are in the process of loading data |
| return; |
| } |
| |
| node.collapse(); |
| this.onClose(node.item, node); |
| |
| if(node.item){ |
| this._state(node.item,false); |
| this._saveState(); |
| } |
| } |
| }, |
| |
| _expandNode: function(/*_TreeNode*/ node, /*Boolean?*/ recursive){ |
| // summary: |
| // Called when the user has requested to expand the node |
| // recursive: |
| // Internal flag used when _expandNode() calls itself, don't set. |
| // returns: |
| // Deferred that fires when the node is loaded and opened and (if persist=true) all it's descendants |
| // that were previously opened too |
| |
| if(node._expandNodeDeferred && !recursive){ |
| // there's already an expand in progress (or completed), so just return |
| return node._expandNodeDeferred; // dojo.Deferred |
| } |
| |
| var model = this.model, |
| item = node.item, |
| _this = this; |
| |
| switch(node.state){ |
| case "UNCHECKED": |
| // need to load all the children, and then expand |
| node.markProcessing(); |
| |
| // Setup deferred to signal when the load and expand are finished. |
| // Save that deferred in this._expandDeferred as a flag that operation is in progress. |
| var def = (node._expandNodeDeferred = new dojo.Deferred()); |
| |
| // Get the children |
| model.getChildren( |
| item, |
| function(items){ |
| node.unmarkProcessing(); |
| |
| // Display the children and also start expanding any children that were previously expanded |
| // (if this.persist == true). The returned Deferred will fire when those expansions finish. |
| var scid = node.setChildItems(items); |
| |
| // Call _expandNode() again but this time it will just to do the animation (default branch). |
| // The returned Deferred will fire when the animation completes. |
| // TODO: seems like I can avoid recursion and just use a deferred to sequence the events? |
| var ed = _this._expandNode(node, true); |
| |
| // After the above two tasks (setChildItems() and recursive _expandNode()) finish, |
| // signal that I am done. |
| scid.addCallback(function(){ |
| ed.addCallback(function(){ |
| def.callback(); |
| }) |
| }); |
| }, |
| function(err){ |
| console.error(_this, ": error loading root children: ", err); |
| } |
| ); |
| break; |
| |
| default: // "LOADED" |
| // data is already loaded; just expand node |
| def = (node._expandNodeDeferred = node.expand()); |
| |
| this.onOpen(node.item, node); |
| |
| if(item){ |
| this._state(item, true); |
| this._saveState(); |
| } |
| } |
| |
| return def; // dojo.Deferred |
| }, |
| |
| ////////////////// Miscellaneous functions //////////////// |
| |
| focusNode: function(/* _tree.Node */ node){ |
| // summary: |
| // Focus on the specified node (which must be visible) |
| // tags: |
| // protected |
| |
| // set focus so that the label will be voiced using screen readers |
| dijit.focus(node.labelNode); |
| }, |
| |
| _onNodeFocus: function(/*dijit._Widget*/ node){ |
| // summary: |
| // Called when a TreeNode gets focus, either by user clicking |
| // it, or programatically by arrow key handling code. |
| // description: |
| // It marks that the current node is the selected one, and the previously |
| // selected node no longer is. |
| |
| if(node){ |
| if(node != this.lastFocused && this.lastFocused && !this.lastFocused._destroyed){ |
| // mark that the previously selected node is no longer the selected one |
| this.lastFocused.setSelected(false); |
| } |
| |
| // mark that the new node is the currently selected one |
| node.setSelected(true); |
| this.lastFocused = node; |
| } |
| }, |
| |
| _onNodeMouseEnter: function(/*dijit._Widget*/ node){ |
| // summary: |
| // Called when mouse is over a node (onmouseenter event) |
| }, |
| |
| _onNodeMouseLeave: function(/*dijit._Widget*/ node){ |
| // summary: |
| // Called when mouse is over a node (onmouseenter event) |
| }, |
| |
| //////////////// Events from the model ////////////////////////// |
| |
| _onItemChange: function(/*Item*/ item){ |
| // summary: |
| // Processes notification of a change to an item's scalar values like label |
| var model = this.model, |
| identity = model.getIdentity(item), |
| nodes = this._itemNodesMap[identity]; |
| |
| if(nodes){ |
| var self = this; |
| dojo.forEach(nodes,function(node){ |
| node.attr({ |
| label: self.getLabel(item), |
| tooltip: self.getTooltip(item) |
| }); |
| node._updateItemClasses(item); |
| }); |
| } |
| }, |
| |
| _onItemChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){ |
| // summary: |
| // Processes notification of a change to an item's children |
| var model = this.model, |
| identity = model.getIdentity(parent), |
| parentNodes = this._itemNodesMap[identity]; |
| |
| if(parentNodes){ |
| dojo.forEach(parentNodes,function(parentNode){ |
| parentNode.setChildItems(newChildrenList); |
| }); |
| } |
| }, |
| |
| _onItemDelete: function(/*Item*/ item){ |
| // summary: |
| // Processes notification of a deletion of an item |
| var model = this.model, |
| identity = model.getIdentity(item), |
| nodes = this._itemNodesMap[identity]; |
| |
| if(nodes){ |
| dojo.forEach(nodes,function(node){ |
| var parent = node.getParent(); |
| if(parent){ |
| // if node has not already been orphaned from a _onSetItem(parent, "children", ..) call... |
| parent.removeChild(node); |
| } |
| node.destroyRecursive(); |
| }); |
| delete this._itemNodesMap[identity]; |
| } |
| }, |
| |
| /////////////// Miscellaneous funcs |
| |
| _initState: function(){ |
| // summary: |
| // Load in which nodes should be opened automatically |
| if(this.persist){ |
| var cookie = dojo.cookie(this.cookieName); |
| this._openedItemIds = {}; |
| if(cookie){ |
| dojo.forEach(cookie.split(','), function(item){ |
| this._openedItemIds[item] = true; |
| }, this); |
| } |
| } |
| }, |
| _state: function(item,expanded){ |
| // summary: |
| // Query or set expanded state for an item, |
| if(!this.persist){ |
| return false; |
| } |
| var id=this.model.getIdentity(item); |
| if(arguments.length === 1){ |
| return this._openedItemIds[id]; |
| } |
| if(expanded){ |
| this._openedItemIds[id] = true; |
| }else{ |
| delete this._openedItemIds[id]; |
| } |
| }, |
| _saveState: function(){ |
| // summary: |
| // Create and save a cookie with the currently expanded nodes identifiers |
| if(!this.persist){ |
| return; |
| } |
| var ary = []; |
| for(var id in this._openedItemIds){ |
| ary.push(id); |
| } |
| dojo.cookie(this.cookieName, ary.join(","), {expires:365}); |
| }, |
| |
| destroy: function(){ |
| if(this._curSearch){ |
| clearTimeout(this._curSearch.timer); |
| delete this._curSearch; |
| } |
| if(this.rootNode){ |
| this.rootNode.destroyRecursive(); |
| } |
| if(this.dndController && !dojo.isString(this.dndController)){ |
| this.dndController.destroy(); |
| } |
| this.rootNode = null; |
| this.inherited(arguments); |
| }, |
| |
| destroyRecursive: function(){ |
| // A tree is treated as a leaf, not as a node with children (like a grid), |
| // but defining destroyRecursive for back-compat. |
| this.destroy(); |
| }, |
| |
| resize: function(changeSize){ |
| if(changeSize){ |
| dojo.marginBox(this.domNode, changeSize); |
| dojo.style(this.domNode, "overflow", "auto"); // for scrollbars |
| } |
| |
| // The only JS sizing involved w/tree is the indentation, which is specified |
| // in CSS and read in through this dummy indentDetector node (tree must be |
| // visible and attached to the DOM to read this) |
| this._nodePixelIndent = dojo.marginBox(this.tree.indentDetector).w; |
| |
| if(this.tree.rootNode){ |
| // If tree has already loaded, then reset indent for all the nodes |
| this.tree.rootNode.attr('indent', this.showRoot ? 0 : -1); |
| } |
| }, |
| |
| _createTreeNode: function(/*Object*/ args){ |
| // summary: |
| // creates a TreeNode |
| // description: |
| // Developers can override this method to define their own TreeNode class; |
| // However it will probably be removed in a future release in favor of a way |
| // of just specifying a widget for the label, rather than one that contains |
| // the children too. |
| return new dijit._TreeNode(args); |
| } |
| }); |
| |
| // For back-compat. TODO: remove in 2.0 |
| |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.TextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.TextBox"] = true; |
| dojo.provide("dijit.form.TextBox"); |
| |
| |
| |
| dojo.declare( |
| "dijit.form.TextBox", |
| dijit.form._FormValueWidget, |
| { |
| // summary: |
| // A base class for textbox form inputs |
| |
| // trim: Boolean |
| // Removes leading and trailing whitespace if true. Default is false. |
| trim: false, |
| |
| // uppercase: Boolean |
| // Converts all characters to uppercase if true. Default is false. |
| uppercase: false, |
| |
| // lowercase: Boolean |
| // Converts all characters to lowercase if true. Default is false. |
| lowercase: false, |
| |
| // propercase: Boolean |
| // Converts the first character of each word to uppercase if true. |
| propercase: false, |
| |
| // maxLength: String |
| // HTML INPUT tag maxLength declaration. |
| maxLength: "", |
| |
| // selectOnClick: [const] Boolean |
| // If true, all text will be selected when focused with mouse |
| selectOnClick: false, |
| |
| templateString: dojo.cache("dijit.form", "templates/TextBox.html", "<input class=\"dijit dijitReset dijitLeft\" dojoAttachPoint='textbox,focusNode'\n\tdojoAttachEvent='onmouseenter:_onMouse,onmouseleave:_onMouse'\n\tautocomplete=\"off\" type=\"${type}\" ${nameAttrSetting}\n\t/>\n"), |
| baseClass: "dijitTextBox", |
| |
| attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, { |
| maxLength: "focusNode" |
| }), |
| |
| _getValueAttr: function(){ |
| // summary: |
| // Hook so attr('value') works as we like. |
| // description: |
| // For `dijit.form.TextBox` this basically returns the value of the <input>. |
| // |
| // For `dijit.form.MappedTextBox` subclasses, which have both |
| // a "displayed value" and a separate "submit value", |
| // This treats the "displayed value" as the master value, computing the |
| // submit value from it via this.parse(). |
| return this.parse(this.attr('displayedValue'), this.constraints); |
| }, |
| |
| _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){ |
| // summary: |
| // Hook so attr('value', ...) works. |
| // |
| // description: |
| // Sets the value of the widget to "value" which can be of |
| // any type as determined by the widget. |
| // |
| // value: |
| // The visual element value is also set to a corresponding, |
| // but not necessarily the same, value. |
| // |
| // formattedValue: |
| // If specified, used to set the visual element value, |
| // otherwise a computed visual value is used. |
| // |
| // priorityChange: |
| // If true, an onChange event is fired immediately instead of |
| // waiting for the next blur event. |
| |
| var filteredValue; |
| if(value !== undefined){ |
| // TODO: this is calling filter() on both the display value and the actual value. |
| // I added a comment to the filter() definition about this, but it should be changed. |
| filteredValue = this.filter(value); |
| if(typeof formattedValue != "string"){ |
| if(filteredValue !== null && ((typeof filteredValue != "number") || !isNaN(filteredValue))){ |
| formattedValue = this.filter(this.format(filteredValue, this.constraints)); |
| }else{ formattedValue = ''; } |
| } |
| } |
| if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){ |
| this.textbox.value = formattedValue; |
| } |
| this.inherited(arguments, [filteredValue, priorityChange]); |
| }, |
| |
| // displayedValue: String |
| // For subclasses like ComboBox where the displayed value |
| // (ex: Kentucky) and the serialized value (ex: KY) are different, |
| // this represents the displayed value. |
| // |
| // Setting 'displayedValue' through attr('displayedValue', ...) |
| // updates 'value', and vice-versa. Othewise 'value' is updated |
| // from 'displayedValue' periodically, like onBlur etc. |
| // |
| // TODO: move declaration to MappedTextBox? |
| // Problem is that ComboBox references displayedValue, |
| // for benefit of FilteringSelect. |
| displayedValue: "", |
| |
| getDisplayedValue: function(){ |
| // summary: |
| // Deprecated. Use attr('displayedValue') instead. |
| // tags: |
| // deprecated |
| dojo.deprecated(this.declaredClass+"::getDisplayedValue() is deprecated. Use attr('displayedValue') instead.", "", "2.0"); |
| return this.attr('displayedValue'); |
| }, |
| |
| _getDisplayedValueAttr: function(){ |
| // summary: |
| // Hook so attr('displayedValue') works. |
| // description: |
| // Returns the displayed value (what the user sees on the screen), |
| // after filtering (ie, trimming spaces etc.). |
| // |
| // For some subclasses of TextBox (like ComboBox), the displayed value |
| // is different from the serialized value that's actually |
| // sent to the server (see dijit.form.ValidationTextBox.serialize) |
| |
| return this.filter(this.textbox.value); |
| }, |
| |
| setDisplayedValue: function(/*String*/value){ |
| // summary: |
| // Deprecated. Use attr('displayedValue', ...) instead. |
| // tags: |
| // deprecated |
| dojo.deprecated(this.declaredClass+"::setDisplayedValue() is deprecated. Use attr('displayedValue', ...) instead.", "", "2.0"); |
| this.attr('displayedValue', value); |
| }, |
| |
| _setDisplayedValueAttr: function(/*String*/value){ |
| // summary: |
| // Hook so attr('displayedValue', ...) works. |
| // description: |
| // Sets the value of the visual element to the string "value". |
| // The widget value is also set to a corresponding, |
| // but not necessarily the same, value. |
| |
| if(value === null || value === undefined){ value = '' } |
| else if(typeof value != "string"){ value = String(value) } |
| this.textbox.value = value; |
| this._setValueAttr(this.attr('value'), undefined, value); |
| }, |
| |
| format: function(/* String */ value, /* Object */ constraints){ |
| // summary: |
| // Replacable function to convert a value to a properly formatted string. |
| // tags: |
| // protected extension |
| return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value)); |
| }, |
| |
| parse: function(/* String */ value, /* Object */ constraints){ |
| // summary: |
| // Replacable function to convert a formatted string to a value |
| // tags: |
| // protected extension |
| |
| return value; // String |
| }, |
| |
| _refreshState: function(){ |
| // summary: |
| // After the user types some characters, etc., this method is |
| // called to check the field for validity etc. The base method |
| // in `dijit.form.TextBox` does nothing, but subclasses override. |
| // tags: |
| // protected |
| }, |
| |
| _onInput: function(e){ |
| if(e && e.type && /key/i.test(e.type) && e.keyCode){ |
| switch(e.keyCode){ |
| case dojo.keys.SHIFT: |
| case dojo.keys.ALT: |
| case dojo.keys.CTRL: |
| case dojo.keys.TAB: |
| return; |
| } |
| } |
| if(this.intermediateChanges){ |
| var _this = this; |
| // the setTimeout allows the key to post to the widget input box |
| setTimeout(function(){ _this._handleOnChange(_this.attr('value'), false); }, 0); |
| } |
| this._refreshState(); |
| }, |
| |
| postCreate: function(){ |
| // setting the value here is needed since value="" in the template causes "undefined" |
| // and setting in the DOM (instead of the JS object) helps with form reset actions |
| this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values shuld be the same |
| this.inherited(arguments); |
| if(dojo.isMoz || dojo.isOpera){ |
| this.connect(this.textbox, "oninput", this._onInput); |
| }else{ |
| this.connect(this.textbox, "onkeydown", this._onInput); |
| this.connect(this.textbox, "onkeyup", this._onInput); |
| this.connect(this.textbox, "onpaste", this._onInput); |
| this.connect(this.textbox, "oncut", this._onInput); |
| } |
| }, |
| |
| _blankValue: '', // if the textbox is blank, what value should be reported |
| filter: function(val){ |
| // summary: |
| // Auto-corrections (such as trimming) that are applied to textbox |
| // value on blur or form submit. |
| // description: |
| // For MappedTextBox subclasses, this is called twice |
| // - once with the display value |
| // - once the value as set/returned by attr('value', ...) |
| // and attr('value'), ex: a Number for NumberTextBox. |
| // |
| // In the latter case it does corrections like converting null to NaN. In |
| // the former case the NumberTextBox.filter() method calls this.inherited() |
| // to execute standard trimming code in TextBox.filter(). |
| // |
| // TODO: break this into two methods in 2.0 |
| // |
| // tags: |
| // protected extension |
| if(val === null){ return this._blankValue; } |
| if(typeof val != "string"){ return val; } |
| if(this.trim){ |
| val = dojo.trim(val); |
| } |
| if(this.uppercase){ |
| val = val.toUpperCase(); |
| } |
| if(this.lowercase){ |
| val = val.toLowerCase(); |
| } |
| if(this.propercase){ |
| val = val.replace(/[^\s]+/g, function(word){ |
| return word.substring(0,1).toUpperCase() + word.substring(1); |
| }); |
| } |
| return val; |
| }, |
| |
| _setBlurValue: function(){ |
| this._setValueAttr(this.attr('value'), true); |
| }, |
| |
| _onBlur: function(e){ |
| if(this.disabled){ return; } |
| this._setBlurValue(); |
| this.inherited(arguments); |
| |
| if(this._selectOnClickHandle){ |
| this.disconnect(this._selectOnClickHandle); |
| } |
| if(this.selectOnClick && dojo.isMoz){ |
| this.textbox.selectionStart = this.textbox.selectionEnd = undefined; // clear selection so that the next mouse click doesn't reselect |
| } |
| }, |
| |
| _onFocus: function(/*String*/ by){ |
| if(this.disabled || this.readOnly){ return; } |
| |
| // Select all text on focus via click if nothing already selected. |
| // Since mouse-up will clear the selection need to defer selection until after mouse-up. |
| // Don't do anything on focus by tabbing into the widgetm since there's no associated mouse-up event. |
| if(this.selectOnClick && by == "mouse"){ |
| this._selectOnClickHandle = this.connect(this.domNode, "onmouseup", function(){ |
| // Only select all text on first click; otherwise users would have no way to clear |
| // the selection. |
| this.disconnect(this._selectOnClickHandle); |
| |
| // Check if the user selected some text manually (mouse-down, mouse-move, mouse-up) |
| // and if not, then select all the text |
| var textIsNotSelected; |
| if(dojo.isIE){ |
| var range = dojo.doc.selection.createRange(); |
| var parent = range.parentElement(); |
| textIsNotSelected = parent == this.textbox && range.text.length == 0; |
| }else{ |
| textIsNotSelected = this.textbox.selectionStart == this.textbox.selectionEnd; |
| } |
| if(textIsNotSelected){ |
| dijit.selectInputText(this.textbox); |
| } |
| }); |
| } |
| |
| this._refreshState(); |
| this.inherited(arguments); |
| }, |
| |
| reset: function(){ |
| // Overrides dijit._FormWidget.reset(). |
| // Additionally resets the displayed textbox value to '' |
| this.textbox.value = ''; |
| this.inherited(arguments); |
| } |
| } |
| ); |
| |
| dijit.selectInputText = function(/*DomNode*/element, /*Number?*/ start, /*Number?*/ stop){ |
| // summary: |
| // Select text in the input element argument, from start (default 0), to stop (default end). |
| |
| // TODO: use functions in _editor/selection.js? |
| var _window = dojo.global; |
| var _document = dojo.doc; |
| element = dojo.byId(element); |
| if(isNaN(start)){ start = 0; } |
| if(isNaN(stop)){ stop = element.value ? element.value.length : 0; } |
| dijit.focus(element); |
| if(_document["selection"] && dojo.body()["createTextRange"]){ // IE |
| if(element.createTextRange){ |
| var range = element.createTextRange(); |
| with(range){ |
| collapse(true); |
| moveStart("character", -99999); // move to 0 |
| moveStart("character", start); // delta from 0 is the correct position |
| moveEnd("character", stop-start); |
| select(); |
| } |
| } |
| }else if(_window["getSelection"]){ |
| if(element.setSelectionRange){ |
| element.setSelectionRange(start, stop); |
| } |
| } |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dijit.InlineEditBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.InlineEditBox"] = true; |
| dojo.provide("dijit.InlineEditBox"); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| dojo.declare("dijit.InlineEditBox", |
| dijit._Widget, |
| { |
| // summary: |
| // An element with in-line edit capabilitites |
| // |
| // description: |
| // Behavior for an existing node (`<p>`, `<div>`, `<span>`, etc.) so that |
| // when you click it, an editor shows up in place of the original |
| // text. Optionally, Save and Cancel button are displayed below the edit widget. |
| // When Save is clicked, the text is pulled from the edit |
| // widget and redisplayed and the edit widget is again hidden. |
| // By default a plain Textarea widget is used as the editor (or for |
| // inline values a TextBox), but you can specify an editor such as |
| // dijit.Editor (for editing HTML) or a Slider (for adjusting a number). |
| // An edit widget must support the following API to be used: |
| // - displayedValue or value as initialization parameter, |
| // and available through attr('displayedValue') / attr('value') |
| // - void focus() |
| // - DOM-node focusNode = node containing editable text |
| |
| // editing: [readonly] Boolean |
| // Is the node currently in edit mode? |
| editing: false, |
| |
| // autoSave: Boolean |
| // Changing the value automatically saves it; don't have to push save button |
| // (and save button isn't even displayed) |
| autoSave: true, |
| |
| // buttonSave: String |
| // Save button label |
| buttonSave: "", |
| |
| // buttonCancel: String |
| // Cancel button label |
| buttonCancel: "", |
| |
| // renderAsHtml: Boolean |
| // Set this to true if the specified Editor's value should be interpreted as HTML |
| // rather than plain text (ex: `dijit.Editor`) |
| renderAsHtml: false, |
| |
| // editor: String |
| // Class name for Editor widget |
| editor: "dijit.form.TextBox", |
| |
| // editorWrapper: String |
| // Class name for widget that wraps the editor widget, displaying save/cancel |
| // buttons. |
| editorWrapper: "dijit._InlineEditor", |
| |
| // editorParams: Object |
| // Set of parameters for editor, like {required: true} |
| editorParams: {}, |
| |
| onChange: function(value){ |
| // summary: |
| // Set this handler to be notified of changes to value. |
| // tags: |
| // callback |
| }, |
| |
| onCancel: function(){ |
| // summary: |
| // Set this handler to be notified when editing is cancelled. |
| // tags: |
| // callback |
| }, |
| |
| // width: String |
| // Width of editor. By default it's width=100% (ie, block mode). |
| width: "100%", |
| |
| // value: String |
| // The display value of the widget in read-only mode |
| value: "", |
| |
| // noValueIndicator: [const] String |
| // The text that gets displayed when there is no value (so that the user has a place to click to edit) |
| noValueIndicator: "<span style='font-family: wingdings; text-decoration: underline;'> ✍ </span>", |
| |
| constructor: function(){ |
| // summary: |
| // Sets up private arrays etc. |
| // tags: |
| // private |
| this.editorParams = {}; |
| }, |
| |
| postMixInProperties: function(){ |
| this.inherited(arguments); |
| |
| // save pointer to original source node, since Widget nulls-out srcNodeRef |
| this.displayNode = this.srcNodeRef; |
| |
| // connect handlers to the display node |
| var events = { |
| ondijitclick: "_onClick", |
| onmouseover: "_onMouseOver", |
| onmouseout: "_onMouseOut", |
| onfocus: "_onMouseOver", |
| onblur: "_onMouseOut" |
| }; |
| for(var name in events){ |
| this.connect(this.displayNode, name, events[name]); |
| } |
| dijit.setWaiRole(this.displayNode, "button"); |
| if(!this.displayNode.getAttribute("tabIndex")){ |
| this.displayNode.setAttribute("tabIndex", 0); |
| } |
| |
| this.attr('value', this.value || this.displayNode.innerHTML); |
| }, |
| |
| setDisabled: function(/*Boolean*/ disabled){ |
| // summary: |
| // Deprecated. Use attr('disable', ...) instead. |
| // tags: |
| // deprecated |
| dojo.deprecated("dijit.InlineEditBox.setDisabled() is deprecated. Use attr('disabled', bool) instead.", "", "2.0"); |
| this.attr('disabled', disabled); |
| }, |
| |
| _setDisabledAttr: function(/*Boolean*/ disabled){ |
| // summary: |
| // Hook to make attr("disabled", ...) work. |
| // Set disabled state of widget. |
| this.disabled = disabled; |
| dijit.setWaiState(this.domNode, "disabled", disabled); |
| if(disabled){ |
| this.displayNode.removeAttribute("tabIndex"); |
| }else{ |
| this.displayNode.setAttribute("tabIndex", 0); |
| } |
| }, |
| |
| _onMouseOver: function(){ |
| // summary: |
| // Handler for onmouseover event. |
| // tags: |
| // private |
| dojo.addClass(this.displayNode, this.disabled ? "dijitDisabledClickableRegion" : "dijitClickableRegion"); |
| }, |
| |
| _onMouseOut: function(){ |
| // summary: |
| // Handler for onmouseout event. |
| // tags: |
| // private |
| dojo.removeClass(this.displayNode, this.disabled ? "dijitDisabledClickableRegion" : "dijitClickableRegion"); |
| }, |
| |
| _onClick: function(/*Event*/ e){ |
| // summary: |
| // Handler for onclick event. |
| // tags: |
| // private |
| if(this.disabled){ return; } |
| if(e){ dojo.stopEvent(e); } |
| this._onMouseOut(); |
| |
| // Since FF gets upset if you move a node while in an event handler for that node... |
| setTimeout(dojo.hitch(this, "edit"), 0); |
| }, |
| |
| edit: function(){ |
| // summary: |
| // Display the editor widget in place of the original (read only) markup. |
| // tags: |
| // private |
| |
| if(this.disabled || this.editing){ return; } |
| this.editing = true; |
| |
| // save some display node values that can be restored later |
| this._savedPosition = dojo.style(this.displayNode, "position") || "static"; |
| this._savedOpacity = dojo.style(this.displayNode, "opacity") || "1"; |
| this._savedTabIndex = dojo.attr(this.displayNode, "tabIndex") || "0"; |
| |
| if(this.wrapperWidget){ |
| this.wrapperWidget.editWidget.attr("displayedValue" in this.editorParams ? "displayedValue" : "value", this.value); |
| }else{ |
| // Placeholder for edit widget |
| // Put place holder (and eventually editWidget) before the display node so that it's positioned correctly |
| // when Calendar dropdown appears, which happens automatically on focus. |
| var placeholder = dojo.create("span", null, this.domNode, "before"); |
| |
| // Create the editor wrapper (the thing that holds the editor widget and the save/cancel buttons) |
| var ewc = dojo.getObject(this.editorWrapper); |
| this.wrapperWidget = new ewc({ |
| value: this.value, |
| buttonSave: this.buttonSave, |
| buttonCancel: this.buttonCancel, |
| tabIndex: this._savedTabIndex, |
| editor: this.editor, |
| inlineEditBox: this, |
| sourceStyle: dojo.getComputedStyle(this.displayNode), |
| save: dojo.hitch(this, "save"), |
| cancel: dojo.hitch(this, "cancel") |
| }, placeholder); |
| } |
| var ww = this.wrapperWidget; |
| |
| if(dojo.isIE){ |
| dijit.focus(dijit.getFocus()); // IE (at least 8) needs help with tab order changes |
| } |
| // to avoid screen jitter, we first create the editor with position:absolute, visibility:hidden, |
| // and then when it's finished rendering, we switch from display mode to editor |
| // position:absolute releases screen space allocated to the display node |
| // opacity:0 is the same as visibility:hidden but is still focusable |
| // visiblity:hidden removes focus outline |
| |
| dojo.style(this.displayNode, { position: "absolute", opacity: "0", display: "none" }); // makes display node invisible, display style used for focus-ability |
| dojo.style(ww.domNode, { position: this._savedPosition, visibility: "visible", opacity: "1" }); |
| dojo.attr(this.displayNode, "tabIndex", "-1"); // needed by WebKit for TAB from editor to skip displayNode |
| |
| // Replace the display widget with edit widget, leaving them both displayed for a brief time so that |
| // focus can be shifted without incident. (browser may needs some time to render the editor.) |
| setTimeout(dojo.hitch(this, function(){ |
| ww.focus(); // both nodes are showing, so we can switch focus safely |
| ww._resetValue = ww.getValue(); |
| }), 0); |
| }, |
| |
| _onBlur: function(){ |
| // summary: |
| // Called when focus moves outside the InlineEditBox. |
| // Performs garbage collection. |
| // tags: |
| // private |
| |
| this.inherited(arguments); |
| if(!this.editing){ |
| setTimeout(dojo.hitch(this, function(){ |
| if(this.wrapperWidget){ |
| this.wrapperWidget.destroy(); |
| delete this.wrapperWidget; |
| } |
| }), 0); |
| } |
| }, |
| |
| _showText: function(/*Boolean*/ focus){ |
| // summary: |
| // Revert to display mode, and optionally focus on display node |
| // tags: |
| // private |
| |
| var ww = this.wrapperWidget; |
| dojo.style(ww.domNode, { position: "absolute", visibility: "hidden", opacity: "0" }); // hide the editor from mouse/keyboard events |
| dojo.style(this.displayNode, { position: this._savedPosition, opacity: this._savedOpacity, display: "" }); // make the original text visible |
| dojo.attr(this.displayNode, "tabIndex", this._savedTabIndex); |
| if(focus){ |
| dijit.focus(this.displayNode); |
| } |
| }, |
| |
| save: function(/*Boolean*/ focus){ |
| // summary: |
| // Save the contents of the editor and revert to display mode. |
| // focus: Boolean |
| // Focus on the display mode text |
| // tags: |
| // private |
| |
| if(this.disabled || !this.editing){ return; } |
| this.editing = false; |
| |
| var ww = this.wrapperWidget; |
| var value = ww.getValue(); |
| this.attr('value', value); // display changed, formatted value |
| |
| // tell the world that we have changed |
| setTimeout(dojo.hitch(this, "onChange", value), 0); // setTimeout prevents browser freeze for long-running event handlers |
| |
| this._showText(focus); // set focus as needed |
| }, |
| |
| setValue: function(/*String*/ val){ |
| // summary: |
| // Deprecated. Use attr('value', ...) instead. |
| // tags: |
| // deprecated |
| dojo.deprecated("dijit.InlineEditBox.setValue() is deprecated. Use attr('value', ...) instead.", "", "2.0"); |
| return this.attr("value", val); |
| }, |
| |
| _setValueAttr: function(/*String*/ val){ |
| // summary: |
| // Hook to make attr("value", ...) work. |
| // Inserts specified HTML value into this node, or an "input needed" character if node is blank. |
| |
| this.value = val = dojo.trim(val); |
| if(!this.renderAsHtml){ |
| val = val.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """).replace(/\n/g, "<br>"); |
| } |
| this.displayNode.innerHTML = val || this.noValueIndicator; |
| }, |
| |
| getValue: function(){ |
| // summary: |
| // Deprecated. Use attr('value') instead. |
| // tags: |
| // deprecated |
| dojo.deprecated("dijit.InlineEditBox.getValue() is deprecated. Use attr('value') instead.", "", "2.0"); |
| return this.attr("value"); |
| }, |
| |
| cancel: function(/*Boolean*/ focus){ |
| // summary: |
| // Revert to display mode, discarding any changes made in the editor |
| // tags: |
| // private |
| |
| if(this.disabled || !this.editing){ return; } |
| this.editing = false; |
| |
| // tell the world that we have no changes |
| setTimeout(dojo.hitch(this, "onCancel"), 0); // setTimeout prevents browser freeze for long-running event handlers |
| |
| this._showText(focus); |
| } |
| }); |
| |
| dojo.declare( |
| "dijit._InlineEditor", |
| [dijit._Widget, dijit._Templated], |
| { |
| // summary: |
| // Internal widget used by InlineEditBox, displayed when in editing mode |
| // to display the editor and maybe save/cancel buttons. Calling code should |
| // connect to save/cancel methods to detect when editing is finished |
| // |
| // Has mainly the same parameters as InlineEditBox, plus these values: |
| // |
| // style: Object |
| // Set of CSS attributes of display node, to replicate in editor |
| // |
| // value: String |
| // Value as an HTML string or plain text string, depending on renderAsHTML flag |
| |
| templateString: dojo.cache("dijit", "templates/InlineEditBox.html", "<span dojoAttachPoint=\"editNode\" waiRole=\"presentation\" style=\"position: absolute; visibility:hidden\" class=\"dijitReset dijitInline\"\n\tdojoAttachEvent=\"onkeypress: _onKeyPress\"\n\t><span dojoAttachPoint=\"editorPlaceholder\"></span\n\t><span dojoAttachPoint=\"buttonContainer\"\n\t\t><button class='saveButton' dojoAttachPoint=\"saveButton\" dojoType=\"dijit.form.Button\" dojoAttachEvent=\"onClick:save\" label=\"${buttonSave}\"></button\n\t\t><button class='cancelButton' dojoAttachPoint=\"cancelButton\" dojoType=\"dijit.form.Button\" dojoAttachEvent=\"onClick:cancel\" label=\"${buttonCancel}\"></button\n\t></span\n></span>\n"), |
| widgetsInTemplate: true, |
| |
| postMixInProperties: function(){ |
| this.inherited(arguments); |
| this.messages = dojo.i18n.getLocalization("dijit", "common", this.lang); |
| dojo.forEach(["buttonSave", "buttonCancel"], function(prop){ |
| if(!this[prop]){ this[prop] = this.messages[prop]; } |
| }, this); |
| }, |
| |
| postCreate: function(){ |
| // Create edit widget in place in the template |
| var cls = dojo.getObject(this.editor); |
| |
| // Copy the style from the source |
| // Don't copy ALL properties though, just the necessary/applicable ones |
| var srcStyle = this.sourceStyle; |
| var editStyle = "line-height:" + srcStyle.lineHeight + ";"; |
| dojo.forEach(["Weight","Family","Size","Style"], function(prop){ |
| editStyle += "font-"+prop+":"+srcStyle["font"+prop]+";"; |
| }, this); |
| dojo.forEach(["marginTop","marginBottom","marginLeft", "marginRight"], function(prop){ |
| this.domNode.style[prop] = srcStyle[prop]; |
| }, this); |
| var width = this.inlineEditBox.width; |
| if(width == "100%"){ |
| // block mode |
| editStyle += "width:100%;"; |
| this.domNode.style.display = "block"; |
| }else{ |
| // inline-block mode |
| editStyle += "width:" + (width + (Number(width) == width ? "px" : "")) + ";"; |
| } |
| var editorParams = this.inlineEditBox.editorParams; |
| editorParams.style = editStyle; |
| editorParams[ "displayedValue" in cls.prototype ? "displayedValue" : "value"] = this.value; |
| var ew = this.editWidget = new cls(editorParams, this.editorPlaceholder); |
| |
| if(this.inlineEditBox.autoSave){ |
| // Hide the save/cancel buttons since saving is done by simply tabbing away or |
| // selecting a value from the drop down list |
| this.buttonContainer.style.display="none"; |
| |
| // Selecting a value from a drop down list causes an onChange event and then we save |
| this.connect(ew, "onChange", "_onChange"); |
| |
| // ESC and TAB should cancel and save. Note that edit widgets do a stopEvent() on ESC key (to |
| // prevent Dialog from closing when the user just wants to revert the value in the edit widget), |
| // so this is the only way we can see the key press event. |
| this.connect(ew, "onKeyPress", "_onKeyPress"); |
| }else{ |
| // If possible, enable/disable save button based on whether the user has changed the value |
| if("intermediateChanges" in cls.prototype){ |
| ew.attr("intermediateChanges", true); |
| this.connect(ew, "onChange", "_onIntermediateChange"); |
| this.saveButton.attr("disabled", true); |
| } |
| } |
| }, |
| |
| _onIntermediateChange: function(val){ |
| // summary: |
| // Called for editor widgets that support the intermediateChanges=true flag as a way |
| // to detect when to enable/disabled the save button |
| this.saveButton.attr("disabled", (this.getValue() == this._resetValue) || !this.enableSave()); |
| }, |
| |
| destroy: function(){ |
| this.editWidget.destroy(true); // let the parent wrapper widget clean up the DOM |
| this.inherited(arguments); |
| }, |
| |
| getValue: function(){ |
| // summary: |
| // Return the [display] value of the edit widget |
| var ew = this.editWidget; |
| return String(ew.attr("displayedValue" in ew ? "displayedValue" : "value")); |
| }, |
| |
| _onKeyPress: function(e){ |
| // summary: |
| // Handler for keypress in the edit box in autoSave mode. |
| // description: |
| // For autoSave widgets, if Esc/Enter, call cancel/save. |
| // tags: |
| // private |
| |
| if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){ |
| if(e.altKey || e.ctrlKey){ return; } |
| // If Enter/Esc pressed, treat as save/cancel. |
| if(e.charOrCode == dojo.keys.ESCAPE){ |
| dojo.stopEvent(e); |
| this.cancel(true); // sets editing=false which short-circuits _onBlur processing |
| }else if(e.charOrCode == dojo.keys.ENTER && e.target.tagName == "INPUT"){ |
| dojo.stopEvent(e); |
| this._onChange(); // fire _onBlur and then save |
| } |
| |
| // _onBlur will handle TAB automatically by allowing |
| // the TAB to change focus before we mess with the DOM: #6227 |
| // Expounding by request: |
| // The current focus is on the edit widget input field. |
| // save() will hide and destroy this widget. |
| // We want the focus to jump from the currently hidden |
| // displayNode, but since it's hidden, it's impossible to |
| // unhide it, focus it, and then have the browser focus |
| // away from it to the next focusable element since each |
| // of these events is asynchronous and the focus-to-next-element |
| // is already queued. |
| // So we allow the browser time to unqueue the move-focus event |
| // before we do all the hide/show stuff. |
| } |
| }, |
| |
| _onBlur: function(){ |
| // summary: |
| // Called when focus moves outside the editor |
| // tags: |
| // private |
| |
| this.inherited(arguments); |
| if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){ |
| if(this.getValue() == this._resetValue){ |
| this.cancel(false); |
| }else if(this.enableSave()){ |
| this.save(false); |
| } |
| } |
| }, |
| |
| _onChange: function(){ |
| // summary: |
| // Called when the underlying widget fires an onChange event, |
| // such as when the user selects a value from the drop down list of a ComboBox, |
| // which means that the user has finished entering the value and we should save. |
| // tags: |
| // private |
| |
| if(this.inlineEditBox.autoSave && this.inlineEditBox.editing && this.enableSave()){ |
| dojo.style(this.inlineEditBox.displayNode, { display: "" }); |
| dijit.focus(this.inlineEditBox.displayNode); // fires _onBlur which will save the formatted value |
| } |
| }, |
| |
| enableSave: function(){ |
| // summary: |
| // User overridable function returning a Boolean to indicate |
| // if the Save button should be enabled or not - usually due to invalid conditions |
| // tags: |
| // extension |
| return ( |
| this.editWidget.isValid |
| ? this.editWidget.isValid() |
| : true |
| ); |
| }, |
| |
| focus: function(){ |
| // summary: |
| // Focus the edit widget. |
| // tags: |
| // protected |
| |
| this.editWidget.focus(); |
| setTimeout(dojo.hitch(this, function(){ |
| if(this.editWidget.focusNode.tagName == "INPUT"){ |
| dijit.selectInputText(this.editWidget.focusNode); |
| } |
| }), 0); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.Form"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.Form"] = true; |
| dojo.provide("dijit.form.Form"); |
| |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.form.Form", |
| [dijit._Widget, dijit._Templated, dijit.form._FormMixin], |
| { |
| // summary: |
| // Widget corresponding to HTML form tag, for validation and serialization |
| // |
| // example: |
| // | <form dojoType="dijit.form.Form" id="myForm"> |
| // | Name: <input type="text" name="name" /> |
| // | </form> |
| // | myObj = {name: "John Doe"}; |
| // | dijit.byId('myForm').attr('value', myObj); |
| // | |
| // | myObj=dijit.byId('myForm').attr('value'); |
| |
| // HTML <FORM> attributes |
| |
| // name: String? |
| // Name of form for scripting. |
| name: "", |
| |
| // action: String? |
| // Server-side form handler. |
| action: "", |
| |
| // method: String? |
| // HTTP method used to submit the form, either "GET" or "POST". |
| method: "", |
| |
| // encType: String? |
| // Encoding type for the form, ex: application/x-www-form-urlencoded. |
| encType: "", |
| |
| // accept-charset: String? |
| // List of supported charsets. |
| "accept-charset": "", |
| |
| // accept: String? |
| // List of MIME types for file upload. |
| accept: "", |
| |
| // target: String? |
| // Target frame for the document to be opened in. |
| target: "", |
| |
| templateString: "<form dojoAttachPoint='containerNode' dojoAttachEvent='onreset:_onReset,onsubmit:_onSubmit' ${nameAttrSetting}></form>", |
| |
| attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { |
| action: "", |
| method: "", |
| encType: "", |
| "accept-charset": "", |
| accept: "", |
| target: "" |
| }), |
| |
| postMixInProperties: function(){ |
| // Setup name=foo string to be referenced from the template (but only if a name has been specified) |
| // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660 |
| this.nameAttrSetting = this.name ? ("name='" + this.name + "'") : ""; |
| this.inherited(arguments); |
| }, |
| |
| execute: function(/*Object*/ formContents){ |
| // summary: |
| // Deprecated: use submit() |
| // tags: |
| // deprecated |
| }, |
| |
| onExecute: function(){ |
| // summary: |
| // Deprecated: use onSubmit() |
| // tags: |
| // deprecated |
| }, |
| |
| _setEncTypeAttr: function(/*String*/ value){ |
| this.encType = value; |
| dojo.attr(this.domNode, "encType", value); |
| if(dojo.isIE){ this.domNode.encoding = value; } |
| }, |
| |
| postCreate: function(){ |
| // IE tries to hide encType |
| // TODO: this code should be in parser, not here. |
| if(dojo.isIE && this.srcNodeRef && this.srcNodeRef.attributes){ |
| var item = this.srcNodeRef.attributes.getNamedItem('encType'); |
| if(item && !item.specified && (typeof item.value == "string")){ |
| this.attr('encType', item.value); |
| } |
| } |
| this.inherited(arguments); |
| }, |
| |
| onReset: function(/*Event?*/ e){ |
| // summary: |
| // Callback when user resets the form. This method is intended |
| // to be over-ridden. When the `reset` method is called |
| // programmatically, the return value from `onReset` is used |
| // to compute whether or not resetting should proceed |
| // tags: |
| // callback |
| return true; // Boolean |
| }, |
| |
| _onReset: function(e){ |
| // create fake event so we can know if preventDefault() is called |
| var faux = { |
| returnValue: true, // the IE way |
| preventDefault: function(){ // not IE |
| this.returnValue = false; |
| }, |
| stopPropagation: function(){}, currentTarget: e.currentTarget, target: e.target |
| }; |
| // if return value is not exactly false, and haven't called preventDefault(), then reset |
| if(!(this.onReset(faux) === false) && faux.returnValue){ |
| this.reset(); |
| } |
| dojo.stopEvent(e); |
| return false; |
| }, |
| |
| _onSubmit: function(e){ |
| var fp = dijit.form.Form.prototype; |
| // TODO: remove this if statement beginning with 2.0 |
| if(this.execute != fp.execute || this.onExecute != fp.onExecute){ |
| dojo.deprecated("dijit.form.Form:execute()/onExecute() are deprecated. Use onSubmit() instead.", "", "2.0"); |
| this.onExecute(); |
| this.execute(this.getValues()); |
| } |
| if(this.onSubmit(e) === false){ // only exactly false stops submit |
| dojo.stopEvent(e); |
| } |
| }, |
| |
| onSubmit: function(/*Event?*/e){ |
| // summary: |
| // Callback when user submits the form. |
| // description: |
| // This method is intended to be over-ridden, but by default it checks and |
| // returns the validity of form elements. When the `submit` |
| // method is called programmatically, the return value from |
| // `onSubmit` is used to compute whether or not submission |
| // should proceed |
| // tags: |
| // extension |
| |
| return this.isValid(); // Boolean |
| }, |
| |
| submit: function(){ |
| // summary: |
| // programmatically submit form if and only if the `onSubmit` returns true |
| if(!(this.onSubmit() === false)){ |
| this.containerNode.submit(); |
| } |
| } |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.DropDownButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.DropDownButton"] = true; |
| dojo.provide("dijit.form.DropDownButton"); |
| |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.ComboButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.ComboButton"] = true; |
| dojo.provide("dijit.form.ComboButton"); |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.ToggleButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.ToggleButton"] = true; |
| dojo.provide("dijit.form.ToggleButton"); |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.CheckBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.CheckBox"] = true; |
| dojo.provide("dijit.form.CheckBox"); |
| |
| |
| |
| dojo.declare( |
| "dijit.form.CheckBox", |
| dijit.form.ToggleButton, |
| { |
| // summary: |
| // Same as an HTML checkbox, but with fancy styling. |
| // |
| // description: |
| // User interacts with real html inputs. |
| // On onclick (which occurs by mouse click, space-bar, or |
| // using the arrow keys to switch the selected radio button), |
| // we update the state of the checkbox/radio. |
| // |
| // There are two modes: |
| // 1. High contrast mode |
| // 2. Normal mode |
| // In case 1, the regular html inputs are shown and used by the user. |
| // In case 2, the regular html inputs are invisible but still used by |
| // the user. They are turned quasi-invisible and overlay the background-image. |
| |
| templateString: dojo.cache("dijit.form", "templates/CheckBox.html", "<div class=\"dijitReset dijitInline\" waiRole=\"presentation\"\n\t><input\n\t \t${nameAttrSetting} type=\"${type}\" ${checkedAttrSetting}\n\t\tclass=\"dijitReset dijitCheckBoxInput\"\n\t\tdojoAttachPoint=\"focusNode\"\n\t \tdojoAttachEvent=\"onmouseover:_onMouse,onmouseout:_onMouse,onclick:_onClick\"\n/></div>\n"), |
| |
| baseClass: "dijitCheckBox", |
| |
| // type: [private] String |
| // type attribute on <input> node. |
| // Overrides `dijit.form.Button.type`. Users should not change this value. |
| type: "checkbox", |
| |
| // value: String |
| // As an initialization parameter, equivalent to value field on normal checkbox |
| // (if checked, the value is passed as the value when form is submitted). |
| // |
| // However, attr('value') will return either the string or false depending on |
| // whether or not the checkbox is checked. |
| // |
| // attr('value', string) will check the checkbox and change the value to the |
| // specified string |
| // |
| // attr('value', boolean) will change the checked state. |
| value: "on", |
| |
| // readOnly: Boolean |
| // Should this widget respond to user input? |
| // In markup, this is specified as "readOnly". |
| // Similar to disabled except readOnly form values are submitted. |
| readOnly: false, |
| |
| attributeMap: dojo.delegate(dijit.form.ToggleButton.prototype.attributeMap, { |
| readOnly: "focusNode" |
| }), |
| |
| _setReadOnlyAttr: function(/*Boolean*/ value){ |
| this.readOnly = value; |
| dojo.attr(this.focusNode, 'readOnly', value); |
| dijit.setWaiState(this.focusNode, "readonly", value); |
| this._setStateClass(); |
| }, |
| |
| _setValueAttr: function(/*String or Boolean*/ newValue){ |
| // summary: |
| // Handler for value= attribute to constructor, and also calls to |
| // attr('value', val). |
| // description: |
| // During initialization, just saves as attribute to the <input type=checkbox>. |
| // |
| // After initialization, |
| // when passed a boolean, controls whether or not the CheckBox is checked. |
| // If passed a string, changes the value attribute of the CheckBox (the one |
| // specified as "value" when the CheckBox was constructed (ex: <input |
| // dojoType="dijit.CheckBox" value="chicken">) |
| if(typeof newValue == "string"){ |
| this.value = newValue; |
| dojo.attr(this.focusNode, 'value', newValue); |
| newValue = true; |
| } |
| if(this._created){ |
| this.attr('checked', newValue); |
| } |
| }, |
| _getValueAttr: function(){ |
| // summary: |
| // Hook so attr('value') works. |
| // description: |
| // If the CheckBox is checked, returns the value attribute. |
| // Otherwise returns false. |
| return (this.checked ? this.value : false); |
| }, |
| |
| postMixInProperties: function(){ |
| if(this.value == ""){ |
| this.value = "on"; |
| } |
| |
| // Need to set initial checked state as part of template, so that form submit works. |
| // dojo.attr(node, "checked", bool) doesn't work on IEuntil node has been attached |
| // to <body>, see #8666 |
| this.checkedAttrSetting = this.checked ? "checked" : ""; |
| |
| this.inherited(arguments); |
| }, |
| |
| _fillContent: function(/*DomNode*/ source){ |
| // Override Button::_fillContent() since it doesn't make sense for CheckBox, |
| // since CheckBox doesn't even have a container |
| }, |
| |
| reset: function(){ |
| // Override ToggleButton.reset() |
| |
| this._hasBeenBlurred = false; |
| |
| this.attr('checked', this.params.checked || false); |
| |
| // Handle unlikely event that the <input type=checkbox> value attribute has changed |
| this.value = this.params.value || "on"; |
| dojo.attr(this.focusNode, 'value', this.value); |
| }, |
| |
| _onFocus: function(){ |
| if(this.id){ |
| dojo.query("label[for='"+this.id+"']").addClass("dijitFocusedLabel"); |
| } |
| }, |
| |
| _onBlur: function(){ |
| if(this.id){ |
| dojo.query("label[for='"+this.id+"']").removeClass("dijitFocusedLabel"); |
| } |
| }, |
| |
| _onClick: function(/*Event*/ e){ |
| // summary: |
| // Internal function to handle click actions - need to check |
| // readOnly, since button no longer does that check. |
| if(this.readOnly){ |
| return false; |
| } |
| return this.inherited(arguments); |
| } |
| } |
| ); |
| |
| dojo.declare( |
| "dijit.form.RadioButton", |
| dijit.form.CheckBox, |
| { |
| // summary: |
| // Same as an HTML radio, but with fancy styling. |
| |
| type: "radio", |
| baseClass: "dijitRadio", |
| |
| _setCheckedAttr: function(/*Boolean*/ value){ |
| // If I am being checked then have to deselect currently checked radio button |
| this.inherited(arguments); |
| if(!this._created){ return; } |
| if(value){ |
| var _this = this; |
| // search for radio buttons with the same name that need to be unchecked |
| dojo.query("INPUT[type=radio]", this.focusNode.form || dojo.doc).forEach( // can't use name= since dojo.query doesn't support [] in the name |
| function(inputNode){ |
| if(inputNode.name == _this.name && inputNode != _this.focusNode && inputNode.form == _this.focusNode.form){ |
| var widget = dijit.getEnclosingWidget(inputNode); |
| if(widget && widget.checked){ |
| widget.attr('checked', false); |
| } |
| } |
| } |
| ); |
| } |
| }, |
| |
| _clicked: function(/*Event*/ e){ |
| if(!this.checked){ |
| this.attr('checked', true); |
| } |
| } |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.RadioButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.RadioButton"] = true; |
| dojo.provide("dijit.form.RadioButton"); |
| |
| |
| // TODO: for 2.0, move the RadioButton code into this file |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.ValidationTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.ValidationTextBox"] = true; |
| dojo.provide("dijit.form.ValidationTextBox"); |
| |
| |
| |
| |
| |
| |
| |
| |
| /*===== |
| dijit.form.ValidationTextBox.__Constraints = function(){ |
| // locale: String |
| // locale used for validation, picks up value from this widget's lang attribute |
| // _flags_: anything |
| // various flags passed to regExpGen function |
| this.locale = ""; |
| this._flags_ = ""; |
| } |
| =====*/ |
| |
| dojo.declare( |
| "dijit.form.ValidationTextBox", |
| dijit.form.TextBox, |
| { |
| // summary: |
| // Base class for textbox widgets with the ability to validate content of various types and provide user feedback. |
| // tags: |
| // protected |
| |
| templateString: dojo.cache("dijit.form", "templates/ValidationTextBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\tdojoAttachEvent=\"onmouseenter:_onMouse,onmouseleave:_onMouse,onmousedown:_onMouse\" waiRole=\"presentation\"\n\t><div style=\"overflow:hidden;\"\n\t\t><div class=\"dijitReset dijitValidationIcon\"><br></div\n\t\t><div class=\"dijitReset dijitValidationIconText\">Χ</div\n\t\t><div class=\"dijitReset dijitInputField\"\n\t\t\t><input class=\"dijitReset\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${nameAttrSetting} type='${type}'\n\t\t/></div\n\t></div\n></div>\n"), |
| baseClass: "dijitTextBox", |
| |
| // required: Boolean |
| // User is required to enter data into this field. |
| required: false, |
| |
| // promptMessage: String |
| // If defined, display this hint string immediately on focus to the textbox, if empty. |
| // Think of this like a tooltip that tells the user what to do, not an error message |
| // that tells the user what they've done wrong. |
| // |
| // Message disappears when user starts typing. |
| promptMessage: "", |
| |
| // invalidMessage: String |
| // The message to display if value is invalid. |
| invalidMessage: "$_unset_$", // read from the message file if not overridden |
| |
| // constraints: dijit.form.ValidationTextBox.__Constraints |
| // user-defined object needed to pass parameters to the validator functions |
| constraints: {}, |
| |
| // regExp: [extension protected] String |
| // regular expression string used to validate the input |
| // Do not specify both regExp and regExpGen |
| regExp: ".*", |
| |
| regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/constraints){ |
| // summary: |
| // Overridable function used to generate regExp when dependent on constraints. |
| // Do not specify both regExp and regExpGen. |
| // tags: |
| // extension protected |
| return this.regExp; // String |
| }, |
| |
| // state: [readonly] String |
| // Shows current state (ie, validation result) of input (Normal, Warning, or Error) |
| state: "", |
| |
| // tooltipPosition: String[] |
| // See description of `dijit.Tooltip.defaultPosition` for details on this parameter. |
| tooltipPosition: [], |
| |
| _setValueAttr: function(){ |
| // summary: |
| // Hook so attr('value', ...) works. |
| this.inherited(arguments); |
| this.validate(this._focused); |
| }, |
| |
| validator: function(/*anything*/value, /*dijit.form.ValidationTextBox.__Constraints*/constraints){ |
| // summary: |
| // Overridable function used to validate the text input against the regular expression. |
| // tags: |
| // protected |
| return (new RegExp("^(?:" + this.regExpGen(constraints) + ")"+(this.required?"":"?")+"$")).test(value) && |
| (!this.required || !this._isEmpty(value)) && |
| (this._isEmpty(value) || this.parse(value, constraints) !== undefined); // Boolean |
| }, |
| |
| _isValidSubset: function(){ |
| // summary: |
| // Returns true if the value is either already valid or could be made valid by appending characters. |
| // This is used for validation while the user [may be] still typing. |
| return this.textbox.value.search(this._partialre) == 0; |
| }, |
| |
| isValid: function(/*Boolean*/ isFocused){ |
| // summary: |
| // Tests if value is valid. |
| // Can override with your own routine in a subclass. |
| // tags: |
| // protected |
| return this.validator(this.textbox.value, this.constraints); |
| }, |
| |
| _isEmpty: function(value){ |
| // summary: |
| // Checks for whitespace |
| return /^\s*$/.test(value); // Boolean |
| }, |
| |
| getErrorMessage: function(/*Boolean*/ isFocused){ |
| // summary: |
| // Return an error message to show if appropriate |
| // tags: |
| // protected |
| return this.invalidMessage; // String |
| }, |
| |
| getPromptMessage: function(/*Boolean*/ isFocused){ |
| // summary: |
| // Return a hint message to show when widget is first focused |
| // tags: |
| // protected |
| return this.promptMessage; // String |
| }, |
| |
| _maskValidSubsetError: true, |
| validate: function(/*Boolean*/ isFocused){ |
| // summary: |
| // Called by oninit, onblur, and onkeypress. |
| // description: |
| // Show missing or invalid messages if appropriate, and highlight textbox field. |
| // tags: |
| // protected |
| var message = ""; |
| var isValid = this.disabled || this.isValid(isFocused); |
| if(isValid){ this._maskValidSubsetError = true; } |
| var isValidSubset = !isValid && isFocused && this._isValidSubset(); |
| var isEmpty = this._isEmpty(this.textbox.value); |
| if(isEmpty){ this._maskValidSubsetError = true; } |
| this.state = (isValid || (!this._hasBeenBlurred && isEmpty) || isValidSubset) ? "" : "Error"; |
| if(this.state == "Error"){ this._maskValidSubsetError = false; } |
| this._setStateClass(); |
| dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true"); |
| if(isFocused){ |
| if(isEmpty){ |
| message = this.getPromptMessage(true); |
| } |
| if(!message && (this.state == "Error" || (isValidSubset && !this._maskValidSubsetError))){ |
| message = this.getErrorMessage(true); |
| } |
| } |
| this.displayMessage(message); |
| return isValid; |
| }, |
| |
| // _message: String |
| // Currently displayed message |
| _message: "", |
| |
| displayMessage: function(/*String*/ message){ |
| // summary: |
| // Overridable method to display validation errors/hints. |
| // By default uses a tooltip. |
| // tags: |
| // extension |
| if(this._message == message){ return; } |
| this._message = message; |
| dijit.hideTooltip(this.domNode); |
| if(message){ |
| dijit.showTooltip(message, this.domNode, this.tooltipPosition); |
| } |
| }, |
| |
| _refreshState: function(){ |
| // Overrides TextBox._refreshState() |
| this.validate(this._focused); |
| this.inherited(arguments); |
| }, |
| |
| //////////// INITIALIZATION METHODS /////////////////////////////////////// |
| |
| constructor: function(){ |
| this.constraints = {}; |
| }, |
| |
| postMixInProperties: function(){ |
| this.inherited(arguments); |
| this.constraints.locale = this.lang; |
| this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang); |
| if(this.invalidMessage == "$_unset_$"){ this.invalidMessage = this.messages.invalidMessage; } |
| var p = this.regExpGen(this.constraints); |
| this.regExp = p; |
| var partialre = ""; |
| // parse the regexp and produce a new regexp that matches valid subsets |
| // if the regexp is .* then there's no use in matching subsets since everything is valid |
| if(p != ".*"){ this.regExp.replace(/\\.|\[\]|\[.*?[^\\]{1}\]|\{.*?\}|\(\?[=:!]|./g, |
| function (re){ |
| switch(re.charAt(0)){ |
| case '{': |
| case '+': |
| case '?': |
| case '*': |
| case '^': |
| case '$': |
| case '|': |
| case '(': |
| partialre += re; |
| break; |
| case ")": |
| partialre += "|$)"; |
| break; |
| default: |
| partialre += "(?:"+re+"|$)"; |
| break; |
| } |
| } |
| );} |
| try{ // this is needed for now since the above regexp parsing needs more test verification |
| "".search(partialre); |
| }catch(e){ // should never be here unless the original RE is bad or the parsing is bad |
| partialre = this.regExp; |
| console.warn('RegExp error in ' + this.declaredClass + ': ' + this.regExp); |
| } // should never be here unless the original RE is bad or the parsing is bad |
| this._partialre = "^(?:" + partialre + ")$"; |
| }, |
| |
| _setDisabledAttr: function(/*Boolean*/ value){ |
| this.inherited(arguments); // call FormValueWidget._setDisabledAttr() |
| this._refreshState(); |
| }, |
| |
| _setRequiredAttr: function(/*Boolean*/ value){ |
| this.required = value; |
| dijit.setWaiState(this.focusNode,"required", value); |
| this._refreshState(); |
| }, |
| |
| postCreate: function(){ |
| if(dojo.isIE){ // IE INPUT tag fontFamily has to be set directly using STYLE |
| var s = dojo.getComputedStyle(this.focusNode); |
| if(s){ |
| var ff = s.fontFamily; |
| if(ff){ |
| this.focusNode.style.fontFamily = ff; |
| } |
| } |
| } |
| this.inherited(arguments); |
| }, |
| |
| reset:function(){ |
| // Overrides dijit.form.TextBox.reset() by also |
| // hiding errors about partial matches |
| this._maskValidSubsetError = true; |
| this.inherited(arguments); |
| }, |
| |
| _onBlur: function(){ |
| this.displayMessage(''); |
| this.inherited(arguments); |
| } |
| } |
| ); |
| |
| dojo.declare( |
| "dijit.form.MappedTextBox", |
| dijit.form.ValidationTextBox, |
| { |
| // summary: |
| // A dijit.form.ValidationTextBox subclass which provides a base class for widgets that have |
| // a visible formatted display value, and a serializable |
| // value in a hidden input field which is actually sent to the server. |
| // description: |
| // The visible display may |
| // be locale-dependent and interactive. The value sent to the server is stored in a hidden |
| // input field which uses the `name` attribute declared by the original widget. That value sent |
| // to the server is defined by the dijit.form.MappedTextBox.serialize method and is typically |
| // locale-neutral. |
| // tags: |
| // protected |
| |
| postMixInProperties: function(){ |
| this.inherited(arguments); |
| |
| // we want the name attribute to go to the hidden <input>, not the displayed <input>, |
| // so override _FormWidget.postMixInProperties() setting of nameAttrSetting |
| this.nameAttrSetting = ""; |
| }, |
| |
| serialize: function(/*anything*/val, /*Object?*/options){ |
| // summary: |
| // Overridable function used to convert the attr('value') result to a canonical |
| // (non-localized) string. For example, will print dates in ISO format, and |
| // numbers the same way as they are represented in javascript. |
| // tags: |
| // protected extension |
| return val.toString ? val.toString() : ""; // String |
| }, |
| |
| toString: function(){ |
| // summary: |
| // Returns widget as a printable string using the widget's value |
| // tags: |
| // protected |
| var val = this.filter(this.attr('value')); // call filter in case value is nonstring and filter has been customized |
| return val != null ? (typeof val == "string" ? val : this.serialize(val, this.constraints)) : ""; // String |
| }, |
| |
| validate: function(){ |
| // Overrides `dijit.form.TextBox.validate` |
| this.valueNode.value = this.toString(); |
| return this.inherited(arguments); |
| }, |
| |
| buildRendering: function(){ |
| // Overrides `dijit._Templated.buildRendering` |
| |
| this.inherited(arguments); |
| |
| // Create a hidden <input> node with the serialized value used for submit |
| // (as opposed to the displayed value). |
| // Passing in name as markup rather than calling dojo.create() with an attrs argument |
| // to make dojo.query(input[name=...]) work on IE. (see #8660) |
| this.valueNode = dojo.place("<input type='hidden'" + (this.name ? " name='" + this.name + "'" : "") + ">", this.textbox, "after"); |
| }, |
| |
| reset:function(){ |
| // Overrides `dijit.form.ValidationTextBox.reset` to |
| // reset the hidden textbox value to '' |
| this.valueNode.value = ''; |
| this.inherited(arguments); |
| } |
| } |
| ); |
| |
| /*===== |
| dijit.form.RangeBoundTextBox.__Constraints = function(){ |
| // min: Number |
| // Minimum signed value. Default is -Infinity |
| // max: Number |
| // Maximum signed value. Default is +Infinity |
| this.min = min; |
| this.max = max; |
| } |
| =====*/ |
| |
| dojo.declare( |
| "dijit.form.RangeBoundTextBox", |
| dijit.form.MappedTextBox, |
| { |
| // summary: |
| // Base class for textbox form widgets which defines a range of valid values. |
| |
| // rangeMessage: String |
| // The message to display if value is out-of-range |
| rangeMessage: "", |
| |
| /*===== |
| // constraints: dijit.form.RangeBoundTextBox.__Constraints |
| constraints: {}, |
| ======*/ |
| |
| rangeCheck: function(/*Number*/ primitive, /*dijit.form.RangeBoundTextBox.__Constraints*/ constraints){ |
| // summary: |
| // Overridable function used to validate the range of the numeric input value. |
| // tags: |
| // protected |
| return ("min" in constraints? (this.compare(primitive,constraints.min) >= 0) : true) && |
| ("max" in constraints? (this.compare(primitive,constraints.max) <= 0) : true); // Boolean |
| }, |
| |
| isInRange: function(/*Boolean*/ isFocused){ |
| // summary: |
| // Tests if the value is in the min/max range specified in constraints |
| // tags: |
| // protected |
| return this.rangeCheck(this.attr('value'), this.constraints); |
| }, |
| |
| _isDefinitelyOutOfRange: function(){ |
| // summary: |
| // Returns true if the value is out of range and will remain |
| // out of range even if the user types more characters |
| var val = this.attr('value'); |
| var isTooLittle = false; |
| var isTooMuch = false; |
| if("min" in this.constraints){ |
| var min = this.constraints.min; |
| min = this.compare(val, ((typeof min == "number") && min >= 0 && val !=0) ? 0 : min); |
| isTooLittle = (typeof min == "number") && min < 0; |
| } |
| if("max" in this.constraints){ |
| var max = this.constraints.max; |
| max = this.compare(val, ((typeof max != "number") || max > 0) ? max : 0); |
| isTooMuch = (typeof max == "number") && max > 0; |
| } |
| return isTooLittle || isTooMuch; |
| }, |
| |
| _isValidSubset: function(){ |
| // summary: |
| // Overrides `dijit.form.ValidationTextBox._isValidSubset`. |
| // Returns true if the input is syntactically valid, and either within |
| // range or could be made in range by more typing. |
| return this.inherited(arguments) && !this._isDefinitelyOutOfRange(); |
| }, |
| |
| isValid: function(/*Boolean*/ isFocused){ |
| // Overrides dijit.form.ValidationTextBox.isValid to check that the value is also in range. |
| return this.inherited(arguments) && |
| ((this._isEmpty(this.textbox.value) && !this.required) || this.isInRange(isFocused)); // Boolean |
| }, |
| |
| getErrorMessage: function(/*Boolean*/ isFocused){ |
| // Overrides dijit.form.ValidationTextBox.getErrorMessage to print "out of range" message if appropriate |
| var v = this.attr('value'); |
| if(v !== null && v !== '' && v !== undefined && !this.isInRange(isFocused)){ // don't check isInRange w/o a real value |
| return this.rangeMessage; // String |
| } |
| return this.inherited(arguments); |
| }, |
| |
| postMixInProperties: function(){ |
| this.inherited(arguments); |
| if(!this.rangeMessage){ |
| this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang); |
| this.rangeMessage = this.messages.rangeMessage; |
| } |
| }, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| if(this.constraints.min !== undefined){ |
| dijit.setWaiState(this.focusNode, "valuemin", this.constraints.min); |
| } |
| if(this.constraints.max !== undefined){ |
| dijit.setWaiState(this.focusNode, "valuemax", this.constraints.max); |
| } |
| }, |
| |
| _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){ |
| // summary: |
| // Hook so attr('value', ...) works. |
| |
| dijit.setWaiState(this.focusNode, "valuenow", value); |
| this.inherited(arguments); |
| } |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dojo.cldr.monetary"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.cldr.monetary"] = true; |
| dojo.provide("dojo.cldr.monetary"); |
| |
| dojo.cldr.monetary.getData = function(/*String*/code){ |
| // summary: A mapping of currency code to currency-specific formatting information. Returns a unique object with properties: places, round. |
| // code: an [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217) currency code |
| |
| // from http://www.unicode.org/cldr/data/common/supplemental/supplementalData.xml:supplementalData/currencyData/fractions |
| |
| var placesData = { |
| ADP:0,BHD:3,BIF:0,BYR:0,CLF:0,CLP:0,DJF:0,ESP:0,GNF:0, |
| IQD:3,ITL:0,JOD:3,JPY:0,KMF:0,KRW:0,KWD:3,LUF:0,LYD:3, |
| MGA:0,MGF:0,OMR:3,PYG:0,RWF:0,TND:3,TRL:0,VUV:0,XAF:0, |
| XOF:0,XPF:0 |
| }; |
| |
| var roundingData = {CHF:5}; |
| |
| var places = placesData[code], round = roundingData[code]; |
| if(typeof places == "undefined"){ places = 2; } |
| if(typeof round == "undefined"){ round = 0; } |
| |
| return {places: places, round: round}; // Object |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dojo.currency"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.currency"] = true; |
| dojo.provide("dojo.currency"); |
| |
| |
| |
| |
| |
| |
| /*===== |
| dojo.currency = { |
| // summary: localized formatting and parsing routines for currencies |
| } |
| =====*/ |
| |
| dojo.currency._mixInDefaults = function(options){ |
| options = options || {}; |
| options.type = "currency"; |
| |
| // Get locale-depenent currency data, like the symbol |
| var bundle = dojo.i18n.getLocalization("dojo.cldr", "currency", options.locale) || {}; |
| |
| // Mixin locale-independent currency data, like # of places |
| var iso = options.currency; |
| var data = dojo.cldr.monetary.getData(iso); |
| |
| dojo.forEach(["displayName","symbol","group","decimal"], function(prop){ |
| data[prop] = bundle[iso+"_"+prop]; |
| }); |
| |
| data.fractional = [true, false]; |
| |
| // Mixin with provided options |
| return dojo.mixin(data, options); |
| } |
| |
| /*===== |
| dojo.declare("dojo.currency.__FormatOptions", [dojo.number.__FormatOptions], { |
| // type: String? |
| // Should not be set. Value is assumed to be currency. |
| // currency: String? |
| // an [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code, a three letter sequence like "USD". |
| // For use with dojo.currency only. |
| // symbol: String? |
| // localized currency symbol. The default will be looked up in table of supported currencies in `dojo.cldr` |
| // A [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code will be used if not found. |
| // places: Number? |
| // number of decimal places to show. Default is defined based on which currency is used. |
| type: "", |
| symbol: "", |
| places: "", |
| fractional: "" |
| }); |
| =====*/ |
| |
| dojo.currency.format = function(/*Number*/value, /*dojo.currency.__FormatOptions?*/options){ |
| // summary: |
| // Format a Number as a currency, using locale-specific settings |
| // |
| // description: |
| // Create a string from a Number using a known, localized pattern. |
| // [Formatting patterns](http://www.unicode.org/reports/tr35/#Number_Elements) appropriate to the locale are chosen from the [CLDR](http://unicode.org/cldr) |
| // as well as the appropriate symbols and delimiters. |
| // |
| // value: |
| // the number to be formatted. |
| |
| return dojo.number.format(value, dojo.currency._mixInDefaults(options)); |
| } |
| |
| dojo.currency.regexp = function(/*dojo.number.__RegexpOptions?*/options){ |
| // |
| // summary: |
| // Builds the regular needed to parse a currency value |
| // |
| // description: |
| // Returns regular expression with positive and negative match, group and decimal separators |
| // Note: the options.places default, the number of decimal places to accept, is defined by the currency type. |
| return dojo.number.regexp(dojo.currency._mixInDefaults(options)); // String |
| } |
| |
| /*===== |
| dojo.declare("dojo.currency.__ParseOptions", [dojo.number.__ParseOptions], { |
| // type: String? |
| // Should not be set. Value is assumed to be currency. |
| // currency: String? |
| // an [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code, a three letter sequence like "USD". |
| // For use with dojo.currency only. |
| // symbol: String? |
| // localized currency symbol. The default will be looked up in table of supported currencies in `dojo.cldr` |
| // A [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code will be used if not found. |
| // places: Number? |
| // number of decimal places to accept. Default is defined based on which currency is used. |
| // fractional: Boolean?|Array? |
| // Whether to include the fractional portion, where the number of decimal places are implied by pattern |
| // or explicit 'places' parameter. By default for currencies, it the fractional portion is optional. |
| type: "", |
| symbol: "", |
| places: "", |
| fractional: "" |
| }); |
| =====*/ |
| |
| dojo.currency.parse = function(/*String*/expression, /*dojo.currency.__ParseOptions?*/options){ |
| // |
| // summary: |
| // Convert a properly formatted currency string to a primitive Number, |
| // using locale-specific settings. |
| // |
| // description: |
| // Create a Number from a string using a known, localized pattern. |
| // [Formatting patterns](http://www.unicode.org/reports/tr35/#Number_Format_Patterns) are chosen appropriate to the locale. |
| // |
| // expression: A string representation of a Number |
| |
| return dojo.number.parse(expression, dojo.currency._mixInDefaults(options)); |
| } |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.NumberTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.NumberTextBox"] = true; |
| dojo.provide("dijit.form.NumberTextBox"); |
| |
| |
| |
| |
| /*===== |
| dojo.declare( |
| "dijit.form.NumberTextBox.__Constraints", |
| [dijit.form.RangeBoundTextBox.__Constraints, dojo.number.__FormatOptions, dojo.number.__ParseOptions], { |
| // summary: |
| // Specifies both the rules on valid/invalid values (minimum, maximum, |
| // number of required decimal places), and also formatting options for |
| // displaying the value when the field is not focused. |
| // example: |
| // Minimum/maximum: |
| // To specify a field between 0 and 120: |
| // | {min:0,max:120} |
| // To specify a field that must be an integer: |
| // | {fractional:false} |
| // To specify a field where 0 to 3 decimal places are allowed on input, |
| // but after the field is blurred the value is displayed with 3 decimal places: |
| // | {places:'0,3'} |
| }); |
| =====*/ |
| |
| dojo.declare("dijit.form.NumberTextBoxMixin", |
| null, |
| { |
| // summary: |
| // A mixin for all number textboxes |
| // tags: |
| // protected |
| |
| // Override ValidationTextBox.regExpGen().... we use a reg-ex generating function rather |
| // than a straight regexp to deal with locale (plus formatting options too?) |
| regExpGen: dojo.number.regexp, |
| |
| /*===== |
| // constraints: dijit.form.NumberTextBox.__Constraints |
| // Despite the name, this parameter specifies both constraints on the input |
| // (including minimum/maximum allowed values) as well as |
| // formatting options like places (the number of digits to display after |
| // the decimal point). See `dijit.form.NumberTextBox.__Constraints` for details. |
| constraints: {}, |
| ======*/ |
| |
| // value: Number |
| // The value of this NumberTextBox as a javascript Number (ie, not a String). |
| // If the displayed value is blank, the value is NaN, and if the user types in |
| // an gibberish value (like "hello world"), the value is undefined |
| // (i.e. attr('value') returns undefined). |
| // |
| // Symetrically, attr('value', NaN) will clear the displayed value, |
| // whereas attr('value', undefined) will have no effect. |
| value: NaN, |
| |
| // editOptions: [protected] Object |
| // Properties to mix into constraints when the value is being edited. |
| // This is here because we edit the number in the format "12345", which is |
| // different than the display value (ex: "12,345") |
| editOptions: { pattern: '#.######' }, |
| |
| /*===== |
| _formatter: function(value, options){ |
| // summary: |
| // _formatter() is called by format(). It's the base routine for formatting a number, |
| // as a string, for example converting 12345 into "12,345". |
| // value: Number |
| // The number to be converted into a string. |
| // options: dojo.number.__FormatOptions? |
| // Formatting options |
| // tags: |
| // protected extension |
| |
| return "12345"; // String |
| }, |
| =====*/ |
| _formatter: dojo.number.format, |
| |
| postMixInProperties: function(){ |
| var places = typeof this.constraints.places == "number"? this.constraints.places : 0; |
| if(places){ places++; } // decimal rounding errors take away another digit of precision |
| if(typeof this.constraints.max != "number"){ |
| this.constraints.max = 9 * Math.pow(10, 15-places); |
| } |
| if(typeof this.constraints.min != "number"){ |
| this.constraints.min = -9 * Math.pow(10, 15-places); |
| } |
| this.inherited(arguments); |
| }, |
| |
| _onFocus: function(){ |
| if(this.disabled){ return; } |
| var val = this.attr('value'); |
| if(typeof val == "number" && !isNaN(val)){ |
| var formattedValue = this.format(val, this.constraints); |
| if(formattedValue !== undefined){ |
| this.textbox.value = formattedValue; |
| } |
| } |
| this.inherited(arguments); |
| }, |
| |
| format: function(/*Number*/ value, /*dojo.number.__FormatOptions*/ constraints){ |
| // summary: |
| // Formats the value as a Number, according to constraints. |
| // tags: |
| // protected |
| |
| if(typeof value != "number"){ return String(value); } |
| if(isNaN(value)){ return ""; } |
| if(("rangeCheck" in this) && !this.rangeCheck(value, constraints)){ return String(value) } |
| if(this.editOptions && this._focused){ |
| constraints = dojo.mixin({}, constraints, this.editOptions); |
| } |
| return this._formatter(value, constraints); |
| }, |
| |
| /*===== |
| parse: function(value, constraints){ |
| // summary: |
| // Parses the string value as a Number, according to constraints. |
| // value: String |
| // String representing a number |
| // constraints: dojo.number.__ParseOptions |
| // Formatting options |
| // tags: |
| // protected |
| |
| return 123.45; // Number |
| }, |
| =====*/ |
| parse: dojo.number.parse, |
| |
| _getDisplayedValueAttr: function(){ |
| var v = this.inherited(arguments); |
| return isNaN(v) ? this.textbox.value : v; |
| }, |
| |
| filter: function(/*Number*/ value){ |
| // summary: |
| // This is called with both the display value (string), and the actual value (a number). |
| // When called with the actual value it does corrections so that '' etc. are represented as NaN. |
| // Otherwise it dispatches to the superclass's filter() method. |
| // |
| // See `dijit.form.TextBox.filter` for more details. |
| return (value === null || value === '' || value === undefined) ? NaN : this.inherited(arguments); // attr('value', null||''||undefined) should fire onChange(NaN) |
| }, |
| |
| serialize: function(/*Number*/ value, /*Object?*/options){ |
| // summary: |
| // Convert value (a Number) into a canonical string (ie, how the number literal is written in javascript/java/C/etc.) |
| // tags: |
| // protected |
| return (typeof value != "number" || isNaN(value)) ? '' : this.inherited(arguments); |
| }, |
| |
| _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange, /*String?*/formattedValue){ |
| // summary: |
| // Hook so attr('value', ...) works. |
| if(value !== undefined && formattedValue === undefined){ |
| if(typeof value == "number"){ |
| if(isNaN(value)){ formattedValue = '' } |
| else if(("rangeCheck" in this) && !this.rangeCheck(value, this.constraints)){ |
| formattedValue = String(value); |
| } |
| }else if(!value){ // 0 processed in if branch above, ''|null|undefined flow thru here |
| formattedValue = ''; |
| value = NaN; |
| }else{ // non-numeric values |
| formattedValue = String(value); |
| value = undefined; |
| } |
| } |
| this.inherited(arguments, [value, priorityChange, formattedValue]); |
| }, |
| |
| _getValueAttr: function(){ |
| // summary: |
| // Hook so attr('value') works. |
| // Returns Number, NaN for '', or undefined for unparsable text |
| var v = this.inherited(arguments); // returns Number for all values accepted by parse() or NaN for all other displayed values |
| |
| // If the displayed value of the textbox is gibberish (ex: "hello world"), this.inherited() above |
| // returns NaN; this if() branch converts the return value to undefined. |
| // Returning undefined prevents user text from being overwritten when doing _setValueAttr(_getValueAttr()). |
| // A blank displayed value is still returned as NaN. |
| if(isNaN(v) && this.textbox.value !== ''){ |
| if(this.constraints.exponent !== false && /\de[-+]?|\d/i.test(this.textbox.value) && (new RegExp("^"+dojo.number._realNumberRegexp(dojo.mixin({}, this.constraints))+"$").test(this.textbox.value))){ // check for exponential notation that parse() rejected (erroneously?) |
| var n = Number(this.textbox.value); |
| return isNaN(n) ? undefined : n; // return exponential Number or undefined for random text (may not be possible to do with the above RegExp check) |
| }else{ |
| return undefined; // gibberish |
| } |
| }else{ |
| return v; // Number or NaN for '' |
| } |
| }, |
| |
| isValid: function(/*Boolean*/ isFocused){ |
| // Overrides dijit.form.RangeBoundTextBox.isValid to check that the editing-mode value is valid since |
| // it may not be formatted according to the regExp vaidation rules |
| if(!this._focused || this._isEmpty(this.textbox.value)){ |
| return this.inherited(arguments); |
| }else{ |
| var v = this.attr('value'); |
| if(!isNaN(v) && this.rangeCheck(v, this.constraints)){ |
| if(this.constraints.exponent !== false && /\de[-+]?\d/i.test(this.textbox.value)){ // exponential, parse doesn't like it |
| return true; // valid exponential number in range |
| }else{ |
| return this.inherited(arguments); |
| } |
| }else{ |
| return false; |
| } |
| } |
| } |
| } |
| ); |
| |
| dojo.declare("dijit.form.NumberTextBox", |
| [dijit.form.RangeBoundTextBox,dijit.form.NumberTextBoxMixin], |
| { |
| // summary: |
| // A TextBox for entering numbers, with formatting and range checking |
| // description: |
| // NumberTextBox is a textbox for entering and displaying numbers, supporting |
| // the following main features: |
| // |
| // 1. Enforce minimum/maximum allowed values (as well as enforcing that the user types |
| // a number rather than a random string) |
| // 2. NLS support (altering roles of comma and dot as "thousands-separator" and "decimal-point" |
| // depending on locale). |
| // 3. Separate modes for editing the value and displaying it, specifically that |
| // the thousands separator character (typically comma) disappears when editing |
| // but reappears after the field is blurred. |
| // 4. Formatting and constraints regarding the number of places (digits after the decimal point) |
| // allowed on input, and number of places displayed when blurred (see `constraints` parameter). |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.CurrencyTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.CurrencyTextBox"] = true; |
| dojo.provide("dijit.form.CurrencyTextBox"); |
| |
| |
| |
| |
| /*===== |
| dojo.declare( |
| "dijit.form.CurrencyTextBox.__Constraints", |
| [dijit.form.NumberTextBox.__Constraints, dojo.currency.__FormatOptions, dojo.currency.__ParseOptions], { |
| // summary: |
| // Specifies both the rules on valid/invalid values (minimum, maximum, |
| // number of required decimal places), and also formatting options for |
| // displaying the value when the field is not focused (currency symbol, |
| // etc.) |
| // description: |
| // Follows the pattern of `dijit.form.NumberTextBox.constraints`. |
| // In general developers won't need to set this parameter |
| // example: |
| // To ensure that the user types in the cents (for example, 1.00 instead of just 1): |
| // | {fractional:true} |
| }); |
| =====*/ |
| |
| dojo.declare( |
| "dijit.form.CurrencyTextBox", |
| dijit.form.NumberTextBox, |
| { |
| // summary: |
| // A validating currency textbox |
| // description: |
| // CurrencyTextBox is similar to `dijit.form.NumberTextBox` but has a few |
| // extra features related to currency: |
| // |
| // 1. After specifying the currency type (american dollars, euros, etc.) it automatically |
| // sets parse/format options such as how many decimal places to show. |
| // 2. The currency mark (dollar sign, euro mark, etc.) is displayed when the field is blurred |
| // but erased during editing, so that the user can just enter a plain number. |
| |
| // currency: String |
| // the [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code, a three letter sequence like "USD" |
| currency: "", |
| |
| // constraints: dijit.form.CurrencyTextBox.__Constraints |
| // Despite the name, this parameter specifies both constraints on the input |
| // (including minimum/maximum allowed values) as well as |
| // formatting options. See `dijit.form.CurrencyTextBox.__Constraints` for details. |
| /*===== |
| constraints: {}, |
| ======*/ |
| |
| // Override regExpGen ValidationTextBox.regExpGen().... we use a reg-ex generating function rather |
| // than a straight regexp to deal with locale (plus formatting options too?) |
| regExpGen: function(constraints){ |
| // if focused, accept either currency data or NumberTextBox format |
| return '(' + (this._focused? this.inherited(arguments, [ dojo.mixin({}, constraints, this.editOptions) ]) + '|' : '') |
| + dojo.currency.regexp(constraints) + ')'; |
| }, |
| |
| // Override NumberTextBox._formatter to deal with currencies, ex: converts "123.45" to "$123.45" |
| _formatter: dojo.currency.format, |
| |
| parse: function(/* String */ value, /* Object */ constraints){ |
| // summary: |
| // Parses string value as a Currency, according to the constraints object |
| // tags: |
| // protected extension |
| var v = dojo.currency.parse(value, constraints); |
| if(isNaN(v) && /\d+/.test(value)){ // currency parse failed, but it could be because they are using NumberTextBox format so try its parse |
| return this.inherited(arguments, [ value, dojo.mixin({}, constraints, this.editOptions) ]); |
| } |
| return v; |
| }, |
| |
| |
| postMixInProperties: function(){ |
| this.constraints = dojo.currency._mixInDefaults(dojo.mixin(this.constraints, { currency: this.currency, exponent: false })); // get places |
| this.inherited(arguments); |
| } |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dojo.cldr.supplemental"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.cldr.supplemental"] = true; |
| dojo.provide("dojo.cldr.supplemental"); |
| |
| |
| |
| dojo.cldr.supplemental.getFirstDayOfWeek = function(/*String?*/locale){ |
| // summary: Returns a zero-based index for first day of the week |
| // description: |
| // Returns a zero-based index for first day of the week, as used by the local (Gregorian) calendar. |
| // e.g. Sunday (returns 0), or Monday (returns 1) |
| |
| // from http://www.unicode.org/cldr/data/common/supplemental/supplementalData.xml:supplementalData/weekData/firstDay |
| var firstDay = {/*default is 1=Monday*/ |
| mv:5, |
| ae:6,af:6,bh:6,dj:6,dz:6,eg:6,er:6,et:6,iq:6,ir:6,jo:6,ke:6,kw:6,lb:6,ly:6,ma:6,om:6,qa:6,sa:6, |
| sd:6,so:6,tn:6,ye:6, |
| as:0,au:0,az:0,bw:0,ca:0,cn:0,fo:0,ge:0,gl:0,gu:0,hk:0,ie:0,il:0,is:0,jm:0,jp:0,kg:0,kr:0,la:0, |
| mh:0,mo:0,mp:0,mt:0,nz:0,ph:0,pk:0,sg:0,th:0,tt:0,tw:0,um:0,us:0,uz:0,vi:0,za:0,zw:0, |
| et:0,mw:0,ng:0,tj:0, |
| // variant. do not use? gb:0, |
| sy:4 |
| }; |
| |
| var country = dojo.cldr.supplemental._region(locale); |
| var dow = firstDay[country]; |
| return (dow === undefined) ? 1 : dow; /*Number*/ |
| }; |
| |
| dojo.cldr.supplemental._region = function(/*String?*/locale){ |
| locale = dojo.i18n.normalizeLocale(locale); |
| var tags = locale.split('-'); |
| var region = tags[1]; |
| if(!region){ |
| // IE often gives language only (#2269) |
| // Arbitrary mappings of language-only locales to a country: |
| region = {de:"de", en:"us", es:"es", fi:"fi", fr:"fr", he:"il", hu:"hu", it:"it", |
| ja:"jp", ko:"kr", nl:"nl", pt:"br", sv:"se", zh:"cn"}[tags[0]]; |
| }else if(region.length == 4){ |
| // The ISO 3166 country code is usually in the second position, unless a |
| // 4-letter script is given. See http://www.ietf.org/rfc/rfc4646.txt |
| region = tags[2]; |
| } |
| return region; |
| } |
| |
| dojo.cldr.supplemental.getWeekend = function(/*String?*/locale){ |
| // summary: Returns a hash containing the start and end days of the weekend |
| // description: |
| // Returns a hash containing the start and end days of the weekend according to local custom using locale, |
| // or by default in the user's locale. |
| // e.g. {start:6, end:0} |
| |
| // from http://www.unicode.org/cldr/data/common/supplemental/supplementalData.xml:supplementalData/weekData/weekend{Start,End} |
| var weekendStart = {/*default is 6=Saturday*/ |
| eg:5,il:5,sy:5, |
| 'in':0, |
| ae:4,bh:4,dz:4,iq:4,jo:4,kw:4,lb:4,ly:4,ma:4,om:4,qa:4,sa:4,sd:4,tn:4,ye:4 |
| }; |
| |
| var weekendEnd = {/*default is 0=Sunday*/ |
| ae:5,bh:5,dz:5,iq:5,jo:5,kw:5,lb:5,ly:5,ma:5,om:5,qa:5,sa:5,sd:5,tn:5,ye:5,af:5,ir:5, |
| eg:6,il:6,sy:6 |
| }; |
| |
| var country = dojo.cldr.supplemental._region(locale); |
| var start = weekendStart[country]; |
| var end = weekendEnd[country]; |
| if(start === undefined){start=6;} |
| if(end === undefined){end=0;} |
| return {start:start, end:end}; /*Object {start,end}*/ |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dojo.date"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.date"] = true; |
| dojo.provide("dojo.date"); |
| |
| /*===== |
| dojo.date = { |
| // summary: Date manipulation utilities |
| } |
| =====*/ |
| |
| dojo.date.getDaysInMonth = function(/*Date*/dateObject){ |
| // summary: |
| // Returns the number of days in the month used by dateObject |
| var month = dateObject.getMonth(); |
| var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; |
| if(month == 1 && dojo.date.isLeapYear(dateObject)){ return 29; } // Number |
| return days[month]; // Number |
| } |
| |
| dojo.date.isLeapYear = function(/*Date*/dateObject){ |
| // summary: |
| // Determines if the year of the dateObject is a leap year |
| // description: |
| // Leap years are years with an additional day YYYY-02-29, where the |
| // year number is a multiple of four with the following exception: If |
| // a year is a multiple of 100, then it is only a leap year if it is |
| // also a multiple of 400. For example, 1900 was not a leap year, but |
| // 2000 is one. |
| |
| var year = dateObject.getFullYear(); |
| return !(year%400) || (!(year%4) && !!(year%100)); // Boolean |
| } |
| |
| // FIXME: This is not localized |
| dojo.date.getTimezoneName = function(/*Date*/dateObject){ |
| // summary: |
| // Get the user's time zone as provided by the browser |
| // dateObject: |
| // Needed because the timezone may vary with time (daylight savings) |
| // description: |
| // Try to get time zone info from toString or toLocaleString method of |
| // the Date object -- UTC offset is not a time zone. See |
| // http://www.twinsun.com/tz/tz-link.htm Note: results may be |
| // inconsistent across browsers. |
| |
| var str = dateObject.toString(); // Start looking in toString |
| var tz = ''; // The result -- return empty string if nothing found |
| var match; |
| |
| // First look for something in parentheses -- fast lookup, no regex |
| var pos = str.indexOf('('); |
| if(pos > -1){ |
| tz = str.substring(++pos, str.indexOf(')')); |
| }else{ |
| // If at first you don't succeed ... |
| // If IE knows about the TZ, it appears before the year |
| // Capital letters or slash before a 4-digit year |
| // at the end of string |
| var pat = /([A-Z\/]+) \d{4}$/; |
| if((match = str.match(pat))){ |
| tz = match[1]; |
| }else{ |
| // Some browsers (e.g. Safari) glue the TZ on the end |
| // of toLocaleString instead of putting it in toString |
| str = dateObject.toLocaleString(); |
| // Capital letters or slash -- end of string, |
| // after space |
| pat = / ([A-Z\/]+)$/; |
| if((match = str.match(pat))){ |
| tz = match[1]; |
| } |
| } |
| } |
| |
| // Make sure it doesn't somehow end up return AM or PM |
| return (tz == 'AM' || tz == 'PM') ? '' : tz; // String |
| } |
| |
| // Utility methods to do arithmetic calculations with Dates |
| |
| dojo.date.compare = function(/*Date*/date1, /*Date?*/date2, /*String?*/portion){ |
| // summary: |
| // Compare two date objects by date, time, or both. |
| // description: |
| // Returns 0 if equal, positive if a > b, else negative. |
| // date1: |
| // Date object |
| // date2: |
| // Date object. If not specified, the current Date is used. |
| // portion: |
| // A string indicating the "date" or "time" portion of a Date object. |
| // Compares both "date" and "time" by default. One of the following: |
| // "date", "time", "datetime" |
| |
| // Extra step required in copy for IE - see #3112 |
| date1 = new Date(+date1); |
| date2 = new Date(+(date2 || new Date())); |
| |
| if(portion == "date"){ |
| // Ignore times and compare dates. |
| date1.setHours(0, 0, 0, 0); |
| date2.setHours(0, 0, 0, 0); |
| }else if(portion == "time"){ |
| // Ignore dates and compare times. |
| date1.setFullYear(0, 0, 0); |
| date2.setFullYear(0, 0, 0); |
| } |
| |
| if(date1 > date2){ return 1; } // int |
| if(date1 < date2){ return -1; } // int |
| return 0; // int |
| }; |
| |
| dojo.date.add = function(/*Date*/date, /*String*/interval, /*int*/amount){ |
| // summary: |
| // Add to a Date in intervals of different size, from milliseconds to years |
| // date: Date |
| // Date object to start with |
| // interval: |
| // A string representing the interval. One of the following: |
| // "year", "month", "day", "hour", "minute", "second", |
| // "millisecond", "quarter", "week", "weekday" |
| // amount: |
| // How much to add to the date. |
| |
| var sum = new Date(+date); // convert to Number before copying to accomodate IE (#3112) |
| var fixOvershoot = false; |
| var property = "Date"; |
| |
| switch(interval){ |
| case "day": |
| break; |
| case "weekday": |
| //i18n FIXME: assumes Saturday/Sunday weekend, but this is not always true. see dojo.cldr.supplemental |
| |
| // Divide the increment time span into weekspans plus leftover days |
| // e.g., 8 days is one 5-day weekspan / and two leftover days |
| // Can't have zero leftover days, so numbers divisible by 5 get |
| // a days value of 5, and the remaining days make up the number of weeks |
| var days, weeks; |
| var mod = amount % 5; |
| if(!mod){ |
| days = (amount > 0) ? 5 : -5; |
| weeks = (amount > 0) ? ((amount-5)/5) : ((amount+5)/5); |
| }else{ |
| days = mod; |
| weeks = parseInt(amount/5); |
| } |
| // Get weekday value for orig date param |
| var strt = date.getDay(); |
| // Orig date is Sat / positive incrementer |
| // Jump over Sun |
| var adj = 0; |
| if(strt == 6 && amount > 0){ |
| adj = 1; |
| }else if(strt == 0 && amount < 0){ |
| // Orig date is Sun / negative incrementer |
| // Jump back over Sat |
| adj = -1; |
| } |
| // Get weekday val for the new date |
| var trgt = strt + days; |
| // New date is on Sat or Sun |
| if(trgt == 0 || trgt == 6){ |
| adj = (amount > 0) ? 2 : -2; |
| } |
| // Increment by number of weeks plus leftover days plus |
| // weekend adjustments |
| amount = (7 * weeks) + days + adj; |
| break; |
| case "year": |
| property = "FullYear"; |
| // Keep increment/decrement from 2/29 out of March |
| fixOvershoot = true; |
| break; |
| case "week": |
| amount *= 7; |
| break; |
| case "quarter": |
| // Naive quarter is just three months |
| amount *= 3; |
| // fallthrough... |
| case "month": |
| // Reset to last day of month if you overshoot |
| fixOvershoot = true; |
| property = "Month"; |
| break; |
| // case "hour": |
| // case "minute": |
| // case "second": |
| // case "millisecond": |
| default: |
| property = "UTC"+interval.charAt(0).toUpperCase() + interval.substring(1) + "s"; |
| } |
| |
| if(property){ |
| sum["set"+property](sum["get"+property]()+amount); |
| } |
| |
| if(fixOvershoot && (sum.getDate() < date.getDate())){ |
| sum.setDate(0); |
| } |
| |
| return sum; // Date |
| }; |
| |
| dojo.date.difference = function(/*Date*/date1, /*Date?*/date2, /*String?*/interval){ |
| // summary: |
| // Get the difference in a specific unit of time (e.g., number of |
| // months, weeks, days, etc.) between two dates, rounded to the |
| // nearest integer. |
| // date1: |
| // Date object |
| // date2: |
| // Date object. If not specified, the current Date is used. |
| // interval: |
| // A string representing the interval. One of the following: |
| // "year", "month", "day", "hour", "minute", "second", |
| // "millisecond", "quarter", "week", "weekday" |
| // Defaults to "day". |
| |
| date2 = date2 || new Date(); |
| interval = interval || "day"; |
| var yearDiff = date2.getFullYear() - date1.getFullYear(); |
| var delta = 1; // Integer return value |
| |
| switch(interval){ |
| case "quarter": |
| var m1 = date1.getMonth(); |
| var m2 = date2.getMonth(); |
| // Figure out which quarter the months are in |
| var q1 = Math.floor(m1/3) + 1; |
| var q2 = Math.floor(m2/3) + 1; |
| // Add quarters for any year difference between the dates |
| q2 += (yearDiff * 4); |
| delta = q2 - q1; |
| break; |
| case "weekday": |
| var days = Math.round(dojo.date.difference(date1, date2, "day")); |
| var weeks = parseInt(dojo.date.difference(date1, date2, "week")); |
| var mod = days % 7; |
| |
| // Even number of weeks |
| if(mod == 0){ |
| days = weeks*5; |
| }else{ |
| // Weeks plus spare change (< 7 days) |
| var adj = 0; |
| var aDay = date1.getDay(); |
| var bDay = date2.getDay(); |
| |
| weeks = parseInt(days/7); |
| mod = days % 7; |
| // Mark the date advanced by the number of |
| // round weeks (may be zero) |
| var dtMark = new Date(date1); |
| dtMark.setDate(dtMark.getDate()+(weeks*7)); |
| var dayMark = dtMark.getDay(); |
| |
| // Spare change days -- 6 or less |
| if(days > 0){ |
| switch(true){ |
| // Range starts on Sat |
| case aDay == 6: |
| adj = -1; |
| break; |
| // Range starts on Sun |
| case aDay == 0: |
| adj = 0; |
| break; |
| // Range ends on Sat |
| case bDay == 6: |
| adj = -1; |
| break; |
| // Range ends on Sun |
| case bDay == 0: |
| adj = -2; |
| break; |
| // Range contains weekend |
| case (dayMark + mod) > 5: |
| adj = -2; |
| } |
| }else if(days < 0){ |
| switch(true){ |
| // Range starts on Sat |
| case aDay == 6: |
| adj = 0; |
| break; |
| // Range starts on Sun |
| case aDay == 0: |
| adj = 1; |
| break; |
| // Range ends on Sat |
| case bDay == 6: |
| adj = 2; |
| break; |
| // Range ends on Sun |
| case bDay == 0: |
| adj = 1; |
| break; |
| // Range contains weekend |
| case (dayMark + mod) < 0: |
| adj = 2; |
| } |
| } |
| days += adj; |
| days -= (weeks*2); |
| } |
| delta = days; |
| break; |
| case "year": |
| delta = yearDiff; |
| break; |
| case "month": |
| delta = (date2.getMonth() - date1.getMonth()) + (yearDiff * 12); |
| break; |
| case "week": |
| // Truncate instead of rounding |
| // Don't use Math.floor -- value may be negative |
| delta = parseInt(dojo.date.difference(date1, date2, "day")/7); |
| break; |
| case "day": |
| delta /= 24; |
| // fallthrough |
| case "hour": |
| delta /= 60; |
| // fallthrough |
| case "minute": |
| delta /= 60; |
| // fallthrough |
| case "second": |
| delta /= 1000; |
| // fallthrough |
| case "millisecond": |
| delta *= date2.getTime() - date1.getTime(); |
| } |
| |
| // Round for fractional values and DST leaps |
| return Math.round(delta); // Number (integer) |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dojo.date.locale"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.date.locale"] = true; |
| dojo.provide("dojo.date.locale"); |
| |
| // Localization methods for Date. Honor local customs using locale-dependent dojo.cldr data. |
| |
| |
| |
| |
| |
| |
| |
| // Load the bundles containing localization information for |
| // names and formats |
| |
| |
| //NOTE: Everything in this module assumes Gregorian calendars. |
| // Other calendars will be implemented in separate modules. |
| |
| (function(){ |
| // Format a pattern without literals |
| function formatPattern(dateObject, bundle, options, pattern){ |
| return pattern.replace(/([a-z])\1*/ig, function(match){ |
| var s, pad, |
| c = match.charAt(0), |
| l = match.length, |
| widthList = ["abbr", "wide", "narrow"]; |
| switch(c){ |
| case 'G': |
| s = bundle[(l < 4) ? "eraAbbr" : "eraNames"][dateObject.getFullYear() < 0 ? 0 : 1]; |
| break; |
| case 'y': |
| s = dateObject.getFullYear(); |
| switch(l){ |
| case 1: |
| break; |
| case 2: |
| if(!options.fullYear){ |
| s = String(s); s = s.substr(s.length - 2); |
| break; |
| } |
| // fallthrough |
| default: |
| pad = true; |
| } |
| break; |
| case 'Q': |
| case 'q': |
| s = Math.ceil((dateObject.getMonth()+1)/3); |
| // switch(l){ |
| // case 1: case 2: |
| pad = true; |
| // break; |
| // case 3: case 4: // unimplemented |
| // } |
| break; |
| case 'M': |
| var m = dateObject.getMonth(); |
| if(l<3){ |
| s = m+1; pad = true; |
| }else{ |
| var propM = ["months", "format", widthList[l-3]].join("-"); |
| s = bundle[propM][m]; |
| } |
| break; |
| case 'w': |
| var firstDay = 0; |
| s = dojo.date.locale._getWeekOfYear(dateObject, firstDay); pad = true; |
| break; |
| case 'd': |
| s = dateObject.getDate(); pad = true; |
| break; |
| case 'D': |
| s = dojo.date.locale._getDayOfYear(dateObject); pad = true; |
| break; |
| case 'E': |
| var d = dateObject.getDay(); |
| if(l<3){ |
| s = d+1; pad = true; |
| }else{ |
| var propD = ["days", "format", widthList[l-3]].join("-"); |
| s = bundle[propD][d]; |
| } |
| break; |
| case 'a': |
| var timePeriod = (dateObject.getHours() < 12) ? 'am' : 'pm'; |
| s = bundle[timePeriod]; |
| break; |
| case 'h': |
| case 'H': |
| case 'K': |
| case 'k': |
| var h = dateObject.getHours(); |
| // strange choices in the date format make it impossible to write this succinctly |
| switch (c){ |
| case 'h': // 1-12 |
| s = (h % 12) || 12; |
| break; |
| case 'H': // 0-23 |
| s = h; |
| break; |
| case 'K': // 0-11 |
| s = (h % 12); |
| break; |
| case 'k': // 1-24 |
| s = h || 24; |
| break; |
| } |
| pad = true; |
| break; |
| case 'm': |
| s = dateObject.getMinutes(); pad = true; |
| break; |
| case 's': |
| s = dateObject.getSeconds(); pad = true; |
| break; |
| case 'S': |
| s = Math.round(dateObject.getMilliseconds() * Math.pow(10, l-3)); pad = true; |
| break; |
| case 'v': // FIXME: don't know what this is. seems to be same as z? |
| case 'z': |
| // We only have one timezone to offer; the one from the browser |
| s = dojo.date.locale._getZone(dateObject, true, options); |
| if(s){break;} |
| l=4; |
| // fallthrough... use GMT if tz not available |
| case 'Z': |
| var offset = dojo.date.locale._getZone(dateObject, false, options); |
| var tz = [ |
| (offset<=0 ? "+" : "-"), |
| dojo.string.pad(Math.floor(Math.abs(offset)/60), 2), |
| dojo.string.pad(Math.abs(offset)% 60, 2) |
| ]; |
| if(l==4){ |
| tz.splice(0, 0, "GMT"); |
| tz.splice(3, 0, ":"); |
| } |
| s = tz.join(""); |
| break; |
| // case 'Y': case 'u': case 'W': case 'F': case 'g': case 'A': case 'e': |
| // console.log(match+" modifier unimplemented"); |
| default: |
| throw new Error("dojo.date.locale.format: invalid pattern char: "+pattern); |
| } |
| if(pad){ s = dojo.string.pad(s, l); } |
| return s; |
| }); |
| } |
| |
| /*===== |
| dojo.date.locale.__FormatOptions = function(){ |
| // selector: String |
| // choice of 'time','date' (default: date and time) |
| // formatLength: String |
| // choice of long, short, medium or full (plus any custom additions). Defaults to 'short' |
| // datePattern:String |
| // override pattern with this string |
| // timePattern:String |
| // override pattern with this string |
| // am: String |
| // override strings for am in times |
| // pm: String |
| // override strings for pm in times |
| // locale: String |
| // override the locale used to determine formatting rules |
| // fullYear: Boolean |
| // (format only) use 4 digit years whenever 2 digit years are called for |
| // strict: Boolean |
| // (parse only) strict parsing, off by default |
| this.selector = selector; |
| this.formatLength = formatLength; |
| this.datePattern = datePattern; |
| this.timePattern = timePattern; |
| this.am = am; |
| this.pm = pm; |
| this.locale = locale; |
| this.fullYear = fullYear; |
| this.strict = strict; |
| } |
| =====*/ |
| |
| dojo.date.locale._getZone = function(/*Date*/dateObject, /*boolean*/getName, /*dojo.date.locale.__FormatOptions?*/options){ |
| // summary: |
| // Returns the zone (or offset) for the given date and options. This |
| // is broken out into a separate function so that it can be overridden |
| // by timezone-aware code. |
| // |
| // dateObject: |
| // the date and/or time being formatted. |
| // |
| // getName: |
| // Whether to return the timezone string (if true), or the offset (if false) |
| // |
| // options: |
| // The options being used for formatting |
| if(getName){ |
| return dojo.date.getTimezoneName(dateObject); |
| }else{ |
| return dateObject.getTimezoneOffset(); |
| } |
| }; |
| |
| |
| dojo.date.locale.format = function(/*Date*/dateObject, /*dojo.date.locale.__FormatOptions?*/options){ |
| // summary: |
| // Format a Date object as a String, using locale-specific settings. |
| // |
| // description: |
| // Create a string from a Date object using a known localized pattern. |
| // By default, this method formats both date and time from dateObject. |
| // Formatting patterns are chosen appropriate to the locale. Different |
| // formatting lengths may be chosen, with "full" used by default. |
| // Custom patterns may be used or registered with translations using |
| // the dojo.date.locale.addCustomFormats method. |
| // Formatting patterns are implemented using [the syntax described at |
| // unicode.org](http://www.unicode.org/reports/tr35/tr35-4.html#Date_Format_Patterns) |
| // |
| // dateObject: |
| // the date and/or time to be formatted. If a time only is formatted, |
| // the values in the year, month, and day fields are irrelevant. The |
| // opposite is true when formatting only dates. |
| |
| options = options || {}; |
| |
| var locale = dojo.i18n.normalizeLocale(options.locale), |
| formatLength = options.formatLength || 'short', |
| bundle = dojo.date.locale._getGregorianBundle(locale), |
| str = [], |
| sauce = dojo.hitch(this, formatPattern, dateObject, bundle, options); |
| if(options.selector == "year"){ |
| return _processPattern(bundle["dateFormatItem-yyyy"] || "yyyy", sauce); |
| } |
| var pattern; |
| if(options.selector != "date"){ |
| pattern = options.timePattern || bundle["timeFormat-"+formatLength]; |
| if(pattern){str.push(_processPattern(pattern, sauce));} |
| } |
| if(options.selector != "time"){ |
| pattern = options.datePattern || bundle["dateFormat-"+formatLength]; |
| if(pattern){str.push(_processPattern(pattern, sauce));} |
| } |
| |
| return str.length == 1 ? str[0] : bundle["dateTimeFormat-"+formatLength].replace(/\{(\d+)\}/g, |
| function(match, key){ return str[key]; }); // String |
| }; |
| |
| dojo.date.locale.regexp = function(/*dojo.date.locale.__FormatOptions?*/options){ |
| // summary: |
| // Builds the regular needed to parse a localized date |
| |
| return dojo.date.locale._parseInfo(options).regexp; // String |
| }; |
| |
| dojo.date.locale._parseInfo = function(/*dojo.date.locale.__FormatOptions?*/options){ |
| options = options || {}; |
| var locale = dojo.i18n.normalizeLocale(options.locale), |
| bundle = dojo.date.locale._getGregorianBundle(locale), |
| formatLength = options.formatLength || 'short', |
| datePattern = options.datePattern || bundle["dateFormat-" + formatLength], |
| timePattern = options.timePattern || bundle["timeFormat-" + formatLength], |
| pattern; |
| if(options.selector == 'date'){ |
| pattern = datePattern; |
| }else if(options.selector == 'time'){ |
| pattern = timePattern; |
| }else{ |
| pattern = bundle["dateTimeFormat-"+formatLength].replace(/\{(\d+)\}/g, |
| function(match, key){ return [timePattern, datePattern][key]; }); |
| } |
| |
| var tokens = [], |
| re = _processPattern(pattern, dojo.hitch(this, _buildDateTimeRE, tokens, bundle, options)); |
| return {regexp: re, tokens: tokens, bundle: bundle}; |
| }; |
| |
| dojo.date.locale.parse = function(/*String*/value, /*dojo.date.locale.__FormatOptions?*/options){ |
| // summary: |
| // Convert a properly formatted string to a primitive Date object, |
| // using locale-specific settings. |
| // |
| // description: |
| // Create a Date object from a string using a known localized pattern. |
| // By default, this method parses looking for both date and time in the string. |
| // Formatting patterns are chosen appropriate to the locale. Different |
| // formatting lengths may be chosen, with "full" used by default. |
| // Custom patterns may be used or registered with translations using |
| // the dojo.date.locale.addCustomFormats method. |
| // |
| // Formatting patterns are implemented using [the syntax described at |
| // unicode.org](http://www.unicode.org/reports/tr35/tr35-4.html#Date_Format_Patterns) |
| // When two digit years are used, a century is chosen according to a sliding |
| // window of 80 years before and 20 years after present year, for both `yy` and `yyyy` patterns. |
| // year < 100CE requires strict mode. |
| // |
| // value: |
| // A string representation of a date |
| |
| var info = dojo.date.locale._parseInfo(options), |
| tokens = info.tokens, bundle = info.bundle, |
| re = new RegExp("^" + info.regexp + "$", info.strict ? "" : "i"), |
| match = re.exec(value); |
| |
| if(!match){ return null; } // null |
| |
| var widthList = ['abbr', 'wide', 'narrow'], |
| result = [1970,0,1,0,0,0,0], // will get converted to a Date at the end |
| amPm = "", |
| valid = dojo.every(match, function(v, i){ |
| if(!i){return true;} |
| var token=tokens[i-1]; |
| var l=token.length; |
| switch(token.charAt(0)){ |
| case 'y': |
| if(l != 2 && options.strict){ |
| //interpret year literally, so '5' would be 5 A.D. |
| result[0] = v; |
| }else{ |
| if(v<100){ |
| v = Number(v); |
| //choose century to apply, according to a sliding window |
| //of 80 years before and 20 years after present year |
| var year = '' + new Date().getFullYear(), |
| century = year.substring(0, 2) * 100, |
| cutoff = Math.min(Number(year.substring(2, 4)) + 20, 99), |
| num = (v < cutoff) ? century + v : century - 100 + v; |
| result[0] = num; |
| }else{ |
| //we expected 2 digits and got more... |
| if(options.strict){ |
| return false; |
| } |
| //interpret literally, so '150' would be 150 A.D. |
| //also tolerate '1950', if 'yyyy' input passed to 'yy' format |
| result[0] = v; |
| } |
| } |
| break; |
| case 'M': |
| if(l>2){ |
| var months = bundle['months-format-' + widthList[l-3]].concat(); |
| if(!options.strict){ |
| //Tolerate abbreviating period in month part |
| //Case-insensitive comparison |
| v = v.replace(".","").toLowerCase(); |
| months = dojo.map(months, function(s){ return s.replace(".","").toLowerCase(); } ); |
| } |
| v = dojo.indexOf(months, v); |
| if(v == -1){ |
| // console.log("dojo.date.locale.parse: Could not parse month name: '" + v + "'."); |
| return false; |
| } |
| }else{ |
| v--; |
| } |
| result[1] = v; |
| break; |
| case 'E': |
| case 'e': |
| var days = bundle['days-format-' + widthList[l-3]].concat(); |
| if(!options.strict){ |
| //Case-insensitive comparison |
| v = v.toLowerCase(); |
| days = dojo.map(days, function(d){return d.toLowerCase();}); |
| } |
| v = dojo.indexOf(days, v); |
| if(v == -1){ |
| // console.log("dojo.date.locale.parse: Could not parse weekday name: '" + v + "'."); |
| return false; |
| } |
| |
| //TODO: not sure what to actually do with this input, |
| //in terms of setting something on the Date obj...? |
| //without more context, can't affect the actual date |
| //TODO: just validate? |
| break; |
| case 'D': |
| result[1] = 0; |
| // fallthrough... |
| case 'd': |
| result[2] = v; |
| break; |
| case 'a': //am/pm |
| var am = options.am || bundle.am; |
| var pm = options.pm || bundle.pm; |
| if(!options.strict){ |
| var period = /\./g; |
| v = v.replace(period,'').toLowerCase(); |
| am = am.replace(period,'').toLowerCase(); |
| pm = pm.replace(period,'').toLowerCase(); |
| } |
| if(options.strict && v != am && v != pm){ |
| // console.log("dojo.date.locale.parse: Could not parse am/pm part."); |
| return false; |
| } |
| |
| // we might not have seen the hours field yet, so store the state and apply hour change later |
| amPm = (v == pm) ? 'p' : (v == am) ? 'a' : ''; |
| break; |
| case 'K': //hour (1-24) |
| if(v == 24){ v = 0; } |
| // fallthrough... |
| case 'h': //hour (1-12) |
| case 'H': //hour (0-23) |
| case 'k': //hour (0-11) |
| //TODO: strict bounds checking, padding |
| if(v > 23){ |
| // console.log("dojo.date.locale.parse: Illegal hours value"); |
| return false; |
| } |
| |
| //in the 12-hour case, adjusting for am/pm requires the 'a' part |
| //which could come before or after the hour, so we will adjust later |
| result[3] = v; |
| break; |
| case 'm': //minutes |
| result[4] = v; |
| break; |
| case 's': //seconds |
| result[5] = v; |
| break; |
| case 'S': //milliseconds |
| result[6] = v; |
| // break; |
| // case 'w': |
| //TODO var firstDay = 0; |
| // default: |
| //TODO: throw? |
| // console.log("dojo.date.locale.parse: unsupported pattern char=" + token.charAt(0)); |
| } |
| return true; |
| }); |
| |
| var hours = +result[3]; |
| if(amPm === 'p' && hours < 12){ |
| result[3] = hours + 12; //e.g., 3pm -> 15 |
| }else if(amPm === 'a' && hours == 12){ |
| result[3] = 0; //12am -> 0 |
| } |
| |
| //TODO: implement a getWeekday() method in order to test |
| //validity of input strings containing 'EEE' or 'EEEE'... |
| |
| var dateObject = new Date(result[0], result[1], result[2], result[3], result[4], result[5], result[6]); // Date |
| if(options.strict){ |
| dateObject.setFullYear(result[0]); |
| } |
| |
| // Check for overflow. The Date() constructor normalizes things like April 32nd... |
| //TODO: why isn't this done for times as well? |
| var allTokens = tokens.join(""), |
| dateToken = allTokens.indexOf('d') != -1, |
| monthToken = allTokens.indexOf('M') != -1; |
| |
| if(!valid || |
| (monthToken && dateObject.getMonth() > result[1]) || |
| (dateToken && dateObject.getDate() > result[2])){ |
| return null; |
| } |
| |
| // Check for underflow, due to DST shifts. See #9366 |
| // This assumes a 1 hour dst shift correction at midnight |
| // We could compare the timezone offset after the shift and add the difference instead. |
| if((monthToken && dateObject.getMonth() < result[1]) || |
| (dateToken && dateObject.getDate() < result[2])){ |
| dateObject = dojo.date.add(dateObject, "hour", 1); |
| } |
| |
| return dateObject; // Date |
| }; |
| |
| function _processPattern(pattern, applyPattern, applyLiteral, applyAll){ |
| //summary: Process a pattern with literals in it |
| |
| // Break up on single quotes, treat every other one as a literal, except '' which becomes ' |
| var identity = function(x){return x;}; |
| applyPattern = applyPattern || identity; |
| applyLiteral = applyLiteral || identity; |
| applyAll = applyAll || identity; |
| |
| //split on single quotes (which escape literals in date format strings) |
| //but preserve escaped single quotes (e.g., o''clock) |
| var chunks = pattern.match(/(''|[^'])+/g), |
| literal = pattern.charAt(0) == "'"; |
| |
| dojo.forEach(chunks, function(chunk, i){ |
| if(!chunk){ |
| chunks[i]=''; |
| }else{ |
| chunks[i]=(literal ? applyLiteral : applyPattern)(chunk); |
| literal = !literal; |
| } |
| }); |
| return applyAll(chunks.join('')); |
| } |
| |
| function _buildDateTimeRE(tokens, bundle, options, pattern){ |
| pattern = dojo.regexp.escapeString(pattern); |
| if(!options.strict){ pattern = pattern.replace(" a", " ?a"); } // kludge to tolerate no space before am/pm |
| return pattern.replace(/([a-z])\1*/ig, function(match){ |
| // Build a simple regexp. Avoid captures, which would ruin the tokens list |
| var s, |
| c = match.charAt(0), |
| l = match.length, |
| p2 = '', p3 = ''; |
| if(options.strict){ |
| if(l > 1){ p2 = '0' + '{'+(l-1)+'}'; } |
| if(l > 2){ p3 = '0' + '{'+(l-2)+'}'; } |
| }else{ |
| p2 = '0?'; p3 = '0{0,2}'; |
| } |
| switch(c){ |
| case 'y': |
| s = '\\d{2,4}'; |
| break; |
| case 'M': |
| s = (l>2) ? '\\S+?' : p2+'[1-9]|1[0-2]'; |
| break; |
| case 'D': |
| s = p2+'[1-9]|'+p3+'[1-9][0-9]|[12][0-9][0-9]|3[0-5][0-9]|36[0-6]'; |
| break; |
| case 'd': |
| s = '[12]\\d|'+p2+'[1-9]|3[01]'; |
| break; |
| case 'w': |
| s = p2+'[1-9]|[1-4][0-9]|5[0-3]'; |
| break; |
| case 'E': |
| s = '\\S+'; |
| break; |
| case 'h': //hour (1-12) |
| s = p2+'[1-9]|1[0-2]'; |
| break; |
| case 'k': //hour (0-11) |
| s = p2+'\\d|1[01]'; |
| break; |
| case 'H': //hour (0-23) |
| s = p2+'\\d|1\\d|2[0-3]'; |
| break; |
| case 'K': //hour (1-24) |
| s = p2+'[1-9]|1\\d|2[0-4]'; |
| break; |
| case 'm': |
| case 's': |
| s = '[0-5]\\d'; |
| break; |
| case 'S': |
| s = '\\d{'+l+'}'; |
| break; |
| case 'a': |
| var am = options.am || bundle.am || 'AM'; |
| var pm = options.pm || bundle.pm || 'PM'; |
| if(options.strict){ |
| s = am + '|' + pm; |
| }else{ |
| s = am + '|' + pm; |
| if(am != am.toLowerCase()){ s += '|' + am.toLowerCase(); } |
| if(pm != pm.toLowerCase()){ s += '|' + pm.toLowerCase(); } |
| if(s.indexOf('.') != -1){ s += '|' + s.replace(/\./g, ""); } |
| } |
| s = s.replace(/\./g, "\\."); |
| break; |
| default: |
| // case 'v': |
| // case 'z': |
| // case 'Z': |
| s = ".*"; |
| // console.log("parse of date format, pattern=" + pattern); |
| } |
| |
| if(tokens){ tokens.push(match); } |
| |
| return "(" + s + ")"; // add capture |
| }).replace(/[\xa0 ]/g, "[\\s\\xa0]"); // normalize whitespace. Need explicit handling of \xa0 for IE. |
| } |
| })(); |
| |
| (function(){ |
| var _customFormats = []; |
| dojo.date.locale.addCustomFormats = function(/*String*/packageName, /*String*/bundleName){ |
| // summary: |
| // Add a reference to a bundle containing localized custom formats to be |
| // used by date/time formatting and parsing routines. |
| // |
| // description: |
| // The user may add custom localized formats where the bundle has properties following the |
| // same naming convention used by dojo.cldr: `dateFormat-xxxx` / `timeFormat-xxxx` |
| // The pattern string should match the format used by the CLDR. |
| // See dojo.date.locale.format() for details. |
| // The resources must be loaded by dojo.requireLocalization() prior to use |
| |
| _customFormats.push({pkg:packageName,name:bundleName}); |
| }; |
| |
| dojo.date.locale._getGregorianBundle = function(/*String*/locale){ |
| var gregorian = {}; |
| dojo.forEach(_customFormats, function(desc){ |
| var bundle = dojo.i18n.getLocalization(desc.pkg, desc.name, locale); |
| gregorian = dojo.mixin(gregorian, bundle); |
| }, this); |
| return gregorian; /*Object*/ |
| }; |
| })(); |
| |
| dojo.date.locale.addCustomFormats("dojo.cldr","gregorian"); |
| |
| dojo.date.locale.getNames = function(/*String*/item, /*String*/type, /*String?*/context, /*String?*/locale){ |
| // summary: |
| // Used to get localized strings from dojo.cldr for day or month names. |
| // |
| // item: |
| // 'months' || 'days' |
| // type: |
| // 'wide' || 'narrow' || 'abbr' (e.g. "Monday", "Mon", or "M" respectively, in English) |
| // context: |
| // 'standAlone' || 'format' (default) |
| // locale: |
| // override locale used to find the names |
| |
| var label, |
| lookup = dojo.date.locale._getGregorianBundle(locale), |
| props = [item, context, type]; |
| if(context == 'standAlone'){ |
| var key = props.join('-'); |
| label = lookup[key]; |
| // Fall back to 'format' flavor of name |
| if(label[0] == 1){ label = undefined; } // kludge, in the absense of real aliasing support in dojo.cldr |
| } |
| props[1] = 'format'; |
| |
| // return by copy so changes won't be made accidentally to the in-memory model |
| return (label || lookup[props.join('-')]).concat(); /*Array*/ |
| }; |
| |
| dojo.date.locale.isWeekend = function(/*Date?*/dateObject, /*String?*/locale){ |
| // summary: |
| // Determines if the date falls on a weekend, according to local custom. |
| |
| var weekend = dojo.cldr.supplemental.getWeekend(locale), |
| day = (dateObject || new Date()).getDay(); |
| if(weekend.end < weekend.start){ |
| weekend.end += 7; |
| if(day < weekend.start){ day += 7; } |
| } |
| return day >= weekend.start && day <= weekend.end; // Boolean |
| }; |
| |
| // These are used only by format and strftime. Do they need to be public? Which module should they go in? |
| |
| dojo.date.locale._getDayOfYear = function(/*Date*/dateObject){ |
| // summary: gets the day of the year as represented by dateObject |
| return dojo.date.difference(new Date(dateObject.getFullYear(), 0, 1, dateObject.getHours()), dateObject) + 1; // Number |
| }; |
| |
| dojo.date.locale._getWeekOfYear = function(/*Date*/dateObject, /*Number*/firstDayOfWeek){ |
| if(arguments.length == 1){ firstDayOfWeek = 0; } // Sunday |
| |
| var firstDayOfYear = new Date(dateObject.getFullYear(), 0, 1).getDay(), |
| adj = (firstDayOfYear - firstDayOfWeek + 7) % 7, |
| week = Math.floor((dojo.date.locale._getDayOfYear(dateObject) + adj - 1) / 7); |
| |
| // if year starts on the specified day, start counting weeks at 1 |
| if(firstDayOfYear == firstDayOfWeek){ week++; } |
| |
| return week; // Number |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dijit.Calendar"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.Calendar"] = true; |
| dojo.provide("dijit.Calendar"); |
| |
| |
| |
| |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.Calendar", |
| [dijit._Widget, dijit._Templated], |
| { |
| // summary: |
| // A simple GUI for choosing a date in the context of a monthly calendar. |
| // |
| // description: |
| // A simple GUI for choosing a date in the context of a monthly calendar. |
| // This widget can't be used in a form because it doesn't serialize the date to an |
| // `<input>` field. For a form element, use dijit.form.DateTextBox instead. |
| // |
| // Note that the parser takes all dates attributes passed in the |
| // [RFC 3339 format](http://www.faqs.org/rfcs/rfc3339.html), e.g. `2005-06-30T08:05:00-07:00` |
| // so that they are serializable and locale-independent. |
| // |
| // example: |
| // | var calendar = new dijit.Calendar({}, dojo.byId("calendarNode")); |
| // |
| // example: |
| // | <div dojoType="dijit.Calendar"></div> |
| |
| templateString: dojo.cache("dijit", "templates/Calendar.html", "<table cellspacing=\"0\" cellpadding=\"0\" class=\"dijitCalendarContainer\" role=\"grid\" dojoAttachEvent=\"onkeypress: _onKeyPress\">\n\t<thead>\n\t\t<tr class=\"dijitReset dijitCalendarMonthContainer\" valign=\"top\">\n\t\t\t<th class='dijitReset' dojoAttachPoint=\"decrementMonth\">\n\t\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitCalendarIncrementControl dijitCalendarDecrease\" waiRole=\"presentation\">\n\t\t\t\t<span dojoAttachPoint=\"decreaseArrowNode\" class=\"dijitA11ySideArrow\">-</span>\n\t\t\t</th>\n\t\t\t<th class='dijitReset' colspan=\"5\">\n\t\t\t\t<div class=\"dijitVisible\">\n\t\t\t\t\t<div class=\"dijitPopup dijitMenu dijitMenuPassive dijitHidden\" dojoAttachPoint=\"monthDropDown\" dojoAttachEvent=\"onmouseup: _onMonthSelect, onmouseover: _onMenuHover, onmouseout: _onMenuHover\">\n\t\t\t\t\t\t<div class=\"dijitCalendarMonthLabelTemplate dijitCalendarMonthLabel\"></div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div dojoAttachPoint=\"monthLabelSpacer\" class=\"dijitSpacer\"></div>\n\t\t\t\t<div dojoAttachPoint=\"monthLabelNode\" class=\"dijitCalendarMonthLabel dijitInline dijitVisible\" dojoAttachEvent=\"onmousedown: _onMonthToggle\"></div>\n\t\t\t</th>\n\t\t\t<th class='dijitReset' dojoAttachPoint=\"incrementMonth\">\n\t\t\t\t<img src=\"${_blankGif}\" alt=\"\" class=\"dijitCalendarIncrementControl dijitCalendarIncrease\" waiRole=\"presentation\">\n\t\t\t\t<span dojoAttachPoint=\"increaseArrowNode\" class=\"dijitA11ySideArrow\">+</span>\n\t\t\t</th>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<th class=\"dijitReset dijitCalendarDayLabelTemplate\" role=\"columnheader\"><span class=\"dijitCalendarDayLabel\"></span></th>\n\t\t</tr>\n\t</thead>\n\t<tbody dojoAttachEvent=\"onclick: _onDayClick, onmouseover: _onDayMouseOver, onmouseout: _onDayMouseOut\" class=\"dijitReset dijitCalendarBodyContainer\">\n\t\t<tr class=\"dijitReset dijitCalendarWeekTemplate\" role=\"row\">\n\t\t\t<td class=\"dijitReset dijitCalendarDateTemplate\" role=\"gridcell\"><span class=\"dijitCalendarDateLabel\"></span></td>\n\t\t</tr>\n\t</tbody>\n\t<tfoot class=\"dijitReset dijitCalendarYearContainer\">\n\t\t<tr>\n\t\t\t<td class='dijitReset' valign=\"top\" colspan=\"7\">\n\t\t\t\t<h3 class=\"dijitCalendarYearLabel\">\n\t\t\t\t\t<span dojoAttachPoint=\"previousYearLabelNode\" class=\"dijitInline dijitCalendarPreviousYear\"></span>\n\t\t\t\t\t<span dojoAttachPoint=\"currentYearLabelNode\" class=\"dijitInline dijitCalendarSelectedYear\"></span>\n\t\t\t\t\t<span dojoAttachPoint=\"nextYearLabelNode\" class=\"dijitInline dijitCalendarNextYear\"></span>\n\t\t\t\t</h3>\n\t\t\t</td>\n\t\t</tr>\n\t</tfoot>\n</table>\n"), |
| |
| // value: Date |
| // The currently selected Date |
| value: new Date(), |
| |
| // datePackage: String |
| // JavaScript namespace to find Calendar routines. Uses Gregorian Calendar routines |
| // at dojo.date by default. |
| datePackage: "dojo.date", |
| |
| // dayWidth: String |
| // How to represent the days of the week in the calendar header. See dojo.date.locale |
| dayWidth: "narrow", |
| |
| // tabIndex: Integer |
| // Order fields are traversed when user hits the tab key |
| tabIndex: "0", |
| |
| attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { |
| tabIndex: "domNode" |
| }), |
| |
| setValue: function(/*Date*/ value){ |
| // summary: |
| // Deprecated. Used attr('value', ...) instead. |
| // tags: |
| // deprecated |
| dojo.deprecated("dijit.Calendar:setValue() is deprecated. Use attr('value', ...) instead.", "", "2.0"); |
| this.attr('value', value); |
| }, |
| |
| _getValueAttr: function(){ |
| // summary: |
| // Support getter attr('value') |
| var value = new this.dateClassObj(this.value); |
| value.setHours(0, 0, 0, 0); // return midnight, local time for back-compat |
| |
| // If daylight savings pushes midnight to the previous date, fix the Date |
| // object to point at 1am so it will represent the correct day. See #9366 |
| if(value.getDate() < this.value.getDate()){ |
| value = this.dateFuncObj.add(value, "hour", 1); |
| } |
| return value; |
| }, |
| |
| _setValueAttr: function(/*Date*/ value){ |
| // summary: |
| // Support setter attr("value", ...) |
| // description: |
| // Set the current date and update the UI. If the date is disabled, the value will |
| // not change, but the display will change to the corresponding month. |
| // tags: |
| // protected |
| if(!this.value || this.dateFuncObj.compare(value, this.value)){ |
| value = new this.dateClassObj(value); |
| value.setHours(1); // to avoid issues when DST shift occurs at midnight, see #8521, #9366 |
| this.displayMonth = new this.dateClassObj(value); |
| if(!this.isDisabledDate(value, this.lang)){ |
| this.value = value; |
| this.onChange(this.attr('value')); |
| } |
| dojo.attr(this.domNode, "aria-label", |
| this.dateLocaleModule.format(value, |
| {selector:"date", formatLength:"full"})); |
| this._populateGrid(); |
| } |
| }, |
| |
| _setText: function(node, text){ |
| // summary: |
| // This just sets the content of node to the specified text. |
| // Can't do "node.innerHTML=text" because of an IE bug w/tables, see #3434. |
| // tags: |
| // private |
| while(node.firstChild){ |
| node.removeChild(node.firstChild); |
| } |
| node.appendChild(dojo.doc.createTextNode(text)); |
| }, |
| |
| _populateGrid: function(){ |
| // summary: |
| // Fills in the calendar grid with each day (1-31) |
| // tags: |
| // private |
| var month = this.displayMonth; |
| month.setDate(1); |
| var firstDay = month.getDay(), |
| daysInMonth = this.dateFuncObj.getDaysInMonth(month), |
| daysInPreviousMonth = this.dateFuncObj.getDaysInMonth(this.dateFuncObj.add(month, "month", -1)), |
| today = new this.dateClassObj(), |
| dayOffset = dojo.cldr.supplemental.getFirstDayOfWeek(this.lang); |
| if(dayOffset > firstDay){ dayOffset -= 7; } |
| |
| // Iterate through dates in the calendar and fill in date numbers and style info |
| dojo.query(".dijitCalendarDateTemplate", this.domNode).forEach(function(template, i){ |
| i += dayOffset; |
| var date = new this.dateClassObj(month), |
| number, clazz = "dijitCalendar", adj = 0; |
| |
| if(i < firstDay){ |
| number = daysInPreviousMonth - firstDay + i + 1; |
| adj = -1; |
| clazz += "Previous"; |
| }else if(i >= (firstDay + daysInMonth)){ |
| number = i - firstDay - daysInMonth + 1; |
| adj = 1; |
| clazz += "Next"; |
| }else{ |
| number = i - firstDay + 1; |
| clazz += "Current"; |
| } |
| |
| if(adj){ |
| date = this.dateFuncObj.add(date, "month", adj); |
| } |
| date.setDate(number); |
| |
| if(!this.dateFuncObj.compare(date, today, "date")){ |
| clazz = "dijitCalendarCurrentDate " + clazz; |
| } |
| |
| if(this._isSelectedDate(date, this.lang)){ |
| clazz = "dijitCalendarSelectedDate " + clazz; |
| } |
| |
| if(this.isDisabledDate(date, this.lang)){ |
| clazz = "dijitCalendarDisabledDate " + clazz; |
| } |
| |
| var clazz2 = this.getClassForDate(date, this.lang); |
| if(clazz2){ |
| clazz = clazz2 + " " + clazz; |
| } |
| |
| template.className = clazz + "Month dijitCalendarDateTemplate"; |
| template.dijitDateValue = date.valueOf(); |
| var label = dojo.query(".dijitCalendarDateLabel", template)[0], |
| text = date.getDateLocalized ? date.getDateLocalized(this.lang) : date.getDate(); |
| this._setText(label, text); |
| }, this); |
| |
| // Fill in localized month name |
| var monthNames = this.dateLocaleModule.getNames('months', 'wide', 'standAlone', this.lang); |
| this._setText(this.monthLabelNode, monthNames[month.getMonth()]); |
| |
| // Fill in localized prev/current/next years |
| var y = month.getFullYear() - 1; |
| var d = new this.dateClassObj(); |
| dojo.forEach(["previous", "current", "next"], function(name){ |
| d.setFullYear(y++); |
| this._setText(this[name+"YearLabelNode"], |
| this.dateLocaleModule.format(d, {selector:'year', locale:this.lang})); |
| }, this); |
| |
| // Set up repeating mouse behavior |
| var _this = this; |
| var typematic = function(nodeProp, dateProp, adj){ |
| //FIXME: leaks (collects) listeners if populateGrid is called multiple times. Do this once? |
| _this._connects.push( |
| dijit.typematic.addMouseListener(_this[nodeProp], _this, function(count){ |
| if(count >= 0){ _this._adjustDisplay(dateProp, adj); } |
| }, 0.8, 500) |
| ); |
| }; |
| typematic("incrementMonth", "month", 1); |
| typematic("decrementMonth", "month", -1); |
| typematic("nextYearLabelNode", "year", 1); |
| typematic("previousYearLabelNode", "year", -1); |
| }, |
| |
| goToToday: function(){ |
| // summary: |
| // Sets calendar's value to today's date |
| this.attr('value', new this.dateClassObj()); |
| }, |
| |
| constructor: function(/*Object*/args){ |
| var dateClass = (args.datePackage && (args.datePackage != "dojo.date"))? args.datePackage + ".Date" : "Date"; |
| this.dateClassObj = dojo.getObject(dateClass, false); |
| this.datePackage = args.datePackage || this.datePackage; |
| this.dateFuncObj = dojo.getObject(this.datePackage, false); |
| this.dateLocaleModule = dojo.getObject(this.datePackage + ".locale", false); |
| }, |
| |
| postMixInProperties: function(){ |
| // parser.instantiate sometimes passes in NaN for IE. Use default value in prototype instead. |
| if(isNaN(this.value)){ delete this.value; } |
| this.inherited(arguments); |
| }, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| dojo.setSelectable(this.domNode, false); |
| |
| var cloneClass = dojo.hitch(this, function(clazz, n){ |
| var template = dojo.query(clazz, this.domNode)[0]; |
| for(var i=0; i<n; i++){ |
| template.parentNode.appendChild(template.cloneNode(true)); |
| } |
| }); |
| |
| // clone the day label and calendar day templates 6 times to make 7 columns |
| cloneClass(".dijitCalendarDayLabelTemplate", 6); |
| cloneClass(".dijitCalendarDateTemplate", 6); |
| |
| // now make 6 week rows |
| cloneClass(".dijitCalendarWeekTemplate", 5); |
| |
| // insert localized day names in the header |
| var dayNames = this.dateLocaleModule.getNames('days', this.dayWidth, 'standAlone', this.lang); |
| var dayOffset = dojo.cldr.supplemental.getFirstDayOfWeek(this.lang); |
| dojo.query(".dijitCalendarDayLabel", this.domNode).forEach(function(label, i){ |
| this._setText(label, dayNames[(i + dayOffset) % 7]); |
| }, this); |
| |
| // Fill in spacer/month dropdown element with all the month names (invisible) so that the maximum width will affect layout |
| var monthNames = this.dateLocaleModule.getNames('months', 'wide', 'standAlone', this.lang); |
| cloneClass(".dijitCalendarMonthLabelTemplate", monthNames.length-1); |
| dojo.query(".dijitCalendarMonthLabelTemplate", this.domNode).forEach(function(node, i){ |
| dojo.attr(node, "month", i); |
| this._setText(node, monthNames[i]); |
| dojo.place(node.cloneNode(true), this.monthLabelSpacer); |
| }, this); |
| |
| var value = this.value; |
| this.value = null; |
| this.attr('value', new this.dateClassObj(value)); |
| }, |
| |
| _onMenuHover: function(e){ |
| dojo.stopEvent(e); |
| dojo.toggleClass(e.target, "dijitMenuItemHover"); |
| }, |
| |
| _adjustDisplay: function(/*String*/ part, /*int*/ amount){ |
| // summary: |
| // Moves calendar forwards or backwards by months or years |
| // part: |
| // "month" or "year" |
| // amount: |
| // Number of months or years |
| // tags: |
| // private |
| this.displayMonth = this.dateFuncObj.add(this.displayMonth, part, amount); |
| this._populateGrid(); |
| }, |
| |
| _onMonthToggle: function(/*Event*/ evt){ |
| // summary: |
| // Handler for when user triggers or dismisses the month list |
| // tags: |
| // protected |
| dojo.stopEvent(evt); |
| |
| if(evt.type == "mousedown"){ |
| var coords = dojo.position(this.monthLabelNode); |
| // coords.y -= dojo.position(this.domNode, true).y; |
| // Size the dropdown's width to match the label in the widget |
| // so that they are horizontally aligned |
| var dim = { |
| width: coords.w + "px", |
| top: -this.displayMonth.getMonth() * coords.h + "px" |
| }; |
| if((dojo.isIE && dojo.isQuirks) || dojo.isIE < 7){ |
| dim.left = -coords.w/2 + "px"; |
| } |
| dojo.style(this.monthDropDown, dim); |
| this._popupHandler = this.connect(document, "onmouseup", "_onMonthToggle"); |
| }else{ |
| this.disconnect(this._popupHandler); |
| delete this._popupHandler; |
| } |
| |
| dojo.toggleClass(this.monthDropDown, "dijitHidden"); |
| dojo.toggleClass(this.monthLabelNode, "dijitVisible"); |
| }, |
| |
| _onMonthSelect: function(/*Event*/ evt){ |
| // summary: |
| // Handler for when user selects a month from a list |
| // tags: |
| // protected |
| this._onMonthToggle(evt); |
| this.displayMonth.setMonth(dojo.attr(evt.target, "month")); |
| this._populateGrid(); |
| }, |
| |
| _onDayClick: function(/*Event*/ evt){ |
| // summary: |
| // Handler for day clicks, selects the date if appropriate |
| // tags: |
| // protected |
| dojo.stopEvent(evt); |
| for(var node = evt.target; node && !node.dijitDateValue; node = node.parentNode); |
| if(node && !dojo.hasClass(node, "dijitCalendarDisabledDate")){ |
| this.attr('value', node.dijitDateValue); |
| this.onValueSelected(this.attr('value')); |
| } |
| }, |
| |
| _onDayMouseOver: function(/*Event*/ evt){ |
| // summary: |
| // Handler for mouse over events on days, sets up hovered style |
| // tags: |
| // protected |
| var node = evt.target; |
| if(node && (node.dijitDateValue || node == this.previousYearLabelNode || node == this.nextYearLabelNode) ){ |
| dojo.addClass(node, "dijitCalendarHoveredDate"); |
| this._currentNode = node; |
| } |
| }, |
| |
| _onDayMouseOut: function(/*Event*/ evt){ |
| // summary: |
| // Handler for mouse out events on days, clears hovered style |
| // tags: |
| // protected |
| if(!this._currentNode){ return; } |
| for(var node = evt.relatedTarget; node;){ |
| if(node == this._currentNode){ return; } |
| try{ |
| node = node.parentNode; |
| }catch(x){ |
| node = null; |
| } |
| } |
| dojo.removeClass(this._currentNode, "dijitCalendarHoveredDate"); |
| this._currentNode = null; |
| }, |
| |
| //TODO: use typematic |
| //TODO: skip disabled dates without ending up in a loop |
| //TODO: could optimize by avoiding populate grid when month does not change |
| _onKeyPress: function(/*Event*/evt){ |
| // summary: |
| // Provides keyboard navigation of calendar |
| // tags: |
| // protected |
| var dk = dojo.keys, |
| increment = -1, |
| interval, |
| newValue = this.value; |
| switch(evt.keyCode){ |
| case dk.RIGHT_ARROW: |
| increment = 1; |
| //fallthrough... |
| case dk.LEFT_ARROW: |
| interval = "day"; |
| if(!this.isLeftToRight()){ increment *= -1; } |
| break; |
| case dk.DOWN_ARROW: |
| increment = 1; |
| //fallthrough... |
| case dk.UP_ARROW: |
| interval = "week"; |
| break; |
| case dk.PAGE_DOWN: |
| increment = 1; |
| //fallthrough... |
| case dk.PAGE_UP: |
| interval = evt.ctrlKey ? "year" : "month"; |
| break; |
| case dk.END: |
| // go to the next month |
| newValue = this.dateFuncObj.add(newValue, "month", 1); |
| // subtract a day from the result when we're done |
| interval = "day"; |
| //fallthrough... |
| case dk.HOME: |
| newValue = new Date(newValue).setDate(1); |
| break; |
| case dk.ENTER: |
| this.onValueSelected(this.attr('value')); |
| break; |
| case dk.ESCAPE: |
| //TODO |
| default: |
| return; |
| } |
| dojo.stopEvent(evt); |
| |
| if(interval){ |
| newValue = this.dateFuncObj.add(newValue, interval, increment); |
| } |
| |
| this.attr("value", newValue); |
| }, |
| |
| onValueSelected: function(/*Date*/ date){ |
| // summary: |
| // Notification that a date cell was selected. It may be the same as the previous value. |
| // description: |
| // Used by `dijit.form._DateTimeTextBox` (and thus `dijit.form.DateTextBox`) |
| // to get notification when the user has clicked a date. |
| // tags: |
| // protected |
| }, |
| |
| onChange: function(/*Date*/ date){ |
| // summary: |
| // Called only when the selected date has changed |
| }, |
| |
| _isSelectedDate: function(/*Date*/ dateObject, /*String?*/ locale){ |
| // summary: |
| // Extension point so developers can subclass Calendar to |
| // support multiple (concurrently) selected dates |
| // tags: |
| // protected extension |
| return !this.dateFuncObj.compare(dateObject, this.value, "date") |
| }, |
| |
| isDisabledDate: function(/*Date*/ dateObject, /*String?*/ locale){ |
| // summary: |
| // May be overridden to disable certain dates in the calendar e.g. `isDisabledDate=dojo.date.locale.isWeekend` |
| // tags: |
| // extension |
| /*===== |
| return false; // Boolean |
| =====*/ |
| }, |
| |
| getClassForDate: function(/*Date*/ dateObject, /*String?*/ locale){ |
| // summary: |
| // May be overridden to return CSS classes to associate with the date entry for the given dateObject, |
| // for example to indicate a holiday in specified locale. |
| // tags: |
| // extension |
| |
| /*===== |
| return ""; // String |
| =====*/ |
| } |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form._DateTimeTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form._DateTimeTextBox"] = true; |
| dojo.provide("dijit.form._DateTimeTextBox"); |
| |
| |
| |
| |
| |
| |
| /*===== |
| dojo.declare( |
| "dijit.form._DateTimeTextBox.__Constraints", |
| [dijit.form.RangeBoundTextBox.__Constraints, dojo.date.locale.__FormatOptions], { |
| // summary: |
| // Specifies both the rules on valid/invalid values (first/last date/time allowed), |
| // and also formatting options for how the date/time is displayed. |
| // example: |
| // To restrict to dates within 2004, displayed in a long format like "December 25, 2005": |
| // | {min:'2004-01-01',max:'2004-12-31', formatLength:'long'} |
| }); |
| =====*/ |
| |
| dojo.declare( |
| "dijit.form._DateTimeTextBox", |
| dijit.form.RangeBoundTextBox, |
| { |
| // summary: |
| // Base class for validating, serializable, range-bound date or time text box. |
| |
| // constraints: dijit.form._DateTimeTextBox.__Constraints |
| // Despite the name, this parameter specifies both constraints on the input |
| // (including starting/ending dates/times allowed) as well as |
| // formatting options like whether the date is displayed in long (ex: December 25, 2005) |
| // or short (ex: 12/25/2005) format. See `dijit.form._DateTimeTextBox.__Constraints` for details. |
| /*===== |
| constraints: {}, |
| ======*/ |
| |
| // Override ValidationTextBox.regExpGen().... we use a reg-ex generating function rather |
| // than a straight regexp to deal with locale (plus formatting options too?) |
| regExpGen: dojo.date.locale.regexp, |
| |
| // datePackage: String |
| // JavaScript namespace to find calendar routines. Uses Gregorian calendar routines |
| // at dojo.date, by default. |
| datePackage: "dojo.date", |
| |
| // Override _FormWidget.compare() to work for dates/times |
| compare: dojo.date.compare, |
| |
| format: function(/*Date*/ value, /*dojo.date.locale.__FormatOptions*/ constraints){ |
| // summary: |
| // Formats the value as a Date, according to specified locale (second argument) |
| // tags: |
| // protected |
| if(!value){ return ''; } |
| return this.dateLocaleModule.format(value, constraints); |
| }, |
| |
| parse: function(/*String*/ value, /*dojo.date.locale.__FormatOptions*/ constraints){ |
| // summary: |
| // Parses as string as a Date, according to constraints |
| // tags: |
| // protected |
| |
| return this.dateLocaleModule.parse(value, constraints) || (this._isEmpty(value) ? null : undefined); // Date |
| }, |
| |
| // Overrides ValidationTextBox.serialize() to serialize a date in canonical ISO format. |
| serialize: function(/*anything*/val, /*Object?*/options){ |
| if(val.toGregorian){ |
| val = val.toGregorian(); |
| } |
| return dojo.date.stamp.toISOString(val, options); |
| }, |
| |
| // value: Date |
| // The value of this widget as a JavaScript Date object. Use attr("value") / attr("value", val) to manipulate. |
| // When passed to the parser in markup, must be specified according to `dojo.date.stamp.fromISOString` |
| value: new Date(""), // value.toString()="NaN" |
| _blankValue: null, // used by filter() when the textbox is blank |
| |
| // popupClass: [protected extension] String |
| // Name of the popup widget class used to select a date/time. |
| // Subclasses should specify this. |
| popupClass: "", // default is no popup = text only |
| |
| |
| // _selector: [protected extension] String |
| // Specifies constraints.selector passed to dojo.date functions, should be either |
| // "date" or "time". |
| // Subclass must specify this. |
| _selector: "", |
| |
| constructor: function(/*Object*/args){ |
| var dateClass = args.datePackage ? args.datePackage + ".Date" : "Date"; |
| this.dateClassObj = dojo.getObject(dateClass, false); |
| this.value = new this.dateClassObj(""); |
| |
| this.datePackage = args.datePackage || this.datePackage; |
| this.dateLocaleModule = dojo.getObject(this.datePackage + ".locale", false); |
| this.regExpGen = this.dateLocaleModule.regexp; |
| }, |
| |
| postMixInProperties: function(){ |
| this.inherited(arguments); |
| |
| if(!this.value || this.value.toString() == dijit.form._DateTimeTextBox.prototype.value.toString()){ |
| this.value = null; |
| } |
| var constraints = this.constraints; |
| constraints.selector = this._selector; |
| constraints.fullYear = true; // see #5465 - always format with 4-digit years |
| var fromISO = dojo.date.stamp.fromISOString; |
| if(typeof constraints.min == "string"){ constraints.min = fromISO(constraints.min); } |
| if(typeof constraints.max == "string"){ constraints.max = fromISO(constraints.max); } |
| }, |
| |
| _onFocus: function(/*Event*/ evt){ |
| // summary: |
| // open the popup |
| this._open(); |
| this.inherited(arguments); |
| }, |
| |
| _setValueAttr: function(/*Date*/ value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){ |
| // summary: |
| // Sets the date on this textbox. Note that `value` must be like a Javascript Date object. |
| if(value instanceof Date && !(this.dateClassObj instanceof Date)){ |
| value = new this.dateClassObj(value); |
| } |
| |
| this.inherited(arguments); |
| if(this._picker){ |
| // #3948: fix blank date on popup only |
| if(!value){value = new this.dateClassObj();} |
| this._picker.attr('value', value); |
| } |
| }, |
| |
| _open: function(){ |
| // summary: |
| // opens the TimePicker, and sets the onValueSelected value |
| |
| if(this.disabled || this.readOnly || !this.popupClass){return;} |
| |
| var textBox = this; |
| |
| if(!this._picker){ |
| var PopupProto = dojo.getObject(this.popupClass, false); |
| this._picker = new PopupProto({ |
| onValueSelected: function(value){ |
| if(textBox._tabbingAway){ |
| delete textBox._tabbingAway; |
| }else{ |
| textBox.focus(); // focus the textbox before the popup closes to avoid reopening the popup |
| } |
| setTimeout(dojo.hitch(textBox, "_close"), 1); // allow focus time to take |
| |
| // this will cause InlineEditBox and other handlers to do stuff so make sure it's last |
| dijit.form._DateTimeTextBox.superclass._setValueAttr.call(textBox, value, true); |
| }, |
| id: this.id + "_popup", |
| lang: textBox.lang, |
| constraints: textBox.constraints, |
| |
| datePackage: textBox.datePackage, |
| |
| isDisabledDate: function(/*Date*/ date){ |
| // summary: |
| // disables dates outside of the min/max of the _DateTimeTextBox |
| var compare = dojo.date.compare; |
| var constraints = textBox.constraints; |
| return constraints && (constraints.min && (compare(constraints.min, date, textBox._selector) > 0) || |
| (constraints.max && compare(constraints.max, date, textBox._selector) < 0)); |
| } |
| }); |
| this._picker.attr('value', this.attr('value') || new this.dateClassObj()); |
| } |
| if(!this._opened){ |
| // Open drop down. Align left sides of input box and drop down, even in RTL mode, |
| // otherwise positioning thrown off when the drop down width is changed in marginBox call below (#10676) |
| dijit.popup.open({ |
| parent: this, |
| popup: this._picker, |
| orient: {'BL':'TL', 'TL':'BL'}, |
| around: this.domNode, |
| onCancel: dojo.hitch(this, this._close), |
| onClose: function(){ textBox._opened=false; } |
| }); |
| this._opened=true; |
| } |
| |
| dojo.marginBox(this._picker.domNode,{ w:this.domNode.offsetWidth }); |
| }, |
| |
| _close: function(){ |
| if(this._opened){ |
| dijit.popup.close(this._picker); |
| this._opened=false; |
| } |
| }, |
| |
| _onBlur: function(){ |
| // summary: |
| // Called magically when focus has shifted away from this widget and it's dropdown |
| this._close(); |
| if(this._picker){ |
| // teardown so that constraints will be rebuilt next time (redundant reference: #6002) |
| this._picker.destroy(); |
| delete this._picker; |
| } |
| this.inherited(arguments); |
| // don't focus on <input>. the user has explicitly focused on something else. |
| }, |
| |
| _getDisplayedValueAttr: function(){ |
| return this.textbox.value; |
| }, |
| |
| _setDisplayedValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){ |
| this._setValueAttr(this.parse(value, this.constraints), priorityChange, value); |
| }, |
| |
| destroy: function(){ |
| if(this._picker){ |
| this._picker.destroy(); |
| delete this._picker; |
| } |
| this.inherited(arguments); |
| }, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| this.connect(this.focusNode, 'onkeypress', this._onKeyPress); |
| this.connect(this.focusNode, 'onclick', this._open); |
| }, |
| |
| _onKeyPress: function(/*Event*/ e){ |
| // summary: |
| // Handler for keypress events |
| |
| var p = this._picker, dk = dojo.keys; |
| // Handle the key in the picker, if it has a handler. If the handler |
| // returns false, then don't handle any other keys. |
| if(p && this._opened && p.handleKey){ |
| if(p.handleKey(e) === false){ return; } |
| } |
| if(this._opened && e.charOrCode == dk.ESCAPE && !(e.shiftKey || e.ctrlKey || e.altKey || e.metaKey)){ |
| this._close(); |
| dojo.stopEvent(e); |
| }else if(!this._opened && e.charOrCode == dk.DOWN_ARROW){ |
| this._open(); |
| dojo.stopEvent(e); |
| }else if(e.charOrCode === dk.TAB){ |
| this._tabbingAway = true; |
| }else if(this._opened && (e.keyChar || e.charOrCode === dk.BACKSPACE || e.charOrCode == dk.DELETE)){ |
| // Replace the element - but do it after a delay to allow for |
| // filtering to occur |
| setTimeout(dojo.hitch(this, function(){ |
| dijit.placeOnScreenAroundElement(p.domNode.parentNode, this.domNode, {'BL':'TL', 'TL':'BL'}, p.orient ? dojo.hitch(p, "orient") : null); |
| }), 1); |
| } |
| } |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.DateTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.DateTextBox"] = true; |
| dojo.provide("dijit.form.DateTextBox"); |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.form.DateTextBox", |
| dijit.form._DateTimeTextBox, |
| { |
| // summary: |
| // A validating, serializable, range-bound date text box with a drop down calendar |
| // |
| // Example: |
| // | new dijit.form.DateTextBox({value: new Date(2009, 0, 20)}) |
| // |
| // Example: |
| // | <input dojotype='dijit.form.DateTextBox' value='2009-01-20'> |
| |
| baseClass: "dijitTextBox dijitDateTextBox", |
| popupClass: "dijit.Calendar", |
| _selector: "date", |
| |
| // value: Date |
| // The value of this widget as a JavaScript Date object, with only year/month/day specified. |
| // If specified in markup, use the format specified in `dojo.date.stamp.fromISOString` |
| value: new Date("") // value.toString()="NaN" |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form._Spinner"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form._Spinner"] = true; |
| dojo.provide("dijit.form._Spinner"); |
| |
| |
| |
| dojo.declare( |
| "dijit.form._Spinner", |
| dijit.form.RangeBoundTextBox, |
| { |
| // summary: |
| // Mixin for validation widgets with a spinner. |
| // description: |
| // This class basically (conceptually) extends `dijit.form.ValidationTextBox`. |
| // It modifies the template to have up/down arrows, and provides related handling code. |
| |
| // defaultTimeout: Number |
| // Number of milliseconds before a held arrow key or up/down button becomes typematic |
| defaultTimeout: 500, |
| |
| // timeoutChangeRate: Number |
| // Fraction of time used to change the typematic timer between events. |
| // 1.0 means that each typematic event fires at defaultTimeout intervals. |
| // < 1.0 means that each typematic event fires at an increasing faster rate. |
| timeoutChangeRate: 0.90, |
| |
| // smallDelta: Number |
| // Adjust the value by this much when spinning using the arrow keys/buttons |
| smallDelta: 1, |
| |
| // largeDelta: Number |
| // Adjust the value by this much when spinning using the PgUp/Dn keys |
| largeDelta: 10, |
| |
| templateString: dojo.cache("dijit.form", "templates/Spinner.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\tdojoAttachEvent=\"onmouseenter:_onMouse,onmouseleave:_onMouse,onmousedown:_onMouse\" waiRole=\"presentation\"\n\t><div class=\"dijitInputLayoutContainer\"\n\t\t><div class=\"dijitReset dijitSpinnerButtonContainer\"\n\t\t\t> <div class=\"dijitReset dijitLeft dijitButtonNode dijitArrowButton dijitUpArrowButton\"\n\t\t\t\tdojoAttachPoint=\"upArrowNode\"\n\t\t\t\tdojoAttachEvent=\"onmouseenter:_onMouse,onmouseleave:_onMouse\"\n\t\t\t\tstateModifier=\"UpArrow\"\n\t\t\t\t><div class=\"dijitArrowButtonInner\"> </div\n\t\t\t\t><div class=\"dijitArrowButtonChar\">▲</div\n\t\t\t></div\n\t\t\t><div class=\"dijitReset dijitLeft dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\t\tdojoAttachPoint=\"downArrowNode\"\n\t\t\t\tdojoAttachEvent=\"onmouseenter:_onMouse,onmouseleave:_onMouse\"\n\t\t\t\tstateModifier=\"DownArrow\"\n\t\t\t\t><div class=\"dijitArrowButtonInner\"> </div\n\t\t\t\t><div class=\"dijitArrowButtonChar\">▼</div\n\t\t\t></div\n\t\t></div\n\t\t><div class=\"dijitReset dijitValidationIcon\"><br></div\n\t\t><div class=\"dijitReset dijitValidationIconText\">Χ</div\n\t\t><div class=\"dijitReset dijitInputField\"\n\t\t\t><input class='dijitReset' dojoAttachPoint=\"textbox,focusNode\" type=\"${type}\" dojoAttachEvent=\"onkeypress:_onKeyPress\"\n\t\t\t\twaiRole=\"spinbutton\" autocomplete=\"off\" ${nameAttrSetting}\n\t\t/></div\n\t></div\n></div>\n"), |
| baseClass: "dijitSpinner", |
| |
| adjust: function(/* Object */ val, /*Number*/ delta){ |
| // summary: |
| // Overridable function used to adjust a primitive value(Number/Date/...) by the delta amount specified. |
| // The val is adjusted in a way that makes sense to the object type. |
| // tags: |
| // protected extension |
| return val; |
| }, |
| |
| _arrowState: function(/*Node*/ node, /*Boolean*/ pressed){ |
| // summary: |
| // Called when an arrow key is pressed to update the relevant CSS classes |
| this._active = pressed; |
| this.stateModifier = node.getAttribute("stateModifier") || ""; |
| this._setStateClass(); |
| }, |
| |
| _arrowPressed: function(/*Node*/ nodePressed, /*Number*/ direction, /*Number*/ increment){ |
| // summary: |
| // Handler for arrow button or arrow key being pressed |
| if(this.disabled || this.readOnly){ return; } |
| this._arrowState(nodePressed, true); |
| this._setValueAttr(this.adjust(this.attr('value'), direction*increment), false); |
| dijit.selectInputText(this.textbox, this.textbox.value.length); |
| }, |
| |
| _arrowReleased: function(/*Node*/ node){ |
| // summary: |
| // Handler for arrow button or arrow key being released |
| this._wheelTimer = null; |
| if(this.disabled || this.readOnly){ return; } |
| this._arrowState(node, false); |
| }, |
| |
| _typematicCallback: function(/*Number*/ count, /*DOMNode*/ node, /*Event*/ evt){ |
| var inc=this.smallDelta; |
| if(node == this.textbox){ |
| var k=dojo.keys; |
| var key = evt.charOrCode; |
| inc = (key == k.PAGE_UP || key == k.PAGE_DOWN) ? this.largeDelta : this.smallDelta; |
| node = (key == k.UP_ARROW || key == k.PAGE_UP) ? this.upArrowNode : this.downArrowNode; |
| } |
| if(count == -1){ this._arrowReleased(node); } |
| else{ this._arrowPressed(node, (node == this.upArrowNode) ? 1 : -1, inc); } |
| }, |
| |
| _wheelTimer: null, |
| _mouseWheeled: function(/*Event*/ evt){ |
| // summary: |
| // Mouse wheel listener where supported |
| |
| dojo.stopEvent(evt); |
| // FIXME: Safari bubbles |
| |
| // be nice to DOH and scroll as much as the event says to |
| var scrollAmount = evt.detail ? (evt.detail * -1) : (evt.wheelDelta / 120); |
| if(scrollAmount !== 0){ |
| var node = this[(scrollAmount > 0 ? "upArrowNode" : "downArrowNode" )]; |
| |
| this._arrowPressed(node, scrollAmount, this.smallDelta); |
| |
| if(!this._wheelTimer){ |
| clearTimeout(this._wheelTimer); |
| } |
| this._wheelTimer = setTimeout(dojo.hitch(this,"_arrowReleased",node), 50); |
| } |
| |
| }, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| |
| // extra listeners |
| this.connect(this.domNode, !dojo.isMozilla ? "onmousewheel" : 'DOMMouseScroll', "_mouseWheeled"); |
| this._connects.push(dijit.typematic.addListener(this.upArrowNode, this.textbox, {charOrCode:dojo.keys.UP_ARROW,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false}, this, "_typematicCallback", this.timeoutChangeRate, this.defaultTimeout)); |
| this._connects.push(dijit.typematic.addListener(this.downArrowNode, this.textbox, {charOrCode:dojo.keys.DOWN_ARROW,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false}, this, "_typematicCallback", this.timeoutChangeRate, this.defaultTimeout)); |
| this._connects.push(dijit.typematic.addListener(this.upArrowNode, this.textbox, {charOrCode:dojo.keys.PAGE_UP,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false}, this, "_typematicCallback", this.timeoutChangeRate, this.defaultTimeout)); |
| this._connects.push(dijit.typematic.addListener(this.downArrowNode, this.textbox, {charOrCode:dojo.keys.PAGE_DOWN,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false}, this, "_typematicCallback", this.timeoutChangeRate, this.defaultTimeout)); |
| if(dojo.isIE){ |
| var _this = this; |
| (function resize(){ |
| var sz = _this.upArrowNode.parentNode.offsetHeight; |
| if(sz){ |
| _this.upArrowNode.style.height = sz >> 1; |
| _this.downArrowNode.style.height = sz - (sz >> 1); |
| _this.focusNode.parentNode.style.height = sz; |
| } |
| })(); |
| this.connect(this.domNode, "onresize", |
| function(){ setTimeout( |
| function(){ |
| resize(); |
| // cause IE to rerender when spinner is moved from hidden to visible |
| _this._setStateClass(); |
| }, 0); |
| } |
| ); |
| this._layoutHackIE7(); |
| } |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.NumberSpinner"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.NumberSpinner"] = true; |
| dojo.provide("dijit.form.NumberSpinner"); |
| |
| |
| |
| |
| dojo.declare("dijit.form.NumberSpinner", |
| [dijit.form._Spinner, dijit.form.NumberTextBoxMixin], |
| { |
| // summary: |
| // Extends NumberTextBox to add up/down arrows and pageup/pagedown for incremental change to the value |
| // |
| // description: |
| // A `dijit.form.NumberTextBox` extension to provide keyboard accessible value selection |
| // as well as icons for spinning direction. When using the keyboard, the typematic rules |
| // apply, meaning holding the key will gradually increarease or decrease the value and |
| // accelerate. |
| // |
| // example: |
| // | new dijit.form.NumberSpinner({ constraints:{ max:300, min:100 }}, "someInput"); |
| |
| adjust: function(/* Object */val, /* Number*/delta){ |
| // summary: |
| // Change Number val by the given amount |
| // tags: |
| // protected |
| |
| var tc = this.constraints, |
| v = isNaN(val), |
| gotMax = !isNaN(tc.max), |
| gotMin = !isNaN(tc.min) |
| ; |
| if(v && delta != 0){ // blank or invalid value and they want to spin, so create defaults |
| val = (delta > 0) ? |
| gotMin ? tc.min : gotMax ? tc.max : 0 : |
| gotMax ? this.constraints.max : gotMin ? tc.min : 0 |
| ; |
| } |
| var newval = val + delta; |
| if(v || isNaN(newval)){ return val; } |
| if(gotMax && (newval > tc.max)){ |
| newval = tc.max; |
| } |
| if(gotMin && (newval < tc.min)){ |
| newval = tc.min; |
| } |
| return newval; |
| }, |
| |
| _onKeyPress: function(e){ |
| if((e.charOrCode == dojo.keys.HOME || e.charOrCode == dojo.keys.END) && !(e.ctrlKey || e.altKey || e.metaKey) |
| && typeof this.attr('value') != 'undefined' /* gibberish, so HOME and END are default editing keys*/){ |
| var value = this.constraints[(e.charOrCode == dojo.keys.HOME ? "min" : "max")]; |
| if(value){ |
| this._setValueAttr(value,true); |
| } |
| // eat home or end key whether we change the value or not |
| dojo.stopEvent(e); |
| } |
| } |
| |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dojo.data.util.sorter"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.data.util.sorter"] = true; |
| dojo.provide("dojo.data.util.sorter"); |
| |
| dojo.data.util.sorter.basicComparator = function( /*anything*/ a, |
| /*anything*/ b){ |
| // summary: |
| // Basic comparision function that compares if an item is greater or less than another item |
| // description: |
| // returns 1 if a > b, -1 if a < b, 0 if equal. |
| // 'null' values (null, undefined) are treated as larger values so that they're pushed to the end of the list. |
| // And compared to each other, null is equivalent to undefined. |
| |
| //null is a problematic compare, so if null, we set to undefined. |
| //Makes the check logic simple, compact, and consistent |
| //And (null == undefined) === true, so the check later against null |
| //works for undefined and is less bytes. |
| var r = -1; |
| if(a === null){ |
| a = undefined; |
| } |
| if(b === null){ |
| b = undefined; |
| } |
| if(a == b){ |
| r = 0; |
| }else if(a > b || a == null){ |
| r = 1; |
| } |
| return r; //int {-1,0,1} |
| }; |
| |
| dojo.data.util.sorter.createSortFunction = function( /* attributes array */sortSpec, |
| /*dojo.data.core.Read*/ store){ |
| // summary: |
| // Helper function to generate the sorting function based off the list of sort attributes. |
| // description: |
| // The sort function creation will look for a property on the store called 'comparatorMap'. If it exists |
| // it will look in the mapping for comparisons function for the attributes. If one is found, it will |
| // use it instead of the basic comparator, which is typically used for strings, ints, booleans, and dates. |
| // Returns the sorting function for this particular list of attributes and sorting directions. |
| // |
| // sortSpec: array |
| // A JS object that array that defines out what attribute names to sort on and whether it should be descenting or asending. |
| // The objects should be formatted as follows: |
| // { |
| // attribute: "attributeName-string" || attribute, |
| // descending: true|false; // Default is false. |
| // } |
| // store: object |
| // The datastore object to look up item values from. |
| // |
| var sortFunctions=[]; |
| |
| function createSortFunction(attr, dir, comp, s){ |
| //Passing in comp and s (comparator and store), makes this |
| //function much faster. |
| return function(itemA, itemB){ |
| var a = s.getValue(itemA, attr); |
| var b = s.getValue(itemB, attr); |
| return dir * comp(a,b); //int |
| }; |
| } |
| var sortAttribute; |
| var map = store.comparatorMap; |
| var bc = dojo.data.util.sorter.basicComparator; |
| for(var i = 0; i < sortSpec.length; i++){ |
| sortAttribute = sortSpec[i]; |
| var attr = sortAttribute.attribute; |
| if(attr){ |
| var dir = (sortAttribute.descending) ? -1 : 1; |
| var comp = bc; |
| if(map){ |
| if(typeof attr !== "string" && ("toString" in attr)){ |
| attr = attr.toString(); |
| } |
| comp = map[attr] || bc; |
| } |
| sortFunctions.push(createSortFunction(attr, |
| dir, comp, store)); |
| } |
| } |
| return function(rowA, rowB){ |
| var i=0; |
| while(i < sortFunctions.length){ |
| var ret = sortFunctions[i++](rowA, rowB); |
| if(ret !== 0){ |
| return ret;//int |
| } |
| } |
| return 0; //int |
| }; // Function |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dojo.data.util.simpleFetch"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.data.util.simpleFetch"] = true; |
| dojo.provide("dojo.data.util.simpleFetch"); |
| |
| |
| dojo.data.util.simpleFetch.fetch = function(/* Object? */ request){ |
| // summary: |
| // The simpleFetch mixin is designed to serve as a set of function(s) that can |
| // be mixed into other datastore implementations to accelerate their development. |
| // The simpleFetch mixin should work well for any datastore that can respond to a _fetchItems() |
| // call by returning an array of all the found items that matched the query. The simpleFetch mixin |
| // is not designed to work for datastores that respond to a fetch() call by incrementally |
| // loading items, or sequentially loading partial batches of the result |
| // set. For datastores that mixin simpleFetch, simpleFetch |
| // implements a fetch method that automatically handles eight of the fetch() |
| // arguments -- onBegin, onItem, onComplete, onError, start, count, sort and scope |
| // The class mixing in simpleFetch should not implement fetch(), |
| // but should instead implement a _fetchItems() method. The _fetchItems() |
| // method takes three arguments, the keywordArgs object that was passed |
| // to fetch(), a callback function to be called when the result array is |
| // available, and an error callback to be called if something goes wrong. |
| // The _fetchItems() method should ignore any keywordArgs parameters for |
| // start, count, onBegin, onItem, onComplete, onError, sort, and scope. |
| // The _fetchItems() method needs to correctly handle any other keywordArgs |
| // parameters, including the query parameter and any optional parameters |
| // (such as includeChildren). The _fetchItems() method should create an array of |
| // result items and pass it to the fetchHandler along with the original request object |
| // -- or, the _fetchItems() method may, if it wants to, create an new request object |
| // with other specifics about the request that are specific to the datastore and pass |
| // that as the request object to the handler. |
| // |
| // For more information on this specific function, see dojo.data.api.Read.fetch() |
| request = request || {}; |
| if(!request.store){ |
| request.store = this; |
| } |
| var self = this; |
| |
| var _errorHandler = function(errorData, requestObject){ |
| if(requestObject.onError){ |
| var scope = requestObject.scope || dojo.global; |
| requestObject.onError.call(scope, errorData, requestObject); |
| } |
| }; |
| |
| var _fetchHandler = function(items, requestObject){ |
| var oldAbortFunction = requestObject.abort || null; |
| var aborted = false; |
| |
| var startIndex = requestObject.start?requestObject.start:0; |
| var endIndex = (requestObject.count && (requestObject.count !== Infinity))?(startIndex + requestObject.count):items.length; |
| |
| requestObject.abort = function(){ |
| aborted = true; |
| if(oldAbortFunction){ |
| oldAbortFunction.call(requestObject); |
| } |
| }; |
| |
| var scope = requestObject.scope || dojo.global; |
| if(!requestObject.store){ |
| requestObject.store = self; |
| } |
| if(requestObject.onBegin){ |
| requestObject.onBegin.call(scope, items.length, requestObject); |
| } |
| if(requestObject.sort){ |
| items.sort(dojo.data.util.sorter.createSortFunction(requestObject.sort, self)); |
| } |
| if(requestObject.onItem){ |
| for(var i = startIndex; (i < items.length) && (i < endIndex); ++i){ |
| var item = items[i]; |
| if(!aborted){ |
| requestObject.onItem.call(scope, item, requestObject); |
| } |
| } |
| } |
| if(requestObject.onComplete && !aborted){ |
| var subset = null; |
| if(!requestObject.onItem){ |
| subset = items.slice(startIndex, endIndex); |
| } |
| requestObject.onComplete.call(scope, subset, requestObject); |
| } |
| }; |
| this._fetchItems(request, _fetchHandler, _errorHandler); |
| return request; // Object |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dojo.data.util.filter"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dojo.data.util.filter"] = true; |
| dojo.provide("dojo.data.util.filter"); |
| |
| dojo.data.util.filter.patternToRegExp = function(/*String*/pattern, /*boolean?*/ ignoreCase){ |
| // summary: |
| // Helper function to convert a simple pattern to a regular expression for matching. |
| // description: |
| // Returns a regular expression object that conforms to the defined conversion rules. |
| // For example: |
| // ca* -> /^ca.*$/ |
| // *ca* -> /^.*ca.*$/ |
| // *c\*a* -> /^.*c\*a.*$/ |
| // *c\*a?* -> /^.*c\*a..*$/ |
| // and so on. |
| // |
| // pattern: string |
| // A simple matching pattern to convert that follows basic rules: |
| // * Means match anything, so ca* means match anything starting with ca |
| // ? Means match single character. So, b?b will match to bob and bab, and so on. |
| // \ is an escape character. So for example, \* means do not treat * as a match, but literal character *. |
| // To use a \ as a character in the string, it must be escaped. So in the pattern it should be |
| // represented by \\ to be treated as an ordinary \ character instead of an escape. |
| // |
| // ignoreCase: |
| // An optional flag to indicate if the pattern matching should be treated as case-sensitive or not when comparing |
| // By default, it is assumed case sensitive. |
| |
| var rxp = "^"; |
| var c = null; |
| for(var i = 0; i < pattern.length; i++){ |
| c = pattern.charAt(i); |
| switch(c){ |
| case '\\': |
| rxp += c; |
| i++; |
| rxp += pattern.charAt(i); |
| break; |
| case '*': |
| rxp += ".*"; break; |
| case '?': |
| rxp += "."; break; |
| case '$': |
| case '^': |
| case '/': |
| case '+': |
| case '.': |
| case '|': |
| case '(': |
| case ')': |
| case '{': |
| case '}': |
| case '[': |
| case ']': |
| rxp += "\\"; //fallthrough |
| default: |
| rxp += c; |
| } |
| } |
| rxp += "$"; |
| if(ignoreCase){ |
| return new RegExp(rxp,"mi"); //RegExp |
| }else{ |
| return new RegExp(rxp,"m"); //RegExp |
| } |
| |
| }; |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.ComboBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.ComboBox"] = true; |
| dojo.provide("dijit.form.ComboBox"); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.form.ComboBoxMixin", |
| null, |
| { |
| // summary: |
| // Implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect` |
| // description: |
| // All widgets that mix in dijit.form.ComboBoxMixin must extend `dijit.form._FormValueWidget`. |
| // tags: |
| // protected |
| |
| // item: Object |
| // This is the item returned by the dojo.data.store implementation that |
| // provides the data for this ComboBox, it's the currently selected item. |
| item: null, |
| |
| // pageSize: Integer |
| // Argument to data provider. |
| // Specifies number of search results per page (before hitting "next" button) |
| pageSize: Infinity, |
| |
| // store: Object |
| // Reference to data provider object used by this ComboBox |
| store: null, |
| |
| // fetchProperties: Object |
| // Mixin to the dojo.data store's fetch. |
| // For example, to set the sort order of the ComboBox menu, pass: |
| // | { sort: {attribute:"name",descending: true} } |
| // To override the default queryOptions so that deep=false, do: |
| // | { queryOptions: {ignoreCase: true, deep: false} } |
| fetchProperties:{}, |
| |
| // query: Object |
| // A query that can be passed to 'store' to initially filter the items, |
| // before doing further filtering based on `searchAttr` and the key. |
| // Any reference to the `searchAttr` is ignored. |
| query: {}, |
| |
| // autoComplete: Boolean |
| // If user types in a partial string, and then tab out of the `<input>` box, |
| // automatically copy the first entry displayed in the drop down list to |
| // the `<input>` field |
| autoComplete: true, |
| |
| // highlightMatch: String |
| // One of: "first", "all" or "none". |
| // |
| // If the ComboBox/FilteringSelect opens with the search results and the searched |
| // string can be found, it will be highlighted. If set to "all" |
| // then will probably want to change `queryExpr` parameter to '*${0}*' |
| // |
| // Highlighting is only performed when `labelType` is "text", so as to not |
| // interfere with any HTML markup an HTML label might contain. |
| highlightMatch: "first", |
| |
| // searchDelay: Integer |
| // Delay in milliseconds between when user types something and we start |
| // searching based on that value |
| searchDelay: 100, |
| |
| // searchAttr: String |
| // Search for items in the data store where this attribute (in the item) |
| // matches what the user typed |
| searchAttr: "name", |
| |
| // labelAttr: String? |
| // The entries in the drop down list come from this attribute in the |
| // dojo.data items. |
| // If not specified, the searchAttr attribute is used instead. |
| labelAttr: "", |
| |
| // labelType: String |
| // Specifies how to interpret the labelAttr in the data store items. |
| // Can be "html" or "text". |
| labelType: "text", |
| |
| // queryExpr: String |
| // This specifies what query ComboBox/FilteringSelect sends to the data store, |
| // based on what the user has typed. Changing this expression will modify |
| // whether the drop down shows only exact matches, a "starting with" match, |
| // etc. Use it in conjunction with highlightMatch. |
| // dojo.data query expression pattern. |
| // `${0}` will be substituted for the user text. |
| // `*` is used for wildcards. |
| // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is" |
| queryExpr: "${0}*", |
| |
| // ignoreCase: Boolean |
| // Set true if the ComboBox/FilteringSelect should ignore case when matching possible items |
| ignoreCase: true, |
| |
| // hasDownArrow: [const] Boolean |
| // Set this textbox to have a down arrow button, to display the drop down list. |
| // Defaults to true. |
| hasDownArrow: true, |
| |
| templateString: dojo.cache("dijit.form", "templates/ComboBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\tdojoAttachEvent=\"onmouseenter:_onMouse,onmouseleave:_onMouse,onmousedown:_onMouse\" dojoAttachPoint=\"comboNode\" waiRole=\"combobox\" tabIndex=\"-1\"\n\t><div style=\"overflow:hidden;\"\n\t\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton'\n\t\t\tdojoAttachPoint=\"downArrowNode\" waiRole=\"presentation\"\n\t\t\tdojoAttachEvent=\"onmousedown:_onArrowMouseDown,onmouseup:_onMouse,onmouseenter:_onMouse,onmouseleave:_onMouse\"\n\t\t\t><div class=\"dijitArrowButtonInner\"> </div\n\t\t\t><div class=\"dijitArrowButtonChar\">▼</div\n\t\t></div\n\t\t><div class=\"dijitReset dijitValidationIcon\"><br></div\n\t\t><div class=\"dijitReset dijitValidationIconText\">Χ</div\n\t\t><div class=\"dijitReset dijitInputField\"\n\t\t\t><input ${nameAttrSetting} type=\"text\" autocomplete=\"off\" class='dijitReset'\n\t\t\tdojoAttachEvent=\"onkeypress:_onKeyPress,compositionend\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" waiRole=\"textbox\" waiState=\"haspopup-true,autocomplete-list\"\n\t\t/></div\n\t></div\n></div>\n"), |
| |
| baseClass:"dijitComboBox", |
| |
| _getCaretPos: function(/*DomNode*/ element){ |
| // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22 |
| var pos = 0; |
| if(typeof(element.selectionStart) == "number"){ |
| // FIXME: this is totally borked on Moz < 1.3. Any recourse? |
| pos = element.selectionStart; |
| }else if(dojo.isIE){ |
| // in the case of a mouse click in a popup being handled, |
| // then the dojo.doc.selection is not the textarea, but the popup |
| // var r = dojo.doc.selection.createRange(); |
| // hack to get IE 6 to play nice. What a POS browser. |
| var tr = dojo.doc.selection.createRange().duplicate(); |
| var ntr = element.createTextRange(); |
| tr.move("character",0); |
| ntr.move("character",0); |
| try{ |
| // If control doesnt have focus, you get an exception. |
| // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes). |
| // There appears to be no workaround for this - googled for quite a while. |
| ntr.setEndPoint("EndToEnd", tr); |
| pos = String(ntr.text).replace(/\r/g,"").length; |
| }catch(e){ |
| // If focus has shifted, 0 is fine for caret pos. |
| } |
| } |
| return pos; |
| }, |
| |
| _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){ |
| location = parseInt(location); |
| dijit.selectInputText(element, location, location); |
| }, |
| |
| _setDisabledAttr: function(/*Boolean*/ value){ |
| // Additional code to set disabled state of ComboBox node. |
| // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr(). |
| this.inherited(arguments); |
| dijit.setWaiState(this.comboNode, "disabled", value); |
| }, |
| |
| _abortQuery: function(){ |
| // stop in-progress query |
| if(this.searchTimer){ |
| clearTimeout(this.searchTimer); |
| this.searchTimer = null; |
| } |
| if(this._fetchHandle){ |
| if(this._fetchHandle.abort){ this._fetchHandle.abort(); } |
| this._fetchHandle = null; |
| } |
| }, |
| |
| _onKeyPress: function(/*Event*/ evt){ |
| // summary: |
| // Handles keyboard events |
| var key = evt.charOrCode; |
| // except for cutting/pasting case - ctrl + x/v |
| if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == dojo.keys.SHIFT){ |
| return; // throw out weird key combinations and spurious events |
| } |
| var doSearch = false; |
| var searchFunction = "_startSearchFromInput"; |
| var pw = this._popupWidget; |
| var dk = dojo.keys; |
| var highlighted = null; |
| this._prev_key_backspace = false; |
| this._abortQuery(); |
| if(this._isShowingNow){ |
| pw.handleKey(key); |
| highlighted = pw.getHighlightedOption(); |
| } |
| switch(key){ |
| case dk.PAGE_DOWN: |
| case dk.DOWN_ARROW: |
| case dk.PAGE_UP: |
| case dk.UP_ARROW: |
| if(!this._isShowingNow){ |
| this._arrowPressed(); |
| doSearch = true; |
| searchFunction = "_startSearchAll"; |
| }else{ |
| this._announceOption(highlighted); |
| } |
| dojo.stopEvent(evt); |
| break; |
| |
| case dk.ENTER: |
| // prevent submitting form if user presses enter. Also |
| // prevent accepting the value if either Next or Previous |
| // are selected |
| if(highlighted){ |
| // only stop event on prev/next |
| if(highlighted == pw.nextButton){ |
| this._nextSearch(1); |
| dojo.stopEvent(evt); |
| break; |
| }else if(highlighted == pw.previousButton){ |
| this._nextSearch(-1); |
| dojo.stopEvent(evt); |
| break; |
| } |
| }else{ |
| // Update 'value' (ex: KY) according to currently displayed text |
| this._setBlurValue(); // set value if needed |
| this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting |
| } |
| // default case: |
| // prevent submit, but allow event to bubble |
| evt.preventDefault(); |
| // fall through |
| |
| case dk.TAB: |
| var newvalue = this.attr('displayedValue'); |
| // if the user had More Choices selected fall into the |
| // _onBlur handler |
| if(pw && ( |
| newvalue == pw._messages["previousMessage"] || |
| newvalue == pw._messages["nextMessage"]) |
| ){ |
| break; |
| } |
| if(highlighted){ |
| this._selectOption(); |
| } |
| if(this._isShowingNow){ |
| this._lastQuery = null; // in case results come back later |
| this._hideResultList(); |
| } |
| break; |
| |
| case ' ': |
| if(highlighted){ |
| dojo.stopEvent(evt); |
| this._selectOption(); |
| this._hideResultList(); |
| }else{ |
| doSearch = true; |
| } |
| break; |
| |
| case dk.ESCAPE: |
| if(this._isShowingNow){ |
| dojo.stopEvent(evt); |
| this._hideResultList(); |
| } |
| break; |
| |
| case dk.DELETE: |
| case dk.BACKSPACE: |
| this._prev_key_backspace = true; |
| doSearch = true; |
| break; |
| |
| default: |
| // Non char keys (F1-F12 etc..) shouldn't open list. |
| // Ascii characters and IME input (Chinese, Japanese etc.) should. |
| // On IE and safari, IME input produces keycode == 229, and we simulate |
| // it on firefox by attaching to compositionend event (see compositionend method) |
| doSearch = typeof key == 'string' || key == 229; |
| } |
| if(doSearch){ |
| // need to wait a tad before start search so that the event |
| // bubbles through DOM and we have value visible |
| this.item = undefined; // undefined means item needs to be set |
| this.searchTimer = setTimeout(dojo.hitch(this, searchFunction),1); |
| } |
| }, |
| |
| _autoCompleteText: function(/*String*/ text){ |
| // summary: |
| // Fill in the textbox with the first item from the drop down |
| // list, and highlight the characters that were |
| // auto-completed. For example, if user typed "CA" and the |
| // drop down list appeared, the textbox would be changed to |
| // "California" and "ifornia" would be highlighted. |
| |
| var fn = this.focusNode; |
| |
| // IE7: clear selection so next highlight works all the time |
| dijit.selectInputText(fn, fn.value.length); |
| // does text autoComplete the value in the textbox? |
| var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr'; |
| if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){ |
| var cpos = this._getCaretPos(fn); |
| // only try to extend if we added the last character at the end of the input |
| if((cpos+1) > fn.value.length){ |
| // only add to input node as we would overwrite Capitalisation of chars |
| // actually, that is ok |
| fn.value = text;//.substr(cpos); |
| // visually highlight the autocompleted characters |
| dijit.selectInputText(fn, cpos); |
| } |
| }else{ |
| // text does not autoComplete; replace the whole value and highlight |
| fn.value = text; |
| dijit.selectInputText(fn); |
| } |
| }, |
| |
| _openResultList: function(/*Object*/ results, /*Object*/ dataObject){ |
| this._fetchHandle = null; |
| if( this.disabled || |
| this.readOnly || |
| (dataObject.query[this.searchAttr] != this._lastQuery) |
| ){ |
| return; |
| } |
| this._popupWidget.clearResultList(); |
| if(!results.length){ |
| this._hideResultList(); |
| return; |
| } |
| |
| |
| // Fill in the textbox with the first item from the drop down list, |
| // and highlight the characters that were auto-completed. For |
| // example, if user typed "CA" and the drop down list appeared, the |
| // textbox would be changed to "California" and "ifornia" would be |
| // highlighted. |
| |
| dataObject._maxOptions = this._maxOptions; |
| var nodes = this._popupWidget.createOptions( |
| results, |
| dataObject, |
| dojo.hitch(this, "_getMenuLabelFromItem") |
| ); |
| |
| // show our list (only if we have content, else nothing) |
| this._showResultList(); |
| |
| // #4091: |
| // tell the screen reader that the paging callback finished by |
| // shouting the next choice |
| if(dataObject.direction){ |
| if(1 == dataObject.direction){ |
| this._popupWidget.highlightFirstOption(); |
| }else if(-1 == dataObject.direction){ |
| this._popupWidget.highlightLastOption(); |
| } |
| this._announceOption(this._popupWidget.getHighlightedOption()); |
| }else if(this.autoComplete && !this._prev_key_backspace /*&& !dataObject.direction*/ |
| // when the user clicks the arrow button to show the full list, |
| // startSearch looks for "*". |
| // it does not make sense to autocomplete |
| // if they are just previewing the options available. |
| && !/^[*]+$/.test(dataObject.query[this.searchAttr])){ |
| this._announceOption(nodes[1]); // 1st real item |
| } |
| }, |
| |
| _showResultList: function(){ |
| this._hideResultList(); |
| this._arrowPressed(); |
| // hide the tooltip |
| this.displayMessage(""); |
| |
| // Position the list and if it's too big to fit on the screen then |
| // size it to the maximum possible height |
| // Our dear friend IE doesnt take max-height so we need to |
| // calculate that on our own every time |
| |
| // TODO: want to redo this, see |
| // http://trac.dojotoolkit.org/ticket/3272 |
| // and |
| // http://trac.dojotoolkit.org/ticket/4108 |
| |
| |
| // natural size of the list has changed, so erase old |
| // width/height settings, which were hardcoded in a previous |
| // call to this function (via dojo.marginBox() call) |
| dojo.style(this._popupWidget.domNode, {width: "", height: ""}); |
| |
| var best = this.open(); |
| // #3212: |
| // only set auto scroll bars if necessary prevents issues with |
| // scroll bars appearing when they shouldn't when node is made |
| // wider (fractional pixels cause this) |
| var popupbox = dojo.marginBox(this._popupWidget.domNode); |
| this._popupWidget.domNode.style.overflow = |
| ((best.h == popupbox.h) && (best.w == popupbox.w)) ? "hidden" : "auto"; |
| // #4134: |
| // borrow TextArea scrollbar test so content isn't covered by |
| // scrollbar and horizontal scrollbar doesn't appear |
| var newwidth = best.w; |
| if(best.h < this._popupWidget.domNode.scrollHeight){ |
| newwidth += 16; |
| } |
| dojo.marginBox(this._popupWidget.domNode, { |
| h: best.h, |
| w: Math.max(newwidth, this.domNode.offsetWidth) |
| }); |
| |
| // If we increased the width of drop down to match the width of ComboBox.domNode, |
| // then need to reposition the drop down (wrapper) so (all of) the drop down still |
| // appears underneath the ComboBox.domNode |
| if(newwidth < this.domNode.offsetWidth){ |
| this._popupWidget.domNode.parentNode.style.left = dojo.position(this.domNode).x + "px"; |
| } |
| |
| dijit.setWaiState(this.comboNode, "expanded", "true"); |
| }, |
| |
| _hideResultList: function(){ |
| this._abortQuery(); |
| if(this._isShowingNow){ |
| dijit.popup.close(this._popupWidget); |
| this._arrowIdle(); |
| this._isShowingNow=false; |
| dijit.setWaiState(this.comboNode, "expanded", "false"); |
| dijit.removeWaiState(this.focusNode,"activedescendant"); |
| } |
| }, |
| |
| _setBlurValue: function(){ |
| // if the user clicks away from the textbox OR tabs away, set the |
| // value to the textbox value |
| // #4617: |
| // if value is now more choices or previous choices, revert |
| // the value |
| var newvalue=this.attr('displayedValue'); |
| var pw = this._popupWidget; |
| if(pw && ( |
| newvalue == pw._messages["previousMessage"] || |
| newvalue == pw._messages["nextMessage"] |
| ) |
| ){ |
| this._setValueAttr(this._lastValueReported, true); |
| }else if(typeof this.item == "undefined"){ |
| // Update 'value' (ex: KY) according to currently displayed text |
| this.item = null; |
| this.attr('displayedValue', newvalue); |
| }else{ |
| if(this.value != this._lastValueReported){ |
| dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); |
| } |
| this._refreshState(); |
| } |
| }, |
| |
| _onBlur: function(){ |
| // summary: |
| // Called magically when focus has shifted away from this widget and it's drop down |
| this._hideResultList(); |
| this._arrowIdle(); |
| this.inherited(arguments); |
| }, |
| |
| _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ |
| // summary: |
| // Set the displayed valued in the input box, and the hidden value |
| // that gets submitted, based on a dojo.data store item. |
| // description: |
| // Users shouldn't call this function; they should be calling |
| // attr('item', value) |
| // tags: |
| // private |
| if(!displayedValue){ displayedValue = this.labelFunc(item, this.store); } |
| this.value = this._getValueField() != this.searchAttr? this.store.getIdentity(item) : displayedValue; |
| this.item = item; |
| dijit.form.ComboBox.superclass._setValueAttr.call(this, this.value, priorityChange, displayedValue); |
| }, |
| |
| _announceOption: function(/*Node*/ node){ |
| // summary: |
| // a11y code that puts the highlighted option in the textbox. |
| // This way screen readers will know what is happening in the |
| // menu. |
| |
| if(!node){ |
| return; |
| } |
| // pull the text value from the item attached to the DOM node |
| var newValue; |
| if( node == this._popupWidget.nextButton || |
| node == this._popupWidget.previousButton){ |
| newValue = node.innerHTML; |
| this.item = undefined; |
| this.value = ''; |
| }else{ |
| newValue = this.labelFunc(node.item, this.store); |
| this.attr('item', node.item, false, newValue); |
| } |
| // get the text that the user manually entered (cut off autocompleted text) |
| this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length); |
| // set up ARIA activedescendant |
| dijit.setWaiState(this.focusNode, "activedescendant", dojo.attr(node, "id")); |
| // autocomplete the rest of the option to announce change |
| this._autoCompleteText(newValue); |
| }, |
| |
| _selectOption: function(/*Event*/ evt){ |
| // summary: |
| // Menu callback function, called when an item in the menu is selected. |
| if(evt){ |
| this._announceOption(evt.target); |
| } |
| this._hideResultList(); |
| this._setCaretPos(this.focusNode, this.focusNode.value.length); |
| dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); // set this.value and fire onChange |
| }, |
| |
| _onArrowMouseDown: function(evt){ |
| // summary: |
| // Callback when arrow is clicked |
| if(this.disabled || this.readOnly){ |
| return; |
| } |
| dojo.stopEvent(evt); |
| this.focus(); |
| if(this._isShowingNow){ |
| this._hideResultList(); |
| }else{ |
| // forces full population of results, if they click |
| // on the arrow it means they want to see more options |
| this._startSearchAll(); |
| } |
| }, |
| |
| _startSearchAll: function(){ |
| this._startSearch(''); |
| }, |
| |
| _startSearchFromInput: function(){ |
| this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1")); |
| }, |
| |
| _getQueryString: function(/*String*/ text){ |
| return dojo.string.substitute(this.queryExpr, [text]); |
| }, |
| |
| _startSearch: function(/*String*/ key){ |
| if(!this._popupWidget){ |
| var popupId = this.id + "_popup"; |
| this._popupWidget = new dijit.form._ComboBoxMenu({ |
| onChange: dojo.hitch(this, this._selectOption), |
| id: popupId |
| }); |
| dijit.removeWaiState(this.focusNode,"activedescendant"); |
| dijit.setWaiState(this.textbox,"owns",popupId); // associate popup with textbox |
| } |
| // create a new query to prevent accidentally querying for a hidden |
| // value from FilteringSelect's keyField |
| var query = dojo.clone(this.query); // #5970 |
| this._lastInput = key; // Store exactly what was entered by the user. |
| this._lastQuery = query[this.searchAttr] = this._getQueryString(key); |
| // #5970: set _lastQuery, *then* start the timeout |
| // otherwise, if the user types and the last query returns before the timeout, |
| // _lastQuery won't be set and their input gets rewritten |
| this.searchTimer=setTimeout(dojo.hitch(this, function(query, _this){ |
| this.searchTimer = null; |
| var fetch = { |
| queryOptions: { |
| ignoreCase: this.ignoreCase, |
| deep: true |
| }, |
| query: query, |
| onBegin: dojo.hitch(this, "_setMaxOptions"), |
| onComplete: dojo.hitch(this, "_openResultList"), |
| onError: function(errText){ |
| _this._fetchHandle = null; |
| console.error('dijit.form.ComboBox: ' + errText); |
| dojo.hitch(_this, "_hideResultList")(); |
| }, |
| start: 0, |
| count: this.pageSize |
| }; |
| dojo.mixin(fetch, _this.fetchProperties); |
| this._fetchHandle = _this.store.fetch(fetch); |
| |
| var nextSearch = function(dataObject, direction){ |
| dataObject.start += dataObject.count*direction; |
| // #4091: |
| // tell callback the direction of the paging so the screen |
| // reader knows which menu option to shout |
| dataObject.direction = direction; |
| this._fetchHandle = this.store.fetch(dataObject); |
| }; |
| this._nextSearch = this._popupWidget.onPage = dojo.hitch(this, nextSearch, this._fetchHandle); |
| }, query, this), this.searchDelay); |
| }, |
| |
| _setMaxOptions: function(size, request){ |
| this._maxOptions = size; |
| }, |
| |
| _getValueField: function(){ |
| // summmary: |
| // Helper for postMixInProperties() to set this.value based on data inlined into the markup. |
| // Returns the attribute name in the item (in dijit.form._ComboBoxDataStore) to use as the value. |
| return this.searchAttr; |
| }, |
| |
| /////////////// Event handlers ///////////////////// |
| |
| _arrowPressed: function(){ |
| if(!this.disabled && !this.readOnly && this.hasDownArrow){ |
| dojo.addClass(this.downArrowNode, "dijitArrowButtonActive"); |
| } |
| }, |
| |
| _arrowIdle: function(){ |
| if(!this.disabled && !this.readOnly && this.hasDownArrow){ |
| dojo.removeClass(this.downArrowNode, "dojoArrowButtonPushed"); |
| } |
| }, |
| |
| // FIXME: For 2.0, rename to "_compositionEnd" |
| compositionend: function(/*Event*/ evt){ |
| // summary: |
| // When inputting characters using an input method, such as |
| // Asian languages, it will generate this event instead of |
| // onKeyDown event. |
| // Note: this event is only triggered in FF (not in IE/safari) |
| // tags: |
| // private |
| |
| // 229 is the code produced by IE and safari while pressing keys during |
| // IME input mode |
| this._onKeyPress({charOrCode: 229}); |
| }, |
| |
| //////////// INITIALIZATION METHODS /////////////////////////////////////// |
| |
| constructor: function(){ |
| this.query={}; |
| this.fetchProperties={}; |
| }, |
| |
| postMixInProperties: function(){ |
| if(!this.hasDownArrow){ |
| this.baseClass = "dijitTextBox"; |
| } |
| if(!this.store){ |
| var srcNodeRef = this.srcNodeRef; |
| |
| // if user didn't specify store, then assume there are option tags |
| this.store = new dijit.form._ComboBoxDataStore(srcNodeRef); |
| |
| // if there is no value set and there is an option list, set |
| // the value to the first value to be consistent with native |
| // Select |
| |
| // Firefox and Safari set value |
| // IE6 and Opera set selectedIndex, which is automatically set |
| // by the selected attribute of an option tag |
| // IE6 does not set value, Opera sets value = selectedIndex |
| if( !this.value || ( |
| (typeof srcNodeRef.selectedIndex == "number") && |
| srcNodeRef.selectedIndex.toString() === this.value) |
| ){ |
| var item = this.store.fetchSelectedItem(); |
| if(item){ |
| var valueField = this._getValueField(); |
| this.value = valueField != this.searchAttr? this.store.getValue(item, valueField) : this.labelFunc(item, this.store); |
| } |
| } |
| } |
| this.inherited(arguments); |
| }, |
| |
| postCreate: function(){ |
| // summary: |
| // Subclasses must call this method from their postCreate() methods |
| // tags: |
| // protected |
| |
| // find any associated label element and add to ComboBox node. |
| var label=dojo.query('label[for="'+this.id+'"]'); |
| if(label.length){ |
| label[0].id = (this.id+"_label"); |
| var cn=this.comboNode; |
| dijit.setWaiState(cn, "labelledby", label[0].id); |
| |
| } |
| this.inherited(arguments); |
| }, |
| |
| uninitialize: function(){ |
| if(this._popupWidget && !this._popupWidget._destroyed){ |
| this._hideResultList(); |
| this._popupWidget.destroy(); |
| } |
| this.inherited(arguments); |
| }, |
| |
| _getMenuLabelFromItem: function(/*Item*/ item){ |
| var label = this.labelAttr? this.store.getValue(item, this.labelAttr) : this.labelFunc(item, this.store); |
| var labelType = this.labelType; |
| // If labelType is not "text" we don't want to screw any markup ot whatever. |
| if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){ |
| label = this.doHighlight(label, this._escapeHtml(this._lastInput)); |
| labelType = "html"; |
| } |
| return {html: labelType == "html", label: label}; |
| }, |
| |
| doHighlight: function(/*String*/label, /*String*/find){ |
| // summary: |
| // Highlights the string entered by the user in the menu. By default this |
| // highlights the first occurence found. Override this method |
| // to implement your custom highlighing. |
| // tags: |
| // protected |
| |
| // Add greedy when this.highlightMatch == "all" |
| var modifiers = "i"+(this.highlightMatch == "all"?"g":""); |
| var escapedLabel = this._escapeHtml(label); |
| find = dojo.regexp.escapeString(find); // escape regexp special chars |
| var ret = escapedLabel.replace(new RegExp("(^|\\s)("+ find +")", modifiers), |
| '$1<span class="dijitComboBoxHighlightMatch">$2</span>'); |
| return ret;// returns String, (almost) valid HTML (entities encoded) |
| }, |
| |
| _escapeHtml: function(/*string*/str){ |
| // TODO Should become dojo.html.entities(), when exists use instead |
| // summary: |
| // Adds escape sequences for special characters in XML: &<>"' |
| str = String(str).replace(/&/gm, "&").replace(/</gm, "<") |
| .replace(/>/gm, ">").replace(/"/gm, """); |
| return str; // string |
| }, |
| |
| open: function(){ |
| // summary: |
| // Opens the drop down menu. TODO: rename to _open. |
| // tags: |
| // private |
| this._isShowingNow=true; |
| return dijit.popup.open({ |
| popup: this._popupWidget, |
| around: this.domNode, |
| parent: this |
| }); |
| }, |
| |
| reset: function(){ |
| // Overrides the _FormWidget.reset(). |
| // Additionally reset the .item (to clean up). |
| this.item = null; |
| this.inherited(arguments); |
| }, |
| |
| labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){ |
| // summary: |
| // Computes the label to display based on the dojo.data store item. |
| // returns: |
| // The label that the ComboBox should display |
| // tags: |
| // private |
| |
| // Use toString() because XMLStore returns an XMLItem whereas this |
| // method is expected to return a String (#9354) |
| return store.getValue(item, this.searchAttr).toString(); // String |
| } |
| } |
| ); |
| |
| dojo.declare( |
| "dijit.form._ComboBoxMenu", |
| [dijit._Widget, dijit._Templated], |
| { |
| // summary: |
| // Focus-less menu for internal use in `dijit.form.ComboBox` |
| // tags: |
| // private |
| |
| templateString: "<ul class='dijitReset dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' tabIndex='-1' style='overflow: \"auto\"; overflow-x: \"hidden\";'>" |
| +"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton' waiRole='option'></li>" |
| +"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton' waiRole='option'></li>" |
| +"</ul>", |
| |
| // _messages: Object |
| // Holds "next" and "previous" text for paging buttons on drop down |
| _messages: null, |
| |
| postMixInProperties: function(){ |
| this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang); |
| this.inherited(arguments); |
| }, |
| |
| _setValueAttr: function(/*Object*/ value){ |
| this.value = value; |
| this.onChange(value); |
| }, |
| |
| // stubs |
| onChange: function(/*Object*/ value){ |
| // summary: |
| // Notifies ComboBox/FilteringSelect that user clicked an option in the drop down menu. |
| // Probably should be called onSelect. |
| // tags: |
| // callback |
| }, |
| onPage: function(/*Number*/ direction){ |
| // summary: |
| // Notifies ComboBox/FilteringSelect that user clicked to advance to next/previous page. |
| // tags: |
| // callback |
| }, |
| |
| postCreate: function(){ |
| // fill in template with i18n messages |
| this.previousButton.innerHTML = this._messages["previousMessage"]; |
| this.nextButton.innerHTML = this._messages["nextMessage"]; |
| this.inherited(arguments); |
| }, |
| |
| onClose: function(){ |
| // summary: |
| // Callback from dijit.popup code to this widget, notifying it that it closed |
| // tags: |
| // private |
| this._blurOptionNode(); |
| }, |
| |
| _createOption: function(/*Object*/ item, labelFunc){ |
| // summary: |
| // Creates an option to appear on the popup menu subclassed by |
| // `dijit.form.FilteringSelect`. |
| |
| var labelObject = labelFunc(item); |
| var menuitem = dojo.doc.createElement("li"); |
| dijit.setWaiRole(menuitem, "option"); |
| if(labelObject.html){ |
| menuitem.innerHTML = labelObject.label; |
| }else{ |
| menuitem.appendChild( |
| dojo.doc.createTextNode(labelObject.label) |
| ); |
| } |
| // #3250: in blank options, assign a normal height |
| if(menuitem.innerHTML == ""){ |
| menuitem.innerHTML = " "; |
| } |
| menuitem.item=item; |
| return menuitem; |
| }, |
| |
| createOptions: function(results, dataObject, labelFunc){ |
| // summary: |
| // Fills in the items in the drop down list |
| // results: |
| // Array of dojo.data items |
| // dataObject: |
| // dojo.data store |
| // labelFunc: |
| // Function to produce a label in the drop down list from a dojo.data item |
| |
| //this._dataObject=dataObject; |
| //this._dataObject.onComplete=dojo.hitch(comboBox, comboBox._openResultList); |
| // display "Previous . . ." button |
| this.previousButton.style.display = (dataObject.start == 0) ? "none" : ""; |
| dojo.attr(this.previousButton, "id", this.id + "_prev"); |
| // create options using _createOption function defined by parent |
| // ComboBox (or FilteringSelect) class |
| // #2309: |
| // iterate over cache nondestructively |
| dojo.forEach(results, function(item, i){ |
| var menuitem = this._createOption(item, labelFunc); |
| menuitem.className = "dijitReset dijitMenuItem"; |
| dojo.attr(menuitem, "id", this.id + i); |
| this.domNode.insertBefore(menuitem, this.nextButton); |
| }, this); |
| // display "Next . . ." button |
| var displayMore = false; |
| //Try to determine if we should show 'more'... |
| if(dataObject._maxOptions && dataObject._maxOptions != -1){ |
| if((dataObject.start + dataObject.count) < dataObject._maxOptions){ |
| displayMore = true; |
| }else if((dataObject.start + dataObject.count) > (dataObject._maxOptions - 1)){ |
| //Weird return from a datastore, where a start + count > maxOptions |
| // implies maxOptions isn't really valid and we have to go into faking it. |
| //And more or less assume more if count == results.length |
| if(dataObject.count == results.length){ |
| displayMore = true; |
| } |
| } |
| }else if(dataObject.count == results.length){ |
| //Don't know the size, so we do the best we can based off count alone. |
| //So, if we have an exact match to count, assume more. |
| displayMore = true; |
| } |
| |
| this.nextButton.style.display = displayMore ? "" : "none"; |
| dojo.attr(this.nextButton,"id", this.id + "_next"); |
| return this.domNode.childNodes; |
| }, |
| |
| clearResultList: function(){ |
| // summary: |
| // Clears the entries in the drop down list, but of course keeps the previous and next buttons. |
| while(this.domNode.childNodes.length>2){ |
| this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]); |
| } |
| }, |
| |
| _onMouseDown: function(/*Event*/ evt){ |
| dojo.stopEvent(evt); |
| }, |
| |
| _onMouseUp: function(/*Event*/ evt){ |
| if(evt.target === this.domNode){ |
| return; |
| }else if(evt.target == this.previousButton){ |
| this.onPage(-1); |
| }else if(evt.target == this.nextButton){ |
| this.onPage(1); |
| }else{ |
| var tgt = evt.target; |
| // while the clicked node is inside the div |
| while(!tgt.item){ |
| // recurse to the top |
| tgt = tgt.parentNode; |
| } |
| this._setValueAttr({ target: tgt }, true); |
| } |
| }, |
| |
| _onMouseOver: function(/*Event*/ evt){ |
| if(evt.target === this.domNode){ return; } |
| var tgt = evt.target; |
| if(!(tgt == this.previousButton || tgt == this.nextButton)){ |
| // while the clicked node is inside the div |
| while(!tgt.item){ |
| // recurse to the top |
| tgt = tgt.parentNode; |
| } |
| } |
| this._focusOptionNode(tgt); |
| }, |
| |
| _onMouseOut: function(/*Event*/ evt){ |
| if(evt.target === this.domNode){ return; } |
| this._blurOptionNode(); |
| }, |
| |
| _focusOptionNode: function(/*DomNode*/ node){ |
| // summary: |
| // Does the actual highlight. |
| if(this._highlighted_option != node){ |
| this._blurOptionNode(); |
| this._highlighted_option = node; |
| dojo.addClass(this._highlighted_option, "dijitMenuItemSelected"); |
| } |
| }, |
| |
| _blurOptionNode: function(){ |
| // summary: |
| // Removes highlight on highlighted option. |
| if(this._highlighted_option){ |
| dojo.removeClass(this._highlighted_option, "dijitMenuItemSelected"); |
| this._highlighted_option = null; |
| } |
| }, |
| |
| _highlightNextOption: function(){ |
| // summary: |
| // Highlight the item just below the current selection. |
| // If nothing selected, highlight first option. |
| |
| // because each press of a button clears the menu, |
| // the highlighted option sometimes becomes detached from the menu! |
| // test to see if the option has a parent to see if this is the case. |
| var fc = this.domNode.firstChild; |
| if(!this.getHighlightedOption()){ |
| this._focusOptionNode(fc.style.display == "none" ? fc.nextSibling : fc); |
| }else{ |
| var ns = this._highlighted_option.nextSibling; |
| if(ns && ns.style.display != "none"){ |
| this._focusOptionNode(ns); |
| } |
| } |
| // scrollIntoView is called outside of _focusOptionNode because in IE putting it inside causes the menu to scroll up on mouseover |
| dijit.scrollIntoView(this._highlighted_option); |
| }, |
| |
| highlightFirstOption: function(){ |
| // summary: |
| // Highlight the first real item in the list (not Previous Choices). |
| this._focusOptionNode(this.domNode.firstChild.nextSibling); |
| dijit.scrollIntoView(this._highlighted_option); |
| }, |
| |
| highlightLastOption: function(){ |
| // summary: |
| // Highlight the last real item in the list (not More Choices). |
| this._focusOptionNode(this.domNode.lastChild.previousSibling); |
| dijit.scrollIntoView(this._highlighted_option); |
| }, |
| |
| _highlightPrevOption: function(){ |
| // summary: |
| // Highlight the item just above the current selection. |
| // If nothing selected, highlight last option (if |
| // you select Previous and try to keep scrolling up the list). |
| var lc = this.domNode.lastChild; |
| if(!this.getHighlightedOption()){ |
| this._focusOptionNode(lc.style.display == "none" ? lc.previousSibling : lc); |
| }else{ |
| var ps = this._highlighted_option.previousSibling; |
| if(ps && ps.style.display != "none"){ |
| this._focusOptionNode(ps); |
| } |
| } |
| dijit.scrollIntoView(this._highlighted_option); |
| }, |
| |
| _page: function(/*Boolean*/ up){ |
| // summary: |
| // Handles page-up and page-down keypresses |
| |
| var scrollamount = 0; |
| var oldscroll = this.domNode.scrollTop; |
| var height = dojo.style(this.domNode, "height"); |
| // if no item is highlighted, highlight the first option |
| if(!this.getHighlightedOption()){ |
| this._highlightNextOption(); |
| } |
| while(scrollamount<height){ |
| if(up){ |
| // stop at option 1 |
| if(!this.getHighlightedOption().previousSibling || |
| this._highlighted_option.previousSibling.style.display == "none"){ |
| break; |
| } |
| this._highlightPrevOption(); |
| }else{ |
| // stop at last option |
| if(!this.getHighlightedOption().nextSibling || |
| this._highlighted_option.nextSibling.style.display == "none"){ |
| break; |
| } |
| this._highlightNextOption(); |
| } |
| // going backwards |
| var newscroll=this.domNode.scrollTop; |
| scrollamount+=(newscroll-oldscroll)*(up ? -1:1); |
| oldscroll=newscroll; |
| } |
| }, |
| |
| pageUp: function(){ |
| // summary: |
| // Handles pageup keypress. |
| // TODO: just call _page directly from handleKey(). |
| // tags: |
| // private |
| this._page(true); |
| }, |
| |
| pageDown: function(){ |
| // summary: |
| // Handles pagedown keypress. |
| // TODO: just call _page directly from handleKey(). |
| // tags: |
| // private |
| this._page(false); |
| }, |
| |
| getHighlightedOption: function(){ |
| // summary: |
| // Returns the highlighted option. |
| var ho = this._highlighted_option; |
| return (ho && ho.parentNode) ? ho : null; |
| }, |
| |
| handleKey: function(key){ |
| switch(key){ |
| case dojo.keys.DOWN_ARROW: |
| this._highlightNextOption(); |
| break; |
| case dojo.keys.PAGE_DOWN: |
| this.pageDown(); |
| break; |
| case dojo.keys.UP_ARROW: |
| this._highlightPrevOption(); |
| break; |
| case dojo.keys.PAGE_UP: |
| this.pageUp(); |
| break; |
| } |
| } |
| } |
| ); |
| |
| dojo.declare( |
| "dijit.form.ComboBox", |
| [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin], |
| { |
| // summary: |
| // Auto-completing text box, and base class for dijit.form.FilteringSelect. |
| // |
| // description: |
| // The drop down box's values are populated from an class called |
| // a data provider, which returns a list of values based on the characters |
| // that the user has typed into the input box. |
| // If OPTION tags are used as the data provider via markup, |
| // then the OPTION tag's child text node is used as the widget value |
| // when selected. The OPTION tag's value attribute is ignored. |
| // To set the default value when using OPTION tags, specify the selected |
| // attribute on 1 of the child OPTION tags. |
| // |
| // Some of the options to the ComboBox are actually arguments to the data |
| // provider. |
| |
| _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ |
| // summary: |
| // Hook so attr('value', value) works. |
| // description: |
| // Sets the value of the select. |
| this.item = null; // value not looked up in store |
| if(!value){ value = ''; } // null translates to blank |
| dijit.form.ValidationTextBox.prototype._setValueAttr.call(this, value, priorityChange, displayedValue); |
| } |
| } |
| ); |
| |
| dojo.declare("dijit.form._ComboBoxDataStore", null, { |
| // summary: |
| // Inefficient but small data store specialized for inlined `dijit.form.ComboBox` data |
| // |
| // description: |
| // Provides a store for inlined data like: |
| // |
| // | <select> |
| // | <option value="AL">Alabama</option> |
| // | ... |
| // |
| // Actually. just implements the subset of dojo.data.Read/Notification |
| // needed for ComboBox and FilteringSelect to work. |
| // |
| // Note that an item is just a pointer to the <option> DomNode. |
| |
| constructor: function( /*DomNode*/ root){ |
| this.root = root; |
| |
| dojo.query("> option", root).forEach(function(node){ |
| // TODO: this was added in #3858 but unclear why/if it's needed; doesn't seem to be. |
| // If it is needed then can we just hide the select itself instead? |
| //node.style.display="none"; |
| node.innerHTML = dojo.trim(node.innerHTML); |
| }); |
| |
| }, |
| |
| getValue: function( /* item */ item, |
| /* attribute-name-string */ attribute, |
| /* value? */ defaultValue){ |
| return (attribute == "value") ? item.value : (item.innerText || item.textContent || ''); |
| }, |
| |
| isItemLoaded: function(/* anything */ something){ |
| return true; |
| }, |
| |
| getFeatures: function(){ |
| return {"dojo.data.api.Read": true, "dojo.data.api.Identity": true}; |
| }, |
| |
| _fetchItems: function( /* Object */ args, |
| /* Function */ findCallback, |
| /* Function */ errorCallback){ |
| // summary: |
| // See dojo.data.util.simpleFetch.fetch() |
| if(!args.query){ args.query = {}; } |
| if(!args.query.name){ args.query.name = ""; } |
| if(!args.queryOptions){ args.queryOptions = {}; } |
| var matcher = dojo.data.util.filter.patternToRegExp(args.query.name, args.queryOptions.ignoreCase), |
| items = dojo.query("> option", this.root).filter(function(option){ |
| return (option.innerText || option.textContent || '').match(matcher); |
| } ); |
| if(args.sort){ |
| items.sort(dojo.data.util.sorter.createSortFunction(args.sort, this)); |
| } |
| findCallback(items, args); |
| }, |
| |
| close: function(/*dojo.data.api.Request || args || null */ request){ |
| return; |
| }, |
| |
| getLabel: function(/* item */ item){ |
| return item.innerHTML; |
| }, |
| |
| getIdentity: function(/* item */ item){ |
| return dojo.attr(item, "value"); |
| }, |
| |
| fetchItemByIdentity: function(/* Object */ args){ |
| // summary: |
| // Given the identity of an item, this method returns the item that has |
| // that identity through the onItem callback. |
| // Refer to dojo.data.api.Identity.fetchItemByIdentity() for more details. |
| // |
| // description: |
| // Given arguments like: |
| // |
| // | {identity: "CA", onItem: function(item){...} |
| // |
| // Call `onItem()` with the DOM node `<option value="CA">California</option>` |
| var item = dojo.query("option[value='" + args.identity + "']", this.root)[0]; |
| args.onItem(item); |
| }, |
| |
| fetchSelectedItem: function(){ |
| // summary: |
| // Get the option marked as selected, like `<option selected>`. |
| // Not part of dojo.data API. |
| var root = this.root, |
| si = root.selectedIndex; |
| return dojo.query("> option:nth-child(" + |
| (si != -1 ? si+1 : 1) + ")", |
| root)[0]; // dojo.data.Item |
| } |
| }); |
| //Mix in the simple fetch implementation to this class. |
| dojo.extend(dijit.form._ComboBoxDataStore,dojo.data.util.simpleFetch); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.FilteringSelect"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.FilteringSelect"] = true; |
| dojo.provide("dijit.form.FilteringSelect"); |
| |
| |
| |
| dojo.declare( |
| "dijit.form.FilteringSelect", |
| [dijit.form.MappedTextBox, dijit.form.ComboBoxMixin], |
| { |
| // summary: |
| // An enhanced version of the HTML SELECT tag, populated dynamically |
| // |
| // description: |
| // An enhanced version of the HTML SELECT tag, populated dynamically. It works |
| // very nicely with very large data sets because it can load and page data as needed. |
| // It also resembles ComboBox, but does not allow values outside of the provided ones. |
| // If OPTION tags are used as the data provider via markup, then the |
| // OPTION tag's child text node is used as the displayed value when selected |
| // while the OPTION tag's value attribute is used as the widget value on form submit. |
| // To set the default value when using OPTION tags, specify the selected |
| // attribute on 1 of the child OPTION tags. |
| // |
| // Similar features: |
| // - There is a drop down list of possible values. |
| // - You can only enter a value from the drop down list. (You can't |
| // enter an arbitrary value.) |
| // - The value submitted with the form is the hidden value (ex: CA), |
| // not the displayed value a.k.a. label (ex: California) |
| // |
| // Enhancements over plain HTML version: |
| // - If you type in some text then it will filter down the list of |
| // possible values in the drop down list. |
| // - List can be specified either as a static list or via a javascript |
| // function (that can get the list from a server) |
| |
| _isvalid: true, |
| |
| // required: Boolean |
| // True (default) if user is required to enter a value into this field. |
| required: true, |
| |
| _lastDisplayedValue: "", |
| |
| isValid: function(){ |
| // Overrides ValidationTextBox.isValid() |
| return this._isvalid || (!this.required && this.attr('displayedValue') == ""); // #5974 |
| }, |
| |
| _callbackSetLabel: function( /*Array*/ result, |
| /*Object*/ dataObject, |
| /*Boolean?*/ priorityChange){ |
| // summary: |
| // Callback function that dynamically sets the label of the |
| // ComboBox |
| |
| // setValue does a synchronous lookup, |
| // so it calls _callbackSetLabel directly, |
| // and so does not pass dataObject |
| // still need to test against _lastQuery in case it came too late |
| if((dataObject && dataObject.query[this.searchAttr] != this._lastQuery) || (!dataObject && result.length && this.store.getIdentity(result[0]) != this._lastQuery)){ |
| return; |
| } |
| if(!result.length){ |
| //#3268: do nothing on bad input |
| //#3285: change CSS to indicate error |
| this.valueNode.value = ""; |
| dijit.form.TextBox.superclass._setValueAttr.call(this, "", priorityChange || (priorityChange === undefined && !this._focused)); |
| this._isvalid = false; |
| this.validate(this._focused); |
| this.item = null; |
| }else{ |
| this.attr('item', result[0], priorityChange); |
| } |
| }, |
| |
| _openResultList: function(/*Object*/ results, /*Object*/ dataObject){ |
| // Overrides ComboBox._openResultList() |
| |
| // #3285: tap into search callback to see if user's query resembles a match |
| if(dataObject.query[this.searchAttr] != this._lastQuery){ |
| return; |
| } |
| this._isvalid = results.length != 0; // FIXME: should this be greater-than? |
| this.validate(true); |
| dijit.form.ComboBoxMixin.prototype._openResultList.apply(this, arguments); |
| }, |
| |
| _getValueAttr: function(){ |
| // summary: |
| // Hook for attr('value') to work. |
| |
| // don't get the textbox value but rather the previously set hidden value. |
| // Use this.valueNode.value which isn't always set for other MappedTextBox widgets until blur |
| return this.valueNode.value; |
| }, |
| |
| _getValueField: function(){ |
| // Overrides ComboBox._getValueField() |
| return "value"; |
| }, |
| |
| _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){ |
| // summary: |
| // Hook so attr('value', value) works. |
| // description: |
| // Sets the value of the select. |
| // Also sets the label to the corresponding value by reverse lookup. |
| if(!this._onChangeActive){ priorityChange = null; } |
| this._lastQuery = value; |
| |
| if(value === null || value === ''){ |
| this._setDisplayedValueAttr('', priorityChange); |
| return; |
| } |
| |
| //#3347: fetchItemByIdentity if no keyAttr specified |
| var self = this; |
| this.store.fetchItemByIdentity({ |
| identity: value, |
| onItem: function(item){ |
| self._callbackSetLabel([item], undefined, priorityChange); |
| } |
| }); |
| }, |
| |
| _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ |
| // summary: |
| // Set the displayed valued in the input box, and the hidden value |
| // that gets submitted, based on a dojo.data store item. |
| // description: |
| // Users shouldn't call this function; they should be calling |
| // attr('item', value) |
| // tags: |
| // private |
| this._isvalid = true; |
| this.inherited(arguments); |
| this.valueNode.value = this.value; |
| this._lastDisplayedValue = this.textbox.value; |
| }, |
| |
| _getDisplayQueryString: function(/*String*/ text){ |
| return text.replace(/([\\\*\?])/g, "\\$1"); |
| }, |
| |
| _setDisplayedValueAttr: function(/*String*/ label, /*Boolean?*/ priorityChange){ |
| // summary: |
| // Hook so attr('displayedValue', label) works. |
| // description: |
| // Sets textbox to display label. Also performs reverse lookup |
| // to set the hidden value. |
| |
| // When this is called during initialization it'll ping the datastore |
| // for reverse lookup, and when that completes (after an XHR request) |
| // will call setValueAttr()... but that shouldn't trigger an onChange() |
| // event, even when it happens after creation has finished |
| if(!this._created){ |
| priorityChange = false; |
| } |
| |
| if(this.store){ |
| this._hideResultList(); |
| var query = dojo.clone(this.query); // #6196: populate query with user-specifics |
| // escape meta characters of dojo.data.util.filter.patternToRegExp(). |
| this._lastQuery = query[this.searchAttr] = this._getDisplayQueryString(label); |
| // if the label is not valid, the callback will never set it, |
| // so the last valid value will get the warning textbox set the |
| // textbox value now so that the impending warning will make |
| // sense to the user |
| this.textbox.value = label; |
| this._lastDisplayedValue = label; |
| var _this = this; |
| var fetch = { |
| query: query, |
| queryOptions: { |
| ignoreCase: this.ignoreCase, |
| deep: true |
| }, |
| onComplete: function(result, dataObject){ |
| _this._fetchHandle = null; |
| dojo.hitch(_this, "_callbackSetLabel")(result, dataObject, priorityChange); |
| }, |
| onError: function(errText){ |
| _this._fetchHandle = null; |
| console.error('dijit.form.FilteringSelect: ' + errText); |
| dojo.hitch(_this, "_callbackSetLabel")([], undefined, false); |
| } |
| }; |
| dojo.mixin(fetch, this.fetchProperties); |
| this._fetchHandle = this.store.fetch(fetch); |
| } |
| }, |
| |
| postMixInProperties: function(){ |
| this.inherited(arguments); |
| this._isvalid = !this.required; |
| }, |
| |
| undo: function(){ |
| this.attr('displayedValue', this._lastDisplayedValue); |
| } |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.MultiSelect"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.MultiSelect"] = true; |
| dojo.provide("dijit.form.MultiSelect"); |
| |
| |
| |
| dojo.declare("dijit.form.MultiSelect", dijit.form._FormValueWidget, { |
| // summary: |
| // Widget version of a <select multiple=true> element, |
| // for selecting multiple options. |
| |
| // size: Number |
| // Number of elements to display on a page |
| // NOTE: may be removed in version 2.0, since elements may have variable height; |
| // set the size via style="..." or CSS class names instead. |
| size: 7, |
| |
| templateString: "<select multiple='true' ${nameAttrSetting} dojoAttachPoint='containerNode,focusNode' dojoAttachEvent='onchange: _onChange'></select>", |
| |
| attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { |
| size: "focusNode" |
| }), |
| |
| reset: function(){ |
| // summary: |
| // Reset the widget's value to what it was at initialization time |
| |
| // TODO: once we inherit from FormValueWidget this won't be needed |
| this._hasBeenBlurred = false; |
| this._setValueAttr(this._resetValue, true); |
| }, |
| |
| addSelected: function(/* dijit.form.MultiSelect */ select){ |
| // summary: |
| // Move the selected nodes of a passed Select widget |
| // instance to this Select widget. |
| // |
| // example: |
| // | // move all the selected values from "bar" to "foo" |
| // | dijit.byId("foo").addSelected(dijit.byId("bar")); |
| |
| select.getSelected().forEach(function(n){ |
| this.containerNode.appendChild(n); |
| // scroll to bottom to see item |
| // cannot use scrollIntoView since <option> tags don't support all attributes |
| // does not work on IE due to a bug where <select> always shows scrollTop = 0 |
| this.domNode.scrollTop = this.domNode.offsetHeight; // overshoot will be ignored |
| // scrolling the source select is trickier esp. on safari who forgets to change the scrollbar size |
| var oldscroll = select.domNode.scrollTop; |
| select.domNode.scrollTop = 0; |
| select.domNode.scrollTop = oldscroll; |
| },this); |
| }, |
| |
| getSelected: function(){ |
| // summary: |
| // Access the NodeList of the selected options directly |
| return dojo.query("option",this.containerNode).filter(function(n){ |
| return n.selected; // Boolean |
| }); // dojo.NodeList |
| }, |
| |
| _getValueAttr: function(){ |
| // summary: |
| // Hook so attr('value') works. |
| // description: |
| // Returns an array of the selected options' values. |
| return this.getSelected().map(function(n){ |
| return n.value; |
| }); |
| }, |
| |
| multiple: true, // for Form |
| |
| _setValueAttr: function(/* Array */values){ |
| // summary: |
| // Hook so attr('value', values) works. |
| // description: |
| // Set the value(s) of this Select based on passed values |
| dojo.query("option",this.containerNode).forEach(function(n){ |
| n.selected = (dojo.indexOf(values,n.value) != -1); |
| }); |
| }, |
| |
| invertSelection: function(onChange){ |
| // summary: |
| // Invert the selection |
| // onChange: Boolean |
| // If null, onChange is not fired. |
| dojo.query("option",this.containerNode).forEach(function(n){ |
| n.selected = !n.selected; |
| }); |
| this._handleOnChange(this.attr('value'), onChange == true); |
| }, |
| |
| _onChange: function(/*Event*/ e){ |
| this._handleOnChange(this.attr('value'), true); |
| }, |
| |
| // for layout widgets: |
| resize: function(/* Object */size){ |
| if(size){ |
| dojo.marginBox(this.domNode, size); |
| } |
| }, |
| |
| postCreate: function(){ |
| this._onChange(); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.HorizontalSlider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.HorizontalSlider"] = true; |
| dojo.provide("dijit.form.HorizontalSlider"); |
| |
| |
| |
| |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.form.HorizontalSlider", |
| [dijit.form._FormValueWidget, dijit._Container], |
| { |
| // summary: |
| // A form widget that allows one to select a value with a horizontally draggable handle |
| |
| templateString: dojo.cache("dijit.form", "templates/HorizontalSlider.html", "<table class=\"dijit dijitReset dijitSlider\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" rules=\"none\" dojoAttachEvent=\"onkeypress:_onKeyPress,onkeyup:_onKeyUp\"\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t\t><td dojoAttachPoint=\"topDecoration\" class=\"dijitReset\" style=\"text-align:center;width:100%;\"></td\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerH\"\n\t\t\t><div class=\"dijitSliderDecrementIconH\" tabIndex=\"-1\" style=\"display:none\" dojoAttachPoint=\"decrementButton\"><span class=\"dijitSliderButtonInner\">-</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperH dijitSliderLeftBumper dijitSliderLeftBumper\" dojoAttachEvent=\"onmousedown:_onClkDecBumper\"></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><input dojoAttachPoint=\"valueNode\" type=\"hidden\" ${nameAttrSetting}\n\t\t\t/><div class=\"dijitReset dijitSliderBarContainerH\" waiRole=\"presentation\" dojoAttachPoint=\"sliderBarContainer\"\n\t\t\t\t><div waiRole=\"presentation\" dojoAttachPoint=\"progressBar\" class=\"dijitSliderBar dijitSliderBarH dijitSliderProgressBar dijitSliderProgressBarH\" dojoAttachEvent=\"onmousedown:_onBarClick\"\n\t\t\t\t\t><div class=\"dijitSliderMoveable dijitSliderMoveableH\"\n\t\t\t\t\t\t><div dojoAttachPoint=\"sliderHandle,focusNode\" class=\"dijitSliderImageHandle dijitSliderImageHandleH\" dojoAttachEvent=\"onmousedown:_onHandleClick\" waiRole=\"slider\" valuemin=\"${minimum}\" valuemax=\"${maximum}\"></div\n\t\t\t\t\t></div\n\t\t\t\t></div\n\t\t\t\t><div waiRole=\"presentation\" dojoAttachPoint=\"remainingBar\" class=\"dijitSliderBar dijitSliderBarH dijitSliderRemainingBar dijitSliderRemainingBarH\" dojoAttachEvent=\"onmousedown:_onBarClick\"></div\n\t\t\t></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperH dijitSliderRightBumper dijitSliderRightBumper\" dojoAttachEvent=\"onmousedown:_onClkIncBumper\"></div\n\t\t></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerH\" style=\"right:0px;\"\n\t\t\t><div class=\"dijitSliderIncrementIconH\" tabIndex=\"-1\" style=\"display:none\" dojoAttachPoint=\"incrementButton\"><span class=\"dijitSliderButtonInner\">+</span></div\n\t\t></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t\t><td dojoAttachPoint=\"containerNode,bottomDecoration\" class=\"dijitReset\" style=\"text-align:center;\"></td\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t></tr\n></table>\n"), |
| |
| // Overrides FormValueWidget.value to indicate numeric value |
| value: 0, |
| |
| // showButtons: Boolean |
| // Show increment/decrement buttons at the ends of the slider? |
| showButtons: true, |
| |
| // minimum:: Integer |
| // The minimum value the slider can be set to. |
| minimum: 0, |
| |
| // maximum: Integer |
| // The maximum value the slider can be set to. |
| maximum: 100, |
| |
| // discreteValues: Integer |
| // If specified, indicates that the slider handle has only 'discreteValues' possible positions, |
| // and that after dragging the handle, it will snap to the nearest possible position. |
| // Thus, the slider has only 'discreteValues' possible values. |
| // |
| // For example, if minimum=10, maxiumum=30, and discreteValues=3, then the slider handle has |
| // three possible positions, representing values 10, 20, or 30. |
| // |
| // If discreteValues is not specified or if it's value is higher than the number of pixels |
| // in the slider bar, then the slider handle can be moved freely, and the slider's value will be |
| // computed/reported based on pixel position (in this case it will likely be fractional, |
| // such as 123.456789). |
| discreteValues: Infinity, |
| |
| // pageIncrement: Integer |
| // If discreteValues is also specified, this indicates the amount of clicks (ie, snap positions) |
| // that the slider handle is moved via pageup/pagedown keys. |
| // If discreteValues is not specified, it indicates the number of pixels. |
| pageIncrement: 2, |
| |
| // clickSelect: Boolean |
| // If clicking the slider bar changes the value or not |
| clickSelect: true, |
| |
| // slideDuration: Number |
| // The time in ms to take to animate the slider handle from 0% to 100%, |
| // when clicking the slider bar to make the handle move. |
| slideDuration: dijit.defaultDuration, |
| |
| // Flag to _Templated |
| widgetsInTemplate: true, |
| |
| attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { |
| id: "" |
| }), |
| |
| baseClass: "dijitSlider", |
| |
| _mousePixelCoord: "pageX", |
| _pixelCount: "w", |
| _startingPixelCoord: "x", |
| _startingPixelCount: "l", |
| _handleOffsetCoord: "left", |
| _progressPixelSize: "width", |
| |
| _onKeyUp: function(/*Event*/ e){ |
| if(this.disabled || this.readOnly || e.altKey || e.ctrlKey || e.metaKey){ return; } |
| this._setValueAttr(this.value, true); |
| }, |
| |
| _onKeyPress: function(/*Event*/ e){ |
| if(this.disabled || this.readOnly || e.altKey || e.ctrlKey || e.metaKey){ return; } |
| switch(e.charOrCode){ |
| case dojo.keys.HOME: |
| this._setValueAttr(this.minimum, false); |
| break; |
| case dojo.keys.END: |
| this._setValueAttr(this.maximum, false); |
| break; |
| // this._descending === false: if ascending vertical (min on top) |
| // (this._descending || this.isLeftToRight()): if left-to-right horizontal or descending vertical |
| case ((this._descending || this.isLeftToRight()) ? dojo.keys.RIGHT_ARROW : dojo.keys.LEFT_ARROW): |
| case (this._descending === false ? dojo.keys.DOWN_ARROW : dojo.keys.UP_ARROW): |
| case (this._descending === false ? dojo.keys.PAGE_DOWN : dojo.keys.PAGE_UP): |
| this.increment(e); |
| break; |
| case ((this._descending || this.isLeftToRight()) ? dojo.keys.LEFT_ARROW : dojo.keys.RIGHT_ARROW): |
| case (this._descending === false ? dojo.keys.UP_ARROW : dojo.keys.DOWN_ARROW): |
| case (this._descending === false ? dojo.keys.PAGE_UP : dojo.keys.PAGE_DOWN): |
| this.decrement(e); |
| break; |
| default: |
| return; |
| } |
| dojo.stopEvent(e); |
| }, |
| |
| _onHandleClick: function(e){ |
| if(this.disabled || this.readOnly){ return; } |
| if(!dojo.isIE){ |
| // make sure you get focus when dragging the handle |
| // (but don't do on IE because it causes a flicker on mouse up (due to blur then focus) |
| dijit.focus(this.sliderHandle); |
| } |
| dojo.stopEvent(e); |
| }, |
| |
| _isReversed: function(){ |
| // summary: |
| // Returns true if direction is from right to left |
| // tags: |
| // protected extension |
| return !this.isLeftToRight(); |
| }, |
| |
| _onBarClick: function(e){ |
| if(this.disabled || this.readOnly || !this.clickSelect){ return; } |
| dijit.focus(this.sliderHandle); |
| dojo.stopEvent(e); |
| var abspos = dojo.position(this.sliderBarContainer, true); |
| var pixelValue = e[this._mousePixelCoord] - abspos[this._startingPixelCoord]; |
| this._setPixelValue(this._isReversed() ? (abspos[this._pixelCount] - pixelValue) : pixelValue, abspos[this._pixelCount], true); |
| this._movable.onMouseDown(e); |
| }, |
| |
| _setPixelValue: function(/*Number*/ pixelValue, /*Number*/ maxPixels, /*Boolean, optional*/ priorityChange){ |
| if(this.disabled || this.readOnly){ return; } |
| pixelValue = pixelValue < 0 ? 0 : maxPixels < pixelValue ? maxPixels : pixelValue; |
| var count = this.discreteValues; |
| if(count <= 1 || count == Infinity){ count = maxPixels; } |
| count--; |
| var pixelsPerValue = maxPixels / count; |
| var wholeIncrements = Math.round(pixelValue / pixelsPerValue); |
| this._setValueAttr((this.maximum-this.minimum)*wholeIncrements/count + this.minimum, priorityChange); |
| }, |
| |
| _setValueAttr: function(/*Number*/ value, /*Boolean, optional*/ priorityChange){ |
| // summary: |
| // Hook so attr('value', value) works. |
| this.valueNode.value = this.value = value; |
| dijit.setWaiState(this.focusNode, "valuenow", value); |
| this.inherited(arguments); |
| var percent = (value - this.minimum) / (this.maximum - this.minimum); |
| var progressBar = (this._descending === false) ? this.remainingBar : this.progressBar; |
| var remainingBar = (this._descending === false) ? this.progressBar : this.remainingBar; |
| if(this._inProgressAnim && this._inProgressAnim.status != "stopped"){ |
| this._inProgressAnim.stop(true); |
| } |
| if(priorityChange && this.slideDuration > 0 && progressBar.style[this._progressPixelSize]){ |
| // animate the slider |
| var _this = this; |
| var props = {}; |
| var start = parseFloat(progressBar.style[this._progressPixelSize]); |
| var duration = this.slideDuration * (percent-start/100); |
| if(duration == 0){ return; } |
| if(duration < 0){ duration = 0 - duration; } |
| props[this._progressPixelSize] = { start: start, end: percent*100, units:"%" }; |
| this._inProgressAnim = dojo.animateProperty({ node: progressBar, duration: duration, |
| onAnimate: function(v){ remainingBar.style[_this._progressPixelSize] = (100-parseFloat(v[_this._progressPixelSize])) + "%"; }, |
| onEnd: function(){ delete _this._inProgressAnim; }, |
| properties: props |
| }) |
| this._inProgressAnim.play(); |
| } |
| else{ |
| progressBar.style[this._progressPixelSize] = (percent*100) + "%"; |
| remainingBar.style[this._progressPixelSize] = ((1-percent)*100) + "%"; |
| } |
| }, |
| |
| _bumpValue: function(signedChange, /*Boolean, optional*/ priorityChange){ |
| if(this.disabled || this.readOnly){ return; } |
| var s = dojo.getComputedStyle(this.sliderBarContainer); |
| var c = dojo._getContentBox(this.sliderBarContainer, s); |
| var count = this.discreteValues; |
| if(count <= 1 || count == Infinity){ count = c[this._pixelCount]; } |
| count--; |
| var value = (this.value - this.minimum) * count / (this.maximum - this.minimum) + signedChange; |
| if(value < 0){ value = 0; } |
| if(value > count){ value = count; } |
| value = value * (this.maximum - this.minimum) / count + this.minimum; |
| this._setValueAttr(value, priorityChange); |
| }, |
| |
| _onClkBumper: function(val){ |
| if(this.disabled || this.readOnly || !this.clickSelect){ return; } |
| this._setValueAttr(val, true); |
| }, |
| |
| _onClkIncBumper: function(){ |
| this._onClkBumper(this._descending === false ? this.minimum : this.maximum); |
| }, |
| |
| _onClkDecBumper: function(){ |
| this._onClkBumper(this._descending === false ? this.maximum : this.minimum); |
| }, |
| |
| decrement: function(/*Event*/ e){ |
| // summary: |
| // Decrement slider |
| // tags: |
| // private |
| this._bumpValue(e.charOrCode == dojo.keys.PAGE_DOWN ? -this.pageIncrement : -1); |
| }, |
| |
| increment: function(/*Event*/ e){ |
| // summary: |
| // Increment slider |
| // tags: |
| // private |
| this._bumpValue(e.charOrCode == dojo.keys.PAGE_UP ? this.pageIncrement : 1); |
| }, |
| |
| _mouseWheeled: function(/*Event*/ evt){ |
| // summary: |
| // Event handler for mousewheel where supported |
| dojo.stopEvent(evt); |
| var janky = !dojo.isMozilla; |
| var scroll = evt[(janky ? "wheelDelta" : "detail")] * (janky ? 1 : -1); |
| this._bumpValue(scroll < 0 ? -1 : 1, true); // negative scroll acts like a decrement |
| }, |
| |
| startup: function(){ |
| dojo.forEach(this.getChildren(), function(child){ |
| if(this[child.container] != this.containerNode){ |
| this[child.container].appendChild(child.domNode); |
| } |
| }, this); |
| }, |
| |
| _typematicCallback: function(/*Number*/ count, /*Object*/ button, /*Event*/ e){ |
| if(count == -1){ |
| this._setValueAttr(this.value, true); |
| }else{ |
| this[(button == (this._descending? this.incrementButton : this.decrementButton)) ? "decrement" : "increment"](e); |
| } |
| }, |
| |
| postCreate: function(){ |
| if(this.showButtons){ |
| this.incrementButton.style.display=""; |
| this.decrementButton.style.display=""; |
| this._connects.push(dijit.typematic.addMouseListener( |
| this.decrementButton, this, "_typematicCallback", 25, 500)); |
| this._connects.push(dijit.typematic.addMouseListener( |
| this.incrementButton, this, "_typematicCallback", 25, 500)); |
| } |
| this.connect(this.domNode, !dojo.isMozilla ? "onmousewheel" : "DOMMouseScroll", "_mouseWheeled"); |
| |
| // define a custom constructor for a SliderMover that points back to me |
| var mover = dojo.declare(dijit.form._SliderMover, { |
| widget: this |
| }); |
| |
| this._movable = new dojo.dnd.Moveable(this.sliderHandle, {mover: mover}); |
| // find any associated label element and add to slider focusnode. |
| var label=dojo.query('label[for="'+this.id+'"]'); |
| if(label.length){ |
| label[0].id = (this.id+"_label"); |
| dijit.setWaiState(this.focusNode, "labelledby", label[0].id); |
| } |
| dijit.setWaiState(this.focusNode, "valuemin", this.minimum); |
| dijit.setWaiState(this.focusNode, "valuemax", this.maximum); |
| |
| this.inherited(arguments); |
| this._layoutHackIE7(); |
| }, |
| |
| destroy: function(){ |
| this._movable.destroy(); |
| if(this._inProgressAnim && this._inProgressAnim.status != "stopped"){ |
| this._inProgressAnim.stop(true); |
| } |
| this._supportingWidgets = dijit.findWidgets(this.domNode); // tells destroy about pseudo-child widgets (ruler/labels) |
| this.inherited(arguments); |
| } |
| }); |
| |
| dojo.declare("dijit.form._SliderMover", |
| dojo.dnd.Mover, |
| { |
| onMouseMove: function(e){ |
| var widget = this.widget; |
| var abspos = widget._abspos; |
| if(!abspos){ |
| abspos = widget._abspos = dojo.position(widget.sliderBarContainer, true); |
| widget._setPixelValue_ = dojo.hitch(widget, "_setPixelValue"); |
| widget._isReversed_ = widget._isReversed(); |
| } |
| var pixelValue = e[widget._mousePixelCoord] - abspos[widget._startingPixelCoord]; |
| widget._setPixelValue_(widget._isReversed_ ? (abspos[widget._pixelCount]-pixelValue) : pixelValue, abspos[widget._pixelCount], false); |
| }, |
| |
| destroy: function(e){ |
| dojo.dnd.Mover.prototype.destroy.apply(this, arguments); |
| var widget = this.widget; |
| widget._abspos = null; |
| widget._setValueAttr(widget.value, true); |
| } |
| }); |
| |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.VerticalSlider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.VerticalSlider"] = true; |
| dojo.provide("dijit.form.VerticalSlider"); |
| |
| |
| |
| dojo.declare( |
| "dijit.form.VerticalSlider", |
| dijit.form.HorizontalSlider, |
| { |
| // summary: |
| // A form widget that allows one to select a value with a vertically draggable handle |
| |
| templateString: dojo.cache("dijit.form", "templates/VerticalSlider.html", "<table class=\"dijitReset dijitSlider\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" rules=\"none\" dojoAttachEvent=\"onkeypress:_onKeyPress,onkeyup:_onKeyUp\"\n><tbody class=\"dijitReset\"\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerV\"\n\t\t\t><div class=\"dijitSliderIncrementIconV\" tabIndex=\"-1\" style=\"display:none\" dojoAttachPoint=\"decrementButton\"><span class=\"dijitSliderButtonInner\">+</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><center><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperV dijitSliderTopBumper dijitSliderTopBumper\" dojoAttachEvent=\"onmousedown:_onClkIncBumper\"></div></center\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td dojoAttachPoint=\"leftDecoration\" class=\"dijitReset\" style=\"text-align:center;height:100%;\"></td\n\t\t><td class=\"dijitReset\" style=\"height:100%;\"\n\t\t\t><input dojoAttachPoint=\"valueNode\" type=\"hidden\" ${nameAttrSetting}\n\t\t\t/><center class=\"dijitReset dijitSliderBarContainerV\" waiRole=\"presentation\" dojoAttachPoint=\"sliderBarContainer\"\n\t\t\t\t><div waiRole=\"presentation\" dojoAttachPoint=\"remainingBar\" class=\"dijitSliderBar dijitSliderBarV dijitSliderRemainingBar dijitSliderRemainingBarV\" dojoAttachEvent=\"onmousedown:_onBarClick\"><!--#5629--></div\n\t\t\t\t><div waiRole=\"presentation\" dojoAttachPoint=\"progressBar\" class=\"dijitSliderBar dijitSliderBarV dijitSliderProgressBar dijitSliderProgressBarV\" dojoAttachEvent=\"onmousedown:_onBarClick\"\n\t\t\t\t\t><div class=\"dijitSliderMoveable\" style=\"vertical-align:top;\"\n\t\t\t\t\t\t><div dojoAttachPoint=\"sliderHandle,focusNode\" class=\"dijitSliderImageHandle dijitSliderImageHandleV\" dojoAttachEvent=\"onmousedown:_onHandleClick\" waiRole=\"slider\" valuemin=\"${minimum}\" valuemax=\"${maximum}\"></div\n\t\t\t\t\t></div\n\t\t\t\t></div\n\t\t\t></center\n\t\t></td\n\t\t><td dojoAttachPoint=\"containerNode,rightDecoration\" class=\"dijitReset\" style=\"text-align:center;height:100%;\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><center><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperV dijitSliderBottomBumper dijitSliderBottomBumper\" dojoAttachEvent=\"onmousedown:_onClkDecBumper\"></div></center\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerV\"\n\t\t\t><div class=\"dijitSliderDecrementIconV\" tabIndex=\"-1\" style=\"display:none\" dojoAttachPoint=\"incrementButton\"><span class=\"dijitSliderButtonInner\">-</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n></tbody></table>\n"), |
| _mousePixelCoord: "pageY", |
| _pixelCount: "h", |
| _startingPixelCoord: "y", |
| _startingPixelCount: "t", |
| _handleOffsetCoord: "top", |
| _progressPixelSize: "height", |
| |
| // _descending: Boolean |
| // Specifies if the slider values go from high-on-top (true), or low-on-top (false) |
| // TODO: expose this in 1.2 - the css progress/remaining bar classes need to be reversed |
| _descending: true, |
| |
| startup: function(){ |
| if(this._started){ return; } |
| |
| if(!this.isLeftToRight() && dojo.isMoz){ |
| if(this.leftDecoration){this._rtlRectify(this.leftDecoration);} |
| if(this.rightDecoration){this._rtlRectify(this.rightDecoration);} |
| } |
| |
| this.inherited(arguments); |
| }, |
| |
| _isReversed: function(){ |
| // summary: |
| // Overrides HorizontalSlider._isReversed. |
| // Indicates if values are high on top (with low numbers on the bottom). |
| return this._descending; |
| }, |
| |
| _rtlRectify: function(decorationNode/*NodeList*/){ |
| // summary: |
| // Helper function on gecko. |
| // Rectify children nodes for left/right decoration in rtl case. |
| // Simply switch the rule and label child for each decoration node. |
| // tags: |
| // private |
| var childNodes = []; |
| while(decorationNode.firstChild){ |
| childNodes.push(decorationNode.firstChild); |
| decorationNode.removeChild(decorationNode.firstChild); |
| } |
| for(var i = childNodes.length-1; i >=0; i--){ |
| if(childNodes[i]){ |
| decorationNode.appendChild(childNodes[i]); |
| } |
| } |
| } |
| }); |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.HorizontalRule"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.HorizontalRule"] = true; |
| dojo.provide("dijit.form.HorizontalRule"); |
| |
| |
| |
| |
| dojo.declare("dijit.form.HorizontalRule", [dijit._Widget, dijit._Templated], |
| { |
| // summary: |
| // Hash marks for `dijit.form.HorizontalSlider` |
| |
| templateString: '<div class="dijitRuleContainer dijitRuleContainerH"></div>', |
| |
| // count: Integer |
| // Number of hash marks to generate |
| count: 3, |
| |
| // container: String |
| // For HorizontalSlider, this is either "topDecoration" or "bottomDecoration", |
| // and indicates whether this rule goes above or below the slider. |
| container: "containerNode", |
| |
| // ruleStyle: String |
| // CSS style to apply to individual hash marks |
| ruleStyle: "", |
| |
| _positionPrefix: '<div class="dijitRuleMark dijitRuleMarkH" style="left:', |
| _positionSuffix: '%;', |
| _suffix: '"></div>', |
| |
| _genHTML: function(pos, ndx){ |
| return this._positionPrefix + pos + this._positionSuffix + this.ruleStyle + this._suffix; |
| }, |
| |
| // _isHorizontal: [protected extension] Boolean |
| // VerticalRule will override this... |
| _isHorizontal: true, |
| |
| postCreate: function(){ |
| var innerHTML; |
| if(this.count == 1){ |
| innerHTML = this._genHTML(50, 0); |
| }else{ |
| var i; |
| var interval = 100 / (this.count-1); |
| if(!this._isHorizontal || this.isLeftToRight()){ |
| innerHTML = this._genHTML(0, 0); |
| for(i=1; i < this.count-1; i++){ |
| innerHTML += this._genHTML(interval*i, i); |
| } |
| innerHTML += this._genHTML(100, this.count-1); |
| }else{ |
| innerHTML = this._genHTML(100, 0); |
| for(i=1; i < this.count-1; i++){ |
| innerHTML += this._genHTML(100-interval*i, i); |
| } |
| innerHTML += this._genHTML(0, this.count-1); |
| } |
| } |
| this.domNode.innerHTML = innerHTML; |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.VerticalRule"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.VerticalRule"] = true; |
| dojo.provide("dijit.form.VerticalRule"); |
| |
| |
| |
| dojo.declare("dijit.form.VerticalRule", dijit.form.HorizontalRule, |
| { |
| // summary: |
| // Hash marks for the `dijit.form.VerticalSlider` |
| |
| templateString: '<div class="dijitRuleContainer dijitRuleContainerV"></div>', |
| _positionPrefix: '<div class="dijitRuleMark dijitRuleMarkV" style="top:', |
| |
| /*===== |
| // container: String |
| // This is either "leftDecoration" or "rightDecoration", |
| // to indicate whether this rule goes to the left or to the right of the slider. |
| // Note that on RTL system, "leftDecoration" would actually go to the right, and vice-versa. |
| container: "", |
| =====*/ |
| |
| // Overrides HorizontalRule._isHorizontal |
| _isHorizontal: false |
| |
| }); |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.HorizontalRuleLabels"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.HorizontalRuleLabels"] = true; |
| dojo.provide("dijit.form.HorizontalRuleLabels"); |
| |
| |
| |
| dojo.declare("dijit.form.HorizontalRuleLabels", dijit.form.HorizontalRule, |
| { |
| // summary: |
| // Labels for `dijit.form.HorizontalSlider` |
| |
| templateString: '<div class="dijitRuleContainer dijitRuleContainerH dijitRuleLabelsContainer dijitRuleLabelsContainerH"></div>', |
| |
| // labelStyle: String |
| // CSS style to apply to individual text labels |
| labelStyle: "", |
| |
| // labels: String[]? |
| // Array of text labels to render - evenly spaced from left-to-right or bottom-to-top. |
| // Alternately, minimum and maximum can be specified, to get numeric labels. |
| labels: [], |
| |
| // numericMargin: Integer |
| // Number of generated numeric labels that should be rendered as '' on the ends when labels[] are not specified |
| numericMargin: 0, |
| |
| // numericMinimum: Integer |
| // Leftmost label value for generated numeric labels when labels[] are not specified |
| minimum: 0, |
| |
| // numericMaximum: Integer |
| // Rightmost label value for generated numeric labels when labels[] are not specified |
| maximum: 1, |
| |
| // constraints: Object |
| // pattern, places, lang, et al (see dojo.number) for generated numeric labels when labels[] are not specified |
| constraints: {pattern:"#%"}, |
| |
| _positionPrefix: '<div class="dijitRuleLabelContainer dijitRuleLabelContainerH" style="left:', |
| _labelPrefix: '"><span class="dijitRuleLabel dijitRuleLabelH">', |
| _suffix: '</span></div>', |
| |
| _calcPosition: function(pos){ |
| // summary: |
| // Returns the value to be used in HTML for the label as part of the left: attribute |
| // tags: |
| // protected extension |
| return pos; |
| }, |
| |
| _genHTML: function(pos, ndx){ |
| return this._positionPrefix + this._calcPosition(pos) + this._positionSuffix + this.labelStyle + this._labelPrefix + this.labels[ndx] + this._suffix; |
| }, |
| |
| getLabels: function(){ |
| // summary: |
| // Overridable function to return array of labels to use for this slider. |
| // Can specify a getLabels() method instead of a labels[] array, or min/max attributes. |
| // tags: |
| // protected extension |
| |
| // if the labels array was not specified directly, then see if <li> children were |
| var labels = this.labels; |
| if(!labels.length){ |
| // for markup creation, labels are specified as child elements |
| labels = dojo.query("> li", this.srcNodeRef).map(function(node){ |
| return String(node.innerHTML); |
| }); |
| } |
| this.srcNodeRef.innerHTML = ''; |
| // if the labels were not specified directly and not as <li> children, then calculate numeric labels |
| if(!labels.length && this.count > 1){ |
| var start = this.minimum; |
| var inc = (this.maximum - start) / (this.count-1); |
| for(var i=0; i < this.count; i++){ |
| labels.push((i < this.numericMargin || i >= (this.count-this.numericMargin)) ? '' : dojo.number.format(start, this.constraints)); |
| start += inc; |
| } |
| } |
| return labels; |
| }, |
| |
| postMixInProperties: function(){ |
| this.inherited(arguments); |
| this.labels = this.getLabels(); |
| this.count = this.labels.length; |
| } |
| }); |
| |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.VerticalRuleLabels"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.VerticalRuleLabels"] = true; |
| dojo.provide("dijit.form.VerticalRuleLabels"); |
| |
| |
| |
| dojo.declare("dijit.form.VerticalRuleLabels", dijit.form.HorizontalRuleLabels, |
| { |
| // summary: |
| // Labels for the `dijit.form.VerticalSlider` |
| |
| templateString: '<div class="dijitRuleContainer dijitRuleContainerV dijitRuleLabelsContainer dijitRuleLabelsContainerV"></div>', |
| |
| _positionPrefix: '<div class="dijitRuleLabelContainer dijitRuleLabelContainerV" style="top:', |
| _labelPrefix: '"><span class="dijitRuleLabel dijitRuleLabelV">', |
| |
| _calcPosition: function(pos){ |
| // Overrides HorizontalRuleLabel._calcPosition() |
| return 100-pos; |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.SimpleTextarea"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.SimpleTextarea"] = true; |
| dojo.provide("dijit.form.SimpleTextarea"); |
| |
| |
| |
| dojo.declare("dijit.form.SimpleTextarea", |
| dijit.form.TextBox, |
| { |
| // summary: |
| // A simple textarea that degrades, and responds to |
| // minimal LayoutContainer usage, and works with dijit.form.Form. |
| // Doesn't automatically size according to input, like Textarea. |
| // |
| // example: |
| // | <textarea dojoType="dijit.form.SimpleTextarea" name="foo" value="bar" rows=30 cols=40></textarea> |
| // |
| // example: |
| // | new dijit.form.SimpleTextarea({ rows:20, cols:30 }, "foo"); |
| |
| baseClass: "dijitTextArea", |
| |
| attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, { |
| rows:"textbox", cols: "textbox" |
| }), |
| |
| // rows: Number |
| // The number of rows of text. |
| rows: "3", |
| |
| // rows: Number |
| // The number of characters per line. |
| cols: "20", |
| |
| templateString: "<textarea ${nameAttrSetting} dojoAttachPoint='focusNode,containerNode,textbox' autocomplete='off'></textarea>", |
| |
| postMixInProperties: function(){ |
| // Copy value from srcNodeRef, unless user specified a value explicitly (or there is no srcNodeRef) |
| if(!this.value && this.srcNodeRef){ |
| this.value = this.srcNodeRef.value; |
| } |
| this.inherited(arguments); |
| }, |
| |
| filter: function(/*String*/ value){ |
| // Override TextBox.filter to deal with newlines... specifically (IIRC) this is for IE which writes newlines |
| // as \r\n instead of just \n |
| if(value){ |
| value = value.replace(/\r/g,""); |
| } |
| return this.inherited(arguments); |
| }, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| if(dojo.isIE && this.cols){ // attribute selectors is not supported in IE6 |
| dojo.addClass(this.textbox, "dijitTextAreaCols"); |
| } |
| }, |
| |
| _previousValue: "", |
| _onInput: function(/*Event?*/ e){ |
| // Override TextBox._onInput() to enforce maxLength restriction |
| if(this.maxLength){ |
| var maxLength = parseInt(this.maxLength); |
| var value = this.textbox.value.replace(/\r/g,''); |
| var overflow = value.length - maxLength; |
| if(overflow > 0){ |
| if(e){ dojo.stopEvent(e); } |
| var textarea = this.textbox; |
| if(textarea.selectionStart){ |
| var pos = textarea.selectionStart; |
| var cr = 0; |
| if(dojo.isOpera){ |
| cr = (this.textbox.value.substring(0,pos).match(/\r/g) || []).length; |
| } |
| this.textbox.value = value.substring(0,pos-overflow-cr)+value.substring(pos-cr); |
| textarea.setSelectionRange(pos-overflow, pos-overflow); |
| }else if(dojo.doc.selection){ //IE |
| textarea.focus(); |
| var range = dojo.doc.selection.createRange(); |
| // delete overflow characters |
| range.moveStart("character", -overflow); |
| range.text = ''; |
| // show cursor |
| range.select(); |
| } |
| } |
| this._previousValue = this.textbox.value; |
| } |
| this.inherited(arguments); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.form.Textarea"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.form.Textarea"] = true; |
| dojo.provide("dijit.form.Textarea"); |
| |
| |
| |
| dojo.declare( |
| "dijit.form.Textarea", |
| dijit.form.SimpleTextarea, |
| { |
| // summary: |
| // A textarea widget that adjusts it's height according to the amount of data. |
| // |
| // description: |
| // A textarea that dynamically expands/contracts (changing it's height) as |
| // the user types, to display all the text without requiring a scroll bar. |
| // |
| // Takes nearly all the parameters (name, value, etc.) that a vanilla textarea takes. |
| // Rows is not supported since this widget adjusts the height. |
| // |
| // example: |
| // | <textarea dojoType="dijit.form.TextArea">...</textarea> |
| |
| |
| // Override SimpleTextArea.cols to default to width:100%, for backward compatibility |
| cols: "", |
| |
| _previousNewlines: 0, |
| _strictMode: (dojo.doc.compatMode != 'BackCompat'), // not the same as !dojo.isQuirks |
| |
| _getHeight: function(textarea){ |
| var newH = textarea.scrollHeight; |
| if(dojo.isIE){ |
| newH += textarea.offsetHeight - textarea.clientHeight - ((dojo.isIE < 8 && this._strictMode) ? dojo._getPadBorderExtents(textarea).h : 0); |
| }else if(dojo.isMoz){ |
| newH += textarea.offsetHeight - textarea.clientHeight; // creates room for horizontal scrollbar |
| }else if(dojo.isWebKit && !(dojo.isSafari < 4)){ // Safari 4.0 && Chrome |
| newH += dojo._getBorderExtents(textarea).h; |
| }else{ // Safari 3.x and Opera 9.6 |
| newH += dojo._getPadBorderExtents(textarea).h; |
| } |
| return newH; |
| }, |
| |
| _estimateHeight: function(textarea){ |
| // summary: |
| // Approximate the height when the textarea is invisible with the number of lines in the text. |
| // Fails when someone calls setValue with a long wrapping line, but the layout fixes itself when the user clicks inside so . . . |
| // In IE, the resize event is supposed to fire when the textarea becomes visible again and that will correct the size automatically. |
| // |
| textarea.style.maxHeight = ""; |
| textarea.style.height = "auto"; |
| // #rows = #newlines+1 |
| // Note: on Moz, the following #rows appears to be 1 too many. |
| // Actually, Moz is reserving room for the scrollbar. |
| // If you increase the font size, this behavior becomes readily apparent as the last line gets cut off without the +1. |
| textarea.rows = (textarea.value.match(/\n/g) || []).length + 1; |
| }, |
| |
| _needsHelpShrinking: dojo.isMoz || dojo.isWebKit, |
| |
| _onInput: function(){ |
| // Override SimpleTextArea._onInput() to deal with height adjustment |
| this.inherited(arguments); |
| if(this._busyResizing){ return; } |
| this._busyResizing = true; |
| var textarea = this.textbox; |
| if(textarea.scrollHeight && textarea.offsetHeight && textarea.clientHeight){ |
| var newH = this._getHeight(textarea) + "px"; |
| if(textarea.style.height != newH){ |
| textarea.style.maxHeight = textarea.style.height = newH; |
| } |
| if(this._needsHelpShrinking){ |
| if(this._setTimeoutHandle){ |
| clearTimeout(this._setTimeoutHandle); |
| } |
| this._setTimeoutHandle = setTimeout(dojo.hitch(this, "_shrink"), 0); // try to collapse multiple shrinks into 1 |
| } |
| }else{ |
| // hidden content of unknown size |
| this._estimateHeight(textarea); |
| } |
| this._busyResizing = false; |
| }, |
| |
| _busyResizing: false, |
| _shrink: function(){ |
| // grow paddingBottom to see if scrollHeight shrinks (when it is unneccesarily big) |
| this._setTimeoutHandle = null; |
| if(this._needsHelpShrinking && !this._busyResizing){ |
| this._busyResizing = true; |
| var textarea = this.textbox; |
| var empty = false; |
| if(textarea.value == ''){ |
| textarea.value = ' '; // prevent collapse all the way back to 0 |
| empty = true; |
| } |
| var scrollHeight = textarea.scrollHeight; |
| if(!scrollHeight){ |
| this._estimateHeight(textarea); |
| }else{ |
| var oldPadding = textarea.style.paddingBottom; |
| var newPadding = dojo._getPadExtents(textarea); |
| newPadding = newPadding.h - newPadding.t; |
| textarea.style.paddingBottom = newPadding + 1 + "px"; // tweak padding to see if height can be reduced |
| var newH = this._getHeight(textarea) - 1 + "px"; // see if the height changed by the 1px added |
| if(textarea.style.maxHeight != newH){ // if can be reduced, so now try a big chunk |
| textarea.style.paddingBottom = newPadding + scrollHeight + "px"; |
| textarea.scrollTop = 0; |
| textarea.style.maxHeight = this._getHeight(textarea) - scrollHeight + "px"; // scrollHeight is the added padding |
| } |
| textarea.style.paddingBottom = oldPadding; |
| } |
| if(empty){ |
| textarea.value = ''; |
| } |
| this._busyResizing = false; |
| } |
| }, |
| |
| resize: function(){ |
| // summary: |
| // Resizes the textarea vertically (should be called after a style/value change) |
| this._onInput(); |
| }, |
| |
| _setValueAttr: function(){ |
| this.inherited(arguments); |
| this.resize(); |
| }, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| // tweak textarea style to reduce browser differences |
| dojo.style(this.textbox, { overflowY: 'hidden', overflowX: 'auto', boxSizing: 'border-box', MsBoxSizing: 'border-box', WebkitBoxSizing: 'border-box', MozBoxSizing: 'border-box' }); |
| this.connect(this.textbox, "onscroll", this._onInput); |
| this.connect(this.textbox, "onresize", this._onInput); |
| this.connect(this.textbox, "onfocus", this._onInput); // useful when a previous estimate was off a bit |
| setTimeout(dojo.hitch(this, "resize"), 0); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout.StackController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout.StackController"] = true; |
| dojo.provide("dijit.layout.StackController"); |
| |
| |
| |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.layout.StackController", |
| [dijit._Widget, dijit._Templated, dijit._Container], |
| { |
| // summary: |
| // Set of buttons to select a page in a page list. |
| // description: |
| // Monitors the specified StackContainer, and whenever a page is |
| // added, deleted, or selected, updates itself accordingly. |
| |
| templateString: "<span wairole='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>", |
| |
| // containerId: [const] String |
| // The id of the page container that I point to |
| containerId: "", |
| |
| // buttonWidget: [const] String |
| // The name of the button widget to create to correspond to each page |
| buttonWidget: "dijit.layout._StackButton", |
| |
| postCreate: function(){ |
| dijit.setWaiRole(this.domNode, "tablist"); |
| |
| this.pane2button = {}; // mapping from pane id to buttons |
| this.pane2handles = {}; // mapping from pane id to this.connect() handles |
| |
| // Listen to notifications from StackContainer |
| this.subscribe(this.containerId+"-startup", "onStartup"); |
| this.subscribe(this.containerId+"-addChild", "onAddChild"); |
| this.subscribe(this.containerId+"-removeChild", "onRemoveChild"); |
| this.subscribe(this.containerId+"-selectChild", "onSelectChild"); |
| this.subscribe(this.containerId+"-containerKeyPress", "onContainerKeyPress"); |
| }, |
| |
| onStartup: function(/*Object*/ info){ |
| // summary: |
| // Called after StackContainer has finished initializing |
| // tags: |
| // private |
| dojo.forEach(info.children, this.onAddChild, this); |
| if(info.selected){ |
| // Show button corresponding to selected pane (unless selected |
| // is null because there are no panes) |
| this.onSelectChild(info.selected); |
| } |
| }, |
| |
| destroy: function(){ |
| for(var pane in this.pane2button){ |
| this.onRemoveChild(dijit.byId(pane)); |
| } |
| this.inherited(arguments); |
| }, |
| |
| onAddChild: function(/*dijit._Widget*/ page, /*Integer?*/ insertIndex){ |
| // summary: |
| // Called whenever a page is added to the container. |
| // Create button corresponding to the page. |
| // tags: |
| // private |
| |
| // add a node that will be promoted to the button widget |
| var refNode = dojo.doc.createElement("span"); |
| this.domNode.appendChild(refNode); |
| // create an instance of the button widget |
| var cls = dojo.getObject(this.buttonWidget); |
| var button = new cls({ |
| id: this.id + "_" + page.id, |
| label: page.title, |
| showLabel: page.showTitle, |
| iconClass: page.iconClass, |
| closeButton: page.closable, |
| title: page.tooltip |
| }, refNode); |
| dijit.setWaiState(button.focusNode,"selected", "false"); |
| this.pane2handles[page.id] = [ |
| this.connect(page, 'attr', function(name, value){ |
| if(arguments.length == 2){ |
| var buttonAttr = { |
| title: 'label', |
| showTitle: 'showLabel', |
| iconClass: 'iconClass', |
| closable: 'closeButton', |
| tooltip: 'title' |
| }[name]; |
| if(buttonAttr){ |
| button.attr(buttonAttr, value); |
| } |
| } |
| }), |
| this.connect(button, 'onClick', dojo.hitch(this,"onButtonClick", page)), |
| this.connect(button, 'onClickCloseButton', dojo.hitch(this,"onCloseButtonClick", page)) |
| ]; |
| this.addChild(button, insertIndex); |
| this.pane2button[page.id] = button; |
| page.controlButton = button; // this value might be overwritten if two tabs point to same container |
| if(!this._currentChild){ // put the first child into the tab order |
| button.focusNode.setAttribute("tabIndex", "0"); |
| dijit.setWaiState(button.focusNode, "selected", "true"); |
| this._currentChild = page; |
| } |
| // make sure all tabs have the same length |
| if(!this.isLeftToRight() && dojo.isIE && this._rectifyRtlTabList){ |
| this._rectifyRtlTabList(); |
| } |
| }, |
| |
| onRemoveChild: function(/*dijit._Widget*/ page){ |
| // summary: |
| // Called whenever a page is removed from the container. |
| // Remove the button corresponding to the page. |
| // tags: |
| // private |
| |
| if(this._currentChild === page){ this._currentChild = null; } |
| dojo.forEach(this.pane2handles[page.id], this.disconnect, this); |
| delete this.pane2handles[page.id]; |
| var button = this.pane2button[page.id]; |
| if(button){ |
| this.removeChild(button); |
| delete this.pane2button[page.id]; |
| button.destroy(); |
| } |
| delete page.controlButton; |
| }, |
| |
| onSelectChild: function(/*dijit._Widget*/ page){ |
| // summary: |
| // Called when a page has been selected in the StackContainer, either by me or by another StackController |
| // tags: |
| // private |
| |
| if(!page){ return; } |
| |
| if(this._currentChild){ |
| var oldButton=this.pane2button[this._currentChild.id]; |
| oldButton.attr('checked', false); |
| dijit.setWaiState(oldButton.focusNode, "selected", "false"); |
| oldButton.focusNode.setAttribute("tabIndex", "-1"); |
| } |
| |
| var newButton=this.pane2button[page.id]; |
| newButton.attr('checked', true); |
| dijit.setWaiState(newButton.focusNode, "selected", "true"); |
| this._currentChild = page; |
| newButton.focusNode.setAttribute("tabIndex", "0"); |
| var container = dijit.byId(this.containerId); |
| dijit.setWaiState(container.containerNode, "labelledby", newButton.id); |
| }, |
| |
| onButtonClick: function(/*dijit._Widget*/ page){ |
| // summary: |
| // Called whenever one of my child buttons is pressed in an attempt to select a page |
| // tags: |
| // private |
| |
| var container = dijit.byId(this.containerId); |
| container.selectChild(page); |
| }, |
| |
| onCloseButtonClick: function(/*dijit._Widget*/ page){ |
| // summary: |
| // Called whenever one of my child buttons [X] is pressed in an attempt to close a page |
| // tags: |
| // private |
| |
| var container = dijit.byId(this.containerId); |
| container.closeChild(page); |
| if(this._currentChild){ |
| var b = this.pane2button[this._currentChild.id]; |
| if(b){ |
| dijit.focus(b.focusNode || b.domNode); |
| } |
| } |
| }, |
| |
| // TODO: this is a bit redundant with forward, back api in StackContainer |
| adjacent: function(/*Boolean*/ forward){ |
| // summary: |
| // Helper for onkeypress to find next/previous button |
| // tags: |
| // private |
| |
| if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !forward; } |
| // find currently focused button in children array |
| var children = this.getChildren(); |
| var current = dojo.indexOf(children, this.pane2button[this._currentChild.id]); |
| // pick next button to focus on |
| var offset = forward ? 1 : children.length - 1; |
| return children[ (current + offset) % children.length ]; // dijit._Widget |
| }, |
| |
| onkeypress: function(/*Event*/ e){ |
| // summary: |
| // Handle keystrokes on the page list, for advancing to next/previous button |
| // and closing the current page if the page is closable. |
| // tags: |
| // private |
| |
| if(this.disabled || e.altKey ){ return; } |
| var forward = null; |
| if(e.ctrlKey || !e._djpage){ |
| var k = dojo.keys; |
| switch(e.charOrCode){ |
| case k.LEFT_ARROW: |
| case k.UP_ARROW: |
| if(!e._djpage){ forward = false; } |
| break; |
| case k.PAGE_UP: |
| if(e.ctrlKey){ forward = false; } |
| break; |
| case k.RIGHT_ARROW: |
| case k.DOWN_ARROW: |
| if(!e._djpage){ forward = true; } |
| break; |
| case k.PAGE_DOWN: |
| if(e.ctrlKey){ forward = true; } |
| break; |
| case k.DELETE: |
| if(this._currentChild.closable){ |
| this.onCloseButtonClick(this._currentChild); |
| } |
| dojo.stopEvent(e); |
| break; |
| default: |
| if(e.ctrlKey){ |
| if(e.charOrCode === k.TAB){ |
| this.adjacent(!e.shiftKey).onClick(); |
| dojo.stopEvent(e); |
| }else if(e.charOrCode == "w"){ |
| if(this._currentChild.closable){ |
| this.onCloseButtonClick(this._currentChild); |
| } |
| dojo.stopEvent(e); // avoid browser tab closing. |
| } |
| } |
| } |
| // handle page navigation |
| if(forward !== null){ |
| this.adjacent(forward).onClick(); |
| dojo.stopEvent(e); |
| } |
| } |
| }, |
| |
| onContainerKeyPress: function(/*Object*/ info){ |
| // summary: |
| // Called when there was a keypress on the container |
| // tags: |
| // private |
| info.e._djpage = info.page; |
| this.onkeypress(info.e); |
| } |
| }); |
| |
| |
| dojo.declare("dijit.layout._StackButton", |
| dijit.form.ToggleButton, |
| { |
| // summary: |
| // Internal widget used by StackContainer. |
| // description: |
| // The button-like or tab-like object you click to select or delete a page |
| // tags: |
| // private |
| |
| // Override _FormWidget.tabIndex. |
| // StackContainer buttons are not in the tab order by default. |
| // Probably we should be calling this.startupKeyNavChildren() instead. |
| tabIndex: "-1", |
| |
| postCreate: function(/*Event*/ evt){ |
| dijit.setWaiRole((this.focusNode || this.domNode), "tab"); |
| this.inherited(arguments); |
| }, |
| |
| onClick: function(/*Event*/ evt){ |
| // summary: |
| // This is for TabContainer where the tabs are <span> rather than button, |
| // so need to set focus explicitly (on some browsers) |
| // Note that you shouldn't override this method, but you can connect to it. |
| dijit.focus(this.focusNode); |
| |
| // ... now let StackController catch the event and tell me what to do |
| }, |
| |
| onClickCloseButton: function(/*Event*/ evt){ |
| // summary: |
| // StackContainer connects to this function; if your widget contains a close button |
| // then clicking it should call this function. |
| // Note that you shouldn't override this method, but you can connect to it. |
| evt.stopPropagation(); |
| } |
| }); |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout.StackContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout.StackContainer"] = true; |
| dojo.provide("dijit.layout.StackContainer"); |
| |
| |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.layout.StackContainer", |
| dijit.layout._LayoutWidget, |
| { |
| // summary: |
| // A container that has multiple children, but shows only |
| // one child at a time |
| // |
| // description: |
| // A container for widgets (ContentPanes, for example) That displays |
| // only one Widget at a time. |
| // |
| // Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild |
| // |
| // Can be base class for container, Wizard, Show, etc. |
| |
| // doLayout: Boolean |
| // If true, change the size of my currently displayed child to match my size |
| doLayout: true, |
| |
| // persist: Boolean |
| // Remembers the selected child across sessions |
| persist: false, |
| |
| baseClass: "dijitStackContainer", |
| |
| /*===== |
| // selectedChildWidget: [readonly] dijit._Widget |
| // References the currently selected child widget, if any. |
| // Adjust selected child with selectChild() method. |
| selectedChildWidget: null, |
| =====*/ |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| dojo.addClass(this.domNode, "dijitLayoutContainer"); |
| dijit.setWaiRole(this.containerNode, "tabpanel"); |
| this.connect(this.domNode, "onkeypress", this._onKeyPress); |
| }, |
| |
| startup: function(){ |
| if(this._started){ return; } |
| |
| var children = this.getChildren(); |
| |
| // Setup each page panel to be initially hidden |
| dojo.forEach(children, this._setupChild, this); |
| |
| // Figure out which child to initially display, defaulting to first one |
| if(this.persist){ |
| this.selectedChildWidget = dijit.byId(dojo.cookie(this.id + "_selectedChild")); |
| }else{ |
| dojo.some(children, function(child){ |
| if(child.selected){ |
| this.selectedChildWidget = child; |
| } |
| return child.selected; |
| }, this); |
| } |
| var selected = this.selectedChildWidget; |
| if(!selected && children[0]){ |
| selected = this.selectedChildWidget = children[0]; |
| selected.selected = true; |
| } |
| |
| // Publish information about myself so any StackControllers can initialize. |
| // This needs to happen before this.inherited(arguments) so that for |
| // TabContainer, this._contentBox doesn't include the space for the tab labels. |
| dojo.publish(this.id+"-startup", [{children: children, selected: selected}]); |
| |
| // Startup each child widget, and do initial layout like setting this._contentBox, |
| // then calls this.resize() which does the initial sizing on the selected child. |
| this.inherited(arguments); |
| }, |
| |
| resize: function(){ |
| // Resize is called when we are first made visible (it's called from startup() |
| // if we are initially visible). If this is the first time we've been made |
| // visible then show our first child. |
| var selected = this.selectedChildWidget; |
| if(selected && !this._hasBeenShown){ |
| this._hasBeenShown = true; |
| this._showChild(selected); |
| } |
| this.inherited(arguments); |
| }, |
| |
| _setupChild: function(/*dijit._Widget*/ child){ |
| // Overrides _LayoutWidget._setupChild() |
| |
| this.inherited(arguments); |
| |
| dojo.removeClass(child.domNode, "dijitVisible"); |
| dojo.addClass(child.domNode, "dijitHidden"); |
| |
| // remove the title attribute so it doesn't show up when i hover |
| // over a node |
| child.domNode.title = ""; |
| }, |
| |
| addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ |
| // Overrides _Container.addChild() to do layout and publish events |
| |
| this.inherited(arguments); |
| |
| if(this._started){ |
| dojo.publish(this.id+"-addChild", [child, insertIndex]); |
| |
| // in case the tab titles have overflowed from one line to two lines |
| // (or, if this if first child, from zero lines to one line) |
| // TODO: w/ScrollingTabController this is no longer necessary, although |
| // ScrollTabController.resize() does need to get called to show/hide |
| // the navigation buttons as appropriate, but that's handled in ScrollingTabController.onAddChild() |
| this.layout(); |
| |
| // if this is the first child, then select it |
| if(!this.selectedChildWidget){ |
| this.selectChild(child); |
| } |
| } |
| }, |
| |
| removeChild: function(/*dijit._Widget*/ page){ |
| // Overrides _Container.removeChild() to do layout and publish events |
| |
| this.inherited(arguments); |
| |
| if(this._started){ |
| // this will notify any tablists to remove a button; do this first because it may affect sizing |
| dojo.publish(this.id + "-removeChild", [page]); |
| } |
| |
| // If we are being destroyed than don't run the code below (to select another page), because we are deleting |
| // every page one by one |
| if(this._beingDestroyed){ return; } |
| |
| if(this._started){ |
| // in case the tab titles now take up one line instead of two lines |
| // TODO: this is overkill in most cases since ScrollingTabController never changes size (for >= 1 tab) |
| this.layout(); |
| } |
| |
| if(this.selectedChildWidget === page){ |
| this.selectedChildWidget = undefined; |
| if(this._started){ |
| var children = this.getChildren(); |
| if(children.length){ |
| this.selectChild(children[0]); |
| } |
| } |
| } |
| }, |
| |
| selectChild: function(/*dijit._Widget|String*/ page){ |
| // summary: |
| // Show the given widget (which must be one of my children) |
| // page: |
| // Reference to child widget or id of child widget |
| |
| page = dijit.byId(page); |
| |
| if(this.selectedChildWidget != page){ |
| // Deselect old page and select new one |
| this._transition(page, this.selectedChildWidget); |
| this.selectedChildWidget = page; |
| dojo.publish(this.id+"-selectChild", [page]); |
| |
| if(this.persist){ |
| dojo.cookie(this.id + "_selectedChild", this.selectedChildWidget.id); |
| } |
| } |
| }, |
| |
| _transition: function(/*dijit._Widget*/newWidget, /*dijit._Widget*/oldWidget){ |
| // summary: |
| // Hide the old widget and display the new widget. |
| // Subclasses should override this. |
| // tags: |
| // protected extension |
| if(oldWidget){ |
| this._hideChild(oldWidget); |
| } |
| this._showChild(newWidget); |
| |
| // Size the new widget, in case this is the first time it's being shown, |
| // or I have been resized since the last time it was shown. |
| // Note that page must be visible for resizing to work. |
| if(newWidget.resize){ |
| if(this.doLayout){ |
| newWidget.resize(this._containerContentBox || this._contentBox); |
| }else{ |
| // the child should pick it's own size but we still need to call resize() |
| // (with no arguments) to let the widget lay itself out |
| newWidget.resize(); |
| } |
| } |
| }, |
| |
| _adjacent: function(/*Boolean*/ forward){ |
| // summary: |
| // Gets the next/previous child widget in this container from the current selection. |
| var children = this.getChildren(); |
| var index = dojo.indexOf(children, this.selectedChildWidget); |
| index += forward ? 1 : children.length - 1; |
| return children[ index % children.length ]; // dijit._Widget |
| }, |
| |
| forward: function(){ |
| // summary: |
| // Advance to next page. |
| this.selectChild(this._adjacent(true)); |
| }, |
| |
| back: function(){ |
| // summary: |
| // Go back to previous page. |
| this.selectChild(this._adjacent(false)); |
| }, |
| |
| _onKeyPress: function(e){ |
| dojo.publish(this.id+"-containerKeyPress", [{ e: e, page: this}]); |
| }, |
| |
| layout: function(){ |
| // Implement _LayoutWidget.layout() virtual method. |
| if(this.doLayout && this.selectedChildWidget && this.selectedChildWidget.resize){ |
| this.selectedChildWidget.resize(this._contentBox); |
| } |
| }, |
| |
| _showChild: function(/*dijit._Widget*/ page){ |
| // summary: |
| // Show the specified child by changing it's CSS, and call _onShow()/onShow() so |
| // it can do any updates it needs regarding loading href's etc. |
| var children = this.getChildren(); |
| page.isFirstChild = (page == children[0]); |
| page.isLastChild = (page == children[children.length-1]); |
| page.selected = true; |
| |
| dojo.removeClass(page.domNode, "dijitHidden"); |
| dojo.addClass(page.domNode, "dijitVisible"); |
| |
| page._onShow(); |
| }, |
| |
| _hideChild: function(/*dijit._Widget*/ page){ |
| // summary: |
| // Hide the specified child by changing it's CSS, and call _onHide() so |
| // it's notified. |
| page.selected=false; |
| dojo.removeClass(page.domNode, "dijitVisible"); |
| dojo.addClass(page.domNode, "dijitHidden"); |
| |
| page.onHide(); |
| }, |
| |
| closeChild: function(/*dijit._Widget*/ page){ |
| // summary: |
| // Callback when user clicks the [X] to remove a page. |
| // If onClose() returns true then remove and destroy the child. |
| // tags: |
| // private |
| var remove = page.onClose(this, page); |
| if(remove){ |
| this.removeChild(page); |
| // makes sure we can clean up executeScripts in ContentPane onUnLoad |
| page.destroyRecursive(); |
| } |
| }, |
| |
| destroyDescendants: function(/*Boolean*/preserveDom){ |
| dojo.forEach(this.getChildren(), function(child){ |
| this.removeChild(child); |
| child.destroyRecursive(preserveDom); |
| }, this); |
| } |
| }); |
| |
| // For back-compat, remove for 2.0 |
| |
| |
| |
| // These arguments can be specified for the children of a StackContainer. |
| // Since any widget can be specified as a StackContainer child, mix them |
| // into the base widget class. (This is a hack, but it's effective.) |
| dojo.extend(dijit._Widget, { |
| // selected: Boolean |
| // Parameter for children of `dijit.layout.StackContainer` or subclasses. |
| // Specifies that this widget should be the initially displayed pane. |
| // Note: to change the selected child use `dijit.layout.StackContainer.selectChild` |
| selected: false, |
| |
| // closable: Boolean |
| // Parameter for children of `dijit.layout.StackContainer` or subclasses. |
| // True if user can close (destroy) this child, such as (for example) clicking the X on the tab. |
| closable: false, |
| |
| // iconClass: String |
| // Parameter for children of `dijit.layout.StackContainer` or subclasses. |
| // CSS Class specifying icon to use in label associated with this pane. |
| iconClass: "", |
| |
| // showTitle: Boolean |
| // Parameter for children of `dijit.layout.StackContainer` or subclasses. |
| // When true, display title of this widget as tab label etc., rather than just using |
| // icon specified in iconClass |
| showTitle: true, |
| |
| onClose: function(){ |
| // summary: |
| // Parameter for children of `dijit.layout.StackContainer` or subclasses. |
| // Callback if a user tries to close the child. Child will be closed if this function returns true. |
| // tags: |
| // extension |
| |
| return true; // Boolean |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout.AccordionPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout.AccordionPane"] = true; |
| dojo.provide("dijit.layout.AccordionPane"); |
| |
| |
| |
| dojo.declare("dijit.layout.AccordionPane", dijit.layout.ContentPane, { |
| // summary: |
| // Deprecated widget. Use `dijit.layout.ContentPane` instead. |
| // tags: |
| // deprecated |
| |
| constructor: function(){ |
| dojo.deprecated("dijit.layout.AccordionPane deprecated, use ContentPane instead", "", "2.0"); |
| }, |
| |
| onSelected: function(){ |
| // summary: |
| // called when this pane is selected |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout.AccordionContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout.AccordionContainer"] = true; |
| dojo.provide("dijit.layout.AccordionContainer"); |
| |
| |
| |
| |
| |
| |
| |
| |
| // for back compat, remove for 2.0 |
| |
| dojo.declare( |
| "dijit.layout.AccordionContainer", |
| dijit.layout.StackContainer, |
| { |
| // summary: |
| // Holds a set of panes where every pane's title is visible, but only one pane's content is visible at a time, |
| // and switching between panes is visualized by sliding the other panes up/down. |
| // example: |
| // | <div dojoType="dijit.layout.AccordionContainer"> |
| // | <div dojoType="dijit.layout.ContentPane" title="pane 1"> |
| // | </div> |
| // | <div dojoType="dijit.layout.ContentPane" title="pane 2"> |
| // | <p>This is some text</p> |
| // | </div> |
| // | </div> |
| |
| // duration: Integer |
| // Amount of time (in ms) it takes to slide panes |
| duration: dijit.defaultDuration, |
| |
| // buttonWidget: [const] String |
| // The name of the widget used to display the title of each pane |
| buttonWidget: "dijit.layout._AccordionButton", |
| |
| // _verticalSpace: Number |
| // Pixels of space available for the open pane |
| // (my content box size minus the cumulative size of all the title bars) |
| _verticalSpace: 0, |
| |
| baseClass: "dijitAccordionContainer", |
| |
| postCreate: function(){ |
| this.domNode.style.overflow = "hidden"; |
| this.inherited(arguments); |
| dijit.setWaiRole(this.domNode, "tablist"); |
| }, |
| |
| startup: function(){ |
| if(this._started){ return; } |
| this.inherited(arguments); |
| if(this.selectedChildWidget){ |
| var style = this.selectedChildWidget.containerNode.style; |
| style.display = ""; |
| style.overflow = "auto"; |
| this.selectedChildWidget._buttonWidget._setSelectedState(true); |
| } |
| }, |
| |
| _getTargetHeight: function(/* Node */ node){ |
| // summary: |
| // For the given node, returns the height that should be |
| // set to achieve our vertical space (subtract any padding |
| // we may have). |
| // |
| // This is used by the animations. |
| // |
| // TODO: I don't think this works correctly in IE quirks when an elements |
| // style.height including padding and borders |
| var cs = dojo.getComputedStyle(node); |
| return Math.max(this._verticalSpace - dojo._getPadBorderExtents(node, cs).h, 0); |
| }, |
| |
| layout: function(){ |
| // Implement _LayoutWidget.layout() virtual method. |
| // Set the height of the open pane based on what room remains. |
| |
| var openPane = this.selectedChildWidget; |
| |
| // get cumulative height of all the title bars |
| var totalCollapsedHeight = 0; |
| dojo.forEach(this.getChildren(), function(child){ |
| totalCollapsedHeight += child._buttonWidget.getTitleHeight(); |
| }); |
| var mySize = this._contentBox; |
| this._verticalSpace = mySize.h - totalCollapsedHeight; |
| |
| // Memo size to make displayed child |
| this._containerContentBox = { |
| h: this._verticalSpace, |
| w: mySize.w |
| }; |
| |
| if(openPane){ |
| openPane.resize(this._containerContentBox); |
| } |
| }, |
| |
| _setupChild: function(child){ |
| // Overrides _LayoutWidget._setupChild(). |
| // Setup clickable title to sit above the child widget, |
| // and stash pointer to it inside the widget itself. |
| |
| var cls = dojo.getObject(this.buttonWidget); |
| var button = (child._buttonWidget = new cls({ |
| contentWidget: child, |
| label: child.title, |
| title: child.tooltip, |
| iconClass: child.iconClass, |
| id: child.id + "_button", |
| parent: this |
| })); |
| |
| child._accordionConnectHandle = this.connect(child, 'attr', function(name, value){ |
| if(arguments.length == 2){ |
| switch(name){ |
| case 'title': |
| case 'iconClass': |
| button.attr(name, value); |
| } |
| } |
| }); |
| |
| dojo.place(child._buttonWidget.domNode, child.domNode, "before"); |
| |
| this.inherited(arguments); |
| }, |
| |
| removeChild: function(child){ |
| // Overrides _LayoutWidget.removeChild(). |
| this.disconnect(child._accordionConnectHandle); |
| delete child._accordionConnectHandle; |
| |
| child._buttonWidget.destroy(); |
| delete child._buttonWidget; |
| |
| this.inherited(arguments); |
| }, |
| |
| getChildren: function(){ |
| // Overrides _Container.getChildren() to ignore titles and only look at panes. |
| return dojo.filter(this.inherited(arguments), function(child){ |
| return child.declaredClass != this.buttonWidget; |
| }, this); |
| }, |
| |
| destroy: function(){ |
| dojo.forEach(this.getChildren(), function(child){ |
| child._buttonWidget.destroy(); |
| }); |
| this.inherited(arguments); |
| }, |
| |
| _transition: function(/*Widget?*/newWidget, /*Widget?*/oldWidget){ |
| // Overrides StackContainer._transition() to provide sliding of title bars etc. |
| |
| //TODO: should be able to replace this with calls to slideIn/slideOut |
| if(this._inTransition){ return; } |
| this._inTransition = true; |
| var animations = []; |
| var paneHeight = this._verticalSpace; |
| if(newWidget){ |
| newWidget._buttonWidget.setSelected(true); |
| |
| this._showChild(newWidget); // prepare widget to be slid in |
| |
| // Size the new widget, in case this is the first time it's being shown, |
| // or I have been resized since the last time it was shown. |
| // Note that page must be visible for resizing to work. |
| if(this.doLayout && newWidget.resize){ |
| newWidget.resize(this._containerContentBox); |
| } |
| |
| var newContents = newWidget.domNode; |
| dojo.addClass(newContents, "dijitVisible"); |
| dojo.removeClass(newContents, "dijitHidden"); |
| var newContentsOverflow = newContents.style.overflow; |
| newContents.style.overflow = "hidden"; |
| animations.push(dojo.animateProperty({ |
| node: newContents, |
| duration: this.duration, |
| properties: { |
| height: { start: 1, end: this._getTargetHeight(newContents) } |
| }, |
| onEnd: dojo.hitch(this, function(){ |
| newContents.style.overflow = newContentsOverflow; |
| delete this._inTransition; |
| }) |
| })); |
| } |
| if(oldWidget){ |
| oldWidget._buttonWidget.setSelected(false); |
| var oldContents = oldWidget.domNode, |
| oldContentsOverflow = oldContents.style.overflow; |
| oldContents.style.overflow = "hidden"; |
| animations.push(dojo.animateProperty({ |
| node: oldContents, |
| duration: this.duration, |
| properties: { |
| height: { start: this._getTargetHeight(oldContents), end: 1 } |
| }, |
| onEnd: function(){ |
| dojo.addClass(oldContents, "dijitHidden"); |
| dojo.removeClass(oldContents, "dijitVisible"); |
| oldContents.style.overflow = oldContentsOverflow; |
| if(oldWidget.onHide){ |
| oldWidget.onHide(); |
| } |
| } |
| })); |
| } |
| |
| dojo.fx.combine(animations).play(); |
| }, |
| |
| // note: we are treating the container as controller here |
| _onKeyPress: function(/*Event*/ e, /*dijit._Widget*/ fromTitle){ |
| // summary: |
| // Handle keypress events |
| // description: |
| // This is called from a handler on AccordionContainer.domNode |
| // (setup in StackContainer), and is also called directly from |
| // the click handler for accordion labels |
| if(this._inTransition || this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){ |
| if(this._inTransition){ |
| dojo.stopEvent(e); |
| } |
| return; |
| } |
| var k = dojo.keys, |
| c = e.charOrCode; |
| if((fromTitle && (c == k.LEFT_ARROW || c == k.UP_ARROW)) || |
| (e.ctrlKey && c == k.PAGE_UP)){ |
| this._adjacent(false)._buttonWidget._onTitleClick(); |
| dojo.stopEvent(e); |
| }else if((fromTitle && (c == k.RIGHT_ARROW || c == k.DOWN_ARROW)) || |
| (e.ctrlKey && (c == k.PAGE_DOWN || c == k.TAB))){ |
| this._adjacent(true)._buttonWidget._onTitleClick(); |
| dojo.stopEvent(e); |
| } |
| } |
| } |
| ); |
| |
| dojo.declare("dijit.layout._AccordionButton", |
| [dijit._Widget, dijit._Templated], |
| { |
| // summary: |
| // The title bar to click to open up an accordion pane. |
| // Internal widget used by AccordionContainer. |
| // tags: |
| // private |
| |
| templateString: dojo.cache("dijit.layout", "templates/AccordionButton.html", "<div dojoAttachPoint='titleNode,focusNode' dojoAttachEvent='ondijitclick:_onTitleClick,onkeypress:_onTitleKeyPress,onfocus:_handleFocus,onblur:_handleFocus,onmouseenter:_onTitleEnter,onmouseleave:_onTitleLeave'\n\t\tclass='dijitAccordionTitle' wairole=\"tab\" waiState=\"expanded-false\"\n\t\t><span class='dijitInline dijitAccordionArrow' waiRole=\"presentation\"></span\n\t\t><span class='arrowTextUp' waiRole=\"presentation\">+</span\n\t\t><span class='arrowTextDown' waiRole=\"presentation\">-</span\n\t\t><img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint='iconNode' style=\"vertical-align: middle\" waiRole=\"presentation\"/>\n\t\t<span waiRole=\"presentation\" dojoAttachPoint='titleTextNode' class='dijitAccordionText'></span>\n</div>\n"), |
| attributeMap: dojo.mixin(dojo.clone(dijit.layout.ContentPane.prototype.attributeMap), { |
| label: {node: "titleTextNode", type: "innerHTML" }, |
| title: {node: "titleTextNode", type: "attribute", attribute: "title"}, |
| iconClass: { node: "iconNode", type: "class" } |
| }), |
| |
| baseClass: "dijitAccordionTitle", |
| |
| getParent: function(){ |
| // summary: |
| // Returns the parent. |
| // tags: |
| // private |
| return this.parent; |
| }, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| dojo.setSelectable(this.domNode, false); |
| this.setSelected(this.selected); |
| var titleTextNodeId = dojo.attr(this.domNode,'id').replace(' ','_'); |
| dojo.attr(this.titleTextNode, "id", titleTextNodeId+"_title"); |
| dijit.setWaiState(this.focusNode, "labelledby", dojo.attr(this.titleTextNode, "id")); |
| }, |
| |
| getTitleHeight: function(){ |
| // summary: |
| // Returns the height of the title dom node. |
| return dojo.marginBox(this.titleNode).h; // Integer |
| }, |
| |
| _onTitleClick: function(){ |
| // summary: |
| // Callback when someone clicks my title. |
| var parent = this.getParent(); |
| if(!parent._inTransition){ |
| parent.selectChild(this.contentWidget); |
| dijit.focus(this.focusNode); |
| } |
| }, |
| |
| _onTitleEnter: function(){ |
| // summary: |
| // Callback when someone hovers over my title. |
| dojo.addClass(this.focusNode, "dijitAccordionTitle-hover"); |
| }, |
| |
| _onTitleLeave: function(){ |
| // summary: |
| // Callback when someone stops hovering over my title. |
| dojo.removeClass(this.focusNode, "dijitAccordionTitle-hover"); |
| }, |
| |
| _onTitleKeyPress: function(/*Event*/ evt){ |
| return this.getParent()._onKeyPress(evt, this.contentWidget); |
| }, |
| |
| _setSelectedState: function(/*Boolean*/ isSelected){ |
| this.selected = isSelected; |
| dojo[(isSelected ? "addClass" : "removeClass")](this.titleNode,"dijitAccordionTitle-selected"); |
| dijit.setWaiState(this.focusNode, "expanded", isSelected); |
| dijit.setWaiState(this.focusNode, "selected", isSelected); |
| this.focusNode.setAttribute("tabIndex", isSelected ? "0" : "-1"); |
| }, |
| |
| _handleFocus: function(/*Event*/ e){ |
| // summary: |
| // Handle the blur and focus state of this widget. |
| dojo.toggleClass(this.titleTextNode, "dijitAccordionFocused", e.type == "focus"); |
| }, |
| |
| setSelected: function(/*Boolean*/ isSelected){ |
| // summary: |
| // Change the selected state on this pane. |
| this._setSelectedState(isSelected); |
| if(isSelected){ |
| var cw = this.contentWidget; |
| if(cw.onSelected){ cw.onSelected(); } |
| } |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout.BorderContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout.BorderContainer"] = true; |
| dojo.provide("dijit.layout.BorderContainer"); |
| |
| |
| |
| |
| dojo.declare( |
| "dijit.layout.BorderContainer", |
| dijit.layout._LayoutWidget, |
| { |
| // summary: |
| // Provides layout in up to 5 regions, a mandatory center with optional borders along its 4 sides. |
| // |
| // description: |
| // A BorderContainer is a box with a specified size, such as style="width: 500px; height: 500px;", |
| // that contains a child widget marked region="center" and optionally children widgets marked |
| // region equal to "top", "bottom", "leading", "trailing", "left" or "right". |
| // Children along the edges will be laid out according to width or height dimensions and may |
| // include optional splitters (splitter="true") to make them resizable by the user. The remaining |
| // space is designated for the center region. |
| // |
| // NOTE: Splitters must not be more than 50 pixels in width. |
| // |
| // The outer size must be specified on the BorderContainer node. Width must be specified for the sides |
| // and height for the top and bottom, respectively. No dimensions should be specified on the center; |
| // it will fill the remaining space. Regions named "leading" and "trailing" may be used just like |
| // "left" and "right" except that they will be reversed in right-to-left environments. |
| // |
| // example: |
| // | <div dojoType="dijit.layout.BorderContainer" design="sidebar" gutters="false" |
| // | style="width: 400px; height: 300px;"> |
| // | <div dojoType="ContentPane" region="top">header text</div> |
| // | <div dojoType="ContentPane" region="right" splitter="true" style="width: 200px;">table of contents</div> |
| // | <div dojoType="ContentPane" region="center">client area</div> |
| // | </div> |
| |
| // design: String |
| // Which design is used for the layout: |
| // - "headline" (default) where the top and bottom extend |
| // the full width of the container |
| // - "sidebar" where the left and right sides extend from top to bottom. |
| design: "headline", |
| |
| // gutters: Boolean |
| // Give each pane a border and margin. |
| // Margin determined by domNode.paddingLeft. |
| // When false, only resizable panes have a gutter (i.e. draggable splitter) for resizing. |
| gutters: true, |
| |
| // liveSplitters: Boolean |
| // Specifies whether splitters resize as you drag (true) or only upon mouseup (false) |
| liveSplitters: true, |
| |
| // persist: Boolean |
| // Save splitter positions in a cookie. |
| persist: false, |
| |
| baseClass: "dijitBorderContainer", |
| |
| // _splitterClass: String |
| // Optional hook to override the default Splitter widget used by BorderContainer |
| _splitterClass: "dijit.layout._Splitter", |
| |
| postMixInProperties: function(){ |
| // change class name to indicate that BorderContainer is being used purely for |
| // layout (like LayoutContainer) rather than for pretty formatting. |
| if(!this.gutters){ |
| this.baseClass += "NoGutter"; |
| } |
| this.inherited(arguments); |
| }, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| |
| this._splitters = {}; |
| this._splitterThickness = {}; |
| }, |
| |
| startup: function(){ |
| if(this._started){ return; } |
| dojo.forEach(this.getChildren(), this._setupChild, this); |
| this.inherited(arguments); |
| }, |
| |
| _setupChild: function(/*dijit._Widget*/ child){ |
| // Override _LayoutWidget._setupChild(). |
| |
| var region = child.region; |
| if(region){ |
| this.inherited(arguments); |
| |
| dojo.addClass(child.domNode, this.baseClass+"Pane"); |
| |
| var ltr = this.isLeftToRight(); |
| if(region == "leading"){ region = ltr ? "left" : "right"; } |
| if(region == "trailing"){ region = ltr ? "right" : "left"; } |
| |
| //FIXME: redundant? |
| this["_"+region] = child.domNode; |
| this["_"+region+"Widget"] = child; |
| |
| // Create draggable splitter for resizing pane, |
| // or alternately if splitter=false but BorderContainer.gutters=true then |
| // insert dummy div just for spacing |
| if((child.splitter || this.gutters) && !this._splitters[region]){ |
| var _Splitter = dojo.getObject(child.splitter ? this._splitterClass : "dijit.layout._Gutter"); |
| var splitter = new _Splitter({ |
| container: this, |
| child: child, |
| region: region, |
| live: this.liveSplitters |
| }); |
| splitter.isSplitter = true; |
| this._splitters[region] = splitter.domNode; |
| dojo.place(this._splitters[region], child.domNode, "after"); |
| |
| // Splitters arent added as Contained children, so we need to call startup explicitly |
| splitter.startup(); |
| } |
| child.region = region; |
| } |
| }, |
| |
| _computeSplitterThickness: function(region){ |
| this._splitterThickness[region] = this._splitterThickness[region] || |
| dojo.marginBox(this._splitters[region])[(/top|bottom/.test(region) ? 'h' : 'w')]; |
| }, |
| |
| layout: function(){ |
| // Implement _LayoutWidget.layout() virtual method. |
| for(var region in this._splitters){ this._computeSplitterThickness(region); } |
| this._layoutChildren(); |
| }, |
| |
| addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ |
| // Override _LayoutWidget.addChild(). |
| this.inherited(arguments); |
| if(this._started){ |
| this.layout(); //OPT |
| } |
| }, |
| |
| removeChild: function(/*dijit._Widget*/ child){ |
| // Override _LayoutWidget.removeChild(). |
| var region = child.region; |
| var splitter = this._splitters[region]; |
| if(splitter){ |
| dijit.byNode(splitter).destroy(); |
| delete this._splitters[region]; |
| delete this._splitterThickness[region]; |
| } |
| this.inherited(arguments); |
| delete this["_"+region]; |
| delete this["_" +region+"Widget"]; |
| if(this._started){ |
| this._layoutChildren(child.region); |
| } |
| dojo.removeClass(child.domNode, this.baseClass+"Pane"); |
| }, |
| |
| getChildren: function(){ |
| // Override _LayoutWidget.getChildren() to only return real children, not the splitters. |
| return dojo.filter(this.inherited(arguments), function(widget){ |
| return !widget.isSplitter; |
| }); |
| }, |
| |
| getSplitter: function(/*String*/region){ |
| // summary: |
| // Returns the widget responsible for rendering the splitter associated with region |
| var splitter = this._splitters[region]; |
| return splitter ? dijit.byNode(splitter) : null; |
| }, |
| |
| resize: function(newSize, currentSize){ |
| // Overrides _LayoutWidget.resize(). |
| |
| // resetting potential padding to 0px to provide support for 100% width/height + padding |
| // TODO: this hack doesn't respect the box model and is a temporary fix |
| if(!this.cs || !this.pe){ |
| var node = this.domNode; |
| this.cs = dojo.getComputedStyle(node); |
| this.pe = dojo._getPadExtents(node, this.cs); |
| this.pe.r = dojo._toPixelValue(node, this.cs.paddingRight); |
| this.pe.b = dojo._toPixelValue(node, this.cs.paddingBottom); |
| |
| dojo.style(node, "padding", "0px"); |
| } |
| |
| this.inherited(arguments); |
| }, |
| |
| _layoutChildren: function(/*String?*/changedRegion){ |
| // summary: |
| // This is the main routine for setting size/position of each child |
| |
| if(!this._borderBox || !this._borderBox.h){ |
| // We are currently hidden, or we haven't been sized by our parent yet. |
| // Abort. Someone will resize us later. |
| return; |
| } |
| |
| var sidebarLayout = (this.design == "sidebar"); |
| var topHeight = 0, bottomHeight = 0, leftWidth = 0, rightWidth = 0; |
| var topStyle = {}, leftStyle = {}, rightStyle = {}, bottomStyle = {}, |
| centerStyle = (this._center && this._center.style) || {}; |
| |
| var changedSide = /left|right/.test(changedRegion); |
| |
| var layoutSides = !changedRegion || (!changedSide && !sidebarLayout); |
| var layoutTopBottom = !changedRegion || (changedSide && sidebarLayout); |
| |
| // Ask browser for width/height of side panes. |
| // Would be nice to cache this but height can change according to width |
| // (because words wrap around). I don't think width will ever change though |
| // (except when the user drags a splitter). |
| if(this._top){ |
| topStyle = layoutTopBottom && this._top.style; |
| topHeight = dojo.marginBox(this._top).h; |
| } |
| if(this._left){ |
| leftStyle = layoutSides && this._left.style; |
| leftWidth = dojo.marginBox(this._left).w; |
| } |
| if(this._right){ |
| rightStyle = layoutSides && this._right.style; |
| rightWidth = dojo.marginBox(this._right).w; |
| } |
| if(this._bottom){ |
| bottomStyle = layoutTopBottom && this._bottom.style; |
| bottomHeight = dojo.marginBox(this._bottom).h; |
| } |
| |
| var splitters = this._splitters; |
| var topSplitter = splitters.top, bottomSplitter = splitters.bottom, |
| leftSplitter = splitters.left, rightSplitter = splitters.right; |
| var splitterThickness = this._splitterThickness; |
| var topSplitterThickness = splitterThickness.top || 0, |
| leftSplitterThickness = splitterThickness.left || 0, |
| rightSplitterThickness = splitterThickness.right || 0, |
| bottomSplitterThickness = splitterThickness.bottom || 0; |
| |
| // Check for race condition where CSS hasn't finished loading, so |
| // the splitter width == the viewport width (#5824) |
| if(leftSplitterThickness > 50 || rightSplitterThickness > 50){ |
| setTimeout(dojo.hitch(this, function(){ |
| // Results are invalid. Clear them out. |
| this._splitterThickness = {}; |
| |
| for(var region in this._splitters){ |
| this._computeSplitterThickness(region); |
| } |
| this._layoutChildren(); |
| }), 50); |
| return false; |
| } |
| |
| var pe = this.pe; |
| |
| var splitterBounds = { |
| left: (sidebarLayout ? leftWidth + leftSplitterThickness: 0) + pe.l + "px", |
| right: (sidebarLayout ? rightWidth + rightSplitterThickness: 0) + pe.r + "px" |
| }; |
| |
| if(topSplitter){ |
| dojo.mixin(topSplitter.style, splitterBounds); |
| topSplitter.style.top = topHeight + pe.t + "px"; |
| } |
| |
| if(bottomSplitter){ |
| dojo.mixin(bottomSplitter.style, splitterBounds); |
| bottomSplitter.style.bottom = bottomHeight + pe.b + "px"; |
| } |
| |
| splitterBounds = { |
| top: (sidebarLayout ? 0 : topHeight + topSplitterThickness) + pe.t + "px", |
| bottom: (sidebarLayout ? 0 : bottomHeight + bottomSplitterThickness) + pe.b + "px" |
| }; |
| |
| if(leftSplitter){ |
| dojo.mixin(leftSplitter.style, splitterBounds); |
| leftSplitter.style.left = leftWidth + pe.l + "px"; |
| } |
| |
| if(rightSplitter){ |
| dojo.mixin(rightSplitter.style, splitterBounds); |
| rightSplitter.style.right = rightWidth + pe.r + "px"; |
| } |
| |
| dojo.mixin(centerStyle, { |
| top: pe.t + topHeight + topSplitterThickness + "px", |
| left: pe.l + leftWidth + leftSplitterThickness + "px", |
| right: pe.r + rightWidth + rightSplitterThickness + "px", |
| bottom: pe.b + bottomHeight + bottomSplitterThickness + "px" |
| }); |
| |
| var bounds = { |
| top: sidebarLayout ? pe.t + "px" : centerStyle.top, |
| bottom: sidebarLayout ? pe.b + "px" : centerStyle.bottom |
| }; |
| dojo.mixin(leftStyle, bounds); |
| dojo.mixin(rightStyle, bounds); |
| leftStyle.left = pe.l + "px"; rightStyle.right = pe.r + "px"; topStyle.top = pe.t + "px"; bottomStyle.bottom = pe.b + "px"; |
| if(sidebarLayout){ |
| topStyle.left = bottomStyle.left = leftWidth + leftSplitterThickness + pe.l + "px"; |
| topStyle.right = bottomStyle.right = rightWidth + rightSplitterThickness + pe.r + "px"; |
| }else{ |
| topStyle.left = bottomStyle.left = pe.l + "px"; |
| topStyle.right = bottomStyle.right = pe.r + "px"; |
| } |
| |
| // More calculations about sizes of panes |
| var containerHeight = this._borderBox.h - pe.t - pe.b, |
| middleHeight = containerHeight - ( topHeight + topSplitterThickness + bottomHeight + bottomSplitterThickness), |
| sidebarHeight = sidebarLayout ? containerHeight : middleHeight; |
| |
| var containerWidth = this._borderBox.w - pe.l - pe.r, |
| middleWidth = containerWidth - (leftWidth + leftSplitterThickness + rightWidth + rightSplitterThickness), |
| sidebarWidth = sidebarLayout ? middleWidth : containerWidth; |
| |
| // New margin-box size of each pane |
| var dim = { |
| top: { w: sidebarWidth, h: topHeight }, |
| bottom: { w: sidebarWidth, h: bottomHeight }, |
| left: { w: leftWidth, h: sidebarHeight }, |
| right: { w: rightWidth, h: sidebarHeight }, |
| center: { h: middleHeight, w: middleWidth } |
| }; |
| |
| // Nodes in IE<8 don't respond to t/l/b/r, and TEXTAREA doesn't respond in any browser |
| var janky = dojo.isIE < 8 || (dojo.isIE && dojo.isQuirks) || dojo.some(this.getChildren(), function(child){ |
| return child.domNode.tagName == "TEXTAREA" || child.domNode.tagName == "INPUT"; |
| }); |
| if(janky){ |
| // Set the size of the children the old fashioned way, by setting |
| // CSS width and height |
| |
| var resizeWidget = function(widget, changes, result){ |
| if(widget){ |
| (widget.resize ? widget.resize(changes, result) : dojo.marginBox(widget.domNode, changes)); |
| } |
| }; |
| |
| if(leftSplitter){ leftSplitter.style.height = sidebarHeight; } |
| if(rightSplitter){ rightSplitter.style.height = sidebarHeight; } |
| resizeWidget(this._leftWidget, {h: sidebarHeight}, dim.left); |
| resizeWidget(this._rightWidget, {h: sidebarHeight}, dim.right); |
| |
| if(topSplitter){ topSplitter.style.width = sidebarWidth; } |
| if(bottomSplitter){ bottomSplitter.style.width = sidebarWidth; } |
| resizeWidget(this._topWidget, {w: sidebarWidth}, dim.top); |
| resizeWidget(this._bottomWidget, {w: sidebarWidth}, dim.bottom); |
| |
| resizeWidget(this._centerWidget, dim.center); |
| }else{ |
| // We've already sized the children by setting style.top/bottom/left/right... |
| // Now just need to call resize() on those children telling them their new size, |
| // so they can re-layout themselves |
| |
| // Calculate which panes need a notification |
| var resizeList = {}; |
| if(changedRegion){ |
| resizeList[changedRegion] = resizeList.center = true; |
| if(/top|bottom/.test(changedRegion) && this.design != "sidebar"){ |
| resizeList.left = resizeList.right = true; |
| }else if(/left|right/.test(changedRegion) && this.design == "sidebar"){ |
| resizeList.top = resizeList.bottom = true; |
| } |
| } |
| |
| dojo.forEach(this.getChildren(), function(child){ |
| if(child.resize && (!changedRegion || child.region in resizeList)){ |
| child.resize(null, dim[child.region]); |
| } |
| }, this); |
| } |
| }, |
| |
| destroy: function(){ |
| for(var region in this._splitters){ |
| var splitter = this._splitters[region]; |
| dijit.byNode(splitter).destroy(); |
| dojo.destroy(splitter); |
| } |
| delete this._splitters; |
| delete this._splitterThickness; |
| this.inherited(arguments); |
| } |
| }); |
| |
| // This argument can be specified for the children of a BorderContainer. |
| // Since any widget can be specified as a LayoutContainer child, mix it |
| // into the base widget class. (This is a hack, but it's effective.) |
| dojo.extend(dijit._Widget, { |
| // region: [const] String |
| // Parameter for children of `dijit.layout.BorderContainer`. |
| // Values: "top", "bottom", "leading", "trailing", "left", "right", "center". |
| // See the `dijit.layout.BorderContainer` description for details. |
| region: '', |
| |
| // splitter: [const] Boolean |
| // Parameter for child of `dijit.layout.BorderContainer` where region != "center". |
| // If true, enables user to resize the widget by putting a draggable splitter between |
| // this widget and the region=center widget. |
| splitter: false, |
| |
| // minSize: [const] Number |
| // Parameter for children of `dijit.layout.BorderContainer`. |
| // Specifies a minimum size (in pixels) for this widget when resized by a splitter. |
| minSize: 0, |
| |
| // maxSize: [const] Number |
| // Parameter for children of `dijit.layout.BorderContainer`. |
| // Specifies a maximum size (in pixels) for this widget when resized by a splitter. |
| maxSize: Infinity |
| }); |
| |
| |
| |
| dojo.declare("dijit.layout._Splitter", [ dijit._Widget, dijit._Templated ], |
| { |
| // summary: |
| // A draggable spacer between two items in a `dijit.layout.BorderContainer`. |
| // description: |
| // This is instantiated by `dijit.layout.BorderContainer`. Users should not |
| // create it directly. |
| // tags: |
| // private |
| |
| /*===== |
| // container: [const] dijit.layout.BorderContainer |
| // Pointer to the parent BorderContainer |
| container: null, |
| |
| // child: [const] dijit.layout._LayoutWidget |
| // Pointer to the pane associated with this splitter |
| child: null, |
| |
| // region: String |
| // Region of pane associated with this splitter. |
| // "top", "bottom", "left", "right". |
| region: null, |
| =====*/ |
| |
| // live: [const] Boolean |
| // If true, the child's size changes and the child widget is redrawn as you drag the splitter; |
| // otherwise, the size doesn't change until you drop the splitter (by mouse-up) |
| live: true, |
| |
| templateString: '<div class="dijitSplitter" dojoAttachEvent="onkeypress:_onKeyPress,onmousedown:_startDrag,onmouseenter:_onMouse,onmouseleave:_onMouse" tabIndex="0" waiRole="separator"><div class="dijitSplitterThumb"></div></div>', |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| this.horizontal = /top|bottom/.test(this.region); |
| dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V")); |
| // dojo.addClass(this.child.domNode, "dijitSplitterPane"); |
| // dojo.setSelectable(this.domNode, false); //TODO is this necessary? |
| |
| this._factor = /top|left/.test(this.region) ? 1 : -1; |
| |
| this._cookieName = this.container.id + "_" + this.region; |
| if(this.container.persist){ |
| // restore old size |
| var persistSize = dojo.cookie(this._cookieName); |
| if(persistSize){ |
| this.child.domNode.style[this.horizontal ? "height" : "width"] = persistSize; |
| } |
| } |
| }, |
| |
| _computeMaxSize: function(){ |
| // summary: |
| // Compute the maximum size that my corresponding pane can be set to |
| |
| var dim = this.horizontal ? 'h' : 'w', |
| thickness = this.container._splitterThickness[this.region]; |
| |
| // Get DOMNode of opposite pane, if an opposite pane exists. |
| // Ex: if I am the _Splitter for the left pane, then get the right pane. |
| var flip = {left:'right', right:'left', top:'bottom', bottom:'top', leading:'trailing', trailing:'leading'}, |
| oppNode = this.container["_" + flip[this.region]]; |
| |
| // I can expand up to the edge of the opposite pane, or if there's no opposite pane, then to |
| // edge of BorderContainer |
| var available = dojo.contentBox(this.container.domNode)[dim] - |
| (oppNode ? dojo.marginBox(oppNode)[dim] : 0) - |
| 20 - thickness * 2; |
| |
| return Math.min(this.child.maxSize, available); |
| }, |
| |
| _startDrag: function(e){ |
| if(!this.cover){ |
| this.cover = dojo.doc.createElement('div'); |
| dojo.addClass(this.cover, "dijitSplitterCover"); |
| dojo.place(this.cover, this.child.domNode, "after"); |
| } |
| dojo.addClass(this.cover, "dijitSplitterCoverActive"); |
| |
| // Safeguard in case the stop event was missed. Shouldn't be necessary if we always get the mouse up. |
| if(this.fake){ dojo.destroy(this.fake); } |
| if(!(this._resize = this.live)){ //TODO: disable live for IE6? |
| // create fake splitter to display at old position while we drag |
| (this.fake = this.domNode.cloneNode(true)).removeAttribute("id"); |
| dojo.addClass(this.domNode, "dijitSplitterShadow"); |
| dojo.place(this.fake, this.domNode, "after"); |
| } |
| dojo.addClass(this.domNode, "dijitSplitterActive"); |
| dojo.addClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Active"); |
| if(this.fake){ |
| dojo.removeClass(this.fake, "dijitSplitterHover"); |
| dojo.removeClass(this.fake, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover"); |
| } |
| |
| //Performance: load data info local vars for onmousevent function closure |
| var factor = this._factor, |
| max = this._computeMaxSize(), |
| min = this.child.minSize || 20, |
| isHorizontal = this.horizontal, |
| axis = isHorizontal ? "pageY" : "pageX", |
| pageStart = e[axis], |
| splitterStyle = this.domNode.style, |
| dim = isHorizontal ? 'h' : 'w', |
| childStart = dojo.marginBox(this.child.domNode)[dim], |
| region = this.region, |
| splitterStart = parseInt(this.domNode.style[region], 10), |
| resize = this._resize, |
| mb = {}, |
| childNode = this.child.domNode, |
| layoutFunc = dojo.hitch(this.container, this.container._layoutChildren), |
| de = dojo.doc.body; |
| |
| this._handlers = (this._handlers || []).concat([ |
| dojo.connect(de, "onmousemove", this._drag = function(e, forceResize){ |
| var delta = e[axis] - pageStart, |
| childSize = factor * delta + childStart, |
| boundChildSize = Math.max(Math.min(childSize, max), min); |
| |
| if(resize || forceResize){ |
| mb[dim] = boundChildSize; |
| // TODO: inefficient; we set the marginBox here and then immediately layoutFunc() needs to query it |
| dojo.marginBox(childNode, mb); |
| layoutFunc(region); |
| } |
| splitterStyle[region] = factor * delta + splitterStart + (boundChildSize - childSize) + "px"; |
| }), |
| dojo.connect(dojo.doc, "ondragstart", dojo.stopEvent), |
| dojo.connect(dojo.body(), "onselectstart", dojo.stopEvent), |
| dojo.connect(de, "onmouseup", this, "_stopDrag") |
| ]); |
| dojo.stopEvent(e); |
| }, |
| |
| _onMouse: function(e){ |
| var o = (e.type == "mouseover" || e.type == "mouseenter"); |
| dojo.toggleClass(this.domNode, "dijitSplitterHover", o); |
| dojo.toggleClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Hover", o); |
| }, |
| |
| _stopDrag: function(e){ |
| try{ |
| if(this.cover){ |
| dojo.removeClass(this.cover, "dijitSplitterCoverActive"); |
| } |
| if(this.fake){ dojo.destroy(this.fake); } |
| dojo.removeClass(this.domNode, "dijitSplitterActive"); |
| dojo.removeClass(this.domNode, "dijitSplitter" + (this.horizontal ? "H" : "V") + "Active"); |
| dojo.removeClass(this.domNode, "dijitSplitterShadow"); |
| this._drag(e); //TODO: redundant with onmousemove? |
| this._drag(e, true); |
| }finally{ |
| this._cleanupHandlers(); |
| delete this._drag; |
| } |
| |
| if(this.container.persist){ |
| dojo.cookie(this._cookieName, this.child.domNode.style[this.horizontal ? "height" : "width"], {expires:365}); |
| } |
| }, |
| |
| _cleanupHandlers: function(){ |
| dojo.forEach(this._handlers, dojo.disconnect); |
| delete this._handlers; |
| }, |
| |
| _onKeyPress: function(/*Event*/ e){ |
| // should we apply typematic to this? |
| this._resize = true; |
| var horizontal = this.horizontal; |
| var tick = 1; |
| var dk = dojo.keys; |
| switch(e.charOrCode){ |
| case horizontal ? dk.UP_ARROW : dk.LEFT_ARROW: |
| tick *= -1; |
| // break; |
| case horizontal ? dk.DOWN_ARROW : dk.RIGHT_ARROW: |
| break; |
| default: |
| // this.inherited(arguments); |
| return; |
| } |
| var childSize = dojo.marginBox(this.child.domNode)[ horizontal ? 'h' : 'w' ] + this._factor * tick; |
| var mb = {}; |
| mb[ this.horizontal ? "h" : "w"] = Math.max(Math.min(childSize, this._computeMaxSize()), this.child.minSize); |
| dojo.marginBox(this.child.domNode, mb); |
| this.container._layoutChildren(this.region); |
| dojo.stopEvent(e); |
| }, |
| |
| destroy: function(){ |
| this._cleanupHandlers(); |
| delete this.child; |
| delete this.container; |
| delete this.cover; |
| delete this.fake; |
| this.inherited(arguments); |
| } |
| }); |
| |
| dojo.declare("dijit.layout._Gutter", [dijit._Widget, dijit._Templated ], |
| { |
| // summary: |
| // Just a spacer div to separate side pane from center pane. |
| // Basically a trick to lookup the gutter/splitter width from the theme. |
| // description: |
| // Instantiated by `dijit.layout.BorderContainer`. Users should not |
| // create directly. |
| // tags: |
| // private |
| |
| templateString: '<div class="dijitGutter" waiRole="presentation"></div>', |
| |
| postCreate: function(){ |
| this.horizontal = /top|bottom/.test(this.region); |
| dojo.addClass(this.domNode, "dijitGutter" + (this.horizontal ? "H" : "V")); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout.LayoutContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout.LayoutContainer"] = true; |
| dojo.provide("dijit.layout.LayoutContainer"); |
| |
| |
| |
| dojo.declare("dijit.layout.LayoutContainer", |
| dijit.layout._LayoutWidget, |
| { |
| // summary: |
| // Deprecated. Use `dijit.layout.BorderContainer` instead. |
| // |
| // description: |
| // Provides Delphi-style panel layout semantics. |
| // |
| // A LayoutContainer is a box with a specified size (like style="width: 500px; height: 500px;"), |
| // that contains children widgets marked with "layoutAlign" of "left", "right", "bottom", "top", and "client". |
| // It takes it's children marked as left/top/bottom/right, and lays them out along the edges of the box, |
| // and then it takes the child marked "client" and puts it into the remaining space in the middle. |
| // |
| // Left/right positioning is similar to CSS's "float: left" and "float: right", |
| // and top/bottom positioning would be similar to "float: top" and "float: bottom", if there were such |
| // CSS. |
| // |
| // Note that there can only be one client element, but there can be multiple left, right, top, |
| // or bottom elements. |
| // |
| // example: |
| // | <style> |
| // | html, body{ height: 100%; width: 100%; } |
| // | </style> |
| // | <div dojoType="dijit.layout.LayoutContainer" style="width: 100%; height: 100%"> |
| // | <div dojoType="dijit.layout.ContentPane" layoutAlign="top">header text</div> |
| // | <div dojoType="dijit.layout.ContentPane" layoutAlign="left" style="width: 200px;">table of contents</div> |
| // | <div dojoType="dijit.layout.ContentPane" layoutAlign="client">client area</div> |
| // | </div> |
| // |
| // Lays out each child in the natural order the children occur in. |
| // Basically each child is laid out into the "remaining space", where "remaining space" is initially |
| // the content area of this widget, but is reduced to a smaller rectangle each time a child is added. |
| // tags: |
| // deprecated |
| |
| baseClass: "dijitLayoutContainer", |
| |
| constructor: function(){ |
| dojo.deprecated("dijit.layout.LayoutContainer is deprecated", "use BorderContainer instead", 2.0); |
| }, |
| |
| layout: function(){ |
| dijit.layout.layoutChildren(this.domNode, this._contentBox, this.getChildren()); |
| }, |
| |
| addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ |
| this.inherited(arguments); |
| if(this._started){ |
| dijit.layout.layoutChildren(this.domNode, this._contentBox, this.getChildren()); |
| } |
| }, |
| |
| removeChild: function(/*dijit._Widget*/ widget){ |
| this.inherited(arguments); |
| if(this._started){ |
| dijit.layout.layoutChildren(this.domNode, this._contentBox, this.getChildren()); |
| } |
| } |
| }); |
| |
| // This argument can be specified for the children of a LayoutContainer. |
| // Since any widget can be specified as a LayoutContainer child, mix it |
| // into the base widget class. (This is a hack, but it's effective.) |
| dojo.extend(dijit._Widget, { |
| // layoutAlign: String |
| // "none", "left", "right", "bottom", "top", and "client". |
| // See the LayoutContainer description for details on this parameter. |
| layoutAlign: 'none' |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout.LinkPane"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout.LinkPane"] = true; |
| dojo.provide("dijit.layout.LinkPane"); |
| |
| |
| |
| |
| dojo.declare("dijit.layout.LinkPane", |
| [dijit.layout.ContentPane, dijit._Templated], |
| { |
| // summary: |
| // A ContentPane with an href where (when declared in markup) |
| // the title is specified as innerHTML rather than as a title attribute. |
| // description: |
| // LinkPane is just a ContentPane that is declared in markup similarly |
| // to an anchor. The anchor's body (the words between `<a>` and `</a>`) |
| // become the title of the widget (used for TabContainer, AccordionContainer, etc.) |
| // example: |
| // | <a href="foo.html">my title</a> |
| |
| // I'm using a template because the user may specify the input as |
| // <a href="foo.html">title</a>, in which case we need to get rid of the |
| // <a> because we don't want a link. |
| templateString: '<div class="dijitLinkPane" dojoAttachPoint="containerNode"></div>', |
| |
| postMixInProperties: function(){ |
| // If user has specified node contents, they become the title |
| // (the link must be plain text) |
| if(this.srcNodeRef){ |
| this.title += this.srcNodeRef.innerHTML; |
| } |
| this.inherited(arguments); |
| }, |
| |
| _fillContent: function(/*DomNode*/ source){ |
| // Overrides _Templated._fillContent(). |
| |
| // _Templated._fillContent() relocates srcNodeRef innerHTML to templated container node, |
| // but in our case the srcNodeRef innerHTML is the title, so shouldn't be |
| // copied |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout.SplitContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout.SplitContainer"] = true; |
| dojo.provide("dijit.layout.SplitContainer"); |
| |
| // |
| // FIXME: make it prettier |
| // FIXME: active dragging upwards doesn't always shift other bars (direction calculation is wrong in this case) |
| // |
| |
| |
| |
| |
| dojo.declare("dijit.layout.SplitContainer", |
| dijit.layout._LayoutWidget, |
| { |
| // summary: |
| // Deprecated. Use `dijit.layout.BorderContainer` instead. |
| // description: |
| // A Container widget with sizing handles in-between each child. |
| // Contains multiple children widgets, all of which are displayed side by side |
| // (either horizontally or vertically); there's a bar between each of the children, |
| // and you can adjust the relative size of each child by dragging the bars. |
| // |
| // You must specify a size (width and height) for the SplitContainer. |
| // tags: |
| // deprecated |
| |
| constructor: function(){ |
| dojo.deprecated("dijit.layout.SplitContainer is deprecated", "use BorderContainer with splitter instead", 2.0); |
| }, |
| |
| // activeSizing: Boolean |
| // If true, the children's size changes as you drag the bar; |
| // otherwise, the sizes don't change until you drop the bar (by mouse-up) |
| activeSizing: false, |
| |
| // sizerWidth: Integer |
| // Size in pixels of the bar between each child |
| sizerWidth: 7, // FIXME: this should be a CSS attribute (at 7 because css wants it to be 7 until we fix to css) |
| |
| // orientation: String |
| // either 'horizontal' or vertical; indicates whether the children are |
| // arranged side-by-side or up/down. |
| orientation: 'horizontal', |
| |
| // persist: Boolean |
| // Save splitter positions in a cookie |
| persist: true, |
| |
| baseClass: "dijitSplitContainer", |
| |
| postMixInProperties: function(){ |
| this.inherited("postMixInProperties",arguments); |
| this.isHorizontal = (this.orientation == 'horizontal'); |
| }, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| this.sizers = []; |
| |
| // overflow has to be explicitly hidden for splitContainers using gekko (trac #1435) |
| // to keep other combined css classes from inadvertantly making the overflow visible |
| if(dojo.isMozilla){ |
| this.domNode.style.overflow = '-moz-scrollbars-none'; // hidden doesn't work |
| } |
| |
| // create the fake dragger |
| if(typeof this.sizerWidth == "object"){ |
| try{ //FIXME: do this without a try/catch |
| this.sizerWidth = parseInt(this.sizerWidth.toString()); |
| }catch(e){ this.sizerWidth = 7; } |
| } |
| var sizer = dojo.doc.createElement('div'); |
| this.virtualSizer = sizer; |
| sizer.style.position = 'relative'; |
| |
| // #1681: work around the dreaded 'quirky percentages in IE' layout bug |
| // If the splitcontainer's dimensions are specified in percentages, it |
| // will be resized when the virtualsizer is displayed in _showSizingLine |
| // (typically expanding its bounds unnecessarily). This happens because |
| // we use position: relative for .dijitSplitContainer. |
| // The workaround: instead of changing the display style attribute, |
| // switch to changing the zIndex (bring to front/move to back) |
| |
| sizer.style.zIndex = 10; |
| sizer.className = this.isHorizontal ? 'dijitSplitContainerVirtualSizerH' : 'dijitSplitContainerVirtualSizerV'; |
| this.domNode.appendChild(sizer); |
| dojo.setSelectable(sizer, false); |
| }, |
| |
| destroy: function(){ |
| delete this.virtualSizer; |
| dojo.forEach(this._ownconnects, dojo.disconnect); |
| this.inherited(arguments); |
| }, |
| startup: function(){ |
| if(this._started){ return; } |
| |
| dojo.forEach(this.getChildren(), function(child, i, children){ |
| // attach the children and create the draggers |
| this._setupChild(child); |
| |
| if(i < children.length-1){ |
| this._addSizer(); |
| } |
| }, this); |
| |
| if(this.persist){ |
| this._restoreState(); |
| } |
| |
| this.inherited(arguments); |
| }, |
| |
| _setupChild: function(/*dijit._Widget*/ child){ |
| this.inherited(arguments); |
| child.domNode.style.position = "absolute"; |
| dojo.addClass(child.domNode, "dijitSplitPane"); |
| }, |
| |
| _onSizerMouseDown: function(e){ |
| if(e.target.id){ |
| for(var i=0;i<this.sizers.length;i++){ |
| if(this.sizers[i].id == e.target.id){ |
| break; |
| } |
| } |
| if(i<this.sizers.length){ |
| this.beginSizing(e,i); |
| } |
| } |
| }, |
| _addSizer: function(index){ |
| index = index === undefined ? this.sizers.length : index; |
| |
| // TODO: use a template for this!!! |
| var sizer = dojo.doc.createElement('div'); |
| sizer.id=dijit.getUniqueId('dijit_layout_SplitterContainer_Splitter'); |
| this.sizers.splice(index,0,sizer); |
| this.domNode.appendChild(sizer); |
| |
| sizer.className = this.isHorizontal ? 'dijitSplitContainerSizerH' : 'dijitSplitContainerSizerV'; |
| |
| // add the thumb div |
| var thumb = dojo.doc.createElement('div'); |
| thumb.className = 'thumb'; |
| thumb.id = sizer.id; |
| sizer.appendChild(thumb); |
| |
| // FIXME: are you serious? why aren't we using mover start/stop combo? |
| this.connect(sizer, "onmousedown", '_onSizerMouseDown'); |
| |
| dojo.setSelectable(sizer, false); |
| }, |
| |
| removeChild: function(widget){ |
| // summary: |
| // Remove sizer, but only if widget is really our child and |
| // we have at least one sizer to throw away |
| if(this.sizers.length){ |
| var i=dojo.indexOf(this.getChildren(), widget) |
| if(i != -1){ |
| if(i == this.sizers.length){ |
| i--; |
| } |
| dojo.destroy(this.sizers[i]); |
| this.sizers.splice(i,1); |
| } |
| } |
| |
| // Remove widget and repaint |
| this.inherited(arguments); |
| if(this._started){ |
| this.layout(); |
| } |
| }, |
| |
| addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){ |
| // summary: |
| // Add a child widget to the container |
| // child: |
| // a widget to add |
| // insertIndex: |
| // postion in the "stack" to add the child widget |
| |
| this.inherited(arguments); |
| |
| if(this._started){ |
| // Do the stuff that startup() does for each widget |
| var children = this.getChildren(); |
| if(children.length > 1){ |
| this._addSizer(insertIndex); |
| } |
| |
| // and then reposition (ie, shrink) every pane to make room for the new guy |
| this.layout(); |
| } |
| }, |
| |
| layout: function(){ |
| // summary: |
| // Do layout of panels |
| |
| // base class defines this._contentBox on initial creation and also |
| // on resize |
| this.paneWidth = this._contentBox.w; |
| this.paneHeight = this._contentBox.h; |
| |
| var children = this.getChildren(); |
| if(!children.length){ return; } |
| |
| // |
| // calculate space |
| // |
| |
| var space = this.isHorizontal ? this.paneWidth : this.paneHeight; |
| if(children.length > 1){ |
| space -= this.sizerWidth * (children.length - 1); |
| } |
| |
| // |
| // calculate total of SizeShare values |
| // |
| var outOf = 0; |
| dojo.forEach(children, function(child){ |
| outOf += child.sizeShare; |
| }); |
| |
| // |
| // work out actual pixels per sizeshare unit |
| // |
| var pixPerUnit = space / outOf; |
| |
| // |
| // set the SizeActual member of each pane |
| // |
| var totalSize = 0; |
| dojo.forEach(children.slice(0, children.length - 1), function(child){ |
| var size = Math.round(pixPerUnit * child.sizeShare); |
| child.sizeActual = size; |
| totalSize += size; |
| }); |
| |
| children[children.length-1].sizeActual = space - totalSize; |
| |
| // |
| // make sure the sizes are ok |
| // |
| this._checkSizes(); |
| |
| // |
| // now loop, positioning each pane and letting children resize themselves |
| // |
| |
| var pos = 0; |
| var size = children[0].sizeActual; |
| this._movePanel(children[0], pos, size); |
| children[0].position = pos; |
| pos += size; |
| |
| // if we don't have any sizers, our layout method hasn't been called yet |
| // so bail until we are called..TODO: REVISIT: need to change the startup |
| // algorithm to guaranteed the ordering of calls to layout method |
| if(!this.sizers){ |
| return; |
| } |
| |
| dojo.some(children.slice(1), function(child, i){ |
| // error-checking |
| if(!this.sizers[i]){ |
| return true; |
| } |
| // first we position the sizing handle before this pane |
| this._moveSlider(this.sizers[i], pos, this.sizerWidth); |
| this.sizers[i].position = pos; |
| pos += this.sizerWidth; |
| |
| size = child.sizeActual; |
| this._movePanel(child, pos, size); |
| child.position = pos; |
| pos += size; |
| }, this); |
| }, |
| |
| _movePanel: function(panel, pos, size){ |
| if(this.isHorizontal){ |
| panel.domNode.style.left = pos + 'px'; // TODO: resize() takes l and t parameters too, don't need to set manually |
| panel.domNode.style.top = 0; |
| var box = {w: size, h: this.paneHeight}; |
| if(panel.resize){ |
| panel.resize(box); |
| }else{ |
| dojo.marginBox(panel.domNode, box); |
| } |
| }else{ |
| panel.domNode.style.left = 0; // TODO: resize() takes l and t parameters too, don't need to set manually |
| panel.domNode.style.top = pos + 'px'; |
| var box = {w: this.paneWidth, h: size}; |
| if(panel.resize){ |
| panel.resize(box); |
| }else{ |
| dojo.marginBox(panel.domNode, box); |
| } |
| } |
| }, |
| |
| _moveSlider: function(slider, pos, size){ |
| if(this.isHorizontal){ |
| slider.style.left = pos + 'px'; |
| slider.style.top = 0; |
| dojo.marginBox(slider, { w: size, h: this.paneHeight }); |
| }else{ |
| slider.style.left = 0; |
| slider.style.top = pos + 'px'; |
| dojo.marginBox(slider, { w: this.paneWidth, h: size }); |
| } |
| }, |
| |
| _growPane: function(growth, pane){ |
| if(growth > 0){ |
| if(pane.sizeActual > pane.sizeMin){ |
| if((pane.sizeActual - pane.sizeMin) > growth){ |
| |
| // stick all the growth in this pane |
| pane.sizeActual = pane.sizeActual - growth; |
| growth = 0; |
| }else{ |
| // put as much growth in here as we can |
| growth -= pane.sizeActual - pane.sizeMin; |
| pane.sizeActual = pane.sizeMin; |
| } |
| } |
| } |
| return growth; |
| }, |
| |
| _checkSizes: function(){ |
| |
| var totalMinSize = 0; |
| var totalSize = 0; |
| var children = this.getChildren(); |
| |
| dojo.forEach(children, function(child){ |
| totalSize += child.sizeActual; |
| totalMinSize += child.sizeMin; |
| }); |
| |
| // only make adjustments if we have enough space for all the minimums |
| |
| if(totalMinSize <= totalSize){ |
| |
| var growth = 0; |
| |
| dojo.forEach(children, function(child){ |
| if(child.sizeActual < child.sizeMin){ |
| growth += child.sizeMin - child.sizeActual; |
| child.sizeActual = child.sizeMin; |
| } |
| }); |
| |
| if(growth > 0){ |
| var list = this.isDraggingLeft ? children.reverse() : children; |
| dojo.forEach(list, function(child){ |
| growth = this._growPane(growth, child); |
| }, this); |
| } |
| }else{ |
| dojo.forEach(children, function(child){ |
| child.sizeActual = Math.round(totalSize * (child.sizeMin / totalMinSize)); |
| }); |
| } |
| }, |
| |
| beginSizing: function(e, i){ |
| var children = this.getChildren(); |
| this.paneBefore = children[i]; |
| this.paneAfter = children[i+1]; |
| |
| this.isSizing = true; |
| this.sizingSplitter = this.sizers[i]; |
| |
| if(!this.cover){ |
| this.cover = dojo.create('div', { |
| style: { |
| position:'absolute', |
| zIndex:5, |
| top: 0, |
| left: 0, |
| width: "100%", |
| height: "100%" |
| } |
| }, this.domNode); |
| }else{ |
| this.cover.style.zIndex = 5; |
| } |
| this.sizingSplitter.style.zIndex = 6; |
| |
| // TODO: REVISIT - we want MARGIN_BOX and core hasn't exposed that yet (but can't we use it anyway if we pay attention? we do elsewhere.) |
| this.originPos = dojo.position(children[0].domNode, true); |
| if(this.isHorizontal){ |
| var client = e.layerX || e.offsetX || 0; |
| var screen = e.pageX; |
| this.originPos = this.originPos.x; |
| }else{ |
| var client = e.layerY || e.offsetY || 0; |
| var screen = e.pageY; |
| this.originPos = this.originPos.y; |
| } |
| this.startPoint = this.lastPoint = screen; |
| this.screenToClientOffset = screen - client; |
| this.dragOffset = this.lastPoint - this.paneBefore.sizeActual - this.originPos - this.paneBefore.position; |
| |
| if(!this.activeSizing){ |
| this._showSizingLine(); |
| } |
| |
| // |
| // attach mouse events |
| // |
| this._ownconnects = []; |
| this._ownconnects.push(dojo.connect(dojo.doc.documentElement, "onmousemove", this, "changeSizing")); |
| this._ownconnects.push(dojo.connect(dojo.doc.documentElement, "onmouseup", this, "endSizing")); |
| |
| dojo.stopEvent(e); |
| }, |
| |
| changeSizing: function(e){ |
| if(!this.isSizing){ return; } |
| this.lastPoint = this.isHorizontal ? e.pageX : e.pageY; |
| this.movePoint(); |
| if(this.activeSizing){ |
| this._updateSize(); |
| }else{ |
| this._moveSizingLine(); |
| } |
| dojo.stopEvent(e); |
| }, |
| |
| endSizing: function(e){ |
| if(!this.isSizing){ return; } |
| if(this.cover){ |
| this.cover.style.zIndex = -1; |
| } |
| if(!this.activeSizing){ |
| this._hideSizingLine(); |
| } |
| |
| this._updateSize(); |
| |
| this.isSizing = false; |
| |
| if(this.persist){ |
| this._saveState(this); |
| } |
| |
| dojo.forEach(this._ownconnects, dojo.disconnect); |
| }, |
| |
| movePoint: function(){ |
| |
| // make sure lastPoint is a legal point to drag to |
| var p = this.lastPoint - this.screenToClientOffset; |
| |
| var a = p - this.dragOffset; |
| a = this.legaliseSplitPoint(a); |
| p = a + this.dragOffset; |
| |
| this.lastPoint = p + this.screenToClientOffset; |
| }, |
| |
| legaliseSplitPoint: function(a){ |
| |
| a += this.sizingSplitter.position; |
| |
| this.isDraggingLeft = !!(a > 0); |
| |
| if(!this.activeSizing){ |
| var min = this.paneBefore.position + this.paneBefore.sizeMin; |
| if(a < min){ |
| a = min; |
| } |
| |
| var max = this.paneAfter.position + (this.paneAfter.sizeActual - (this.sizerWidth + this.paneAfter.sizeMin)); |
| if(a > max){ |
| a = max; |
| } |
| } |
| |
| a -= this.sizingSplitter.position; |
| |
| this._checkSizes(); |
| |
| return a; |
| }, |
| |
| _updateSize: function(){ |
| //FIXME: sometimes this.lastPoint is NaN |
| var pos = this.lastPoint - this.dragOffset - this.originPos; |
| |
| var start_region = this.paneBefore.position; |
| var end_region = this.paneAfter.position + this.paneAfter.sizeActual; |
| |
| this.paneBefore.sizeActual = pos - start_region; |
| this.paneAfter.position = pos + this.sizerWidth; |
| this.paneAfter.sizeActual = end_region - this.paneAfter.position; |
| |
| dojo.forEach(this.getChildren(), function(child){ |
| child.sizeShare = child.sizeActual; |
| }); |
| |
| if(this._started){ |
| this.layout(); |
| } |
| }, |
| |
| _showSizingLine: function(){ |
| |
| this._moveSizingLine(); |
| |
| dojo.marginBox(this.virtualSizer, |
| this.isHorizontal ? { w: this.sizerWidth, h: this.paneHeight } : { w: this.paneWidth, h: this.sizerWidth }); |
| |
| this.virtualSizer.style.display = 'block'; |
| }, |
| |
| _hideSizingLine: function(){ |
| this.virtualSizer.style.display = 'none'; |
| }, |
| |
| _moveSizingLine: function(){ |
| var pos = (this.lastPoint - this.startPoint) + this.sizingSplitter.position; |
| dojo.style(this.virtualSizer,(this.isHorizontal ? "left" : "top"),pos+"px"); |
| // this.virtualSizer.style[ this.isHorizontal ? "left" : "top" ] = pos + 'px'; // FIXME: remove this line if the previous is better |
| }, |
| |
| _getCookieName: function(i){ |
| return this.id + "_" + i; |
| }, |
| |
| _restoreState: function(){ |
| dojo.forEach(this.getChildren(), function(child, i){ |
| var cookieName = this._getCookieName(i); |
| var cookieValue = dojo.cookie(cookieName); |
| if(cookieValue){ |
| var pos = parseInt(cookieValue); |
| if(typeof pos == "number"){ |
| child.sizeShare = pos; |
| } |
| } |
| }, this); |
| }, |
| |
| _saveState: function(){ |
| if(!this.persist){ |
| return; |
| } |
| dojo.forEach(this.getChildren(), function(child, i){ |
| dojo.cookie(this._getCookieName(i), child.sizeShare, {expires:365}); |
| }, this); |
| } |
| }); |
| |
| // These arguments can be specified for the children of a SplitContainer. |
| // Since any widget can be specified as a SplitContainer child, mix them |
| // into the base widget class. (This is a hack, but it's effective.) |
| dojo.extend(dijit._Widget, { |
| // sizeMin: [deprecated] Integer |
| // Deprecated. Parameter for children of `dijit.layout.SplitContainer`. |
| // Minimum size (width or height) of a child of a SplitContainer. |
| // The value is relative to other children's sizeShare properties. |
| sizeMin: 10, |
| |
| // sizeShare: [deprecated] Integer |
| // Deprecated. Parameter for children of `dijit.layout.SplitContainer`. |
| // Size (width or height) of a child of a SplitContainer. |
| // The value is relative to other children's sizeShare properties. |
| // For example, if there are two children and each has sizeShare=10, then |
| // each takes up 50% of the available space. |
| sizeShare: 10 |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout._TabContainerBase"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout._TabContainerBase"] = true; |
| dojo.provide("dijit.layout._TabContainerBase"); |
| |
| |
| |
| |
| dojo.declare("dijit.layout._TabContainerBase", |
| [dijit.layout.StackContainer, dijit._Templated], |
| { |
| // summary: |
| // Abstract base class for TabContainer. Must define _makeController() to instantiate |
| // and return the widget that displays the tab labels |
| // description: |
| // A TabContainer is a container that has multiple panes, but shows only |
| // one pane at a time. There are a set of tabs corresponding to each pane, |
| // where each tab has the name (aka title) of the pane, and optionally a close button. |
| |
| // tabPosition: String |
| // Defines where tabs go relative to tab content. |
| // "top", "bottom", "left-h", "right-h" |
| tabPosition: "top", |
| |
| baseClass: "dijitTabContainer", |
| |
| // tabStrip: Boolean |
| // Defines whether the tablist gets an extra class for layouting, putting a border/shading |
| // around the set of tabs. |
| tabStrip: false, |
| |
| // nested: Boolean |
| // If true, use styling for a TabContainer nested inside another TabContainer. |
| // For tundra etc., makes tabs look like links, and hides the outer |
| // border since the outer TabContainer already has a border. |
| nested: false, |
| |
| templateString: dojo.cache("dijit.layout", "templates/TabContainer.html", "<div class=\"dijitTabContainer\">\n\t<div class=\"dijitTabListWrapper\" dojoAttachPoint=\"tablistNode\"></div>\n\t<div dojoAttachPoint=\"tablistSpacer\" class=\"dijitTabSpacer ${baseClass}-spacer\"></div>\n\t<div class=\"dijitTabPaneWrapper ${baseClass}-container\" dojoAttachPoint=\"containerNode\"></div>\n</div>\n"), |
| |
| postMixInProperties: function(){ |
| // set class name according to tab position, ex: dijitTabContainerTop |
| this.baseClass += this.tabPosition.charAt(0).toUpperCase() + this.tabPosition.substr(1).replace(/-.*/, ""); |
| |
| this.srcNodeRef && dojo.style(this.srcNodeRef, "visibility", "hidden"); |
| |
| this.inherited(arguments); |
| }, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| |
| // Create the tab list that will have a tab (a.k.a. tab button) for each tab panel |
| this.tablist = this._makeController(this.tablistNode); |
| |
| if(!this.doLayout){ dojo.addClass(this.domNode, "dijitTabContainerNoLayout"); } |
| |
| if(this.nested){ |
| /* workaround IE's lack of support for "a > b" selectors by |
| * tagging each node in the template. |
| */ |
| dojo.addClass(this.domNode, "dijitTabContainerNested"); |
| dojo.addClass(this.tablist.containerNode, "dijitTabContainerTabListNested"); |
| dojo.addClass(this.tablistSpacer, "dijitTabContainerSpacerNested"); |
| dojo.addClass(this.containerNode, "dijitTabPaneWrapperNested"); |
| }else{ |
| dojo.addClass(this.domNode, "tabStrip-" + (this.tabStrip ? "enabled" : "disabled")); |
| } |
| }, |
| |
| _setupChild: function(/*dijit._Widget*/ tab){ |
| // Overrides StackContainer._setupChild(). |
| dojo.addClass(tab.domNode, "dijitTabPane"); |
| this.inherited(arguments); |
| }, |
| |
| startup: function(){ |
| if(this._started){ return; } |
| |
| // wire up the tablist and its tabs |
| this.tablist.startup(); |
| |
| this.inherited(arguments); |
| }, |
| |
| layout: function(){ |
| // Overrides StackContainer.layout(). |
| // Configure the content pane to take up all the space except for where the tabs are |
| |
| if(!this._contentBox || typeof(this._contentBox.l) == "undefined"){return;} |
| |
| var sc = this.selectedChildWidget; |
| |
| if(this.doLayout){ |
| // position and size the titles and the container node |
| var titleAlign = this.tabPosition.replace(/-h/, ""); |
| this.tablist.layoutAlign = titleAlign; |
| var children = [this.tablist, { |
| domNode: this.tablistSpacer, |
| layoutAlign: titleAlign |
| }, { |
| domNode: this.containerNode, |
| layoutAlign: "client" |
| }]; |
| dijit.layout.layoutChildren(this.domNode, this._contentBox, children); |
| |
| // Compute size to make each of my children. |
| // children[2] is the margin-box size of this.containerNode, set by layoutChildren() call above |
| this._containerContentBox = dijit.layout.marginBox2contentBox(this.containerNode, children[2]); |
| |
| if(sc && sc.resize){ |
| sc.resize(this._containerContentBox); |
| } |
| }else{ |
| // just layout the tab controller, so it can position left/right buttons etc. |
| if(this.tablist.resize){ |
| this.tablist.resize({w: dojo.contentBox(this.domNode).w}); |
| } |
| |
| // and call resize() on the selected pane just to tell it that it's been made visible |
| if(sc && sc.resize){ |
| sc.resize(); |
| } |
| } |
| }, |
| |
| destroy: function(){ |
| if(this.tablist){ |
| this.tablist.destroy(); |
| } |
| this.inherited(arguments); |
| } |
| }); |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout.TabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout.TabController"] = true; |
| dojo.provide("dijit.layout.TabController"); |
| |
| |
| |
| // Menu is used for an accessible close button, would be nice to have a lighter-weight solution |
| |
| |
| |
| |
| |
| dojo.declare("dijit.layout.TabController", |
| dijit.layout.StackController, |
| { |
| // summary: |
| // Set of tabs (the things with titles and a close button, that you click to show a tab panel). |
| // Used internally by `dijit.layout.TabContainer`. |
| // description: |
| // Lets the user select the currently shown pane in a TabContainer or StackContainer. |
| // TabController also monitors the TabContainer, and whenever a pane is |
| // added or deleted updates itself accordingly. |
| // tags: |
| // private |
| |
| templateString: "<div wairole='tablist' dojoAttachEvent='onkeypress:onkeypress'></div>", |
| |
| // tabPosition: String |
| // Defines where tabs go relative to the content. |
| // "top", "bottom", "left-h", "right-h" |
| tabPosition: "top", |
| |
| // buttonWidget: String |
| // The name of the tab widget to create to correspond to each page |
| buttonWidget: "dijit.layout._TabButton", |
| |
| _rectifyRtlTabList: function(){ |
| // summary: |
| // For left/right TabContainer when page is RTL mode, rectify the width of all tabs to be equal, otherwise the tab widths are different in IE |
| |
| if(0 >= this.tabPosition.indexOf('-h')){ return; } |
| if(!this.pane2button){ return; } |
| |
| var maxWidth = 0; |
| for(var pane in this.pane2button){ |
| var ow = this.pane2button[pane].innerDiv.scrollWidth; |
| maxWidth = Math.max(maxWidth, ow); |
| } |
| //unify the length of all the tabs |
| for(pane in this.pane2button){ |
| this.pane2button[pane].innerDiv.style.width = maxWidth + 'px'; |
| } |
| } |
| }); |
| |
| dojo.declare("dijit.layout._TabButton", |
| dijit.layout._StackButton, |
| { |
| // summary: |
| // A tab (the thing you click to select a pane). |
| // description: |
| // Contains the title of the pane, and optionally a close-button to destroy the pane. |
| // This is an internal widget and should not be instantiated directly. |
| // tags: |
| // private |
| |
| // baseClass: String |
| // The CSS class applied to the domNode. |
| baseClass: "dijitTab", |
| |
| templateString: dojo.cache("dijit.layout", "templates/_TabButton.html", "<div waiRole=\"presentation\" dojoAttachPoint=\"titleNode\" dojoAttachEvent='onclick:onClick,onmouseenter:_onMouse,onmouseleave:_onMouse'>\n <div waiRole=\"presentation\" class='dijitTabInnerDiv' dojoAttachPoint='innerDiv'>\n <div waiRole=\"presentation\" class='dijitTabContent' dojoAttachPoint='tabContent,focusNode'>\n\t <img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint='iconNode' waiRole=\"presentation\"/>\n\t <span dojoAttachPoint='containerNode' class='tabLabel'></span>\n\t <span class=\"closeButton\" dojoAttachPoint='closeNode'\n\t \t\tdojoAttachEvent='onclick: onClickCloseButton, onmouseenter: _onCloseButtonEnter, onmouseleave: _onCloseButtonLeave'>\n\t \t<img src=\"${_blankGif}\" alt=\"\" dojoAttachPoint='closeIcon' class='closeImage' waiRole=\"presentation\"/>\n\t <span dojoAttachPoint='closeText' class='closeText'>x</span>\n\t </span>\n </div>\n </div>\n</div>\n"), |
| |
| // Override _FormWidget.scrollOnFocus. |
| // Don't scroll the whole tab container into view when the button is focused. |
| scrollOnFocus: false, |
| |
| postMixInProperties: function(){ |
| // Override blank iconClass from Button to do tab height adjustment on IE6, |
| // to make sure that tabs with and w/out close icons are same height |
| if(!this.iconClass){ |
| this.iconClass = "dijitTabButtonIcon"; |
| } |
| }, |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| dojo.setSelectable(this.containerNode, false); |
| |
| // If a custom icon class has not been set for the |
| // tab icon, set its width to one pixel. This ensures |
| // that the height styling of the tab is maintained, |
| // as it is based on the height of the icon. |
| // TODO: I still think we can just set dijitTabButtonIcon to 1px in CSS <Bill> |
| if(this.iconNode.className == "dijitTabButtonIcon"){ |
| dojo.style(this.iconNode, "width", "1px"); |
| } |
| }, |
| |
| startup: function(){ |
| this.inherited(arguments); |
| var n = this.domNode; |
| |
| // Required to give IE6 a kick, as it initially hides the |
| // tabs until they are focused on. |
| setTimeout(function(){ |
| n.className = n.className; |
| }, 1); |
| }, |
| |
| _setCloseButtonAttr: function(disp){ |
| this.closeButton = disp; |
| dojo.toggleClass(this.innerDiv, "dijitClosable", disp); |
| this.closeNode.style.display = disp ? "" : "none"; |
| if(disp){ |
| var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); |
| if(this.closeNode){ |
| dojo.attr(this.closeNode,"title", _nlsResources.itemClose); |
| if (dojo.isIE<8){ |
| // IE<8 needs title set directly on image. Only set for IE since alt="" |
| // for this node and WCAG 2.0 does not allow title when alt="" |
| dojo.attr(this.closeIcon, "title", _nlsResources.itemClose); |
| } |
| } |
| // add context menu onto title button |
| var _nlsResources = dojo.i18n.getLocalization("dijit", "common"); |
| this._closeMenu = new dijit.Menu({ |
| id: this.id+"_Menu", |
| targetNodeIds: [this.domNode] |
| }); |
| |
| this._closeMenu.addChild(new dijit.MenuItem({ |
| label: _nlsResources.itemClose, |
| onClick: dojo.hitch(this, "onClickCloseButton") |
| })); |
| }else{ |
| if(this._closeMenu){ |
| this._closeMenu.destroyRecursive(); |
| delete this._closeMenu; |
| } |
| } |
| }, |
| |
| destroy: function(){ |
| if(this._closeMenu){ |
| this._closeMenu.destroyRecursive(); |
| delete this._closeMenu; |
| } |
| this.inherited(arguments); |
| }, |
| |
| _onCloseButtonEnter: function(){ |
| // summary: |
| // Handler when mouse is moved over the close icon (the X) |
| dojo.addClass(this.closeNode, "closeButton-hover"); |
| }, |
| |
| _onCloseButtonLeave: function(){ |
| // summary: |
| // Handler when mouse is moved off the close icon (the X) |
| dojo.removeClass(this.closeNode, "closeButton-hover"); |
| } |
| }); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout.ScrollingTabController"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout.ScrollingTabController"] = true; |
| dojo.provide("dijit.layout.ScrollingTabController"); |
| |
| |
| |
| |
| dojo.declare("dijit.layout.ScrollingTabController", |
| dijit.layout.TabController, |
| { |
| // summary: |
| // Set of tabs with left/right arrow keys and a menu to switch between tabs not |
| // all fitting on a single row. |
| // Works only for horizontal tabs (either above or below the content, not to the left |
| // or right). |
| // tags: |
| // private |
| |
| templateString: dojo.cache("dijit.layout", "templates/ScrollingTabController.html", "<div class=\"dijitTabListContainer-${tabPosition}\" style=\"visibility:hidden\">\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\" buttonType=\"menuBtn\" buttonClass=\"tabStripMenuButton\"\n\t\t\ttabPosition=\"${tabPosition}\" dojoAttachPoint=\"_menuBtn\" showLabel=false>↓</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\" buttonType=\"leftBtn\" buttonClass=\"tabStripSlideButtonLeft\"\n\t\t\ttabPosition=\"${tabPosition}\" dojoAttachPoint=\"_leftBtn\" dojoAttachEvent=\"onClick: doSlideLeft\" showLabel=false>←</div>\n\t<div dojoType=\"dijit.layout._ScrollingTabControllerButton\" buttonType=\"rightBtn\" buttonClass=\"tabStripSlideButtonRight\"\n\t\t\ttabPosition=\"${tabPosition}\" dojoAttachPoint=\"_rightBtn\" dojoAttachEvent=\"onClick: doSlideRight\" showLabel=false>→</div>\n\t<div class='dijitTabListWrapper' dojoAttachPoint='tablistWrapper'>\n\t\t<div wairole='tablist' dojoAttachEvent='onkeypress:onkeypress'\n\t\t\t\tdojoAttachPoint='containerNode' class='nowrapTabStrip'>\n\t\t</div>\n\t</div>\n</div>\n"), |
| |
| // useMenu:[const] Boolean |
| // True if a menu should be used to select tabs when they are too |
| // wide to fit the TabContainer, false otherwise. |
| useMenu: true, |
| |
| // useSlider: [const] Boolean |
| // True if a slider should be used to select tabs when they are too |
| // wide to fit the TabContainer, false otherwise. |
| useSlider: true, |
| |
| // tabStripClass: String |
| // The css class to apply to the tab strip, if it is visible. |
| tabStripClass: "", |
| |
| widgetsInTemplate: true, |
| |
| // _minScroll: Number |
| // The distance in pixels from the edge of the tab strip which, |
| // if a scroll animation is less than, forces the scroll to |
| // go all the way to the left/right. |
| _minScroll: 5, |
| |
| attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { |
| "class": "containerNode" |
| }), |
| |
| postCreate: function(){ |
| this.inherited(arguments); |
| var n = this.domNode; |
| |
| this.scrollNode = this.tablistWrapper; |
| this._initButtons(); |
| |
| if(!this.tabStripClass){ |
| this.tabStripClass = "dijitTabContainer" + |
| this.tabPosition.charAt(0).toUpperCase() + |
| this.tabPosition.substr(1).replace(/-.*/, "") + |
| "None"; |
| dojo.addClass(n, "tabStrip-disabled") |
| } |
| |
| dojo.addClass(this.tablistWrapper, this.tabStripClass); |
| }, |
| |
| onStartup: function(){ |
| this.inherited(arguments); |
| |
| // Do not show the TabController until the related |
| // StackController has added it's children. This gives |
| // a less visually jumpy instantiation. |
| dojo.style(this.domNode, "visibility", "visible"); |
| this._postStartup = true; |
| }, |
| |
| onAddChild: function(page, insertIndex){ |
| this.inherited(arguments); |
| var menuItem; |
| if(this.useMenu){ |
| var containerId = this.containerId; |
| menuItem = new dijit.MenuItem({ |
| label: page.title, |
| onClick: dojo.hitch(this, function(){ |
| var container = dijit.byId(containerId); |
| container.selectChild(page); |
| }) |
| }); |
| this._menuChildren[page.id] = menuItem; |
| this._menu.addChild(menuItem, insertIndex); |
| } |
| |
| // update the menuItem label when the button label is updated |
| this.pane2handles[page.id].push( |
| this.connect(this.pane2button[page.id], "attr", function(name, value){ |
| if(this._postStartup){ |
| if(arguments.length == 2 && name == "label"){ |
| if(menuItem){ |
| menuItem.attr(name, value); |
| } |
| |
| // The changed label will have changed the width of the |
| // buttons, so do a resize |
| if(this._dim){ |
| this.resize(this._dim); |
| } |
| } |
| } |
| }) |
| ); |
| |
| // Increment the width of the wrapper when a tab is added |
| // This makes sure that the buttons never wrap. |
| // The value 200 is chosen as it should be bigger than most |
| // Tab button widths. |
| dojo.style(this.containerNode, "width", |
| (dojo.style(this.containerNode, "width") + 200) + "px"); |
| }, |
| |
| onRemoveChild: function(page, insertIndex){ |
| // null out _selectedTab because we are about to delete that dom node |
| var button = this.pane2button[page.id]; |
| if(this._selectedTab === button.domNode){ |
| this._selectedTab = null; |
| } |
| |
| // delete menu entry corresponding to pane that was removed from TabContainer |
| if(this.useMenu && page && page.id && this._menuChildren[page.id]){ |
| this._menu.removeChild(this._menuChildren[page.id]); |
| this._menuChildren[page.id].destroy(); |
| delete this._menuChildren[page.id]; |
| } |
| |
| this.inherited(arguments); |
| }, |
| |
| _initButtons: function(){ |
| // summary: |
| // Creates the buttons used to scroll to view tabs that |
| // may not be visible if the TabContainer is too narrow. |
| this._menuChildren = {}; |
| |
| // Make a list of the buttons to display when the tab labels become |
| // wider than the TabContainer, and hide the other buttons. |
| // Also gets the total width of the displayed buttons. |
| this._btnWidth = 0; |
| this._buttons = dojo.query("> .tabStripButton", this.domNode).filter(function(btn){ |
| if((this.useMenu && btn == this._menuBtn.domNode) || |
| (this.useSlider && (btn == this._rightBtn.domNode || btn == this._leftBtn.domNode))){ |
| this._btnWidth += dojo.marginBox(btn).w; |
| return true; |
| }else{ |
| dojo.style(btn, "display", "none"); |
| return false; |
| } |
| }, this); |
| |
| if(this.useMenu){ |
| // Create the menu that is used to select tabs. |
| this._menu = new dijit.Menu({ |
| id: this.id + "_menu", |
| targetNodeIds: [this._menuBtn.domNode], |
| leftClickToOpen: true, |
| refocus: false // selecting a menu item sets focus to a TabButton |
| }); |
| this._supportingWidgets.push(this._menu); |
| } |
| }, |
| |
| _getTabsWidth: function(){ |
| var children = this.getChildren(); |
| if(children.length){ |
| var leftTab = children[this.isLeftToRight() ? 0 : children.length - 1].domNode, |
| rightTab = children[this.isLeftToRight() ? children.length - 1 : 0].domNode; |
| return rightTab.offsetLeft + dojo.style(rightTab, "width") - leftTab.offsetLeft; |
| }else{ |
| return 0; |
| } |
| }, |
| |
| _enableBtn: function(width){ |
| // summary: |
| // Determines if the tabs are wider than the width of the TabContainer, and |
| // thus that we need to display left/right/menu navigation buttons. |
| var tabsWidth = this._getTabsWidth(); |
| width = width || dojo.style(this.scrollNode, "width"); |
| return tabsWidth > 0 && width < tabsWidth; |
| }, |
| |
| resize: function(dim){ |
| // summary: |
| // Hides or displays the buttons used to scroll the tab list and launch the menu |
| // that selects tabs. |
| |
| if(this.domNode.offsetWidth == 0){ |
| return; |
| } |
| |
| // Save the dimensions to be used when a child is renamed. |
| this._dim = dim; |
| |
| // Set my height to be my natural height (tall enough for one row of tab labels), |
| // and my content-box width based on margin-box width specified in dim parameter. |
| // But first reset scrollNode.height in case it was set by layoutChildren() call |
| // in a previous run of this method. |
| this.scrollNode.style.height = "auto"; |
| this._contentBox = dijit.layout.marginBox2contentBox(this.domNode, {h: 0, w: dim.w}); |
| this._contentBox.h = this.scrollNode.offsetHeight; |
| dojo.contentBox(this.domNode, this._contentBox); |
| |
| // Show/hide the left/right/menu navigation buttons depending on whether or not they |
| // are needed. |
| var enable = this._enableBtn(this._contentBox.w); |
| this._buttons.style("display", enable ? "" : "none"); |
| |
| // Position and size the navigation buttons and the tablist |
| this._leftBtn.layoutAlign = "left"; |
| this._rightBtn.layoutAlign = "right"; |
| this._menuBtn.layoutAlign = this.isLeftToRight() ? "right" : "left"; |
| dijit.layout.layoutChildren(this.domNode, this._contentBox, |
| [this._menuBtn, this._leftBtn, this._rightBtn, {domNode: this.scrollNode, layoutAlign: "client"}]); |
| |
| // set proper scroll so that selected tab is visible |
| if(this._selectedTab){ |
| var w = this.scrollNode, |
| sl = this._convertToScrollLeft(this._getScrollForSelectedTab()); |
| w.scrollLeft = sl; |
| } |
| |
| // Enable/disabled left right buttons depending on whether or not user can scroll to left or right |
| this._setButtonClass(this._getScroll()); |
| }, |
| |
| _getScroll: function(){ |
| // summary: |
| // Returns the current scroll of the tabs where 0 means |
| // "scrolled all the way to the left" and some positive number, based on # |
| // of pixels of possible scroll (ex: 1000) means "scrolled all the way to the right" |
| var sl = (this.isLeftToRight() || dojo.isIE < 8 || dojo.isQuirks || dojo.isWebKit) ? this.scrollNode.scrollLeft : |
| dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width") |
| + (dojo.isIE == 8 ? -1 : 1) * this.scrollNode.scrollLeft; |
| return sl; |
| }, |
| |
| _convertToScrollLeft: function(val){ |
| // summary: |
| // Given a scroll value where 0 means "scrolled all the way to the left" |
| // and some positive number, based on # of pixels of possible scroll (ex: 1000) |
| // means "scrolled all the way to the right", return value to set this.scrollNode.scrollLeft |
| // to achieve that scroll. |
| // |
| // This method is to adjust for RTL funniness in various browsers and versions. |
| if(this.isLeftToRight() || dojo.isIE < 8 || dojo.isQuirks || dojo.isWebKit){ |
| return val; |
| }else{ |
| var maxScroll = dojo.style(this.containerNode, "width") - dojo.style(this.scrollNode, "width"); |
| return (dojo.isIE == 8 ? -1 : 1) * (val - maxScroll); |
| } |
| }, |
| |
| onSelectChild: function(/*dijit._Widget*/ page){ |
| // summary: |
| // Smoothly scrolls to a tab when it is selected. |
| |
| var tab = this.pane2button[page.id]; |
| if(!tab || !page){return;} |
| |
| var node = tab.domNode; |
| if(node != this._selectedTab){ |
| this._selectedTab = node; |
| |
| var sl = this._getScroll(); |
| |
| if(sl > node.offsetLeft || |
| sl + dojo.style(this.scrollNode, "width") < |
| node.offsetLeft + dojo.style(node, "width")){ |
| this.createSmoothScroll().play(); |
| } |
| } |
| |
| this.inherited(arguments); |
| }, |
| |
| _getScrollBounds: function(){ |
| // summary: |
| // Returns the minimum and maximum scroll setting to show the leftmost and rightmost |
| // tabs (respectively) |
| var children = this.getChildren(), |
| scrollNodeWidth = dojo.style(this.scrollNode, "width"), // about 500px |
| containerWidth = dojo.style(this.containerNode, "width"), // 50,000px |
| maxPossibleScroll = containerWidth - scrollNodeWidth, // scrolling until right edge of containerNode visible |
| tabsWidth = this._getTabsWidth(); |
| |
| if(children.length && tabsWidth > scrollNodeWidth){ |
| // Scrolling should happen |
| return { |
| min: this.isLeftToRight() ? 0 : children[children.length-1].domNode.offsetLeft, |
| max: this.isLeftToRight() ? |
| (children[children.length-1].domNode.offsetLeft + dojo.style(children[children.length-1].domNode, "width")) - scrollNodeWidth : |
| maxPossibleScroll |
| }; |
| }else{ |
| // No scrolling needed, all tabs visible, we stay either scrolled to far left or far right (depending on dir) |
| var onlyScrollPosition = this.isLeftToRight() ? 0 : maxPossibleScroll; |
| return { |
| min: onlyScrollPosition, |
| max: onlyScrollPosition |
| }; |
| } |
| }, |
| |
| _getScrollForSelectedTab: function(){ |
| // summary: |
| // Returns the scroll value setting so that the selected tab |
| // will appear in the center |
| var w = this.scrollNode, |
| n = this._selectedTab, |
| scrollNodeWidth = dojo.style(this.scrollNode, "width"), |
| scrollBounds = this._getScrollBounds(); |
| |
| // TODO: scroll minimal amount (to either right or left) so that |
| // selected tab is fully visible, and just return if it's already visible? |
| var pos = (n.offsetLeft + dojo.style(n, "width")/2) - scrollNodeWidth/2; |
| pos = Math.min(Math.max(pos, scrollBounds.min), scrollBounds.max); |
| |
| // TODO: |
| // If scrolling close to the left side or right side, scroll |
| // all the way to the left or right. See this._minScroll. |
| // (But need to make sure that doesn't scroll the tab out of view...) |
| return pos; |
| }, |
| |
| createSmoothScroll : function(x){ |
| // summary: |
| // Creates a dojo._Animation object that smoothly scrolls the tab list |
| // either to a fixed horizontal pixel value, or to the selected tab. |
| // description: |
| // If an number argument is passed to the function, that horizontal |
| // pixel position is scrolled to. Otherwise the currently selected |
| // tab is scrolled to. |
| // x: Integer? |
| // An optional pixel value to scroll to, indicating distance from left. |
| |
| // Calculate position to scroll to |
| if(arguments.length > 0){ |
| // position specified by caller, just make sure it's within bounds |
| var scrollBounds = this._getScrollBounds(); |
| x = Math.min(Math.max(x, scrollBounds.min), scrollBounds.max); |
| }else{ |
| // scroll to center the current tab |
| x = this._getScrollForSelectedTab(); |
| } |
| |
| if(this._anim && this._anim.status() == "playing"){ |
| this._anim.stop(); |
| } |
| |
| var self = this, |
| w = this.scrollNode, |
| anim = new dojo._Animation({ |
| beforeBegin: function(){ |
| if(this.curve){ delete this.curve; } |
| var oldS = w.scrollLeft, |
| newS = self._convertToScrollLeft(x); |
| anim.curve = new dojo._Line(oldS, newS); |
| }, |
| onAnimate: function(val){ |
| w.scrollLeft = val; |
| } |
| }); |
| this._anim = anim; |
| |
| // Disable/enable left/right buttons according to new scroll position |
| this._setButtonClass(x); |
| |
| return anim; // dojo._Animation |
| }, |
| |
| _getBtnNode: function(e){ |
| // summary: |
| // Gets a button DOM node from a mouse click event. |
| // e: |
| // The mouse click event. |
| var n = e.target; |
| while(n && !dojo.hasClass(n, "tabStripButton")){ |
| n = n.parentNode; |
| } |
| return n; |
| }, |
| |
| doSlideRight: function(e){ |
| // summary: |
| // Scrolls the menu to the right. |
| // e: |
| // The mouse click event. |
| this.doSlide(1, this._getBtnNode(e)); |
| }, |
| |
| doSlideLeft: function(e){ |
| // summary: |
| // Scrolls the menu to the left. |
| // e: |
| // The mouse click event. |
| this.doSlide(-1,this._getBtnNode(e)); |
| }, |
| |
| doSlide: function(direction, node){ |
| // summary: |
| // Scrolls the tab list to the left or right by 75% of the widget width. |
| // direction: |
| // If the direction is 1, the widget scrolls to the right, if it is |
| // -1, it scrolls to the left. |
| |
| if(node && dojo.hasClass(node, "dijitTabBtnDisabled")){return;} |
| |
| var sWidth = dojo.style(this.scrollNode, "width"); |
| var d = (sWidth * 0.75) * direction; |
| |
| var to = this._getScroll() + d; |
| |
| this._setButtonClass(to); |
| |
| this.createSmoothScroll(to).play(); |
| }, |
| |
| _setButtonClass: function(scroll){ |
| // summary: |
| // Adds or removes a class to the left and right scroll buttons |
| // to indicate whether each one is enabled/disabled. |
| // description: |
| // If the tabs are scrolled all the way to the left, the class |
| // 'dijitTabBtnDisabled' is added to the left button. |
| // If the tabs are scrolled all the way to the right, the class |
| // 'dijitTabBtnDisabled' is added to the right button. |
| // scroll: Integer |
| // amount of horizontal scroll |
| |
| var cls = "dijitTabBtnDisabled", |
| scrollBounds = this._getScrollBounds(); |
| dojo.toggleClass(this._leftBtn.domNode, cls, scroll <= scrollBounds.min); |
| dojo.toggleClass(this._rightBtn.domNode, cls, scroll >= scrollBounds.max); |
| } |
| }); |
| |
| dojo.declare("dijit.layout._ScrollingTabControllerButton", |
| dijit.form.Button, |
| { |
| baseClass: "dijitTab", |
| |
| buttonType: "", |
| |
| buttonClass: "", |
| |
| tabPosition: "top", |
| |
| templateString: dojo.cache("dijit.layout", "templates/_ScrollingTabControllerButton.html", "<div id=\"${id}-${buttonType}\" class=\"tabStripButton dijitTab ${buttonClass} tabStripButton-${tabPosition}\"\n\t\tdojoAttachEvent=\"onclick:_onButtonClick,onmouseenter:_onMouse,onmouseleave:_onMouse,onmousedown:_onMouse\">\n\t<div role=\"presentation\" wairole=\"presentation\" class=\"dijitTabInnerDiv\" dojoattachpoint=\"innerDiv,focusNode\">\n\t\t<div role=\"presentation\" wairole=\"presentation\" class=\"dijitTabContent dijitButtonContents\" dojoattachpoint=\"tabContent\">\n\t\t\t<img src=\"${_blankGif}\"/>\n\t\t\t<span dojoAttachPoint=\"containerNode,titleNode\" class=\"dijitButtonText\"></span>\n\t\t</div>\n\t</div>\n</div>\n"), |
| |
| // Override inherited tabIndex: 0 from dijit.form.Button, because user shouldn't be |
| // able to tab to the left/right/menu buttons |
| tabIndex: "" |
| } |
| ); |
| |
| } |
| |
| if(!dojo._hasResource["dijit.layout.TabContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.layout.TabContainer"] = true; |
| dojo.provide("dijit.layout.TabContainer"); |
| |
| |
| |
| |
| |
| dojo.declare("dijit.layout.TabContainer", |
| dijit.layout._TabContainerBase, |
| { |
| // summary: |
| // A Container with tabs to select each child (only one of which is displayed at a time). |
| // description: |
| // A TabContainer is a container that has multiple panes, but shows only |
| // one pane at a time. There are a set of tabs corresponding to each pane, |
| // where each tab has the name (aka title) of the pane, and optionally a close button. |
| |
| // useMenu: [const] Boolean |
| // True if a menu should be used to select tabs when they are too |
| // wide to fit the TabContainer, false otherwise. |
| useMenu: true, |
| |
| // useSlider: [const] Boolean |
| // True if a slider should be used to select tabs when they are too |
| // wide to fit the TabContainer, false otherwise. |
| useSlider: true, |
| |
| // controllerWidget: String |
| // An optional parameter to override the widget used to display the tab labels |
| controllerWidget: "", |
| |
| _makeController: function(/*DomNode*/ srcNode){ |
| // summary: |
| // Instantiate tablist controller widget and return reference to it. |
| // Callback from _TabContainerBase.postCreate(). |
| // tags: |
| // protected extension |
| |
| var cls = this.baseClass + "-tabs" + (this.doLayout ? "" : " dijitTabNoLayout"), |
| TabController = dojo.getObject(this.controllerWidget); |
| |
| return new TabController({ |
| id: this.id + "_tablist", |
| tabPosition: this.tabPosition, |
| doLayout: this.doLayout, |
| containerId: this.id, |
| "class": cls, |
| nested: this.nested, |
| useMenu: this.useMenu, |
| useSlider: this.useSlider, |
| tabStripClass: this.tabStrip ? this.baseClass + (this.tabStrip ? "":"No") + "Strip": null |
| }, srcNode); |
| }, |
| |
| postMixInProperties: function(){ |
| this.inherited(arguments); |
| |
| // Scrolling controller only works for horizontal non-nested tabs |
| if(!this.controllerWidget){ |
| this.controllerWidget = (this.tabPosition == "top" || this.tabPosition == "bottom") && !this.nested ? |
| "dijit.layout.ScrollingTabController" : "dijit.layout.TabController"; |
| } |
| } |
| }); |
| |
| |
| } |
| |
| if(!dojo._hasResource["dijit.dijit-all"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
| dojo._hasResource["dijit.dijit-all"] = true; |
| console.warn("dijit-all may include much more code than your application actually requires. We strongly recommend that you investigate a custom build or the web build tool"); |
| dojo.provide("dijit.dijit-all"); |
| |
| /*===== |
| dijit["dijit-all"] = { |
| // summary: |
| // A rollup that includes every dijit. You probably don't need this. |
| }; |
| =====*/ |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| // Form widgets |
| |
| |
| // Button widgets |
| |
| |
| |
| |
| |
| |
| |
| // Textbox widgets |
| |
| |
| |
| |
| |
| |
| |
| // Select widgets |
| |
| |
| |
| |
| // Slider widgets |
| |
| |
| |
| |
| |
| |
| |
| // Textarea widgets |
| |
| |
| |
| // Layout widgets |
| |
| |
| |
| //deprecated |
| |
| //deprecated |
| |
| |
| |
| } |
| |
| |
| dojo.i18n._preloadLocalizations("dijit.nls.dijit-all", ["ROOT","ar","ca","cs","da","de","de-de","el","en","en-gb","en-us","es","es-es","fi","fi-fi","fr","fr-fr","he","he-il","hu","it","it-it","ja","ja-jp","ko","ko-kr","nb","nl","nl-nl","pl","pt","pt-br","pt-pt","ru","sk","sl","sv","th","tr","xx","zh","zh-cn","zh-tw"]); |