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

1785 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-05-16 08:24:46 +00:00
/*@
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 'integer', 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
autocompleteSelectSubmit <b> if true, submit input on menu selection
autocorrect <s|r|f|null> ('email', 'float', 'integer', '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
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
max <n> max value if type is 'integer' or 'float'
min <n> min value if type is 'integer' 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
textAlign <s> 'left', 'center' or 'right'
type <s> 'float', 'integer', 'password', 'text', 'textarea'
value <s> string
validate <f> remote validation
width <n> px
change <!> input changed event
submit <!> input submit event
@*/
2011-04-22 22:03:10 +00:00
2011-05-16 08:24:46 +00:00
Ox.Input = function(options, self) {
2011-04-22 22:03:10 +00:00
var self = self || {},
2011-04-29 12:40:51 +00:00
that = new Ox.Element({}, self)
2011-04-22 22:03:10 +00:00
.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, {
2011-04-25 09:33:39 +00:00
colors: Ox.Theme() == 'classic' ?
2011-04-22 22:03:10 +00:00
[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
2011-04-22 22:03:10 +00:00
})
.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);
}
2011-05-15 16:18:58 +00:00
that.triggerEvent('blur');
2011-04-22 22:03:10 +00:00
}
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();
2011-04-27 07:13:12 +00:00
that.is('.OxError') && that.removeClass('OxError');
2011-04-22 22:03:10 +00:00
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?
}
2011-05-15 16:18:58 +00:00
that.triggerEvent('focus');
2011-04-22 22:03:10 +00:00
}
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);
});
}
2011-04-29 12:40:51 +00:00
self.setOption = function(key, value) {
2011-04-22 22:03:10 +00:00
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'
});
}
};
2011-05-16 18:05:29 +00:00
that.blurInput = function() {
self.$input.blur();
return that;
};
2011-05-17 07:19:30 +00:00
that.clearInput = function() {
clear();
return that;
}
2011-05-12 20:02:22 +00:00
that.focusInput = function(select) {
select = Ox.isUndefined(select) ? true : select;
2011-04-22 22:03:10 +00:00
self.$input.focus();
2011-05-12 20:02:22 +00:00
if (select) {
cursor(0, self.$input.val().length);
} else {
cursor(self.$input.val().length);
}
2011-04-22 22:03:10 +00:00
return that;
};
2011-05-12 20:02:22 +00:00
// fixme: deprecate, options are enough
2011-04-22 22:03:10 +00:00
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 || {},
2011-04-29 12:40:51 +00:00
that = new Ox.Element({}, self)
2011-04-22 22:03:10 +00:00
.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)
);
}
2011-04-29 12:40:51 +00:00
self.setOption = function(key, value) {
2011-04-22 22:03:10 +00:00
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() {
}
2011-04-29 12:40:51 +00:00
self.setOption = function(key, value) {
2011-04-22 22:03:10 +00:00
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
*/
2011-04-29 12:40:51 +00:00
self.setOption = function(key, value) {
2011-04-22 22:03:10 +00:00
}
return that;
};