//>>built define("dojox/form/ListInput", [ "dojo/_base/kernel", "dojo/_base/lang", "dojo/_base/array", "dojo/_base/json", "dojo/_base/fx", "dojo/_base/window", "dojo/_base/connect", "dojo/dom-class", "dojo/dom-style", "dojo/dom-construct", "dojo/dom-geometry", "dojo/keys", "dijit/_Widget", "dijit/_TemplatedMixin", "dijit/form/_FormValueWidget", "dijit/form/ValidationTextBox", "dijit/InlineEditBox", "dojo/i18n!dijit/nls/common", "dojo/_base/declare" ], function(kernel, lang, array, jsonUtil, fx, win, connect, domClass, domStyle, domConstruct, domGeometry, keys, Widget, TemplatedMixin, FormValueWidget, ValidationTextBox, InlineEditBox, i18nCommon, declare){ kernel.experimental("dojox.form.ListInput"); /*===== Widget = dijit._Widget; Templated = dijit._TemplatedMixin; FormValueWidget = dijit.form._FormValueWidget; ValidationTextBox = dijit.form.ValidationTextBox; =====*/ var ListInput = declare("dojox.form.ListInput", [FormValueWidget], { // summary: // An automatic list maker // description: // you can add value to list with add method. // you can only remove by clicking close button constructor: function(){ this._items = []; if(!lang.isArray(this.delimiter)){ this.delimiter=[this.delimiter]; } var r="("+this.delimiter.join("|")+")?"; this.regExp="^"+this.regExp+r+"$"; }, // inputClass: String // Class which will be used to create the input box. You can implements yours. // It must be a widget, focusNode or domNode must have "onkeydown" event // It must have .attr("value") to get value // It also must impement an (or more) handler for the "onChange" method inputClass: "dojox.form._ListInputInputBox", // inputHandler: String || Array // The widget will connect on all handler to check input value // You can use comma separated list inputHandler: "onChange", // inputProperties: String || Object // Properties used to create input box // If String, it must be a valid JSON inputProperties: { minWidth:50 }, // submitOnlyValidValue: Boolean // If true, only valid value will be submited with form submitOnlyValidValue:true, // useOnBlur: Boolean // If true, onBlur event do a validate (like pressing ENTER) useOnBlur:true, // readOnlyInput: Boolean // if false, the list will be editable // Can only be set when instanciate readOnlyInput: false, // maxItems: Int // Specify max item the list can have // null = infiny maxItems: null, // showCloseButtonWhenValid: Boolean // if true, a close button will be added on valid item showCloseButtonWhenValid: true, // showCloseButtonWhenInvalid: Boolean // if true, a close button will be added on invalid item showCloseButtonWhenInvalid: true, // regExp: [extension protected] String // regular expression string used to validate the input // Do not specify both regExp and regExpGen regExp: ".*", //"[a-zA-Z.-_]+@[a-zA-Z.-_]+.[a-zA-Z]+", // delimiter: String || Array // delimiter for the string. Every match will be splitted // The string can contain only one delimiter delimiter: ",", // constraints: ValidationTextBox.__Constraints // user-defined object needed to pass parameters to the validator functions constraints: {}, baseClass:"dojoxListInput", type: "select", value: "", templateString: "
", // useAnim: Boolean // If true, then item will use an anime to show hide itself useAnim: true, // duration: Integer // Animation duration duration: 500, // easingIn: function // function used to easing on fadeIn end easingIn: null, // easingOut: function // function used to easing on fadeOut end easingOut: null, // readOnlyItem: Boolean // If true, items can be edited // Can only be set when instanciate readOnlyItem: false, // useArrowForEdit: Boolean // If true, arraow left and right can be used for editing // Can only be set when instanciate useArrowForEdit: true, // _items: Array // Array of widget. // Contain all reference to _ListInputInputItem _items: null, // _lastAddedItem: Widget // Contain a reference to the last created item _lastAddedItem: null, // _currentItem: Widget // Widget currently in edition _currentItem: null, // _input: Widget // Widget use for input box _input: null, // _count: Int // Count items _count: 0, postCreate: function(){ // summary: // If closeButton is used, add a class this.inherited(arguments); this._createInputBox(); }, _setReadOnlyInputAttr: function(/*Boolean*/value){ // summary: // Change status and if needed, create the inputbox // tags: // private if(!this._started){ return this._createInputBox(); } this.readOnlyInput = value; this._createInputBox(); }, _setReadOnlyItemAttr: function(/*Boolean*/value){ // summary: // set read only items // tags: // private if(!this._started){ return; } for(var i in this._items){ this._items[i].set("readOnlyItem", value); } }, _createInputBox: function(){ // summary: // Create the input box // tags: // private domClass.toggle(this._inputNode, "dijitHidden", this.readOnlyInput); if(this.readOnlyInput){ return; } if(this._input){ return; } if(this.inputHandler === null){ console.warn("you must add some handler to connect to input field"); return false; } if(lang.isString(this.inputHandler)){ this.inputHandler = this.inputHandler.split(","); } if(lang.isString(this.inputProperties)){ this.inputProperties = jsonUtil.fromJson(this.inputProperties); } var input = lang.getObject(this.inputClass, false); this.inputProperties.regExp = this.regExpGen(this.constraints); this._input = new input(this.inputProperties); this._input.startup(); this._inputNode.appendChild(this._input.domNode); array.forEach(this.inputHandler, function(handler){ this.connect(this._input,lang.trim(handler),"_onHandler"); },this); this.connect(this._input, "onKeyDown", "_inputOnKeyDown"); this.connect(this._input, "onBlur", "_inputOnBlur"); }, compare: function(/*Array*/val1,/*Array*/val2){ // summary: // Compare 2 values (as returned by attr('value') for this widget). // tags: // protected val1 = val1.join(","); val2 = val2.join(","); if(val1 > val2){ return 1; }else if(val1 < val2){ return -1; }else{ return 0; } }, add: function(/*String || Array*/values){ // summary: // Create new list element if(this._count>=this.maxItems && this.maxItems !== null){return;} this._lastValueReported = this._getValues(); if(!lang.isArray(values)){ values = [values]; } for(var i in values){ var value=values[i]; if(value === "" || typeof value != "string"){ continue; } this._count++; var re = new RegExp(this.regExpGen(this.constraints)); this._lastAddedItem = new _ListInputInputItem({ "index" : this._items.length, readOnlyItem : this.readOnlyItem, value : value, regExp: this.regExpGen(this.constraints) }); this._lastAddedItem.startup(); this._testItem(this._lastAddedItem,value); this._lastAddedItem.onClose = lang.hitch(this,"_onItemClose",this._lastAddedItem); this._lastAddedItem.onChange = lang.hitch(this,"_onItemChange",this._lastAddedItem); this._lastAddedItem.onEdit = lang.hitch(this,"_onItemEdit",this._lastAddedItem); this._lastAddedItem.onKeyDown = lang.hitch(this,"_onItemKeyDown",this._lastAddedItem); if(this.useAnim){ domStyle.set(this._lastAddedItem.domNode, {opacity:0, display:""}); } this._placeItem(this._lastAddedItem.domNode); if(this.useAnim){ var anim = fx.fadeIn({ node : this._lastAddedItem.domNode, duration : this.duration, easing : this.easingIn }).play(); } this._items[this._lastAddedItem.index] = this._lastAddedItem; if(this._onChangeActive && this.intermediateChanges){ this.onChange(value); } if(this._count>=this.maxItems && this.maxItems !== null){ break; } } this._updateValues(); if(this._lastValueReported.length==0){ this._lastValueReported = this.value; } if(!this.readOnlyInput){ this._input.set("value", ""); } if(this._onChangeActive){ this.onChange(this.value); } this._setReadOnlyWhenMaxItemsReached(); }, _setReadOnlyWhenMaxItemsReached: function(){ // summary: // set input to readonly when max is reached // tags: // private this.set("readOnlyInput",(this._count>=this.maxItems && this.maxItems !== null)); }, _setSelectNode: function(){ // summary: // put all item in the select (for a submit) // tags: // private this._selectNode.options.length = 0; var values=this.submitOnlyValidValue?this.get("MatchedValue"):this.value; if(!lang.isArray(values)){ return; } array.forEach(values,function(item){ this._selectNode.options[this._selectNode.options.length]=new Option(item,item,true,true); },this); }, _placeItem: function(/*domNode*/node){ // summary: // Place item in the list // tags: // private domConstruct.place(node,this._inputNode,"before"); }, _getCursorPos: function(/*domNode*/node){ // summary: // get current cursor pos // tags: // private if(typeof node.selectionStart != 'undefined'){ return node.selectionStart; } // IE Support try{ node.focus(); }catch(e){} var range = node.createTextRange(); range.moveToBookmark(win.doc.selection.createRange().getBookmark()); range.moveEnd('character', node.value.length); try{ return node.value.length - range.text.length; }finally{ range=null; } }, _onItemClose: function(/*dijit._Widget*/ item){ // summary: // Destroy a list element when close button is clicked // tags: // private if(this.disabled){ return; } if(this.useAnim){ var anim = fx.fadeOut({ node : item.domNode, duration : this.duration, easing : this.easingOut, onEnd : lang.hitch(this, "_destroyItem", item) }).play(); }else{ this._destroyItem(item); } }, _onItemKeyDown: function(/*dijit._Widget*/ item, /*Event*/ e){ // summary: // Call when item get a keypress // tags: // private if(this.readOnlyItem || !this.useArrowForEdit){ return; } if(e.keyCode == keys.LEFT_ARROW && this._getCursorPos(e.target)==0){ this._editBefore(item); }else if(e.keyCode == keys.RIGHT_ARROW && this._getCursorPos(e.target)==e.target.value.length){ this._editAfter(item); } }, _editBefore: function(/*widget*/item){ // summary: // move trough items // tags: // private this._currentItem = this._getPreviousItem(item); if(this._currentItem !== null){ this._currentItem.edit(); } }, _editAfter: function(/*widget*/item){ // summary: // move trough items // tags: // private this._currentItem = this._getNextItem(item); if(this._currentItem !== null){ this._currentItem.edit(); } if(!this.readOnlyInput){ if(this._currentItem === null){ //no more item ? //so edit input (if available) this._focusInput(); } } }, _onItemChange: function(/*dijit._Widget*/ item, /*String*/ value){ // summary: // Call when item value change // tags: // private value = value || item.get("value"); //revalidate content this._testItem(item,value); //update value this._updateValues(); }, _onItemEdit: function(/*dijit._Widget*/ item){ // summary: // Call when item is edited // tags: // private domClass.remove(item.domNode,["dijitError", this.baseClass + "Match", this.baseClass + "Mismatch"]); }, _testItem: function(/*Object*/item,/*String*/value){ // summary: // Change class of item (match, mismatch) // tags: // private var re = new RegExp(this.regExpGen(this.constraints)); var match = ('' + value).match(re); domClass.remove(item.domNode, this.baseClass + (!match ? "Match" : "Mismatch")); domClass.add(item.domNode, this.baseClass + (match ? "Match" : "Mismatch")); domClass.toggle(item.domNode, "dijitError", !match); if((this.showCloseButtonWhenValid && match) || (this.showCloseButtonWhenInvalid && !match)){ domClass.add(item.domNode,this.baseClass+"Closable"); }else { domClass.remove(item.domNode,this.baseClass+"Closable"); } }, _getValueAttr: function(){ // summary: // get all value in then list and return an array // tags: // private return this.value; }, _setValueAttr: function(/*Array || String*/ newValue){ // summary: // Hook so attr('value', value) works. // description: // Sets the value of the widget. // If the value has changed, then fire onChange event, unless priorityChange // is specified as null (or false?) this._destroyAllItems(); this.add(this._parseValue(newValue)); }, _parseValue: function(/*String*/newValue){ // summary: // search for delemiters and split if needed // tags: // private if(typeof newValue == "string"){ if(lang.isString(this.delimiter)){ this.delimiter = [this.delimiter]; } var re = new RegExp("^.*("+this.delimiter.join("|")+").*"); if(newValue.match(re)){ re = new RegExp(this.delimiter.join("|")); return newValue.split(re); } } return newValue; }, regExpGen: function(/*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 }, _setDisabledAttr: function(/*Boolean*/ value){ // summary: // also enable/disable editable items // tags: // private if(!this.readOnlyItem){ for(var i in this._items){ this._items[i].set("disabled", value); } } if(!this.readOnlyInput){ this._input.set("disabled", value); } this.inherited(arguments); }, _onHandler: function(/*String*/value){ // summary: // When handlers of input are fired, this method check input value and (if needed) modify it // tags: // private var parsedValue = this._parseValue(value); if(lang.isArray(parsedValue)){ this.add(parsedValue); } }, _onClick: function(/*event*/e){ // summary: // give focus to inputbox // tags: // private this._focusInput(); }, _focusInput: function(){ // summary: // give focus to input // tags: // private if(!this.readOnlyInput && this._input.focus){ this._input.focus(); } }, _inputOnKeyDown: function(/*event*/e){ // summary: // Used to add keybord interactivity // tags: // private this._currentItem = null; var val = this._input.get("value"); if(e.keyCode == keys.BACKSPACE && val == "" && this.get("lastItem")){ this._destroyItem(this.get("lastItem")); }else if(e.keyCode == keys.ENTER && val != ""){ this.add(val); }else if(e.keyCode == keys.LEFT_ARROW && this._getCursorPos(this._input.focusNode) == 0 && !this.readOnlyItem && this.useArrowForEdit){ this._editBefore(); } }, _inputOnBlur: function(){ // summary: // Remove focus class and act like pressing ENTER key // tags: // private var val = this._input.get('value'); if(this.useOnBlur && val != ""){ this.add(val); } }, _getMatchedValueAttr: function(){ // summary: // get value that match regexp in then list and return an array // tags: // private return this._getValues(lang.hitch(this,this._matchValidator)); }, _getMismatchedValueAttr: function(){ // summary: // get value that mismatch regexp in then list and return an array // tags: // private return this._getValues(lang.hitch(this,this._mismatchValidator)); }, _getValues: function(/*function*/validator){ // summary: // return values with comparator constraint // tags: // private var value = []; validator = validator||this._nullValidator; for(var i in this._items){ var item = this._items[i]; if(item === null){ continue; } var itemValue = item.get("value"); if(validator(itemValue)){ value.push(itemValue); } } return value; }, _nullValidator: function(/*String*/itemValue){ // summary: // return true or false // tags: // private return true; }, _matchValidator: function(/*String*/itemValue){ // summary: // return true or false // tags: // private var re = new RegExp(this.regExpGen(this.constraints)); return itemValue.match(re); }, _mismatchValidator: function(/*String*/itemValue){ // summary: // return true or false // tags: // private var re = new RegExp(this.regExpGen(this.constraints)); return !(itemValue.match(re)); }, _getLastItemAttr: function(){ // summary: // return the last item in list // tags: // private return this._getSomeItem(); }, _getSomeItem: function(/*dijit._Widget*/ item,/*String*/ position){ // summary: // return the item before the one in params // tags: // private item=item||false; position=position||"last"; var lastItem = null; var stop=-1; for(var i in this._items){ if(this._items[i] === null){ continue; } if(position=="before" && this._items[i] === item){ break; } lastItem = this._items[i]; if(position=="first" ||stop==0){ stop=1; break; } if(position=="after" && this._items[i] === item){ stop=0; } } if(position=="after" && stop==0){ lastItem = null; } return lastItem; }, _getPreviousItem: function(/*dijit._Widget*/ item){ // summary: // return the item before the one in params // tags: // private return this._getSomeItem(item,"before"); }, _getNextItem: function(/*dijit._Widget*/ item){ // summary: // return the item before the one in params // tags: // private return this._getSomeItem(item,"after"); }, _destroyItem: function(/*dijit._Widget*/ item, /*Boolean?*/ updateValue){ // summary: // destroy an item // tags: // private this._items[item.index] = null; item.destroy(); this._count--; if(updateValue!==false){ this._updateValues(); this._setReadOnlyWhenMaxItemsReached(); } }, _updateValues: function(){ // summary: // update this.value and the select node // tags: // private this.value = this._getValues(); this._setSelectNode(); }, _destroyAllItems: function(){ // summary: // destroy all items // tags: // private for(var i in this._items){ if(this._items[i]==null){ continue; } this._destroyItem(this._items[i],false); } this._items = []; this._count = 0; this.value = null; this._setSelectNode(); this._setReadOnlyWhenMaxItemsReached(); }, destroy: function(){ // summary: // Destroy all widget this._destroyAllItems(); this._lastAddedItem = null; if(!this._input){ this._input.destroy(); } this.inherited(arguments); } }); var _ListInputInputItem = declare("dojox.form._ListInputInputItem", [Widget, TemplatedMixin], { // summary: // Item created by ListInputInput when delimiter is found // description: // Simple
  • with close button added to ListInputInput when delimiter is found templateString: "
  • ", // closeButtonNode: domNode // ref to the close button node closeButtonNode: null, // readOnlyItem: Boolean // if true, item is editable readOnlyItem: true, baseClass:"dojoxListInputItem", // value: String // value of item value: "", // regExp: [extension protected] String // regular expression string used to validate the input // Do not specify both regExp and regExpGen regExp: ".*", // _editBox: Widget // inline edit box _editBox: null, // _handleKeyDown: handle // handle for the keyDown connect _handleKeyDown: null, attributeMap: { value: { node: "labelNode", type: "innerHTML" } }, postMixInProperties: function(){ var _nlsResources = i18nCommon; lang.mixin(this, _nlsResources); this.inherited(arguments); }, postCreate: function(){ // summary: // Create the close button if needed this.inherited(arguments); this.closeButtonNode = domConstruct.create("span",{ "class" : "dijitButtonNode dijitDialogCloseIcon", title : this.itemClose, onclick: lang.hitch(this, "onClose"), onmouseenter: lang.hitch(this, "_onCloseEnter"), onmouseleave: lang.hitch(this, "_onCloseLeave") }, this.domNode); domConstruct.create("span",{ "class" : "closeText", title : this.itemClose, innerHTML : "x" }, this.closeButtonNode); }, startup: function(){ // summary: // add the edit box this.inherited(arguments); this._createInlineEditBox(); }, _setReadOnlyItemAttr: function(/*Boolean*/value){ // summary: // change the readonly state // tags: // private this.readOnlyItem = value; if(!value){ this._createInlineEditBox(); }else if(this._editBox){ this._editBox.set("disabled", true); } }, _createInlineEditBox: function(){ // summary: // create the inline editbox if needed // tags: // private if(this.readOnlyItem){ return; } if(!this._started){ return; } if(this._editBox){ this._editBox.set("disabled",false); return; } this._editBox = new InlineEditBox({ value:this.value, editor: "dijit.form.ValidationTextBox", editorParams:{ regExp:this.regExp } },this.labelNode); this.connect(this._editBox,"edit","_onEdit"); this.connect(this._editBox,"onChange","_onCloseEdit"); this.connect(this._editBox,"onCancel","_onCloseEdit"); }, edit: function(){ // summary: // enter inline editbox in edit mode if(!this.readOnlyItem){ this._editBox.edit(); } }, _onCloseEdit: function(/*String*/value){ // summary: // call when inline editor close himself // tags: // private domClass.remove(this.closeButtonNode,this.baseClass + "Edited"); connect.disconnect(this._handleKeyDown); this.onChange(value); }, _onEdit: function(){ // summary: // call when inline editor start editing // tags: // private domClass.add(this.closeButtonNode,this.baseClass + "Edited"); this._handleKeyDown = connect.connect(this._editBox.editWidget,"_onKeyPress",this,"onKeyDown"); this.onEdit(); }, _setDisabledAttr: function(/*Boolean*/value){ // summary: // disable inline edit box // tags: // private if(!this.readOnlyItem){ this._editBox.set("disabled", value); } }, _getValueAttr: function(){ // summary: // return value // tags: // private return (!this.readOnlyItem && this._started ? this._editBox.get("value") : this.value); }, destroy: function(){ // summary: // Destroy the inline editbox if(this._editBox){ this._editBox.destroy(); } this.inherited(arguments); }, _onCloseEnter: function(){ // summary: // Called when user hovers over close icon // tags: // private domClass.add(this.closeButtonNode, "dijitDialogCloseIcon-hover"); }, _onCloseLeave: function(){ // summary: // Called when user stops hovering over close icon // tags: // private domClass.remove(this.closeButtonNode, "dijitDialogCloseIcon-hover"); }, onClose: function(){ // summary: // callback when close button is clicked }, onEdit: function(){ // summary: // callback when widget come in edition }, onClick: function(){ // summary: // callback when widget is click }, onChange: function(/*String*/value){ // summary: // callback when widget change its content }, onKeyDown: function(/*String*/value){ // summary: // callback when widget get a KeyDown } }); var _ListInputInputBox = declare("dojox.form._ListInputInputBox", [ValidationTextBox], { // summary: // auto-sized text box // description: // Auto sized textbox based on dijit.form.TextBox // minWidth: Integer // Min width of the input box minWidth:50, // intermediateChanges: Boolean // Fires onChange for each value change or only on demand // Force to true in order to get onChanged called intermediateChanges:true, // regExp: [extension protected] String // regular expression string used to validate the input // Do not specify both regExp and regExpGen regExp: ".*", // _sizer: DomNode // Used to get size of textbox content _sizer:null, onChange: function(/*string*/value){ // summary: // compute content width this.inherited(arguments); if(this._sizer === null){ this._sizer = domConstruct.create("div",{ style : { position : "absolute", left : "-10000px", top : "-10000px" } },win.body()); } this._sizer.innerHTML = value; var w = domGeometry.getContentBox(this._sizer).w + this.minWidth; domGeometry.setContentSize(this.domNode,{ w : w }); }, destroy: function(){ // summary: // destroy the widget domConstruct.destroy(this._sizer); this.inherited(arguments); } }); return ListInput; });