14524 lines
507 KiB
JavaScript
14524 lines
507 KiB
JavaScript
/*
|
|
################################################################################
|
|
ox.ui.js
|
|
|
|
requires
|
|
jquery-1.4.js
|
|
ox.js
|
|
################################################################################
|
|
*/
|
|
|
|
// also see test.js, in demos ...
|
|
|
|
// fixme: render might be a better function name than construct
|
|
|
|
(function() {
|
|
|
|
// fixme: move into Ox.UI
|
|
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];
|
|
},
|
|
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
|
|
============================================================================
|
|
*/
|
|
|
|
Ox.App = (function() {
|
|
|
|
/***
|
|
Ox.App
|
|
Basic application instance that communicates with a JSON API.
|
|
The JSON API should support at least the following actions:
|
|
api returns all api methods
|
|
init returns {config: {...}, user: {...}}
|
|
Options
|
|
timeout API timeout in msec
|
|
type 'GET' or 'POST'
|
|
url URL of the API
|
|
Methods
|
|
api[action] make a request
|
|
api.cancel cancel a request
|
|
launch launch the App
|
|
options get or set options
|
|
***/
|
|
|
|
return function(options) {
|
|
|
|
options = options || {};
|
|
var self = {},
|
|
that = this;
|
|
|
|
self.time = +new Date();
|
|
|
|
self.options = $.extend({
|
|
timeout: 60000,
|
|
type: 'POST',
|
|
url: '/api/',
|
|
}, options);
|
|
|
|
that.$element = new Ox.Element('body');
|
|
|
|
function getUserAgent() {
|
|
var userAgent = '';
|
|
$.each(['Chrome', 'Firefox', 'Internet Explorer', 'Opera', 'Safari'], function(i, v) {
|
|
if (navigator.userAgent.indexOf(v) > -1) {
|
|
userAgent = v;
|
|
return false;
|
|
}
|
|
});
|
|
if (!userAgent && $.browser.mozilla) {
|
|
userAgent = 'Firefox';
|
|
}
|
|
if (!userAgent && $.browser.webkit) {
|
|
userAgent = 'Chrome';
|
|
}
|
|
return userAgent;
|
|
}
|
|
|
|
function getUserData() {
|
|
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) {
|
|
window.OxImageCache = [];
|
|
$.getJSON(oxui.path + 'json/ox.ui.images.json', function(data) {
|
|
var counter = 0,
|
|
length = data.length;
|
|
data.forEach(function(src, i) {
|
|
var image = new Image();
|
|
image.src = oxui.path + src;
|
|
image.onload = function() {
|
|
(++counter == length) && callback();
|
|
}
|
|
window.OxImageCache.push(image);
|
|
});
|
|
});
|
|
}
|
|
|
|
self.change = function(key, value) {
|
|
|
|
};
|
|
|
|
that.api = {
|
|
api: function(callback) {
|
|
Ox.Request.send({
|
|
url: self.options.url,
|
|
data: {
|
|
action: 'api'
|
|
},
|
|
callback: callback
|
|
});
|
|
},
|
|
cancel: function(id) {
|
|
Ox.Request.cancel(id);
|
|
}
|
|
};
|
|
|
|
that.bindEvent = function() {
|
|
|
|
};
|
|
|
|
that.launch = function(callback) {
|
|
var time = +new Date(),
|
|
userAgent = getUserAgent(),
|
|
userAgents = ['Chrome', 'Firefox', 'Opera', 'Safari'];
|
|
$.ajaxSetup({
|
|
timeout: self.options.timeour,
|
|
type: self.options.type,
|
|
url: self.options.url
|
|
});
|
|
userAgents.indexOf(userAgent) > -1 ? start() : stop();
|
|
function start() {
|
|
// fixme: rename config to site?
|
|
var counter = 0, config, user;
|
|
that.api.api(function(result) {
|
|
$.each(result.data.actions, function(key, value) {
|
|
that.api[key] = function(data, callback) {
|
|
if (arguments.length == 1 && Ox.isFunction(data)) {
|
|
callback = data;
|
|
data = {};
|
|
}
|
|
return Ox.Request.send($.extend({
|
|
url: self.options.url,
|
|
data: {
|
|
action: key,
|
|
data: JSON.stringify(data)
|
|
},
|
|
callback: callback
|
|
}, !value.cache ? {age: 0}: {}));
|
|
};
|
|
});
|
|
that.api.init(getUserData(), function(result) {
|
|
config = result.data.config;
|
|
user = result.data.user;
|
|
document.title = config.site.name;
|
|
launchCallback();
|
|
});
|
|
});
|
|
loadImages(launchCallback);
|
|
function launchCallback() {
|
|
++counter == 2 && $(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.Clipboard = function() {
|
|
/***
|
|
Ox.Clipboard
|
|
Basic clipboard handler
|
|
Methods
|
|
copy(data) copy data to clipboard
|
|
paste paste data from clipboard
|
|
***/
|
|
var clipboard = {};
|
|
return {
|
|
_print: function() {
|
|
Ox.print(JSON.stringify(clipboard));
|
|
},
|
|
copy: function(data) {
|
|
clipboard = data;
|
|
Ox.print('copy', JSON.stringify(clipboard));
|
|
},
|
|
paste: function(type) {
|
|
return clipboard;
|
|
}
|
|
};
|
|
}();
|
|
|
|
Ox.Focus = function() {
|
|
/***
|
|
Ox.Focus
|
|
Basic focus handler
|
|
Methods
|
|
blur(id) blur element
|
|
focus(id) focus element
|
|
focused() return id of focused element, or null
|
|
***/
|
|
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());
|
|
//$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) {
|
|
index > -1 && stack.splice(index, 1);
|
|
stack.push(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,
|
|
// wrapped in function so it can be collapsed in text editor
|
|
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',
|
|
91: 'meta',
|
|
92: 'meta',
|
|
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) {
|
|
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 !== null && $elements[focused].triggerEvent('key_' + key);
|
|
if (['down', 'space', 'up'].indexOf(key) > -1 && !$elements[focused].hasClass('OxInput')) {
|
|
// prevent chrome from scrolling
|
|
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.Request = function(options) {
|
|
|
|
/***
|
|
Ox.Request
|
|
Basic request handler
|
|
Options
|
|
timeout
|
|
Methods
|
|
cancel() cancel request
|
|
clearCache() clear cache
|
|
options() get or set options
|
|
requests() return number of active requests
|
|
send() send request
|
|
***/
|
|
|
|
var cache = {},
|
|
pending = {},
|
|
requests = {},
|
|
self = {
|
|
options: $.extend({
|
|
timeout: 60000,
|
|
type: 'POST',
|
|
url: '/api/'
|
|
}, options)
|
|
};
|
|
|
|
return {
|
|
|
|
cancel: function() {
|
|
if (arguments.length == 0) {
|
|
// cancel all requests
|
|
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]];
|
|
}
|
|
},
|
|
|
|
clearCache: function() {
|
|
cache = {};
|
|
},
|
|
|
|
options: function(options) {
|
|
return Ox.getset(self.options, options, $.noop(), this);
|
|
},
|
|
|
|
requests: function() {
|
|
return Ox.length(requests);
|
|
},
|
|
|
|
send: function(options) {
|
|
|
|
var options = $.extend({
|
|
age: -1,
|
|
callback: null,
|
|
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
|
|
});
|
|
|
|
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 && 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
|
|
});
|
|
}
|
|
}
|
|
|
|
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({
|
|
id: 'details',
|
|
title: 'Details'
|
|
})
|
|
.bindEvent({
|
|
click: function() {
|
|
$dialog.close(function() {
|
|
debug(request);
|
|
});
|
|
}
|
|
}),
|
|
new Ox.Button({
|
|
id: 'close',
|
|
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.',
|
|
keys: {enter: 'close', escape: 'close'},
|
|
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);
|
|
}
|
|
|
|
return options.id;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}();
|
|
|
|
Ox.Theme = function() {
|
|
|
|
};
|
|
|
|
Ox.UI = (function() {
|
|
/*
|
|
$(function() {
|
|
Ox.extend(Ox.UI, {
|
|
$body: $('body'),
|
|
$document: $(document),
|
|
$window: $(window)
|
|
});
|
|
});
|
|
*/
|
|
return {
|
|
getDimensions: function(orientation) {
|
|
return orientation == 'horizontal' ?
|
|
['width', 'height'] : ['height', 'width'];
|
|
},
|
|
getEdges: function(orientation) {
|
|
return orientation == 'horizontal' ?
|
|
['left', 'right', 'top', 'bottom'] :
|
|
['top', 'bottom', 'left', 'right'];
|
|
},
|
|
DIMENSIONS: {
|
|
horizontal: ['width', 'height'],
|
|
vertical: ['height', 'width']
|
|
},
|
|
EDGES: {
|
|
horizontal: [['left', 'right'], ['top', 'bottom']],
|
|
vertical: [['top', 'bottom'], ['left', 'right']]
|
|
},
|
|
PATH: $('script[src*="ox.ui.js"]')
|
|
.attr('src').replace('js/ox.ui.js', ''),
|
|
theme: function() {
|
|
|
|
},
|
|
themePath: 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) {
|
|
// fixme: to be deprecated
|
|
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;
|
|
};
|
|
|
|
Ox.jQueryElement = (function() {
|
|
// Basic jQuery element
|
|
var jQueryFunctions = (function() {
|
|
var functions = [];
|
|
Ox.each($('<div>'), function(key, val) {
|
|
typeof val == 'function' && functions.push(key);
|
|
});
|
|
return functions.sort();
|
|
})();
|
|
return function(element) {
|
|
var that = {};
|
|
Ox.each(jQueryFunctions, function(i, fn) {
|
|
that[fn] = function() {
|
|
var args = arguments, id, ret;
|
|
$.each(args, function(i, arg) {
|
|
// if an ox object was passed
|
|
// then pass its $element instead
|
|
// so that we can do oxObj.jqFn(oxObj)
|
|
if (arg && arg.ox) {
|
|
args[i] = arg.$element;
|
|
}
|
|
});
|
|
ret = element.$element[fn].apply(element.$element, args);
|
|
// if the $element of an ox object was returned
|
|
// then return the ox object instead
|
|
// so that we can do oxObj.jqFn().oxFn()
|
|
/*
|
|
if (fn == 'appendTo') {
|
|
Ox.print('ret', ret, $element, ret.jquery && $elements[id = ret.data('ox')] == true)
|
|
}
|
|
*/
|
|
return ret.jquery && $elements[id = ret.data('ox')] ?
|
|
$elements[id] : ret;
|
|
};
|
|
});
|
|
return that;
|
|
}
|
|
})();
|
|
|
|
// check out http://ejohn.org/apps/learn/#36 (-#38, making fns work w/o new)
|
|
|
|
Ox.Element = function() {
|
|
|
|
/***
|
|
Basic element object
|
|
***/
|
|
|
|
return function(options, self) {
|
|
|
|
if (!(this instanceof arguments.callee)) {
|
|
return new arguments.callee(options, self);
|
|
}
|
|
|
|
self = self || {};
|
|
self.options = options || {};
|
|
if (!self.$eventHandler) {
|
|
self.$eventHandler = $('<div>');
|
|
}
|
|
var that = this;
|
|
|
|
// 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
|
|
},
|
|
mousedown: mousedown
|
|
});
|
|
$elements[that.id] = that;
|
|
|
|
$.extend(that, Ox.jQueryElement(that));
|
|
|
|
function mousedown(e) {
|
|
/*
|
|
better mouse events
|
|
on mousedown:
|
|
trigger mousedown
|
|
within 250 msec:
|
|
mouseup: trigger anyclick ("click" would collide with click events of certain widgets)
|
|
mouseup + mousedown: trigger doubleclick
|
|
after 250 msec:
|
|
mouseup + no mousedown within 250 msec: trigger singleclick
|
|
no mouseup within 250 msec:
|
|
trigger mouserepeat every 50 msec
|
|
trigger dragstart
|
|
mousemove: trigger drag
|
|
mouseup: trigger dragend
|
|
*/
|
|
var mouseInterval = 0;
|
|
if (!self.mouseTimeout) {
|
|
// first mousedown
|
|
that.triggerEvent('mousedown', e);
|
|
self.mouseup = false;
|
|
self.mouseTimeout = setTimeout(function() {
|
|
self.mouseTimeout = 0;
|
|
if (self.mouseup) {
|
|
// singleclick
|
|
that.triggerEvent('singleclick', e);
|
|
} else {
|
|
// mouserepeat, drag
|
|
that.triggerEvent({
|
|
mouserepeat: e,
|
|
dragstart: e
|
|
});
|
|
mouseInterval = setInterval(function() {
|
|
that.triggerEvent('mouserepeat');
|
|
}, 50);
|
|
$window.unbind('mouseup', mouseup)
|
|
.mousemove(mousemove)
|
|
.one('mouseup', function(e) {
|
|
clearInterval(mouseInterval);
|
|
$window.unbind('mousemove', mousemove);
|
|
that.triggerEvent('dragend', e);
|
|
});
|
|
that.one('mouseleave', function() {
|
|
clearInterval(mouseInterval);
|
|
});
|
|
}
|
|
}, 250);
|
|
} else {
|
|
// second mousedown
|
|
clearTimeout(self.mouseTimeout);
|
|
self.mouseTimeout = 0;
|
|
that.triggerEvent('doubleclick');
|
|
}
|
|
$window.one('mouseup', mouseup);
|
|
function mousemove(e) {
|
|
that.triggerEvent('drag', e);
|
|
}
|
|
function mouseup(e) {
|
|
if (!self.mouseup) { // fixme: shouldn't be necessary, bound only once
|
|
that.triggerEvent('anyclick', e);
|
|
self.mouseup = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
self.onChange = function() {
|
|
// self.onChange(key, value)
|
|
// is called when an option changes
|
|
// (to be implemented by widget)
|
|
// fixme: rename to self.setOption
|
|
};
|
|
|
|
that._leakSelf = function() { // fixme: remove
|
|
return self;
|
|
}
|
|
|
|
that.bindEvent = function() {
|
|
/***
|
|
binds a function to an event triggered by this object
|
|
Usage
|
|
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) {
|
|
/***
|
|
sets the default options
|
|
Usage
|
|
that.defaults({key0: value0, key1: value1, ...})
|
|
***/
|
|
self.defaults = defaults;
|
|
delete self.options; // fixme: hackish fix for that = Ox.Foo({...}, self).defaults({...}).options({...})
|
|
return that;
|
|
};
|
|
|
|
that.gainFocus = function() {
|
|
/***
|
|
make this object gain focus
|
|
***/
|
|
Ox.Focus.focus(that.id);
|
|
return that;
|
|
};
|
|
|
|
that.hasFocus = function() {
|
|
/***
|
|
returns true if this object has focus
|
|
***/
|
|
return Ox.Focus.focused() == that.id;
|
|
};
|
|
|
|
that.loseFocus = function() {
|
|
/***
|
|
make this object lose focus
|
|
***/
|
|
Ox.Focus.blur(that.id);
|
|
return that;
|
|
};
|
|
|
|
that.options = function() { // fixme: use Ox.getset
|
|
/***
|
|
get or set options
|
|
Usage
|
|
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
|
|
/***
|
|
remove this element, including its event handler
|
|
***/
|
|
that.loseFocus();
|
|
delete self.$eventHandler;
|
|
that.$element.remove();
|
|
delete $elements[that.ox];
|
|
return that;
|
|
};
|
|
|
|
that.triggerEvent = function() {
|
|
/***
|
|
triggers an event
|
|
Usage
|
|
triggerEvent(event)
|
|
triggerEvent(event, data)
|
|
triggerEvent({event0: data0, event1: data1, ...})
|
|
***/
|
|
if (Ox.isObject(arguments[0])) {
|
|
$.each(arguments[0], function(event, data) {
|
|
if (['mousedown', 'mouserepeat', 'anyclick', 'singleclick', 'doubleclick', 'dragstart', 'drag', 'dragend', 'playing'].indexOf(event) == -1) {
|
|
Ox.print(that.id, self.options.id, 'trigger', event, data);
|
|
}
|
|
self.$eventHandler.trigger('ox_' + event, data);
|
|
});
|
|
} else {
|
|
if (['mousedown', 'mouserepeat', 'anyclick', 'singleclick', 'doubleclick', 'dragstart', 'drag', 'dragend', 'playing'].indexOf(arguments[0]) == -1) {
|
|
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 a function from an event triggered by this element
|
|
Usage
|
|
unbindEvent(event, fn)
|
|
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 that;
|
|
|
|
}
|
|
|
|
}();
|
|
|
|
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) {
|
|
$('img').each(function() {
|
|
var $this = $(this);
|
|
if (!$this.attr('src')) return; // fixme: remove, should't be neccessary
|
|
$this.attr({
|
|
src: $this.attr('src').replace(
|
|
'/ox.ui.' + theme + '/', '/ox.ui.' + arg + '/'
|
|
)
|
|
});
|
|
});
|
|
$('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;
|
|
};
|
|
|
|
/*
|
|
============================================================================
|
|
Bars
|
|
============================================================================
|
|
*/
|
|
|
|
/**
|
|
*/
|
|
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)
|
|
})
|
|
*/
|
|
.bindEvent({
|
|
anyclick: toggle,
|
|
dragstart: dragstart,
|
|
drag: drag,
|
|
dragend: dragend
|
|
})
|
|
.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'
|
|
});
|
|
|
|
function dragstart(event, e) {
|
|
if (self.options.resizable && !self.options.collapsed) {
|
|
Ox.print('DRAGSTART')
|
|
self.drag = {
|
|
startPos: e[self.clientXY],
|
|
startSize: self.options.size
|
|
}
|
|
} else { Ox.print('NO DRAGSTART r !c', self.options.resizable, !self.options.collapsed) }
|
|
}
|
|
|
|
function drag(event, e) {
|
|
if (self.options.resizable && !self.options.collapsed) {
|
|
var d = e[self.clientXY] - self.drag.startPos,
|
|
size = self.options.size;
|
|
self.options.size = Ox.limit(
|
|
self.drag.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')
|
|
self.options.elements[1]
|
|
.css(self.edges[2], (self.options.size + 1) + 'px')
|
|
} else {
|
|
self.options.elements[0]
|
|
.css(self.edges[3], (self.options.size + 1) + 'px')
|
|
self.options.elements[1]
|
|
.css(self.dimensions[1], self.options.size + 'px')
|
|
}
|
|
triggerEvents('resize');
|
|
self.options.parent.updateSize(self.leftOrTop ? 0 : 1, self.options.size); // fixme: listen to event instead?
|
|
}
|
|
}
|
|
}
|
|
|
|
function dragend() {
|
|
if (self.options.resizable && !self.options.collapsed) {
|
|
self.options.size != self.drag.startSize && triggerEvents('resizeend');
|
|
}
|
|
}
|
|
|
|
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]]());
|
|
});
|
|
*/
|
|
}
|
|
|
|
function triggerEvents(event) {
|
|
self.options.elements[0].triggerEvent(event,
|
|
self.leftOrTop ?
|
|
self.options.size :
|
|
self.options.elements[0][self.dimensions[1]]()
|
|
);
|
|
self.options.elements[1].triggerEvent(event,
|
|
self.leftOrTop ?
|
|
self.options.elements[1][self.dimensions[1]]() :
|
|
self.options.size
|
|
);
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
/*
|
|
============================================================================
|
|
Calendars
|
|
============================================================================
|
|
*/
|
|
|
|
Ox.Calendar = function(options, self) {
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
dates: [],
|
|
height: 256,
|
|
width: 256
|
|
})
|
|
.options(options || {});
|
|
|
|
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
|
|
.dblclick(center)
|
|
.bindEvent({
|
|
dragstart: dragstart,
|
|
drag: drag
|
|
});
|
|
|
|
that.$title = new Ox.Element()
|
|
.addClass('OxTitle')
|
|
.html(self.options.title)
|
|
.appendTo(that.$titlebar);
|
|
|
|
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 dragstart(event, e) {
|
|
self.drag = {
|
|
bodyWidth: $body.width(),
|
|
bodyHeight: $document.height(),
|
|
elementWidth: that.width(),
|
|
offset: that.offset(),
|
|
x: e.clientX,
|
|
y: e.clientY
|
|
};
|
|
that.css({
|
|
margin: 0
|
|
});
|
|
}
|
|
|
|
function drag(event, e) {
|
|
var left = Ox.limit(
|
|
self.drag.offset.left - self.drag.x + e.clientX,
|
|
24 - self.drag.elementWidth, self.drag.bodyWidth - 24
|
|
//0, self.drag.documentWidth - self.drag.elementWidth
|
|
),
|
|
top = Ox.limit(
|
|
self.drag.offset.top - self.drag.y + e.clientY,
|
|
24, self.drag.bodyHeight - 24
|
|
//24, self.drag.documentHeight - self.drag.elementHeight
|
|
);
|
|
that.css({
|
|
left: left + 'px',
|
|
top: top + 'px'
|
|
});
|
|
}
|
|
|
|
function dragstartResize(event, e) {
|
|
self.drag = {
|
|
documentWidth: $document.width(),
|
|
documentHeight: $document.height(),
|
|
elementWidth: that.width(),
|
|
elementHeight: that.height(),
|
|
offset: that.offset(),
|
|
x: e.clientX,
|
|
y: e.clientY
|
|
};
|
|
$.extend(self.drag, {
|
|
ratio: self.drag.elementWidth / self.drag.elementHeight
|
|
});
|
|
that.css({
|
|
left: self.drag.offset.left,
|
|
top: self.drag.offset.top,
|
|
margin: 0
|
|
});
|
|
}
|
|
|
|
function dragResize(event, e) {
|
|
if (!e.shiftKey) {
|
|
self.drag.ratio = self.options.width / self.options.height;
|
|
}
|
|
self.options.width = Ox.limit(
|
|
self.drag.elementWidth - self.drag.x + e.clientX,
|
|
self.options.minWidth,
|
|
Math.min(
|
|
self.drag.documentWidth,
|
|
self.drag.documentWidth - self.drag.offset.left
|
|
)
|
|
);
|
|
self.options.height = Ox.limit(
|
|
self.drag.elementHeight - self.drag.y + e.clientY,
|
|
self.options.minHeight,
|
|
Math.min(
|
|
self.drag.documentHeight,
|
|
self.drag.documentHeight - self.drag.offset.top
|
|
)
|
|
);
|
|
if (e.shiftKey) {
|
|
self.options.height = Ox.limit(
|
|
self.options.width / self.drag.ratio,
|
|
self.options.minHeight,
|
|
Math.min(
|
|
self.drag.documentHeight,
|
|
self.drag.documentHeight - self.drag.offset.top
|
|
)
|
|
);
|
|
self.options.width = self.options.height * self.drag.ratio;
|
|
}
|
|
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
|
|
}
|
|
|
|
function dragendResize(event, e) {
|
|
triggerResizeEvent();
|
|
}
|
|
|
|
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')
|
|
.dblclick(reset)
|
|
.bindEvent({
|
|
dragstart: dragstartResize,
|
|
drag: dragResize,
|
|
dragend: dragendResize
|
|
})
|
|
.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 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) {
|
|
|
|
/***
|
|
Options:
|
|
Methods:
|
|
Events:
|
|
***/
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
findKeys: [],
|
|
query: {
|
|
conditions: [],
|
|
operator: '&'
|
|
},
|
|
sortKeys: [],
|
|
viewKeys: []
|
|
})
|
|
.options(options || {});
|
|
|
|
Ox.print('Ox.Filter self.options', self.options)
|
|
|
|
$.extend(self, {
|
|
conditionOperators: {
|
|
date: [
|
|
{id: '', title: 'is'},
|
|
{id: '!', title: 'is not'},
|
|
{id: '<', title: 'is before'},
|
|
{id: '>', title: 'is after'},
|
|
{id: '>&<', title: 'is between'},
|
|
{id: '<|>', title: 'is not between'}
|
|
],
|
|
list: [
|
|
{id: '', title: 'is'},
|
|
{id: '!', title: 'is not'}
|
|
],
|
|
number: [
|
|
{id: '', title: 'is'},
|
|
{id: '!', title: 'is not'},
|
|
{id: '<', title: 'is less than'},
|
|
{id: '>', title: 'is greater than'},
|
|
{id: '>&<', title: 'is between'},
|
|
{id: '<|>', title: 'is not between'}
|
|
],
|
|
string: [
|
|
{id: '=', title: 'is'},
|
|
{id: '!=', title: 'is not'},
|
|
{id: '^', title: 'begins with'},
|
|
{id: '$', title: 'ends with'},
|
|
{id: '', title: 'contains'},
|
|
{id: '!', title: 'does not contain'}
|
|
],
|
|
text: [
|
|
{id: '', title: 'contains'},
|
|
{id: '!', title: 'does not contain'}
|
|
]
|
|
},
|
|
operators: [
|
|
{id: '&', title: 'all'},
|
|
{id: '|', title: 'any'}
|
|
]
|
|
});
|
|
|
|
if (!self.options.query.conditions.length) {
|
|
self.options.query.conditions = [{
|
|
key: self.options.findKeys[0].id,
|
|
value: '',
|
|
operator: self.conditionOperators[
|
|
getConditionType(self.options.findKeys[0].type)
|
|
][0].id
|
|
}];
|
|
}
|
|
|
|
self.$operator = new Ox.FormElementGroup({
|
|
elements: [
|
|
new Ox.Label({
|
|
title: 'Match',
|
|
overlap: 'right',
|
|
width: 48
|
|
}),
|
|
new Ox.FormElementGroup({
|
|
elements: [
|
|
new Ox.Select({
|
|
items: self.operators,
|
|
width: 48
|
|
})
|
|
.bindEvent({
|
|
change: changeOperator
|
|
}),
|
|
new Ox.Label({
|
|
overlap: 'left',
|
|
title: 'of the following conditions',
|
|
width: 160
|
|
})
|
|
],
|
|
float: 'right',
|
|
width: 208
|
|
})
|
|
],
|
|
float: 'left',
|
|
});
|
|
|
|
self.$buttons = [];
|
|
self.$conditions = $.map(self.options.query.conditions, function(condition, i) {
|
|
return constructCondition(condition, i);
|
|
});
|
|
|
|
self.$limit = new Ox.InputGroup({
|
|
inputs: [
|
|
new Ox.Checkbox({
|
|
width: 16
|
|
}),
|
|
new Ox.FormElementGroup({
|
|
elements: [
|
|
new Ox.Input({
|
|
width: 56
|
|
}),
|
|
new Ox.Select({
|
|
items: [
|
|
{id: 'items', title: 'items'},
|
|
{},
|
|
{id: 'hours', title: 'hours'},
|
|
{id: 'days', title: 'days'},
|
|
{},
|
|
{id: 'GB', title: 'GB'}
|
|
],
|
|
overlap: 'left',
|
|
width: 64
|
|
})
|
|
],
|
|
float: 'right',
|
|
width: 120
|
|
}),
|
|
new Ox.Select({
|
|
items: self.options.sortKeys,
|
|
width: 128
|
|
}),
|
|
new Ox.FormElementGroup({
|
|
elements: [
|
|
new Ox.Select({
|
|
items: [
|
|
{id: 'ascending', title: 'ascending'},
|
|
{id: 'descending', title: 'descending'}
|
|
],
|
|
width: 96
|
|
}),
|
|
new Ox.Label({
|
|
overlap: 'left',
|
|
title: 'order',
|
|
width: 72
|
|
})
|
|
],
|
|
float: 'right',
|
|
width: 168
|
|
})
|
|
],
|
|
separators: [
|
|
{title: 'Limit to', width: 56},
|
|
{title: 'sorted by', width: 64},
|
|
{title: 'in', width: 32}
|
|
]
|
|
});
|
|
|
|
self.$view = new Ox.InputGroup({
|
|
inputs: [
|
|
new Ox.Checkbox({
|
|
width: 16
|
|
}),
|
|
new Ox.Select({
|
|
items: self.options.viewKeys,
|
|
width: 128
|
|
})
|
|
],
|
|
separators: [
|
|
{title: 'By default, view', width: 112}
|
|
]
|
|
});
|
|
|
|
self.$save = new Ox.InputGroup({
|
|
inputs: [
|
|
new Ox.Checkbox({
|
|
width: 16
|
|
}),
|
|
new Ox.Input({
|
|
id: 'list',
|
|
width: 128
|
|
})
|
|
],
|
|
separators: [
|
|
{title: 'Save as Smart List', width: 112}
|
|
]
|
|
});
|
|
|
|
self.$items = $.merge($.merge([self.$operator], self.$conditions), [self.$limit, self.$view, self.$save]);
|
|
|
|
self.$form = new Ox.Form({
|
|
items: self.$items
|
|
});
|
|
that.$element = self.$form.$element;
|
|
|
|
function addCondition(pos) {
|
|
var key = self.options.findKeys[0];
|
|
self.options.query.conditions.splice(pos, 0, {
|
|
key: key.id,
|
|
value: '',
|
|
operator: self.conditionOperators[key.type][0].id
|
|
});
|
|
self.$conditions.splice(pos, 0, constructCondition({}, pos));
|
|
updateConditions();
|
|
self.$form.addItem(pos + 1, self.$conditions[pos]);
|
|
}
|
|
|
|
function addGroup(pos) {
|
|
self.$form.addItem(pos + 1, constructGroup(pos))
|
|
}
|
|
|
|
function changeConditionKey(pos, key) {
|
|
Ox.print('changeConditionKey', pos, key);
|
|
var oldOperator = self.options.query.conditions[pos].operator,
|
|
oldType = Ox.getObjectById(
|
|
self.options.findKeys, self.options.query.conditions[pos].key
|
|
).type,
|
|
newType = Ox.getObjectById(
|
|
self.options.findKeys, key
|
|
).type,
|
|
oldConditionType = getConditionType(oldType),
|
|
newConditionType = getConditionType(newType);
|
|
changeConditionType = oldConditionType != newConditionType;
|
|
Ox.print('old new', oldConditionType, newConditionType)
|
|
self.options.query.conditions[pos].key = key;
|
|
if (changeConditionType) {
|
|
self.$conditions[pos].replaceElement(1, constructConditionOperator(pos, oldOperator));
|
|
}
|
|
}
|
|
|
|
function changeConditionOperator(pos, operator) {
|
|
self.options.query.conditions[pos].operator = operator;
|
|
}
|
|
|
|
function changeOperator(event, data) {
|
|
self.options.query.operator = data.selected[0].id;
|
|
}
|
|
|
|
function constructCondition(condition, pos) {
|
|
var $condition;
|
|
return $condition = new Ox.FormElementGroup({
|
|
elements: [
|
|
new Ox.Select({
|
|
items: $.map(self.options.findKeys, function(key) {
|
|
return {
|
|
id: key.id,
|
|
title: key.title
|
|
};
|
|
}),
|
|
//items: $.extend({}, self.options.findKeys), // fixme: Ox.Menu messes with keys
|
|
overlap: 'right',
|
|
width: 128
|
|
})
|
|
.bindEvent({
|
|
change: function(event, data) {
|
|
Ox.print('event', event)
|
|
changeConditionKey($condition.data('position'), data.selected[0].id);
|
|
}
|
|
}),
|
|
constructConditionOperator(pos),
|
|
new Ox.Input({
|
|
width: 256
|
|
}),
|
|
new Ox.Button({
|
|
disabled: self.options.query.conditions.length == 1,
|
|
id: 'remove',
|
|
title: 'remove',
|
|
type: 'image'
|
|
})
|
|
.css({margin: '0 4px 0 8px'})
|
|
.bindEvent({
|
|
click: function() {
|
|
removeCondition($condition.data('position'));
|
|
}
|
|
}),
|
|
new Ox.Button({
|
|
id: 'add',
|
|
title: 'add',
|
|
type: 'image'
|
|
})
|
|
.css({margin: '0 4px 0 4px'})
|
|
.bindEvent({
|
|
click: function() {
|
|
Ox.print('add', $(this).parent().parent().data('position'))
|
|
addCondition($condition.data('position') + 1)
|
|
}
|
|
}),
|
|
new Ox.Button({
|
|
id: 'addgroup',
|
|
title: 'more',
|
|
type: 'image'
|
|
})
|
|
.css({margin: '0 0 0 4px'})
|
|
.bindEvent({
|
|
click: function() {
|
|
addGroup($condition.data('position') + 1)
|
|
}
|
|
})
|
|
]
|
|
})
|
|
.data({position: pos});
|
|
}
|
|
|
|
function constructConditionOperator(pos, selected) {
|
|
return new Ox.Select({
|
|
items: $.map(self.conditionOperators[getConditionType(
|
|
Ox.getObjectById(
|
|
self.options.findKeys,
|
|
self.options.query.conditions[pos].key
|
|
).type
|
|
)], function(operator) {
|
|
return {
|
|
checked: operator.id == selected, // fixme: should be "selected", not "checked"
|
|
id: operator.operator,
|
|
title: operator.title
|
|
};
|
|
}),
|
|
overlap: 'right',
|
|
width: 128
|
|
})
|
|
.bindEvent({
|
|
change: function(event, data) {
|
|
changeConditionOperator(/*$condition.data('position')*/ pos, data.selected[0].id)
|
|
}
|
|
});
|
|
}
|
|
|
|
function constructGroup() {
|
|
// fixme: duplicated
|
|
return new Ox.FormElementGroup({
|
|
elements: [
|
|
new Ox.Label({
|
|
title: self.options.operator == '&' ? 'and' : 'or',
|
|
overlap: 'right',
|
|
width: 48
|
|
}),
|
|
new Ox.FormElementGroup({
|
|
elements: [
|
|
new Ox.Select({
|
|
items: $.map(self.operators, function(operator) {
|
|
Ox.print('!!!!', {
|
|
checked: operator.id != self.options.operator,
|
|
id: operator.id,
|
|
title: operator.title
|
|
});
|
|
return {
|
|
//checked: operator.id != self.options.operator,
|
|
id: operator.id,
|
|
title: operator.title
|
|
}
|
|
}),
|
|
width: 48
|
|
})
|
|
.bindEvent({
|
|
change: changeOperator
|
|
}),
|
|
new Ox.Label({
|
|
overlap: 'left',
|
|
title: 'of the following conditions',
|
|
width: 160
|
|
})
|
|
],
|
|
float: 'right',
|
|
width: 208
|
|
})
|
|
],
|
|
float: 'left',
|
|
});
|
|
}
|
|
|
|
function getConditionType(type) {
|
|
type = Ox.isArray(type) ? type[0] : type;
|
|
if (['float', 'integer', 'year'].indexOf(type) > -1) {
|
|
type = 'number';
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function removeCondition(pos) {
|
|
self.options.query.conditions.splice(pos, 1);
|
|
self.$conditions.splice(pos, 1);
|
|
updateConditions();
|
|
self.$form.removeItem(pos + 1);
|
|
}
|
|
|
|
function updateConditions() {
|
|
self.$conditions.forEach(function(condition, pos) {
|
|
condition.data({position: pos});
|
|
});
|
|
self.$conditions[0].options('elements')[3].options({
|
|
disabled: self.options.query.conditions.length == 1
|
|
});
|
|
}
|
|
|
|
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}));
|
|
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);
|
|
data.valid && self.$items[i].setMessage('');
|
|
},
|
|
submit: function(event, data) {
|
|
self.formIsValid && that.submit();
|
|
},
|
|
validate: function(event, data) {
|
|
validate(i, data.valid);
|
|
self.$items[i].setMessage(data.valid ? '' : data.message);
|
|
}
|
|
});
|
|
});
|
|
|
|
function getItemPositionById(id) {
|
|
return self.itemIds.indexOf(id);
|
|
}
|
|
|
|
function submitCallback(data) {
|
|
$.each(data, function(i, v) {
|
|
self.$items[i].setMessage(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.addItem = function(pos, item) {
|
|
Ox.print('addItem', pos)
|
|
self.options.items.splice(pos, 0, item);
|
|
self.$items.splice(pos, 0, new Ox.FormItem({element: item}));
|
|
pos == 0 ?
|
|
self.$items[pos].insertBefore(self.$items[0]) :
|
|
self.$items[pos].insertAfter(self.$items[pos - 1]);
|
|
}
|
|
|
|
that.removeItem = function(pos) {
|
|
Ox.print('removeItem', pos);
|
|
self.$items[pos].remove();
|
|
self.options.items.splice(pos, 1);
|
|
self.$items.splice(pos, 1);
|
|
}
|
|
|
|
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);
|
|
|
|
self.$message = new Ox.Element()
|
|
.addClass('OxFormMessage')
|
|
.appendTo(that);
|
|
|
|
that.setMessage = function(message) {
|
|
self.$message.html(message)[message !== '' ? 'show' : 'hide']();
|
|
}
|
|
|
|
that.value = function() {
|
|
return self.options.element.value();
|
|
};
|
|
|
|
return that;
|
|
|
|
}
|
|
|
|
/**
|
|
Form Elements
|
|
*/
|
|
|
|
Ox.Button = function(options, self) {
|
|
|
|
/**
|
|
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
|
|
*/
|
|
|
|
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 {
|
|
//self.options.selected = !self.options.selected;
|
|
//that.toggleClass('OxSelected');
|
|
if (self.options.group) {
|
|
that.triggerEvent('select', data);
|
|
} else {
|
|
that.toggleSelected();
|
|
//that.triggerEvent('change', {selected: self.options.selected});
|
|
}
|
|
}
|
|
if (self.titles.length == 2) {
|
|
that.toggleTitle();
|
|
}
|
|
}
|
|
}
|
|
|
|
function mousedown(e) {
|
|
if (self.options.type == 'image' && $.browser.safari) {
|
|
// keep image from being draggable
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
|
|
function mouseenter(e) {
|
|
self.$tooltip.show(e.clientX, e.clientY);
|
|
}
|
|
|
|
function mouseleave() {
|
|
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
|
|
});
|
|
//self.options.disabled = !self.options.disabled;
|
|
}
|
|
|
|
that.toggleSelected = function() {
|
|
that.options({
|
|
selected: !self.options.selected
|
|
});
|
|
//self.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;
|
|
|
|
};
|
|
|
|
Ox.ButtonGroup = function(options, self) {
|
|
|
|
/**
|
|
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
|
|
*/
|
|
|
|
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;
|
|
};
|
|
|
|
Ox.Checkbox = function(options, self) {
|
|
|
|
/**
|
|
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}
|
|
*/
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
disabled: false,
|
|
id: '',
|
|
group: false,
|
|
checked: false,
|
|
overlap: 'none',
|
|
title: '',
|
|
width: 'auto'
|
|
})
|
|
.options(options || {})
|
|
.addClass('OxCheckbox' +
|
|
(self.options.overlap == 'none' ? '' : ' OxOverlap' +
|
|
Ox.toTitleCase(self.options.overlap))
|
|
)
|
|
.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;
|
|
|
|
};
|
|
|
|
Ox.CheckboxGroup = function(options, self) {
|
|
|
|
/**
|
|
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}
|
|
*/
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
Ox.Input = function(options, self) {
|
|
|
|
/**
|
|
options
|
|
arrows boolearn, if true, and type is 'float' or 'integer', display arrows
|
|
arrowStep number, step when clicking arrows
|
|
autocomplete array of possible values, or
|
|
function(key, value, callback), returns one or more values
|
|
autocompleteReplace boolean, if true, value is replaced
|
|
autocompleteReplaceCorrect boolean, if true, only valid values can be entered
|
|
autocompleteSelect boolean, if true, menu is displayed
|
|
autocompleteSelectHighlight boolean, if true, value in menu is highlighted
|
|
autocompleteSelectSubmit boolean, if true, submit input on menu selection
|
|
autocorrect string ('email', 'float', 'integer', 'phone', 'url'), or
|
|
regexp(value), or
|
|
function(key, value, blur, callback), returns value
|
|
autovalidate --remote validation--
|
|
clear boolean, if true, has clear button
|
|
disabled boolean, if true, is disabled
|
|
height integer, px (for type='textarea' and type='range' with orientation='horizontal')
|
|
id string, element id
|
|
key string, to be passed to autocomplete and autovalidate functions
|
|
max number, max value if type is 'integer' or 'float'
|
|
min number, min value if type is 'integer' or 'float'
|
|
name string, will be displayed by autovalidate function ('invalid ' + name)
|
|
overlap string, '', 'left' or 'right', will cause padding and negative margin
|
|
picker
|
|
//rangeOptions
|
|
arrows boolean, if true, display arrows
|
|
//arrowStep number, step when clicking arrows
|
|
//arrowSymbols array of two strings
|
|
max number, maximum value
|
|
min number, minimum value
|
|
orientation 'horizontal' or 'vertical'
|
|
step number, step
|
|
thumbValue boolean, if true, value is displayed on thumb, or
|
|
array of strings per value, or
|
|
function(value), returns string
|
|
thumbSize integer, px
|
|
trackGradient string, css gradient for track
|
|
trackImage string, image url, or
|
|
array of image urls
|
|
//trackStep number, 0 for 'scroll here', positive for step
|
|
trackValues boolean
|
|
serialize
|
|
textAlign 'left', 'center' or 'right'
|
|
type 'float', 'integer', 'password', 'text', 'textarea'
|
|
value string
|
|
validate function, remote validation
|
|
width integer, px
|
|
methods:
|
|
events:
|
|
change
|
|
submit
|
|
*/
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
arrows: false,
|
|
arrowStep: 1,
|
|
autocomplete: null,
|
|
autocompleteReplace: false,
|
|
autocompleteReplaceCorrect: false,
|
|
autocompleteSelect: false,
|
|
autocompleteSelectHighlight: false,
|
|
autocompleteSelectSubmit: false,
|
|
autovalidate: null,
|
|
clear: false,
|
|
disabled: false,
|
|
key: '',
|
|
min: 0,
|
|
max: 100,
|
|
label: '',
|
|
labelWidth: 64,
|
|
overlap: 'none',
|
|
placeholder: '',
|
|
serialize: null,
|
|
style: 'rounded',
|
|
textAlign: 'left',
|
|
type: 'text',
|
|
validate: null,
|
|
value: '',
|
|
width: 128
|
|
})
|
|
.options(options)
|
|
.addClass(
|
|
'OxInput OxMedium Ox' + Ox.toTitleCase(self.options.style) /*+ (
|
|
self.options.overlap != 'none' ?
|
|
' OxOverlap' + Ox.toTitleCase(self.options.overlap) : ''
|
|
)*/
|
|
)
|
|
.bindEvent($.extend(self.options.type == 'textarea' ? {} : {
|
|
key_enter: submit
|
|
}, {
|
|
key_control_v: paste,
|
|
key_escape: cancel
|
|
}));
|
|
|
|
if (
|
|
Ox.isArray(self.options.autocomplete) &&
|
|
self.options.autocompleteReplace &&
|
|
self.options.autocompleteReplaceCorrect &&
|
|
self.options.value === ''
|
|
) {
|
|
self.options.value = self.options.autocomplete[0]
|
|
}
|
|
|
|
// fixme: set to min, not 0
|
|
if (self.options.type == 'float') {
|
|
$.extend(self.options, {
|
|
autovalidate: 'float',
|
|
textAlign: 'right',
|
|
value: self.options.value || '0.0'
|
|
});
|
|
} else if (self.options.type == 'integer') {
|
|
$.extend(self.options, {
|
|
autovalidate: 'integer',
|
|
textAlign: 'right',
|
|
value: self.options.value || '0'
|
|
});
|
|
}
|
|
|
|
if (self.options.label) {
|
|
self.$label = new Ox.Label({
|
|
overlap: 'right',
|
|
textAlign: 'right',
|
|
title: self.options.label,
|
|
width: self.options.labelWidth
|
|
})
|
|
.css({
|
|
float: 'left', // fixme: use css rule
|
|
})
|
|
.click(function() {
|
|
// fixme: ???
|
|
// that.focus();
|
|
})
|
|
.appendTo(that);
|
|
}
|
|
|
|
if (self.options.arrows) {
|
|
self.arrows = [];
|
|
self.arrows[0] = [
|
|
new Ox.Button({
|
|
overlap: 'right',
|
|
title: '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 = $(self.options.type == 'textarea' ? '<textarea>' : '<input>')
|
|
.addClass('OxInput OxMedium Ox' + Ox.toTitleCase(self.options.style))
|
|
.attr({
|
|
disabled: self.options.disabled ? 'disabled' : '',
|
|
type: self.options.type == 'password' ? 'password' : 'text'
|
|
})
|
|
.css($.extend({
|
|
width: self.inputWidth + 'px',
|
|
textAlign: self.options.textAlign
|
|
}, self.options.type == 'textarea' ? {
|
|
height: self.options.height + 'px',
|
|
} : {}))
|
|
.val(self.options.value)
|
|
.blur(blur)
|
|
.change(change)
|
|
.focus(focus)
|
|
.appendTo(that.$element);
|
|
|
|
// fixme: is there a better way than this one?
|
|
// should at least go into ox.ui.theme.foo.js
|
|
// probably better: divs in the background
|
|
if (self.options.type == 'textarea') {
|
|
$.extend(self, {
|
|
colors: Ox.theme() == 'classic' ?
|
|
[208, 232, 244] :
|
|
//[0, 16, 32],
|
|
[32, 48, 64],
|
|
colorstops: [8 / self.options.height, self.options.height - 8 / self.options.height]
|
|
});
|
|
self.$input.css({
|
|
background: '-moz-linear-gradient(top, rgb(' +
|
|
[self.colors[0], self.colors[0], self.colors[0]].join(', ') + '), rgb(' +
|
|
[self.colors[1], self.colors[1], self.colors[1]].join(', ') + ') ' +
|
|
Math.round(self.colorstops[0] * 100) + '%, rgb(' +
|
|
[self.colors[1], self.colors[1], self.colors[1]].join(', ') + ') ' +
|
|
Math.round(self.colorstops[1] * 100) + '%, rgb(' +
|
|
[self.colors[2], self.colors[2], self.colors[2]].join(', ') + '))'
|
|
});
|
|
self.$input.css({
|
|
background: '-webkit-gradient(linear, left top, left bottom, from(rgb(' +
|
|
[self.colors[0], self.colors[0], self.colors[0]].join(', ') + ')), color-stop(' +
|
|
self.colorstops[0] + ', ' + 'rgb(' +
|
|
[self.colors[1], self.colors[1], self.colors[1]].join(', ') + ')), color-stop( ' +
|
|
self.colorstops[1] + ', ' + 'rgb(' +
|
|
[self.colors[1], self.colors[1], self.colors[1]].join(', ') + ')), to(rgb(' +
|
|
[self.colors[2], self.colors[2], self.colors[2]].join(', ') + ')))'
|
|
});
|
|
}
|
|
|
|
if (self.hasPasswordPlaceholder) {
|
|
self.$input.hide();
|
|
self.$placeholder = $('<input>')
|
|
.addClass('OxInput OxMedium Ox' +
|
|
Ox.toTitleCase(self.options.style) +
|
|
' OxPlaceholder')
|
|
.attr({
|
|
type: 'text'
|
|
})
|
|
.css({
|
|
//float: 'left',
|
|
width: self.inputWidth + 'px'
|
|
})
|
|
.val(self.options.placeholder)
|
|
.focus(focus)
|
|
.appendTo(that.$element);
|
|
}
|
|
|
|
if (self.options.autocomplete && self.options.autocompleteSelect) {
|
|
self.$autocompleteMenu = constructAutocompleteMenu();
|
|
}
|
|
|
|
self.options.placeholder && setPlaceholder();
|
|
|
|
function autocomplete(oldValue, oldCursor) {
|
|
|
|
oldValue = Ox.isUndefined(oldValue) ? self.options.value : oldValue;
|
|
oldCursor = Ox.isUndefined(oldCursor) ? cursor : oldCursor;
|
|
|
|
Ox.print('autocomplete', oldValue, oldCursor)
|
|
|
|
if (self.options.value || self.options.autocompleteReplaceCorrect) {
|
|
if(Ox.isFunction(self.options.autocomplete)) {
|
|
if(self.options.key) {
|
|
self.options.autocomplete(self.options.key,
|
|
self.options.value,
|
|
autocompleteCallback)
|
|
} else {
|
|
self.options.autocomplete(self.options.value,
|
|
autocompleteCallback)
|
|
}
|
|
} else {
|
|
autocompleteCallback(autocompleteFunction(self.options.value));
|
|
}
|
|
}
|
|
if (!self.options.value) {
|
|
self.options.autocompleteSelect && self.$autocompleteMenu.hideMenu();
|
|
}
|
|
|
|
function autocompleteFunction() {
|
|
var values = Ox.find(self.options.autocomplete, self.options.value);
|
|
return self.options.autocompleteReplace ? values[0] :
|
|
$.merge(values[0], values[1]);
|
|
}
|
|
|
|
function autocompleteCallback(values) {
|
|
|
|
//Ox.print('autocompleteCallback', values[0], self.options.value, self.options.value.length, oldValue, oldCursor)
|
|
|
|
var length = self.options.value.length,
|
|
deleted = length <= oldValue.length - (oldCursor[1] - oldCursor[0]),
|
|
newValue = values[0] ?
|
|
((self.options.autocompleteReplaceCorrect || !deleted) ?
|
|
values[0] : self.options.value) :
|
|
(self.options.autocompleteReplaceCorrect ? oldValue : self.options.value),
|
|
newLength = newValue.length,
|
|
pos = cursor(),
|
|
selected = -1,
|
|
selectEnd = length == 0 || (values[0] && values[0].length),
|
|
value;
|
|
|
|
//Ox.print('selectEnd', selectEnd)
|
|
|
|
if (self.options.autocompleteReplace) {
|
|
self.options.value = newValue;
|
|
self.$input.val(self.options.value);
|
|
if (selectEnd) {
|
|
cursor(length, newLength);
|
|
} else if (self.options.autocompleteReplaceCorrect) {
|
|
cursor(oldCursor);
|
|
} else {
|
|
cursor(pos);
|
|
}
|
|
selected = 0;
|
|
}
|
|
|
|
if (self.options.autocompleteSelect) {
|
|
value = self.options.value.toLowerCase();
|
|
if (values.length) {
|
|
self.oldCursor = cursor();
|
|
self.oldValue = self.options.value;
|
|
self.$autocompleteMenu.options({
|
|
items: $.map(values, function(v, i) {
|
|
if (value == v.toLowerCase()) {
|
|
selected = i;
|
|
}
|
|
return {
|
|
id: v.toLowerCase().replace(/ /g, '_'), // fixme: need function to do lowercase, underscores etc?
|
|
title: self.options.autocompleteSelectHighlight ? v.replace(
|
|
new RegExp('(' + value + ')', 'ig'),
|
|
'<span class="OxHighlight">$1</span>'
|
|
) : v
|
|
};
|
|
}),
|
|
selected: selected
|
|
}).showMenu();
|
|
} else {
|
|
self.$autocompleteMenu.hideMenu();
|
|
}
|
|
}
|
|
|
|
that.triggerEvent('autocomplete', {
|
|
value: newValue
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
function constructAutocompleteMenu() {
|
|
var menu = new Ox.Menu({
|
|
element: self.$input,
|
|
id: self.options.id + 'Menu', // fixme: we do this in other places ... are we doing it the same way? var name?,
|
|
offset: {
|
|
left: 4,
|
|
top: 0
|
|
},
|
|
size: self.options.size
|
|
})
|
|
.bindEvent('click', clickMenu);
|
|
if (self.options.autocompleteReplace) {
|
|
menu.bindEvent({
|
|
deselect: deselectMenu,
|
|
select: selectMenu,
|
|
});
|
|
}
|
|
return menu;
|
|
}
|
|
|
|
function autovalidate() {
|
|
|
|
var blur, oldCursor, oldValue;
|
|
|
|
if (arguments.length == 1) {
|
|
blur = arguments[0];
|
|
} else {
|
|
blur = false;
|
|
oldValue = arguments[0];
|
|
oldCursor = arguments[1];
|
|
}
|
|
|
|
if(Ox.isFunction(self.options.autovalidate)) {
|
|
if(self.options.key) {
|
|
self.options.autovalidate(self.options.key, self.options.value,
|
|
blur, autovalidateCallback);
|
|
} else {
|
|
self.options.autovalidate(self.options.value, blur,
|
|
autovalidateCallback);
|
|
}
|
|
} else {
|
|
if(Ox.isRegExp(self.options.autovalidate)) {
|
|
autovalidateCallback(autovalidateFunction(self.options.value));
|
|
} else {
|
|
autovalidateTypeFunction(self.options.type, self.options.value);
|
|
}
|
|
}
|
|
|
|
function autovalidateFunction(value) {
|
|
var regexp = new RegExp(self.options.autovalidate);
|
|
return $.map(value.split(''), function(v) {
|
|
return regexp(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() {
|
|
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 paste() {
|
|
var data = Ox.Clipboard.paste();
|
|
data.text && self.$input.val(data.text);
|
|
}
|
|
|
|
function selectMenu(event, data) {
|
|
var pos = cursor();
|
|
//Ox.print('selectMenu', pos)
|
|
self.options.value = data.title
|
|
self.$input.val(self.options.value);
|
|
cursor(pos[0], self.options.value.length)
|
|
}
|
|
|
|
function setPlaceholder() {
|
|
if (self.options.placeholder) {
|
|
if (that.hasClass('OxFocus')) {
|
|
if (self.options.value === '') {
|
|
if (self.options.type == 'password') {
|
|
self.$placeholder.hide();
|
|
self.$input.show().focusInput();
|
|
} else {
|
|
self.$input
|
|
.removeClass('OxPlaceholder')
|
|
.val('');
|
|
}
|
|
}
|
|
} else {
|
|
if (self.options.value === '') {
|
|
if (self.options.type == 'password') {
|
|
self.$input.hide();
|
|
self.$placeholder.show();
|
|
} else {
|
|
self.$input
|
|
.addClass('OxPlaceholder')
|
|
.val(self.options.placeholder)
|
|
}
|
|
} else {
|
|
self.$input
|
|
.removeClass('OxPlaceholder')
|
|
.val(self.options.value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function setWidth() {
|
|
|
|
}
|
|
|
|
function submit() {
|
|
self.$input.blur();
|
|
that.triggerEvent('submit', {
|
|
value: self.options.value
|
|
});
|
|
}
|
|
|
|
function validate() {
|
|
self.options.validate(self.options.value, function(data) {
|
|
that.triggerEvent('validate', data);
|
|
});
|
|
}
|
|
|
|
self.onChange = function(key, value) {
|
|
var inputWidth, val;
|
|
if (['autocomplete', 'autocompleteReplace', 'autocompleteSelect', 'autovalidate'].indexOf(key) > -1) {
|
|
if (self.options.autocomplete && self.options.autocompleteSelect) {
|
|
self.$autocompleteMenu = constructAutocompleteMenu();
|
|
}
|
|
self.bindKeyboard = self.options.autocomplete || self.options.autovalidate;
|
|
} else if (key == 'disabled') {
|
|
self.$input.attr({
|
|
disabled: value ? 'disabled' : ''
|
|
});
|
|
} else if (key == 'placeholder') {
|
|
setPlaceholder();
|
|
} else if (key == 'value') {
|
|
val = self.$input.val(); // fixme: ??
|
|
self.$input.val(value);
|
|
setPlaceholder();
|
|
} else if (key == 'width') {
|
|
inputWidth = getInputWidth();
|
|
self.$input.css({
|
|
width: inputWidth + 'px'
|
|
});
|
|
self.hasPasswordPlaceholder && self.$placeholder.css({
|
|
width: inputWidth + 'px'
|
|
});
|
|
}
|
|
};
|
|
|
|
that.focusInput = function() {
|
|
self.$input.focus();
|
|
cursor(0, self.$input.val().length);
|
|
return that;
|
|
};
|
|
|
|
that.value = function() {
|
|
return self.$input.hasClass('OxPlaceholder') ? '' : self.$input.val();
|
|
};
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
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.options.id == 'debug' && Ox.print('separator #' + i + ' ' + self.options.inputs[i].options('id') + ' ' + self.options.inputs[i].options('width'))
|
|
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].focusInput();
|
|
}
|
|
}
|
|
|
|
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;
|
|
})) + 2; // fixme: why + 2?
|
|
}
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
Ox.DateInput = function(options, self) {
|
|
|
|
/**
|
|
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
|
|
}
|
|
*/
|
|
|
|
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: new 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: new 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: new 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: new 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: new 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;
|
|
|
|
}
|
|
|
|
Ox.Range = function(options, self) {
|
|
|
|
/**
|
|
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
|
|
*/
|
|
|
|
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,
|
|
values: (self.options.max - self.options.min + self.options.step) /
|
|
self.options.step
|
|
});
|
|
setSizes();
|
|
|
|
if (self.options.arrows) {
|
|
self.$arrows = [];
|
|
$.each(Ox.range(0, 2), function(i) {
|
|
self.$arrows[i] = new Ox.Button({
|
|
overlap: i == 0 ? 'right' : 'left',
|
|
title: self.options.arrowSymbols[i],
|
|
type: 'image'
|
|
})
|
|
.addClass('OxArrow')
|
|
.bindEvent({
|
|
mousedown: function(event, e) {
|
|
clickArrow(e, i, true);
|
|
},
|
|
mouserepeat: function(event, e) {
|
|
clickArrow(e, i, false);
|
|
}
|
|
})
|
|
.appendTo(that.$element);
|
|
});
|
|
}
|
|
|
|
self.$track = new Ox.Element()
|
|
.addClass('OxTrack')
|
|
.css($.extend({
|
|
width: (self.trackSize - 2) + 'px'
|
|
}, self.trackImages == 1 ? {
|
|
background: 'rgb(0, 0, 0)'
|
|
} : {}))
|
|
.bindEvent({
|
|
mousedown: clickTrack,
|
|
drag: dragTrack
|
|
})
|
|
.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.$element);
|
|
$.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, animate) {
|
|
// fixme: shift doesn't work, see menu scrolling
|
|
setValue(self.options.value + self.options.arrowStep * (i == 0 ? -1 : 1) * (e.shiftKey ? 2 : 1), animate);
|
|
}
|
|
|
|
function clickTrack(event, e) {
|
|
// fixme: thumb ends up a bit too far on the right
|
|
var isThumb = $(e.target).hasClass('OxThumb');
|
|
self.drag = {
|
|
left: self.$track.offset().left,
|
|
offset: isThumb ? e.clientX - self.$thumb.offset().left - 8 /*self.thumbSize / 2*/ : 0
|
|
};
|
|
setValue(getVal(e.clientX - self.drag.left - self.drag.offset), !isThumb);
|
|
}
|
|
|
|
function dragTrack(event, e) {
|
|
setValue(getVal(e.clientX - self.drag.left - self.drag.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 setSizes() {
|
|
self.trackSize = self.options.size - self.options.arrows * 32;
|
|
self.thumbSize = Math.max(self.trackSize / self.values, self.options.thumbSize);
|
|
self.trackImageWidths = self.trackImages == 1 ? [self.trackSize - 16] :
|
|
Ox.divideInt(self.trackSize - 2, self.trackImages);
|
|
self.trackColorsStart = self.thumbSize / 2 / self.options.size;
|
|
self.trackColorsStep = (self.options.size - self.thumbSize) /
|
|
(self.trackColors - 1) / self.options.size;
|
|
self.$track && self.$track.css({
|
|
width: (self.trackSize - 2) + 'px'
|
|
});
|
|
self.$thumb && self.$thumb.options({
|
|
width: self.thumbSize
|
|
});
|
|
}
|
|
|
|
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 == 'size') {
|
|
setSizes();
|
|
} else if (key == 'trackColors') {
|
|
setTrackColors();
|
|
} else if (key == 'value') {
|
|
setThumb();
|
|
}
|
|
}
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
Ox.Select = function(options, self) {
|
|
|
|
// fixme: selected item needs attribute "checked", not "selected" ... that's strange
|
|
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'
|
|
})
|
|
// fixme: make default selection restorable
|
|
// or allow for extra action items below options
|
|
.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
|
|
});
|
|
|
|
Ox.print('Ox.Select', self.options)
|
|
|
|
$.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) {
|
|
that.triggerEvent('click', data);
|
|
}
|
|
|
|
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) {
|
|
return {
|
|
id: self.options.items[v].id,
|
|
title: self.options.items[v].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) {
|
|
|
|
};
|
|
|
|
that.replaceElement = function(pos, element) {
|
|
Ox.print('Ox.FormElementGroup replaceElement', pos, element)
|
|
self.options.elements[pos].replaceWith(element.$element);
|
|
self.options.elements[pos] = element;
|
|
};
|
|
|
|
that.value = function() {
|
|
return $.map(self.options.elements, function(element) {
|
|
var ret = null;
|
|
['checked', 'selected', 'value'].forEach(function(v) {
|
|
element[v] && (ret = element[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].focusInput();
|
|
})
|
|
.appendTo(that);
|
|
} else if (key.label.length > 1) {
|
|
//Ox.print('key.length > 1')
|
|
self.selectKeyId = self.options.id + Ox.toTitleCase(self.keyName) +
|
|
(self.options[self.keyName].length == 1 ? '' : keyPos);
|
|
//Ox.print('three', self.selectedKey, keyPos, self.selectedKey[keyPos]);
|
|
that.$key[keyPos] = new Ox.Select({
|
|
id: self.selectKeyId,
|
|
items: $.map(key.label, function(value, valuePos) {
|
|
return {
|
|
checked: valuePos == self.selectedKey[keyPos],
|
|
id: value.id,
|
|
group: self.selectKeyId, // fixme: same id, works here, but should be different
|
|
title: value.title
|
|
};
|
|
}),
|
|
overlap: 'right',
|
|
type: self.options.label ? 'text' : 'image',
|
|
width: self.options.label ? (self.options.label.length == 1 ? self.options.labelWidth : key.width) : self.placeholderWidth
|
|
})
|
|
.css({
|
|
float: 'left'
|
|
})
|
|
.appendTo(that);
|
|
that.bindEvent('change_' + self.selectKeyId, changeKey);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (self.options.clear) {
|
|
that.$clear = new Ox.Button({
|
|
overlap: 'left',
|
|
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].focusInput();
|
|
})
|
|
.appendTo(that);
|
|
} else if (self.options.unit.length > 1) {
|
|
self.selectUnitId = self.options.id + 'Unit';
|
|
that.$unit = new Ox.Select({
|
|
id: self.selectUnitId,
|
|
items: $.map(self.options.unit, function(unit, i) {
|
|
//Ox.print('unit', unit)
|
|
return {
|
|
checked: i == 0,
|
|
id: unit.id,
|
|
group: self.selectUnitId, // fixme: same id, works here, but should be different
|
|
title: unit.title
|
|
};
|
|
}),
|
|
overlap: 'left',
|
|
size: self.options.size,
|
|
width: self.options.unitWidth
|
|
})
|
|
.css({
|
|
float: 'right'
|
|
})
|
|
.appendTo(that);
|
|
}
|
|
|
|
if (self.values) {
|
|
that.$separator = [];
|
|
$.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].focusInput();
|
|
})
|
|
.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].focusInput();
|
|
//autocompleteCall();
|
|
} else {
|
|
that.$input[0].options({
|
|
placeholder: data.value // fixme: should be data.title
|
|
});
|
|
/*
|
|
if (that.$input.hasClass('OxPlaceholder')) {
|
|
that.$input.val(self.key.title);
|
|
//that.$input.focus();
|
|
} else {
|
|
that.$input.focus();
|
|
self.options.autosuggest && autosuggestCall();
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
function changeUnit() {
|
|
that.$input[0].focusInput();
|
|
}
|
|
|
|
function clear() {
|
|
$.each(that.$input, function(i, v) {
|
|
v.val('');
|
|
});
|
|
that.$input[0].focusInput();
|
|
}
|
|
|
|
function height(value) {
|
|
var stop = 8 / value;
|
|
if (self.options.type == 'textarea') {
|
|
that.$element
|
|
.height(value)
|
|
.css({
|
|
background: '-moz-linear-gradient(top, rgb(224, 224, 224), rgb(208, 208, 208) ' + (stop * 100) + '%, rgb(208, 208, 208) ' + (100 - stop * 100) + '%, rgb(192, 192, 192))'
|
|
})
|
|
.css({
|
|
background: '-webkit-gradient(linear, left top, left bottom, from(rgb(224, 224, 224)), color-stop(' + stop + ', rgb(208, 208, 208)), color-stop(' + (1 - stop) + ', rgb(208, 208, 208)), to(rgb(192, 192, 192)))'
|
|
});
|
|
that.$input
|
|
.height(value)
|
|
.css({
|
|
background: '-moz-linear-gradient(top, rgb(224, 224, 224), rgb(240, 240, 240) ' + (stop * 100) + '%, rgb(240, 240, 240) ' + (100 - stop * 100) + '%, rgb(255, 255, 255))'
|
|
})
|
|
.css({
|
|
background: '-webkit-gradient(linear, left top, left bottom, from(rgb(224, 224, 224)), color-stop(' + stop + ', rgb(240, 240, 240)), color-stop(' + (1 - stop) + ', rgb(240, 240, 240)), to(rgb(255, 255, 255)))'
|
|
});
|
|
}
|
|
}
|
|
|
|
function selectUnit() {
|
|
self.$selectUnitMenu.show();
|
|
}
|
|
|
|
function submit() {
|
|
//Ox.print('submit')
|
|
var value = that.$input.val();
|
|
that.$input.blur();
|
|
that.triggerEvent('submit', self.options.key ? {
|
|
key: self.options.key,
|
|
value: value
|
|
} : value);
|
|
}
|
|
|
|
function width(value) {
|
|
that.$element.width(value);
|
|
that.$input.width(
|
|
value - (self.options.type == 'textarea' ? 0 : 12) -
|
|
(self.options.label ? self.options.labelWidth : 0) -
|
|
(self.options.placeholder.length > 1 ? 16 : 0) -
|
|
(self.options.unit ? self.options.unitWidth : 0) -
|
|
(self.options.clear ? 16 : 0)
|
|
);
|
|
}
|
|
|
|
self.onChange = function(key, value) {
|
|
if (key == 'height') {
|
|
height(value);
|
|
} else if (key == 'width') {
|
|
width(value);
|
|
}
|
|
};
|
|
|
|
that.changeLabel = function(id) {
|
|
that.$key.html(Ox.getObjectById(self.options.label, id).title);
|
|
self.selectMenu.checkItem(id);
|
|
};
|
|
|
|
return that;
|
|
|
|
}
|
|
|
|
Ox.InputElement_ = function(options, self) {
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element(
|
|
options.type == 'textarea' ? 'textarea' : 'input', self
|
|
)
|
|
.defaults({
|
|
autocomplete: null,
|
|
autocorrect: null,
|
|
autosuggest: null,
|
|
autosuggestHighlight: false,
|
|
autosuggestSubmit: false,
|
|
autovalidate: null,
|
|
disabled: false,
|
|
height: 128,
|
|
id: '',
|
|
key: '',
|
|
parent: null,
|
|
placeholder: '',
|
|
size: 'medium',
|
|
type: 'text',
|
|
value: '',
|
|
width: 128
|
|
})
|
|
.options(options || {})
|
|
.addClass('OxInput Ox' + Ox.toTitleCase(self.options.size) + (
|
|
(self.options.placeholder && self.options.value === '') ?
|
|
' OxPlaceholder' : ''
|
|
))
|
|
.attr(self.options.type == 'textarea' ? {} : {
|
|
type: self.options.type
|
|
})
|
|
.css({
|
|
float: 'left',
|
|
width: (self.options.width - 14) + 'px'
|
|
})
|
|
.val(
|
|
(self.options.placeholder && self.options.value === '') ?
|
|
self.options.placeholder : self.options.value
|
|
)
|
|
.blur(blur)
|
|
.change(change)
|
|
.focus(focus);
|
|
|
|
//Ox.print('InputElement self.options', self.options)
|
|
|
|
self.bindKeyboard = self.options.autocomplete || self.options.autocorrect ||
|
|
self.options.autosuggest || self.options.autovalidate;
|
|
|
|
if (self.options.autosuggest) {
|
|
self.autosuggestId = self.options.id + 'Menu'; // fixme: we do this in other places ... are we doing it the same way? var name?
|
|
self.$autosuggestMenu = new Ox.Menu({
|
|
element: that.$element,
|
|
id: self.autosuggestId,
|
|
offset: {
|
|
left: 4,
|
|
top: 0
|
|
},
|
|
size: self.options.size
|
|
});
|
|
that.bindEvent('click_' + self.autosuggestId, clickMenu);
|
|
}
|
|
|
|
that.bindEvent($.extend(self.options.type == 'textarea' ? {} : {
|
|
key_enter: submit
|
|
}, {
|
|
key_escape: cancel
|
|
}));
|
|
|
|
function autocomplete(value) {
|
|
var value = value.toLowerCase(),
|
|
ret = '';
|
|
if (value !== '') {
|
|
$.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: null,
|
|
items: null,
|
|
keys: [],
|
|
max: -1,
|
|
min: 0,
|
|
orientation: 'both',
|
|
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,
|
|
items: self.options.items,
|
|
itemWidth: self.itemWidth,
|
|
keys: self.options.keys,
|
|
orientation: self.options.orientation,
|
|
keys: self.options.keys,
|
|
max: self.options.max,
|
|
min: self.options.min,
|
|
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 == 'items') {
|
|
that.$element.options(key, value);
|
|
} else if (key == 'paste') {
|
|
that.$element.options(key, value);
|
|
} else if (key == 'selected') {
|
|
that.$element.options(key, value);
|
|
}
|
|
}
|
|
|
|
that.closePreview = function() {
|
|
that.$element.closePreview();
|
|
};
|
|
|
|
that.paste = function(data) {
|
|
that.$element.paste(data);
|
|
return that;
|
|
};
|
|
|
|
that.reloadList = function() {
|
|
that.$element.reloadList();
|
|
return that;
|
|
};
|
|
|
|
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);
|
|
};
|
|
|
|
that.value = function(id, key, value) {
|
|
// fixme: make this accept id, {k: v, ...}
|
|
if (arguments.length == 1) {
|
|
return that.$element.value(id);
|
|
} else if (arguments.length == 2) {
|
|
return that.$element.value(id, key);
|
|
} else {
|
|
that.$element.value(id, key, value);
|
|
return that;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
|
|
/***
|
|
basic list object
|
|
Options
|
|
centered boolean if true, and orientation is 'horizontal',
|
|
then keep the selected item centered
|
|
construct function function(data), returns the list item HTML
|
|
items function function(callback) returns {items, size, ...}
|
|
function(data, callback) returns [items]
|
|
or array of items
|
|
Methods
|
|
Events
|
|
***/
|
|
|
|
var self = self || {},
|
|
that = new Ox.Container({}, self)
|
|
.defaults({
|
|
centered: false,
|
|
construct: null,
|
|
draggable: false,
|
|
format: [],
|
|
itemHeight: 16,
|
|
items: null,
|
|
itemWidth: 16,
|
|
keys: [],
|
|
max: -1,
|
|
min: 0,
|
|
orientation: 'vertical',
|
|
pageLength: 100,
|
|
selected: [],
|
|
sort: [],
|
|
sortable: false,
|
|
type: 'text',
|
|
unique: ''
|
|
})
|
|
.options(options || {})
|
|
.scroll(scroll);
|
|
|
|
that.$content.mousedown(_mousedown);
|
|
//that.bindEvent('doubleclick', function() {alert('d')})
|
|
/*
|
|
that.$content.bindEvent({ // fixme: port to new Ox mouse events
|
|
mousedown: mousedown,
|
|
singleclick: singleclick,
|
|
doubleclick: doubleclick,
|
|
dragstart: dragstart,
|
|
drag: drag,
|
|
dragend: dragend
|
|
});
|
|
*/
|
|
|
|
$.extend(self, {
|
|
$items: [],
|
|
$pages: [],
|
|
clickTimeout: 0,
|
|
dragTimeout: 0,
|
|
format: {},
|
|
itemMargin: self.options.type == 'text' ? 0 : 8, // 2 x 4 px margin ... fixme: the 2x should be computed later
|
|
keyboardEvents: {
|
|
key_control_c: copyItems,
|
|
key_control_n: addItem,
|
|
key_control_v: pasteItems,
|
|
key_control_x: cutItems,
|
|
key_delete: deleteItems,
|
|
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: []
|
|
});
|
|
self.options.max == -1 && $.extend(self.keyboardEvents, {
|
|
key_alt_control_a: invertSelection,
|
|
key_control_a: selectAll
|
|
});
|
|
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 == 'vertical') {
|
|
$.extend(self.keyboardEvents, {
|
|
key_left: function() {
|
|
triggerToggleEvent(false);
|
|
},
|
|
key_right: function() {
|
|
triggerToggleEvent(true);
|
|
}
|
|
});
|
|
} else 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) {
|
|
//alert('DRAGSTART')
|
|
Ox.print('DRAGSTART', e);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (Ox.isArray(self.options.items)) {
|
|
self.listLength = self.options.items.length;
|
|
loadItems();
|
|
} else {
|
|
updateQuery(self.options.selected);
|
|
}
|
|
that.bindEvent(self.keyboardEvents);
|
|
$window.resize(that.size); // fixme: this is not the widget's job
|
|
|
|
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 addItem() {
|
|
that.triggerEvent('add', {});
|
|
}
|
|
|
|
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 < getPageLength(page); i++
|
|
) {
|
|
// fixme: why does chainging fail here?
|
|
new Ox.ListItem({
|
|
construct: self.options.construct
|
|
}).appendTo($page);
|
|
}
|
|
//Ox.print('cEP done')
|
|
return $page;
|
|
}
|
|
|
|
function copyItems() {
|
|
var ids = getSelectedIds();
|
|
ids.length && that.triggerEvent('copy', {
|
|
ids: ids
|
|
});
|
|
/*
|
|
ids.length && self.options.copy && Ox.Clipboard.copy(
|
|
self.options.copy(
|
|
$.map(ids, function(id) {
|
|
return that.value(id);
|
|
})
|
|
)
|
|
);
|
|
*/
|
|
}
|
|
|
|
function cutItems() {
|
|
copyItems();
|
|
deleteItems();
|
|
}
|
|
|
|
function deleteItems() {
|
|
var ids = getSelectedIds();
|
|
ids.length && that.triggerEvent('delete', {
|
|
ids: ids
|
|
});
|
|
}
|
|
|
|
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 dragstart(event, e) { // fixme: doesn't work yet
|
|
self.drag = {
|
|
pos: findItemPosition(e)
|
|
};
|
|
$.extend(self.drag, {
|
|
id: self.$items[self.drag.pos].options('data')[self.options.unique],
|
|
startPos: self.drag.pos,
|
|
startY: e.clientY,
|
|
stopPos: self.drag.pos
|
|
});
|
|
self.$items[pos].addClass('OxDrag') // fixme: why does the class not work?
|
|
.css({
|
|
cursor: 'move',
|
|
});
|
|
}
|
|
|
|
function drag(event, e) { // fixme: doesn't work yet
|
|
var clientY = e.clientY - that.offset()['top'],
|
|
offset = clientY % 16,
|
|
position = Ox.limit(parseInt(clientY / 16), 0, self.$items.length - 1);
|
|
if (position < self.drag.pos) {
|
|
self.drag.stopPos = position + (offset > 8 ? 1 : 0);
|
|
} else if (position > self.drag.pos) {
|
|
self.drag.stopPos = position - (offset <= 8 ? 1 : 0);
|
|
}
|
|
if (self.drag.stopPos != self.drag.pos) {
|
|
moveItem(self.drag.pos, self.drag.stopPos);
|
|
self.drag.pos = self.drag.stopPos;
|
|
}
|
|
}
|
|
|
|
function dragend(event, e) { // fixme: doesn't work yet
|
|
var $item = self.$items[self.drag.pos];
|
|
$item.removeClass('OxDrag')
|
|
.css({
|
|
cursor: 'default',
|
|
});
|
|
that.triggerEvent('move', {
|
|
//id: id,
|
|
ids: $.map(self.$items, function($item) {
|
|
return $item.options('data')[self.options.unique];
|
|
})
|
|
//position: pos
|
|
});
|
|
}
|
|
|
|
function dragItem(pos, e) {
|
|
var $item = self.$items[pos],
|
|
id = self.$items[pos].options('data')[self.options.unique],
|
|
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');
|
|
$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 dropItem(id, pos) {
|
|
var $item = self.$items[pos];
|
|
$item.removeClass('OxDrag')
|
|
.css({
|
|
cursor: 'default',
|
|
});
|
|
that.triggerEvent('move', {
|
|
//id: id,
|
|
ids: $.map(self.$items, function($item) {
|
|
return $item.options('data')[self.options.unique];
|
|
})
|
|
//position: pos
|
|
});
|
|
}
|
|
|
|
function emptyFirstPage() {
|
|
//Ox.print('emptyFirstPage', self.$pages);
|
|
self.$pages[0] && self.$pages[0].find('.OxEmpty').remove();
|
|
}
|
|
|
|
function fillFirstPage() {
|
|
Ox.print('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 findItemPosition(e) {
|
|
//Ox.print('---- findItem', e.target)
|
|
var $element = $(e.target),
|
|
position = -1;
|
|
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')) {
|
|
position = $element.data('position');
|
|
}
|
|
}
|
|
return position;
|
|
}
|
|
|
|
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 :
|
|
getPageLength(page) * (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 getPositionById(id) {
|
|
// fixme: is this really needed?
|
|
var pos = -1;
|
|
$.each(self.$items, function(i, $item) {
|
|
if ($item.options('data')[self.options.unique] == id) {
|
|
pos = i;
|
|
return false;
|
|
}
|
|
});
|
|
return pos;
|
|
}
|
|
|
|
function getPositions(ids) {
|
|
Ox.print('getPositions', ids)
|
|
ids = ids || getSelectedIds();
|
|
Ox.print('getPositions', ids)
|
|
// 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.items({
|
|
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 getScrollPosition() {
|
|
// 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 getSelectedIds() {
|
|
//Ox.print('gSI', self.selected, self.$items)
|
|
return $.map(self.selected, function(pos) {
|
|
//Ox.print('....', pos, self.options.unique, self.$items[pos].options('data')[self.options.unique])
|
|
return self.$items[pos].options('data')[self.options.unique];
|
|
});
|
|
}
|
|
|
|
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 loadItems() {
|
|
that.$content.empty();
|
|
self.options.items.forEach(function(item, pos) {
|
|
// fixme: duplicated
|
|
self.$items[pos] = new Ox.ListItem({
|
|
construct: self.options.construct,
|
|
data: item,
|
|
draggable: self.options.draggable,
|
|
position: pos,
|
|
unique: self.options.unique
|
|
});
|
|
isSelected(pos) && self.$items[pos].addClass('OxSelected');
|
|
self.$items[pos].appendTo(that.$content);
|
|
});
|
|
}
|
|
|
|
function getPageLength(page) {
|
|
var mod = self.listLength % self.pageLength;
|
|
return page < self.pages - 1 || mod == 0 ? self.pageLength : mod;
|
|
}
|
|
|
|
function loadPage(page, callback) {
|
|
if (page < 0 || page >= self.pages) {
|
|
!Ox.isUndefined(callback) && callback();
|
|
return;
|
|
}
|
|
//Ox.print('loadPage', page);
|
|
var keys = $.merge(self.options.keys.indexOf(self.options.unique) == -1 ? [self.options.unique] : [], self.options.keys),
|
|
offset = page * self.pageLength,
|
|
range = [offset, offset + getPageLength(page)];
|
|
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.items({
|
|
keys: keys,
|
|
range: range,
|
|
sort: self.options.sort
|
|
}, function(result) {
|
|
var $emptyPage = Ox.clone(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,
|
|
position: pos,
|
|
unique: self.options.unique
|
|
});
|
|
isSelected(pos) && 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(); // fixme: callback necessary? why not bind to event?
|
|
}));
|
|
} 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() {
|
|
if (++counter == 3) {
|
|
!Ox.isUndefined(callback) && callback();
|
|
that.triggerEvent('load');
|
|
}
|
|
};
|
|
// fixme: find out which option is better
|
|
/*
|
|
loadPage(page, function() {
|
|
loadPage(page - 1, fn);
|
|
loadPage(page + 1, fn);
|
|
});
|
|
*/
|
|
loadPage(page, fn);
|
|
loadPage(page - 1, fn);
|
|
loadPage(page + 1, fn);
|
|
}
|
|
|
|
function mousedown(event, e) { // fixme: doesn't work yet
|
|
var pos = findItemPosition(e);
|
|
self.hadFocus = that.hasFocus();
|
|
that.gainFocus();
|
|
if (pos > -1) {
|
|
if (e.metaKey) {
|
|
if (!isSelected(pos) && (self.options.max == -1 || self.options.max > self.selected.length)) {
|
|
// meta-click on unselected item
|
|
addToSelection(pos);
|
|
} else if (isSelected(pos) && self.options.min < self.selected.length) {
|
|
// meta-click on selected item
|
|
deselect(pos);
|
|
}
|
|
} else if (e.shiftKey) {
|
|
if (self.options.max == -1) {
|
|
// shift-click on item
|
|
addAllToSelection(pos);
|
|
}
|
|
} else if (!isSelected(pos)) {
|
|
// click on unselected item
|
|
select(pos);
|
|
}
|
|
} else if (self.options.min == 0) {
|
|
// click on empty area
|
|
selectNone();
|
|
}
|
|
}
|
|
|
|
function singleclick(event, e) { // fixme: doesn't work yet
|
|
// these can't trigger on mousedown,
|
|
// since it could be a doubleclick
|
|
var pos = findItemPosition(e),
|
|
clickable, editable;
|
|
alert('singleclick')
|
|
if (pos > -1) {
|
|
if (!e.metaKey && !e.shiftKey && isSelected(pos)) {
|
|
alert('??')
|
|
if (self.selected.length > 1) {
|
|
// click on one of multiple selected items
|
|
alert('!!')
|
|
select(pos);
|
|
} else if (self.options.type == 'text' && self.hadFocus) {
|
|
$cell = findCell(e);
|
|
if ($cell) {
|
|
clickable = $cell.hasClass('OxClickable');
|
|
editable = $cell.hasClass('OxEditable') && !$cell.hasClass('OxEdit');
|
|
if (clickable || editable) {
|
|
// click on a clickable or editable cell
|
|
triggerClickEvent(clickable ? 'click' : 'edit', self.$items[pos], $cell);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function doubleclick(event, e) { // fixme: doesn't work yet
|
|
alert('doubleclick')
|
|
open();
|
|
}
|
|
|
|
function _mousedown(e) {
|
|
var pos = findItemPosition(e),
|
|
clickable, editable,
|
|
clickTimeout = false,
|
|
selectTimeout = false,
|
|
$element,
|
|
hadFocus = that.hasFocus();
|
|
//Ox.print('mousedown', pos)
|
|
that.gainFocus();
|
|
if (pos > -1) {
|
|
if (!self.clickTimeout) {
|
|
// click
|
|
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)) {
|
|
Ox.print('select', 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' && hadFocus) {
|
|
var $cell = findCell(e),
|
|
$element = $cell || self.$items[pos];
|
|
clickable = $element.hasClass('OxClickable');
|
|
editable = $element.hasClass('OxEditable') && !$element.hasClass('OxEdit');
|
|
if (clickable || editable) {
|
|
if (self.options.sortable && self.listLength > 1) {
|
|
clickTimeout = true;
|
|
} else {
|
|
!$cell && that.editItem(pos);
|
|
triggerClickEvent(clickable ? 'click' : 'edit', self.$items[pos], $cell);
|
|
}
|
|
}
|
|
}
|
|
self.clickTimeout = setTimeout(function() {
|
|
self.clickTimeout = 0;
|
|
if (selectTimeout) {
|
|
select(pos);
|
|
}
|
|
}, 250);
|
|
if (self.options.sortable && self.listLength > 1) {
|
|
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 (clickTimeout) {
|
|
triggerClickEvent(clickable ? 'click' : 'edit', self.$items[pos], $cell);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
// dblclick
|
|
clearTimeout(self.clickTimeout);
|
|
self.clickTimeout = 0;
|
|
open();
|
|
}
|
|
} else if (!$(e.target).hasClass('OxToggle') && self.options.min == 0) {
|
|
selectNone();
|
|
}
|
|
}
|
|
|
|
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];
|
|
self.$items.splice(stopPos, 0, $item);
|
|
self.$items.forEach(function($item, pos) {
|
|
$item.data({position: pos});
|
|
});
|
|
self.selected = [stopPos];
|
|
//Ox.print('ids', self.ids, $.map(self.$items, function(v, i) { return v.data('id'); }));
|
|
}
|
|
|
|
function open() {
|
|
var ids = getSelectedIds();
|
|
ids.length && that.triggerEvent('open', {
|
|
ids: ids
|
|
});
|
|
}
|
|
|
|
function pasteItems() {
|
|
that.triggerEvent('paste', Ox.Clipboard.paste());
|
|
}
|
|
|
|
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);
|
|
that.gainFocus();
|
|
}
|
|
|
|
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() {
|
|
Ox.range(self.listLength).forEach(function(pos) {
|
|
addToSelection(pos);
|
|
});
|
|
}
|
|
|
|
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) {
|
|
// 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');
|
|
}
|
|
});
|
|
ids.forEach(function(id, i) {
|
|
var pos = getPositionById(id);
|
|
self.selected.push(pos);
|
|
!Ox.isUndefined(self.$items[pos]) &&
|
|
self.$items[pos].addClass('OxSelected');
|
|
});
|
|
}
|
|
|
|
function toggleSelection(pos) {
|
|
if (!isSelected(pos)) {
|
|
addToSelection(pos);
|
|
} else {
|
|
deselect(pos);
|
|
}
|
|
}
|
|
|
|
function triggerClickEvent(event, $item, $cell) {
|
|
// event can be 'click' or 'edit'
|
|
that.triggerEvent(event, $.extend({
|
|
id: $item.data('id')
|
|
}, $cell ? {
|
|
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_, Ox.isEqual(ids, ids_))
|
|
if (Ox.isEqual(ids, ids_)) {
|
|
that.triggerEvent('select', {
|
|
ids: ids
|
|
});
|
|
self.preview && that.triggerEvent('openpreview', {
|
|
ids: ids
|
|
});
|
|
} else {
|
|
Ox.print('select event not triggered after timeout');
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
function triggerToggleEvent(expanded) {
|
|
that.triggerEvent('toggle', {
|
|
expanded: expanded,
|
|
ids: getSelectedIds()
|
|
});
|
|
}
|
|
|
|
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 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 updatePositions() {
|
|
self.$items.forEach(function(item, pos) {
|
|
item.data('position', pos);
|
|
});
|
|
}
|
|
|
|
function updateQuery(ids) { // fixme: shouldn't this be setQuery?
|
|
// ids are the selcected ids
|
|
// (in case list is loaded with selection)
|
|
Ox.print('updateQuery', self.options)
|
|
clear();
|
|
self.requests.push(self.options.items({}, function(result) {
|
|
var keys = {};
|
|
that.triggerEvent('init', 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.max(Math.ceil(result.data.items / self.pageLength), 1),
|
|
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 updateSort() {
|
|
var key = self.options.sort[0].key,
|
|
operator = self.options.sort[0].operator;
|
|
if (self.listLength > 1) {
|
|
if (Ox.isArray(self.options.items)) {
|
|
self.options.items.sort(function(a, b) {
|
|
var ret = 0
|
|
if (a[key] < b[key]) {
|
|
return operator == '+' ? -1 : 1
|
|
} else if (a[key] > b[key]) {
|
|
return operator == '+' ? 1 : -1;
|
|
}
|
|
return ret;
|
|
});
|
|
loadItems();
|
|
} else {
|
|
clear(); // fixme: bad function name
|
|
getPositions();
|
|
}
|
|
}
|
|
}
|
|
|
|
self.onChange = function(key, value) {
|
|
//Ox.print('list onChange', key, value);
|
|
if (key == 'items') {
|
|
updateQuery();
|
|
} else if (key == 'selected') {
|
|
Ox.print('onChange selected', value)
|
|
setSelected(value);
|
|
}
|
|
};
|
|
|
|
that.addItems = function(pos, items) {
|
|
var $items = [],
|
|
length = items.length
|
|
first = self.$items.length == 0;
|
|
self.selected.forEach(function(v, i) {
|
|
if (v >= pos) {
|
|
self.selected[i] += length;
|
|
}
|
|
});
|
|
items.forEach(function(item, i) {
|
|
var $item;
|
|
$items.push($item = new Ox.ListItem({
|
|
construct: self.options.construct,
|
|
data: item,
|
|
draggable: self.options.draggable,
|
|
position: pos + i,
|
|
unique: self.options.unique
|
|
}));
|
|
if (i == 0) {
|
|
if (pos == 0) {
|
|
$item.insertBefore(self.$items[0]);
|
|
} else {
|
|
$item.insertAfter(self.$items[pos - 1]);
|
|
}
|
|
} else {
|
|
$item.insertAfter($items[i - 1]);
|
|
}
|
|
});
|
|
|
|
self.options.items.splice.apply(self.options.items, $.merge([pos, 0], items));
|
|
self.$items.splice.apply(self.$items, $.merge([pos, 0], $items));
|
|
if(first)
|
|
loadItems();
|
|
updatePositions();
|
|
}
|
|
|
|
that.editItem = function(pos) {
|
|
var $input,
|
|
item = self.options.items[pos],
|
|
$item = self.$items[pos],
|
|
width = $item.width(), // fixme: don't lookup in DOM
|
|
height = $item.height();
|
|
$item
|
|
.height(height + 8 + 16)
|
|
.empty()
|
|
.addClass('OxEdit');
|
|
|
|
$input = new Ox.ItemInput({
|
|
type: 'textarea',
|
|
value: item.value,
|
|
height: height,
|
|
width: width
|
|
}).bindEvent({
|
|
cancel: cancel,
|
|
save: submit
|
|
}).appendTo($item.$element);
|
|
/*
|
|
setTimeout(function() {
|
|
$input.gainFocus();
|
|
$input.focus();
|
|
});
|
|
*/
|
|
function cancel() {
|
|
$item.options('data', item);
|
|
//fixme: trigger event to reset i/o points
|
|
}
|
|
function submit(event, data) {
|
|
item.value = data.value;
|
|
//$input.loseFocus().remove();
|
|
// fixme: leaky, inputs remain in focus stack
|
|
$item.options('data', item);
|
|
that.triggerEvent('submit', item);
|
|
}
|
|
}
|
|
|
|
that.clearCache = function() { // fixme: was used by TextList resizeColumn, now probably no longer necessary
|
|
self.$pages = [];
|
|
return that;
|
|
};
|
|
|
|
that.closePreview = function() {
|
|
self.preview = false;
|
|
return that;
|
|
};
|
|
|
|
that.paste = function(data) {
|
|
pasteItems(data);
|
|
return that;
|
|
};
|
|
|
|
that.reloadList = function() {
|
|
updateQuery();
|
|
return that;
|
|
};
|
|
|
|
that.reloadPages = 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.removeItems = function(pos, length) {
|
|
/*
|
|
removeItems(ids)
|
|
or
|
|
removeItems(pos, length)
|
|
*/
|
|
if(!length) { //pos is list of ids
|
|
pos.forEach(function(id) {
|
|
var p = getPositionById(id);
|
|
that.removeItems(p, 1);
|
|
});
|
|
} else { //remove items from pos to pos+length
|
|
Ox.range(pos, pos + length).forEach(function(i) {
|
|
self.selected.indexOf(i) > -1 && deselect(i);
|
|
self.$items[i].remove();
|
|
});
|
|
self.options.items.splice(pos, length);
|
|
self.$items.splice(pos, length);
|
|
self.selected.forEach(function(v, i) {
|
|
if (v >= pos + length) {
|
|
self.selected[i] -= length;
|
|
}
|
|
});
|
|
updatePositions();
|
|
}
|
|
}
|
|
|
|
that.scrollToSelection = function() {
|
|
self.selected.length && scrollToPosition(self.selected[0]);
|
|
return that;
|
|
};
|
|
|
|
that.size = function() { // fixme: not a good function name
|
|
if (self.options.orientation == 'both') {
|
|
var rowLength = getRowLength(),
|
|
pageLength = self.pageLengthByRowLength[rowLength],
|
|
pos = getScrollPosition(),
|
|
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();
|
|
}
|
|
return that;
|
|
}
|
|
|
|
that.sortList = function(key, operator) {
|
|
Ox.print('sortList', key, operator)
|
|
if (key != self.options.sort[0].key || operator != self.options.sort[0].operator) {
|
|
self.options.sort[0] = {key: key, operator: operator};
|
|
updateSort();
|
|
that.triggerEvent('sort', self.options.sort[0]);
|
|
}
|
|
return that;
|
|
}
|
|
|
|
that.value = function(id, key, value) {
|
|
var pos = getPositionById(id),
|
|
$item = self.$items[pos],
|
|
data = $item.options('data'),
|
|
oldValue;
|
|
if (arguments.length == 1) {
|
|
return data;
|
|
} else if (arguments.length == 2) {
|
|
return data[key];
|
|
} else {
|
|
oldValue = data[key];
|
|
data[key] = value;
|
|
$item.options({data: data});
|
|
return that;
|
|
}
|
|
};
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
Ox.ItemInput = function(options, self) {
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element({}, self)
|
|
.defaults({
|
|
type: 'textarea',
|
|
value: '',
|
|
height: 300,
|
|
width: 100
|
|
})
|
|
.options(options || {}),
|
|
$input;
|
|
|
|
that.append(
|
|
$input = new Ox.Input({
|
|
height: self.options.height,
|
|
style: 'square',
|
|
type: self.options.type,
|
|
value: self.options.value,
|
|
width: self.options.width + 6
|
|
})
|
|
.bind({
|
|
mousedown: function(e) {
|
|
// keep mousedown from reaching list
|
|
e.stopPropagation();
|
|
}
|
|
})
|
|
)
|
|
.append(new Ox.Element()
|
|
.append(new Ox.Button({type: 'text', title: 'Cancel'})
|
|
.css('width', '42%')
|
|
.bindEvent({
|
|
'click': function() {
|
|
that.triggerEvent('cancel');
|
|
}
|
|
}))
|
|
.append(new Ox.Button({type: 'text', title: 'Save'})
|
|
.css('width', '42%')
|
|
.bindEvent({
|
|
'click': function() {
|
|
that.triggerEvent('save', {
|
|
value: $input.value()
|
|
});
|
|
}
|
|
}))
|
|
.css({
|
|
'margin-top': self.options.height-8,
|
|
'height': '16px',
|
|
'text-align': 'right',
|
|
})
|
|
);
|
|
Ox.print($input);
|
|
return that;
|
|
}
|
|
|
|
Ox.ListItem = function(options, self) {
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element({}, self)
|
|
.defaults({
|
|
construct: function() {},
|
|
data: {},
|
|
draggable: false,
|
|
position: 0,
|
|
unique: ''
|
|
})
|
|
.options(options || {});
|
|
|
|
constructItem();
|
|
|
|
function constructItem(update) {
|
|
var $element = self.options.construct(self.options.data)
|
|
.addClass('OxItem')
|
|
.attr({
|
|
draggable: self.options.draggable
|
|
})
|
|
.data({
|
|
id: self.options.data[self.options.unique],
|
|
position: self.options.position
|
|
});
|
|
if (update) {
|
|
that.$element.hasClass('OxSelected') && $element.addClass('OxSelected');
|
|
that.$element.replaceWith($element);
|
|
}
|
|
that.$element = $element;
|
|
}
|
|
|
|
self.onChange = function(key, value) {
|
|
if (key == 'data') {
|
|
constructItem(true);
|
|
}
|
|
}
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
Ox.ListPage = function(options, self) {
|
|
var self = self || {},
|
|
that = new Ox.Element({}, self)
|
|
.addClass('OxPage');
|
|
return that;
|
|
};
|
|
|
|
Ox.TextList = function(options, self) {
|
|
|
|
// fixme: rename to TableList
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element({}, self)
|
|
.defaults({
|
|
columns: [],
|
|
columnsMovable: false,
|
|
columnsRemovable: false,
|
|
columnsResizable: false,
|
|
columnsVisible: false,
|
|
columnWidth: [40, 800],
|
|
id: '',
|
|
items: null, // function() {} {sort, range, keys, callback} or array
|
|
max: -1,
|
|
min: 0,
|
|
pageLength: 100,
|
|
scrollbarVisible: false,
|
|
selected: [],
|
|
sort: []
|
|
})
|
|
.options(options || {})
|
|
.addClass('OxTextList');
|
|
|
|
Ox.print('Ox.TextList self.options', self.options)
|
|
|
|
$.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.align)) {
|
|
v.align = 'left';
|
|
}
|
|
if (Ox.isUndefined(v.clickable)) {
|
|
v.clickable = false;
|
|
}
|
|
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: [],
|
|
defaultColumnWidths: $.map(self.options.columns, function(v) {
|
|
return v.defaultWidth || v.width;
|
|
}),
|
|
itemHeight: 16,
|
|
page: 0,
|
|
pageLength: 100,
|
|
scrollLeft: 0,
|
|
selectedColumn: getColumnIndexById(self.options.sort[0].key),
|
|
visibleColumns: $.map(self.options.columns, function(v) {
|
|
return v.visible ? v : null;
|
|
})
|
|
});
|
|
// fixme: there might be a better way than passing both visible and position
|
|
self.options.columns.forEach(function(v) {
|
|
if (!Ox.isUndefined(v.position)) {
|
|
self.visibleColumns[v.position] = v;
|
|
}
|
|
})
|
|
$.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')
|
|
.css({
|
|
right: self.options.scrollbarVisible ? oxui.scrollbarSize + 'px' : 0
|
|
})
|
|
.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,
|
|
items: self.options.items,
|
|
itemHeight: 16,
|
|
items: self.options.items,
|
|
itemWidth: getItemWidth(),
|
|
format: self.format, // fixme: not needed, happens in TextList
|
|
keys: $.map(self.visibleColumns, function(v) {
|
|
return v.id;
|
|
}),
|
|
max: self.options.max,
|
|
min: self.options.min,
|
|
pageLength: self.options.pageLength,
|
|
paste: self.options.paste,
|
|
orientation: 'vertical',
|
|
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.scrollbarVisible ? 'scroll' : 'hidden')
|
|
})
|
|
.scroll(function() {
|
|
var scrollLeft = $(this).scrollLeft();
|
|
if (scrollLeft != self.scrollLeft) {
|
|
self.scrollLeft = scrollLeft;
|
|
that.$head && that.$head.scrollLeft(scrollLeft);
|
|
}
|
|
})
|
|
.bindEvent({
|
|
edit: function(event, data) {
|
|
that.editCell(data.id, data.key);
|
|
},
|
|
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, ids,
|
|
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.reloadPages();
|
|
}
|
|
|
|
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;
|
|
}
|
|
});
|
|
}
|
|
triggerColumnChangeEvent();
|
|
}
|
|
|
|
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;
|
|
offset += self.columnWidths[i];
|
|
self.columnOffsets[i] = offset - self.columnWidths[i] / 2;
|
|
that.$titles[i] = new Ox.Element()
|
|
.addClass('OxTitle OxColumn' + Ox.toTitleCase(v.id))
|
|
.css({
|
|
width: (self.columnWidths[i] - 9) + 'px',
|
|
textAlign: v.align
|
|
})
|
|
.html(v.title)
|
|
.bindEvent({
|
|
anyclick: function(event, e) {
|
|
clickColumn(v.id);
|
|
},
|
|
dragstart: function(event, e) {
|
|
dragstartColumn(v.id, e);
|
|
},
|
|
drag: function(event, e) {
|
|
dragColumn(v.id, e);
|
|
},
|
|
dragend: function(event, e) {
|
|
dragendColumn(v.id, e);
|
|
}
|
|
})
|
|
.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 = new Ox.Element()
|
|
.addClass('OxResize')
|
|
.appendTo(that.$head.$content.$element);
|
|
if (self.options.columnsResizable) {
|
|
$resize.addClass('OxResizable')
|
|
.bindEvent({
|
|
doubleclick: function(event, e) {
|
|
resetColumn(v.id, e);
|
|
},
|
|
dragstart: function(event, e) {
|
|
dragstartResize(v.id, e);
|
|
},
|
|
drag: function(event, e) {
|
|
dragResize(v.id, e);
|
|
},
|
|
dragend: function(event, e) {
|
|
dragendResize(v.id, e);
|
|
}
|
|
});
|
|
}
|
|
$left = $('<div>').addClass('OxLeft').appendTo($resize.$element);
|
|
$center = $('<div>').addClass('OxCenter').appendTo($resize.$element);
|
|
$right = $('<div>').addClass('OxRight').appendTo($resize.$element);
|
|
});
|
|
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 clickable = Ox.isBoolean(v.clickable) ? v.clickable : v.clickable(data),
|
|
editable = Ox.isBoolean(v.editable) ? v.editable : v.editable(data),
|
|
$cell = $('<div>')
|
|
.addClass(
|
|
'OxCell OxColumn' + Ox.toTitleCase(v.id) +
|
|
(clickable ? ' OxClickable' : '') +
|
|
(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 ? formatValue(data[v.id], v.format) : '')
|
|
.appendTo($item);
|
|
});
|
|
function formatValue(value, format) {
|
|
if (value === null) {
|
|
value = '';
|
|
} else if (format) {
|
|
value = Ox.isObject(format) ?
|
|
Ox['format' + Ox.toTitleCase(format.type)]
|
|
.apply(this, $.merge([value], format.args)) :
|
|
format(value);
|
|
} else if (Ox.isArray(value)) {
|
|
value = value.join(', ');
|
|
}
|
|
return value;
|
|
}
|
|
//Math.random() < 0.01 && Ox.print('item', data, $item);
|
|
return $item;
|
|
}
|
|
|
|
function dragstartColumn(id, e) {
|
|
self.drag = {
|
|
startX: e.clientX,
|
|
startPos: getColumnPositionById(id)
|
|
}
|
|
$.extend(self.drag, {
|
|
stopPos: self.drag.startPos,
|
|
offsets: $.map(self.visibleColumns, function(v, i) {
|
|
return self.columnOffsets[i] - self.columnOffsets[self.drag.startPos]
|
|
})
|
|
});
|
|
$('.OxColumn' + Ox.toTitleCase(id)).css({
|
|
opacity: 0.25
|
|
});
|
|
that.$titles[self.drag.startPos].addClass('OxDrag').css({ // fixme: why does the class not work?
|
|
cursor: 'move'
|
|
});
|
|
}
|
|
|
|
function dragColumn(id, e) {
|
|
var d = e.clientX - self.drag.startX,
|
|
pos = self.drag.stopPos;
|
|
$.each(self.drag.offsets, function(i, v) {
|
|
if (d < 0 && d < v) {
|
|
self.drag.stopPos = i;
|
|
return false;
|
|
} else if (d > 0 && d > v) {
|
|
self.drag.stopPos = i;
|
|
}
|
|
});
|
|
if (self.drag.stopPos != pos) {
|
|
moveColumn(id, self.drag.stopPos);
|
|
}
|
|
}
|
|
|
|
function dragendColumn(id, e) {
|
|
var column = self.visibleColumns.splice(self.drag.stopPos, 1)[0],
|
|
width = self.columnWidths.splice(self.drag.stopPos, 1)[0];
|
|
self.visibleColumns.splice(self.drag.stopPos, 0, column);
|
|
self.columnWidths.splice(self.drag.stopPos, 0, width);
|
|
that.$head.$content.empty();
|
|
constructHead();
|
|
$('.OxColumn' + Ox.toTitleCase(id)).css({
|
|
opacity: 1
|
|
});
|
|
that.$titles[self.drag.stopPos].removeClass('OxDrag').css({
|
|
cursor: 'pointer'
|
|
});
|
|
that.$body.clearCache();
|
|
triggerColumnChangeEvent();
|
|
}
|
|
|
|
function dragstartResize(id, e) {
|
|
var pos = getColumnPositionById(id);
|
|
self.drag = {
|
|
startX: e.clientX,
|
|
startWidth: self.columnWidths[pos]
|
|
};
|
|
}
|
|
|
|
function dragResize(id, e) {
|
|
var width = Ox.limit(
|
|
self.drag.startWidth - self.drag.startX + e.clientX,
|
|
self.options.columnWidth[0],
|
|
self.options.columnWidth[1]
|
|
);
|
|
resizeColumn(id, width);
|
|
}
|
|
|
|
function dragendResize(id, e) {
|
|
var pos = getColumnPositionById(id);
|
|
that.triggerEvent('columnresize', {
|
|
id: id,
|
|
width: self.columnWidths[pos]
|
|
});
|
|
}
|
|
|
|
function getCell(id, key) {
|
|
Ox.print('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) {
|
|
//Ox.print('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() -
|
|
(self.options.scrollbarVisible ? oxui.scrollbarSize : 0)
|
|
);
|
|
//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 resetColumn(id) {
|
|
var width = self.defaultColumnWidths[getColumnIndexById(id)];
|
|
resizeColumn(id, width);
|
|
that.triggerEvent('columnresize', {
|
|
id: id,
|
|
width: width
|
|
});
|
|
}
|
|
|
|
function resizeColumn(id, width) {
|
|
var i = getColumnIndexById(id),
|
|
pos = getColumnPositionById(id);
|
|
self.options.columns[i].width = width;
|
|
self.columnWidths[pos] = width;
|
|
if (self.options.columnsVisible) {
|
|
that.$head.$content.css({
|
|
width: (Ox.sum(self.columnWidths) + 2) + 'px'
|
|
});
|
|
that.$titles[pos].css({
|
|
width: (width - 9 - (i == self.selectedColumn ? 16 : 0)) + 'px'
|
|
});
|
|
}
|
|
that.find('.OxCell.OxColumn' + Ox.toTitleCase(self.options.columns[i].id)).css({
|
|
width: (width - (self.options.columnsVisible ? 9 : 8)) + 'px'
|
|
});
|
|
setWidth();
|
|
}
|
|
|
|
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 triggerColumnChangeEvent() {
|
|
that.triggerEvent('columnchange', {
|
|
ids: $.map(self.visibleColumns, function(v, i) {
|
|
return v.id;
|
|
})
|
|
});
|
|
}
|
|
|
|
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 == 'items') {
|
|
//alert('request set!!')
|
|
that.$body.options(key, value);
|
|
} else if (key == 'paste') {
|
|
that.$body.options(key, value);
|
|
} else if (key == 'selected') {
|
|
that.$body.options(key, value);
|
|
}
|
|
};
|
|
|
|
// fixme: doesn't work, doesn't return that
|
|
that.closePreview = that.$body.closePreview;
|
|
|
|
that.editCell = function(id, key) {
|
|
Ox.print('editCell', id, key)
|
|
var $item = getItem(id),
|
|
$cell = getCell(id, key),
|
|
$input,
|
|
html = $cell.html(),
|
|
index = getColumnIndexById(key),
|
|
column = self.options.columns[index],
|
|
width = column.width - self.options.columnsVisible;
|
|
$cell.empty()
|
|
.addClass('OxEdit')
|
|
.css({
|
|
width: width + 'px'
|
|
});
|
|
$input = new Ox.Input({
|
|
autovalidate: column.input ? column.input.autovalidate : null,
|
|
style: 'square',
|
|
value: html,
|
|
width: width
|
|
})
|
|
.bind({
|
|
mousedown: function(e) {
|
|
// keep mousedown from reaching list
|
|
e.stopPropagation();
|
|
}
|
|
})
|
|
.bindEvent({
|
|
blur: submit,
|
|
})
|
|
.appendTo($cell);
|
|
//.focusInput();
|
|
setTimeout($input.focusInput, 0); // fixme: strange
|
|
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
|
|
});
|
|
}
|
|
}
|
|
|
|
that.gainFocus = function() {
|
|
that.$body.gainFocus();
|
|
return that;
|
|
};
|
|
|
|
that.loseFocus = function() {
|
|
that.$body.loseFocus();
|
|
return that;
|
|
}
|
|
|
|
that.paste = function(data) {
|
|
that.$body.paste();
|
|
return that;
|
|
};
|
|
|
|
that.reloadList = function() {
|
|
that.$body.reloadList();
|
|
return that;
|
|
};
|
|
|
|
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) {
|
|
// fixme: make this accept id, {k: v, ...}
|
|
var $item = getItem(id),
|
|
//$cell = getCell(id, key),
|
|
column = self.options.columns[getColumnIndexById(key)];
|
|
if (arguments.length == 1) {
|
|
return that.$body.value(id);
|
|
} else if (arguments.length == 2) {
|
|
return that.$body.value(id, key);
|
|
} else {
|
|
that.$body.value(id, key, value);
|
|
/*
|
|
$cell && $cell.html(column.format ? column.format(value) : value);
|
|
if (column.unique) {
|
|
that.$body.setId($item.data('id'), value);
|
|
$item.data({id: value});
|
|
}
|
|
*/
|
|
return that;
|
|
}
|
|
}
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
Ox.TreeList = function(options, self) {
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
data: null,
|
|
items: [],
|
|
max: -1,
|
|
min: 0,
|
|
selected: [],
|
|
width: 256
|
|
})
|
|
.options(options || {});
|
|
|
|
if (self.options.data) {
|
|
self.options.items = [];
|
|
Ox.print('d', self.options.data, 'i', self.options.items)
|
|
Ox.forEach(self.options.data, function(value, key) {
|
|
self.options.items.push(parseData(key, value));
|
|
});
|
|
Ox.print('d', self.options.data, 'i', self.options.items)
|
|
}
|
|
|
|
that.$element = new Ox.List({
|
|
construct: constructItem,
|
|
itemHeight: 16,
|
|
items: parseItems(),
|
|
itemWidth: self.options.width,
|
|
max: self.options.max,
|
|
min: self.options.min,
|
|
unique: 'id',
|
|
}, $.extend({}, self))
|
|
.addClass('OxTextList OxTreeList')
|
|
.css({
|
|
width: self.options.width + 'px'
|
|
})
|
|
.click(clickItem)
|
|
.bindEvent({
|
|
toggle: toggleItems
|
|
});
|
|
|
|
function clickItem(e) {
|
|
var $target = $(e.target),
|
|
$item, id, item;
|
|
if ($target.hasClass('OxToggle')) {
|
|
$item = $target.parent().parent();
|
|
id = $item.data('id');
|
|
item = getItemById(id);
|
|
toggleItem(item, !item.expanded)
|
|
}
|
|
}
|
|
|
|
function constructItem(data) {
|
|
var $item = $('<div>'),
|
|
padding = (data.level + !data.items) * 16 - 8;
|
|
if (data.level || !data.items) {
|
|
$('<div>')
|
|
.addClass('OxCell OxTarget')
|
|
.css({
|
|
width: padding + 'px',
|
|
})
|
|
.appendTo($item);
|
|
}
|
|
if (data.items) {
|
|
$('<div>')
|
|
.addClass('OxCell')
|
|
.css({
|
|
width: '8px',
|
|
})
|
|
.append(
|
|
// fixme: need Ox.Icon()
|
|
$('<img>')
|
|
.addClass('OxToggle')
|
|
.attr({
|
|
src: oxui.path + '/png/ox.ui.' + Ox.theme() + '/symbol' +
|
|
(data.expanded ? 'Collapse' : 'Expand') + '.png'
|
|
})
|
|
)
|
|
.appendTo($item);
|
|
}
|
|
$('<div>')
|
|
.addClass('OxCell OxTarget')
|
|
.css({
|
|
width: (self.options.width - padding - 32 + !data.items * 16) + 'px'
|
|
})
|
|
.html(data.title)
|
|
.appendTo($item);
|
|
return $item;
|
|
}
|
|
|
|
function getItemById(id, items, level) {
|
|
var items = items || self.options.items,
|
|
level = level || 0,
|
|
ret = null;
|
|
$.each(items, function(i, item) {
|
|
if (item.id == id) {
|
|
ret = $.extend(item, {
|
|
level: level
|
|
});
|
|
return false;
|
|
}
|
|
if (item.items) {
|
|
ret = getItemById(id, item.items, level + 1);
|
|
if (ret) {
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
return ret;
|
|
}
|
|
|
|
function parseData(key, value) {
|
|
Ox.print('parseData', key, value)
|
|
var ret = {
|
|
id: key,
|
|
title: key.toString().split('.').pop()
|
|
},
|
|
type = Ox.typeOf(value);
|
|
if (type == 'array' || type == 'object') {
|
|
ret.title += ': ' + Ox.toTitleCase(Ox.typeOf(value));
|
|
ret.items = Ox.map(Ox.sort(Ox.keys(value)), function(k) {
|
|
return parseData(key + '.' + k, value[k]);
|
|
});
|
|
} else {
|
|
ret.title += ': ' + (
|
|
type == 'function' ?
|
|
value.toString().split('{')[0] :
|
|
JSON.stringify(value)
|
|
)
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function parseItems(items, level) {
|
|
var items = items || self.options.items,
|
|
level = level || 0,
|
|
ret = [];
|
|
items.forEach(function(item, i) {
|
|
var item_ = $.extend({
|
|
level: level
|
|
}, item, item.items ? {
|
|
items: !!item.expanded ?
|
|
parseItems(item.items, level + 1) : []
|
|
} : {});
|
|
ret.push(item_);
|
|
item.items && $.merge(ret, item_.items);
|
|
});
|
|
return ret;
|
|
}
|
|
|
|
function toggleItem(item, expanded) {
|
|
var $img, $item, pos;
|
|
item.expanded = expanded;
|
|
$.each(that.$element.find('.OxItem'), function(i, v) {
|
|
var $item = $(v);
|
|
if ($item.data('id') == item.id) {
|
|
$img = $item.find('img');
|
|
pos = $item.data('position');
|
|
return false;
|
|
}
|
|
})
|
|
$img.attr({
|
|
src: oxui.path + '/png/ox.ui.' + Ox.theme() + '/symbol' +
|
|
(item.expanded ? 'Collapse' : 'Expand') + '.png'
|
|
});
|
|
item.expanded ?
|
|
that.$element.addItems(pos + 1, parseItems(item.items, item.level + 1)) :
|
|
that.$element.removeItems(pos + 1, parseItems(item.items, item.level + 1).length);
|
|
}
|
|
|
|
function toggleItems(event, data) {
|
|
data.ids.forEach(function(id, i) {
|
|
var item = getItemById(id);
|
|
if (item.items && data.expanded != !!item.expanded) {
|
|
toggleItem(item, data.expanded);
|
|
}
|
|
});
|
|
}
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
/*
|
|
============================================================================
|
|
Maps
|
|
============================================================================
|
|
*/
|
|
|
|
Ox.ListMap = function(options, self) {
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
addPlace: null,
|
|
height: 256,
|
|
labels: false,
|
|
places: null,
|
|
selected: [],
|
|
width: 256
|
|
})
|
|
.options(options || {})
|
|
.css({
|
|
width: self.options.width + 'px',
|
|
height: self.options.height + 'px'
|
|
});
|
|
|
|
self.columns = [
|
|
{
|
|
addable: false, // fixme: implement
|
|
id: 'id',
|
|
unique: true,
|
|
visible: false
|
|
},
|
|
{
|
|
editable: true,
|
|
id: 'name',
|
|
operator: '+',
|
|
removable: false,
|
|
title: 'Name',
|
|
visible: true,
|
|
width: 144
|
|
},
|
|
{
|
|
editable: true,
|
|
id: 'geoname',
|
|
removable: false,
|
|
operator: '+',
|
|
title: 'Geoname',
|
|
visible: true,
|
|
width: 192
|
|
},
|
|
{
|
|
format: function(value) {
|
|
return $('<img>')
|
|
.attr({
|
|
// fixme: not the right place to do these
|
|
src: '/static/oxjs/build/svg/' + (value || 'NTHH') + '.' + (value == 'RE' ? 'png' : 'svg')
|
|
})
|
|
.load(function() {
|
|
$(this).css({
|
|
width: '21px',
|
|
height: '14px',
|
|
padding: '1px 0 0 1px'
|
|
})
|
|
});
|
|
},
|
|
id: 'countryCode',
|
|
operator: '+',
|
|
title: 'Flag',
|
|
visible: true,
|
|
width: 48
|
|
},
|
|
{
|
|
align: 'right',
|
|
format: {type: 'area', args: [0]},
|
|
id: 'size',
|
|
operator: '-',
|
|
title: 'Size',
|
|
visible: true,
|
|
width: 128
|
|
},
|
|
{
|
|
align: 'right',
|
|
format: toFixed,
|
|
id: 'lat',
|
|
operator: '+',
|
|
title: 'Latitude',
|
|
visible: true,
|
|
width: 96
|
|
},
|
|
{
|
|
align: 'right',
|
|
format: toFixed,
|
|
id: 'lng',
|
|
operator: '+',
|
|
title: 'Longitude',
|
|
visible: true,
|
|
width: 96
|
|
},
|
|
{
|
|
align: 'right',
|
|
format: toFixed,
|
|
id: 'south',
|
|
operator: '+',
|
|
title: 'South',
|
|
visible: false,
|
|
width: 96
|
|
},
|
|
{
|
|
align: 'right',
|
|
id: 'west',
|
|
operator: '+',
|
|
title: 'West',
|
|
visible: false,
|
|
width: 96
|
|
},
|
|
{
|
|
align: 'right',
|
|
format: toFixed,
|
|
id: 'north',
|
|
operator: '+',
|
|
title: 'North',
|
|
visible: false,
|
|
width: 96
|
|
},
|
|
{
|
|
align: 'right',
|
|
format: toFixed,
|
|
id: 'east',
|
|
operator: '+',
|
|
title: 'East',
|
|
visible: false,
|
|
width: 96
|
|
},
|
|
{
|
|
id: 'user',
|
|
operator: '+',
|
|
title: 'User',
|
|
visible: false,
|
|
width: 96
|
|
},
|
|
{
|
|
format: 'date',
|
|
id: 'created',
|
|
operator: '-',
|
|
title: 'Date Created',
|
|
visible: false,
|
|
width: 96,
|
|
},
|
|
{
|
|
format: 'date',
|
|
id: 'modified',
|
|
operator: '-',
|
|
title: 'Date Modified',
|
|
visible: false,
|
|
width: 96,
|
|
},
|
|
{
|
|
align: 'right',
|
|
id: 'matches',
|
|
operator: '-',
|
|
title: 'Matches',
|
|
visible: false,
|
|
width: 96,
|
|
}
|
|
];
|
|
|
|
self.$toolbar = new Ox.Bar({
|
|
size: 24
|
|
});
|
|
|
|
self.$findElement = new Ox.FormElementGroup({
|
|
elements: [
|
|
self.$findSelect = new Ox.Select({
|
|
items: [
|
|
{id: 'all', title: 'Find: All'},
|
|
{id: 'name', title: 'Find: Name'},
|
|
{id: 'geoname', title: 'Find: Geoname'},
|
|
{id: 'country', title: 'Find: Country'}
|
|
],
|
|
overlap: 'right',
|
|
width: 128
|
|
}),
|
|
self.$findInput = new Ox.Input({
|
|
clear: true,
|
|
width: 192
|
|
})
|
|
]
|
|
})
|
|
.css({float: 'right', margin: '4px'})
|
|
.appendTo(self.$toolbar)
|
|
|
|
self.$list = new Ox.TextList({
|
|
columns: self.columns,
|
|
columnsRemovable: true,
|
|
columnsVisible: true,
|
|
items: self.options.places,
|
|
pageLength: 100,
|
|
scrollbarVisible: true,
|
|
sort: [
|
|
{key: 'name', operator: '+'}
|
|
]
|
|
})
|
|
.bindEvent({
|
|
'delete': removeItem,
|
|
init: initList,
|
|
load: function() {
|
|
that.triggerEvent('loadlist');
|
|
},
|
|
open: openItem,
|
|
select: selectItem
|
|
});
|
|
|
|
self.$statusbar = new Ox.Bar({
|
|
size: 24
|
|
});
|
|
|
|
self.$status = new Ox.Element()
|
|
.css({paddingTop: '4px', margin: 'auto', textAlign: 'center'})
|
|
.appendTo(self.$statusbar);
|
|
|
|
self.mapResize = [
|
|
Math.round(self.options.width * 0.25),
|
|
Math.round(self.options.width * 0.5),
|
|
Math.round(self.options.width * 0.75)
|
|
];
|
|
|
|
if (Ox.isArray(self.options.places)) {
|
|
init(self.options.places)
|
|
} else {
|
|
self.options.places({}, function(result) {
|
|
Ox.print('$$$$', result.data.items)
|
|
self.options.places({
|
|
keys: self.columns.map(function(column) {
|
|
return column.id
|
|
}),
|
|
range: [0, result.data.items]
|
|
}, function(result) {
|
|
Ox.print('DATA', result)
|
|
init(result.data.items);
|
|
});
|
|
});
|
|
}
|
|
|
|
function init(places) {
|
|
Ox.print('PLACES', places)
|
|
self.$map = new Ox.Map({
|
|
clickable: true,
|
|
height: self.options.height,
|
|
// fixme: place can still be string, and maybe shouldn't be array at all
|
|
places: places.map(function(place) {
|
|
return Ox.extend({}, place, {
|
|
name: place.name.length == 0 ? '' : place.name[0]
|
|
});
|
|
}),
|
|
statusbar: true,
|
|
toolbar: true,
|
|
width: self.mapResize[1],
|
|
zoombar: true
|
|
})
|
|
.bindEvent({
|
|
addplace: function(event, data) {
|
|
that.triggerEvent('addplace', data);
|
|
},
|
|
resize: function() {
|
|
self.$map.resizeMap();
|
|
},
|
|
select: selectPlace
|
|
});
|
|
|
|
that.$element.replaceWith(
|
|
that.$element = new Ox.SplitPanel({
|
|
elements: [
|
|
{
|
|
element: new Ox.SplitPanel({
|
|
elements: [
|
|
{
|
|
element: self.$toolbar,
|
|
size: 24
|
|
},
|
|
{
|
|
element: self.$list
|
|
},
|
|
{
|
|
element: self.$statusbar,
|
|
size: 24
|
|
}
|
|
],
|
|
orientation: 'vertical'
|
|
})
|
|
},
|
|
{
|
|
element: self.$map,
|
|
resizable: true,
|
|
resize: self.mapResize,
|
|
size: self.mapResize[1]
|
|
}
|
|
],
|
|
orientation: 'horizontal'
|
|
}).$element
|
|
);
|
|
}
|
|
|
|
function initList(event, data) {
|
|
self.$status.html(data.items + ' place' + (data.items == 1 ? '' : 's'))
|
|
}
|
|
|
|
function openItem(event, data) {
|
|
selectItem(event, data);
|
|
self.$map.zoomToPlace(data.ids[0]);
|
|
}
|
|
|
|
function removeItem(event, data) {
|
|
var id = data.ids[0];
|
|
that.triggerEvent('removeplace', {id: id});
|
|
self.$map.removePlace(id);
|
|
}
|
|
|
|
function selectItem(event, data) {
|
|
Ox.print('selectItem', data.ids[0])
|
|
self.$map.options({selected: data.ids.length ? data.ids[0] : ''});
|
|
}
|
|
|
|
function selectPlace(event, data) {
|
|
Ox.print('selectPlace', data, data.id)
|
|
data.id[0] != '_' && self.$list.options({
|
|
selected: data.id ? [data.id] : []
|
|
});
|
|
}
|
|
|
|
function toFixed(val) {
|
|
return val.toFixed(8);
|
|
}
|
|
|
|
self.onChange = function(key, value) {
|
|
if (key == 'selected') {
|
|
self.$list.options({selected: value});
|
|
}
|
|
}
|
|
|
|
that.focusList = function() {
|
|
self.$list.gainFocus();
|
|
return that;
|
|
}
|
|
|
|
that.reloadList = function() {
|
|
self.$list.reloadList();
|
|
return that;
|
|
}
|
|
|
|
that.resizeMap = function() {
|
|
Ox.print('Ox.ListMap.resizeMap()')
|
|
self.$map.resizeMap();
|
|
return that;
|
|
};
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
Ox.Map = function(options, self) {
|
|
|
|
var self = self || {}
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
clickable: false,
|
|
height: 256,
|
|
labels: false,
|
|
places: [],
|
|
selected: null,
|
|
statusbar: false,
|
|
toolbar: false,
|
|
width: 256,
|
|
zoombar: false
|
|
})
|
|
.options(options || {})
|
|
.css({
|
|
width: self.options.width + 'px',
|
|
height: self.options.height + 'px'
|
|
})
|
|
.bindEvent({
|
|
key_up: function() {
|
|
pan(0, -1);
|
|
},
|
|
key_down: function() {
|
|
pan(0, 1);
|
|
},
|
|
key_l: toggleLabels,
|
|
key_left: function() {
|
|
pan(-1, 0);
|
|
},
|
|
key_right: function() {
|
|
pan(1, 0);
|
|
},
|
|
key_0: reset,
|
|
key_meta: function() {
|
|
self.metaKey = true;
|
|
$(document).one({
|
|
keyup: function() {
|
|
self.metaKey = false;
|
|
}
|
|
});
|
|
},
|
|
key_minus: function() {
|
|
zoom(-1);
|
|
},
|
|
key_equal: function() {
|
|
zoom(1);
|
|
},
|
|
key_enter: panToPlace,
|
|
key_shift_enter: zoomToPlace,
|
|
key_escape: function() {
|
|
selectPlace('');
|
|
}
|
|
});
|
|
|
|
self.metaKey = false;
|
|
self.resultPlace = null;
|
|
|
|
if (self.options.toolbar) {
|
|
self.$toolbar = new Ox.Bar({
|
|
size: 24
|
|
})
|
|
.appendTo(that);
|
|
self.$labelsButton = new Ox.Button({
|
|
title: 'Show Labels',
|
|
width: 80
|
|
})
|
|
.css({float: 'left', margin: '4px'})
|
|
.bindEvent({
|
|
click: toggleLabels
|
|
})
|
|
.appendTo(self.$toolbar)
|
|
self.$findInput = new Ox.Input({
|
|
clear: true,
|
|
placeholder: 'Find on Map',
|
|
width: 192
|
|
})
|
|
.css({float: 'right', margin: '4px'})
|
|
.bindEvent({
|
|
submit: submitFind
|
|
})
|
|
.appendTo(self.$toolbar)
|
|
}
|
|
|
|
self.$map = new Ox.Element('div')
|
|
.css({
|
|
width: self.options.width + 'px',
|
|
height: getMapHeight() + 'px'
|
|
})
|
|
.appendTo(that);
|
|
|
|
if (self.options.zoombar) {
|
|
self.$zoombar = new Ox.Bar({
|
|
size: 16
|
|
})
|
|
.appendTo(that);
|
|
self.$zoomInput = new Ox.Range({
|
|
arrows: true,
|
|
max: 22,
|
|
size: self.options.width,
|
|
thumbSize: 32,
|
|
thumbValue: true
|
|
})
|
|
.bindEvent({
|
|
change: changeZoom
|
|
})
|
|
.appendTo(self.$zoombar)
|
|
}
|
|
|
|
if (self.options.statusbar) {
|
|
self.$statusbar = new Ox.Bar({
|
|
size: 24
|
|
})
|
|
.css({padding: '2px'})
|
|
.appendTo(that);
|
|
self.$placeNameInput = new Ox.Input({
|
|
placeholder: 'Name',
|
|
width: Math.floor((self.options.width - 96) / 2)
|
|
})
|
|
.css({float: 'left', margin: '2px'})
|
|
.appendTo(self.$statusbar);
|
|
self.$placeGeonameInput = new Ox.Input({
|
|
placeholder: 'Geoname',
|
|
width: Math.ceil((self.options.width - 96) / 2)
|
|
})
|
|
.css({float: 'left', margin: '2px'})
|
|
.appendTo(self.$statusbar)
|
|
self.$placeButton = new Ox.Button({
|
|
title: 'New Place',
|
|
width: 80
|
|
})
|
|
.css({float: 'left', margin: '2px'})
|
|
.bindEvent({
|
|
click: clickPlaceButton
|
|
})
|
|
.appendTo(self.$statusbar);
|
|
}
|
|
|
|
if (Ox.isUndefined(window.google)) {
|
|
googleCallback = function() {
|
|
Ox.print('googleCallback')
|
|
delete googleCallback;
|
|
initMap();
|
|
};
|
|
$.getScript('http://maps.google.com/maps/api/js?callback=googleCallback&sensor=false');
|
|
} else {
|
|
initMap();
|
|
}
|
|
|
|
function addNewPlace() {
|
|
var bounds = self.map.getBounds(),
|
|
center = self.map.getCenter(),
|
|
southwest = new google.maps.LatLngBounds(
|
|
bounds.getSouthWest(), center
|
|
).getCenter(),
|
|
northeast = new google.maps.LatLngBounds(
|
|
center, bounds.getNorthEast()
|
|
).getCenter(),
|
|
place = new Place({
|
|
countryCode: '',
|
|
geoname: '',
|
|
id: '_' + Ox.uid(), // fixme: stupid
|
|
name: '',
|
|
south: southwest.lat(),
|
|
west: southwest.lng(),
|
|
north: northeast.lat(),
|
|
east: northeast.lng()
|
|
});
|
|
addPlace(place);
|
|
selectPlace(place.name);
|
|
}
|
|
|
|
function addPlace(place) {
|
|
Ox.print('addPlace', place)
|
|
Ox.print('self.resultPlace', self.resultPlace)
|
|
self.resultPlace && self.resultPlace.remove();
|
|
if (place.id[0] == '_') {
|
|
self.resultPlace = place;
|
|
}
|
|
place.add();
|
|
}
|
|
|
|
function canContain(outerBounds, innerBounds) {
|
|
var outerSpan = outerBounds.toSpan(),
|
|
innerSpan = innerBounds.toSpan();
|
|
return outerSpan.lat() > innerSpan.lat() &&
|
|
outerSpan.lng() > innerSpan.lng();
|
|
}
|
|
|
|
function changeZoom(event, data) {
|
|
self.map.setZoom(data.value);
|
|
}
|
|
|
|
function clickMap(event) {
|
|
Ox.print('Ox.Map clickMap')
|
|
that.gainFocus();
|
|
if (self.options.clickable) {
|
|
getPlaceByLatLng(event.latLng, self.map.getBounds(), function(place) {
|
|
if (place) {
|
|
addPlace(place);
|
|
selectPlace(place.id);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function clickPlaceButton() {
|
|
if (self.$placeButton.options('title') == 'New Place') {
|
|
addNewPlace();
|
|
} else {
|
|
var place = getPlaceById(self.selected),
|
|
data = {
|
|
place: {}
|
|
};
|
|
data.place.name = self.$placeNameInput.value();
|
|
data.place.geoname = self.$placeGeonameInput.value();
|
|
data.place.countryCode = Ox.getCountryCode(data.place.geoname);
|
|
[
|
|
'lat', 'lng', 'south', 'west', 'north', 'east', 'size'
|
|
].forEach(function(key) {
|
|
data.place[key] = place[key];
|
|
});
|
|
that.triggerEvent('addplace', data)
|
|
}
|
|
}
|
|
|
|
function getPlaceById(id) {
|
|
var place = Ox.getObjectById(self.places, id);
|
|
if (!place && self.resultPlace && self.resultPlace.id == id) {
|
|
place = self.resultPlace;
|
|
}
|
|
Ox.print('getPlaceById', id, place)
|
|
return place;
|
|
}
|
|
|
|
function getPlaceByLatLng(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(Place(results[i]));
|
|
return false;
|
|
}
|
|
});
|
|
} else {
|
|
callback(Place(results[0]));
|
|
}
|
|
} else {
|
|
callback(null);
|
|
}
|
|
} else {
|
|
//Ox.print('geocode failed:', status);
|
|
callback(null);
|
|
}
|
|
});
|
|
}
|
|
|
|
function getPlaceByName(name, callback) {
|
|
self.geocoder.geocode({
|
|
address: name
|
|
}, function(results, status) {
|
|
if (status == google.maps.GeocoderStatus.OK) {
|
|
if (status != google.maps.GeocoderStatus.ZERO_RESULTS) {
|
|
Ox.print('GEOCODER RESULT', results[0])
|
|
callback(Place(results[0]));
|
|
} else {
|
|
callback(null);
|
|
}
|
|
} else {
|
|
Ox.print('geocode failed:', status);
|
|
callback(null);
|
|
}
|
|
});
|
|
}
|
|
|
|
function getMapHeight() {
|
|
return self.options.height -
|
|
self.options.statusbar * 24 -
|
|
self.options.toolbar * 24 -
|
|
self.options.zoombar * 16;
|
|
}
|
|
|
|
function getMapType() {
|
|
return self.options.labels ? 'HYBRID' : 'SATELLITE'
|
|
}
|
|
|
|
function getPositionByName(name) {
|
|
var position = -1;
|
|
$.each(self.options.places, function(i, place) {
|
|
if (place.name == name) {
|
|
position = i;
|
|
return false;
|
|
}
|
|
});
|
|
return position;
|
|
}
|
|
|
|
function initMap() {
|
|
self.geocoder = new google.maps.Geocoder();
|
|
self.places = [];
|
|
self.options.places.forEach(function(place, i) {
|
|
if (Ox.isUndefined(place.id)) {
|
|
place.id = Ox.uid();
|
|
}
|
|
self.places[i] = Place(Ox.clone(place));
|
|
if (i == 0) {
|
|
Ox.print('0000', self.places[i].bounds, self.places[i].bounds.union)
|
|
} else {
|
|
Ox.print('$$$$', self.bounds)
|
|
}
|
|
self.bounds = i == 0 ?
|
|
new google.maps.LatLngBounds(
|
|
new google.maps.LatLng(self.places[i].south, self.places[i].west),
|
|
new google.maps.LatLng(self.places[i].north, self.places[i].east)
|
|
) :
|
|
self.bounds.union(self.places[i].bounds);
|
|
});
|
|
self.center = self.bounds ? self.bounds.getCenter() : new google.maps.LatLng(0, 0);
|
|
self.zoom = 1; // fixme: should depend on height
|
|
self.map = new google.maps.Map(self.$map.$element[0], {
|
|
center: self.center,
|
|
disableDefaultUI: true,
|
|
disableDoubleClickZoom: true,
|
|
mapTypeId: google.maps.MapTypeId[getMapType()],
|
|
zoom: self.zoom
|
|
});
|
|
if (self.bounds) {
|
|
self.map.fitBounds(self.bounds);
|
|
self.zoom = self.map.getZoom();
|
|
}
|
|
self.places.forEach(function(place) {
|
|
place.add(self.map);
|
|
});
|
|
google.maps.event.addListener(self.map, 'click', clickMap);
|
|
google.maps.event.addListener(self.map, 'zoom_changed', zoomChanged);
|
|
google.maps.event.trigger(self.map, 'resize');
|
|
that.gainFocus();
|
|
that.triggerEvent('load');
|
|
}
|
|
|
|
function pan(x, y) {
|
|
self.map.panBy(x * self.options.width / 2, y * getMapHeight() / 2);
|
|
};
|
|
|
|
function panToPlace() {
|
|
Ox.print('panToPlace:', self.options.selected)
|
|
if (self.options.selected !== null) {
|
|
self.map.panTo(getPlaceById(self.options.selected).center);
|
|
}
|
|
}
|
|
|
|
function removePlace(id) {
|
|
|
|
}
|
|
|
|
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 selectPlace(id) {
|
|
Ox.print('Ox.Map selectPlace()', id, self.selected)
|
|
var place;
|
|
if (id != self.selected) {
|
|
place = getPlaceById(self.selected);
|
|
place && place.deselect();
|
|
place = getPlaceById(id);
|
|
place && place.select();
|
|
}
|
|
if (id) {
|
|
//self.map.setCenter(place.center);
|
|
/*
|
|
if (
|
|
self.map.getBounds().contains(place.bounds.getSouthWest()) &&
|
|
self.map.getBounds().contains(place.bounds.getNorthEast())
|
|
) {
|
|
} else {
|
|
self.map.fitBounds(place.bounds);
|
|
}
|
|
*/
|
|
}
|
|
self.options.selected = id;
|
|
self.selected = id;
|
|
setStatus();
|
|
//that.triggerEvent('select', place);
|
|
/*
|
|
that.triggerEvent('select', {
|
|
id: self.options.selected
|
|
});
|
|
*/
|
|
};
|
|
|
|
function setStatus() {
|
|
Ox.print('setStatus()', self.options.selected)
|
|
var place;
|
|
if (self.options.statusbar) {
|
|
if (self.options.selected) {
|
|
place = getPlaceById(self.options.selected);
|
|
}
|
|
self.$placeNameInput.options({
|
|
value: self.options.selected ? place.name : ''
|
|
});
|
|
self.$placeGeonameInput.options({
|
|
value: self.options.selected ? place.geoname : ''
|
|
});
|
|
self.$placeButton.options({
|
|
title: self.options.selected ? 'Add Place' : 'New Place'
|
|
});
|
|
}
|
|
}
|
|
|
|
function submitFind(event, data) {
|
|
that.findPlace(data.value, function(place) {
|
|
setStatus(place);
|
|
});
|
|
}
|
|
|
|
function toggleLabels() {
|
|
self.options.labels = !self.options.labels
|
|
self.map.setMapTypeId(google.maps.MapTypeId[getMapType()]);
|
|
self.$labelsButton.options({
|
|
title: self.$labelsButton.options('title') == 'Show Labels' ?
|
|
'Hide Labels' : 'Show Labels'
|
|
});
|
|
}
|
|
|
|
function zoom(z) {
|
|
self.map.setZoom(self.map.getZoom() + z);
|
|
}
|
|
|
|
function zoomChanged() {
|
|
var zoom = self.map.getZoom();
|
|
self.options.zoombar && self.$zoomInput.options({value: zoom});
|
|
that.triggerEvent('zoom', {
|
|
value: zoom
|
|
});
|
|
}
|
|
|
|
function zoomToPlace() {
|
|
Ox.print('zoomToPlace')
|
|
if (self.options.selected !== null) {
|
|
self.map.fitBounds(getPlaceById(self.options.selected).bounds);
|
|
}
|
|
}
|
|
|
|
function Marker(place) {
|
|
var marker = new google.maps.Marker({
|
|
position: place.center,
|
|
title: place.name
|
|
}),
|
|
selected = false,
|
|
timeout = 0;
|
|
setOptions();
|
|
Ox.print('MARKER', marker)
|
|
function click() {
|
|
var metaKey = self.metaKey;
|
|
timeout = setTimeout(function() {
|
|
Ox.print('$$$$ CLICK', metaKey, selected)
|
|
if (!selected) {
|
|
selected = true;
|
|
selectPlace(place.id);
|
|
} else if (!metaKey) {
|
|
panToPlace(place);
|
|
} else {
|
|
selected = false;
|
|
selectPlace(null);
|
|
}
|
|
}, 250);
|
|
Ox.print('$$$$ TIMEOUT')
|
|
}
|
|
function dblclick() {
|
|
Ox.print('$$$$ DBLCLICK', timeout)
|
|
clearTimeout(timeout);
|
|
if (!selected) {
|
|
selected = true;
|
|
selectPlace(place.id);
|
|
}
|
|
self.map.fitBounds(place.bounds);
|
|
return false;
|
|
}
|
|
function setOptions() {
|
|
marker.setOptions({
|
|
icon: oxui.path + 'png/ox.ui/marker' +
|
|
(place.id[0] == '_' ? 'Result' : '') +
|
|
(selected ? 'Selected' : '') + '.png'
|
|
});
|
|
}
|
|
return {
|
|
add: function() {
|
|
Ox.print('Marker.add()')
|
|
marker.setMap(self.map);
|
|
google.maps.event.addListener(marker, 'click', click);
|
|
google.maps.event.addListener(marker, 'dblclick', dblclick);
|
|
},
|
|
deselect: function() {
|
|
selected = false;
|
|
setOptions();
|
|
},
|
|
remove: function() {
|
|
marker.setMap(null);
|
|
google.maps.event.clearListeners(marker);
|
|
},
|
|
select: function() {
|
|
selected = true;
|
|
setOptions();
|
|
}
|
|
};
|
|
};
|
|
|
|
function Place(place) {
|
|
var marker, polygon, selected;
|
|
if ('name' in place) {
|
|
// place object
|
|
//Ox.extend(place, place);
|
|
place.bounds = new google.maps.LatLngBounds(
|
|
new google.maps.LatLng(place.south, place.west),
|
|
new google.maps.LatLng(place.north, place.east)
|
|
);
|
|
Ox.print('place.bounds', place.bounds, place.bounds.union)
|
|
} else {
|
|
// geodata object
|
|
if (!place.geometry.bounds) {
|
|
Ox.print('NO BOUNDS, ONLY VIEWPORT')
|
|
}
|
|
Ox.extend(place, {
|
|
bounds: place.geometry.bounds || place.geometry.viewport,
|
|
countryCode: Ox.getCountryCode(place.formatted_address),
|
|
geoname: place.formatted_address,
|
|
id: '_' + Ox.uid(),
|
|
name: place.formatted_address.split(', ')[0]
|
|
});
|
|
Ox.extend(place, {
|
|
south: Ox.round(place.bounds.getSouthWest().lat(), 8),
|
|
west: Ox.round(place.bounds.getSouthWest().lng(), 8),
|
|
north: Ox.round(place.bounds.getNorthEast().lat(), 8),
|
|
east: Ox.round(place.bounds.getNorthEast().lng(), 8)
|
|
});
|
|
}
|
|
place.center = place.bounds.getCenter();
|
|
Ox.extend(place, {
|
|
lat: Ox.round(place.center.lat(), 8),
|
|
lng: Ox.round(place.center.lng(), 8),
|
|
size: Ox.getArea(
|
|
{lat: place.south, lng: place.west},
|
|
{lat: place.north, lng: place.east}
|
|
)
|
|
});
|
|
Ox.print('PLACE', place)
|
|
marker = Marker(place);
|
|
polygon = Polygon(place);
|
|
selected = false;
|
|
return Ox.extend(place, {
|
|
add: function() {
|
|
Ox.print('Place.add()', self.resultPlace)
|
|
marker.add();
|
|
},
|
|
deselect: function() {
|
|
selected = false;
|
|
marker.deselect();
|
|
polygon.remove();
|
|
},
|
|
remove: function() {
|
|
Ox.print('REMOVE!!!', selected)
|
|
if (place.id[0] == '_') {
|
|
self.resultPlace = null;
|
|
}
|
|
selected && polygon.remove();
|
|
marker.remove();
|
|
},
|
|
select: function() {
|
|
Ox.print('Place.select()')
|
|
selected = true;
|
|
marker.select();
|
|
polygon.add();
|
|
}
|
|
});
|
|
};
|
|
|
|
function Polygon(place) {
|
|
var listeners = {},
|
|
polygon = new google.maps.Polygon({
|
|
paths: [
|
|
new google.maps.LatLng(place.south, place.west),
|
|
new google.maps.LatLng(place.north, place.west),
|
|
new google.maps.LatLng(place.north, place.east),
|
|
new google.maps.LatLng(place.south, place.east),
|
|
new google.maps.LatLng(place.south, place.west)
|
|
]
|
|
}),
|
|
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() {
|
|
Ox.print('Polygon.add()')
|
|
polygon.setMap(self.map);
|
|
listeners.click = google.maps.event.addListener(polygon, 'click', click);
|
|
},
|
|
deselect: function() {
|
|
selected = false;
|
|
setOptions();
|
|
},
|
|
remove: function() {
|
|
polygon.setMap(null);
|
|
google.maps.event.removeListener(listeners.click);
|
|
},
|
|
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 == 'places') {
|
|
loadPlaces();
|
|
} else if (key == 'selected') {
|
|
selectPlace(value);
|
|
} else if (key == 'type') {
|
|
|
|
}
|
|
};
|
|
|
|
that.removePlace = function(id) {
|
|
|
|
};
|
|
|
|
that.findPlace = function(name, callback) {
|
|
getPlaceByName(name, function(place) {
|
|
if (place) {
|
|
/*
|
|
self.marker = place.marker.add('yellow');
|
|
self.polygon && self.polygon.remove();
|
|
self.polygon = place.polygon.add();
|
|
*/
|
|
addPlace(place);
|
|
self.resultPlace = place;
|
|
selectPlace(place.id);
|
|
self.bounds = place.bounds;
|
|
Ox.print('SELF.BOUNDS', self.bounds)
|
|
self.map.fitBounds(self.bounds);
|
|
}
|
|
callback(place);
|
|
});
|
|
};
|
|
|
|
that.resizeMap = function() {
|
|
Ox.print('Ox.Map.resizeMap()');
|
|
var center = self.map.getCenter();
|
|
self.options.height = that.$element.height();
|
|
self.options.width = that.$element.width();
|
|
Ox.print(self.options.width, self.options.height)
|
|
self.$map.css({
|
|
height: getMapHeight() + 'px',
|
|
width: self.options.width + 'px'
|
|
});
|
|
google.maps.event.trigger(self.map, 'resize');
|
|
self.map.setCenter(center);
|
|
self.options.zoombar && self.$zoomInput.options({
|
|
size: self.options.width
|
|
});
|
|
}
|
|
|
|
that.zoomToPlace = function(id) {
|
|
Ox.print('zoomToPlace', id)
|
|
var place = getPlaceById(id);
|
|
self.bounds = place.bounds;
|
|
self.map.fitBounds(self.bounds);
|
|
};
|
|
|
|
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 += '¢er=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],
|
|
menu = self.options.mainmenu || self.options.parent || that,
|
|
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--');
|
|
menu.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();
|
|
menu.triggerEvent('change', {
|
|
checked: item.options('checked'),
|
|
id: item.options('id'),
|
|
title: Ox.stripTags(item.options('title')[0])
|
|
});
|
|
}
|
|
} else {
|
|
menu.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 ('id' in item) {
|
|
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, {
|
|
keyboard: parseKeyboard(options.keyboard || self.defaults.keyboard),
|
|
title: Ox.toArray(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.toArray(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,
|
|
extras: [],
|
|
size: 16,
|
|
title: ''
|
|
})
|
|
.options(options)
|
|
.addClass('OxCollapsePanel'),
|
|
// fixme: the following should all be self.foo
|
|
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({
|
|
style: 'symbol',
|
|
title: title,
|
|
type: 'image',
|
|
})
|
|
.click(toggleCollapsed)
|
|
.appendTo($titlebar),
|
|
$title = new Ox.Element()
|
|
.addClass('OxTitle')
|
|
.html(self.options.title/*.toUpperCase()*/)
|
|
.appendTo($titlebar),
|
|
$extras;
|
|
if (self.options.extras.length) {
|
|
$extras = new Ox.Element()
|
|
.addClass('OxExtras')
|
|
.appendTo($titlebar);
|
|
self.options.extras.forEach(function($extra) {
|
|
$extra.appendTo($extras);
|
|
});
|
|
}
|
|
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 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);
|
|
that.triggerEvent('toggle', {
|
|
collapsed: self.options.collapsed
|
|
});
|
|
}
|
|
self.onChange = function(key, value) {
|
|
if (key == 'collapsed') {
|
|
|
|
} else if (key == 'title') {
|
|
$title.html(self.options.title);
|
|
}
|
|
};
|
|
that.update = function() { // fixme: used anywhere?
|
|
self.options.collapsed && that.$content.css({
|
|
marginTop: -that.$content.height()
|
|
});
|
|
};
|
|
return that;
|
|
};
|
|
|
|
/**
|
|
*/
|
|
Ox.Panel = function(options, self) {
|
|
var self = self || {},
|
|
that = new Ox.Element({}, self)
|
|
.addClass('OxPanel');
|
|
return that;
|
|
};
|
|
|
|
Ox.SplitPanel_ = function(options, self) {
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
elements: [],
|
|
orientation: 'horizontal'
|
|
})
|
|
.options(options)
|
|
.addClass(
|
|
'OxSplitPanel_ Ox' + Ox.toTitleCase(self.options.orientation)
|
|
);
|
|
|
|
Ox.extend(self, {
|
|
$separators: [],
|
|
clientXY: self.options.orientation == 'horizontal' ? 'clientX' : 'clientY',
|
|
dimensions: Ox.UI.DIMENSIONS[self.options.orientation],
|
|
edges: Ox.UI.EDGES[self.options.orientation]
|
|
});
|
|
|
|
self.options.elements.forEach(function(element, i) {
|
|
self.options.elements[i] = Ox.extend({
|
|
collapsible: false,
|
|
collapsed: false,
|
|
resizable: false,
|
|
resize: [],
|
|
size: 'auto'
|
|
}, element);
|
|
});
|
|
|
|
self.autoPercent = (100 - self.options.elements.reduce(function(val, element) {
|
|
return val + (Ox.endsWith(element.size, '%') ? parseFloat(element.size) : 0);
|
|
}, 0)) / self.options.elements.filter(function(element) {
|
|
return element.size == 'auto';
|
|
}).length + '%';
|
|
|
|
self.options.elements.forEach(function(element, i) {
|
|
var flex, index = i == 0 ? 0 : 1;
|
|
if (Ox.isNumber(element.size)) {
|
|
element.element.css(self.dimensions[0], element.size + 'px');
|
|
} else {
|
|
flex = (
|
|
element.size == 'auto' ? self.autoPercent : element.size
|
|
).replace('%', '');
|
|
element.element.css({
|
|
boxFlex: flex,
|
|
MozBoxFlex: flex,
|
|
WebkitBoxFlex: flex
|
|
});
|
|
}
|
|
element.element.appendTo(that);
|
|
if (element.collapsible || element.resizable) {
|
|
self.$separators.push(
|
|
Ox.Element()
|
|
.addClass('OxSeparator')
|
|
.bindEvent({
|
|
anyclick: function() {
|
|
that.toggle(i);
|
|
},
|
|
dragstart: function(event, e) {
|
|
dragstart(i, e);
|
|
},
|
|
drag: function(event, e) {
|
|
drag(i, e);
|
|
},
|
|
dragend: function(event, e) {
|
|
dragend(i, e);
|
|
},
|
|
})
|
|
.append($('<div>').addClass('OxSpace'))
|
|
.append($('<div>').addClass('OxLine'))
|
|
.append($('<div>').addClass('OxSpace'))
|
|
['insert' + (index ? 'Before' : 'After')](element.element)
|
|
);
|
|
}
|
|
|
|
});
|
|
|
|
function dragstart(pos, e) {
|
|
var element = self.options.elements[pos],
|
|
size = element.element[self.dimensions[0]]();
|
|
if (element.resizable && !element.collapsed) {
|
|
self.drag = {
|
|
size: size,
|
|
startPos: e[self.clientXY],
|
|
startSize: size
|
|
};
|
|
Ox.print('self.drag', self.drag)
|
|
}
|
|
}
|
|
|
|
function drag(pos, e) {
|
|
var data = {},
|
|
element = self.options.elements[pos],
|
|
index = pos == 0 ? 0 : 1;
|
|
if (element.resizable && !element.collapsed) {
|
|
var d = e[self.clientXY] - self.drag.startPos,
|
|
size = Ox.limit(
|
|
self.drag.startSize + d * (index ? -1 : 1),
|
|
element.resize[0],
|
|
element.resize[element.resize.length - 1]
|
|
);
|
|
element.resize.forEach(function(v) {
|
|
if (size >= v - 8 && size <= v + 8) {
|
|
size = v;
|
|
return false;
|
|
}
|
|
});
|
|
if (size != self.drag.size) {
|
|
self.drag.size = size;
|
|
data[self.dimensions[0]] = size;
|
|
element.element
|
|
.css(self.dimensions[0], size + 'px')
|
|
.triggerEvent('resize', data);
|
|
triggerEvents('resize', pos);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
function dragend(pos, e) {
|
|
var data = {},
|
|
element = self.options.elements[pos];
|
|
if (element.resizable && !element.collapsed) {
|
|
data[self.dimensions[0]] = self.drag.size
|
|
element.element.triggerEvent('resizeend', data);
|
|
triggerEvents('resizeend', pos);
|
|
}
|
|
}
|
|
|
|
function triggerEvents(event, pos) {
|
|
var data = {};
|
|
self.options.elements.forEach(function(element, i) {
|
|
if (i != pos && element.size == 'auto') {
|
|
data[self.dimensions[0]] = element.element[self.dimensions[0]]();
|
|
element.element.triggerEvent(event, data);
|
|
}
|
|
});
|
|
}
|
|
|
|
that.replaceElement = function(pos, element) {
|
|
var $element = self.options.elements[pos].element,
|
|
size = self.options.elements[pos].size;
|
|
$element.replaceWith(self.options.elements[pos].element = element);
|
|
if (size == 'auto') {
|
|
$element.css(self.boxFlexCSS);
|
|
} else {
|
|
$element.css(self.dimensions[0], size + 'px')
|
|
}
|
|
return that;
|
|
};
|
|
|
|
that.size = function(pos, size) {
|
|
var element = self.options.elements[pos],
|
|
ret;
|
|
if (Ox.isUndefined(size)) {
|
|
ret = element.element[self.dimensions[0]]();
|
|
} else {
|
|
element.size = size;
|
|
element.element.css(self.dimensions[0], size + 'px')
|
|
ret = that;
|
|
}
|
|
return that;
|
|
}
|
|
|
|
that.toggle = function(pos) {
|
|
var css = {},
|
|
element = self.options.elements[pos],
|
|
flex,
|
|
index = pos == 0 ? 0 : 1,
|
|
size = element.element[self.dimensions[0]]();
|
|
if (element.collapsible) {
|
|
element.collapsed = !element.collapsed;
|
|
css['margin' + Ox.toTitleCase(self.edges[0][index])] =
|
|
element.collapsed ? -size : 0;
|
|
Ox.print('css', css);
|
|
that.animate(css, 250, function() {
|
|
element.element.triggerEvent('toggle', {collapsed: element.collapsed});
|
|
triggerEvents('resize', pos);
|
|
});
|
|
}
|
|
}
|
|
|
|
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) {
|
|
// fixme: maybe we can add a conditional here, since init
|
|
// is about elements that are collapsed splitpanels
|
|
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.replaceElement = function(id, element) {
|
|
// 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
|
|
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;
|
|
that.animate(animate, 200, function() { // fixme: 250?
|
|
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.AnnotationPanel = function(options, self) {
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
id: '',
|
|
items: [],
|
|
title: '',
|
|
type: 'text',
|
|
width: 0
|
|
})
|
|
.options(options || {});
|
|
|
|
self.selected = -1;
|
|
|
|
that.$element = new Ox.CollapsePanel({
|
|
collapsed: false,
|
|
extras: [
|
|
new Ox.Button({
|
|
id: 'add',
|
|
style: 'symbol',
|
|
title: 'Add',
|
|
type: 'image'
|
|
}).bindEvent({
|
|
click: function(event, data) {
|
|
that.triggerEvent('add', {value: ''});
|
|
}
|
|
})
|
|
],
|
|
size: 16,
|
|
title: self.options.title
|
|
})
|
|
.addClass('OxAnnotationPanel')
|
|
.bindEvent({
|
|
toggle: togglePanel
|
|
});
|
|
that.$content = that.$element.$content;
|
|
|
|
self.$annotations = new Ox.List({
|
|
construct: function(data) {
|
|
return new Ox.Element('div')
|
|
.addClass('OxAnnotation OxEditable OxTarget')
|
|
.html(Ox.parseHTML(data.value));
|
|
},
|
|
items: $.map(self.options.items, function(v, i) {
|
|
return {
|
|
id: v.id || i + '',
|
|
value: v.value
|
|
};
|
|
}),
|
|
unique: 'id'
|
|
})
|
|
.bindEvent({
|
|
open: function(event, data) {
|
|
if (data.ids.length == 1) {
|
|
var pos = Ox.getPositionById(self.$annotations.options('items'), data.ids[0]);
|
|
self.$annotations.editItem(pos);
|
|
|
|
}
|
|
},
|
|
'delete': function(event, data) {
|
|
that.triggerEvent('delete', data);
|
|
},
|
|
select: selectAnnotation,
|
|
submit: updateAnnotation
|
|
})
|
|
.appendTo(that.$content);
|
|
|
|
/*
|
|
self.$annotations = new Ox.Element('div')
|
|
.appendTo(that.$content);
|
|
self.$annotation = [];
|
|
self.options.items.forEach(function(item, i) {
|
|
self.$annotation[i] = new Ox.Element('div')
|
|
.addClass('OxAnnotation')
|
|
.html(item.value.replace(/\n/g, '<br/>'))
|
|
.click(function() {
|
|
clickAnnotation(i);
|
|
})
|
|
.appendTo(self.$annotations);
|
|
});
|
|
*/
|
|
function selectAnnotation(event, data) {
|
|
var item = Ox.getObjectById(self.options.items, data.ids[0]);
|
|
that.triggerEvent('select', {
|
|
'in': item['in'],
|
|
'out': item.out,
|
|
'layer': self.options.id
|
|
});
|
|
}
|
|
function updateAnnotation(event, data) {
|
|
var item = Ox.getObjectById(self.options.items, data.id);
|
|
item.value = data.value;
|
|
that.triggerEvent('submit', item);
|
|
}
|
|
|
|
function togglePanel() {
|
|
|
|
}
|
|
|
|
that.addItem = function(item) {
|
|
var pos = 0;
|
|
self.options.items.splice(pos, 0, item);
|
|
self.$annotations.addItems(pos, [item]);
|
|
self.$annotations.editItem(pos);
|
|
}
|
|
|
|
that.removeItems = function(ids) {
|
|
self.$annotations.removeItems(ids);
|
|
}
|
|
that.deselectItems = function() {
|
|
if(self.$annotations.options('selected'))
|
|
self.$annotations.options('selected',[]);
|
|
}
|
|
return that;
|
|
|
|
};
|
|
|
|
Ox.BlockTimeline = 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)
|
|
.bindEvent({
|
|
drag: function(event, e) {
|
|
mousedown(e);
|
|
}
|
|
});
|
|
|
|
$.extend(self, {
|
|
$images: [],
|
|
$lines: [],
|
|
$markerPoint: [],
|
|
$selection: [],
|
|
$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) {
|
|
// fixme: get URLs once, not once for every line
|
|
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>')
|
|
.addClass('OxTimelineSmallSubtitles')
|
|
.attr({
|
|
src: self.subtitlesImageURL
|
|
})
|
|
.css({
|
|
marginLeft: (-i * self.options.width) + 'px'
|
|
})
|
|
.appendTo(self.$lines[i].$element);
|
|
}
|
|
if (self.options.points[0] != self.options.points[1]) {
|
|
addSelection[i];
|
|
}
|
|
}
|
|
|
|
function addSelection(i) {
|
|
self.selectionImageURL = getSelectionImageURL();
|
|
self.$selection[i] && self.$selection[i].remove();
|
|
self.$selection[i] = $('<img>')
|
|
.addClass('OxTimelineSmallSelection')
|
|
.attr({
|
|
src: self.selectionImageURL
|
|
})
|
|
.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 getSelectionImageURL() {
|
|
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,
|
|
points = $.map(self.options.points, function(v, i) {
|
|
return Math.round(v) + i;
|
|
}),
|
|
top = 0,
|
|
bottom = 18;
|
|
$.each(Ox.range(points[0], points[1]), function(i, x) {
|
|
$.each(Ox.range(top, bottom), function(i, y) {
|
|
var color = (y == top || y == bottom - 1) ? [255, 255, 255, 255] : [255, 255, 255, 64],
|
|
index = x * 4 + y * 4 * width;
|
|
data[index] = color[0];
|
|
data[index + 1] = color[1];
|
|
data[index + 2] = color[2];
|
|
data[index + 3] = color[3]
|
|
});
|
|
});
|
|
context.putImageData(imageData, 0, 0);
|
|
return canvas.toDataURL();
|
|
}
|
|
|
|
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]
|
|
var inPoint = Math.round(v['in']),
|
|
outPoint = Math.round(v.out) + 1,
|
|
lines = v.value.split('\n').length,
|
|
bottom = 15,
|
|
top = bottom - lines - 2;
|
|
$.each(Ox.range(inPoint, outPoint), function(i, x) {
|
|
$.each(Ox.range(top, bottom), function(i, y) {
|
|
var color = (y == top || y == bottom - 1) ? [0, 0, 0] : [255, 255, 255],
|
|
index = x * 4 + y * 4 * width;
|
|
data[index] = color[0];
|
|
data[index + 1] = color[1];
|
|
data[index + 2] = color[2];
|
|
data[index + 3] = 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) {
|
|
var $target = $(e.target);
|
|
if (
|
|
$target.hasClass('OxTimelineSmallImage') ||
|
|
$target.hasClass('OxTimelineSmallSubtitles') ||
|
|
$target.hasClass('OxTimelineSmallSelection')
|
|
) {
|
|
self.options.position = getPosition(e);
|
|
setPosition();
|
|
that.triggerEvent('change', {
|
|
position: self.options.position
|
|
});
|
|
}
|
|
e.preventDefault();
|
|
}
|
|
|
|
function mouseleave(e) {
|
|
self.$tooltip && self.$tooltip.hide();
|
|
}
|
|
|
|
function mousemove(e) {
|
|
var $target = $(e.target),
|
|
position,
|
|
subtitle;
|
|
if (
|
|
$target.hasClass('OxTimelineSmallImage') ||
|
|
$target.hasClass('OxTimelineSmallSubtitles') ||
|
|
$target.hasClass('OxTimelineSmallSelection')
|
|
) {
|
|
position = getPosition(e),
|
|
subtitle = getSubtitle(position);
|
|
self.$tooltip = new Ox.Tooltip({
|
|
title: subtitle ?
|
|
'<span class=\'OxBright\'>' +
|
|
Ox.highlight(subtitle.value, 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);
|
|
} else {
|
|
self.$tooltip && self.$tooltip.hide();
|
|
}
|
|
|
|
}
|
|
|
|
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 = Math.round(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);
|
|
}
|
|
|
|
function updateSelection() {
|
|
self.$lines.forEach(function($line, i) {
|
|
addSelection(i);
|
|
});
|
|
}
|
|
|
|
self.onChange = function(key, value) {
|
|
//Ox.print('onChange:', key, value)
|
|
if (key == 'points') {
|
|
//Ox.print('key', key, 'value', value)
|
|
setMarkerPoint(0);
|
|
setMarkerPoint(1);
|
|
updateSelection()
|
|
} else if (key == 'position') {
|
|
setPosition();
|
|
} else if (key == 'width') {
|
|
setWidth();
|
|
}
|
|
};
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
Ox.Flipbook = function(options, self) {
|
|
|
|
var self = self || {},
|
|
frame = $('<img>').css({
|
|
'position': 'absolute',
|
|
'width': '100%',
|
|
'height': 'auto'
|
|
})
|
|
.hide(),
|
|
icon = $('<img>').css({
|
|
'position': 'absolute',
|
|
'width': '100%',
|
|
'height': 'auto'
|
|
}),
|
|
frames = {},
|
|
timestamp = $('<div>').css({
|
|
'position': 'absolute',
|
|
'text-align': 'center',
|
|
'width': '100%',
|
|
})
|
|
.hide(),
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
frames: {},
|
|
duration: 0,
|
|
icon: '',
|
|
})
|
|
.options(options || {})
|
|
.append(icon)
|
|
.append(frame)
|
|
.append(timestamp)
|
|
.mouseover(function() {
|
|
frame.show();
|
|
timestamp.show();
|
|
icon.hide();
|
|
})
|
|
.mousemove(function(event) {
|
|
var position = getPosition(event),
|
|
image = getFrame(position),
|
|
frameHeight = image?image.height:that.height();
|
|
frame.attr('src', image.src);
|
|
timestamp.html(Ox.formatDuration(position, 'short'));
|
|
|
|
var height = (that.height() - frameHeight)/2;
|
|
frame.css({'top': height + 'px'});
|
|
timestamp.css({'top': (frameHeight + height) + 'px'});
|
|
})
|
|
.mouseout(function() {
|
|
frame.hide();
|
|
timestamp.hide();
|
|
icon.show();
|
|
})
|
|
.mousedown(function(event) {
|
|
that.triggerEvent('click', {
|
|
'position': getPosition(event)
|
|
});
|
|
});
|
|
|
|
function getPosition(event) {
|
|
var position = Math.floor(event.clientX - that.offset().left);
|
|
position = (position / that.width()) * self.options.duration;
|
|
return position;
|
|
}
|
|
|
|
function getFrame(position) {
|
|
var frame;
|
|
$.each(frames, function(i, img) {
|
|
if(!frame || i <= position)
|
|
frame = img;
|
|
});
|
|
return frame;
|
|
}
|
|
|
|
function cacheFrames() {
|
|
$.each(self.options.frames, function(i, src) {
|
|
frames[i] = new Image();
|
|
frames[i].onload = function() {
|
|
frameHeight = frames[i].height / frames[i].width * that.width();
|
|
}
|
|
frames[i].src = src;
|
|
});
|
|
}
|
|
|
|
self.onChange = function(key, value) {
|
|
if (key == 'frames') {
|
|
cacheFrames();
|
|
} else if (key == 'icon') {
|
|
icon.attr('src', value);
|
|
}
|
|
}
|
|
|
|
if(options.icon)
|
|
icon.attr('src', options.icon);
|
|
cacheFrames();
|
|
return that;
|
|
};
|
|
|
|
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')
|
|
.mouseleave(mouseleave)
|
|
.mousemove(mousemove)
|
|
.bindEvent({
|
|
anyclick: click,
|
|
dragstart: dragstart,
|
|
drag: drag
|
|
});
|
|
|
|
$.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) - 2) + 'px'
|
|
})
|
|
.html(Ox.highlight(v.value, 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 click(event, e) {
|
|
self.options.position = Ox.limit(
|
|
self.options.position + (e.clientX - that.$element.offset().left - self.center - 1) / self.fps,
|
|
0, self.options.duration
|
|
);
|
|
setPosition();
|
|
triggerChangeEvent();
|
|
}
|
|
|
|
function dragstart(event, e) {
|
|
self.drag = {x: e.clientX};
|
|
}
|
|
|
|
function drag(event, e) {
|
|
self.options.position = Ox.limit(
|
|
self.options.position + (self.drag.x - e.clientX) / self.fps,
|
|
0, self.options.duration
|
|
);
|
|
self.drag.x = e.clientX;
|
|
setPosition();
|
|
triggerChangeEvent();
|
|
}
|
|
|
|
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 triggerChangeEvent() {
|
|
that.triggerEvent('change', {
|
|
position: self.options.position
|
|
});
|
|
}
|
|
|
|
function updateTooltip() {
|
|
// fixme: duplicated, need getPosition(e)
|
|
var position = self.options.position + (self.clientX - that.offset().left - self.center - 1) / 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({
|
|
duration: 0,
|
|
find: '',
|
|
matches: [],
|
|
points: [0, 0],
|
|
position: 0,
|
|
subtitles: [],
|
|
videoId: '',
|
|
width: 0
|
|
})
|
|
.options(options || {})
|
|
.addClass('OxTimelineSmall')
|
|
.mousedown(mousedown)
|
|
.mouseleave(mouseleave)
|
|
.mousemove(mousemove)
|
|
.bindEvent({
|
|
drag: function(event, e) {
|
|
mousedown(e);
|
|
}
|
|
});
|
|
|
|
$.extend(self, {
|
|
$images: [],
|
|
$markerPoint: [],
|
|
$subtitles: [],
|
|
hasSubtitles: self.options.subtitles.length,
|
|
height: 16,
|
|
margin: 8
|
|
});
|
|
|
|
that.css({
|
|
width: (self.options.width + self.margin) + 'px',
|
|
height: (self.height + self.margin) + 'px'
|
|
});
|
|
|
|
self.$line = $('<img>')
|
|
.addClass('OxTimelineSmallImage')
|
|
.attr({
|
|
src: '/' + self.options.videoId + '/timelines/timeline.16.0.png'
|
|
})
|
|
.css({
|
|
position: 'absolute',
|
|
left: '4px',
|
|
top: '4px',
|
|
width: self.options.width,
|
|
height: '16px'
|
|
})
|
|
.appendTo(that.$element);
|
|
|
|
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 getPosition(e) {
|
|
return e.offsetX / self.options.width * self.options.duration;
|
|
}
|
|
|
|
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 mousedown(e) {
|
|
var $target = $(e.target);
|
|
if (
|
|
$target.hasClass('OxTimelineSmallImage') ||
|
|
$target.hasClass('OxTimelineSmallSubtitles')
|
|
) {
|
|
self.options.position = getPosition(e);
|
|
setPosition();
|
|
that.triggerEvent('change', {
|
|
position: self.options.position
|
|
});
|
|
}
|
|
e.preventDefault();
|
|
}
|
|
|
|
function mouseleave(e) {
|
|
self.$tooltip && self.$tooltip.hide();
|
|
}
|
|
|
|
function mousemove(e) {
|
|
var $target = $(e.target),
|
|
position,
|
|
subtitle;
|
|
if (
|
|
$target.hasClass('OxTimelineSmallImage') ||
|
|
$target.hasClass('OxTimelineSmallSubtitles')
|
|
) {
|
|
position = getPosition(e),
|
|
subtitle = getSubtitle(position);
|
|
self.$tooltip = new Ox.Tooltip({
|
|
title: subtitle ?
|
|
'<span class=\'OxBright\'>' +
|
|
Ox.highlight(subtitle.value, 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);
|
|
} else {
|
|
self.$tooltip && self.$tooltip.hide();
|
|
}
|
|
|
|
}
|
|
|
|
function setMarker() {
|
|
self.$markerPosition
|
|
.css({
|
|
left: parseInt(
|
|
self.options.position / self.options.duration * self.options.width
|
|
) + 'px',
|
|
top: '2px',
|
|
});
|
|
}
|
|
|
|
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.$line.css({
|
|
width: self.options.width + 'px',
|
|
});
|
|
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({
|
|
annotationsSize: 0,
|
|
cuts: [],
|
|
duration: 0,
|
|
find: '',
|
|
frameURL: function() {},
|
|
fps: 25, // fixme: doesn't get handed through to player
|
|
height: 0,
|
|
largeTimeline: true,
|
|
layers: [],
|
|
matches: [],
|
|
points: [0, 0],
|
|
position: 0,
|
|
posterFrame: 0,
|
|
showAnnotations: false,
|
|
subtitles: [],
|
|
videoHeight: 0,
|
|
videoId: '',
|
|
videoWidth: 0,
|
|
videoSize: 'small',
|
|
videoURL: '',
|
|
width: 0
|
|
})
|
|
.options(options || {})
|
|
.mousedown(function() {
|
|
that.gainFocus();
|
|
})
|
|
.bindEvent({
|
|
key_shift_0: function() {
|
|
movePositionBy(-self.options.position);
|
|
},
|
|
key_alt_left: function() {
|
|
},
|
|
key_alt_right: function() {
|
|
},
|
|
key_alt_shift_left: function() {
|
|
},
|
|
key_alt_shift_right: function() {
|
|
},
|
|
key_backslash: function() {
|
|
select('subtitle');
|
|
},
|
|
key_closebracket: function() {
|
|
movePositionTo('subtitle', 1);
|
|
},
|
|
key_comma: function() {
|
|
movePositionTo('cut', -1);
|
|
},
|
|
key_dot: function() {
|
|
movePositionTo('cut', 1);
|
|
},
|
|
key_down: function() {
|
|
movePositionBy(self.sizes.timeline[0].width);
|
|
},
|
|
key_i: function() {
|
|
setPoint('in');
|
|
},
|
|
key_left: function() {
|
|
movePositionBy(-1);
|
|
},
|
|
key_m: toggleMute,
|
|
key_o: function() {
|
|
setPoint('out');
|
|
},
|
|
key_openbracket: function() {
|
|
movePositionTo('subtitle', -1);
|
|
},
|
|
key_p: playInToOut,
|
|
key_right: function() {
|
|
movePositionBy(1);
|
|
},
|
|
key_s: function() {
|
|
// toggleSize
|
|
},
|
|
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_i: function() {
|
|
goToPoint('in');
|
|
},
|
|
key_shift_o: function() {
|
|
goToPoint('out');
|
|
},
|
|
key_shift_right: function() {
|
|
movePositionBy(0.04);
|
|
//movePositionBy(60);
|
|
},
|
|
key_shift_up: function() {
|
|
movePositionBy(-self.options.duration);
|
|
},
|
|
key_slash: function() {
|
|
select('cut');
|
|
},
|
|
key_space: togglePlay,
|
|
key_up: function() {
|
|
movePositionBy(-self.sizes.timeline[0].width);
|
|
}
|
|
});
|
|
|
|
$.extend(self, {
|
|
$player: [],
|
|
$timeline: [],
|
|
controlsHeight: 16,
|
|
margin: 8,
|
|
videoRatio: self.options.videoWidth / self.options.videoHeight
|
|
});
|
|
|
|
self.$editor = new Ox.Element()
|
|
.addClass('OxVideoEditor')
|
|
.click(function() {
|
|
that.gainFocus()
|
|
});
|
|
|
|
self.sizes = getSizes();
|
|
|
|
$.each(['play', 'in', 'out'], function(i, type) {
|
|
self.$player[i] = new Ox.VideoEditorPlayer({
|
|
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'
|
|
})
|
|
.bindEvent(type == 'play' ? {
|
|
playing: changePlayer,
|
|
togglesize: toggleSize
|
|
} : {
|
|
change: function() {
|
|
goToPoint(type);
|
|
},
|
|
set: function() {
|
|
setPoint(type);
|
|
}
|
|
})
|
|
.appendTo(self.$editor);
|
|
});
|
|
|
|
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(self.$editor);
|
|
|
|
self.$timeline[1] = new Ox.BlockTimeline({
|
|
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(self.$editor);
|
|
|
|
self.$annotations = new Ox.Element()
|
|
.css({
|
|
overflowY: 'auto'
|
|
})
|
|
.bindEvent({
|
|
resize: resizeAnnotations,
|
|
toggle: toggleAnnotations
|
|
});
|
|
self.$annotationPanel = [];
|
|
|
|
self.options.layers.forEach(function(layer, i) {
|
|
self.$annotationPanel[i] = new Ox.AnnotationPanel(
|
|
$.extend({
|
|
width: self.options.annotationSize
|
|
}, layer)
|
|
)
|
|
.bindEvent({
|
|
add: function(event, data) {
|
|
data.layer = layer.id;
|
|
data['in'] = self.options.points[0];
|
|
data.out = self.options.points[1];
|
|
that.triggerEvent('addAnnotation', data);
|
|
},
|
|
'delete': function(event, data) {
|
|
data.layer = layer.id;
|
|
that.triggerEvent('removeAnnotations', data);
|
|
},
|
|
select: function(event, data) {
|
|
self.options.layers.forEach(function(l, j) {
|
|
if(l.id != layer.id) {
|
|
self.$annotationPanel[j].deselectItems();
|
|
}
|
|
});
|
|
selectAnnotation(event, data);
|
|
},
|
|
submit: updateAnnotation
|
|
});
|
|
self.$annotationPanel[i]
|
|
.appendTo(self.$annotations);
|
|
});
|
|
|
|
that.$element = new Ox.SplitPanel({
|
|
elements: [
|
|
{
|
|
element: self.$editor
|
|
},
|
|
{
|
|
collapsed: !self.options.showAnnotations,
|
|
collapsible: true,
|
|
element: self.$annotations,
|
|
resizable: true,
|
|
resize: [192, 256, 320, 384],
|
|
size: self.options.annotationsSize
|
|
}
|
|
],
|
|
orientation: 'horizontal'
|
|
});
|
|
|
|
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 getPoints(type) {
|
|
var found = false,
|
|
points,
|
|
positions = [];
|
|
if (type == 'cut') {
|
|
positions = self.options.cuts;
|
|
} else if (type == 'match') {
|
|
// ...
|
|
} else if (type == 'subtitle') {
|
|
self.options.subtitles.forEach(function(v, i) {
|
|
positions.push(v['in']);
|
|
positions.push(v.out);
|
|
});
|
|
}
|
|
positions.indexOf(0) == -1 && positions.unshift(0);
|
|
positions.indexOf(self.options.duration) == -1 &&
|
|
positions.push(self.options.duration);
|
|
$.each(positions, function(i, v) {
|
|
if (v > self.options.position) {
|
|
points = [positions[i - 1], positions[i]];
|
|
found = true;
|
|
return false;
|
|
}
|
|
});
|
|
return points;
|
|
}
|
|
|
|
function getSizes(scrollbarIsVisible) {
|
|
//Ox.print('getSizes', scrollbarIsVisible)
|
|
var scrollbarWidth = oxui.scrollbarSize,
|
|
contentWidth = self.options.width -
|
|
(self.options.showAnnotations * self.options.annotationsSize) - 1 -
|
|
(scrollbarIsVisible ? scrollbarWidth : 0),
|
|
height,
|
|
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);
|
|
height = getHeight();
|
|
//Ox.print('lines', lines, getHeight(), self.options.height, (scrollbarIsVisible && getHeight() <= self.options.height) ? 'scroll' : 'auto')
|
|
self.$editor.css({
|
|
overflowY: (scrollbarIsVisible && height <= self.options.height) ? 'scroll' : 'auto'
|
|
});
|
|
return (!scrollbarIsVisible && height > 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 resizeAnnotations(event, data) {
|
|
self.options.annotationsSize = data;
|
|
setSizes();
|
|
}
|
|
|
|
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 selectAnnotation(event, data) {
|
|
self.options.position = data['in']
|
|
self.options.points = [data['in'], data.out];
|
|
setPosition();
|
|
setPoints();
|
|
}
|
|
function updateAnnotation(event, data) {
|
|
data['in'] = self.options.points[0];
|
|
data.out = self.options.points[1];
|
|
that.triggerEvent('updateAnnotation', data);
|
|
}
|
|
|
|
function select(type) {
|
|
self.options.points = getPoints(type);
|
|
setPoints();
|
|
}
|
|
|
|
function setPoint(point) {
|
|
self.options.points[point == 'in' ? 0 : 1] = self.options.position;
|
|
if (self.options.points[1] < self.options.points[0]) {
|
|
self.options.points[point == 'in' ? 1 : 0] = self.options.position;
|
|
}
|
|
setPoints();
|
|
}
|
|
|
|
function setPoints() {
|
|
$.each(self.$player, function(i, v) {
|
|
v.options($.extend({
|
|
points: self.options.points
|
|
}, i ? {
|
|
position: self.options.points[i - 1]
|
|
} : {}));
|
|
});
|
|
$.each(self.$timeline, function(i, v) {
|
|
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 toggleAnnotations(event, data) {
|
|
self.options.showAnnotations = !data.collapsed;
|
|
setSizes();
|
|
}
|
|
|
|
function toggleMute() {
|
|
self.$player[0].toggleMute();
|
|
}
|
|
|
|
function togglePlay() {
|
|
self.$player[0].togglePlay();
|
|
}
|
|
|
|
function toggleSize(event, data) {
|
|
self.options.videoSize = data.size
|
|
setSizes();
|
|
that.triggerEvent('togglesize', {
|
|
size: self.options.videoSize
|
|
});
|
|
}
|
|
|
|
self.onChange = function(key, value) {
|
|
if (key == 'width' || key == 'height') {
|
|
//Ox.print('XXXX setSizes', key, value, self.options.width, self.options.height)
|
|
setSizes();
|
|
} else if (key == 'position') {
|
|
self.$player[0].position(value);
|
|
}
|
|
};
|
|
|
|
that.addAnnotation = function(layer, item) {
|
|
var i = Ox.getPositionById(self.options.layers, layer);
|
|
self.$annotationPanel[i].addItem(item);
|
|
}
|
|
|
|
that.removeAnnotations = function(layer, ids) {
|
|
var i = Ox.getPositionById(self.options.layers, layer);
|
|
self.$annotationPanel[i].removeItems(ids);
|
|
}
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
Ox.VideoEditorPlayer = function(options, self) {
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
duration: 0,
|
|
find: '',
|
|
height: 0,
|
|
points: [0, 0],
|
|
position: 0,
|
|
posterFrame: 0,
|
|
size: 'small',
|
|
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 = new Ox.VideoElement({
|
|
height: self.options.height,
|
|
paused: true,
|
|
points: self.options.points,
|
|
position: self.options.position,
|
|
url: self.options.url,
|
|
width: self.options.width
|
|
})
|
|
.bindEvent({
|
|
paused: paused,
|
|
playing: playing
|
|
})
|
|
.appendTo(that);
|
|
self.video = self.$video.$element[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: self.options.size == 'small' ? [
|
|
{id: 'large', title: 'grow'},
|
|
{id: 'small', title: 'shrink'}
|
|
] : [
|
|
{id: 'small', title: 'shrink'},
|
|
{id: 'large', title: 'grow'}
|
|
],
|
|
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.value;
|
|
return false;
|
|
}
|
|
});
|
|
return subtitle;
|
|
}
|
|
|
|
function goToPoint() {
|
|
that.triggerEvent('change', {
|
|
position: self.options.points[self.options.type == 'in' ? 0 : 1]
|
|
});
|
|
}
|
|
|
|
function paused(event, data) {
|
|
self.$playButton.toggleTitle();
|
|
}
|
|
|
|
function playing(event, data) {
|
|
self.options.position = data.position;
|
|
setMarkers();
|
|
setSubtitle();
|
|
self.$positionInput.options({
|
|
value: Ox.formatDuration(self.options.position, 3)
|
|
});
|
|
that.triggerEvent('playing', {
|
|
position: self.options.position
|
|
});
|
|
}
|
|
|
|
function setHeight() {
|
|
that.css({
|
|
height: (self.options.height + 16) + 'px'
|
|
});
|
|
self.options.type == 'play' ? self.$video.options({
|
|
height: self.options.height
|
|
}) : self.$video.css({
|
|
height: self.options.height + 'px'
|
|
});
|
|
}
|
|
|
|
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() {
|
|
var position = Ox.limit(
|
|
self.options.position - (self.options.type == 'out' ? 0.01 : 0),
|
|
0, self.options.duration - 0.01
|
|
),
|
|
url;
|
|
if (self.options.type == 'play') {
|
|
self.$video.position(self.options.position);
|
|
} else {
|
|
self.$loadingIcon && self.$loadingIcon.stop();
|
|
url = self.options.url(position);
|
|
if (self.$video.attr('src') != url) {
|
|
self.$loadingIcon = new Ox.LoadingIcon()
|
|
.appendTo(that)
|
|
.start();
|
|
self.$video.attr({
|
|
src: url
|
|
})
|
|
.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.options.type == 'play' ? self.$video.options({
|
|
width: self.options.width
|
|
}) : self.$video.css({
|
|
width: self.options.width + 'px'
|
|
});
|
|
setSubtitleSize();
|
|
}
|
|
|
|
function toggleMute() {
|
|
self.$video.toggleMute();
|
|
}
|
|
|
|
function togglePlay() {
|
|
self.video.paused ? that.play() : that.pause();
|
|
}
|
|
|
|
function toggleSize(event, data) {
|
|
self.options.size = data.id
|
|
that.triggerEvent('togglesize', {
|
|
size: self.options.size
|
|
});
|
|
}
|
|
|
|
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.mute();
|
|
return that;
|
|
};
|
|
|
|
that.pause = function() {
|
|
self.$video.pause();
|
|
return that;
|
|
};
|
|
|
|
that.play = function() {
|
|
self.$video.play();
|
|
return that;
|
|
};
|
|
|
|
that.playInToOut = function() {
|
|
self.$video.paused() && self.$playButton.toggleTitle();
|
|
self.$video.playInToOut();
|
|
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.unmute();
|
|
return that;
|
|
};
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
Ox.VideoElement = function(options, self) {
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element('video', self)
|
|
.defaults({
|
|
fps: 25,
|
|
height: 0,
|
|
loop: false,
|
|
muted: false,
|
|
paused: false,
|
|
playInToOut: false,
|
|
points: [0, 0],
|
|
position: 0,
|
|
poster: '',
|
|
url: '',
|
|
width: 0
|
|
})
|
|
.options(options || {})
|
|
.attr({
|
|
poster: self.options.poster,
|
|
preload: 'auto',
|
|
src: self.options.url
|
|
})
|
|
.css({
|
|
height: self.options.height + 'px',
|
|
width: self.options.width + 'px'
|
|
})
|
|
.bind({
|
|
ended: ended,
|
|
loadedmetadata: function() {
|
|
self.video.currentTime = self.options.position;
|
|
}
|
|
});
|
|
|
|
$.extend(self, {
|
|
millisecondsPerFrame: 1000 / self.options.fps,
|
|
video: that.$element[0]
|
|
});
|
|
|
|
function ended() {
|
|
that.pause()
|
|
.triggerEvent('paused', {
|
|
position: self.options.position
|
|
});
|
|
}
|
|
|
|
function playing() {
|
|
var event = 'playing';
|
|
self.options.position = Math.round(self.video.currentTime * self.options.fps) / self.options.fps;
|
|
if (self.options.playInToOut && self.options.position >= self.options.points[1]) {
|
|
event = 'paused';
|
|
that.position(self.options.points[1]).pause();
|
|
}
|
|
that.triggerEvent(event, {
|
|
position: self.options.position
|
|
});
|
|
}
|
|
|
|
self.onChange = function(key, value) {
|
|
if (key == 'height') {
|
|
that.size(self.options.width, value);
|
|
} else if (key == 'muted') {
|
|
that[value ? 'mute' : 'unmute']();
|
|
} else if (key == 'paused') {
|
|
that[value ? 'pause' : 'play']();
|
|
} else if (key == 'points') {
|
|
that.points(value);
|
|
} else if (key == 'width') {
|
|
that.size(value, self.options.height);
|
|
}
|
|
};
|
|
|
|
that.mute = function() {
|
|
self.options.muted = true;
|
|
self.video.muted = true;
|
|
return that;
|
|
};
|
|
|
|
that.muted = function() {
|
|
return self.options.muted;
|
|
}
|
|
|
|
that.pause = function() {
|
|
self.options.paused = true;
|
|
self.options.playInToOut = false;
|
|
self.video.pause();
|
|
clearInterval(self.playInterval);
|
|
return that;
|
|
};
|
|
|
|
that.paused = function() {
|
|
return self.options.paused;
|
|
}
|
|
|
|
that.play = function() {
|
|
self.options.paused = false;
|
|
self.video.play();
|
|
self.playInterval = setInterval(playing, self.millisecondsPerFrame);
|
|
return that;
|
|
};
|
|
|
|
that.playInToOut = function() {
|
|
self.options.playInToOut = true;
|
|
that.position(self.options.points[0]);
|
|
self.options.paused && that.play();
|
|
return that;
|
|
};
|
|
|
|
that.points = function(points) {
|
|
self.options.points = points;
|
|
}
|
|
|
|
that.position = function(pos) {
|
|
if (arguments.length == 0) {
|
|
return self.video.currentTime;
|
|
} else {
|
|
self.options.position = pos;
|
|
self.video.currentTime = self.options.position;
|
|
return that;
|
|
}
|
|
};
|
|
|
|
that.size = function(width, height) {
|
|
// fixme: why options? use css!
|
|
if (arguments.length == 0) {
|
|
return {
|
|
width: self.options.width,
|
|
height: self.options.height
|
|
};
|
|
} else {
|
|
self.options.width = width;
|
|
self.options.height = height;
|
|
that.css({
|
|
width: width + 'px',
|
|
height: height + 'px'
|
|
});
|
|
return that;
|
|
}
|
|
};
|
|
|
|
that.toggleMute = function() {
|
|
self.video.muted = !self.video.muted;
|
|
return that;
|
|
}
|
|
|
|
that.togglePlay = function() {
|
|
self.options.paused = !self.options.paused;
|
|
that[self.options.paused ? 'pause' : 'play']();
|
|
}
|
|
|
|
that.unmute = function() {
|
|
self.video.muted = false;
|
|
return that;
|
|
};
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
Ox.VideoPanelPlayer = function(options, self) {
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
annotationsSize: 256,
|
|
duration: 0,
|
|
height: 0,
|
|
loop: false,
|
|
muted: false,
|
|
paused: false,
|
|
playInToOut: false,
|
|
points: [0, 0],
|
|
position: 0,
|
|
poster: '',
|
|
showAnnotations: true,
|
|
showControls: true,
|
|
subtitles: [],
|
|
videoHeight: 0,
|
|
videoSize: 'fit',
|
|
videoWidth: 0,
|
|
videoURL: '',
|
|
width: 0
|
|
})
|
|
.options(options || {})
|
|
.css({
|
|
height: self.options.height + 'px',
|
|
width: self.options.width + 'px'
|
|
})
|
|
.bindEvent({
|
|
resize: resizeElement,
|
|
key_shift_a: function() {
|
|
that.toggleAnnotations();
|
|
},
|
|
key_shift_c: function() {
|
|
that.toggleControls();
|
|
},
|
|
key_shift_s: function() {
|
|
that.toggleSize();
|
|
},
|
|
key_space: function() {
|
|
that.togglePlay();
|
|
}
|
|
});
|
|
|
|
$.extend(self, {
|
|
fullscreen: false,
|
|
videoCSS: getVideoCSS()
|
|
});
|
|
//alert(JSON.stringify([self.playerHeight, self.playerWidth, self.videoCSS]))
|
|
|
|
self.$player = new Ox.Element()
|
|
.css({
|
|
overflowX: 'hidden',
|
|
overflowY: 'hidden'
|
|
})
|
|
.bind({
|
|
mousedown: that.gainFocus
|
|
})
|
|
.bindEvent({
|
|
resize: resizeVideo
|
|
});
|
|
|
|
self.$video = new Ox.VideoElement({
|
|
height: self.videoCSS.height,
|
|
paused: true,
|
|
points: self.options.points,
|
|
position: self.options.position,
|
|
url: self.options.videoURL,
|
|
width: self.videoCSS.width
|
|
})
|
|
.css(self.videoCSS)
|
|
.bindEvent({
|
|
paused: paused,
|
|
playing: playing
|
|
})
|
|
.appendTo(self.$player);
|
|
|
|
self.$controls = new Ox.Element()
|
|
.bindEvent({
|
|
toggle: toggleControls
|
|
});
|
|
|
|
self.$buttons = new Ox.Element()
|
|
.css({
|
|
float: 'left',
|
|
width: '16px',
|
|
margin: '4px'
|
|
})
|
|
.appendTo(self.$controls);
|
|
|
|
self.$button = {
|
|
play: new Ox.Button({
|
|
id: 'play',
|
|
title: [
|
|
{id: 'play', title: 'play'},
|
|
{id: 'pause', title: 'pause'}
|
|
],
|
|
tooltip: ['Play', 'Pause'],
|
|
type: 'image'
|
|
})
|
|
.bindEvent({
|
|
click: self.$video.togglePlay
|
|
}),
|
|
mute: new Ox.Button({
|
|
id: 'mute',
|
|
title: [
|
|
{id: 'mute', title: 'mute'},
|
|
{id: 'unmute', title: 'unmute'}
|
|
],
|
|
tooltip: ['Mute', 'Unmute'],
|
|
type: 'image'
|
|
})
|
|
.bindEvent({
|
|
click: self.$video.toggleMute
|
|
}),
|
|
size: new Ox.Button({
|
|
id: 'size',
|
|
title: self.options.videoSize == 'fit' ? [
|
|
{id: 'fill', title: 'fill'},
|
|
{id: 'fit', title: 'fit'}
|
|
] : [
|
|
{id: 'fit', title: 'fit'},
|
|
{id: 'fill', title: 'fill'}
|
|
],
|
|
tooltip: self.options.videoSize == 'fit' ? [
|
|
'Fill Screen', 'Fit to Screen'
|
|
] : [
|
|
'Fit to Screen', 'Fill Screen'
|
|
],
|
|
type: 'image'
|
|
})
|
|
.bindEvent({
|
|
click: toggleSize
|
|
}),
|
|
fullscreen: new Ox.Button({
|
|
id: 'size',
|
|
title: [
|
|
{id: 'grow', title: 'grow'},
|
|
{id: 'shrink', title: 'shrink'}
|
|
],
|
|
tooltip: [
|
|
'Enter Fullscreen', 'Exit Fullscreen'
|
|
],
|
|
type: 'image'
|
|
})
|
|
.bindEvent({
|
|
click: toggleFullscreen
|
|
})
|
|
}
|
|
var i = 0;
|
|
$.each(self.$button, function(k, $button) {
|
|
$button.css({
|
|
position: 'absolute',
|
|
left: '8px',
|
|
top: (8 + i++ * 24) + 'px'
|
|
})
|
|
.appendTo(self.$buttons);
|
|
});
|
|
|
|
self.$timelines = new Ox.Element()
|
|
.css({
|
|
float: 'left',
|
|
margin: '4px'
|
|
})
|
|
.appendTo(self.$controls);
|
|
|
|
self.$timeline = {
|
|
large: new Ox.LargeTimeline({
|
|
duration: self.options.duration,
|
|
position: self.options.position,
|
|
subtitles: self.options.subtitles,
|
|
videoId: self.options.videoId,
|
|
width: getTimelineWidth()
|
|
})
|
|
.css({
|
|
top: '4px'
|
|
})
|
|
.bindEvent({
|
|
change: changeLargeTimeline
|
|
}),
|
|
small: new Ox.SmallTimeline({
|
|
duration: self.options.duration,
|
|
position: self.options.position,
|
|
subtitles: self.options.subtitles,
|
|
videoId: self.options.videoId,
|
|
width: getTimelineWidth()
|
|
})
|
|
.css({
|
|
top: '76px'
|
|
})
|
|
.bindEvent({
|
|
change: changeSmallTimeline
|
|
})
|
|
};
|
|
$.each(self.$timeline, function(i, $timeline) {
|
|
$timeline.appendTo(self.$timelines);
|
|
});
|
|
|
|
self.$panel = new Ox.SplitPanel({
|
|
elements: [
|
|
{
|
|
element: self.$player
|
|
},
|
|
{
|
|
collapsed: !self.options.showControls,
|
|
collapsible: true,
|
|
element: self.$controls,
|
|
size: 104
|
|
}
|
|
],
|
|
orientation: 'vertical'
|
|
})
|
|
.bindEvent({
|
|
resize: resizePanel
|
|
});
|
|
|
|
self.$annotations = new Ox.Element()
|
|
.bindEvent({
|
|
resize: resizeAnnotations,
|
|
resizeend: resizeendAnnotations,
|
|
toggle: toggleAnnotations
|
|
});
|
|
|
|
that.$element = new Ox.SplitPanel({
|
|
elements: [
|
|
{
|
|
element: self.$panel
|
|
},
|
|
{
|
|
collapsed: !self.options.showAnnotations,
|
|
collapsible: true,
|
|
element: self.$annotations,
|
|
resizable: true,
|
|
resize: [192, 256, 320, 384],
|
|
size: self.options.annotationsSize
|
|
}
|
|
],
|
|
orientation: 'horizontal'
|
|
});
|
|
|
|
function changeLargeTimeline(event, data) {
|
|
self.options.position = data.position;
|
|
self.$video.position(self.options.position);
|
|
self.$timeline.small.options({
|
|
position: self.options.position
|
|
});
|
|
}
|
|
|
|
function changeSmallTimeline(event, data) {
|
|
self.options.position = data.position;
|
|
self.$video.position(self.options.position);
|
|
self.$timeline.large.options({
|
|
position: self.options.position
|
|
});
|
|
}
|
|
|
|
function getPlayerHeight() {
|
|
return self.options.height -
|
|
self.options.showControls * 104 - 1;
|
|
}
|
|
|
|
function getPlayerWidth() {
|
|
return self.options.width -
|
|
(self.options.showAnnotations && !self.fullscreen) *
|
|
self.options.annotationsSize - 1;
|
|
}
|
|
|
|
function getTimelineWidth() {
|
|
return self.options.width -
|
|
(self.options.showAnnotations && !self.fullscreen) *
|
|
self.options.annotationsSize - 40
|
|
}
|
|
|
|
function getVideoCSS() {
|
|
var width = getPlayerWidth(),
|
|
height = getPlayerHeight(),
|
|
ratio = width / height,
|
|
videoRatio = self.options.videoWidth / self.options.videoHeight,
|
|
isWide = ratio < videoRatio;
|
|
return self.options.videoSize == 'fit' ? {
|
|
position: 'absolute',
|
|
left: 0,
|
|
right: 0,
|
|
top: 0,
|
|
bottom: 0,
|
|
width: (isWide ? width : Math.round(height * videoRatio)) + 'px',
|
|
height: (isWide ? Math.round(width / videoRatio) : height) + 'px',
|
|
margin: 'auto'
|
|
} : {
|
|
width: (isWide ? Math.round(height * videoRatio) : width) + 'px',
|
|
height: (isWide ? height : Math.round(width / videoRatio)) + 'px',
|
|
margin: [
|
|
isWide ? '0' : Math.floor((height - width / videoRatio) / 2) + 'px',
|
|
isWide ? Math.ceil((width - height * videoRatio) / 2) + 'px' : '0',
|
|
isWide ? '0' : Math.ceil((height - width / videoRatio) / 2) + 'px',
|
|
isWide ? Math.floor((width - height * videoRatio) / 2) + 'px' : '0'
|
|
].join(' ')
|
|
};
|
|
}
|
|
|
|
function paused() {
|
|
|
|
}
|
|
|
|
function playing(event, data) {
|
|
self.options.position = data.position;
|
|
self.$timeline.large.options({
|
|
position: self.options.position
|
|
});
|
|
self.$timeline.small.options({
|
|
position: self.options.position
|
|
});
|
|
}
|
|
|
|
function resizeAnnotations(event, data) {
|
|
self.options.annotationsSize = data;
|
|
resizeVideoAndControls();
|
|
}
|
|
|
|
function resizeendAnnotations(event, data) {
|
|
self.options.annotationsSize = data;
|
|
that.triggerEvent('change', {
|
|
annotationsSize: self.options.annotationsSize
|
|
});
|
|
}
|
|
|
|
function resizeControls() {
|
|
self.$timeline.large.options({
|
|
width: getTimelineWidth()
|
|
});
|
|
self.$timeline.small.options({
|
|
width: getTimelineWidth()
|
|
});
|
|
}
|
|
|
|
function resizeElement(event, data) {
|
|
// called on browser toggle
|
|
self.options.height = data;
|
|
resizeVideo();
|
|
}
|
|
|
|
function resizePanel(event, data) {
|
|
// called on annotations toggle
|
|
resizeVideoAndControls();
|
|
}
|
|
|
|
function resizeVideoAndControls() {
|
|
resizeVideo();
|
|
resizeControls();
|
|
}
|
|
|
|
function resizeVideo() {
|
|
self.videoCSS = getVideoCSS();
|
|
self.$video.css(self.videoCSS);
|
|
};
|
|
|
|
function toggleAnnotations(event, data) {
|
|
self.options.showAnnotations = !data.collapsed;
|
|
that.triggerEvent('change', {
|
|
showAnnotations: self.options.showAnnotations
|
|
});
|
|
}
|
|
|
|
function toggleControls(event, data) {
|
|
self.options.showControls = !data.collapsed;
|
|
that.triggerEvent('change', {
|
|
showControls: self.options.showControls
|
|
});
|
|
}
|
|
|
|
function toggleFullscreen() {
|
|
self.fullscreen = !self.fullscreen;
|
|
self.options.showAnnotations && that.$element.toggle(1);
|
|
self.fullscreen && self.options.showControls && self.$panel.toggle(1);
|
|
that.triggerEvent((self.fullscreen ? 'enter' : 'exit') + 'fullscreen', {});
|
|
}
|
|
|
|
function toggleSize() {
|
|
self.options.videoSize = self.options.videoSize == 'fit' ? 'fill' : 'fit';
|
|
resizeVideo();
|
|
that.triggerEvent('change', {
|
|
videoSize: self.options.videoSize
|
|
});
|
|
}
|
|
|
|
self.onChange = function(key, value) {
|
|
if (key == 'height') {
|
|
resizeVideo();
|
|
} else if (key == 'position') {
|
|
self.$video.position(value);
|
|
} else if (key == 'width') {
|
|
resizeVideoAndControls();
|
|
}
|
|
}
|
|
|
|
that.toggleAnnotations = function() {
|
|
that.$element.toggle(1);
|
|
//that.toggleAnnotations(null, !self.options.showAnnotations);
|
|
};
|
|
|
|
that.toggleControls = function() {
|
|
self.$panel.toggle(1);
|
|
//that.toggleControls(null, !self.options.showControls);
|
|
};
|
|
|
|
that.toggleMute = function() {
|
|
self.$button.mute.trigger('click');
|
|
};
|
|
|
|
that.togglePlay = function() {
|
|
self.$button.play.trigger('click');
|
|
};
|
|
|
|
that.toggleSize = function() {
|
|
self.$button.size.trigger('click');
|
|
}
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
/*
|
|
============================================================================
|
|
Pan.do/ra
|
|
============================================================================
|
|
*/
|
|
|
|
Ox.FilesView = function(options, self) {
|
|
|
|
var self = self || {},
|
|
that = new Ox.Element('div', self)
|
|
.defaults({
|
|
id: ''
|
|
})
|
|
.options(options || {});
|
|
|
|
self.$toolbar = new Ox.Bar({
|
|
size: 24
|
|
});
|
|
|
|
self.$orderButton = new Ox.Button({
|
|
title: 'Change Order of Users...'
|
|
})
|
|
.css({
|
|
float: 'left',
|
|
margin: '4px'
|
|
})
|
|
.appendTo(self.$toolbar);
|
|
|
|
self.$moveButton = new Ox.Button({
|
|
disabled: 'true',
|
|
title: 'Move Selected Files...'
|
|
})
|
|
.css({
|
|
float: 'right',
|
|
margin: '4px'
|
|
})
|
|
.appendTo(self.$toolbar);
|
|
|
|
self.$filesList = new Ox.TextList({
|
|
columns: [
|
|
{
|
|
align: 'left',
|
|
id: 'users',
|
|
operator: '+',
|
|
title: 'Users',
|
|
visible: true,
|
|
width: 120
|
|
},
|
|
{
|
|
align: 'left',
|
|
id: 'folder',
|
|
operator: '+',
|
|
title: 'Folder',
|
|
visible: true,
|
|
width: 180
|
|
},
|
|
{
|
|
align: 'left',
|
|
id: 'name',
|
|
operator: '+',
|
|
title: 'Name',
|
|
visible: true,
|
|
width: 360
|
|
},
|
|
{
|
|
align: 'left',
|
|
id: 'type',
|
|
operator: '+',
|
|
title: 'Type',
|
|
visible: true,
|
|
width: 60
|
|
},
|
|
{
|
|
align: 'right',
|
|
id: 'part',
|
|
operator: '+',
|
|
title: 'Part',
|
|
visible: true,
|
|
width: 60
|
|
},
|
|
{
|
|
align: 'right',
|
|
format: {type: 'value', args: ['B']},
|
|
id: 'size',
|
|
operator: '-',
|
|
title: 'Size',
|
|
visible: true,
|
|
width: 90
|
|
},
|
|
{
|
|
align: 'right',
|
|
format: {type: 'resolution', args: ['px']},
|
|
id: 'resolution',
|
|
operator: '-',
|
|
title: 'Resolution',
|
|
visible: true,
|
|
width: 90
|
|
},
|
|
{
|
|
align: 'right',
|
|
format: {type: 'duration', args: [0, 'short']},
|
|
id: 'duration',
|
|
operator: '-',
|
|
title: 'Duration',
|
|
visible: true,
|
|
width: 90
|
|
},
|
|
{
|
|
align: 'left',
|
|
id: 'oshash',
|
|
operator: '+',
|
|
title: 'Hash',
|
|
unique: true,
|
|
visible: false,
|
|
width: 120
|
|
},
|
|
{
|
|
align: 'left',
|
|
id: 'instances',
|
|
operator: '+',
|
|
title: 'Instances',
|
|
visible: false,
|
|
width: 120
|
|
}
|
|
],
|
|
columnsMovable: true,
|
|
columnsRemovable: true,
|
|
columnsResizable: true,
|
|
columnsVisible: true,
|
|
id: 'files',
|
|
items: function(data, callback) {
|
|
pandora.api.findFiles($.extend(data, {
|
|
query: {
|
|
conditions: [{
|
|
key: 'id',
|
|
value: self.options.id,
|
|
operator: '='
|
|
}]
|
|
}
|
|
}), callback);
|
|
},
|
|
scrollbarVisible: true,
|
|
sort: [{key: 'name', operator:'+'}]
|
|
})
|
|
.bindEvent({
|
|
open: openFiles,
|
|
select: selectFiles
|
|
});
|
|
|
|
self.$instancesList = new Ox.Element()
|
|
.html('No files selected');
|
|
|
|
that.$element = new Ox.SplitPanel({
|
|
elements: [
|
|
{
|
|
element: self.$toolbar,
|
|
size: 24
|
|
},
|
|
{
|
|
element: self.$filesList
|
|
},
|
|
{
|
|
element: self.$instancesList,
|
|
size: 80
|
|
}
|
|
],
|
|
orientation: 'vertical'
|
|
});
|
|
|
|
function openFiles(event, data) {
|
|
//alert(JSON.stringify(self.$filesList.value(data.ids[0], 'instances')))
|
|
}
|
|
|
|
function selectFiles(event, data) {
|
|
//alert(JSON.stringify(self.$filesList.value(data.ids[0], 'instances')))
|
|
}
|
|
|
|
return that;
|
|
|
|
};
|
|
|
|
|
|
})();
|