oxjstmp/build/js/ox.ui.js

11134 lines
391 KiB
JavaScript

/*
################################################################################
ox.ui.js
requires
jquery-1.4.js
ox.js
################################################################################
*/
// also see test.js, in demos ...
(function() {
var oxui = {
defaultTheme: 'classic',
elements: {},
getDimensions: function(orientation) {
return orientation == 'horizontal' ?
['width', 'height'] : ['height', 'width'];
},
getEdges: function(orientation) {
return orientation == 'horizontal' ?
['left', 'right', 'top', 'bottom'] :
['top', 'bottom', 'left', 'right'];
},
getBarSize: function(size) {
var sizes = {
small: 20,
medium: 24,
large: 28,
};
return sizes[size];
},
jQueryFunctions: function() {
var functions = [],
$element = $('<div>');
//delete $element.length;
Ox.each($element, function(k, v) {
if (typeof v == 'function') {
functions.push(k);
}
});
return functions.sort();
}(),
path: $('script[src*=ox.ui.js]').attr('src').replace('js/ox.ui.js', ''),
scrollbarSize: $.browser.mozilla ? 16 : 12,
symbols: {
alt: '\u2325',
apple: '\uF8FF',
arrow_down: '\u2193',
arrow_left: '\u2190',
arrow_right: '\u2192',
arrow_up: '\u2191',
backspace: '\u232B',
backup: '\u2707',
ballot: '\u2717',
black_star: '\u2605',
burn: '\u2622',
caps_lock: '\u21EA',
check: '\u2713',
//clear: '\u2327',
clear: '\u00D7',
click: '\uF803',
close: '\u2715',
command: '\u2318',
control: '\u2303',
cut: '\u2702',
'delete': '\u2326',
diamond: '\u25C6',
edit: '\uF802',
eject: '\u23CF',
escape: '\u238B',
end: '\u2198',
enter: '\u2324',
fly: '\u2708',
gear: '\u2699',
home: '\u2196',
info: '\u24D8',
navigate: '\u2388',
option: '\u2387',
page_up: '\u21DE',
page_down: '\u21DF',
redo: '\u21BA',
'return': '\u21A9',
//select: '\u21D5',
select: '\u25BE',
shift: '\u21E7',
sound: '\u266B',
space: '\u2423',
tab: '\u21E5',
trash: '\u267A',
triangle_down: '\u25BC',
triangle_left: '\u25C0',
triangle_right: '\u25BA',
triangle_up: '\u25B2',
undo: '\u21BB',
voltage: '\u26A1',
warning: '\u26A0',
white_star: '\u2606'
}
},
$elements = {},
$window, $document, $body;
_$elements = $elements;
$(function() {
$window = $(window),
$document = $(document),
$body = $('body'),
Ox.theme(oxui.defaultTheme);
});
/*
============================================================================
Application
============================================================================
*/
/**
Creates an App instance.
*/
Ox.App = function() {
return function(options) {
options = options || {};
var self = {},
that = this;
self.time = +new Date();
self.options = $.extend({
apiTimeout: 15000,
apiType: 'POST',
apiURL: '',
config: '',
init: ''
}, options);
function getUserAgent() {
var userAgent = '';
$.each(['Chrome', 'Firefox', 'Internet Explorer', 'Opera', 'Safari'], function(i, v) {
if (navigator.userAgent.indexOf(v) > -1) {
userAgent = v;
return false;
}
});
return userAgent;
}
function getUserData() {
//return {};
return {};
return {
navigator: {
cookieEnabled: navigator.cookieEnabled,
plugins: $.map(navigator.plugins, function(plugin, i) {
return plugin.name;
}),
userAgent: navigator.userAgent
},
screen: screen,
time: (+new Date() - self.time) / 1000,
window: {
innerHeight: window.innerHeight,
innerWidth: window.innerWidth,
outerHeight: window.outerHeight,
outerWidth: window.outerWidth,
screenLeft: window.screenLeft,
screenTop: window.screenTop
}
};
}
function loadImages(callback) {
$.getJSON(oxui.path + 'json/ox.ui.images.json', function(data) {
var counter = 0,
length = data.length;
data.forEach(function(src, i) {
image = new Image()
image.src = oxui.path + src;
image.onload = function() {
(++counter == length) && callback();
}
});
});
}
self.change = function(key, value) {
};
that.api = {
api: function(callback) {
Ox.Request.send({
url: self.options.apiURL,
data: {
action: 'api'
},
callback: callback
});
},
cancel: function(id) {
Ox.Request.cancel(id);
}
};
that.launch = function(callback) {
var time = +new Date(),
userAgent = getUserAgent(),
userAgents = ['Chrome', 'Firefox', 'Opera', 'Safari'];
$.ajaxSetup({
timeout: self.options.apiTimeout,
type: self.options.apiType,
url: self.options.apiURL
});
userAgents.indexOf(userAgent) > -1 ? start() : stop();
function start() {
$.getJSON(self.options.config, function(data) {
var config = data;
Ox.print('config', config);
document.title = config.site.name;
loadImages(function() {
that.api.api(function(result) {
$.each(result.data.actions, function(i, action) {
that.api[action] = function(data, callback) {
if (arguments.length == 1 && Ox.isFunction(data)) {
callback = data;
data = {};
}
return Ox.Request.send({
url: self.options.apiURL,
data: {
action: action,
data: JSON.stringify(data)
},
callback: callback
});
};
});
that.api[self.options.init](getUserData(), function(data) {
var user = data.data.user;
$(function() {
var $div = $body.find('div');
$body.find('img').remove();
$div.animate({
opacity: 0
}, 1000, function() {
$div.remove();
});
callback({
config: config,
user: user
});
});
});
});
});
});
}
function stop() {
that.request.send(self.options.init, getUserData(), function() {});
}
return that;
};
that.options = function() {
return Ox.getset(self.options, Array.prototype.slice.call(arguments), self.change, that);
};
return that;
};
}();
/**
Ox.Event event object
*/
/*
Ox.Event = function() {
var $eventHandler = $('<div>'),
events = {};
function bindEvent(id, event, callback) {
Ox.print('bind', id, event);
events[id] = events[id] || {};
events[id][event] = events[id][event] || [];
if ($.map(events[id][event], function(fn) {
return fn.toString();
}).indexOf(callback.toString()) == -1) {
events[id][event].push(callback);
if (!isKeyboardEvent(event) || Ox.Focus.focused() == id) {
$eventHandler.bind(event + '_' + id, callback); // requestStart/requestStop currently have '' as id
}
}
}
function unbindEvent(id, event, callback) {
Ox.print('unbind', id, event);
var toString = (callback || '').toString(),
unbind = !event || (event && !isKeyboardEvent(event)) || Ox.Focus.focused() == id;
if (events[id] && (!event || events[id][event])) {
$.each(events[id], function(e, fns) {
if (!event || event == e) {
events[id][e] = $.map(events[id][e], function(fn, i) {
if (!callback || toString == fn.toString()) {
Ox.print(unbind, 'eventHandler.unbind', e, id)
unbind && $eventHandler.unbind(e + '_' + id, fn);
return null;
} else {
return fn;
}
});
}
});
Ox.print('id/events', id, events, events[id])
if (!callback || (event && events[id][event].length == 0)) {
delete events[id][event];
}
if (!event || Ox.length(events[id]) == 0) {
delete events[id];
}
}
}
function isKeyboardEvent(event) {
return event.substr(0, 4) == 'key_';
}
return {
_print: function() {
Ox.print(events);
},
bind: function(id, event, callback) {
// bind event
bindEvent(id, event, callback);
},
bindKeyboard: function(id) {
// bind all keyboard events
//Ox.print('binding', 'id', id, 'events', events[id], Ox.length(events[id]), 'keyboardevents', events[id]['keyboard'])
$.each(events[id], function(k, v) {
Ox.print('|' + k + '|');
})
events[id] && $.each(events[id], function(event, callbacks) {
Ox.print('BIND ID EVENT', id, event)
isKeyboardEvent(event) && $.each(callbacks, function(i, callback) {
Ox.print('bind', id, event);
$eventHandler.bind(event, callback);
});
});
},
changeId: function(oldId, newId) {
// we need to reference events by options('id'),
// not by that.id, since textlist events fire on list,
// new lists have new that.ids, etc.
// so this renaming is necessary
Ox.print('changeId', oldId, newId, events[oldId]);
$.each($.extend({}, events[oldId] || {}), function(event, callbacks) {
$.each(callbacks, function(i, callback) {
Ox.Event.unbind(oldId, event, callback);
Ox.Event.bind(newId, event, callback);
});
});
},
unbind: function(id, event, callback) {
// unbind event
// event and callback are optional
unbindEvent(id, event, callback);
},
unbindAll: function() {
Ox.print('beginUnbindAll')
$.each(events, function(id, eventsById) {
$.each(eventsById, function(event, callbacks) {
$.each(callbacks, function(i, callback) {
Ox.Event.unbind(id, event, callback);
});
});
});
Ox.print('endUnbindAll')
},
trigger: function(id, event, data) {
// trigger event
// data is optional
// keyboard handler will call this with '' as id
Ox.print('trigger', id, event, data || {});
$eventHandler.trigger(event + (id ? '_' + id : ''), data || {});
},
unbindKeyboard: function(id) {
// unbind all keyboard events
Ox.print('unbinding', id)
events[id] && $.each(events[id], function(event, callbacks) {
Ox.print('UNBIND ID EVENT', id, event)
isKeyboardEvent(event) && $.each(callbacks, function(i, callback) {
Ox.print('unbind', event)
$eventHandler.unbind(event, callback);
});
});
}
}
}();
*/
/**
Ox.Focus
*/
Ox.Focus = function() {
var stack = [];
return {
_print: function() {
Ox.print(stack);
},
blur: function(id) {
var index = stack.indexOf(id);
if (index > -1 && index == stack.length - 1) {
stack.length == 1 ? stack.pop() :
stack.splice(stack.length - 2, 0, stack.pop());
// fix later: Ox.Event.unbindKeyboard($elements[id].options('id'));
// fix later: stack.length && Ox.Event.bindKeyboard($elements[stack[stack.length - 1]].options('id'));
//$elements[id].removeClass('OxFocus');
$('.OxFocus').removeClass('OxFocus'); // fixme: the above is better, and should work
stack.length && $elements[stack[stack.length - 1]].addClass('OxFocus');
Ox.print('blur', id, stack);
}
},
focus: function(id) {
var index = stack.indexOf(id);
if (index == -1 || index < stack.length - 1) {
// fix later: stack.length && Ox.Event.unbindKeyboard($elements[stack[stack.length - 1]].options('id'));
index > -1 && stack.splice(index, 1);
stack.push(id);
// fix later: Ox.Event.bindKeyboard($elements[id].options('id'));
$('.OxFocus').removeClass('OxFocus'); // fixme: see above
$elements[id].addClass('OxFocus');
Ox.print('focus', id, stack);
}
},
focused: function() {
return stack.length ? stack[stack.length - 1] : null;
}
};
}();
/**
Ox.History
*/
/**
Ox.Keyboard
*/
(function() {
var buffer = '',
bufferTime = 0,
bufferTimeout = 1000,
keyNames = function() {
return {
0: 'section',
8: 'backspace',
9: 'tab',
12: 'clear',
13: 'enter',
16: 'shift',
17: 'control',
18: 'alt',
20: 'capslock',
27: 'escape',
32: 'space',
33: 'pageup',
34: 'pagedown',
35: 'end',
36: 'home',
37: 'left',
38: 'up',
39: 'right',
40: 'down',
45: 'insert',
46: 'delete',
47: 'help',
48: '0',
49: '1',
50: '2',
51: '3',
52: '4',
53: '5',
54: '6',
55: '7',
56: '8',
57: '9',
65: 'a',
66: 'b',
67: 'c',
68: 'd',
69: 'e',
70: 'f',
71: 'g',
72: 'h',
73: 'i',
74: 'j',
75: 'k',
76: 'l',
77: 'm',
78: 'n',
79: 'o',
80: 'p',
81: 'q',
82: 'r',
83: 's',
84: 't',
85: 'u',
86: 'v',
87: 'w',
88: 'x',
89: 'y',
90: 'z',
91: 'meta.left',
92: 'meta.right',
93: 'select',
96: '0.numpad',
97: '1.numpad',
98: '2.numpad',
99: '3.numpad',
100: '4.numpad',
101: '5.numpad',
102: '6.numpad',
103: '7.numpad',
104: '8.numpad',
105: '9.numpad',
106: 'asterisk.numpad',
107: 'plus.numpad',
109: 'minus.numpad',
108: 'enter.numpad',
110: 'dot.numpad',
111: 'slash.numpad',
112: 'f1',
113: 'f2',
114: 'f3',
115: 'f4',
116: 'f5',
117: 'f6',
118: 'f7',
119: 'f8',
120: 'f9',
121: 'f10',
122: 'f11',
123: 'f12',
124: 'f13',
125: 'f14',
126: 'f15',
127: 'f16',
144: 'numlock',
145: 'scrolllock',
186: 'semicolon',
187: 'equal',
188: 'comma',
189: 'minus',
190: 'dot',
191: 'slash',
192: 'backtick',
219: 'openbracket',
220: 'backslash',
221: 'closebracket',
222: 'quote'
// see dojo, for ex.
};
}(),
modifierNames = {
altKey: 'alt', // mac: option
ctrlKey: 'control',
// metaKey: 'meta', // mac: command
shiftKey: 'shift'
};
$(function() {
// fixme: how to do this better?
// in firefox on mac, keypress doesn't fire for up/down
// if the cursor is at the start/end of an input element
// on linux, it doesn't seem to fire if the input element has focus
if ($.browser.mozilla) {
$document.keypress(keypress);
$document.keydown(function(event) {
var $element = $('input:focus');
if ($element.length) {
if (
(
keyNames[event.keyCode] == 'up' &&
$element[0].selectionStart + $element[0].selectionEnd == 0
) || (
keyNames[event.keyCode] == 'down' &&
$element[0].selectionStart == $element.val().length &&
$element[0].selectionEnd == $element.val().length
)
) {
keypress(event);
}
}
});
} else {
$document.keydown(keypress);
}
});
function keypress(event) {
var focused = Ox.Focus.focused(),
key,
keys = [],
//ret = true,
time;
$.each(modifierNames, function(k, v) {
if (event[k]) {
keys.push(v);
}
});
// avoid pushing modifier twice
Ox.print('keys', keys)
if (keyNames[event.keyCode] && keys.indexOf(keyNames[event.keyCode]) == -1) {
keys.push(keyNames[event.keyCode]);
}
key = keys.join('_');
if (key.match(/^[\w\d-]$|SPACE/)) {
time = Ox.getTime();
if (time - bufferTime > bufferTimeout) {
buffer = '';
}
buffer += key == 'SPACE' ? ' ' : key;
bufferTime = time;
}
focused && $elements[focused].triggerEvent('key_' + key);
//return false;
/*
$.each(stack, function(i, v) {
// fixme: we dont get the return value!
ret = Ox.event.trigger(keyboard + Ox.toCamelCase(key) + '.' + v);
return ret;
});
*/
}
})();
/**
Ox.Mouse (??)
*/
/**
Ox.Request request object
*/
Ox.Request = function() {
var cache = {},
pending = {},
requests = {},
self = {
options: {
timeout: 15000,
type: 'POST',
url: 'api'
}
};
return {
cancel: function() {
var index;
if (arguments.length == 0) {
requests = {};
} else if (Ox.isFunction(arguments[0])) {
// cancel with function
$.each(requests, function(id, req) {
if (arguments[0](req)) {
delete requests[id];
}
})
} else {
// cancel by id
delete requests[arguments[0]]
}
},
emptyCache: function() {
cache = {};
},
options: function(options) {
return Ox.getset(self.options, options, $.noop(), this);
},
send: function(options) {
var options = $.extend({
age: -1,
callback: function() {},
id: Ox.uid(),
timeout: self.options.timeout,
type: self.options.type,
url: self.options.url
}, options),
req = JSON.stringify({
url: options.url,
data: options.data
});
function callback(data) {
delete requests[options.id];
Ox.length(requests) == 0 && $body.trigger('requestStop');
options.callback && options.callback(data);
}
function debug(request) {
var $iframe = $('<iframe>')
.css({ // fixme: should go into a class
width: 768,
height: 384
}),
$dialog = new Ox.Dialog({
title: 'Application Error',
buttons: [
new Ox.Button({
title: 'Close'
})
.bindEvent({
click: function() {
$dialog.close();
}
})
],
content: $iframe,
width: 800,
height: 400
})
.open(),
iframe = $iframe[0].contentDocument || $iframe[0].contentWindow.document;
iframe.open();
iframe.write(request.responseText);
iframe.close();
}
function error(request, status, error) {
var data;
if (arguments.length == 1) {
data = arguments[0]
} else {
try {
data = JSON.parse(request.responseText);
} catch (err) {
try {
data = {
status: {
code: request.status,
text: request.statusText
}
};
} catch (err) {
data = {
status: {
code: '500',
text: 'Unknown Error'
}
};
}
}
}
if (data.status.code < 500) {
callback(data);
} else {
var $dialog = new Ox.Dialog({
title: 'Application Error',
buttons: [
new Ox.Button({
title: 'Details'
})
.bindEvent({
click: function() {
$dialog.close(function() {
debug(request);
})
}
}),
new Ox.Button({
title: 'Close'
})
.bindEvent({
click: function() {
$dialog.close();
}
})
],
content: 'Sorry, we have encountered an application error while handling your request. To help us find out what went wrong, you may want to report this error to an administrator. Otherwise, please try again later.',
width: 400,
height: 200
})
.open();
// fixme: change this to Send / Don't Send
Ox.print({
request: request,
status: status,
error: error
});
}
pending[options.id] = false;
}
function success(data) {
pending[options.id] = false;
cache[req] = {
data: data,
time: Ox.getTime()
};
callback(data);
}
if (pending[options.id]) {
setTimeout(function() {
Ox.Request.send(options);
}, 0);
} else {
requests[options.id] = {
url: options.url,
data: options.data
};
if (cache[req] && (options.age == -1 || options.age > Ox.getTime() - cache[req].time)) {
setTimeout(function() {
callback(cache[req].data);
}, 0);
} else {
pending[options.id] = true;
$.ajax({
data: options.data,
dataType: 'json',
error: error,
success: success,
timeout: options.timeout,
type: options.type,
url: options.url
});
Ox.print('request', options.data, Ox.length(requests));
Ox.length(requests) == 1 && $body.trigger('requestStart');
}
}
return options.id;
},
requests: function() {
return Ox.length(requests);
}
};
}();
/**
not implemented
*/
Ox.Theme = function() {
};
/**
Ox.URL
*/
/*
============================================================================
Core
============================================================================
*/
// fixme: wouldn't it be better to let the elements be,
// rather then $element, $content, and potentially others,
// 0, 1, 2, etc, so that append would append 0, and appendTo
// would append (length - 1)?
/**
*/
Ox.Container = function(options, self) {
var that = new Ox.Element('div', self)
.options(options || {})
.addClass('OxContainer');
that.$content = new Ox.Element('div', self)
.options(options || {})
.addClass('OxContent')
.appendTo(that);
return that;
};
// check out http://ejohn.org/apps/learn/#36 (-#38, making fns work w/o new)
/**
Ox.Element
*/
Ox.Element = function() {
return function(options, self) {
// construct
self = self || {};
self.options = options || {};
if (!self.$eventHandler) {
self.$eventHandler = $('<div>');
}
var that = this;
// init
(function() {
// allow for Ox.Element('tagname', self)
if (typeof self.options == 'string') {
self.options = {
element: self.options
};
}
that.ox = Ox.version;
that.id = Ox.uid();
that.$element = $('<' + (self.options.element || 'div') + '/>', {
data: {
ox: that.id
}
});
$elements[that.id] = that;
wrapjQuery();
})();
// private
function wrapjQuery() {
$.each(oxui.jQueryFunctions, function(i, fn) {
that[fn] = function() {
var args = arguments,
length = args.length,
id, ret;
$.each(args, function(i, arg) {
if (Ox.isUndefined(arg)) {
Ox.print('fn', fn, 'undefined argument')
}
// if an ox object was passed
// then pass its $element instead
// so we can do oxObj.jqFn(oxObj)
if (arg && arg.ox) {
args[i] = arg.$element;
}
/*
if (arg.ox) { // fixme: or is this too much magic?
if (fn == 'appendTo' && arg.$content) {
args[i] = arg.$content
} else {
args[i] = arg.$element;
}
}
*/
});
/*
if (fn == 'html' && that.$content) { // fixme: or is this too much magic?
$element = that.$content;
} else {
$element = that.$element;
}
*/
// why does this not work? (that?)
// ret = that.$element[fn].apply(this, arguments);
if (length == 0) {
ret = that.$element[fn]();
} else if (length == 1) {
ret = that.$element[fn](args[0]);
} else if (length == 2) {
ret = that.$element[fn](args[0], args[1]);
} else if (length == 3) {
ret = that.$element[fn](args[0], args[1], args[2]);
} else if (length == 4) {
ret = that.$element[fn](args[0], args[1], args[2], args[3]);
}
if (fn == 'data') {
// Ox.print('data ret', ret, $(ret))
}
// if the $element of an ox object was returned
// then return the ox object instead
// so we can do oxObj.jqFn().oxFn()
return ret.jquery && $elements[id = ret.data('ox')] ?
$elements[id] : ret;
}
});
}
// shared
self.onChange = function() {
// self.onChange(key, value)
// is called when an option changes
// (to be implemented by widget)
};
// public
that.bindEvent = function() {
/*
binds a function to an event triggered by this object
bindEvent(event, fn) or bindEvent({event0: fn0, event1: fn1, ...})
*/
if (arguments.length == 1) {
$.each(arguments[0], function(event, fn) {
Ox.print(that.id, 'bind', event);
self.$eventHandler.bind('ox_' + event, fn);
});
} else {
Ox.print(that.id, 'bind', arguments[0]);
self.$eventHandler.bind('ox_' + arguments[0], arguments[1]);
}
return that;
}
that.defaults = function(defaults) {
/*
that.defaults({foo: x}) sets self.defaults
*/
self.defaults = defaults;
delete self.options; // fixme: hackish fix for that = Ox.Foo({...}, self).defaults({...}).options({...})
return that;
};
that.gainFocus = function() {
Ox.Focus.focus(that.id);
return that;
};
that.hasFocus = function() {
return Ox.Focus.focused() == that.id;
};
that.loseFocus = function() {
Ox.Focus.blur(that.id);
return that;
};
that.options = function() { // fixme: use Ox.getset
/*
that.options() returns self.options
that.options('foo') returns self.options.foo
that.options('foo', x) sets self.options.foo,
returns that
that.options({foo: x, bar: y}) sets self.options.foo
and self.options.bar,
returns that
*/
var args,
length = arguments.length,
oldOptions,
ret;
if (length == 0) {
// options()
ret = self.options;
} else if (length == 1 && typeof arguments[0] == 'string') {
// options(str)
ret = self.options ? self.options[arguments[0]] : options[arguments[0]];
} else {
// options (str, val) or options({str: val, ...})
// translate (str, val) to ({str: val})
args = Ox.makeObject.apply(that, arguments || {});
oldOptions = $.extend({}, self.options);
// if options have not been set, extend defaults,
// otherwise, extend options
//self.options = $.extend(self.options, self.options ? {} : self.defaults, args);
self.options = $.extend({}, self.defaults, self.options, args);
//self.options = $.extend(self.options || self.defaults, args);
$.each(args, function(key, value) {
// key == 'id' && id && Ox.Event.changeId(id, value);
/*!Ox.equals(value, oldOptions[key]) &&*/ self.onChange(key, value);
});
ret = that;
}
return ret;
};
that.remove = function() { // fixme: clashes with jquery, should be removeElement
that.loseFocus();
delete self.$eventHandler;
that.$element.remove();
delete $elements[that.ox];
return that;
};
that.triggerEvent = function() {
/*
triggers an event
triggerEvent(event) or triggerEvent(event, data) or triggerEvent({event0: data, event1: data, ...})
*/
if (Ox.isObject(arguments[0])) {
$.each(arguments[0], function(event, data) {
Ox.print(that.id, self.options.id, 'trigger', event, data);
self.$eventHandler.trigger('ox_' + event, data);
});
} else {
Ox.print('??', that.id, self.options ? self.options.id : '', 'trigger', arguments[0], arguments[1] || {});
self.$eventHandler.trigger('ox_' + arguments[0], arguments[1] || {});
}
return that;
};
that.unbindEvent = function() {
/*
unbinds an event
unbindEvent(event, fn) or unbindEvent({event0: fn0, event1: fn1, ...})
*/
if (arguments.length == 1) {
$.each(arguments[0], function(event, fn) {
Ox.print(that.id, 'unbind', arguments[0]);
self.$eventHandler.unbind('ox_' + event, fn);
});
} else {
Ox.print(that.id, 'unbind', arguments[0]);
self.$eventHandler.unbind('ox_' + arguments[0], arguments[1]);
}
return that;
};
// return
return that;
}
}();
Ox._Element = function(element) {
var that = this;
that.def = {};
that.opt = {};
that.ox = Ox.version;
that.id = Ox.uid();
//Ox.print('that.id', that.id)
that.$element = $('<' + (element || 'div') + '/>')
//.addClass('OxElement')
.data('ox', that.id);
oxui.elements[that.id] = that;
// Ox.print('oxui.elements', oxui.elements)
//function setOption() {};
that.setOption = function() {};
/*
*/
that.destroy = function() {
that.$element.remove();
delete oxui.elements[that.ox];
}
/*
*/
that.disable = function() {
}
/*
*/
that.enable = function() {
}
/*
*/
///*
that.defaults = function() {
var length = arguments.length,
ret;
if (length == 0) {
ret = that.def
} else if (length == 1 && typeof arguments[0] == 'string') {
ret = that.def[arguments[0]];
} else {
// translate ('key', 'value') to {'key': 'value'}
that.def = $.extend(
that.def, Ox.makeObject.apply(that, arguments)
);
ret = that;
}
return ret;
}
//*/
/*
Ox.Element.options()
get options
Ox.Element.options('foo')
get options.foo
Ox.Element.options('foo', 0)
set options.foo
Ox.Element.options({foo: 0, bar: 1})
set options.foo and options.bar
*/
///*
that.options = function() {
var length = arguments.length,
args, ret;
if (length == 0) {
//Ox.print('getting all options', options);
ret = that.opt;
} else if (length == 1 && typeof arguments[0] == 'string') {
//Ox.print('getting one option', options, arguments[0], options[arguments[0]]);
ret = that.opt[arguments[0]];
} else {
// translate ('key', 'value') to {'key': 'value'}
args = Ox.makeObject.apply(that, arguments);
// if options have been set then extend options,
// otherwise extend defaults
that.opt = $.extend(Ox.length(that.opt) ?
that.opt : that.def, args);
// that.trigger('OxElement' + that.id + 'SetOptions', args);
$.each(args, function(k, v) {
that.setOption(k, v);
//Ox.print('triggering', 'OxElement' + that.id + 'SetOption', {k: v})
//that.trigger('OxElement' + that.id + 'SetOption', {k: v});
})
ret = that;
}
return ret;
}
// should become self.publish
that.publish = function(event, data) {
Ox.Event.publish(event + that.id, data);
return that;
}
that.subscribe = function(event, callback) {
Ox.Event.subscribe(event, callback);
return that;
}
//that.setOptions = function() {};
//*/
// wrap jquery functions
// so we can do oxObj.jqFn()
$.each(oxui.jqueryFunctions, function(i, v) {
that[v] = function() {
var args = arguments,
length = args.length,
$element, id, ret;
$.each(args, function(i, v) {
// if an oxui object was passed
// then pass its $element instead
// so we can do jqObj.append(oxObj)
if (v.ox) {
args[i] = v.$element;
}
});
if (v == 'html' && that.$content) {
$element = that.$content;
} else {
$element = that.$element;
}
// why does this not work?
// ret = that.$element[v].apply(this, arguments);
// maybe because we pass this, and not that.$element[v] ... ?
// ret = that.$element[v].apply(that.$element[v], arguments);
// doesn't work either ...
if (length == 0) {
ret = $element[v]();
} else if (length == 1) {
ret = $element[v](args[0]);
} else if (length == 2) {
ret = $element[v](args[0], args[1]);
} else if (length == 3) {
ret = $element[v](args[0], args[1], args[2]);
} else if (length == 4) {
ret = $element[v](args[0], args[1], args[2], args[3]);
}
// if the $element of an oxui object was returned
// then return the oxui object instead
// so we can do oxObj.jqFn().oxFn()
//Ox.print('v', v, 'arguments', arguments)
if (ret.jquery) {
//Ox.print('ret', ret, 'ret.data('id')', ret.data('ox'))
}
return ret.jquery && oxui.elements[id = ret.data('ox')] ?
oxui.elements[id] : ret;
}
});
return that;
};
/**
Ox.Window
*/
Ox.Window = function(options, self) {
self = self || {},
that = new Ox.Element('div', self)
.defaults({
draggable: true,
fullscreenable: true, // fixme: silly name
height: 225,
resizeable: true,
scaleable: true,
width: 400
})
.options(options || {})
self.center = function() {
};
self.drag = function() {
};
self.fullscreen = function() {
};
self.onChange = function() {
};
self.reset = function() {
};
self.resize = function() {
};
self.scale = function() {
};
that.close = function() {
};
that.open = function() {
};
return that;
};
// fixme: this should be Ox.Theme, and provide Ox.Theme.set(), Ox.Theme.load, etc.
/**
if name is given as argument, switch to this theme.
return current theme otherwise.
Ox.theme()
get theme
Ox.theme('foo')
set theme to 'foo'
*/
Ox.theme = function() {
var length = arguments.length,
classes = $body.attr('class').split(' '),
arg, theme;
$.each(classes, function(i, v) {
if (Ox.startsWith(v, 'OxTheme')) {
theme = v.replace('OxTheme', '').toLowerCase();
if (length == 1) {
$body.removeClass(v);
}
return false;
}
});
if (length == 1) {
arg = arguments[0]
$body.addClass('OxTheme' + Ox.toTitleCase(arg));
if (theme) {
$('input[type=image]').each(function() {
var $this = $(this);
$this.attr({
src: $this.attr('src').replace(
'/ox.ui.' + theme + '/', '/ox.ui.' + arg + '/'
)
});
});
$('.OxLoadingIcon').each(function() {
var $this = $(this);
$this.attr({
src: $this.attr('src').replace(
'/ox.ui.' + theme + '/', '/ox.ui.' + arg + '/'
)
});
})
}
}
return theme;
};
/**
*/
Ox.Bar = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
orientation: 'horizontal',
size: 'medium' // can be int
})
.options(options || {})
.addClass('OxBar Ox' + Ox.toTitleCase(self.options.orientation)),
dimensions = oxui.getDimensions(self.options.orientation);
self.options.size = Ox.isString(self.options.size) ?
oxui.getBarSize(self.options.size) : self.options.size;
that.css(dimensions[0], '100%')
.css(dimensions[1], self.options.size + 'px');
return that;
};
/**
*/
Ox.Resizebar = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
collapsed: false,
collapsible: true,
edge: 'left',
elements: [],
orientation: 'horizontal',
parent: null,
resizable: true,
resize: [],
size: 0
})
.options(options || {}) // fixme: options function should be able to handle undefined, no need for || {}
.addClass('OxResizebar Ox' + Ox.toTitleCase(self.options.orientation))
/*
.attr({
draggable: 'true'
})
.bind('dragstart', function(e) {
// e.originalEvent.dataTransfer.setDragImage($('<div>')[0], 0, 0);
})
.bind('drag', function(e) {
Ox.print('dragging', e)
})
*/
.mousedown(dragStart)
.dblclick(toggle)
.append($('<div>').addClass('OxSpace'))
.append($('<div>').addClass('OxLine'))
.append($('<div>').addClass('OxSpace'));
$.extend(self, {
clientXY: self.options.orientation == 'horizontal' ? 'clientY' : 'clientX',
dimensions: oxui.getDimensions(self.options.orientation), // fixme: should orientation be the opposite orientation here?
edges: oxui.getEdges(self.options.orientation),
leftOrTop: self.options.edge == 'left' || self.options.edge == 'top',
startPos: 0,
startSize: 0
});
function drag(e) {
var d = e[self.clientXY] - self.startPos
size = self.options.size;
self.options.size = Ox.limit(
self.startSize + d * (self.leftOrTop ? 1 : -1),
self.options.resize[0],
self.options.resize[self.options.resize.length - 1]
);
$.each(self.options.resize, function(i, v) {
if (self.options.size >= v - 8 && self.options.size <= v + 8) {
self.options.size = v;
return false;
}
});
if (self.options.size != size) {
that.css(self.edges[self.leftOrTop ? 2 : 3], self.options.size + 'px');
// fixme: send {size: x}, not x
if (self.leftOrTop) {
self.options.elements[0]
.css(self.dimensions[1], self.options.size + 'px')
.triggerEvent('resize', self.options.size);
self.options.elements[1]
.css(self.edges[2], (self.options.size + 1) + 'px')
.triggerEvent('resize', self.options.elements[1][self.dimensions[1]]());
self.options.parent.updateSize(0, self.options.size);
} else {
self.options.elements[0]
.css(self.edges[3], (self.options.size + 1) + 'px')
.triggerEvent('resize', self.options.elements[0][self.dimensions[1]]());
self.options.elements[1]
.css(self.dimensions[1], self.options.size + 'px')
.triggerEvent('resize', self.options.size);
self.options.parent.updateSize(1, self.options.size); // fixme: listen to event instead?
}
}
}
function dragStart(e) {
if (self.options.resizable && !self.options.collapsed) {
self.startPos = e[self.clientXY];
self.startSize = self.options.size;
Ox.print('startSize', self.startSize)
$window.mousemove(drag);
$window.one('mouseup', dragStop);
}
}
function dragStop() {
$window.unbind('mousemove');
}
function toggle() {
if (self.options.collapsible) {
// fixme: silly, pass a parameter
self.options.parent.toggle(self.leftOrTop ? 0 : self.options.parent.options('elements').length - 1);
self.options.collapsed = !self.options.collapsed;
}
/*
Ox.print('toggle');
if (Ox.isUndefined(self.options.position)) {
self.options.position = parseInt(self.options.parent.css(self.options.edge)) +
(self.options.collapsed ? self.options.size : 0);
}
var size = self.options.position -
(self.options.collapsed ? 0 : self.options.size),
animate = {};
Ox.print('s.o.e', self.options.edge);
animate[self.options.edge] = size;
self.options.parent.animate(animate, 200, function() {
var i = (self.options.edge == 'left' || self.options.edge == 'top') ? 0 : 1;
self.options.collapsed = !self.options.collapsed;
Ox.Event.trigger(self.ids[i], 'toggle', self.options.collapsed);
Ox.Event.trigger(self.ids[1 - i], 'resize', self.options.elements[1 - i][self.dimensions[1]]());
});
*/
}
return that;
};
/**
*/
Ox.Tabbar = function(options, self) {
var self = self || {},
that = new Ox.Bar({
size: 20
}, self)
.defaults({
selected: 0,
tabs: []
})
.options(options || {})
.addClass('OxTabbar');
Ox.ButtonGroup({
buttons: self.options.tabs,
group: true,
selectable: true,
selected: self.options.selected,
size: 'medium',
style: 'tab',
}).appendTo(that);
return that;
};
/**
fixme: no need for this
*/
Ox.Toolbar = function(options, self) {
var self = self || {},
that = new Ox.Bar({
size: oxui.getBarSize(options.size)
}, self);
return that;
};
/**
*/
Ox.Dialog = function(options, self) {
// fixme: dialog should be derived from a generic draggable
// fixme: buttons should have a close attribute, or the dialog a close id
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
title: '',
buttons: [],
content: null,
height: 216,
keys: {},
minHeight: 144,
minWidth: 256,
movable: true,
padding: 16,
resizable: true,
width: 384
})
.options(options || {})
.addClass('OxDialog')
.bindEvent({
key_enter: function() {
keypress('enter');
},
key_escape: function() {
Ox.print('KEY ESCAPE')
keypress('escape');
}
});
$.extend(self, {
initialWidth: self.options.width,
initialHeight: self.options.height
})
that.$titlebar = new Ox.Bar({
size: 'medium'
})
.addClass('OxTitleBar')
.appendTo(that);
self.options.movable && that.$titlebar
.mousedown(drag)
.dblclick(center);
that.$title = new Ox.Element()
.addClass('OxTitle')
.html(self.options.title)
.appendTo(that.$titlebar);
// fixme: should the following be a container?
that.$content = new Ox.Element()
.addClass('OxContent')
.css({
padding: self.options.padding + 'px',
overflow: 'auto'
})
.append(self.options.content)
.appendTo(that);
that.$buttonsbar = new Ox.Bar({})
.addClass('OxButtonsBar')
.appendTo(that);
loadButtons();
//that.$buttons[0].focus();
that.$layer = new Ox.Element() // fixme: Layer widget that would handle click?
.addClass('OxLayer')
.mousedown(mousedownLayer)
.mouseup(mouseupLayer);
function center() {
var documentHeight = $document.height();
that.css({
left: 0,
top: Math.max(parseInt(-documentHeight / 10), self.options.height - documentHeight + 40) + 'px',
right: 0,
bottom: 0,
margin: 'auto'
});
}
function drag(event) {
var bodyWidth = $body.width(),
bodyHeight = $document.height(),
elementWidth = that.width(),
offset = that.offset(),
x = event.clientX,
y = event.clientY;
$window.mousemove(function(event) {
that.css({
margin: 0
});
var left = Ox.limit(
offset.left - x + event.clientX,
24 - elementWidth, bodyWidth - 24
//0, documentWidth - elementWidth
),
top = Ox.limit(
offset.top - y + event.clientY,
24, bodyHeight - 24
//24, documentHeight - elementHeight
);
that.css({
left: left + 'px',
top: top + 'px'
});
});
$window.one('mouseup', function() {
$window.unbind('mousemove');
});
}
function getButtonById(id) {
var ret = null;
Ox.print('that.$buttons', that.$buttons, id)
$.each(that.$buttons, function(i, button) {
if (button.options('id') == id) {
ret = button;
return false;
}
});
return ret;
}
function keypress(key) {
var id = self.options.keys[key];
Ox.print('X', key, self.options.keys)
id && getButtonById(id).$element.trigger('click');
}
function loadButtons() {
Ox.print('loadButtons', $.map(self.options.buttons, function(v) {
return v;
}));
if (that.$buttons) {
that.$buttons.forEach(function($button) {
$button.remove();
});
that.$resize.remove();
// that.$buttonsbar.empty();
}
that.$buttons = [];
if (!Ox.isArray(self.options.buttons[0])) {
self.options.buttons = [[], self.options.buttons];
}
Ox.print('--- one', self.options.buttons[0]);
$.each(self.options.buttons[0], function(i, button) {
// Ox.print('---', button, self.options.buttons)
that.$buttons[i] = button
.addClass('OxLeft')
.appendTo(that.$buttonsbar);
});
if (self.options.resizable) {
that.$resize = new Ox.Element()
.addClass('OxResize')
.mousedown(resize)
.dblclick(reset)
.appendTo(that.$buttonsbar);
}
Ox.print('--- two', self.options.buttons[1]);
$.each(self.options.buttons[1].reverse(), function(i, button) {
//Ox.print('---', button, self.options.buttons)
that.$buttons[that.$buttons.length] = button
.addClass('OxRight')
.appendTo(that.$buttonsbar);
});
}
function mousedownLayer() {
that.$layer.stop().animate({
opacity: 0.5
}, 0);
}
function mouseupLayer() {
that.$layer.stop().animate({
opacity: 0
}, 0);
}
function reset() {
$.extend(self.options, {
height: self.initialHeight,
width: self.initialWidth
});
that/*.css({
left: Math.max(that.offset().left, 24 - that.width())
})*/
.width(self.options.width)
.height(self.options.height);
that.$content.height(self.options.height - 48 - 2 * self.options.padding); // fixme: this should happen automatically
triggerResizeEvent();
}
function resize(event) { // fixme: reserved jquery string?
var documentWidth = $document.width(),
documentHeight = $document.height(),
elementWidth = that.width(),
elementHeight = that.height(),
offset = that.offset(),
x = event.clientX,
y = event.clientY;
$window.mousemove(function(event) {
that.css({
left: offset.left,
top: offset.top,
margin: 0
});
self.options.width = Ox.limit(
elementWidth - x + event.clientX,
self.options.minWidth, Math.min(documentWidth, documentWidth - offset.left)
);
self.options.height = Ox.limit(
elementHeight - y + event.clientY,
self.options.minHeight, documentHeight - offset.top
);
that.width(self.options.width);
that.height(self.options.height);
that.$content.height(self.options.height - 48 - 2 * self.options.padding); // fixme: this should happen automatically
triggerResizeEvent();
});
$window.one('mouseup', function() {
$window.unbind('mousemove');
});
}
function triggerResizeEvent() {
that.triggerEvent('resize', {
width: self.options.width,
height: self.options.height
});
}
self.onChange = function(key, value) {
if (key == 'buttons') {
loadButtons();
/*
that.$buttonsbar.children().animate({
opacity: 0
}, 100, function() {
loadButtons();
that.$buttonsbar.children().animate({
opacity: 1
}, 100);
});
*/
} else if (key == 'content') {
that.$content.html(value);
} else if (key == 'height' || key == 'width') {
that.animate({
height: self.options.height + 'px',
width: self.options.width + 'px'
}, 100);
that.$content.height(self.options.height - 48 - 2 * self.options.padding); // fixme: this should happen automatically
} else if (key == 'title') {
that.$title.animate({
opacity: 0
}, 100, function() {
that.$title.html(value).animate({
opacity: 1
}, 100);
});
}
}
that.center = function() {
};
that.close = function(callback) {
callback = callback || function() {};
that.animate({
opacity: 0
}, 200, function() {
that.$buttons.forEach(function($button) {
$button.remove();
});
that.loseFocus();
that.$layer.remove();
that.remove();
callback();
});
$window.unbind('mouseup', mouseupLayer)
return that;
};
that.content = function($element) {
that.$content.empty().append($element);
return that;
}
that.disable = function() {
// to be used on submit of form, like login
that.$layer.addClass('OxFront');
return that;
};
that.disableButton = function(id) {
getButtonById(id).options({
disabled: true
});
return that;
};
that.enable = function() {
that.$layer.removeClass('OxFront');
return that;
};
that.enableButton = function(id) {
getButtonById(id).options({
disabled: false
});
return that;
};
that.open = function() {
Ox.print('before open')
that.$layer.appendTo($body);
that.css({
opacity: 0
}).appendTo($body).animate({
opacity: 1
}, 200);
center();
reset();
// fixme: the following line prevents preview-style dialog
that.gainFocus();
$window.bind('mouseup', mouseupLayer)
Ox.print('after open')
return that;
};
that.size = function(width, height, callback) {
$.extend(self, {
initialWidth: width,
initialHeight: height
});
$.extend(self.options, {
width: width,
height: height
});
// fixme: duplicated
that.animate({
height: self.options.height + 'px',
width: self.options.width + 'px'
}, 100, function() {
that.$content.height(self.options.height - 48 - 2 * self.options.padding); // fixme: this should happen automatically
callback();
});
}
return that;
}
/*
============================================================================
Forms
============================================================================
*/
/**
*/
Ox.Filter = function(options, self) {
var self = self || {}
that = new Ox.Element()
.defaults({
})
.options(options || {});
return that;
};
/**
*/
Ox.Form = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
error: '',
id: '',
items: [],
submit: null
})
.options(options || {}) // fixme: the || {} can be done once, in the options function
.addClass('OxForm');
$.extend(self, {
$items: [],
$messages: [],
formIsValid: false,
itemIds: [],
itemIsValid: []
});
// fixme: form isn't necessarily empty/invalid
$.each(self.options.items, function(i, item) {
self.itemIds[i] = item.options('id') || item.id;
self.itemIsValid[i] = !!item.value().length;
that.append(self.$items[i] = new Ox.FormItem({element: item}))
.append(self.$messages[i] = new Ox.Element().addClass('OxFormMessage'));
item.bindEvent({
/*
blur: function(event, data) {
validate(i, data.valid);
if (data.valid) {
self.$messages[i].html('').hide();
} else {
self.$messages[i].html(data.message).show();
}
},
*/
autovalidate: function(event, data) {
data.valid = !!data.value.length;
validate(i, data.valid);
if (data.valid) {
self.$messages[i].html('').hide();
} else {
//self.$messages[i].html(data.message).show();
}
},
submit: function(event, data) {
self.formIsValid && that.submit();
},
validate: function(event, data) {
validate(i, data.valid);
if (data.valid) {
self.$messages[i].html('').hide();
} else {
self.$messages[i].html(data.message).show();
}
}
});
});
function getItemPositionById(id) {
return self.itemIds.indexOf(id);
}
function setMessage(id, message) {
self.$messages[getItemPositionById(id)].html(message)[message !== '' ? 'show' : 'hide']();
}
function submitCallback(data) {
$.each(data, function(i, v) {
setMessage(v.id, v.message);
});
}
function validate(pos, valid) {
Ox.print('FORM validate', pos, valid)
self.itemIsValid[pos] = valid;
if (Ox.every(self.itemIsValid) != self.formIsValid) {
self.formIsValid = !self.formIsValid;
that.triggerEvent('validate', {
valid: self.formIsValid
});
}
}
that.submit = function() {
Ox.print('---- that.values()', that.values())
self.options.submit(that.values(), submitCallback);
};
that.values = function() { // fixme: can this be private?
/*
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) {
$.each(self.$items, function(i, $item) {
values[self.itemIds[i]] = self.$items[i].value();
});
Ox.print('VALUES', values)
return values;
} else {
$.each(arguments[0], function(key, value) {
});
return that;
}
};
return that;
};
/**
*/
Ox.FormItem = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
element: null,
error: '',
})
.options(options || {})
.addClass('OxFormItem')
.append(self.options.element);
that.value = function() {
return self.options.element.value();
};
return that;
}
/**
Form Elements
*/
/**
methods:
toggleDisabled enable/disable button
toggleSelected select/unselect button
toggleTitle if more than one title was provided,
toggle to next title.
events:
click non-selectable button was clicked
deselect selectable button was deselected
select selectable button was selected
*/
Ox.Button = function(options, self) {
var self = self || {},
that = new Ox.Element('input', self)
.defaults({
disabled: false,
group: false,
id: '',
overlap: 'none',
selectable: false,
selected: false,
size: 'medium',
// fixme: 'default' or ''?
style: 'default', // can be default, checkbox, symbol, or tab
title: '',
tooltip: '',
type: 'text',
width: 'auto'
})
.options(options || {})
.attr({
disabled: self.options.disabled ? 'disabled' : '',
type: self.options.type == 'text' ? 'button' : 'image'
})
.addClass('OxButton Ox' + Ox.toTitleCase(self.options.size) +
(self.options.disabled ? ' OxDisabled': '') +
(self.options.selected ? ' OxSelected': '') +
(self.options.style != 'default' ? ' Ox' + Ox.toTitleCase(self.options.style) : '') +
(self.options.overlap != 'none' ? ' OxOverlap' + Ox.toTitleCase(self.options.overlap) : ''))
.css(self.options.width == 'auto' ? {} : {
width: (self.options.width - 14) + 'px'
})
.mousedown(mousedown)
.click(click);
$.extend(self, Ox.isArray(self.options.title) ? {
selectedTitle: Ox.setPropertyOnce(self.options.title, 'selected'),
titles: self.options.title
} : {
selectedTitle: 0,
titles: [{
id: '',
title: self.options.title
}]
});
setTitle(self.titles[self.selectedTitle].title);
if (self.options.tooltip) {
self.tooltips = Ox.isArray(self.options.tooltip) ? self.options.tooltip : [self.options.tooltip];
self.$tooltip = new Ox.Tooltip({
title: self.tooltips[self.selectedTitle]
});
that.mouseenter(mouseenter)
.mouseleave(mouseleave);
}
function click() {
if (!self.options.disabled) {
var data = self.titles[self.selectedTitle];
if (!self.options.selectable) {
that.triggerEvent('click', data);
} else {
if (self.options.group) {
that.triggerEvent('select', data);
} else {
that.toggleSelected();
}
}
if (self.titles.length == 2) {
that.toggleTitle();
}
}
}
function mousedown(event) {
if (self.options.type == 'image' && $.browser.safari) {
// keep image from being draggable
event.preventDefault();
}
}
function mouseenter(event) {
self.$tooltip.show(event.clientX, event.clientY);
}
function mouseleave(event) {
self.$tooltip.hide();
}
function setTitle(title) {
self.title = title;
if (self.options.type == 'image') {
that.attr({
src: oxui.path + 'png/ox.ui.' + Ox.theme() +
'/symbol' + Ox.toTitleCase(title) + '.png'
});
} else {
that.val(title);
}
}
self.onChange = function(key, value) {
if (key == 'disabled') {
that.attr({
disabled: value ? 'disabled' : ''
})
.toggleClass('OxDisabled');
} else if (key == 'selected') {
if (value != that.hasClass('OxSelected')) { // fixme: neccessary?
that.toggleClass('OxSelected');
}
that.triggerEvent('change');
} else if (key == 'title') {
setTitle(value);
} else if (key == 'width') {
that.$element.css({
width: (value - 14) + 'px'
});
}
}
that.toggleDisabled = function() {
that.options({
enabled: !self.options.disabled
});
}
that.toggleSelected = function() {
that.options({
selected: !self.options.selected
});
}
that.toggleTitle = function() {
self.selectedTitle = 1 - self.selectedTitle;
setTitle(self.titles[self.selectedTitle].title);
self.$tooltip && self.$tooltip.options({
title: self.tooltips[self.selectedTitle]
});
}
return that;
};
/**
options
buttons array of buttons
max integer, maximum number of selected buttons, 0 for all
min integer, minimum number of selected buttons, 0 for none
selectable if true, buttons are selectable
type string, 'image' or 'text'
methods:
events:
change {id, value} selection within a group changed
*/
Ox.ButtonGroup = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
buttons: [],
max: 1,
min: 1,
selectable: false,
size: 'medium',
style: '',
type: 'text',
})
.options(options || {})
.addClass('OxButtonGroup');
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.$buttons = [];
$.each(self.options.buttons, function(position, button) {
var id = self.options.id + Ox.toTitleCase(button.id)
self.$buttons[position] = Ox.Button({
disabled: button.disabled,
group: true,
id: id,
selectable: self.options.selectable,
selected: button.selected,
size: self.options.size,
style: self.options.style,
title: button.title,
type: self.options.type
})
.bindEvent('select', function() {
selectButton(position);
})
.appendTo(that);
});
function selectButton(pos) {
var toggled = self.optionGroup.toggle(pos);
if (toggled.length) {
$.each(toggled, function(i, pos) {
self.$buttons[pos].toggleSelected();
});
that.triggerEvent('change', {
selected: $.map(self.optionGroup.selected(), function(v, i) {
return self.options.buttons[v].id;
})
});
}
}
return that;
};
/**
options
disabled boolean, if true, checkbox is disabled
id element id
group boolean, if true, checkbox is part of a group
checked boolean, if true, checkbox is checked
title string, text on label
width integer, width in px
methods:
toggleChecked function()
toggles checked property
returns that
events:
change triggered when checked property changes
passes {checked, id, title}
*/
Ox.Checkbox = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
disabled: false,
id: '',
group: false,
checked: false,
title: '',
width: 'auto'
})
.options(options || {})
.addClass('OxCheckbox')
.attr(self.options.disabled ? {
disabled: 'disabled'
} : {});
if (self.options.title) {
self.options.width != 'auto' && that.css({
width: self.options.width + 'px'
});
self.$title = new Ox.Label({
disabled: self.options.disabled,
id: self.options.id + 'Label',
overlap: 'left',
title: self.options.title,
width: self.options.width - 16
})
.css({
float: 'right'
})
.click(clickTitle)
.appendTo(that);
}
self.$button = new Ox.Button({
disabled: self.options.disabled,
id: self.options.id + 'Button',
title: [
{id: 'none', title: 'none', selected: !self.options.checked},
{id: 'check', title: 'check', selected: self.options.checked}
],
type: 'image'
})
.addClass('OxCheckbox')
.click(clickButton)
.appendTo(that);
function clickButton() {
self.options.checked = !self.options.checked;
// click will have toggled the button,
// if it is part of a group, we have to revert that
self.options.group && that.toggleChecked();
that.triggerEvent('change', {
checked: self.options.checked,
id: self.options.id,
title: self.options.title
});
}
function clickTitle() {
!self.options.disabled && self.$button.trigger('click');
}
self.onChange = function(key, value) {
if (key == 'checked') {
that.toggleChecked();
}
};
that.checked = function() {
return self.options.checked;
}
that.toggleChecked = function() {
self.$button.toggleTitle();
return that;
}
return that;
};
/**
options
checkboxes [] array of checkboxes
max 1 integer
min 1 integer
width integer, width in px
events:
change triggered when checked property changes
passes {checked, id, title}
*/
Ox.CheckboxGroup = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
checkboxes: [],
max: 1,
min: 1,
width: 256
})
.options(options || {})
.addClass('OxCheckboxGroup');
self.optionGroup = new Ox.OptionGroup(
self.options.checkboxes,
self.options.min,
self.options.max);
self.options.checkboxes = self.optionGroup.init();
$.extend(self, {
$checkboxes: [],
checkboxWidth: $.map(Ox.divideInt(
self.options.width + (self.options.checkboxes.length - 1) * 6,
self.options.checkboxes.length
), function(v, i) {
return v + (i < self.options.checkboxes.length - 1 ? 10 : 0);
})
});
$.each(self.options.checkboxes, function(position, checkbox) {
var id = self.options.id + Ox.toTitleCase(checkbox.id)
self.$checkboxes[position] = new Ox.Checkbox($.extend(checkbox, {
group: true,
id: id,
width: self.checkboxWidth[position]
}))
.bindEvent('change', function() {
change(position);
})
.appendTo(that);
});
function change(pos) {
var toggled = self.optionGroup.toggle(pos);
//Ox.print('change', pos, 'toggled', toggled)
if (toggled.length) {
$.each(toggled, function(i, pos) {
self.$checkboxes[pos].toggleChecked();
});
that.triggerEvent('change', {
checked: $.map(self.optionGroup.checked(), function(v, i) {
return self.options.checkboxes[v].id;
})
});
}
}
return that;
};
/**
options
arrows boolearn, if true, and type is 'float' or 'integer', display arrows
arrowStep number, step when clicking arrows
autocomplete array of possible values, or
function(key, value, callback), returns one or more values
autocompleteReplace boolean, if true, value is replaced
autocompleteReplaceCorrect boolean, if true, only valid values can be entered
autocompleteSelect boolean, if true, menu is displayed
autocompleteSelectHighlight boolean, if true, value in menu is highlighted
autocompleteSelectSubmit boolean, if true, submit input on menu selection
autocorrect string ('email', 'float', 'integer', 'phone', 'url'), or
regexp(value), or
function(key, value, blur, callback), returns value
auto validate --remote validation--
clear boolean, if true, has clear button
disabled boolean, if true, is disabled
height integer, px (for type='textarea' and type='range' with orientation='horizontal')
id string, element id
key string, to be passed to autocomplete and autovalidate functions
max number, max value if type is 'integer' or 'float'
min number, min value if type is 'integer' or 'float'
name string, will be displayed by autovalidate function ('invalid ' + name)
overlap string, '', 'left' or 'right', will cause padding and negative margin
picker
//rangeOptions
arrows boolean, if true, display arrows
//arrowStep number, step when clicking arrows
//arrowSymbols array of two strings
max number, maximum value
min number, minimum value
orientation 'horizontal' or 'vertical'
step number, step
thumbValue boolean, if true, value is displayed on thumb, or
array of strings per value, or
function(value), returns string
thumbSize integer, px
trackGradient string, css gradient for track
trackImage string, image url, or
array of image urls
//trackStep number, 0 for 'scroll here', positive for step
trackValues boolean
serialize
textAlign 'left', 'center' or 'right'
type 'float', 'integer', 'password', 'text'
value string
validate function, remote validation
width integer, px
methods:
events:
change
submit
*/
Ox.Input = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
arrows: false,
arrowStep: 1,
autocomplete: null,
autocompleteReplace: false,
autocompleteReplaceCorrect: false,
autocompleteSelect: false,
autocompleteSelectHighlight: false,
autocompleteSelectSubmit: false,
autovalidate: null,
clear: false,
disabled: false,
key: '',
min: 0,
max: 100,
label: '',
labelWidth: 64,
overlap: 'none',
placeholder: '',
serialize: null,
style: 'rounded',
textAlign: 'left',
type: 'text',
validate: null,
value: '',
width: 128
})
.options(options)
.addClass('OxInput OxMedium Ox' + Ox.toTitleCase(self.options.style))
.bindEvent($.extend(self.options.type == 'textarea' ? {} : {
key_enter: submit
}, {
key_escape: cancel
}));
if (
Ox.isArray(self.options.autocomplete) &&
self.options.autocompleteReplace &&
self.options.autocompleteReplaceCorrect &&
self.options.value === ''
) {
self.options.value = self.options.autocomplete[0]
}
// fixme: set to min, not 0
if (self.options.type == 'float') {
$.extend(self.options, {
autovalidate: 'float',
textAlign: 'right',
value: self.options.value || '0.0'
});
} else if (self.options.type == 'integer') {
$.extend(self.options, {
autovalidate: 'integer',
textAlign: 'right',
value: self.options.value || '0'
});
}
if (self.options.label) {
self.$label = new Ox.Label({
overlap: 'right',
textAlign: 'right',
title: self.options.label,
width: self.options.labelWidth
})
.css({
float: 'left', // fixme: use css rule
})
.click(function() {
that.focus();
})
.appendTo(that);
}
if (self.options.arrows) {
self.arrows = [];
self.arrows[0] = [
new Ox.Button({
overlap: 'right',
title: 'previous',
type: 'image'
})
.css({
float: 'left'
})
.click(function() {
clickArrow(0);
})
.appendTo(that),
new Ox.Button({
overlap: 'left',
title: 'next',
type: 'image'
})
.css({
float: 'right'
})
.click(function() {
clickArrow(1);
})
.appendTo(that)
]
}
$.extend(self, {
bindKeyboard: self.options.autocomplete || self.options.autovalidate,
hasPasswordPlaceholder: self.options.type == 'password' && self.options.placeholder,
inputWidth: getInputWidth()
});
if (self.options.clear) {
self.$button = new Ox.Button({
overlap: 'left',
title: 'clear',
type: 'image'
})
.css({
float: 'right' // fixme: use css rule
})
.click(clear)
.appendTo(that);
}
self.$input = $('<input>')
.addClass('OxInput OxMedium Ox' + Ox.toTitleCase(self.options.style))
.attr({
disabled: self.options.disabled ? 'disabled' : '',
type: self.options.type == 'password' ? 'password' : 'text'
})
.css({
width: self.inputWidth + 'px',
textAlign: self.options.textAlign
})
.val(self.options.value)
.blur(blur)
.change(change)
.focus(focus)
.appendTo(that.$element);
if (self.hasPasswordPlaceholder) {
self.$input.hide();
self.$placeholder = $('<input>')
.addClass('OxInput OxMedium Ox' +
Ox.toTitleCase(self.options.style) +
' OxPlaceholder')
.attr({
type: 'text'
})
.css({
//float: 'left',
width: self.inputWidth + 'px'
})
.val(self.options.placeholder)
.focus(focus)
.appendTo(that.$element);
}
if (self.options.autocomplete && self.options.autocompleteSelect) {
self.$autocompleteMenu = constructAutocompleteMenu();
}
self.options.placeholder && setPlaceholder();
function autocomplete(oldValue, oldCursor) {
if (self.options.value || self.options.autocompleteReplaceCorrect) {
Ox.isFunction(self.options.autocomplete) ?
(self.options.key ? self.options.autocomplete(
self.options.key,
self.options.value,
autocompleteCallback
) : self.options.autocomplete(
self.options.value,
autocompleteCallback
)) : autocompleteCallback(autocompleteFunction(self.options.value));
}
if (!self.options.value) {
self.options.autocompleteSelect && self.$autocompleteMenu.hideMenu();
}
function autocompleteFunction() {
var values = Ox.find(self.options.autocomplete, self.options.value);
return self.options.autocompleteReplace ? values[0] :
$.merge(values[0], values[1]);
}
function autocompleteCallback(values) {
Ox.print('autocompleteCallback', values[0], self.options.value, self.options.value.length, oldValue, oldCursor)
var length = self.options.value.length,
deleted = length <= oldValue.length - (oldCursor[1] - oldCursor[0]),
newValue = values[0] ?
((self.options.autocompleteReplaceCorrect || !deleted) ?
values[0] : self.options.value) :
(self.options.autocompleteReplaceCorrect ? oldValue : self.options.value),
newLength = newValue.length,
pos = cursor(),
selected = -1,
selectEnd = length == 0 || (values[0] && values[0].length),
value;
Ox.print('selectEnd', selectEnd)
if (self.options.autocompleteReplace) {
self.options.value = newValue;
self.$input.val(self.options.value);
if (selectEnd) {
cursor(length, newLength);
} else if (self.options.autocompleteReplaceCorrect) {
cursor(oldCursor);
} else {
cursor(pos);
}
selected = 0;
}
if (self.options.autocompleteSelect) {
value = self.options.value.toLowerCase();
if (values.length) {
self.oldCursor = cursor();
self.oldValue = self.options.value;
self.$autocompleteMenu.options({
items: $.map(values, function(v, i) {
if (value == v.toLowerCase()) {
selected = i;
}
return {
id: v.toLowerCase().replace(/ /g, '_'), // fixme: need function to do lowercase, underscores etc?
title: self.options.autocompleteSelectHighlight ? v.replace(
new RegExp('(' + value + ')', 'ig'),
'<span class="OxHighlight">$1</span>'
) : v
};
}),
selected: selected
}).showMenu();
} else {
self.$autocompleteMenu.hideMenu();
}
}
that.triggerEvent('autocomplete', {
value: newValue
});
}
}
function constructAutocompleteMenu() {
var menu = new Ox.Menu({
element: self.$input,
id: self.options.id + 'Menu', // fixme: we do this in other places ... are we doing it the same way? var name?,
offset: {
left: 4,
top: 0
},
size: self.options.size
})
.bindEvent('click', clickMenu);
if (self.options.autocompleteReplace) {
menu.bindEvent({
deselect: deselectMenu,
select: selectMenu,
});
}
return menu;
}
function autovalidate() {
var blur, oldCursor, oldValue;
if (arguments.length == 1) {
blur = arguments[0];
} else {
blur = false;
oldValue = arguments[0];
oldCursor = arguments[1];
}
Ox.isFunction(self.options.autovalidate) ?
(self.options.key ? self.options.autovalidate(
self.options.key,
self.options.value,
blur,
autovalidateCallback
) : self.options.autovalidate(
self.options.value,
blur,
autovalidateCallback
)) : (Ox.isRegExp(self.options.autovalidate) ?
autovalidateCallback(autovalidateFunction(self.options.value)) :
autovalidateTypeFunction(self.options.type, self.options.value)
);
function autovalidateFunction(value) {
var regexp = new RegExp(self.options.autovalidate);
return $.map(value.split(''), function(v) {
return regexp(v) ? v : null;
}).join('');
}
function autovalidateTypeFunction(type, value) {
// fixme: remove trailing zeroes on blur
var cursor,
regexp = type == 'float' ? /[\d\.]/ : /\d/;
if (type == 'float') {
if (value.indexOf('.') != value.lastIndexOf('.')) {
value = oldValue;
} else {
if (self.autovalidateFloatFlag) {
if (Ox.endsWith(value, '.')) {
value = value.substr(0, value.length - 1);
}
self.autovalidateFloatFlag = false;
}
while (value[0] == '0' && value[1] != '.') {
value = value.substr(1);
}
while (Ox.startsWith(value, '.')) {
if (Ox.startsWith(value, '..')) {
value = value.substr(1);
} else {
value = '0' + value;
}
}
if (Ox.endsWith(value, '.')) {
value += '0';
cursor = [value.length - 1, value.length];
self.autovalidateFloatFlag = true;
}
}
}
value = $.map(value.split(''), function(v) {
return regexp(v) ? v : null;
}).join('');
if (type == 'integer') {
while (value.length > 1 && Ox.startsWith(value, '0')) {
value = value.substr(1);
}
}
if (value === '') {
value = type == 'float' ? '0.0' : '0';
cursor = [0, value.length];
} else if (value > self.options.max) {
value = oldValue;
}
autovalidateCallback(value, cursor);
}
function autovalidateCallback(newValue, newCursor) {
Ox.print('autovalidateCallback', newValue, oldCursor)
self.options.value = newValue;
self.$input.val(self.options.value);
!blur && cursor(
newCursor || (oldCursor[1] + newValue.length - oldValue.length)
);
that.triggerEvent('autovalidate', {
value: self.options.value
});
}
}
/*
function autovalidate(blur) {
Ox.print('autovalidate', self.options.value, blur || false)
self.autocorrectBlur = blur || false;
self.autocorrectCursor = cursor();
Ox.isFunction(self.options.autocorrect) ?
(self.options.key ? self.options.autocorrect(
self.options.key,
self.options.value,
self.autocorrectBlur,
autocorrectCallback
) : self.options.autocorrect(
self.options.value,
self.autocorrectBlur,
autocorrectCallback
)) : autocorrectCallback(autocorrect(self.options.value));
}
function autovalidateFunction(value) {
var length = value.length;
return $.map(value.toLowerCase().split(''), function(v, i) {
if (new RegExp(self.options.autocorrect)(v)) {
return v;
} else {
return null;
}
}).join('');
}
*/
function blur() {
Ox.print('blur')
that.loseFocus();
//that.removeClass('OxFocus');
self.options.value = self.$input.val();
self.options.autovalidate && autovalidate(true);
self.options.placeholder && setPlaceholder();
self.options.validate && validate();
if (self.bindKeyboard) {
$document.unbind('keydown', keypress);
$document.unbind('keypress', keypress);
}
that.triggerEvent('blur', {});
}
function cancel() {
self.$input.blur();
}
function change() {
self.options.value = self.$input.val();
that.triggerEvent('change', {
value: self.options.value
});
}
function clear() {
// fixme: set to min, not zero
// fixme: make this work for password
var value = '';
if (self.options.type == 'float') {
value = '0.0';
} else if (self.options.type == 'integer') {
value = '0'
}
self.$input.val(value);
cursor(0, value.length);
}
function clickArrow(i) {
self.options.value = Ox.limit(
parseFloat(self.options.value) + (i == 0 ? -1 : 1) * self.options.arrowStep,
self.options.min,
self.options.max
).toString();
self.$input.val(self.options.value);//.focus();
}
function clickMenu(event, data) {
Ox.print('clickMenu', data);
self.options.value = data.title;
self.$input.val(self.options.value).focus();
that.gainFocus();
self.options.autocompleteSelectSubmit && submit();
}
function cursor(start, end) {
/*
cursor() returns [start, end]
cursor(start) sets start
cursor([start, end]) sets start and end
cursor(start, end) sets start and end
*/
var isArray = Ox.isArray(start);
if (arguments.length == 0) {
return [self.$input[0].selectionStart, self.$input[0].selectionEnd];
} else {
end = isArray ? start[1] : (end ? end : start);
start = isArray ? start[0] : start;
self.$input[0].setSelectionRange(start, end);
}
}
function deselectMenu() {
self.options.value = self.oldValue;
self.$input.val(self.options.value);
cursor(self.oldCursor);
}
function focus() {
Ox.print('focus()')
if (
that.hasClass('OxFocus') || // fixme: this is just a workaround, since for some reason, focus() gets called twice on focus
(self.$autocompleteMenu && self.$autocompleteMenu.is(':visible')) ||
(self.hasPasswordPlaceholder && self.$input.is(':visible'))
) {
return;
}
that.gainFocus();
self.options.placeholder && setPlaceholder();
if (self.bindKeyboard) {
Ox.print('binding...')
// fixme: different in webkit and firefox (?), see keyboard handler, need generic function
$document.keydown(keypress);
$document.keypress(keypress);
self.options.autocompleteSelect && setTimeout(autocomplete, 0); // fixme: why is the timeout needed?
}
}
function getInputWidth() {
return self.options.width -
(self.options.arrows ? 32 : 0) -
(self.options.clear ? 16 : 0) -
(self.options.label ? self.options.labelWidth : 0) -
(self.options.style == 'rounded' ? 14 : 6);
}
function keypress(event) {
var oldCursor = cursor(),
oldValue = self.options.value,
newValue = oldValue.substr(0, oldCursor[0] - 1),
hasDeletedSelectedEnd = (event.keyCode == 8 || event.keyCode == 46) &&
oldCursor[0] < oldCursor[1] && oldCursor[1] == oldValue.length;
Ox.print('keypress', event.keyCode)
if (event.keyCode != 9 && event.keyCode != 13 && event.keyCode != 27) { // fixme: can't 13 and 27 return false?
setTimeout(function() { // wait for val to be set
var value = self.$input.val();
if (self.options.autocompleteReplaceCorrect && hasDeletedSelectedEnd) {
Ox.print(value, '->', newValue);
value = newValue; // value.substr(0, value.length - 1);
self.$input.val(value);
}
if (value != self.options.value) {
self.options.value = value;
self.options.autocomplete && autocomplete(oldValue, oldCursor);
self.options.autovalidate && autovalidate(oldValue, oldCursor);
}
}, 0);
}
if ((event.keyCode == 38 || event.keyCode == 40) && self.options.autocompleteSelect && self.$autocompleteMenu.is(':visible')) {
return false;
}
}
function selectMenu(event, data) {
var pos = cursor();
Ox.print('selectMenu', pos)
self.options.value = data.title
self.$input.val(self.options.value);
cursor(pos[0], self.options.value.length)
}
function setPlaceholder() {
if (self.options.placeholder) {
if (that.hasClass('OxFocus')) {
if (self.options.value === '') {
if (self.options.type == 'password') {
self.$placeholder.hide();
self.$input.show().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)
}
}
}
}
function setWidth() {
}
function submit() {
self.$input.blur();
that.triggerEvent('submit', {
value: self.options.value
});
}
function validate() {
self.options.validate(self.options.value, function(data) {
that.triggerEvent('validate', data);
});
}
self.onChange = function(key, value) {
var inputWidth, val;
if (['autocomplete', 'autocompleteReplace', 'autocompleteSelect', 'autovalidate'].indexOf(key) > -1) {
if (self.options.autocomplete && self.options.autocompleteSelect) {
self.$autocompleteMenu = constructAutocompleteMenu();
}
self.bindKeyboard = self.options.autocomplete || self.options.autovalidate;
} else if (key == 'disabled') {
self.$input.attr({
disabled: value ? 'disabled' : ''
});
} else if (key == 'placeholder') {
setPlaceholder();
} else if (key == 'value') {
val = self.$input.val();
self.$input.val(value);
setPlaceholder();
} else if (key == 'width') {
inputWidth = getInputWidth();
self.$input.css({
width: inputWidth + 'px'
});
self.hasPasswordPlaceholder && self.$placeholder.css({
width: inputWidth + 'px'
});
}
};
that.focus = function() {
self.$input.focus();
cursor(0, self.$input.val().length);
return that;
};
that.value = function() {
return self.$input.hasClass('OxPlaceholder') ? '' : self.$input.val();
};
return that;
};
Ox.AutocorrectIntFunction = function(min, max, pad, year) {
var pad = pad || false,
year = year || false,
maxLength = max.toString().length,
ret = null,
values = [];
$.each(Ox.range(min, max + 1), function(i, v) {
values.push(v + '');
pad && v.toString().length < maxLength && values.push(Ox.pad(v, maxLength));
});
return function(value, blur, callback) {
var results;
if (year && value == '1') {
value = '1900';
} else {
results = Ox.find(values, value);
value = results[0].length == 1 && results[0][0].length < maxLength ?
(pad ? Ox.pad(results[0][0], maxLength) : results[0][0]) :
(results[0].length ? results[0][0] : null);
}
callback(value);
};
};
Ox.InputGroup = function(options, self) {
/***
Ox.InputGroup
Options:
Methods:
Events:
***/
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
id: '',
inputs: [],
separators: [],
width: 0
})
.options(options || {})
.addClass('OxInputGroup')
.click(click);
if (self.options.width) {
setWidths();
} else {
self.options.width = getWidth();
}
that.css({
width: self.options.width + 'px'
});
$.extend(self, {
//$input: [],
$separator: []
});
$.each(self.options.separators, function(i, v) {
self.$separator[i] = new 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);
});
$.each(self.options.inputs, function(i, $input) {
$input.options({
id: self.options.id + Ox.toTitleCase($input.options('id')),
parent: that
})
.css({
marginLeft: -Ox.sum($.map(self.options.inputs, function(v_, 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(event, data) {
Ox.print('InputGroup change')
// fixme: would be good to pass a value here
that.triggerEvent('change');
}
function click(event) {
if ($(event.target).hasClass('OxSeparator')) {
self.options.inputs[0].focus();
}
}
function getWidth() {
return Ox.sum($.map(self.options.inputs, function(v, i) {
return v.options('width');
})) + Ox.sum($.map(self.options.separators, function(v, i) {
return v.width;
}));
}
function setWidths() {
var length = self.options.inputs.length,
inputWidths = Ox.divideInt(
self.options.width - Ox.sum($.map(self.options.separators, function(v, i) {
return v.width;
})), length
);
$.each(self.options.inputs, function(i, v) {
v.options({
width: inputWidths[1]
});
});
}
function validate(event, data) {
Ox.print('INPUTGROUP TRIGGER VALIDATE')
that.triggerEvent('validate', data);
}
// fixme: is this used?
that.getInputById = function(id) {
var input = null;
$.each(self.options.inputs, function(i, v) {
Ox.print(v, v.options('id'), id)
if (v.options('id') == self.options.id + Ox.toTitleCase(id)) {
input = v;
return false;
}
});
return input;
};
that.value = function() {
return $.map(self.options.inputs, function(input) {
var ret = null;
['checked', 'selected', 'value'].forEach(function(v) {
input[v] && (ret = input[v]());
});
return ret;
});
};
return that;
};
Ox.ColorInput = function(options, self) {
var self = $.extend(self || {}, {
options: $.extend({
id: '',
value: '0, 0, 0'
}, options)
}),
that;
self.values = self.options.value.split(', ');
self.$inputs = [];
$.each(['red', 'green', 'blue'], function(i, v) {
self.$inputs[i] = new Ox.Input({
id: v,
max: 255,
type: 'integer',
value: self.values[i],
width: 36
})
.bindEvent('autovalidate', change);
});
self.$inputs[3] = new Ox.Label({
id: 'color',
width: 36
})
.css({
background: 'rgb(' + self.options.value + ')'
});
self.$inputs[4] = new Ox.ColorPicker({
id: 'picker'
})
.bindEvent('change', function(event, data) {
Ox.print('change function called');
self.options.value = data.value;
self.values = data.value.split(', ');
$.each(Ox.range(3), function(i) {
self.$inputs[i].options({
value: self.values[i]
});
});
})
.options({
width: 16 // this is just a hack to make the InputGroup layout work
});
that = new Ox.InputGroup({
id: self.options.id,
inputs: self.$inputs,
separators: [
{title: ',', width: 8},
{title: ',', width: 8},
{title: '', width: 8},
{title: '', width: 8}
],
value: self.options.value // fixme: it'd be nicer if this would be taken care of by passing self
}, self)
.bindEvent('change', change);
function change() {
self.options.value = $.map(self.$inputs, function(v, i) {
return v.options('value');
}).join(', ');
self.$inputs[3].css({
background: 'rgb(' + self.options.value + ')'
});
}
return that;
};
/**
options:
format: 'short'
value: date value
weekday: false
width: {
day: 32,
month: options.format == 'long' ? 80 : (options.format == 'medium' ? 40 : 32),
weekday: options.format == 'long' ? 80 : 40,
year: 48
}
*/
Ox.DateInput = function(options, self) {
var self = $.extend(self || {}, {
options: $.extend({
format: 'short',
value: Ox.formatDate(new Date(), '%F'),
weekday: false,
width: {
day: 32,
month: options.format == 'long' ? 80 : (options.format == 'medium' ? 40 : 32),
weekday: options.format == 'long' ? 80 : 40,
year: 48
}
}, options)
}),
that;
$.extend(self, {
date: new Date(self.options.value.replace(/-/g, '/')),
formats: {
day: '%d',
month: self.options.format == 'short' ? '%m' :
(self.options.format == 'medium' ? '%b' : '%B'),
weekday: self.options.format == 'long' ? '%A' : '%a',
year: '%Y'
},
months: self.options.format == 'long' ? Ox.MONTHS : $.map(Ox.MONTHS, function(v, i) {
return v.substr(0, 3);
}),
weekdays: self.options.format == 'long' ? Ox.WEEKDAYS : $.map(Ox.WEEKDAYS, function(v, i) {
return v.substr(0, 3);
})
});
self.$input = $.extend(self.options.weekday ? {
weekday: new Ox.Input({
autocomplete: self.weekdays,
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'weekday',
value: Ox.formatDate(self.date, self.formats.weekday),
width: self.options.width.weekday
})
.bindEvent('autocomplete', changeWeekday),
} : {}, {
day: new Ox.Input({
autocomplete: $.map(Ox.range(1, Ox.getDaysInMonth(
parseInt(Ox.formatDate(self.date, '%Y'), 10),
parseInt(Ox.formatDate(self.date, '%m'), 10)
) + 1), function(v, i) {
return self.options.format == 'short' ? Ox.pad(v, 2) : v.toString();
}),
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'day',
value: Ox.formatDate(self.date, self.formats.day),
textAlign: 'right',
width: self.options.width.day
})
.bindEvent('autocomplete', changeDay),
month: new Ox.Input({
autocomplete: self.options.format == 'short' ? $.map(Ox.range(1, 13), function(v, i) {
return Ox.pad(v, 2);
}) : self.months,
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'month',
value: Ox.formatDate(self.date, self.formats.month),
textAlign: self.options.format == 'short' ? 'right' : 'left',
width: self.options.width.month
})
.bindEvent('autocomplete', changeMonthOrYear),
year: new Ox.Input({
autocomplete: $.map($.merge(Ox.range(1900, 3000), Ox.range(1000, 1900)), function(v, i) {
return v.toString();
}),
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'year',
value: Ox.formatDate(self.date, self.formats.year),
textAlign: 'right',
width: self.options.width.year
})
.bindEvent('autocomplete', changeMonthOrYear)
});
that = new Ox.InputGroup($.extend(self.options, {
id: self.options.id,
inputs: $.merge(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
]),
separators: $.merge(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}
]),
width: 0
}), self);
Ox.print('SELF', self)
function changeDay() {
self.options.weekday && self.$input.weekday.options({
value: Ox.formatDate(new Date([
self.$input.month.options('value'),
self.$input.day.options('value'),
self.$input.year.options('value')
].join(' ')), self.formats.weekday)
});
setValue();
}
function changeMonthOrYear() {
var day = self.$input.day.options('value'),
month = self.$input.month.options('value'),
year = self.$input.year.options('value'),
days = Ox.getDaysInMonth(year, self.options.format == 'short' ? parseInt(month, 10) : month);
day = day <= days ? day : days;
Ox.print(year, month, 'day days', day, days)
self.options.weekday && self.$input.weekday.options({
value: Ox.formatDate(new Date([month, day, year].join(' ')), self.formats.weekday)
});
self.$input.day.options({
autocomplete: $.map(Ox.range(1, days + 1), function(v, i) {
return self.options.format == 'short' ? Ox.pad(v, 2) : v.toString();
}),
value: self.options.format == 'short' ? Ox.pad(day, 2) : day.toString()
});
setValue();
}
function changeWeekday() {
var date = getDateInWeek(
self.$input.weekday.options('value'),
self.$input.month.options('value'),
self.$input.day.options('value'),
self.$input.year.options('value')
);
self.$input.month.options({value: date.month});
self.$input.day.options({
autocomplete: $.map(Ox.range(1, Ox.getDaysInMonth(date.year, date.month) + 1), function(v, i) {
return self.options.format == 'short' ? Ox.pad(v, 2) : v.toString();
}),
value: date.day
});
self.$input.year.options({value: date.year});
setValue();
}
function getDateInWeek(weekday, month, day, year) {
Ox.print([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 setValue() {
self.options.value = Ox.formatDate(new Date(self.options.format == 'short' ? [
self.$input.year.options('value'),
self.$input.month.options('value'),
self.$input.day.options('value')
].join('/') : [
self.$input.month.options('value'),
self.$input.day.options('value'),
self.$input.year.options('value')
].join(' ')), '%F');
}
/*
function normalize() {
var year = that.getInputById('year').options('value'),
month = that.getInputById('month').options('value'),
day = that.getInputById('day').options('value')
return {
year: year,
month: self.options.format == 'short' ? month :
Ox.pad((format == 'medium' ? Ox.WEEKDAYS.map(function(v, i) {
return v.substr(0, 3);
}) : Ox.WEEKDAYS).indexOf(month), 2),
day: Ox.pad(day, 2)
}
}
*/
/*
that.serialize = function() {
var normal = normalize();
return [normal.year, normal.month, normal.day].join('-');
}
*/
return that;
};
Ox.DateTimeInput = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
ampm: false,
format: 'short',
seconds: false,
value: Ox.formatDate(new Date(), '%F %T'),
weekday: false
})
.options(options || {});
self.values = self.options.value.split(' ');
Ox.print(self.values)
that = new Ox.InputGroup({
inputs: [
new Ox.DateInput({
format: self.options.format,
id: 'date',
value: self.values[0],
weekday: self.options.weekday
}),
new Ox.TimeInput({
ampm: self.options.ampm,
id: 'time',
value: self.values[1],
seconds: self.options.seconds
})
],
separators: [
{title: '', width: 8}
],
value: self.options.value
})
.bindEvent('change', setValue);
function setValue() {
self.options.value = [
self.options('inputs')[0].options('value'),
self.options('inputs')[1].options('value')
].join(' ');
}
return that;
};
Ox.PlaceInput = function(options, self) {
var self = $.extend(self || {}, {
options: $.extend({
id: '',
value: 'United States'
}, options)
}),
that;
that = new Ox.FormElementGroup({
id: self.options.id,
elements: [
new Ox.Input({
id: 'input',
value: self.options.value
}),
new Ox.PlacePicker({
id: 'picker',
overlap: 'left',
value: self.options.value
})
],
float: 'right'
}, self)
.bindEvent('change', change);
function change() {
}
return that;
};
Ox.TimeInput = function(options, self) {
// fixme: seconds get set even if options.seconds is false
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
ampm: false,
seconds: false,
milliseconds: false,
value: Ox.formatDate(new Date(), '%T'),
})
.options(options || {});
if (self.options.milliseconds) {
self.options.seconds = true;
if (self.options.value.indexOf('.') == -1) {
self.options.value += '.000';
}
}
self.date = getDate();
self.values = getValues();
self.$input = {
hours: Ox.Input({
autocomplete: $.map(self.options.ampm ? Ox.range(1, 13) : Ox.range(0, 24), function(v) {
return Ox.pad(v, 2);
}),
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'hours',
textAlign: 'right',
value: self.values.hours,
width: 32
}),
minutes: Ox.Input({
autocomplete: $.map(Ox.range(0, 60), function(v) {
return Ox.pad(v, 2);
}),
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'minutes',
textAlign: 'right',
value: self.values.minutes,
width: 32
}),
seconds: Ox.Input({
autocomplete: $.map(Ox.range(0, 60), function(v) {
return Ox.pad(v, 2);
}),
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'seconds',
textAlign: 'right',
value: self.values.seconds,
width: 32
}),
milliseconds: Ox.Input({
autocomplete: $.map(Ox.range(0, 1000), function(v) {
return Ox.pad(v, 3);
}),
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'milliseconds',
textAlign: 'right',
value: self.values.milliseconds,
width: 40
}),
ampm: Ox.Input({
autocomplete: ['AM', 'PM'],
autocompleteReplace: true,
autocompleteReplaceCorrect: true,
id: 'ampm',
value: self.values.ampm,
width: 32
})
};
that = new Ox.InputGroup($.extend(self.options, {
inputs: $.merge($.merge($.merge([
self.$input.hours,
self.$input.minutes,
], self.options.seconds ? [
self.$input.seconds
] : []), self.options.milliseconds ? [
self.$input.milliseconds
] : []), self.options.ampm ? [
self.$input.ampm
] : []),
separators: $.merge($.merge($.merge([
{title: ':', width: 8},
], self.options.seconds ? [
{title: ':', width: 8}
] : []), self.options.milliseconds ? [
{title: '.', width: 8}
] : []), self.options.ampm ? [
{title: '', width: 8}
] : []),
//width: self.options.width || 128
}), self)
.bindEvent('change', setValue);
setValue();
function getDate() {
return new Date('1970/01/01 ' + (
self.options.milliseconds ?
self.options.value.substr(0, self.options.value.length - 4) :
self.options.value
));
}
function getValues() {
self.date = getDate();
return {
ampm: Ox.formatDate(self.date, '%p'),
hours: Ox.formatDate(self.date, self.options.ampm ? '%I' : '%H'),
milliseconds: self.options.milliseconds ? self.options.value.substr(-3) : '000',
minutes: Ox.formatDate(self.date, '%M'),
seconds: Ox.formatDate(self.date, '%S')
};
}
function setValue() {
self.options.value = Ox.formatDate(new Date('1970/01/01 ' + [
self.$input.hours.options('value'),
self.$input.minutes.options('value'),
self.options.seconds ? self.$input.seconds.options('value') : '00'
].join(':') + (self.options.ampm ? ' ' + self.$input.ampm.options('value') : '')),
(self.options.seconds? '%T' : '%H:%M')) +
(self.options.milliseconds ? '.' + self.$input.milliseconds.options('value') : '');
Ox.print('SETVALUE', self.options.value);
}
function setValues() {
self.values = getValues();
$.each(self.$input, function(k, v) {
self.$input[k].options({
value: self.values[k]
});
});
}
self.onChange = function(key, value) {
if (key == 'value') {
setValues();
}
}
return that;
};
Ox.Label = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
disabled: false,
id: '',
overlap: 'none',
textAlign: 'left',
title: '',
width: 'auto'
})
.options(options)
.addClass(
'OxLabel' + (self.options.disabled ? ' OxDisabled' : '') +
(self.options.overlap != 'none' ?
' OxOverlap' + Ox.toTitleCase(self.options.overlap) : '')
)
.css($.extend(self.options.width == 'auto' ? {} : {
width: (self.options.width - 14) + 'px'
}, {
textAlign: self.options.textAlign
}))
.html(self.options.title);
self.onChange = function(key, value) {
if (key == 'title') {
that.html(value);
}
}
return that;
};
Ox.OptionGroup = function(items, min, max, property) {
/*
to be used by ButtonGroup, CheckboxGroup, Select and Menu
*/
var property = property || 'checked'
length = items.length,
max = max == -1 ? length : max;
function getLastBefore(pos) {
// returns the position of the last checked item before position pos
var last = -1;
Ox.print(items, items.length, length, $.merge(
pos > 0 ? Ox.range(pos - 1, -1, -1) : [],
pos < items.length - 1 ? Ox.range(items.length - 1, pos, -1) : []
))
// fixme: why is length not == items.length here?
$.each($.merge(
pos > 0 ? Ox.range(pos - 1, -1, -1) : [],
pos < items.length - 1 ? Ox.range(items.length - 1, pos, -1) : []
), function(i, v) {
Ox.print(pos, v)
if (items[v][property]) {
last = v;
return false;
}
});
return last;
}
function getNumber() {
// returns the number of checked items
var num = 0;
$.each(items, function(i, item) {
if (item[property]) {
num++;
}
})
return num;
}
this[property] = function() {
// returns an array with the positions of all checked item
var checked = [];
$.each(items, function(i, item) {
if (item[property]) {
checked.push(i);
}
})
return checked;
};
this.init = function() {
var num = getNumber(),
count = 0;
//if (num < min || num > max) {
$.each(items, function(i, 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;
};
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;
}
return this;
}
/**
options
arrows boolean if true, show arrows
arrowStep number step when clicking arrows
arrowSymbols array arrow symbols, like ['minus', 'plus']
max number maximum value
min number minimum value
orientation string 'horizontal' or 'vertical'
step number step between values
size number width or height, in px
thumbSize number minimum width or height of thumb, in px
thumbValue boolean if true, display value on thumb
trackGradient array colors
trackImages string or array one or multiple track background image URLs
trackStep number 0 (scroll here) or step when clicking track
value number initial value
valueNames array value names to display on thumb
*/
Ox.Range = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
arrows: false,
arrowStep: 1,
arrowSymbols: ['previous', 'next'],
max: 100,
min: 0,
orientation: 'horizontal',
step: 1,
size: 128,
thumbSize: 16,
thumbValue: false,
trackColors: [],
trackImages: [],
trackStep: 0,
value: 0,
valueNames: null,
})
.options($.extend(options, {
arrowStep: options.arrowStep ?
options.arrowStep : options.step,
trackImages: $.makeArray(options.trackImages || [])
}))
.addClass('OxRange')
.css({
width: self.options.size + 'px'
});
$.extend(self, {
trackColors: self.options.trackColors.length,
trackImages: self.options.trackImages.length,
trackSize: self.options.size - self.options.arrows * 32,
values: (self.options.max - self.options.min + self.options.step) /
self.options.step
});
$.extend(self, {
thumbSize: Math.max(self.trackSize / self.values, self.options.thumbSize),
trackImageWidths: self.trackImages == 1 ? [self.trackSize - 16] :
Ox.divideInt(self.trackSize - 2, self.trackImages)
});
$.extend(self, {
trackColorsStart: self.thumbSize / 2 / self.options.size,
trackColorsStep: (self.options.size - self.thumbSize) / (self.trackColors - 1) / self.options.size
});
if (self.options.arrows) {
self.$arrows = [];
$.each(Ox.range(0, 2), function(i) {
self.$arrows[i] = Ox.Button({
overlap: i == 0 ? 'right' : 'left',
title: self.options.arrowSymbols[i],
type: 'image'
})
.addClass('OxArrow')
.mousedown(function(e) {
clickArrow(e, i);
})
.appendTo(that.$element);
});
}
self.$track = $('<div>')
.addClass('OxTrack')
.css($.extend({
width: (self.trackSize - 2) + 'px'
}, self.trackImages == 1 ? {
background: 'rgb(0, 0, 0)'
} : {}))
.mousedown(clickTrack)
.appendTo(that.$element);
self.trackColors && setTrackColors();
if (self.trackImages) {
self.$trackImages = $('<div>')
.css({
width: self.trackSize + 'px',
marginRight: (-self.trackSize - 1) + 'px'
})
.appendTo(self.$track);
$.each(self.options.trackImages, function(i, v) {
Ox.print(self.trackImageWidths[i])
$('<img>')
.attr({
src: v
})
.addClass(i == 0 ? 'OxFirstChild' : '')
.addClass(i == self.trackImages - 1 ? 'OxLastChild' : '')
.css({
width: self.trackImageWidths[i] + 'px'
})
.mousedown(function(e) {
e.preventDefault(); // prevent drag
})
.appendTo(self.$trackImages);
//left += self.trackImageWidths[i];
});
}
self.$thumb = Ox.Button({
id: self.options.id + 'Thumb',
title: self.options.thumbValue ? (self.options.valueNames ?
self.options.valueNames[self.options.value] :
self.options.value) : '',
width: self.thumbSize
})
.addClass('OxThumb')
/*
.css({
border: '1px solid rgb(255, 255, 255)',
background: 'rgba(0, 0, 0, 0)'
})
*/
.appendTo(self.$track);
setThumb();
function clickArrow(e, i) {
// fixme: shift doesn't work, see menu scrolling
var interval,
timeout = setTimeout(function() {
interval = setInterval(function() {
setValue(self.options.value + self.options.arrowStep * (i == 0 ? -1 : 1));
}, 50);
}, 500);
setValue(self.options.value + self.options.arrowStep * (i == 0 ? -1 : 1) * (e.shiftKey ? 2 : 1), true);
$window.one('mouseup', function() {
clearInterval(interval);
clearTimeout(timeout);
});
}
function clickTrack(e) {
//Ox.Focus.focus();
var isThumb = $(e.target).hasClass('OxThumb'),
left = self.$track.offset().left,
offset = isThumb ? e.clientX - self.$thumb.offset().left - 8 /*self.thumbSize / 2*/ : 0;
setValue(val(e), !isThumb);
$window.mousemove(function(e) {
setValue(val(e));
});
$window.one('mouseup', function() {
$window.unbind('mousemove');
});
function val(e) {
return getVal(e.clientX - left - offset);
}
}
function getPx(val) {
var pxPerVal = (self.trackSize - self.thumbSize) /
(self.options.max - self.options.min);
return Math.ceil((val - self.options.min) * pxPerVal);
}
/*
function getTime(oldValue, newValue) {
return self.animationTime * Math.abs(oldValue - newValue) / (self.options.max - self.options.min);
}
*/
function getVal(px) {
var px = self.trackSize / self.values >= 16 ? px : px - 8,
valPerPx = (self.options.max - self.options.min) /
(self.trackSize - self.thumbSize);
return Ox.limit(self.options.min +
Math.floor(px * valPerPx / self.options.step) * self.options.step,
self.options.min, self.options.max);
}
function setThumb(animate) {
self.$thumb.stop().animate({
marginLeft: (getPx(self.options.value) - 1) + 'px',
//width: self.thumbSize + 'px'
}, animate ? 200 : 0, function() {
if (self.options.thumbValue) {
self.$thumb.options({
title: self.options.valueNames ?
self.options.valueNames[self.options.value] :
self.options.value
});
}
});
}
function setTrackColors() {
self.$track.css({
backgroundImage: $.browser.mozilla ?
('-moz-linear-gradient(left, ' +
self.options.trackColors[0] + ' 0%, ' + $.map(self.options.trackColors, function(v, i) {
return v + ' ' + ((self.trackColorsStart + self.trackColorsStep * i) * 100) + '%';
}).join(', ') + ', ' + self.options.trackColors[self.trackColors - 1] + ' 100%)') :
('-webkit-gradient(linear, left top, right top, color-stop(0, ' +
self.options.trackColors[0] + '), ' + $.map(self.options.trackColors, function(v, i) {
return 'color-stop(' + (self.trackColorsStart + self.trackColorsStep * i) + ', ' + v + ')';
}).join(', ') + ', color-stop(1, ' + self.options.trackColors[self.trackColors - 1] + '))')
});
}
function setValue(value, animate) {
var value = Ox.limit(value, self.options.min, self.options.max);
if (value != self.options.value) {
//time = getTime(self.options.value, value);
self.options.value = value;
setThumb(animate);
that.triggerEvent('change', {
value: value
});
}
}
self.onChange = function(key, value) {
if (key == 'trackColors') {
setTrackColors();
} else if (key == 'value') {
setThumb();
}
}
return that;
};
Ox.Select = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self) // fixme: do we use 'div', or {}, or '', by default?
.defaults({
id: '',
items: [],
max: 1,
min: 1,
overlap: 'none', // can be none, left or right
selectable: true,
size: 'medium',
title: '',
type: 'text', // can be 'text' or 'image'
width: 'auto'
})
.options(options)
.addClass(
'OxSelect Ox' + Ox.toTitleCase(self.options.size) +
(self.options.overlap == 'none' ? '' : ' OxOverlap' +
Ox.toTitleCase(self.options.overlap))
)
.css(self.options.width == 'auto' ? {} : {
width: self.options.width + 'px'
})
.bindEvent({
key_escape: loseFocus,
key_down: showMenu
});
$.extend(self, {
buttonId: self.options.id + 'Button',
groupId: self.options.id + 'Group',
menuId: self.options.id + 'Menu'
});
if (self.options.selectable) {
self.optionGroup = new Ox.OptionGroup(
self.options.items,
self.options.min,
self.options.max
);
self.options.items = self.optionGroup.init();
self.checked = self.optionGroup.checked();
}
if (self.options.type == 'text') {
self.$title = $('<div>')
.addClass('OxTitle')
.css({
width: (self.options.width - 22) + 'px'
})
.html(
self.options.title ? self.options.title :
self.options.items[self.checked[0]].title
)
.click(showMenu)
.appendTo(that.$element);
}
self.$button = new Ox.Button({
id: self.buttonId,
style: 'symbol',
title: 'select',
type: 'image'
})
.bindEvent('click', showMenu)
.appendTo(that);
self.$menu = new Ox.Menu({
element: self.$title || self.$button,
id: self.menuId,
items: [self.options.selectable ? {
group: self.groupId,
items: self.options.items,
max: self.options.max,
min: self.options.min
} : self.options.items],
side: 'bottom',
size: self.options.size
})
.bindEvent({
change: changeMenu,
click: clickMenu,
hide: hideMenu
});
self.options.type == 'image' && self.$menu.addClass('OxRight');
function clickMenu(event, data) {
Ox.print('%% clickMenu')
}
function changeMenu(event, data) {
Ox.print('clickMenu: ', self.options.id, data)
self.checked = self.optionGroup.checked();
self.$title && self.$title.html(
self.options.title ? self.options.title :
data.checked[0].title
);
that.triggerEvent('change', {
selected: data.checked
});
}
function hideMenu() {
Ox.print('%% hideMenu that', that, 'self', self)
that.removeClass('OxSelected');
// self.$button.removeClass('OxSelected');
Ox.print('%% hideMenu end')
}
function loseFocus() {
that.loseFocus();
}
function showMenu() {
that.gainFocus();
that.addClass('OxSelected');
self.$menu.showMenu();
}
self.onChange = function(key, value) {
};
that.selected = function() {
return $.map(/*self.checked*/self.optionGroup.checked(), function(v, i) {
return {
id: self.options.items[i].id,
title: self.options.items[i].title
};
});
};
that.selectItem = function(id) {
Ox.print('selectItem', id, Ox.getObjectById(self.options.items, id).title)
self.options.type == 'text' && self.$title.html(
Ox.getObjectById(self.options.items, id).title[0] // fixme: title should not have become an array
);
self.$menu.checkItem(id);
self.checked = self.optionGroup.checked();
};
/*
that.width = function(val) {
// fixme: silly hack, and won't work for css() ... remove!
that.$element.width(val + 16);
that.$button.width(val);
//that.$symbol.width(val);
return that;
};
*/
return that;
};
Ox.FormElementGroup = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
id: '',
elements: [],
float: 'left',
separators: [],
width: 0
})
.options(options || {})
.addClass('OxInputGroup');
$.each(self.options.float == 'left' ? self.options.elements : self.options.elements.reverse(), function(i, $element) {
$element.css({
float: self.options.float // fixme: make this a class
})
.bindEvent({
validate: function(event, data) {
that.triggerEvent({
validate: data
});
}
})
.appendTo(that);
});
/*
if (self.options.width) {
setWidths();
} else {
self.options.width = getWidth();
}
that.css({
width: self.options.width + 'px'
});
*/
function getWidth() {
}
function setWidth() {
}
self.onChange = function(key, value) {
if (key == 'trackColors') {
}
}
that.value = function() {
return $.map(self.options.elements, function(input) {
var ret = null;
['checked', 'selected', 'value'].forEach(function(v) {
input[v] && (ret = input[v]());
});
return ret;
});
};
return that;
};
Ox.Picker = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
element: null,
elementHeight: 128,
elementWidth: 256,
id: '',
overlap: 'none'
})
.options(options || {});
self.$selectButton = new Ox.Button({
overlap: self.options.overlap,
title: 'select',
type: 'image'
})
.click(showMenu)
.appendTo(that);
self.$menu = new Ox.Element('div')
.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 = new Ox.Bar({
orientation: 'horizontal',
size: 24
})
.appendTo(self.$menu);
that.$label = new Ox.Label({
width: self.options.elementWidth - 60
})
.appendTo(self.$bar);
self.$doneButton = new Ox.Button({
title: 'Done',
width: 48
})
.click(hideMenu)
.appendTo(self.$bar);
self.$layer = $('<div>')
.addClass('OxLayer')
.click(hideMenu);
function hideMenu() {
self.$menu.detach();
self.$layer.detach();
self.$selectButton
.removeClass('OxSelected')
.css({
MozBorderRadius: '8px',
WebkitBorderRadius: '8px'
});
that.triggerEvent('hide');
};
function showMenu() {
var offset = that.offset(),
left = offset.left,
top = offset.top + 15;
self.$selectButton
.addClass('OxSelected')
.css({
MozBorderRadius: '8px 8px 0 0',
WebkitBorderRadius: '8px 8px 0 0'
});
self.$layer.appendTo($body);
self.$menu
.css({
left: left + 'px',
top: top + 'px'
})
.appendTo($body);
that.triggerEvent('show');
};
return that;
};
Ox.ColorPicker = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
id: '',
value: '0, 0, 0'
})
.options(options || {});
Ox.print(self)
self.$ranges = [];
self.rgb = ['red', 'green', 'blue'];
self.values = self.options.value.split(', ');
$.each(Ox.range(3), function(i) {
self.$ranges[i] = new Ox.Range({
arrows: true,
id: self.options.id + Ox.toTitleCase(self.rgb[i]),
max: 255,
size: 328, // 256 + 16 + 40 + 16
thumbSize: 40,
thumbValue: true,
trackColors: getColors(i),
value: self.values[i]
})
.css({
position: 'absolute',
top: (i * 15) + 'px'
})
.bindEvent('change', function(event, data) {
change(i, data.value);
})
.appendTo(that);
// fixme: make self.$ranges[i].children() work
if (i == 0) {
self.$ranges[i].$element.children('input.OxOverlapRight').css({
MozBorderRadius: 0,
WebkitBorderRadius: 0
});
self.$ranges[i].$element.children('input.OxOverlapLeft').css({
MozBorderRadius: '0 8px 0 0',
WebkitBorderRadius: '0 8px 0 0'
});
} else {
self.$ranges[i].$element.children('input').css({
MozBorderRadius: 0,
WebkitBorderRadius: 0
});
}
});
that = new Ox.Picker({
element: that,
elementHeight: 46,
elementWidth: 328,
id: self.options.id
});
function change(index, value) {
self.values[index] = value;
self.options.value = self.values.join(', ');
that.$label.css({
background: 'rgb(' + self.options.value + ')'
});
$.each(Ox.range(3), function(i) {
if (i != index) {
self.$ranges[i].options({
trackColors: getColors(i)
});
}
});
that.triggerEvent('change', {
value: self.options.value
});
}
function getColors(index) {
return [
'rgb(' + $.map(Ox.range(3), function(v) {
return v == index ? 0 : self.values[v];
}).join(', ') + ')',
'rgb(' + $.map(Ox.range(3), function(v) {
return v == index ? 255 : self.values[v];
}).join(', ') + ')'
]
}
return that;
};
Ox.PlacePicker = function(options, self) {
var self = $.extend(self || {}, {
options: $.extend({
id: '',
value: 'United States'
}, options)
}),
that;
self.$element = new Ox.Element('div')
.css({
width: '256px',
height: '192px'
})
.append(
self.$topBar = new Ox.Bar({
size: 16
})
.css({
MozBorderRadius: '0 8px 0 0',
WebkitBorderRadius: '0 8px 0 0'
})
.append(
self.$input = new Ox.Input({
clear: true,
id: self.options.id + 'Input',
placeholder: 'Find',
width: 256
})
.bindEvent('submit', findPlace)
)
)
.append(
self.$container = new Ox.Element('div')
.css({
width: '256px',
height: '160px'
})
)
.append(
self.$bottomBar = new Ox.Bar({
size: 16
})
.append(
self.$range = new Ox.Range({
arrows: true,
id: self.options.id + 'Range',
max: 22,
size: 256,
thumbSize: 32,
thumbValue: true
})
.bindEvent('change', changeZoom)
)
);
self.$input.$element.children('input[type=text]').css({
width: '230px',
paddingLeft: '2px',
MozBorderRadius: '0 8px 8px 0',
WebkitBorderRadius: '0 8px 8px 0'
});
self.$input.$element.children('input[type=image]').css({
MozBorderRadius: '0 8px 0 0',
WebkitBorderRadius: '0 8px 0 0'
});
self.$range.$element.children('input').css({
MozBorderRadius: 0,
WebkitBorderRadius: 0
});
that = new 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.bind('click', clickLabel)
self.map = false;
function changeZoom(event, data) {
Ox.print('changeZoom')
self.$map.zoom(data.value);
}
function clickLabel() {
var name = that.$label.html();
if (name) {
self.$input.options({
value: name
})
.triggerEvent('submit', {
value: name
});
}
}
function findPlace(event, data) {
Ox.print('findPlace', data);
self.$map.find(data.value, function(place) {
place && that.$label.html(place.geoname);
})
}
function onSelect(event, data) {
that.$label.html(data.geoname);
}
function onZoom(event, data) {
self.$range.options({
value: data.value
});
}
function showPicker() {
if (!self.map) {
self.$map = new Ox.Map({
id: self.options.id + 'Map',
places: [self.options.value]
})
.css({
width: '256px',
height: '160px'
})
.bindEvent({
select: onSelect,
zoom: onZoom
})
.appendTo(self.$container);
self.map = true;
}
}
return that;
};
/**
delete below
*/
Ox.Input_ = function(options, self) {
/*
options:
clear boolean, clear button, or not
disabled boolean, disabled, or not
height height (px), if type is 'textarea'
id
label string, or
array [{ id, title, checked }] (selectable label) or
array [{ id, label: [{ id, title, checked }], width }] (multiple selectable labels)
label and placeholder are mutually exclusive
labelWidth integer (px)
placeholder string, or
array [{ id, title, checked }] (selectable placeholder)
label and placeholder are mutually exclusive
separator string, or
array of strings
to separate multiple values
separatorWidth integer (px), or
array of integers
serialize function
size 'large', 'medium' or 'small'
type 'password', 'select' or 'text'
unit string, or
array [{ id, title, checked }] (selectable unit)
unitWidth integer (px)
value string, or
array [{ id, value, width }] (multiple values)
width integer (px)
methods:
events:
*/
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
autocomplete: null,
autocorrect: null,
autosuggest: null,
autosuggestHighlight: false,
autosuggestSubmit: false,
autovalidate: null,
autovalidateName: 'Value',
clear: false,
disabled: false,
height: 128,
id: '',
key: '',
label: '',
labelWidth: 64,
placeholder: '',
separator: '',
separatorWidth: 16,
serialize: null,
size: 'medium',
type: 'text',
unit: '',
unitWidth: 64,
value: '',
width: 128
})
.options(options || {})
.addClass('OxInput Ox' + Ox.toTitleCase(self.options.size))
.css({
width: self.options.width + 'px'
});
$.extend(self, {
clearWidth: 16,
hasMultipleKeys: Ox.isArray(self.options.label) && 'label' in self.options.label[0],
hasMultipleValues: Ox.isArray(self.options.value) &&
(self.options.type != 'select' || 'items' in self.options.value[0]),
hasSelectableKeys: Ox.isArray(self.options.label) || Ox.isArray(self.options.placeholder),
hasSelectableUnits: Ox.isArray(self.options.unit),
keyName: self.options.label ? 'label' : (self.options.placeholder ? 'placeholder' : ''),
placeholderWidth: 16,
selectedKey: [0], // fixme: only set on demand?
selectedValue: 0,
selectedUnit: 0,
/* valid: autovalidateCall(true) */
});
$.each(['autocomplete', 'autocorrect', 'autosuggest', 'autovalidate'], function(i, v) {
//if (!Ox.isFunction(self.options[v])) {
self.options[v] = {
'': self.options[v]
};
//}
});
if (self.keyName && !self.hasMultipleKeys) {
self.options[self.keyName] = [$.extend({
id: '',
label: self.options[self.keyName],
}, self.keyName == 'label' ? {
id: '',
width: self.options.labelWidth
} : {})];
if (!self.hasSelectableKeys) {
self.options[self.keyName][0].label = [{
id: '',
title: self.options[self.keyName][0].label
}];
}
}
if (self.hasSelectableKeys) {
$.each(self.options[self.keyName], function(keyPos, key) {
if (key.width) {
self.options.labelWidth = (keyPos == 0 ? 0 : self.options.labelWidth) + key.width;
}
self.selectedKey[keyPos] = 0;
$.each(key, function(valuePos, value) {
if (value.checked) {
self.selectedKey[keyPos] = valuePos;
return false;
}
});
});
}
self.valueWidth = self.options.width -
(self.options.label ? self.options.labelWidth : 0) -
((self.options.placeholder && self.options.placeholder[0].label.length > 1) ? self.placeholderWidth : 0) -
(self.options.unit ? self.options.unitWidth : 0) -
(self.options.clear ? self.clearWidth : 0);
/*
if (self.hasMultipleValues) {
self.valueWidth -= Ox.isArray(self.options.separatorWidth) ?
Ox.sum(self.options.separatorWidth) :
(self.options.value.length - 1) * self.options.separatorWidth;
}
*/
Ox.print('self.hasMulVal', self.hasMultipleValues);
Ox.print('self.options.value', self.options.value)
if (!self.hasMultipleValues) {
if (self.options.type == 'select') {
self.options.value = [{
id: '',
items: self.options.value,
width: self.valueWidth
}];
} else if (self.options.type == 'range') {
self.options.value = [$.extend({
id: '',
size: self.valueWidth
}, self.options.value)];
} else {
self.options.value = [{
id: '',
value: self.options.value,
width: self.valueWidth
}]
}
}
Ox.print('self.options.value', self.options.value)
self.values = self.options.value.length;
Ox.print(self.options.id, 'self.values', self.values)
if (Ox.isString(self.options.separator)) {
self.options.separator = $.map(new Array(self.values - 1), function(v, i) {
return self.options.separator;
});
}
if (Ox.isNumber(self.options.separatorWidth)) {
self.options.separatorWidth = $.map(new Array(self.values - 1), function(v, i) {
return self.options.separatorWidth;
});
}
if (self.options.unit) {
if (self.hasSelectableUnits) {
$.each(self.options.unit, function(pos, unit) {
if (unit.checked) {
self.selectedUnit = pos;
return false;
}
});
} else {
self.options.unit = [{
id: '',
title: self.options.unit
}];
}
}
Ox.print('self', self);
if (self.keyName) {
that.$key = [];
$.each(self.options[self.keyName], function(keyPos, key) {
Ox.print('keyPos key', keyPos, key)
if (self.keyName == 'label' && key.label.length == 1) {
that.$key[keyPos] = new Ox.Label({
overlap: 'right',
title: key.label[0].title,
width: self.options.labelWidth
})
.css({
float: 'left'
})
.click(function() {
that.$input[0].focus();
})
.appendTo(that);
} else if (key.label.length > 1) {
Ox.print('key.length > 1')
self.selectKeyId = self.options.id + Ox.toTitleCase(self.keyName) +
(self.options[self.keyName].length == 1 ? '' : keyPos);
Ox.print('three', self.selectedKey, keyPos, self.selectedKey[keyPos]);
that.$key[keyPos] = new Ox.Select({
id: self.selectKeyId,
items: $.map(key.label, function(value, valuePos) {
return {
checked: valuePos == self.selectedKey[keyPos],
id: value.id,
group: self.selectKeyId, // fixme: same id, works here, but should be different
title: value.title
};
}),
overlap: 'right',
type: self.options.label ? 'text' : 'image',
width: self.options.label ? (self.options.label.length == 1 ? self.options.labelWidth : key.width) : self.placeholderWidth
})
.css({
float: 'left'
})
.appendTo(that);
that.bindEvent('change_' + self.selectKeyId, changeKey);
}
});
}
if (self.options.clear) {
that.$clear = new Ox.Button({
overlap: 'left',
type: 'image',
value: 'clear'
})
.css({
float: 'right'
})
.click(clear)
.appendTo(that);
}
if (self.options.unit.length == 1) {
that.$unit = new Ox.Label({
overlap: 'left',
title: self.options.unit[0].title,
width: self.options.unitWidth
})
.css({
float: 'right'
})
.click(function() {
that.$input[0].focus();
})
.appendTo(that);
} else if (self.options.unit.length > 1) {
self.selectUnitId = self.options.id + 'Unit';
that.$unit = new Ox.Select({
id: self.selectUnitId,
items: $.map(self.options.unit, function(unit, i) {
Ox.print('unit', unit)
return {
checked: i == 0,
id: unit.id,
group: self.selectUnitId, // fixme: same id, works here, but should be different
title: unit.title
};
}),
overlap: 'left',
size: self.options.size,
width: self.options.unitWidth
})
.css({
float: 'right'
})
.appendTo(that);
}
if (self.values) {
that.$separator = [];
$.each(self.options.value, function(i, v) {
if (i < self.values - 1) {
that.$separator[i] = new Ox.Label({
textAlign: 'center',
title: self.options.separator[i],
width: self.options.separatorWidth[i] + 32
})
.css({
float: 'left',
marginLeft: (v.width - (i == 0 ? 16 : 32)) + 'px'
})
.click(function() {
that.$input[0].focus();
})
.appendTo(that);
}
});
}
that.$input = [];
//self.margin = 0;
$.each(self.options.value, function(i, v) {
Ox.print('o k i', self.options, self.keyName, i);
var id = self.keyName ? $.map(self.selectedKey, function(v, i) {
return self.options[self.keyName][i].id;
}).join('.') : '';
//self.margin -= (i == 0 ? 16 : self.options.value[i - 1].width)
Ox.print('v:', v, 'id:', id)
if (self.options.type == 'select') {
that.$input[i] = new Ox.Select({
id: v.id,
items: v.items,
width: v.width
}).
css({
float: 'left'
});
} else if (self.options.type == 'range') {
that.$input[i] = new Ox.Range(v)
.css({
float: 'left'
});
} else {
that.$input[i] = new Ox.InputElement({
autocomplete: self.options.autocomplete[id],
autocorrect: self.options.autocorrect[id],
autosuggest: self.options.autosuggest[id],
autosuggestHighlight: self.options.autosuggestHighlight,
autosuggestSubmit: self.options.autosuggestSubmit,
autovalidate: self.options.autovalidate[id],
autovalidateName: self.options.autovalidateName,
disabled: self.options.disabled,
height: self.options.height,
id: self.options.id + 'Input' + Ox.toTitleCase(v.id),
key: self.hasSelectableKeys ? self.options[self.keyName][0].label[self.selectedKey[0]].id : '',
parent: that,
placeholder: self.options.placeholder ? self.options.placeholder[0].label[0].title : '',
size: self.options.size,
type: self.options.type,
value: v.value,
width: v.width
});
}
that.$input[i]
.css($.extend({}, self.options.value.length > 1 ? {
float: 'left',
marginLeft: -Ox.sum($.map(self.options.value, function(v_, i_) {
return i_ > i ? self.options.value[i_ - 1].width + self.options.separatorWidth[i_ - 1] : (i_ == i ? 16 : 0);
}))
} : {}))
.appendTo(that);
});
//width(self.options.width);
function changeKey(event, data) {
Ox.print('changeKey', data);
if (data) { // fixme: necessary?
self.key = {
id: data.id,
title: data.value // fixme: should be data.title
};
that.$input[0].options({
key: data.id
});
}
if (self.options.label) {
//that.$label.html(self.option.title);
that.$input[0].focus();
//autocompleteCall();
} else {
that.$input[0].options({
placeholder: data.value // fixme: should be data.title
});
/*
if (that.$input.hasClass('OxPlaceholder')) {
that.$input.val(self.key.title);
//that.$input.focus();
} else {
that.$input.focus();
self.options.autosuggest && autosuggestCall();
}
*/
}
}
function changeUnit() {
that.$input[0].focus();
}
function clear() {
$.each(that.$input, function(i, v) {
v.val('');
});
that.$input[0].focus();
}
function height(value) {
var stop = 8 / value;
if (self.options.type == 'textarea') {
that.$element
.height(value)
.css({
background: '-moz-linear-gradient(top, rgb(224, 224, 224), rgb(208, 208, 208) ' + (stop * 100) + '%, rgb(208, 208, 208) ' + (100 - stop * 100) + '%, rgb(192, 192, 192))'
})
.css({
background: '-webkit-gradient(linear, left top, left bottom, from(rgb(224, 224, 224)), color-stop(' + stop + ', rgb(208, 208, 208)), color-stop(' + (1 - stop) + ', rgb(208, 208, 208)), to(rgb(192, 192, 192)))'
});
that.$input
.height(value)
.css({
background: '-moz-linear-gradient(top, rgb(224, 224, 224), rgb(240, 240, 240) ' + (stop * 100) + '%, rgb(240, 240, 240) ' + (100 - stop * 100) + '%, rgb(255, 255, 255))'
})
.css({
background: '-webkit-gradient(linear, left top, left bottom, from(rgb(224, 224, 224)), color-stop(' + stop + ', rgb(240, 240, 240)), color-stop(' + (1 - stop) + ', rgb(240, 240, 240)), to(rgb(255, 255, 255)))'
});
}
}
function selectUnit() {
self.$selectUnitMenu.show();
}
function submit() {
Ox.print('submit')
var value = that.$input.val();
that.$input.blur();
that.triggerEvent('submit', self.options.key ? {
key: self.options.key,
value: value
} : value);
}
function width(value) {
that.$element.width(value);
that.$input.width(
value - (self.options.type == 'textarea' ? 0 : 12) -
(self.options.label ? self.options.labelWidth : 0) -
(self.options.placeholder.length > 1 ? 16 : 0) -
(self.options.unit ? self.options.unitWidth : 0) -
(self.options.clear ? 16 : 0)
);
}
self.onChange = function(key, value) {
if (key == 'height') {
height(value);
} else if (key == 'width') {
width(value);
}
};
that.changeLabel = function(id) {
that.$key.html(Ox.getObjectById(self.options.label, id).title);
self.selectMenu.checkItem(id);
};
return that;
}
Ox.InputElement_ = function(options, self) {
var self = self || {},
that = new Ox.Element(
options.type == 'textarea' ? 'textarea' : 'input', self
)
.defaults({
autocomplete: null,
autocorrect: null,
autosuggest: null,
autosuggestHighlight: false,
autosuggestSubmit: false,
autovalidate: null,
disabled: false,
height: 128,
id: '',
key: '',
parent: null,
placeholder: '',
size: 'medium',
type: 'text',
value: '',
width: 128
})
.options(options || {})
.addClass('OxInput Ox' + Ox.toTitleCase(self.options.size) + (
(self.options.placeholder && self.options.value === '') ?
' OxPlaceholder' : ''
))
.attr(self.options.type == 'textarea' ? {} : {
type: self.options.type
})
.css({
float: 'left',
width: (self.options.width - 14) + 'px'
})
.val(
(self.options.placeholder && self.options.value === '') ?
self.options.placeholder : self.options.value
)
.blur(blur)
.change(change)
.focus(focus);
Ox.print('InputElement self.options', self.options)
self.bindKeyboard = self.options.autocomplete || self.options.autocorrect ||
self.options.autosuggest || self.options.autovalidate;
if (self.options.autosuggest) {
self.autosuggestId = self.options.id + 'Menu'; // fixme: we do this in other places ... are we doing it the same way? var name?
self.$autosuggestMenu = new Ox.Menu({
element: that.$element,
id: self.autosuggestId,
offset: {
left: 4,
top: 0
},
size: self.options.size
});
that.bindEvent('click_' + self.autosuggestId, clickMenu);
}
that.bindEvent($.extend(self.options.type == 'textarea' ? {} : {
key_enter: submit
}, {
key_escape: cancel
}));
function autocomplete(value) {
var value = value.toLowerCase(),
ret = '';
if (value !== '') {
$.each(self.options.autocomplete, function(i, v) {
if (v.toLowerCase().indexOf(value) == 0) {
ret = v;
return false;
}
});
}
return ret;
}
function autocompleteCall() {
var value = that.$element.val();
Ox.isFunction(self.options.autocomplete) ?
self.options.autocomplete(self.options.key ? {
key: self.options.key,
value: value
} : value, autocompleteCallback) :
autocompleteCallback(autocomplete(value));
}
function autocompleteCallback(value) {
var pos = cursor()[0];
if (value) {
that.$element.val(value);
cursor(pos, value.length);
}
}
function autocorrect(value) {
var length = value.length;
return $.map(value.toLowerCase().split(''), function(v, i) {
if (new RegExp(self.options.autocorrect)(v)) {
return v
} else {
return null;
}
}).join('');
}
function autocorrectCall(blur) {
var blur = blur || false,
value = that.$element.val(),
pos = cursor()[0];
Ox.isFunction(self.options.autocorrect) ?
self.options.autocorrect(value, blur, autocorrectCallback) :
autocorrectCallback(autocorrect(value), blue);
}
function autocorrectCallback(value, blur) {
var length = that.$element.val().length;
that.$element.val(self.options.value);
!blur && cursor(pos + value.length - length);
}
function autosuggest(value) {
var value = value.toLowerCase(),
values = [[], []];
if (value !== '') {
$.each(self.options.key ? self.options.autosuggest[self.options.key] : self.options.autosuggest, function(i, v) {
//Ox.print('v...', v)
var index = v.toLowerCase().indexOf(value);
index > -1 && values[index == 0 ? 0 : 1].push(v);
});
}
return $.merge(values[0], values[1]);
}
function autosuggestCall() {
var value = that.$element.val();
Ox.isFunction(self.options.autosuggest) ?
self.options.autosuggest(self.options.key ? {
key: self.options.key,
value: value
} : value, autosuggestCallback) :
autosuggestCallback(autosuggest(value));
}
function autosuggestCallback(values) {
var values = values || [],
selected = values.length == 1 ? 0 : -1,
value = that.$element.val().toLowerCase();
//Ox.print('values', values);
if (values.length) {
values = $.map(values, function(v, i) {
if (value == v.toLowerCase()) {
selected = i;
}
return {
id: v.toLowerCase().replace(/ /g, '_'), // fixme: need function to do lowercase, underscores etc?
title: self.options.autosuggestHighlight ? v.replace(
new RegExp('(' + value + ')', 'ig'),
'<span class="OxHighlight">$1</span>'
) : v
};
});
// self.selectMenu && self.selectMenu.hideMenu(); // fixme: need event
self.$autosuggestMenu.options({
items: values,
selected: selected
}).showMenu();
} else {
self.$autosuggestMenu.hideMenu();
}
}
function autovalidate(value) {
return {
valid: self.options.autovalidate(value) != null,
message: 'Invalid ' + self.options.name
};
}
function autovalidateCall(blur) {
var blur = blur || false,
value = that.$element.val();
if (value !== '') {
Ox.isFunction(self.options.autovalidate) ?
self.options.autovalidate(value, autovalidateCallback) :
autovalidateCallback(autovalidate(value), blur);
} else {
autovalidateCallback({
blur: blur,
valid: false,
message: 'Empty ' + self.options.name
});
}
}
function autovalidateCallback(data, blur) {
if (data.valid != self.valid) {
self.valid = data.valid;
that.triggerEvent('validate', $.extend(data, {
blur: blur
}));
}
}
function blur() {
if (!self.options.autosuggest || self.$autosuggestMenu.is(':hidden')) {
Ox.print('losing focus...')
that.loseFocus();
self.options.parent.removeClass('OxFocus');
self.options.autocorrect && autocorrectCall(true);
// self.options.autosuggest && self.$autosuggestMenu.hideMenu();
self.options.autovalidate && autovalidateCall(true);
if (self.options.placeholder && that.$element.val() === '') {
that.$element.addClass('OxPlaceholder').val(self.options.placeholder);
}
}
if (self.bindKeyboard) {
$document.unbind('keydown', keypress);
$document.unbind('keypress', keypress);
}
}
function cancel() {
that.$element.blur();
}
function change() {
}
function clear() {
that.$element.val('').focus();
}
function clickMenu(event, data) {
Ox.print('clickMenu', data);
that.$element.val(data.title);
//self.$autosuggestMenu.hideMenu();
self.options.autosuggestSubmit && submit();
}
function cursor(start, end) {
/*
cursor() returns [start, end]
cursor(start) sets start
cursor([start, end]) sets start and end
cursor(start, end) sets start and end
*/
var isArray = Ox.isArray(start);
if (arguments.length == 0) {
return [that.$element[0].selectionStart, that.$element[0].selectionEnd];
} else {
start = isArray ? start[0] : start;
end = isArray ? start[1] : (end ? end : start);
that.$element[0].setSelectionRange(start, end);
}
}
function focus() {
var val = that.$element.val();
that.gainFocus();
self.options.parent.addClass('OxFocus');
if (that.$element.hasClass('OxPlaceholder')) {
that.$element.val('').removeClass('OxPlaceholder');
}
if (self.bindKeyboard) {
// fixme: different in webkit and firefox (?), see keyboard handler, need generic function
$document.keydown(keypress);
$document.keypress(keypress);
Ox.print('calling autosuggest...')
self.options.autosuggest && setTimeout(autosuggestCall, 0); // fixme: why is the timeout needed?
}
}
function keypress(event) {
Ox.print('keyCode', event.keyCode)
if (event.keyCode != 9 && event.keyCode != 13 && event.keyCode != 27) { // fixme: can't 13 and 27 return false?
setTimeout(function() { // fixme: document what this timeout is for
var value = that.$element.val();
if (value != self.options.value) {
self.options.value = value;
self.options.autocomplete && autocompleteCall();
self.options.autocorrect && autocorrectCall();
self.options.autosuggest && autosuggestCall();
self.options.autovalidate && autovalidateCall();
}
}, 25);
}
}
function submit() {
}
self.onChange = function(key, value) {
if (key == 'placeholder') {
that.$element.hasClass('OxPlaceholder') && that.$element.val(value);
} else if (key == 'value') {
if (self.options.placeholder) {
if (value === '') {
that.$element.addClass('OxPlaceholder').val(self.options.placeholder);
} else {
that.$element.removeClass('OxPlaceholder');
}
}
change(); // fixme: keypress too
}
}
return that;
}
Ox.Range_ = function(options, self) {
/*
init
*/
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
animate: false,
arrows: false,
arrowStep: 1,
arrowSymbols: ['previous', 'next'],
max: 100,
min: 0,
orientation: 'horizontal',
step: 1,
size: 128,
thumbSize: 16,
thumbValue: false,
trackImages: [],
trackStep: 0,
value: 0,
valueNames: null
})
.options($.extend(options, {
arrowStep: options.arrowStep ?
options.arrowStep : options.step,
trackImages: $.makeArray(options.trackImages || [])
}))
.addClass('OxRange')
.css({
width: self.options.size + 'px'
});
// fixme: self. ... ?
var trackImages = self.options.trackImages.length,
values = (self.options.max - self.options.min + self.options.step) /
self.options.step;
/*
construct
*/
that.$element
.css({
width: self.options.size + 'px'
});
if (self.options.arrows) {
var $arrowDec = Ox.Button({
style: 'symbol',
type: 'image',
value: self.options.arrowSymbols[0]
})
.addClass('OxArrow')
.mousedown(mousedownArrow)
.click(clickArrowDec)
.appendTo(that.$element);
}
var $track = new Ox.Element()
.addClass('OxTrack')
.mousedown(clickTrack)
.appendTo(that.$element);
if (trackImages) {
var width = parseFloat(screen.width / trackImages),
$image = $('<canvas>')
.attr({
width: width * trackImages,
height: 14
})
.addClass('OxImage')
.appendTo($track.$element),
c = $image[0].getContext('2d');
c.mozImageSmoothingEnabled = false; // we may want to remove this later
$.each(self.options.trackImages, function(i, v) {
var left = 0;
$('<img/>')
.attr({
src: v
})
.load(function() {
c.drawImage(this, left, 0, self.trackImageWidth[i], 14);
});
left += self.trackImageWidth[i];
});
}
var $thumb = Ox.Button({})
.addClass('OxThumb')
.appendTo($track);
Ox.print('----')
if (self.options.arrows) {
var $arrowInc = Ox.Button({
style: 'symbol',
type: 'image',
value: self.options.arrowSymbols[1]
})
.addClass('OxArrow')
.mousedown(mousedownArrow)
.click(clickArrowInc)
.appendTo(that.$element);
}
var rangeWidth, trackWidth, imageWidth, thumbWidth;
setWidth(self.options.size);
/*
private functions
*/
function clickArrowDec() {
that.removeClass('OxActive');
setValue(self.options.value - self.options.arrowStep, 200)
}
function clickArrowInc() {
that.removeClass('OxActive');
setValue(self.options.value + self.options.arrowStep, 200);
}
function clickTrack(e) {
//Ox.Focus.focus();
var left = $track.offset().left,
offset = $(e.target).hasClass('OxThumb') ?
e.clientX - $thumb.offset().left - thumbWidth / 2 - 2 : 0;
function val(e) {
return getVal(e.clientX - left - offset);
}
setValue(val(e), 200);
$window.mousemove(function(e) {
setValue(val(e));
});
$window.one('mouseup', function() {
$window.unbind('mousemove');
});
}
function getPx(val) {
var pxPerVal = (trackWidth - thumbWidth - 2) /
(self.options.max - self.options.min);
return Math.ceil((val - self.options.min) * pxPerVal + 1);
}
function getVal(px) {
var px = trackWidth / values >= 16 ? px : px - 8,
valPerPx = (self.options.max - self.options.min) /
(trackWidth - thumbWidth);
return Ox.limit(self.options.min +
Math.floor(px * valPerPx / self.options.step) * self.options.step,
self.options.min, self.options.max);
}
function mousedownArrow() {
that.addClass('OxActive');
}
function setThumb(animate) {
var animate = typeof animate != 'undefined' ? animate : 0;
$thumb.animate({
marginLeft: (getPx(self.options.value) - 2) + 'px',
width: thumbWidth + 'px'
}, self.options.animate ? animate : 0, function() {
if (self.options.thumbValue) {
$thumb.options({
value: self.options.valueNames ?
self.options.valueNames[self.options.value] :
self.options.value
});
}
});
}
function setValue(val, animate) {
val = Ox.limit(val, self.options.min, self.options.max);
if (val != self.options.value) {
that.options({
value: val
});
setThumb(animate);
that.triggerEvent('change', { value: val });
}
}
function setWidth(width) {
trackWidth = width - self.options.arrows * 32;
thumbWidth = Math.max(trackWidth / values - 2, self.options.thumbSize - 2);
that.$element.css({
width: (width - 2) + 'px'
});
$track.css({
width: (trackWidth - 2) + 'px'
});
if (trackImages) {
$image.css({
width: (trackWidth - 2) + 'px'
});
}
$thumb.css({
width: (thumbWidth - 2) + 'px',
padding: 0
});
setThumb();
}
/*
shared functions
*/
self.onChange = function(key, value) {
}
return that;
};
/*
============================================================================
Lists
============================================================================
*/
Ox.IconList = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
centerSelection: false,
draggable: true,
id: '',
item: function() {},
keys: [],
max: -1,
min: 0,
orientation: 'both',
request: function() {},
selected: [],
size: 128,
sort: [],
})
.options(options || {});
$.extend(self, {
itemHeight: self.options.size * 1.5,
itemWidth: self.options.size
});
that.$element = new Ox.List({
centered: self.options.centered,
construct: constructItem,
draggable: self.options.draggable,
id: self.options.id,
itemHeight: self.itemHeight,
itemWidth: self.itemWidth,
keys: self.options.keys,
orientation: self.options.orientation,
keys: self.options.keys,
max: self.options.max,
min: self.options.min,
request: self.options.request,
selected: self.options.selected,
size: self.options.size,
sort: self.options.sort,
type: 'icon',
unique: self.options.unique
}, $.extend({}, self)) // pass event handler
.addClass('OxIconList Ox' + Ox.toTitleCase(self.options.orientation))
.click(click)
.dblclick(dblclick)
.scroll(scroll);
updateKeys();
function click() {
}
function constructItem(data) {
var data = !$.isEmptyObject(data) ?
self.options.item(data, self.options.sort, self.options.size) :
{height: 8, width: 5},
ratio = data.width / data.height;
return new Ox.IconItem($.extend(data, {
height: Math.round(self.options.size / (ratio <= 1 ? 1 : ratio)),
size: self.options.size,
width: Math.round(self.options.size * (ratio >= 1 ? 1 : ratio))
}));
}
function dblclick() {
}
function scroll() {
}
function updateKeys() {
self.options.keys = Ox.unique($.merge(self.options.keys, [self.options.sort[0].key]));
that.$element.options({
keys: self.options.keys
});
}
self.onChange = function(key, value) {
if (key == 'request') {
that.$element.options(key, value);
} else if (key == 'selected') {
that.$element.options(key, value);
}
}
that.closePreview = function() {
that.$element.closePreview();
};
that.scrollToSelection = function() {
that.$element.scrollToSelection();
};
that.size = function() {
that.$element.size();
}
that.sortList = function(key, operator) {
self.options.sort = [{
key: key,
operator: operator
}];
updateKeys();
that.$element.sortList(key, operator);
}
return that;
};
Ox.IconItem = function(options, self) {
//Ox.print('IconItem', options, self)
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
height: 128,
id: '',
info: '',
size: 128,
title: '',
width: 128,
url: ''
})
.options(options || {})
$.extend(self, {
fontSize: self.options.size == 64 ? 6 : 9,
height: self.options.size * 1.5,
lineLength: self.options.size == 64 ? 15 : 23,
lines: self.options.size == 64 ? 4 : 5,
url: oxui.path + '/png/ox.ui/transparent.png',
width: self.options.size
});
self.title = formatText(self.options.title, self.lines - 1, self.lineLength);
self.info = formatText(self.options.info, 5 - self.title.split('<br/>').length, self.lineLength);
that.css({
width: self.width + 'px',
height: self.height + 'px'
});
that.$icon = $('<div>')
.addClass('OxIcon')
.css({
top: self.options.size == 64 ? -64 : -124,
width: (self.options.size + 4) + 'px',
height: (self.options.size + 4) + 'px'
});
that.$iconImage = $('<img>')
.addClass('OxLoading OxTarget')
.attr({
src: self.url
})
.css({
width: self.options.width + 'px',
height: self.options.height + 'px'
})
.mousedown(mousedown)
.mouseenter(mouseenter)
.mouseleave(mouseleave);
self.options.url && that.$iconImage.one('load', load);
that.$textBox = $('<div>')
.addClass('OxText')
.css({
top: (self.options.size / 2) + 'px',
width: (self.options.size + 4) + 'px',
height: (self.options.size == 64 ? 30 : 58) + 'px'
})
that.$text = $('<div>')
.addClass('OxTarget')
.css({
fontSize: self.fontSize + 'px'
})
.html(
self.title + '<br/><span class="OxInfo">' + self.info + '</span>'
)
.mouseenter(mouseenter)
.mouseleave(mouseleave);
that.$reflection = $('<div>')
.addClass('OxReflection')
.css({
top: self.options.size + 'px',
width: (self.options.size + 4) + 'px',
height: (self.options.size / 2) + 'px'
});
that.$reflectionImage = $('<img>')
.addClass('OxLoading')
.attr({
src: self.url
})
.css({
width: self.options.width + 'px',
height: self.options.height + 'px',
// firefox is 1px off when centering images with odd width and scaleY(-1)
paddingLeft: ($.browser.mozilla && self.options.width % 2 ? 1 : 0) + 'px'
});
that.$gradient = $('<div>')
.css({
//top: (-self.options.size / 2) + 'px',
width: self.options.width + 'px',
height: (self.options.size / 2) + 'px'
});
that.append(
that.$reflection.append(
that.$reflectionImage
).append(
that.$gradient
)
).append(
that.$textBox.append(
that.$text
)
).append(
that.$icon.append(
that.$iconImage
)
);
function formatText(text, maxLines, maxLength) {
var lines = Ox.wordwrap(text, maxLength, '<br/>', true, false).split('<br/>');
return $.map(lines, function(line, i) {
if (i < maxLines - 1) {
return line;
} else if (i == maxLines - 1) {
return lines.length == maxLines ? line : Ox.truncate($.map(lines, function(line, i) {
return i < maxLines - 1 ? null : line;
}).join(' '), maxLength, '...', 'center');
} else {
return null;
}
}).join('<br/>');
}
function load() {
that.$iconImage.attr({
src: self.options.url
})
.one('load', function() {
that.$iconImage.removeClass('OxLoading');
that.$reflectionImage
.attr({
src: self.options.url
})
.removeClass('OxLoading');
});
}
function mousedown(e) {
// fixme: preventDefault keeps image from being draggable in safari - but also keeps the list from getting focus
// e.preventDefault();
}
function mouseenter() {
that.addClass('OxHover');
}
function mouseleave() {
that.removeClass('OxHover');
}
return that;
};
Ox.List = function(options, self) {
var self = self || {},
that = new Ox.Container({}, self)
.defaults({
centered: false,
construct: function() {},
draggable: false,
format: [],
itemHeight: 16,
itemWidth: 16,
keys: [],
max: -1,
min: 0,
orientation: 'vertical',
pageLength: 100,
request: function() {}, // (data, callback), without data returns {items, size etc.}
selected: [],
sort: [],
sortable: false,
type: 'text',
unique: ''
})
.options(options || {})
.scroll(scroll);
that.$content.mousedown(mousedown);
$.extend(self, {
$items: [],
$pages: [],
clickTimeout: 0,
dragTimeout: 0,
format: {},
ids: [],
itemMargin: self.options.type == 'text' ? 0 : 8, // 2 x 4 px margin ... fixme: the 2x should be computed later
keyboardEvents: {
key_end: scrollToFirst,
key_enter: open,
key_home: scrollToLast,
key_pagedown: scrollPageDown,
key_pageup: scrollPageUp,
key_section: preview, // fixme: firefox gets keyCode 0 when pressing space
key_space: preview
},
listMargin: self.options.type == 'text' ? 0 : 8, // 2 x 4 px padding
page: 0,
preview: false,
requests: [],
scrollTimeout: 0,
selected: []
});
if (self.options.max == -1) {
$.extend(self.keyboardEvents, {
key_alt_control_a: invertSelection,
key_control_a: selectAll
});
}
if (self.options.min == 0) {
$.extend(self.keyboardEvents, {
key_control_shift_a: selectNone
});
}
self.keyboardEvents[
'key_' + (self.options.orientation == 'vertical' ? 'up' : 'left')
] = selectPrevious;
self.keyboardEvents[
'key_' + (self.options.orientation == 'vertical' ? 'down' : 'right')
] = selectNext;
if (self.options.max == -1) {
self.keyboardEvents[
'key_' + (self.options.orientation == 'vertical' ? 'shift_up' : 'shift_left')
] = addPreviousToSelection;
self.keyboardEvents[
'key_' + (self.options.orientation == 'vertical' ? 'shift_down' : 'shift_right')
] = addNextToSelection;
}
if (self.options.orientation == 'both') {
$.extend(self.keyboardEvents, {
key_down: selectBelow,
key_up: selectAbove
});
if (self.options.max == -1) {
$.extend(self.keyboardEvents, {
key_shift_down: addBelowToSelection,
key_shift_up: addAboveToSelection
});
}
self.pageLengthByRowLength = [
0, 60, 60, 60, 60, 60, 60, 63, 64, 63, 60, 66, 60, 65, 70, 60, 64, 68, 72, 76, 60
];
}
if (self.options.draggable) {
that.bind({
dragstart: function(e) {
Ox.print('DRAGSTART', e);
}
});
}
updateQuery(self.options.selected);
that.bindEvent(self.keyboardEvents);
$window.resize(that.size);
function addAboveToSelection() {
var pos = getAbove();
if (pos > -1) {
addToSelection(pos);
scrollToPosition(pos);
}
}
function addAllToSelection(pos) {
var arr,
len = self.$items.length;
if (!isSelected(pos)) {
if (self.selected.length == 0) {
addToSelection(pos);
} else {
if (Ox.min(self.selected) < pos) {
var arr = [pos];
for (var i = pos - 1; i >= 0; i--) {
if (isSelected(i)) {
$.each(arr, function(i, v) {
addToSelection(v);
});
break;
}
arr.push(i);
}
}
if (Ox.max(self.selected) > pos) {
var arr = [pos];
for (var i = pos + 1; i < len; i++) {
if (isSelected(i)) {
$.each(arr, function(i, v) {
addToSelection(v);
});
break;
}
arr.push(i);
}
}
}
}
}
function addBelowToSelection() {
var pos = getBelow();
if (pos > -1) {
addToSelection(pos);
scrollToPosition(pos);
}
}
function addNextToSelection() {
var pos = getNext();
if (pos > -1) {
addToSelection(pos);
scrollToPosition(pos);
}
}
function addPreviousToSelection() {
var pos = getPrevious();
if (pos > -1) {
addToSelection(pos);
scrollToPosition(pos);
}
}
function addToSelection(pos) {
if (!isSelected(pos)) {
self.selected.push(pos);
!Ox.isUndefined(self.$items[pos]) &&
self.$items[pos].addClass('OxSelected');
Ox.print('addToSelection')
triggerSelectEvent();
} else {
// allow for 'cursor navigation' if orientation == 'both'
self.selected.splice(self.selected.indexOf(pos), 1);
self.selected.push(pos);
Ox.print('self.selected', self.selected)
}
}
function clear() {
$.each(self.requests, function(i, v) {
Ox.print('Ox.Request.cancel', v);
Ox.Request.cancel(v);
});
$.extend(self, {
$items: [],
$pages: [],
page: 0,
requests: []
});
}
function constructEmptyPage(page) {
Ox.print('cEP', page)
var i, $page = new Ox.ListPage().css(getPageCSS(page));
for (i = 0; i < (page < self.pages - 1 ?
self.pageLength : self.listLength % self.pageLength); i++
) {
// fixme: why does chainging fail here?
new Ox.ListItem({
construct: self.options.construct
}).appendTo($page);
}
Ox.print('cEP done')
return $page;
}
function deselect(pos) {
if (isSelected(pos)) {
self.selected.splice(self.selected.indexOf(pos), 1);
!Ox.isUndefined(self.$items[pos]) &&
self.$items[pos].removeClass('OxSelected');
triggerSelectEvent();
}
}
function dragItem(pos, e) {
var $item = self.$items[pos],
id = self.ids[pos],
startPos = pos,
startY = e.clientY,
stopPos = startPos,
offsets = $.map(self.$items, function($item, pos) {
return (pos - startPos) * 16 - e.offsetY + 8;
});
Ox.print('dragItem', e);
Ox.print(e.offsetY, offsets)
$item.addClass('OxDrag') // fixme: why does the class not work?
.css({
cursor: 'move',
opacity: 0.5
});
$window.mousemove(function(e) {
var clientY = e.clientY - that.offset()['top'],
offset = clientY % 16,
position = Ox.limit(parseInt(clientY / 16), 0, self.$items.length - 1);
if (position < pos) {
stopPos = position + (offset > 8 ? 1 : 0);
} else if (position > pos) {
stopPos = position - (offset <= 8 ? 1 : 0);
}
if (stopPos != pos) {
moveItem(pos, stopPos);
pos = stopPos;
}
});
$window.one('mouseup', function() {
dropItem(id, pos);
$window.unbind('mousemove');
});
}
function moveItem(startPos, stopPos) {
var $item = self.$items[startPos],
insert = startPos < stopPos ? 'insertAfter' : 'insertBefore';
$item.detach()[insert](self.$items[stopPos].$element); // fixme: why do we need .$element here?
Ox.print('moveItem', startPos, stopPos, insert, self.ids);
var $item = self.$items.splice(startPos, 1)[0];
id = self.ids.splice(startPos, 1)[0];
self.$items.splice(stopPos, 0, $item);
self.ids.splice(stopPos, 0, id);
self.$items.forEach(function($item, pos) {
$item.data({position: pos});
});
self.selected = [stopPos];
Ox.print('ids', self.ids);
}
function dropItem(id, pos) {
var $item = self.$items[pos];
$item.removeClass('OxDrag')
.css({
cursor: 'pointer',
opacity: 1
});
that.triggerEvent('sort', {
ids: self.ids
});
/*
Ox.print('dropColumn', id, pos)
var startPos = getColumnPositionById(id),
stopPos = pos,
$title = that.$titles.splice(startPos, 1)[0],
column = self.visibleColumns.splice(startPos, 1)[0],
width = self.columnWidths.splice(startPos, 1)[0];
self.visibleColumns.splice(stopPos, 0, column);
self.columnWidths.splice(stopPos, 0, width);
that.$head.$content.empty();
constructHead();
Ox.print('s.vC', self.visibleColumns)
$('.OxColumn' + Ox.toTitleCase(id)).css({
opacity: 1
});
that.$titles[stopPos].removeClass('OxDrag').css({
cursor: 'pointer'
});
that.$body.clearCache();
*/
}
function emptyFirstPage() {
Ox.print('emptyFirstPage', self.$pages);
self.$pages[0] && self.$pages[0].find('.OxEmpty').remove();
}
function fillFirstPage() {
if (self.$pages[0]) {
var height = getHeight(),
lastItemHeight = height % self.options.itemHeight || self.options.itemHeight,
visibleItems = Math.ceil(height / self.options.itemHeight);
if (self.listLength < visibleItems) {
$.each(Ox.range(self.listLength, visibleItems), function(i, v) {
var $item = new Ox.ListItem({
construct: self.options.construct,
});
$item.addClass('OxEmpty').removeClass('OxTarget');
if (v == visibleItems - 1) {
$item.$element.css({
height: lastItemHeight + 'px',
overflowY: 'hidden'
});
}
$item.appendTo(self.$pages[0]);
});
}
}
}
function findCell(e) {
var $element = $(e.target);
while (!$element.hasClass('OxCell') && !$element.hasClass('OxPage') && !$element.is('body')) {
$element = $element.parent();
}
return $element.hasClass('OxCell') ? $element : null;
}
function findItem(e) {
Ox.print('---- findItem', e.target)
var $element = $(e.target),
$item = null;
while (!$element.hasClass('OxTarget') && !$element.hasClass('OxPage') && !$element.is('body')) {
$element = $element.parent();
}
if ($element.hasClass('OxTarget')) {
while (!$element.hasClass('OxItem') && !$element.hasClass('OxPage') && !$element.is('body')) {
$element = $element.parent();
}
if ($element.hasClass('OxItem')) {
$item = $element;
}
}
return $item;
}
function getAbove() {
var pos = -1;
if (self.selected.length) {
pos = self.selected[self.selected.length - 1] - self.rowLength
if (pos < 0) {
pos = -1;
}
}
return pos;
}
function getBelow() {
var pos = -1;
if (self.selected.length) {
pos = self.selected[self.selected.length - 1] + self.rowLength;
if (pos >= self.$items.length) {
pos = -1;
}
}
return pos;
}
function getHeight() {
return that.height() - (that.$content.width() > that.width() ? oxui.scrollbarSize : 0);
}
function getListSize() {
return Math.ceil(self.listLength *
(self.options[self.options.orientation == 'horizontal' ?
'itemWidth' : 'itemHeight'] + self.itemMargin) / self.rowLength);
}
function getNext() {
var pos = -1;
if (self.selected.length) {
pos = (self.options.orientation == 'both' ?
self.selected[self.selected.length - 1] :
Ox.max(self.selected)) + 1;
if (pos == self.$items.length) {
pos = -1;
}
}
return pos;
}
function getPage() {
return Math.max(
Math.floor(self.options.orientation == 'horizontal' ?
(that.scrollLeft() - self.listMargin / 2) / self.pageWidth :
(that.scrollTop() - self.listMargin / 2) / self.pageHeight
), 0);
}
function getPageByPosition(pos) {
return parseInt(pos / self.options.pageLength);
}
function getPageCSS(page) {
return self.options.orientation == 'horizontal' ? {
left: (page * self.pageWidth + self.listMargin / 2) + 'px',
top: (self.listMargin / 2) + 'px',
width: (page < self.pages - 1 ? self.pageWidth :
self.listLength % self.pageLength *
(self.options.itemWidth + self.itemMargin)) + 'px'
} : {
top: (page * self.pageHeight + self.listMargin / 2) + 'px',
width: self.pageWidth + 'px'
}
}
function getPageHeight() {
return Math.ceil(self.pageLength * (self.options.itemHeight + self.itemMargin) / self.rowLength);
}
function getPosition() {
// if orientation is both, this returns the
// element position at the current scroll position
return parseInt(
that.scrollTop() / (self.options.itemHeight + self.itemMargin)
) * self.rowLength;
}
function getPositions(ids) {
ids = ids || getSelectedIds();
// fixme: optimize: send non-selected ids if more than half of the items are selected
if (ids.length /*&& ids.length < self.listLength*/) {
Ox.print('-------- request', {
ids: ids,
sort: self.options.sort
});
self.requests.push(self.options.request({
ids: ids,
sort: self.options.sort
}, getPositionsCallback));
} else {
getPositionsCallback();
}
}
function getPositionsCallback(result) {
Ox.print('getPositionsCallback', result)
var pos = 0;
if (result) {
$.extend(self, {
ids: {},
selected: []
});
$.each(result.data.positions, function(id, pos) {
Ox.print('id', id, 'pos', pos)
self.selected.push(pos);
});
pos = Ox.min(self.selected);
self.page = getPageByPosition(pos);
}
// that.scrollTop(0);
that.$content.empty();
Ox.print('self.selected', self.selected, 'self.page', self.page);
loadPages(self.page, function() {
scrollToPosition(pos, true);
});
}
function getPrevious() {
var pos = -1;
if (self.selected.length) {
pos = (self.options.orientation == 'both' ?
self.selected[self.selected.length - 1] :
Ox.min(self.selected)) - 1;
}
return pos;
}
function getRow(pos) {
return Math.floor(pos / self.rowLength);
}
function getRowLength() {
return self.options.orientation == 'both' ?
Math.floor((getWidth() - self.listMargin) /
(self.options.itemWidth + self.itemMargin)) : 1
}
function getSelectedIds() {
// fixme: is carring self.ids around the best way?
return $.map(self.selected, function(pos) {
return self.ids[pos];
});
}
function getWidth() {
return that.width() - (that.$content.height() > that.height() ? oxui.scrollbarSize : 0);
}
function invertSelection() {
$.each(Ox.range(self.listLength), function(i, v) {
toggleSelection(v);
});
}
function isSelected(pos) {
return self.selected.indexOf(pos) > -1;
}
function loadPage(page, callback) {
if (page < 0 || page >= self.pages) {
!Ox.isUndefined(callback) && callback();
return;
}
Ox.print('loadPage', page);
var keys = $.inArray('id', self.options.keys) > -1 ? self.options.keys :
$.merge(self.options.keys, ['id']),
offset = page * self.pageLength,
range = [offset, offset + (page < self.pages - 1 ?
self.pageLength : self.listLength % self.pageLength)];
if (Ox.isUndefined(self.$pages[page])) { // fixme: unload will have made this undefined already
self.$pages[page] = constructEmptyPage(page);
self.options.type == 'text' && page == 0 && fillFirstPage();
self.$pages[page].appendTo(that.$content);
self.requests.push(self.options.request({
keys: keys,
range: range,
sort: self.options.sort
}, function(result) {
var $emptyPage = $.extend({}, self.$pages[page]);
self.$pages[page] = new Ox.ListPage().css(getPageCSS(page));
$.each(result.data.items, function(i, v) {
var pos = offset + i;
self.$items[pos] = new Ox.ListItem({
construct: self.options.construct,
data: v,
draggable: self.options.draggable,
format: self.options.format,
id: v[self.options.unique],
position: pos
});
self.ids[pos] = v[self.options.unique]; // fixme: why not use self.$items[pos].options('id')?
if (isSelected(pos)) {
Ox.print('pos', pos, 'isSelected')
self.$items[pos].addClass('OxSelected');
}
self.$items[pos].appendTo(self.$pages[page]);
});
self.options.type == 'text' && page == 0 && fillFirstPage();
$emptyPage.remove();
self.$pages[page].appendTo(that.$content);
!Ox.isUndefined(callback) && callback();
}));
} else {
Ox.print('loading a page from cache, this should probably not happen -----------')
self.$pages[page].appendTo(that.$content);
}
}
function loadPages(page, callback) {
var counter = 0,
fn = function() {
Ox.print('---- self.$pages', self.$pages)
++counter == 2 && !Ox.isUndefined(callback) && callback();
};
// fixme: find out which option is better
loadPage(page, function() {
//loadPage(page - 1, fn);
//loadPage(page + 1, fn);
});
loadPage(page - 1, fn);
loadPage(page + 1, fn);
}
function mousedown(e) {
Ox.print('mousedown')
var $cell,
$item = findItem(e),
pos,
editTimeout = false;
selectTimeout = false;
that.gainFocus();
if ($item) {
if (!self.clickTimeout) {
// click
pos = $item.data('position');
if (e.metaKey) {
if (!isSelected(pos) && (self.options.max == -1 || self.options.max > self.selected.length)) {
addToSelection(pos);
} else if (isSelected(pos) && self.options.min < self.selected.length) {
deselect(pos);
}
} else if (e.shiftKey) {
if (self.options.max == -1) {
addAllToSelection(pos);
}
} else if (!isSelected(pos)) {
select(pos);
} else if (self.selected.length > 1) {
// this could be the first click
// of a double click on multiple items
selectTimeout = true;
} else if (self.options.type == 'text') {
$cell = findCell(e);
if ($cell && $cell.hasClass('OxEditable') && !$cell.hasClass('OxEdit')) {
if (self.options.sortable) {
editTimeout = true;
} else {
triggerEditEvent($item, $cell);
}
}
}
self.clickTimeout = setTimeout(function() {
self.clickTimeout = 0;
if (selectTimeout) {
select(pos);
}
}, 250);
if (self.options.sortable) {
self.dragTimeout = setTimeout(function() {
if (self.dragTimeout) {
dragItem(pos, e);
self.dragTimeout = 0;
}
}, 250);
$window.one('mouseup', function(e) {
if (self.dragTimeout) {
clearTimeout(self.dragTimeout);
self.dragTimeout = 0;
if (editTimeout) {
triggerEditEvent($item, $cell);
}
}
});
}
} else {
// dblclick
clearTimeout(self.clickTimeout);
self.clickTimeout = 0;
open();
}
} else if (self.options.min == 0) {
selectNone();
}
}
function open() {
var ids = getSelectedIds();
ids.length && that.triggerEvent('open', {
ids: ids
});
}
function preview() {
var ids = getSelectedIds();
if (ids.length) {
self.preview = !self.preview;
if (self.preview) {
that.triggerEvent('openpreview', {
ids: getSelectedIds()
});
} else {
that.triggerEvent('closepreview');
}
}
}
function scroll() {
var page = self.page;
self.scrollTimeout && clearTimeout(self.scrollTimeout);
self.scrollTimeout = setTimeout(function() {
self.scrollTimeout = 0;
self.page = getPage();
if (self.page != page) {
Ox.print('page', page, '-->', self.page);
}
if (self.page == page - 1) {
unloadPage(self.page + 2);
loadPage(self.page - 1);
} else if (self.page == page + 1) {
unloadPage(self.page - 2);
loadPage(self.page + 1);
} else if (self.page == page - 2) {
unloadPage(self.page + 3);
unloadPage(self.page + 2);
loadPage(self.page);
loadPage(self.page - 1);
} else if (self.page == page + 2) {
unloadPage(self.page - 3);
unloadPage(self.page - 2);
loadPage(self.page);
loadPage(self.page + 1);
} else if (self.page != page) {
unloadPages(page);
loadPages(self.page);
}
}, 250);
}
function scrollPageDown() {
that.scrollBy(getHeight());
}
function scrollPageUp() {
that.scrollBy(-getHeight());
}
function scrollTo(value) {
that.animate(self.options.orientation == 'horizontal' ? {
scrollLeft: (self.listSize * value) + 'px'
} : {
scrollTop: (self.listSize * value) + 'px'
}, 0);
}
function scrollToFirst() {
that[self.options.orientation == 'horizontal' ? 'scrollLeft' : 'scrollTop'](0);
}
function scrollToLast() {
that[self.options.orientation == 'horizontal' ? 'scrollLeft' : 'scrollTop'](self.listSize);
}
function scrollToPosition(pos, leftOrTopAlign) {
var itemHeight = self.options.itemHeight + self.itemMargin,
itemWidth = self.options.itemWidth + self.itemMargin,
positions = [],
scroll,
size;
if (self.options.orientation == 'horizontal') {
if (self.options.centered) {
that.animate({
scrollLeft: (self.listMargin / 2 + (pos + 0.5) * itemWidth - that.width() / 2) + 'px'
}, 0);
} else {
positions[0] = pos * itemWidth + self.listMargin / 2;
positions[1] = positions[0] + itemWidth + self.itemMargin / 2;
scroll = that.scrollLeft();
size = getWidth();
if (positions[0] < scroll || leftOrTopAlign) {
that.animate({
scrollLeft: positions[0] + 'px'
}, 0);
} else if (positions[1] > scroll + size) {
that.animate({
scrollLeft: (positions[1] - size) + 'px'
}, 0);
}
}
} else {
positions[0] = (self.options.orientation == 'vertical' ? pos : getRow(pos)) * itemHeight;
positions[1] = positions[0] + itemHeight + (self.options.orientation == 'vertical' ? 0 : self.itemMargin);
scroll = that.scrollTop();
size = getHeight();
if (positions[0] < scroll || leftOrTopAlign) {
that.animate({
scrollTop: positions[0] + 'px'
}, 0);
} else if (positions[1] > scroll + size) {
that.animate({
scrollTop: (positions[1] - size) + 'px'
}, 0);
}
}
}
function select(pos) {
if (!isSelected(pos) || self.selected.length > 1) {
selectNone();
addToSelection(pos);
self.options.centered && scrollToPosition(pos);
}
}
function selectAbove() {
var pos = getAbove();
if (pos > -1) {
select(pos);
scrollToPosition(pos);
}
}
function selectAll() {
$.each(Ox.range(self.listLength), function(i, v) {
Ox.print('adding', v);
addToSelection(v);
});
}
function selectBelow() {
var pos = getBelow();
if (pos > -1) {
select(pos);
scrollToPosition(pos);
}
}
function selectNext() {
var pos = getNext();
if (pos > -1) {
select(pos);
scrollToPosition(pos);
}
}
function selectNone() {
$.each(self.$items, function(i, v) {
deselect(i);
});
}
function selectPrevious() {
var pos = getPrevious();
if (pos > -1) {
select(pos);
scrollToPosition(pos);
}
}
function selectQuery(str) {
$.each(self.$items, function(i, v) {
if (Ox.toLatin(v.title).toUpperCase().indexOf(str) == 0) {
select(i);
scrollToPosition(i);
return false;
}
});
}
function setSelected(ids) {
if (ids.length == 0) {
// fixme: can't use selectNone here,
// since it'd trigger a select event
$.each(self.$items, function(pos) {
if (isSelected(pos)) {
self.selected.splice(self.selected.indexOf(pos), 1);
!Ox.isUndefined(self.$items[pos]) &&
self.$items[pos].removeClass('OxSelected');
}
});
}
// fixme: implement the rest of this
/*
self.selected.forEach(function(pos) {
if (value.indexOf(self.ids[pos]) == -1) {
deselect(pos);
}
});
value.forEach(function(id) {
if (self.ids.indexOf(id) > -1) {
addToSelection(pos)
} else {
}
});
*/
}
function toggleSelection(pos) {
if (!isSelected(pos)) {
addToSelection(pos);
} else {
deselect(pos);
}
}
function triggerEditEvent($item, $cell) {
that.triggerEvent('edit', {
id: $item.data('id'),
key: $cell.attr('class').split('OxColumn')[1].split(' ')[0].toLowerCase()
});
}
function triggerSelectEvent() {
var ids = self.options.selected = getSelectedIds();
setTimeout(function() {
var ids_ = getSelectedIds();
Ox.print('ids', ids, 'ids after 100 msec', ids_)
if (ids.length == ids_.length && (ids.length == 0 || ids[0] == ids_[0])) {
that.triggerEvent('select', {
ids: ids
});
self.preview && that.triggerEvent('openpreview', {
ids: ids
});
} else {
Ox.print('select event not triggered after timeout');
}
}, 100);
}
function unloadPage(page) {
if (page < 0 || page >= self.pages) {
return;
}
Ox.print('unloadPage', page)
Ox.print('self.$pages', self.$pages)
Ox.print('page not undefined', !Ox.isUndefined(self.$pages[page]))
if (!Ox.isUndefined(self.$pages[page])) {
self.$pages[page].remove();
delete self.$pages[page];
}
}
function unloadPages(page) {
unloadPage(page);
unloadPage(page - 1);
unloadPage(page + 1)
}
function updateQuery(ids) {
// ids are the selcected ids
// (in case list is loaded with selection)
clear();
self.requests.push(self.options.request({}, function(result) {
var keys = {};
that.triggerEvent('load', result.data);
self.rowLength = getRowLength();
self.pageLength = self.options.orientation == 'both' ?
self.pageLengthByRowLength[self.rowLength] :
self.options.pageLength;
$.extend(self, {
listLength: result.data.items,
pages: Math.ceil(result.data.items / self.pageLength),
pageWidth: self.options.orientation == 'vertical' ? 0 :
(self.options.itemWidth + self.itemMargin) *
(self.options.orientation == 'horizontal' ?
self.pageLength : self.rowLength),
pageHeight: self.options.orientation == 'horizontal' ? 0 :
Math.ceil(self.pageLength * (self.options.itemHeight +
self.itemMargin) / self.rowLength)
});
self.listSize = getListSize();
that.$content.css(
self.options.orientation == 'horizontal' ? 'width' : 'height',
self.listSize + 'px'
);
getPositions(ids);
}));
}
function updatePages(pos, scroll) {
// only used if orientation is both
clear();
self.pageLength = self.pageLengthByRowLength[self.rowLength]
$.extend(self, {
listSize: getListSize(),
pages: Math.ceil(self.listLength / self.pageLength),
pageWidth: (self.options.itemWidth + self.itemMargin) * self.rowLength,
pageHeight: getPageHeight()
});
that.$content.css({
height: self.listSize + 'px'
});
self.page = getPageByPosition(pos);
//that.scrollTop(0);
that.$content.empty();
loadPages(self.page, function() {
scrollTo(scroll);
});
}
function updateSort() {
if (self.listLength > 1) {
clear();
getPositions();
}
}
self.onChange = function(key, value) {
Ox.print('list onChange', key, value);
if (key == 'request') {
updateQuery();
} else if (key == 'selected') {
setSelected(value);
}
};
that.clearCache = function() { // fixme: was used by TextList resizeColumn, now probably no longer necessary
self.$pages = [];
return that;
};
that.closePreview = function() {
self.preview = false;
};
that.reload = function() {
Ox.print('---------------- list reload, page', self.page)
var page = self.page;
clear();
self.page = page
that.$content.empty();
loadPages(self.page);
return that;
};
that.scrollToSelection = function() {
self.selected.length && scrollToPosition(self.selected[0]);
};
that.size = function() { // fixme: not a good function name
if (self.options.orientation == 'both') {
var rowLength = getRowLength(),
pageLength = self.pageLengthByRowLength[rowLength],
pos = getPosition(),
scroll = that.scrollTop() / self.listSize;
if (pageLength != self.pageLength) {
self.pageLength = pageLength;
self.rowLength = rowLength;
updatePages(pos, scroll);
} else if (rowLength != self.rowLength) {
self.rowLength = rowLength;
self.pageWidth = (self.options.itemWidth + self.itemMargin) * self.rowLength; // fixme: make function
self.listSize = getListSize();
self.pageHeight = getPageHeight();
$.each(self.$pages, function(i, $page) {
!Ox.isUndefined($page) && $page.css({
width: self.pageWidth + 'px',
top: (i * self.pageHeight + self.listMargin / 2) + 'px'
});
});
that.$content.css({
height: self.listSize + 'px'
});
Ox.print('scrolling to', scroll)
scrollTo(scroll);
}
} else if (self.options.type == 'text') {
Ox.print('that.size, type==text')
emptyFirstPage();
fillFirstPage();
}
}
that.sortList = function(key, operator) {
if (key != self.options.sort[0].key || operator != self.options.sort[0].operator) {
self.options.sort[0] = {
key: key,
operator: operator
}
that.triggerEvent('sort', self.options.sort[0]);
updateSort();
}
return that;
}
return that;
};
Ox.ListItem = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
construct: function() {},
data: {},
draggable: false,
format: [],
id: '',
position: 0
})
.options(options || {});
// make a clone, so we don't format cached data
self.data = $.extend({}, self.options.data);
$.each(self.data, function(k, v) {
var format = self.options.format[k];
if (Ox.isArray(v)) {
self.data[k] = v.join(', ');
} else if (format) {
self.data[k] = Ox.isObject(format) ?
Ox['format' + Ox.toTitleCase(format.type)]
.apply(this, $.merge([v], format.args)) :
format(v);
}
});
that.$element = self.options.construct(self.data)
.addClass('OxItem')
.attr({
draggable: self.options.draggable
//id: self.options.id.replace(/./g, '_') // fixme: dots are not the only problem
})
.data({
id: self.options.id,
position: self.options.position
});
return that;
};
Ox.ListPage = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.addClass('OxPage');
return that;
};
Ox.TextList = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
columns: [],
columnsMovable: false,
columnsRemovable: false,
columnsResizable: false,
columnsVisible: false,
columnWidth: [40, 800],
id: '',
max: -1,
min: 0,
pageLength: 100,
selected: [],
request: function() {}, // {sort, range, keys, callback}
sort: []
})
.options(options || {})
.addClass('OxTextList');
$.each(self.options.columns, function(i, v) { // fixme: can this go into a generic ox.js function?
// fixme: and can't these just remain undefined?
if (Ox.isUndefined(v.editable)) {
v.editable = false;
}
if (Ox.isUndefined(v.unique)) {
v.unique = false;
}
if (Ox.isUndefined(v.visible)) {
v.visible = false;
}
if (v.unique) {
self.unique = v.id;
}
});
$.extend(self, {
columnPositions: [],
itemHeight: 16,
page: 0,
pageLength: 100,
scrollLeft: 0,
selectedColumn: getColumnIndexById(self.options.sort[0].key),
visibleColumns: $.map(self.options.columns, function(v, i) {
return v.visible ? v : null;
})
});
$.extend(self, {
columnWidths: $.map(self.visibleColumns, function(v, i) {
return v.width;
}),
pageHeight: self.options.pageLength * self.itemHeight
});
self.format = {};
self.options.columns.forEach(function(v, i) {
if (v.format) {
self.format[v.id] = v.format;
}
});
// Head
if (self.options.columnsVisible) {
that.$bar = new Ox.Bar({
orientation: 'horizontal',
size: 16
}).appendTo(that);
that.$head = new Ox.Container()
.addClass('OxHead')
.appendTo(that.$bar);
that.$head.$content.addClass('OxTitles');
constructHead();
if (self.options.columnsRemovable) {
that.$select = new Ox.Select({
id: self.options.id + 'SelectColumns',
items: $.map(self.options.columns, function(v, i) {
return {
checked: v.visible,
disabled: v.removable === false,
id: v.id,
title: v.title
}
}),
max: -1,
min: 1,
type: 'image'
})
.bindEvent('change', changeColumns)
.appendTo(that.$bar.$element);
}
}
// Body
that.$body = new Ox.List({
construct: constructItem,
id: self.options.id,
itemHeight: 16,
itemWidth: getItemWidth(),
format: self.format,
keys: $.map(self.visibleColumns, function(v) {
return v.id;
}),
max: self.options.max,
min: self.options.min,
pageLength: self.options.pageLength,
orientation: 'vertical',
request: self.options.request,
selected: self.options.selected,
sort: self.options.sort,
sortable: self.options.sortable,
type: 'text',
unique: self.unique
}, $.extend({}, self)) // pass event handler
.addClass('OxBody')
.css({
top: (self.options.columnsVisible ? 16 : 0) + 'px',
overflowY: (self.options.columnsVisible ? 'scroll' : 'auto')
})
.scroll(function() {
var scrollLeft = $(this).scrollLeft();
if (scrollLeft != self.scrollLeft) {
self.scrollLeft = scrollLeft;
that.$head && that.$head.scrollLeft(scrollLeft);
}
})
.bindEvent({
edit: function(event, data) {
editCell(data.id, data.key, data.item, data.cell);
},
select: function(event, data) {
self.options.selected = data.ids;
}
})
.appendTo(that);
that.$body.$content.css({
width: getItemWidth() + 'px'
});
Ox.print('s.vC', self.visibleColumns)
function addColumn(id) {
Ox.print('addColumn', id);
var column,
index = 0;
$.each(self.options.columns, function(i, v) {
if (v.visible) {
index++;
} else if (v.id == id) {
column = v;
return false;
}
});
column.visible = true;
self.visibleColumns.splice(index, 0, column);
self.columnWidths.splice(index, 0, column.width);
that.$head.$content.empty();
constructHead();
that.$body.options({
keys: $.map(self.visibleColumns, function(v, i) {
return v.id;
})
});
that.$body.reload();
}
function changeColumns(event, data) {
var add,
ids = [];
$.each(data.selected, function(i, column) {
var index = getColumnIndexById(column.id);
if (!self.options.columns[index].visible) {
addColumn(column.id);
add = true;
return false;
}
ids.push(column.id);
});
if (!add) {
$.each(self.visibleColumns, function(i, column) {
if (ids.indexOf(column.id) == -1) {
removeColumn(column.id);
return false;
}
});
}
}
function clickColumn(id) {
Ox.print('clickColumn', id);
var i = getColumnIndexById(id),
isSelected = self.options.sort[0].key == self.options.columns[i].id;
that.sortList(
self.options.columns[i].id, isSelected ?
(self.options.sort[0].operator === '' ? '-' : '') :
self.options.columns[i].operator
);
}
function constructHead() {
var offset = 0;
that.$titles = [];
self.columnOffsets = [];
$.each(self.visibleColumns, function(i, v) {
var $order, $resize, $left, $center, $right, timeout = 0;
offset += self.columnWidths[i];
self.columnOffsets[i] = offset - self.columnWidths[i] / 2;
that.$titles[i] = $('<div>')
.addClass('OxTitle OxColumn' + Ox.toTitleCase(v.id))
.css({
width: (self.columnWidths[i] - 9) + 'px',
textAlign: v.align
})
.html(v.title)
.mousedown(function(e) {
timeout = setTimeout(function() {
self.options.columnsMovable && dragColumn(v.id, e);
timeout = 0;
}, 250);
})
.mouseup(function() {
if (timeout) {
clearTimeout(timeout);
timeout = 0;
clickColumn(v.id);
}
})
.appendTo(that.$head.$content.$element);
$order = $('<div>')
.addClass('OxOrder')
.html(oxui.symbols['triangle_' + (
v.operator === '' ? 'up' : 'down'
)])
.click(function() {
$(this).prev().trigger('click')
})
.appendTo(that.$head.$content.$element);
$resize = $('<div>')
.addClass('OxResize')
.appendTo(that.$head.$content.$element);
if (self.options.columnsResizable) {
$resize.addClass('OxResizable')
.mousedown(function(e) {
var startWidth = self.columnWidths[i],
startX = e.clientX;
$window.mousemove(function(e) {
var x = e.clientX,
width = Ox.limit(
startWidth - startX + x,
self.options.columnWidth[0],
self.options.columnWidth[1]
);
resizeColumn(v.id, width);
});
$window.one('mouseup', function() {
$window.unbind('mousemove');
});
})
.dblclick(function() {
resizeColumn(v.id, v.width);
});
}
$left = $('<div>').addClass('OxLeft').appendTo($resize);
$center = $('<div>').addClass('OxCenter').appendTo($resize);
$right = $('<div>').addClass('OxRight').appendTo($resize);
});
that.$head.$content.css({
width: (Ox.sum(self.columnWidths) + 2) + 'px'
});
Ox.print('s.sC', self.selectedColumn)
Ox.print('s.cO', self.columnOffsets)
if (getColumnPositionById(self.options.columns[self.selectedColumn].id) > -1) { // fixme: save in var
toggleSelected(self.options.columns[self.selectedColumn].id);
that.$titles[getColumnPositionById(self.options.columns[self.selectedColumn].id)].css({
width: (self.options.columns[self.selectedColumn].width - 25) + 'px'
});
}
}
function constructItem(data) {
var $item = $('<div>')
.addClass('OxTarget')
.css({
width: getItemWidth() + 'px'
});
$.each(self.visibleColumns, function(i, v) {
var $cell = $('<div>')
.addClass(
'OxCell OxColumn' + Ox.toTitleCase(v.id) +
(v.editable ? ' OxEditable' : '')
)
.css({
width: (self.columnWidths[i] - (self.options.columnsVisible ? 9 : 8)) + 'px',
borderRightWidth: (self.options.columnsVisible ? 1 : 0) + 'px',
textAlign: v.align
})
.html(v.id in data ? data[v.id] : '')
.appendTo($item);
});
return $item;
}
function dragColumn(id, e) {
var startX = e.clientX,
startPos = getColumnPositionById(id),
pos = startPos,
stopPos = startPos,
offsets = $.map(self.visibleColumns, function(v, i) {
return self.columnOffsets[i] - self.columnOffsets[startPos]
});
$('.OxColumn' + Ox.toTitleCase(id)).css({
opacity: 0.25
});
that.$titles[startPos].addClass('OxDrag').css({ // fixme: why does the class not work?
cursor: 'move'
});
Ox.print('offsets', offsets)
$window.mousemove(function(e) {
var d = e.clientX - startX;
$.each(offsets, function(i, v) {
if (d < 0 && d < v) {
stopPos = i;
return false;
} else if (d > 0 && d > v) {
stopPos = i;
}
});
if (stopPos != pos) {
pos = stopPos;
moveColumn(id, pos);
}
});
$window.one('mouseup', function() {
dropColumn(id, pos);
$window.unbind('mousemove');
});
}
function dropColumn(id, pos) {
Ox.print('dropColumn', id, pos)
var startPos = getColumnPositionById(id),
stopPos = pos,
$title = that.$titles.splice(startPos, 1)[0],
column = self.visibleColumns.splice(startPos, 1)[0],
width = self.columnWidths.splice(startPos, 1)[0];
self.visibleColumns.splice(stopPos, 0, column);
self.columnWidths.splice(stopPos, 0, width);
that.$head.$content.empty();
constructHead();
Ox.print('s.vC', self.visibleColumns)
$('.OxColumn' + Ox.toTitleCase(id)).css({
opacity: 1
});
that.$titles[stopPos].removeClass('OxDrag').css({
cursor: 'pointer'
});
that.$body.clearCache();
}
function editCell(id, key) {
Ox.print('editCell')
var $item = getItem(id),
$cell = getCell(id, key),
$input,
html = $cell.html(),
index = getColumnIndexById(key),
column = self.options.columns[index],
width = column.width;
$cell.empty()
.addClass('OxEdit')
.css({
width: width + 'px'
});
$input = Ox.Input({
autovalidate: column.input.autovalidate,
style: 'square',
value: html,
width: width
})
.bind({
mousedown: function(e) {
// keep mousedown from reaching list
e.stopPropagation();
}
})
.bindEvent({
blur: submit,
})
.appendTo($cell)
.focus();
function submit() {
var value = $input.value();
//$input.loseFocus().remove();
// fixme: leaky, inputs remain in focus stack
$cell.removeClass('OxEdit')
.css({
width: (width - 8) + 'px'
})
.html(value)
that.triggerEvent('submit', {
id: id,
key: key,
value: value
});
}
}
function getCell(id, key) {
var $item = getItem(id);
return $($item.find('.OxCell.OxColumn' + Ox.toTitleCase(key))[0]);
}
function getColumnIndexById(id) {
return Ox.getPositionById(self.options.columns, id);
}
function getColumnPositionById(id) {
return Ox.getPositionById(self.visibleColumns, id);
}
function getItem(id) {
var $item = null;
$.each(that.find('.OxItem'), function(i, v) {
$v = $(v);
if ($v.data('id') == id) {
$item = $v;
return false;
}
})
return $item;
}
function getItemWidth() {
return Math.max(Ox.sum(self.columnWidths), that.$element.width() - oxui.scrollbarSize);
//return Ox.sum(self.columnWidths)
}
function moveColumn(id, pos) {
// fixme: column head should be one element, not three
Ox.print('moveColumn', id, pos)
var startPos = getColumnPositionById(id),
stopPos = pos,
startClassName = '.OxColumn' + Ox.toTitleCase(id),
stopClassName = '.OxColumn' + Ox.toTitleCase(self.visibleColumns[stopPos].id),
insert = startPos < stopPos ? 'insertAfter' : 'insertBefore'
$column = $('.OxTitle' + startClassName),
$order = $column.next(),
$resize = $order.next();
Ox.print(startClassName, insert, stopClassName)
$column.detach()[insert](insert == 'insertAfter' ? $('.OxTitle' + stopClassName).next().next() : $('.OxTitle' + stopClassName));
$order.detach().insertAfter($column);
$resize.detach().insertAfter($order);
$.each(that.$body.find('.OxItem'), function(i, v) {
var $v = $(v);
$v.children(startClassName).detach()[insert]($v.children(stopClassName));
});
var column = self.visibleColumns.splice(startPos, 1)[0],
width = self.columnWidths.splice(startPos, 1)[0];
self.visibleColumns.splice(stopPos, 0, column);
self.columnWidths.splice(stopPos, 0, width);
}
function removeColumn(id) {
Ox.print('removeColumn', id);
var className = '.OxColumn' + Ox.toTitleCase(id),
index = getColumnIndexById(id),
itemWidth,
position = getColumnPositionById(id),
$column = $('.OxTitle' + className),
$order = $column.next(),
$resize = $order.next();
self.options.columns[index].visible = false;
self.visibleColumns.splice(position, 1);
self.columnWidths.splice(position, 1);
that.$head.$content.empty();
constructHead();
itemWidth = getItemWidth();
$.each(that.$body.find('.OxItem'), function(i, v) {
var $v = $(v);
$v.children(className).remove();
$v.css({
width: itemWidth + 'px'
});
});
that.$body.$content.css({
width: itemWidth + 'px'
});
that.$body.options({
keys: $.map(self.visibleColumns, function(v, i) {
return v.id;
})
});
//that.$body.clearCache();
}
function resize() {
}
function resizeColumn(id, width) {
var i = getColumnIndexById(id),
pos = getColumnPositionById(id);
self.columnWidths[pos] = width;
that.$head.$content.css({
width: (Ox.sum(self.columnWidths) + 2) + 'px'
});
that.$titles[pos].css({
width: (width - 9 - (i == self.selectedColumn ? 16 : 0)) + 'px'
});
$('.OxCell.OxColumn' + Ox.toTitleCase(self.options.columns[i].id)).css({
width: (width - 9) + 'px'
});
setWidth();
//that.$body.clearCache();
}
function setWidth() {
var width = getItemWidth();
that.$body.$content.find('.OxItem').css({ // fixme: can we avoid this lookup?
width: width + 'px'
});
that.$body.$content.css({
width: width + 'px' // fixme: check if scrollbar visible, and listen to resize/toggle event
});
}
function toggleSelected(id) {
var pos = getColumnPositionById(id);
if (pos > -1) {
updateOrder(id);
pos > 0 && that.$titles[pos].prev().children().eq(2).toggleClass('OxSelected');
that.$titles[pos].toggleClass('OxSelected');
that.$titles[pos].next().toggleClass('OxSelected');
that.$titles[pos].next().next().children().eq(0).toggleClass('OxSelected');
that.$titles[pos].css({
width: (
that.$titles[pos].width() + (that.$titles[pos].hasClass('OxSelected') ? -16 : 16)
) + 'px'
});
}
}
function updateOrder(id) {
var pos = getColumnPositionById(id);
Ox.print(id, pos)
that.$titles[pos].next().html(oxui.symbols[
'triangle_' + (self.options.sort[0].operator === '' ? 'up' : 'down')
]);
}
self.onChange = function(key, value) {
if (key == 'request') {
//alert('request set!!')
that.$body.options(key, value);
} else if (key == 'selected') {
that.$body.options(key, value);
}
};
that.closePreview = function() {
that.$body.closePreview();
};
that.resizeColumn = function(id, width) {
resizeColumn(id, width);
return that;
}
that.size = function() {
setWidth();
that.$body.size();
}
that.sortList = function(key, operator) {
var isSelected = key == self.options.sort[0].key;
self.options.sort = [
{
key: key,
operator: operator
}
];
if (self.options.columnsVisible) {
if (isSelected) {
updateOrder(self.options.columns[self.selectedColumn].id);
} else {
toggleSelected(self.options.columns[self.selectedColumn].id);
self.selectedColumn = getColumnIndexById(key);
toggleSelected(self.options.columns[self.selectedColumn].id);
}
}
that.$body.sortList(self.options.sort[0].key, self.options.sort[0].operator);
return that;
};
that.value = function(id, key, value) {
if (Ox.isUndefined(value)) {
} else {
return that;
}
}
return that;
};
/*
============================================================================
Maps
============================================================================
*/
Ox.Map = function(options, self) {
var self = self || {}
that = new Ox.Element('div', self)
.defaults({
clickable: false,
places: [],
type: 'satellite'
})
.options(options || {})
.bindEvent({
key_up: function() {
pan(0, -1);
},
key_down: function() {
pan(0, 1);
},
key_left: function() {
pan(-1, 0);
},
key_right: function() {
pan(1, 0);
},
key_0: reset,
key_minus: function() {
zoom(-1);
},
key_equal: function() {
zoom(1);
},
key_enter: focusOnPlace,
key_shift_enter: zoomToPlace,
key_escape: deselectPlace
});
if (Ox.isUndefined(window.google)) {
window.google = function() {};
$.getScript('http://maps.google.com/maps/api/js?callback=google&sensor=false', function() {
var interval = setInterval(function() {
if (!Ox.isUndefined(google.maps)) {
clearInterval(interval);
initMap();
}
}, 100);
});
} else {
initMap();
}
function initMap() {
$.extend(self, {
geocoder: new google.maps.Geocoder(),
selected: -1
});
if (Ox.isObject(self.options.places[0])) {
$.each(self.options.places, function(i, place) {
place.bounds = getBounds(),
place.center = place.bounds.getCenter();
$.extend(place, {
marker: new Marker(place),
polygon: new Polygon(place)
});
self.bounds = i == 0 ? getBounds() : self.bounds.union(place.bounds);
self.options.places[i] = place;
function getBounds() {
return new google.maps.LatLngBounds(
new google.maps.LatLng(place.points.southwest[0], place.points.southwest[1]),
new google.maps.LatLng(place.points.northeast[0], place.points.northeast[1])
);
}
});
loadMap();
} else {
self.counter = 0;
$.each(self.options.places, function(i, place) {
getLocationByName(place, function(place) {
Ox.print(self.counter, i, place);
self.options.places[i] = place;
if (self.counter++ == self.options.places.length - 1) {
loadMap();
}
});
});
}
}
function loadMap() {
self.center = self.bounds ? self.bounds.getCenter() : new google.maps.LatLng(0, 0);
self.zoom = 1;
$.extend(self, {
map: new google.maps.Map(that.$element[0], {
center: self.center,
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId[self.options.type.toUpperCase()],
zoom: self.zoom
})
});
if (self.bounds) {
self.map.fitBounds(self.bounds);
// self.center = self.map.getCenter();
self.zoom = self.map.getZoom();
}
google.maps.event.addListener(self.map, 'click', click);
google.maps.event.addListener(self.map, 'zoom_changed', zoomChanged);
$.each(self.options.places, function(i, place) {
place.marker.add();
});
resize();
that.gainFocus();
that.triggerEvent('loaded');
}
function canContain(outerBounds, innerBounds) {
var outerSpan = outerBounds.toSpan(),
innerSpan = innerBounds.toSpan();
return outerSpan.lat() > innerSpan.lat() &&
outerSpan.lng() > innerSpan.lng();
}
function click(event) {
Ox.print('event', event);
that.gainFocus();
if (self.options.clickable) {
getLocationByLatLng(event.latLng, self.map.getBounds(), function(location) {
self.marker && self.marker.remove();
self.polygon && self.polygon.remove();
if (location) {
self.marker = location.marker.add();
self.polygon && self.polygon.remove();
self.polygon = location.polygon.add();
that.triggerEvent('select', location);
}
});
}
}
function deselectPlace() {
}
function focusOnPlace() {
if (self.selected > -1) {
self.map.panTo(self.options.places[self.selected].center);
}
}
function getLocationByLatLng(latlng, bounds, callback) {
Ox.print('ll b', latlng, bounds)
var callback = arguments.length == 3 ? callback : bounds,
bounds = arguments.length == 3 ? bounds : null;
self.geocoder.geocode({
latLng: latlng
}, function(results, status) {
Ox.print('results', results)
var length = results.length;
if (status == google.maps.GeocoderStatus.OK) {
if (status != google.maps.GeocoderStatus.ZERO_RESULTS) {
if (bounds) {
$.each(results.reverse(), function(i, result) {
if (
i == length - 1 ||
canContain(bounds, result.geometry.bounds || result.geometry.viewport)
) {
callback(new Location(results[i]));
return false;
}
});
} else {
callback(new Location(results[0]));
}
} else {
callback(null);
}
} else {
Ox.print('geocode failed:', status);
callback(null);
}
});
}
function getLocationByName(name, callback) {
self.geocoder.geocode({
address: name
}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (status != google.maps.GeocoderStatus.ZERO_RESULTS) {
callback(new Location(results[0]))
} else {
callback(null);
}
} else {
Ox.print('geocode failed:', status);
callback(null);
}
});
}
function getPositionByName(name) {
var position = -1;
$.each(self.options.places, function(i, place) {
if (place.name == name) {
position = i;
return false;
}
});
return position;
}
function pan(x, y) {
self.map.panBy(x * 256, y * 256);
};
function resize() {
google.maps.event.trigger(self.map, 'resize');
}
function reset() {
Ox.print(self.map.getZoom(), self.zoom);
self.map.getZoom() == self.zoom ?
self.map.panTo(self.center) :
self.map.fitBounds(self.bounds);
}
function zoomChanged() {
that.triggerEvent('zoom', {
value: self.map.getZoom()
});
}
function zoom(z) {
self.map.setZoom(self.map.getZoom() + z);
}
function zoomToPlace() {
if (self.selected > -1) {
self.map.fitBounds(self.options.places[self.selected].bounds);
}
}
function Location(geodata) {
Ox.print('geodata', geodata);
var bounds = geodata.geometry.bounds || geodata.geometry.viewport,
center = bounds.getCenter(),
location = {
bounds: bounds,
center: center,
geoname: geodata.formatted_address,
points: {
'center': [center.lat(), center.lng()],
'southwest': [bounds.getSouthWest().lat(), bounds.getSouthWest().lng()],
'northeast': [bounds.getNorthEast().lat(), bounds.getNorthEast().lng()]
},
name: geodata.formatted_address.split(', ')[0],
size: 0,
type: geodata.address_components[0].types.join(', ')
};
return $.extend(location, {
marker: new Marker(location),
polygon: new Polygon(location)
});
}
function Marker(place) {
Ox.print(place.center)
var listeners = {},
marker = new google.maps.Marker({
icon: icon('red'),
position: place.center,
title: place.name
}),
selected = false;
function click() {
Ox.print('click', self.selected, selected)
selected = !selected;
selected && self.selected > -1 && self.options.places[self.selected].marker.deselect();
self.selected = selected ? getPositionByName(place.name) : -1;
marker.setOptions({
icon: icon(selected ? 'blue' : 'red')
});
place.polygon[selected ? 'add' : 'remove']();
that.triggerEvent(selected ? 'select' : 'deselect', place);
}
function deselect() {
}
function dblclick() {
Ox.print('dblclick', place.bounds)
self.map.fitBounds(place.bounds);
}
function select() {
}
function icon(color) {
return oxui.path + 'png/ox.ui/marker' + Ox.toTitleCase(color) + '.png'
}
return {
add: function() {
Ox.print('add Marker')
marker.setMap(self.map);
listeners = {
click: google.maps.event.addListener(marker, 'click', click),
dblclick: google.maps.event.addListener(marker, 'dblclick', dblclick),
};
return this;
},
deselect: function() {
self.selected = -1;
selected = false;
marker.setOptions({
icon: icon('red')
});
place.polygon.remove();
},
remove: function() {
marker.setMap(null);
$.each(listeners, function(i, listener) {
google.maps.event.removeListener(listener);
});
return this;
},
select: function() {
if (self.selected > -1) {
self.options.places[self.selected].marker.deselect();
}
self.selected = getPositionByName(place.name);
selected = true;
marker.setOptions({
icon: icon('blue')
});
place.polygon.add();
}
};
}
function Polygon(location) {
var listeners = {},
paths = [
new google.maps.LatLng(location.points.southwest[0], location.points.southwest[1]),
new google.maps.LatLng(location.points.northeast[0], location.points.southwest[1]),
new google.maps.LatLng(location.points.northeast[0], location.points.northeast[1]),
new google.maps.LatLng(location.points.southwest[0], location.points.northeast[1]),
new google.maps.LatLng(location.points.southwest[0], location.points.southwest[1])
],
polygon = new google.maps.Polygon({
paths: paths
}),
selected = false;
setOptions();
function click() {
selected = !selected;
setOptions();
}
function setOptions() {
var color = selected ? '#8080FF' : '#FFFFFF';
polygon.setOptions({
clickable: true,
fillColor: color,
fillOpacity: selected ? 0.1 : 0,
strokeColor: color,
strokeOpacity: 1,
strokeWeight: 2
});
}
return {
add: function() {
polygon.setMap(self.map);
listeners.click = google.maps.event.addListener(polygon, 'click', click);
return this;
},
deselect: function() {
selected = false;
setOptions();
},
remove: function() {
polygon.setMap(null);
google.maps.event.removeListener(listeners.click);
return this;
},
select: function() {
selected = true;
setOptions();
}
};
}
function Rectangle(area) { // fixme: not used
var latlng = {
sw: new google.maps.LatLng(area[0][0], area[0][1]),
ne: new google.maps.LatLng(area[1][0], area[1][1])
},
bounds = new google.maps.LatLngBounds(latlng.sw, latlng.ne),
lat = {},
lng = {};
latlng.mc = bounds.getCenter();
$.each(latlng, function(k, v) {
lat[k] = v.lat();
lng[k] = v.lng();
});
$.extend(latlng, {
sc: new google.maps.LatLng(lat.sw, lng.mc),
se: new google.maps.LatLng(lat.sw, lng.ne),
mw: new google.maps.LatLng(lat.mc, lng.sw),
me: new google.maps.LatLng(lat.mc, lng.ne),
nw: new google.maps.LatLng(lat.ne, lng.sw),
nc: new google.maps.LatLng(lat.ne, lng.mc),
});
return {
area: area,
bounds: bounds,
canContain: function(rectangle) {
var outerSpan = this.bounds.toSpan(),
innerSpan = rectangle.bounds.toSpan();
return outerSpan.lat() > innerSpan.lat() &&
outerSpan.lng() > innerSpan.lng();
},
center: latlng.mc,
contains: function(rectangle) {
return this.bounds.contains(rectangle.bounds.getSouthWest()) &&
this.bounds.contains(rectangle.bounds.getNorthEast());
},
latlng: latlng
};
}
self.onChange = function(key, value) {
if (key == 'type') {
}
};
that.find = function(name, callback) {
getLocationByName(name, function(place) {
if (place) {
//self.marker = location.marker.add();
self.polygon && self.polygon.remove();
self.polygon = place.polygon.add();
self.bounds = place.bounds;
self.map.fitBounds(self.bounds);
}
callback(place);
});
};
that.triggerResize = function() {
Ox.print('---- triggerResize');
var center = self.map.getCenter();
google.maps.event.trigger(self.map, 'resize');
self.map.setCenter(center);
}
that.zoom = function(value) {
self.map.setZoom(value);
};
return that;
};
/**
options
height image height (px)
places array of either names (''), points ([0, 0]),
or objects ({name, point, highlight})
type map type ('hybrid', 'roadmap', 'satellite', 'terrain')
width image width (px)
*/
Ox.MapImage = function(options, self) {
var self = self || {},
that = new Ox.Element('img', self)
.defaults({
height: 360,
markerColorHighlight: 'yellow',
markerColorNormal: 'blue',
places: [],
type: 'satellite',
width: 640
})
.options(options || {})
$.extend(self, {
markers: {
highlight: [],
normal: []
},
src: 'http://maps.google.com/maps/api/staticmap?sensor=false' +
'&size=' + self.options.width + 'x' + self.options.height +
'&maptype=' + self.options.type
});
if (self.options.places.length) {
$.each(self.options.places, function(i, place) {
if (Ox.isString(place)) {
self.markers.normal.push(place);
} else if (Ox.isArray(place)) {
self.markers.normal.push(place.join(','));
} else {
self.markers[place.highlight ? 'highlight' : 'normal']
.push('point' in place ? place.point.join(',') : place.name)
}
});
$.each(self.markers, function(k, markers) {
if (markers.length) {
self.src += '&markers=icon:' + 'http://dev.pan.do:8000' + oxui.path + 'png/ox.ui/marker' +
Ox.toTitleCase(self.options['markerColor' + Ox.toTitleCase(k)]) + '.png|' +
markers.join('|')
}
});
} else {
self.src += '&center=0,0&zoom=2'
}
that.attr({
src: self.src
});
self.onChange = function(key, value) {
};
return that;
};
/*
============================================================================
Menus
============================================================================
*/
/**
*/
Ox.MainMenu = function(options, self) {
var self = self || {},
that = new Ox.Bar({}, self)
.defaults({
extras: [],
menus: [],
size: 'medium'
})
.options(options || {})
.addClass('OxMainMenu Ox' + Ox.toTitleCase(self.options.size)) // fixme: bar should accept small/medium/large ... like toolbar
.click(click)
.mousemove(mousemove);
self.focused = false;
self.selected = -1;
that.menus = [];
that.titles = [];
that.layer = $('<div>').addClass('OxLayer');
$.each(self.options.menus, function(position, menu) {
that.titles[position] = $('<div>')
.addClass('OxTitle')
.html(menu.title)
.data('position', position)
.appendTo(that.$element);
that.menus[position] = new Ox.Menu($.extend(menu, {
element: that.titles[position],
mainmenu: that,
size: self.options.size
}))
.bindEvent({
hide: onHideMenu
});
});
if (self.options.extras.length) {
that.extras = $('<div>')
.addClass('OxExtras')
.appendTo(that.$element);
$.each(self.options.extras, function(position, extra) {
extra.css({
float: 'left' // fixme: need class!
}).appendTo(that.extras);
});
}
function click(event) {
var $target = $(event.target),
position = typeof $target.data('position') != 'undefined' ?
$target.data('position') : -1;
clickTitle(position);
}
function clickTitle(position) {
var selected = self.selected;
if (self.selected > -1) {
that.menus[self.selected].hideMenu();
}
if (position > -1) {
if (position != selected) {
self.focused = true;
self.selected = position;
that.titles[self.selected].addClass('OxSelected');
that.menus[self.selected].showMenu();
}
}
}
function mousemove(event) {
var $target = $(event.target),
focused,
position = typeof $target.data('position') != 'undefined' ?
$target.data('position') : -1;
if (self.focused && position != self.selected) {
if (position > -1) {
clickTitle(position);
} else {
focused = self.focused;
that.menus[self.selected].hideMenu();
self.focused = focused;
}
}
}
function onHideMenu() {
if (self.selected > -1) {
that.titles[self.selected].removeClass('OxSelected');
self.selected = -1;
}
self.focused = false;
}
self.onChange = function(key, value) {
};
that.addMenuAfter = function(id) {
};
that.addMenuBefore = function(id) {
};
that.checkItem = function(id) {
var ids = id.split('_'),
itemId = ids.pop(),
menuId = ids.join('_');
that.getMenu(menuId).checkItem(itemId);
};
that.disableItem = function(id) {
that.getItem(id).options({
disabled: true
});
};
that.enableItem = function(id) {
that.getItem(id).options({
disabled: false
});
};
that.getItem = function(id) {
var ids = id.split('_'),
item;
if (ids.length == 1) {
$.each(that.menus, function(i, menu) {
item = menu.getItem(id);
return !item;
});
} else {
item = that.getMenu(ids.shift()).getItem(ids.join('_'));
}
Ox.print('getItem', id, item);
return item;
};
that.getMenu = function(id) {
var ids = id.split('_'),
menu;
if (ids.length == 1) {
$.each(that.menus, function(i, v) {
if (v.options('id') == id) {
menu = v;
return false;
}
});
} else {
menu = that.getMenu(ids.shift()).getSubmenu(ids.join('_'));
}
Ox.print('getMenu', id, menu);
return menu;
};
that.removeMenu = function() {
};
that.selectNextMenu = function() {
if (self.selected < self.options.menus.length - 1) {
clickTitle(self.selected + 1);
}
};
that.selectPreviousMenu = function() {
if (self.selected) {
clickTitle(self.selected - 1);
}
};
that.uncheckItem = function(id) {
that.getItem(id).options({
checked: false
});
};
return that;
};
/**
options
element the element the menu is attached to
id the menu id
items array of menu items
mainmenu the main menu this menu is part of, if any
offset offset of the menu, in px
parent the supermenu, if any
selected the position of the selected item
side open to 'bottom' or 'right'
size 'large', 'medium' or 'small'
events:
change_groupId {id, value} checked item of a group has changed
click_itemId item not belonging to a group was clicked
click_menuId {id, value} item not belonging to a group was clicked
deselect_menuId {id, value} item was deselected not needed, not implemented
hide_menuId menu was hidden
select_menuId {id, value} item was selected
*/
Ox.Menu = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
element: null,
id: '',
items: [],
mainmenu: null,
offset: {
left: 0,
top: 0
},
parent: null,
selected: -1,
side: 'bottom',
size: 'medium',
})
.options(options || {})
.addClass(
'OxMenu Ox' + Ox.toTitleCase(self.options.side) +
' Ox' + Ox.toTitleCase(self.options.size)
)
.click(click)
.mouseenter(mouseenter)
.mouseleave(mouseleave)
.mousemove(mousemove)
.bindEvent({
key_up: selectPreviousItem,
key_down: selectNextItem,
key_left: selectSupermenu,
key_right: selectSubmenu,
key_escape: hideMenu,
key_enter: clickSelectedItem
}),
itemHeight = self.options.size == 'small' ? 12 : (self.options.size == 'medium' ? 16 : 20),
// menuHeight,
scrollSpeed = 1,
$item; // fixme: used?
// fixme: attach all private vars to self
// construct
that.items = [];
that.submenus = {};
that.$scrollbars = [];
that.$top = $('<div>')
.addClass('OxTop')
.appendTo(that.$element);
that.$scrollbars.up = constructScrollbar('up')
.appendTo(that.$element);
that.$container = $('<div>')
.addClass('OxContainer')
.appendTo(that.$element);
that.$content = $('<table>')
.addClass('OxContent')
.appendTo(that.$container);
constructItems(self.options.items);
that.$scrollbars.down = constructScrollbar('down')
.appendTo(that.$element);
that.$bottom = $('<div>')
.addClass('OxBottom')
.appendTo(that.$element);
that.$layer = $('<div>')
.addClass(self.options.mainmenu ? 'OxMainMenuLayer' : 'OxMenuLayer')
.click(click);
function click(event) {
var item,
position,
$target = $(event.target),
$parent = $target.parent();
// necessary for highlight
if ($parent.is('.OxCell')) {
$target = $parent;
$parent = $target.parent();
}
if ($target.is('.OxCell')) {
position = $parent.data('position');
item = that.items[position];
if (!item.options('disabled')) {
clickItem(position);
} else {
that.hideMenu();
}
} else {
that.hideMenu();
}
}
function clickItem(position) {
var item = that.items[position],
toggled;
that.hideMenu();
if (!item.options('items').length) {
if (that.options('parent')) {
that.options('parent').hideMenu().triggerEvent('click');
}
if (item.options('checked') !== null) {
if (item.options('group')) {
Ox.print('has group', item.options('group'))
toggled = self.optionGroups[item.options('group')].toggle(position);
Ox.print('toggled', toggled)
if (toggled.length) {
$.each(toggled, function(i, pos) {
that.items[pos].toggleChecked();
});
Ox.print('--triggering change event--');
(self.options.mainmenu || that).triggerEvent('change', {
id: item.options('group'),
checked: $.map(self.optionGroups[item.options('group')].checked(), function(v, i) {
return {
id: that.items[v].options('id'),
title: Ox.stripTags(that.items[v].options('title')[0])
};
})
});
}
} else {
item.toggleChecked();
(self.options.mainmenu || that).triggerEvent('change', {
checked: item.options('checked'),
id: item.options('id'),
title: Ox.stripTags(item.options('title')[0])
});
}
} else {
(self.options.mainmenu || that).triggerEvent('click', {
id: item.options('id'),
title: Ox.stripTags(item.options('title')[0])
});
}
if (item.options('title').length == 2) {
item.toggleTitle();
}
}
}
function clickSelectedItem() {
// called on key.enter
if (self.options.selected > -1) {
clickItem(self.options.selected);
} else {
that.hideMenu();
}
}
function constructItems(items) {
that.$content.empty();
scrollMenuUp();
self.optionGroups = {};
$.each(items, function(i, item) {
if (item.group) {
items[i] = $.map(item.items, function(v, i) {
return $.extend(v, {
group: item.group
});
});
self.optionGroups[item.group] = new Ox.OptionGroup(
items[i],
'min' in item ? item.min : 1,
'max' in item ? item.max : 1
);
}
});
items = Ox.flatten(items);
that.items = [];
$.each(items, function(i, item) {
var position;
if (item.id) {
that.items.push(new Ox.MenuItem($.extend(item, {
menu: that,
position: position = that.items.length
})).data('position', position).appendTo(that.$content)); // fixme: jquery bug when passing {position: position}? does not return the object?;
if (item.items) {
that.submenus[item.id] = new Ox.Menu({
element: that.items[position],
id: Ox.toCamelCase(self.options.id + '/' + item.id),
items: item.items,
mainmenu: self.options.mainmenu,
offset: {
left: 0,
top: -4
},
parent: that,
side: 'right',
size: self.options.size,
});
}
} else {
that.$content.append(constructSpace());
that.$content.append(constructLine());
that.$content.append(constructSpace());
}
});
if (!that.is(':hidden')) {
that.hideMenu();
that.showMenu();
}
}
function constructLine() {
return $('<tr>').append(
$('<td>', {
'class': 'OxLine',
colspan: 5
})
);
}
function constructScrollbar(direction) {
var interval,
speed = direction == 'up' ? -1 : 1;
return $('<div/>', {
'class': 'OxScrollbar Ox' + Ox.toTitleCase(direction),
html: oxui.symbols['triangle_' + direction],
click: function() { // fixme: do we need to listen to click event?
return false;
},
mousedown: function() {
scrollSpeed = 2;
return false;
},
mouseenter: function() {
var $otherScrollbar = that.$scrollbars[direction == 'up' ? 'down' : 'up'];
$(this).addClass('OxSelected');
if ($otherScrollbar.is(':hidden')) {
$otherScrollbar.show();
that.$container.height(that.$container.height() - itemHeight);
if (direction == 'down') {
that.$content.css({
top: -itemHeight + 'px'
});
}
}
scrollMenu(speed);
interval = setInterval(function() {
scrollMenu(speed);
}, 100);
},
mouseleave: function() {
$(this).removeClass('OxSelected');
clearInterval(interval);
},
mouseup: function() {
scrollSpeed = 1;
return false;
}
});
}
function constructSpace() {
return $('<tr>').append(
$('<td>', {
'class': 'OxSpace',
colspan: 5
})
);
}
function getElement(id) {
// fixme: needed?
return $('#' + Ox.toCamelCase(options.id + '/' + id));
}
function getItemPositionById(id) {
var position;
$.each(that.items, function(i, v) {
if (v.options('id') == id) {
position = i;
return false;
}
});
return position;
}
function hideMenu() {
// called on key_escape
that.hideMenu();
}
function isFirstEnabledItem() {
var ret = true;
$.each(that.items, function(i, item) {
if (i < self.options.selected && !item.options('disabled')) {
return ret = false;
}
});
return ret;
}
function isLastEnabledItem() {
var ret = true;
$.each(that.items, function(i, item) {
if (i > self.options.selected && !item.options('disabled')) {
return ret = false;
}
});
return ret;
}
function mouseenter() {
that.gainFocus();
}
function mouseleave() {
if (self.options.selected > -1 && !that.items[self.options.selected].options('items').length) {
selectItem(-1);
}
}
function mousemove(event) {
var item,
position,
$target = $(event.target);
$parent = $target.parent();
if ($parent.is('.OxCell')) {
$target = $parent;
$parent = $target.parent();
}
if ($target.is('.OxCell')) {
position = $parent.data('position');
item = that.items[position];
if (!item.options('disabled') && position != self.options.selected) {
selectItem(position);
}
} else {
mouseleave();
}
}
function scrollMenu(speed) {
var containerHeight = that.$container.height(),
contentHeight = that.$content.height(),
top = parseInt(that.$content.css('top')) || 0,
min = containerHeight - contentHeight + itemHeight,
max = 0;
top += speed * scrollSpeed * -itemHeight;
if (top <= min) {
top = min;
that.$scrollbars.down.hide().trigger('mouseleave');
that.$container.height(containerHeight + itemHeight);
that.items[that.items.length - 1].trigger('mouseover');
} else if (top >= max - itemHeight) {
top = max;
that.$scrollbars.up.hide().trigger('mouseleave');
that.$container.height(containerHeight + itemHeight);
that.items[0].trigger('mouseover');
}
that.$content.css({
top: top + 'px'
});
}
function scrollMenuUp() {
if (that.$scrollbars.up.is(':visible')) {
that.$content.css({
top: '0px'
});
that.$scrollbars.up.hide();
if (that.$scrollbars.down.is(':hidden')) {
that.$scrollbars.down.show();
} else {
that.$container.height(that.$container.height() + itemHeight);
}
}
}
function selectItem(position) {
var item;
if (self.options.selected > -1) {
//Ox.print('s.o.s', self.options.selected, that.items)
item = that.items[self.options.selected]
item.removeClass('OxSelected');
/* disabled
that.triggerEvent('deselect', {
id: item.options('id'),
title: Ox.stripTags(item.options('title')[0])
});
*/
}
if (position > -1) {
item = that.items[position];
$.each(that.submenus, function(id, submenu) {
if (!submenu.is(':hidden')) {
submenu.hideMenu();
return false;
}
});
item.options('items').length && that.submenus[item.options('id')].showMenu(); // fixme: do we want to switch to this style?
item.addClass('OxSelected');
/* disabled
that.triggerEvent('select', {
id: item.options('id'),
title: Ox.stripTags(item.options('title')[0])
});
*/
}
self.options.selected = position;
}
function selectNextItem() {
var offset,
selected = self.options.selected;
Ox.print('sNI', selected)
if (!isLastEnabledItem()) {
if (selected == -1) {
scrollMenuUp();
} else {
that.items[selected].removeClass('OxSelected');
}
do {
selected++;
} while (that.items[selected].options('disabled'))
selectItem(selected);
offset = that.items[selected].offset().top + itemHeight -
that.$container.offset().top - that.$container.height();
if (offset > 0) {
if (that.$scrollbars.up.is(':hidden')) {
that.$scrollbars.up.show();
that.$container.height(that.$container.height() - itemHeight);
offset += itemHeight;
}
if (selected == that.items.length - 1) {
that.$scrollbars.down.hide();
that.$container.height(that.$container.height() + itemHeight);
} else {
that.$content.css({
top: ((parseInt(that.$content.css('top')) || 0) - offset) + 'px'
});
}
}
}
}
function selectPreviousItem() {
var offset,
selected = self.options.selected;
Ox.print('sPI', selected)
if (selected > - 1) {
if (!isFirstEnabledItem()) {
that.items[selected].removeClass('OxSelected');
do {
selected--;
} while (that.items[selected].options('disabled'))
selectItem(selected);
}
offset = that.items[selected].offset().top - that.$container.offset().top;
if (offset < 0) {
if (that.$scrollbars.down.is(':hidden')) {
that.$scrollbars.down.show();
that.$container.height(that.$container.height() - itemHeight);
}
if (selected == 0) {
that.$scrollbars.up.hide();
that.$container.height(that.$container.height() + itemHeight);
}
that.$content.css({
top: ((parseInt(that.$content.css('top')) || 0) - offset) + 'px'
});
}
}
}
function selectSubmenu() {
Ox.print('selectSubmenu', self.options.selected)
if (self.options.selected > -1) {
var submenu = that.submenus[that.items[self.options.selected].options('id')];
Ox.print('submenu', submenu, that.submenus);
if (submenu && submenu.hasEnabledItems()) {
submenu.gainFocus();
submenu.selectFirstItem();
} else if (self.options.mainmenu) {
self.options.mainmenu.selectNextMenu();
}
} else if (self.options.mainmenu) {
self.options.mainmenu.selectNextMenu();
}
}
function selectSupermenu() {
Ox.print('selectSupermenu', self.options.selected)
if (self.options.parent) {
self.options.selected > -1 && that.items[self.options.selected].trigger('mouseleave');
scrollMenuUp();
self.options.parent.gainFocus();
} else if (self.options.mainmenu) {
self.options.mainmenu.selectPreviousMenu();
}
}
self.onChange = function(key, value) {
if (key == 'items') {
constructItems(value);
} else if (key == 'selected') {
that.$content.find('.OxSelected').removeClass('OxSelected');
selectItem(value);
}
}
that.addItem = function(item, position) {
};
that.addItemAfter = function(item, id) {
};
that.addItemBefore = function(item, id) {
};
that.checkItem = function(id) {
var item = that.getItem(id);
if (item.options('group')) {
var position = getItemPositionById(id),
toggled = self.optionGroups[item.options('group')].toggle(position);
if (toggled.length) {
$.each(toggled, function(i, pos) {
that.items[pos].toggleChecked();
});
}
} else {
item.options({
checked: true
});
}
};
that.getItem = function(id) {
//Ox.print('id', id)
var ids = id.split('_'),
item;
if (ids.length == 1) {
$.each(that.items, function(i, v) {
if (v.options('id') == id) {
item = v;
return false;
}
});
if (!item) {
$.each(that.submenus, function(k, submenu) {
item = submenu.getItem(id);
return !item;
});
}
} else {
item = that.submenus[ids.shift()].getItem(ids.join('_'));
}
return item;
};
that.getSubmenu = function(id) {
var ids = id.split('_'),
submenu;
if (ids.length == 1) {
submenu = that.submenus[id];
} else {
submenu = that.submenus[ids.shift()].getSubmenu(ids.join('_'));
}
Ox.print('getSubmenu', id, submenu);
return submenu;
}
that.hasEnabledItems = function() {
var ret = false;
$.each(that.items, function(i, item) {
if (!item.options('disabled')) {
return ret = true;
}
});
return ret;
};
that.hideMenu = function() {
if (that.is(':hidden')) {
return;
}
$.each(that.submenus, function(i, submenu) {
if (submenu.is(':visible')) {
submenu.hideMenu();
return false;
}
});
selectItem(-1);
scrollMenuUp();
that.$scrollbars.up.is(':visible') && that.$scrollbars.up.hide();
that.$scrollbars.down.is(':visible') && that.$scrollbars.down.hide();
if (self.options.parent) {
//self.options.element.removeClass('OxSelected');
self.options.parent.options({
selected: -1
});
}
that.hide()
.loseFocus()
.triggerEvent('hide');
that.$layer.hide();
return that;
};
that.removeItem = function() {
};
that.selectFirstItem = function() {
selectNextItem();
};
that.showMenu = function() {
if (!that.is(':hidden')) {
return;
}
if (!self.options.parent && !that.$layer.parent().length) {
that.$layer.appendTo($body);
}
that.parent().length == 0 && that.appendTo($body);
that.css({
left: '-1000px',
top: '-1000px',
}).show();
var offset = self.options.element.offset(),
width = self.options.element.outerWidth(),
height = self.options.element.outerHeight(),
left = Ox.limit(
offset.left + self.options.offset.left + (self.options.side == 'bottom' ? 0 : width),
0, $window.width() - that.width()
),
top = offset.top + self.options.offset.top + (self.options.side == 'bottom' ? height : 0),
menuHeight = that.$content.outerHeight(); // fixme: why is outerHeight 0 when hidden?
menuMaxHeight = Math.floor($window.height() - top - 16);
if (self.options.parent) {
if (menuHeight > menuMaxHeight) {
top = Ox.limit(top - menuHeight + menuMaxHeight, self.options.parent.offset().top, top);
menuMaxHeight = Math.floor($window.height() - top - 16);
}
}
that.css({
left: left + 'px',
top: top + 'px'
});
if (menuHeight > menuMaxHeight) {
that.$container.height(menuMaxHeight - itemHeight - 8); // margin
that.$scrollbars.down.show();
} else {
that.$container.height(menuHeight);
}
if (!self.options.parent) {
that.gainFocus();
}
that.$layer.show();
return that;
//that.triggerEvent('show');
};
that.toggleMenu = function() {
that.is(':hidden') ? that.showMenu() : that.hideMenu();
};
return that;
};
Ox.MenuItem = function(options, self) {
var self = self || {},
that = new Ox.Element('tr', self)
.defaults({
bind: [], // fixme: what's this?
checked: null,
disabled: false,
group: '',
icon: '',
id: '',
items: [],
keyboard: '',
menu: null, // fixme: is passing the menu to 100s of menu items really memory-neutral?
position: 0,
title: [],
})
.options($.extend(options, {
foo: Ox.print(options, self.defaults.keyboard),
keyboard: parseKeyboard(options.keyboard || self.defaults.keyboard),
title: Ox.makeArray(options.title || self.defaults.title)
}))
.addClass('OxItem' + (self.options.disabled ? ' OxDisabled' : ''))
.attr({
id: Ox.toCamelCase(self.options.menu.options('id') + '/' + self.options.id)
})
.data('group', self.options.group); // fixme: why?
if (self.options.group && self.options.checked === null) {
self.options.checked = false;
}
// construct
that.append(
that.$status = $('<td>', {
'class': 'OxCell OxStatus',
html: self.options.checked ? oxui.symbols.check : ''
})
)
.append(
that.$icon = $('<td>', {
'class': 'OxCell OxIcon'
})
.append(self.options.icon ?
$('<img>', {
src: self.options.icon
}) : null
)
)
.append(
that.$title = $('<td>', {
'class': 'OxCell OxTitle',
html: self.options.title[0]
})
)
.append(
$('<td>', {
'class': 'OxCell OxModifiers',
html: $.map(self.options.keyboard.modifiers, function(modifier) {
return oxui.symbols[modifier];
}).join('')
})
)
.append(
$('<td>', {
'class': 'OxCell Ox' + (self.options.items.length ? 'Submenu' : 'Key'),
html: self.options.items.length ? oxui.symbols.triangle_right :
oxui.symbols[self.options.keyboard.key] ||
self.options.keyboard.key.toUpperCase()
})
);
function parseKeyboard(str) {
var modifiers = str.split(' '),
key = modifiers.pop();
return {
modifiers: modifiers,
key: key
};
}
self.onChange = function(key, value) {
if (key == 'checked') {
that.$status.html(value ? oxui.symbols.check : '')
} else if (key == 'disabled') {
that.toggleClass('OxDisabled'); // fixme: this will only work if onChange is only invoked on actual change
} else if (key == 'title') {
self.options.title = Ox.makeArray(value);
that.$title.html(self.options.title[0]);
}
}
that.toggle = function() {
// toggle id and title
};
that.toggleChecked = function() {
that.options({
checked: !self.options.checked
});
};
that.toggleDisabled = function() {
};
that.toggleTitle = function() {
Ox.print('s.o.t', self.options.title)
that.options({
title: self.options.title.reverse()
});
};
return that;
};
/*
============================================================================
Panels
============================================================================
*/
/**
*/
Ox.CollapsePanel = function(options, self) {
var self = self || {},
that = new Ox.Panel({}, self)
.defaults({
collapsed: false,
menu: [],
size: 16,
title: ''
})
.options(options)
.addClass('OxCollapsePanel'),
title = self.options.collapsed ?
[{id: 'expand', title: 'expand'}, {id: 'collapse', title: 'collapse'}] :
[{id: 'collapse', title: 'collapse'}, {id: 'expand', title: 'expand'}],
$titlebar = new Ox.Bar({
orientation: 'horizontal',
size: self.options.size,
})
.dblclick(dblclickTitlebar)
.appendTo(that),
$switch = new Ox.Button({
id: self.options.id + 'Switch',
style: 'symbol',
title: title,
type: 'image',
})
.click(toggleCollapsed)
.appendTo($titlebar),
$title = new Ox.Element()
.addClass('OxTitle')
.html(self.options.title/*.toUpperCase()*/)
.appendTo($titlebar);
if (self.options.menu.length) {
that.$menu = new Ox.Select({
id: self.options.id + 'Menu',
items: self.options.menu,
max: 0,
min: 0,
type: 'image'
})
.bindEvent('click', clickMenu)
.appendTo($titlebar)
}
that.$content = new Ox.Element()
.addClass('OxContent')
.appendTo(that);
// fixme: doesn't work, content still empty
// need to hide it if collapsed
if (self.options.collapsed) {
that.$content.css({
marginTop: -that.$content.height() + 'px'
});
}
function clickMenu() {
}
function dblclickTitlebar(e) {
if (!$(e.target).hasClass('OxButton')) {
$switch.trigger('click');
}
}
function toggleCollapsed() {
var marginTop;
self.options.collapsed = !self.options.collapsed;
marginTop = self.options.collapsed ? -that.$content.height() : 0;
that.$content.animate({
marginTop: marginTop + 'px'
}, 200);
}
self.onChange = function(key, value) {
if (key == 'collapsed') {
} else if (key == 'title') {
$title.html(self.options.title);
}
};
return that;
};
/**
*/
Ox.Panel = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.addClass('OxPanel');
return that;
};
/**
options:
elements: [{ array of one, two or three elements
collapsible: false, collapsible or not (only for outer elements)
collapsed: false, collapsed or not (only for collapsible elements)
element: {}, OxElement (if any element is resizable or
collapsible, all OxElements must have an id)
resizable: false, resizable or not (only for outer elements)
resize: [], array of sizes (only for resizable elements,
first value is min, last value is max,
other values are 'snappy' points in between)
size: 0 size in px (one element must have no size)
}],
orientation: '' 'horizontal' or 'vertical'
events:
resize
toggle
*/
Ox.SplitPanel = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self) // fixme: Container
.defaults({
elements: [],
orientation: 'horizontal'
})
.options(options || {})
.addClass('OxSplitPanel');
$.extend(self, {
dimensions: oxui.getDimensions(self.options.orientation),
edges: oxui.getEdges(self.options.orientation),
length: self.options.elements.length,
resizebarElements: [],
$resizebars: []
});
// create elements
that.$elements = [];
self.options.elements.forEach(function(v, i) {
self.options.elements[i] = $.extend({
collapsible: false,
collapsed: false,
resizable: false,
resize: [],
size: 'auto'
}, v);
that.$elements[i] = v.element
.css(self.edges[2], (parseInt(v.element.css(self.edges[2])) || 0) + 'px')
.css(self.edges[3], (parseInt(v.element.css(self.edges[3])) || 0) + 'px');
//alert(v.element.css(self.edges[3]))
});
// create resizebars
self.options.elements.forEach(function(v, i) {
//that.append(element)
//Ox.print('V: ', v, that.$elements[i])
var index = i == 0 ? 0 : 1;
that.$elements[i].appendTo(that.$element); // fixme: that.$content
if (v.collapsible || v.resizable) {
Ox.print('v.size', v.size)
self.resizebarElements[index] = i < 2 ? [0, 1] : [1, 2];
self.$resizebars[index] = new Ox.Resizebar({
collapsible: v.collapsible,
edge: self.edges[index],
elements: [
that.$elements[self.resizebarElements[index][0]],
that.$elements[self.resizebarElements[index][1]]
],
id: v.element.options('id'),
orientation: self.options.orientation == 'horizontal' ? 'vertical' : 'horizontal',
parent: that, // fixme: that.$content
resizable: v.resizable,
resize: v.resize,
size: v.size
});
self.$resizebars[index][i == 0 ? 'insertAfter' : 'insertBefore'](that.$elements[i]);
}
});
self.options.elements.forEach(function(v, i) {
v.collapsed && that.css(
self.edges[i == 0 ? 0 : 1], -self.options.elements[i].size + 'px'
);
});
setSizes(true);
function getPositionById(id) {
var position = -1;
$.each(self.options.elements, function(i, element) {
if (element.element.options('id') == id) {
position = i;
return false;
}
});
Ox.print('getPositionById', id, position);
return position;
}
function getSize(element) {
return element.size + (element.collapsible || element.resizable);
//return (element.size + (element.collapsible || element.resizable)) * !element.collapsed;
}
function getVisibleSize(element) {
return getSize(element) * !element.collapsed;
}
function setSizes(init) {
self.options.elements.forEach(function(v, i) {
var edges = [
(init && parseInt(that.$elements[i].css(self.edges[0]))) || 0,
(init && parseInt(that.$elements[i].css(self.edges[1]))) || 0
];
v.size != 'auto' && that.$elements[i].css(self.dimensions[0], v.size + 'px');
if (i == 0) {
that.$elements[i].css(
self.edges[0], edges[0] + 'px'
);
that.$elements[i].css(
self.edges[1], (getSize(self.options.elements[1]) + (length == 3 ? getSize(self.options.elements[2]) : 0)) + 'px'
);
} else if (i == 1) {
that.$elements[i].css(
self.edges[0], self.options.elements[0].size == 'auto' ? 'auto' :
edges[0] + getSize(self.options.elements[0]) + 'px'
);
(self.options.elements[0].size != 'auto' || v.size != 'auto') && that.$elements[i].css(
self.edges[1], (self.length == 3 ? getSize(self.options.elements[2]) : 0) + 'px'
);
} else {
that.$elements[i].css(
self.edges[0], (self.options.elements[1].size == 'auto' || v.size == 'auto') ? 'auto' :
(getVisibleSize(self.options.elements[0]) + getVisibleSize(self.options.elements[1])) + 'px'
);
that.$elements[i].css(
self.edges[1], edges[1] + 'px'
);
}
if (v.collapsible || v.resizable) {
self.$resizebars[i == 0 ? 0 : 1].css(self.edges[i == 0 ? 0 : 1], v.size);
}
});
}
that.isCollapsed = function(id) {
var pos = Ox.isNumber(id) ? id : getPositionById(id);
return self.options.elements[pos].collapsed;
};
that.replace = function(id, element) { // fixme: should be replaceElement
// one can pass pos instead of id
var pos = Ox.isNumber(id) ? id : getPositionById(id);
Ox.print('replace', pos, element);
Ox.print('element', self.options.elements[pos].element, element)
that.$elements[pos] = element
.css(self.edges[2], (parseInt(element.css(self.edges[2])) || 0) + 'px')
.css(self.edges[3], (parseInt(element.css(self.edges[3])) || 0) + 'px');
//alert(element.css(self.edges[3]))
self.options.elements[pos].element.replaceWith(element.$element.$element || element.$element);
self.options.elements[pos].element = element;
setSizes();
self.$resizebars.forEach(function($resizebar, i) {
$resizebar.options({
elements: [
that.$elements[self.resizebarElements[i][0]],
that.$elements[self.resizebarElements[i][1]]
]
});
});
Ox.print(self.options.elements[pos])
return that;
};
that.replaceElements = function(elements) {
elements.forEach(function(element, i) {
if (Ox.isNumber(element.size)) {
that.size(i, element.size);
if (element.collapsible || element.resizable) {
self.$resizebars[i == 0 ? 0 : 1].options({
collapsible: element.collapsible,
resizable: element.resizable,
size: element.size
});
}
}
that.replace(i, element.element);
});
self.options.elements = elements;
self.$resizebars.forEach(function($resizebar, i) {
$resizebar.options({
elements: [
that.$elements[self.resizebarElements[i][0]],
that.$elements[self.resizebarElements[i][1]]
]
});
});
return that;
}
that.size = function(id, size) {
// one can pass pos instead of id
var pos = Ox.isNumber(id) ? id : getPositionById(id),
element = self.options.elements[pos];
if (arguments.length == 1) {
return element.element[self.dimensions[0]]() * !that.isCollapsed(pos);
} else {
element.size = size;
setSizes();
return that;
}
};
that.toggle = function(id) {
// one can pass pos instead of id
Ox.print('toggle', id);
var pos = Ox.isNumber(id) ? id : getPositionById(id),
element = self.options.elements[pos],
value = parseInt(that.css(self.edges[pos == 0 ? 0 : 1])) +
element.element[self.dimensions[0]]() *
(element.collapsed ? 1 : -1),
animate = {};
animate[self.edges[pos == 0 ? 0 : 1]] = value;
Ox.print('animate', animate, self.resizebarElements)
that.animate(animate, 200, function() {
element.collapsed = !element.collapsed;
element.element.triggerEvent('toggle', {
'collapsed': element.collapsed
});
element = self.options.elements[pos == 0 ? 1 : pos - 1];
element.element.triggerEvent(
'resize',
element.element[self.dimensions[0]]()
);
});
};
that.updateSize = function(pos, size) {
// this is called from resizebar
var pos = pos == 0 ? 0 : self.options.elements.length - 1; // fixme: silly that 0 or 1 is passed, and not pos
self.options.elements[pos].size = size;
}
return that;
};
Ox.TabPanel = function(options, self) {
};
/*
============================================================================
Requests
============================================================================
*/
/**
*/
Ox.LoadingIcon = function(options, self) {
var self = self || {},
that = new Ox.Element('img', self)
.defaults({
size: 'medium'
})
.options(options || {})
.attr({
src: oxui.path + '/png/ox.ui.' + Ox.theme() + '/loading.png' // fixme: oxui.themePath needed?
})
.addClass(
'OxLoadingIcon Ox' + Ox.toTitleCase(self.options.size)
);
self.deg = 0;
self.interval = 0;
self.isRunning = false;
function clear() {
clearInterval(self.interval);
self.deg = 0;
self.interval = 0;
update();
}
function update() {
that.css({
MozTransform: 'rotate(' + self.deg + 'deg)',
WebkitTransform: 'rotate(' + self.deg + 'deg)'
});
}
that.start = function() {
self.isRunning = true;
clear();
that.animate({
opacity: 1
}, 250);
self.interval = setInterval(function() {
self.deg = (self.deg + 30) % 360;
update();
}, 83);
return that;
};
that.stop = function() {
that.animate({
opacity: 0
}, 250, function() {
self.isRunning && clear();
self.isRunning = false;
});
return that;
}
return that;
}
/**
Ox.Progressbar
*/
/*
============================================================================
Video
============================================================================
*/
/**
*/
Ox.LargeTimeline = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
cuts: [],
duration: 0,
find: '',
matches: [],
points: [0, 0],
position: 0,
style: 'default',
subtitles: [],
videoId: '',
width: 0
})
.options(options || {})
.addClass('OxTimelineLarge')
.mousedown(mousedown)
.mouseleave(mouseleave)
.mousemove(mousemove);
$.extend(self, {
$cuts: [],
$markerPoint: [],
$subtitles: [],
$tiles: {},
$tooltip: new Ox.Tooltip(),
center: parseInt(self.options.width / 2),
element: that.$element[0],
fps: 25,
height: 64,
tileWidth: 1500
});
self.tiles = self.options.duration * self.fps / self.tileWidth;
self.$timeline = $('<div>')
.css({
left: self.center + 'px'
})
.appendTo(that.$element)
$.each(self.options.subtitles, function(i, v) {
self.$subtitles[i] = $('<div>')
.addClass('OxSubtitle' + (self.options.matches.indexOf(i) > -1 ? ' OxHighlight' : ''))
.css({
left: (v['in'] * self.fps) + 'px',
width: (((v['out'] - v['in']) * self.fps) - 4) + 'px'
})
.html(Ox.highlight(v.text, self.options.find))
.appendTo(self.$timeline)
});
$.each(self.options.cuts, function(i, v) {
self.$cuts[i] = $('<img>')
.addClass('OxCut')
.attr({
src: '/static/oxjs/build/png/ox.ui/videoMarkerCut.png'
})
.css({
left: (v * self.fps) + 'px'
})
.appendTo(self.$timeline)
});
self.$markerPosition = $('<img>')
.addClass('OxMarkerPosition')
.attr({
src: '/static/oxjs/build/png/ox.ui/videoMarkerPlay.png'
})
.appendTo(that.$element);
setMarker();
$.each(['In', 'Out'], function(i, v) {
self.$markerPoint[i] = $('<img>')
.addClass('OxMarkerPoint' + v)
.attr({
src: '/static/oxjs/build/png/ox.ui/videoMarker' + v + '.png'
})
.appendTo(self.$timeline);
setMarkerPoint(i);
});
setWidth();
setPosition();
function mousedown(e) {
var mousemove = false,
x = e.clientX;
$window.mousemove(function(e) {
mousemove = true;
self.options.position = Ox.limit(
self.options.position + (x - e.clientX) / self.fps,
0, self.options.duration
);
x = e.clientX;
setPosition();
that.triggerEvent('change', {
position: self.options.position
});
});
$window.one('mouseup', function() {
$window.unbind('mousemove');
if (!mousemove) {
self.options.position = Ox.limit(
self.options.position + (e.clientX - that.$element.offset().left - self.center) / self.fps,
0, self.options.duration
);
setPosition();
}
that.triggerEvent('change', {
position: self.options.position
});
});
e.preventDefault();
}
function mouseleave(e) {
self.clientX = 0;
self.clientY = 0;
self.$tooltip.hide();
}
function mousemove(e) {
self.clientX = e.clientX;
self.clientY = e.clientY;
updateTooltip();
}
function setMarkerPoint(i) {
self.$markerPoint[i].css({
left: (self.options.points[i] * self.fps) + 'px'
});
}
function setMarker() {
self.$markerPosition.css({
left: (self.center - 4) + 'px',
});
}
function setPosition() {
self.tile = parseInt(self.options.position * self.fps / self.tileWidth);
self.$timeline.css({
marginLeft: (-self.options.position * self.fps) + 'px'
});
$.each(Ox.range(Math.max(self.tile - 1, 0), Math.min(self.tile + 2, self.tiles)), function(i, v) {
if (!self.$tiles[v]) {
self.$tiles[v] = $('<img>')
.attr({
src: '/' + self.options.videoId + '/timelines/' +
(self.options.style == 'default' ? 'timeline' : self.options.style) + '.64.' + v + '.png'
})
.css({
left: (v * self.tileWidth) + 'px'
})
.appendTo(self.$timeline);
}
});
if (self.clientX && self.clientY) {
updateTooltip();
}
}
function setWidth() {
self.center = parseInt(self.options.width / 2);
that.css({
width: self.options.width + 'px'
});
self.$timeline.css({
left: self.center + 'px'
});
setMarker();
}
function updateTooltip() {
var position = self.options.position + (self.clientX - that.offset().left - self.center) / self.fps;
if (position >= 0 && position <= self.options.duration) {
self.$tooltip
.options({
title: Ox.formatDuration(position, 3)
})
.show(self.clientX, self.clientY);
} else {
self.$tooltip.hide();
}
}
self.onChange = function(key, value) {
if (key == 'points') {
setMarkerPoint(0);
setMarkerPoint(1);
} else if (key == 'position') {
setPosition();
} else if (key == 'width') {
setWidth();
}
};
return that;
};
/**
*/
Ox.SmallTimeline = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
cuts: [],
duration: 0,
find: '',
matches: [],
points: [0, 0],
position: 0,
subtitles: [],
videoId: '',
width: 0
})
.options(options || {})
.addClass('OxTimelineSmall')
.mousedown(mousedown)
.mouseleave(mouseleave)
.mousemove(mousemove);
$.extend(self, {
$images: [],
$lines: [],
$markerPoint: [],
$subtitles: [],
hasSubtitles: self.options.subtitles.length,
height: 16,
lines: Math.ceil(self.options.duration / self.options.width),
margin: 8
});
that.css({
width: (self.options.width + self.margin) + 'px',
height: ((self.height + self.margin) * self.lines + 4) + 'px'
});
getTimelineImageURL(function(url) {
self.timelineImageURL = url;
$.each(Ox.range(0, self.lines), function(i) {
addLine(i);
});
self.$markerPosition = $('<img>')
.addClass('OxMarkerPosition')
.attr({
src: '/static/oxjs/build/png/ox.ui/videoMarkerPlay.png'
})
.css({
position: 'absolute',
width: '9px',
height: '5px',
zIndex: 10
})
.appendTo(that.$element);
setPosition();
$.each(['in', 'out'], function(i, v) {
var titleCase = Ox.toTitleCase(v);
self.$markerPoint[i] = $('<img>')
.addClass('OxMarkerPoint' + titleCase)
.attr({
src: '/static/oxjs/build/png/ox.ui/videoMarker' + titleCase + '.png'
})
.appendTo(that.$element);
setMarkerPoint(i);
});
});
function addLine(i) {
self.$lines[i] = new Ox.Element('div')
.css({
top: i * (self.height + self.margin) + 'px',
width: self.options.width + 'px'
})
.appendTo(that);
self.$images[i] = $('<img>')
.addClass('OxTimelineSmallImage')
.attr({
src: self.timelineImageURL
})
.css({
marginLeft: (-i * self.options.width) + 'px'
})
.appendTo(self.$lines[i].$element)
if (self.hasSubtitles) {
self.subtitlesImageURL = getSubtitlesImageURL();
self.$subtitles[i] = $('<img>')
.attr({
src: self.subtitlesImageURL
})
.css({
marginLeft: (-i * self.options.width) + 'px'
})
.appendTo(self.$lines[i].$element);
}
}
function getPosition(e) {
//FIXME: this might still be broken in opera according to http://acko.net/blog/mouse-handling-and-absolute-positions-in-javascript
return (e.offsetX ? e.offsetX : e.clientX - $(e.target).offset().left);
}
function getSubtitle(position) {
var subtitle = null;
$.each(self.options.subtitles, function(i, v) {
if (v['in'] <= position && v['out'] >= position) {
subtitle = v;
return false;
}
});
return subtitle;
}
function getSubtitlesImageURL() {
var height = 18,
width = Math.ceil(self.options.duration),
$canvas = $('<canvas>')
.attr({
height: height,
width: width
}),
canvas = $canvas[0],
context = canvas.getContext('2d'),
imageData = context.createImageData(width, height),
data = imageData.data;
$.each(self.options.subtitles, function(i, v) {
var color = self.options.matches.indexOf(i) > -1 ? [255, 255, 0] : [255, 255, 255]
$.each(Ox.range(
Math.round(v['in']),
Math.round(v['out']) + 1
), function(i, x) {
$.each(Ox.range(0, 18), function(i, y) {
var index = x * 4 + y * 4 * width;
data[index] = color[0];
data[index + 1] = color[1];
data[index + 2] = color[2];
data[index + 3] = (y == 0 || y == 17) ? 255 : 128
});
});
});
context.putImageData(imageData, 0, 0);
return canvas.toDataURL();
}
function getTimelineImageURL(callback) {
var height = 16,
images = Math.ceil(self.options.duration / 3600),
loaded = 0,
width = Math.ceil(self.options.duration),
$canvas = $('<canvas>')
.attr({
height: height,
width: width
}),
canvas = $canvas[0],
context = canvas.getContext('2d');
Ox.range(images).forEach(function(i) {
var $img = $('<img>')
.attr({
src: '/' + self.options.videoId + '/timelines/timeline.16.' + i + '.png'
})
.load(function() {
context.drawImage($img[0], i * 3600, 0);
Ox.print('loaded, images', loaded, images, $img[0])
if (++loaded == images) {
Ox.print('callback', canvas.toDataURL().length)
callback(canvas.toDataURL());
}
});
});
}
function mousedown(e) {
if ($(e.target).is('img')) {
self.options.position = getPosition(e);
setPosition();
that.triggerEvent('change', {
position: self.options.position
});
}
$window.mousemove(function(e) {
if ($(e.target).is('img')) {
self.options.position = getPosition(e);
setPosition();
that.triggerEvent('change', {
position: self.options.position
});
}
});
$window.one('mouseup', function() {
$window.unbind('mousemove');
})
e.preventDefault();
}
function mouseleave(e) {
self.$tooltip.hide();
}
function mousemove(e) {
var $target = $(e.target),
position,
subtitle;
if ($target.is('img')) {
//FIXME: this might still be broken in opera according to http://acko.net/blog/mouse-handling-and-absolute-positions-in-javascript
position = getPosition(e),
subtitle = getSubtitle(position);
Ox.print('position', position, e)
self.$tooltip = new Ox.Tooltip({
title: subtitle ?
'<span class=\'OxBright\'>' +
Ox.highlight(subtitle.text, self.options.find).replace(/\n/g, '<br/>') + '</span><br/>' +
Ox.formatDuration(subtitle['in'], 3) + ' - ' + Ox.formatDuration(subtitle['out'], 3) :
Ox.formatDuration(position, 3)
})
.css({
textAlign: 'center'
})
.show(e.clientX, e.clientY);
}
}
function setMarker() {
self.$markerPosition
.css({
left: (self.options.position % self.options.width) + 'px',
top: (parseInt(self.options.position / self.options.width) * (self.height + self.margin) + 2) + 'px',
});
}
function setMarkerPoint(i) {
var position = self.options.points[i];
self.$markerPoint[i]
.css({
left: (position % self.options.width) + 'px',
top: (parseInt(position / self.options.width) * (self.height + self.margin) + 16) + 'px',
});
}
function setPosition() {
self.options.position = Ox.limit(self.options.position, 0, self.options.duration);
setMarker();
}
function setWidth() {
self.lines = Math.ceil(self.options.duration / self.options.width);
that.css({
width: (self.options.width + self.margin) + 'px',
height: ((self.height + self.margin) * self.lines + 4) + 'px'
});
$.each(Ox.range(self.lines), function(i) {
if (self.$lines[i]) {
self.$lines[i].css({
width: self.options.width + 'px'
});
self.$images[i].css({
marginLeft: (-i * self.options.width) + 'px'
});
if (self.hasSubtitles) {
self.$subtitles[i].css({
marginLeft: (-i * self.options.width) + 'px'
});
}
} else {
addLine(i);
}
});
while (self.$lines.length > self.lines) {
self.$lines[self.$lines.length - 1].remove();
self.$lines.pop();
}
setMarker();
setMarkerPoint(0);
setMarkerPoint(1);
}
self.onChange = function(key, value) {
Ox.print('onChange:', key, value)
if (key == 'points') {
Ox.print('key', key, 'value', value)
setMarkerPoint(0);
setMarkerPoint(1);
} else if (key == 'position') {
setPosition();
} else if (key == 'width') {
setWidth();
}
};
return that;
};
/**
*/
Ox.VideoEditor = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
cuts: [],
duration: 0,
find: '',
frameURL: function() {},
height: 0,
largeTimeline: true,
matches: [],
points: [0, 0],
position: 0,
posterFrame: 0,
subtitles: [],
videoHeight: 0,
videoId: '',
videoWidth: 0,
videoSize: 'small',
videoURL: '',
width: 0
})
.options(options || {})
.addClass('OxEditor')
/*.css({
//height: self.options.height + 'px',
overflowY: 'scroll',
//width: self.options.width + 'px'
});*/
that.click(that.gainFocus)
$.extend(self, {
$player: [],
$timeline: [],
controlsHeight: 16,
margin: 8,
videoRatio: self.options.videoWidth / self.options.videoHeight
});
self.sizes = getSizes();
$.each(['play', 'in', 'out'], function(i, type) {
self.$player[i] = new Ox.VideoPlayer({
duration: self.options.duration,
find: self.options.find,
height: self.sizes.player[i].height,
id: 'player' + Ox.toTitleCase(type),
points: self.options.points,
position: type == 'play' ? self.options.position : self.options.points[type == 'in' ? 0 : 1],
posterFrame: self.options.posterFrame,
subtitles: self.options.subtitles,
type: type,
url: type == 'play' ? self.options.videoURL : self.options.frameURL,
width: self.sizes.player[i].width
})
.css({
left: self.sizes.player[i].left + 'px',
top: self.sizes.player[i].top + 'px'
})
.appendTo(that);
if (type == 'in' || type == 'out') {
self.$player[i].bindEvent({
change: function() {
goToPoint(type);
},
set: function() {
setPoint(type);
}
})
}
});
self.$player[0].bindEvent({
change: changePlayer,
togglesize: togglePlayerSize
});
self.$timeline[0] = new Ox.LargeTimeline({
cuts: self.options.cuts,
duration: self.options.duration,
find: self.options.find,
id: 'timelineLarge',
matches: self.options.matches,
points: self.options.points,
position: self.options.position,
subtitles: self.options.subtitles,
videoId: self.options.videoId,
width: self.sizes.timeline[0].width
})
.css({
left: self.sizes.timeline[0].left + 'px',
top: self.sizes.timeline[0].top + 'px'
})
.bindEvent('change', changeTimelineLarge)
.bindEvent('changeEnd', changeTimelineLarge)
.appendTo(that);
self.$timeline[1] = new Ox.SmallTimeline({
cuts: self.options.cuts,
duration: self.options.duration,
find: self.options.find,
id: 'timelineSmall',
matches: self.options.matches,
points: self.options.points,
position: self.options.position,
subtitles: self.options.subtitles,
videoId: self.options.videoId,
width: self.sizes.timeline[1].width
})
.css({
left: self.sizes.timeline[1].left + 'px',
top: self.sizes.timeline[1].top + 'px'
})
.bindEvent('change', changeTimelineSmall)
.appendTo(that);
that.bindEvent({
key_alt_left: function() {
movePositionTo('cut', -1);
},
key_alt_right: function() {
movePositionTo('cut', 1);
},
key_alt_shift_left: function() {
},
key_alt_shift_right: function() {
},
key_closebracket: function() {
goToPoint('out');
},
key_comma: function() {
movePositionTo('subtitle', -1);
},
key_dot: function() {
movePositionTo('subtitle', 1);
},
key_down: function() {
movePositionBy(self.options.width - 2 * self.margin);
},
key_i: function() {
setPoint('in');
},
key_left: function() {
movePositionBy(-1);
},
key_m: toggleMute,
key_o: function() {
setPoint('out');
},
key_openbracket: function() {
goToPoint('in');
},
key_p: playInToOut,
key_right: function() {
movePositionBy(1);
},
key_shift_comma: function() {
movePositionTo('match', -1)
},
key_shift_dot: function() {
movePositionTo('match', 1)
},
key_shift_down: function() {
movePositionBy(self.options.duration);
},
key_shift_left: function() {
movePositionBy(-0.04);
//movePositionBy(-60);
},
key_shift_right: function() {
movePositionBy(0.04);
//movePositionBy(60);
},
key_shift_up: function() {
movePositionBy(-self.options.duration);
},
key_space: togglePlay,
key_up: function() {
movePositionBy(-(self.options.width - 2 * self.margin));
}
});
// that.gainFocus();
function changePlayer(event, data) {
self.options.position = data.position;
self.$timeline[0].options({
position: data.position
});
self.$timeline[1].options({
position: data.position
});
}
function changeTimelineLarge(event, data) {
self.options.position = data.position;
self.$player[0].options({
position: data.position
});
self.$timeline[1].options({
position: data.position
});
}
function changeTimelineSmall(event, data) {
self.options.position = data.position;
self.$player[0].options({
position: data.position
});
self.$timeline[0].options({
position: data.position
});
}
function getNextPosition(type, direction) {
var found = false,
position = 0,
positions;
if (type == 'cut') {
positions = self.options.cuts;
} else if (type == 'match') {
positions = $.map(self.options.matches, function(v, i) {
return self.options.subtitles[v]['in'];
});
} else if (type == 'subtitle') {
positions = $.map(self.options.subtitles, function(v, i) {
return v['in'];
});
}
direction == -1 && positions.reverse();
$.each(positions, function(i, v) {
if (direction == 1 ? v > self.options.position : v < self.options.position) {
position = v;
found = true;
return false;
}
});
direction == -1 && positions.reverse();
if (!found) {
position = positions[direction == 1 ? 0 : positions.length - 1];
}
return position;
}
function getSizes(scrollbarIsVisible) {
Ox.print('getSizes', scrollbarIsVisible)
var scrollbarWidth = oxui.scrollbarSize,
contentWidth = self.options.width - (scrollbarIsVisible ? scrollbarWidth : 0),
lines,
size = {
player: [],
timeline: []
},
width, widths;
if (self.options.videoSize == 'small') {
width = 0;
widths = Ox.divideInt(contentWidth - 4 * self.margin, 3);
[1, 0, 2].forEach(function(v, i) {
size.player[v] = {
left: (i + 0.5) * self.margin + width,
top: self.margin / 2,
width: widths[i],
height: Math.round(widths[1] / self.videoRatio)
}
width += widths[i];
});
} else {
size.player[0] = {
left: self.margin / 2,
top: self.margin / 2,
width: Math.round((contentWidth - 3 * self.margin + (self.controlsHeight + self.margin) / 2 * self.videoRatio) * 2/3),
}
size.player[0].height = Math.round(size.player[0].width / self.videoRatio);
size.player[1] = {
left: size.player[0].left + size.player[0].width + self.margin,
top: size.player[0].top,
width: contentWidth - 3 * self.margin - size.player[0].width
}
size.player[1].height = Math.ceil(size.player[1].width / self.videoRatio)
size.player[2] = {
left: size.player[1].left,
top: size.player[0].top + size.player[1].height + self.controlsHeight + self.margin,
width: size.player[1].width,
height: size.player[0].height - size.player[1].height - self.controlsHeight - self.margin
}
}
size.timeline[0] = {
left: self.margin / 2,
top: size.player[0].height + self.controlsHeight + 1.5 * self.margin,
width: contentWidth - 2 * self.margin,
height: 64
}
size.timeline[1] = {
left: size.timeline[0].left,
top: size.timeline[0].top + size.timeline[0].height + self.margin,
width: size.timeline[0].width
}
lines = Math.ceil(self.options.duration / size.timeline[1].width);
Ox.print('lines', lines, getHeight(), self.options.height, (scrollbarIsVisible && getHeight() <= self.options.height) ? 'scroll' : 'auto')
that.css({
overflowY: (scrollbarIsVisible && getHeight() <= self.options.height) ? 'scroll' : 'auto'
});
return (!scrollbarIsVisible && getHeight() > self.options.height) ? getSizes(true) : size;
function getHeight() {
return size.player[0].height + self.controlsHeight +
size.timeline[0].height + lines * 16 +
(lines + 3) * self.margin;
}
}
function goToPoint(point) {
self.options.position = self.options.points[point == 'in' ? 0 : 1];
setPosition();
that.triggerEvent('change', {
position: self.options.position
});
}
function movePositionBy(sec) {
self.options.position = Ox.limit(self.options.position + sec, 0, self.options.duration);
setPosition();
that.triggerEvent('change', {
position: self.options.position
});
}
function movePositionTo(type, direction) {
self.options.position = getNextPosition(type, direction);
setPosition();
that.triggerEvent('change', {
position: self.options.position
});
}
function playInToOut() {
self.$player[0].playInToOut();
}
function resizeEditor(event, data) {
var width = data - 2 * margin + 100;
resizeVideoPlayers(width);
$timelineLarge.options({
width: width
});
$timelineSmall.options({
width: width
});
}
function resizePlayers() {
$.each(self.$player, function(i, v) {
v.options({
width: size[i].width,
height: size[i].height
})
.css({
left: size[i].left + 'px',
top: size[i].top + 'px',
});
});
}
function setPoint(point) {
self.options.points[point == 'in' ? 0 : 1] = self.options.position;
self.$player[point == 'in' ? 1 : 2].options({
position: self.options.position
});
if (self.options.points[1] < self.options.points[0]) {
self.options.points[point == 'in' ? 1 : 0] = self.options.position;
self.$player[point == 'in' ? 2 : 1].options({
position: self.options.position
});
}
$.each(self.$player, function(i, v) {
v.options({
points: self.options.points
});
});
$.each(self.$timeline, function(i, v) {
Ox.print('points:', self.options.points)
v.options({
points: self.options.points
});
});
}
function setPosition() {
self.$player[0].options({
position: self.options.position
});
$.each(self.$timeline, function(i, v) {
v.options({
position: self.options.position
});
});
}
function setSizes() {
self.sizes = getSizes();
$.each(self.$player, function(i, v) {
v.options({
height: self.sizes.player[i].height,
width: self.sizes.player[i].width
})
.css({
left: self.sizes.player[i].left + 'px',
top: self.sizes.player[i].top + 'px'
});
});
$.each(self.$timeline, function(i, v) {
v.options({
width: self.sizes.timeline[i].width
})
.css({
left: self.sizes.timeline[i].left + 'px',
top: self.sizes.timeline[i].top + 'px'
});
});
}
function toggleMute() {
self.$player[0].toggleMute();
}
function togglePlay() {
self.$player[0].togglePlay();
}
function togglePlayerSize() {
self.options.videoSize = self.options.videoSize == 'large' ? 'small' : 'large';
setSizes();
}
self.onChange = function(key, value) {
if (key == 'width' || key == 'height') {
Ox.print('XXXX setSizes', key, value, self.options.width, self.options.height)
setSizes();
}
};
return that;
};
/**
*/
Ox.VideoPlayer = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
find: '',
height: 0,
points: [0, 0],
position: 0,
posterFrame: 0,
subtitles: [],
type: 'play',
url: '',
width: 0
})
.options(options || {})
.addClass('OxVideoPlayer')
.css({
height: (self.options.height + 16) + 'px',
width: self.options.width + 'px'
});
self.controlsHeight = 16;
if (self.options.type == 'play') {
self.$video = $('<video>')
.attr({
preload: 'auto',
src: self.options.url
})
.css({
height: self.options.height + 'px',
width: self.options.width + 'px'
})
.appendTo(that.$element);
self.video = self.$video[0];
} else {
self.$video = $('<img>')
.css({
height: self.options.height + 'px',
width: self.options.width + 'px'
})
.appendTo(that.$element)
}
self.$subtitle = $('<div>')
.addClass('OxSubtitle')
.appendTo(that.$element);
setSubtitleSize();
self.$markerFrame = $('<div>')
.addClass('OxMarkerFrame')
.append(
$('<div>')
.addClass('OxFrame')
.css({
width: Math.floor((self.options.width - self.options.height) / 2) + 'px',
height: self.options.height + 'px'
})
)
.append(
$('<div>')
.addClass('OxPoster')
.css({
width: (self.options.height - 2) + 'px',
height: (self.options.height - 2) + 'px'
})
)
.append(
$('<div>')
.addClass('OxFrame')
.css({
width: Math.ceil((self.options.width - self.options.height) / 2) + 'px',
height: self.options.height + 'px'
})
)
.hide()
.appendTo(that.$element);
self.$markerPoint = {}
$.each(['in', 'out'], function(i, point) {
self.$markerPoint[point] = {};
$.each(['top', 'bottom'], function(i, edge) {
var titleCase = Ox.toTitleCase(point) + Ox.toTitleCase(edge);
self.$markerPoint[point][edge] = $('<img>')
.addClass('OxMarkerPoint OxMarker' + titleCase)
.attr({
src: '/static/oxjs/build/png/ox.ui/videoMarker' + titleCase + '.png' // fixme: remove static path
})
.hide()
.appendTo(that.$element);
if (self.options.points[point == 'in' ? 0 : 1] == self.options.position) {
self.$markerPoint[point][edge].show();
}
});
});
self.$controls = new Ox.Bar({
size: self.controlsHeight
})
.css({
marginTop: '-2px'
})
.appendTo(that);
if (self.options.type == 'play') {
// fixme: $buttonPlay etc.
self.$playButton = new Ox.Button({
id: self.options.id + 'Play',
title: [
{id: 'play', title: 'play'},
{id: 'pause', title: 'pause'}
],
tooltip: ['Play', 'Pause'],
type: 'image'
})
.bindEvent('click', togglePlay)
.appendTo(self.$controls);
self.$playInToOutButton = new Ox.Button({
id: self.options.id + 'PlayInToOut',
title: 'PlayInToOut',
tooltip: 'Play In to Out',
type: 'image'
})
.bindEvent('click', function() {
that.playInToOut();
})
.appendTo(self.$controls);
self.$muteButton = new Ox.Button({
id: self.options.id + 'Mute',
title: [
{id: 'mute', title: 'mute'},
{id: 'unmute', title: 'unmute'}
],
tooltip: ['Mute', 'Unmute'],
type: 'image'
})
.bindEvent('click', toggleMute)
.appendTo(self.$controls);
self.$sizeButton = new Ox.Button({
id: self.options.id + 'Size',
title: [
{id: 'large', title: 'add'},
{id: 'small', title: 'remove'}
],
tooltip: ['Larger', 'Smaller'],
type: 'image'
})
.bindEvent('click', toggleSize)
.appendTo(self.$controls);
} else {
self.$goToPointButton = new Ox.Button({
id: self.options.id + 'GoTo' + Ox.toTitleCase(self.options.type),
title: 'GoTo' + Ox.toTitleCase(self.options.type),
tooltip: 'Go to ' + Ox.toTitleCase(self.options.type) + ' Point',
type: 'image'
})
.bindEvent('click', goToPoint)
.appendTo(self.$controls);
self.$setPointButton = new Ox.Button({
id: self.options.id + 'Set' + Ox.toTitleCase(self.options.type),
title: 'Set' + Ox.toTitleCase(self.options.type),
tooltip: 'Set ' + Ox.toTitleCase(self.options.type) + ' Point',
type: 'image'
})
.bindEvent('click', setPoint)
.appendTo(self.$controls);
}
self.$positionInput = new Ox.TimeInput({
milliseconds: true,
seconds: true,
value: Ox.formatDuration(self.options.position, 3)
})
.css({
float: 'right',
})
.appendTo(self.$controls)
self.$positionInput.css({
width: '98px'
});
// fixme: children doesnt work w/o $element
self.$positionInput.$element.children('.OxLabel').each(function(i, element) {
$(this).css({
width: '22px',
marginLeft: (i == 0 ? 8 : 0) + 'px',
background: 'rgb(32, 32, 32)'
});
});
self.$positionInput.$element.children('div.OxInput').each(function(i) {
var marginLeft = [-82, -58, -34, -10];
$(this).css({
marginLeft: marginLeft[i] + 'px'
}).addClass('foo');
});
if (self.options.type == 'play') {
self.$loadingIcon = new Ox.LoadingIcon()
.appendTo(that)
.start();
self.loadingInterval = setInterval(function() {
if (self.video.readyState) {
clearInterval(self.loadingInterval);
self.$loadingIcon.stop();
setPosition();
}
}, 50);
} else {
setPosition();
}
function getSubtitle() {
var subtitle = '';
$.each(self.options.subtitles, function(i, v) {
if (v['in'] <= self.options.position && v['out'] > self.options.position) {
subtitle = v.text;
return false;
}
});
return subtitle;
}
function goToPoint() {
that.triggerEvent('change', {
position: self.options.points[self.options.type == 'in' ? 0 : 1]
});
}
function playing() {
self.options.position = Math.round(self.video.currentTime * 25) / 25;
if (self.video.ended) {
self.$playButton.trigger('click');
}
if (self.playingInToOut && self.options.position >= self.options.points[1]) {
self.$playButton.trigger('click');
self.options.position = self.options.points[1];
}
setMarkers();
setSubtitle();
self.$positionInput.options({
value: Ox.formatDuration(self.options.position, 3)
});
that.triggerEvent('change', {
position: self.options.position
});
}
function setHeight() {
that.css({
height: (self.options.height + 16) + 'px'
});
self.$video.css({
height: self.options.height + 'px'
});
setSubtitleSize();
}
function setMarkers() {
self.options.position == self.options.posterFrame ? self.$markerFrame.show() : self.$markerFrame.hide();
$.each(self.$markerPoint, function(point, markers) {
$.each(markers, function(edge, marker) {
self.options.position == self.options.points[point == 'in' ? 0 : 1] ?
marker.show() : marker.hide();
});
})
}
function setPoint() {
var data = {};
self.options.points[self.options.type == 'in' ? 0 : 1] = self.options.position;
setMarkers();
data[self.options.type] = self.options.position;
that.triggerEvent('set', data);
}
function setPosition() {
if (self.options.type == 'play') {
self.video.currentTime = self.options.position;
} else {
self.$loadingIcon && self.$loadingIcon.stop();
if (self.$video.attr('src') != self.options.url(self.options.position)) {
self.$loadingIcon = new Ox.LoadingIcon()
.appendTo(that)
.start();
self.$video.attr({
src: self.options.url(self.options.position)
})
.load(self.$loadingIcon.stop);
}
}
setMarkers();
setSubtitle();
self.$positionInput.options({
value: Ox.formatDuration(self.options.position, 3)
});
}
function setSubtitle() {
var subtitle = getSubtitle();
if (subtitle != self.subtitle) {
self.subtitle = subtitle;
self.$subtitle.html(Ox.highlight(self.subtitle, self.options.find).replace(/\n/g, '<br/>'));
}
}
function setSubtitleSize() {
self.$subtitle.css({
bottom: parseInt(self.controlsHeight + self.options.height / 16) + 'px',
width: self.options.width + 'px',
fontSize: parseInt(self.options.height / 20) + 'px',
WebkitTextStroke: (self.options.height / 1000) + 'px rgb(0, 0, 0)'
});
}
function setWidth() {
that.css({
width: self.options.width + 'px'
});
self.$video.css({
width: self.options.width + 'px'
});
setSubtitleSize();
}
function toggleMute() {
self.video.muted = !self.video.muted;
}
function togglePlay() {
self.video.paused ? that.play() : that.pause();
}
function toggleSize(event, data) {
that.triggerEvent('togglesize', {
size: data.id
});
}
self.onChange = function(key, value) {
if (key == 'height') {
setHeight();
} else if (key == 'points') {
setMarkers();
} else if (key == 'position') {
setPosition();
} else if (key == 'posterFrame') {
setMarkers();
} else if (key == 'width') {
setWidth();
}
}
that.mute = function() {
self.video.muted = true;
return that;
};
that.pause = function() {
self.video.pause();
clearInterval(self.playInterval);
self.playingInToOut = false;
return that;
};
that.play = function() {
self.video.play();
self.playInterval = setInterval(playing, 40);
return that;
};
that.playInToOut = function() {
self.options.position = self.options.points[0];
setPosition();
Ox.print('sop', self.options.position, self.options.points);
self.playingInToOut = true;
self.video.paused && self.$playButton.trigger('click');
return that;
};
that.toggleMute = function() {
self.$muteButton.trigger('click');
return that;
}
that.togglePlay = function() {
self.$playButton.trigger('click');
return that;
}
that.unmute = function() {
self.video.muted = false;
return that;
};
return that;
};
/*
============================================================================
Miscellaneous
============================================================================
*/
/**
*/
Ox.Tooltip = function(options, self) {
var self = self || {},
that = new Ox.Element('div', self)
.defaults({
title: ''
})
.options(options || {})
.addClass('OxTooltip')
.html(self.options.title);
self.onChange = function(key, value) {
if (key == 'title') {
that.html(value);
}
};
that.hide = function() {
that.animate({
opacity: 0
}, 0, function() {
that.remove();
});
return that;
};
that.show = function(x, y) {
var left, top, width, height;
$('.OxTooltip').remove(); // fixme: don't use dom
that.appendTo($body);
width = that.width();
height = that.height();
left = Ox.limit(x - width / 2, 0, $document.width() - width);
top = y > $document.height() - height - 16 ? y - 32 : y + 16;
that.css({
left: left + 'px',
top: top + 'px'
})
.animate({
opacity: 1
}, 0);
return that;
};
return that;
};
})();