remove unneeded Ox. prefix from path and file names

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

82
source/UI/js/Core/API.js Normal file
View file

@ -0,0 +1,82 @@
'use strict';
/*@
Ox.API <f> Remote API controller
options <o> Options object
timeout <n|60000> request timeout
url <s> request url
callback <f> called once api discover is done
([options, ] callback) -> <o> API controller
api <f> Remote API discovery (calls the API's `api` method)
(callback) -> <n> Request id
callback <f> Callback functions
.* <f> Remote API method call
([data, [age, ]]callback) -> <n> Request id
data <o> Request data
age <n|-1> Max-age in ms (0: not from cache, -1: from cache)
callback <f> Callback function
cancel <f> Cancels a request
(id) -> <u> undefined
id <n> Request id
@*/
Ox.API = function(options, callback) {
var self = {
options: Ox.extend({
timeout: 60000,
type: 'POST',
url: '/api/'
}, options || {}),
time: new Date()
},
that = {
api: function(callback) {
return Ox.Request.send({
url: self.options.url,
data: {action: 'api'},
callback: callback
});
},
cancel: function(id) {
Ox.Request.cancel(id);
}
};
$.ajaxSetup({
timeout: self.options.timeout,
type: self.options.type,
url: self.options.url
});
that.api(function(result) {
Ox.forEach(result.data.actions, function(val, key) {
that[key] = function(/*data, age, callback*/) {
var data = {}, age = -1, callback = null;
Ox.forEach(arguments, function(argument) {
var type = Ox.typeOf(argument);
if (type == 'object') {
data = argument;
} else if (type == 'number') {
age = argument;
} else if (type == 'function') {
callback = argument;
}
});
return Ox.Request.send(Ox.extend({
age: age,
callback: callback,
data: {
action: key,
data: JSON.stringify(data)
},
url: self.options.url
}, !val.cache ? {age: 0} : {}));
};
});
callback && callback(that);
});
return that;
};

107
source/UI/js/Core/App.js Normal file
View file

@ -0,0 +1,107 @@
'use strict';
/*@
Ox.App <f> Basic application instance that communicates with a JSON API
options <o> Options object
name <s> App name
timeout <n> Request timeout
type <s> HTTP Request type, i.e. 'GET' or 'POST'
url <s> JSON API URL
([options]) -> <o> App object
load <!> App loaded
@*/
Ox.App = function(options) {
var self = {
options: Ox.extend({
name: 'App',
socket: '',
timeout: 60000,
type: 'POST',
url: '/api/'
}, options || {}),
time: new Date()
},
that = Ox.Element({}, Ox.extend({}, self));
//@ api <o> API endpoint
that.api = Ox.API({
type: self.options.type,
timeout: self.options.timeout,
url: self.options.url
}, function() {
that.api.init(getUserData(), function(result) {
that.triggerEvent({load: result.data});
});
});
self.options.socket && connectSocket();
//@ localStorage <f> Ox.localStorage instance
that.localStorage = Ox.localStorage(self.options.name);
function connectSocket() {
that.socket = new WebSocket(self.options.socket);
that.socket.onopen = function(event) {
that.triggerEvent('open', event);
};
that.socket.onmessage = function(event) {
var data = JSON.parse(event.data);
that.triggerEvent(data[0], data[1]);
};
that.socket.onerror = function(event) {
that.triggerEvent('error', event);
that.socket.close();
};
that.socket.onclose = function(event) {
that.triggerEvent('close', event);
setTimeout(connectSocket, 1000);
};
}
function getUserData() {
return {
document: {referrer: document.referrer},
history: {length: history.length},
location: {href: location.href},
navigator: {
cookieEnabled: navigator.cookieEnabled,
plugins: Ox.slice(navigator.plugins).map(function(plugin) {
return plugin.name;
}),
userAgent: navigator.userAgent
},
screen: screen,
time: (+new Date() - self.time) / 1000,
window: {
innerHeight: window.innerHeight,
innerWidth: window.innerWidth,
outerHeight: window.outerHeight,
outerWidth: window.outerWidth,
screenLeft: window.screenLeft,
screenTop: window.screenTop
}
};
}
function update() {
// ...
}
/*@
options <f> Gets or sets options (see Ox.getset)
() -> <o> All options
(key) -> <*> The value of option[key]
(key, value) -> <o> Sets one option, returns App object
({key: value, ...}) -> <o> Sets multiple options, returns App object
key <s> The name of the option
value <*> The value of the option
@*/
that.options = function() {
return Ox.getset(self.options, arguments, update, that);
};
return that;
};

View file

@ -0,0 +1,70 @@
'use strict';
/*@
Ox.Clipboard <o> Basic clipboard handler
add <f> Add items to clipboard
(items[, type]) -> <n> Number of items
clear <f> Clear clipboard
() -> <n> Number of items
copy <f> Copy items to clipboard
(items[, type]) -> <n> Number of items
paste <f> Paste items from clipboard
() -> <a> Items
type <f> Get item type
() -> <s|undefined> Item type
@*/
Ox.Clipboard = function() {
var clipboard = {items: [], type: void 0},
$element;
return {
_print: function() {
Ox.print(JSON.stringify(clipboard));
},
add: function(items, type) {
items = Ox.makeArray(items);
if (items.length) {
if (type != clipboard.type) {
Ox.Clipboard.clear();
}
clipboard = {
items: Ox.unique(clipboard.items.concat(items)),
type: type
};
$element && $element.triggerEvent('add');
}
return clipboard.items.length;
},
bindEvent: function() {
if (!$element) {
$element = Ox.Element();
}
$element.bindEvent.apply($element, arguments);
},
clear: function() {
clipboard = {items: [], type: void 0};
$element && $element.triggerEvent('clear');
return clipboard.items.length;
},
copy: function(items, type) {
items = Ox.makeArray(items);
if (items.length) {
clipboard = {items: items, type: type};
$element && $element.triggerEvent('copy');
}
return clipboard.items.length;
},
items: function(type) {
return !type || type == clipboard.type ? clipboard.items.length : 0;
},
paste: function(type) {
$element && $element.triggerEvent('paste');
return !type || type == clipboard.type ? clipboard.items : [];
},
type: function() {
return clipboard.type;
},
unbindEvent: function() {
$element && $element.unbindEvent.apply($element, arguments);
}
};
};

View file

@ -0,0 +1,26 @@
'use strict';
// 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 <f> Container element
options <o> Options object
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Container object
@*/
Ox.Container = function(options, self) {
var that = Ox.Element({}, self)
.options(options || {})
.addClass('OxContainer');
// fixme: we used to pass self _again_ to the content,
// which obviously makes clicks trigger twice
// removed for now, but may break something else.
// (maybe, if needed, content can pass a container event along)
that.$content = Ox.Element({})
.options(options || {})
.addClass('OxContent')
.appendTo(that);
return that;
};

View file

@ -0,0 +1,20 @@
Ox.Cookies = function() {
var name, value, cookies;
if (arguments.length == 1) {
name = arguments[0];
return Ox.Cookies()[name];
} else if (arguments.length == 2) {
name = arguments[0];
value = arguments[1];
document.cookie = name + '=' + encodeURIComponent(value);
} else {
value = {}
if (document.cookie && document.cookie != '') {
document.cookie.split('; ').forEach(function(cookie) {
name = cookie.split('=')[0];
value[name] = Ox.decodeURIComponent(cookie.substring(name.length + 1));
});
}
return value;
}
}

View file

@ -0,0 +1,843 @@
'use strict';
(function(_) {
/*@
Ox.Element <f> Basic UI element object
# Arguments -----------------------------------------------------------
options <o|s> Options of the element, or just the `element` option
element <s> Tagname or CSS selector
tooltip <s|f> Tooltip title, or a function that returns one
(e) -> <s> Tooltip title
e <o> Mouse event
self <o> Shared private variable
# Usage ---------------------------------------------------------------
([options[, self]]) -> <o> Element object
# Events ----------------------------------------------------------
anyclick <!> anyclick
Fires on mouseup, but not on any subsequent mouseup within 250
ms (this is useful if one wants to listen for singleclicks, but
not doubleclicks, since it will fire immediately, and won't
fire again in case of a doubleclick)
* <*> Original event properties
doubleclick <!> doubleclick
Fires on the second mousedown within 250 ms (this is useful if
one wants to listen for both singleclicks and doubleclicks,
since it will not trigger a singleclick event)
* <*> Original event properties
drag <!> drag
Fires on mousemove after dragstart, stops firing on mouseup
clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px
* <*> Original event properties
dragend <!> dragpause
Fires on mouseup after dragstart
clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px
* <*> Original event properties
dragenter <!> dragenter
Fires when entering an element during drag (this fires on the
element being dragged -- the target element is the event's
target property)
clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px
* <*> Original event properties
dragleave <!> dragleave
Fires when leaving an element during drag (this fires on the
element being dragged -- the target element is the event's
target property)
clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px
* <*> Original event properties
dragpause <!> dragpause
Fires once when the mouse doesn't move for 250 ms during drag
(this is useful in order to execute operations that are too
expensive to be attached to the drag event)
clientDX <n> Horizontal drag delta in px
clientDY <n> Vertical drag delta in px
* <*> Original event properties
dragstart <!> dragstart
Fires when the mouse is down for 250 ms
* <*> Original event properties
mousedown <!> mousedown
Fires on mousedown (this is useful if one wants to listen for
singleclicks, but not doubleclicks or drag events, and wants
the event to fire as early as possible)
* <*> Original event properties
mouserepeat <!> mouserepeat
Fires every 50 ms after the mouse was down for 250 ms, stops
firing on mouseleave or mouseup (this fires like a key that is
being pressed and held, and is useful for buttons like
scrollbar arrows that need to react to both clicking and
holding)
mousewheel <!> mousewheel
Fires on mousewheel scroll or trackpad swipe
deltaFactor <n> Original delta = normalized delta * delta factor
deltaX <n> Normalized horizontal scroll delta in px
deltaY <n> Normalized vertical scroll delta in px
* <*> Original event properties
singleclick <!> singleclick
Fires 250 ms after mouseup, if there was no subsequent
mousedown (this is useful if one wants to listen for both
singleclicks and doubleclicks, since it will not fire for
doubleclicks)
* <*> Original event properties
*/
Ox.Element = function Element(options, self) {
// create private object
self = self || {};
self.boundTooltipEvents = {}; // FIXME?
self.defaults = {};
self.eventCallbacks = self.eventCallbacks || {};
// allow for Ox.Element('<tagname>') or Ox.Element('cssSelector')
self.options = Ox.isString(options) ? {element: options} : options || {};
self.unbindKeyboard = function unbindKeyboard() {
Object.keys(self.eventCallbacks).filter(function(event) {
return /^key([\._][\w\.]+)?$/.test(event);
}).forEach(function(event) {
that.unbindEvent(event);
});
};
self.update = function update(key, value) {
// update is called whenever an option is modified or added
Ox.loop(self.updateCallbacks.length - 1, -1, -1, function(index) {
// break if the callback returns false
return self.updateCallbacks[index](key, value) !== false;
});
};
self.updateCallbacks = self.updateCallbacks || [];
// create public object
var that = Object.create(Ox.Element.prototype);
that.oxid = Ox.uid();
that.$element = $(self.options.element || '<div>')
.addClass('OxElement')
.data({oxid: that.oxid})
.on({
mousedown: onMousedown,
mousewheel: onMousewheel
});
that[0] = that.$element[0];
that.length = 1;
that.self = function _self() {
return arguments[0] === _ ? self : {};
};
Ox.$elements[that.oxid] = that;
if (self.options.element == '<iframe>') {
self.messageCallbacks = self.messageCallbacks || {};
that.on({
load: function init() {
if (that.attr('src')) {
// send oxid to iframe
that.postMessage({init: {oxid: that.oxid}});
self.initTime = self.initTime || new Date();
if (new Date() < self.initTime + 60000) {
self.initTimeout = setTimeout(init, 250);
}
}
}
}).bindEvent({
init: function() {
// iframe has received oxid
clearTimeout(self.initTimeout);
}
});
}
setTooltip();
function bindTooltipEvents(events) {
that.off(Ox.filter(self.boundTooltipEvents, function(value, key) {
return !events[key];
})).on(self.boundTooltipEvents = Ox.filter(events, function(value, key) {
return !self.boundTooltipEvents[key];
}));
}
function onMousedown(e) {
/*
better mouse events
mousedown:
trigger mousedown
within 250 msec:
mouseup: trigger anyclick
mouseup + mousedown: trigger doubleclick
after 250 msec:
mouseup + no mousedown within 250 msec: trigger singleclick
no mouseup within 250 msec:
trigger mouserepeat every 50 msec
trigger dragstart
mousemove: trigger drag
no mousemove for 250 msec:
trigger dragpause
mouseup: trigger dragend
"anyclick" is not called "click" since this would collide with the click
events of some widgets
*/
var clientX, clientY,
dragTimeout = 0,
mouseInterval = 0;
that.triggerEvent('mousedown', e);
if (!self._mouseTimeout) {
// first mousedown
self._drag = false;
self._mouseup = false;
self._mouseTimeout = setTimeout(function() {
// 250 ms later, no subsequent click
self._mouseTimeout = 0;
if (self._mouseup) {
// mouse went up, trigger singleclick
that.triggerEvent('singleclick', e);
} else {
// mouse is still down, trigger mouserepeat
// every 50 ms until mouseleave or mouseup
mouserepeat();
mouseInterval = setInterval(mouserepeat, 50);
that.one('mouseleave', function() {
clearInterval(mouseInterval);
});
// trigger dragstart, set up drag events
that.triggerEvent('dragstart', e);
$('.OxElement').live({
mouseenter: dragenter,
mouseleave: dragleave
});
clientX = e.clientX;
clientY = e.clientY;
Ox.$window
.off('mouseup', mouseup)
.on({mousemove: mousemove})
.one('mouseup', function(e) {
// stop checking for mouserepeat
clearInterval(mouseInterval);
// stop checking for dragpause
clearTimeout(dragTimeout);
// stop checking for drag
Ox.$window.off({mousemove: mousemove});
// stop checking for dragenter and dragleave
$('.OxElement').off({
mouseenter: dragenter,
mouseleave: dragleave
});
// trigger dragend
that.triggerEvent('dragend', extend(e));
});
self._drag = true;
}
}, 250);
} else {
// second mousedown within 250 ms, trigger doubleclick
clearTimeout(self._mouseTimeout);
self._mouseTimeout = 0;
that.triggerEvent('doubleclick', e);
}
Ox.$window.one({mouseup: mouseup});
function dragenter(e) {
that.triggerEvent('dragenter', extend(e));
}
function dragleave(e) {
that.triggerEvent('dragleave', extend(e));
}
function extend(e) {
return Ox.extend({
clientDX: e.clientX - clientX,
clientDY: e.clientY - clientY
}, e);
}
function mousemove(e) {
e = extend(e);
clearTimeout(dragTimeout);
dragTimeout = setTimeout(function() {
// mouse did not move for 250 ms, trigger dragpause
that.triggerEvent('dragpause', e);
}, 250);
that.triggerEvent('drag', e);
}
function mouserepeat(e) {
that.triggerEvent('mouserepeat', e);
}
function mouseup(e) {
if (!self._mouseup && !self._drag) {
// mouse went up for the first time, trigger anyclick
that.triggerEvent('anyclick', e);
self._mouseup = true;
}
}
}
function onMouseenter(e) {
if (!that.$tooltip) {
that.$tooltip = Ox.Tooltip({title: self.options.tooltip});
}
that.$tooltip.show(e);
}
function onMouseleave(e) {
that.$tooltip && that.$tooltip.hide();
}
function onMousemove(e) {
that.$tooltip.options({title: self.options.tooltip(e)}).show(e);
}
function onMousewheel(e) {
// see https://github.com/brandonaaron/jquery-mousewheel/blob/master/jquery.mousewheel.js
e = e.originalEvent;
var absDelta,
deltaX = 'deltaX' in e ? e.deltaX
: 'wheelDeltaX' in e ? -e.wheelDeltaX
: 0,
deltaY = 'deltaY' in e ? -e.deltaY
: 'wheelDeltaY' in e ? e.wheelDeltaY
: 'wheelDelta' in e ? e.wheelDelta
: 0;
// Firefox < 17
if ('axis' in e && e.axis === e.HORIZONTAL_AXIS) {
deltaX = -deltaY;
deltaY = 0;
}
if (deltaX || deltaY) {
absDelta = Math.max(Math.abs(deltaY), Math.abs(deltaX));
if (!self._deltaFactor || self._deltaFactor > absDelta) {
self._deltaFactor = absDelta;
}
that.triggerEvent('mousewheel', Ox.extend(e, {
deltaFactor: self._deltaFactor,
deltaX: Ox.trunc(deltaX / self._deltaFactor),
deltaY: Ox.trunc(deltaY / self._deltaFactor)
}));
clearTimeout(self._deltaTimeout)
self._deltaTimeout = setTimeout(function() {
self._deltaFactor = null;
}, 200);
}
}
// TODO: in other widgets, use this,
// rather than some self.$tooltip that
// will not get garbage collected
function setTooltip() {
if (self.options.tooltip) {
if (Ox.isString(self.options.tooltip)) {
bindTooltipEvents({
mouseenter: onMouseenter,
mouseleave: onMouseleave
});
that.$tooltip && that.$tooltip.options({
title: self.options.tooltip,
animate: true
});
} else {
that.$tooltip = Ox.Tooltip({animate: false});
bindTooltipEvents({
mousemove: onMousemove,
mouseleave: onMouseleave
});
}
} else {
if (that.$tooltip) {
that.$tooltip.remove();
}
bindTooltipEvents({});
}
}
that.update({tooltip: setTooltip});
return that;
};
// add all jQuery methods to the prototype of Ox.Element
Ox.methods($('<div>'), true).forEach(function(method) {
Ox.Element.prototype[method] = function() {
var ret = this.$element[method].apply(this.$element, arguments),
oxid;
// If exactly one $element of an Ox Element was returned, then
// return the Ox Element instead, so that we can do
// oxObj.jqFn().oxFn()
return ret && ret.jquery && ret.length == 1
&& Ox.$elements[oxid = ret.data('oxid')]
? Ox.$elements[oxid] : ret;
};
});
/*@
bindEvent <f> Adds event handler(s)
(callback) -> <o> This element object
Adds a catch-all handler
(event, callback) -> <o> This element object
Adds a handler for a single event
({event: callback, ...}) -> <o> This element object
Adds handlers for one or more events
callback <f> Callback function
data <o> event data (key/value pairs)
event <s> Event name
Event names can be namespaced, like `'click.foo'`
callback <f> Callback function
@*/
Ox.Element.prototype.bindEvent = function bindEvent() {
Ox.Event.bind.apply(this, [this.self(_)].concat(Ox.slice(arguments)));
return this;
};
/*@
bindEventOnce <f> Adds event handler(s) that run(s) only once
(callback) -> <o> This element
Adds a catch-all handler
(event, callback) -> <o> This element
Adds a handler for a single event
({event: callback, ...}) -> <o> This element
Adds handlers for one or more events
callback <f> Callback function
data <o> event data (key/value pairs)
event <s> Event name
Event names can be namespaced, like `'click.foo'`
callback <f> Callback function
@*/
Ox.Element.prototype.bindEventOnce = function bindEventOnce() {
Ox.Event.bindOnce.apply(
this, [this.self(_)].concat(Ox.slice(arguments))
);
return this;
};
/*@
bindMessage <f> Adds message handler(s) (if the element is an iframe)
(callback) -> <o> This element object
Adds a catch-all handler
(message, callback) -> <o> This element object
Adds a handler for a single message
({message: callback, ...}) -> <o> This element object
Adds handlers for one or more messages
message <s> Message name
callback <f> Callback function
data <o> Message data (key/value pairs)
event <s> Event name
element <o> Element object
@*/
Ox.Element.prototype.bindMessage = Ox.Element.prototype.onMessage = function bindMessage() {
var self = this.self(_);
if (self.options.element == '<iframe>') {
Ox.Message.bind.apply(this, [self].concat(Ox.slice(arguments)));
}
return this;
};
/*@
bindMessageOnce <f> Adds message handler(s) that run only once
(callback) -> <o> This element object
Adds a catch-all handler
(message, callback) -> <o> This element object
Adds a handler for a single message
({message: callback, ...}) -> <o> This element object
Adds handlers for one or more messages
event <s> Message name
callback <f> Callback function
data <o> Message data (key/value pairs)
event <s> Event name
element <o> Element object
@*/
Ox.Element.prototype.bindMessageOnce = Ox.Element.prototype.onMessageOnce = function bindMessageOnce() {
var self = this.self(_);
if (self.options.element == '<iframe>') {
Ox.Message.bindOnce.apply(
this, [self].concat(Ox.slice(arguments))
);
}
return this;
};
/*@
childrenElements <f> Gets all direct children element objects
() -> <[o]> Array of element objects
@*/
Ox.Element.prototype.childrenElements = function childrenElements() {
return Ox.compact(
Ox.slice(this.children())
.filter(Ox.UI.isElement)
.map(Ox.UI.getElement)
);
};
/*@
defaults <function> Gets or sets the default options for an element object
({key: value, ...}) -> <obj> This element object
key <str> The name of the default option
value <*> The value of the default option
@*/
Ox.Element.prototype.defaults = function defaults() {
var self = this.self(_);
var ret;
if (arguments.length == 0) {
ret = self.defaults;
} else if (Ox.isString(arguments[0])) {
ret = self.defaults[arguments[0]];
} else {
self.defaults = Ox.makeObject(arguments);
self.options = Ox.clone(self.defaults);
ret = this;
}
return ret;
};
Ox.Element.prototype.empty = function empty() {
this.childrenElements().forEach(function($element) {
$element.removeElement();
});
this.$element.empty.apply(this, arguments);
return this;
};
/*@
findElements <f> Gets all descendant element objects
() -> <[o]> Array of element objects
@*/
Ox.Element.prototype.findElements = function findElements() {
return Ox.compact(
Ox.slice(this.find('.OxElement')).map(Ox.UI.getElement)
);
};
/*@
gainFocus <function> Makes an element object gain focus
() -> <obj> This element object
@*/
Ox.Element.prototype.gainFocus = function gainFocus() {
Ox.Focus.gainFocus(this);
return this;
};
/*@
hasFocus <function> Returns true if an element object has focus
() -> <boolean> True if the element has focus
@*/
Ox.Element.prototype.hasFocus = function hasFocus() {
return Ox.Focus.focusedElement() === this;
};
Ox.Element.prototype.html = function html() {
var ret;
this.childrenElements().forEach(function($element) {
$element.removeElement();
});
ret = this.$element.html.apply(this, arguments);
return arguments.length == 0 ? ret : this;
};
/*@
loseFocus <function> Makes an element object lose focus
() -> <object> This element object
@*/
Ox.Element.prototype.loseFocus = function loseFocus() {
Ox.Focus.loseFocus(this);
return this;
};
/*@
nextElement <f> Gets the closest following sibling element object
() -> <o> Element object
@*/
Ox.Element.prototype.nextElement = function nextElement() {
return this.nextElements()[0];
};
/*@
nextElements <f> Gets all following sibling element objects
() -> <[o]> Array of element objects
@*/
Ox.Element.prototype.nextElements = function nextElements() {
return Ox.compact(
this.nextAll().filter(Ox.UI.isElement).map(Ox.UI.getElement)
);
};
/*@
options <f> Gets or sets the options of an element object
() -> <o> All options
(key) -> <*> The value of option[key]
(key, value) -> <o> This element
Sets options[key] to value and calls update(key, value)
if the key/value pair was added or modified
({key: value, ...}) -> <o> This element
Sets one or more options and calls update(key, value)
for every key/value pair that was added or modified
key <s> The name of the option
value <*> The value of the option
@*/
Ox.Element.prototype.options = function options() {
var self = this.self(_);
return Ox.getset(self.options, arguments, self.update, this);
};
/*@
parentElement <f> Gets the closest parent element object
() -> <o> Element object
@*/
Ox.Element.prototype.parentElement = function parentElement() {
return Ox.last(this.parentElements());
};
/*@
parentElements <f> Gets all parent element objects
() -> <[o]> Array of element objects
@*/
Ox.Element.prototype.parentElements = function parentElements() {
return Ox.compact(
Ox.slice(this.parents())
.filter(Ox.UI.isElement)
.map(Ox.UI.getElement)
);
};
/*@
postMessage <f> Sends a message (if the element is an iframe)
(event, data) -> This element object
event <s> Event name
data <o> Event data
@*/
Ox.Element.prototype.postMessage = function postMessage(event, data) {
if (this.self(_).options.element == '<iframe>') {
Ox.Message.post.apply(this, Ox.slice(arguments));
}
return this;
};
/*@
prevElement <f> Gets the closest preceding sibling element object
() -> <[o]> Array of element objects
@*/
Ox.Element.prototype.prevElement = function prevElement() {
return Ox.last(this.prevElements());
};
/*@
prevElements <f> Gets all preceding sibling element objects
() -> <[o]> Array of element objects
@*/
Ox.Element.prototype.prevElements = function prevElements() {
return Ox.compact(
this.prevAll().filter(Ox.UI.isElement).map(Ox.UI.getElement)
);
};
Ox.Element.prototype.remove = function remove() {
var parent = this[0].parentNode;
this.removeElement();
parent && parent.removeChild(this[0]);
return this;
};
/*@
removeElement <f> Clean up after removal from DOM
This gets invoked on .remove()
@*/
Ox.Element.prototype.removeElement = function removeElement(includeChildren) {
if (includeChildren !== false) {
this.findElements().forEach(function($element) {
if (!$element) {
Ox.print(
'*** Found undefined descendant element,'
+ ' this should never happen. ***'
);
return;
}
$element.removeElement(false);
});
}
Ox.Focus.removeElement(this.oxid);
this.self(_).unbindKeyboard();
this.$tooltip && this.$tooltip.remove();
delete Ox.$elements[this.oxid];
// If setElement($element) was used, delete $element too
delete Ox.$elements[this.$element.oxid];
return this;
};
Ox.Element.prototype.replace = function replace() {
arguments[0].removeElement();
this.$element.replace.apply(this.$element, arguments);
return this;
};
Ox.Element.prototype.replaceWith = function replaceWith() {
this.removeElement();
this.$element.replaceWith.apply(this.$element, arguments);
return this;
};
/*@
setElement <f> Sets the element to the element of another element object
This is useful if an element has specific options, but uses another
generic element as its DOM representation
($element) -> <o> This element object
@*/
Ox.Element.prototype.setElement = function setElement($element) {
this.findElements().forEach(function($element) {
$element.removeElement(false);
});
this.$element.replaceWith($element);
if ($element.$element) { // $element is Ox.Element
this.$element = $element.$element;
this.$element.oxid = $element.oxid;
} else { // $element is jQuery Element
this.$element = $element;
}
this.$element.addClass('OxElement').data({oxid: this.oxid});
this[0] = $element[0];
return this;
};
/*@
siblingElements <f> Gets all sibling element objects
() -> <[o]> Array of element objects
@*/
Ox.Element.prototype.siblingElements = function siblingElements() {
return Ox.compact(
Ox.slice(this.siblings())
.filter(Ox.UI.isElement)
.map(Ox.UI.getElement)
);
};
Ox.Element.prototype.text = function text() {
var ret;
this.childrenElements().forEach(function($element) {
$element.removeElement();
});
ret = this.$element.text.apply(this, arguments);
return arguments.length == 0 ? ret : this;
};
/*@
toggleOption <f> Toggle boolean option(s)
(key[, key[, ...]]) -> <o> This element object
@*/
Ox.Element.prototype.toggleOption = function toggleOption() {
var options = {}, self = this.self(_);
Ox.slice(arguments).forEach(function(key) {
options[key] == !self.options[key];
});
return this.options(options);
};
/*@
triggerEvent <f> Triggers all handlers for one or more events
(event) -> <o> This element object
Triggers an event
(event, data) -> <o> This element object
Triggers an event with data
({event: data, ...}) -> <o> This element object
Triggers one or more events with data
event <s> Event name
data <o> Event data (key/value pairs)
@*/
Ox.Element.prototype.triggerEvent = function triggerEvent() {
Ox.Event.trigger.apply(
this, [this.self(_)].concat(Ox.slice(arguments))
);
return this;
};
/*@
triggerMessage <f> Triggers all handlers for one or more messages
(message) -> <o> This element object
Triggers an event
(message, data) -> <o> This element object
Triggers a message with data
({message: data, ...}) -> <o> This element object
Triggers one or more messages with data
message <s> Message name
data <o> Message data (key/value pairs)
@*/
Ox.Element.prototype.triggerMessage = function triggerMessage() {
var self = this.self(_);
if (self.options.element == '<iframe>') {
Ox.Message.trigger.apply(this, [self].concat(Ox.slice(arguments)));
}
return this;
};
/*@
unbindEvent <f> Removes event handler(s)
() -> <o> This element object
Removes all handlers.
(callback) -> <o> This element object
Removes a specific catch-all handler
(event) -> <o> This element object
Removes all handlers for a single event (to remove all catch-all
handlers, pass '*' as event)
(event, callback) -> <o> This element object
Removes a specific handler for a single event
({event: callback}, ...) -> <o> This element object
Removes specific handlers for one or more events
event <s> Event name
callback <f> Event handler
@*/
Ox.Element.prototype.unbindEvent = function unbindEvent() {
Ox.Event.unbind.apply(this, [this.self(_)].concat(Ox.slice(arguments)));
return this;
};
/*@
unbindMessage <f> Removes message handler(s)
() -> <o> This element
Removes all handlers.
(callback) -> <o> This element object
Removes a specific catch-all handler
(message) -> <o> This element object
Removes all handlers for a single message (to remove all catch-all
handlers, pass '*' as message)
(message, callback) -> <o> This element object
Removes a specific handler for a single event
({message: callback}, ...) -> <o> This element object
Removes specific handlers for one or more messages
message <s> Message name
callback <f> Message handler
@*/
Ox.Element.prototype.unbindMessage = function unbindMessage() {
var self = this.self(_);
if (self.options.element == '<iframe>') {
Ox.Message.unbind.apply(this, [self].concat(Ox.slice(arguments)));
}
return this;
};
/*@
update <f> Adds one or more handlers for options updates
(callback) -> <o> This element object
(key, callback) -> <o> This element object
({key: callback, ...}) -> <o> This element object
@*/
Ox.Element.prototype.update = function update() {
var callbacks, self = this.self(_);
if (Ox.isFunction(arguments[0])) {
self.updateCallbacks.push(arguments[0]);
} else {
callbacks = Ox.makeObject(arguments);
self.updateCallbacks.push(function(key, value) {
if (callbacks[key]) {
return callbacks[key](value);
}
});
}
return this;
};
/*@
value <f> Shortcut to get or set self.options.value
() -> <*> Value
(value) -> <o> This element object
value <*> Value
@*/
Ox.Element.prototype.value = function value() {
return this.options(
arguments.length == 0 ? 'value' : {value: arguments[0]}
);
};
}({}));

502
source/UI/js/Core/Event.js Normal file
View file

@ -0,0 +1,502 @@
(function() {
var chars = {
comma: ',',
dot: '.',
minus: '-',
quote: '\'',
semicolon: ';',
slash: '/',
space: ' '
},
keyboardCallbacks = {},
keyboardEventRegExp = /^key(\.[\w\d.]+)?$/,
keys = '',
keysEventRegExp = new RegExp(
'^[\\w\\d](\\.numpad)?$|^(' + Object.keys(chars).join('|') + ')$'
),
resetTimeout,
triggerTimeout;
function bind(options) {
var args = Ox.slice(arguments, 1),
callbacks = options.callbacks,
that = this,
oxid = that.oxid || 0;
Ox.forEach(
Ox.isFunction(args[0]) ? {'*': args[0]} : Ox.makeObject(args),
function(originalCallback, event) {
event = event.replace(/^key_/, 'key.');
callbacks[event] = (callbacks[event] || []).concat(
options.once ? function callback() {
unbind.call(
that, {callbacks: callbacks}, event, callback
);
return originalCallback.apply(null, arguments);
}
: originalCallback
);
if (isKeyboardEvent(event)) {
keyboardCallbacks[oxid] = (
keyboardCallbacks[oxid] || []
).concat(event);
}
}
);
return this;
}
function isKeyboardEvent(event) {
return keyboardEventRegExp.test(event);
}
function isKeysEventKey(key) {
return keysEventRegExp.test(key);
}
function onMessage(e) {
var element, message = {};
try {
message = Ox.extend({data: {}}, JSON.parse(e.data));
} catch (e) {}
if (message.event == 'init') {
if (message.data.oxid) {
// The inner window receives the oxid of the outer iframe element
Ox.oxid = message.data.oxid;
Ox.$parent.postMessage('init', {})
} else if (message.target) {
// The outer window receives init from iframe
Ox.$elements[message.target].triggerEvent('init');
}
} else {
(message.target ? Ox.$elements[message.target] : Ox.$parent)
.triggerMessage(message.event, message.data);
}
}
function onKeydown(e) {
var $element = Ox.Focus.focusedElement(),
isInput = Ox.Focus.focusedElementIsInput(),
keyName = Ox.KEYS[e.keyCode],
keyBasename = keyName.split('.')[0],
key = Object.keys(Ox.MODIFIER_KEYS).filter(function(key) {
return e[key] && Ox.MODIFIER_KEYS[key] != keyBasename;
}).map(function(key) {
return Ox.MODIFIER_KEYS[key];
}).concat(keyName).join('_'),
event = 'key.' + key,
triggerEvent = function() {
if ($element) {
$element.triggerEvent.apply($element, arguments);
} else if (!isInput) {
Ox.Event.trigger.apply(
Ox.$body, [{}].concat(Ox.slice(arguments))
);
}
};
triggerEvent(event, e);
if (!isInput) {
if (isKeysEventKey(key)) {
// don't register leading spaces or trailing double spaces
if (keyName != 'space' || (
keys != '' && !Ox.endsWith(keys, ' ')
)) {
keys += chars[keyName] || keyBasename;
// clear the trigger timeout only if the key registered
clearTimeout(triggerTimeout);
triggerTimeout = setTimeout(function() {
triggerEvent('keys', Ox.extend(e, {keys: keys}));
}, 250);
}
}
// clear the reset timeout even if the key didn't register
clearTimeout(resetTimeout);
resetTimeout = setTimeout(function() {
keys = '';
}, 1000);
if ((
keyboardCallbacks[0]
&& Ox.contains(keyboardCallbacks[0], event)
) || (
$element && keyboardCallbacks[$element.oxid]
&& Ox.contains(keyboardCallbacks[$element.oxid], event)
)) {
// if there is a global handler for this keyboard event, or a
// handler on the focused element, then prevent default
e.preventDefault();
}
}
}
function trigger(options) {
var args = Ox.slice(arguments, 1),
callbacks = options.callbacks,
that = this;
Ox.forEach(Ox.makeObject(args), function(data, originalEvent) {
var events = originalEvent.split('.'),
triggerGlobally = !isKeyboardEvent(originalEvent)
|| !Ox.Focus.focusedElementIsInput();
['*'].concat(events.map(function(event, index) {
return events.slice(0, index + 1).join('.');
})).forEach(function(event) {
(triggerGlobally ? callbacks[0][event] || [] : [])
.concat(callbacks[1][event] || [])
.forEach(function(callback) {
callback.call(that, data, originalEvent, that);
});
});
});
return this;
}
function unbind(options) {
var args = Ox.slice(arguments, 1),
callbacks = options.callbacks,
oxid = this.oxid || 0;
if (args.length == 0) {
// unbind all handlers for all events
callbacks = [];
} else {
Ox.forEach(
Ox.isFunction(args[0]) ? {'*': args[0]}
: Ox.makeObject(args),
function(callback, event) {
if (!callback) {
// unbind all handlers for this event
delete callbacks[event];
} else if (callbacks[event]) {
// unbind this handler for this event
callbacks[event] = callbacks[event].filter(
function(eventCallback) {
return eventCallback !== callback;
}
);
if (callbacks[event].length == 0) {
delete callbacks[event];
}
}
if (isKeyboardEvent(event)) {
var index = keyboardCallbacks[oxid].indexOf(event);
keyboardCallbacks[oxid].splice(
keyboardCallbacks[oxid].indexOf(event), 1
)
if (keyboardCallbacks[oxid].length == 0) {
delete keyboardCallbacks[oxid];
}
}
}
);
}
return this;
}
/*@
Ox.$parent <o> Proxy to be used by iframes for messaging with outer window
@*/
Ox.$parent = (function() {
var self = {messageCallbacks: {}},
that = {oxid: Ox.uid()};
/*@
bindMessage <f> Adds one or more message handlers
@*/
that.bindMessage = function() {
return Ox.Message.bind.apply(
this, [self].concat(Ox.slice(arguments))
);
};
/*@
bindMessageOnce <f> Adds one or more message handlers that run only once
@*/
that.bindMessageOnce = function() {
return Ox.Message.bindOnce.apply(
this, [self].concat(Ox.slice(arguments))
);
};
/*@
postMessage <f> Sends one or more messages
@*/
that.postMessage = function() {
if (window !== window.top) {
// There actually is an outer window
if (!Ox.oxid) {
// Inner window has not received init message yet
self.initTime = self.initTime || new Date();
if (new Date() < self.initTime + 60000) {
setTimeout(function() {
that.postMessage.apply(that, arguments);
}, 250);
}
} else {
return Ox.Message.post.apply(this, arguments);
}
}
};
/*@
triggerMessage <f> Triggers all handlers for one or more messages
@*/
that.triggerMessage = function() {
return Ox.Message.trigger.apply(
this, [self].concat(Ox.slice(arguments))
);
};
/*@
unbindMessage <f> Removes one or more message handlers
@*/
that.unbindMessage = function() {
return Ox.Message.unbind.apply(
this, [self].concat(Ox.slice(arguments))
);
};
return that;
}());
/*@
Ox.Event <o> Event controller
@*/
Ox.Event = (function() {
var callbacks = {},
that = {};
/*@
bind <f> Adds one or more event handlers
([self, ]callback) -> <o> This method's `this` binding
Adds a catch-all handler
([self, ]event, callback) -> <o> This method's `this` binding
Adds a handler for a single event
([self, ]{event: callback, ...}) -> <o> This method's `this` binding
Adds handlers for multiple events
self <o> Object with `eventCallbacks` (`Ox.Element`'s `self`)
If `self` is missing and this method is not rebound, then the
handler is global and is not bound to a specific `Ox.Element`
event <s> Event name
callback <f> Callback function
data <o> Event data (key/value pairs)
event <s> Event name
element <o> Element object (this method's `this` binding)
@*/
that.bind = function() {
var isElement = this !== that;
return bind.apply(this, [{
callbacks: isElement ? arguments[0].eventCallbacks : callbacks
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
/*@
bindOnce <f> Adds one or more event handlers that run only once
([self, ]callback) -> <o> This method's `this` binding
Adds a catch-all handler
([self, ]event, callback) -> <o> This method's `this` binding
Adds a handler for a single event
([self, ]{event: callback, ...}) -> <o> This method's `this` binding
Adds handlers for multiple events
self <o> Object with `eventCallbacks` (`Ox.Element`'s `self`)
If `self` is missing and this method is not rebound, then the
handler is global and is not bound to a specific `Ox.Element`
event <s> Event name
callback <f> Callback function
data <o> Event data (key/value pairs)
event <s> Event name
element <o> Element object (this method's `this` binding)
@*/
that.bindOnce = function() {
var isElement = this !== that;
return bind.apply(this, [{
callbacks: isElement ? arguments[0].eventCallbacks : callbacks,
once: true
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
/*@
trigger <f> Triggers all event handlers for one or more events
([self, ]event[, data]) -> <o> This method's `this` binding
Triggers one event, with optional event data
([self, ]{event: data, ...}) -> <o> This method's `this` binding
Triggers multiple events
self <o> Object with `eventCallbacks` (`Ox.Element`'s `self`)
If `self` is missing and this method is not rebound, then the
handler is global and is not bound to a specific `Ox.Element`
event <s> Event name
data <o> Event data (key/value pairs)
@*/
that.trigger = function() {
var isElement = this !== that;
return trigger.apply(this, [{
callbacks: [
callbacks,
isElement ? arguments[0].eventCallbacks || {} : {}
]
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
/*@
unbind <f> Removes one or more event handlers
([self]) -> <o> This method's `this` binding
Unbinds all handlers
([self, ]callback) -> <o> This method's `this` binding
Unbinds a catch-all handler
([self, ]event, callback) -> <o> This method's `this` binding
Unbinds a handler for a single event
([self, ]{event: callback, ...}) -> <o> This method's `this` binding
Unbinds handlers for multiple events
self <o> Object with `eventCallbacks` (`Ox.Element`'s `self`)
If `self` is missing and this method is not rebound, then the
handler is global and is not bound to a specific `Ox.Element`
event <s> Event name
callback <f> Event handler
@*/
that.unbind = function() {
var isElement = this !== that;
return unbind.apply(this, [{
callbacks: isElement ? arguments[0].eventCallbacks : callbacks
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
return that;
}());
/*@
Ox.Message <o> Message controller
@*/
Ox.Message = (function() {
var callbacks = {},
that = {};
/*@
bind <f> Adds one or more message handlers
([self, ]callback) -> <o> This method's `this` binding
Adds a catch-all handler
([self, ]message, callback) -> <o> This method's `this` binding
Adds a handler for a single message
([self, ]{message: callback, ...}) -> <o> This method's `this` binding
Adds handlers for multiple messages
self <o> Object with `messageCallbacks` (`Ox.Element`'s `self`)
If `self` is missing and this method is not rebound, then the
handler is bound to the outer window (via `Ox.$parent`)
message <s> Message name
callback <f> Callback function
data <o> Message data (key/value pairs)
message <s> Message name
element <o> Element object (this method's `this` binding)
@*/
that.bind = function() {
var isElement = this !== that;
return bind.apply(this, [{
callbacks: isElement ? arguments[0].messageCallbacks
: callbacks
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
/*@
bindOnce <f> Adds one or more message handlers that run only once
([self, ]callback) -> <o> This method's `this` binding
Adds a catch-all handler
([self, ]message, callback) -> <o> This method's `this` binding
Adds a handler for a single message
([self, ]{message: callback, ...}) -> <o> This method's `this` binding
Adds handlers for multiple messages
self <o> Object with `messageCallbacks` (`Ox.Element`'s `self`)
If `self` is missing and this method is not rebound, then the
handler is bound to the outer window (via `Ox.$parent`)
message <s> Message name
callback <f> Callback function
data <o> Message data (key/value pairs)
message <s> Message name
element <o> Element object (this method's `this` binding)
@*/
that.bindOnce = function() {
var isElement = this !== that;
return bind.apply(this, [{
callbacks: isElement ? arguments[0].messageCallbacks
: callbacks,
once: true
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
/*@
post <f> Post a message into or out of an iframe
(message[, data]) -> <o> This method's `this` binding
Posts one message, with optional message data
({message: data, ...}) -> <o> This method's `this` binding
Posts multiple messages
message <s> Message name
data <o> Message data (key/value pairs)
@*/
that.post = function() {
var isParent = this == Ox.$parent,
target = isParent ? window.parent : this[0].contentWindow;
Ox.forEach(
Ox.makeObject(Ox.slice(arguments)),
function(data, event) {
target.postMessage(JSON.stringify({
data: data,
event: event,
target: isParent ? Ox.oxid : null
}), '*');
}
);
return this;
};
/*@
trigger <f> Triggers all message handlers for one or more messages
([self, ]message[, data]) -> <o> This method's `this` binding
Triggers one message, with optional message data
([self, ]{message: data, ...}) -> <o> This method's `this` binding
Triggers multiple messages
self <o> Object with `eventCallbacks` (`Ox.Element`'s `self`)
If `self` is missing and this method is not rebound, then the
handler is global and is not bound to a specific `Ox.Element`
message <s> Message name
data <o> Message data (key/value pairs)
@*/
that.trigger = function() {
var isElement = this !== that;
return trigger.apply(this, [{
callbacks: [
callbacks,
isElement ? arguments[0].messageCallbacks || {} : {}
]
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
/*@
unbind <f> Removes one or more message handlers
([self, ]callback) -> <o> This method's `this` binding
Removes a catch-all handler
([self, ]message, callback) -> <o> This method's `this` binding
Removes a handler for a single message
([self, ]{message: callback, ...}) -> <o> This method's `this` binding
Removes handlers for multiple messages
self <o> Object with `messageCallbacks` (`Ox.Element`'s `self`)
If `self` is missing and this method is not rebound, then the
handler is bound to the outer window (via `Ox.$parent`)
message <s> Message name
callback <f> Message handler
@*/
that.unbind = function() {
var isElement = this !== that;
return unbind.apply(this, [{
callbacks: isElement ? arguments[0].messageCallbacks
: callbacks
}].concat(Ox.slice(arguments, isElement ? 1 : 0)));
};
return that;
}());
document.addEventListener('keydown', onKeydown);
window.addEventListener('message', onMessage);
}());

View file

@ -0,0 +1,66 @@
'use strict';
/*@
Ox.Focus <o> Basic focus controller
@*/
Ox.Focus = (function() {
var stack = [],
that = {
focusedElement: function() {
return Ox.$elements[Ox.last(stack)];
},
focusedElementIsInput: function() {
var $element = that.focusedElement();
return $element && $element.hasClass('OxKeyboardFocus');
},
gainFocus: function($element) {
var $focusedElement = that.focusedElement(),
oxid = $element.oxid,
index = stack.indexOf(oxid);
if (index == -1 || index < stack.length - 1) {
stack = $element.parentElements().map(function($element) {
return $element.oxid;
}).concat(oxid);
if ($focusedElement) {
$focusedElement
.removeClass('OxFocus')
.triggerEvent('losefocus');
}
$element
.addClass('OxFocus')
.triggerEvent('gainfocus');
}
},
hasFocus: function($element) {
return Ox.last(stack) == $element.oxid;
},
loseFocus: function($element) {
var index = stack.indexOf($element.oxid);
if (index > -1 && index == stack.length - 1) {
stack.pop();
$element
.removeClass('OxFocus')
.triggerEvent('losefocus');
if (stack.length) {
Ox.$elements[Ox.last(stack)]
.addClass('OxFocus')
.triggerEvent('gainfocus');
}
}
},
removeElement: function($element) {
var index = stack.indexOf($element.oxid);
if (index == stack.length - 1) {
that.loseFocus($element);
} else if (index > -1) {
stack.splice(index, 1);
}
}
};
return that;
}());

View file

@ -0,0 +1,147 @@
'use strict';
/*@
Ox.Fullscreen <o> Fullscreen controller
bind <f> Add a fullscreen event handler
event <s> Event name ('change', 'enter' or 'exit')
handler <f> Event handler
state <b> Fullscreen state (present in case of a 'change' event)
bindOnce <f> Add a fullscreen event handler that will run only once
event <s> Event name ('change', 'enter' or 'exit')
handler <f> Event handler
state <b> Fullscreen state (present in case of a 'change' event)
enter <f> Enter fullscreen
exit <f> Exit fullscreen
getState <f> Get fullscreen state (true, false or undefined)
toggle <f> Toggle fullscreen
unbind <f> Remove a fullscreen event handler
event <s> Event name ('change', 'enter' or 'exit')
handler <f> Event handler
@*/
Ox.Fullscreen = (function() {
var documentElement = document.body,
enter = document.body.requestFullscreen
|| document.body.mozRequestFullScreen
|| document.body.webkitRequestFullscreen,
exit = document.exitFullscreen
|| document.mozCancelFullScreen
|| document.webkitExitFullscreen,
state = 'fullscreen' in document ? 'fullscreen'
: 'mozFullScreen' in document ? 'mozFullScreen'
: 'webkitIsFullScreen' in document ? 'webkitIsFullScreen'
: void 0,
handlers = {
'': {'change': [], 'enter': [], 'exit': []},
'once': {'change': [], 'enter': [], 'exit': []}
},
types = Object.keys(handlers),
that = {};
[
'fullscreenchange', 'mozfullscreenchange', 'webkitfullscreenchange'
].forEach(function(originalEvent) {
document.addEventListener(originalEvent, change);
});
function bind(event, handler, once) {
var type = once ? 'once' : '';
if (
handlers[type][event]
&& handlers[type][event].indexOf(handler) == -1
) {
handlers[type][event].push(handler);
}
}
function change() {
var state = that.getState(),
events = ['change'].concat(state ? 'enter' : 'exit'),
unbind = [];
types.forEach(function(type) {
events.forEach(function(event) {
handlers[type][event].forEach(function(handler) {
event == 'change' ? handler(state) : handler();
type == 'once' && unbind.push(
{event: event, handler: handler}
);
});
});
});
unbind.forEach(function(value) {
that.unbind(value.event, value.handler, true);
});
};
function unbind(event, handler, once) {
var index;
[once ? ['once'] : types].forEach(function(type) {
if (handlers[type][event]) {
while ((index = handlers[type][event].indexOf(handler)) > -1) {
handlers[type][event].splice(index, 1);
}
}
});
}
that.available = document.fullscreenEnabled
|| document.webkitFullscreenEnabled
|| document.mozFullScreenEnabled
|| false;
that.bind = function(event, handler) {
bind(event, handler);
};
that.bindOnce = function(event, handler) {
bind(event, handler, true);
};
that.enter = function(element) {
var element = element || document.body;
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
}
// FIXME: Why does storing the function in a variable not work?
// enter && enter();
// ^ Missing `this` binding
};
that.exit = function() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen()
}
// FIXME: Why does storing the function in a variable not work?
// exit && exit();
// ^ Missing `this` binding
};
that.getState = function() {
return document[state];
};
that.toggle = function() {
var state = that.getState();
if (state === false) {
that.enter();
} else if (state === true) {
that.exit();
}
};
that.unbind = function(event, handler) {
unbind(event, handler);
};
return that;
}());

View file

@ -0,0 +1,52 @@
'use strict';
/*@
Ox.GarbageCollection <f> GarbageCollection
() -> <o> run garbage collection
debug() -> {} output debug information
@*/
Ox.GarbageCollection = (function() {
var that = {},
timeout;
that.collect = function() {
var len = Ox.len(Ox.$elements);
Object.keys(Ox.$elements).forEach(function(id) {
var $element = Ox.$elements[id];
if ($element && Ox.isUndefined($element.data('oxid'))) {
$element.remove();
delete Ox.$elements[id];
}
});
timeout && clearTimeout(timeout);
timeout = setTimeout(that.collect, 60000);
Ox.Log('GC', len, '-->', Ox.len(Ox.$elements));
};
/*@
debug <f> debug info
() -> <s>
@*/
that.debug = function() {
var classNames = {}, sorted = [];
Ox.forEach(Ox.$elements, function($element, id) {
var className = $element[0].className;
classNames[className] = (classNames[className] || 0) + 1;
});
Ox.forEach(classNames, function(count, className) {
sorted.push({className: className, count: count});
});
return sorted.sort(function(a, b) {
return a.count - b.count;
}).map(function(v) {
return v.count + ' ' + v.className
}).join('\n');
};
setTimeout(that.collect, 60000);
return that;
}());

View file

@ -0,0 +1,73 @@
'use strict';
Ox.History = function(options) {
options = Ox.extend({
text: function(item) {
return item.text;
}
}, options || {});
var history = [],
position = 0,
$element;
return {
_print: function() {
Ox.print(JSON.stringify({history: history, position: position}));
},
add: function(items) {
items = Ox.makeArray(items);
history = history.slice(0, position).concat(items);
position += items.length;
$element && $element.triggerEvent('add');
return history.length;
},
bindEvent: function() {
if (!$element) {
$element = Ox.Element();
}
$element.bindEvent.apply($element, arguments);
},
clear: function() {
history = [];
position = 0;
$element && $element.triggerEvent('clear');
return history.length;
},
items: function() {
return history.length;
},
redo: function() {
if (position < history.length) {
position++;
$element && $element.triggerEvent('redo');
return history[position - 1];
}
},
redoText: function() {
if (position < history.length) {
return options.text(history[position]);
}
},
remove: function(test) {
},
unbindEvent: function() {
$element && $element.unbindEvent.apply($element, arguments);
},
undo: function() {
if (position > 0) {
position--;
$element && $element.triggerEvent('undo');
return history[position];
}
},
undoText: function() {
if (position > 0) {
return options.text(history[position - 1]);
}
}
};
};

View file

@ -0,0 +1,89 @@
'use strict';
/*@
Ox.LoadingIcon <f> Loading Icon Element
options <o> Options object
size <n|s|16> size of icon
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Loading Icon Element
@*/
Ox.LoadingIcon = function(options, self) {
self = self || {};
var that = Ox.Element('<img>', self)
.defaults({
size: 16,
video: false
})
.options(options || {})
.addClass('OxLoadingIcon')
.attr({
src: Ox.UI.getImageURL(
'symbolLoading',
self.options.video ? 'videoIcon' : null
)
});
Ox.isNumber(self.options.size)
? that.css({width: self.options.size, height: self.options.size})
: that.addClass('Ox' + Ox.toTitleCase(self.options.size));
that.stopAnimation = that.stop;
/*@
start <f> Start loading animation
() -> <f> Loading Icon Element
@*/
that.start = function(callback) {
var css, deg = 0, previousTime = +new Date();
if (!self.loadingInterval) {
self.loadingInterval = setInterval(function() {
var currentTime = +new Date(),
delta = (currentTime - previousTime) / 1000;
previousTime = currentTime;
deg = Math.round((deg + delta * 360) % 360 / 30) * 30;
css = 'rotate(' + deg + 'deg)';
that.css({
MozTransform: css,
MsTransform: css,
OTransform: css,
WebkitTransform: css,
transform: css
});
}, 83);
that.stopAnimation().animate({opacity: 1}, 250, function() {
callback && callback();
});
}
return that;
};
/*@
stop <f> Stop loading animation
() -> <f> Loading Icon Element
@*/
that.stop = function(callback) {
if (self.loadingInterval && !self.stopping) {
self.stopping = true;
that.stopAnimation().animate({opacity: 0}, 250, function() {
var css = 'rotate(0deg)';
clearInterval(self.loadingInterval);
self.loadingInterval = null;
self.stopping = false;
that.css({
MozTransform: css,
MsTransform: css,
OTransform: css,
WebkitTransform: css,
transform: css
});
callback && callback();
});
}
return that;
};
return that;
};

View file

@ -0,0 +1,82 @@
'use strict';
/*@
Ox.LoadingScreen <f> Simple loading screen
@*/
Ox.LoadingScreen = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
height: 0,
size: 32,
text: '',
width: 0
})
.options(options || {})
.update({
height: function() {
!self.isAuto && setSizes();
},
text: function() {
self.$text && self.$text.html(self.options.text);
},
width: function() {
!self.isAuto && setSizes();
}
})
.addClass('OxLoadingScreen');
self.isAuto = !self.options.width && !self.options.height;
self.isAuto && that.addClass('OxAuto')
self.$box = $('<div>').appendTo(that);
setSizes();
self.$loadingIcon = Ox.LoadingIcon({
size: self.options.size
})
.appendTo(self.$box);
if (self.options.text) {
self.$text = $('<div>')
.html(self.options.text)
.appendTo(self.$box);
}
function setSizes() {
var css = {
width: (self.options.text ? 256 : self.options.size),
height: self.options.size + (self.options.text ? 24 : 0)
};
if (!self.isAuto) {
css.left = Math.floor((self.options.width - css.width) / 2);
css.top = Math.floor((self.options.height - css.height) / 2);
that.css({
width: self.options.width + 'px',
height: self.options.height + 'px'
});
}
css = Ox.map(css, function(value) {
return value + 'px';
});
self.$box.css(css);
}
that.start = function(callback) {
self.$loadingIcon.start(callback);
self.$text && self.$text.stop().animate({opacity: 1}, 250);
return that;
};
that.stop = function(callback) {
self.$loadingIcon.stop(callback);
self.$text && self.$text.stop().animate({opacity: 0}, 250);
return that;
};
return that;
};

View file

@ -0,0 +1,226 @@
'use strict';
/*@
Ox.Request <o> Basic request controller
# FIXME: options is not a property, just documenting defaults
# options <o> Options object
# timeout <n|60000> request timeout
# type <s|"POST"> request type, possible values POST, GET, PUT, DELETE
# url <s> request url
@*/
Ox.Request = (function() {
var cache = {},
pending = {},
requests = {},
self = {
options: {
cache: true,
timeout: 60000,
type: 'POST',
url: '/api/'
}
},
$element;
return {
/*@
bindEvent <f> Bind event
@*/
bindEvent: function() {
if (!$element) {
$element = Ox.Element();
}
$element.bindEvent.apply($element, arguments);
},
/*@
cancel <f> cancel pending requests
() -> <u> cancel all requests
(fn) -> <u> cancel all requests where function returns true
(id) -> <u> cancel request by id
@*/
cancel: function() {
if (arguments.length == 0) {
// cancel all requests
requests = {};
} else if (Ox.isFunction(arguments[0])) {
// cancel with function
Ox.forEach(requests, function(req, id) {
if (arguments[0](req)) {
delete requests[id];
}
});
} else {
// cancel by id
delete requests[arguments[0]];
}
$element && $element.triggerEvent('request', {
requests: Ox.len(requests)
});
},
/*@
clearCache <f> clear cached results
() -> <u> ...
@*/
clearCache: function(query) {
if (!query) {
cache = {};
} else {
cache = Ox.filter(cache, function(val, key) {
return key.indexOf(query) == -1;
});
}
},
/*@
options <f> get/set options
() -> <o> get options
(options) -> <o> set options
options <o> Options Object
@*/
options: function() {
return Ox.getset(self.options, arguments, function() {}, this);
},
/*@
requests <f> pending requests
() -> <n> returns number of requests
@*/
requests: function() {
return Ox.len(requests);
},
/*@
send <f> send request
(options) -> <n> returns request id
options <o> Options Object
age <n|-1> cache age
id <n|Ox.uid()> request id
timeout <n|self.options.timeout> overwrite default timeout
type <n|self.options.timeout> overwrite default type
url <n|self.options.timeout> overwrite default url
@*/
send: function(options) {
var data,
options = Ox.extend({
age: -1,
callback: null,
id: Ox.uid(),
timeout: self.options.timeout,
type: self.options.type,
url: self.options.url
}, options),
req = JSON.stringify({
url: options.url,
data: options.data
});
if (pending[options.id]) {
setTimeout(function() {
Ox.Request.send(options);
});
} else {
requests[options.id] = {
url: options.url,
data: options.data
};
if (cache[req] && (
options.age == -1
|| options.age > +new Date() - cache[req].time
)) {
data = cache[req].data;
setTimeout(function() {
callback(data, true);
});
} else {
pending[options.id] = true;
$.ajax({
beforeSend: function(request) {
var csrftoken = Ox.Cookies('csrftoken');
if (csrftoken) {
request.setRequestHeader('X-CSRFToken', csrftoken);
}
},
complete: complete,
data: options.data,
// dataType: 'json',
timeout: options.timeout,
type: options.type,
url: options.url
});
}
$element && $element.triggerEvent('request', {
requests: Ox.len(requests)
});
}
function callback(data, success) {
if (requests[options.id]) {
delete requests[options.id];
$element && $element.triggerEvent('request', {
requests: Ox.len(requests)
});
if (success) {
options.callback && options.callback(data);
} else {
$element && $element.triggerEvent('error', data);
}
}
}
function complete(request) {
var $dialog, data;
try {
data = JSON.parse(request.responseText);
} catch (error) {
try {
data = {
status: {
code: request.status,
text: request.statusText
}
};
} catch (error) {
data = {
status: {
code: '500',
text: 'Unknown Error'
}
};
}
}
if (Ox.contains([200, 404, 409], data.status.code)) {
// we have to include not found and conflict
// so that handlers can handle these cases
if (self.options.cache) {
cache[req] = {
data: data,
time: Ox.getTime()
};
}
callback(data, true);
} else {
callback(data, false);
}
pending[options.id] = false;
}
return options.id;
},
/*@
unbindEvent <f> Unbind event
@*/
unbindEvent: function() {
$element && $element.unbindEvent.apply($element, arguments);
}
};
}());

192
source/UI/js/Core/Theme.js Normal file
View file

@ -0,0 +1,192 @@
'use strict';
/*@
Ox.Theme <f> get/set theme
() -> <s> Get current theme
(theme) -> <s> Set current theme
theme <s> name of theme
@*/
Ox.Theme = (function() {
var localStorage = Ox.localStorage('Ox'),
that = function(theme) {
return theme ? setTheme(theme) : getTheme();
};
function getTheme() {
var classNames = Ox.$body.attr('class'),
theme = '';
classNames && Ox.forEach(classNames.split(' '), function(className) {
if (Ox.startsWith(className, 'OxTheme')) {
theme = className.replace('OxTheme', '');
theme = theme[0].toLowerCase() + theme.slice(1);
return false; // break
}
});
return theme;
}
function renderElement(value, type) {
var $element, background, color, data = that.getThemeData(), saturation;
if (type == 'hue') {
background = Ox.rgb(value, 1, data.themeBackgroundLightness);
color = Ox.rgb(value, 1, data.themeColorLightness);
} else if (type == 'saturation') {
background = Ox.range(7).map(function(i) {
return Ox.rgb(i * 60, value, data.themeBackgroundLightness);
});
color = Ox.rgb(0, 0, data.themeColorLightness);
} else if (type == 'lightness') {
background = Ox.range(3).map(function() {
return Math.round(value * 255);
});
color = Ox.range(3).map(function() {
return Math.round(value * 255) + (value < 0.5 ? 128 : -128);
});
} else if (type == 'gradient') {
saturation = value === null ? 0 : 1;
background = Ox.range(2).map(function(i) {
return Ox.rgb(value || 0, saturation, data.themeBackgroundLightness).map(function(value) {
return (value || 0) + (i == 0 ? 16 : -16);
});
});
color = Ox.rgb(value || 0, saturation, data.themeColorLightness);
}
$element = $('<div>')
.addClass(
'OxColor'
+ (type == 'lightness' ? '' : ' OxColor' + Ox.toTitleCase(type))
)
.css({
color: 'rgb(' + color.join(', ') + ')'
})
.data(type == 'lightness' ? {} : {OxColor: value});
if (Ox.isNumber(background[0])) {
$element.css({
background: 'rgb(' + background.join(', ') + ')'
});
} else {
['moz', 'o', 'webkit'].forEach(function(browser) {
$element.css({
background: '-' + browser + '-linear-gradient('
+ (background.length == 2 ? 'top' : 'left') + ', '
+ background.map(function(rgb, i) {
return 'rgb(' + rgb.join(', ') + ') '
+ Math.round(i * 100 / (background.length - 1)) + '%';
}).join(', ')
+ ')'
});
});
}
return $element;
};
function setTheme(theme) {
var currentTheme = getTheme();
if (theme != currentTheme && Ox.contains(that.getThemes(), theme)) {
Ox.$body
.addClass(
'OxTheme'
+ theme[0].toUpperCase()
+ theme.substr(1)
)
.removeClass(
'OxTheme'
+ currentTheme[0].toUpperCase()
+ currentTheme.substr(1)
);
$('.OxColor').each(function() {
var $element = $(this);
if ($element.hasClass('OxColorName')) {
$element.attr({src: Ox.UI.getImageURL(
$element.data('OxImage'), $element.data('OxColor'), theme
)});
} else {
Ox.forEach(['hue', 'saturation', 'gradient'], function(type) {
if ($element.hasClass('OxColor' + Ox.toTitleCase(type))) {
var value = $element.data('OxColor'),
$element_ = renderElement(value, type);
$element.css({
background: $element_.css('background'),
color: $element_.css('color')
});
return false; // break
}
});
}
});
$('img').add('input[type="image"]').not('.OxColor')
.each(function(element) {
var $element = $(this),
data = Ox.UI.getImageData($element.attr('src'));
data && $element.attr({
src: Ox.UI.getImageURL(data.name, data.color, theme)
});
});
}
localStorage({theme: theme});
return that;
}
/*@
getThemes <f> Returns the names of all available themes
() -> <[s]> Theme names
@*/
that.getThemes = function() {
return Object.keys(Ox.UI.THEMES);
};
/*@
getThemeData <f> Returns data for a given theme, or for the current theme
([theme]) -> <o> Theme data
theme <s> Theme name
@*/
that.getThemeData = function(theme) {
return Ox.UI.THEMES[theme || Ox.Theme()];
};
/*@
formatColor <f> Returns a themed colored element
@*/
that.formatColor = function(value, type) {
return renderElement(value, type)
.css({textAlign: 'center'})
.html(value === null ? '' : Ox.formatNumber(value, 3));
};
/*@
formatColorLevel <f> Returns a themed colored element
@*/
that.formatColorLevel = function(index, values, hueRange) {
hueRange = hueRange || (Ox.isBoolean(index) ? [0, 120] : [120, 0]);
var step = (hueRange[1] - hueRange[0]) / (values.length - 1),
hue = hueRange[0] + index * step;
return renderElement(hue, 'gradient')
.css({textAlign: 'center'})
.html(values[+index]);
};
/*@
formatColorPercent <f> Returns a themed colored element
@*/
that.formatColorPercent = function(value, decimals, sqrt) {
var hue = (sqrt ? Math.sqrt(value) * 10 : value) * 1.2;
return renderElement(hue, 'gradient')
.css({textAlign: 'center'})
.html(Ox.formatNumber(value, decimals) + '%')
};
/*@
getColorImage <f> Returns a themed colored image
@*/
that.getColorImage = function(name, value, tooltip) {
return (tooltip ? Ox.Element({element: '<img>', tooltip: tooltip}) : $('<img>'))
.addClass('OxColor OxColorName')
.attr({src: Ox.UI.getImageURL(name, value)})
.data({OxColor: value, OxImage: name});
};
return that;
}());

137
source/UI/js/Core/UI.js Normal file
View file

@ -0,0 +1,137 @@
'use strict';
Ox.documentReady(function() {
// FIXME: use Ox.$foo everywhere!
//@ Ox.$body <o> jQuery-wrapped body
Ox.$body = $('body');
//@ Ox.$document <o> jQuery-wrapped document
Ox.$document = $(document);
//@ Ox.$head <o> jQuery-wrapped head
Ox.$head = $('head');
//@ Ox.$window <o> jQuery-wrapped window
Ox.$window = $(window);
});
//@ Ox.$elements <o> Reference to all Ox Elements
Ox.$elements = {};
//@ Ox.UI.DIMENSIONS <o> Names of horizontal and vertical dimensions
Ox.DIMENSIONS = Ox.UI.DIMENSIONS = {
horizontal: ['width', 'height'],
vertical: ['height', 'width']
};
//@ Ox.UI.EDGES <o> Names of horizontal and vertical edges
Ox.EDGES = Ox.UI.EDGES = {
horizontal: ['left', 'right', 'top', 'bottom'],
vertical: ['top', 'bottom', 'left', 'right']
};
//@ Ox.UI.SCROLLBAR_SIZE <n> Size of scrollbars
Ox.SCROLLBAR_SIZE = Ox.UI.SCROLLBAR_SIZE = $.browser.webkit ? 8 : (function() {
var inner = Ox.$('<p>').css({
height: '200px',
width: '100%'
}),
outer = Ox.$('<div>').css({
height: '150px',
left: 0,
overflow: 'hidden',
position: 'absolute',
top: 0,
visibility: 'hidden',
width: '200px'
}).append(inner).appendTo($('body')),
width = inner[0].offsetWidth;
outer.css({overflow: 'scroll'});
width = 1 + width - (inner[0].offsetWidth == width
? outer[0].clientWidth : inner[0].offsetWidth);
outer.remove();
return width;
})();
//@ Ox.UI.PATH <str> Path of Ox UI
Ox.UI.PATH = Ox.PATH + 'UI/';
/*@
Ox.UI.getImageData <f> Returns properties of an Ox UI image
(url) -> <s> Image Name
@*/
Ox.UI.getImageData = Ox.cache(function(url) {
var str = 'data:image/svg+xml;base64,';
return Ox.startsWith(url, str)
? JSON.parse(atob(url.split(',')[1]).match(/<!--(.+?)-->/)[1])
: null;
});
/*@
Ox.UI.getImageURL <f> Returns the URL of an Ox UI image
(name[, color[, theme]]) -> <s> Image URL
name <s> Image name
color <s|[n]> Color name or RGB values
theme <s> Theme name
@*/
Ox.UI.getImageURL = Ox.cache(function(name, color, theme) {
var colorName,
colors = {
marker: {
'#000000': 'videoMarkerBorder',
'#FFFFFF': 'videoMarkerBackground'
},
symbol: {
'#FF0000': 'symbolWarningColor'
}
},
image = Ox.UI.IMAGES[name],
themeData,
type = Ox.toDashes(name).split('-')[0];
color = color || 'default';
theme = theme || Ox.Theme();
themeData = Ox.Theme.getThemeData(theme);
if (type == 'symbol') {
if (Ox.isString(color)) {
colorName = color;
color = themeData[
'symbol' + color[0].toUpperCase() + color.slice(1) + 'Color'
];
}
image = image.replace(/#808080/g, '#' + Ox.toHex(color));
}
Ox.forEach(colors[type], function(name, hex) {
image = image.replace(
new RegExp(hex, 'g'),
'$' + Ox.toHex(themeData[name])
);
});
image = image.replace(/\$/g, '#');
return 'data:image/svg+xml;base64,' + btoa(
image + '<!--' + JSON.stringify(Ox.extend(color ? {
color: colorName
} : {}, {
name: name, theme: theme
})) + '-->'
);
}, {
key: function(args) {
args[1] = args[1] || 'default';
args[2] = args[2] || Ox.Theme();
return JSON.stringify(args);
}
});
//@ Ox.UI.getElement <f> Returns the Ox.Element of a DOM element, or `undefined`
Ox.UI.getElement = function(element) {
return Ox.$elements[$(element).data('oxid')];
};
/*@
Ox.UI.hideScreen <f> Hide and remove Ox UI loading screen
@*/
Ox.UI.hideScreen = function() {
Ox.UI.LoadingScreen.hide();
};
//@ Ox.UI.isElement <f> Returns `true` if a DOM element is an Ox.Element
Ox.UI.isElement = function(element) {
return !!$(element).data('oxid');
};

1117
source/UI/js/Core/URL.js Normal file

File diff suppressed because it is too large Load diff