diff options
Diffstat (limited to 'includes/js/dijit/InlineEditBox.js')
-rw-r--r-- | includes/js/dijit/InlineEditBox.js | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/includes/js/dijit/InlineEditBox.js b/includes/js/dijit/InlineEditBox.js new file mode 100644 index 0000000..d4568d0 --- /dev/null +++ b/includes/js/dijit/InlineEditBox.js @@ -0,0 +1,422 @@ +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.require("dojo.i18n"); + +dojo.require("dijit._Widget"); +dojo.require("dijit._Container"); +dojo.require("dijit.form.Button"); +dojo.require("dijit.form.TextBox"); + +dojo.requireLocalization("dijit", "common", null, "zh,pt,da,tr,ru,de,sv,ja,he,fi,nb,el,ar,ROOT,pt-pt,cs,fr,es,ko,nl,zh-tw,pl,it,hu"); + +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: + // String getDisplayedValue() OR String getValue() + // void setDisplayedValue(String) OR void setValue(String) + // void focus() + // + // editing: 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 (ie, dijit.Editor) + renderAsHtml: false, + + // editor: String + // Class name for Editor widget + editor: "dijit.form.TextBox", + + // editorParams: Object + // Set of parameters for editor, like {required: true} + editorParams: {}, + + onChange: function(value){ + // summary: User should set this handler to be notified of changes to value + }, + + // 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: 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>", + + postMixInProperties: function(){ + this.inherited('postMixInProperties', 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.setValue(this.value || this.displayNode.innerHTML); + }, + + setDisabled: function(/*Boolean*/ disabled){ + // summary: + // Set disabled state of widget. + + this.disabled = disabled; + dijit.setWaiState(this.focusNode || this.domNode, "disabled", disabled); + }, + + _onMouseOver: function(){ + dojo.addClass(this.displayNode, this.disabled ? "dijitDisabledClickableRegion" : "dijitClickableRegion"); + }, + + _onMouseOut: function(){ + dojo.removeClass(this.displayNode, this.disabled ? "dijitDisabledClickableRegion" : "dijitClickableRegion"); + }, + + _onClick: function(/*Event*/ e){ + 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 + + this.editing = true; + + var editValue = + (this.renderAsHtml ? + this.value : + this.value.replace(/\s*\r?\n\s*/g,"").replace(/<br\/?>/gi, "\n").replace(/>/g,">").replace(/</g,"<").replace(/&/g,"&")); + + // 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.doc.createElement("span"); + dojo.place(placeholder, this.domNode, "before"); + + var ew = this.editWidget = new dijit._InlineEditor({ + value: dojo.trim(editValue), + autoSave: this.autoSave, + buttonSave: this.buttonSave, + buttonCancel: this.buttonCancel, + renderAsHtml: this.renderAsHtml, + editor: this.editor, + editorParams: this.editorParams, + style: dojo.getComputedStyle(this.displayNode), + save: dojo.hitch(this, "save"), + cancel: dojo.hitch(this, "cancel"), + width: this.width + }, placeholder); + + // 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 + var ews = ew.domNode.style; + this.displayNode.style.display="none"; + ews.position = "static"; + ews.visibility = "visible"; + + // 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.) + this.domNode = ew.domNode; + setTimeout(function(){ + ew.focus(); + }, 100); + }, + + _showText: function(/*Boolean*/ focus){ + // summary: revert to display mode, and optionally focus on display node + + // display the read-only text and then quickly hide the editor (to avoid screen jitter) + this.displayNode.style.display=""; + var ew = this.editWidget; + var ews = ew.domNode.style; + ews.position="absolute"; + ews.visibility="hidden"; + + this.domNode = this.displayNode; + + if(focus){ + dijit.focus(this.displayNode); + } + ews.display = "none"; + // give the browser some time to render the display node and then shift focus to it + // and hide the edit widget before garbage collecting the edit widget + setTimeout(function(){ + ew.destroy(); + delete ew; + if(dojo.isIE){ + // messing with the DOM tab order can cause IE to focus the body - so restore + dijit.focus(dijit.getFocus()); + } + }, 1000); // no hurry - wait for things to quiesce + }, + + save: function(/*Boolean*/ focus){ + // summary: + // Save the contents of the editor and revert to display mode. + // focus: Boolean + // Focus on the display mode text + this.editing = false; + + var value = this.editWidget.getValue() + ""; + if(!this.renderAsHtml){ + value = value.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """) + .replace(/\n/g, "<br>"); + } + this.setValue(value); + + // tell the world that we have changed + this.onChange(value); + + this._showText(focus); + }, + + setValue: function(/*String*/ val){ + // summary: inserts specified HTML value into this node, or an "input needed" character if node is blank + this.value = val; + this.displayNode.innerHTML = dojo.trim(val) || this.noValueIndicator; + }, + + getValue: function(){ + return this.value; + }, + + cancel: function(/*Boolean*/ focus){ + // summary: + // Revert to display mode, discarding any changes made in the editor + this.editing = false; + 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:"<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\" disabled=\"true\">${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", + widgetsInTemplate: true, + + postMixInProperties: function(){ + this.inherited('postMixInProperties', 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); + var ew = this.editWidget = new cls(this.editorParams, this.editorPlaceholder); + + // Copy the style from the source + // Don't copy ALL properties though, just the necessary/applicable ones + var srcStyle = this.style; + dojo.forEach(["fontWeight","fontFamily","fontSize","fontStyle"], function(prop){ + ew.focusNode.style[prop]=srcStyle[prop]; + }, this); + dojo.forEach(["marginTop","marginBottom","marginLeft", "marginRight"], function(prop){ + this.domNode.style[prop]=srcStyle[prop]; + }, this); + if(this.width=="100%"){ + // block mode + ew.domNode.style.width = "100%"; // because display: block doesn't work for table widgets + this.domNode.style.display="block"; + }else{ + // inline-block mode + ew.domNode.style.width = this.width + (Number(this.width)==this.width ? "px" : ""); + } + + this.connect(ew, "onChange", "_onChange"); + + // Monitor keypress on the edit widget. 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.focusNode || ew.domNode, "onkeypress", "_onKeyPress"); + + // priorityChange=false will prevent bogus onChange event + (this.editWidget.setDisplayedValue||this.editWidget.setValue).call(this.editWidget, this.value, false); + + this._initialText = this.getValue(); + + if(this.autoSave){ + this.buttonContainer.style.display="none"; + } + }, + + destroy: function(){ + this.editWidget.destroy(); + this.inherited(arguments); + }, + + getValue: function(){ + var ew = this.editWidget; + return ew.getDisplayedValue ? ew.getDisplayedValue() : ew.getValue(); + }, + + _onKeyPress: function(e){ + // summary: Callback when keypress in the edit box (see template). + // description: + // For autoSave widgets, if Esc/Enter, call cancel/save. + // For non-autoSave widgets, enable save button if the text value is + // different than the original value. + if(this._exitInProgress){ + return; + } + if(this.autoSave){ + if(e.altKey || e.ctrlKey){ return; } + // If Enter/Esc pressed, treat as save/cancel. + if(e.keyCode == dojo.keys.ESCAPE){ + dojo.stopEvent(e); + this._exitInProgress = true; + this.cancel(true); + }else if(e.keyCode == dojo.keys.ENTER){ + dojo.stopEvent(e); + this._exitInProgress = true; + this.save(true); + }else if(e.keyCode == dojo.keys.TAB){ + this._exitInProgress = true; + // allow 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. + setTimeout(dojo.hitch(this, "save", false), 0); + } + }else{ + var _this = this; + // Delay before calling getValue(). + // The delay gives the browser a chance to update the Textarea. + setTimeout( + function(){ + _this.saveButton.setAttribute("disabled", _this.getValue() == _this._initialText); + }, 100); + } + }, + + _onBlur: function(){ + // summary: + // Called when focus moves outside the editor + this.inherited(arguments); + if(this._exitInProgress){ + // when user clicks the "save" button, focus is shifted back to display text, causing this + // function to be called, but in that case don't do anything + return; + } + if(this.autoSave){ + this._exitInProgress = true; + if(this.getValue() == this._initialText){ + this.cancel(false); + }else{ + this.save(false); + } + } + }, + + enableSave: function(){ + // summary: User replacable function returning a Boolean to indicate + // if the Save button should be enabled or not - usually due to invalid conditions + return this.editWidget.isValid ? this.editWidget.isValid() : true; // Boolean + }, + + _onChange: function(){ + // summary: + // Called when the underlying widget fires an onChange event, + // which means that the user has finished entering the value + if(this._exitInProgress){ + // TODO: the onChange event might happen after the return key for an async widget + // like FilteringSelect. Shouldn't be deleting the edit widget on end-of-edit + return; + } + if(this.autoSave){ + this._exitInProgress = true; + this.save(true); + }else{ + // in case the keypress event didn't get through (old problem with Textarea that has been fixed + // in theory) or if the keypress event comes too quickly and the value inside the Textarea hasn't + // been updated yet) + this.saveButton.setAttribute("disabled", (this.getValue() == this._initialText) || !this.enableSave()); + } + }, + + enableSave: function(){ + // summary: User replacable function returning a Boolean to indicate + // if the Save button should be enabled or not - usually due to invalid conditions + return this.editWidget.isValid ? this.editWidget.isValid() : true; + }, + + focus: function(){ + this.editWidget.focus(); + dijit.selectInputText(this.editWidget.focusNode); + } +}); + +} |