oxjstmp/build/js/ox.ui.js
2010-07-24 21:27:39 +02:00

5592 lines
193 KiB
JavaScript

/*
################################################################################
ox.ui.js
requires
jquery-1.4.js
ox.js
################################################################################
*/
// also see test.js, in demos ...
(function() {
var oxui = {
defaultTheme: "classic",
elements: {},
getDimensions: function(orientation) {
return orientation == "horizontal" ?
["width", "height"] : ["height", "width"];
},
getEdges: function(orientation) {
return orientation == "horizontal" ?
["left", "right", "top", "bottom"] :
["top", "bottom", "left", "right"];
},
getBarSize: function(size) {
var sizes = {
small: 20,
medium: 24,
large: 28,
};
return sizes[size];
},
jQueryFunctions: function() {
var functions = [],
$element = $("<div>");
//delete $element.length;
Ox.each($element, function(k, v) {
if (typeof v == "function") {
functions.push(k);
}
});
return functions.sort();
}(),
path: $("script[src*=ox.ui.js]").attr("src")
.replace("js/ox.ui.js", ""),
scrollbarSize: $.browser.mozilla ? 16 : 12,
symbols: {
alt: "\u2325",
apple: "\uF8FF",
arrow_down: "\u2193",
arrow_left: "\u2190",
arrow_right: "\u2192",
arrow_up: "\u2191",
backspace: "\u232B",
backup: "\u2707",
ballot: "\u2717",
black_star: "\u2605",
burn: "\u2622",
caps_lock: "\u21EA",
check: "\u2713",
//clear: "\u2327",
clear: "\u00D7",
click: "\uF803",
close: "\u2715",
command: "\u2318",
control: "\u2303",
cut: "\u2702",
"delete": "\u2326",
diamond: "\u25C6",
edit: "\uF802",
eject: "\u23CF",
escape: "\u238B",
end: "\u2198",
enter: "\u2324",
fly: "\u2708",
gear: "\u2699",
home: "\u2196",
info: "\u24D8",
navigate: "\u2388",
option: "\u2387",
page_up: "\u21DE",
page_down: "\u21DF",
redo: "\u21BA",
"return": "\u21A9",
//select: "\u21D5",
select: "\u25BE",
shift: "\u21E7",
sound: "\u266B",
space: "\u2423",
tab: "\u21E5",
trash: "\u267A",
triangle_down: "\u25BC",
triangle_left: "\u25C0",
triangle_right: "\u25BA",
triangle_up: "\u25B2",
undo: "\u21BB",
voltage: "\u26A1",
warning: "\u26A0",
white_star: "\u2606"
}
},
$window, $document, $body;
$(function() {
$window = $(window),
$document = $(document),
$body = $("body"),
$elements = {};
Ox.theme(oxui.defaultTheme);
});
/*
============================================================================
Application
============================================================================
*/
/*
----------------------------------------------------------------------------
Ox.App
----------------------------------------------------------------------------
*/
Ox.App = function() {
/*
options:
requestTimeout
requestType
requestURL
*/
return function(options) {
options = options || {};
var self = {},
that = this;
self.options = $.extend({
requestTimeout: oxui.requestTimeout,
requestType: oxui.requestType,
requestURL: oxui.requestURL
}, options);
self.change = function(key, value) {
};
that.launch = function() {
$.ajaxSetup({
timeout: self.options.requestTimeout,
type: self.options.requestType,
url: self.options.requestURL
});
};
that.options = function() {
return Ox.getset(self.options, Array.slice.call(arguments), self.change, that);
};
that.request = function(action, data, callback) {
if (arguments.length == 2) {
callback = data;
data = {};
}
return Ox.Request.send({
url: self.options.requestURL,
data: {
action: action,
data: JSON.stringify(data)
},
callback: callback
});
};
return that;
};
}();
/*
----------------------------------------------------------------------------
Ox.Event
----------------------------------------------------------------------------
naming convention for event/trigger
verb.id.namespace, i.e. verb.sourceId.targetId (?)
...
bind("keydown.shift+dot.numpad", function() {
// ...
})
keyboard handler then would:
$.each(stack, function(i, v) {
elements[v].trigger("keydown.shift+0.numpad");
});
and the element would implement
this.trigger(event, data) {
}
...
keyboard handler also triggers keydown.buffer
*/
// use dom elements / jquery instead
Ox.Event = function() {
var keyboardEvents = {};
$eventHandler = $("<div>");
function isKeyboardEvent(event) {
return event.substr(0, 4) == "key_";
}
return {
bind: function(id, event, callback) {
if (arguments.length == 2) {
callback = event;
event = id;
}
if (isKeyboardEvent(event)) {
keyboardEvents[id] = keyboardEvents[id] || {};
keyboardEvents[id][event] = callback;
}
if (!isKeyboardEvent(event) || Ox.Focus.focused() == id) {
Ox.print("bind", id, event)
$eventHandler.bind(event, callback);
}
},
bindKeyboard: function(id) {
$.each(keyboardEvents[id] || [], function(event, callback) {
Ox.Event.bind(id, event, callback);
//$eventHandler.bind(event, callback);
});
},
trigger: function(event, data) {
Ox.print("trigger", event, data || {});
$eventHandler.trigger(event, data || {});
},
unbind: function(id, event, callback) {
if (isKeyboardEvent(event)) {
$.each(keyboardEvents[id] || [], function(e, callback) {
if (e == event) {
delete keyboardEvents[id][e];
return false;
}
});
}
Ox.print("unbind", id, event)
$eventHandler.unbind(event, callback);
},
unbindKeyboard: function(id) {
$.each(keyboardEvents[id] || [], function(event, callback) {
Ox.print("unbind", id, event)
$eventHandler.unbind(event, callback);
});
}
}
}();
Ox.Event_ = function() { // unused
var events = {};
return {
// make these bind, trigger, unbind
publish: function(event, data) {
if (events[event]) {
$.each(events[event], function(i, v) {
setTimeout(function() {
v(data);
}, 0);
});
}
},
subscribe: function(event, callback) {
if (events[event]) {
events[event].push(callback);
} else {
events[event] = [callback];
}
},
unsubscribe: function(event, callback) {
$.each(events[event], function(i, v) {
if (Ox.startsWith(callback.toString(), v.toString())) {
events[event].splice(i, 1);
}
});
}
};
}();
/*
----------------------------------------------------------------------------
Ox.Focus
----------------------------------------------------------------------------
*/
Ox.Focus = function() {
var stack = [];
return {
blur: function(id) {
if (stack.indexOf(id) == stack.length - 1) {
$elements[Ox.Focus.focused()].removeClass("OxFocus");
$(".OxFocus").removeClass("OxFocus"); // fixme: the above is better, and should work
stack.splice(stack.length - 2, 0, stack.pop());
Ox.Event.unbindKeyboard(id);
Ox.Event.bindKeyboard(stack[stack.length - 1]);
Ox.print("blur", id, stack);
}
},
focus: function(id) {
var index = stack.indexOf(id);
if (stack.length) {
Ox.Event.unbindKeyboard(stack[stack.length - 1])
}
if (index > -1) {
stack.splice(index, 1);
}
stack.push(id);
$elements[Ox.Focus.focused()].addClass("OxFocus");
Ox.Event.bindKeyboard(id);
Ox.print("focus", id, stack);
},
focused: function() {
return stack[stack.length - 1];
}
};
}();
/*
----------------------------------------------------------------------------
Ox.History
----------------------------------------------------------------------------
*/
/*
----------------------------------------------------------------------------
Ox.Keyboard
----------------------------------------------------------------------------
*/
(function() {
var buffer = "",
bufferTime = 0,
bufferTimeout = 1000,
keyNames = function() {
return {
0: "section",
8: "backspace",
9: "tab",
12: "clear",
13: "enter",
16: "shift",
17: "control",
18: "alt",
20: "capslock",
27: "escape",
32: "space",
33: "pageup",
34: "pagedown",
35: "end",
36: "home",
37: "left",
38: "up",
39: "right",
40: "down",
45: "insert",
46: "delete",
47: "help",
48: "0",
49: "1",
50: "2",
51: "3",
52: "4",
53: "5",
54: "6",
55: "7",
56: "8",
57: "9",
65: "a",
66: "b",
67: "c",
68: "d",
69: "e",
70: "f",
71: "g",
72: "h",
73: "i",
74: "j",
75: "k",
76: "l",
77: "m",
78: "n",
79: "o",
80: "p",
81: "q",
82: "r",
83: "s",
84: "t",
85: "u",
86: "v",
87: "w",
88: "x",
89: "y",
90: "z",
91: "meta.left",
92: "meta.right",
93: "select",
96: "0.numpad",
97: "1.numpad",
98: "2.numpad",
99: "3.numpad",
100: "4.numpad",
101: "5.numpad",
102: "6.numpad",
103: "7.numpad",
104: "8.numpad",
105: "9.numpad",
106: "asterisk.numpad",
107: "plus.numpad",
109: "minus.numpad",
108: "enter.numpad",
110: "dot.numpad",
111: "slash.numpad",
112: "f1",
113: "f2",
114: "f3",
115: "f4",
116: "f5",
117: "f6",
118: "f7",
119: "f8",
120: "f9",
121: "f10",
122: "f11",
123: "f12",
124: "f13",
125: "f14",
126: "f15",
127: "f16",
144: "numlock",
145: "scrolllock",
186: "semicolon",
187: "equal",
188: "comma",
189: "minus",
190: "dot",
191: "slash",
192: "backtick",
219: "openbracket",
220: "backslash",
221: "closebracket",
222: "quote"
// see dojo, for ex.
};
}(),
modifierNames = {
altKey: "alt", // mac: option
ctrlKey: "control",
// metaKey: "meta", // mac: command
shiftKey: "shift"
};
$(function() {
// fixme: how to do this better?
// in firefox on mac, keypress doesn't fire for up/down
// if the cursor is at the start/end of an input element
// on linux, it doesn't seem to fire if the input element has focus
if ($.browser.mozilla) {
$document.keypress(keypress);
$document.keydown(function(event) {
var $element = $("input:focus");
if ($element.length) {
if (
(
keyNames[event.keyCode] == "up" &&
$element[0].selectionStart + $element[0].selectionEnd == 0
) || (
keyNames[event.keyCode] == "down" &&
$element[0].selectionStart == $element.val().length &&
$element[0].selectionEnd == $element.val().length
)
) {
keypress(event);
}
}
});
} else {
$document.keydown(keypress);
}
});
function keypress(event) {
var key,
keys = [],
//ret = true,
time;
$.each(modifierNames, function(k, v) {
if (event[k]) {
keys.push(v);
}
});
// avoid pushing modifier twice
Ox.print("keys", keys)
if (keyNames[event.keyCode] && keys.indexOf(keyNames[event.keyCode]) == -1) {
keys.push(keyNames[event.keyCode]);
}
key = keys.join("_");
if (key.match(/^[\w\d-]$|SPACE/)) {
time = Ox.getTime();
if (time - bufferTime > bufferTimeout) {
buffer = "";
}
buffer += key == "SPACE" ? " " : key;
bufferTime = time;
}
Ox.Event.trigger("key_" + key);
//return false;
/*
$.each(stack, function(i, v) {
// fixme: we dont get the return value!
ret = Ox.event.trigger(keyboard + Ox.toCamelCase(key) + "." + v);
return ret;
});
*/
}
})();
/*
----------------------------------------------------------------------------
Ox.Mouse (??)
----------------------------------------------------------------------------
*/
/*
----------------------------------------------------------------------------
Ox.Request
----------------------------------------------------------------------------
*/
Ox.Request = function() {
var cache = {},
pending = {},
requests = {},
self = {
options: {
timeout: 15000,
type: "POST",
url: "api"
}
};
return {
cancel: function() {
var index;
if (arguments.length == 0) {
requests = {};
} else if (Ox.isFunction(arguments[0])) {
// cancel with function
$.each(requests, function(id, req) {
if (arguments[0](req)) {
delete requests[id];
}
})
} else {
// cancel by id
delete requests[arguments[0]]
}
},
emptyCache: function() {
cache = {};
},
options: function(options) {
return Ox.getset(self.options, options, $.noop(), this);
},
send: function(options) {
var options = $.extend({
age: -1,
callback: function() {},
id: Ox.uid(),
timeout: self.options.timeout,
type: self.options.type,
url: self.options.url
}, options),
req = JSON.stringify({
url: options.url,
data: options.data
});
function callback(data) {
delete requests[options.id];
Ox.length(requests) == 0 && Ox.Event.trigger("requestStop");
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: [
{
value: "Close",
click: function() {
$dialog.close();
}
}
],
width: 800,
height: 400
})
.append($iframe)
.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) {
data = {
status: {
code: request.status,
text: request.statusText
}
};
}
}
if (data.status.code < 500) {
callback(data);
} else {
var $dialog = new Ox.Dialog({
title: "Application Error",
buttons: [
{
value: "Details",
click: function() {
$dialog.close(function() {
debug(request);
});
}
},
{
value: "Close",
click: function() {
$dialog.close(function() {
callback(data);
});
}
}
],
width: 400,
height: 200
})
.append("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.")
.open();
// fixme: change this to Send / Don't Send
Ox.print({
request: request,
status: status,
error: error
});
}
pending[options.id] = false;
}
function success(data) {
pending[options.id] = false;
cache[req] = {
data: data,
time: Ox.getTime()
};
callback(data);
}
if (pending[options.id]) {
setTimeout(function() {
Ox.Request.send(options);
}, 0);
} else {
requests[options.id] = {
url: options.url,
data: options.data
};
if (cache[req] && (options.age == -1 || options.age > Ox.getTime() - cache[req].time)) {
setTimeout(function() {
callback(cache[req].data);
}, 0);
} else {
pending[options.id] = true;
$.ajax({
data: options.data,
dataType: "json",
error: error,
success: success,
timeout: options.timeout,
type: options.type,
url: options.url
});
Ox.print("request", options.data);
Ox.length(requests) == 1 && Ox.Event.trigger("requestStart");
}
}
return options.id;
},
requests: function() {
return Ox.length(requests);
}
};
}();
/*
----------------------------------------------------------------------------
Ox.URL
----------------------------------------------------------------------------
*/
/*
============================================================================
Core
============================================================================
*/
/*
----------------------------------------------------------------------------
Ox.Container
----------------------------------------------------------------------------
*/
// fixme: wouldn't it be better to let the elements be,
// rather then $element, $content, and potentially others,
// 0, 1, 2, etc, so that append would append 0, and appendTo
// would append (length - 1)?
Ox.Container = function(options, self) {
var that = new Ox.Element(options, self)
.addClass("OxContainer");
that.$content = new Ox.Element(options, self)
.addClass("OxContent")
.appendTo(that);
return that;
};
/*
----------------------------------------------------------------------------
Ox.Element
----------------------------------------------------------------------------
*/
// check out http://ejohn.org/apps/learn/#36 (-#38, making fns work w/o new)
Ox.Element = function() {
var elements = {}; // fixme: unused, we need this outside Element (for Focus)
return function(options, self) {
// construct
options = options || {};
self = self || {};
var that = this;
// init
(function() {
// allow for Ox.Widget("tagname", self)
if (typeof options == "string") {
options = {
element: options
};
}
that.ox = Ox.version;
that.id = Ox.uid();
that.$element = $("<" + (options.element || "div") + "/>", {
data: {
ox: that.id
}
});
$elements[that.id] = that;
wrapjQuery();
})();
// private
function wrapjQuery() {
$.each(oxui.jQueryFunctions, function(i, fn) {
that[fn] = function() {
var args = arguments,
length = args.length,
id, ret;
$.each(args, function(i, arg) {
if (Ox.isUndefined(arg)) {
Ox.print("fn", fn, "undefined argument")
}
// if an ox object was passed
// then pass its $element instead
// so we can do oxObj.jqFn(oxObj)
if (arg.ox) {
args[i] = arg.$element;
}
/*
if (arg.ox) { // fixme: or is this too much magic?
if (fn == "appendTo" && arg.$content) {
args[i] = arg.$content
} else {
args[i] = arg.$element;
}
}
*/
});
/*
if (fn == "html" && that.$content) { // fixme: or is this too much magic?
$element = that.$content;
} else {
$element = that.$element;
}
*/
// why does this not work? (that?)
// ret = that.$element[fn].apply(this, arguments);
if (length == 0) {
ret = that.$element[fn]();
} else if (length == 1) {
ret = that.$element[fn](args[0]);
} else if (length == 2) {
ret = that.$element[fn](args[0], args[1]);
} else if (length == 3) {
ret = that.$element[fn](args[0], args[1], args[2]);
} else if (length == 4) {
ret = that.$element[fn](args[0], args[1], args[2], args[3]);
}
if (fn == "data") {
// Ox.print("data ret", ret, $(ret))
}
// if the $element of an ox object was returned
// then return the ox object instead
// so we can do oxObj.jqFn().oxFn()
return ret.jquery && $elements[id = ret.data("ox")] ?
$elements[id] : ret;
}
});
}
// shared
self.onChange = function() {
// self.onChange(key, value)
// is called when an option changes
// (to be implemented by widget)
};
// public
that.bindEvent = function() {
// fixme: shouldn't this work the other way around,
// and bind a function to an event triggered by this widget?
/*
bindEvent(event, fn) or bindEvent({event0: fn0, event1: fn1, ...})
*/
if (arguments.length == 1) {
$.each(arguments[0], function(event, fn) {
Ox.Event.bind(that.id, event, fn);
});
} else {
Ox.Event.bind(that.id, arguments[0], arguments[1]);
}
return that;
};
that.defaults = function(defaults) {
/*
that.defaults({foo: x}) sets self.defaults
*/
self.defaults = defaults;
delete self.options; // fixme: hackish fix for that = Ox.Foo({...}, self).defaults({...}).options({...})
return that;
};
that.gainFocus = function() {
Ox.Focus.focus(that.id);
return that;
};
that.hasFocus = function() {
return Ox.Focus.focused() == that.id;
};
that.loseFocus = function() {
Ox.Focus.blur(that.id);
return that;
};
that.options = function() { // fixme: use Ox.getset
/*
that.options() returns self.options
that.options("foo") returns self.options.foo
that.options("foo", x) sets self.options.foo,
returns that
that.options({foo: x, bar: y}) sets self.options.foo
and self.options.bar,
returns that
*/
var length = arguments.length,
// args, options, ret;
args, ret;
if (length == 0) {
// options()
ret = self.options || options; // this is silly. make sure self.options get populated with 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);
/*
options = self.options;
*/
// if options have not been set, extend defaults,
// otherwise, extend options
self.options = $.extend(self.options || self.defaults, args);
$.each(args, function(key, value) {
self.onChange(key, value);
/*
fixme: why does this not work?
Ox.print("options", options, key, value)
//Ox.print(!options, !options || !options[key], !options || !options[key] || options[key] !== value)
if (!options || !options[key] || options[key] !== value) {
Ox.print("onChange...")
self.onChange(key, value);
} else {
Ox.print("NO CHANGE");
}
*/
});
ret = that;
}
return ret;
};
that.remove = function() {
that.$element.remove();
delete $elements[that.ox];
return that;
};
that.triggerEvent = function() {
/*
triggerEvent(event, fn) or triggerEvent({event0: fn0, event1: fn1, ...})
*/
if (Ox.isObject(arguments[0])) {
$.each(arguments[0], function(event, fn) {
Ox.Event.trigger(event + "_" + self.options.id, fn);
});
} else {
Ox.Event.trigger(arguments[0] + "_" + self.options.id, arguments[1] || {});
}
return that;
};
that.unbindEvent = function() {
/*
unbindEvent(event, fn) or unbindEvent({event0: fn0, event1: fn1, ...})
*/
if (arguments.length == 1) {
$.each(arguments[0], function(event, fn) {
Ox.Event.unbind(that.id, event, fn);
})
} else {
Ox.Event.unbind(that.id, arguments[0], arguments[1]);
}
return that;
};
// return
return that;
}
}();
Ox._Element = function(element) {
var that = this;
that.def = {};
that.opt = {};
that.ox = Ox.version;
that.id = Ox.uid();
//Ox.print("that.id", that.id)
that.$element = $("<" + (element || "div") + "/>")
//.addClass("OxElement")
.data("ox", that.id);
oxui.elements[that.id] = that;
// Ox.print("oxui.elements", oxui.elements)
//function setOption() {};
that.setOption = function() {};
/*
*/
that.destroy = function() {
that.$element.remove();
delete oxui.elements[that.ox];
}
/*
*/
that.disable = function() {
}
/*
*/
that.enable = function() {
}
/*
*/
///*
that.defaults = function() {
var length = arguments.length,
ret;
if (length == 0) {
ret = that.def
} else if (length == 1 && typeof arguments[0] == "string") {
ret = that.def[arguments[0]];
} else {
// translate ("key", "value") to {"key": "value"}
that.def = $.extend(
that.def, Ox.makeObject.apply(that, arguments)
);
ret = that;
}
return ret;
}
//*/
/*
Ox.Element.options()
get options
Ox.Element.options("foo")
get options.foo
Ox.Element.options("foo", 0)
set options.foo
Ox.Element.options({foo: 0, bar: 1})
set options.foo and options.bar
*/
///*
that.options = function() {
var length = arguments.length,
args, ret;
if (length == 0) {
//Ox.print("getting all options", options);
ret = that.opt;
} else if (length == 1 && typeof arguments[0] == "string") {
//Ox.print("getting one option", options, arguments[0], options[arguments[0]]);
ret = that.opt[arguments[0]];
} else {
// translate ("key", "value") to {"key": "value"}
args = Ox.makeObject.apply(that, arguments);
// if options have been set then extend options,
// otherwise extend defaults
that.opt = $.extend(Ox.length(that.opt) ?
that.opt : that.def, args);
// that.trigger("OxElement" + that.id + "SetOptions", args);
$.each(args, function(k, v) {
that.setOption(k, v);
//Ox.print("triggering", "OxElement" + that.id + "SetOption", {k: v})
//that.trigger("OxElement" + that.id + "SetOption", {k: v});
})
ret = that;
}
return ret;
}
// should become self.publish
that.publish = function(event, data) {
Ox.Event.publish(event + that.id, data);
return that;
}
that.subscribe = function(event, callback) {
Ox.Event.subscribe(event, callback);
return that;
}
//that.setOptions = function() {};
//*/
// wrap jquery functions
// so we can do oxObj.jqFn()
$.each(oxui.jqueryFunctions, function(i, v) {
that[v] = function() {
var args = arguments,
length = args.length,
$element, id, ret;
$.each(args, function(i, v) {
// if an oxui object was passed
// then pass its $element instead
// so we can do jqObj.append(oxObj)
if (v.ox) {
args[i] = v.$element;
}
});
if (v == "html" && that.$content) {
$element = that.$content;
} else {
$element = that.$element;
}
// why does this not work?
// ret = that.$element[v].apply(this, arguments);
// maybe because we pass this, and not that.$element[v] ... ?
// ret = that.$element[v].apply(that.$element[v], arguments);
// doesn't work either ...
if (length == 0) {
ret = $element[v]();
} else if (length == 1) {
ret = $element[v](args[0]);
} else if (length == 2) {
ret = $element[v](args[0], args[1]);
} else if (length == 3) {
ret = $element[v](args[0], args[1], args[2]);
} else if (length == 4) {
ret = $element[v](args[0], args[1], args[2], args[3]);
}
// if the $element of an oxui object was returned
// then return the oxui object instead
// so we can do oxObj.jqFn().oxFn()
//Ox.print("v", v, "arguments", arguments)
if (ret.jquery) {
//Ox.print("ret", ret, "ret.data('id')", ret.data("ox"))
}
return ret.jquery && oxui.elements[id = ret.data("ox")] ?
oxui.elements[id] : ret;
}
});
return that;
};
/*
----------------------------------------------------------------------------
Ox.Window
----------------------------------------------------------------------------
*/
Ox.Window = function(options, self) {
self = self || {},
that = new Ox.Element("div", self)
.defaults({
draggable: true,
fullscreenable: true, // fixme: silly name
height: 225,
resizeable: true,
scaleable: true,
width: 400
})
.options(options || {})
self.center = function() {
};
self.drag = function() {
};
self.fullscreen = function() {
};
self.onChange = function() {
};
self.reset = function() {
};
self.resize = function() {
};
self.scale = function() {
};
that.close = function() {
};
that.open = function() {
};
return that;
};
/*
----------------------------------------------------------------------------
Ox.theme()
get theme
Ox.theme("foo")
set theme to "foo"
----------------------------------------------------------------------------
*/
Ox.theme = function() {
var length = arguments.length,
classes = $body.attr("class").split(" "),
arg, theme;
$.each(classes, function(i, v) {
if (Ox.startsWith(v, "OxTheme")) {
theme = v.replace("OxTheme", "").toLowerCase();
if (length == 1) {
$body.removeClass(v);
}
return false;
}
});
if (length == 1) {
arg = arguments[0]
$body.addClass("OxTheme" + Ox.toTitleCase(arg));
if (theme) {
$("input[type=image]").each(function() {
var $this = $(this);
$this.attr({
src: $this.attr("src").replace(
"/ox.ui." + theme + "/", "/ox.ui." + arg + "/"
)
});
});
$(".OxLoadingIcon").each(function() {
var $this = $(this);
$this.attr({
src: $this.attr("src").replace(
"/ox.ui." + theme + "/", "/ox.ui." + arg + "/"
)
});
})
}
}
return theme;
};
/*
============================================================================
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
----------------------------------------------------------------------------
*/
Ox.Resizebar = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
collapsed: false,
collapsible: true,
edge: "left",
elements: [],
orientation: "horizontal",
parent: null,
resizable: true,
resize: [],
size: 0
})
.options(options || {}) // fixme: options function should be able to handle undefined, no need for || {}
.addClass("OxResizebar Ox" + Ox.toTitleCase(self.options.orientation))
/*
.attr({
draggable: "true"
})
.bind("dragstart", function(e) {
// e.originalEvent.dataTransfer.setDragImage($("<div>")[0], 0, 0);
})
.bind("drag", function(e) {
Ox.print("dragging", e)
})
*/
.mousedown(dragStart)
.dblclick(toggle)
.append($("<div>").addClass("OxSpace"))
.append($("<div>").addClass("OxLine"))
.append($("<div>").addClass("OxSpace"));
$.extend(self, {
clientXY: self.options.orientation == "horizontal" ? "clientY" : "clientX",
dimensions: oxui.getDimensions(self.options.orientation), // fixme: should orientation be the opposite orientation here?
edges: oxui.getEdges(self.options.orientation),
ids: $.map(self.options.elements, function(v, i) {
return v.options("id");
}),
length: self.options.resize.length,
startPos: 0,
startSize: 0
});
function drag(e) {
var d = e[self.clientXY] - self.startPos, i;
self.options.size = Ox.limit(self.startSize + d, self.options.resize[0], self.options.resize[self.length - 1]);
Ox.print("sS", self.startSize, "d", d, "s.o.s", self.options.size)
$.each(self.options.resize, function(i, v) {
if (self.options.size > v - 8 && self.options.size < v + 8) {
self.options.size = v;
return false;
}
});
that.css(self.edges[2], self.options.size + "px");
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");
Ox.Event.trigger("resize_" + self.ids[0], self.options.size);
Ox.Event.trigger("resize_" + self.ids[1], self.options.elements[1][self.dimensions[1]]());
}
function dragStart(e) {
self.startPos = e[self.clientXY];
self.startSize = self.options.size;
Ox.print("startSize", self.startSize)
$window.mousemove(drag);
$window.one("mouseup", dragStop);
}
function dragStop() {
$window.unbind("mousemove");
}
function toggle() {
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") ? 1 : 0;
Ox.Event.trigger("resize_" + self.ids[i], self.options.elements[i][self.dimensions[1]]());
self.options.collapsed = !self.options.collapsed;
});
}
return that;
};
/*
----------------------------------------------------------------------------
Ox.Tabbar
----------------------------------------------------------------------------
*/
Ox.Tabbar = function(options, self) {
var self = self || {},
that = new Ox.Bar({
size: 20
}, self)
.defaults({
selected: 0,
tabs: []
})
.options(options || {})
.addClass("OxTabbar");
Ox.ButtonGroup({
buttons: self.options.tabs,
group: true,
selectable: true,
selected: self.options.selected,
size: "medium",
style: "tab",
}).appendTo(that);
return that;
};
// fixme: no need for this
Ox.Toolbar = function(options, self) {
var self = self || {},
that = new Ox.Bar({
size: oxui.getBarSize(options.size)
}, self);
return that;
};
/*
============================================================================
Ox.Dialog
============================================================================
*/
Ox.Dialog = function(options, self) {
// fixme: dialog should be derived from a generic draggable
// fixme: pass button elements directly
var self = self || {},
that = new Ox.Element("div", self)
.defaults({
title: "",
buttons: [],
height: 216,
minHeight: 144,
minWidth: 256,
padding: 16,
width: 384
})
.options(options || {})
.addClass("OxDialog");
if (!Ox.isArray(self.options.buttons[0])) {
self.options.buttons = [[], self.options.buttons];
}
that.$titlebar = new Ox.Bar({
size: "medium"
})
.addClass("OxTitleBar")
.mousedown(drag)
.dblclick(center)
.appendTo(that);
that.$title = new Ox.Element()
.addClass("OxTitle")
.html(self.options.title)
.appendTo(that.$titlebar);
// fixme: should the following be a container?
that.$content = new Ox.Element()
.addClass("OxContent")
.css({
padding: self.options.padding + "px",
overflow: "auto"
})
.appendTo(that);
that.$buttonsbar = new Ox.Bar({})
.addClass("OxButtonsBar")
.appendTo(that);
that.$buttons = [];
$.each(self.options.buttons[0], function(i, button) {
that.$buttons[i] = new Ox.Button({
disabled: button.disabled || false,
size: "medium",
value: button.value // fixme: use title
})
.addClass("OxLeft")
.click(button.click) // fixme: rather use event?
.appendTo(that.$buttonsbar);
});
that.$resize = new Ox.Element()
.addClass("OxResize")
.mousedown(resize)
.dblclick(reset)
.appendTo(that.$buttonsbar);
$.each(self.options.buttons[1].reverse(), function(i, button) {
that.$buttons[that.$buttons.length] = new Ox.Button({
disabled: button.disabled || false,
id: button.id,
size: "medium",
value: button.value
})
.addClass("OxRight")
.click(button.click) // fixme: rather use event?
.appendTo(that.$buttonsbar);
});
that.$buttons[0].focus();
that.$layer = new Ox.Element() // fixme: Layer widget, that would handle click?
.addClass("OxLayer")
.mousedown(mousedownLayer)
.mouseup(mouseupLayer);
function center() {
var documentHeight = $document.height();
that.css({
left: 0,
top: Math.max(parseInt(-documentHeight / 10), self.options.height - documentHeight + 40) + "px",
right: 0,
bottom: 0,
margin: "auto"
});
}
function drag(event) {
var bodyWidth = $body.width(),
bodyHeight = $document.height(),
elementWidth = that.width(),
offset = that.offset(),
x = event.clientX,
y = event.clientY;
$window.mousemove(function(event) {
that.css({
margin: 0
});
var left = Ox.limit(
offset.left - x + event.clientX,
24 - elementWidth, bodyWidth - 24
//0, documentWidth - elementWidth
),
top = Ox.limit(
offset.top - y + event.clientY,
24, bodyHeight - 24
//24, documentHeight - elementHeight
);
that.css({
left: left + "px",
top: top + "px"
});
});
$window.one("mouseup", function() {
$window.unbind("mousemove");
});
}
function getButtonById(id) {
var ret = null
$.each(that.$buttons, function(i, button) {
if (button.options("id") == id) {
ret = button;
return false;
}
});
return ret;
}
function mousedownLayer() {
that.$layer.stop().animate({
opacity: 0.5
}, 0);
}
function mouseupLayer() {
that.$layer.stop().animate({
opacity: 0
}, 0);
}
function reset() {
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
}
function resize(event) {
var documentWidth = $document.width(),
documentHeight = $document.height(),
elementWidth = that.width(),
elementHeight = that.height(),
offset = that.offset(),
x = event.clientX,
y = event.clientY;
$window.mousemove(function(event) {
that.css({
left: offset.left,
top: offset.top,
margin: 0
});
var width = Ox.limit(
elementWidth - x + event.clientX,
self.options.minWidth, Math.min(documentWidth, documentWidth - offset.left)
),
height = Ox.limit(
elementHeight - y + event.clientY,
self.options.minHeight, documentHeight - offset.top
);
that.width(width);
that.height(height);
that.$content.height(height - 48 - 2 * self.options.padding); // fixme: this should happen automatically
});
$window.one("mouseup", function() {
$window.unbind("mousemove");
});
}
self.onChange = function(key, value) {
if (key == "height" || key == "width") {
that.animate({
height: self.options.height + "px",
width: self.options.width + "px"
}, 250);
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
}, 250, function() {
that.$title.html(value).animate({
opacity: 1
}, 250);
});
}
}
that.append = function($element) {
that.$content.append($element);
return that;
}
that.close = function(callback) {
callback = callback || function() {};
that.animate({
opacity: 0
}, 200, function() {
that.remove();
that.$layer.remove();
callback();
});
$window.unbind("mouseup", mouseupLayer)
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
});
};
that.enable = function() {
that.$layer.removeClass("OxFront");
return that;
};
that.enableButton = function(id) {
getButtonById(id).options({
disabled: false
});
};
that.open = function() {
that.$layer.appendTo($body);
center();
reset();
that.css({
opacity: 0
}).appendTo($body).animate({
opacity: 1
}, 200);
$window.bind("mouseup", mouseupLayer)
return that;
};
return that;
}
/*
============================================================================
Forms
============================================================================
*/
/*
----------------------------------------------------------------------------
Ox.Filter
----------------------------------------------------------------------------
*/
Ox.Filter = function(options, self) {
var self = self || {}
that = new Ox.Element()
.defaults({
})
.options(options || {});
return that;
};
/*
----------------------------------------------------------------------------
Ox.Form
----------------------------------------------------------------------------
*/
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
$.extend(self, {
$items: [],
$messages: [],
formIsValid: false,
itemIds: [],
itemIsValid: []
});
// fixme: form isn't necessarily empty/invalid
$.map(self.options.items, function(item, i) {
self.itemIds[i] = item.id || item.element.options("id");
self.itemIsValid[i] = false;
});
$.each(self.options.items, function(i, item) {
var id = item.element.options("id");
that.append(self.$items[i] = new Ox.FormItem(item))
.append(self.$messages[i] = new Ox.Element().addClass("OxFormMessage"));
Ox.Event.bind("validate_" + id, function(event, data) {
validate(i, data.valid);
});
Ox.Event.bind("blur_" + id, function(event, data) {
validate(i, data.valid);
if (data.valid) {
self.$messages[i].html("").hide();
} else {
self.$messages[i].html(data.message).show();
}
});
Ox.Event.bind("submit_" + id, function(event, data) {
self.formIsValid && that.submit();
});
});
function getItemPositionById(id) {
return self.itemIds.indexOf(id);
}
function setMessage(id, message) {
self.$messages[getItemPositionById(id)].html(message)[message !== "" ? "show" : "hide"]();
}
function submitCallback(data) {
$.each(data, function(i, v) {
setMessage(v.id, v.message);
});
}
function validate(pos, valid) {
Ox.print("validate", pos, valid)
self.itemIsValid[pos] = valid;
if (Ox.every(self.itemIsValid) != self.formIsValid) {
self.formIsValid = !self.formIsValid;
that.triggerEvent("validate", {
valid: self.formIsValid
});
}
}
that.submit = function() {
self.options.submit(that.values(), submitCallback);
};
that.values = function() { // fixme: can this be private?
var values = {};
if (arguments.length == 0) {
$.each(self.$items, function(i, $item) {
values[self.itemIds[i]] = self.$items[i].value();
});
return values;
} else {
$.each(arguments[0], function(key, value) {
});
return that;
}
};
return that;
};
Ox.FormItem = function(options, self) {
var self = self || {},
that = new Ox.Element("div", self)
.defaults({
element: null,
error: "",
})
.options(options || {})
.addClass("OxFormItem")
.append(self.options.element);
that.value = function() {
return self.options.element.$input.val();
};
return that;
}
/*
----------------------------------------------------------------------------
Ox.Button
----------------------------------------------------------------------------
*/
Ox.Button = function(options, self) {
/*
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: null,
id: "",
selectable: false,
selected: false,
size: "medium",
style: "default", // can be default, symbol or tab
type: "text",
value: "",
values: [] // fixme: shouldn't this go into self.values?
})
.options($.extend(options, {
value: $.isArray(options.value) ?
options.value[0] : options.value,
values: $.makeArray(options.value)
}))
.attr({
disabled: self.options.disabled ? "disabled" : "",
type: self.options.type == "text" ? "button" : "image"
})
.addClass("OxButton Ox" + Ox.toTitleCase(self.options.size) +
(self.options.style != "default" ? " Ox" + Ox.toTitleCase(self.options.style) : "") +
(self.options.disabled ? " OxDisabled": "") +
(self.options.selected ? " OxSelected": ""))
.mousedown(mousedown)
.click(click);
//Ox.print(self.options.value, self.options.disabled)
/*
that.bind("OxElement" + that.id + "SetOptions", function(e, data) {
if (typeof data.selected != "undefined") {
if (data.selected != that.hasClass("OxSelected")) {
that.toggleClass("OxSelected");
}
}
if (typeof data.value != "undefined") {
if (self.options.type == "image") {
that.attr({
src: oxui.path + "png/" + Ox.theme() +
"/button" + Ox.toTitleCase(options.value) + ".png"
});
} else {
that.val(self.options.value);
}
}
})
*/
function mousedown(e) {
if (self.options.type == "image" && $.browser.safari) {
// keep image from being draggable
e.preventDefault();
}
}
function click() {
if (!self.options.selectable) {
that.triggerEvent("click");
} else if (!self.options.group || !self.options.selected) {
if (self.options.group) {
that.triggerEvent("select");
} else {
that.toggleSelected();
}
}
if (self.options.values.length == 2) {
that.options({
value: self.options.value == self.options.values[0] ?
self.options.values[1] : self.options.values[0]
});
}
//self.options.click();
}
self.onChange = function(key, value) {
//Ox.print("setOption", option, 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 == "value") {
if (self.options.type == "image") {
that.attr({
src: oxui.path + "png/ox.ui." + Ox.theme() +
"/button" + Ox.toTitleCase(value) + ".png"
});
} else {
that.val(value);
}
}
}
that.toggleDisabled = function() {
that.options({
enabled: !self.options.disabled
});
}
that.toggleSelected = function() {
that.options({
selected: !self.options.selected
});
}
that.options("value", self.options.value);
return that;
};
/*
----------------------------------------------------------------------------
Ox.ButtonGroup
----------------------------------------------------------------------------
*/
Ox.ButtonGroup = function(options, self) {
/*
events:
change {id, value} selection within a group changed
*/
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
buttons: [],
group: false,
selectable: false,
selected: -1,
size: "medium",
style: "",
type: "text",
})
.options(options || {})
.addClass("OxButtonGroup");
self.position = {};
that.$buttons = [];
$.each(self.options.buttons, function(position, button) {
that.$buttons[position] = Ox.Button({
disabled: button.disabled,
group: self.options.group ? that : null,
id: button.id,
selectable: self.options.selectable,
selected: position == self.options.selected,
size: self.options.size,
style: self.options.style,
type: self.options.type,
value: button.value
}).appendTo(that);
self.position[that.$buttons.id] = position;
that.bindEvent("select_" + that.$buttons[position].options("id"), function() {
selectButton(position);
});
});
function onChange(event, data) {
console.log("event", event, "data", data)
var id = event.split("_")[1];
if (self.options.selected > -1) {
that.$buttons[self.options.selected].toggleSelected();
}
self.options.selected = self.position[id];
that.triggerEvent("change", {
value: id
});
}
function selectButton(position) {
that.$buttons[self.options.selected].toggleSelected();
self.options.selected = position;
that.$buttons[self.options.selected].toggleSelected();
};
return that;
};
/*
----------------------------------------------------------------------------
Ox.Input
----------------------------------------------------------------------------
*/
Ox.Input = function(options, self) {
/* options:
* autocomplete function, or array of values, or dict with array of values per label or placeholder
* autocorrect function for live correction
* clear boolean, clear button, or not
* highlight boolean, highlight value in autocomplete menu, or not
* id
* label string or array [{id, title}] (select) -- label and placeholder are mutually exclusive
* labelWidth integer (px)
* placeholder string or array [{id, title}] (select) -- label and placeholder are mutually exclusive
* selected integer, selected label or placeholder
* size "large", "medium" or "small"
* type "text", "password", "textarea", etc.
* validate function vor live validation, returns { message: "", valid: false }
* value string
*/
var self = self || {},
that = new Ox.Element("div", self)
.defaults({
autocomplete: null,
autocorrect: null,
clear: false,
highlight: false,
id: "",
label: "",
labelWidth: 64,
placeholder: "",
selected: 0,
size: "medium",
type: "text",
validate: null,
value: ""
})
.options(options || {})
.addClass("OxInput Ox" + Ox.toTitleCase(self.options.size)),
autocomplete; // fixme: should be self.autocomplete
if (self.options.label) {
self.options.label = Ox.makeArray(self.options.label);
self.option = self.options.label[self.options.selected]; // fixme: id or title? or not use this at all?
self.items = self.options.label;
} else if (self.options.placeholder) {
self.options.placeholder = Ox.makeArray(self.options.placeholder);
self.option = self.options.placeholder[self.options.selected];
self.items = self.options.placeholder;
}
if (Ox.isArray(self.options.autocomplete)) {
autocomplete = self.options.autocomplete;
self.options.autocomplete = {};
self.options.autocomplete[self.placeholder] = autocomplete;
}
if (self.options.label) {
that.$label = new Ox.Element()
.addClass("OxInputLabel")
.width(self.options.labelWidth)
.html(self.options.label.length == 1 ? self.options.label[0] : self.options.label[0].title)
.appendTo(that);
}
if (self.options.label.length > 1 || self.options.placeholder.length > 1) {
that.$label && that.$label.click(select);
that.$select = new Ox.Button({
style: "symbol",
type: "image",
value: "select"
// value: oxui.symbols.select
})
.click(select)
.appendTo(that);
self.selectId = Ox.toCamelCase(
self.options.id + "_" + (self.options.label.length > 1 ? "label" : "placeholder")
);
self.selectMenu = new Ox.Menu({
element: that,
id: self.selectId,
items: $.map(self.items, function(item, position) {
return {
checked: position == self.options.selected,
id: item.id,
group: self.selectId, // fixme: same id, works here, but should be different
title: item.title
};
}),
offset: {
left: 4,
top: 0
}
});
that.bindEvent("change_" + self.selectId, change);
}
that.$input = new Ox.Element(
self.options.type == "textarea" ? "textarea" : "input", self
)
.attr({
type: self.options.type == "textarea" ? undefined : self.options.type // fixme: make conditional?
})
.addClass(
"OxInput Ox" + Ox.toTitleCase(self.options.size) +
" OxPlaceholder"
)
.focus(focus)
.blur(blur)
.change(change)
.appendTo(that);
self.options.placeholder && that.$input.val(self.option.title);
if (self.options.clear) {
that.$clear = new Ox.Button({
style: "symbol",
type: "image",
value: "clear"
//value: oxui.symbols.clear
})
.click(clear)
.appendTo(that);
}
if (self.options.autocomplete) {
that.$input.attr({
autocomplete: "off"
});
self.autocompleteId = self.options.id + "_menu"; // fixme: we do this in other places ... are we doing it the same way? var name?
self.autocompleteMenu = new Ox.Menu({
element: that.$input,
id: self.autocompleteId,
offset: {
left: 4,
top: 0
},
size: self.options.size
});
that.bindEvent("click_" + self.autocompleteId, onClick);
}
if (self.options.type != "textarea") {
that.bindEvent({
key_enter: submit,
});
}
/*
if (self.options.validate) {
self.valid = self.options.validate(self.options.value);
}
*/
that.bindEvent({
key_escape: cancel
});
function autocomplete(value) {
var value = value.toLowerCase(),
items = [];
if (value === "") {
// items = self.options.autocomplete[self.placeholder];
} else {
$.each(self.options.autocomplete[self.option], function(i, item) {
if (item.toLowerCase().indexOf(value) > -1) {
items.push(item);
}
});
}
autocompleteCallback(items);
}
function autocompleteCall() {
var value = that.$input.val();
Ox.print("autocomplete call", self.option, value)
if (self.options.autocomplete) {
if (value !== "") {
Ox.isFunction(self.options.autocomplete) ? (
self.option ?
self.options.autocomplete(self.option.id, value, autocompleteCallback) :
self.options.autocomplete(value, autocompleteCallback)
) : autocomplete(value);
} else {
autocompleteCallback();
}
}
}
function autocompleteCallback(items) {
Ox.print("autocomplete callback", items)
var items = items || [],
selected = items.length == 1 ? 0 : -1,
value = that.$input.val().toLowerCase();
if (items.length) {
items = $.map(items, function(title, position) {
if (value == Ox.stripTags(title.toLowerCase())) {
selected = position;
}
return {
id: title.toLowerCase(), // fixme: need function to do lowercase, underscores etc?
title: self.options.highlight ? title.replace(
new RegExp("(" + value + ")", "ig"),
"<span class=\"OxHighlight\">$1</span>"
) : title
};
});
self.selectMenu.hideMenu();
self.autocompleteMenu.options({
items: items,
selected: selected
}).showMenu();
} else {
self.autocompleteMenu.hideMenu();
}
}
function autocorrect(blur) {
var blur = blur || false,
length = self.value.length;
pos = cursor(),
self.value = self.options.autocorrect(self.value, blur);
that.$input.val(self.value);
if (!blur) {
cursor(pos + self.value.length - length);
}
}
function blur() {
that.loseFocus();
that.removeClass("OxFocus");
self.options.autocorrect && autocorrect(that.$input.val(), true);
self.options.validate && that.triggerEvent("blur", { // fixme: is this a good event name for validation on blur?
message: self.message,
valid: self.valid
});
if (self.options.placeholder && that.$input.val() === "") {
that.$input.addClass("OxPlaceholder").val(self.option.title);
}
if (self.options.autocomplete || self.options.autocorrect || self.options.validate) { // fixme: duplicated, make a var
$document.unbind("keydown", keypress);
$document.unbind("keypress", keypress);
}
}
function cancel() {
that.$input.blur();
}
function change(event, data) {
Ox.print("input change", event, data)
if (data) {
self.option = {
id: data.id,
title: data.value // fixme: should be data.title
};
}
if (self.options.label) {
that.$label.html(self.option.title);
that.$input.focus();
autocompleteCall();
} else {
if (that.$input.is(".OxPlaceholder")) {
that.$input.val(self.option.title);
//that.$input.focus();
} else {
that.$input.focus();
autocompleteCall();
}
}
}
function clear() {
that.$input.val("").focus();
//autocompleteCall();
}
function cursor() {
// fixme: see selection()
if (arguments.length == 0) {
return that.$input.$element[0].selectionStart;
} else {
that.$input.$element[0].setSelectionRange(arguments[0], arguments[0]);
}
}
function focus() {
var value;
that.gainFocus();
that.addClass("OxFocus");
if (that.$input.is(".OxPlaceholder")) {
that.$input.val("").removeClass("OxPlaceholder");
}
value = that.$input.val();
if (self.options.autocomplete || self.options.autocorrect || self.options.validate) {
// fixme: different in webkit and firefox (?), see keyboard handler, need generic function
$document.bind("keydown", keypress);
$document.bind("keypress", keypress);
if (self.options.autocomplete) {
value !== "" && Ox.isFunction(self.options.autocomplete) ?
self.options.autocomplete(self.option.id, value, autocompleteCallback) :
autocomplete(value);
}
}
}
function keypress(event) {
if (event.keyCode != 13) {
setTimeout(function() {
var value = that.$input.val();
if (value != self.value) {
self.value = value;
self.options.autocomplete && autocompleteCall();
self.options.autocorrect && autocorrect();
self.options.validate && validateCall();
}
}, 25);
}
}
function onClick(event, data) {
Ox.print("onClick", data);
that.$input.val(Ox.stripTags(data.title));
self.autocompleteMenu.hideMenu();
submit();
}
function select() {
self.selectMenu.showMenu();
}
function selection() {
// fixme: not used!
var start, end;
if (arguments.length == 0) {
return [self.element.selectionStart, self.element.selectionEnd];
} else {
start = arguments[0];
end = arguments[1] || start;
self.element.setSelectionRange(start, end);
}
}
function submit() {
Ox.print("input submit", that.$input.val())
that.$input.trigger("blur");
that.triggerEvent("submit", self.option ? {
key: self.option.id,
value: that.$input.val()
} : that.$input.val());
}
function validate() {
var value = that.$input.val(),
valid = self.options.validate(value) != null;
validateCallback({
valid: valid,
message: "Invalid " + self.options.label
});
}
function validateCall() {
var value = that.$input.val();
Ox.print("validate call", value)
if (self.options.validate) {
if (value !== "") {
Ox.isFunction(self.options.validate) ?
self.options.validate(value, validateCallback) :
validate(value);
} else {
validateCallback({
valid: false,
message: ""
});
}
}
}
function validateCallback(data) {
if (data.valid != self.valid) {
self.message = data.message;
self.valid = data.valid;
that.triggerEvent("validate", data);
}
}
that.changeLabel = function(id) {
that.$label.html(Ox.getObjectById(self.options.label, id).title);
self.selectMenu.checkItem(id);
};
that.height = function(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)))"
});
}
return that;
}
that.width = function(value) {
that.$element.width(value);
that.$input.width(value - (self.options.type == "textarea" ? 0 : 2) -
(self.options.label ? self.options.labelWidth + 25 : 0) -
(self.options.placeholder.length > 1 ? 16 : 0) -
(self.options.clear ? 25 : 0));
// fixme: the values above are all weird guesswork
return that;
}
return that;
};
/*
----------------------------------------------------------------------------
Ox.Label
----------------------------------------------------------------------------
*/
Ox.Label = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
id: "",
title: ""
})
.options(options)
.addClass("OxLabel");
that.html(self.options.title);
return that;
};
/*
----------------------------------------------------------------------------
Ox.Range
options:
animate boolean if true, animate thumb
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
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
----------------------------------------------------------------------------
*/
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
})
.options($.extend(options, {
arrowStep: options.arrowStep ?
options.arrowStep : options.step,
trackImages: $.makeArray(options.trackImages || [])
}))
.addClass("OxRange");
// 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); // fixme: make that work
if (trackImages) {
var width = parseFloat(screen.width / trackImages),
$image = $("<canvas/>")
.attr({
width: width * trackImages,
height: 14
})
.addClass("OxImage")
.appendTo($track.$element), // fixme: make that work
c = $image[0].getContext('2d');
c.mozImageSmoothingEnabled = false; // we may want to remove this later
$.each(self.options.trackImages, function(i, v) {
$("<img/>")
.attr({
src: v
})
.load(function() {
c.drawImage(this, i * width, 0, width, 14);
});
});
}
var $thumb = Ox.Button({})
.addClass("OxThumb")
.appendTo($track);
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.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(option, value) {
}
return that;
};
/*
----------------------------------------------------------------------------
Ox.Select
----------------------------------------------------------------------------
*/
Ox.Select = function(options, self) {
var self = self || {},
that = new Ox.Element("div", self) // fixme: do we use "div", or {}, or "", by default?
.defaults({
id: "",
items: [],
size: "medium"
})
.options(options)
.addClass("OxSelect Ox" + Ox.toTitleCase(self.options.size));
self.buttonId = self.options.id + "_button";
self.groupId = self.options.id; // + "_group"
self.menuId = self.options.id + "_menu",
$.each(self.options.items, function(i, item) {
self.options.items[i] = $.extend(self.options.items[i], {
checked: item.checked || false,
group: self.groupId
});
if (item.checked) {
self.selected = i;
}
});
Ox.print(self.options.id)
that.$button = new Ox.Button($.extend(self.options, {
id: self.buttonId,
type: "text", // fixme: this shouldn't be necessary
value: self.options.items[self.selected].title // fixme: title instead of value?
}), {})
.click(clickButton)
.appendTo(that);
that.$symbol = new Ox.Button({
style: "symbol",
type: "image",
value: "select"
})
.click(clickButton)
.appendTo(that);
that.$menu = new Ox.Menu({
element: that.$button,
id: self.menuId,
items: self.options.items,
offset: {
left: 8,
top: 0
},
side: "bottom",
size: self.options.size
});
that.bindEvent("change_" + self.groupId, clickMenu);
function clickButton() {
that.$menu.toggleMenu();
}
function clickMenu(event, data) {
that.$button.options({
value: data.value
});
that.triggerEvent("change", data.value);
}
self.onChange = function(key, value) {
};
that.selectItem = function(id) {
that.$button.options({
value: Ox.getObjectById(self.options.items, id).title
});
that.$menu.checkItem(id);
};
that.width = function(val) {
// fixme: silly hack, and won't work for css()
that.$element.width(val + 16);
that.$button.width(val);
//that.$symbol.width(val);
return that;
};
return that;
}
/*
============================================================================
Lists
============================================================================
*/
Ox.IconList = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
id: "",
item: function() {},
keys: [],
orientation: "both",
request: function() {},
size: 128,
sort: [],
})
.options(options || {});
$.extend(self, {
itemHeight: self.options.size * 1.5,
itemWidth: self.options.size
});
that.$element = new Ox.List({
construct: constructItem,
itemHeight: self.itemHeight,
itemWidth: self.itemWidth,
keys: $,
orientation: "both",
request: function() {},
rowLength: 1,
size: 128,
type: "icon",
}, self)
.click(click)
.dblclick(dblclick)
.scroll(scroll);
function click() {
}
function constructItem(data) {
var data = self.options.item(data, self.options.sort);
return new Ox.IconItem($.extend(data, {
size: self.options.size
}));
}
function dblclick() {
}
function scroll() {
}
return that;
};
Ox.IconItem = function(options, self) {
var self = self || {}
that = new Ox.Element({}, self)
.defaults({
height: 0,
id: "",
info: "",
size: 128,
title: "",
width: 0,
url: ""
})
.options(options || {});
$.extend(self, {
height: self.options.size * 1.5,
url: oxui.path + "/png/ox.ui." + Ox.theme() + "/icon.png",
width: self.options.size + 4
});
that.css({
width: self.width + "px",
height: self.height + "px"
});
that.$icon = $("<div>")
.addClass("OxIcon")
.css({
top: self.options.size == 64 ? -70 : -128,
width: self.options.size + "px",
height: self.options.size + "px"
});
that.$iconImage = $("<img>")
.attr({
src: self.url
})
.css({
width: iconWidth + "px",
height: iconHeight + "px"
})
.mouseenter(mouseenter)
.mouseleave(mouseleave)
.one("load", function() {
that.$iconImage.attr({
src: self.options.url
});
});
that.$textBox = $("<div>")
.addClass("OxText")
.css({
top: (self.options.size / 2 + 2) + "px",
width: self.width + "px",
height: (self.options.size == 64 ? 38 : 58) + "px"
})
that.$text = $("<div>")
.html(self.options.title + "<br/><span class=\"OxInfo\">" + self.options.info + "</span>")
.mouseenter(mouseenter)
.mouseleave(mouseleave)
that.$reflection = $("<div>")
.addClass("OxReflection")
.css({
top: (self.options.size + 2) + "px",
width: self.options.size + "px",
height: (self.options.size / 2) + "px"
});
that.$reflectionImage = $("<img>")
.addClass("OxReflection")
.attr({
src: self.url
})
.css({
width: self.options.width + "px",
height: self.options.height + "px"
})
.one("load", function() {
that.$reflectionImage.attr({
src: self.options.url
});
});
that.$gradient = $("<div>")
.addClass("OxGradient")
.css({
top: (-self.options.size - 2) + "px"
});
that.append(
that.$reflection.append(
that.$reflectionImage
).append(
that.$gradientImage
)
).append(
that.$textBox.append(
that.$text
)
).append(
that.$icon.append(
that.$iconImage
)
);
function mouseenter() {
that.addClass("OxHover");
}
function mouseleave() {
that.removeClass("OxHover");
}
return that;
};
Ox.List = function(options, self) {
var self = self || {},
that = new Ox.Container({}, self)
.defaults({
construct: function() {},
itemHeight: 16,
itemWidth: 16,
keys: [],
orientation: "vertical",
request: function() {}, // {sort:, range:, callback:}, without parameter returns {items, size etc.}
rowLength: 1,
sort: [],
type: "text",
unique: ""
})
.options(options || {})
.click(click)
.scroll(scroll);
$.extend(self, {
$items: [],
$pages: [],
ids: {},
keyboardEvents: {
key_alt_control_a: invertSelection,
key_control_a: selectAll,
key_control_shift_a: selectNone,
key_end: scrollToFirst,
key_enter: open,
key_home: scrollToLast,
key_pagedown: scrollPageDown,
key_pageup: scrollPageUp,
key_space: preview
},
page: 0,
pageLength: 100,
preview: false,
requests: [],
selected: []
});
self.keyboardEvents["key_" + (self.options.orientation == "horizontal" ? "left" : "up")] = selectPrevious;
self.keyboardEvents["key_" + (self.options.orientation == "horizontal" ? "right" : "down")] = selectNext;
self.keyboardEvents["key_" + (self.options.orientation == "horizontal" ? "shift_left" : "shift_up")] = addPreviousToSelection;
self.keyboardEvents["key_" + (self.options.orientation == "horizontal" ? "shift_right" : "shift_down")] = addNextToSelection;
updateQuery();
Ox.print("s.o", self.options)
that.bindEvent(self.keyboardEvents);
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 addNextToSelection() {
var pos = getNext();
if (pos > -1) {
addToSelection(pos);
scrollTo(pos);
}
}
function addPreviousToSelection() {
var pos = getPrevious();
if (pos > -1) {
addToSelection(pos);
scrollTo(pos);
}
}
function addToSelection(pos) {
if (!isSelected(pos)) {
self.selected.push(pos);
if (!Ox.isUndefined(self.$items[pos])) {
self.$items[pos].addClass("OxSelected");
}
Ox.print("addToSelection")
triggerSelectEvent();
}
}
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 click(e) {
var $element = $(e.target), pos;
that.gainFocus();
while (!$element.hasClass("OxItem") && !$element.hasClass("OxPage")) {
$element = $element.parent();
}
if ($element.hasClass("OxItem")) {
Ox.print($element.attr("id"), $element.data("position"));
pos = $element.data("position");
if (e.shiftKey) {
addAllToSelection(pos);
} else if (e.metaKey) {
toggleSelection(pos);
} else {
select(pos);
}
} else {
selectNone();
}
}
function deselect(pos) {
if (isSelected(pos)) {
self.selected.splice(self.selected.indexOf(pos), 1);
if (!Ox.isUndefined(self.$items[pos])) {
self.$items[pos].removeClass("OxSelected");
}
triggerSelectEvent();
}
}
function getHeight() {
return that.height() - (that.$content.width() > that.width() ? oxui.scrollbarSize : 0);
}
function getNext() {
var pos = -1;
if (self.selected.length) {
pos = Ox.max(self.selected) + 1;
if (pos == self.$items.length) {
pos = -1;
}
}
return pos;
}
function getPage() {
return self.options.orientation == "horizontal"
? Math.floor(that.scrollLeft() / self.pageWidth)
: Math.floor(that.scrollTop() / self.pageHeight);
}
function getPositions() {
Ox.print("getPositions", $.map(self.selected, function(v, i) {
return self.ids[v];
}));
// fixme: optimize: send non-selected ids if more than half of the items are selected
if (self.selected.length /*&& self.selected.length < self.listLength*/) {
self.requests.push(self.options.request({
callback: getPositionsCallback,
ids: $.map(self.selected, function(v, i) {
return self.ids[v];
}),
sort: self.options.sort
}));
} else {
getPositionsCallback();
}
}
function getPositionsCallback(result) {
Ox.print("getPositionsCallback", result)
if (result) {
$.extend(self, {
ids: {},
selected: []
});
$.each(result.data.positions, function(id, pos) {
Ox.print("id", id, "pos", pos)
self.selected.push(pos);
});
}
load();
}
function getPrevious() {
var pos = -1;
if (self.selected.length) {
pos = Ox.min(self.selected) - 1;
}
return pos;
}
function getSelectedIds() {
// fixme: is carring self.ids around the best way?
return $.map(self.selected, function(v, i) {
return self.ids[v];
});
}
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 load() {
that.scrollTop(0);
that.$content.empty();
loadPages(self.page);
}
function loadPage(page, callback) {
Ox.print("loadPage", page)
if (page < 0 || page >= self.pages) {
!Ox.isUndefined(callback) && callback();
return;
}
var keys = $.inArray("id", self.options.keys) > -1 ? self.options.keys :
$.merge(self.options.keys, ["id"]),
offset = page * self.pageLength,
range = [offset, offset + (page < self.pages - 1 ?
self.pageLength : self.listLength % self.pageLength)];
if (Ox.isUndefined(self.$pages[page])) {
self.requests.push(self.options.request({
callback: function(result) {
self.$pages[page] = new Ox.ListPage();
if (self.options.type == "text") {
self.$pages[page].css({
top: (page * self.pageHeight) + "px"
});
} else {
self.$pages[page].css({
});
}
$.each(result.data.items, function(i, v) {
var pos = offset + i;
self.$items[pos] = new Ox.ListItem({
construct: self.options.construct,
data: v,
id: v[self.options.unique],
position: pos
});
self.ids[pos] = v[self.options.unique];
if (isSelected(pos)) {
self.$items[pos].addClass("OxSelected");
}
self.$items[pos].appendTo(self.$pages[page]);
});
if (self.options.type == "text" && page == 0) {
var height = that.height() - (that.width() < that.$content.width() ? oxui.scrollbarSize : 0),
visibleItems = Math.ceil(height / self.options.itemHeight);
if (result.data.items.length < visibleItems) {
self.$pages[page].height(height).css({
overflow: "hidden"
});
$.each(Ox.range(result.data.items.length, visibleItems), function(i, v) {
new Ox.ListItem({
construct: self.options.construct,
data: {},
id: "",
position: v
}).appendTo(self.$pages[page]);
});
}
}
self.$pages[page].appendTo(that.$content);
!Ox.isUndefined(callback) && callback();
},
keys: keys,
range: range,
sort: self.options.sort
}));
} else {
self.$pages[page].appendTo(that.$content);
}
}
function loadPages(page, callback) {
var counter = 0,
fn = function() {
counter++;
counter == 2 && !Ox.isUndefined(callback) && callback();
};
loadPage(page, function() {
loadPage(page - 1, fn);
loadPage(page + 1, fn);
});
}
function open() {
that.triggerEvent("open", {
ids: getSelectedIds()
});
}
function preview() {
self.preview = !self.preview;
if (self.preview) {
that.triggerEvent("openpreview", {
ids: getSelectedIds()
});
} else {
that.triggerEvent("closepreview");
}
}
function scroll() {
var page = self.page;
self.page = getPage();
setTimeout(function() {
if (self.page == getPage()) {
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);
}
}
}, 100);
}
function scrollPageDown() {
that.scrollBy(getHeight());
}
function scrollPageUp() {
that.scrollBy(-getHeight());
}
function scrollTo(pos) {
var positions = [], scroll, size;
if (self.options.orientation == "horizontal") {
} else if (self.options.orientation == "vertical") {
positions[0] = self.options.itemHeight * pos;
positions[1] = positions[0] + self.options.itemHeight;
scroll = that.scrollTop();
size = getHeight();
if (positions[0] < scroll) {
that.animate({
scrollTop: positions[0] + "px"
}, 0);
} else if (positions[1] > scroll + size) {
that.animate({
scrollTop: (positions[1] - size) + "px"
}, 0);
}
} else {
}
}
function scrollToFirst() {
that.scrollTop(0);
}
function scrollToLast() {
that.scrollTop(self.listHeight);
}
function select(pos) {
if (!isSelected(pos) || self.selected.length > 1) {
selectNone();
addToSelection(pos);
}
}
function selectAll() {
$.each(Ox.range(self.listLength), function(i, v) {
Ox.print("adding", v);
addToSelection(v);
});
}
function selectNext() {
var pos = getNext();
if (pos > -1) {
select(pos);
scrollTo(pos);
}
}
function selectNone() {
$.each(self.$items, function(i, v) {
deselect(i);
});
}
function selectPrevious() {
var pos = getPrevious();
if (pos > -1) {
select(pos);
scrollTo(pos);
}
}
function selectQuery(str) {
$.each(self.$items, function(i, v) {
if (Ox.toLatin(v.title).toUpperCase().indexOf(str) == 0) {
select(i);
scrollTo(i);
return false;
}
});
}
function toggleSelection(pos) {
if (!isSelected(pos)) {
addToSelection(pos);
} else {
deselect(pos);
}
}
function triggerSelectEvent() {
var ids = getSelectedIds();
setTimeout(function() {
var ids_ = getSelectedIds();
if (ids.length == ids_.length && (ids.length == 0 || ids[0] == ids_[0])) {
that.triggerEvent("select", {
ids: ids
});
self.preview && that.triggerEvent("openpreview", {
ids: ids
});
} else {
Ox.print("select event not triggered after timeout");
}
}, 100);
}
function unloadPage(page) {
if (page < 0 || page >= self.pages) {
return;
}
Ox.print("unloadPage", page)
Ox.print("self.$pages", self.$pages)
Ox.print(!Ox.isUndefined(self.$pages[page]))
!Ox.isUndefined(self.$pages[page]) && self.$pages[page].remove();
}
function unloadPages(page) {
unloadPage(page);
unloadPage(page - 1);
unloadPage(page + 1)
}
function updateQuery() {
clear();
self.requests.push(self.options.request({
callback: function(result) {
var keys = {};
that.triggerEvent("load", result.data);
$.extend(self, {
listHeight: result.data.items * self.options.itemHeight, // fixme: should be listSize
listLength: result.data.items,
pages: Math.ceil(result.data.items / self.pageLength),
pageWidth: self.options.orientation == "horizontal" ?
self.pageLength * self.options.itemWidth : 0,
pageHeight: self.options.orientation == "horizontal" ? 0 :
self.pageLength * self.options.itemHeight / self.options.rowLength
});
that.$content.css({
height: self.listHeight + "px"
});
getPositions();
}
}));
}
function updateSort() {
if (self.listLength > 1) {
clear();
getPositions();
}
}
self.onChange = function(key, value) {
Ox.print("list onChange", key, value);
if (key == "request") {
updateQuery();
}
};
that.clearCache = function() { // fixme: unused? make private?
self.$pages = [];
};
that.sort = function(key, operator) {
if (key != self.options.sort[0].key || operator != self.options.sort[0].operator) {
self.options.sort[0] = {
key: key,
operator: operator
}
that.triggerEvent("sort", self.options.sort[0]);
updateSort();
}
}
return that;
};
Ox.ListItem = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
construct: function() {},
data: {},
id: "",
position: 0
})
.options(options || {});
$.each(self.options.data, function(k, v) {
self.options.data[k] = $.isArray(v) ? v.join(", ") : v;
});
that.$element = self.options.construct(self.options.data)
.addClass("OxItem")
.attr({
id: self.options.id
})
.data("position", self.options.position);
return that;
};
Ox.ListPage = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.addClass("OxPage");
return that;
};
Ox.TextList = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.defaults({
columns: [],
columnWidth: [40, 800],
id: "",
request: function() {}, // {sort, range, keys, callback}
sort: []
})
.options(options || {})
.addClass("OxTextList");
$.each(self.options.columns, function(i, v) { // fixme: can this go into a generic ox.js function?
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: [],
columnWidths: [],
itemHeight: 16,
page: 0,
pageLength: 100,
scrollLeft: 0,
selectedColumn: getColumnIndexById(self.options.sort[0].key),
visibleColumns: $.map(self.options.columns, function(v, i) {
return v.visible ? v : null;
})
});
$.extend(self, {
pageHeight: self.pageLength * self.itemHeight
});
// Head
that.$bar = new Ox.Bar({
orientation: "horizontal",
size: 16
}).appendTo(that);
that.$head = new Ox.Container()
.addClass("OxHead")
.appendTo(that.$bar);
that.$head.$content.addClass("OxTitles");
that.$titles = [];
$.each(self.visibleColumns, function(i, v) {
var $order, $resize, $left, $center, $right, timeout = 0;
self.columnWidths[i] = v.width;
that.$titles[i] = $("<div>")
.addClass("OxTitle OxColumn" + Ox.toTitleCase(v.id))
.css({
width: (v.width - 9) + "px",
textAlign: v.align
})
.html(v.title)
.mousedown(function(e) {
timeout = setTimeout(function() {
dragColumn(v.id, e);
timeout = 0;
}, 250);
})
.mouseup(function() {
if (timeout) {
clearTimeout(timeout);
timeout = 0;
clickColumn(v.id);
}
})
.appendTo(that.$head.$content.$element);
self.columnPositions[i] = Ox.sum(self.columnWidths) - self.columnWidths[i] / 2;
$order = $("<div>")
.addClass("OxOrder")
.html(oxui.symbols["triangle_" + (
v.operator === "" ? "up" : "down"
)])
.click(function() {
$(this).prev().trigger("click")
})
.appendTo(that.$head.$content.$element);
$resize = $("<div>")
.addClass("OxResize")
.mousedown(function(e) {
var startWidth = self.columnWidths[i],
startX = e.clientX;
$window.mousemove(function(e) {
var x = e.clientX,
width = Ox.limit(
startWidth - startX + x,
self.options.columnWidth[0],
self.options.columnWidth[1]
);
resizeColumn(v.id, width);
});
$window.mouseup(function() {
$window.unbind("mousemove");
$window.unbind("mouseup");
});
})
.dblclick(function() {
resizeColumn(v.id, v.width);
})
.appendTo(that.$head.$content.$element);
$left = $("<div>").addClass("OxLeft").appendTo($resize);
$center = $("<div>").addClass("OxCenter").appendTo($resize);
$right = $("<div>").addClass("OxRight").appendTo($resize);
});
that.$head.$content.css({
width: (Ox.sum(self.columnWidths) + 2) + "px"
});
Ox.print("s.sC", self.selectedColumn)
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"
});
}
that.$select = new Ox.Button({
style: "symbol",
type: "image",
value: "select"
}).appendTo(that.$bar.$element);
// Body
that.$body = new Ox.List({
construct: constructItem,
id: self.options.id,
itemHeight: 16,
itemWidth: getItemWidth(),
keys: $.map(self.visibleColumns, function(v, i) {
return v.id;
}),
orientation: "vertical",
request: self.options.request,
sort: self.options.sort,
type: "text",
unique: self.unique
})
.addClass("OxBody")
.scroll(function() {
var scrollLeft = $(this).scrollLeft();
if (scrollLeft != self.scrollLeft) {
self.scrollLeft = scrollLeft;
that.$head.scrollLeft(scrollLeft);
}
})
.appendTo(that);
that.$body.$content.css({
width: getItemWidth() + "px"
});
Ox.print("s.vC", self.visibleColumns)
function addColumn(id) {
}
function clickColumn(id) {
Ox.print("clickColumn", id);
var i = getColumnIndexById(id),
isSelected = self.options.sort[0].key == self.options.columns[i].id;
that.sort(
self.options.columns[i].id, isSelected ?
(self.options.sort[0].operator === "" ? "-" : "") :
self.options.columns[i].operator
);
}
function constructItem(data) {
var $item = $("<div>")
.css({
width: Math.max(Ox.sum(self.columnWidths), that.$element.width() - oxui.scrollbarSize) + "px"
});
$.each(self.visibleColumns, function(i, v) {
var $cell = $("<div>")
.addClass("OxCell OxColumn" + Ox.toTitleCase(v.id))
.css({
width: (self.columnWidths[i] - 9) + "px",
textAlign: v.align
})
.html(!$.isEmptyObject(data) ? data[v.id] : "")
.appendTo($item);
});
return $item;
}
function dragColumn(id, e) {
var startX = e.clientX,
startPos = getColumnPositionById(id),
pos = startPos,
stopPos = startPos,
positions = $.map(self.visibleColumns, function(v, i) {
return self.columnPositions[i] - self.columnPositions[startPos]
});
$(".OxColumn" + Ox.toTitleCase(id)).css({
opacity: 0.1
});
that.$titles[startPos].addClass("OxDrag").css({ // fixme: why does the class not work?
cursor: "move"
});
Ox.print("positions", positions)
$window.mousemove(function(e) {
var d = e.clientX - startX;
$.each(positions, function(i, v) {
if (d < 0 && d < v) {
stopPos = i;
return false;
} else if (d > 0 && d > v) {
stopPos = i;
}
});
if (stopPos != pos) {
pos = stopPos;
moveColumn(id, pos);
}
});
$window.mouseup(function() {
dropColumn(id, pos);
$window.unbind("mousemove");
$window.unbind("mouseup");
});
}
function dropColumn(id, pos) {
Ox.print("dropColumn", id, pos)
var startPos = getColumnPositionById(id),
stopPos = pos,
$title = that.$titles.splice(startPos, 1)[0],
column = self.visibleColumns.splice(startPos, 1)[0],
width = self.columnWidths.splice(startPos, 1)[0];
that.$titles.splice(stopPos, 0, $title);
self.visibleColumns.splice(stopPos, 0, column);
self.columnWidths.splice(stopPos, 0, width);
Ox.print("s.vC", self.visibleColumns)
$(".OxColumn" + Ox.toTitleCase(id)).css({
opacity: 1
});
that.$titles[stopPos].removeClass("OxDrag").css({
cursor: "pointer"
});
}
function getColumnIndexById(id) {
var pos = -1;
$.each(self.options.columns, function(i, v) {
if (v.id == id) {
pos = i;
return false;
}
});
return pos;
}
function getColumnPositionById(id) {
var pos = -1;
$.each(self.visibleColumns, function(i, v) {
if (v.id == id) {
pos = i;
return false;
}
});
return pos;
}
function getItemWidth() {
return Ox.sum(self.columnWidths)
return Math.max(Ox.sum(self.columnWidths), that.$element.width() - oxui.scrollbarSize);
}
function moveColumn(id, pos) {
Ox.print("moveColumn", id, pos)
var startPos = getColumnPositionById(id),
stopPos = pos,
startClassName = ".OxColumn" + Ox.toTitleCase(id),
stopClassName = ".OxColumn" + Ox.toTitleCase(self.visibleColumns[stopPos].id),
$column = $(".OxTitle" + startClassName),
$order = $column.next(),
$resize = $order.next();
$column.detach().insertBefore($(".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().insertBefore($v.children(stopClassName));
});
}
function removeColumn(id) {
}
function resize() {
}
function resizeColumn(id, width) {
var i = getColumnIndexById(id),
pos = getColumnPositionById(id);
self.columnWidths[pos] = width;
that.$head.$content.css({
width: (Ox.sum(self.columnWidths) + 2) + "px"
});
that.$titles[pos].css({
width: (width - 9 - (i == self.selectedColumn ? 16 : 0)) + "px"
});
that.$body.$content.find(".OxItem").css({ // fixme: can we avoid this lookup?
width: getItemWidth() + "px"
});
that.$body.$content.css({
width: getItemWidth() + "px" // fixme: check if scrollbar visible, and listen to resize/toggle event
});
$(".OxCell.OxColumn" + Ox.toTitleCase(self.options.columns[i].id)).css({
width: (width - 9) + "px"
});
that.$body.clearCache();
}
function toggleSelected(id) {
var pos = getColumnPositionById(id);
if (pos > -1) {
updateOrder(id);
pos > 0 && that.$titles[pos].prev().children().eq(2).toggleClass("OxSelected");
that.$titles[pos].toggleClass("OxSelected");
that.$titles[pos].next().toggleClass("OxSelected");
that.$titles[pos].next().next().children().eq(0).toggleClass("OxSelected");
that.$titles[pos].css({
width: (
that.$titles[pos].width() + (that.$titles[pos].hasClass("OxSelected") ? -16 : 16)
) + "px"
});
}
}
function updateOrder(id) {
var pos = getColumnPositionById(id);
Ox.print(id, pos)
that.$titles[pos].next().html(oxui.symbols[
"triangle_" + (self.options.sort[0].operator === "" ? "up" : "down")
]);
}
self.onChange = function(key, value) {
if (key == "request") {
that.$body.options(key, value);
}
};
that.closePreview = function() {
self.preview = false;
};
that.resizeColumn = function(id, width) {
resizeColumn(id, width);
return that;
}
that.sort = function(key, operator) {
var isSelected = key == self.options.sort[0].key;
self.options.sort = [
{
key: key,
operator: operator
}
];
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.sort(self.options.sort[0].key, self.options.sort[0].operator);
return that;
};
return that;
};
/*
============================================================================
Maps
============================================================================
*/
Ox.Map = function(options, self) {
var self = self || {}
that = new Ox.Element("div", self)
.defaults({
places: [],
type: "satellite"
})
.options(options || {});
init();
function canContain(outerBounds, innerBounds) {
var outerSpan = outerBounds.toSpan(),
innerSpan = innerBounds.toSpan();
return outerSpan.lat() > innerSpan.lat() &&
outerSpan.lng() > innerSpan.lng();
}
function click(event) {
Ox.print("event", event);
getLocationByLatLng(event.latLng, self.map.getBounds(), function(location) {
self.marker && self.marker.remove();
self.polygon && self.polygon.remove();
if (location) {
self.marker = location.marker.add();
self.polygon = location.polygon.add();
}
})
}
function getLocationByLatLng(latlng, bounds, callback) {
Ox.print("ll b", latlng, bounds)
var callback = arguments.length == 3 ? callback : bounds,
bounds = arguments.length == 3 ? bounds : null;
self.geocoder.geocode({
latLng: latlng
}, function(results, status) {
Ox.print("results", results)
var length = results.length;
if (status == google.maps.GeocoderStatus.OK) {
if (status != google.maps.GeocoderStatus.ZERO_RESULTS) {
if (bounds) {
$.each(results.reverse(), function(i, result) {
if (
i == length - 1 ||
canContain(bounds, result.geometry.bounds || result.geometry.viewport)
) {
callback(new Location(results[i]));
return false;
}
});
} else {
callback(new Location(results[0]));
}
} else {
callback(null);
}
} else {
Ox.print("geocode failed:", status);
callback(null);
}
});
}
function getLocationByName(name, callback) {
self.geocoder.geocode({
address: name
}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (status != google.maps.GeocoderStatus.ZERO_RESULTS) {
callback(new Location(results[0]))
} else {
callback(null);
}
} else {
Ox.print("geocode failed:", status);
callback(null);
}
});
}
function init() {
var counter = 0,
length = self.options.places.length;
$.extend(self, {
geocoder: new google.maps.Geocoder(),
locations: [],
selectedMarker: null
});
$.each(self.options.places, function(i, place) {
if (Ox.isString(place)) {
self.options.places[i] = {
name: place
};
} else if (Ox.isArray(place)) {
self.options.places[i] = {
name: "",
point: place
};
}
"point" in self.options.places[i] ?
getLocationByPoint(self.options.places[i].point, callback) :
getLocationByName(self.options.places[i].name, callback);
});
function callback(location) {
if (location) {
Ox.print("counter", counter, location)
self.locations.push(location);
self.bounds = counter == 0 ? location.rectangle.bounds :
self.bounds.union(location.rectangle.bounds);
}
if (counter++ == length - 1) {
loadMap();
}
}
}
function loadMap() {
Ox.print("loadMap");
$.extend(self, {
map: new google.maps.Map(that.$element[0], {
center: self.bounds.getCenter(),
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId[self.options.type.toUpperCase()],
zoom: 0
})
});
self.map.fitBounds(self.bounds)
google.maps.event.addListener(self.map, "click", click);
google.maps.event.addListener(self.map, "zoom_changed", zoom);
$.each(self.locations, function(i, location) {
location.marker.add();
});
};
function resize() {
google.maps.event.trigger(self.map, "resize");
}
function zoom() {
}
function Location(geocode) {
Ox.print("geocode", geocode);
var bounds = geocode.geometry.bounds || geocode.geometry.viewport,
area = [
[bounds.getSouthWest().lat(), bounds.getSouthWest().lng()],
[bounds.getNorthEast().lat(), bounds.getNorthEast().lng()]
],
location = {
geocode: geocode,
name: {
formatted: geocode.formatted_address,
long: $.map(geocode.address_components, function(v) {
return v.long_name;
}).join(", ")
},
rectangle: new Rectangle(area),
};
Ox.print("area", area)
return $.extend(location, {
marker: new Marker(location),
polygon: new Polygon(location)
});
}
function Marker(location) {
var listeners = {},
marker = new google.maps.Marker({
icon: icon("red"),
position: location.rectangle.center,
title: location.name.formatted
}),
selected = false;
function click() {
selected = !selected;
marker.setOptions({
icon: icon(selected ? "blue" : "red")
});
location.polygon[selected ? "add" : "remove"]();
}
function dblclick() {
click();
self.map.fitBounds(location.rectangle.bounds);
}
function icon(color) {
return "http://dev.pan.do:8000" + oxui.path + "png/ox.ui/marker" + Ox.toTitleCase(color) + ".png"
}
return {
add: function() {
marker.setMap(self.map);
listeners = {
click: google.maps.event.addListener(marker, "click", click),
dblclick: google.maps.event.addListener(marker, "dblclick", dblclick),
};
return this;
},
deselect: function() {
},
remove: function() {
marker.setMap(null);
$.each(listeners, function(i, listener) {
google.maps.event.removeListener(listener);
});
return this;
},
select: function() {
}
};
}
function Polygon(location) {
var listeners = {},
points = [
location.rectangle.latlng.sw,
location.rectangle.latlng.nw,
location.rectangle.latlng.ne,
location.rectangle.latlng.se,
location.rectangle.latlng.sw
],
polygon = new google.maps.Polygon({
paths: points
}),
selected = false;
setOptions();
function click() {
selected = !selected;
setOptions();
}
function setOptions() {
var color = selected ? "#8080FF" : "#FFFFFF";
polygon.setOptions({
clickable: true,
fillColor: color,
fillOpacity: selected ? 0.1 : 0,
strokeColor: color,
strokeOpacity: 1,
strokeWeight: 2
});
}
return {
add: function() {
polygon.setMap(self.map);
listeners.click = google.maps.event.addListener(polygon, "click", click);
return this;
},
deselect: function() {
selected = false;
setOptions();
},
remove: function() {
polygon.setMap(null);
google.maps.event.removeListener(listeners.click);
return this;
},
select: function() {
selected = true;
setOptions();
}
};
}
function Rectangle(area) {
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),
mw: new google.maps.LatLng(lat.mc, lng.ne),
nw: new google.maps.LatLng(lat.ne, lng.sw),
nc: new google.maps.LatLng(lat.ne, lng.mc),
});
return {
area: area,
bounds: bounds,
canContain: function(rectangle) {
var outerSpan = this.bounds.toSpan(),
innerSpan = rectangle.bounds.toSpan();
return outerSpan.lat() > innerSpan.lat() &&
outerSpan.lng() > innerSpan.lng();
},
center: latlng.mc,
contains: function(rectangle) {
return this.bounds.contains(rectangle.bounds.getSouthWest()) &&
this.bounds.contains(rectangle.bounds.getNorthEast());
},
latlng: latlng
};
}
self.onChange = function(key, value) {
if (key == "type") {
}
};
return that;
};
Ox.MapImage = function(options, self) {
/*
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)
*/
var self = self || {},
that = new Ox.Element("img", self)
.defaults({
height: 360,
markerColorHighlight: "yellow",
markerColorNormal: "blue",
places: [],
type: "satellite",
width: 640
})
.options(options || {})
$.extend(self, {
markers: {
highlight: [],
normal: []
},
src: "http://maps.google.com/maps/api/staticmap?sensor=false" +
"&size=" + self.options.width + "x" + self.options.height +
"&maptype=" + self.options.type
});
if (self.options.places.length) {
$.each(self.options.places, function(i, place) {
if (Ox.isString(place)) {
self.markers.normal.push(place);
} else if (Ox.isArray(place)) {
self.markers.normal.push(place.join(","));
} else {
self.markers[place.highlight ? "highlight" : "normal"]
.push("point" in place ? place.point.join(",") : place.name)
}
});
$.each(self.markers, function(k, markers) {
if (markers.length) {
self.src += "&markers=icon:" + "http://dev.pan.do:8000" + oxui.path + "png/ox.ui/marker" +
Ox.toTitleCase(self.options["markerColor" + Ox.toTitleCase(k)]) + ".png|" +
markers.join("|")
}
});
} else {
self.src += "&center=0,0&zoom=2"
}
that.attr({
src: self.src
});
self.onChange = function(key, value) {
};
return that;
};
/*
============================================================================
Menus
============================================================================
*/
Ox.MainMenu = function(options, self) {
/* options:
* extras
* menus
* size
*/
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
}));
that.bindEvent("hide_" + that.menus[position].options("id"), 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 getMenuById(id) {
var menu = null;
$.each(that.menus, function(i, v) {
if (v.options("id") == id) {
menu = v;
return false;
}
});
return menu;
}
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) {
that.getItem(id).options({
checked: true
});
};
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 = getMenuById(ids.shift()).getItem(ids.join("_"));
}
return item;
};
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;
};
Ox.Menu = function(options, self) {
/*
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"
methods:
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 not needed, not implemented
*/
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),
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?
self.keyboardEvents = {
key_up: selectPreviousItem,
key_down: selectNextItem,
key_left: selectSupermenu,
key_right: selectSubmenu,
key_escape: hideMenu,
key_enter: clickSelectedItem
};
// 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" : "OxLayer");
function click(event) {
var item,
position,
$target = $(event.target);
if ($target.is(".OxCell")) {
position = $target.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];
if (!item.options("items").length) {
if (that.options("parent")) {
Ox.print("t.o.p", that.options("parent"))
that.options("parent").hideMenu().triggerEvent("click");
}
if (item.options("checked") !== null && (!item.options("group") || !item.options("checked"))) {
item.options({
checked: !item.options("checked")
});
Ox.Event.trigger("change_" + item.options("group"), {
id: item.options("id"),
value: item.options("title")[0] // fixme: value or title?
});
} else {
Ox.Event.trigger("click_" + self.options.id, {
id: item.options("id"),
title: item.options("title")[0]
});
Ox.Event.trigger("click_" + item.options("id"));
}
if (item.options("title").length == 2) {
item.toggleTitle();
}
}
that.hideMenu();
}
function clickSelectedItem() {
// called on key.enter
if (self.options.selected > -1) {
clickItem(self.options.selected);
} else {
that.hideMenu();
}
}
function constructItems(items) {
that.items = [];
that.$content.empty();
scrollMenuUp();
$.each(items, function(i, item) {
var position;
if (item.id) {
that.items.push(new Ox.MenuItem($.extend(item, {
menu: that,
position: position = that.items.length
})).data("position", position).appendTo(that.$content)); // fixme: jquery bug when passing {position: position}? does not return the object?;
if (item.items) {
that.submenus[item.id] = new Ox.Menu({
element: that.items[position],
id: Ox.toCamelCase(self.options.id + "/" + item.id),
items: item.items,
mainmenu: self.options.mainmenu,
offset: {
left: 0,
top: -4
},
parent: that,
side: "right",
size: self.options.size,
});
}
} else {
that.$content.append(constructSpace());
that.$content.append(constructLine());
that.$content.append(constructSpace());
}
});
if (!that.is(":hidden")) {
that.hideMenu();
that.showMenu();
}
}
function constructLine() {
return $("<tr>").append(
$("<td>", {
"class": "OxLine",
colspan: 5
})
);
}
function constructScrollbar(direction) {
var interval,
speed = direction == "up" ? -1 : 1;
return $("<div/>", {
"class": "OxScrollbar Ox" + Ox.toTitleCase(direction),
html: oxui.symbols["triangle_" + direction],
click: function() { // fixme: do we need to listen to click event?
return false;
},
mousedown: function() {
scrollSpeed = 2;
return false;
},
mouseenter: function() {
var $otherScrollbar = that.$scrollbars[direction == "up" ? "down" : "up"];
$(this).addClass("OxSelected");
if ($otherScrollbar.is(":hidden")) {
$otherScrollbar.show();
that.$container.height(that.$container.height() - itemHeight);
if (direction == "down") {
that.$content.css({
top: -itemHeight + "px"
});
}
}
scrollMenu(speed);
interval = setInterval(function() {
scrollMenu(speed);
}, 100);
},
mouseleave: function() {
$(this).removeClass("OxSelected");
clearInterval(interval);
},
mouseup: function() {
scrollSpeed = 1;
return false;
}
});
}
function constructSpace() {
return $("<tr>").append(
$("<td>", {
"class": "OxSpace",
colspan: 5
})
);
}
function getElement(id) {
// fixme: needed?
return $("#" + Ox.toCamelCase(options.id + "/" + id));
}
function 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);
if ($target.is(".OxCell")) {
position = $target.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) {
item = that.items[self.options.selected]
item.removeClass("OxSelected");
}
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");
}
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")];
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) {
that.items[self.options.selected].trigger("mouseleave");
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") {
selectItem(value);
}
}
that.addItem = function(item, position) {
};
that.addItemAfter = function(item, id) {
};
that.addItemBefore = function(item, id) {
};
that.checkItem = function(id) {
that.getItem(id).options({
checked: true
});
};
that.getItem = function(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.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();
//that.$scrollbars.down.hide();
if (self.options.parent) {
self.options.element.removeClass("OxSelected");
}
that.hide()
.loseFocus()
.unbindEvent(self.keyboardEvents)
.triggerEvent("hide");
that.$layer.hide();
$document.unbind("click", click);
return that;
//that.triggerEvent("hide");
};
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 || 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 = offset.left + self.options.offset.left + (self.options.side == "bottom" ? 0 : 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);
}
!self.options.parent && that.gainFocus();
that.bindEvent(self.keyboardEvents);
setTimeout(function() {
$document.bind("click", click);
}, 100);
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: [],
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.makeArray(options.title || self.defaults.title)
}))
.addClass("OxItem" + (self.options.disabled ? " OxDisabled" : ""))
.attr({
id: Ox.toCamelCase(self.options.menu.options("id") + "/" + self.options.id)
})
.data("group", self.options.group); // fixme: why?
// 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") {
if (value && self.options.group) {
$.each(self.options.menu.items, function(i, item) {
if (
item.options("id") != self.options.id &&
item.options("group") == self.options.group &&
item.options("checked")
) {
item.options({
checked: false
});
return false;
}
});
}
that.$status.html(value ? oxui.symbols.check : "")
} else if (key == "disabled") {
that.toggleClass("OxDisabled"); // fixme: this will only work if onChange is only invoked on actual change
} else if (key == "title") {
self.options.title = Ox.makeArray(value);
that.$title.html(self.options.title[0]);
}
}
that.toggle = function() {
// toggle id and title
};
that.toggleChecked = function() {
};
that.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
----------------------------------------------------------------------------
*/
Ox.CollapsePanel = function(options, self) {
var self = self || {},
that = new Ox.Panel({}, self)
.defaults({
collapsed: false,
size: 20,
title: ""
})
.options(options)
.addClass("OxCollapsePanel"),
value = self.options.collapsed ?
["expand", "collapsed"] : ["collapse", "expand"],
$titlebar = new Ox.Bar({
orientation: "horizontal",
size: self.options.size,
})
.dblclick(dblclickTitlebar)
.appendTo(that),
$switch = new Ox.Button({
style: "symbol",
type: "image",
value: value,
})
.click(toggleCollapsed)
.appendTo($titlebar),
$title = new Ox.Element()
.addClass("OxTitle")
.html(self.options.title/*.toUpperCase()*/)
.appendTo($titlebar);
that.$content = new Ox.Element()
.addClass("OxContent")
.appendTo(that);
// fixme: doesn't work, content still empty
// need to hide it if collapsed
if (self.options.collapsed) {
that.$content.css({
marginTop: -that.$content.height() + "px"
});
}
function dblclickTitlebar(e) {
if (!$(e.target).hasClass("OxButton")) {
toggleCollapsed();
}
}
function toggleCollapsed() {
that.options({
collapsed: !self.options.collapsed
});
var top = self.options.collapsed ?
-that.$content.height() : 0;
that.$content.animate({
marginTop: top + "px"
}, 200);
}
self.onChange = function(option, value) {
if (option == "collapsed") {
$switch.options({
value: value ? "expand" : "collapse"
});
} else if (option == "title") {
$title.html(self.options.title);
}
};
return that;
};
/*
----------------------------------------------------------------------------
Ox.Panel
----------------------------------------------------------------------------
*/
Ox.Panel = function(options, self) {
var self = self || {},
that = new Ox.Element({}, self)
.addClass("OxPanel");
return that;
};
/*
----------------------------------------------------------------------------
Ox.SplitPanel
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"
methods:
isCollapsed(id) element is collapsed or not
resize(id, size) resize element to size px
toggle(id) collapse or expand element
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
});
that.$elements = [];
$.each(self.options.elements, function(i, v) {
self.options.elements[i] = $.extend({
collapsible: false,
collapsed: false,
resizable: false,
resize: [],
size: "auto"
}, v);
that.$elements[i] = v.element
.css(self.edges[2], 0)
.css(self.edges[3], 0);
});
setSizes();
$.each(self.options.elements, function(i, v) {
//that.append(element)
Ox.print("V: ", v)
that.$elements[i].appendTo(that); // fixme: that.$content
if (v.collapsible || v.resizable) {
Ox.print("v.size", v.size)
var $resizebar = new Ox.Resizebar({
collapsible: v.collapsible,
edge: self.edges[i == 0 ? 0 : 1],
elements: i < 2 ?
[that.$elements[0], that.$elements[1]] :
[that.$elements[1], that.$elements[2]],
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
})
.css(self.edges[i == 0 ? 0 : 1], v.size);
$resizebar[i == 0 ? "insertAfter" : "insertBefore"](that.$elements[i]);
}
});
function getPositionById(id) {
var position = -1;
$.each(self.options.elements, function(i, element) {
if (element.element.options("id") == id) {
position = i;
return false;
}
});
return position;
}
function getSize(element) {
return element.size + element.resizable;
}
function setSizes() {
$.each(self.options.elements, function(i, v) {
v.size != "auto" && that.$elements[i].css(self.dimensions[0], v.size + "px");
if (i == 0) {
that.$elements[i].css(self.edges[0], 0);
v.size != "auto" && 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) {
self.options.elements[0].size != "auto" && that.$elements[i].css(
self.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[1], 0);
v.size != "auto" && that.$elements[i].css(
self.edges[0], (getSize(self.options.elements[0]) + getSize(self.options.elements[1])) + "px"
);
}
});
}
that.isCollapsed = function(id) {
return self.options.elements[getPositionById(id)].collapsed;
};
that.resize = function(id, size) {
// one can pass pos instead of id
var pos = Ox.isNumber(id) ? id : getPositionById(id);
// Ox.print("pos", pos, self.options.elements, $.map(self.options.elements, function(v, i) { return v.element.options("id"); }))
self.options.elements[pos].size = size;
setSizes();
return that;
};
that.toggle = function(id) {
Ox.print("toggle", id);
/*
// something like this is needed to load in collapsed state
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);
*/
var pos = getPositionById(id),
size = self.options.elements[pos].collapsed ? 0 : self.options.elements[pos].size,
animate = {};
animate[self.edges[pos == 0 ? 0 : 1]] = size;
self.options.parent.animate(animate, 200, function() {
var i = (self.options.edge == "left" || self.options.edge == "top") ? 1 : 0;
Ox.Event.trigger("resize_" + id, self.options.elements[i][self.dimensions[1]]());
self.options.elements[pos].collapsed = !self.options.elements[pos].collapsed;
});
};
return that;
};
Ox.TabPanel = function(options, self) {
};
/*
============================================================================
Requests
============================================================================
*/
/*
----------------------------------------------------------------------------
Ox.LoadingIcon
----------------------------------------------------------------------------
*/
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);
};
that.stop = function() {
that.animate({
opacity: 0
}, 250, function() {
!self.isRunning && clear();
self.isRunning = false;
});
}
return that;
}
/*
----------------------------------------------------------------------------
Ox.Progressbar
----------------------------------------------------------------------------
*/
/*
============================================================================
Miscellaneous
============================================================================
*/
/*
----------------------------------------------------------------------------
Ox.Tooltip
----------------------------------------------------------------------------
*/
Ox.Tooltip = function(options, self) {
var self = self || {},
that = new Ox.Element()
.defaults({
text: ""
})
.options(options || {})
.addClass("OxTooltip")
.html(self.options.text);
self.onChange = function(key, value) {
if (key == "text") {
that.html(value);
}
};
that.hide = function() {
that.animate({
opacity: 0
}, 250, function() {
that.remove();
});
return that;
};
that.show = function(e) {
var left, top, width, height;
that.appendTo($body);
width = that.width();
height = that.height();
left = Ox.limit(e.clientX - width / 2, 0, $document.width() - width);
top = e.clientY > $document.height() - height - 16 ? e.clientY - 32 : e.clientY + 16;
that.css({
left: left + "px",
top: top + "px"
})
.animate({
opacity: 1
}, 250);
return that;
};
return that;
};
})();