983 lines
37 KiB
JavaScript
983 lines
37 KiB
JavaScript
'use strict';
|
|
|
|
/*@
|
|
Ox.Input <f> Input Element
|
|
options <o> Options object
|
|
arrows <b> if true, and type is 'float' or 'int', display arrows
|
|
arrowStep <n> step when clicking arrows
|
|
autocomplete <a> array of possible values, or
|
|
<f> function(key, value, callback), returns one or more values
|
|
autocompleteReplace <b> if true, value is replaced
|
|
autocompleteReplaceCorrect <b> if true, only valid values can be entered
|
|
autocompleteSelect <b> if true, menu is displayed
|
|
autocompleteSelectHighlight <b> if true, value in menu is highlighted
|
|
autocompleteSelectMaxWidth <n|0> Maximum width of autocomplete menu, or 0
|
|
autocompleteSelectSubmit <b> if true, submit input on menu selection
|
|
autocorrect <s|r|f|null> ('email', 'float', 'int', 'phone', 'url'), or
|
|
<r> regexp(value), or
|
|
<f> function(key, value, blur, callback), returns value
|
|
autovalidate <f> --remote validation--
|
|
clear <b> if true, has clear button
|
|
clearTooltip <s|f|''> clear button tooltip
|
|
changeOnKeypress <b> if true, fire change event while typing
|
|
disabled <b> if true, is disabled
|
|
height <n> px (for type='textarea' and type='range' with orientation='horizontal')
|
|
id <s> element id
|
|
key <s> to be passed to autocomplete and autovalidate functions
|
|
label <s|''> Label
|
|
labelWidth <n|64> Label width
|
|
max <n> max value if type is 'int' or 'float'
|
|
min <n> min value if type is 'int' or 'float'
|
|
name <s> will be displayed by autovalidate function ('invalid ' + name)
|
|
overlap <s> '', 'left' or 'right', will cause padding and negative margin
|
|
picker <o> picker object
|
|
rangeOptions <o> range options
|
|
arrows <b>boolean, if true, display arrows
|
|
//arrowStep <n> number, step when clicking arrows
|
|
//arrowSymbols <a> array of two strings
|
|
max <n> number, maximum value
|
|
min <n> number, minimum value
|
|
orientation <s> 'horizontal' or 'vertical'
|
|
step <n> number, step
|
|
thumbValue <b> boolean, if true, value is displayed on thumb, or
|
|
<a> array of strings per value, or
|
|
<f> function(value), returns string
|
|
thumbSize <n> integer, px
|
|
trackGradient <s> string, css gradient for track
|
|
trackImage <s> string, image url, or
|
|
<a> array of image urls
|
|
//trackStep <n> number, 0 for 'scroll here', positive for step
|
|
trackValues <b> boolean
|
|
serialize <f> function used to serialize value in submit
|
|
style <s> 'rounded' or 'square'
|
|
textAlign <s> 'left', 'center' or 'right'
|
|
type <s> 'float', 'int', 'password', 'text', 'textarea'
|
|
value <s> string
|
|
validate <f> remote validation
|
|
width <n> px
|
|
([options[, self]]) -> <o:Ox.Element> Input Element
|
|
autocomplete <!> autocomplete
|
|
autovalidate <!> autovalidate
|
|
blur <!> blur
|
|
cancel <!> cancel
|
|
change <!> input changed event
|
|
clear <!> clear
|
|
focus <!> focus
|
|
insert <!> insert
|
|
submit <!> input submit event
|
|
validate <!> validate
|
|
@*/
|
|
|
|
Ox.Input = function(options, self) {
|
|
|
|
self = self || {};
|
|
var that = Ox.Element({
|
|
element: (options || {}).element || '<div>'
|
|
}, self)
|
|
.defaults({
|
|
arrows: false,
|
|
arrowStep: 1,
|
|
autocomplete: null,
|
|
autocompleteReplace: false,
|
|
autocompleteReplaceCorrect: false,
|
|
autocompleteSelect: false,
|
|
autocompleteSelectHighlight: false,
|
|
autocompleteSelectMax: 0,
|
|
autocompleteSelectMaxWidth: 0,
|
|
autocompleteSelectSubmit: false,
|
|
autovalidate: null,
|
|
changeOnKeypress: false,
|
|
clear: false,
|
|
clearTooltip: '',
|
|
decimals: 0,
|
|
disabled: false,
|
|
height: 16,
|
|
key: '',
|
|
min: -Infinity,
|
|
max: Infinity,
|
|
label: '',
|
|
labelWidth: 64,
|
|
overlap: 'none',
|
|
placeholder: '',
|
|
serialize: null,
|
|
style: 'rounded',
|
|
textAlign: 'left',
|
|
type: 'text',
|
|
validate: null,
|
|
value: '',
|
|
width: 128
|
|
})
|
|
.options(options || {})
|
|
.update(function(key, value) {
|
|
var inputWidth;
|
|
if ([
|
|
'autocomplete', 'autocompleteReplace', 'autocompleteSelect', 'autovalidate'
|
|
].indexOf(key) > -1) {
|
|
if (self.options.autocomplete && self.options.autocompleteSelect) {
|
|
self.$autocompleteMenu = constructAutocompleteMenu();
|
|
}
|
|
self.bindKeyboard = self.options.autocomplete || self.options.autovalidate;
|
|
} else if (key == 'disabled') {
|
|
self.$input.attr({disabled: value});
|
|
} else if (key == 'height') {
|
|
that.css({height: value + 'px'});
|
|
self.$input.css({height: value - 6 + 'px'});
|
|
} else if (key == 'label') {
|
|
self.$label.options({title: value});
|
|
} else if (key == 'labelWidth') {
|
|
self.$label.options({width: value});
|
|
inputWidth = getInputWidth();
|
|
self.$input.css({
|
|
width: inputWidth + 'px'
|
|
});
|
|
self.hasPasswordPlaceholder && self.$placeholder.css({
|
|
width: inputWidth + 'px'
|
|
});
|
|
} else if (key == 'placeholder') {
|
|
setPlaceholder();
|
|
} else if (key == 'value') {
|
|
if (self.options.type == 'float' && self.options.decimals) {
|
|
self.options.value = self.options.value.toFixed(self.options.decimals);
|
|
}
|
|
self.$input.val(self.options.value);
|
|
that.is('.OxError') && that.removeClass('OxError');
|
|
setPlaceholder();
|
|
} else if (key == 'width') {
|
|
that.css({width: self.options.width + 'px'});
|
|
inputWidth = getInputWidth();
|
|
self.$input.css({
|
|
width: inputWidth + 'px'
|
|
});
|
|
self.hasPasswordPlaceholder && self.$placeholder.css({
|
|
width: inputWidth + 'px'
|
|
});
|
|
}
|
|
})
|
|
.addClass(
|
|
'OxInput OxKeyboardFocus OxMedium Ox' + Ox.toTitleCase(self.options.style)
|
|
+ (self.options.type == 'textarea' ? ' OxTextarea' : '') /*+ (
|
|
self.options.overlap != 'none' ?
|
|
' OxOverlap' + Ox.toTitleCase(self.options.overlap) : ''
|
|
)*/
|
|
)
|
|
.css(
|
|
Ox.extend({
|
|
width: self.options.width + 'px'
|
|
}, self.options.type == 'textarea' ? {
|
|
height: self.options.height + 'px'
|
|
} : {})
|
|
)
|
|
.bindEvent(Ox.extend(self.options.type != 'textarea' ? {
|
|
key_enter: submit
|
|
} : {}, {
|
|
key_control_i: insert,
|
|
key_escape: cancel,
|
|
key_shift_enter: submit
|
|
}));
|
|
|
|
if (
|
|
Ox.isArray(self.options.autocomplete)
|
|
&& self.options.autocompleteReplace
|
|
&& self.options.autocompleteReplaceCorrect
|
|
&& self.options.value === ''
|
|
) {
|
|
self.options.value = self.options.autocomplete[0]
|
|
}
|
|
|
|
// fixme: set to min, not 0
|
|
// fixme: validate self.options.value !
|
|
if (self.options.type == 'float') {
|
|
self.decimals = Ox.repeat('0', self.options.decimals || 1)
|
|
Ox.extend(self.options, {
|
|
autovalidate: 'float',
|
|
textAlign: 'right',
|
|
value: self.options.value || '0.' + self.decimals
|
|
});
|
|
} else if (self.options.type == 'int') {
|
|
Ox.extend(self.options, {
|
|
autovalidate: 'int',
|
|
textAlign: 'right',
|
|
value: self.options.value || '0'
|
|
});
|
|
}
|
|
|
|
if (self.options.label) {
|
|
self.$label = Ox.Label({
|
|
overlap: 'right',
|
|
textAlign: 'right',
|
|
title: self.options.label,
|
|
width: self.options.labelWidth
|
|
})
|
|
.css({
|
|
float: 'left' // fixme: use css rule
|
|
})
|
|
.on({
|
|
click: function() {
|
|
// fixme: ???
|
|
// that.focus();
|
|
}
|
|
})
|
|
.appendTo(that);
|
|
}
|
|
|
|
if (self.options.arrows) {
|
|
self.arrows = [];
|
|
self.arrows[0] = [
|
|
Ox.Button({
|
|
overlap: 'right',
|
|
title: 'left',
|
|
type: 'image'
|
|
})
|
|
.css({float: 'left'})
|
|
.on({
|
|
click: function() {
|
|
clickArrow(0);
|
|
}
|
|
})
|
|
.appendTo(that),
|
|
Ox.Button({
|
|
overlap: 'left',
|
|
title: 'right',
|
|
type: 'image'
|
|
})
|
|
.css({float: 'right'})
|
|
.on({
|
|
click: function() {
|
|
clickArrow(0);
|
|
}
|
|
})
|
|
.appendTo(that)
|
|
]
|
|
}
|
|
|
|
self.bindKeyboard = self.options.autocomplete
|
|
|| self.options.autovalidate
|
|
|| self.options.changeOnKeypress;
|
|
self.hasPasswordPlaceholder = self.options.type == 'password'
|
|
&& self.options.placeholder;
|
|
self.inputWidth = getInputWidth();
|
|
|
|
if (self.options.clear) {
|
|
self.$button = Ox.Button({
|
|
overlap: 'left',
|
|
// FIXME: should always be self.options.style, but there
|
|
// is a CSS bug for rounded image buttons
|
|
style: self.options.style == 'squared' ? 'squared' : '',
|
|
title: 'close',
|
|
tooltip: self.options.clearTooltip,
|
|
type: 'image'
|
|
})
|
|
.css({
|
|
float: 'right' // fixme: use css rule
|
|
})
|
|
.bindEvent({
|
|
click: clear,
|
|
doubleclick: submit
|
|
})
|
|
.appendTo(that);
|
|
}
|
|
|
|
self.$input = $(self.options.type == 'textarea' ? '<textarea>' : '<input>')
|
|
.addClass('OxInput OxKeyboardFocus OxMedium Ox' + Ox.toTitleCase(self.options.style))
|
|
.attr({
|
|
disabled: self.options.disabled,
|
|
type: self.options.type == 'password' ? 'password' : 'text'
|
|
})
|
|
.css(
|
|
Ox.extend({
|
|
width: self.inputWidth + 'px',
|
|
textAlign: self.options.textAlign
|
|
}, self.options.type == 'textarea' ? {
|
|
height: self.options.height - 6 + 'px'
|
|
} : {})
|
|
)
|
|
.val(self.options.value)
|
|
.on({
|
|
blur: blur,
|
|
change: change,
|
|
focus: focus
|
|
})
|
|
.appendTo(that);
|
|
|
|
if (self.bindKeyboard) {
|
|
self.$input.on({
|
|
paste: keydown
|
|
});
|
|
}
|
|
|
|
if (self.options.type == 'textarea') {
|
|
Ox.Log('Form', 'TEXTAREA', self.options.width, self.options.height, '...', that.css('width'), that.css('height'), '...', self.$input.css('width'), self.$input.css('height'), '...', self.$input.css('border'))
|
|
}
|
|
|
|
// fixme: is there a better way than this one?
|
|
// should at least go into ox.ui.theme.foo.js
|
|
// probably better: divs in the background
|
|
/*
|
|
if (self.options.type == 'textarea') {
|
|
Ox.extend(self, {
|
|
colors: Ox.Theme() == 'oxlight' ?
|
|
[208, 232, 244] :
|
|
//[0, 16, 32],
|
|
[32, 48, 64],
|
|
colorstops: [8 / self.options.height, self.options.height - 8 / self.options.height]
|
|
});
|
|
self.$input.css({
|
|
background: '-moz-linear-gradient(top, rgb(' +
|
|
[self.colors[0], self.colors[0], self.colors[0]].join(', ') + '), rgb(' +
|
|
[self.colors[1], self.colors[1], self.colors[1]].join(', ') + ') ' +
|
|
Math.round(self.colorstops[0] * 100) + '%, rgb(' +
|
|
[self.colors[1], self.colors[1], self.colors[1]].join(', ') + ') ' +
|
|
Math.round(self.colorstops[1] * 100) + '%, rgb(' +
|
|
[self.colors[2], self.colors[2], self.colors[2]].join(', ') + '))'
|
|
});
|
|
self.$input.css({
|
|
background: '-webkit-linear-gradient(top, rgb(' +
|
|
[self.colors[0], self.colors[0], self.colors[0]].join(', ') + '), rgb(' +
|
|
[self.colors[1], self.colors[1], self.colors[1]].join(', ') + ') ' +
|
|
Math.round(self.colorstops[0] * 100) + '%, rgb(' +
|
|
[self.colors[1], self.colors[1], self.colors[1]].join(', ') + ') ' +
|
|
Math.round(self.colorstops[1] * 100) + '%, rgb(' +
|
|
[self.colors[2], self.colors[2], self.colors[2]].join(', ') + '))'
|
|
});
|
|
}
|
|
*/
|
|
|
|
if (self.hasPasswordPlaceholder) {
|
|
self.$input.hide();
|
|
self.$placeholder = $('<input>')
|
|
.addClass('OxInput OxKeyboardFocus OxMedium Ox' +
|
|
Ox.toTitleCase(self.options.style) +
|
|
' OxPlaceholder')
|
|
.attr({type: 'text'})
|
|
.css({width: self.inputWidth + 'px'})
|
|
.val(self.options.placeholder)
|
|
.on({focus: focus})
|
|
.appendTo(that);
|
|
}
|
|
|
|
if (self.options.autocomplete && self.options.autocompleteSelect) {
|
|
self.$autocompleteMenu = constructAutocompleteMenu();
|
|
}
|
|
|
|
self.options.placeholder && setPlaceholder();
|
|
|
|
function autocomplete(oldValue, oldCursor) {
|
|
|
|
oldValue = Ox.isUndefined(oldValue) ? self.options.value : oldValue;
|
|
oldCursor = Ox.isUndefined(oldCursor) ? cursor() : oldCursor;
|
|
|
|
Ox.Log('AUTO', 'autocomplete', oldValue, oldCursor)
|
|
|
|
if (self.options.value || self.options.autocompleteReplaceCorrect) {
|
|
var id = Ox.uid();
|
|
self.autocompleteId = id;
|
|
if (Ox.isFunction(self.options.autocomplete)) {
|
|
if (self.options.key) {
|
|
self.options.autocomplete(
|
|
self.options.key, self.options.value, autocompleteCallback
|
|
);
|
|
} else {
|
|
self.options.autocomplete(
|
|
self.options.value, autocompleteCallback
|
|
);
|
|
}
|
|
} else {
|
|
autocompleteCallback(autocompleteFunction());
|
|
}
|
|
}
|
|
if (!self.options.value) {
|
|
if (self.options.autocompleteSelect) {
|
|
self.$autocompleteMenu
|
|
.unbindEvent('select')
|
|
.hideMenu();
|
|
self.selectEventBound = false;
|
|
}
|
|
}
|
|
|
|
function autocompleteFunction() {
|
|
return Ox.find(
|
|
self.options.autocomplete,
|
|
self.options.value,
|
|
self.options.autocompleteReplace
|
|
);
|
|
}
|
|
|
|
function autocompleteCallback(values) {
|
|
|
|
if (self.autocompleteId != id) {
|
|
return;
|
|
}
|
|
//Ox.Log('Form', 'autocompleteCallback', values[0], self.options.value, self.options.value.length, oldValue, oldCursor)
|
|
|
|
var length = self.options.value.length,
|
|
newValue, newLength,
|
|
pos = cursor(),
|
|
selected = -1,
|
|
selectEnd = length == 0 || (values[0] && values[0].length),
|
|
value;
|
|
|
|
if (values[0]) {
|
|
if (self.options.autocompleteReplace) {
|
|
newValue = values[0];
|
|
} else {
|
|
newValue = self.options.value;
|
|
}
|
|
} else {
|
|
if (self.options.autocompleteReplaceCorrect) {
|
|
newValue = oldValue;
|
|
} else {
|
|
newValue = self.options.value
|
|
}
|
|
}
|
|
newLength = newValue.length;
|
|
|
|
if (self.options.autocompleteReplace) {
|
|
value = self.options.value;
|
|
self.options.value = newValue;
|
|
self.$input.val(self.options.value);
|
|
if (selectEnd) {
|
|
cursor(length, newLength);
|
|
} else if (self.options.autocompleteReplaceCorrect) {
|
|
cursor(oldCursor);
|
|
} else {
|
|
cursor(pos);
|
|
}
|
|
selected = 0;
|
|
}
|
|
|
|
if (self.options.autocompleteSelect) {
|
|
value = (
|
|
self.options.autocompleteReplace
|
|
? value : self.options.value
|
|
).toLowerCase();
|
|
if (values.length) {
|
|
self.oldCursor = cursor();
|
|
self.oldValue = self.options.value;
|
|
self.$autocompleteMenu.options({
|
|
items: Ox.filter(values, function(v, i) {
|
|
var ret = false;
|
|
if (
|
|
!self.options.autocompleteSelectMax ||
|
|
i < self.options.autocompleteSelectMax
|
|
) {
|
|
if (v.toLowerCase() === value) {
|
|
selected = i;
|
|
}
|
|
ret = true;
|
|
}
|
|
return ret;
|
|
}).map(function(v) {
|
|
return {
|
|
id: v.toLowerCase().replace(/ /g, '_'), // fixme: need function to do lowercase, underscores etc?
|
|
title: self.options.autocompleteSelectHighlight
|
|
? Ox.highlight(v, value, 'OxHighlight') : v
|
|
};
|
|
})
|
|
});
|
|
if (!self.selectEventBound) {
|
|
self.$autocompleteMenu.bindEvent({
|
|
select: selectMenu
|
|
});
|
|
self.selectEventBound = true;
|
|
}
|
|
self.$autocompleteMenu.options({
|
|
selected: selected
|
|
}).showMenu();
|
|
} else {
|
|
self.$autocompleteMenu
|
|
.unbindEvent('select')
|
|
.hideMenu();
|
|
self.selectEventBound = false;
|
|
}
|
|
}
|
|
|
|
that.triggerEvent('autocomplete', {
|
|
value: newValue
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function autovalidate() {
|
|
|
|
var blur, oldCursor, oldValue;
|
|
|
|
if (arguments.length == 1) {
|
|
blur = arguments[0];
|
|
} else {
|
|
blur = false;
|
|
oldValue = arguments[0];
|
|
oldCursor = arguments[1];
|
|
}
|
|
|
|
if (Ox.isFunction(self.options.autovalidate)) {
|
|
if (self.options.key) {
|
|
self.options.autovalidate(
|
|
self.options.key, self.options.value, blur, autovalidateCallback
|
|
);
|
|
} else {
|
|
self.options.autovalidate(
|
|
self.options.value, blur, autovalidateCallback
|
|
);
|
|
}
|
|
} else if (Ox.isRegExp(self.options.autovalidate)) {
|
|
autovalidateCallback(autovalidateFunction(self.options.value));
|
|
} else {
|
|
autovalidateTypeFunction(self.options.type, self.options.value);
|
|
}
|
|
|
|
function autovalidateFunction(value) {
|
|
value = value.split('').map(function(v) {
|
|
return self.options.autovalidate.test(v) ? v : null;
|
|
}).join('');
|
|
return {
|
|
valid: !!value.length,
|
|
value: value
|
|
};
|
|
}
|
|
|
|
function autovalidateTypeFunction(type, value) {
|
|
// fixme: remove trailing zeroes on blur
|
|
// /(^\-?\d+\.?\d{0,8}$)/('-13000.12345678')
|
|
var cursor,
|
|
length,
|
|
regexp = type == 'float' ? new RegExp(
|
|
'(^' + (self.options.min < 0 ? '\\-?' : '') + '\\d+\\.?\\d'
|
|
+ (self.options.decimals ? '{0,' + self.options.decimals + '}' : '*')
|
|
+ '$)'
|
|
) : new RegExp('(^' + (self.options.min < 0 ? '\\-?' : '') + '\\d+$)');
|
|
if (type == 'float') {
|
|
if (value === '') {
|
|
value = '0.' + self.decimals;
|
|
cursor = [0, value.length];
|
|
} else if (value == '-') {
|
|
value = '-0.' + self.decimals;
|
|
cursor = [1, value.length];
|
|
} else if (value == '.') {
|
|
value = '0.' + self.decimals;
|
|
cursor = [2, value.length];
|
|
} else if (!/\./.test(value)) {
|
|
value += '.' + self.decimals;
|
|
cursor = [value.indexOf('.'), value.length];
|
|
} else if (/^\./.test(value)) {
|
|
value = '0' + value;
|
|
cursor = [2, value.length];
|
|
} else if (/\.$/.test(value)) {
|
|
value += self.decimals;
|
|
cursor = [value.indexOf('.') + 1, value.length];
|
|
} else if (/\./.test(value) && self.options.decimals) {
|
|
length = value.split('.')[1].length;
|
|
if (length > self.options.decimals) {
|
|
value = value.slice(0, value.indexOf('.') + 1 + self.options.decimals);
|
|
cursor = [oldCursor[0] + 1, oldCursor[1] + 1];
|
|
} else if (length < self.options.decimals) {
|
|
value += Ox.repeat('0', self.options.decimals - length);
|
|
cursor = [value.indexOf('.') + 1 + length, value.length];
|
|
}
|
|
}
|
|
} else {
|
|
if (value === '') {
|
|
value = '0';
|
|
cursor = [0, 1];
|
|
}
|
|
}
|
|
while (/^0\d/.test(value)) {
|
|
value = value.slice(1);
|
|
}
|
|
if (!regexp.test(value) || value < self.options.min || value > self.options.max) {
|
|
value = oldValue;
|
|
cursor = oldCursor;
|
|
}
|
|
autovalidateCallback({
|
|
cursor: cursor,
|
|
valid: true,
|
|
value: value
|
|
});
|
|
}
|
|
|
|
function autovalidateCallback(data) {
|
|
//Ox.Log('Form', 'autovalidateCallback', newValue, oldCursor)
|
|
self.options.value = data.value;
|
|
self.$input.val(self.options.value);
|
|
!blur && cursor(
|
|
data.cursor || (oldCursor[1] + data.value.length - oldValue.length)
|
|
);
|
|
that.triggerEvent('autovalidate', {
|
|
valid: data.valid,
|
|
value: data.value
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
function autovalidate(blur) {
|
|
Ox.Log('Form', 'autovalidate', self.options.value, blur || false)
|
|
self.autocorrectBlur = blur || false;
|
|
self.autocorrectCursor = cursor();
|
|
Ox.isFunction(self.options.autocorrect) ?
|
|
(self.options.key ? self.options.autocorrect(
|
|
self.options.key,
|
|
self.options.value,
|
|
self.autocorrectBlur,
|
|
autocorrectCallback
|
|
) : self.options.autocorrect(
|
|
self.options.value,
|
|
self.autocorrectBlur,
|
|
autocorrectCallback
|
|
)) : autocorrectCallback(autocorrect(self.options.value));
|
|
}
|
|
|
|
function autovalidateFunction(value) {
|
|
var length = value.length;
|
|
return value.toLowerCase().split('').map(function(v) {
|
|
if (new RegExp(self.options.autocorrect).test(v)) {
|
|
return v;
|
|
} else {
|
|
return null;
|
|
}
|
|
}).join('');
|
|
}
|
|
|
|
*/
|
|
|
|
function blur() {
|
|
that.loseFocus();
|
|
//that.removeClass('OxFocus');
|
|
self.options.value = self.$input.val();
|
|
self.options.autovalidate && autovalidate(true);
|
|
self.options.placeholder && setPlaceholder();
|
|
self.options.validate && validate();
|
|
self.bindKeyboard && Ox.$document.off('keydown', keydown);
|
|
if (!self.cancelled && !self.submitted) {
|
|
that.triggerEvent('blur', {value: self.options.value});
|
|
self.options.value !== self.originalValue && that.triggerEvent('change', {
|
|
value: self.options.value
|
|
});
|
|
}
|
|
}
|
|
|
|
function cancel() {
|
|
self.cancelled = true;
|
|
self.$input.val(self.originalValue).blur();
|
|
self.cancelled = false;
|
|
that.triggerEvent('cancel');
|
|
}
|
|
|
|
function cancelAutocomplete() {
|
|
self.autocompleteId = null;
|
|
}
|
|
|
|
function change() {
|
|
// change gets invoked before blur
|
|
self.options.value = self.$input.val();
|
|
self.originalValue = self.options.value;
|
|
!self.options.changeOnKeypress && that.triggerEvent('change', {
|
|
value: self.options.value
|
|
});
|
|
}
|
|
|
|
function clear() {
|
|
// fixme: set to min, not zero
|
|
// fixme: make this work for password
|
|
if (!self.clearTimeout) {
|
|
that.triggerEvent('clear');
|
|
self.options.value = '';
|
|
self.options.value = self.options.type == 'float' ? '0.0'
|
|
: self.options.type == 'int' ? '0'
|
|
: '';
|
|
self.$input.val(self.options.value);
|
|
cursor(0, self.options.value.length);
|
|
self.options.changeOnKeypress && that.triggerEvent({
|
|
change: {value: self.options.value}
|
|
});
|
|
self.clearTimeout = setTimeout(function() {
|
|
self.clearTimeout = 0;
|
|
}, 500);
|
|
}
|
|
}
|
|
|
|
function clickArrow(i) {
|
|
var originalValue = self.options.value;
|
|
self.options.value = Ox.limit(
|
|
parseFloat(self.options.value) + (i == 0 ? -1 : 1) * self.options.arrowStep,
|
|
self.options.min,
|
|
self.options.max
|
|
).toString();
|
|
if (self.options.value != originalValue) {
|
|
self.$input.val(self.options.value);//.focus();
|
|
that.triggerEvent('change', {value: self.options.value});
|
|
}
|
|
}
|
|
|
|
function clickMenu(data) {
|
|
//Ox.Log('Form', 'clickMenu', data);
|
|
self.options.value = data.title;
|
|
self.$input.val(self.options.value).focus();
|
|
that.gainFocus();
|
|
self.options.autocompleteSelectSubmit && submit();
|
|
}
|
|
|
|
function constructAutocompleteMenu() {
|
|
return Ox.Menu({
|
|
element: self.$input,
|
|
id: self.options.id + 'Menu', // fixme: we do this in other places ... are we doing it the same way? var name?,
|
|
maxWidth: self.options.autocompleteSelectMaxWidth,
|
|
offset: {
|
|
left: 4,
|
|
top: 0
|
|
}
|
|
})
|
|
.addClass('OxAutocompleteMenu OxKeyboardFocus')
|
|
.bindEvent({
|
|
click: clickMenu,
|
|
key_enter: function() {
|
|
if (self.$autocompleteMenu.is(':visible')) {
|
|
self.$autocompleteMenu.hideMenu();
|
|
submit();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function cursor(start, end) {
|
|
/*
|
|
cursor() returns [start, end]
|
|
cursor(start) sets start
|
|
cursor([start, end]) sets start and end
|
|
cursor(start, end) sets start and end
|
|
*/
|
|
var isArray = Ox.isArray(start);
|
|
if (arguments.length == 0) {
|
|
return [self.$input[0].selectionStart, self.$input[0].selectionEnd];
|
|
} else {
|
|
end = isArray ? start[1] : (end ? end : start);
|
|
start = isArray ? start[0] : start;
|
|
//IE8 does not have setSelectionRange
|
|
self.$input[0].setSelectionRange && self.$input[0].setSelectionRange(start, end);
|
|
}
|
|
}
|
|
|
|
function deselectMenu() {
|
|
return;
|
|
//Ox.Log('Form', 'deselectMenu')
|
|
self.options.value = self.oldValue;
|
|
self.$input.val(self.options.value);
|
|
cursor(self.oldCursor);
|
|
}
|
|
|
|
function focus() {
|
|
if (
|
|
// that.hasClass('OxFocus') || // fixme: this is just a workaround, since for some reason, focus() gets called twice on focus
|
|
(self.$autocompleteMenu && self.$autocompleteMenu.is(':visible')) ||
|
|
(self.hasPasswordPlaceholder && self.$input.is(':visible'))
|
|
) {
|
|
return;
|
|
}
|
|
self.originalValue = self.options.value;
|
|
that.gainFocus();
|
|
that.is('.OxError') && that.removeClass('OxError');
|
|
self.options.placeholder && setPlaceholder();
|
|
if (self.bindKeyboard) {
|
|
// fixme: different in webkit and firefox (?), see keyboard handler, need generic function
|
|
Ox.$document.keydown(keydown);
|
|
//Ox.$document.keypress(keypress);
|
|
// temporarily disabled autocomplete on focus
|
|
//self.options.autocompleteSelect && setTimeout(autocomplete, 0); // fixme: why is the timeout needed?
|
|
}
|
|
that.triggerEvent('focus');
|
|
}
|
|
|
|
function getInputWidth() {
|
|
return self.options.width
|
|
- (self.options.arrows ? 32 : 0)
|
|
- (self.options.clear ? 16 : 0)
|
|
- (self.options.label ? self.options.labelWidth : 0)
|
|
- (Ox.contains(['rounded', 'squared'], self.options.style) ? 14 : 6);
|
|
}
|
|
|
|
function insert() {
|
|
var input = self.$input[0];
|
|
that.triggerEvent('insert', {
|
|
end: input.selectionEnd,
|
|
id: that.oxid,
|
|
selection: input.value.slice(input.selectionStart, input.selectionEnd),
|
|
start: input.selectionStart,
|
|
value: input.value
|
|
});
|
|
}
|
|
|
|
function keydown(event) {
|
|
var oldCursor = cursor(),
|
|
oldValue = self.options.value,
|
|
newValue = oldValue.toString().slice(0, oldCursor[0] - 1),
|
|
hasDeletedSelectedEnd = (event.keyCode == 8 || event.keyCode == 46)
|
|
&& oldCursor[0] < oldCursor[1]
|
|
&& oldCursor[1] == oldValue.length;
|
|
if (
|
|
event.keyCode != 9 // tab
|
|
&& (self.options.type == 'textarea' || event.keyCode != 13) // enter
|
|
&& event.keyCode != 27 // escape
|
|
) { // fixme: can't 13 and 27 return false?
|
|
setTimeout(function() { // wait for val to be set
|
|
var value = self.$input.val();
|
|
if ((self.options.autocompleteReplace || self.options.decimals) && hasDeletedSelectedEnd) {
|
|
//Ox.Log('Form', 'HAS DELETED SELECTED END', event.keyCode)
|
|
value = newValue;
|
|
self.$input.val(value);
|
|
}
|
|
if (value != self.options.value) {
|
|
self.options.value = value;
|
|
Ox.Log('AUTO', 'call autocomplete from keydown')
|
|
self.options.autocomplete && autocomplete(oldValue, oldCursor);
|
|
self.options.autovalidate && autovalidate(oldValue, oldCursor);
|
|
self.options.changeOnKeypress && that.triggerEvent({
|
|
change: {value: self.options.value}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
if (
|
|
(event.keyCode == 38 || event.keyCode == 40) // up/down
|
|
&& self.options.autocompleteSelect
|
|
&& self.$autocompleteMenu.is(':visible')
|
|
) {
|
|
//return false;
|
|
}
|
|
}
|
|
|
|
function paste() {
|
|
// fixme: unused
|
|
var data = Ox.Clipboard.paste();
|
|
data.text && self.$input.val(data.text);
|
|
}
|
|
|
|
function selectMenu(data) {
|
|
var pos = cursor();
|
|
//if (self.options.value) {
|
|
//Ox.Log('Form', 'selectMenu', pos, data.title)
|
|
self.options.value = Ox.decodeHTMLEntities(data.title);
|
|
self.$input.val(self.options.value);
|
|
cursor(pos[0], self.options.value.length);
|
|
self.options.changeOnKeypress && that.triggerEvent({
|
|
change: {value: self.options.value}
|
|
});
|
|
//}
|
|
}
|
|
|
|
function setPlaceholder() {
|
|
if (self.options.placeholder) {
|
|
if (that.hasClass('OxFocus')) {
|
|
if (self.options.value === '') {
|
|
if (self.options.type == 'password') {
|
|
self.$placeholder.hide();
|
|
self.$input.show().focus();
|
|
} else {
|
|
self.$input
|
|
.removeClass('OxPlaceholder')
|
|
.val('');
|
|
}
|
|
}
|
|
} else {
|
|
if (self.options.value === '') {
|
|
if (self.options.type == 'password') {
|
|
self.$input.hide();
|
|
self.$placeholder.show();
|
|
} else {
|
|
self.$input
|
|
.addClass('OxPlaceholder')
|
|
.val(self.options.placeholder)
|
|
}
|
|
} else {
|
|
self.$input
|
|
.removeClass('OxPlaceholder')
|
|
.val(self.options.value)
|
|
}
|
|
}
|
|
} else {
|
|
self.$input
|
|
.removeClass('OxPlaceholder')
|
|
.val(self.options.value);
|
|
}
|
|
}
|
|
|
|
function setWidth() {
|
|
|
|
}
|
|
|
|
function submit() {
|
|
cancelAutocomplete();
|
|
self.submitted = true;
|
|
self.$input.blur();
|
|
self.submitted = false;
|
|
//self.options.type == 'textarea' && self.$input.blur();
|
|
that.triggerEvent('submit', {value: self.options.value});
|
|
}
|
|
|
|
function validate() {
|
|
self.options.validate(self.options.value, function(data) {
|
|
that.triggerEvent('validate', data);
|
|
});
|
|
}
|
|
|
|
/*@
|
|
blurInput <f> blurInput
|
|
@*/
|
|
that.blurInput = function() {
|
|
self.$input.blur();
|
|
return that;
|
|
};
|
|
|
|
/*@
|
|
clearInput <f> clearInput
|
|
@*/
|
|
that.clearInput = function() {
|
|
clear();
|
|
return that;
|
|
};
|
|
|
|
/*@
|
|
focusInput <f> Focus input element
|
|
(select) -> <o> Input object
|
|
(start, end) -> <o> Input object
|
|
select <b|false> If true, select all, otherwise position cursor at the end
|
|
start <n> Selection start (can be negative)
|
|
end <n> Selection end (can be negative), or equal to start if omitted
|
|
@*/
|
|
that.focusInput = function() {
|
|
var length = self.$input.val().length,
|
|
start = Ox.isNumber(arguments[0])
|
|
? (arguments[0] < 0 ? length + arguments[0] : arguments[0])
|
|
: arguments[0] ? 0 : length,
|
|
stop = Ox.isNumber(arguments[1])
|
|
? (arguments[1] < 0 ? length + arguments[1] : arguments[1])
|
|
: Ox.isNumber(arguments[0]) ? arguments[0]
|
|
: arguments[0] ? length : 0;
|
|
self.$input.focus();
|
|
cursor(start, stop);
|
|
return that;
|
|
};
|
|
|
|
/*@
|
|
value <f> get/set value
|
|
@*/
|
|
// FIXME: deprecate, options are enough
|
|
that.value = function() {
|
|
if (arguments.length == 0) {
|
|
var value = self.$input.hasClass('OxPlaceholder') ? '' : self.$input.val();
|
|
if (self.options.type == 'float') {
|
|
value = parseFloat(value);
|
|
} else if (self.options.type == 'int') {
|
|
value = parseInt(value); // cannot have leading zero
|
|
}
|
|
return value;
|
|
} else {
|
|
return that.options({value: arguments[0]});
|
|
}
|
|
};
|
|
|
|
return that;
|
|
|
|
};
|