]> git.wh0rd.org - tt-rss.git/blob - lib/dijit/InlineEditBox.js.uncompressed.js
upgrade dojo to 1.8.3 (refs #570)
[tt-rss.git] / lib / dijit / InlineEditBox.js.uncompressed.js
1 require({cache:{
2 'url:dijit/templates/InlineEditBox.html':"<span data-dojo-attach-point=\"editNode\" role=\"presentation\" class=\"dijitReset dijitInline dijitOffScreen\"\n\tdata-dojo-attach-event=\"onkeypress: _onKeyPress\"\n\t><span data-dojo-attach-point=\"editorPlaceholder\"></span\n\t><span data-dojo-attach-point=\"buttonContainer\"\n\t\t><button data-dojo-type=\"dijit/form/Button\" data-dojo-props=\"label: '${buttonSave}', 'class': 'saveButton'\"\n\t\t\tdata-dojo-attach-point=\"saveButton\" data-dojo-attach-event=\"onClick:save\"></button\n\t\t><button data-dojo-type=\"dijit/form/Button\" data-dojo-props=\"label: '${buttonCancel}', 'class': 'cancelButton'\"\n\t\t\tdata-dojo-attach-point=\"cancelButton\" data-dojo-attach-event=\"onClick:cancel\"></button\n\t></span\n></span>\n"}});
3 define("dijit/InlineEditBox", [
4 "require",
5 "dojo/_base/array", // array.forEach
6 "dojo/_base/declare", // declare
7 "dojo/dom-attr", // domAttr.set domAttr.get
8 "dojo/dom-class", // domClass.add domClass.remove domClass.toggle
9 "dojo/dom-construct", // domConstruct.create domConstruct.destroy
10 "dojo/dom-style", // domStyle.getComputedStyle domStyle.set domStyle.get
11 "dojo/_base/event", // event.stop
12 "dojo/i18n", // i18n.getLocalization
13 "dojo/_base/kernel", // kernel.deprecated
14 "dojo/keys", // keys.ENTER keys.ESCAPE
15 "dojo/_base/lang", // lang.getObject
16 "dojo/sniff", // has("ie")
17 "dojo/when",
18 "./focus",
19 "./_Widget",
20 "./_TemplatedMixin",
21 "./_WidgetsInTemplateMixin",
22 "./_Container",
23 "./form/Button",
24 "./form/_TextBoxMixin",
25 "./form/TextBox",
26 "dojo/text!./templates/InlineEditBox.html",
27 "dojo/i18n!./nls/common"
28 ], function(require, array, declare, domAttr, domClass, domConstruct, domStyle, event, i18n, kernel, keys, lang, has, when,
29 fm, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, _Container, Button, _TextBoxMixin, TextBox, template){
30
31 // module:
32 // dijit/InlineEditBox
33
34 var InlineEditor = declare("dijit._InlineEditor", [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], {
35 // summary:
36 // Internal widget used by InlineEditBox, displayed when in editing mode
37 // to display the editor and maybe save/cancel buttons. Calling code should
38 // connect to save/cancel methods to detect when editing is finished
39 //
40 // Has mainly the same parameters as InlineEditBox, plus these values:
41 //
42 // style: Object
43 // Set of CSS attributes of display node, to replicate in editor
44 //
45 // value: String
46 // Value as an HTML string or plain text string, depending on renderAsHTML flag
47
48 templateString: template,
49
50 postMixInProperties: function(){
51 this.inherited(arguments);
52 this.messages = i18n.getLocalization("dijit", "common", this.lang);
53 array.forEach(["buttonSave", "buttonCancel"], function(prop){
54 if(!this[prop]){
55 this[prop] = this.messages[prop];
56 }
57 }, this);
58 },
59
60 buildRendering: function(){
61 this.inherited(arguments);
62
63 // Create edit widget in place in the template
64 // TODO: remove getObject() for 2.0
65 var Cls = typeof this.editor == "string" ? (lang.getObject(this.editor) || require(this.editor)) : this.editor;
66
67 // Copy the style from the source
68 // Don't copy ALL properties though, just the necessary/applicable ones.
69 // wrapperStyle/destStyle code is to workaround IE bug where getComputedStyle().fontSize
70 // is a relative value like 200%, rather than an absolute value like 24px, and
71 // the 200% can refer *either* to a setting on the node or it's ancestor (see #11175)
72 var srcStyle = this.sourceStyle,
73 editStyle = "line-height:" + srcStyle.lineHeight + ";",
74 destStyle = domStyle.getComputedStyle(this.domNode);
75 array.forEach(["Weight", "Family", "Size", "Style"], function(prop){
76 var textStyle = srcStyle["font" + prop],
77 wrapperStyle = destStyle["font" + prop];
78 if(wrapperStyle != textStyle){
79 editStyle += "font-" + prop + ":" + srcStyle["font" + prop] + ";";
80 }
81 }, this);
82 array.forEach(["marginTop", "marginBottom", "marginLeft", "marginRight", "position", "left", "top", "right", "bottom", "float", "clear", "display"], function(prop){
83 this.domNode.style[prop] = srcStyle[prop];
84 }, this);
85 var width = this.inlineEditBox.width;
86 if(width == "100%"){
87 // block mode
88 editStyle += "width:100%;";
89 this.domNode.style.display = "block";
90 }else{
91 // inline-block mode
92 editStyle += "width:" + (width + (Number(width) == width ? "px" : "")) + ";";
93 }
94 var editorParams = lang.delegate(this.inlineEditBox.editorParams, {
95 style: editStyle,
96 dir: this.dir,
97 lang: this.lang,
98 textDir: this.textDir
99 });
100 editorParams[ "displayedValue" in Cls.prototype ? "displayedValue" : "value"] = this.value;
101 this.editWidget = new Cls(editorParams, this.editorPlaceholder);
102
103 if(this.inlineEditBox.autoSave){
104 // Remove the save/cancel buttons since saving is done by simply tabbing away or
105 // selecting a value from the drop down list
106 domConstruct.destroy(this.buttonContainer);
107 }
108 },
109
110 postCreate: function(){
111 this.inherited(arguments);
112
113 var ew = this.editWidget;
114
115 if(this.inlineEditBox.autoSave){
116 // Selecting a value from a drop down list causes an onChange event and then we save
117 this.connect(ew, "onChange", "_onChange");
118
119 // ESC and TAB should cancel and save. Note that edit widgets do a stopEvent() on ESC key (to
120 // prevent Dialog from closing when the user just wants to revert the value in the edit widget),
121 // so this is the only way we can see the key press event.
122 this.connect(ew, "onKeyPress", "_onKeyPress");
123 }else{
124 // If possible, enable/disable save button based on whether the user has changed the value
125 if("intermediateChanges" in ew){
126 ew.set("intermediateChanges", true);
127 this.connect(ew, "onChange", "_onIntermediateChange");
128 this.saveButton.set("disabled", true);
129 }
130 }
131 },
132
133 startup: function(){
134 this.editWidget.startup();
135 this.inherited(arguments);
136 },
137
138 _onIntermediateChange: function(/*===== val =====*/){
139 // summary:
140 // Called for editor widgets that support the intermediateChanges=true flag as a way
141 // to detect when to enable/disabled the save button
142 this.saveButton.set("disabled", (this.getValue() == this._resetValue) || !this.enableSave());
143 },
144
145 destroy: function(){
146 this.editWidget.destroy(true); // let the parent wrapper widget clean up the DOM
147 this.inherited(arguments);
148 },
149
150 getValue: function(){
151 // summary:
152 // Return the [display] value of the edit widget
153 var ew = this.editWidget;
154 return String(ew.get("displayedValue" in ew ? "displayedValue" : "value"));
155 },
156
157 _onKeyPress: function(e){
158 // summary:
159 // Handler for keypress in the edit box in autoSave mode.
160 // description:
161 // For autoSave widgets, if Esc/Enter, call cancel/save.
162 // tags:
163 // private
164
165 if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){
166 if(e.altKey || e.ctrlKey){
167 return;
168 }
169 // If Enter/Esc pressed, treat as save/cancel.
170 if(e.charOrCode == keys.ESCAPE){
171 event.stop(e);
172 this.cancel(true); // sets editing=false which short-circuits _onBlur processing
173 }else if(e.charOrCode == keys.ENTER && e.target.tagName == "INPUT"){
174 event.stop(e);
175 this._onChange(); // fire _onBlur and then save
176 }
177
178 // _onBlur will handle TAB automatically by allowing
179 // the TAB to change focus before we mess with the DOM: #6227
180 // Expounding by request:
181 // The current focus is on the edit widget input field.
182 // save() will hide and destroy this widget.
183 // We want the focus to jump from the currently hidden
184 // displayNode, but since it's hidden, it's impossible to
185 // unhide it, focus it, and then have the browser focus
186 // away from it to the next focusable element since each
187 // of these events is asynchronous and the focus-to-next-element
188 // is already queued.
189 // So we allow the browser time to unqueue the move-focus event
190 // before we do all the hide/show stuff.
191 }
192 },
193
194 _onBlur: function(){
195 // summary:
196 // Called when focus moves outside the editor
197 // tags:
198 // private
199
200 this.inherited(arguments);
201 if(this.inlineEditBox.autoSave && this.inlineEditBox.editing){
202 if(this.getValue() == this._resetValue){
203 this.cancel(false);
204 }else if(this.enableSave()){
205 this.save(false);
206 }
207 }
208 },
209
210 _onChange: function(){
211 // summary:
212 // Called when the underlying widget fires an onChange event,
213 // such as when the user selects a value from the drop down list of a ComboBox,
214 // which means that the user has finished entering the value and we should save.
215 // tags:
216 // private
217
218 if(this.inlineEditBox.autoSave && this.inlineEditBox.editing && this.enableSave()){
219 fm.focus(this.inlineEditBox.displayNode); // fires _onBlur which will save the formatted value
220 }
221 },
222
223 enableSave: function(){
224 // summary:
225 // User overridable function returning a Boolean to indicate
226 // if the Save button should be enabled or not - usually due to invalid conditions
227 // tags:
228 // extension
229 return this.editWidget.isValid ? this.editWidget.isValid() : true;
230 },
231
232 focus: function(){
233 // summary:
234 // Focus the edit widget.
235 // tags:
236 // protected
237
238 this.editWidget.focus();
239
240 if(this.editWidget.focusNode){
241 // IE can take 30ms to report the focus event, but focus manager needs to know before a 0ms timeout.
242 fm._onFocusNode(this.editWidget.focusNode);
243
244 if(this.editWidget.focusNode.tagName == "INPUT"){
245 this.defer(function(){
246 _TextBoxMixin.selectInputText(this.editWidget.focusNode);
247 });
248 }
249 }
250 }
251 });
252
253
254 var InlineEditBox = declare("dijit.InlineEditBox", _Widget, {
255 // summary:
256 // An element with in-line edit capabilities
257 //
258 // description:
259 // Behavior for an existing node (`<p>`, `<div>`, `<span>`, etc.) so that
260 // when you click it, an editor shows up in place of the original
261 // text. Optionally, Save and Cancel button are displayed below the edit widget.
262 // When Save is clicked, the text is pulled from the edit
263 // widget and redisplayed and the edit widget is again hidden.
264 // By default a plain Textarea widget is used as the editor (or for
265 // inline values a TextBox), but you can specify an editor such as
266 // dijit.Editor (for editing HTML) or a Slider (for adjusting a number).
267 // An edit widget must support the following API to be used:
268 //
269 // - displayedValue or value as initialization parameter,
270 // and available through set('displayedValue') / set('value')
271 // - void focus()
272 // - DOM-node focusNode = node containing editable text
273
274 // editing: [readonly] Boolean
275 // Is the node currently in edit mode?
276 editing: false,
277
278 // autoSave: Boolean
279 // Changing the value automatically saves it; don't have to push save button
280 // (and save button isn't even displayed)
281 autoSave: true,
282
283 // buttonSave: String
284 // Save button label
285 buttonSave: "",
286
287 // buttonCancel: String
288 // Cancel button label
289 buttonCancel: "",
290
291 // renderAsHtml: Boolean
292 // Set this to true if the specified Editor's value should be interpreted as HTML
293 // rather than plain text (ex: `dijit.Editor`)
294 renderAsHtml: false,
295
296 // editor: String|Function
297 // MID (ex: "dijit/form/TextBox") or constructor for editor widget
298 editor: TextBox,
299
300 // editorWrapper: String|Function
301 // Class name (or reference to the Class) for widget that wraps the editor widget, displaying save/cancel
302 // buttons.
303 editorWrapper: InlineEditor,
304
305 // editorParams: Object
306 // Set of parameters for editor, like {required: true}
307 editorParams: {},
308
309 // disabled: Boolean
310 // If true, clicking the InlineEditBox to edit it will have no effect.
311 disabled: false,
312
313 onChange: function(/*===== value =====*/){
314 // summary:
315 // Set this handler to be notified of changes to value.
316 // tags:
317 // callback
318 },
319
320 onCancel: function(){
321 // summary:
322 // Set this handler to be notified when editing is cancelled.
323 // tags:
324 // callback
325 },
326
327 // width: String
328 // Width of editor. By default it's width=100% (ie, block mode).
329 width: "100%",
330
331 // value: String
332 // The display value of the widget in read-only mode
333 value: "",
334
335 // noValueIndicator: [const] String
336 // The text that gets displayed when there is no value (so that the user has a place to click to edit)
337 noValueIndicator: has("ie") <= 6 ? // font-family needed on IE6 but it messes up IE8
338 "<span style='font-family: wingdings; text-decoration: underline;'>&#160;&#160;&#160;&#160;&#x270d;&#160;&#160;&#160;&#160;</span>" :
339 "<span style='text-decoration: underline;'>&#160;&#160;&#160;&#160;&#x270d;&#160;&#160;&#160;&#160;</span>", // &#160; == &nbsp;
340
341 constructor: function(/*===== params, srcNodeRef =====*/){
342 // summary:
343 // Create the widget.
344 // params: Object|null
345 // Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
346 // and functions, typically callbacks like onClick.
347 // The hash can contain any of the widget's properties, excluding read-only properties.
348 // srcNodeRef: DOMNode|String?
349 // If a srcNodeRef (DOM node) is specified:
350 //
351 // - use srcNodeRef.innerHTML as my value
352 // - replace srcNodeRef with my generated DOM tree
353
354 this.editorParams = {};
355 },
356
357 postMixInProperties: function(){
358 this.inherited(arguments);
359
360 // save pointer to original source node, since Widget nulls-out srcNodeRef
361 this.displayNode = this.srcNodeRef;
362
363 // connect handlers to the display node
364 var events = {
365 ondijitclick: "_onClick",
366 onmouseover: "_onMouseOver",
367 onmouseout: "_onMouseOut",
368 onfocus: "_onMouseOver",
369 onblur: "_onMouseOut"
370 };
371 for(var name in events){
372 this.connect(this.displayNode, name, events[name]);
373 }
374 this.displayNode.setAttribute("role", "button");
375 if(!this.displayNode.getAttribute("tabIndex")){
376 this.displayNode.setAttribute("tabIndex", 0);
377 }
378
379 if(!this.value && !("value" in this.params)){ // "" is a good value if specified directly so check params){
380 this.value = lang.trim(this.renderAsHtml ? this.displayNode.innerHTML :
381 (this.displayNode.innerText || this.displayNode.textContent || ""));
382 }
383 if(!this.value){
384 this.displayNode.innerHTML = this.noValueIndicator;
385 }
386
387 domClass.add(this.displayNode, 'dijitInlineEditBoxDisplayMode');
388 },
389
390 setDisabled: function(/*Boolean*/ disabled){
391 // summary:
392 // Deprecated. Use set('disabled', ...) instead.
393 // tags:
394 // deprecated
395 kernel.deprecated("dijit.InlineEditBox.setDisabled() is deprecated. Use set('disabled', bool) instead.", "", "2.0");
396 this.set('disabled', disabled);
397 },
398
399 _setDisabledAttr: function(/*Boolean*/ disabled){
400 // summary:
401 // Hook to make set("disabled", ...) work.
402 // Set disabled state of widget.
403 this.domNode.setAttribute("aria-disabled", disabled ? "true" : "false");
404 if(disabled){
405 this.displayNode.removeAttribute("tabIndex");
406 }else{
407 this.displayNode.setAttribute("tabIndex", 0);
408 }
409 domClass.toggle(this.displayNode, "dijitInlineEditBoxDisplayModeDisabled", disabled);
410 this._set("disabled", disabled);
411 },
412
413 _onMouseOver: function(){
414 // summary:
415 // Handler for onmouseover and onfocus event.
416 // tags:
417 // private
418 if(!this.disabled){
419 domClass.add(this.displayNode, "dijitInlineEditBoxDisplayModeHover");
420 }
421 },
422
423 _onMouseOut: function(){
424 // summary:
425 // Handler for onmouseout and onblur event.
426 // tags:
427 // private
428 domClass.remove(this.displayNode, "dijitInlineEditBoxDisplayModeHover");
429 },
430
431 _onClick: function(/*Event*/ e){
432 // summary:
433 // Handler for onclick event.
434 // tags:
435 // private
436 if(this.disabled){
437 return;
438 }
439 if(e){
440 event.stop(e);
441 }
442 this._onMouseOut();
443
444 // Since FF gets upset if you move a node while in an event handler for that node...
445 this.defer("edit");
446 },
447
448 edit: function(){
449 // summary:
450 // Display the editor widget in place of the original (read only) markup.
451 // tags:
452 // private
453
454 if(this.disabled || this.editing){
455 return;
456 }
457 this._set('editing', true);
458
459 // save some display node values that can be restored later
460 this._savedTabIndex = domAttr.get(this.displayNode, "tabIndex") || "0";
461
462 if(this.wrapperWidget){
463 var ew = this.wrapperWidget.editWidget;
464 ew.set("displayedValue" in ew ? "displayedValue" : "value", this.value);
465 }else{
466 // Placeholder for edit widget
467 // Put place holder (and eventually editWidget) before the display node so that it's positioned correctly
468 // when Calendar dropdown appears, which happens automatically on focus.
469 var placeholder = domConstruct.create("span", null, this.domNode, "before");
470
471 // Create the editor wrapper (the thing that holds the editor widget and the save/cancel buttons)
472 var Ewc = typeof this.editorWrapper == "string" ? lang.getObject(this.editorWrapper) : this.editorWrapper;
473 this.wrapperWidget = new Ewc({
474 value: this.value,
475 buttonSave: this.buttonSave,
476 buttonCancel: this.buttonCancel,
477 dir: this.dir,
478 lang: this.lang,
479 tabIndex: this._savedTabIndex,
480 editor: this.editor,
481 inlineEditBox: this,
482 sourceStyle: domStyle.getComputedStyle(this.displayNode),
483 save: lang.hitch(this, "save"),
484 cancel: lang.hitch(this, "cancel"),
485 textDir: this.textDir
486 }, placeholder);
487 if(!this.wrapperWidget._started){
488 this.wrapperWidget.startup();
489 }
490 if(!this._started){
491 this.startup();
492 }
493 }
494 var ww = this.wrapperWidget;
495
496 // to avoid screen jitter, we first create the editor with position: absolute, visibility: hidden,
497 // and then when it's finished rendering, we switch from display mode to editor
498 // position: absolute releases screen space allocated to the display node
499 // opacity:0 is the same as visibility: hidden but is still focusable
500 // visibility: hidden removes focus outline
501
502 domClass.add(this.displayNode, "dijitOffScreen");
503 domClass.remove(ww.domNode, "dijitOffScreen");
504 domStyle.set(ww.domNode, { visibility: "visible" });
505 domAttr.set(this.displayNode, "tabIndex", "-1"); // needed by WebKit for TAB from editor to skip displayNode
506
507 // After edit widget has finished initializing (in particular need to wait for dijit.Editor),
508 // or immediately if there is no onLoadDeferred Deferred,
509 // replace the display widget with edit widget, leaving them both displayed for a brief time so that
510 // focus can be shifted without incident.
511 when(ww.editWidget.onLoadDeferred, lang.hitch(ww, function(){
512 this.defer(function(){ // defer needed so that the change of focus doesn't happen on mousedown which also sets focus
513 this.focus(); // both nodes are showing, so we can switch focus safely
514 this._resetValue = this.getValue();
515 });
516 }));
517 },
518
519 _onBlur: function(){
520 // summary:
521 // Called when focus moves outside the InlineEditBox.
522 // Performs garbage collection.
523 // tags:
524 // private
525
526 this.inherited(arguments);
527 if(!this.editing){
528 /* causes IE focus problems, see TooltipDialog_a11y.html...
529 this.defer(function(){
530 if(this.wrapperWidget){
531 this.wrapperWidget.destroy();
532 delete this.wrapperWidget;
533 }
534 });
535 */
536 }
537 },
538
539 destroy: function(){
540 if(this.wrapperWidget && !this.wrapperWidget._destroyed){
541 this.wrapperWidget.destroy();
542 delete this.wrapperWidget;
543 }
544 this.inherited(arguments);
545 },
546
547 _showText: function(/*Boolean*/ focus){
548 // summary:
549 // Revert to display mode, and optionally focus on display node
550 // tags:
551 // private
552
553 var ww = this.wrapperWidget;
554 domStyle.set(ww.domNode, { visibility: "hidden" }); // hide the editor from mouse/keyboard events
555 domClass.add(ww.domNode, "dijitOffScreen");
556 domClass.remove(this.displayNode, "dijitOffScreen");
557 domAttr.set(this.displayNode, "tabIndex", this._savedTabIndex);
558 if(focus){
559 fm.focus(this.displayNode);
560 }
561 },
562
563 save: function(/*Boolean*/ focus){
564 // summary:
565 // Save the contents of the editor and revert to display mode.
566 // focus: Boolean
567 // Focus on the display mode text
568 // tags:
569 // private
570
571 if(this.disabled || !this.editing){
572 return;
573 }
574 this._set('editing', false);
575
576 var ww = this.wrapperWidget;
577 var value = ww.getValue();
578 this.set('value', value); // display changed, formatted value
579
580 this._showText(focus); // set focus as needed
581 },
582
583 setValue: function(/*String*/ val){
584 // summary:
585 // Deprecated. Use set('value', ...) instead.
586 // tags:
587 // deprecated
588 kernel.deprecated("dijit.InlineEditBox.setValue() is deprecated. Use set('value', ...) instead.", "", "2.0");
589 return this.set("value", val);
590 },
591
592 _setValueAttr: function(/*String*/ val){
593 // summary:
594 // Hook to make set("value", ...) work.
595 // Inserts specified HTML value into this node, or an "input needed" character if node is blank.
596
597 val = lang.trim(val);
598 var renderVal = this.renderAsHtml ? val : val.replace(/&/gm, "&amp;").replace(/</gm, "&lt;").replace(/>/gm, "&gt;").replace(/"/gm, "&quot;").replace(/\n/g, "<br>");
599 this.displayNode.innerHTML = renderVal || this.noValueIndicator;
600 this._set("value", val);
601
602 if(this._started){
603 // tell the world that we have changed
604 this.defer(function(){
605 this.onChange(val);
606 }); // defer prevents browser freeze for long-running event handlers
607 }
608 // contextual (auto) text direction depends on the text value
609 if(this.textDir == "auto"){
610 this.applyTextDir(this.displayNode, this.displayNode.innerText);
611 }
612 },
613
614 getValue: function(){
615 // summary:
616 // Deprecated. Use get('value') instead.
617 // tags:
618 // deprecated
619 kernel.deprecated("dijit.InlineEditBox.getValue() is deprecated. Use get('value') instead.", "", "2.0");
620 return this.get("value");
621 },
622
623 cancel: function(/*Boolean*/ focus){
624 // summary:
625 // Revert to display mode, discarding any changes made in the editor
626 // tags:
627 // private
628
629 if(this.disabled || !this.editing){
630 return;
631 }
632 this._set('editing', false);
633
634 // tell the world that we have no changes
635 this.defer("onCancel"); // defer prevents browser freeze for long-running event handlers
636
637 this._showText(focus);
638 },
639
640 _setTextDirAttr: function(/*String*/ textDir){
641 // summary:
642 // Setter for textDir.
643 // description:
644 // Users shouldn't call this function; they should be calling
645 // set('textDir', value)
646 // tags:
647 // private
648 if(!this._created || this.textDir != textDir){
649 this._set("textDir", textDir);
650 this.applyTextDir(this.displayNode, this.displayNode.innerText);
651 this.displayNode.align = this.dir == "rtl" ? "right" : "left"; //fix the text alignment
652 }
653 }
654 });
655
656 InlineEditBox._InlineEditor = InlineEditor; // for monkey patching
657
658 return InlineEditBox;
659 });