dojox
Version:
Dojo eXtensions, a rollup of many useful sub-projects and varying states of maturity – from very stable and robust, to alpha and experimental. See individual projects contain README files for details.
390 lines (338 loc) • 11.6 kB
JavaScript
define([
"dojo/_base/array",
"dojo/_base/declare",
"dojo/_base/event",
"dojo/_base/lang",
"dojo/_base/window",
"dojo/dom-class",
"dojo/dom-construct",
"dojo/dom-attr",
"dojo/touch",
"dijit/_WidgetBase",
"./iconUtils",
"dojo/has",
"dojo/has!dojo-bidi?dojox/mobile/bidi/ValuePickerSlot"
], function(array, declare, event, lang, win, domClass, domConstruct, domAttr, touch, WidgetBase, iconUtils, has, BidiValuePickerSlot){
// module:
// dojox/mobile/ValuePickerSlot
var ValuePickerSlot = declare(has("dojo-bidi") ? "dojox.mobile.NonBidiValuePickerSlot" : "dojox.mobile.ValuePickerSlot", WidgetBase, {
// summary:
// A widget representing one slot of a ValuePicker widget.
// items: Array
// An array of array of key-label pairs
// (e.g. [[0, "Jan"], [1,"Feb"], ...]). If key values for each label
// are not necessary, labels can be used instead.
items: [],
// labels: String[]
// An array of labels to be displayed on the value picker
// (e.g. ["Jan","Feb",...]). This is a simplified version of the
// items property.
labels: [],
// labelFrom: Number
// The start value of display values of the value picker. This
// parameter is especially useful when value picker has serial
// values.
labelFrom: 0,
// labelTo: Number
// The end value of display values of the value picker.
labelTo: 0,
// zeroPad: Number
// Length of zero padding numbers.
// Ex. zeroPad=2 -> "00", "01", ...
// Ex. zeroPad=3 -> "000", "001", ...
zeroPad: 0,
// value: String
// The initial value of the value picker.
value: "",
// step: Number
// The steps between labelFrom and labelTo.
step: 1,
// readOnly: [const] Boolean
// A flag used to indicate if the input field is readonly or not.
// Note that changing the value of the property after the widget
// creation has no effect.
readOnly: false,
// tabIndex: String
// Tabindex setting for this widget so users can hit the tab key to
// focus on it.
tabIndex: "0",
// key: Object
// The key of the currently selected value in the items array. This is a read-only property.
// Warning: Do not use this property directly, make sure to call the get() method.
/*=====
key: null,
=====*/
// plusBtnLabel: String
// (Accessibility) Text label for plus button
plusBtnLabel: "",
// plusBtnLabelRef: String
// (Accessibility) Reference to a node id containing text label for plus button
plusBtnLabelRef: "",
// minusBtnLabel: String
// (Accessibility) Text label for minus button
minusBtnLabel: "",
// minusBtnLabelRef: String
// (Accessibility) Reference to a node id containing text label for minus button
minusBtnLabelRef: "",
/* internal properties */
baseClass: "mblValuePickerSlot",
buildRendering: function(){
this.inherited(arguments);
this.initLabels();
if(this.labels.length > 0){
this.items = [];
for(var i = 0; i < this.labels.length; i++){
this.items.push([i, this.labels[i]]);
}
}
this.plusBtnNode = domConstruct.create("div", {
className: "mblValuePickerSlotPlusButton mblValuePickerSlotButton",
title: "+"
}, this.domNode);
this.plusIconNode = domConstruct.create("div", {
className: "mblValuePickerSlotIcon"
}, this.plusBtnNode);
iconUtils.createIcon("mblDomButtonGrayPlus", null, this.plusIconNode);
this.inputAreaNode = domConstruct.create("div", {
className: "mblValuePickerSlotInputArea"
}, this.domNode);
this.inputNode = domConstruct.create("input", {
className: "mblValuePickerSlotInput",
readonly: this.readOnly
}, this.inputAreaNode);
this.minusBtnNode = domConstruct.create("div", {
className: "mblValuePickerSlotMinusButton mblValuePickerSlotButton",
title: "-"
}, this.domNode);
this.minusIconNode = domConstruct.create("div", {
className: "mblValuePickerSlotIcon"
}, this.minusBtnNode);
iconUtils.createIcon("mblDomButtonGrayMinus", null, this.minusIconNode);
domAttr.set(this.plusBtnNode, "role", "button"); //a11y
this._setPlusBtnLabelAttr(this.plusBtnLabel);
this._setPlusBtnLabelRefAttr(this.plusBtnLabelRef);
domAttr.set(this.inputNode, "role", "textbox");
var registry = require("dijit/registry");
var inputAreaNodeId = registry.getUniqueId("dojo_mobile__mblValuePickerSlotInput");
domAttr.set(this.inputNode, "id", inputAreaNodeId);
domAttr.set(this.plusBtnNode, "aria-controls", inputAreaNodeId);
domAttr.set(this.minusBtnNode, "role", "button");
domAttr.set(this.minusBtnNode, "aria-controls", inputAreaNodeId);
this._setMinusBtnLabelAttr(this.minusBtnLabel);
this._setMinusBtnLabelRefAttr(this.minusBtnLabelRef);
if(this.value === "" && this.items.length > 0){
this.value = this.items[0][1];
}
this._initialValue = this.value;
},
startup: function(){
if(this._started){ return; }
this._handlers = [
this.connect(this.plusBtnNode, touch.press, "_onTouchStart"),
this.connect(this.minusBtnNode, touch.press, "_onTouchStart"),
this.connect(this.plusBtnNode, "onkeydown", "_onClick"), // for desktop browsers
this.connect(this.minusBtnNode, "onkeydown", "_onClick"), // for desktop browsers
this.connect(this.inputNode, "onchange", lang.hitch(this, function(e){
this._onChange(e);
}))
];
this.inherited(arguments);
this._set(this.plusBtnLabel);
},
initLabels: function(){
// summary:
// Initializes the labels of this slot according to the labelFrom and labelTo properties.
// tags:
// private
if(this.labelFrom !== this.labelTo){
var a = this.labels = [],
zeros = this.zeroPad && Array(this.zeroPad).join("0");
for(var i = this.labelFrom; i <= this.labelTo; i += this.step){
a.push(this.zeroPad ? (zeros + i).slice(-this.zeroPad) : i + "");
}
}
},
spin: function(/*Number*/steps){
// summary:
// Spins the slot as specified by steps.
// find the position of the current value
var pos = -1,
v = this.get("value"),
len = this.items.length;
for(var i = 0; i < len; i++){
if(this.items[i][1] === v){
pos = i;
break;
}
}
if(v == -1){ return; }
pos += steps;
if(pos < 0){ // shift to positive
pos += (Math.abs(Math.ceil(pos / len)) + 1) * len;
}
var newItem = this.items[pos % len];
this.set("value", newItem[1]);
},
setInitialValue: function(){
// summary:
// Sets the initial value using this.value or the first item.
this.set("value", this._initialValue);
},
_onClick: function(e){
// summary:
// Internal handler for click events.
// tags:
// private
if(e && e.type === "keydown" && e.keyCode !== 13){ return; }
if(this.onClick(e) === false){ return; } // user's click action
var node = e.currentTarget;
if(node === this.plusBtnNode || node === this.minusBtnNode){
this._btn = node;
}
this.spin(this._btn === this.plusBtnNode ? 1 : -1);
},
onClick: function(/*Event*/ /*===== e =====*/){
// summary:
// User defined function to handle clicks
// tags:
// callback
},
_onChange: function(e){
// summary:
// Internal handler for the input field's value change events
// tags:
// callback
if(this.onChange(e) === false){ return; } // user's click action
var v = this.get("value"), // text in the input field
a = this.validate(v);
this.set("value", a.length ? a[0][1] : this.value);
},
onChange: function(/*Event*/ /*===== e =====*/){
// summary:
// User defined function to handle value changes
// tags:
// callback
},
validate: function(value){
return array.filter(this.items, function(a){
return (a[1] + "").toLowerCase() == (value + "").toLowerCase();
});
},
_onTouchStart: function(e){
this._conn = [
this.connect(win.body(), touch.move, "_onTouchMove"),
this.connect(win.body(), touch.release, "_onTouchEnd")
];
this.touchStartX = e.touches ? e.touches[0].pageX : e.clientX;
this.touchStartY = e.touches ? e.touches[0].pageY : e.clientY;
domClass.add(e.currentTarget, "mblValuePickerSlotButtonSelected");
this._btn = e.currentTarget;
if(this._timer){
this._timer.remove(); // fail safe
this._timer = null;
}
if(this._interval){
clearInterval(this._interval); // fail safe
this._interval = null;
}
this._timer = this.defer(function(){
this._interval = setInterval(lang.hitch(this, function(){
this.spin(this._btn === this.plusBtnNode ? 1 : -1);
}), 60);
this._timer = null;
}, 1000);
event.stop(e);
},
_onTouchMove: function(e){
var x = e.touches ? e.touches[0].pageX : e.clientX;
var y = e.touches ? e.touches[0].pageY : e.clientY;
if(Math.abs(x - this.touchStartX) >= 4 ||
Math.abs(y - this.touchStartY) >= 4){ // dojox/mobile/scrollable.threshold
if(this._timer){
this._timer.remove(); // fail safe
this._timer = null;
}
if(this._interval){
clearInterval(this._interval); // fail safe
this._interval = null;
}
array.forEach(this._conn, this.disconnect, this);
domClass.remove(this._btn, "mblValuePickerSlotButtonSelected");
}
},
_onTouchEnd: function(e){
if(this._timer){
this._timer.remove();
this._timer = null;
}
array.forEach(this._conn, this.disconnect, this);
domClass.remove(this._btn, "mblValuePickerSlotButtonSelected");
if(this._interval){
clearInterval(this._interval);
this._interval = null;
}else{
this._onClick(e);
}
},
_getKeyAttr: function(){
var val = this.get("value");
var item = array.filter(this.items, function(item){
return item[1] === val;
})[0];
return item ? item[0] : null;
},
_getValueAttr: function(){
// summary:
// Gets the currently selected value.
return this.inputNode.value;
},
_setValueAttr: function(value){
// summary:
// Sets a new value to this slot.
this._spinToValue(value, true);
},
_spinToValue: function(value, applyValue){
// summary:
// Sets a new value to this slot.
// tags:
// private
if(this.get("value") == value){
return; // no change; avoid notification
}
this.inputNode.value = value;
// to avoid unnecessary notifications, applyValue is undefined when
// _spinToValue is called by _DatePickerMixin.
if(applyValue){
this._set("value", value);
}
var parent = this.getParent();
if(parent && parent.onValueChanged){
parent.onValueChanged(this);
}
},
_setTabIndexAttr: function(/*String*/ tabIndex){
this.plusBtnNode.setAttribute("tabIndex", tabIndex);
this.minusBtnNode.setAttribute("tabIndex", tabIndex);
},
_setAria: function(node, attr, value){
if(value){
domAttr.set(node, attr, value);
}else{
domAttr.remove(node, attr);
}
},
_setPlusBtnLabelAttr: function(/*String*/ plusBtnLabel){
this._setAria(this.plusBtnNode, "aria-label", plusBtnLabel);
},
_setPlusBtnLabelRefAttr: function(/*String*/ plusBtnLabelRef){
this._setAria(this.plusBtnNode, "aria-labelledby", plusBtnLabelRef);
},
_setMinusBtnLabelAttr: function(/*String*/ minusBtnLabel){
this._setAria(this.minusBtnNode, "aria-label", minusBtnLabel);
},
_setMinusBtnLabelRefAttr: function(/*String*/ minusBtnLabelRef){
this._setAria(this.minusBtnNode, "aria-labelledby", minusBtnLabelRef);
}
});
return has("dojo-bidi") ? declare("dojox.mobile.ValuePickerSlot", [ValuePickerSlot, BidiValuePickerSlot]) : ValuePickerSlot;
});