InlineEditBox.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. if (!dojo._hasResource["dijit.InlineEditBox"]) { // _hasResource checks added
  2. // by build. Do not use
  3. // _hasResource directly in
  4. // your code.
  5. dojo._hasResource["dijit.InlineEditBox"] = true;
  6. dojo.provide("dijit.InlineEditBox");
  7. dojo.require("dojo.i18n");
  8. dojo.require("dijit._Widget");
  9. dojo.require("dijit._Container");
  10. dojo.require("dijit.form.Button");
  11. dojo.require("dijit.form.TextBox");
  12. dojo.requireLocalization("dijit", "common", null,
  13. "ko,zh,ja,zh-tw,ru,it,hu,fr,pt,ROOT,pl,es,de,cs");
  14. dojo.declare("dijit.InlineEditBox", dijit._Widget, {
  15. // summary: An element with in-line edit capabilitites
  16. //
  17. // description:
  18. // Behavior for an existing node (<p>, <div>, <span>, etc.) so that
  19. // when you click it, an editor shows up in place of the original
  20. // text. Optionally, Save and Cancel button are displayed below the edit
  21. // widget.
  22. // When Save is clicked, the text is pulled from the edit
  23. // widget and redisplayed and the edit widget is again hidden.
  24. // By default a plain Textarea widget is used as the editor (or for
  25. // inline values a TextBox), but you can specify an editor such as
  26. // dijit.Editor (for editing HTML) or a Slider (for adjusting a number).
  27. // An edit widget must support the following API to be used:
  28. // String getDisplayedValue() OR String getValue()
  29. // void setDisplayedValue(String) OR void setValue(String)
  30. // void focus()
  31. //
  32. // editing: Boolean
  33. // Is the node currently in edit mode?
  34. editing : false,
  35. // autoSave: Boolean
  36. // Changing the value automatically saves it; don't have to push save
  37. // button
  38. // (and save button isn't even displayed)
  39. autoSave : true,
  40. // buttonSave: String
  41. // Save button label
  42. buttonSave : "",
  43. // buttonCancel: String
  44. // Cancel button label
  45. buttonCancel : "",
  46. // renderAsHtml: Boolean
  47. // Set this to true if the specified Editor's value should be
  48. // interpreted as HTML
  49. // rather than plain text (ie, dijit.Editor)
  50. renderAsHtml : false,
  51. // editor: String
  52. // Class name for Editor widget
  53. editor : "dijit.form.TextBox",
  54. // editorParams: Object
  55. // Set of parameters for editor, like {required: true}
  56. editorParams : {},
  57. onChange : function(value) {
  58. // summary: User should set this handler to be notified of changes
  59. // to value
  60. },
  61. // width: String
  62. // Width of editor. By default it's width=100% (ie, block mode)
  63. width : "100%",
  64. // value: String
  65. // The display value of the widget in read-only mode
  66. value : "",
  67. // noValueIndicator: String
  68. // The text that gets displayed when there is no value (so that the user
  69. // has a place to click to edit)
  70. noValueIndicator : "<span style='font-family: wingdings; text-decoration: underline;'>&nbsp;&nbsp;&nbsp;&nbsp;&#x270d;&nbsp;&nbsp;&nbsp;&nbsp;</span>",
  71. postMixInProperties : function() {
  72. this.inherited('postMixInProperties', arguments);
  73. // save pointer to original source node, since Widget nulls-out
  74. // srcNodeRef
  75. this.displayNode = this.srcNodeRef;
  76. // connect handlers to the display node
  77. var events = {
  78. ondijitclick : "_onClick",
  79. onmouseover : "_onMouseOver",
  80. onmouseout : "_onMouseOut",
  81. onfocus : "_onMouseOver",
  82. onblur : "_onMouseOut"
  83. };
  84. for (var name in events) {
  85. this.connect(this.displayNode, name, events[name]);
  86. }
  87. dijit.setWaiRole(this.displayNode, "button");
  88. if (!this.displayNode.getAttribute("tabIndex")) {
  89. this.displayNode.setAttribute("tabIndex", 0);
  90. }
  91. if (!this.value) {
  92. this.value = this.displayNode.innerHTML;
  93. }
  94. this._setDisplayValue(this.value); // if blank, change to icon for
  95. // "input needed"
  96. },
  97. _onMouseOver : function() {
  98. dojo.addClass(this.displayNode, this.disabled
  99. ? "dijitDisabledClickableRegion"
  100. : "dijitClickableRegion");
  101. },
  102. _onMouseOut : function() {
  103. dojo.removeClass(this.displayNode, this.disabled
  104. ? "dijitDisabledClickableRegion"
  105. : "dijitClickableRegion");
  106. },
  107. _onClick : function(/* Event */e) {
  108. if (this.disabled) {
  109. return;
  110. }
  111. if (e) {
  112. dojo.stopEvent(e);
  113. }
  114. this._onMouseOut();
  115. // Since FF gets upset if you move a node while in an event handler
  116. // for that node...
  117. setTimeout(dojo.hitch(this, "_edit"), 0);
  118. },
  119. _edit : function() {
  120. // summary: display the editor widget in place of the original (read
  121. // only) markup
  122. this.editing = true;
  123. var editValue = (this.renderAsHtml ? this.value : this.value
  124. .replace(/\s*\r?\n\s*/g, "").replace(/<br\/?>/gi, "\n")
  125. .replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(
  126. /&amp;/g, "&"));
  127. // Placeholder for edit widget
  128. // Put place holder (and eventually editWidget) before the display
  129. // node so that it's positioned correctly
  130. // when Calendar dropdown appears, which happens automatically on
  131. // focus.
  132. var placeholder = document.createElement("span");
  133. dojo.place(placeholder, this.domNode, "before");
  134. var ew = this.editWidget = new dijit._InlineEditor({
  135. value : dojo.trim(editValue),
  136. autoSave : this.autoSave,
  137. buttonSave : this.buttonSave,
  138. buttonCancel : this.buttonCancel,
  139. renderAsHtml : this.renderAsHtml,
  140. editor : this.editor,
  141. editorParams : this.editorParams,
  142. style : dojo.getComputedStyle(this.displayNode),
  143. save : dojo.hitch(this, "save"),
  144. cancel : dojo.hitch(this, "cancel"),
  145. width : this.width
  146. }, placeholder);
  147. // to avoid screen jitter, we first create the editor with
  148. // position:absolute, visibility:hidden,
  149. // and then when it's finished rendering, we switch from display
  150. // mode to editor
  151. var ews = ew.domNode.style;
  152. this.displayNode.style.display = "none";
  153. ews.position = "static";
  154. ews.visibility = "visible";
  155. // Replace the display widget with edit widget, leaving them both
  156. // displayed for a brief time so that
  157. // focus can be shifted without incident. (browser may needs some
  158. // time to render the editor.)
  159. this.domNode = ew.domNode;
  160. setTimeout(function() {
  161. ew.focus();
  162. }, 100);
  163. },
  164. _showText : function(/* Boolean */focus) {
  165. // summary: revert to display mode, and optionally focus on display
  166. // node
  167. // display the read-only text and then quickly hide the editor (to
  168. // avoid screen jitter)
  169. this.displayNode.style.display = "";
  170. var ews = this.editWidget.domNode.style;
  171. ews.position = "absolute";
  172. ews.visibility = "hidden";
  173. this.domNode = this.displayNode;
  174. // give the browser some time to render the display node and then
  175. // shift focus to it
  176. // and hide the edit widget
  177. var _this = this;
  178. setTimeout(function() {
  179. if (focus) {
  180. dijit.focus(_this.displayNode);
  181. }
  182. _this.editWidget.destroy();
  183. delete _this.editWidget;
  184. }, 100);
  185. },
  186. save : function(/* Boolean */focus) {
  187. // summary:
  188. // Save the contents of the editor and revert to display mode.
  189. // focus: Boolean
  190. // Focus on the display mode text
  191. this.editing = false;
  192. this.value = this.editWidget.getValue() + "";
  193. if (this.renderAsHtml) {
  194. this.value = this.value.replace(/&/gm, "&amp;").replace(/</gm,
  195. "&lt;").replace(/>/gm, "&gt;").replace(/"/gm, "&quot;")
  196. .replace("\n", "<br>");
  197. }
  198. this._setDisplayValue(this.value);
  199. // tell the world that we have changed
  200. this.onChange(this.value);
  201. this._showText(focus);
  202. },
  203. _setDisplayValue : function(/* String */val) {
  204. // summary: inserts specified HTML value into this node, or an
  205. // "input needed" character if node is blank
  206. this.displayNode.innerHTML = val || this.noValueIndicator;
  207. },
  208. cancel : function(/* Boolean */focus) {
  209. // summary:
  210. // Revert to display mode, discarding any changes made in the editor
  211. this.editing = false;
  212. this._showText(focus);
  213. }
  214. });
  215. dojo.declare("dijit._InlineEditor", [dijit._Widget, dijit._Templated], {
  216. // summary:
  217. // internal widget used by InlineEditBox, displayed when in editing mode
  218. // to display the editor and maybe save/cancel buttons. Calling code
  219. // should
  220. // connect to save/cancel methods to detect when editing is finished
  221. //
  222. // Has mainly the same parameters as InlineEditBox, plus these values:
  223. //
  224. // style: Object
  225. // Set of CSS attributes of display node, to replicate in editor
  226. //
  227. // value: String
  228. // Value as an HTML string or plain text string, depending on
  229. // renderAsHTML flag
  230. templateString : "<fieldset dojoAttachPoint=\"editNode\" waiRole=\"presentation\" style=\"position: absolute; visibility:hidden\" class=\"dijitReset dijitInline\"\n\tdojoAttachEvent=\"onkeypress: _onKeyPress\" \n\t><input dojoAttachPoint=\"editorPlaceholder\"\n\t/><span dojoAttachPoint=\"buttonContainer\"\n\t\t><button class='saveButton' dojoAttachPoint=\"saveButton\" dojoType=\"dijit.form.Button\" dojoAttachEvent=\"onClick:save\">${buttonSave}</button\n\t\t><button class='cancelButton' dojoAttachPoint=\"cancelButton\" dojoType=\"dijit.form.Button\" dojoAttachEvent=\"onClick:cancel\">${buttonCancel}</button\n\t></span\n></fieldset>\n",
  231. widgetsInTemplate : true,
  232. postMixInProperties : function() {
  233. this.inherited('postMixInProperties', arguments);
  234. this.messages = dojo.i18n.getLocalization("dijit", "common",
  235. this.lang);
  236. dojo.forEach(["buttonSave", "buttonCancel"], function(prop) {
  237. if (!this[prop]) {
  238. this[prop] = this.messages[prop];
  239. }
  240. }, this);
  241. },
  242. postCreate : function() {
  243. // Create edit widget in place in the template
  244. var cls = dojo.getObject(this.editor);
  245. var ew = this.editWidget = new cls(this.editorParams,
  246. this.editorPlaceholder);
  247. // Copy the style from the source
  248. // Don't copy ALL properties though, just the necessary/applicable
  249. // ones
  250. var srcStyle = this.style;
  251. dojo.forEach(["fontWeight", "fontFamily", "fontSize", "fontStyle"],
  252. function(prop) {
  253. ew.focusNode.style[prop] = srcStyle[prop];
  254. }, this);
  255. dojo.forEach(["marginTop", "marginBottom", "marginLeft",
  256. "marginRight"], function(prop) {
  257. this.domNode.style[prop] = srcStyle[prop];
  258. }, this);
  259. if (this.width == "100%") {
  260. // block mode
  261. ew.domNode.style.width = "100%"; // because display: block
  262. // doesn't work for table
  263. // widgets
  264. this.domNode.style.display = "block";
  265. } else {
  266. // inline-block mode
  267. ew.domNode.style.width = this.width
  268. + (Number(this.width) == this.width ? "px" : "");
  269. }
  270. this.connect(this.editWidget, "onChange", "_onChange");
  271. // setting the value of the edit widget will cause a possibly
  272. // asynchronous onChange() call.
  273. // we need to ignore it, since we are only interested in when the
  274. // user changes the value.
  275. this._ignoreNextOnChange = true;
  276. (this.editWidget.setDisplayedValue || this.editWidget.setValue)
  277. .call(this.editWidget, this.value);
  278. this._initialText = this.getValue();
  279. if (this.autoSave) {
  280. this.buttonContainer.style.display = "none";
  281. }
  282. },
  283. destroy : function() {
  284. this.editWidget.destroy();
  285. this.inherited(arguments);
  286. },
  287. getValue : function() {
  288. var ew = this.editWidget;
  289. return ew.getDisplayedValue ? ew.getDisplayedValue() : ew
  290. .getValue();
  291. },
  292. _onKeyPress : function(e) {
  293. // summary: Callback when keypress in the edit box (see template).
  294. // description:
  295. // For autoSave widgets, if Esc/Enter, call cancel/save.
  296. // For non-autoSave widgets, enable save button if the text value is
  297. // different than the original value.
  298. if (this._exitInProgress) {
  299. return;
  300. }
  301. if (this.autoSave) {
  302. // If Enter/Esc pressed, treat as save/cancel.
  303. if (e.keyCode == dojo.keys.ESCAPE) {
  304. dojo.stopEvent(e);
  305. this._exitInProgress = true;
  306. this.cancel(true);
  307. } else if (e.keyCode == dojo.keys.ENTER) {
  308. dojo.stopEvent(e);
  309. this._exitInProgress = true;
  310. this.save(true);
  311. }
  312. } else {
  313. var _this = this;
  314. // Delay before calling getValue().
  315. // The delay gives the browser a chance to update the Textarea.
  316. setTimeout(function() {
  317. _this.saveButton
  318. .setDisabled(_this.getValue() == _this._initialText);
  319. }, 100);
  320. }
  321. },
  322. _onBlur : function() {
  323. // summary:
  324. // Called when focus moves outside the editor
  325. if (this._exitInProgress) {
  326. // when user clicks the "save" button, focus is shifted back to
  327. // display text, causing this
  328. // function to be called, but in that case don't do anything
  329. return;
  330. }
  331. if (this.autoSave) {
  332. this._exitInProgress = true;
  333. if (this.getValue() == this._initialText) {
  334. this.cancel(false);
  335. } else {
  336. this.save(false);
  337. }
  338. }
  339. },
  340. enableSave : function() {
  341. // summary: User replacable function returning a Boolean to indicate
  342. // if the Save button should be enabled or not - usually due to
  343. // invalid conditions
  344. return this.editWidget.isValid ? this.editWidget.isValid() : true; // Boolean
  345. },
  346. _onChange : function() {
  347. // summary:
  348. // Called when the underlying widget fires an onChange event,
  349. // which means that the user has finished entering the value
  350. if (this._ignoreNextOnChange) {
  351. delete this._ignoreNextOnChange;
  352. return;
  353. }
  354. if (this._exitInProgress) {
  355. // TODO: the onChange event might happen after the return key
  356. // for an async widget
  357. // like FilteringSelect. Shouldn't be deleting the edit widget
  358. // on end-of-edit
  359. return;
  360. }
  361. if (this.autoSave) {
  362. this._exitInProgress = true;
  363. this.save(true);
  364. } else {
  365. // in case the keypress event didn't get through (old problem
  366. // with Textarea that has been fixed
  367. // in theory) or if the keypress event comes too quickly and the
  368. // value inside the Textarea hasn't
  369. // been updated yet)
  370. this.saveButton
  371. .setDisabled((this.getValue() == this._initialText)
  372. || !this.enableSave());
  373. }
  374. },
  375. enableSave : function() {
  376. // summary: User replacable function returning a Boolean to indicate
  377. // if the Save button should be enabled or not - usually due to
  378. // invalid conditions
  379. return this.editWidget.isValid ? this.editWidget.isValid() : true;
  380. },
  381. focus : function() {
  382. this.editWidget.focus();
  383. dijit.selectInputText(this.editWidget.focusNode);
  384. }
  385. });
  386. dijit.selectInputText = function(/* DomNode */element) {
  387. // summary: select all the text in an input element
  388. // TODO: use functions in _editor/selection.js?
  389. var _window = dojo.global;
  390. var _document = dojo.doc;
  391. element = dojo.byId(element);
  392. if (_document["selection"] && dojo.body()["createTextRange"]) { // IE
  393. if (element.createTextRange) {
  394. var range = element.createTextRange();
  395. range.moveStart("character", 0);
  396. range.moveEnd("character", element.value.length);
  397. range.select();
  398. }
  399. } else if (_window["getSelection"]) {
  400. var selection = _window.getSelection();
  401. // FIXME: does this work on Safari?
  402. if (element.setSelectionRange) {
  403. element.setSelectionRange(0, element.value.length);
  404. }
  405. }
  406. element.focus();
  407. };
  408. }