Select.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. require({cache:{
  2. 'url:dijit/form/templates/Select.html':"<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tdata-dojo-attach-point=\"_buttonNode,tableNode,focusNode\" cellspacing='0' cellpadding='0'\n\trole=\"combobox\" aria-haspopup=\"true\"\n\t><tbody role=\"presentation\"><tr role=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonContents dijitButtonNode\" role=\"presentation\"\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\" data-dojo-attach-point=\"containerNode,_popupStateNode\"></span\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} data-dojo-attach-point=\"valueNode\" value=\"${value}\" aria-hidden=\"true\"\n\t\t/></td><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\t\tdata-dojo-attach-point=\"titleNode\" role=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" role=\"presentation\">&#9660;</div\n\t\t></td\n\t></tr></tbody\n></table>\n"}});
  3. define("dijit/form/Select", [
  4. "dojo/_base/array", // array.forEach
  5. "dojo/_base/declare", // declare
  6. "dojo/dom-attr", // domAttr.set
  7. "dojo/dom-class", // domClass.add domClass.remove domClass.toggle
  8. "dojo/dom-construct", // domConstruct.create
  9. "dojo/dom-geometry", // domGeometry.setMarginBox
  10. "dojo/_base/event", // event.stop
  11. "dojo/i18n", // i18n.getLocalization
  12. "dojo/_base/lang", // lang.hitch
  13. "./_FormSelectWidget",
  14. "../_HasDropDown",
  15. "../Menu",
  16. "../MenuItem",
  17. "../MenuSeparator",
  18. "../Tooltip",
  19. "dojo/text!./templates/Select.html",
  20. "dojo/i18n!./nls/validate"
  21. ], function(array, declare, domAttr, domClass, domConstruct, domGeometry, event, i18n, lang,
  22. _FormSelectWidget, _HasDropDown, Menu, MenuItem, MenuSeparator, Tooltip, template){
  23. /*=====
  24. var _FormSelectWidget = dijit.form._FormSelectWidget;
  25. var _HasDropDown = dijit._HasDropDown;
  26. var _FormSelectWidget = dijit._FormSelectWidget;
  27. var Menu = dijit.Menu;
  28. var MenuItem = dijit.MenuItem;
  29. var MenuSeparator = dijit.MenuSeparator;
  30. var Tooltip = dijit.Tooltip;
  31. =====*/
  32. // module:
  33. // dijit/form/Select
  34. // summary:
  35. // This is a "styleable" select box - it is basically a DropDownButton which
  36. // can take a <select> as its input.
  37. var _SelectMenu = declare("dijit.form._SelectMenu", Menu, {
  38. // summary:
  39. // An internally-used menu for dropdown that allows us a vertical scrollbar
  40. buildRendering: function(){
  41. // summary:
  42. // Stub in our own changes, so that our domNode is not a table
  43. // otherwise, we won't respond correctly to heights/overflows
  44. this.inherited(arguments);
  45. var o = (this.menuTableNode = this.domNode);
  46. var n = (this.domNode = domConstruct.create("div", {style: {overflowX: "hidden", overflowY: "scroll"}}));
  47. if(o.parentNode){
  48. o.parentNode.replaceChild(n, o);
  49. }
  50. domClass.remove(o, "dijitMenuTable");
  51. n.className = o.className + " dijitSelectMenu";
  52. o.className = "dijitReset dijitMenuTable";
  53. o.setAttribute("role", "listbox");
  54. n.setAttribute("role", "presentation");
  55. n.appendChild(o);
  56. },
  57. postCreate: function(){
  58. // summary:
  59. // stop mousemove from selecting text on IE to be consistent with other browsers
  60. this.inherited(arguments);
  61. this.connect(this.domNode, "onmousemove", event.stop);
  62. },
  63. resize: function(/*Object*/ mb){
  64. // summary:
  65. // Overridden so that we are able to handle resizing our
  66. // internal widget. Note that this is not a "full" resize
  67. // implementation - it only works correctly if you pass it a
  68. // marginBox.
  69. //
  70. // mb: Object
  71. // The margin box to set this dropdown to.
  72. if(mb){
  73. domGeometry.setMarginBox(this.domNode, mb);
  74. if("w" in mb){
  75. // We've explicitly set the wrapper <div>'s width, so set <table> width to match.
  76. // 100% is safer than a pixel value because there may be a scroll bar with
  77. // browser/OS specific width.
  78. this.menuTableNode.style.width = "100%";
  79. }
  80. }
  81. }
  82. });
  83. var Select = declare("dijit.form.Select", [_FormSelectWidget, _HasDropDown], {
  84. // summary:
  85. // This is a "styleable" select box - it is basically a DropDownButton which
  86. // can take a <select> as its input.
  87. baseClass: "dijitSelect",
  88. templateString: template,
  89. // required: Boolean
  90. // Can be true or false, default is false.
  91. required: false,
  92. // state: [readonly] String
  93. // "Incomplete" if this select is required but unset (i.e. blank value), "" otherwise
  94. state: "",
  95. // message: String
  96. // Currently displayed error/prompt message
  97. message: "",
  98. // tooltipPosition: String[]
  99. // See description of dijit.Tooltip.defaultPosition for details on this parameter.
  100. tooltipPosition: [],
  101. // emptyLabel: string
  102. // What to display in an "empty" dropdown
  103. emptyLabel: "&#160;", // &nbsp;
  104. // _isLoaded: Boolean
  105. // Whether or not we have been loaded
  106. _isLoaded: false,
  107. // _childrenLoaded: Boolean
  108. // Whether or not our children have been loaded
  109. _childrenLoaded: false,
  110. _fillContent: function(){
  111. // summary:
  112. // Set the value to be the first, or the selected index
  113. this.inherited(arguments);
  114. // set value from selected option
  115. if(this.options.length && !this.value && this.srcNodeRef){
  116. var si = this.srcNodeRef.selectedIndex || 0; // || 0 needed for when srcNodeRef is not a SELECT
  117. this.value = this.options[si >= 0 ? si : 0].value;
  118. }
  119. // Create the dropDown widget
  120. this.dropDown = new _SelectMenu({id: this.id + "_menu"});
  121. domClass.add(this.dropDown.domNode, this.baseClass + "Menu");
  122. },
  123. _getMenuItemForOption: function(/*dijit.form.__SelectOption*/ option){
  124. // summary:
  125. // For the given option, return the menu item that should be
  126. // used to display it. This can be overridden as needed
  127. if(!option.value && !option.label){
  128. // We are a separator (no label set for it)
  129. return new MenuSeparator();
  130. }else{
  131. // Just a regular menu option
  132. var click = lang.hitch(this, "_setValueAttr", option);
  133. var item = new MenuItem({
  134. option: option,
  135. label: option.label || this.emptyLabel,
  136. onClick: click,
  137. disabled: option.disabled || false
  138. });
  139. item.focusNode.setAttribute("role", "listitem");
  140. return item;
  141. }
  142. },
  143. _addOptionItem: function(/*dijit.form.__SelectOption*/ option){
  144. // summary:
  145. // For the given option, add an option to our dropdown.
  146. // If the option doesn't have a value, then a separator is added
  147. // in that place.
  148. if(this.dropDown){
  149. this.dropDown.addChild(this._getMenuItemForOption(option));
  150. }
  151. },
  152. _getChildren: function(){
  153. if(!this.dropDown){
  154. return [];
  155. }
  156. return this.dropDown.getChildren();
  157. },
  158. _loadChildren: function(/*Boolean*/ loadMenuItems){
  159. // summary:
  160. // Resets the menu and the length attribute of the button - and
  161. // ensures that the label is appropriately set.
  162. // loadMenuItems: Boolean
  163. // actually loads the child menu items - we only do this when we are
  164. // populating for showing the dropdown.
  165. if(loadMenuItems === true){
  166. // this.inherited destroys this.dropDown's child widgets (MenuItems).
  167. // Avoid this.dropDown (Menu widget) having a pointer to a destroyed widget (which will cause
  168. // issues later in _setSelected). (see #10296)
  169. if(this.dropDown){
  170. delete this.dropDown.focusedChild;
  171. }
  172. if(this.options.length){
  173. this.inherited(arguments);
  174. }else{
  175. // Drop down menu is blank but add one blank entry just so something appears on the screen
  176. // to let users know that they are no choices (mimicing native select behavior)
  177. array.forEach(this._getChildren(), function(child){ child.destroyRecursive(); });
  178. var item = new MenuItem({label: "&#160;"});
  179. this.dropDown.addChild(item);
  180. }
  181. }else{
  182. this._updateSelection();
  183. }
  184. this._isLoaded = false;
  185. this._childrenLoaded = true;
  186. if(!this._loadingStore){
  187. // Don't call this if we are loading - since we will handle it later
  188. this._setValueAttr(this.value);
  189. }
  190. },
  191. _setValueAttr: function(value){
  192. this.inherited(arguments);
  193. domAttr.set(this.valueNode, "value", this.get("value"));
  194. this.validate(this.focused); // to update this.state
  195. },
  196. _setDisabledAttr: function(/*Boolean*/ value){
  197. this.inherited(arguments);
  198. this.validate(this.focused); // to update this.state
  199. },
  200. _setRequiredAttr: function(/*Boolean*/ value){
  201. this._set("required", value);
  202. this.focusNode.setAttribute("aria-required", value);
  203. this.validate(this.focused); // to update this.state
  204. },
  205. _setDisplay: function(/*String*/ newDisplay){
  206. // summary:
  207. // sets the display for the given value (or values)
  208. var lbl = newDisplay || this.emptyLabel;
  209. this.containerNode.innerHTML = '<span class="dijitReset dijitInline ' + this.baseClass + 'Label">' + lbl + '</span>';
  210. this.focusNode.setAttribute("aria-valuetext", lbl);
  211. },
  212. validate: function(/*Boolean*/ isFocused){
  213. // summary:
  214. // Called by oninit, onblur, and onkeypress, and whenever required/disabled state changes
  215. // description:
  216. // Show missing or invalid messages if appropriate, and highlight textbox field.
  217. // Used when a select is initially set to no value and the user is required to
  218. // set the value.
  219. var isValid = this.disabled || this.isValid(isFocused);
  220. this._set("state", isValid ? "" : "Incomplete");
  221. this.focusNode.setAttribute("aria-invalid", isValid ? "false" : "true");
  222. var message = isValid ? "" : this._missingMsg;
  223. if(message && this.focused && this._hasBeenBlurred){
  224. Tooltip.show(message, this.domNode, this.tooltipPosition, !this.isLeftToRight());
  225. }else{
  226. Tooltip.hide(this.domNode);
  227. }
  228. this._set("message", message);
  229. return isValid;
  230. },
  231. isValid: function(/*Boolean*/ /*===== isFocused =====*/){
  232. // summary:
  233. // Whether or not this is a valid value. The only way a Select
  234. // can be invalid is when it's required but nothing is selected.
  235. return (!this.required || this.value === 0 || !(/^\s*$/.test(this.value || ""))); // handle value is null or undefined
  236. },
  237. reset: function(){
  238. // summary:
  239. // Overridden so that the state will be cleared.
  240. this.inherited(arguments);
  241. Tooltip.hide(this.domNode);
  242. this.validate(this.focused); // to update this.state
  243. },
  244. postMixInProperties: function(){
  245. // summary:
  246. // set the missing message
  247. this.inherited(arguments);
  248. this._missingMsg = i18n.getLocalization("dijit.form", "validate",
  249. this.lang).missingMessage;
  250. },
  251. postCreate: function(){
  252. // summary:
  253. // stop mousemove from selecting text on IE to be consistent with other browsers
  254. this.inherited(arguments);
  255. this.connect(this.domNode, "onmousemove", event.stop);
  256. },
  257. _setStyleAttr: function(/*String||Object*/ value){
  258. this.inherited(arguments);
  259. domClass.toggle(this.domNode, this.baseClass + "FixedWidth", !!this.domNode.style.width);
  260. },
  261. isLoaded: function(){
  262. return this._isLoaded;
  263. },
  264. loadDropDown: function(/*Function*/ loadCallback){
  265. // summary:
  266. // populates the menu
  267. this._loadChildren(true);
  268. this._isLoaded = true;
  269. loadCallback();
  270. },
  271. closeDropDown: function(){
  272. // overriding _HasDropDown.closeDropDown()
  273. this.inherited(arguments);
  274. if(this.dropDown && this.dropDown.menuTableNode){
  275. // Erase possible width: 100% setting from _SelectMenu.resize().
  276. // Leaving it would interfere with the next openDropDown() call, which
  277. // queries the natural size of the drop down.
  278. this.dropDown.menuTableNode.style.width = "";
  279. }
  280. },
  281. uninitialize: function(preserveDom){
  282. if(this.dropDown && !this.dropDown._destroyed){
  283. this.dropDown.destroyRecursive(preserveDom);
  284. delete this.dropDown;
  285. }
  286. this.inherited(arguments);
  287. },
  288. _onFocus: function(){
  289. this.validate(true); // show tooltip if second focus of required tooltip, but no selection
  290. this.inherited(arguments);
  291. },
  292. _onBlur: function(){
  293. Tooltip.hide(this.domNode);
  294. this.inherited(arguments);
  295. }
  296. });
  297. Select._Menu = _SelectMenu; // for monkey patching
  298. return Select;
  299. });