oxjs/source/js/Ox.Input.js

1764 lines
62 KiB
JavaScript
Raw Normal View History

2011-04-23 16:45:50 +00:00
// vim: et:ts=4:sw=4:sts=4:ft=js
2011-04-22 22:03:10 +00:00
Ox.Input = function(options, self) {
/**
options
arrows boolearn, if true, and type is 'float' or 'integer', display arrows
arrowStep number, step when clicking arrows
autocomplete array of possible values, or
function(key, value, callback), returns one or more values
autocompleteReplace boolean, if true, value is replaced
autocompleteReplaceCorrect boolean, if true, only valid values can be entered
autocompleteSelect boolean, if true, menu is displayed
autocompleteSelectHighlight boolean, if true, value in menu is highlighted
autocompleteSelectSubmit boolean, if true, submit input on menu selection
autocorrect string ('email', 'float', 'integer', 'phone', 'url'), or
regexp(value), or
function(key, value, blur, callback), returns value
autovalidate --remote validation--
clear boolean, if true, has clear button
disabled boolean, if true, is disabled
height integer, px (for type='textarea' and type='range' with orientation='horizontal')
id string, element id
key string, to be passed to autocomplete and autovalidate functions
max number, max value if type is 'integer' or 'float'
min number, min value if type is 'integer' or 'float'
name string, will be displayed by autovalidate function ('invalid ' + name)
overlap string, '', 'left' or 'right', will cause padding and negative margin
picker
//rangeOptions
arrows boolean, if true, display arrows
//arrowStep number, step when clicking arrows
//arrowSymbols array of two strings
max number, maximum value
min number, minimum value
orientation 'horizontal' or 'vertical'
step number, step
thumbValue boolean, if true, value is displayed on thumb, or
array of strings per value, or
function(value), returns string
thumbSize integer, px
trackGradient string, css gradient for track
trackImage string, image url, or
array of image urls
//trackStep number, 0 for 'scroll here', positive for step
trackValues boolean
serialize
textAlign 'left', 'center' or 'right'
type 'float', 'integer', 'password', 'text', 'textarea'
value string
validate function, remote validation
width integer, px
methods:
events:
change
submit
*/
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
arrows: false,
arrowStep: 1,
autocomplete: null,
autocompleteReplace: false,
autocompleteReplaceCorrect: false,
autocompleteSelect: false,
autocompleteSelectHighlight: false,
autocompleteSelectSubmit: false,
autovalidate: null,
clear: false,
disabled: false,
key: '',
min: 0,
max: 100,
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) : ''
)*/
)
.bindEvent($.extend(self.options.type == 'textarea' ? {} : {
key_enter: submit
}, {
key_control_v: paste,
key_escape: cancel
}));
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
if (self.options.type == 'float') {
$.extend(self.options, {
autovalidate: 'float',
textAlign: 'right',
value: self.options.value || '0.0'
});
} else if (self.options.type == 'integer') {
$.extend(self.options, {
autovalidate: 'integer',
textAlign: 'right',
value: self.options.value || '0'
});
}
if (self.options.label) {
self.$label = new 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] = [
new Ox.Button({
overlap: 'right',
title: 'left',
type: 'image'
})
.css({
float: 'left'
})
.click(function() {
clickArrow(0);
})
.appendTo(that),
new Ox.Button({
overlap: 'left',
title: 'right',
type: 'image'
})
.css({
float: 'right'
})
.click(function() {
clickArrow(1);
})
.appendTo(that)
]
}
$.extend(self, {
bindKeyboard: self.options.autocomplete || self.options.autovalidate,
hasPasswordPlaceholder: self.options.type == 'password' && self.options.placeholder,
inputWidth: getInputWidth()
});
if (self.options.clear) {
self.$button = new 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 ? 'disabled' : '',
type: self.options.type == 'password' ? 'password' : 'text'
})
.css($.extend({
width: self.inputWidth + 'px',
textAlign: self.options.textAlign
}, self.options.type == 'textarea' ? {
height: self.options.height + 'px',
} : {}))
.val(self.options.value)
.blur(blur)
.change(change)
.focus(focus)
.appendTo(that.$element);
// 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') {
$.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-gradient(linear, left top, left bottom, from(rgb(' +
[self.colors[0], self.colors[0], self.colors[0]].join(', ') + ')), color-stop(' +
self.colorstops[0] + ', ' + 'rgb(' +
[self.colors[1], self.colors[1], self.colors[1]].join(', ') + ')), color-stop( ' +
self.colorstops[1] + ', ' + 'rgb(' +
[self.colors[1], self.colors[1], self.colors[1]].join(', ') + ')), to(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.print('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) {
self.options.autocompleteSelect && self.$autocompleteMenu.hideMenu();
}
function autocompleteFunction() {
var values = Ox.find(self.options.autocomplete, self.options.value);
return self.options.autocompleteReplace ? values[0] :
$.merge(values[0], values[1]);
}
function autocompleteCallback(values) {
//Ox.print('autocompleteCallback', values[0], self.options.value, self.options.value.length, oldValue, oldCursor)
var length = self.options.value.length,
deleted = length <= oldValue.length - (oldCursor[1] - oldCursor[0]),
newValue = values[0] ?
((self.options.autocompleteReplaceCorrect || !deleted) ?
values[0] : self.options.value) :
(self.options.autocompleteReplaceCorrect ? oldValue : self.options.value),
newLength = newValue.length,
pos = cursor(),
selected = -1,
selectEnd = length == 0 || (values[0] && values[0].length),
value;
//Ox.print('selectEnd', selectEnd)
if (self.options.autocompleteReplace) {
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.value.toLowerCase();
if (values.length) {
self.oldCursor = cursor();
self.oldValue = self.options.value;
self.$autocompleteMenu.options({
items: $.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.autocompleteSelectHighlight ? v.replace(
new RegExp('(' + value + ')', 'ig'),
'<span class="OxHighlight">$1</span>'
) : v
};
}),
selected: selected
}).showMenu();
} else {
self.$autocompleteMenu.hideMenu();
}
}
that.triggerEvent('autocomplete', {
value: newValue
});
}
}
function constructAutocompleteMenu() {
var menu = new 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?,
offset: {
left: 4,
top: 0
},
size: self.options.size
})
.bindEvent('click', clickMenu);
if (self.options.autocompleteReplace) {
menu.bindEvent({
deselect: deselectMenu,
select: selectMenu,
});
}
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) {
var regexp = new RegExp(self.options.autovalidate);
return $.map(value.split(''), function(v) {
return regexp.test(v) ? v : null;
}).join('');
}
function autovalidateTypeFunction(type, value) {
// fixme: remove trailing zeroes on blur
var cursor,
regexp = type == 'float' ? /[\d\.]/ : /\d/;
if (type == 'float') {
if (value.indexOf('.') != value.lastIndexOf('.')) {
value = oldValue;
} else {
if (self.autovalidateFloatFlag) {
if (Ox.endsWith(value, '.')) {
value = value.substr(0, value.length - 1);
}
self.autovalidateFloatFlag = false;
}
while (value[0] == '0' && value[1] != '.') {
value = value.substr(1);
}
while (Ox.startsWith(value, '.')) {
if (Ox.startsWith(value, '..')) {
value = value.substr(1);
} else {
value = '0' + value;
}
}
if (Ox.endsWith(value, '.')) {
value += '0';
cursor = [value.length - 1, value.length];
self.autovalidateFloatFlag = true;
}
}
}
value = $.map(value.split(''), function(v) {
return regexp(v) ? v : null;
}).join('');
if (type == 'integer') {
while (value.length > 1 && Ox.startsWith(value, '0')) {
value = value.substr(1);
}
}
if (value === '') {
value = type == 'float' ? '0.0' : '0';
cursor = [0, value.length];
} else if (value > self.options.max) {
value = oldValue;
}
autovalidateCallback(value, cursor);
}
function autovalidateCallback(newValue, newCursor) {
//Ox.print('autovalidateCallback', newValue, oldCursor)
self.options.value = newValue;
self.$input.val(self.options.value);
!blur && cursor(
newCursor || (oldCursor[1] + newValue.length - oldValue.length)
);
that.triggerEvent('autovalidate', {
value: self.options.value
});
}
}
/*
function autovalidate(blur) {
Ox.print('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 $.map(value.toLowerCase().split(''), function(v, i) {
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();
if (self.bindKeyboard) {
Ox.UI.$document.unbind('keydown', keypress);
Ox.UI.$document.unbind('keypress', keypress);
}
that.triggerEvent('blur', {});
}
function cancel() {
self.$input.blur();
}
function change() {
self.options.value = self.$input.val();
that.triggerEvent('change', {
value: self.options.value
});
}
function clear() {
// fixme: set to min, not zero
// fixme: make this work for password
var value = '';
if (self.options.type == 'float') {
value = '0.0';
} else if (self.options.type == 'integer') {
value = '0'
}
self.$input.val(value);
cursor(0, value.length);
}
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(event, data) {
//Ox.print('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() {
self.options.value = self.oldValue;
self.$input.val(self.options.value);
cursor(self.oldCursor);
}
function focus() {
//Ox.print('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;
}
that.gainFocus();
self.options.placeholder && setPlaceholder();
if (self.bindKeyboard) {
//Ox.print('binding...')
// fixme: different in webkit and firefox (?), see keyboard handler, need generic function
Ox.UI.$document.keydown(keypress);
Ox.UI.$document.keypress(keypress);
self.options.autocompleteSelect && setTimeout(autocomplete, 0); // fixme: why is the timeout needed?
}
}
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 keypress(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;
//Ox.print('keypress', event.keyCode)
if (event.keyCode != 9 && event.keyCode != 13 && event.keyCode != 27) { // fixme: can't 13 and 27 return false?
setTimeout(function() { // wait for val to be set
var value = self.$input.val();
if (self.options.autocompleteReplaceCorrect && hasDeletedSelectedEnd) {
//Ox.print(value, '->', newValue);
value = newValue; // value.substr(0, value.length - 1);
self.$input.val(value);
}
if (value != self.options.value) {
self.options.value = value;
self.options.autocomplete && autocomplete(oldValue, oldCursor);
self.options.autovalidate && autovalidate(oldValue, oldCursor);
}
}, 0);
}
if ((event.keyCode == 38 || event.keyCode == 40) && 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(event, data) {
var pos = cursor();
//Ox.print('selectMenu', pos)
self.options.value = data.title
self.$input.val(self.options.value);
cursor(pos[0], self.options.value.length)
}
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().focusInput();
} 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)
}
}
}
}
function setWidth() {
}
function submit() {
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.onChange = 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 ? 'disabled' : ''
});
} else if (key == 'placeholder') {
setPlaceholder();
} else if (key == 'value') {
val = self.$input.val(); // fixme: ??
self.$input.val(value);
setPlaceholder();
} else if (key == 'width') {
inputWidth = getInputWidth();
self.$input.css({
width: inputWidth + 'px'
});
self.hasPasswordPlaceholder && self.$placeholder.css({
width: inputWidth + 'px'
});
}
};
that.focusInput = function() {
self.$input.focus();
cursor(0, self.$input.val().length);
return that;
};
that.value = function() {
return self.$input.hasClass('OxPlaceholder') ? '' : self.$input.val();
};
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);
};
};
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:
*/
var self = self || {},
that = new Ox.Element('div', 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'
});
$.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] = [$.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.print('self.hasMulVal', self.hasMultipleValues);
//Ox.print('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 = [$.extend({
id: '',
size: self.valueWidth
}, self.options.value)];
} else {
self.options.value = [{
id: '',
value: self.options.value,
width: self.valueWidth
}]
}
}
//Ox.print('self.options.value', self.options.value)
self.values = self.options.value.length;
//Ox.print(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.print('self', self);
if (self.keyName) {
that.$key = [];
self.options[self.keyName].forEach(function(key, keyPos) {
//Ox.print('keyPos key', keyPos, key)
if (self.keyName == 'label' && key.label.length == 1) {
that.$key[keyPos] = new Ox.Label({
overlap: 'right',
title: key.label[0].title,
width: self.options.labelWidth
})
.css({
float: 'left'
})
.click(function() {
that.$input[0].focusInput();
})
.appendTo(that);
} else if (key.label.length > 1) {
//Ox.print('key.length > 1')
self.selectKeyId = self.options.id + Ox.toTitleCase(self.keyName) +
(self.options[self.keyName].length == 1 ? '' : keyPos);
//Ox.print('three', self.selectedKey, keyPos, self.selectedKey[keyPos]);
that.$key[keyPos] = new 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 = new Ox.Button({
overlap: 'left',
title: 'close',
type: 'image'
})
.css({
float: 'right'
})
.click(clear)
.appendTo(that);
}
if (self.options.unit.length == 1) {
that.$unit = new Ox.Label({
overlap: 'left',
title: self.options.unit[0].title,
width: self.options.unitWidth
})
.css({
float: 'right'
})
.click(function() {
that.$input[0].focusInput();
})
.appendTo(that);
} else if (self.options.unit.length > 1) {
self.selectUnitId = self.options.id + 'Unit';
that.$unit = new Ox.Select({
id: self.selectUnitId,
items: $.map(self.options.unit, function(unit, i) {
//Ox.print('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] = new 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();
})
.appendTo(that);
}
});
}
that.$input = [];
//self.margin = 0;
self.options.value.forEach(function(v, i) {
//Ox.print('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.print('v:', v, 'id:', id)
if (self.options.type == 'select') {
that.$input[i] = new Ox.Select({
id: v.id,
items: v.items,
width: v.width
}).
css({
float: 'left'
});
} else if (self.options.type == 'range') {
that.$input[i] = new Ox.Range(v)
.css({
float: 'left'
});
} else {
that.$input[i] = new 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($.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(event, data) {
//Ox.print('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();
//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();
}
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.print('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.onChange = 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) {
var self = self || {},
that = new 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.print('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 = new 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($.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.print('v...', v)
var index = v.toLowerCase().indexOf(value);
index > -1 && values[index == 0 ? 0 : 1].push(v);
});
}
return $.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.print('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', $.extend(data, {
blur: blur
}));
}
}
function blur() {
if (!self.options.autosuggest || self.$autosuggestMenu.is(':hidden')) {
//Ox.print('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', keypress);
Ox.UI.$document.unbind('keypress', keypress);
}
}
function cancel() {
that.$element.blur();
}
function change() {
}
function clear() {
that.$element.val('').focus();
}
function clickMenu(event, data) {
//Ox.print('clickMenu', data);
that.$element.val(data.title);
//self.$autosuggestMenu.hideMenu();
self.options.autosuggestSubmit && 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 [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(keypress);
Ox.UI.$document.keypress(keypress);
//Ox.print('calling autosuggest...')
self.options.autosuggest && setTimeout(autosuggestCall, 0); // fixme: why is the timeout needed?
}
}
function keypress(event) {
//Ox.print('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.onChange = 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
*/
var self = self || {},
that = new 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($.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 = new 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.print('----')
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.options({
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.options({
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.onChange = function(key, value) {
}
return that;
};