webui-aria2/js/libs/dojox/form/ListInput.js.uncompressed.js

1017 lines
25 KiB
JavaScript
Raw Normal View History

//>>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: "<div dojoAttachPoint=\"focusNode\" class=\"dijit dijitReset dijitLeft dojoxListInput\"><select dojoAttachpoint=\"_selectNode\" multiple=\"multiple\" class=\"dijitHidden\" ${!nameAttrSetting}></select><ul dojoAttachPoint=\"_listInput\"><li dojoAttachEvent=\"onclick: _onClick\" class=\"dijitInputField dojoxListInputNode dijitHidden\" dojoAttachPoint=\"_inputNode\"></li></ul></div>",
// 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 <li> with close button added to ListInputInput when delimiter is found
templateString: "<li class=\"dijit dijitReset dijitLeft dojoxListInputItem\" dojoAttachEvent=\"onclick: onClick\" ><span dojoAttachPoint=\"labelNode\"></span></li>",
// 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;
});