1942 lines
69 KiB
JavaScript
1942 lines
69 KiB
JavaScript
// vim: et:ts=4:sw=4:sts=4:ft=javascript
|
|
'use strict';
|
|
/*@
|
|
Ox.Input <f:Ox.Element> Input Element
|
|
() -> <f> Input Element
|
|
(options) -> <f> Input Element
|
|
(options, self) -> <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
|
|
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
|
|
|
|
change <!> input changed event
|
|
submit <!> input submit event
|
|
@*/
|
|
|
|
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,
|
|
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 || {})
|
|
.addClass(
|
|
'OxInput OxMedium Ox' + Ox.toTitleCase(self.options.style) /*+ (
|
|
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_v: paste,
|
|
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
|
|
})
|
|
.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'
|
|
})
|
|
.click(function() {
|
|
clickArrow(0);
|
|
})
|
|
.appendTo(that),
|
|
Ox.Button({
|
|
overlap: 'left',
|
|
title: 'right',
|
|
type: 'image'
|
|
})
|
|
.css({
|
|
float: 'right'
|
|
})
|
|
.click(function() {
|
|
clickArrow(1);
|
|
})
|
|
.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',
|
|
title: 'close',
|
|
type: 'image'
|
|
})
|
|
.css({
|
|
float: 'right' // fixme: use css rule
|
|
})
|
|
.click(clear)
|
|
.appendTo(that);
|
|
}
|
|
|
|
self.$input = $(self.options.type == 'textarea' ? '<textarea>' : '<input>')
|
|
.addClass('OxInput 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)
|
|
.blur(blur)
|
|
.change(change)
|
|
.focus(focus)
|
|
.appendTo(that.$element);
|
|
|
|
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() == 'classic' ?
|
|
[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 OxMedium Ox' +
|
|
Ox.toTitleCase(self.options.style) +
|
|
' OxPlaceholder')
|
|
.attr({
|
|
type: 'text'
|
|
})
|
|
.css({
|
|
//float: 'left',
|
|
width: self.inputWidth + 'px'
|
|
})
|
|
.val(self.options.placeholder)
|
|
.focus(focus)
|
|
.appendTo(that.$element);
|
|
}
|
|
|
|
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) {
|
|
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(self.options.value));
|
|
}
|
|
}
|
|
if (!self.options.value) {
|
|
if (self.options.autocompleteSelect) {
|
|
self.$autocompleteMenu
|
|
.unbindEvent('select')
|
|
.hideMenu();
|
|
self.selectEventBound = false;
|
|
}
|
|
}
|
|
|
|
function autocompleteFunction() {
|
|
var values = Ox.find(self.options.autocomplete, self.options.value);
|
|
return self.options.autocompleteReplace
|
|
? values[0] : Ox.merge(values[0], values[1]);
|
|
}
|
|
|
|
function autocompleteCallback(values) {
|
|
|
|
//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;
|
|
|
|
//Ox.Log('Form', 'selectEnd', selectEnd)
|
|
|
|
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.map(values, function(v, i) {
|
|
var ret = null;
|
|
if (
|
|
!self.options.autocompleteSelectMax ||
|
|
i < self.options.autocompleteSelectMax
|
|
) {
|
|
if (value == v.toLowerCase()) {
|
|
selected = i;
|
|
}
|
|
ret = {
|
|
id: v.toLowerCase().replace(/ /g, '_'), // fixme: need function to do lowercase, underscores etc?
|
|
title: self.options.autocompleteSelectHighlight ?
|
|
Ox.highlight(v, value, 'OxHighlight') : v
|
|
};
|
|
}
|
|
return ret;
|
|
})
|
|
});
|
|
if (!self.selectEventBound) {
|
|
self.$autocompleteMenu.bindEvent({
|
|
select: selectMenu,
|
|
});
|
|
self.selectEventBound = true;
|
|
}
|
|
Ox.Log('AUTO', 'show menu')
|
|
self.$autocompleteMenu.options({
|
|
selected: selected
|
|
}).showMenu();
|
|
} else {
|
|
self.$autocompleteMenu
|
|
.unbindEvent('select')
|
|
.hideMenu();
|
|
self.selectEventBound = false;
|
|
}
|
|
}
|
|
|
|
that.triggerEvent('autocomplete', {
|
|
value: newValue
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function constructAutocompleteMenu() {
|
|
var menu = 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
|
|
}
|
|
//size: self.options.size
|
|
})
|
|
.addClass('OxAutocompleteMenu')
|
|
.bindEvent({
|
|
click: clickMenu,
|
|
});
|
|
return menu;
|
|
}
|
|
|
|
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.substr(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.substr(1, value.length);
|
|
}
|
|
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.UI.$document.unbind('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
|
|
});
|
|
}
|
|
/*
|
|
self.options.value !== self.originalValue && that.triggerEvent('change', {
|
|
value: self.options.value
|
|
});
|
|
// fixme: for some reason, if options.type is set, no change event fires
|
|
// as a workaround, blur sends a value. remove later...
|
|
!self.cancelled && !self.submitted && that.triggerEvent('blur', {
|
|
value: self.options.value
|
|
});
|
|
*/
|
|
}
|
|
|
|
function cancel() {
|
|
self.cancelled = true;
|
|
self.$input.val(self.originalValue).blur();
|
|
self.cancelled = false;
|
|
that.triggerEvent('cancel');
|
|
}
|
|
|
|
function change() {
|
|
// change gets invoked before blur
|
|
self.options.value = self.$input.val();
|
|
self.originalValue = self.options.value;
|
|
that.triggerEvent('change', {
|
|
value: self.options.value
|
|
});
|
|
}
|
|
|
|
function clear() {
|
|
// fixme: set to min, not zero
|
|
// fixme: make this work for password
|
|
self.options.value = '';
|
|
if (self.options.type == 'float') {
|
|
self.options.value = '0.0';
|
|
} else if (self.options.type == 'int') {
|
|
self.options.value = '0'
|
|
}
|
|
self.$input.val(self.options.value);
|
|
cursor(0, self.options.value.length);
|
|
self.options.changeOnKeypress && that.triggerEvent({
|
|
change: {value: self.options.value}
|
|
});
|
|
}
|
|
|
|
function clickArrow(i) {
|
|
self.options.value = Ox.limit(
|
|
parseFloat(self.options.value) + (i == 0 ? -1 : 1) * self.options.arrowStep,
|
|
self.options.min,
|
|
self.options.max
|
|
).toString();
|
|
self.$input.val(self.options.value);//.focus();
|
|
}
|
|
|
|
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 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;
|
|
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.UI.$document.keydown(keydown);
|
|
//Ox.UI.$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)
|
|
- (self.options.style == 'rounded' ? 14 : 6);
|
|
}
|
|
|
|
function keydown(event) {
|
|
var oldCursor = cursor(),
|
|
oldValue = self.options.value,
|
|
newValue = oldValue.substr(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}
|
|
});
|
|
}
|
|
}, 0);
|
|
}
|
|
if (
|
|
(event.keyCode == 38 || event.keyCode == 40) // up/down
|
|
&& self.options.autocompleteSelect
|
|
&& self.$autocompleteMenu.is(':visible')
|
|
) {
|
|
//return false;
|
|
}
|
|
}
|
|
|
|
function paste() {
|
|
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 = 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() {
|
|
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);
|
|
});
|
|
}
|
|
|
|
self.setOption = function(key, value) {
|
|
var inputWidth, val;
|
|
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 == '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'
|
|
});
|
|
}
|
|
};
|
|
|
|
that.blurInput = function() {
|
|
self.$input.blur();
|
|
return that;
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
// 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;
|
|
|
|
};
|
|
|
|
/**
|
|
delete below
|
|
*/
|
|
|
|
Ox.AutocorrectIntFunction = function(min, max, pad, year) {
|
|
var pad = pad || false,
|
|
year = year || false,
|
|
maxLength = max.toString().length,
|
|
ret = null,
|
|
values = [];
|
|
Ox.range(min, max + 1).forEach(function(v) {
|
|
values.push(v + '');
|
|
pad && v.toString().length < maxLength && values.push(Ox.pad(v, maxLength));
|
|
});
|
|
return function(value, blur, callback) {
|
|
var results;
|
|
if (year && value == '1') {
|
|
value = '1900';
|
|
} else {
|
|
results = Ox.find(values, value);
|
|
value = results[0].length == 1 && results[0][0].length < maxLength ?
|
|
(pad ? Ox.pad(results[0][0], maxLength) : results[0][0]) :
|
|
(results[0].length ? results[0][0] : null);
|
|
}
|
|
callback(value);
|
|
};
|
|
};
|
|
|
|
// fixme: remove
|
|
|
|
Ox.Input_ = function(options, self) {
|
|
|
|
/*
|
|
options:
|
|
clear boolean, clear button, or not
|
|
disabled boolean, disabled, or not
|
|
height height (px), if type is 'textarea'
|
|
id
|
|
label string, or
|
|
array [{ id, title, checked }] (selectable label) or
|
|
array [{ id, label: [{ id, title, checked }], width }] (multiple selectable labels)
|
|
label and placeholder are mutually exclusive
|
|
labelWidth integer (px)
|
|
placeholder string, or
|
|
array [{ id, title, checked }] (selectable placeholder)
|
|
label and placeholder are mutually exclusive
|
|
separator string, or
|
|
array of strings
|
|
to separate multiple values
|
|
separatorWidth integer (px), or
|
|
array of integers
|
|
serialize function
|
|
size 'large', 'medium' or 'small'
|
|
type 'password', 'select' or 'text'
|
|
unit string, or
|
|
array [{ id, title, checked }] (selectable unit)
|
|
unitWidth integer (px)
|
|
value string, or
|
|
array [{ id, value, width }] (multiple values)
|
|
width integer (px)
|
|
methods:
|
|
events:
|
|
*/
|
|
|
|
self = self || {};
|
|
var that = Ox.Element({}, self)
|
|
.defaults({
|
|
autocomplete: null,
|
|
autocorrect: null,
|
|
autosuggest: null,
|
|
autosuggestHighlight: false,
|
|
autosuggestSubmit: false,
|
|
autovalidate: null,
|
|
autovalidateName: 'Value',
|
|
clear: false,
|
|
disabled: false,
|
|
height: 128,
|
|
id: '',
|
|
key: '',
|
|
label: '',
|
|
labelWidth: 64,
|
|
placeholder: '',
|
|
separator: '',
|
|
separatorWidth: 16,
|
|
serialize: null,
|
|
size: 'medium',
|
|
type: 'text',
|
|
unit: '',
|
|
unitWidth: 64,
|
|
value: '',
|
|
width: 128
|
|
})
|
|
.options(options || {})
|
|
.addClass('OxInput Ox' + Ox.toTitleCase(self.options.size))
|
|
.css({
|
|
width: self.options.width + 'px'
|
|
});
|
|
|
|
Ox.extend(self, {
|
|
clearWidth: 16,
|
|
hasMultipleKeys: Ox.isArray(self.options.label) && 'label' in self.options.label[0],
|
|
hasMultipleValues: Ox.isArray(self.options.value) &&
|
|
(self.options.type != 'select' || 'items' in self.options.value[0]),
|
|
hasSelectableKeys: Ox.isArray(self.options.label) || Ox.isArray(self.options.placeholder),
|
|
hasSelectableUnits: Ox.isArray(self.options.unit),
|
|
keyName: self.options.label ? 'label' : (self.options.placeholder ? 'placeholder' : ''),
|
|
placeholderWidth: 16,
|
|
selectedKey: [0], // fixme: only set on demand?
|
|
selectedValue: 0,
|
|
selectedUnit: 0,
|
|
/* valid: autovalidateCall(true) */
|
|
});
|
|
|
|
['autocomplete', 'autocorrect', 'autosuggest', 'autovalidate'].forEach(function(v) {
|
|
//if (!Ox.isFunction(self.options[v])) {
|
|
self.options[v] = {
|
|
'': self.options[v]
|
|
};
|
|
//}
|
|
});
|
|
|
|
if (self.keyName && !self.hasMultipleKeys) {
|
|
self.options[self.keyName] = [Ox.extend({
|
|
id: '',
|
|
label: self.options[self.keyName],
|
|
}, self.keyName == 'label' ? {
|
|
id: '',
|
|
width: self.options.labelWidth
|
|
} : {})];
|
|
if (!self.hasSelectableKeys) {
|
|
self.options[self.keyName][0].label = [{
|
|
id: '',
|
|
title: self.options[self.keyName][0].label
|
|
}];
|
|
}
|
|
}
|
|
if (self.hasSelectableKeys) {
|
|
self.options[self.keyName].forEach(function(key, keyPos) {
|
|
if (key.width) {
|
|
self.options.labelWidth = (keyPos == 0 ? 0 : self.options.labelWidth) + key.width;
|
|
}
|
|
self.selectedKey[keyPos] = 0;
|
|
Ox.forEach(key, function(value, valuePos) {
|
|
if (value.checked) {
|
|
self.selectedKey[keyPos] = valuePos;
|
|
return false;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
self.valueWidth = self.options.width -
|
|
(self.options.label ? self.options.labelWidth : 0) -
|
|
((self.options.placeholder && self.options.placeholder[0].label.length > 1) ? self.placeholderWidth : 0) -
|
|
(self.options.unit ? self.options.unitWidth : 0) -
|
|
(self.options.clear ? self.clearWidth : 0);
|
|
/*
|
|
if (self.hasMultipleValues) {
|
|
self.valueWidth -= Ox.isArray(self.options.separatorWidth) ?
|
|
Ox.sum(self.options.separatorWidth) :
|
|
(self.options.value.length - 1) * self.options.separatorWidth;
|
|
}
|
|
*/
|
|
//Ox.Log('Form', 'self.hasMulVal', self.hasMultipleValues);
|
|
//Ox.Log('Form', 'self.options.value', self.options.value)
|
|
if (!self.hasMultipleValues) {
|
|
if (self.options.type == 'select') {
|
|
self.options.value = [{
|
|
id: '',
|
|
items: self.options.value,
|
|
width: self.valueWidth
|
|
}];
|
|
} else if (self.options.type == 'range') {
|
|
self.options.value = [Ox.extend({
|
|
id: '',
|
|
size: self.valueWidth
|
|
}, self.options.value)];
|
|
} else {
|
|
self.options.value = [{
|
|
id: '',
|
|
value: self.options.value,
|
|
width: self.valueWidth
|
|
}]
|
|
}
|
|
}
|
|
//Ox.Log('Form', 'self.options.value', self.options.value)
|
|
self.values = self.options.value.length;
|
|
//Ox.Log('Form', self.options.id, 'self.values', self.values)
|
|
|
|
if (Ox.isString(self.options.separator)) {
|
|
self.options.separator = $.map(new Array(self.values - 1), function(v, i) {
|
|
return self.options.separator;
|
|
});
|
|
}
|
|
if (Ox.isNumber(self.options.separatorWidth)) {
|
|
self.options.separatorWidth = $.map(new Array(self.values - 1), function(v, i) {
|
|
return self.options.separatorWidth;
|
|
});
|
|
}
|
|
|
|
if (self.options.unit) {
|
|
if (self.hasSelectableUnits) {
|
|
Ox.forEach(self.options.unit, function(unit, pos) {
|
|
if (unit.checked) {
|
|
self.selectedUnit = pos;
|
|
return false;
|
|
}
|
|
});
|
|
} else {
|
|
self.options.unit = [{
|
|
id: '',
|
|
title: self.options.unit
|
|
}];
|
|
}
|
|
}
|
|
|
|
//Ox.Log('Form', 'self', self);
|
|
|
|
if (self.keyName) {
|
|
that.$key = [];
|
|
self.options[self.keyName].forEach(function(key, keyPos) {
|
|
//Ox.Log('Form', 'keyPos key', keyPos, key)
|
|
if (self.keyName == 'label' && key.label.length == 1) {
|
|
that.$key[keyPos] = Ox.Label({
|
|
overlap: 'right',
|
|
title: key.label[0].title,
|
|
width: self.options.labelWidth
|
|
})
|
|
.css({
|
|
float: 'left'
|
|
})
|
|
.click(function() {
|
|
that.$input[0].focusInput(true);
|
|
})
|
|
.appendTo(that);
|
|
} else if (key.label.length > 1) {
|
|
//Ox.Log('Form', 'key.length > 1')
|
|
self.selectKeyId = self.options.id + Ox.toTitleCase(self.keyName) +
|
|
(self.options[self.keyName].length == 1 ? '' : keyPos);
|
|
//Ox.Log('Form', 'three', self.selectedKey, keyPos, self.selectedKey[keyPos]);
|
|
that.$key[keyPos] = Ox.Select({
|
|
id: self.selectKeyId,
|
|
items: $.map(key.label, function(value, valuePos) {
|
|
return {
|
|
checked: valuePos == self.selectedKey[keyPos],
|
|
id: value.id,
|
|
group: self.selectKeyId, // fixme: same id, works here, but should be different
|
|
title: value.title
|
|
};
|
|
}),
|
|
overlap: 'right',
|
|
type: self.options.label ? 'text' : 'image',
|
|
width: self.options.label ? (self.options.label.length == 1 ? self.options.labelWidth : key.width) : self.placeholderWidth
|
|
})
|
|
.css({
|
|
float: 'left'
|
|
})
|
|
.appendTo(that);
|
|
that.bindEvent('change_' + self.selectKeyId, changeKey);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (self.options.clear) {
|
|
that.$clear = Ox.Button({
|
|
overlap: 'left',
|
|
title: 'close',
|
|
type: 'image'
|
|
})
|
|
.css({
|
|
float: 'right'
|
|
})
|
|
.click(clear)
|
|
.appendTo(that);
|
|
}
|
|
|
|
if (self.options.unit.length == 1) {
|
|
that.$unit = Ox.Label({
|
|
overlap: 'left',
|
|
title: self.options.unit[0].title,
|
|
width: self.options.unitWidth
|
|
})
|
|
.css({
|
|
float: 'right'
|
|
})
|
|
.click(function() {
|
|
that.$input[0].focusInput(true);
|
|
})
|
|
.appendTo(that);
|
|
} else if (self.options.unit.length > 1) {
|
|
self.selectUnitId = self.options.id + 'Unit';
|
|
that.$unit = Ox.Select({
|
|
id: self.selectUnitId,
|
|
items: $.map(self.options.unit, function(unit, i) {
|
|
//Ox.Log('Form', 'unit', unit)
|
|
return {
|
|
checked: i == 0,
|
|
id: unit.id,
|
|
group: self.selectUnitId, // fixme: same id, works here, but should be different
|
|
title: unit.title
|
|
};
|
|
}),
|
|
overlap: 'left',
|
|
size: self.options.size,
|
|
width: self.options.unitWidth
|
|
})
|
|
.css({
|
|
float: 'right'
|
|
})
|
|
.appendTo(that);
|
|
}
|
|
|
|
if (self.values) {
|
|
that.$separator = [];
|
|
self.options.value.forEach(function(v, i) {
|
|
if (i < self.values - 1) {
|
|
that.$separator[i] = Ox.Label({
|
|
textAlign: 'center',
|
|
title: self.options.separator[i],
|
|
width: self.options.separatorWidth[i] + 32
|
|
})
|
|
.css({
|
|
float: 'left',
|
|
marginLeft: (v.width - (i == 0 ? 16 : 32)) + 'px'
|
|
})
|
|
.click(function() {
|
|
that.$input[0].focusInput(true);
|
|
})
|
|
.appendTo(that);
|
|
}
|
|
});
|
|
}
|
|
that.$input = [];
|
|
//self.margin = 0;
|
|
self.options.value.forEach(function(v, i) {
|
|
//Ox.Log('Form', 'o k i', self.options, self.keyName, i);
|
|
var id = self.keyName ? $.map(self.selectedKey, function(v, i) {
|
|
return self.options[self.keyName][i].id;
|
|
}).join('.') : '';
|
|
//self.margin -= (i == 0 ? 16 : self.options.value[i - 1].width)
|
|
//Ox.Log('Form', 'v:', v, 'id:', id)
|
|
if (self.options.type == 'select') {
|
|
that.$input[i] = Ox.Select({
|
|
id: v.id,
|
|
items: v.items,
|
|
width: v.width
|
|
}).
|
|
css({
|
|
float: 'left'
|
|
});
|
|
} else if (self.options.type == 'range') {
|
|
that.$input[i] = Ox.Range(v)
|
|
.css({
|
|
float: 'left'
|
|
});
|
|
} else {
|
|
that.$input[i] = Ox.InputElement({
|
|
autocomplete: self.options.autocomplete[id],
|
|
autocorrect: self.options.autocorrect[id],
|
|
autosuggest: self.options.autosuggest[id],
|
|
autosuggestHighlight: self.options.autosuggestHighlight,
|
|
autosuggestSubmit: self.options.autosuggestSubmit,
|
|
autovalidate: self.options.autovalidate[id],
|
|
autovalidateName: self.options.autovalidateName,
|
|
disabled: self.options.disabled,
|
|
height: self.options.height,
|
|
id: self.options.id + 'Input' + Ox.toTitleCase(v.id),
|
|
key: self.hasSelectableKeys ? self.options[self.keyName][0].label[self.selectedKey[0]].id : '',
|
|
parent: that,
|
|
placeholder: self.options.placeholder ? self.options.placeholder[0].label[0].title : '',
|
|
size: self.options.size,
|
|
type: self.options.type,
|
|
value: v.value,
|
|
width: v.width
|
|
});
|
|
}
|
|
that.$input[i]
|
|
.css(Ox.extend({}, self.options.value.length > 1 ? {
|
|
float: 'left',
|
|
marginLeft: -Ox.sum($.map(self.options.value, function(v_, i_) {
|
|
return i_ > i ? self.options.value[i_ - 1].width + self.options.separatorWidth[i_ - 1] : (i_ == i ? 16 : 0);
|
|
}))
|
|
} : {}))
|
|
.appendTo(that);
|
|
});
|
|
|
|
//width(self.options.width);
|
|
|
|
function changeKey(data) {
|
|
//Ox.Log('Form', 'changeKey', data);
|
|
if (data) { // fixme: necessary?
|
|
self.key = {
|
|
id: data.id,
|
|
title: data.value // fixme: should be data.title
|
|
};
|
|
that.$input[0].options({
|
|
key: data.id
|
|
});
|
|
}
|
|
if (self.options.label) {
|
|
//that.$label.html(self.option.title);
|
|
that.$input[0].focusInput(true);
|
|
//autocompleteCall();
|
|
} else {
|
|
that.$input[0].options({
|
|
placeholder: data.value // fixme: should be data.title
|
|
});
|
|
/*
|
|
if (that.$input.hasClass('OxPlaceholder')) {
|
|
that.$input.val(self.key.title);
|
|
//that.$input.focus();
|
|
} else {
|
|
that.$input.focus();
|
|
self.options.autosuggest && autosuggestCall();
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
function changeUnit() {
|
|
that.$input[0].focusInput(true);
|
|
}
|
|
|
|
function clear() {
|
|
that.$input.forEach(function(v, i) {
|
|
v.val('');
|
|
});
|
|
that.$input[0].focusInput();
|
|
}
|
|
|
|
function height(value) {
|
|
var stop = 8 / value;
|
|
if (self.options.type == 'textarea') {
|
|
that.$element
|
|
.height(value)
|
|
.css({
|
|
background: '-moz-linear-gradient(top, rgb(224, 224, 224), rgb(208, 208, 208) ' + (stop * 100) + '%, rgb(208, 208, 208) ' + (100 - stop * 100) + '%, rgb(192, 192, 192))'
|
|
})
|
|
.css({
|
|
background: '-webkit-gradient(linear, left top, left bottom, from(rgb(224, 224, 224)), color-stop(' + stop + ', rgb(208, 208, 208)), color-stop(' + (1 - stop) + ', rgb(208, 208, 208)), to(rgb(192, 192, 192)))'
|
|
});
|
|
that.$input
|
|
.height(value)
|
|
.css({
|
|
background: '-moz-linear-gradient(top, rgb(224, 224, 224), rgb(240, 240, 240) ' + (stop * 100) + '%, rgb(240, 240, 240) ' + (100 - stop * 100) + '%, rgb(255, 255, 255))'
|
|
})
|
|
.css({
|
|
background: '-webkit-gradient(linear, left top, left bottom, from(rgb(224, 224, 224)), color-stop(' + stop + ', rgb(240, 240, 240)), color-stop(' + (1 - stop) + ', rgb(240, 240, 240)), to(rgb(255, 255, 255)))'
|
|
});
|
|
}
|
|
}
|
|
|
|
function selectUnit() {
|
|
self.$selectUnitMenu.show();
|
|
}
|
|
|
|
function submit() {
|
|
//Ox.Log('Form', 'submit')
|
|
var value = that.$input.val();
|
|
that.$input.blur();
|
|
that.triggerEvent('submit', self.options.key ? {
|
|
key: self.options.key,
|
|
value: value
|
|
} : value);
|
|
}
|
|
|
|
function width(value) {
|
|
that.$element.width(value);
|
|
that.$input.width(
|
|
value - (self.options.type == 'textarea' ? 0 : 12) -
|
|
(self.options.label ? self.options.labelWidth : 0) -
|
|
(self.options.placeholder.length > 1 ? 16 : 0) -
|
|
(self.options.unit ? self.options.unitWidth : 0) -
|
|
(self.options.clear ? 16 : 0)
|
|
);
|
|
}
|
|
|
|
self.setOption = function(key, value) {
|
|
if (key == 'height') {
|
|
height(value);
|
|
} else if (key == 'width') {
|
|
width(value);
|
|
}
|
|
};
|
|
|
|
that.changeLabel = function(id) {
|
|
that.$key.html(Ox.getObjectById(self.options.label, id).title);
|
|
self.selectMenu.checkItem(id);
|
|
};
|
|
|
|
return that;
|
|
|
|
}
|
|
|
|
Ox.InputElement_ = function(options, self) {
|
|
|
|
self = self || {};
|
|
var that = Ox.Element(
|
|
options.type == 'textarea' ? 'textarea' : 'input', self
|
|
)
|
|
.defaults({
|
|
autocomplete: null,
|
|
autocorrect: null,
|
|
autosuggest: null,
|
|
autosuggestHighlight: false,
|
|
autosuggestSubmit: false,
|
|
autovalidate: null,
|
|
disabled: false,
|
|
height: 128,
|
|
id: '',
|
|
key: '',
|
|
parent: null,
|
|
placeholder: '',
|
|
size: 'medium',
|
|
type: 'text',
|
|
value: '',
|
|
width: 128
|
|
})
|
|
.options(options || {})
|
|
.addClass('OxInput Ox' + Ox.toTitleCase(self.options.size) + (
|
|
(self.options.placeholder && self.options.value === '') ?
|
|
' OxPlaceholder' : ''
|
|
))
|
|
.attr(self.options.type == 'textarea' ? {} : {
|
|
type: self.options.type
|
|
})
|
|
.css({
|
|
float: 'left',
|
|
width: (self.options.width - 14) + 'px'
|
|
})
|
|
.val(
|
|
(self.options.placeholder && self.options.value === '') ?
|
|
self.options.placeholder : self.options.value
|
|
)
|
|
.blur(blur)
|
|
.change(change)
|
|
.focus(focus);
|
|
|
|
//Ox.Log('Form', 'InputElement self.options', self.options)
|
|
|
|
self.bindKeyboard = self.options.autocomplete || self.options.autocorrect
|
|
|| self.options.autosuggest || self.options.autovalidate;
|
|
|
|
if (self.options.autosuggest) {
|
|
self.autosuggestId = self.options.id + 'Menu'; // fixme: we do this in other places ... are we doing it the same way? var name?
|
|
self.$autosuggestMenu = Ox.Menu({
|
|
element: that.$element,
|
|
id: self.autosuggestId,
|
|
offset: {
|
|
left: 4,
|
|
top: 0
|
|
},
|
|
size: self.options.size
|
|
});
|
|
that.bindEvent('click_' + self.autosuggestId, clickMenu);
|
|
}
|
|
|
|
that.bindEvent(Ox.extend(self.options.type == 'textarea' ? {} : {
|
|
key_enter: submit
|
|
}, {
|
|
key_escape: cancel
|
|
}));
|
|
|
|
function autocomplete(value) {
|
|
var value = value.toLowerCase(),
|
|
ret = '';
|
|
if (value !== '') {
|
|
Ox.forEach(self.options.autocomplete, function(v, i) {
|
|
if (v.toLowerCase().indexOf(value) == 0) {
|
|
ret = v;
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function autocompleteCall() {
|
|
var value = that.$element.val();
|
|
Ox.isFunction(self.options.autocomplete) ?
|
|
self.options.autocomplete(self.options.key ? {
|
|
key: self.options.key,
|
|
value: value
|
|
} : value, autocompleteCallback) :
|
|
autocompleteCallback(autocomplete(value));
|
|
}
|
|
|
|
function autocompleteCallback(value) {
|
|
var pos = cursor()[0];
|
|
if (value) {
|
|
that.$element.val(value);
|
|
cursor(pos, value.length);
|
|
}
|
|
}
|
|
|
|
function autocorrect(value) {
|
|
var length = value.length;
|
|
return $.map(value.toLowerCase().split(''), function(v, i) {
|
|
if (new RegExp(self.options.autocorrect).test(v)) {
|
|
return v
|
|
} else {
|
|
return null;
|
|
}
|
|
}).join('');
|
|
}
|
|
|
|
function autocorrectCall(blur) {
|
|
var blur = blur || false,
|
|
value = that.$element.val(),
|
|
pos = cursor()[0];
|
|
Ox.isFunction(self.options.autocorrect) ?
|
|
self.options.autocorrect(value, blur, autocorrectCallback) :
|
|
autocorrectCallback(autocorrect(value), blue);
|
|
}
|
|
|
|
function autocorrectCallback(value, blur) {
|
|
var length = that.$element.val().length;
|
|
that.$element.val(self.options.value);
|
|
!blur && cursor(pos + value.length - length);
|
|
}
|
|
|
|
function autosuggest(value) {
|
|
var value = value.toLowerCase(),
|
|
values = [[], []];
|
|
if (value !== '') {
|
|
(
|
|
self.options.key ?
|
|
self.options.autosuggest[self.options.key] : self.options.autosuggest
|
|
).forEach(function(v, i) {
|
|
//Ox.Log('Form', 'v...', v)
|
|
var index = v.toLowerCase().indexOf(value);
|
|
index > -1 && values[index == 0 ? 0 : 1].push(v);
|
|
});
|
|
}
|
|
return Ox.merge(values[0], values[1]);
|
|
}
|
|
|
|
function autosuggestCall() {
|
|
var value = that.$element.val();
|
|
Ox.isFunction(self.options.autosuggest) ?
|
|
self.options.autosuggest(self.options.key ? {
|
|
key: self.options.key,
|
|
value: value
|
|
} : value, autosuggestCallback) :
|
|
autosuggestCallback(autosuggest(value));
|
|
}
|
|
|
|
function autosuggestCallback(values) {
|
|
var values = values || [],
|
|
selected = values.length == 1 ? 0 : -1,
|
|
value = that.$element.val().toLowerCase();
|
|
//Ox.Log('Form', 'values', values);
|
|
if (values.length) {
|
|
values = $.map(values, function(v, i) {
|
|
if (value == v.toLowerCase()) {
|
|
selected = i;
|
|
}
|
|
return {
|
|
id: v.toLowerCase().replace(/ /g, '_'), // fixme: need function to do lowercase, underscores etc?
|
|
title: self.options.autosuggestHighlight ? v.replace(
|
|
new RegExp('(' + value + ')', 'ig'),
|
|
'<span class="OxHighlight">$1</span>'
|
|
) : v
|
|
};
|
|
});
|
|
// self.selectMenu && self.selectMenu.hideMenu(); // fixme: need event
|
|
self.$autosuggestMenu.options({
|
|
items: values,
|
|
selected: selected
|
|
}).showMenu();
|
|
} else {
|
|
self.$autosuggestMenu.hideMenu();
|
|
}
|
|
}
|
|
|
|
function autovalidate(value) {
|
|
return {
|
|
valid: self.options.autovalidate(value) != null,
|
|
message: 'Invalid ' + self.options.name
|
|
};
|
|
}
|
|
|
|
function autovalidateCall(blur) {
|
|
var blur = blur || false,
|
|
value = that.$element.val();
|
|
if (value !== '') {
|
|
Ox.isFunction(self.options.autovalidate) ?
|
|
self.options.autovalidate(value, autovalidateCallback) :
|
|
autovalidateCallback(autovalidate(value), blur);
|
|
} else {
|
|
autovalidateCallback({
|
|
blur: blur,
|
|
valid: false,
|
|
message: 'Empty ' + self.options.name
|
|
});
|
|
}
|
|
}
|
|
|
|
function autovalidateCallback(data, blur) {
|
|
if (data.valid != self.valid) {
|
|
self.valid = data.valid;
|
|
that.triggerEvent('validate', Ox.extend(data, {
|
|
blur: blur
|
|
}));
|
|
}
|
|
}
|
|
|
|
function blur() {
|
|
if (!self.options.autosuggest || self.$autosuggestMenu.is(':hidden')) {
|
|
//Ox.Log('Form', 'losing focus...')
|
|
that.loseFocus();
|
|
self.options.parent.removeClass('OxFocus');
|
|
self.options.autocorrect && autocorrectCall(true);
|
|
// self.options.autosuggest && self.$autosuggestMenu.hideMenu();
|
|
self.options.autovalidate && autovalidateCall(true);
|
|
if (self.options.placeholder && that.$element.val() === '') {
|
|
that.$element.addClass('OxPlaceholder').val(self.options.placeholder);
|
|
}
|
|
}
|
|
if (self.bindKeyboard) {
|
|
Ox.UI.$document.unbind('keydown', keydown);
|
|
}
|
|
}
|
|
|
|
function cancel() {
|
|
that.$element.blur();
|
|
}
|
|
|
|
function change() {
|
|
|
|
}
|
|
|
|
function clear() {
|
|
that.$element.val('').focus();
|
|
}
|
|
|
|
function clickMenu(data) {
|
|
//Ox.Log('Form', 'clickMenu', data);
|
|
that.$element.val(data.title);
|
|
//self.$autosuggestMenu.hideMenu();
|
|
self.options.autosuggestSubmit && submit();
|
|
}
|
|
|
|
// FIXME: make this public!
|
|
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 [that.$element[0].selectionStart, that.$element[0].selectionEnd];
|
|
} else {
|
|
start = isArray ? start[0] : start;
|
|
end = isArray ? start[1] : (end ? end : start);
|
|
that.$element[0].setSelectionRange(start, end);
|
|
}
|
|
}
|
|
|
|
function focus() {
|
|
var val = that.$element.val();
|
|
that.gainFocus();
|
|
self.options.parent.addClass('OxFocus');
|
|
if (that.$element.hasClass('OxPlaceholder')) {
|
|
that.$element.val('').removeClass('OxPlaceholder');
|
|
}
|
|
if (self.bindKeyboard) {
|
|
// fixme: different in webkit and firefox (?), see keyboard handler, need generic function
|
|
Ox.UI.$document.keydown(keydown);
|
|
//Ox.Log('Form', 'calling autosuggest...')
|
|
self.options.autosuggest && setTimeout(autosuggestCall, 0); // fixme: why is the timeout needed?
|
|
}
|
|
}
|
|
|
|
function keydown(event) {
|
|
//Ox.Log('Form', 'keyCode', event.keyCode)
|
|
if (event.keyCode != 9 && event.keyCode != 13 && event.keyCode != 27) { // fixme: can't 13 and 27 return false?
|
|
setTimeout(function() { // fixme: document what this timeout is for
|
|
var value = that.$element.val();
|
|
if (value != self.options.value) {
|
|
self.options.value = value;
|
|
self.options.autocomplete && autocompleteCall();
|
|
self.options.autocorrect && autocorrectCall();
|
|
self.options.autosuggest && autosuggestCall();
|
|
self.options.autovalidate && autovalidateCall();
|
|
}
|
|
}, 25);
|
|
}
|
|
}
|
|
|
|
function submit() {
|
|
|
|
}
|
|
|
|
self.setOption = function(key, value) {
|
|
if (key == 'placeholder') {
|
|
that.$element.hasClass('OxPlaceholder') && that.$element.val(value);
|
|
} else if (key == 'value') {
|
|
if (self.options.placeholder) {
|
|
if (value === '') {
|
|
that.$element.addClass('OxPlaceholder').val(self.options.placeholder);
|
|
} else {
|
|
that.$element.removeClass('OxPlaceholder');
|
|
}
|
|
}
|
|
change(); // fixme: keypress too
|
|
}
|
|
}
|
|
|
|
return that;
|
|
|
|
}
|
|
|
|
Ox.Range_ = function(options, self) {
|
|
|
|
/*
|
|
init
|
|
*/
|
|
self = self || {};
|
|
var that = Ox.Element({}, self)
|
|
.defaults({
|
|
animate: false,
|
|
arrows: false,
|
|
arrowStep: 1,
|
|
arrowSymbols: ['left', 'right'],
|
|
max: 100,
|
|
min: 0,
|
|
orientation: 'horizontal',
|
|
step: 1,
|
|
size: 128,
|
|
thumbSize: 16,
|
|
thumbValue: false,
|
|
trackImages: [],
|
|
trackStep: 0,
|
|
value: 0,
|
|
valueNames: null
|
|
})
|
|
.options(Ox.extend(options, {
|
|
arrowStep: options.arrowStep ?
|
|
options.arrowStep : options.step,
|
|
trackImages: $.makeArray(options.trackImages || [])
|
|
}))
|
|
.addClass('OxRange')
|
|
.css({
|
|
width: self.options.size + 'px'
|
|
});
|
|
|
|
// fixme: self. ... ?
|
|
var trackImages = self.options.trackImages.length,
|
|
values = (self.options.max - self.options.min + self.options.step) /
|
|
self.options.step;
|
|
|
|
/*
|
|
construct
|
|
*/
|
|
that.$element
|
|
.css({
|
|
width: self.options.size + 'px'
|
|
});
|
|
if (self.options.arrows) {
|
|
var $arrowDec = Ox.Button({
|
|
style: 'symbol',
|
|
type: 'image',
|
|
value: self.options.arrowSymbols[0]
|
|
})
|
|
.addClass('OxArrow')
|
|
.mousedown(mousedownArrow)
|
|
.click(clickArrowDec)
|
|
.appendTo(that.$element);
|
|
}
|
|
var $track = Ox.Element()
|
|
.addClass('OxTrack')
|
|
.mousedown(clickTrack)
|
|
.appendTo(that.$element);
|
|
|
|
if (trackImages) {
|
|
var width = parseFloat(screen.width / trackImages),
|
|
$image = $('<canvas>')
|
|
.attr({
|
|
width: width * trackImages,
|
|
height: 14
|
|
})
|
|
.addClass('OxImage')
|
|
.appendTo($track.$element),
|
|
c = $image[0].getContext('2d');
|
|
c.mozImageSmoothingEnabled = false; // we may want to remove this later
|
|
self.options.trackImages.forEach(function(v, i) {
|
|
var left = 0;
|
|
$('<img/>')
|
|
.attr({
|
|
src: v
|
|
})
|
|
.load(function() {
|
|
c.drawImage(this, left, 0, self.trackImageWidth[i], 14);
|
|
});
|
|
left += self.trackImageWidth[i];
|
|
});
|
|
}
|
|
var $thumb = Ox.Button({})
|
|
.addClass('OxThumb')
|
|
.appendTo($track);
|
|
//Ox.Log('Form', '----')
|
|
if (self.options.arrows) {
|
|
var $arrowInc = Ox.Button({
|
|
style: 'symbol',
|
|
type: 'image',
|
|
value: self.options.arrowSymbols[1]
|
|
})
|
|
.addClass('OxArrow')
|
|
.mousedown(mousedownArrow)
|
|
.click(clickArrowInc)
|
|
.appendTo(that.$element);
|
|
}
|
|
var rangeWidth, trackWidth, imageWidth, thumbWidth;
|
|
setWidth(self.options.size);
|
|
|
|
/*
|
|
private functions
|
|
*/
|
|
|
|
function clickArrowDec() {
|
|
that.removeClass('OxActive');
|
|
setValue(self.options.value - self.options.arrowStep, 200)
|
|
}
|
|
function clickArrowInc() {
|
|
that.removeClass('OxActive');
|
|
setValue(self.options.value + self.options.arrowStep, 200);
|
|
}
|
|
function clickTrack(e) {
|
|
//Ox.Focus.focus();
|
|
var left = $track.offset().left,
|
|
offset = $(e.target).hasClass('OxThumb') ?
|
|
e.clientX - $thumb.offset().left - thumbWidth / 2 - 2 : 0;
|
|
function val(e) {
|
|
return getVal(e.clientX - left - offset);
|
|
}
|
|
setValue(val(e), 200);
|
|
Ox.UI.$window.mousemove(function(e) {
|
|
setValue(val(e));
|
|
});
|
|
Ox.UI.$window.one('mouseup', function() {
|
|
Ox.UI.$window.unbind('mousemove');
|
|
});
|
|
}
|
|
function getPx(val) {
|
|
var pxPerVal = (trackWidth - thumbWidth - 2) /
|
|
(self.options.max - self.options.min);
|
|
return Math.ceil((val - self.options.min) * pxPerVal + 1);
|
|
}
|
|
function getVal(px) {
|
|
var px = trackWidth / values >= 16 ? px : px - 8,
|
|
valPerPx = (self.options.max - self.options.min) /
|
|
(trackWidth - thumbWidth);
|
|
return Ox.limit(self.options.min +
|
|
Math.floor(px * valPerPx / self.options.step) * self.options.step,
|
|
self.options.min, self.options.max);
|
|
}
|
|
function mousedownArrow() {
|
|
that.addClass('OxActive');
|
|
}
|
|
function setThumb(animate) {
|
|
var animate = typeof animate != 'undefined' ? animate : 0;
|
|
$thumb.animate({
|
|
marginLeft: (getPx(self.options.value) - 2) + 'px',
|
|
width: thumbWidth + 'px'
|
|
}, self.options.animate ? animate : 0, function() {
|
|
if (self.options.thumbValue) {
|
|
$thumb.value(
|
|
self.options.valueNames
|
|
? self.options.valueNames[self.options.value]
|
|
: self.options.value
|
|
);
|
|
}
|
|
});
|
|
}
|
|
function setValue(val, animate) {
|
|
val = Ox.limit(val, self.options.min, self.options.max);
|
|
if (val != self.options.value) {
|
|
that.value(val);
|
|
setThumb(animate);
|
|
that.triggerEvent('change', {value: val});
|
|
}
|
|
}
|
|
function setWidth(width) {
|
|
trackWidth = width - self.options.arrows * 32;
|
|
thumbWidth = Math.max(trackWidth / values - 2, self.options.thumbSize - 2);
|
|
that.$element.css({
|
|
width: (width - 2) + 'px'
|
|
});
|
|
$track.css({
|
|
width: (trackWidth - 2) + 'px'
|
|
});
|
|
if (trackImages) {
|
|
$image.css({
|
|
width: (trackWidth - 2) + 'px'
|
|
});
|
|
}
|
|
$thumb.css({
|
|
width: (thumbWidth - 2) + 'px',
|
|
padding: 0
|
|
});
|
|
setThumb();
|
|
}
|
|
|
|
/*
|
|
shared functions
|
|
*/
|
|
|
|
self.setOption = function(key, value) {
|
|
|
|
}
|
|
|
|
return that;
|
|
|
|
};
|