oxjs/source/Ox.UI/js/Form/Ox.Input.js

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;
};