1
0
Fork 0
forked from 0x2620/oxjs

remove unneeded Ox. prefix from path and file names

This commit is contained in:
rolux 2014-09-26 15:51:50 +02:00
commit 51696562f1
1365 changed files with 43 additions and 43 deletions

View file

@ -0,0 +1,437 @@
'use strict';
/*@
Ox.ArrayEditable <f> Array Editable
options <o> Options object
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Array Editable
add <!> add
blur <!> blur
change <!> change
delete <!> delete
edit <!> edit
insert <!> insert
open <!> open
selectnext <!> selectnext
selectprevious <!> selectprevious
selectnone <!> selectnone
select <!> select
submit <!> submit
@*/
Ox.ArrayEditable = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
clickLink: null,
editable: true,
getSortValue: null,
globalAttributes: [],
highlight: '',
itemName: 'item',
items: [],
maxHeight: void 0,
placeholder: '',
position: -1,
selected: '',
separator: ',',
sort: [],
submitOnBlur: true,
tooltipText: '',
type: 'input',
width: 256
})
.options(options || {})
.update({
highlight: function() {
self.$items.forEach(function($item) {
$item.options({highlight: self.options.highlight})
});
},
items: function() {
if (self.options.selected && getSelectedPosition() == -1) {
selectNone();
}
renderItems(true);
},
placeholder: function() {
if (self.options.items.length == 0) {
self.$items[0].options({
placeholder: self.options.placeholder,
value: ''
});
}
},
selected: function() {
selectItem(self.options.selected);
},
sort: renderItems,
width: function() {
var width = self.options.width;
that.css({width: width - 8 + 'px'}); // 2 x 4 px padding
self.options.type == 'textarea' && self.$items.forEach(function($item) {
$item.options({width: width})
});
}
})
.addClass('OxArrayEditable OxArrayEditable' + Ox.toTitleCase(self.options.type))
.css({width: self.options.width - (self.options.type == 'input' ? 8 : 0) + 'px'}) // 2 x 4 px padding
.bindEvent({
key_delete: deleteItem,
key_enter: function() {
// make sure the newline does
// not end up in the textarea
setTimeout(function() {
that.editItem();
}, 0);
},
key_escape: selectNone,
key_down: self.options.type == 'input' ? selectLast : selectNext,
key_left: self.options.type == 'input' ? selectPrevious : selectFirst,
key_right: self.options.type == 'input' ? selectNext : selectLast,
key_up: self.options.type == 'input' ? selectFirst : selectPrevious,
singleclick: singleclick
});
self.$items = [];
self.editing = false;
renderItems();
self.selected = getSelectedPosition();
function deleteItem() {
if (self.options.editable) {
self.options.items.splice(self.selected, 1);
renderItems();
that.triggerEvent('delete', {
id: self.options.selected
});
self.editing = false;
self.selected = -1;
self.options.selected = '';
}
}
function doubleclick(e) {
// fixme: unused
var $target = $(e.target),
$parent = $target.parent();
if ($parent.is('.OxEditableElement')) {
that.editItem();
} else if (!$target.is('.OxInput')) {
that.triggerEvent('add');
}
}
function getSelectedId() {
return self.selected > -1 ? self.options.items[self.selected].id : '';
}
function getSelectedPosition() {
return Ox.getIndexById(self.options.items, self.options.selected);
}
function renderItems(blur) {
if (self.editing) {
self.options.items[getSelectedPosition()].value = that.find(self.options.type + ':visible').val();
}
that.empty();
if (self.options.items.length == 0) {
self.$items[0] = Ox.Editable({
editable: false,
placeholder: self.options.placeholder,
type: self.options.type,
value: ''
})
.appendTo(that);
} else {
sortItems();
self.options.items.forEach(function(item, i) {
if (i && self.options.type == 'input') {
$('<span>')
.addClass('OxSeparator')
.html(self.options.separator + ' ')
.appendTo(that);
}
self.$items[i] = Ox.Editable({
blurred: self.editing && i == self.selected ? blur : false,
clickLink: self.options.clickLink,
editable: self.options.editable && item.editable,
editing: self.editing && i == self.selected,
globalAttributes: self.options.globalAttributes,
highlight: self.options.highlight,
maxHeight: self.options.maxHeight,
submitOnBlur: self.options.submitOnBlur,
tooltip: (
self.options.tooltipText
? self.options.tooltipText(item) + '<br>'
: ''
) + (
self.options.editable
? Ox._('Click to select') + (
item.editable ? Ox._(', doubleclick to edit') : ''
)
: ''
),
type: self.options.type,
value: item.value,
width: self.options.type == 'input' ? 0 : self.options.width - 9
})
.addClass(item.id == self.options.selected ? 'OxSelected' : '')
.data({id: item.id, position: i})
.bindEvent({
blur: function(data) {
// fixme: remove data
that.gainFocus();
that.triggerEvent('blur', {
id: item.id,
value: data.value
});
self.blurred = true;
setTimeout(function() {
self.blurred = false;
}, 250);
},
cancel: function(data) {
self.editing = false;
that.gainFocus();
data.value === ''
? submitItem(i, '')
: that.triggerEvent('blur', data);
},
change: function(data) {
that.triggerEvent('change', {
id: item.id,
value: data.value
});
},
edit: function() {
if (item.id != self.options.selected) {
selectItem(item.id);
}
self.editing = true;
that.triggerEvent('edit');
},
insert: function(data) {
that.triggerEvent('insert', data);
},
open: function(data) {
that.triggerEvent('open');
},
submit: function(data) {
self.editing = false;
that.gainFocus();
submitItem(i, data.value);
}
})
.appendTo(that);
});
}
//self.editing && that.editItem(blur);
}
function selectFirst() {
if (self.selected > -1) {
self.selected > 0
? selectItem(0)
: that.triggerEvent('selectprevious');
}
}
function selectItem(idOrPosition) {
if (Ox.isString(idOrPosition)) {
self.options.selected = idOrPosition;
self.selected = getSelectedPosition();
} else {
self.selected = idOrPosition;
self.options.selected = getSelectedId();
}
if (/*self.options.selected == '' && */self.editing) {
self.editing = false;
that.blurItem();
}
that.find('.OxSelected').removeClass('OxSelected');
self.selected > -1 && self.$items[self.selected].addClass('OxSelected');
triggerSelectEvent();
}
function selectLast() {
if (self.selected > -1) {
self.selected < self.options.items.length - 1
? selectItem(self.options.items.length - 1)
: that.triggerEvent('selectnext');
}
}
function selectNext() {
if (self.selected > -1) {
self.selected < self.options.items.length - 1
? selectItem(self.selected + 1)
: that.triggerEvent('selectnext');
}
}
function selectNone() {
selectItem(-1);
}
function selectPrevious() {
if (self.selected > -1) {
self.selected > 0
? selectItem(self.selected - 1)
: that.triggerEvent('selectprevious');
}
}
function singleclick(e) {
var $target = $(e.target),
$element = $target.is('.OxEditableElement')
? $target : $target.parents('.OxEditableElement'),
position;
if (!$target.is('.OxInput')) {
if ($element.length) {
position = $element.data('position');
// if clicked on an element
if (position != self.selected) {
// select another item
selectItem(position);
} else if (e.metaKey) {
// or deselect current item
selectNone();
}
} else if (!self.blurred) {
// otherwise, if there wasn't an active input element
if (self.editing) {
// blur if still in editing mode
that.blurItem();
} else {
// otherwise
if (self.selected > -1) {
// deselect selected
selectNone();
} else {
// or trigger event
that.triggerEvent('selectnone');
}
}
}
that.gainFocus();
}
}
function sortItems() {
if (!Ox.isEmpty(self.options.sort)) {
self.options.items = Ox.sortBy(
self.options.items,
self.options.sort,
self.options.getSortValue
? {value: self.options.getSortValue}
: {}
);
self.selected = getSelectedPosition();
}
}
function submitItem(position, value) {
var item = self.options.items[position];
if (value === '') {
deleteItem();
} else {
that.triggerEvent(item.value === value ? 'blur' : 'submit', {
id: item.id,
value: value
});
item.value = value;
}
}
var triggerSelectEvent = Ox.debounce(function() {
that.triggerEvent('select', {
id: self.options.selected
});
}, true);
/*@
addItem <f> addItem
(position, item) -> <o> add item at position
@*/
that.addItem = function(position, item) {
if (self.options.editable) {
self.options.items.splice(position, 0, item);
renderItems();
}
return that;
//that.triggerEvent('add');
/*
self.values = Ox.filter(values, function(value) {
return value;
});
self.values.push('');
renderItems();
Ox.last(self.$items).triggerEvent('doubleclick');
*/
};
/*@
blurItem <f> blurItem
@*/
that.blurItem = function() {
/*
if (self.options.selected) {
self.$items[self.selected].options({editing: false});
} else {
*/
self.editing = false;
self.$items.forEach(function($item) {
$item.options({editing: false});
});
//}
return that;
};
/*@
editItem <f> editItem
@*/
that.editItem = function() {
Ox.Log('AE', 'EDIT ITEM', self.options.editable, self.options.selected);
if (self.options.editable && self.options.selected) {
self.editing = true;
self.$items[self.selected].options({editing: true});
} else if (!self.options.editable) {
that.triggerEvent('open');
}
return that;
};
/*@
reloadItems <f> reloadItems
@*/
that.reloadItems = function() {
renderItems();
return that;
};
/*@
removeItem <f> removeItem
@*/
that.removeItem = function() {
if (self.options.editable && self.options.selected) {
deleteItem();
}
return that;
};
/*
that.submitItem = function() {
if (self.editing) {
self.editing = false;
self.$items[self.selected].options({editing: false});
}
}
*/
return that;
};

View file

@ -0,0 +1,198 @@
'use strict';
/*@
Ox.ArrayInput <f> Array input
options <o> Options object
label <s> string, ''
max <n> integer, maximum number of items, 0 for all
sort <b> fixme: this should probably be removed
value <[]> value
width <n|256> width
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Array input
change <!> change
@*/
Ox.ArrayInput = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
label: '',
max: 0,
sort: false, // fixme: this should probably be removed
value: [],
width: 256
})
.options(options || {})
.update({
value: setValue,
width: setWidths
});
if (self.options.label) {
self.$label = Ox.Label({
title: self.options.label,
width: self.options.width
})
.appendTo(that);
}
self.$element = [];
self.$input = [];
self.$removeButton = [];
self.$addButton = [];
(
self.options.value.length ? self.options.value : ['']
).forEach(function(value, i) {
addInput(i, value);
});
self.options.value = getValue();
function addInput(index, value, focus) {
Ox.Log('Form', 'add', index)
self.$element.splice(index, 0, Ox.Element()
.css({
height: '16px',
marginTop: self.options.label || index > 0 ? '8px' : 0
})
.data({index: index}));
if (index == 0) {
self.$element[index].appendTo(that);
} else {
self.$element[index].insertAfter(self.$element[index - 1]);
}
self.$input.splice(index, 0, Ox.Input({
value: value,
width: self.options.width - 48
})
.css({float: 'left'})
.bindEvent({
change: function(data) {
self.options.sort && data.value !== '' && sortInputs();
self.options.value = getValue();
that.triggerEvent('change', {
value: self.options.value
});
}
})
.appendTo(self.$element[index]));
focus && self.$input[index].focusInput(true);
self.$removeButton.splice(index, 0, Ox.Button({
title: self.$input.length == 1 ? 'close' : 'remove',
type: 'image'
})
.css({float: 'left', marginLeft: '8px'})
.on({
click: function() {
var index = $(this).parent().data('index');
if (self.$input[index].value() !== '') {
self.$input[index].value('');
self.options.value = getValue();
that.triggerEvent('change', {
value: self.options.value
});
}
if (self.$input.length == 1) {
self.$input[0].focusInput(true);
} else {
removeInput(index);
}
}
})
.appendTo(self.$element[index]));
self.$addButton.splice(index, 0, Ox.Button({
disabled: index == self.options.max - 1,
title: 'add',
type: 'image'
})
.css({float: 'left', marginLeft: '8px'})
.on({
click: function() {
var index = $(this).parent().data('index');
addInput(index + 1, '', true);
}
})
.appendTo(self.$element[index]));
updateInputs();
}
function getValue() {
return Ox.map(self.$input, function($input) {
return $input.value();
}).filter(function(value) {
return value !== '';
});
};
function removeInput(index) {
Ox.Log('Form', 'remove', index);
[
'input', 'removeButton', 'addButton', 'element'
].forEach(function(element) {
var key = '$' + element;
self[key][index].remove();
self[key].splice(index, 1);
});
updateInputs();
}
function setValue() {
while (self.$input.length) {
removeInput(0);
}
(
self.options.value.length ? self.options.value : ['']
).forEach(function(value, i) {
addInput(i, value);
});
}
function setWidths() {
self.$label && self.$label.options({width: self.options.width})
self.$element.forEach(function($element, i) {
$element.css({width: self.options.width + 'px'});
self.$input[i].options({width: self.options.width - 48});
});
}
function sortInputs() {
Ox.sort(self.$element, function($element) {
return self.$input[$element.data('index')].value();
}).forEach(function($element) {
$element.detach();
});
self.$element.forEach(function($element, i) {
$element.data({index: i}).appendTo(that);
});
}
function updateInputs() {
self.$element.forEach(function($element, i) {
$element.data({index: i});
self.$removeButton[i].options({
title: self.$element.length == 1 ? 'close' : 'remove'
});
self.$addButton[i].options({
disabled: self.$element.length == self.options.max
});
});
}
/*@
setErrors <f> setErrors
(values) -> <u> set errors
@*/
that.setErrors = function(values) {
self.$input.forEach(function($input) {
$input[
values.indexOf($input.value()) > -1 ? 'addClass' : 'removeClass'
]('OxError');
});
};
return that;
};

198
source/UI/js/Form/Button.js Normal file
View file

@ -0,0 +1,198 @@
'use strict';
/*@
Ox.Button <f> Button Object
options <o> Options object
If a button is both selectable and has two values, its value is the
selected id, and the second value corresponds to the selected state
disabled <b|false> If true, button is disabled
group <b|false> If true, button is part of group
id <s|''> Element id
overlap <s|'none'> 'none', 'left' or 'right'
selectable <b|false> If true, button is selectable
style <s|'default'> 'default', 'checkbox', 'symbol', 'tab' or 'video'
title <s|''> Button title
tooltip <s|[s]|''> Tooltip
type <s|text> 'text' or 'image'
value <b|s|undefined> True for selected, or current value id
values <[o]|[]> [{id, title}, {id, title}]
width <s|'auto'> Button width
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Button Object
click <!> non-selectable button was clicked
change <!> selectable button was clicked
@*/
Ox.Button = function(options, self) {
self = self || {};
var that = Ox.Element('<input>', self)
.defaults({
disabled: false,
group: false,
id: '',
overlap: 'none',
selectable: false,
size: 'medium',
// fixme: 'default' or ''?
style: 'default',
title: '',
tooltip: '',
type: 'text',
value: void 0,
values: [],
width: 'auto'
})
.options(Ox.isArray(options.tooltip) ? Ox.extend(Ox.clone(options), {
tooltip: options.tooltip[0]
}) : options || {})
.update({
disabled: setDisabled,
tooltip: function() {
if (Ox.isArray(self.options.tooltip) && that.$tooltip) {
that.$tooltip.options({
title: self.options.tooltip[self.value]
});
}
},
title: setTitle,
value: function() {
if (self.options.values.length) {
self.options.title = Ox.getObjectById(
self.options.values, self.options.value
).title;
setTitle();
}
self.options.selectable && setSelected();
},
width: function() {
that.css({width: (self.options.width - 14) + 'px'});
}
})
.addClass(
'OxButton Ox' + Ox.toTitleCase(self.options.size)
+ (self.options.disabled ? ' OxDisabled': '')
+ (self.options.selectable && self.options.value ? ' OxSelected' : '')
+ (self.options.style != 'default' ? ' Ox' + Ox.toTitleCase(self.options.style) : '')
+ (self.options.overlap != 'none' ? ' OxOverlap' + Ox.toTitleCase(self.options.overlap) : '')
)
.attr({
disabled: self.options.disabled,
type: self.options.type == 'text' ? 'button' : 'image'
})
.css(self.options.width == 'auto' ? {} : {
width: (self.options.width - 14) + 'px'
})
.on({
click: click,
mousedown: mousedown
});
if (self.options.values.length) {
self.options.values = self.options.values.map(function(value) {
return {
id: value.id || value,
title: value.title || value
};
});
self.value = Ox.getIndexById(self.options.values, self.options.value);
if (self.value == -1) {
self.value = 0;
self.options.value = self.options.values[0].id;
}
self.options.title = self.options.values[self.value].title;
} else if (self.options.selectable) {
self.options.value = self.options.value || false;
}
setTitle();
if (Ox.isArray(options.tooltip)) {
self.options.tooltip = options.tooltip;
// Ox.Element creates tooltip only on mouse over.
// create here to overwrite tooltip title
if (!that.$tooltip) {
that.$tooltip = Ox.Tooltip();
}
that.$tooltip.options({
title: self.options.tooltip[self.value]
});
}
function click() {
if (!self.options.disabled) {
that.$tooltip && that.$tooltip.hide();
that.triggerEvent('click');
if (self.options.values.length || self.options.selectable) {
that.toggle();
that.triggerEvent('change', {value: self.options.value});
}
}
}
function mousedown(e) {
if (self.options.type == 'image' && $.browser.safari) {
// keep image from being draggable
e.preventDefault();
}
}
function setDisabled() {
that.attr({disabled: self.options.disabled});
that[self.options.disabled ? 'addClass' : 'removeClass']('OxDisabled');
self.options.disabled && that.$tooltip && that.$tooltip.hide();
self.options.type == 'image' && setTitle();
}
function setSelected() {
that[self.options.value ? 'addClass' : 'removeClass']('OxSelected');
self.options.type == 'image' && setTitle();
}
function setTitle() {
if (self.options.type == 'image') {
that.attr({
src: Ox.UI.getImageURL(
'symbol' + self.options.title[0].toUpperCase()
+ self.options.title.slice(1),
self.options.style == 'overlay' ? 'overlay' + (
self.options.disabled ? 'Disabled'
: self.options.selectable && self.options.value ? 'Selected'
: ''
)
: self.options.style == 'video' ? 'video'
: self.options.disabled ? 'disabled'
: self.options.selectable && self.options.value ? 'selected'
: ''
)
});
} else {
that.val(self.options.title);
}
}
/*@
toggle <f> toggle
() -> <o> toggle button
@*/
that.toggle = function() {
if (self.options.values.length) {
self.value = 1 - Ox.getIndexById(self.options.values, self.options.value);
self.options.title = self.options.values[self.value].title;
self.options.value = self.options.values[self.value].id;
setTitle();
// fixme: if the tooltip is visible
// we also need to call show()
that.$tooltip && that.$tooltip.options({
title: self.options.tooltip[self.value]
});
} else {
self.options.value = !self.options.value;
}
self.options.selectable && setSelected();
return that;
}
return that;
};

View file

@ -0,0 +1,157 @@
'use strict';
/*@
Ox.ButtonGroup <f> ButtonGroup Object
options <o> Options object
buttons <[o]> array of button options
max <n> integer, maximum number of selected buttons, 0 for all
min <n> integer, minimum number of selected buttons, 0 for none
selectable <b> if true, buttons are selectable
type <s> string, 'image' or 'text'
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> ButtonGroup Object
change <!> {id, value} selection within a group changed
@*/
Ox.ButtonGroup = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
buttons: [],
max: 1,
min: 1,
overlap: 'none',
selectable: false,
size: 'medium',
style: 'default',
type: 'text',
value: options.max != 1 ? [] : ''
})
.options(options || {})
.update({
value: function() {
// fixme: this doesn't work in cases where
// multiple buttons can be selected
var position = Ox.getIndexById(
self.options.buttons, self.options.value
);
if (position > -1) {
self.$buttons[position].trigger('click');
} else if (self.options.min == 0) {
self.$buttons.forEach(function($button, i) {
$button.options('value') && $button.trigger('click');
});
}
}
})
.addClass(
'OxButtonGroup'
+ (self.options.style != 'default' ? ' Ox' + Ox.toTitleCase(self.options.style) : '')
+ (self.options.overlap != 'none' ? ' OxOverlap' + Ox.toTitleCase(self.options.overlap) : '')
);
self.options.buttons = self.options.buttons.map(function(button, i) {
return Ox.extend({
disabled: button.disabled,
id: button.id || button,
overlap: self.options.overlap == 'left' && i == 0 ? 'left'
: self.options.overlap == 'right' && i == self.options.buttons.length - 1 ? 'right'
: 'none',
title: button.title || button,
tooltip: button.tooltip,
width: button.width
}, self.options.selectable ? {
selected: Ox.makeArray(self.options.value).indexOf(button.id || button) > -1
} : {});
});
if (self.options.selectable) {
self.optionGroup = new Ox.OptionGroup(
self.options.buttons,
self.options.min,
self.options.max,
'selected'
);
self.options.buttons = self.optionGroup.init();
self.options.value = self.optionGroup.value();
}
self.$buttons = [];
self.options.buttons.forEach(function(button, pos) {
self.$buttons[pos] = Ox.Button({
disabled: button.disabled || false, // FIXME: getset should handle undefined
group: true,
id: button.id,
overlap: button.overlap,
selectable: self.options.selectable,
size: self.options.size,
style: self.options.style == 'squared' ? 'default' : self.options.style, // FIXME: ugly
title: button.title,
tooltip: button.tooltip,
type: self.options.type,
value: button.selected || false, // FIXME: getset should handle undefined
width: button.width
})
.bindEvent(self.options.selectable ? {
change: function() {
toggleButton(pos);
}
} : {
click: function() {
that.triggerEvent('click', {id: button.id});
}
})
.appendTo(that);
});
function getButtonById(id) {
return self.$buttons[Ox.getIndexById(self.options.buttons, id)];
}
function toggleButton(pos) {
var toggled = self.optionGroup.toggle(pos);
if (!toggled.length) {
// FIXME: fix and use that.toggleOption()
self.$buttons[pos].value(!self.$buttons[pos].value());
} else {
toggled.forEach(function(i) {
i != pos && self.$buttons[i].value(!self.$buttons[i].value());
});
self.options.value = self.optionGroup.value();
that.triggerEvent('change', {
title: self.options.value === '' ? ''
: Ox.isString(self.options.value)
? Ox.getObjectById(self.options.buttons, self.options.value).title
: self.options.value.map(function(value) {
return Ox.getObjectById(self.options.buttons, value).title;
}),
value: self.options.value
});
}
}
/*@
disableButton <f> Disable button
@*/
that.disableButton = function(id) {
getButtonById(id).options({disabled: true});
};
/*@
enableButton <f> Enable button
@*/
that.enableButton = function(id) {
getButtonById(id).options({disabled: false});
};
/*
buttonOptions <f> Get or set button options
*/
that.buttonOptions = function(id, options) {
return getButtonById(id).options(options);
};
return that;
};

View file

@ -0,0 +1,123 @@
'use strict';
/*@
Ox.Checkbox <f> Checkbox Element
options <o> Options object
disabled <b> if true, checkbox is disabled
group <b> if true, checkbox is part of a group
label <s> Label (on the left side)
labelWidth <n|64> Label width
title <s> Title (on the right side)
value <b> if true, checkbox is checked
width <n> width in px
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Checkbox Element
change <!> triggered when value changes
@*/
Ox.Checkbox = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
disabled: false,
group: false,
label: '',
labelWidth: 64,
overlap: 'none',
title: '',
value: false,
width: options && (options.label || options.title) ? 'auto' : 16
})
.options(options || {})
.update({
disabled: function() {
var disabled = self.options.disabled;
that.attr({disabled: disabled});
self.$button.options({disabled: disabled});
self.$title && self.$title.options({disabled: disabled});
},
label: function() {
self.$label.options({title: self.options.label});
},
title: function() {
self.$title.options({title: self.options.title});
},
value: function() {
self.$button.toggle();
},
width: function() {
that.css({width: self.options.width + 'px'});
self.$title && self.$title.options({width: getTitleWidth()});
}
})
.addClass('OxCheckbox' + (
self.options.overlap == 'none'
? '' : ' OxOverlap' + Ox.toTitleCase(self.options.overlap)
))
.attr({
disabled: self.options.disabled
})
.css(self.options.width != 'auto' ? {
width: self.options.width
} : {});
if (self.options.title) {
self.options.width != 'auto' && that.css({
width: self.options.width + 'px'
});
self.$title = Ox.Label({
disabled: self.options.disabled,
id: self.options.id + 'Label',
overlap: 'left',
title: self.options.title,
width: getTitleWidth()
})
.css({float: 'right'})
.on({click: clickTitle})
.appendTo(that);
}
if (self.options.label) {
self.$label = Ox.Label({
overlap: 'right',
textAlign: 'right',
title: self.options.label,
width: self.options.labelWidth
})
.css({float: 'left'})
.appendTo(that);
}
self.$button = Ox.Button({
disabled: self.options.disabled,
id: self.options.id + 'Button',
type: 'image',
value: self.options.value ? 'check' : 'none',
values: ['none', 'check']
})
.addClass('OxCheckbox')
.bindEvent({
change: clickButton
})
.appendTo(that);
function clickButton() {
self.options.value = !self.options.value;
that.triggerEvent('change', {
value: self.options.value
});
}
function clickTitle() {
!self.options.disabled && self.$button.trigger('click');
}
function getTitleWidth() {
return self.options.width - 16
- !!self.options.label * self.options.labelWidth;
}
return that;
};

View file

@ -0,0 +1,119 @@
'use strict';
/*@
Ox.CheckboxGroup <f> CheckboxGroup Object
options <o> Options object
checkboxes <a|[]> array of checkboxes
max <n|1> max selected
min <n|1> min selected
type <s|"group"> type ("group" or "list")
width <n> width in px
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> CheckboxGroup Object
change <!> triggered when checked property changes
passes {id, title, value}
@*/
Ox.CheckboxGroup = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
// fixme: 'checkboxes' should be 'items'?
checkboxes: [],
max: 1,
min: 1,
type: 'group',
value: options.max != 1 ? [] : '',
width: 256
})
.options(options || {})
.update({
value: function() {
var value = Ox.clone(self.options.value);
self.$checkboxes.forEach(function($checkbox, index) {
var checked = Ox.contains(value, $checkbox.options('id'));
if (checked != $checkbox.value()) {
$checkbox.value(!$checkbox.value());
toggleCheckbox(index);
}
});
},
width: function() {
self.$checkboxes.forEach(function($checkbox) {
$checkbox.options({width: self.options.width});
});
}
})
.addClass('OxCheckboxGroup Ox' + Ox.toTitleCase(self.options.type));
self.options.checkboxes = self.options.checkboxes.map(function(checkbox) {
return {
checked: Ox.makeArray(self.options.value).indexOf(checkbox.id || checkbox) > -1,
id: checkbox.id || checkbox,
title: checkbox.title || checkbox
};
});
self.optionGroup = new Ox.OptionGroup(
self.options.checkboxes,
self.options.min,
self.options.max,
'checked'
);
self.options.checkboxes = self.optionGroup.init();
self.options.value = self.optionGroup.value();
self.$checkboxes = [];
if (self.options.type == 'group') {
self.checkboxWidth = Ox.splitInt(
self.options.width + (self.options.checkboxes.length - 1) * 6,
self.options.checkboxes.length
).map(function(v, i) {
return v + (i < self.options.checkboxes.length - 1 ? 10 : 0);
});
};
self.options.checkboxes.forEach(function(checkbox, pos) {
self.$checkboxes[pos] = Ox.Checkbox(Ox.extend(checkbox, {
group: true,
id: checkbox.id,
width: self.options.type == 'group'
? self.checkboxWidth[pos] : self.options.width,
value: checkbox.checked
}))
.bindEvent('change', function() {
toggleCheckbox(pos);
})
.appendTo(that);
});
function toggleCheckbox(pos) {
var toggled = self.optionGroup.toggle(pos);
Ox.Log('Form', 'change', pos, 'toggled', toggled)
if (!toggled.length) {
// FIXME: fix and use that.toggleOption()
self.$checkboxes[pos].value(!self.$checkboxes[pos].value());
} else {
toggled.forEach(function(i) {
i != pos && self.$checkboxes[i].value(!self.$checkboxes[i].value());
});
self.options.value = self.optionGroup.value();
that.triggerEvent('change', {
title: Ox.isString(self.options.value)
? (
self.options.value
? Ox.getObjectById(self.options.checkboxes, self.options.value).title
: ''
)
: self.options.value.map(function(value) {
return Ox.getObjectById(self.options.checkboxes, value).title;
}),
value: self.options.value
});
}
}
return that;
};

View file

@ -0,0 +1,88 @@
'use strict';
/*@
Ox.ColorInput <f> ColorInput Element
options <o> Options object
mode <s|'rgb'> Mode ('rgb' or 'hsl')
value <[n]|[0, 0, 0]> Value
self <o> Shared private variable
([options[, self]]) -> <o:Ox.InputGroup> ColorInput Element
change <!> change
@*/
Ox.ColorInput = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
mode: 'rgb',
value: options.mode == 'hsl' ? [0, 1, 0.5] : [0, 0, 0]
})
.options(options || {});
self.$inputs = [];
Ox.loop(3, function(i) {
self.$inputs[i] = Ox.Input({
max: self.options.mode == 'rgb' ? 255 : i == 0 ? 360 : 1,
type: self.options.mode == 'rgb' || i == 0 ? 'int' : 'float',
value: self.options.value[i],
width: 40
})
.bindEvent('autovalidate', change);
});
self.$inputs[3] = Ox.Label({
width: 40
})
.css({
background: 'rgb(' + getRGB().join(', ') + ')'
});
self.$inputs[4] = Ox.ColorPicker({
mode: self.options.mode,
width: 16
})
.bindEvent({
change: function(data) {
self.options.value = data.value;
Ox.loop(3, function(i) {
self.$inputs[i].options({value: self.options.value[i]});
});
self.$inputs[3].css({
background: 'rgb(' + getRGB().join(', ') + ')'
});
}
})
.options({
width: 16 // this is just a hack to make the InputGroup layout work
});
that.setElement(Ox.InputGroup({
inputs: self.$inputs,
separators: [
{title: ',', width: 8},
{title: ',', width: 8},
{title: '', width: 8},
{title: '', width: 8}
],
value: Ox.clone(self.options.value)
})
.bindEvent('change', change)
);
function change() {
self.options.value = Ox.range(3).map(function(i) {
return self.$inputs[i].options('value');
})
self.$inputs[3].css({
background: 'rgb(' + getRGB().join(', ') + ')'
});
that.triggerEvent('change', {value: self.options.value});
}
function getRGB() {
return self.options.mode == 'rgb'
? self.options.value
: Ox.rgb(self.options.value);
}
return that;
};

View file

@ -0,0 +1,117 @@
'use strict';
/*@
Ox.ColorPicker <f> ColorPicker Element
options <o> Options object
mode <s|'rgb'> Mode ('rgb' or 'hsl')
value <[n]|[0, 0, 0]> Value
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Picker> ColorPicker Element
change <!> triggered on change of value
@*/
Ox.ColorPicker = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
mode: 'rgb',
value: options && options.mode == 'hsl' ? [0, 1, 0.5] : [0, 0, 0]
})
.options(options || {});
//Ox.Log('Form', self)
self.$ranges = [];
Ox.loop(3, function(i) {
self.$ranges[i] = Ox.Range({
arrows: true,
changeOnDrag: true,
id: self.options.id + i,
max: self.options.mode == 'rgb' ? 255 : i == 0 ? 359 : 1,
size: self.options.mode == 'rgb' ? 328 : 432, // 256|360 + 16 + 40 + 16
step: self.options.mode == 'rgb' || i == 0 ? 1 : 0.01,
thumbSize: 40,
thumbValue: true,
trackColors: getColors(i),
trackGradient: true,
value: self.options.value[i]
})
.css({
position: 'absolute',
top: (i * 15) + 'px'
})
.bindEvent({
change: function(data) {
change(i, data.value);
}
})
.appendTo(that);
if (i == 0) {
// fixme: this should go into Ox.UI.css
self.$ranges[i].children('input.OxOverlapRight').css({
borderRadius: 0
});
self.$ranges[i].children('input.OxOverlapLeft').css({
borderRadius: '0 8px 0 0'
});
} else {
self.$ranges[i].children('input').css({
borderRadius: 0
});
}
});
that = Ox.Picker({
element: that,
elementHeight: 46,
elementWidth: self.options.mode == 'rgb' ? 328 : 432
});
function change(index, value) {
self.options.value[index] = value;
that.$label.css({
background: 'rgb(' + getRGB(self.options.value).join(', ') + ')'
});
Ox.loop(3, function(i) {
if (i != index) {
self.$ranges[i].options({
trackColors: getColors(i)
});
}
});
that.triggerEvent('change', {value: self.options.value});
}
function getColors(index) {
return (
self.options.mode == 'rgb' ? [
Ox.range(3).map(function(i) {
return i == index ? 0 : self.options.value[i];
}),
Ox.range(3).map(function(i) {
return i == index ? 255 : self.options.value[i];
})
]
: index == 0 ? Ox.range(7).map(function(i) {
return [i * 60, self.options.value[1], self.options.value[2]];
})
: Ox.range(3).map(function(i) {
return [
self.options.value[0],
index == 1 ? i / 2 : self.options.value[1],
index == 2 ? i / 2 : self.options.value[2]
];
})
).map(function(values) {
return 'rgb(' + getRGB(values).join(', ') + ')';
});
}
function getRGB(values) {
return self.options.mode == 'rgb' ? values : Ox.rgb(values);
}
return that;
};

View file

@ -0,0 +1,216 @@
'use strict';
/*@
Ox.DateInput <f> DateInput Element
options <o> Options object
format <s|short> format can be short, medium, long
value <d> date value, defaults to current date
weekday <b|false> weekday
width <o> width of individual input elements, in px
day <n> width of day input element
month <n> width of month input element
weekday <n> width of weekday input element
year <n> width of year input element
self <o> Shared private variable
([options[, self]]) -> <o:Ox.InputGroup> DateInput Element
change <!> triggered on change of value
@*/
Ox.DateInput = function(options, self) {
var that;
self = Ox.extend(self || {}, {
options: Ox.extend({
format: 'short',
value: Ox.formatDate(new Date(), '%F'),
weekday: false,
width: {
day: 32,
month: options && options.format == 'long' ? 80
: options && options.format == 'medium' ? 40 : 32,
weekday: options && options.format == 'long' ? 80 : 40,
year: 48
}
}, options || {})
});
self.formats = {
day: '%d',
month: self.options.format == 'short' ? '%m' :
(self.options.format == 'medium' ? '%b' : '%B'),
weekday: self.options.format == 'long' ? '%A' : '%a',
year: '%Y'
};
self.months = self.options.format == 'long' ? Ox.MONTHS : Ox.SHORT_MONTHS;
self.weekdays = self.options.format == 'long' ? Ox.WEEKDAYS : Ox.SHORT_WEEKDAYS;
self.$input = Ox.extend(self.options.weekday ? {
weekday: Ox.Input({
autocomplete: self.weekdays,
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'weekday',
width: self.options.width.weekday
})
.bindEvent('autocomplete', changeWeekday)
} : {}, {
day: Ox.Input({
autocomplete: Ox.range(1, Ox.getDaysInMonth(
parseInt(Ox.formatDate(self.date, '%Y'), 10),
parseInt(Ox.formatDate(self.date, '%m'), 10)
) + 1).map(function(i) {
return self.options.format == 'short' ? Ox.pad(i, 2) : i.toString();
}),
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'day',
textAlign: 'right',
width: self.options.width.day
})
.bindEvent('autocomplete', changeDay),
month: Ox.Input({
autocomplete: self.options.format == 'short' ? Ox.range(1, 13).map(function(i) {
return Ox.pad(i, 2);
}) : self.months,
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'month',
textAlign: self.options.format == 'short' ? 'right' : 'left',
width: self.options.width.month
})
.bindEvent('autocomplete', changeMonthOrYear),
year: Ox.Input({
autocomplete: Ox.range(1900, 3000).concat(Ox.range(1000, 1900)).map(function(i) {
return i.toString();
}),
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'year',
textAlign: 'right',
width: self.options.width.year
})
.bindEvent('autocomplete', changeMonthOrYear)
});
that = Ox.InputGroup(Ox.extend(self.options, {
id: self.options.id,
inputs: [].concat(self.options.weekday ? [
self.$input.weekday
] : [], self.options.format == 'short' ? [
self.$input.year, self.$input.month, self.$input.day
] : [
self.$input.month, self.$input.day, self.$input.year
]),
join: join,
separators: [].concat(self.options.weekday ? [
{title: self.options.format == 'short' ? '' : ',', width: 8},
] : [], self.options.format == 'short' ? [
{title: '-', width: 8}, {title: '-', width: 8}
] : [
{title: '', width: 8}, {title: ',', width: 8}
]),
split: split,
width: 0
}), self);
function changeDay() {
self.options.weekday && self.$input.weekday.value(
Ox.formatDate(new Date([
self.$input.month.value(),
self.$input.day.value(),
self.$input.year.value()
].join(' ')), self.formats.weekday)
);
self.options.value = join();
}
function changeMonthOrYear() {
var day = self.$input.day.value(),
month = self.$input.month.value(),
year = self.$input.year.value(),
days = Ox.getDaysInMonth(year, self.options.format == 'short' ? parseInt(month, 10) : month);
day = day <= days ? day : days;
//Ox.Log('Form', year, month, 'day days', day, days)
self.options.weekday && self.$input.weekday.value(
Ox.formatDate(
new Date([month, day, year].join(' ')),
self.formats.weekday
)
);
self.$input.day.options({
autocomplete: Ox.range(1, days + 1).map(function(i) {
return self.options.format == 'short' ? Ox.pad(i, 2) : i.toString();
}),
value: self.options.format == 'short' ? Ox.pad(parseInt(day, 10), 2) : day.toString()
});
self.options.value = join();
}
function changeWeekday() {
var date = getDateInWeek(
self.$input.weekday.value(),
self.$input.month.value(),
self.$input.day.value(),
self.$input.year.value()
);
self.$input.month.value(date.month);
self.$input.day.options({
autocomplete: Ox.range(1, Ox.getDaysInMonth(date.year, date.month) + 1).map(function(i) {
return self.options.format == 'short' ? Ox.pad(i, 2) : i.toString();
}),
value: date.day
});
self.$input.year.value(date.year);
self.options.value = join();
}
function getDate(value) {
return new Date(self.options.value.replace(/-/g, '/'));
}
function getDateInWeek(weekday, month, day, year) {
//Ox.Log('Form', [month, day, year].join(' '))
var date = new Date([month, day, year].join(' '));
date = Ox.getDateInWeek(date, weekday);
return {
day: Ox.formatDate(date, self.formats.day),
month: Ox.formatDate(date, self.formats.month),
year: Ox.formatDate(date, self.formats.year)
};
}
function getValues() {
var date = getDate();
return {
day: Ox.formatDate(date, self.formats.day),
month: Ox.formatDate(date, self.formats.month),
weekday: Ox.formatDate(date, self.formats.weekday),
year: Ox.formatDate(date, self.formats.year)
};
}
function join() {
return Ox.formatDate(new Date(self.options.format == 'short' ? [
self.$input.year.value(),
self.$input.month.value(),
self.$input.day.value()
].join('/') : [
self.$input.month.value(),
self.$input.day.value(),
self.$input.year.value()
].join(' ')), '%F');
}
function split() {
var values = getValues();
return [].concat(self.options.weekday ? [
values.weekday
] : [], self.options.format == 'short' ? [
values.year, values.month, values.day
] : [
values.month, values.day, values.year
]);
}
return that;
};

View file

@ -0,0 +1,70 @@
'use strict';
/*@
Ox.DateTimeInput <f> DateTimeInput Element
options <o> Options object
ampm <b|false> false is 24h true is am/pm
format <s|short> options are short, medium, long
seconds <b|false> show seconds
value <d> defautls to now
weekday <b|false> weekday
self <o> Shared private variable
([options[, self]]) -> <o:Ox.InputGroup> DateTimeInput Element
change <!> triggered on change of value
@*/
Ox.DateTimeInput = function(options, self) {
var that;
self = Ox.extend(self || {}, {
options: Ox.extend({
ampm: false,
format: 'short',
milliseconds: false,
seconds: false,
value: (function() {
var date = new Date();
return Ox.formatDate(
date,
'%F ' + (options && (options.seconds || options.milliseconds) ? '%T' : '%H:%M')
) + (options && options.milliseconds ? '.' + Ox.pad(date % 1000, 3) : '');
}()),
weekday: false
}, options || {})
});
self.options.seconds = self.options.seconds || self.options.milliseconds;
that = Ox.InputGroup(Ox.extend(self.options, {
inputs: [
Ox.DateInput({
format: self.options.format,
id: 'date',
weekday: self.options.weekday
}),
Ox.TimeInput({
ampm: self.options.ampm,
id: 'time',
seconds: self.options.seconds
})
],
join: join,
separators: [
{title: '', width: 8}
],
split: split
}), self);
function join() {
return that.options('inputs').map(function($input) {
return $input.value();
}).join(' ');
}
function split() {
return self.options.value.split(' ');
}
return that;
};

View file

@ -0,0 +1,301 @@
'use strict';
/*@
Ox.Editable <f> Editable element
options <o> Options object
editing <b|false> If true, loads in editing state
format <f|null> Format function
(value) -> <s> Formatted value
value <s> Input value
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Input Element
blur <!> blur
cancel <!> cancel
edit <!> edit
open <!> open
submit <!> submit
@*/
Ox.Editable = function(options, self) {
self = self || {};
var that = Ox.Element({
element: options.type == 'textarea' ? '<div>' : '<span>',
tooltip: options.tooltip
}, self)
.defaults({
blurred: false,
clickLink: null,
editable: true,
editing: false,
format: null,
globalAttributes: [],
height: 0,
highlight: null,
maxHeight: void 0,
placeholder: '',
submitOnBlur: true,
tags: null,
tooltip: '',
type: 'input',
value: '',
width: 0
})
.options(options || {})
.update({
editing: function() {
if (self.options.editing) {
// edit will toggle self.options.editing
self.options.editing = false;
edit();
} else {
submit();
}
},
height: function() {
setCSS({height: self.options.height + 'px'});
},
width: function() {
setCSS({width: self.options.width + 'px'});
},
highlight: function() {
self.$value.html(formatValue());
},
placeholder: function() {
self.$value.html(formatValue());
},
value: function() {
self.$value.html(formatValue());
}
})
.addClass(
'OxEditableElement OxKeyboardFocus'
+ (self.options.editable ? ' OxEditable' : '')
)
.on({
click: function(e) {
var $target = $(e.target);
if (!e.shiftKey && ($target.is('a') || ($target = $target.parents('a')).length)) {
e.preventDefault();
if (self.options.clickLink) {
e.target = $target[0];
self.options.clickLink(e);
} else {
document.location.href = $target.attr('href');
}
}
return false;
}
})
.bindEvent({
doubleclick: edit,
singleclick: function(e) {
}
});
self.options.value = self.options.value.toString();
self.css = {};
self.$value = Ox.Element(self.options.type == 'input' ? '<span>' : '<div>')
.addClass('OxValue')
.html(formatValue())
.appendTo(that);
if (self.options.editing) {
// need timeout so that when determining height
// the element is actually in the DOM
setTimeout(function() {
// edit will toggle self.options.editing
self.options.editing = false;
edit();
});
}
function blur(data) {
self.options.value = parseValue();
if (self.options.value !== self.originalValue) {
self.originalValue = self.options.value;
that.triggerEvent('change', {value: self.options.value});
}
that.triggerEvent('blur', data);
}
function cancel() {
self.options.editing = false;
that.removeClass('OxEditing');
self.options.value = self.originalValue;
self.$input.value(formatInputValue()).hide();
self.$test.html(formatTestValue());
self.$value.html(formatValue()).show();
that.triggerEvent('cancel', {value: self.options.value});
}
function change(data) {
self.options.value = parseValue(data.value);
self.$value.html(formatValue());
self.$test.html(formatTestValue());
setSizes();
}
function edit() {
var height, width;
if (self.options.editable && !self.options.editing) {
self.options.editing = true;
that.addClass('OxEditing');
self.originalValue = self.options.value;
if (!self.$input) {
self.$input = Ox.Input({
changeOnKeypress: true,
element: self.options.type == 'input' ? '<span>' : '<div>',
style: 'square',
type: self.options.type,
value: formatInputValue()
})
.css(self.css)
.bindEvent({
blur: self.options.submitOnBlur ? submit : blur,
cancel: cancel,
change: change,
insert: function(data) {
that.triggerEvent('insert', data);
},
submit: submit
})
.appendTo(that);
self.$input.find('input').css(self.css);
self.$test = self.$value.clone()
.css(Ox.extend({display: 'inline-block'}, self.css))
.html(formatTestValue())
.css({background: 'rgb(192, 192, 192)'})
.appendTo(that);
}
self.minWidth = 8;
self.maxWidth = that.parent().width();
self.minHeight = 13;
self.maxHeight = self.options.type == 'input'
? self.minHeight
: self.options.maxHeight || that.parent().height();
setSizes();
self.$value.hide();
self.$input.show();
if (!self.options.blurred) {
setTimeout(function() {
self.$input.focusInput(self.options.type == 'input');
}, 0);
that.$tooltip && that.$tooltip.options({title: ''});
that.triggerEvent('edit');
}
} else if (!self.options.editable) {
that.triggerEvent('open');
}
self.options.blurred = false;
}
function formatInputValue() {
return self.options.type == 'input'
? self.options.value
: self.options.value.replace(/<br\/?><br\/?>/g, '\n\n');
}
function formatTestValue() {
var value = Ox.encodeHTMLEntities(self.$input.options('value'));
return !value ? '&nbsp;'
: self.options.type == 'input'
? value.replace(/ /g, '&nbsp;')
: value.replace(/\n$/, '\n ')
.replace(/ /g, ' &nbsp;')
.replace(/(^ | $)/, '&nbsp;')
.replace(/\n/g, '<br/>')
}
function formatValue() {
var value = self.options.value;
that.removeClass('OxPlaceholder');
if (self.options.value === '' && self.options.placeholder) {
value = self.options.placeholder;
that.addClass('OxPlaceholder');
} else if (self.options.format) {
value = self.options.format(self.options.value);
}
if (self.options.highlight) {
value = Ox.highlight(
value, self.options.highlight, 'OxHighlight', true
);
}
// timeout needed since formatValue is used when assinging self.$value
setTimeout(function() {
self.$value[
self.options.value === '' ? 'removeClass' : 'addClass'
]('OxSelectable');
})
return value;
}
function parseValue() {
var value = Ox.clean(
self.$input.value().replace(/\n\n+/g, '\0')
).replace(/\0/g, '\n\n').trim();
return (
self.options.type == 'input'
? Ox.encodeHTMLEntities(value)
: Ox.sanitizeHTML(value, self.options.tags, self.options.globalAttributes)
);
}
function setCSS(css) {
self.$test && self.$test.css(css);
self.$input && self.$input.css(css);
self.$input && self.$input.find(self.options.type).css(css);
}
function setSizes() {
var height, width;
self.$test.css({display: 'inline-block'});
height = self.options.height || Ox.limit(self.$test.height(), self.minHeight, self.maxHeight);
width = self.$test.width();
// +Ox.UI.SCROLLBAR_SIZE to prevent scrollbar from showing up
if (self.options.type == 'textarea') {
width += Ox.UI.SCROLLBAR_SIZE;
}
width = self.options.width || Ox.limit(width, self.minWidth, self.maxWidth);
self.$test.css({display: 'none'});
/*
that.css({
width: width + 'px',
height: height + 'px'
});
*/
self.$input.options({
width: width,
height: height
});
self.$input.find(self.options.type).css({
width: width + 'px',
height: height + 'px'
});
}
function submit() {
self.options.editing = false;
that.removeClass('OxEditing');
self.$input.value(formatInputValue()).hide();
self.$test.html(formatTestValue());
self.$value.html(formatValue()).show();
that.$tooltip && that.$tooltip.options({title: self.options.tooltip});
that.triggerEvent('submit', {value: self.options.value});
}
/*@
css <f> css
@*/
that.css = function(css) {
self.css = css;
that.$element.css(css);
self.$value.css(css);
self.$test && self.$test.css(css);
self.$input && self.$input.css(css);
return that;
};
return that;
};

View file

@ -0,0 +1,229 @@
Ox.EditableContent = function(options, self) {
self = self || {};
if (options.tooltip) {
self.tooltip = options.tooltip;
options.tooltip = function(e) {
return that.hasClass('OxEditing') ? ''
: Ox.isString(self.tooltip) ? self.tooltip
: self.tooltip(e);
}
}
var that = Ox.Element(options.type == 'textarea' ? '<div>' : '<span>', self)
.defaults({
clickLink: null,
collapseToEnd: true,
editable: true,
editing: false,
format: null,
globalAttributes: [],
highlight: null,
placeholder: '',
submitOnBlur: true,
tags: null,
tooltip: '',
type: 'input',
value: ''
})
.options(options || {})
.update({
editing: function() {
if (self.options.editing) {
// edit will toggle self.options.editing
self.options.editing = false;
edit();
} else {
submit();
}
},
highlight: function() {
!self.options.editing && that.html(formatValue());
},
value: function() {
!self.options.editing && that.html(formatValue());
}
})
.addClass('OxEditableContent OxKeyboardFocus')
.on({
blur: self.options.submitOnBlur ? submit : blur,
click: function(e) {
var $target = $(e.target);
if (!e.shiftKey && ($target.is('a') || ($target = $target.parents('a')).length)) {
e.preventDefault();
if (self.options.clickLink) {
e.target = $target[0];
self.options.clickLink(e);
} else {
document.location.href = $target.attr('href');
}
}
return false;
},
keydown: function(e) {
if (e.keyCode == 13) {
if (e.shiftKey || self.options.type == 'input') {
submit();
} else {
var selection = window.getSelection(),
node = selection.anchorNode,
offset = selection.anchorOffset,
range = document.createRange(),
text = node.textContent;
e.preventDefault();
node.textContent = text.substr(0, offset)
+ '\n' + (text.substr(offset) || ' ');
range.setStart(node, offset + 1);
range.setEnd(node, offset + 1);
selection.removeAllRanges();
selection.addRange(range);
}
return false;
} else if (e.keyCode == 27) {
cancel();
return false;
}
setTimeout(function() {
that.css({padding: that.text() ? 0 : '0 2px'});
});
},
paste: function(e) {
//Ox.print('PASTE', e);
if (e.originalEvent.clipboardData && e.originalEvent.clipboardData.getData) {
//Ox.print('TYPES', e.originalEvent.clipboardData.types);
var value = e.originalEvent.clipboardData.getData('text/plain');
value = Ox.encodeHTMLEntities(value).replace(/\n\n\n/g, '<br/><br/>\n');
document.execCommand('insertHTML', false, value);
e.originalEvent.stopPropagation();
e.originalEvent.preventDefault();
return false;
}
}
})
.bindEvent({
doubleclick: edit
});
self.options.value = self.options.value.toString();
that.html(formatValue());
if (self.options.editing) {
// wait for the element to be in the DOM
setTimeout(function() {
// edit will toggle self.options.editing
self.options.editing = false;
edit();
});
}
function blur() {
// ...
}
function cancel() {
if (self.options.editing) {
that.loseFocus();
self.options.editing = false;
that.removeClass('OxEditing')
.attr({contenteditable: false})
.html(formatValue());
if (self.options.type == 'input') {
that.css({padding: 0});
}
that.triggerEvent('cancel', {value: self.options.value});
}
}
function edit() {
if (self.options.editable && !self.options.editing) {
var value = formatInputValue();
that.$tooltip && that.$tooltip.remove();
that.addClass('OxEditing')
.removeClass('OxPlaceholder')
.attr({contenteditable: true});
if (value) {
that.text(value);
} else {
that.text('');
if (self.options.type == 'input') {
that.css({padding: '0 2px'});
}
}
self.options.editing = true;
that.gainFocus();
setTimeout(updateSelection);
that.triggerEvent('edit');
} else if (!self.options.editable) {
that.triggerEvent('open');
}
}
function formatInputValue() {
return self.options.type == 'input'
? Ox.decodeHTMLEntities(self.options.value)
: self.options.value.replace(/<br\/?><br\/?>/g, '\n\n');
}
function formatValue() {
var value = self.options.value;
that.removeClass('OxPlaceholder');
if (self.options.value === '' && self.options.placeholder) {
value = self.options.placeholder;
that.addClass('OxPlaceholder');
} else if (self.options.format) {
value = self.options.format(self.options.value);
}
if (self.options.highlight) {
value = Ox.highlight(
value, self.options.highlight, 'OxHighlight', true
);
}
that[
self.options.value === '' ? 'removeClass' : 'addClass'
]('OxSelectable');
return value;
}
function parseValue() {
var value = Ox.clean(
that.text().replace(/\n\n+/g, '\0')
).replace(/\0/g, '\n\n').trim();
return (
self.options.type == 'input'
? Ox.encodeHTMLEntities(value)
: Ox.sanitizeHTML(value, self.options.tags, self.options.globalAttributes)
);
}
function submit() {
if (self.options.editing) {
that.loseFocus();
self.options.editing = false;
self.options.value = parseValue();
that.removeClass('OxEditing')
.attr({contenteditable: false})
.html(formatValue());
if (self.options.type == 'input') {
that.css({padding: 0});
}
that.triggerEvent('submit', {value: self.options.value});
}
}
function updateSelection() {
var range = document.createRange(),
selection = window.getSelection();
that[0].focus();
if (self.options.collapseToEnd) {
selection.removeAllRanges();
range.selectNodeContents(that[0]);
selection.addRange(range);
}
setTimeout(function() {
selection.collapseToEnd();
});
}
return that;
};

View file

@ -0,0 +1,140 @@
'use strict';
/*@
Ox.FileButton <f> File Button
options <o> Options
disabled <b|false> If true, the button is disabled
image <s|'file'> Symbol name (if type is 'image')
The default value will be 'files' if maxFiles is not 1
maxFiles <n|-1> Maximum number of files (or -1 for unlimited)
maxSize <n|-1> Maximum total file size in bytes (or -1 for unlimited)
title <s|''> Title of the button (and its tooltip)
type <s|'text'> Type of the button ('text' or 'image')
width <n|256> Width of the button in px
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> File Button
click <!> click
@*/
Ox.FileButton = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
disabled: false,
image: options && options.maxFiles == 1 ? 'file' : 'files',
maxFiles: -1,
maxSize: -1,
style: 'default',
title: '',
type: 'text',
width: options.type == 'image' ? 16 : 256
})
.options(options || {})
.update({
disabled: function() {
self.$button.options({disabled: self.options.disabled});
self.$input[self.options.disabled ? 'hide' : 'show']();
},
title: function() {
self.$button.options({title: self.options.title});
}
})
.addClass('OxFileButton')
.css({overflow: 'hidden'});
self.files = [];
self.multiple = self.options.maxFiles != 1;
self.$button = Ox.Button({
disabled: self.options.disabled,
style: self.options.style,
title: self.options.type == 'image'
? self.options.image
: self.options.title,
type: self.options.type,
width: self.options.type == 'image'
? 'auto'
: self.options.width
})
.css({
float: 'left'
})
.appendTo(that);
self.$input = renderInput();
self.options.disabled && self.$input.hide();
function selectFiles(e) {
var filelist = e.target.files,
files = [];
self.files = [];
Ox.loop(filelist.length, function(i) {
files.push(filelist.item(i));
});
files.sort(self.options.maxSize == -1 ? function(a, b) {
a = a.name.toLowerCase();
b = b.name.toLowerCase();
return a < b ? -1 : a > b ? 1 : 0;
} : function(a, b) {
// if there's a max size,
// try to add small files first
return a.size - b.size;
}).forEach(function(file) {
if ((
self.options.maxFiles == -1
|| self.files.length < self.options.maxFiles
) && (
self.options.maxSize == -1
|| self.size + file.size < self.options.maxSize
)) {
self.files.push(file);
self.size += file.size;
}
});
self.$input = renderInput();
if (self.files.length) {
that.triggerEvent('click', {files: self.files});
}
}
function renderInput() {
self.$input && self.$input.remove();
return $('<input>')
.attr(
Ox.extend({
title: self.options.title,
type: 'file'
}, self.multiple ? {
multiple: true
} : {})
)
.css({
float: 'left',
width: self.options.width + 'px',
height: '16px',
marginLeft: -self.options.width + 'px',
opacity: 0
})
.on({
change: selectFiles
})
.appendTo(that);
}
/*@
blurButton <f> blurButton
@*/
that.blurButton = function() {
self.$input.blur();
};
/*@
focusButton <f> focusButton
@*/
that.focusButton = function() {
self.$input.focus();
};
return that;
}

View file

@ -0,0 +1,337 @@
'use strict';
/*@
Ox.FileInput <f> File Input
options <o> Options
disabled <b|false> If true, the element is disabled
maxFiles <n|-1> Maximum number of files (or -1 for unlimited)
maxLines <n|-1> Maximum number of lines to display (or -1 for unlimited)
maxSize <n|-1> Maximum total file size in bytes (or -1 for unlimited)
value <a|[]> Value (array of file objects)
width <w|256> Width in px
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> File Input
change <!> change
@*/
Ox.FileInput = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
disabled: false,
label: '',
labelWidth: 128,
maxFiles: -1,
maxLines: -1,
maxSize: -1,
value: [],
width: 256
})
.options(options || {})
.update({
disabled: function() {
that[self.options.disabled ? 'addClass' : 'removeClass']('OxDisabled');
self.$button.options({disabled: self.options.disabled});
self.$input && self.$input[self.options.disabled ? 'hide' : 'show']();
},
label: function() {
self.$label && self.$label.options({title: self.options.label});
}
})
.addClass('OxFileInput' + (self.options.disabled ? ' OxDisabled' : ''))
.css({width: self.options.width + 'px'});
self.multiple = self.options.maxFiles != 1;
self.size = getSize();
if (self.options.label) {
self.$label = Ox.Label({
overlap: 'right',
textAlign: 'right',
title: self.options.label,
width: self.options.labelWidth
})
.appendTo(that);
}
self.$bar = Ox.Bar({size: 14})
.css(
Ox.extend({
width: getWidth() - 2 + 'px'
}, self.multiple && self.options.value.length ? {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0
} : {})
)
.appendTo(that);
self.$title = $('<div>')
.css({
float: 'left',
width: getWidth() - 104 + 'px',
paddingLeft: '6px',
overflow: 'hidden',
textOverflow: 'ellipsis'
})
.html(getTitleText())
.appendTo(self.$bar);
self.$size = $('<div>')
.css({
float: 'left',
width: '64px',
height: '14px',
paddingRight: '16px',
textAlign: 'right'
})
.html(getSizeText())
.appendTo(self.$bar);
self.$button = Ox.Button({
disabled: self.options.disabled,
style: 'symbol',
title: self.multiple || self.options.value.length == 0
? 'add' : 'close',
type: 'image'
})
.attr({
title: self.multiple || self.options.value.length == 0
? '' : 'Clear'
})
.css({
float: 'left',
marginTop: '-1px'
})
.bindEvent({
click: clearFile
})
.appendTo(self.$bar);
if (self.multiple || self.options.value.length == 0) {
self.$input = renderInput();
self.options.disabled && self.$input.hide();
}
if (self.multiple) {
self.$files = $('<div>')
.addClass('OxFiles')
.css({
width: getWidth() - 2 + 'px',
height: getHeight()
})
.appendTo(that);
self.options.value.length == 0 && self.$files.hide();
self.$list = Ox.TableList({
columns: [
{
id: 'name',
visible: true,
width: getWidth() - 94
},
{
align: 'right',
format: function(value) {
return Ox.formatValue(value, 'B');
},
id: 'size',
visible: true,
width: 64
},
{
align: 'right',
format: function(value, data) {
return Ox.Button({
style: 'symbol',
title: 'close',
type: 'image'
})
.attr({title: Ox._('Remove File')})
.css({margin: '-1px -4px 0 0'})
.bindEvent({
click: function() {
self.$list.options({selected: [value]});
removeFiles([value]);
}
});
},
id: 'id',
visible: true,
width: 28
}
],
items: getItems(),
sort: [{key: 'name', operator: '+'}],
unique: 'id'
})
.css({
left: 0,
top: 0,
width: getWidth() - 2 + 'px',
height: '64px'
})
.bindEvent({
'delete': function(data) {
removeFiles(data.ids);
}
})
.appendTo(self.$files);
}
function addFiles(e) {
var filelist = e.target.files,
files = [];
Ox.loop(filelist.length, function(i) {
files.push(filelist.item(i));
});
files.sort(self.options.maxSize == -1 ? function(a, b) {
a = a.name.toLowerCase();
b = b.name.toLowerCase();
return a < b ? -1 : a > b ? 1 : 0;
} : function(a, b) {
// if there is a max size,
// try to add small files first
return a.size - b.size;
}).forEach(function(file) {
if (!exists(file) && (
self.options.maxFiles == -1
|| self.options.value.length < self.options.maxFiles
) && (
self.options.maxSize == -1
|| self.size + file.size < self.options.maxSize
)) {
self.options.value.push(file);
self.size += file.size;
}
});
self.$title.html(getTitleText());
self.$size.html(getSizeText());
if (self.multiple) {
self.$bar.css({
borderBottomLeftRadius: 0,
borderBottomRightRadius: 1
});
self.$files.css({height: getHeight()}).show();
self.$list.options({items: getItems()});
if (
self.options.value.length == self.options.maxFiles
|| self.size == self.options.maxSize
) {
self.$button.options({disabled: true});
}
self.$input = renderInput();
} else {
self.$button.options({title: 'close'}).attr({title: Ox._('Clear')});
self.$input.remove();
}
that.triggerEvent('change', {value: self.options.value});
}
function clearFile() {
self.options.value = [];
self.size = 0;
self.$title.html(getTitleText());
self.$size.html(getSizeText());
self.$button.options({title: 'add'}).attr({title: ''});
self.$input = renderInput();
that.triggerEvent('change', {value: self.options.value});
}
function exists(file) {
return self.options.value.some(function(f) {
return f.name == file.name
&& f.size == file.size
&& Ox.isEqual(f.lastModifiedDate, file.lastModifiedDate);
});
}
function getHeight() {
return (
self.options.maxLines == -1
? self.options.value.length
: Math.min(self.options.value.length, self.options.maxLines)
) * 16 + 'px';
}
function getItems() {
return self.options.value.map(function(file, i) {
return {name: file.name, size: file.size, id: i};
});
}
function getSize() {
return self.options.value.reduce(function(prev, curr) {
return prev + curr.size;
}, 0);
}
function getSizeText() {
return self.size ? Ox.formatValue(self.size, 'B') : '';
}
function getTitleText() {
var length = self.options.value.length
return length == 0
? Ox._('No file' + (self.multiple ? 's' : '') + ' selected')
: self.multiple
? Ox.formatCount(length, 'file')
: self.options.value[0].name;
}
function getWidth() {
return self.options.width - (
self.options.label ? self.options.labelWidth : 0
);
}
function removeFiles(ids) {
self.options.value = self.options.value.filter(function(v, i) {
return ids.indexOf(i) == -1;
});
self.size = getSize();
if (self.options.value.length == 0) {
self.$bar.css({
borderBottomLeftRadius: '8px',
borderBottomRightRadius: '8px'
});
self.$files.hide();
}
self.$title.html(getTitleText());
self.$size.html(getSizeText());
self.$list.options({items: getItems(), selected: []});
self.$files.css({height: getHeight()});
that.triggerEvent('change', {value: self.options.value});
}
function renderInput() {
self.$input && self.$input.remove();
return $('<input>')
.attr(
Ox.extend({
title: self.multiple ? Ox._('Add Files') : Ox._('Select File'),
type: 'file'
}, self.multiple ? {
multiple: true
} : {})
)
.css({
float: 'left',
width: '16px',
height: '14px',
margin: '-1px -7px 0 -16px',
opacity: 0
})
.on({
change: addFiles
})
.appendTo(self.$bar);
}
that.clear = function() {
clearFile();
return that;
};
return that;
};

907
source/UI/js/Form/Filter.js Normal file
View file

@ -0,0 +1,907 @@
'use strict';
/*@
Ox.Filter <f> Filter Object
options <o> Options object
findKeys <[]|[]> keys
list <o> list object
sort <s> List sort
view <s> List view
sortKeys <a|[]> keys to sort by
value <o> query object
conditions <[o]> Conditions (array of {key, value, operator})
operator <s> Operator ('&' or '|')
limit <o> Limit
key <s> Limit key
sort <s> Limit sort
value <n> Limit value
viewKeys <a|[]> visible keys
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Filter Object
change <!> change
@*/
Ox.Filter = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
findKeys: [],
list: null,
sortKeys: [],
value: {
conditions: [],
operator: '&'
},
viewKeys: []
})
.options(options || {})
.update({
value: function() {
setValue();
renderConditions();
}
});
self.conditionOperators = {
boolean: [
{id: '=', title: Ox._('is')},
{id: '!=', title: Ox._('is not')}
],
date: [
{id: '=', title: Ox._('is')},
{id: '!=', title: Ox._('is not')},
{id: '<', title: Ox._('is before')},
{id: '!<', title: Ox._('is not before')},
{id: '>', title: Ox._('is after')},
{id: '!>', title: Ox._('is not after')},
{id: '=,', title: Ox._('is between')},
{id: '!=,', title: Ox._('is not between')}
],
'enum': [
{id: '=', title: Ox._('is')},
{id: '!=', title: Ox._('is not')},
{id: '<', title: Ox._('is less than')},
{id: '!<', title: Ox._('is not less than')},
{id: '>', title: Ox._('is greater than')},
{id: '!>', title: Ox._('is not greater than')},
{id: '=,', title: Ox._('is between')},
{id: '!=,', title: Ox._('is not between')}
],
item: [
{id: '==', title: Ox._('is')},
{id: '!==', title: Ox._('is not')}
],
list: [
{id: '==', title: Ox._('is')},
{id: '!==', title: Ox._('is not')}
],
number: [
{id: '=', title: Ox._('is')},
{id: '!=', title: Ox._('is not')},
{id: '<', title: Ox._('is less than')},
{id: '!<', title: Ox._('is not less than')},
{id: '>', title: Ox._('is greater than')},
{id: '!>', title: Ox._('is not greater than')},
{id: '=,', title: Ox._('is between')},
{id: '!=,', title: Ox._('is not between')}
],
string: [
{id: '==', title: Ox._('is')},
{id: '!==', title: Ox._('is not')},
{id: '=', title: Ox._('contains')},
{id: '!=', title: Ox._('does not contain')},
{id: '^', title: Ox._('starts with')},
{id: '!^', title: Ox._('does not start with')},
{id: '$', title: Ox._('ends with')},
{id: '!$', title: Ox._('does not end with')}
],
text: [
{id: '=', title: Ox._('contains')},
{id: '!=', title: Ox._('does not contain')}
],
year: [
{id: '==', title: Ox._('is')},
{id: '!==', title: Ox._('is not')},
{id: '<', title: Ox._('is before')},
{id: '!<', title: Ox._('is not before')},
{id: '>', title: Ox._('is after')},
{id: '!>', title: Ox._('is not after')},
{id: '=,', title: Ox._('is between')},
{id: '!=,', title: Ox._('is not between')}
]
};
self.defaultValue = {
boolean: 'true',
date: Ox.formatDate(new Date(), '%F'),
'enum': 0,
float: 0,
hue: 0,
integer: 0,
item: void 0,
list: '',
string: '',
text: '',
time: '00:00:00',
year: new Date().getFullYear().toString()
};
self.operators = [
{id: '&', title: Ox._('all')},
{id: '|', title: Ox._('any')}
];
setValue();
self.$operator = Ox.FormElementGroup({
elements: [
Ox.Label({
title: Ox._('Match'),
overlap: 'right',
width: 48
}),
Ox.FormElementGroup({
elements: [
Ox.Select({
items: self.operators,
value: self.options.value.operator,
width: 48
})
.bindEvent({
change: changeOperator
}),
Ox.Label({
overlap: 'left',
title: Ox._('of the following conditions'),
width: 160
})
],
float: 'right',
width: 208
})
],
float: 'left'
});
self.$save = Ox.InputGroup({
inputs: [
Ox.Checkbox({
width: 16
}),
Ox.Input({
id: 'list',
placeholder: Ox._('Untitled'),
width: 128
})
],
separators: [
{title: Ox._('Save as Smart List'), width: 112}
]
});
self.$limit = Ox.InputGroup({
inputs: [
Ox.Checkbox({
width: 16
}),
Ox.FormElementGroup({
elements: [
Ox.Input({
type: 'int',
width: 56
}),
Ox.Select({
items: [
{id: 'items', title: Ox._('items')},
{},
{id: 'hours', title: Ox._('hours')},
{id: 'days', title: Ox._('days')},
{},
{id: 'GB', title: 'GB'}
],
overlap: 'left',
width: 64
})
],
float: 'right',
width: 120
}),
Ox.Select({
items: self.options.sortKeys,
width: 128
}),
Ox.FormElementGroup({
elements: [
Ox.Select({
items: [
{id: 'ascending', title: Ox._('ascending')},
{id: 'descending', title: Ox._('descending')}
],
width: 128
}),
Ox.Label({
overlap: 'left',
title: Ox._('order'),
width: 72
})
],
float: 'right',
width: 200
})
],
separators: [
{title: Ox._('Limit to'), width: 56},
{title: Ox._('sorted by'), width: 60}, // fixme: this is odd, should be 64
{title: Ox._('in'), width: 32}
]
});
self.$view = Ox.InputGroup({
inputs: [
Ox.Checkbox({
width: 16
}),
Ox.Select({
items: self.options.viewKeys,
width: 128
}),
Ox.Select({
items: self.options.sortKeys,
width: 128
}),
Ox.FormElementGroup({
elements: [
Ox.Select({
items: [
{id: 'ascending', title: Ox._('ascending')},
{id: 'descending', title: Ox._('descending')}
],
width: 128
}),
Ox.Label({
overlap: 'left',
title: Ox._('order'),
width: 72
})
],
float: 'right',
width: 200
})
],
separators: [
{title: Ox._('View'), width: 48},
{title: Ox._('sorted by'), width: 60},
{title: Ox._('in'), width: 32}
]
});
// limit and view temporarily disabled
self.$items = self.options.list
? [self.$operator, self.$save/*, self.$limit, self.$view*/]
: [self.$operator/*, self.$limit, self.$view*/];
self.numberOfAdditionalFormItems = self.$items.length;
self.$form = Ox.Form({
items: self.$items
}).appendTo(that);
renderConditions();
function addCondition(pos, subpos, isGroup) {
subpos = Ox.isUndefined(subpos) ? -1 : subpos;
var condition, key;
if (subpos == -1) {
condition = self.options.value.conditions[pos - 1]
} else {
condition = self.options.value.conditions[pos].conditions[subpos - 1];
}
key = Ox.getObjectById(self.options.findKeys, condition.key);
condition = {
key: key.id,
operator: condition.operator,
value: ''
};
if (isGroup) {
Ox.Log('Form', 'isGroup', self.options.value.operator)
condition = {
conditions: [condition],
operator: self.options.value.operator == '&' ? '|' : '&'
};
}
if (subpos == -1) {
self.options.value.conditions.splice(pos, 0, condition);
} else {
self.options.value.conditions[pos].conditions.splice(subpos, 0, condition);
}
renderConditions(pos, subpos);
if (!isUselessCondition(pos, subpos)) {
triggerChangeEvent();
}
}
function changeConditionKey(pos, subpos, key) {
subpos = Ox.isUndefined(subpos) ? -1 : subpos;
Ox.Log('Form', 'changeConditionKey', pos, subpos, key);
var condition = subpos == -1
? self.options.value.conditions[pos]
: self.options.value.conditions[pos].conditions[subpos],
oldFindKey = Ox.getObjectById(self.options.findKeys, condition.key),
newFindKey = Ox.getObjectById(self.options.findKeys, key),
oldConditionType = getConditionType(oldFindKey.type),
newConditionType = getConditionType(newFindKey.type),
changeConditionType = oldConditionType != newConditionType,
changeConditionFormat = !Ox.isEqual(oldFindKey.format, newFindKey.format),
wasUselessCondition = isUselessCondition(pos, subpos);
Ox.Log('Form', 'old new', oldConditionType, newConditionType)
condition.key = key;
if (changeConditionType || changeConditionFormat) {
if (Ox.getIndexById(self.conditionOperators[newConditionType], condition.operator) == -1) {
condition.operator = self.conditionOperators[newConditionType][0].id;
}
if (
['string', 'text'].indexOf(oldConditionType) == -1
|| ['string', 'text'].indexOf(newConditionType) == -1
) {
condition.value = newFindKey.type == 'item'
? newFindKey.values[0].id
: self.defaultValue[newFindKey.type];
}
renderConditions();
}
if (!(wasUselessCondition && isUselessCondition(pos, subpos))) {
triggerChangeEvent();
}
}
function changeConditionOperator(pos, subpos, operator) {
subpos = Ox.isUndefined(subpos) ? -1 : subpos;
Ox.Log('FILTER', 'chCoOp', 'query', self.options.value)
var condition = subpos == -1
? self.options.value.conditions[pos]
: self.options.value.conditions[pos].conditions[subpos],
isBetween = operator.indexOf(',') > -1,
wasBetween = Ox.isArray(condition.value),
wasUselessCondition = isUselessCondition(pos, subpos);
Ox.Log('FILTER', 'chCoOp', 'iB/wB', isBetween, wasBetween)
condition.operator = operator;
if (isBetween && !wasBetween) {
condition.operator = condition.operator.replace(',', '');
condition.value = [condition.value, condition.value]
renderConditions();
} else if (!isBetween && wasBetween) {
condition.value = condition.value[0]
renderConditions();
}
if (!(wasUselessCondition && isUselessCondition(pos, subpos))) {
triggerChangeEvent();
}
}
function changeConditionValue(pos, subpos, value) {
Ox.Log('FILTER', 'cCV', pos, subpos, value);
var condition = subpos == -1
? self.options.value.conditions[pos]
: self.options.value.conditions[pos].conditions[subpos];
condition.value = value;
triggerChangeEvent();
}
function changeGroupOperator(pos, value) {
self.options.value.conditions[pos].operator = value;
triggerChangeEvent();
}
function changeOperator(data) {
var hasGroups = false;
self.options.value.operator = data.value;
Ox.forEach(self.options.value.conditions, function(condition) {
if (condition.conditions) {
hasGroups = true;
return false; // break
}
});
hasGroups && renderConditions();
self.options.value.conditions.length > 1 && triggerChangeEvent();
}
function getConditionType(type) {
type = Ox.isArray(type) ? type[0] : type;
if (['float', 'hue', 'integer', 'time'].indexOf(type) > -1) {
type = 'number';
}
return type;
}
function isUselessCondition(pos, subpos) {
subpos = Ox.isUndefined(subpos) ? -1 : subpos;
var conditions = subpos == -1
? self.options.value.conditions[pos].conditions
|| [self.options.value.conditions[pos]]
: [self.options.value.conditions[pos].conditions[subpos]],
isUseless = false;
Ox.forEach(conditions, function(condition) {
isUseless = ['string', 'text'].indexOf(getConditionType(
Ox.getObjectById(self.options.findKeys, condition.key).type
)) > -1
&& (
self.options.value.operator == '&' ? ['', '^', '$'] : ['!', '!^', '!$']
).indexOf(condition.operator) > -1
&& condition.value === '';
if (!isUseless) {
return false; // break if one of the conditions is not useless
}
});
return isUseless;
}
function removeCondition(pos, subpos) {
subpos = Ox.isUndefined(subpos) ? -1 : subpos;
var wasUselessCondition = isUselessCondition(pos, subpos);
if (subpos == -1 || self.options.value.conditions[pos].conditions.length == 1) {
self.options.value.conditions.splice(pos, 1);
} else {
self.options.value.conditions[pos].conditions.splice(subpos, 1);
}
renderConditions();
if (!wasUselessCondition) {
triggerChangeEvent();
}
}
function renderButtons(pos, subpos) {
subpos = Ox.isUndefined(subpos) ? -1 : subpos;
var isGroup = subpos == -1 && self.options.value.conditions[pos].conditions;
return [].concat([
Ox.Button({
id: 'remove',
title: self.options.value.conditions.length == 1 ? 'close' : 'remove',
tooltip: self.options.value.conditions.length == 1 ? Ox._('Reset this condition')
: isGroup ? Ox._('Remove this group of conditions')
: Ox._('Remove this condition'),
type: 'image'
})
.css({margin: '0 4px 0 ' + (isGroup ? '292px' : '8px')}) // fixme: 296 is probably correct, but labels seem to be too wide
.bindEvent({
click: function(data) {
var key, that = this;
that.focus(); // make input trigger change
setTimeout(function() {
Ox.print(self.options.value.conditions.length, that.parent().data('subposition') == -1);
if (self.options.value.conditions.length == 1) {
key = self.options.findKeys[0];
self.options.value.conditions = [{
key: key.id,
value: '',
operator: self.conditionOperators[key.type][0].id
}];
renderConditions();
triggerChangeEvent();
} else if (that.parent().data('subposition') == -1) {
removeCondition(that.parent().data('position'));
} else {
removeCondition(
that.parent().data('position'),
that.parent().data('subposition')
);
}
});
}
}),
Ox.Button({
id: 'add',
title: 'add',
tooltip: Ox._('Add a condition'),
type: 'image'
})
.css({margin: '0 ' + (subpos == -1 ? '4px' : '0') + ' 0 4px'})
.bindEvent({
click: function(data) {
var that = this;
that.focus(); // make input trigger change
setTimeout(function() {
if (that.parent().data('subposition') == -1) {
addCondition(that.parent().data('position') + 1);
} else {
addCondition(
that.parent().data('position'),
that.parent().data('subposition') + 1
);
}
});
}
})
], subpos == -1 ? [
Ox.Button({
id: 'addgroup',
title: 'bracket',
tooltip: Ox._('Add a group of conditions'),
type: 'image'
})
.css({margin: '0 0 0 4px'})
.bindEvent({
click: function(data) {
var that = this;
that.focus(); // make input trigger change
setTimeout(function() {
addCondition(that.parent().data('position') + 1, -1, true);
});
}
})
] : []);
}
function renderCondition(condition, pos, subpos) {
subpos = Ox.isUndefined(subpos) ? -1 : subpos;
var condition = subpos == -1
? self.options.value.conditions[pos]
: self.options.value.conditions[pos].conditions[subpos];
Ox.Log('Form', 'renderCondition', condition, pos, subpos)
return Ox.FormElementGroup({
elements: [
renderConditionKey(condition),
renderConditionOperator(condition),
renderConditionValue(condition)
].concat(renderButtons(pos, subpos))
})
.css({marginLeft: subpos == -1 ? 0 : '24px'})
.data({position: pos, subposition: subpos});
}
function renderConditionKey(condition) {
return Ox.Select({
items: self.options.findKeys,
//items: Ox.extend({}, self.options.findKeys), // fixme: Ox.Menu messes with keys
overlap: 'right',
value: condition.key,
width: 128
})
.bindEvent({
change: function(data) {
var $element = this.parent();
changeConditionKey(
$element.data('position'),
$element.data('subposition'),
data.value
);
}
});
}
function renderConditionOperator(condition) {
Ox.Log('FILTER', 'rCO', condition, self.conditionOperators[getConditionType(
Ox.getObjectById(self.options.findKeys, condition.key).type
)])
return Ox.Select({
items: self.conditionOperators[getConditionType(
Ox.getObjectById(self.options.findKeys, condition.key).type
)],
overlap: 'right',
value: condition.operator + (Ox.isArray(condition.value) ? ',' : ''),
width: 128
})
.bindEvent({
change: function(data) {
var $element = this.parent();
changeConditionOperator(
$element.data('position'),
$element.data('subposition'),
data.value
);
}
});
}
function renderConditionValue(condition) {
return (!Ox.isArray(condition.value)
? renderInput(condition)
: Ox.InputGroup({
inputs: [
renderInput(condition, 0).options({id: 'start'}),
renderInput(condition, 1).options({id: 'end'})
],
separators: [
{title: Ox._('and'), width: 32}
]
})
).bindEvent({
change: change,
submit: change
});
function change(data) {
var $element = this.parent();
changeConditionValue(
$element.data('position'),
$element.data('subposition'),
data.value
);
}
}
function renderConditions(focusPos, focusSubpos) {
Ox.Log('Form', 'renderConditions', self.options.value)
var $conditions = [], focusIndex;
while (self.$form.options('items').length > self.numberOfAdditionalFormItems) {
self.$form.removeItem(1);
}
self.options.value.conditions.forEach(function(condition, pos) {
if (!condition.conditions) {
$conditions.push(renderCondition(condition, pos));
if (pos == focusPos && focusSubpos == -1) {
focusIndex = $conditions.length - 1;
}
} else {
$conditions.push(renderGroup(condition, pos));
condition.conditions.forEach(function(subcondition, subpos) {
$conditions.push(renderCondition(subcondition, pos, subpos));
if (pos == focusPos && subpos == focusSubpos) {
focusIndex = $conditions.length - 1;
}
});
}
});
$conditions.forEach(function($condition, pos) {
var $input;
self.$form.addItem(1 + pos, $condition);
if (focusIndex == pos) {
$input = $condition.options('elements')[2];
if ($input.focusInput) {
$input.focusInput();
}
}
});
}
function renderGroup(condition, pos) {
var subpos = -1;
var $condition = Ox.FormElementGroup({
elements: [
Ox.Label({
title: self.options.value.operator == '&'
? (pos == 0 ? 'Both' : 'and')
: (pos == 0 ? 'Either': 'or'),
overlap: 'right',
width: 48
}).addClass('OxGroupLabel'),
Ox.FormElementGroup({
elements: [
Ox.Select({
items: self.operators,
value: self.options.value.operator == '&' ? '|' : '&',
width: 48
})
.bindEvent({
change: function(data) {
var $element = this.parent().parent();
changeGroupOperator(
$element.data('position'),
data.value
);
}
}),
Ox.Label({
overlap: 'left',
title: Ox._('of the following conditions'),
width: 160
})
],
float: 'right',
width: 208
}),
].concat(renderButtons(pos, subpos, true)),
float: 'left'
})
.data({position: pos});
return $condition;
}
function renderInput(condition, index) {
Ox.Log('Form', 'renderInput', condition)
var $input,
findKey = Ox.getObjectById(self.options.findKeys, condition.key),
isArray = Ox.isArray(condition.value),
isHue,
// FIXME: always use 'int'
type = findKey.type == 'integer' ? 'int' : findKey.type,
value = !isArray ? condition.value : condition.value[index],
formatArgs, formatType, title;
if (type == 'boolean') {
$input = Ox.Select({
items: ['true', 'false'],
max: 1,
min: 1,
value: value ? 'true' : 'false',
width: 288
});
} else if (type == 'enum') {
Ox.Log('FILTER', findKey, condition)
$input = Ox.Select({
items: findKey.values.map(function(v, i) {
return {id: i, title: v}
}),
value: value,
width: !isArray ? 288 : 128
});
} else if (type == 'item') {
$input = Ox.Select({
items: findKey.values,
max: 1,
min: 1,
value: value,
width: 288
});
} else if (type == 'list') {
Ox.Log('FILTER', findKey)
$input = Ox.Input({
autocomplete: findKey.values,
autocompleteSelect: true,
autocompleteSelectSubmit: true,
value: value,
width: 288
});
} else if (findKey.format) {
formatArgs = findKey.format.args
formatType = findKey.format.type;
if (formatType == 'color') {
isHue = formatArgs[0] == 'hue';
$input = Ox.Range({
max: isHue ? 360 : 1,
min: 0,
size: !isArray ? 288 : 128, // fixme: should be width!
width: !isArray ? 288 : 128, // have to set this too, for formatting when tuple
step: isHue ? 1 : 0.01,
thumbSize: 48,
thumbValue: true,
trackColors: isHue ? [
'rgb(255, 0, 0)', 'rgb(255, 255, 0)',
'rgb(0, 255, 0)', 'rgb(0, 255, 255)',
'rgb(0, 0, 255)', 'rgb(255, 0, 255)',
'rgb(255, 0, 0)'
] : ['rgb(0, 0, 0)', 'rgb(255, 255, 255)'],
value: value
});
} else if (formatType == 'date') {
$input = Ox.DateInput(!isArray ? {
value: value,
width: {day: 66, month: 66, year: 140}
} : {
value: value,
width: {day: 32, month: 32, year: 48}
});
} else if (formatType == 'duration') {
$input = Ox.TimeInput(!isArray ? {
seconds: true,
value: value,
width: {hours: 91, minutes: 91, seconds: 90}
} : {
seconds: true,
value: value,
width: {hours: 38, minutes: 37, seconds: 37}
});
} else if (formatType == 'number') {
$input = Ox.Input({
type: type,
value: value,
width: 288
});
} else if (formatType == 'resolution') {
$input = Ox.InputGroup({
inputs: [
Ox.Input({
id: 'width',
type: 'int',
value: value
}),
Ox.Input({
id: 'height',
type: 'int',
value: value
})
],
separators: [{title: 'x', width: 16}],
width: !isArray ? 288 : 128
})
} else if ([
'currency', 'percent', 'unit', 'value'
].indexOf(formatType) > -1) {
title = formatType == 'percent' ? '%' : formatArgs[0];
$input = Ox.FormElementGroup({
elements: [
Ox.Input({
type: type,
value: value,
width: !isArray ? 242 : 80
}),
formatType == 'value' ? Ox.Select({
overlap: 'left',
items: ['K', 'M', 'G', 'T'].map(function(prefix, i) {
return {id: Math.pow(1000, i + 1), title: prefix + title};
}),
width: 48
}) : Ox.Label({
overlap: 'left',
textAlign: 'center',
title: title,
width: 48
})
],
float: 'right',
join: function(value) {
return formatType == 'value'
? value[0] * value[1]
: value[0];
},
split: function(value) {
},
width: !isArray ? 288 : 128
})
}
} else {
$input = Ox.Input({
type: type,
value: value,
width: !isArray ? 288 : 128
});
}
return $input;
}
function setValue() {
// fixme: this should not happen, but some lists
// have their query set to {} or their query operator set to ''
if (Ox.isEmpty(self.options.value)) {
self.options.value = {conditions: [], operator: '&'};
} else if (self.options.value.operator == '') {
self.options.value.operator = '&';
}
Ox.Log('Form', 'Ox.Filter self.options', self.options);
// end fixme
if (!self.options.value.conditions.length) {
self.options.value.conditions = [{
key: self.options.findKeys[0].id,
value: '',
operator: self.conditionOperators[
getConditionType(self.options.findKeys[0].type)
][0].id
}];
}
}
function triggerChangeEvent() {
var value = Ox.clone(self.options.value, true);
/*
// FIXME: doesn't work for nested conditions
value.conditions.forEach(function(condition) {
// Ox.print('CO', condition.operator)
condition.operator = condition.operator.replace(':', '');
});
*/
that.triggerEvent('change', {value: value});
}
/*@
getList <f> getList
@*/
// fixme: is this the best way/name?
that.getList = function() {
if (self.$save) {
var value = self.$save.value();
return {
save: value[0],
name: value[1],
query: self.options.value
};
}
};
that.value = function() {
if (arguments.length == 0) {
return self.options.value;
} else {
return that.options({value: arguments[0]});
}
};
return that;
};

184
source/UI/js/Form/Form.js Normal file
View file

@ -0,0 +1,184 @@
'use strict';
/*@
Ox.Form <f> Form Object
options <o> Options object
error <s> error
id <s> id
items <a|[]> []
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Form Object
change <!> change
validate <!> validate
submit <!> submit
@*/
Ox.Form = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
error: '',
id: '',
items: [],
validate: function(valid) {
return Ox.every(valid);
}
})
.options(options || {})
.addClass('OxForm');
Ox.extend(self, {
$items: [],
$messages: [],
itemIds: [],
itemIsValid: []
});
self.options.items.forEach(function(item, i) {
validateItem(i, function(valid) {
self.itemIsValid[i] = valid;
});
self.itemIds[i] = item.options('id') || item.id;
self.$items[i] = Ox.FormItem({element: item}).appendTo(that);
item.bindEvent({
autovalidate: function(data) {
validateForm(i, data.valid);
data.valid && self.$items[i].setMessage('');
},
/*
// fixme: should't inputs also trigger a change event?
blur: function(data) {
that.triggerEvent('change', {
id: self.itemIds[i],
data: data
});
},
*/
change: function(data) {
// fixme: shouldn't this be key/value instead of id/data?
that.triggerEvent('change', {
id: self.itemIds[i],
data: data
});
validateItem(i, function(valid) {
validateForm(i, valid);
});
},
submit: function(data) {
self.formIsValid && that.submit();
},
validate: function(data) {
validateForm(i, data.valid);
// timeout needed for cases where the form is removed
// from the DOM, triggering blur of an empty item -
// in this case, we don't want the message to appear
setTimeout(function() {
self.$items[i].setMessage(data.valid ? '' : data.message);
}, 0);
}
});
});
self.formIsValid = self.options.validate(self.itemIsValid);
function getItemIndexById(id) {
return self.itemIds.indexOf(id);
}
function validateForm(pos, valid) {
self.itemIsValid[pos] = valid;
if (self.options.validate(self.itemIsValid) != self.formIsValid) {
self.formIsValid = !self.formIsValid;
that.triggerEvent('validate', {
valid: self.formIsValid
});
}
}
function validateItem(pos, callback) {
var item = self.options.items[pos],
validate = item.options('validate');
if (validate) {
validate(item.value(), function(data) {
callback(data.valid);
});
} else {
callback(item.value && !Ox.isEmpty(item.value));
}
}
/*@
addItem <f> addItem
(pos, item) -> <u> add item at position
@*/
that.addItem = function(pos, item) {
Ox.Log('Form', 'addItem', pos)
self.options.items.splice(pos, 0, item);
self.$items.splice(pos, 0, Ox.FormItem({element: item}));
pos == 0 ?
self.$items[pos].insertBefore(self.$items[0]) :
self.$items[pos].insertAfter(self.$items[pos - 1]);
}
/*@
removeItem <f> removeItem
(pos) -> <u> remove item from position
@*/
that.removeItem = function(pos) {
Ox.Log('Form', 'removeItem', pos);
self.$items[pos].remove();
self.options.items.splice(pos, 1);
self.$items.splice(pos, 1);
}
that.setMessages = function(messages) {
Ox.forEach(messages, function(v) {
self.$items[getItemIndexById(v.id)].setMessage(v.message);
});
};
/*@
submit <f> submit
@*/
that.submit = function() {
that.triggerEvent('submit', {values: that.values()});
};
/*@
valid <f> valid
@*/
that.valid = function() {
return self.formIsValid;
};
/*@
values <f> values
@*/
that.values = function() {
// FIXME: this should accept a single string argument to get a single value
/*
get/set form values
call without arguments to get current form values
pass values as array to set values (not implemented)
*/
var values = {};
if (arguments.length == 0) {
self.$items.forEach(function($item, i) {
values[self.itemIds[i]] = self.$items[i].value();
});
//Ox.Log('Form', 'VALUES', values)
return values;
} else {
Ox.Log('Form', 'SET FORM VALUES', arguments[0])
Ox.forEach(arguments[0], function(value, key) {
var index = getItemIndexById(key);
index > -1 && self.options.items[index].value(value);
});
return that;
}
};
return that;
};

View file

@ -0,0 +1,128 @@
'use strict';
/*@
Ox.FormElementGroup <f> FormElementGroup Element
options <o> Options object
id <s> element id
elements <[o:Ox.Element]|[]> elements in group
float <s|left> alignment
separators <a|[]> separators (not implemented)
width <n|0> group width
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> FormElementGroup Element
autovalidate <!> autovalidate
change <!> change
validate <!> validate
@*/
Ox.FormElementGroup = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
id: '',
elements: [],
float: 'left',
join: null,
separators: [],
split: null,
value: options.split ? '' : [],
width: 0
})
.options(options || {})
.update({
value: setValue
})
.addClass('OxInputGroup');
if (Ox.isEmpty(self.options.value)) {
self.options.value = getValue();
} else {
setValue();
}
(
self.options.float == 'left' ?
self.options.elements : Ox.clone(self.options.elements).reverse()
).forEach(function($element) {
$element.css({
float: self.options.float // fixme: make this a class
})
.bindEvent({
autovalidate: function(data) {
that.triggerEvent({autovalidate: data});
},
change: change,
//submit: change,
validate: function(data) {
that.triggerEvent({validate: data});
}
})
.appendTo(that);
});
function change(data) {
self.options.value = getValue();
that.triggerEvent('change', {value: self.options.value});
}
/*
if (self.options.width) {
setWidths();
} else {
self.options.width = getWidth();
}
that.css({
width: self.options.width + 'px'
});
*/
function getValue() {
var value = self.options.elements.map(function($element) {
return $element.value ? $element.value() : void 0;
});
return self.options.join ? self.options.join(value) : value;
}
function getWidth() {
}
function setValue() {
var values = self.options.split
? self.options.split(self.options.value)
: self.options.value;
values.forEach(function(value, i) {
self.options.elements[i].value && self.options.elements[i].value(value);
});
}
function setWidth() {
}
/*@
replaceElement <f> replaceElement
(pos, element) -> <u> replcae element at position
@*/
that.replaceElement = function(pos, element) {
Ox.Log('Form', 'Ox.FormElementGroup replaceElement', pos, element)
self.options.elements[pos].replaceWith(element.$element);
self.options.elements[pos] = element;
};
/*@
value <f> value
@*/
that.value = function() {
var values = self.options.elements.map(function(element) {
return element.value ? element.value() : void 0;
});
return self.options.joinValues
? self.options.joinValues(values)
: values;
};
return that;
};

View file

@ -0,0 +1,53 @@
'use strict';
/*@
Ox.FormItem <f> FormItem Element, wraps form element with an error message
options <o> Options object
element <o|null> element
error <s> error message
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> FormItem Element
@*/
Ox.FormItem = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
element: null,
error: ''
})
.options(options || {})
.addClass('OxFormItem');
self.description = self.options.element.options('description');
if (self.description) {
$('<div>')
.addClass('OxFormDescription OxSelectable')
.html(self.description)
.appendTo(that);
}
that.append(self.options.element);
self.$message = Ox.Element()
.addClass('OxFormMessage OxSelectable')
.appendTo(that);
/*@
setMessage <f> set message
(message) -> <u> set message
@*/
that.setMessage = function(message) {
self.$message.html(message)[message !== '' ? 'show' : 'hide']();
};
/*@
value <f> get value
() -> <s> get value of wrapped element
@*/
that.value = function() {
return self.options.element.value();
};
return that;
};

View file

@ -0,0 +1,203 @@
'use strict';
/*@
Ox.FormPanel <f> Form Panel
options <o> Options
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Form Panel
change <!> Fires when a value changed
select <!> Fires when a section gets selected
validate <!> Fires when the form becomes valid or invalid
@*/
Ox.FormPanel = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
form: [],
listSize: 256
})
.options(options || {});
self.section = 0;
self.sectionTitle = self.options.form[self.section].title;
self.$list = Ox.TableList({
columns: [
{
id: 'id',
visible: false
},
{
format: function(value) {
return $('<img>')
.attr({
src: Ox.UI.getImageURL('symbolCheck')
})
.css({
width: '10px',
height: '10px',
margin: '2px 2px 2px 0',
opacity: value ? 1 : 0.1
})
},
id: 'valid',
title: Ox._('Valid'),
visible: true,
width: 16
},
{
format: function(value) {
return (Ox.indexOf(self.options.form, function(section) {
return section.title == value;
}) + 1) + '. ' + value;
},
id: 'title',
title: Ox._('Title'),
visible: true,
width: self.options.listSize - 16
}
],
items: self.options.form.map(function(section) {
return {id: section.id, title: section.title, valid: false};
}),
max: 1,
min: 1,
selected: [self.options.form[0].id],
sort: [{key: 'id', operator: '+'}],
unique: 'id',
width: self.options.listSize
}).bindEvent({
select: function(data) {
self.$sections[self.section].hide();
self.section = Ox.getIndexById(self.options.form, data.ids[0]);
self.$sections[self.section].show();
that.triggerEvent('select', {section: data.ids[0]});
}
});
self.$section = $('<div>')
.css({overflowY: 'auto'});
self.$forms = [];
self.$sections = self.options.form.map(function(section, i) {
return $('<div>')
.css({
width: (
section.descriptionWidth || section.items[0].options('width')
) + 'px',
margin: '16px'
})
.append(
$('<div>')
.addClass('OxSelectable')
.css({marginBottom: '8px', fontWeight: 'bold'})
.html((i + 1) + '. ' + section.title)
)
.append(
$('<div>')
.addClass('OxSelectable')
.css({marginBottom: '16px'})
.html(section.description)
)
.append(
self.$forms[i] = Ox.Form({
items: section.items,
validate: section.validate
})
.bindEvent({
change: function(data) {
self.$list.value(section.id, 'valid', self.$forms[i].valid());
that.triggerEvent('change', {
section: section.id,
data: data
});
},
validate: function(data) {
self.$list.value(section.id, 'valid', data.valid);
that.triggerEvent('validate', {
section: section.id,
data: data
});
}
})
)
.hide()
.appendTo(self.$section);
});
self.$list.bindEvent('load', function() {
self.$forms.forEach(function($form, i) {
self.$list.value(self.options.form[i].id, 'valid', $form.valid());
});
});
self.$sections[0].show();
that.setElement(Ox.SplitPanel({
elements: [
{
element: self.$list,
size: self.options.listSize
},
{
element: self.$section
}
],
orientation: 'horizontal'
}));
/*@
renderPrintVersion <f> renderPrintVersion
(title) -> <s>
@*/
that.renderPrintVersion = function(title) {
var $printVersion = $('<div>').css({overflowY: 'auto'});
$printVersion.append(
$('<div>')
.addClass('OxFormSectionTitle')
.css({
height: '16px',
padding: '16px 16px 8px 16px',
fontWeight: 'bold'
})
.html(title)
);
self.$sections.forEach(function($section, i) {
// jQuery bug: textarea html/val does not survive cloning
// http://bugs.jquery.com/ticket/3016
var $clone = $section.clone(true),
textareas = {
section: $section.find('textarea'),
clone: $clone.find('textarea')
};
textareas.section.each(function(i) {
$(textareas.clone[i]).val($(this).val());
});
$printVersion
.append(
$('<div>').css({
height: '1px',
margin: '8px 0 8px 0',
background: 'rgb(128, 128, 128)'
})
)
.append(
$clone.show()
);
});
return $printVersion;
};
/*@
values <f> values
@*/
that.values = function() {
var values = {};
self.options.form.forEach(function(section, i) {
values[section.id] = self.$forms[i].values();
});
return values;
};
return that;
};

983
source/UI/js/Form/Input.js Normal file
View file

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

View file

@ -0,0 +1,161 @@
'use strict';
/*@
Ox.InputGroup <f> InputGroup Object
options <o> Options object
id <s|''> id
inputs <a|[]> inputs
separators <a|[]> seperators
width <n|0> width
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> InputGroup Object
change <!> change
validate <!> validate
@*/
Ox.InputGroup = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
id: '',
inputs: [],
join: null,
separators: [],
split: null,
value: options.split ? '' : [],
width: 0
})
.options(options || {})
.update({
value: setValue
})
.addClass('OxInputGroup')
.on({click: click});
if (Ox.isEmpty(self.options.value)) {
self.options.value = getValue();
} else {
setValue();
}
if (self.options.width) {
setWidths();
} else {
self.options.width = getWidth();
}
that.css({
width: self.options.width + 'px'
});
self.$separator = [];
self.options.separators.forEach(function(v, i) {
self.$separator[i] = Ox.Label({
textAlign: 'center',
title: v.title,
width: v.width + 32
})
.addClass('OxSeparator')
.css({
marginLeft: (self.options.inputs[i].options('width') - (i == 0 ? 16 : 32)) + 'px'
})
.appendTo(that);
});
self.options.inputs.forEach(function($input, i) {
$input.options({
id: self.options.id + Ox.toTitleCase($input.options('id') || '')
})
.css({
marginLeft: -Ox.sum(self.options.inputs.map(function($input, i_) {
return i_ > i
? self.options.inputs[i_ - 1].options('width')
+ self.options.separators[i_ - 1].width
: i_ == i ? 16
: 0;
})) + 'px'
})
.bindEvent({
change: change,
submit: change,
validate: validate
})
.appendTo(that);
});
function change(data) {
self.options.value = getValue();
that.triggerEvent('change', {
value: self.options.value
});
}
function click(event) {
if ($(event.target).hasClass('OxSeparator')) {
Ox.forEach(self.options.inputs, function($input) {
if ($input.focusInput) {
$input.focusInput(true);
return false; // break
}
});
}
}
function getValue() {
var value = self.options.inputs.map(function($input) {
return $input.value();
});
return self.options.join ? self.options.join(value) : value;
}
function getWidth() {
return Ox.sum(self.options.inputs.map(function(v) {
return v.options('width');
})) + Ox.sum(self.options.separators.map(function(v) {
return v.width;
}));
}
function setValue() {
var values = self.options.split
? self.options.split(self.options.value)
: self.options.value;
values.forEach(function(value, i) {
self.options.inputs[i].value(value);
});
}
function setWidths() {
var length = self.options.inputs.length,
inputWidths = Ox.splitInt(
self.options.width - Ox.sum(self.options.separators.map(function(v) {
return v.width;
})), length
);
self.options.inputs.forEach(function(v) {
v.options({
width: inputWidths[1] // fixme: 1?? i?
});
});
}
function validate(data) {
that.triggerEvent('validate', data);
}
// fixme: is this used?
that.getInputById = function(id) {
var input = null;
Ox.forEach(self.options.inputs, function(v, i) {
if (v.options('id') == self.options.id + Ox.toTitleCase(id)) {
input = v;
return false; // break
}
});
return input;
};
return that;
};

View file

@ -0,0 +1,222 @@
'use strict';
/*@
Ox.InsertHTMLDialog <f> Insert HTML Dialog
options <o> Options
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Dialog> Insert HTML Dialog
@*/
Ox.InsertHTMLDialog = function(options, self) {
var that;
self = self || {};
self.options = Ox.extend({
callback: void 0,
end: 0,
selection: '',
start: 0
}, options || {});
self.type = self.options.selection.indexOf('\n') > -1
? 'textarea' : 'input';
self.items = [
{id: 'img', title: Ox._('Image')},
{id: 'a', title: Ox._('Link')},
{id: 'li', title: Ox._('List')},
{},
{id: 'blockquote', title: Ox._('Blockquote')},
{id: 'h1', title: Ox._('Headline')},
{id: 'p', title: Ox._('Paragraph')},
{id: 'div', title: Ox._('Right-to-Left')},
{},
{id: 'b', title: Ox._('Bold')},
{id: 'i', title: Ox._('Italic')},
{id: 'code', title: Ox._('Monospace')},
{id: 's', title: Ox._('Strike')},
{id: 'sub', title: Ox._('Subscript')},
{id: 'sup', title: Ox._('Superscript')},
{id: 'u', title: Ox._('Underline')},
{},
{id: 'br', title: Ox._('Linebreak')}
].map(function(item, i) {
var form, format;
if (item.id == 'img') {
form = [
Ox.Input({
id: 'url',
label: 'URL',
labelWidth: 128,
width: 384
})
];
format = function(values) {
return '<img src="' + values.url + '"/>';
};
} else if (item.id == 'a') {
form = [
Ox.Input({
height: 104,
id: 'text',
label: 'Text',
labelWidth: 128,
type: self.type,
width: 384,
value: self.options.selection
})
.css({background: 'transparent'}),
Ox.Input({
id: 'url',
label: 'URL',
labelWidth: 128,
width: 384
})
];
format = function(values) {
return '<a href="' + values.url + '">' + values.text + '</a>';
};
} else if (item.id == 'li') {
form = [
Ox.Select({
id: 'style',
items: [
{id: 'ul', title: Ox._('Bullets')},
{id: 'ol', title: Ox._('Numbers')}
],
label: 'Style',
labelWidth: 128,
width: 384
}),
Ox.ArrayInput({
id: 'items',
label: 'Items',
max: 10,
value: self.options.selection.split('\n'),
width: 384
})
];
format = function(values) {
return '<' + values.style + '>\n' + values.items.map(function(value) {
return '<li>' + value + '</li>\n';
}).join('') + '</' + values.style + '>';
};
} else if (['p', 'blockquote', 'div'].indexOf(item.id) > -1) {
form = [
Ox.Input({
height: 128,
id: 'text',
label: 'Text',
labelWidth: 128,
type: 'textarea',
value: self.options.selection,
width: 384
})
.css({background: 'transparent'})
];
format = function(values) {
return '<' + item.id + (
item.id == 'div' ? ' style="direction: rtl"' : ''
) + '>' + values.text + '</' + item.id + '>';
};
} else if (['h1', 'b', 'i', 'code', 's', 'sub', 'sup', 'u'].indexOf(item.id) > -1) {
form = [
Ox.Input({
height: 128,
id: 'text',
label: 'Text',
labelWidth: 128,
type: self.type,
value: self.options.selection,
width: 384
})
.css({background: 'transparent'})
];
format = function(values) {
return '<' + item.id + '>' + values.text + '</' + item.id + '>';
};
} else if (item.id == 'br') {
form = [];
format = function() {
return '<br/>';
};
}
return item.id ? Ox.extend(item, {
form: form,
format: format
}) : item;
});
self.$content = $('<div>')
.css({padding: '16px'});
self.$select = Ox.Select({
items: self.items,
label: Ox._('Insert'),
labelWidth: 128,
value: 'img',
width: 384
})
.bindEvent({
change: renderForm
})
.appendTo(self.$content);
renderForm();
that = Ox.Dialog({
buttons: [
Ox.Button({
id: 'cancel',
title: Ox._('Cancel'),
width: 64
})
.bindEvent({
click: function() {
that.close();
}
}),
Ox.Button({
id: 'insert',
title: Ox._('Insert'),
width: 64
})
.bindEvent({
click: function() {
var item = Ox.getObjectById(self.items, self.$select.value()),
value = item.format(
item.form.length ? self.$form.values() : void 0
);
self.options.callback({
position: self.options.start + value.length,
value: self.options.value.slice(0, self.options.start)
+ value
+ self.options.value.slice(self.options.end)
});
that.close();
}
})
],
closeButton: true,
content: self.$content,
height: 184,
keys: {enter: 'insert', escape: 'cancel'},
title: Ox._('Insert HTML'),
width: 416 + Ox.UI.SCROLLBAR_SIZE
});
function renderForm() {
var items = Ox.getObjectById(self.items, self.$select.value()).form;
self.$form && self.$form.remove();
if (items.length) {
self.$form = Ox.Form({
items: items
})
.css({paddingTop: '8px'})
.appendTo(self.$content);
}
}
return that;
};

View file

@ -0,0 +1,54 @@
'use strict';
/*@
Ox.Label <f> Label element
options <o|u> Options object
self <o|u> Shared private variable
([options[, self]]) -> <o:Ox.Element> Label element
@*/
Ox.Label = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
disabled: false,
id: '',
overlap: 'none',
textAlign: 'left',
style: 'rounded',
title: '',
width: 'auto'
})
.options(options || {})
.update({
title: function() {
that.html(self.options.title);
},
width: function() {
that.css({
width: self.options.width - (
self.options.style == 'rounded' ? 14 : 8
) + 'px'
});
}
})
.addClass(
'OxLabel Ox' + Ox.toTitleCase(self.options.style)
+ (self.options.disabled ? ' OxDisabled' : '')
+ (
self.options.overlap != 'none'
? ' OxOverlap' + Ox.toTitleCase(self.options.overlap) : ''
)
)
.css(Ox.extend(self.options.width == 'auto' ? {} : {
width: self.options.width - (
self.options.style == 'rounded' ? 14 : 8
) + 'px'
}, {
textAlign: self.options.textAlign
}))
.html(Ox.isUndefined(self.options.title) ? '' : self.options.title);
return that;
};

View file

@ -0,0 +1,172 @@
'use strict';
/*@
Ox.ObjectArrayInput <f> Object Array Input
options <o> Options
buttonTitles
inputs
labelWidth
max
value
width
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Object Array Input
change <!> change
@*/
Ox.ObjectArrayInput = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
buttonTitles: {add: Ox._('Add'), remove: Ox._('Remove')},
inputs: [],
labelWidth: 128,
max: 0,
value: [],
width: 256
})
.options(options || {})
.update({
value: function() {
setValue(self.options.value);
}
})
.addClass('OxObjectArrayInput');
if (Ox.isEmpty(self.options.value)) {
self.options.value = [getDefaultValue()];
}
self.$element = [];
self.$input = [];
self.$removeButton = [];
self.$addButton = [];
self.buttonWidth = self.options.width / 2 - 4;
setValue(self.options.value);
function addInput(index, value) {
self.$element.splice(index, 0, Ox.Element()
.css({
width: self.options.width + 'px',
})
);
if (index == 0) {
self.$element[index].appendTo(that);
} else {
self.$element[index].insertAfter(self.$element[index - 1]);
}
self.$input.splice(index, 0, Ox.ObjectInput({
elements: self.options.inputs.map(function(input) {
return Ox[input.element](input.options || {})
.bindEvent(input.events || {});
}),
labelWidth: self.options.labelWidth,
value: value,
width: self.options.width
})
.bindEvent({
change: function(data) {
var index = $(this).parent().data('index');
self.options.value[index] = self.$input[index].value();
that.triggerEvent('change', {
value: self.options.value
});
}
})
.appendTo(self.$element[index])
);
self.$removeButton.splice(index, 0, Ox.Button({
disabled: self.$input.length == 1,
title: self.options.buttonTitles.remove,
width: self.buttonWidth
})
.css({margin: '8px 4px 0 0'})
.on({
click: function() {
var index = $(this).parent().data('index');
if (self.$input.length > 1) {
removeInput(index);
self.options.value = getValue();
that.triggerEvent('change', {
value: self.options.value
});
}
}
})
.appendTo(self.$element[index])
);
self.$addButton.splice(index, 0, Ox.Button({
disabled: index == self.options.max - 1,
title: self.options.buttonTitles.add,
width: self.buttonWidth
})
.css({margin: '8px 0 0 4px'})
.on({
click: function() {
var index = $(this).parent().data('index');
addInput(index + 1, getDefaultValue());
self.options.value = getValue();
that.triggerEvent('change', {
value: self.options.value
});
}
})
.appendTo(self.$element[index])
);
updateInputs();
}
function getDefaultValue() {
var value = {};
self.options.inputs.forEach(function(input) {
value[input.options.id] = '';
});
return value;
}
function getValue() {
return self.$input.map(function($input) {
return $input.value();
});
}
function removeInput(index) {
[
'input', 'removeButton', 'addButton', 'element'
].forEach(function(element) {
var key = '$' + element;
self[key][index].remove();
self[key].splice(index, 1);
});
updateInputs();
}
function setValue(value) {
while (self.$element.length) {
removeInput(0);
}
value.forEach(function(value, i) {
addInput(i, value);
});
}
function updateInputs() {
var length = self.$element.length;
self.$element.forEach(function($element, i) {
$element
[i == 0 ? 'addClass' : 'removeClass']('OxFirst')
[i == length - 1 ? 'addClass' : 'removeClass']('OxLast')
.data({index: i});
self.$removeButton[i].options({
disabled: length == 1
});
self.$addButton[i].options({
disabled: length == self.options.max
});
});
}
return that;
};

View file

@ -0,0 +1,69 @@
'use strict';
/*@
Ox.ObjectInput <f> Object Input
options <o> Options
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Object Input
change <!> change
@*/
Ox.ObjectInput = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
elements: [],
labelWidth: 128,
value: {},
width: 256
})
.options(options || {})
.update({
value: function() {
setValue(self.options.value);
}
})
.addClass('OxObjectInput')
.css({
width: self.options.width + 'px',
});
if (Ox.isEmpty(self.options.value)) {
self.options.value = getValue();
} else {
setValue(self.options.value);
}
self.options.elements.forEach(function($element) {
$element.options({
labelWidth: self.options.labelWidth,
width: self.options.width
})
.bindEvent({
change: function(data) {
self.options.value = getValue();
that.triggerEvent('change', {
value: self.options.value
});
}
})
.appendTo(that);
});
function getValue() {
var value = {};
self.options.elements.forEach(function(element) {
value[element.options('id')] = element.value();
});
return value;
}
function setValue(value) {
self.options.elements.forEach(function(element) {
element.value(value[element.options('id')]);
});
}
return that;
};

View file

@ -0,0 +1,124 @@
'use strict';
/*@
Ox.OptionGroup <f> OptionGroup
Helper object, used by ButtonGroup, CheckboxGroup, Select and Menu
(items, min, max, property) -> <f> OptionGroup
items <a> array of items
min <n> minimum number of selected items
max <n> maximum number of selected items
property <s|'checked'> property to check
@*/
// FIXME: Should be moved to Ox.js
Ox.OptionGroup = function(items, min, max, property) {
var length = items.length;
property = property || 'checked';
max = max == -1 ? length : max;
function getLastBefore(pos) {
// returns the position of the last checked item before position pos
var last = -1;
// fixme: why is length not == items.length here?
Ox.forEach([].concat(
pos > 0 ? Ox.range(pos - 1, -1, -1) : [],
pos < items.length - 1 ? Ox.range(items.length - 1, pos, -1) : []
), function(v) {
if (items[v][property]) {
last = v;
return false; // break
}
});
return last;
}
function getNumber() {
// returns the number of checked items
return items.reduce(function(prev, curr) {
return prev + !!curr[property];
}, 0);
}
/*@
[property] <f> returns an array with the positions of all checked items
() -> <a> positions of checked items
@*/
// FIXME: isn't value more useful in all cases?
this[property] = function() {
return Ox.indicesOf(items, function(item) {
return item[property];
});
};
/*@
init <f> init group
() -> <a> returns items
@*/
this.init = function() {
var num = getNumber(),
count = 0;
//if (num < min || num > max) {
items.forEach(function(item) {
if (Ox.isUndefined(item[property])) {
item[property] = false;
}
if (item[property]) {
count++;
if (count > max) {
item[property] = false;
}
} else {
if (num < min) {
item[property] = true;
num++;
}
}
});
//}
return items;
};
/*@
toggle <f> toggle options
(pos) -> <a> returns toggled state
@*/
this.toggle = function(pos) {
var last,
num = getNumber(),
toggled = [];
if (!items[pos][property]) { // check
if (num >= max) {
last = getLastBefore(pos);
items[last][property] = false;
toggled.push(last);
}
if (!items[pos][property]) {
items[pos][property] = true;
toggled.push(pos);
}
} else { // uncheck
if (num > min) {
items[pos][property] = false;
toggled.push(pos);
}
}
return toggled;
};
/*@
value <f> get value
@*/
this.value = function() {
var value = items.filter(function(item) {
return item[property];
}).map(function(item) {
return item.id;
});
return max == 1 ? (value.length ? value[0] : '') : value;
};
return this;
}

110
source/UI/js/Form/Picker.js Normal file
View file

@ -0,0 +1,110 @@
'use strict';
/*@
Ox.Picker <f> Picker Object
options <o> Options object
element <o|null> picker element
elementHeight <n|128> height
elemementWidth <n|256> width
id <s> picker id
overlap <s|none> select button overlap value
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Picker Object
show <!> picker is shown
hide <!> picker is hidden
@*/
Ox.Picker = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
element: null,
elementHeight: 128,
elementWidth: 256,
overlap: 'right', // 'none'
})
.options(options || {});
self.$selectButton = Ox.Button({
overlap: self.options.overlap,
title: 'select',
type: 'image'
})
.bindEvent({
click: showMenu
})
.appendTo(that);
self.$menu = Ox.Element()
.addClass('OxPicker')
.css({
width: self.options.elementWidth + 'px',
height: (self.options.elementHeight + 24) + 'px'
});
self.options.element.css({
width: self.options.elementWidth + 'px',
height: self.options.elementHeight + 'px'
})
.appendTo(self.$menu);
self.$bar = Ox.Bar({
orientation: 'horizontal',
size: 24
})
.appendTo(self.$menu);
that.$label = Ox.Label({
width: self.options.elementWidth - 60
})
.appendTo(self.$bar);
self.$doneButton = Ox.Button({
title: Ox._('Done'),
width: 48
})
.bindEvent({
click: hideMenu
})
.appendTo(self.$bar);
self.$layer = Ox.$('<div>')
.addClass('OxLayer')
.on({
click: hideMenu
});
function hideMenu() {
self.$menu.detach();
self.$layer.detach();
self.$selectButton
.removeClass('OxSelected')
.css({
borderRadius: '8px'
});
that.triggerEvent('hide');
}
function showMenu() {
var offset = that.offset(),
left = offset.left,
top = offset.top + 15;
self.$selectButton
.addClass('OxSelected')
.css({
borderRadius: '8px 8px 0 0'
});
self.$layer.appendTo(Ox.$body);
self.$menu
.css({
left: left + 'px',
top: top + 'px'
})
.appendTo(Ox.$body);
that.triggerEvent('show');
}
return that;
};

View file

@ -0,0 +1,44 @@
'use strict';
/*@
Ox.PlaceInput <f> PlaceInput Object
options <o> Options object
id <s> element id
value <s|United States> default value of place input
self <o> Shared private variable
([options[, self]]) -> <o:Ox.FormElementGroup> PlaceInput Object
@*/
Ox.PlaceInput = function(options, self) {
var that;
self = Ox.extend(self || {}, {
options: Ox.extend({
id: '',
value: 'United States'
}, options)
});
that = Ox.FormElementGroup({
id: self.options.id,
elements: [
Ox.Input({
id: 'input',
value: self.options.value
}),
Ox.PlacePicker({
id: 'picker',
overlap: 'left',
value: self.options.value
})
],
float: 'right'
}, self)
.bindEvent('change', change);
function change() {
}
return that;
};

View file

@ -0,0 +1,155 @@
'use strict';
/*@
Ox.PlacePicker <f> PlacePicker Object
options <o> Options object
id <s> element id
value <s|United States> default value of place input
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Picker> PlacePicker Object
@*/
Ox.PlacePicker = function(options, self) {
var that;
self = Ox.extend(self || {}, {
options: Ox.extend({
id: '',
value: 'United States'
}, options)
});
self.$element = Ox.Element()
.css({
width: '256px',
height: '192px'
})
.append(
self.$topBar = Ox.Bar({
size: 16
})
.css({
MozBorderRadius: '0 8px 0 0',
WebkitBorderRadius: '0 8px 0 0'
})
.append(
self.$input = Ox.Input({
clear: true,
id: self.options.id + 'Input',
placeholder: Ox._('Find'),
width: 256
})
.bindEvent('submit', findPlace)
)
)
.append(
self.$container = Ox.Element()
.css({
width: '256px',
height: '160px'
})
)
.append(
self.$bottomBar = Ox.Bar({
size: 16
})
.append(
self.$range = Ox.Range({
arrows: true,
changeOnDrag: true,
id: self.options.id + 'Range',
max: 22,
size: 256,
thumbSize: 32,
thumbValue: true
})
.bindEvent('change', changeZoom)
)
);
self.$input.children('input[type=text]').css({
width: '230px',
paddingLeft: '2px',
MozBorderRadius: '0 8px 8px 0',
WebkitBorderRadius: '0 8px 8px 0'
});
self.$input.children('input[type=image]').css({
MozBorderRadius: '0 8px 0 0',
WebkitBorderRadius: '0 8px 0 0'
});
self.$range.children('input').css({
MozBorderRadius: 0,
WebkitBorderRadius: 0
});
that = Ox.Picker({
element: self.$element,
elementHeight: 192,
elementWidth: 256,
id: self.options.id,
overlap: self.options.overlap,
value: self.options.value
}, self)
.bindEvent('show', showPicker);
that.$label.on('click', clickLabel);
self.map = false;
function changeZoom(data) {
//Ox.Log('Form', 'changeZoom')
self.$map.zoom(data.value);
}
function clickLabel() {
var name = that.$label.html();
if (name) {
self.$input
.value(name)
.triggerEvent('submit', {
value: name
});
}
}
function findPlace(data) {
//Ox.Log('Form', 'findPlace', data);
self.$map.find(data.value, function(place) {
place && that.$label.html(place.geoname);
});
}
function onSelect(data) {
that.$label.html(data.geoname);
}
function onZoom(data) {
self.$range.value(data.value);
}
function showPicker() {
if (!self.map) {
self.$map = Ox.Map({
clickable: true,
id: self.options.id + 'Map',
// fixme: this is retarded, allow for map without places
places: [{south: -85, west: -179, north: -85, east: 179}]
//places: [self.options.value]
})
.css({
top: '16px',
width: '256px',
height: '160px'
})
.bindEvent({
select: onSelect,
zoom: onZoom
})
.appendTo(self.$container);
self.map = true;
}
}
return that;
};

295
source/UI/js/Form/Range.js Normal file
View file

@ -0,0 +1,295 @@
'use strict';
/*@
Ox.Range <f> Range Object
options <o> Options object
arrows <b> if true, show arrows
arrowStep <n> step when clicking arrows
arrowSymbols <[s]> arrow symbols, like ['minus', 'plus']
arrowTooltips <[s]> arrow tooltips
max <n> maximum value
min <n> minimum value
orientation <s> 'horizontal' or 'vertical'
step <n> step between values
size <n> width or height, in px
thumbSize <n> minimum width or height of thumb, in px
thumbStyle <s|'opaque'> Thumb style ('opaque' or 'transparent')
thumbValue <b> if true, display value on thumb
trackColors <[s]> CSS colors
trackGradient <b|false> if true, display track colors as gradient
trackImages <s|[s]> one or multiple track background image URLs
trackStep <n> 0 (scroll here) or step when clicking track
value <n> initial value
values <[s]> values to display on thumb
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Range Object
change <!> triggered on change of the range
@*/
Ox.Range = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
arrows: false,
arrowStep: 1,
arrowSymbols: ['left', 'right'],
arrowTooltips: ['', ''],
changeOnDrag: false,
max: 100,
min: 0,
orientation: 'horizontal',
step: 1,
size: 128, // fixme: shouldn't this be width?
thumbSize: 16,
thumbStyle: 'default',
thumbValue: false,
trackColors: [],
trackGradient: false,
trackImages: [],
trackStep: 0,
value: 0,
values: []
})
.options(options || {})
.update({
size: setSizes,
trackColors: setTrackColors,
value: setThumb
})
.addClass('OxRange')
.css({
width: self.options.size + 'px'
});
self.hasValues = !Ox.isEmpty(self.options.values);
if (self.hasValues) {
self.options.max = self.options.values.length - 1;
self.options.min = 0;
self.options.step = 1;
self.options.thumbValue = true;
self.options.value = Ox.isNumber(self.options.value)
? self.options.values[self.options.value] : self.options.value;
}
self.options.arrowStep = options.arrowStep || self.options.step;
self.options.trackImages = Ox.makeArray(self.options.trackImages);
self.trackColors = self.options.trackColors.length;
self.trackImages = self.options.trackImages.length;
self.values = (
self.options.max - self.options.min + self.options.step
) / self.options.step;
setSizes();
if (self.options.arrows) {
self.$arrows = [];
Ox.range(0, 2).forEach(function(i) {
self.$arrows[i] = Ox.Button({
overlap: i == 0 ? 'right' : 'left',
title: self.options.arrowSymbols[i],
tooltip: self.options.arrowTooltips[i],
type: 'image'
})
.addClass('OxArrow')
.bindEvent({
mousedown: function(data) {
clickArrow(data, i, true);
},
mouserepeat: function(data) {
clickArrow(data, i, false);
}
})
.appendTo(that);
});
}
self.$track = Ox.Element()
.addClass('OxTrack')
.css(Ox.extend({
width: (self.trackSize - 2) + 'px'
}, self.trackImages == 1 ? {
background: 'rgb(0, 0, 0)'
} : {}))
.bindEvent(Ox.extend({
mousedown: clickTrack,
drag: dragTrack
}, self.options.changeOnDrag ? {} : {
dragend: dragendTrack
}))
.appendTo(that);
self.trackColors && setTrackColors();
if (self.trackImages) {
self.$trackImages = $('<div>')
.css({
width: self.trackSize + 'px',
marginRight: (-self.trackSize - 1) + 'px'
})
.appendTo(self.$track.$element);
self.options.trackImages.forEach(function(v, i) {
$('<img>')
.attr({
src: v
})
.addClass(i == 0 ? 'OxFirstChild' : '')
.addClass(i == self.trackImages - 1 ? 'OxLastChild' : '')
.css({
width: self.trackImageWidths[i] + 'px'
})
.on({
mousedown: function(e) {
e.preventDefault(); // prevent drag
}
})
.appendTo(self.$trackImages);
//left += self.trackImageWidths[i];
});
}
self.$thumb = Ox.Button({
id: self.options.id + 'Thumb',
width: self.thumbSize
})
.addClass('OxThumb' + (
self.options.thumbStyle == 'transparent' ? ' OxTransparent' : ''
))
.appendTo(self.$track);
setThumb();
function clickArrow(data, i, animate) {
// fixme: shift doesn't work, see menu scrolling
setValue(
self.options.value
+ self.options.arrowStep * (i == 0 ? -1 : 1) * (data.shiftKey ? 2 : 1),
animate,
true
);
}
function clickTrack(data) {
// fixme: thumb ends up a bit too far on the right
var isThumb = $(data.target).hasClass('OxThumb');
self.drag = {
left: self.$track.offset().left,
offset: isThumb ? data.clientX - self.$thumb.offset().left - 8 /*self.thumbSize / 2*/ : 0
};
setValue(getValue(data.clientX - self.drag.left - self.drag.offset), !isThumb, true);
}
function dragTrack(data) {
setValue(
getValue(data.clientX - self.drag.left - self.drag.offset),
false,
self.options.changeOnDrag
);
}
function dragendTrack(data) {
self.options.value = void 0;
setValue(getValue(data.clientX - self.drag.left - self.drag.offset), false, true);
}
function getPx(value) {
var pxPerValue = (self.trackSize - self.thumbSize)
/ (self.options.max - self.options.min);
value = self.hasValues ? self.options.values.indexOf(value) : value;
return Math.ceil((value - self.options.min) * pxPerValue);
}
/*
function getTime(oldValue, newValue) {
return self.animationTime * Math.abs(oldValue - newValue) / (self.options.max - self.options.min);
}
*/
function getValue(px) {
var px = self.trackSize / self.values >= 16 ? px : px - 8,
valuePerPx = (self.options.max - self.options.min)
/ (self.trackSize - self.thumbSize),
value = Ox.limit(
self.options.min
+ Math.floor(px * valuePerPx / self.options.step) * self.options.step,
self.options.min,
self.options.max
);
return self.hasValues ? self.options.values[value] : value;
}
function setSizes() {
self.trackSize = self.options.size - self.options.arrows * 32;
self.thumbSize = Math.max(self.trackSize / self.values, self.options.thumbSize);
self.trackImageWidths = self.trackImages == 1
? [self.trackSize - 16]
: Ox.splitInt(self.trackSize - 2, self.trackImages);
self.trackColorStart = self.options.trackGradient
? self.thumbSize / 2 / self.options.size : 0;
self.trackColorStep = self.options.trackGradient
? (self.options.size - self.thumbSize) / (self.trackColors - 1) / self.options.size
: 1 / self.trackColors;
that.css({
width: self.options.size + 'px'
});
self.$track && self.$track.css({
width: (self.trackSize - 2) + 'px'
});
if (self.$thumb) {
self.$thumb.options({width: self.thumbSize});
setThumb();
}
}
function setThumb(animate) {
self.$thumb.stop().animate({
marginLeft: getPx(self.options.value) - 1 + 'px'
//, width: self.thumbSize + 'px'
}, animate ? 250 : 0, function() {
self.options.thumbValue && self.$thumb.options({
title: self.options.value
});
});
}
function setTrackColors() {
['moz', 'o', 'webkit'].forEach(function(browser) {
self.$track.css({
background: '-' + browser + '-linear-gradient(left, '
+ self.options.trackColors[0] + ' 0%, '
+ self.options.trackColors.map(function(v, i) {
var ret = v + ' ' + (
self.trackColorStart + self.trackColorStep * i
) * 100 + '%';
if (!self.options.trackGradient) {
ret += ', ' + v + ' ' + (
self.trackColorStart + self.trackColorStep * (i + 1)
) * 100 + '%';
}
return ret;
}).join(', ') + ', '
+ self.options.trackColors[self.trackColors - 1] + ' 100%)'
});
});
}
function setValue(value, animate, trigger) {
// fixme: toPrecision helps with imprecise results of divisions,
// but won't work for very large values. And is 10 a good value?
value = Ox.limit(
self.hasValues ? self.options.values.indexOf(value) : value.toPrecision(10),
self.options.min,
self.options.max
);
value = self.hasValues ? self.options.values[value] : value;
if (value != self.options.value) {
//time = getTime(self.options.value, value);
self.options.value = value;
setThumb(animate);
trigger && that.triggerEvent('change', {value: self.options.value});
}
}
return that;
};

272
source/UI/js/Form/Select.js Normal file
View file

@ -0,0 +1,272 @@
'use strict';
/*@
Ox.Select <f> Select Object
options <o> Options object
disabled <b|false> If true, select is disabled
id <s> Element id
items <a|[]> Items (array of {id, title} or strings)
label <s|''> Label
labelWidth <n|64> Label width
max <n|1> Maximum number of selected items
maxWidth <n|0> Maximum menu width
min <n|1> Minimum number of selected items
overlap <s|'none'> Can be 'none', 'left' or 'right'
selectable <b|true> is selectable
size <s|'medium'> Size, can be small, medium, large
style <s|'rounded'> Style ('rounded' or 'square')
title <s|''> Select title
tooltip <s|f|''> Tooltip title, or function that returns one
(e) -> <string> Tooltip title
e <object> Mouse event
type <s|'text'> Type ('text' or 'image')
value <a|s> Selected id, or array of selected ids
width <s|n|'auto'> Width in px, or 'auto'
self <o> Shared private variable
([options[, self]) -> <o:Ox.Element> Select Object
click <!> Click event
change <!> Change event
@*/
Ox.Select = function(options, self) {
self = self || {};
var that = Ox.Element({
tooltip: options.tooltip || ''
}, self)
.defaults({
id: '',
items: [],
label: '',
labelWidth: 64,
max: 1,
maxWidth: 0,
min: 1,
overlap: 'none',
size: 'medium',
style: 'rounded',
title: '',
type: 'text',
value: options.max != 1 ? [] : '',
width: 'auto'
})
// fixme: make default selection restorable
.options(options)
.update({
label: function() {
self.$label.options({title: self.options.label});
},
labelWidth: function() {
self.$label.options({width: self.options.labelWidth});
self.$title.css({width: getTitleWidth() + 'px'});
},
title: function() {
var title = self.options.title
? self.options.title
: getItem(self.options.value).title;
if (self.options.type == 'text') {
self.$title.html(title);
} else {
self.$button.options({title: title});
}
},
width: function() {
that.css({width: self.options.width- 2 + 'px'});
self.$title.css({width: getTitleWidth() + 'px'});
},
value: function() {
var value = self.options.value;
if (self.options.type == 'text' && !self.options.title) {
self.$title.html(getItem(value).title);
}
value !== '' && Ox.makeArray(value).forEach(function(value) {
self.$menu.checkItem(value);
});
self.options.value = self.optionGroup.value();
}
})
.addClass(
'OxSelect Ox' + Ox.toTitleCase(self.options.size)
+ ' Ox' + Ox.toTitleCase(self.options.style) + (
self.options.overlap == 'none'
? '' : ' OxOverlap' + Ox.toTitleCase(self.options.overlap)
) + (self.options.label ? ' OxLabelSelect' : '')
)
.css(self.options.width == 'auto' ? {} : {
width: self.options.width - 2 + 'px'
})
.bindEvent({
anyclick: function(e) {
showMenu($(e.target).is('.OxButton') ? 'button' : null);
},
key_escape: loseFocus,
key_down: showMenu
});
self.options.items = self.options.items.map(function(item) {
var isObject = Ox.isObject(item);
return Ox.isEmpty(item) ? item : {
id: isObject ? item.id : item,
title: isObject ? item.title : item,
checked: Ox.makeArray(self.options.value).indexOf(
isObject ? item.id : item
) > -1,
disabled: isObject ? item.disabled : false
};
});
self.optionGroup = new Ox.OptionGroup(
self.options.items,
self.options.min,
self.options.max,
'checked'
);
self.options.items = self.optionGroup.init();
self.options.value = self.optionGroup.value();
if (self.options.label) {
self.$label = Ox.Label({
overlap: 'right',
textAlign: 'right',
title: self.options.label,
width: self.options.labelWidth
})
.appendTo(that);
}
if (self.options.type == 'text') {
self.$title = $('<div>')
.addClass('OxTitle')
.css({
width: getTitleWidth() + 'px'
})
.html(
self.options.title || getItem(self.options.value).title
)
.appendTo(that);
}
self.$button = Ox.Button({
id: self.options.id + 'Button',
selectable: true,
style: 'symbol',
title: self.options.type == 'text' || !self.options.title
? 'select' : self.options.title,
type: 'image'
})
.appendTo(that);
self.$menu = Ox.Menu({
edge: 'bottom',
element: self.$title || self.$button,
id: self.options.id + 'Menu',
items: [{
group: self.options.id + 'Group',
items: self.options.items,
max: self.options.max,
min: self.options.min
}],
maxWidth: self.options.maxWidth,
size: self.options.size
})
.bindEvent({
change: changeMenu,
click: clickMenu,
hide: hideMenu
});
self.options.type == 'image' && self.$menu.addClass('OxRight');
function clickMenu(data) {
that.triggerEvent('click', data);
}
function changeMenu(data) {
self.options.value = self.optionGroup.value();
self.$title && self.$title.html(
self.options.title || getItem(self.options.value).title
);
that.triggerEvent('change', {
title: Ox.isEmpty(self.options.value) ? ''
: Ox.isArray(self.options.value)
? self.options.value.map(function(value) {
return getItem(value).title;
})
: getItem(self.options.value).title,
value: self.options.value
});
}
function getItem(id) {
return Ox.getObjectById(self.options.items, id);
}
function getTitleWidth() {
// fixme: used to be 22. obscure
return self.options.width - 24 - (
self.options.label ? self.options.labelWidth : 0
);
}
function hideMenu() {
that.loseFocus();
that.removeClass('OxSelected');
self.$button.options({value: false});
}
function loseFocus() {
that.loseFocus();
}
function selectItem() {
}
function showMenu(from) {
that.gainFocus();
that.addClass('OxSelected');
from != 'button' && self.$button.options({value: true});
self.options.tooltip && that.$tooltip.hide();
self.$menu.showMenu();
}
/*@
disableItem <f> disableItem
@*/
that.disableItem = function(id) {
self.$menu.getItem(id).options({disabled: true});
return that;
};
/*@
enableItem <f> enableItem
@*/
that.enableItem = function(id) {
self.$menu.getItem(id).options({disabled: false});
return that;
};
/*@
removeElement <f> removeElement
@*/
that.removeElement = function() {
self.$menu.remove();
return Ox.Element.prototype.removeElement.apply(that, arguments);
};
/*@
selected <f> gets selected item
() -> <o> returns array of selected items with id and title
@*/
that.selected = function() {
return Ox.makeArray(self.optionGroup.value()).map(function(id) {
return {
id: id,
title: getItem(id).title
};
});
};
return that;
};

View file

@ -0,0 +1,149 @@
'use strict';
//FIXME: does not work without options
/*@
Ox.SelectInput <f> Select Input
options <o> Options
self <o> Shared private variable
([options[, self]]) -> <o:Ox.FormElementGroup> Select Input
@*/
Ox.SelectInput = function(options, self) {
var that;
self = Ox.extend(self || {}, {
options: Ox.extend({
inputValue: '',
inputWidth: 128,
items: [],
label: '',
labelWidth: 128,
max: 1,
min: 0,
placeholder: '',
title: '',
value: options.max == 1 || options.max == void 0 ? '' : [],
width: 384
}, options || {})
});
self.other = self.options.items[self.options.items.length - 1].id;
self.otherWidth = self.options.width - self.options.inputWidth;
self.$select = Ox.Select({
items: self.options.items,
label: self.options.label,
labelWidth: self.options.labelWidth,
max: self.options.max,
min: self.options.min,
title: getTitle(),
value: self.options.value,
width: self.options.width
})
.bindEvent({
change: function(data) {
self.options.value = getValue();
setValue(self.isOther);
}
});
self.$input = Ox.Input({
placeholder: self.options.placeholder,
width: self.options.inputWidth,
value: self.options.inputValue
})
.bindEvent({
change: function(data) {
self.options.value = data.value;
}
})
.hide();
setValue();
that = Ox.FormElementGroup({
elements: [
self.$select,
self.$input
],
id: self.options.id,
join: function(value) {
return value[value[0] == self.other ? 1 : 0]
},
split: function(value) {
return Ox.filter(self.options.items, function(item, i) {
return i < item.length - 1;
}).map(function(item) {
return item.id;
}).indexOf(value) > -1 ? [value, ''] : [self.other, value];
},
width: self.options.width
})
.update({
label: function() {
self.$select.options({label: self.options.label});
},
value: function() {
self.options.value = that.options('value');
setValue();
}
});
function getTitle() {
var value = self.$select ? self.$select.value() : self.options.value;
return Ox.isEmpty(value)
? self.options.title
: Ox.getObjectById(self.options.items, value).title;
}
function getValue() {
self.isOther = self.$select.value() == self.other;
return !self.isOther ? self.$select.value() : self.$input.value();
}
function setValue(isOther) {
if (
(!self.options.value && isOther !== true)
|| Ox.filter(self.options.items, function(item) {
return item.id != self.other;
}).map(function(item) {
return item.id;
}).indexOf(self.options.value) > -1
) {
self.$select.options({
title: !self.options.value
? self.options.title
: Ox.getObjectById(self.options.items, self.options.value).title,
value: self.options.value,
width: self.options.width
})
.removeClass('OxOverlapRight');
self.$input.hide();
} else {
self.$select.options({
title: Ox.getObjectById(self.options.items, self.other).title,
value: self.other,
width: self.otherWidth
})
.addClass('OxOverlapRight');
self.$input.show().focusInput(true);
}
self.$select.options({title: getTitle()});
}
/*@
value <f> get/set value
() -> value get value
(value) -> <o> set value
@*/
that.value = function() {
if (arguments.length == 0) {
return getValue();
} else {
self.options.value = arguments[0];
setValue();
return that;
}
};
return that;
};

View file

@ -0,0 +1,251 @@
'use strict';
/*@
Ox.Spreadsheet <f> Spreadsheet
options <o> Options
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Spreadsheet
change <!> change
@*/
Ox.Spreadsheet = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
columnPlaceholder: '',
columns: [],
columnTitleType: 'str',
columnWidth: 64,
rowPlaceholder: '',
rows: [],
rowTitleType: 'str',
rowTitleWidth: 128,
title: '',
value: {}
})
.options(options || {})
.addClass('OxSpreadsheet');
if (Ox.isEmpty(self.options.value)) {
self.options.value = {
columns: [],
rows: [],
values: []
}
Ox.loop(4, function(i) {
self.options.value.columns.push('');
self.options.value.rows.push('');
self.options.value.values.push([0, 0, 0, 0]);
});
} else {
self.options.value.values = self.options.value.values || [];
if (Ox.isEmpty(self.options.value.values)) {
self.options.value.values = [];
self.options.value.rows.forEach(function(row, r) {
self.options.value.values.push([]);
self.options.value.columns.forEach(function(column, c) {
self.options.value.values[r].push(0);
});
});
}
}
renderSpreadsheet();
function addColumn(index) {
self.options.value.columns.splice(index, 0, '');
self.options.value.values.forEach(function(columns) {
columns.splice(index, 0, 0);
});
renderSpreadsheet();
}
function addRow(index) {
self.options.value.rows.splice(index, 0, '');
self.options.value.values.splice(index, 0, Ox.repeat([0], self.columns));
renderSpreadsheet();
}
function getSums() {
var sums = {
column: Ox.repeat([0], self.columns),
row: Ox.repeat([0], self.rows),
sheet: 0
};
self.options.value.values.forEach(function(columns, r) {
columns.forEach(function(value, c) {
sums.column[c] += value;
sums.row[r] += value;
sums.sheet += value;
});
});
return sums;
}
function removeColumn(index) {
self.options.value.columns.splice(index, 1);
self.options.value.values.forEach(function(columns) {
columns.splice(index, 1);
});
renderSpreadsheet();
}
function removeRow(index) {
self.options.value.rows.splice(index, 1);
self.options.value.values.splice(index, 1);
renderSpreadsheet();
}
function renderSpreadsheet() {
self.columns = self.options.value.columns.length;
self.rows = self.options.value.rows.length;
self.sums = getSums();
self.$input = {};
that.empty()
.css({
width: self.options.rowTitleWidth
+ self.options.columnWidth * (self.columns + 1) + 'px',
height: 16 * (self.rows + 2) + 'px'
});
[self.options.title].concat(Ox.clone(self.options.value.rows), ['Total']).forEach(function(row, r) {
r--;
[''].concat(Ox.clone(self.options.value.columns), ['Total']).forEach(function(column, c) {
c--;
if (r == -1) {
if (c == -1 || c == self.columns) {
Ox.Label({
style: 'square',
textAlign: c == -1 ? 'left' : 'right',
title: c == -1 ? self.options.title : 'Total',
width: c == -1 ? self.options.rowTitleWidth : self.options.columnWidth
})
.appendTo(that);
} else {
Ox.MenuButton({
style: 'square',
type: 'image',
items: [
{id: 'before', title: Ox._('Add column before')},
{id: 'after', title: Ox._('Add column after')},
{id: 'remove', title: Ox._('Remove this column'), disabled: self.columns == 1}
]
})
.bindEvent({
click: function(data) {
if (data.id == 'remove') {
removeColumn(c);
} else {
addColumn(c + (data.id == 'after'));
}
triggerChangeEvent();
}
})
.appendTo(that);
Ox.Input({
placeholder: self.options.columnPlaceholder,
style: 'square',
type: self.options.columnTitleType,
value: column,
width: self.options.columnWidth - 16
})
.bindEvent({
change: function(data) {
self.options.value.columns[c] = data.value;
triggerChangeEvent();
}
})
.appendTo(that);
}
} else {
if (c == -1) {
if (r < self.rows) {
Ox.MenuButton({
style: 'square',
type: 'image',
items: [
{id: 'before', title: Ox._('Add row above')},
{id: 'after', title: Ox._('Add row below')},
{id: 'remove', title: Ox._('Remove this row'), disabled: self.rows == 1}
]
})
.bindEvent({
click: function(data) {
if (data.id == 'remove') {
removeRow(r);
} else {
addRow(r + (data.id == 'after'));
}
triggerChangeEvent();
}
})
.appendTo(that);
Ox.Input({
placeholder: self.options.rowPlaceholder,
style: 'square',
type: self.options.rowTitleType,
value: row,
width: self.options.rowTitleWidth - 16
})
.bindEvent({
change: function(data) {
self.options.value.rows[r] = data.value;
triggerChangeEvent();
}
})
.appendTo(that);
} else {
Ox.Label({
style: 'square',
textAlign: 'right',
title: row,
width: self.options.rowTitleWidth
})
.appendTo(that);
}
} else {
var id = c + ',' + r,
isColumnSum = r == self.rows,
isRowSum = c == self.columns,
isSheetSum = isColumnSum && isRowSum,
isSum = isColumnSum || isRowSum;
self.$input[id] = Ox.Input({
//changeOnKeypress: true,
disabled: isSum,
style: 'square',
type: 'int',
value: isSheetSum ? self.sums.sheet
: isColumnSum ? self.sums.column[c]
: isRowSum ? self.sums.row[r]
: self.options.value.values[r][c],
width: self.options.columnWidth
})
.appendTo(that);
!isSum && self.$input[id].bindEvent({
change: function(data) {
self.options.value.values[r][c] = parseInt(data.value, 10);
self.sums = getSums();
self.$input[c + ',' + self.rows].value(self.sums.column[c]);
self.$input[self.columns + ',' + r].value(self.sums.row[r]);
self.$input[self.columns + ',' + self.rows].value(self.sums.sheet);
triggerChangeEvent();
}
});
}
}
});
});
}
function triggerChangeEvent() {
that.triggerEvent('change', {
value: self.options.value
});
}
return that;
};

View file

@ -0,0 +1,173 @@
'use strict';
/*@
Ox.TimeInput <f> TimeInput Object
options <o> Options object
ampm <b|false> 24h/ampm
seconds <b|false> show seconds
milliseconds <b|false> show milliseconds
value <d> value, defaults to current time
self <o> Shared private variable
([options[, self]]) -> <o:Ox.InputGroup> TimeInput Object
@*/
Ox.TimeInput = function(options, self) {
// fixme: seconds get set even if options.seconds is false
var that;
self = Ox.extend(self || {}, {
options: Ox.extend({
ampm: false,
seconds: false,
milliseconds: false,
value: (function() {
var date = new Date();
return Ox.formatDate(
date,
options && (options.seconds || options.milliseconds) ? '%T' : '%H:%M'
) + (options && options.milliseconds ? '.' + Ox.pad(date % 1000, 3) : '');
}()),
width: {
hours: 32,
minutes: 32,
seconds: 32,
milliseconds: 40,
ampm: 32
}
}, options || {})
});
self.options.seconds = self.options.seconds || self.options.milliseconds;
self.$input = {
hours: Ox.Input({
autocomplete: (self.options.ampm ? Ox.range(1, 13) : Ox.range(0, 24)).map(function(i) {
return Ox.pad(i, 2);
}),
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'hours',
textAlign: 'right',
width: self.options.width.hours
}),
minutes: Ox.Input({
autocomplete: Ox.range(0, 60).map(function(i) {
return Ox.pad(i, 2);
}),
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'minutes',
textAlign: 'right',
width: self.options.width.minutes
}),
seconds: Ox.Input({
autocomplete: Ox.range(0, 60).map(function(i) {
return Ox.pad(i, 2);
}),
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'seconds',
textAlign: 'right',
width: self.options.width.seconds
}),
milliseconds: Ox.Input({
autocomplete: Ox.range(0, 1000).map(function(i) {
return Ox.pad(i, 3);
}),
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'milliseconds',
textAlign: 'right',
width: self.options.width.milliseconds
}),
ampm: Ox.Input({
autocomplete: ['AM', 'PM'],
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'ampm',
width: self.options.width.ampm
})
};
that = Ox.InputGroup(Ox.extend(self.options, {
id: self.options.id,
inputs: [].concat([
self.$input.hours,
self.$input.minutes,
], self.options.seconds ? [
self.$input.seconds
] : [], self.options.milliseconds ? [
self.$input.milliseconds
] : [], self.options.ampm ? [
self.$input.ampm
] : []),
join: join,
separators: [].concat([
{title: ':', width: 8},
], self.options.seconds ? [
{title: ':', width: 8}
] : [], self.options.milliseconds ? [
{title: '.', width: 8}
] : [], self.options.ampm ? [
{title: '', width: 8}
] : []),
split: split,
value: self.options.value,
width: 0
}), self);
function getDate() {
return new Date('1970/01/01 ' + (
self.options.milliseconds
? self.options.value.slice(0, -4)
: self.options.value
));
}
function getValues() {
var date = getDate();
return {
ampm: Ox.formatDate(date, '%p'),
hours: Ox.formatDate(date, self.options.ampm ? '%I' : '%H'),
milliseconds: self.options.milliseconds ? self.options.value.slice(-3) : '000',
minutes: Ox.formatDate(date, '%M'),
seconds: Ox.formatDate(date, '%S')
};
}
function join() {
return Ox.formatDate(
new Date(
'1970/01/01 ' + [
self.$input.hours.value(),
self.$input.minutes.value(),
self.options.seconds ? self.$input.seconds.value() : '00'
].join(':') + (
self.options.ampm ? ' ' + self.$input.ampm.value() : ''
)
),
(
self.options.seconds ? '%T' : '%H:%M'
) + (
self.options.milliseconds ? '.' + self.$input.milliseconds.value() : ''
)
);
}
function split(value) {
var values = getValues();
return [].concat([
values.hours,
values.minutes,
], self.options.seconds ? [
values.seconds
] : [], self.options.milliseconds ? [
values.milliseconds
] : [], self.options.ampm ? [
values.ampm
] : []);
}
return that;
};