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

286
source/UI/UI.js Normal file
View file

@ -0,0 +1,286 @@
'use strict';
Ox.load.UI = function(options, callback) {
options = Ox.extend({
hideScreen: true,
loadCSS: true,
showScreen: false,
theme: 'oxlight'
}, options);
var browsers = [
{
name: 'Chrome Frame',
url: 'http://www.google.com/chromeframe/'
},
{
name: 'Chrome',
regexp: /Chrome\/(\d+)\./,
url: 'http://www.google.com/chrome/',
version: 10
},
{
name: 'Firefox',
regexp: /Firefox\/(\d+)\./,
url: 'http://www.mozilla.org/firefox/',
version: 4
},
{
name: 'Safari',
regexp: /Version\/(\d+).*? Safari/,
url: 'http://www.apple.com/safari/',
version: 5
},
{
name: 'WebKit',
regexp: /AppleWebKit\/(\d+)\./,
version: 534
},
{
name: 'Googlebot',
regexp: /Googlebot\/(\d+)\./,
version: 2
},
{
name: 'Internet Explorer',
url: 'http://windows.microsoft.com/en-US/internet-explorer/products/ie/home',
version: 9
}
],
browserSupported = false,
isInternetExplorer = /MSIE/.test(navigator.userAgent);
browsers.forEach(function(browser) {
var match = browser.regexp && browser.regexp.exec(navigator.userAgent);
if (match && match[1] >= browser.version) {
browserSupported = true;
}
});
Ox.UI = {};
Ox.UI.LoadingScreen = (function() {
var $body = Ox.$('body'),
$screen = Ox.$('<div>')
.addClass('OxLoadingScreen')
.css({
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
padding: '4px',
background: 'rgb(' + (
options.theme == 'oxlight' ? '240, 240, 240'
: options.theme == 'oxmedium' ? '144, 144, 144'
: '16, 16, 16'
) + ')',
opacity: 1,
zIndex: 1000
}),
css = {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
margin: 'auto',
MozUserSelect: 'none',
WebkitUserSelect: 'none'
},
loadingInterval;
browserSupported ? showIcon() : showWarning();
function showIcon() {
/*
// SVG transform performs worse than CSS transform
var src = Ox.PATH + 'UI/themes/' + options.theme + '/svg/symbolLoadingAnimated.svg'
Ox.getFile(src, function() {
Ox.$('<img>')
.attr({
src: src
})
.css(Ox.extend({
width: '32px',
height: '32px'
}, css))
.on({
mousedown: function(e) {
e.preventDefault();
}
})
.appendTo(div);
});
*/
var deg = 0,
$element,
src = Ox.PATH + 'UI/themes/' + options.theme + '/svg/symbolLoading.svg'
Ox.getFile(src, function() {
var $icon = Ox.$('<img>')
.attr({
src: src
})
.css(Ox.extend({
width: '32px',
height: '32px'
}, css))
.on({
mousedown: function(e) {
e.preventDefault()
}
})
.appendTo($screen),
deg = 0;
loadingInterval = setInterval(function() {
deg = (deg + 30) % 360;
$icon.css({
MozTransform: 'rotate(' + deg + 'deg)',
OTransform: 'rotate(' + deg + 'deg)',
WebkitTransform: 'rotate(' + deg + 'deg)',
transform: 'rotate(' + deg + 'deg)'
});
}, 83);
});
}
function showWarning() {
var counter = 0;
browsers = browsers.filter(function(browser) {
return browser.url;
});
isInternetExplorer ? browsers.pop() : browsers.shift();
browsers.forEach(function(browser) {
browser.src = Ox.PATH + 'UI/png/browser' + browser.name.replace(' ', '') + '128.png';
Ox.getFile(browser.src, function() {
++counter == browsers.length && showIcons();
});
});
function showIcons() {
var $box = Ox.$('<div>')
.css(Ox.extend({
width: (browsers.length * 72) + 'px',
height: '72px'
}, css))
.appendTo($screen);
browsers.forEach(function(browser, i) {
Ox.$('<a>')
.attr({
href: browser.url,
title: (
browser.name == 'Chrome Frame'
? Ox._('Install') : Ox._('Download')
) + ' ' + browser.name
})
.css({
position: 'absolute',
left: (i * 72) + 'px',
width: '72px',
height: '72px'
})
.append(
Ox.$('<img>')
.attr({
src: browser.src
})
.css(Ox.extend({
width: '64px',
height: '64px',
border: 0,
cursor: 'pointer'
}, css))
.on({
mousedown: function(e) {
e.preventDefault();
}
})
)
.appendTo($box);
});
}
}
return {
hide: function() {
$('.OxLoadingScreen').animate({
opacity: browserSupported ? 0 : 0.9
}, 1000, function() {
if (browserSupported) {
clearInterval(loadingInterval);
$screen.remove();
} else {
$screen.on({
click: function() {
$screen.remove();
}
});
}
});
},
show: function() {
$screen.appendTo($body);
}
};
}());
Ox.documentReady(function() {
Ox.$('body').addClass('OxTheme' + Ox.toTitleCase(options.theme));
options.showScreen && Ox.UI.LoadingScreen.show();
});
loadUI();
function loadUI() {
var path = Ox.PATH + 'UI/jquery/jquery.js?' + Ox.VERSION;
Ox.getFile(path, function() {
path = Ox.PATH + 'UI/json/UI.json?' + Ox.VERSION;
Ox.getJSON(path, function(data) {
var counter = 0, length;
if (!options.loadCSS) {
data.files = data.files.filter(function(file) {
return !Ox.endsWith(file, '.css');
});
}
length = data.files.length;
Ox.UI.IMAGES = data.images;
Ox.UI.THEMES = {};
data.files.forEach(function(file) {
path = Ox.PATH + file + '?' + Ox.VERSION;
if (/\.jsonc$/.test(file)) {
Ox.getJSONC(path, function(data) {
var theme = /\/themes\/(\w+)\/json\//.exec(file)[1];
Ox.UI.THEMES[theme] = data;
++counter == length && initUI();
});
} else {
Ox.getFile(path, function() {
++counter == length && initUI();
});
}
});
});
});
}
function initUI() {
Ox.documentReady(function() {
// fixme: is this the right place to do this?
$.browser.mozilla && Ox.$document.on('dragstart', function() {
return false;
});
if (options.showScreen && options.hideScreen) {
Ox.UI.LoadingScreen.hide();
}
callback(browserSupported);
});
}
};

2932
source/UI/css/UI.css Normal file

File diff suppressed because it is too large Load diff

1538
source/UI/css/theme.css Normal file

File diff suppressed because it is too large Load diff

9266
source/UI/jquery/jquery-1.7.1.js vendored Normal file

File diff suppressed because it is too large Load diff

4
source/UI/jquery/jquery-1.7.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,84 @@
/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
* Licensed under the MIT License (LICENSE.txt).
*
* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
* Thanks to: Seamus Leahy for adding deltaX and deltaY
*
* Version: 3.0.6
*
* Requires: 1.2.2+
*/
(function($) {
var types = ['DOMMouseScroll', 'mousewheel'];
if ($.event.fixHooks) {
for ( var i=types.length; i; ) {
$.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
}
}
$.event.special.mousewheel = {
setup: function() {
if ( this.addEventListener ) {
for ( var i=types.length; i; ) {
this.addEventListener( types[--i], handler, false );
}
} else {
this.onmousewheel = handler;
}
},
teardown: function() {
if ( this.removeEventListener ) {
for ( var i=types.length; i; ) {
this.removeEventListener( types[--i], handler, false );
}
} else {
this.onmousewheel = null;
}
}
};
$.fn.extend({
mousewheel: function(fn) {
return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
},
unmousewheel: function(fn) {
return this.unbind("mousewheel", fn);
}
});
function handler(event) {
var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
event = $.event.fix(orgEvent);
event.type = "mousewheel";
// Old school scrollwheel delta
if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; }
if ( orgEvent.detail ) { delta = -orgEvent.detail/3; }
// New school multidimensional scroll (touchpads) deltas
deltaY = delta;
// Gecko
if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
deltaY = 0;
deltaX = -1*delta;
}
// Webkit
if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
// Add event and delta to the front of the arguments
args.unshift(event, delta, deltaX, deltaY);
return ($.event.dispatch || $.event.handle).apply(this, args);
}
})(jQuery);

View file

@ -0,0 +1,118 @@
'use strict';
/*@
Ox.AudioElement <f> AudioElement Object
@*/
Ox.AudioElement = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
autoplay: false,
preload: 'none',
src: ''
})
.options(options || {})
.update({
src: function() {
self.audio.src = self.options.src;
}
});
self.loadedMetadata = false;
self.paused = true;
self.$audio = $('<audio>')
.attr({src: self.options.src})
.on({
ended: function() {
that.triggerEvent('ended');
},
loadedmetadata: function() {
that.triggerEvent('loadedmetadata', {
duration: self.audio.duration
});
},
seeked: function() {
that.triggerEvent('seeked');
},
seeking: function() {
that.triggerEvent('seeking');
}
})
.appendTo(that);
self.audio = self.$audio[0];
function getset(key, value) {
var ret;
if (Ox.isUndefined(value)) {
ret = self.audio[key];
} else {
self.audio[key] = value;
ret = that;
}
return ret;
}
/*@
currentTime <f> get/set currentTime
@*/
that.currentTime = function() {
var ret;
self.ended = false;
if (arguments.length == 0) {
ret = self.audio.currentTime;
} else {
self.audio.currentTime = arguments[0];
ret = that;
}
return ret;
};
/*@
pause <f> pause
@*/
that.pause = function() {
self.paused = true;
self.audio.pause();
return that;
};
/*@
play <f> play
@*/
that.play = function() {
if (self.ended) {
that.currentTime(0);
self.ended = false;
}
self.paused = false;
self.audio.play();
return that;
};
/*@
src <f> get/set source
@*/
that.src = function() {
var ret;
if (arguments.length == 0) {
ret = self.audio.src;
} else {
self.options.src = arguments[0];
self.audio.src = self.options.src;
ret = that;
}
return ret;
};
/*@
volume <f> get/set volume
@*/
that.volume = function(value) {
return getset('volume', arguments[0]);
};
return that;
};

View file

@ -0,0 +1,390 @@
'use strict';
/*@
Ox.AudioPlayer <f> Generic Audio Player
@*/
Ox.AudioPlayer = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
audio: [],
muted: false,
paused: false,
position: 0,
repeat: 0,
shuffle: false,
time: 'elapsed',
track: 0,
volume: 1,
width: 512
})
.options(options || {})
.update({
audio: function() {
self.options.position = -1;
self.options.track = -1;
that.options({
paused: true,
position: 0,
track: 0
});
},
muted: function() {
self.options.muted = !self.options.muted;
toggleMuted();
},
paused: function() {
self.options.paused = !self.options.paused;
togglePaused();
},
position: function() {
setPosition(self.options.position);
},
repeat: function() {
setRepeat(self.options.repeat);
},
shuffle: function() {
self.options.shuffle = !self.options.shuffle;
toggleShuffle();
},
time: function() {
self.options.time = self.options.time == 'elapsed'
? 'remaining' : 'elapsed';
toggleTime();
},
track: function() {
setTrack(self.options.track);
},
volume: function() {
setVolume(self.options.volume);
},
width: setSizes
})
.addClass('OxAudioPlayer')
.css({width: self.options.width + 'px'})
.bindEvent({
key_0: toggleMuted,
key_equal: function() {
setVolume(Ox.min(self.options.volume + 0.1, 1));
},
key_minus: function() {
setVolume(Ox.max(self.options.volume - 0.1, 0));
},
key_space: togglePaused
});
self.tracks = self.options.audio.length;
self.volume = self.options.muted ? 1 : self.options.volume;
self.$listButton = Ox.MenuButton({
items: self.options.audio.slice(
Math.max(self.options.track - 10, 0),
Math.min(self.options.track + 11, self.tracks)
).map(function(track, index) {
index += Math.max(self.options.track - 10, 0);
return {
id: index.toString(),
title: formatTrack(track),
checked: index == self.options.track
};
}),
maxWidth: 256,
overlap: 'left',
title: 'select',
type: 'image'
})
.addClass('OxListButton')
.appendTo(that);
self.$shuffleButton = Ox.Button(
Ox.extend({overlap: 'left', type: 'image'}, getButtonOptions('shuffle'))
)
.addClass('OxShuffleButton')
.bindEvent({
click: toggleShuffle
})
.appendTo(that);
self.$repeatButton = Ox.Button(
Ox.extend({overlap: 'left', type: 'image'}, getButtonOptions('repeat'))
)
.addClass('OxRepeatButton')
.bindEvent({
click: function() {
setRepeat(
self.options.repeat == 0 ? -1
: self.options.repeat == -1 ? 1
: 0
);
}
})
.appendTo(that);
self.$trackLabel = Ox.Label({
textAlign: 'center',
title: '',
})
.addClass('OxTrackLabel')
.appendTo(that);
self.$playButtons = Ox.ButtonGroup({
buttons: [
{
id: 'current',
title: 'playPrevious',
tooltip: Ox._('Play Current Track')
},
Ox.extend({id: 'play'}, getButtonOptions('play')),
{
id: 'next',
title: 'playNext',
tooltip: Ox._('Play Next Track')
}
],
overlap: 'right',
type: 'image',
})
.addClass('OxPlayButtons')
.bindEvent({
click: function(data) {
if (data.id == 'current') {
setPosition(0);
} else if (data.id == 'play') {
togglePaused();
} else if (data.id == 'next') {
playNext();
}
}
})
.appendTo(that);
self.$positionLabel = Ox.Label({
textAlign: 'center',
title: '00:00:00',
tooltip: Ox._('Show Remaining Time'),
width: 80
})
.addClass('OxPositionLabel')
.bindEvent({
anyclick: toggleTime
})
.appendTo(that);
self.$positionSlider = Ox.Range({
changeOnDrag: true,
max: 1,
min: 0,
step: 0.0000001,
})
.addClass('OxPositionSlider')
.bindEvent({
change: function(data) {
setPosition(data.value * self.duration);
}
})
.appendTo(that);
self.$muteButton = Ox.Button({
overlap: 'right',
title: 'mute',
tooltip: Ox._('Mute'),
type: 'image'
})
.addClass('OxMuteButton')
.bindEvent({
click: toggleMuted
})
.appendTo(that);
self.$volumeLabel = Ox.Label({
textAlign: 'center',
title: '&nbsp;&nbsp;100%',
width: 46
})
.addClass('OxVolumeLabel')
.bindEvent({
anyclick: function() {
setVolume(1);
}
})
.appendTo(that);
self.$volumeSlider = Ox.Range({
changeOnDrag: true,
max: 1,
min: 0,
size: 116,
step: 0.01,
value: self.options.volume,
})
.addClass('OxVolumeSlider')
.bindEvent({
change: function(data) {
setVolume(data.value);
}
})
.appendTo(that);
self.$audio = Ox.AudioElement({
src: self.options.audio[self.options.track].file
})
.bindEvent({
ended: function() {
playNext();
},
loadedmetadata: function(data) {
self.duration = data.duration;
}
})
.appendTo(that);
setTrack(self.options.track);
function formatTrack(data) {
return [
data.name, data.artist, data.album, data.year
].join(' &middot; ');
}
function getButtonOptions(id) {
var options;
if (id == 'mute') {
options = self.options.muted || self.options.volume == 0
? {title: 'unmute', tooltip: Ox._('Unmute')}
: self.options.volume < 1/3
? {title: 'volumeUp', tooltip: Ox._('Mute')}
: self.options.volume < 2/3
? {title: 'volumeDown', tooltip: Ox._('Mute')}
: {title: 'mute', tooltip: Ox._('Mute')};
} else if (id == 'play') {
options = self.options.paused
? {title: 'play', tooltip: Ox._('Play')}
: {title: 'pause', tooltip: Ox._('Pause')};
} else if (id == 'repeat') {
options = self.options.repeat == 0
? {title: 'repeatNone', tooltip: Ox._('Repeat All')}
: self.options.repeat == -1
? {title: 'repeatAll', tooltip: Ox._('Repeat One')}
: {title: 'repeatOne', tooltip: Ox._('Repeat None')};
} else if (id == 'shuffle') {
options = self.options.shuffle
? {title: 'shuffleAll', tooltip: Ox._('Don\'t Shuffle')}
: {title: 'shuffleNone', tooltip: Ox._('Shuffle')};
}
return options;
}
function getNextTrack() {
return self.options.repeat == 1 ? self.options.track
: self.options.track < self.tracks - 1 ? self.options.track + 1
: self.options.repeat == -1 ? 0
: null;
}
function playing() {
self.options.position = self.$audio.currentTime();
setPosition(self.options.position, 'audio')
that.triggerEvent('playing', {position: self.options.position});
}
function playNext() {
var track = getNextTrack();
if (track === null) {
// ...
} else {
setTrack(track);
}
}
function setPosition(position, from) {
self.options.position = position;
if (from != 'audio') {
self.$audio.currentTime(position);
}
self.$positionSlider.options({
value: position / self.duration
});
setTime();
}
function setRepeat(repeat) {
self.options.repeat = repeat;
self.$repeatButton.options(getButtonOptions('repeat'));
}
function setSizes() {
that.css({width: self.options.width + 'px'});
self.$trackLabel.options({width: self.options.width - 46});
self.$positionSlider.options({size: self.options.width - 262});
self.$positionLabel.css({left: self.options.width - 232 + 'px'});
self.$volumeLabel.css({left: self.options.width - 46 + 'px'})
}
function setTime() {
self.$positionLabel.options({
title: Ox.formatDuration(
self.options.time == 'elapsed'
? self.options.position
: self.options.audio[self.options.track].duration
- self.options.position
)
});
}
function setTrack(track) {
self.options.track = track;
self.$trackLabel.options({
title: formatTrack(self.options.audio[track])
});
self.$audio.options({src: self.options.audio[self.options.track].file});
!self.options.paused && self.$audio.play();
that.triggerEvent('track', {track: self.options.track});
}
function setVolume(volume) {
self.options.volume = volume;
if (volume > 0) {
self.volume = volume;
}
self.$audio.volume(volume);
self.$muteButton.options(getButtonOptions('mute'));
self.$volumeSlider.options({value: volume});
self.$volumeLabel.options({
title: '&nbsp;&nbsp;' + Math.round(volume * 100) + '%'
});
}
function toggleMuted() {
self.options.muted = !self.options.muted;
setVolume(self.options.muted ? 0 : self.volume);
}
function togglePaused() {
self.options.paused = !self.options.paused;
self.$playButtons.buttonOptions('play', getButtonOptions('play'));
if (self.options.paused) {
self.$audio.pause();
clearInterval(self.playInterval);
} else {
self.$audio.play();
self.playInterval = setInterval(playing, 100);
}
that.triggerEvent('paused', {paused: self.options.paused});
}
function toggleShuffle() {
self.options.shuffle = !self.options.shuffle;
self.$shuffleButton.options(getButtonOptions('shuffle'))
}
function toggleTime() {
self.options.time = self.options.time == 'remaining'
? 'elapsed' : 'remaining';
setTime();
}
return that;
};

28
source/UI/js/Bar/Bar.js Normal file
View file

@ -0,0 +1,28 @@
'use strict';
/*@
Ox.Bar <f> Bar
options <o> Options object
orientation <s|'horizontal'> Orientation ('horizontal' or 'vertical')
size <n|s|'medium'> can be 'small', 'medium', 'large' or number
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Bar object
@*/
Ox.Bar = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
orientation: 'horizontal',
size: 16
})
.options(options || {})
.addClass('OxBar Ox' + Ox.toTitleCase(self.options.orientation));
self.dimensions = Ox.UI.DIMENSIONS[self.options.orientation];
that.css(self.dimensions[0], '100%')
.css(self.dimensions[1], self.options.size + 'px');
return that;
};

View file

@ -0,0 +1,243 @@
'use strict';
/*@
Ox.Progressbar <f> Progress Bar
options <o|{}> Options object
cancelled <b|false> If true, progress bar is cancelled
paused <b|false> If true, progress bar is paused
progress <n|0> Progress, float between 0 and 1, or -1 for indeterminate
showCancelButton <b|false> If true, show cancel button
showPauseButton <b|false> If true, show pause button
showPercent <b|false> If true, show progress in percent
showRestartButton <b|false> If true, show restart button
showTime <b|false> If true, show remaining time
showTooltips <b|false> If true, buttons have tooltips
width <n|256> Width in px
self <o|{}> Shared private variable
([options[, self]]) -> <o:Ox.Element> Progress Bar
cancel <!> cancelled
complete <!> completed
pause <!> paused
restart <!> restart
resume <!> resumed
@*/
Ox.Progressbar = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
cancelled: false,
paused: false,
progress: 0,
showCancelButton: false,
showPauseButton: false,
showPercent: false,
showRestartButton: false,
showTime: false,
showTooltips: false,
width: 256
})
.options(options || {})
.update({
cancelled: toggleCancelled,
paused: togglePaused,
progress: function() {
self.indeterminate = self.options.progress == -1;
if (self.options.progress != -1) {
self.options.progress = Ox.limit(self.options.progress, 0, 1);
}
!self.options.paused && !self.options.cancelled && setProgress(true);
}
})
.addClass('OxProgressbar')
.css({width: self.options.width - 2 + 'px'});
self.indeterminate = self.options.progress == -1;
self.trackWidth = self.options.width
- self.options.showPercent * 36
- self.options.showTime * 60
- self.options.showPauseButton * 16
- self.options.showCancelButton * 16;
self.$track = $('<div>')
.addClass('OxTrack')
.css({
width: self.trackWidth - 2 + 'px'
})
.appendTo(that);
self.$progress = $('<div>')
.addClass('OxProgress')
.appendTo(self.$track);
if (self.options.showPercent) {
self.$percent = $('<div>')
.addClass('OxText')
.css({width: '36px'})
.appendTo(that);
}
if (self.options.showTime) {
self.$time = $('<div>')
.addClass('OxText')
.css({width: '60px'})
.appendTo(that);
}
if (self.options.showPauseButton) {
self.$pauseButton = Ox.Button({
style: 'symbol',
tooltip: self.options.showTooltips ? [Ox._('Pause'), Ox._('Resume')] : '',
type: 'image',
value: !self.options.paused ? 'pause' : 'redo',
values: ['pause', 'redo']
})
.bindEvent({
click: togglePaused
})
.appendTo(that);
}
if (self.options.showCancelButton) {
self.$cancelButton = Ox.Button(Ox.extend({
style: 'symbol',
type: 'image'
}, self.options.showRestartButton ? {
tooltip: self.options.showTooltips ? [Ox._('Cancel'), Ox._('Restart')] : '',
value: 'close',
values: ['close', 'redo']
} : {
title: 'close',
tooltip: self.options.showTooltips ? Ox._('Cancel') : ''
}))
.bindEvent({
click: toggleCancelled
})
.appendTo(that);
}
setProgress();
!self.options.paused && resume();
function cancel() {
self.options.cancelled = true;
if (self.options.paused) {
self.options.paused = false;
self.$pauseButton && self.$pauseButton.toggle();
}
stop();
that.triggerEvent('cancel');
}
function complete() {
self.complete = true;
stop();
self.paused = false;
that.triggerEvent('complete');
}
function pause() {
self.pauseTime = +new Date();
self.$progress.removeClass('OxAnimate');
($.browser.mozilla || $.browser.opera) && clearInterval(self.interval);
self.$time && self.$time.html(
self.options.cancelled ? Ox._('Cancelled') : Ox._('Paused')
);
}
function restart() {
self.options.cancelled = false;
if (!self.indeterminate) {
self.options.progress = 0;
}
delete self.startTime;
self.$pauseButton && self.$pauseButton.options({disabled: false});
setProgress();
resume();
that.triggerEvent('restart');
}
function resume() {
self.startTime = !self.startTime
? +new Date()
: self.startTime + +new Date() - self.pauseTime;
self.$progress.addClass('OxAnimate');
if ($.browser.mozilla || $.browser.opera) {
self.offset = 0;
self.interval = setInterval(function() {
self.$progress.css({backgroundPosition: --self.offset + 'px 0, 0 0'})
}, 1000 / 32);
}
self.$time && self.$time.html(
self.options.progress ? Ox.formatDuration(that.status().remaining) : Ox._('unknown')
);
}
function setProgress(animate) {
self.$percent && self.$percent.html(
Math.floor(self.options.progress * 100) + '%'
);
self.$time && self.$time.html(
self.options.progress ? Ox.formatDuration(that.status().remaining) : Ox._('unknown')
);
self.$progress.stop().animate({
width: Math.round(14 + Math.abs(self.options.progress) * (self.trackWidth - 16)) + 'px'
}, animate ? 250 : 0, function() {
self.options.progress == 1 && complete();
});
}
function stop() {
pause();
self.$time && self.$time.html(
self.options.cancelled ? Ox._('Cancelled') : Ox._('Complete')
);
if (self.$pauseButton && (self.options.cancelled || self.complete)) {
self.$pauseButton.options({disabled: true});
}
if (self.$cancelButton && (self.complete || !self.options.showRestartButton)) {
self.$cancelButton.options({disabled: true});
}
}
function toggleCancelled(e) {
if (e) {
self.options.cancelled = !self.options.cancelled;
} else if (self.$cancelButton) {
self.$cancelButton.toggle();
}
self.options.cancelled ? cancel() : restart();
that.triggerEvent(self.options.cancelled ? 'cancel' : 'restart');
}
function togglePaused(e) {
if (e) {
self.options.paused = !self.options.paused;
} else if (self.$pauseButton) {
self.$pauseButton.toggle();
}
self.options.paused ? pause() : resume();
that.triggerEvent(self.options.paused ? 'pause' : 'resume');
}
/*@
that.status <f> Returns time elapsed / remaining
() -> <o> status
@*/
that.status = function() {
var elapsed = +new Date() - self.startTime,
remaining = elapsed / self.options.progress * (1 - self.options.progress);
return {
elapsed: Math.floor(elapsed / 1000),
remaining: self.options.progress
? Math.ceil(remaining / 1000)
: Infinity
};
};
return that;
};

View file

@ -0,0 +1,194 @@
'use strict';
/*@
Ox.Resizebar <f> Resizebar
options <o> Options object
collapsed <b|false> Inital collapse state
collapsible <b|true> If true, can be collapsed/expanded
edge <s|left> Edge
elements <a|[]> Elements of the bar
orientation <s|horizontal> Orientation ('horizontal' or 'vertical')
panel <o|null> Panel object
resizeable <b|true> If true, can be resetted to default or original size
resizeable <b|true> If true, can be resized
resize <a|[]> Array of sizes
size <n|0> Default size
tooltip <b|s|false> If true, display tooltip, if string, append it
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Resizebar object
@*/
Ox.Resizebar = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
collapsed: false,
collapsible: false,
defaultSize: null,
edge: 'left',
orientation: 'horizontal',
resettable: false,
resizable: false,
resize: [],
size: 0,
tooltip: false
})
.options(options || {})
.update({
collapsed: function() {
that.css({cursor: getCursor()});
self.$tooltip && self.$tooltip.options({title: getTooltipTitle()});
}
})
.addClass('OxResizebar Ox' + Ox.toTitleCase(self.options.orientation))
.bindEvent(Ox.extend({
dragstart: onDragstart,
drag: onDrag,
dragpause: onDragpause,
dragend: onDragend
}, self.options.resettable ? {
doubleclick: reset,
singleclick: toggle
} : {
anyclick: toggle
}))
.append(Ox.$('<div>').addClass('OxSpace'))
.append(Ox.$('<div>').addClass('OxLine'))
.append(Ox.$('<div>').addClass('OxSpace'));
if (Ox.isString(self.options.tooltip)) {
// FIXME: Use Ox.Element's tooltip
self.$tooltip = Ox.Tooltip({title: getTooltipTitle()});
that.on({
mouseenter: self.$tooltip.show,
mouseleave: self.$tooltip.hide
});
}
self.clientXY = self.options.orientation == 'horizontal'
? 'clientY' : 'clientX';
self.dimensions = Ox.UI.DIMENSIONS[self.options.orientation];
self.edges = Ox.UI.EDGES[self.options.orientation];
self.isLeftOrTop = self.options.edge == 'left' || self.options.edge == 'top';
that.css({cursor: getCursor()});
function getCursor() {
var cursor = '';
if (self.options.collapsed) {
cursor = self.options.orientation == 'horizontal'
? (self.isLeftOrTop ? 's' : 'n')
: (self.isLeftOrTop ? 'e' : 'w');
} else {
if (self.options.resizable) {
cursor = self.options.orientation == 'horizontal'
? 'ns' : 'ew';
} else if (self.options.collapsible) {
cursor = self.options.orientation == 'horizontal'
? (self.isLeftOrTop ? 'n' : 's')
: (self.isLeftOrTop ? 'w' : 'e');
}
}
return cursor + '-resize';
}
function getTooltipTitle() {
var title = '';
if (self.options.collapsed) {
title = Ox._('Click to show');
} else {
if (self.options.resizable) {
title = Ox._('Drag to resize');
}
if (self.options.collapsible) {
title = title
? Ox._('{0}{1} click to hide', [
title, self.options.resettable ? ',' : ' or'
])
: Ox._('Click to hide');
}
if (self.options.resettable) {
title += ' or doubleclick to reset'
}
}
if (title && self.options.tooltip) {
title += ' ' + self.options.tooltip;
}
return title;
}
function onDragstart(data) {
if (self.options.resizable && !self.options.collapsed) {
Ox.$body.addClass('OxDragging');
self.drag = {
startPos: data[self.clientXY],
startSize: self.options.size
}
}
that.triggerEvent('resizestart', {size: self.options.size});
}
function onDrag(data) {
if (self.options.resizable && !self.options.collapsed) {
var delta = data[self.clientXY] - self.drag.startPos,
size = self.options.size;
self.options.size = Ox.limit(
self.drag.startSize + delta * (self.isLeftOrTop ? 1 : -1),
self.options.resize[0],
self.options.resize[self.options.resize.length - 1]
);
Ox.forEach(self.options.resize, function(value) {
if (
self.options.size >= value - 8
&& self.options.size <= value + 8
) {
self.options.size = value;
return false; // break
}
});
if (self.options.size != size) {
that.css(
self.edges[self.isLeftOrTop ? 2 : 3],
self.options.size + 'px'
)
.triggerEvent('resize', {size: self.options.size});
}
}
}
function onDragpause() {
if (self.options.resizable && !self.options.collapsed) {
if (self.options.size != self.drag.startSize) {
that.triggerEvent('resizepause', {size: self.options.size});
}
}
}
function onDragend() {
if (self.options.resizable && !self.options.collapsed) {
Ox.$body.removeClass('OxDragging');
if (self.options.size != self.drag.startSize) {
that.triggerEvent('resizeend', {size: self.options.size});
}
}
}
function reset() {
if (self.options.resizable && !self.options.collapsed) {
that.triggerEvent('reset');
}
}
function toggle() {
if (self.options.collapsible) {
self.options.collapsed = !self.options.collapsed;
that.css({cursor: getCursor()});
self.$tooltip && self.$tooltip.hide(function() {
self.$tooltip.options({title: getTooltipTitle()});
});
that.triggerEvent('toggle', {collapsed: self.options.collapsed});
}
}
return that;
};

View file

@ -0,0 +1,34 @@
'use strict';
/*@
Ox.Tabbar <f> Tabbar
options <o> Options object
selected <n|0> selected item
tabs <a|[]> tabs
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Bar> Tabbar object
@*/
Ox.Tabbar = function(options, self) {
self = self || {};
var that = 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;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,832 @@
'use strict';
/*@
Ox.CalendarEditor <f> Calendar Editor
([options[, self]]) -> <o:Ox.SplitPanel> Calendar Editor
options <o> Options
self <o> Shared private variable
loadlist <!> loadlist
@*/
Ox.CalendarEditor = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
addEvent: null,
collapsible: false,
editEvent: null,
events: [],
hasMatches: false,
height: 256,
mode: 'add',
pageLength: 100,
removeEvent: null,
selected: '',
showControls: false,
sort: [{key: 'name', operator: '+'}],
width: 256
})
.options(options || {})
.update({
height: function() {
// fixme: should be .resizeList
self.$list.size();
self.$calendar.resizeCalendar();
},
width: function() {
self.$calendar.resizeCalendar();
}
})
.css({
width: self.options.width + 'px',
height: self.options.height + 'px'
});
self.durationCache = {};
self.durationSize = {
86400: 10,
31622400: 12
};
self.columns = [
{
format: function(value, data) {
var eventDuration = (Ox.parseDate(data.end) - Ox.parseDate(data.start)) / 1000,
iconSize = 8;
Ox.forEach(self.durationSize, function(size, duration) {
if (eventDuration > duration) {
iconSize = size;
} else {
return false; // break
}
});
return data.type
? $('<div>')
.addClass('OxEvent Ox' + Ox.toTitleCase(data.type))
.css({
width: iconSize + 'px',
height: iconSize + 'px',
margin: [0, 0, 0, -3].map(function(v) {
return v + (14 - iconSize) / 2 + 'px';
}).join(' '),
borderRadius: '2px'
})
: '';
},
id: 'type',
operator: '+',
title: Ox._('Type'),
titleImage: 'icon',
visible: true,
width: 16
},
{
format: function(value, data) {
return data.type
? value
: $('<span>').addClass('OxWarning').html(value);
},
id: 'name',
operator: '+',
removable: false,
title: Ox._('Name'),
visible: true,
width: 144
},
{
editable: false,
format: function(value) {
return value.join('; ');
},
id: 'alternativeNames',
operator: '+',
title: Ox._('Alternative Names'),
visible: true,
width: 144
},
{
id: 'start',
operator: '-',
sort: function(value) {
return Ox.parseDate(value);
},
title: Ox._('Start'),
visible: true,
width: 144
},
{
id: 'end',
operator: '-',
sort: function(value) {
return Ox.parseDate(value);
},
title: Ox._('End'),
visible: true,
width: 144
},
{
format: function(value, data) {
var key = data.start + ' - ' + data.end;
if (!self.durationCache[key]) {
self.durationCache[key] = data.start
? Ox.formatDateRangeDuration(data.start, data.end, true)
: '';
}
return self.durationCache[key];
},
id: 'id',
operator: '-',
sort: function(value, data) {
return Ox.parseDate(data.end) - Ox.parseDate(data.start);
},
title: Ox._('Duration'),
visible: true,
width: 256
},
{
format: function(value) {
return Ox.encodeHTMLEntities(value);
},
id: 'user',
operator: '+',
title: Ox._('User'),
visible: false,
width: 96
},
{
format: function(value) {
return value.replace('T', ' ').replace('Z', '');
},
id: 'created',
operator: '-',
title: Ox._('Date Created'),
visible: false,
width: 128
},
{
format: function(value) {
return value.replace('T', ' ').replace('Z', '');
},
id: 'modified',
operator: '-',
title: Ox._('Date Modified'),
visible: false,
width: 128
}
];
self.options.hasMatches && self.columns.push({
align: 'right',
id: 'matches',
operator: '-',
title: Ox._('Matches'),
visible: true,
width: 64
});
self.$listToolbar = Ox.Bar({
size: 24
});
self.$findElement = Ox.FormElementGroup({
elements: [
self.$findSelect = Ox.Select({
items: [
{id: 'all', title: Ox._('Find: All')},
{id: 'name', title: Ox._('Find: Name')},
{id: 'alternativeNames', title: Ox._('Find: Alternative Names')},
],
overlap: 'right',
type: 'image'
})
.bindEvent({
change: function(data) {
var key = data.value,
value = self.$findInput.value();
value && updateList(key, value);
}
}),
self.$findInput = Ox.Input({
clear: true,
placeholder: Ox._('Find in List'),
width: 234
})
.bindEvent({
submit: function(data) {
var key = self.$findSelect.value(),
value = data.value;
updateList(key, value);
}
})
]
})
.css({float: 'right', margin: '4px'})
.appendTo(self.$listToolbar);
self.$list = Ox.TableList({
columns: self.columns,
columnsRemovable: true,
columnsVisible: true,
// we have to clone so that when self.options.events changes,
// self.$list.options({items: self.options.events}) still
// registers as a change
items: Ox.clone(self.options.events, true),
max: 1,
min: 0,
pageLength: self.options.pageLength,
scrollbarVisible: true,
selected: self.options.selected ? [self.options.selected] : [],
sort: self.options.sort,
unique: 'id'
})
.bindEvent({
'delete': removeEvent,
init: initList, // fixme: won't fire from static list
key_0: function() {
self.$calendar.panToEvent();
},
key_equal: function() {
self.$calendar.zoom(1);
},
key_minus: function() {
self.$calendar.zoom(-1);
},
key_shift_0: function() {
self.$calendar.zoomToEvent();
},
load: function() {
that.triggerEvent('loadlist');
},
open: openItem,
select: function(data) {
selectItem(data);
}
});
self.$listStatusbar = Ox.Bar({
size: 16
});
self.$status = Ox.Element()
.css({paddingTop: '2px', margin: 'auto', fontSize: '9px', textAlign: 'center'})
.html(
Ox.formatCount(self.options.events, Ox._('Event'), Ox._('Events'))
)
.appendTo(self.$listStatusbar);
self.$calendar = Ox.Calendar({
date: new Date(0),
//events: Ox.clone(self.options.events, true),
events: self.options.events.filter(function(event) {
return !!event.type;
}),
height: self.options.height,
showControls: self.options.showControls,
showToolbar: true,
showZoombar: true,
width: self.options.width - 514,
zoom: 4
})
.bindEvent({
resize: function(data) {
// triggered by SplitPanel
self.$calendar.resizeCalendar();
},
select: selectEvent
});
self.$eventTitlebar = Ox.Bar({
size: 24
});
self.$eventTitle = $('<div>')
.hide()
.appendTo(self.$eventTitlebar);
self.$eventName = Ox.Label({
title: '',
width: 228
})
.css({float: 'left', margin: '4px'})
.bindEvent({
singleclick: function() {
self.$calendar.panToEvent();
},
doubleclick: function() {
self.$calendar.zoomToEvent();
}
})
.appendTo(self.$eventTitle);
self.$deselectEventButton = Ox.Button({
title: 'close',
tooltip: Ox._('Done'),
type: 'image'
})
.css({float: 'left', margin: '4px 4px 4px 0'})
.bindEvent({
click: function() {
self.$list.options({selected: []});
// FIXME: list doesn't fire select event
selectItem({ids: []});
}
})
.appendTo(self.$eventTitle);
self.$eventData = Ox.Element();
self.$eventForm = Ox.Form({
items: [
self.$nameInput = Ox.Input({
id: 'name',
label: Ox._('Name'),
labelWidth: 64,
width: 240
}),
self.$alternativeNamesInput = Ox.ArrayInput({
id: 'alternativeNames',
label: Ox._('Alternative Names'),
max: 10,
values: [],
width: 240
}),
Ox.Select({
id: 'type',
items: [
{id: 'date', title: Ox._('Date')},
{id: 'place', title: Ox._('Place')},
{id: 'person', title: Ox._('Person')},
{id: 'other', title: Ox._('Other')}
],
label: Ox._('Type'),
labelWidth: 64,
width: 240
}),
self.$startInput = Ox.Input({
id: 'start',
label: Ox._('Start'),
labelWidth: 64,
width: 240
}),
self.$endInput = Ox.Input({
id: 'end',
label: Ox._('End'),
labelWidth: 64,
width: 240
}),
self.$durationInput = Ox.Input({
disabled: true,
id: 'durationText',
label: Ox._('Duration'),
labelWidth: 64,
width: 240
})
],
width: 240
})
.css({margin: '8px'})
.hide()
.bindEvent({
change: function(data) {
var exists = false, values;
if (['name', 'alternativeNames'].indexOf(data.id) > -1) {
exists = '';
values = data.id == 'name' ? [data.data.value] : data.data.value;
Ox.forEach(self.options.events, function(event) {
Ox.forEach(values, function(value) {
if (event.type && (
event.name == data.data.value
|| event.alternativeNames.indexOf(data.data.value) > -1
)) {
exists = value;
return false; // break
}
});
if (exists) {
return false; // break
}
});
}
if (data.id == 'name') {
if (!exists) {
// FIXME: can we change this to data.value?
editEvent('name', data.data.value);
} else {
self.$nameInput.addClass('OxError');
}
} else if (data.id == 'alternativeNames') {
if (!exists) {
editEvent('alternativeNames', data.data.value);
} else {
self.$alternativeNamesInput.setErrors([exists]);
}
} else if (data.id == 'type') {
editEvent('type', data.data.value);
} else if (data.id == 'start') {
editEvent('start', data.data.value);
} else if (data.id == 'end') {
editEvent('end', data.data.value);
}
}
})
.appendTo(self.$eventData);
if (self.options.hasMatches) {
self.$matchesInput = Ox.Input({
disabled: true,
id: 'matches',
label: Ox._('Matches'),
labelWidth: 64,
type: 'int',
width: 240
})
.css({margin: '8px'})
.hide()
.appendTo(self.$eventData);
}
self.$eventStatusbar = Ox.Bar({
size: 24
});
self.$removeEventButton = Ox.Button({
title: Ox._('Remove Event'),
width: 90
})
.css({float: 'left', margin: '4px'})
.bindEvent({
click: removeEvent
})
.hide()
.appendTo(self.$eventStatusbar);
self.$newEventButton = Ox.Button({
title: Ox._('New Event'),
width: 70
})
.css({float: 'right', margin: '4px'})
.bindEvent({
click: addEvent
})
.appendTo(self.$eventStatusbar);
if (self.options.mode == 'define') {
self.$defineEventButton = Ox.Button({
title: Ox._('Define Event'),
width: 80
})
.css({float: 'right', margin: '4px 0 4px 0'})
.bindEvent({
click: function() {
if (this.options('title') == Ox._('Define Event')) {
defineEvent();
} else {
clearEvent();
}
}
})
.hide()
.appendTo(self.$eventStatusbar);
}
that.setElement(
Ox.SplitPanel({
elements: [
{
collapsible: self.options.collapsible,
element: Ox.SplitPanel({
elements: [
{
element: self.$listToolbar,
size: 24
},
{
element: self.$list
},
{
element: self.$listStatusbar,
size: 16
}
],
orientation: 'vertical'
}),
resizable: true,
resize: [256, 384, 512],
size: 256
},
{
element: self.$calendar
},
{
collapsible: self.options.collapsible,
element: Ox.SplitPanel({
elements: [
{
element: self.$eventTitlebar,
size: 24
},
{
element: self.$eventData
},
{
element: self.$eventStatusbar,
size: 24
}
],
orientation: 'vertical'
})
.bindEvent({
resize: function(data) {
self.$eventName.options({width: data.size - 28});
// fixme: pass width through form
self.$eventForm.options('items').forEach(function($item) {
$item.options({width: data.size - 16});
});
self.$matchesInput.options({width: data.size - 16});
}
}),
resizable: true,
resize: [256, 384],
size: 256
}
],
orientation: 'horizontal'
})
.addClass('OxCalendarEditor')
);
// if loaded with selection, set calendar and form
self.options.selected && self.$list.triggerEvent({
select: {ids: [self.options.selected]}
});
function addEvent() {
Ox.Log('Calendar', 'ADD', self.$calendar.getBounds())
var bounds = self.$calendar.getBounds(),
middle = +self.$calendar.options('date'),
startTime = +new Date((+bounds.startTime + middle) / 2),
endTime = +new Date((+bounds.endTime + middle) / 2),
event = {},
i = 1;
event.name = Ox._('Untitled');
while (nameExists(event.name)) {
event.name = Ox._('Untitled') + ' [' + (++i) + ']';
};
event.alternativeNames = [];
event.type = 'other';
event.start = Ox.formatDate(startTime, '%Y-%m-%d %H:%M:%S', true);
event.end = Ox.formatDate(endTime, '%Y-%m-%d %H:%M:%S', true);
Ox.Log('Calendar', event);
self.$newEventButton.options({disabled: true});
self.$removeEventButton.options({disabled: true});
self.options.addEvent(encodeValues(event), function(result) {
if (result.status.code == '200') {
event.id = result.data.id;
event.created = result.data.created;
event.modified = result.data.modified;
if (self.options.hasMatches) {
event.matches = result.data.matches;
}
self.options.events.push(event);
// var time0 = +new Date()
self.$list.options({items: Ox.clone(self.options.events, true)});
// Ox.Log('Calendar', 'TIME TO SET LIST OPTIONS:', +new Date() - time0);
self.$calendar.addEvent(event);
selectEvent(event);
self.$nameInput.focusInput(true);
self.$newEventButton.options({disabled: false});
self.$removeEventButton.options({disabled: false});
} else {
// FIXME
// alert(result.status.text);
}
});
}
function clearEvent() {
var event = Ox.getObjectById(self.options.events, self.options.selected),
values = {
id: self.options.selected,
alternativeNames: [], type: '',
start: '', end: ''
};
self.$defineEventButton.options({disabled: true, title: Ox._('Clear Event')});
self.options.editEvent(encodeValues(values), function() {
Ox.forEach(values, function(value, key) {
self.$list.value(self.options.selected, key, value);
});
self.$list.reloadList();
self.$calendar.removeEvent();
self.$eventForm.hide();
self.$defineEventButton.options({disabled: false, title: Ox._('Define Event')});
});
}
function decodeValues(place) {
return Ox.map(place, function(value) {
var type = Ox.typeOf(value);
return type == 'string' ? Ox.decodeHTMLEntities(value)
: type == 'array' ? Ox.map(value, function(value) {
return decodeValues(value);
})
: value;
});
}
function defineEvent() {
var bounds = self.$calendar.getBounds(),
middle = +self.$calendar.options('date'),
startTime = +new Date((+bounds.startTime + middle) / 2),
endTime = +new Date((+bounds.endTime + middle) / 2),
event = Ox.getObjectById(self.options.events, self.options.selected);
event.name = self.$list.value(self.options.selected, 'name');
event.alternativeNames = [];
event.type = 'other';
event.start = Ox.formatDate(startTime, '%Y-%m-%d %H:%M:%S', true);
event.end = Ox.formatDate(endTime, '%Y-%m-%d %H:%M:%S', true);
self.$list.options({items: Ox.clone(self.options.events, true)});
self.$calendar.addEvent(event);
self.$defineEventButton.options({title: Ox._('Clear Event')});
}
function editEvent(key, value) {
var id = self.selectedEvent,
index = Ox.getIndexById(self.options.events, id),
data = {id: id};
data[key] = value;
self.options.editEvent(encodeValues(data), function(result) {
if (result.status.code == 200) {
self.options.events[index][key] = value;
if (self.options.hasMatches) {
self.options.events[index].matches = result.data.matches;
}
self.$list.options({items: Ox.clone(self.options.events, true)});
self.$calendar.editEvent(id, key, value);
if (key == 'name') {
self.$eventName.options({title: value});
} else if (['start', 'end'].indexOf(key) > -1) {
self.$durationInput.value(
Ox.formatDateRangeDuration(
self.$startInput.value(),
self.$endInput.value()
|| Ox.formatDate(new Date(), '%Y-%m-%d %H%:%M:%S'),
true
)
);
}
self.options.hasMatches && self.$matchesInput.value(result.data.matches);
self.options.mode == 'define' && self.$removeEventButton.options({
disabled: !!result.data.matches
});
} else {
// ...
}
});
}
function encodeValues(place) {
return Ox.map(place, function(value) {
var type = Ox.typeOf(value);
return type == 'string' ? Ox.encodeHTMLEntities(value)
: type == 'array' ? Ox.map(value, function(value) {
return encodeValues(value);
})
: value;
});
}
function initList(data) {
self.$status.html(
Ox.formatCount(data.items, Ox._('Event'), Ox._('Events'))
);
}
function nameExists(name) {
var exists = false;
Ox.forEach(self.options.events, function(event) {
if (
event.name == name
|| event.alternativeNames.indexOf(name) > -1
) {
exists = true;
return false; // break
}
});
return exists;
}
function openItem(data) {
selectItem(data);
self.$calendar.zoomToEvent(data.ids[0]);
}
function removeEvent() {
var id = self.selectedEvent,
index = Ox.getIndexById(self.options.events, id);
self.$newEventButton.options({disabled: true});
self.$removeEventButton.options({disabled: true});
self.options.removeEvent({id: id}, function(result) {
if (result.status.code == '200') {
selectEvent({});
self.options.events.splice(index, 1);
var time0 = +new Date();
self.$list.options({items: Ox.clone(self.options.events, true)});
Ox.Log('Calendar', 'TIME TO SET LIST OPTIONS:', +new Date() - time0);
self.$calendar.removeEvent();
self.$newEventButton.options({disabled: false});
self.$removeEventButton.options({disabled: false});
} else {
// FIXME
// alert(result.status.text);
}
});
}
function selectEvent(event) {
// Select event on calendar
var isUndefined = !!self.options.selected
&& !self.$list.value(self.options.selected, 'type');
self.selectedEvent = event.id || '';
if (!self.selectedEvent && isUndefined) {
// deselect triggered by selecting an undefined item,
// so do nothing
} else {
self.options.selected = self.selectedEvent;
self.$list.options({
selected: self.options.selected ? [self.options.selected] : []
});
selectItem({ids: self.$list.options('selected')}, event);
}
}
function selectItem(data, event) {
// Select item in list
var fromCalendar = !!event, isUndefined, selectedEvent;
self.options.selected = data.ids.length ? data.ids[0] : '';
event = event || (
self.options.selected
? Ox.getObjectById(self.options.events, self.options.selected)
: {}
);
isUndefined = !fromCalendar && !!self.options.selected && !event.type;
if (!fromCalendar) {
selectedEvent = self.options.selected && !isUndefined
? self.options.selected
: '';
self.$calendar.options({selected: selectedEvent});
selectedEvent && self.$calendar.panToEvent();
}
if (self.options.selected) {
self.$eventName.options({title: event.name || ''});
self.$eventTitle.show();
if (!isUndefined) {
self.$eventForm.values(
decodeValues(Ox.extend({}, event, {
end: event.current ? '' : event.end,
durationText: Ox.formatDateRangeDuration(
event.start, event.end, true
)
}))
).show();
} else {
self.$eventForm.hide();
}
self.options.hasMatches && self.$matchesInput.value(event.matches || 0).show();
self.options.mode == 'define' && self.$defineEventButton.options({
disabled: !event.matches,
title: isUndefined ? 'Define Event' : 'Clear Event'
}).show();
self.$removeEventButton.options({
disabled: self.options.mode == 'define' && !!event.matches
}).show();
} else {
self.$eventTitle.hide();
self.$eventForm.hide();
self.options.hasMatches && self.$matchesInput.hide();
self.options.mode == 'define' && self.$defineEventButton.hide();
self.$removeEventButton.hide();
}
}
function updateList(key, value) {
var events;
if (value === '') {
events = Ox.clone(self.options.events);
} else {
events = [];
self.options.events.forEach(function(event) {
if ((
['all', 'name'].indexOf(key) > -1
&& event.name.toLowerCase().indexOf(value) > -1
) || (
['all', 'alternativeNames'].indexOf(key) > -1
&& event.alternativeNames.join('\n').toLowerCase().indexOf(value) > -1
)) {
events.push(event)
}
});
}
self.$list.options({items: events});
}
return that;
};

View file

@ -0,0 +1,291 @@
'use strict';
/*@
Ox.DocPage <f> DocPage
options <o> Options object
item <o> doc item
replace <[[]]|[]> See Ox.SyntaxHighlighter
stripComments <b|false> If true, strip comments in source code
self <o> Shared private variable
([options[, self]]) -> <o:Ox.SplitPanel> DocPage object
example <!> Fires when an example was selected
id <s> Id of the selected example
@*/
Ox.DocPage = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
item: {},
replace: []
})
.options(options || {})
.css({
overflow: 'auto'
});
self.$toolbar = Ox.Bar({size: 24});
self.$homeButton = Ox.Button({
title: 'home',
tooltip: Ox._('Home'),
type: 'image'
})
.css({float: 'left', margin: '4px 2px 4px 4px'})
.bindEvent({
click: function() {
that.triggerEvent('close');
}
})
.appendTo(self.$toolbar);
self.$title = Ox.Label({
style: 'square',
title: self.options.item.name
})
.addClass('OxMonospace')
.css({
float: 'left',
height: '13px',
paddingTop: '1px',
borderRadius: '4px',
margin: '4px 2px 4px 2px'
})
.appendTo(self.$toolbar)
if (self.options.item.examples) {
self.$examplesMenu = Ox.MenuButton({
items: self.options.item.examples,
title: Ox._('Examples...'),
})
.css({float: 'right', margin: '4px 4px 4px 2px'})
.bindEvent({
click: function(data) {
that.triggerEvent('example', {id: data.id});
}
})
.appendTo(self.$toolbar);
}
self.$page = Ox.Container().addClass('OxDocPage OxDocument OxSelectable');
that.setElement(
Ox.SplitPanel({
elements: [
{element: self.$toolbar, size: 24},
{element: self.$page}
],
orientation: 'vertical'
})
.addClass('OxDocPage')
);
getItem(self.options.item, 0).forEach(function($element) {
self.$page.append($element);
});
function getItem(item, level, name) {
Ox.Log('Core', 'getItem', item, level, name);
var $elements = [
Ox.$('<div>')
.css({paddingLeft: (level ? level * 32 - 16 : 0) + 'px'})
.html(
'<code><b>' + (name || item.name) + '</b> '
+ '&lt;' + item.types.join('&gt;</code> or <code>&lt;') + '&gt; </code>'
+ (item['class'] ? '(class: <code>' + item['class'] + '</code>) ' : '')
+ (item['default'] ? '(default: <code>' + item['default'] + '</code>) ' : '')
+ Ox.sanitizeHTML(item.summary)
)
],
sections = ['description'].concat(
item.order || ['returns', 'arguments', 'properties']
).concat(['events', 'tests', 'source']),
index = {
events: sections.indexOf('events') + 1 + (
item.inheritedproperties ? item.inheritedproperties.length : 0
),
properties: sections.indexOf('properties') + 1 || 1
};
['properties', 'events'].forEach(function(key) {
var key_ = 'inherited' + key;
if (item[key_]) {
Array.prototype.splice.apply(sections, [index[key], 0].concat(
item[key_].map(function(v, i) {
var section = key + ' inherited from <code>'
+ v.name + '</code>';
item[section] = v[key];
return section;
})
));
}
});
sections.forEach(function(section) {
var className = 'OxLine' + Ox.uid(),
isExpanded = !Ox.contains(section, 'inherited');
if (item[section]) {
if (section == 'description') {
$elements.push(Ox.$('<div>')
.css({
paddingTop: (level ? 0 : 8) + 'px',
borderTopWidth: level ? 0 : '1px',
marginTop: (level ? 0 : 8) + 'px',
marginLeft: (level * 32) + 'px'
})
.html(Ox.sanitizeHTML(item.description))
);
} else {
$elements.push(Ox.$('<div>')
.css({
paddingTop: (level ? 0 : 8) + 'px',
borderTopWidth: level ? 0 : '1px',
marginTop: (level ? 0 : 8) + 'px',
marginLeft: (level * 32) + 'px'
})
.append(
Ox.$('<img>')
.attr({
src: isExpanded
? Ox.UI.getImageURL('symbolDown')
: Ox.UI.getImageURL('symbolRight')
})
.css({
width: '12px',
height: '12px',
margin: '0 4px -1px 0'
})
.on({
click: function() {
var $this = $(this),
isExpanded = $this.attr('src') == Ox.UI.getImageURL('symbolRight');
$this.attr({
src: isExpanded
? Ox.UI.getImageURL('symbolDown')
: Ox.UI.getImageURL('symbolRight')
});
$('.' + className).each(function() {
var $this = $(this), isHidden = false;
$this[isExpanded ? 'removeClass' : 'addClass'](className + 'Hidden');
if (isExpanded) {
Ox.forEach(this.className.split(' '), function(v) {
if (/Hidden$/.test(v)) {
isHidden = true;
return false; // break
}
});
!isHidden && $this.show();
} else {
$this.hide();
}
});
}
})
)
.append(
Ox.$('<span>')
.addClass('OxSection')
.html(
Ox.contains(section, 'inherited')
? section[0].toUpperCase() + section.slice(1)
: Ox.toTitleCase(
section == 'returns' ? 'usage' : section
)
)
)
);
if (section == 'tests') {
item.tests.forEach(function(test) {
var isAsync = test.expected && /(.+Ox\.test\()/.test(test.statement);
$elements.push(
Ox.$('<div>')
.addClass(className)
.css({marginLeft: (level * 32 + 16) + 'px'})
.html(
'<code><b>&gt;&nbsp;'
+ Ox.encodeHTMLEntities(test.statement)
.replace(/ /g, '&nbsp;')
.replace(/\n/g, '<br>\n&nbsp;&nbsp;')
+ '</b>'
+ (
test.passed === false && isAsync
? ' <span class="OxFailed"> // actual: '
+ Ox.encodeHTMLEntities(test.actual)
+ '</span>'
: ''
)
+ '</code>'
)
);
if (test.expected) {
$elements.push(
Ox.$('<div>')
.addClass(className)
.css({marginLeft: (level * 32 + 16) + 'px'})
.html(
'<code>'
+ Ox.encodeHTMLEntities(
test.passed === false && !isAsync
? test.actual : test.expected
)
+ (
test.passed === false && !isAsync
? ' <span class="OxFailed"> // expected: '
+ Ox.encodeHTMLEntities(test.expected)
+ '</span>'
: ''
)
+ '</code>'
)
);
}
});
} else if (section == 'source') {
// fixme: not the right place to fix path
$elements.push(Ox.$('<div>')
.addClass(className)
.css({marginLeft: 16 + 'px'})
.html(
'<code><b>'
+ self.options.item.file.replace(Ox.PATH, '')
+ '</b>:' + self.options.item.line + '</code>'
)
);
$elements.push(
Ox.SyntaxHighlighter({
replace: self.options.replace,
showLineNumbers: !self.options.stripComments,
source: item.source,
stripComments: self.options.stripComments,
offset: self.options.item.line
})
.addClass(className)
.css({
borderWidth: '1px',
marginTop: '8px'
})
);
} else {
item[section].forEach(function(v) {
var name = section == 'returns' ?
item.name + v.signature
+ ' </b></code>returns<code> <b>'
+ (v.name || '') : null;
$elements = $elements.concat(
Ox.map(getItem(v, level + 1, name), function($element) {
$element.addClass(className);
if (!isExpanded) {
$element.addClass(className + 'Hidden').hide();
}
return $element;
})
);
});
}
}
}
});
return $elements;
}
return that;
};

View file

@ -0,0 +1,401 @@
'use strict';
/*@
Ox.DocPanel <f> Documentation Panel
options <o> Options object
collapsible <b|false> If true, the list can be collabsed
element <e> Default content
expanded <b> If true, modules and sections are expanded in the list
files <a|[]> Files to parse for docs (alternative to items option)
getModule <f> Returns module for given item
getSection <f> Returns section for given item
items <a|[]> Array of doc items (alternative to files option)
path <s|''> Path prefix
references <r|null> RegExp to find references
replace <[[]]|[]> See Ox.SyntaxHighlighter
resizable <b|true> If true, list is resizable
resize <a|[128, 256, 384]> List resize positions
runTests <b|false> If true, run tests
selected <s|''> Id of the selected item
showLoading <b|false> If true, show loading icon when parsing files
showTests <b|false> If true, show test results in list
showTooltips <b|false> If true, show test result tooltips in list
size <s|256> Default list size
stripComments <b|false> If true, strip comments in source code
self <o> Shared private variable
([options[, self]]) -> <o:Ox.SplitPanel> Documentation Panel
load <!> Fires once all docs are loaded
items [o] Array of doc items
tests <!> Fires once all tests finished
results [o] Array of results
select <!> select
@*/
Ox.DocPanel = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
collapsible: false,
element: null,
examples: [],
examplesPath: '',
expanded: false,
files: [],
getModule: function(item) {
return item.file.replace(self.options.path, '');
},
getSection: function(item) {
return item.section;
},
items: [],
path: '',
references: null,
replace: [],
resizable: false,
resize: [128, 256, 384],
results: null,
runTests: false,
selected: '',
showLoading: false,
showTests: false,
showTooltips: false,
size: 256,
stripComments: false
})
.options(options || {})
.update({
selected: function() {
selectItem(self.options.selected);
}
});
self.$list = Ox.Element();
self.$toolbar = Ox.Bar({size: 24}).css({textAlign: 'center'});
self.$sidebar = Ox.SplitPanel({
elements: [
{element: Ox.Element()},
{element: Ox.Element(), size: 24}
],
orientation: 'vertical'
});
self.$page = Ox.Element();
self.$testsStatus = $('<div>')
.css({marginTop: '5px', textAlign: 'center'})
.appendTo(self.$toolbar);
if (!self.options.results) {
self.options.results = {};
self.$testsButton = Ox.Button({title: Ox._('Run Tests')})
.css({margin: '4px auto'})
.bindEvent({click: runTests})
.appendTo(self.$toolbar);
self.$testsStatus.hide();
} else {
self.$testsStatus.html(formatResults());
}
that.setElement(
self.$panel = Ox.SplitPanel({
elements: [
{
collapsible: self.options.collapsible,
element: self.$sidebar,
resizable: self.options.resizable,
resize: self.options.resize,
size: self.options.size
},
{
element: self.$page
}
],
orientation: 'horizontal'
})
);
if (self.options.files && self.options.files.length) {
setTimeout(function() {
self.options.showLoading && showLoadingScreen();
self.options.files = Ox.makeArray(self.options.files);
self.options.items = Ox.doc(self.options.files.map(function(file) {
return self.options.path + file;
}), function(docItems) {
self.options.items = docItems;
getExamples(function() {
self.options.showLoading && hideLoadingScreen();
self.$sidebar.replaceElement(1, self.$toolbar);
renderList();
self.options.runTests && runTests();
that.triggerEvent('load', {items: self.options.items});
});
});
}, 250); // 0 timeout may fail in Firefox
} else {
getExamples(function() {
self.$sidebar.replaceElement(1, self.$toolbar);
renderList();
self.options.runTests && runTests();
});
}
function formatResults() {
var results = self.options.results[''],
tests = results.passed + results.failed;
return tests + ' test' + (tests == 1 ? '' : 's') + ', '
+ results.passed + ' passed, ' + results.failed + ' failed';
}
function getExamples(callback) {
var i = 0;
if (self.options.examples && self.options.examples.length) {
self.options.examples.forEach(function(example) {
var path = self.options.examplesPath + example;
Ox.get(path + '/index.html', function(html) {
var match = html.match(/<title>(.+)<\/title>/),
title = match ? match[1] : Ox._('Untitled');
Ox.get(path + '/js/example.js', function(js) {
var references = js.match(self.options.references);
if (references) {
Ox.unique(references).forEach(function(reference) {
var item = getItemByName(reference);
if (item) {
item.examples = (
item.examples || []
).concat({
id: example.split('/').pop(),
title: title
});
}
});
}
if (++i == self.options.examples.length) {
self.options.items.forEach(function(item) {
if (item.examples) {
item.examples.sort(function(a, b) {
a = a.title.toLowerCase();
b = b.title.toLowerCase();
return a < b ? -1 : a > b ? 1 : 0;
});
}
});
callback();
}
});
});
});
} else {
callback();
}
}
function getIcon(id, expanded) {
var $icon = null, results = self.options.results[id];
if (!Ox.isEmpty(self.options.results)) {
$icon = Ox.Theme.getColorImage(
'symbol' + (expanded === true ? 'Down' : expanded === false ? 'Right' : 'Center'),
!results ? '' : results.failed === 0 ? 'passed' : 'failed',
self.options.showTooltips ? getTooltip(results) : null
);
} else if (!Ox.endsWith(id, '/')) {
$icon = $('<img>').attr({src: Ox.UI.getImageURL('symbolCenter')});
}
return $icon;
}
function getItemByName(name) {
var item = null;
Ox.forEach(self.options.items, function(v) {
if (v.name == name) {
item = v;
return false; // break
}
});
return item;
}
function getTooltip(results) {
return results
? results.passed + ' test'
+ (results.passed == 1 ? '' : 's') + ' passed'
+ (results.failed
? ', ' + results.failed + ' test'
+ (results.failed == 1 ? '' : 's') + ' failed'
: ''
)
: 'No tests';
}
function hideLoadingScreen() {
self.$loadingIcon.stop().remove();
self.$loadingText.remove();
}
function parseFiles(callback) {
var counter = 0,
docItems = [],
length = self.options.files.length;
self.options.files.forEach(function(file) {
Ox.doc(self.options.path + file, function(fileItems) {
docItems = docItems.concat(fileItems);
++counter == length && callback(docItems);
});
});
}
function renderList() {
var treeItems = [];
self.options.items.forEach(function(docItem) {
var moduleIndex, results, sectionIndex;
docItem.module = self.options.getModule(docItem);
moduleIndex = Ox.getIndexById(treeItems, docItem.module + '/');
if (moduleIndex == -1) {
treeItems.push({
id: docItem.module + '/',
items: [],
title: docItem.module
});
moduleIndex = treeItems.length - 1;
}
docItem.section = self.options.getSection(docItem);
if (docItem.section) {
sectionIndex = Ox.getIndexById(
treeItems[moduleIndex].items,
docItem.module + '/' + docItem.section + '/'
);
if (sectionIndex == -1) {
treeItems[moduleIndex].items.push({
id: docItem.module + '/' + docItem.section + '/',
items: [],
title: docItem.section
});
sectionIndex = treeItems[moduleIndex].items.length - 1;
}
}
(
docItem.section
? treeItems[moduleIndex].items[sectionIndex]
: treeItems[moduleIndex]
).items.push({
id: docItem.module + '/' + (
docItem.section ? docItem.section + '/' : ''
) + docItem.name,
title: docItem.name
});
});
treeItems.sort(sortByTitle);
treeItems.forEach(function(item) {
item.items.sort(sortByTitle);
item.items.forEach(function(subitem) {
subitem.items.sort(sortByTitle);
});
});
self.$list = Ox.TreeList({
expanded: self.options.expanded,
icon: self.options.showTests
? getIcon
: Ox.UI.getImageURL('symbolCenter'),
items: treeItems,
selected: self.options.selected ? [self.options.selected] : '',
width: self.options.size
})
.bindEvent({
select: function(data) {
if (!data.ids[0] || !Ox.endsWith(data.ids[0], '/')) {
selectItem(
data.ids[0] ? data.ids[0].split('/').pop() : ''
);
}
}
});
self.$sidebar.replaceElement(0, self.$list);
selectItem(self.options.selected);
}
function runTests() {
self.$testsButton.remove();
self.$testsStatus.html('Running Tests...').show();
Ox.load({Geo: {}, Image: {}, Unicode: {}}, function() {
Ox.test(self.options.items, function(results) {
results.forEach(function(result) {
var item = getItemByName(result.name),
passed = result.passed ? 'passed' : 'failed';
item.tests[Ox.indexOf(item.tests, function(test) {
return test.statement == result.statement;
})] = result;
['', item.module + '/'].concat(
item.section ? item.module + '/' + item.section + '/' : [],
item.module + '/' + (item.section ? item.section + '/' : '') + item.name
).forEach(function(key) {
self.options.results[key] = self.options.results[key] || {passed: 0, failed: 0};
self.options.results[key][passed]++;
});
});
self.$testsStatus.html(formatResults());
renderList();
that.triggerEvent('tests', {results: self.options.results});
});
});
}
function selectItem(id) {
var item = id ? getItemByName(id) : null;
if (item) {
self.options.selected = id;
self.$list.options({
selected: [item.module + '/' + (
item.section ? item.section + '/' : ''
) + item.name]
});
self.$page = Ox.DocPage({
item: item,
replace: self.options.replace,
stripComments: self.options.stripComments
})
.bindEvent({
close: function() {
selectItem();
},
example: function(data) {
that.triggerEvent('example', data);
}
});
self.$panel.replaceElement(1, self.$page);
} else {
self.options.selected = '';
self.$list.options({selected: []});
self.$page.empty().append(self.options.element || $('<div>'));
}
that.triggerEvent('select', {id: self.options.selected});
}
function showLoadingScreen() {
self.$loadingIcon = Ox.LoadingIcon({size: 16})
.css({
position: 'absolute',
left: (self.$page.width() - self.options.size) / 2 - 8,
top: self.$page.height() / 2 - 20
})
.appendTo(self.$page)
.start();
self.$loadingText = $('<div>')
.addClass('OxLight')
.css({
position: 'absolute',
left: (self.$page.width() - self.options.size) / 2 - 128,
top: self.$page.height() / 2 + 4,
width: 256,
textAlign: 'center'
})
.html(Ox._('Generating Documentation...'))
.appendTo(self.$page);
}
function sortByTitle(a, b) {
var a = Ox.stripTags(a.title).toLowerCase(),
b = Ox.stripTags(b.title).toLowerCase();
return a < b ? -1 : a > b ? 1 : 0;
}
return that;
};

View file

@ -0,0 +1,225 @@
'use strict'
/*@
Ox.ExamplePage <f> Example Page
options <o> Options
self <o> Shared private variable
([options[, self]]) -> <o:Ox.SplitPanel> Example Page
change <!> Change event
close <!> Close event
@*/
Ox.ExamplePage = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
html: '',
js: '',
references: [],
replaceCode: [],
replaceComment: [],
selected: 'source',
title: ''
})
.options(options || {})
.update({
selected: function() {
self.$tabs.options({value: self.options.selected});
}
});
self.$toolbar = Ox.Bar({size: 24});
self.$homeButton = Ox.Button({
title: 'home',
tooltip: Ox._('Home'),
type: 'image'
})
.css({float: 'left', margin: '4px 2px 4px 4px'})
.bindEvent({
click: function() {
that.triggerEvent('close');
}
})
.appendTo(self.$toolbar)
self.$title = Ox.Label({
style: 'square',
title: self.options.title
})
.css({float: 'left', borderRadius: '4px', margin: '4px 2px 4px 2px'})
.appendTo(self.$toolbar);
self.$openButton = Ox.Button({
disabled: self.options.selected == 'source',
title: 'open',
tooltip: Ox._('Open in New Tab'),
type: 'image'
})
.css({float: 'right', margin: '4px 4px 4px 2px'})
.bindEvent({
click: function() {
window.open(self.options.html);
}
})
.appendTo(self.$toolbar);
self.$reloadButton = Ox.Button({
disabled: self.options.selected == 'source',
title: 'redo',
tooltip: Ox._('Reload'),
type: 'image'
})
.css({float: 'right', margin: '4px 2px 4px 2px'})
.bindEvent({
click: function() {
self.$frame.attr({src: self.options.html});
}
})
.appendTo(self.$toolbar);
self.$switchButton = Ox.Button({
disabled: self.options.selected == 'source',
title: 'switch',
tooltip: Ox._('Switch Theme'),
type: 'image'
})
.css({float: 'right', margin: '4px 2px 4px 2px'})
.bindEvent({
click: function() {
self.$frame[0].contentWindow.postMessage(
'Ox && Ox.Theme && Ox.Theme('
+ 'Ox.Theme() == "oxlight" ? "oxmedium"'
+ ' : Ox.Theme() == "oxmedium" ? "oxdark"'
+ ' : "oxlight"'
+ ')',
'*'
);
}
})
.appendTo(self.$toolbar);
self.$tabs = Ox.ButtonGroup({
buttons: [
{
id: 'source',
title: Ox._('View Source'),
width: 80
},
{
id: 'live',
title: Ox._('View Live'),
width: 80
}
],
selectable: true,
value: self.options.selected
})
.css({float: 'right', margin: '4px 2px 4px 2px'})
.bindEvent({
change: function(data) {
var disabled = data.value == 'source';
self.options.selected = data.value;
self.hasUI && self.$switchButton.options({disabled: disabled});
self.$reloadButton.options({disabled: disabled});
self.$openButton.options({disabled: disabled});
self.$content.animate({
marginLeft: self.options.selected == 'source'
? 0 : -self.options.width + 'px'
}, 250, function() {
if (
self.options.selected == 'live'
&& !self.$frame.attr('src')
) {
self.$frame.attr({src: self.options.html});
}
});
that.triggerEvent('change', data);
}
})
.appendTo(self.$toolbar);
self.$viewer = Ox.SourceViewer({
file: self.options.js,
replaceCode: self.options.replaceCode,
replaceComment: self.options.replaceComment
})
.css({
position: 'absolute',
left: 0,
top: 0,
width: self.options.width + 'px',
height: self.options.height - 24 + 'px'
});
self.$frame = Ox.Element('<iframe>')
.css({
position: 'absolute',
left: self.options.width + 'px',
top: 0,
border: 0
})
.attr({
width: self.options.width,
height: self.options.height
});
self.$content = Ox.Element()
.css({
position: 'absolute',
width: self.options.width * 2 + 'px',
marginLeft: self.options.selected == 'source'
? 0 : -self.options.width + 'px'
})
.append(self.$viewer)
.append(self.$frame);
self.$container = Ox.Element()
.append(self.$content);
that.setElement(
Ox.SplitPanel({
elements: [
{element: self.$toolbar, size: 24},
{element: self.$container}
],
orientation: 'vertical'
})
.addClass('OxExamplePage')
);
Ox.get(self.options.js, function(js) {
self.hasUI = /Ox\.load\(.+UI.+,/.test(js);
!self.hasUI && self.$switchButton.options({disabled: true});
});
Ox.$window.on({
resize: function() {
setSize();
}
});
setTimeout(function() {
setSize();
if (self.options.selected == 'live') {
self.$frame.attr({src: self.options.html});
}
}, 100);
function setSize() {
self.options.width = that.width();
self.options.height = that.height();
self.$content.css({
width: self.options.width * 2 + 'px'
})
self.$viewer.css({
width: self.options.width + 'px',
height: self.options.height - 24 + 'px'
})
self.$frame.attr({
width: self.options.width,
height: self.options.height - 24
});
}
return that;
};

View file

@ -0,0 +1,181 @@
'use strict';
/*@
Ox.ExamplePanel <f> Example Panel
options <o> Options
self <o> Shared private variable
([options[, self]]) -> <o:Ox.SplitPanel> Example Panel
change <!> Change event
value <s> 'source' or 'live'
load <!> Load event
select <!> Select event
id <s> selected example
@*/
Ox.ExamplePanel = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
element: '',
examples: [],
mode: 'source',
path: '',
references: null,
replaceCode: [],
replaceComment: [],
selected: '',
size: 256
})
.options(options || {})
.update({
mode: function() {
if (self.options.selected) {
self.$page.options({selected: self.options.mode});
}
},
selected: function() {
self.options.mode = 'source';
selectItem(self.options.selected);
}
});
self.$list = Ox.Element();
self.$page = Ox.Element();
that.setElement(
self.$panel = Ox.SplitPanel({
elements: [
{
element: self.$list,
size: self.options.size
},
{
element: self.$page
}
],
orientation: 'horizontal'
})
);
loadItems(function(items) {
var treeItems = [];
self.items = items;
items.forEach(function(item) {
var sectionIndex = Ox.getIndexById(treeItems, item.section + '/');
if (sectionIndex == -1) {
treeItems.push({
id: item.section + '/',
items: [],
title: item.sectionTitle
});
sectionIndex = treeItems.length - 1;
}
treeItems[sectionIndex].items.push(item);
});
self.$list = Ox.TreeList({
expanded: true,
icon: Ox.UI.getImageURL('symbolCenter'),
items: treeItems,
selected: self.options.selected ? [self.options.selected] : [],
width: self.options.size
})
.bindEvent({
select: function(data) {
if (!data.ids[0] || !Ox.endsWith(data.ids[0], '/')) {
self.options.mode = 'source';
selectItem(
data.ids[0] ? data.ids[0].split('/').pop() : ''
);
}
}
});
self.$panel.replaceElement(0, self.$list);
selectItem(self.options.selected);
that.triggerEvent('load', {items: self.items});
});
function getItemByName(name) {
var item = null;
Ox.forEach(self.items, function(v) {
if (v.id.split('/').pop() == name) {
item = v;
return false; // break
}
});
return item;
}
function loadItems(callback) {
var items = [];
self.options.examples.forEach(function(example) {
var item = {
html: self.options.path + example + '/index.html',
id: example,
js: self.options.path + example + '/js/example.js',
section: example.split('/').shift()
};
Ox.get(item.html, function(html) {
var match = html.match(/<title>(.+)<\/title>/);
item.title = match ? match[1] : 'Untitled';
match = html.match(/<meta http-equiv="Keywords" content="(.+)"\/>/);
item.sectionTitle = match ? match[1] : Ox._('Untitled');
Ox.get(item.js, function(js) {
var references = js.match(self.options.references);
item.references = references ? Ox.unique(references).sort(function(a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
return a < b ? -1 : a > b ? 1 : 0;
}) : [];
items.push(item);
if (items.length == self.options.examples.length) {
callback(items.sort(sortById));
}
});
});
});
}
function selectItem(id) {
var item = id ? getItemByName(id) : null,
selected = self.options.selected;
if (item) {
self.options.selected = id;
self.$list.options({selected: [item.section + '/' + id]});
self.$page = Ox.ExamplePage({
height: window.innerHeight,
html: item.html,
js: item.js,
references: item.references,
replaceCode: self.options.replaceCode,
replaceComment: self.options.replaceComment,
selected: self.options.mode,
title: item.title,
width: window.innerWidth - self.options.size
})
.bindEvent({
change: function(data) {
that.triggerEvent('change', data);
},
close: function() {
selectItem();
}
});
self.$panel.replaceElement(1, self.$page);
} else {
self.options.selected = '';
self.$list.options({selected: []});
self.$page.empty().append(self.options.element);
}
if (self.options.selected != selected) {
that.triggerEvent('select', {id: self.options.selected});
}
}
function sortById(a, b) {
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
}
return that;
};

View file

@ -0,0 +1,95 @@
'use strict';
/*@
Ox.SourceViewer <f> Source Viewer
options <o> Options
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Container> Source Viewer
@*/
Ox.SourceViewer = function(options, self) {
self = self || {};
var that = Ox.Container({}, self)
.defaults({
file: '',
replaceCode: [],
replaceComment: []
})
.options(options)
.addClass('OxSourceViewer');
self.options.replaceComment.unshift(
// removes indentation inside <pre> tags
[
/<pre>([\s\S]+)<\/pre>/g,
function(pre, text) {
var lines = trim(text).split('\n'),
indent = Ox.min(lines.map(function(line) {
var match = line.match(/^\s+/);
return match ? match[0].length : 0;
}));
return '<pre>' + lines.map(function(line) {
return line.slice(indent);
}).join('\n') + '</pre>';
}
]
);
self.$table = $('<table>').appendTo(that.$content);
Ox.get(self.options.file, function(source) {
var sections = [{comment: '', code: ''}];
Ox.tokenize(source).forEach(function(token, i) {
// treat doc comments as code
var type = token.type == 'comment' && token.value[2] != '@'
? 'comment' : 'code';
// remove '//' comments
if (!/^\/\/[^@]/.test(token.value)) {
if (type == 'comment' ) {
i && sections.push({comment: '', code: ''});
token.value = Ox.parseMarkdown(
trim(token.value.slice(2, -2))
);
self.options.replaceComment.forEach(function(replace) {
token.value = token.value.replace(
replace[0], replace[1]
);
});
}
Ox.last(sections)[type] += token.value;
}
});
sections.forEach(function(section) {
var $section = $('<tr>')
.appendTo(self.$table),
$comment = $('<td>')
.addClass('OxComment OxSerif OxSelectable')
.html(Ox.addLinks(section.comment, true))
.appendTo($section),
$code = $('<td>')
.addClass('OxCode')
.append(
Ox.SyntaxHighlighter({
replace: self.options.replaceCode,
source: trim(section.code)
})
)
.appendTo($section);
});
setTimeout(function() {
var height = that.height();
if (self.$table.height() < height) {
self.$table.css({height: height + 'px'});
}
}, 100);
});
function trim(str) {
// removes leading or trailing empty line
return str.replace(/^\s*\n/, '').replace(/\n\s*$/, '');
}
return that;
};

View file

@ -0,0 +1,147 @@
'use strict';
/*@
Ox.SyntaxHighlighter <f> Syntax Highlighter
options <o> Options
file <s|''> JavaScript file (alternative to `source` option)
lineLength <n|0> If larger than zero, show edge of page
offset <n|1> First line number
replace <[[]]|[]> Array of replacements
Each array element is an array of two arguments to be passed to the
replace function, like [str, str], [regexp, str] or [regexp, fn]
showLinebreaks <b|false> If true, show linebreaks
showLineNumbers <b|false> If true, show line numbers
showWhitespace <b|false> If true, show whitespace
showTabs <b|false> If true, show tabs
source <s|[o]|''> JavaScript source, or array of tokens
stripComments <b|false> If true, strip comments
tabSize <n|4> Number of spaces per tab
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Syntax Highlighter
@*/
Ox.SyntaxHighlighter = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
file: '',
lineLength: 0,
offset: 1,
replace: [],
showLinebreaks: false,
showLineNumbers: false,
showTabs: false,
showWhitespace: false,
source: '',
stripComments: false,
tabSize: 4
})
.options(options || {})
.update(renderSource)
.addClass('OxSyntaxHighlighter');
if (self.options.file) {
Ox.get(self.options.file, function(source) {
self.options.source = source;
renderSource();
});
} else {
renderSource();
}
function renderSource() {
var $lineNumbers, $line, $source, width,
lines, source = '', tokens,
linebreak = (
self.options.showLinebreaks
? '<span class="OxLinebreak">\u21A9</span>' : ''
) + '<br/>',
tab = (
self.options.showTabs ?
'<span class="OxTab">\u2192</span>' : ''
) + Ox.repeat('&nbsp;', self.options.tabSize - self.options.showTabs),
whitespace = self.options.showWhitespace ? '\u00B7' : '&nbsp;';
tokens = Ox.isArray(self.options.source)
? self.options.source
: Ox.tokenize(self.options.source);
tokens.forEach(function(token, i) {
var classNames,
type = token.type == 'identifier'
? Ox.identify(token.value) : token.type;
if (
!(self.options.stripComments && type == 'comment')
) {
classNames = 'Ox' + Ox.toTitleCase(type);
if (self.options.showWhitespace && type == 'whitespace') {
if (isAfterLinebreak() && hasIrregularSpaces()) {
classNames += ' OxLeading';
} else if (isBeforeLinebreak()) {
classNames += ' OxTrailing';
}
}
source += '<span class="' + classNames + '">' +
Ox.encodeHTMLEntities(token.value)
.replace(/ /g, whitespace)
.replace(/\t/g, tab)
.replace(/\n/g, linebreak) + '</span>';
}
function isAfterLinebreak() {
return i == 0 ||
tokens[i - 1].type == 'linebreak';
}
function isBeforeLinebreak() {
return i == tokens.length - 1 ||
tokens[i + 1].type == 'linebreak';
}
function hasIrregularSpaces() {
return token.value.split('').reduce(function(prev, curr) {
return prev + (curr == ' ' ? 1 : 0);
}, 0) % self.options.tabSize;
}
});
lines = source.split('<br/>');
that.empty();
if (self.options.showLineNumbers) {
$lineNumbers = Ox.Element()
.addClass('OxLineNumbers')
.html(
Ox.range(lines.length).map(function(line) {
return (line + self.options.offset);
}).join('<br/>')
)
.appendTo(that);
}
self.options.replace.forEach(function(replace) {
source = source.replace(replace[0], replace[1])
});
$source = Ox.Element()
.addClass('OxSourceCode OxSelectable')
.html(source)
.appendTo(that);
if (self.options.lineLength) {
$line = Ox.Element()
.css({
position: 'absolute',
top: '-1000px'
})
.html(Ox.repeat('&nbsp;', self.options.lineLength))
.appendTo(that);
width = $line.width() + 4; // add padding
$line.remove();
['moz', 'webkit'].forEach(function(browser) {
$source.css({
background: '-' + browser +
'-linear-gradient(left, rgb(255, 255, 255) ' +
width + 'px, rgb(192, 192, 192) ' + width +
'px, rgb(255, 255, 255) ' + (width + 1) + 'px)'
});
});
}
}
return that;
};

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

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

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

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

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

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

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,84 @@
'use strict';
/*@
Ox.ImageElement <f> Simple image element with loading indication
@*/
Ox.ImageElement = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
height: 0,
src: '',
width: 0
})
.options(options || {})
.update({
height: function() {
!self.isAuto && setSizes();
},
src: loadImage,
width: function() {
!self.isAuto && setSizes();
}
})
.addClass('OxImageElement');
self.isAuto = !self.options.width && !self.options.height;
self.$screen = Ox.LoadingScreen({
height: self.options.height,
size: 16,
width: self.options.width
})
.start()
.appendTo(that);
loadImage();
!self.isAuto && setSizes();
function loadImage() {
if (self.$image) {
self.$image.off({load: showImage}).remove();
}
self.$image = $('<img>')
.one({
error: stopLoading,
load: showImage
})
.attr({src: self.options.src});
}
function setSizes() {
var css = {
width: self.options.width,
height: self.options.height
};
self.$screen && self.$screen.options(css);
css = Ox.map(css, function(value) {
return value + 'px';
});
that.css(css);
self.$image.css(css);
}
function showImage() {
self.$screen.stop().remove();
self.$image.appendTo(that);
}
function stopLoading() {
self.$screen.stop();
}
that.css = function(css) {
that.$element.css(css);
self.$screen && self.$screen.css(css);
self.$image.css(css);
return that;
};
return that;
};

View file

@ -0,0 +1,578 @@
'use strict';
/*@
Ox.ImageViewer <f> Image Viewer
options <o> Options
center <[n]|s|'auto'> Center ([x, y] or 'auto')
elasticity <n|0> Number of pixels to scroll/zoom beyond min/max
height <n|384> Viewer height in px
imageHeight <n|0> Image height in px
imagePreviewURL <s|''> URL of smaller preview image
imageURL <s|''> Image URL
imageWidth <n|0> Image width in px
maxZoom <n|16> Maximum zoom (minimum zoom is 'fit')
overviewSize <n|128> Size of overview image in px
width <n|512> Viewer width in px
zoom <n|s|'fit'> Zoom (number or 'fit' or 'fill')
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Image Viewer
center <!> Center changed
center <[n]|s> Center
zoom <!> Zoom changed
zoom <n|s> Zoom
@*/
Ox.ImageViewer = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
center: 'auto',
elasticity: 0,
height: 384,
imageHeight: 0,
imagePreviewURL: '',
imageURL: '',
imageWidth: 0,
maxZoom: 16,
overviewSize: 128,
width: 512,
zoom: 'fit'
})
.options(options || {})
.update({
center: function() {
setCenterAndZoom(true, true);
},
// allow for setting height and width at the same time
height: updateSize,
width: updateSize,
zoom: function() {
setCenterAndZoom(true, true);
}
})
.addClass('OxImageViewer OxGrid')
.on({
mousedown: function() {
that.gainFocus();
},
mouseenter: function() {
showInterface();
},
mouseleave: function() {
hideInterface();
},
mousemove: function() {
showInterface();
hideInterface();
}
})
.bindEvent({
doubleclick: onDoubleclick,
dragstart: onDragstart,
drag: onDrag,
dragend: onDragend,
key_0: function() {
that.options({zoom: 1});
},
key_1: function() {
that.options({center: 'auto', zoom: 'fit'});
},
key_2: function() {
that.options({center: 'auto', zoom: 'fill'});
},
key_down: function() {
that.options({
center: [
self.center[0],
self.center[1] + self.options.height / 2 / self.zoom
]
});
},
key_equal: function() {
that.options({zoom: self.zoom * 2});
},
key_left: function() {
that.options({
center: [
self.center[0] - self.options.width / 2 / self.zoom,
self.center[1]
]
});
},
key_minus: function() {
that.options({zoom: self.zoom / 2});
},
key_right: function() {
that.options({
center: [
self.center[0] + self.options.width / 2 / self.zoom,
self.center[1]
]
});
},
key_up: function() {
that.options({
center: [
self.center[0],
self.center[1] - self.options.height / 2 / self.zoom
]
});
},
mousewheel: onMousewheel,
singleclick: onSingleclick
});
self.imageRatio = self.options.imageWidth / self.options.imageHeight;
self.overviewHeight = Math.round(
self.options.overviewSize / (self.imageRatio > 1 ? self.imageRatio : 1)
);
self.overviewWidth = Math.round(
self.options.overviewSize * (self.imageRatio > 1 ? 1 : self.imageRatio)
);
self.overviewZoom = self.overviewWidth / self.options.imageWidth;
self.$image = Ox.Element('<img>')
.addClass('OxImage')
.attr({src: self.options.imagePreviewURL})
//.css(getImageCSS())
.appendTo(that);
Ox.$('<img>')
.one({
load: function() {
self.$image.attr({src: self.options.imageURL});
}
})
.attr({src: self.options.imageURL});
self.$scaleButton = Ox.ButtonGroup({
buttons: [
{
id: 'fit',
title: 'fit',
tooltip: Ox._('Zoom to Fit') + ' <span class="OxBright">[1]</span>'
},
{
id: 'fill',
title: 'fill',
tooltip: Ox._('Zoom to Fill') + ' <span class="OxBright">[2]</span>'
}
],
style: 'overlay',
type: 'image'
})
.addClass('OxInterface OxScaleButton')
.on({
mouseenter: function() {
self.mouseIsInInterface = true;
},
mouseleave: function() {
self.mouseIsInInterface = false;
}
})
.bindEvent({
click: function(data) {
that.options({center: 'auto', zoom: data.id});
}
})
.appendTo(that);
self.$zoomButton = Ox.ButtonGroup({
buttons: [
{
id: 'out',
title: 'remove',
tooltip: Ox._('Zoom Out') + ' <span class="OxBright">[-]</span>'
},
{
id: 'original',
title: 'equal',
tooltip: Ox._('Original Size') + ' <span class="OxBright">[0]</span>'
},
{
id: 'in',
title: 'add',
tooltip: Ox._('Zoom In') + ' <span class="OxBright">[=]</span>'
}
],
style: 'overlay',
type: 'image'
})
.addClass('OxInterface OxZoomButton')
.on({
mouseenter: function() {
self.mouseIsInInterface = true;
},
mouseleave: function() {
self.mouseIsInInterface = false;
}
})
.bindEvent({
click: function(data) {
if (data.id == 'out') {
that.options({zoom: self.zoom / 2});
} else if (data.id == 'original') {
that.options({zoom: 1});
} else {
that.options({zoom: self.zoom * 2});
}
}
})
.appendTo(that);
self.$overview = Ox.Element()
.addClass('OxInterface OxImageOverview')
.css({
height: self.overviewHeight + 'px',
width: self.overviewWidth + 'px'
})
.hide()
.on({
mouseenter: function() {
self.mouseIsInInterface = true;
},
mouseleave: function() {
self.mouseIsInInterface = false;
}
})
.appendTo(that);
self.$overviewImage = Ox.Element('<img>')
.attr({src: self.options.imagePreviewURL})
.css({
height: self.overviewHeight + 'px',
width: self.overviewWidth + 'px'
})
.appendTo(self.$overview);
self.$overlay = Ox.Element()
.addClass('OxImageOverlay')
.appendTo(self.$overview);
self.$area = {};
['bottom', 'center', 'left', 'right', 'top'].forEach(function(area) {
self.$area[area] = Ox.Element()
.addClass('OxImageOverlayArea')
.attr({id: 'OxImageOverlay' + Ox.toTitleCase(area)})
.css(getAreaCSS(area))
.appendTo(self.$overlay);
});
setSize();
function getAreaCSS(area) {
return area == 'bottom' ? {
height: self.overviewHeight + 'px'
} : area == 'center' ? {
left: self.overviewWidth + 'px',
top: self.overviewHeight + 'px',
right: self.overviewWidth + 'px',
bottom: self.overviewHeight + 'px'
} : area == 'left' ? {
top: self.overviewHeight + 'px',
bottom: self.overviewHeight + 'px',
width: self.overviewWidth + 'px'
} : area == 'right' ? {
top: self.overviewHeight + 'px',
bottom: self.overviewHeight + 'px',
width: self.overviewWidth + 'px'
} : {
height: self.overviewHeight + 'px'
};
}
function getCenter(e) {
var $target = $(e.target), center, offset, offsetX, offsetY;
if ($target.is('.OxImage')) {
center = [e.offsetX / self.zoom, e.offsetY / self.zoom];
} else if ($target.is('.OxImageOverlayArea')) {
offset = that.offset();
offsetX = e.clientX - offset.left - self.options.width
+ self.overviewWidth + 6;
offsetY = e.clientY - offset.top - self.options.height
+ self.overviewHeight + 6;
center = [offsetX / self.overviewZoom, offsetY / self.overviewZoom];
}
return center;
}
function getImageCSS() {
return {
left: Math.round(self.options.width / 2 - self.center[0] * self.zoom) + 'px',
top: Math.round(self.options.height / 2 - self.center[1] * self.zoom) + 'px',
width: Math.round(self.options.imageWidth * self.zoom) + 'px',
height: Math.round(self.options.imageHeight * self.zoom) + 'px'
};
}
function getOverlayCSS() {
var centerLeft = self.center[0] / self.options.imageWidth * self.overviewWidth,
centerTop = self.center[1] / self.options.imageHeight * self.overviewHeight,
centerWidth = self.options.width / self.zoom * self.overviewZoom + 4,
centerHeight = self.options.height / self.zoom * self.overviewZoom + 4;
return {
left: Math.round(centerLeft - centerWidth / 2 - self.overviewWidth) + 'px',
top: Math.round(centerTop - centerHeight / 2 - self.overviewHeight) + 'px',
width: Math.round(2 * self.overviewWidth + centerWidth) + 'px',
height: Math.round(2 * self.overviewHeight + centerHeight) + 'px'
};
}
function getZoomCenter(e, factor) {
var center = getCenter(e),
delta = [
center[0] - self.center[0],
center[1] - self.center[1]
];
if (factor == 0.5) {
factor = -1;
}
return [
self.center[0] + delta[0] / factor,
self.center[1] + delta[1] / factor
];
}
function hideInterface() {
clearTimeout(self.interfaceTimeout);
self.interfaceTimeout = setTimeout(function() {
if (!self.mouseIsInInterface) {
self.interfaceIsVisible = false;
self.$scaleButton.animate({opacity: 0}, 250);
self.$zoomButton.animate({opacity: 0}, 250);
self.$overview.animate({opacity: 0}, 250);
}
}, 2500);
}
function limitCenter(elastic) {
var center, imageHeight, imageWidth, maxCenter, minCenter;
if (self.options.zoom == 'fill') {
imageWidth = self.imageIsWider
? self.options.height * self.imageRatio
: self.options.width;
imageHeight = self.imageIsWider
? self.options.height
: self.options.width / self.imageRatio;
} else if (self.options.zoom == 'fit') {
imageWidth = self.imageIsWider
? self.options.width
: self.options.height * self.imageRatio;
imageHeight = self.imageIsWider
? self.options.width / self.imageRatio
: self.options.height;
} else {
imageWidth = self.options.imageWidth * self.options.zoom;
imageHeight = self.options.imageHeight * self.options.zoom;
}
minCenter = [
imageWidth > self.options.width
? self.options.width / 2 / self.zoom
: self.options.imageWidth / 2,
imageHeight > self.options.height
? self.options.height / 2 / self.zoom
: self.options.imageHeight / 2
].map(function(value) {
return elastic ? value - self.options.elasticity / self.zoom : value;
});
maxCenter = [
self.options.imageWidth - minCenter[0],
self.options.imageHeight - minCenter[1]
];
center = self.options.center == 'auto' ? [
self.options.imageWidth / 2,
self.options.imageHeight / 2
] : [
Ox.limit(self.options.center[0], minCenter[0], maxCenter[0]),
Ox.limit(self.options.center[1], minCenter[1], maxCenter[1])
];
if (Ox.isArray(self.options.center)) {
self.options.center = center;
}
return center;
}
function limitZoom(elastic) {
// FIXME: elastic maxZoom is still wrong
var imageSize = self.imageIsWider ? self.options.imageWidth : self.options.imageHeight,
minZoom = elastic
? (self.fitZoom * imageSize - 2 * self.options.elasticity) / imageSize
: self.fitZoom,
maxZoom = elastic
? (self.maxZoom * imageSize + 2 * self.options.elasticity) / imageSize
: self.maxZoom,
zoom = self.options.zoom == 'fill' ? self.fillZoom
: self.options.zoom == 'fit' ? self.fitZoom
: Ox.limit(self.options.zoom, minZoom, maxZoom);
if (Ox.isNumber(self.options.zoom)) {
self.options.zoom = zoom;
}
return zoom;
}
function onDoubleclick(e) {
var $target = $(e.target), factor = e.shiftKey ? 0.5 : 2;
if ((
$target.is('.OxImage') || $target.is('.OxImageOverlayArea')
) && (
(!e.shiftKey && self.zoom < self.maxZoom)
|| (e.shiftKey && self.zoom > self.fitZoom)
)) {
that.options({
center: getZoomCenter(e, factor),
zoom: self.zoom * factor
});
}
}
function onDragstart(e) {
var $target = $(e.target);
if ($target.is('.OxImage') || $target.is('#OxImageOverlayCenter')) {
self.drag = {
center: self.center,
zoom: $target.is('.OxImage') ? self.zoom : -self.overviewZoom
};
}
}
function onDrag(e) {
if (self.drag) {
self.options.center = [
self.drag.center[0] - e.clientDX / self.drag.zoom,
self.drag.center[1] - e.clientDY / self.drag.zoom
];
setCenterAndZoom(false, true);
}
}
function onDragend() {
if (self.drag) {
self.drag = null;
setCenterAndZoom(true);
}
}
function onMousewheel(e) {
var $target = $(e.target),
factor = e.deltaY < 0 ? 2 : 0.5;
if (e.deltaX == 0 && Math.abs(e.deltaY) > 10 && !self.mousewheelTimeout) {
if ($target.is('.OxImage') || $target.is('.OxImageOverlayArea')) {
self.options.center = getZoomCenter(e, factor);
self.options.zoom = self.zoom * factor;
setCenterAndZoom(true, true);
self.mousewheelTimeout = setTimeout(function() {
self.mousewheelTimeout = null;
}, 250);
}
}
}
function onSingleclick(e) {
var $target = $(e.target), offset, offsetX, offsetY;
if ($target.is('.OxImage') || $target.is('.OxImageOverlayArea')) {
that.options({center: getCenter(e)});
}
}
function setCenterAndZoom(animate, elastic) {
self.zoom = limitZoom(elastic);
self.center = limitCenter(elastic);
if (animate) {
self.$image.stop().animate(getImageCSS(), 250, function() {
var center = limitCenter(),
zoom = limitZoom(),
setCenter = center[0] != self.center[0]
|| center[1] != self.center[1],
setZoom = zoom != self.zoom;
if (setCenter || setZoom) {
self.options.center = center;
self.options.zoom = zoom;
setCenterAndZoom();
}
that.triggerEvent({
center: {center: self.options.center},
zoom: {zoom: self.options.zoom}
});
});
self.$overlay.stop().animate(getOverlayCSS(), 250);
} else {
self.$image.css(getImageCSS());
self.$overlay.css(getOverlayCSS());
}
updateInterface();
showInterface();
hideInterface();
}
function setSize() {
self.viewerRatio = self.options.width / self.options.height;
self.imageIsWider = self.imageRatio > self.viewerRatio;
self.fillZoom = (
self.imageIsWider
? self.options.height * self.imageRatio
: self.options.width
) / self.options.imageWidth;
self.fitZoom = (
self.imageIsWider
? self.options.width
: self.options.height * self.imageRatio
) / self.options.imageWidth;
self.maxZoom = Math.max(self.fillZoom, self.options.maxZoom);
that.css({
width: self.options.width + 'px',
height: self.options.height + 'px'
});
setCenterAndZoom();
}
function showInterface() {
clearTimeout(self.interfaceTimeout);
if (!self.interfaceIsVisible) {
self.interfaceIsVisible = true;
self.$scaleButton.animate({opacity: 1}, 250);
self.$zoomButton.animate({opacity: 1}, 250);
self.$overview.animate({opacity: 1}, 250);
}
}
function updateInterface() {
var isFitZoom = self.zoom == self.fitZoom;
self.$scaleButton[
isFitZoom ? 'disableButton' : 'enableButton'
]('fit');
self.$scaleButton[
self.zoom == self.fillZoom
&& self.center[0] == self.options.imageWidth / 2
&& self.center[1] == self.options.imageHeight / 2
? 'disableButton' : 'enableButton'
]('fill');
self.$zoomButton[
isFitZoom ? 'disableButton' : 'enableButton'
]('out');
self.$zoomButton[
self.zoom == 1 ? 'disableButton' : 'enableButton'
]('original');
self.$zoomButton[
self.zoom == self.maxZoom ? 'disableButton' : 'enableButton'
]('in');
!isFitZoom && self.$overview.show();
self.$overview.stop().animate({
opacity: isFitZoom ? 0 : 1
}, 250, function() {
isFitZoom && self.$overview.hide();
});
}
function updateSize() {
if (!self.updateTimeout) {
self.updateTimeout = setTimeout(function() {
self.updateTimeout = null;
setSize();
});
}
}
return that;
};

285
source/UI/js/List/Chart.js vendored Normal file
View file

@ -0,0 +1,285 @@
'use strict';
/*@
Ox.Chart <f> Bar Chart
options <o> Options
color <[n]|[[n]]|[128, 128, 128]> Bar color
data <o> {k: v, ...} or {k: {k: v, ...}, ...}
formatKey <f|null> Format function for keys
keyAlign <s|'right'> Alignment of keys
keyWidth <n|128> Width of keys
limit <n|0> Number of items, or 0 for all
rows <n|1> undocumented
sort <o|{key: 'value', operator: '-'}> Sort
title <s|''> Chart title
width <n|512> Chart width
self <o> shared private variable
([options[, self]]) -> <o:Ox.Element> Chart object
@*/
Ox.Chart = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
color: [128, 128, 128],
data: {},
formatKey: null,
keyAlign: 'right',
keyWidth: 128,
limit: 0,
rows: 1,
sort: {key: 'value', operator: '-'},
sortKey: null,
title: '',
width: 512
})
.options(options || {})
.update({
width: function() {
self.$chart.empty();
renderChart();
}
})
.addClass('OxChart');
self.valueWidth = self.options.width - self.options.keyWidth;
self.keys = Object.keys(self.options.data);
if (Ox.isObject(self.options.data[self.keys[0]])) {
if (Ox.isUndefined(options.color)) {
self.options.color = [
[192, 64, 64], [ 64, 192, 64], [ 64, 64, 192],
[192, 192, 64], [ 64, 192, 192], [192, 64, 192],
[192, 128, 64], [ 64, 192, 128], [128, 64, 192],
[192, 64, 128], [128, 192, 64], [ 64, 128, 192]
];
}
self.subData = {};
}
self.sort = {};
self.totals = {};
Ox.forEach(self.options.data, function(value, key) {
self.totals[key] = self.subData ? Ox.sum(value) : value;
if (self.subData) {
Object.keys(value).forEach(function(subKey) {
self.subData[subKey] = (self.subData[subKey] || 0) + value[subKey];
});
}
self.sort[key] = key.replace(/(\d+)/g, function(number) {
return Ox.pad(parseInt(number, 10), 16);
});
});
self.max = Ox.max(self.totals);
self.sum = Ox.sum(self.totals);
if (self.subData) {
Ox.forEach(self.subData, function(subValue, subKey) {
self.sort[subKey] = subKey.replace(/(\d+)/g, function(number) {
return Ox.pad(parseInt(number, 10), 16);
});
});
self.subKeys = Object.keys(self.subData).sort(function(a, b) {
var aValue = self.subData[a],
bValue = self.subData[b];
return a === '' ? 1
: b === '' ? -1
: self.sort[a] < self.sort[b] ? -1
: self.sort[a] > self.sort[b] ? 1
: 0;
});
}
self.items = self.keys.map(function(key) {
return {
key: key,
keySort: self.sort[key],
value: self.options.data[key],
valueSort: self.subData ? self.totals[key] : self.options.data[key]
};
});
self.sortBy = self.options.sort.key == 'key'
? [
{key: 'keySort', operator: self.options.sort.operator}
]
: [
{key: 'valueSort', operator: self.options.sort.operator},
{key: 'keySort', operator: '+'}
]
if (self.options.limit) {
self.items = Ox.sortBy(
self.items, self.sortBy
).slice(0, self.options.limit);
self.max = Ox.max(self.items.map(function(item) {
return self.subData ? Ox.sum(item.value) : item.value;
}));
}
self.max = self.max || 1;
if (self.options.rows == 2) {
self.row = 0;
}
self.$title = Ox.Bar({size: 16})
.append(
$('<div>')
.css({margin: '1px 0 0 4px'})
.html(self.options.title)
)
.appendTo(that);
self.$chart = $('<div>')
.css({position: 'absolute', top: '16px'})
.append(renderChart())
.appendTo(that);
function getColumns() {
return [
{
align: self.options.keyAlign,
format: self.options.formatKey,
id: 'key',
width: self.options.keyWidth,
visible: true
},
{
format: renderValue,
id: 'value',
width: self.valueWidth,
visible: true
}
];
}
function getWidths(values) {
var max, maxKeys,
total = Ox.sum(values),
totalWidth = Math.ceil(total / self.max * self.valueWidth),
widths = {};
Ox.forEach(values, function(value, key) {
widths[key] = Math.round(value / total * totalWidth);
});
while (Ox.sum(widths) != totalWidth) {
max = Ox.max(widths);
maxKeys = Object.keys(widths).filter(function(key) {
return widths[key] == max;
});
widths[maxKeys[0]] += Ox.sum(widths) < totalWidth ? 1 : -1;
}
return widths;
}
function renderChart() {
that.css({
width: self.options.width + 'px',
height: 16 + self.items.length * 16 + 'px',
overflowY: 'hidden'
});
return Ox.TableList({
columns: getColumns(),
items: self.items,
max: 0,
min: 0,
pageLength: self.items.length,
sort: self.sortBy,
width: self.options.width,
unique: 'key'
})
.css({
left: 0,
top: 0,
width: self.options.width + 'px',
height: self.items.length * 16 + 'px'
});
}
function renderValue(value, data) {
var $bars = [],
$element,
colors = [], len, widths;
if (!self.subData) {
$element = $bars[0] = Ox.Element({
element: '<div>',
tooltip: Ox.formatNumber(value)
+ ' (' + Ox.formatPercent(value * self.options.rows, self.sum, 2) + ')'
})
.css({
width: Math.ceil(value / self.max * self.valueWidth) + 'px',
height: '14px',
borderRadius: '4px',
marginLeft: '-4px'
});
colors[0] = Ox.isFunction(self.options.color)
? self.options.color(data.key) : self.options.color;
} else {
$element = $('<div>')
.css({
width: Math.ceil(self.totals[data.key] / self.max * self.valueWidth) + 'px',
height: '14px',
marginLeft: '-4px'
});
len = Ox.len(value);
widths = getWidths(value);
self.subKeys.forEach(function(subKey, subKeyIndex) {
var i = $bars.length,
subValue = value[subKey];
if (subValue) {
$bars[i] = Ox.Element({
element: '<div>',
/*
tooltip: Ox.formatNumber(self.totals[data.key])
+ ' (' + Ox.formatPercent(self.totals[data.key] * self.options.rows, self.sum, 2) + ')'
+ '<br>' + subKey + ': ' + Ox.formatNumber(subValue)
+ ' (' + Ox.formatPercent(subValue, self.totals[data.key], 2) + ')'
*/
tooltip: Ox.formatNumber(self.totals[data.key])
+ ' (' + Ox.formatPercent(self.totals[data.key] * self.options.rows, self.sum, 2) + ')'
})
.css({
float: 'left',
width: widths[subKey] + 'px',
height: '14px',
borderTopLeftRadius: i == 0 ? '4px' : 0,
borderBottomLeftRadius: i == 0 ? '4px' : 0,
borderTopRightRadius: i == len - 1 ? '4px' : 0,
borderBottomRightRadius: i == len - 1 ? '4px' : 0
})
.appendTo($element);
colors[i] = subKey == '' ? [128, 128, 128]
: Ox.isArray(self.options.color)
? self.options.color[subKeyIndex % self.options.color.length]
: Ox.isObject(self.options.color)
? self.options.color[subKey]
: self.options.color(subKey);
}
});
}
$bars.forEach(function($bar, i) {
/*
if (self.options.rows == 2) {
colors[i] = colors[i].map(function(v) {
return v + (self.row % 2 == 0 ? 16 : -16);
});
}
*/
['moz', 'o', 'webkit'].forEach(function(browser) {
$bar.css({
backgroundImage: '-' + browser
+ '-linear-gradient(top, rgb(' + colors[i].map(function(v) {
return Ox.limit(v + 16, 0, 255);
}).join(', ') + '), rgb(' + colors[i].map(function(v) {
return Ox.limit(v - 16, 0, 255);
}) + '))'
});
});
});
if (self.options.rows == 2) {
self.row++;
}
return $element;
}
return that;
};

View file

@ -0,0 +1,212 @@
'use strict';
/*@
Ox.ColumnList <f> Column List Widget
experimental
@*/
Ox.ColumnList = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
columns: [],
custom: {},
items: [],
list: 'table',
query: {conditions: [], operator: '&'},
resize: null,
width: 768
})
.options(options || {})
.update({
query: updateQuery,
width: function() {
self.columnWidths = getColumnWidths();
self.$panel.size(0, self.columnWidths[0]);
self.$panel.size(2, self.columnWidths[2]);
self.$lists.forEach(function($list, i) {
$list.options({itemWidth: self.columnWidths[i]});
});
}
})
.addClass('OxColumnList');
self.numberOfColumns = self.options.columns.length;
self.columnWidths = getColumnWidths();
self.flatItems = [];
self.ids = [{}, {}, {}];
self.options.items.forEach(function(item0) {
item0.items.forEach(function(item1) {
self.ids[1][item1.id] = [item0.id];
item1.items.forEach(function(item2) {
self.ids[2][item2.id] = [item0.id, item1.id];
self.flatItems.push(item2);
});
});
});
self.api = Ox.api(self.flatItems);
self.$lists = self.options.columns.map(function(column, i) {
return Ox.CustomList({
item: self.options.columns[i].item,
itemHeight: self.options.columns[i].itemHeight,
items: getItems(i),
itemWidth: self.columnWidths[i],
keys: self.options.columns[i].keys,
// FIXME: undefined max will overwrite CustomList default
max: self.options.columns[i].max,
resize: self.options.resize,
scrollbarVisible: true,
selected: self.options.columns[i].selected,
sort: self.options.columns[i].sort,
unique: 'id'
})
.bindEvent({
key_left: function() {
if (i > 0) {
self.$lists[i - 1].gainFocus();
that.triggerEvent('select', {
id: self.options.columns[i - 1].id,
ids: self.$lists[i - 1].options('selected')
});
}
},
key_right: function() {
var index, object, selected = self.$lists[i].options('selected');
if (selected.length) {
if (i < self.numberOfColumns - 1) {
if (self.$lists[i + 1].options('selected').length == 0) {
self.$lists[i + 1].selectPosition(0);
}
self.$lists[i + 1].gainFocus();
that.triggerEvent('select', {
id: self.options.columns[i + 1].id,
ids: self.$lists[i + 1].options('selected')
});
}
}
},
open: function(data) {
that.triggerEvent('open', {
id: column.id,
ids: data.ids
});
},
select: function(data) {
self.options.columns[i].selected = data.ids;
if (i < self.numberOfColumns - 1) {
self.$lists[i + 1].options({items: getItems(i + 1)});
}
if (i == 0 || data.ids.length) {
that.triggerEvent('select', {
id: column.id,
ids: data.ids
});
} else {
self.$lists[i - 1].gainFocus();
that.triggerEvent('select', {
id: self.options.columns[i - 1].id,
ids: self.$lists[i - 1].options('selected')
});
}
}
});
});
self.$panel = Ox.SplitPanel({
elements: self.$lists.map(function($list, index) {
return Ox.extend(
{element: $list},
index == 1 ? {} : {size: self.columnWidths[index]}
);
}),
orientation: 'horizontal'
});
that.setElement(self.$panel);
function getColumnWidths() {
return Ox.splitInt(self.options.width, self.numberOfColumns);
}
function getItems(i) {
var items;
if (i == 0) {
items = self.options.items;
} else {
items = [];
self.options.columns[i - 1].selected.forEach(function(id) {
var index;
if (i == 1) {
index = Ox.getIndexById(self.options.items, id);
items = items.concat(
self.options.items[index].items
);
} else if (i == 2) {
index = [];
Ox.forEach(self.options.columns[0].selected, function(id_) {
index[0] = Ox.getIndexById(
self.options.items, id_
);
index[1] = Ox.getIndexById(
self.options.items[index[0]].items, id
);
if (index[1] > -1) {
return false;
}
});
items = items.concat(
self.options.items[index[0]].items[index[1]].items
);
}
});
}
return items;
}
function updateQuery() {
if (self.options.query.conditions.length == 0) {
self.items = self.options.items;
} else {
self.api({
keys: ['id', '_ids'],
query: self.options.query
}, function(result) {
var ids = [[], [], []];
result.data.items.forEach(function(item) {
ids[0].push(self.ids[2][item.id][0]);
ids[1].push(self.ids[2][item.id][1]);
ids[2].push(item.id);
});
self.items = self.options.items.filter(function(item0) {
return Ox.contains(ids[0], item0.id);
});
self.items.forEach(function(item0) {
item0.items = item0.items.filter(function(item1) {
return Ox.contains(ids[1], item1.id);
});
item0.items.forEach(function(item1) {
item1.items = item1.items.filter(function(item2) {
return Ox.contains(ids[2], item2.id);
});
});
});
});
}
self.$lists.forEach(function($list, i) {
$list.options({items: i == 0 ? self.items : []});
});
}
that.sort = function(id, sort) {
var index = Ox.isNumber(id) ? id
: Ox.getIndexById(self.options.columns, id);
self.$lists[index].options({sort: sort});
self.options.columns[index].sort = sort;
};
return that;
};

View file

@ -0,0 +1,156 @@
'use strict';
/*@
Ox.CustomList <f> Custom List Widget
experimental
@*/
Ox.CustomList = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
draggable: false,
item: null,
itemHeight: 32,
items: null,
itemWidth: 256,
keys: [],
max: -1,
min: 0,
pageLength: 100,
query: {conditions: [], operator: '&'},
resize: null,
scrollbarVisible: false,
selected: [],
sort: [],
sortable: false,
sums: [],
unique: ''
})
.options(options || {})
.update({
items: function() {
self.$list.options({items: self.options.items});
},
itemWidth: function() {
var width = self.options.itemWidth - Ox.UI.SCROLLBAR_SIZE;
if (self.options.resize) {
that.find('.OxItem').each(function(element) {
self.options.resize($(this), width);
});
}
},
query: function() {
self.$list.options({query: self.options.query});
},
selected: function() {
self.$list.options({selected: self.options.selected});
// FIXME: TableList doesn't trigger event here
that.triggerEvent('select', {ids: self.options.selected});
},
sort: function() {
self.$list.options({sort: self.options.sort});
}
})
.addClass('OxCustomList');
self.$list = Ox.List({
construct: function(data) {
return self.options.item(
data, self.options.itemWidth - Ox.UI.SCROLLBAR_SIZE
).addClass('OxTarget');
},
draggable: self.options.draggable,
itemHeight: self.options.itemHeight,
itemWidth: self.options.itemWidth
- self.options.scrollbarVisible * Ox.UI.SCROLLBAR_SIZE,
items: self.options.items,
keys: self.options.keys.concat(self.options.unique),
max: self.options.max,
min: self.options.min,
orientation: 'vertical',
pageLength: self.options.pageLength,
query: Ox.clone(self.options.query, true),
selected: self.options.selected,
sort: Ox.clone(self.options.sort, true),
sortable: self.options.sortable,
sums: self.options.sums,
type: 'text',
unique: self.options.unique
})
.css({
top: 0,
overflowY: (self.options.scrollbarVisible ? 'scroll' : 'hidden')
})
.bindEvent(function(data, event) {
if (event == 'select') {
self.options.selected = data.ids;
}
that.triggerEvent(event, data);
})
.appendTo(that);
that.api = self.$list.options('items');
/*@
gainFocus <f> gain Focus
@*/
that.gainFocus = function() {
self.$list.gainFocus();
return that;
};
that.getPasteIndex = function() {
return self.$list.getPasteIndex();
};
/*@
hasFocus <f> has Focus
@*/
that.hasFocus = function() {
return self.$list.hasFocus();
};
that.invertSelection = function() {
self.$list.invertSelection();
return that;
};
/*@
loseFocus <f> lose Focus
@*/
that.loseFocus = function() {
self.$list.loseFocus();
return that;
};
that.selectAll = function() {
self.$list.selectAll();
return that;
};
/*@
selectPosition <f> select position
@*/
that.selectPosition = function(pos) {
self.$list.selectPosition(pos);
return that;
};
that.selectSelected = function(offset) {
that.$list.selectSelected(offset);
return that;
};
/*@
size <f> size
@*/
that.size = function() {
self.$list.size();
return that;
};
return that;
};

View file

@ -0,0 +1,226 @@
'use strict';
/*@
Ox.IconItem <f> IconItem Object
options <o> Options object
borderRadius <n|0> Border radius for icon images
find <s|''> String to be highlighted
iconHeight <n|128> Icon height
iconWidth <n|128> Icon width
imageHeight <n|128> Icon image height
imageWidth <n|128> Icon image width
id <s> Element id
info <s> Icon info
size <n|128> Icon size
title <s> Title
url <s> Icon url
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> IconItem Object
@*/
Ox.IconItem = function(options, self) {
//Ox.Log('List', 'IconItem', options, self)
self = self || {};
var that = Ox.Element({}, self)
.defaults({
borderRadius: 0,
extra: null,
find: '',
iconHeight: 128,
iconWidth: 128,
imageHeight: 128,
imageWidth: 128,
itemHeight: 192,
itemWidth: 128,
id: '',
info: '',
title: '',
url: ''
})
.options(options || {});
Ox.extend(self, {
fontSize: self.options.itemWidth == 64 ? 6 : 9,
infoIsObject: Ox.isObject(self.options.info),
lineLength: self.options.itemWidth == 64 ? 15 : 23,
lines: self.options.itemWidth == 64 ? 4 : 5,
url: Ox.UI.PATH + 'png/transparent.png'
});
self.title = formatText(self.options.title, self.lines - 1 - self.infoIsObject, self.lineLength);
if (!self.infoIsObject) {
self.info = formatText(self.options.info, 5 - self.title.split('<br/>').length, self.lineLength);
} else {
self.title = $('<div>').css({fontSize: self.fontSize + 'px'}).html(self.title);
self.info = $('<div>').append(
self.options.info.css(Ox.extend(self.options.info.css('width') == '0px' ? {
width: Math.round(self.options.itemWidth / 2) + 'px'
} : {}, {
padding: 0,
margin: '1px auto',
fontSize: self.fontSize + 'px',
textShadow: 'none'
}))
);
}
that.css({
// 2 * 2 px margin (.css), 2 * 2 px border (here)
width: self.options.itemWidth + 4 + 'px',
height: self.options.itemHeight + + 4 + 'px'
});
that.$icon = $('<div>')
.addClass('OxIcon')
.css({
top: (self.options.iconWidth == 64 ? -64 : -122) + 'px',
width: self.options.iconWidth + 4 + 'px',
height: self.options.iconHeight + 4 + 'px'
});
that.$iconImage = $('<img>')
.addClass('OxLoading OxTarget')
.attr({
src: self.url
})
.css({
width: self.options.imageWidth + 'px',
height: self.options.imageHeight + 'px',
borderRadius: self.options.borderRadius + 4 + 'px'
})
.mousedown(mousedown)
.mouseenter(mouseenter)
.mouseleave(mouseleave);
self.options.url && that.$iconImage.one('load', load);
that.$textBox = $('<div>')
.addClass('OxText')
.css({
top: self.options.iconHeight - (self.options.itemWidth == 64 ? 32 : 62) + 'px',
width: self.options.itemWidth + 4 + 'px',
height: (self.options.itemWidth == 64 ? 30 : 58) + 'px'
});
that.$text = $('<div>')
.addClass('OxTarget')
.css({
fontSize: self.fontSize + 'px'
})
.mouseenter(mouseenter)
.mouseleave(mouseleave);
if (!self.infoIsObject) {
that.$text.html(
(self.title ? self.title + '<br/>' : '')
+ '<span class="OxInfo">' + self.info + '</span>'
);
} else {
that.$text.append(self.title).append(self.info);
}
that.$reflection = $('<div>')
.addClass('OxReflection')
.css({
top: self.options.iconHeight + (self.options.itemWidth == 64 ? 0 : 2) + 'px',
width: self.options.itemWidth + 4 + 'px',
height: self.options.itemHeight - self.options.iconHeight + 'px'
});
that.$reflectionImage = $('<img>')
.addClass('OxLoading')
.attr({
src: self.url
})
.css({
width: self.options.imageWidth + 'px',
height: self.options.imageHeight + 'px',
// firefox is 1px off when centering images with odd width and scaleY(-1)
paddingLeft: ($.browser.mozilla && self.options.imageWidth % 2 ? 1 : 0) + 'px',
borderRadius: self.options.borderRadius + 'px'
});
that.$gradient = $('<div>')
.css({
// `+2` is a temporary fix for https://code.google.com/p/chromium/issues/detail?id=167198
width: self.options.itemWidth + 2 + 'px',
height: self.options.itemWidth / 2 + 'px'
});
that.append(
that.$reflection.append(
that.$reflectionImage
).append(
that.$gradient
)
).append(
that.$textBox.append(
that.$text
)
).append(
that.$icon.append(
that.$iconImage
)
);
if (self.options.extra) {
that.$extra = $('<div>')
.addClass('OxTarget')
.css({
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
width: self.options.imageWidth + 'px',
height: self.options.imageHeight + 'px',
border: '2px solid transparent',
margin: 'auto',
cursor: 'pointer',
overflow: 'hidden'
})
that.$icon.append(
that.$extra.append(
self.options.extra
)
);
}
function formatText(text, maxLines, maxLength) {
text = Ox.isArray(text) ? text.join(', ') : text;
var lines = Ox.wordwrap(text, maxLength, true).split('\n');
// if the text has too many lines, replace the last line with the
// truncated rest (including the last line) and discard all extra lines
if (lines.length > maxLines) {
lines[maxLines - 1] = Ox.truncate(
lines.slice(maxLines - 1).join(''), 'center', maxLength
).replace('…', '<span class="OxLight">…</span>');
lines = lines.slice(0, maxLines);
}
return Ox.highlight(
lines.join('<br/>'), self.options.find, 'OxHighlight', true
);
}
function load() {
that.$iconImage.attr({
src: self.options.url
})
.one('load', function() {
that.$iconImage.removeClass('OxLoading');
that.$reflectionImage
.attr({
src: self.options.url
})
.removeClass('OxLoading');
});
}
function mousedown(e) {
// fixme: preventDefault keeps image from being draggable in safari - but also keeps the list from getting focus
// e.preventDefault();
}
function mouseenter() {
that.addClass('OxHover');
}
function mouseleave() {
that.removeClass('OxHover');
}
return that;
};

View file

@ -0,0 +1,308 @@
'use strict';
/*@
Ox.IconList <f> IconList Object
options <o> Options object
borderRadius <n|0> border radius for icon images
centered <b|false> scroll list so selection is always centered
defaultRatio <n|1> aspect ratio of icon placeholders
draggable <b|false> If true, items can be dragged
fixedRatio <b|n|false> if set to a number, icons have a fixed ratio
id <s|''> element id
item <f|null> called with data, sort, size,
extends data with information needed for constructor
itemConstructor <f|Ox.IconItem> contructor used to create item
items <f|null> items array or callback function
keys <a|[]> available item keys
max <n|-1> maximum selected selected items
min <n|0> minimum of selcted items
orientation <s|both> orientation ("horizontal", "vertical" or "both")
pageLength <n|100> Number of items per page (if orientation != "both")
query <o> Query
selectAsYouType <s|''> If set to a key, enables select-as-you-type
selected <a|[]> array of selected items
size <n|128> list size
sort <a|[]> sort keys
sums <[s]|[]> Sums to be included in totals
self <o> Shared private variable
([options[, self]]) -> <o:Ox.List> IconList Object
@*/
Ox.IconList = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
borderRadius: 0,
centered: false,
defaultRatio: 1,
draggable: false,
find: '',
fixedRatio: false,
id: '',
item: null,
itemConstructor: Ox.IconItem,
items: null,
keys: [],
max: -1,
min: 0,
orientation: 'both',
pageLength: 100,
query: {conditions: [], operator: '&'},
selectAsYouType: '',
selected: [],
size: 128,
sort: [],
sums: [],
unique: ''
})
.options(options || {})
.update({
items: function() {
self.$list.options({items: self.options.items});
},
query: function() {
self.$list.options({query: self.options.query});
},
selected: function() {
self.$list.options({selected: self.options.selected});
},
sort: function() {
updateKeys();
self.$list.options({sort: self.options.sort});
}
});
if (self.options.fixedRatio) {
self.options.defaultRatio = self.options.fixedRatio;
}
if (Ox.isArray(self.options.items)) {
self.options.keys = Ox.unique(Ox.flatten(
self.options.items.map(function(item) {
return Object.keys(item);
})
));
}
self.iconWidth = self.options.size;
self.iconHeight = self.options.fixedRatio > 1
? Math.round(self.options.size / self.options.fixedRatio)
: self.options.size;
self.itemWidth = self.options.size;
self.itemHeight = self.iconHeight + self.options.size * 0.5;
self.$list = Ox.List({
centered: self.options.centered,
// fixme: change all occurences of construct to render
construct: constructItem,
draggable: self.options.draggable,
id: self.options.id,
itemHeight: self.itemHeight,
items: self.options.items,
itemWidth: self.itemWidth,
keys: self.options.keys,
max: self.options.max,
min: self.options.min,
orientation: self.options.orientation,
pageLength: self.options.pageLength,
query: self.options.query,
selectAsYouType: self.options.selectAsYouType,
selected: self.options.selected,
sort: self.options.sort,
sums: self.options.sums,
type: 'icon',
unique: self.options.unique
})
.addClass('OxIconList Ox' + Ox.toTitleCase(self.options.orientation))
.bindEvent(function(data, event) {
if (event == 'select') {
self.options.selected = data.ids;
}
that.triggerEvent(event, data);
});
that.setElement(self.$list);
updateKeys();
function constructItem(data) {
var isEmpty = Ox.isEmpty(data);
data = !isEmpty
? self.options.item(data, self.options.sort, self.options.size)
: {
width: Math.round(self.options.size * (
self.options.defaultRatio >= 1 ? 1 : self.options.defaultRatio
)),
height: Math.round(self.options.size / (
self.options.defaultRatio <= 1 ? 1 : self.options.defaultRatio
))
};
return self.options.itemConstructor(Ox.extend(data, {
borderRadius: self.options.borderRadius,
find: self.options.find,
iconHeight: self.iconHeight,
iconWidth: self.iconWidth,
imageHeight: data.height,
imageWidth: data.width,
itemHeight: self.itemHeight,
itemWidth: self.itemWidth
//height: Math.round(self.options.size / (ratio <= 1 ? 1 : ratio)),
//size: self.options.size,
//width: Math.round(self.options.size * (ratio >= 1 ? 1 : ratio))
}));
}
function updateKeys() {
self.$list.options({
keys: Ox.unique(
[self.options.sort[0].key].concat(self.options.keys)
)
});
}
/*@
closePreview <f> close preview
() -> <o> the list
@*/
that.closePreview = function() {
self.$list.closePreview();
return that;
};
/*@
gainFocus <f> gainFocus
@*/
that.gainFocus = function() {
self.$list.gainFocus();
return that;
};
that.getPasteIndex = function() {
return self.$list.getPasteIndex();
};
/*@
hasFocus <f> hasFocus
@*/
that.hasFocus = function() {
return self.$list.hasFocus();
};
that.invertSelection = function() {
self.$list.invertSelection();
return that;
};
/*@
loseFocus <f> loseFocus
@*/
that.loseFocus = function() {
self.$list.loseFocus();
return that;
};
/*@
openPreview <f> openPreview
@*/
that.openPreview = function() {
self.$list.openPreview();
return that;
};
/*@
reloadList <f> reload list
() -> <o> the list
@*/
that.reloadList = function() {
self.$list.reloadList();
return that;
};
/*@
scrollToSelection <f> scroll list to selection
() -> <o> the list
@*/
that.scrollToSelection = function() {
self.$list.scrollToSelection();
return that;
};
that.selectAll = function() {
self.$list.selectAll();
return that;
};
that.selectPosition = function(pos) {
self.$list.selectPosition(pos);
return that;
};
that.selectSelected = function(offset) {
self.$list.selectSelected(offset);
return that;
};
/*@
size <f> get size of list
() -> <n> size
@*/
that.size = function() {
self.$list.size();
return that;
};
// fixme: deprecate, use options()
/*@
sortList <f> sort list
(key, operator) -> <o> the list
key <s> sort key
operator <s> sort operator ("+" or "-")
@*/
that.sortList = function(key, operator) {
self.options.sort = [{
key: key,
operator: operator
}];
updateKeys();
self.$list.sortList(key, operator);
return that;
};
/*@
value <f> get/set value of item in list
(id) -> <o> get all data for item
(id, key) -> <s> get value of key of item with id
(id, key, value) -> <f> set value, returns IconList
(id, {key: value, ...}) -> <f> set values, returns IconList
@*/
that.value = function() {
var args = Ox.slice(arguments),
id = args.shift(),
sort = false;
if (arguments.length == 1) {
return self.$list.value(id);
} else if (arguments.length == 2 && Ox.isString(arguments[1])) {
return self.$list.value(id, arguments[1]);
} else {
Ox.forEach(Ox.makeObject(args), function(value, key) {
self.$list.value(id, key, value);
if (key == self.unique) {
// unique id has changed
self.options.selected = self.options.selected.map(function(id_) {
return id_ == id ? value : id_
});
id = value;
}
if (key == self.options.sort[0].key) {
// sort key has changed
sort = true;
}
});
sort && self.$list.sort();
return that;
}
};
return that;
};

View file

@ -0,0 +1,303 @@
'use strict';
/*@
Ox.InfoList <f> Info List
options <o> Options
self <o> Shared private variable
([options[, self]]) -> <o:Ox.List> Info List
@*/
Ox.InfoList = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
borderRadius: 0,
defaultRatio: 1,
draggable: false,
id: '',
item: null,
items: null,
keys: [],
max: -1,
min: 0,
query: {conditions: [], operator: '&'},
selectAsYouType: '',
selected: [],
size: 192,
sort: [],
sums: [],
unique: ''
})
.options(options || {})
.update({
items: function() {
self.$list.options({items: self.options.items});
},
query: function() {
self.$list.options({query: self.options.query});
},
selected: function() {
self.$list.options({selected: self.options.selected});
},
sort: function() {
updateKeys();
self.$list.options({sort: self.options.sort});
},
width: function() {
// FIXME: don't use classname for this lookup
var width = getItemWidth();
$('.OxInfoElement').each(function() {
var $parent = $(this).parent(),
id = parseInt(/OxId(.*?)$/.exec(this.className)[1], 10);
$parent.css({width: width - 144});
$parent.parent().css({width: width - 144});
$parent.parent().parent().css({width: width - 8});
Ox.$elements[id].options({width: width - 152});
});
}
});
self.iconSize = Math.round(self.options.size * 2/3);
self.itemHeight = self.options.size;
that.setElement(
self.$list = Ox.List({
construct: constructItem,
draggable: self.options.draggable,
id: self.options.id,
itemHeight: self.itemHeight,
items: self.options.items,
itemWidth: getItemWidth(),
keys: self.options.keys,
max: self.options.max,
min: self.options.min,
orientation: 'vertical',
pageLength: 10,
query: self.options.query,
selectAsYouType: self.options.selectAsYouType,
selected: self.options.selected,
sort: self.options.sort,
sums: self.options.sums,
type: 'info',
unique: self.options.unique
})
.addClass('OxInfoList')
.bindEvent(function(data, event) {
if (event == 'select') {
self.options.selected = data.ids;
}
that.triggerEvent(event, data);
})
);
updateKeys();
function constructItem(data) {
var isEmpty = Ox.isEmpty(data),
data = !isEmpty
? self.options.item(data, self.options.sort, self.options.size)
: {
icon: {
width: Math.round(self.iconSize * (
self.options.defaultRatio >= 1 ? 1 : self.options.defaultRatio
)),
height: Math.round(self.iconSize / (
self.options.defaultRatio <= 1 ? 1 : self.options.defaultRatio
))
},
info: {}
},
$icon = Ox.Element()
.css({
float: 'left',
width: '132px',
height: '192px',
margin: '2px'
})
.append(
Ox.IconItem(Ox.extend(data.icon, {
borderRadius: self.options.borderRadius,
iconHeight: self.iconSize,
iconWidth: self.iconSize,
imageHeight: data.icon.height,
imageWidth: data.icon.width,
itemHeight: self.itemHeight,
itemWidth: 128
}))
.addClass('OxInfoIcon')
.css({
position: 'absolute'
})
),
$info = Ox.Element()
.css({
float: 'left',
width: getItemWidth() - 144 + 'px',
height: 196 + 'px'
}),
$infobox = Ox.Element()
.css({
position: 'absolute',
width: getItemWidth() - 144 + 'px',
height: 196 + 'px',
marginTop: '-2px',
overflow: 'hidden'
})
.appendTo($info),
$item = Ox.Element()
.css({
width: getItemWidth() - 8 + 'px',
height: 196 + 'px',
margin: '4px'
})
.append($icon)
.append($info);
if (!isEmpty) {
var $element = data.info.element(Ox.extend(data.info.options, {
width: getItemWidth() - 152
}))
.addClass('OxInfoElement');
data.info.css && $element.css(data.info.css);
data.info.events && $element.bindEvent(data.info.events);
$element.addClass('OxId' + $element.oxid); // fixme: needed?
$infobox.append($element);
}
return $item;
}
function getItemWidth(cached) {
if (!cached) {
self.cachedWidth = that.width() - Ox.UI.SCROLLBAR_SIZE;
} else if (!self.cachedWidth || self.cachedWidthTime < +new Date() - 5000) {
self.cachedWidth = that.width() - Ox.UI.SCROLLBAR_SIZE;
self.cachedWidthTime = +new Date();
}
return self.cachedWidth;
}
function updateKeys() {
self.$list.options({
keys: Ox.unique(
[self.options.sort[0].key].concat(self.options.keys)
)
});
}
/*@
closePreview <f> closePreview
@*/
that.closePreview = function() {
self.$list.closePreview();
return that;
};
/*@
gainFocus <f> gainFocus
@*/
that.gainFocus = function() {
self.$list.gainFocus();
return that;
};
that.getPasteIndex = function() {
return self.$list.getPasteIndex();
};
/*@
hasFocus <f> hasFocus
@*/
that.hasFocus = function() {
return self.$list.hasFocus();
};
that.invertSelection = function() {
self.$list.invertSelection();
return that;
};
/*@
loseFocus <f> loseFocus
@*/
that.loseFocus = function() {
self.$list.loseFocus();
return that;
};
/*@
reloadList <f> reloadList
@*/
that.reloadList = function(stayAtPosition) {
self.$list.reloadList(stayAtPosition);
return that;
};
/*@
scrollToSelection <f> scrollToSelection
@*/
that.scrollToSelection = function() {
self.$list.scrollToSelection();
return that;
};
that.selectAll = function() {
self.$list.selectAll();
return that;
};
that.selectPosition = function(pos) {
self.$list.selectPosition(pos);
return that;
};
that.selectSelected = function(offset) {
self.$list.selectSelected(offset);
return that;
};
/*@
size <f> size
@*/
that.size = function() {
self.$list.size();
return that;
};
/*@
sortList <f> sortList
(key, operator) -> <o>
@*/
that.sortList = function(key, operator) {
self.options.sort = [{
key: key,
operator: operator
}];
updateKeys();
self.$list.sortList(key, operator);
return that;
};
/*@
value <f> value
(id) -> values
(id, key) -> value
(id, key, value) -> <o>
(id, {key: value}) -> <o>
@*/
that.value = function() {
var args = Ox.slice(arguments),
id = args.shift();
if (arguments.length == 1) {
return self.$list.value(id);
} else if (arguments.length == 2 && Ox.isString(arguments[1])) {
return self.$list.value(id, arguments[1]);
} else {
Ox.forEach(Ox.makeObject(args), function(value, key) {
self.$list.value(id, key, value);
});
return that;
}
};
return that;
};

1863
source/UI/js/List/List.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,55 @@
'use strict';
/*@
Ox.ListItem <f> ListItem Object
options <o> Options object
construct <f> construct function
data <o|{}> item data
draggable <b|false> can be dragged
position <n|0> item position
unique <s|''> unique key
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> ListItem Object
cancel <!> triggered if cancel button is pressed
save <!> triggered if save button is pressed
@*/
Ox.ListItem = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
construct: null,
data: {},
draggable: false,
position: 0,
unique: ''
})
.options(options || {})
.update({
data: function() {
constructItem(true);
},
position: function() {
that.data({position: self.options.position});
}
});
constructItem();
function constructItem(update) {
var $element = self.options.construct(self.options.data)
.addClass('OxItem')
.data({
id: self.options.data[self.options.unique],
position: self.options.position
});
if (update) {
that.hasClass('OxSelected') && $element.addClass('OxSelected');
}
that.setElement($element);
}
return that;
};

View file

@ -0,0 +1,64 @@
'use strict';
/*@
Ox.SortList <f> Sortable List
options <o> Options object
items <[o]|[]> Items ({id: ..., title: ...})
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> SortList Object
@*/
Ox.SortList = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
items: [],
scrollbarVisible: false
})
.options(options || {})
.update({
items: function() {
self.items = getItems();
self.$list.reloadList();
that.triggerEvent('sort', {
ids: self.items.map(function(item) {
return item.id;
})
});
}
});
self.items = getItems();
self.$list = Ox.TableList({
columns: [
{id: 'title', visible: true}
],
items: self.items,
max: 1,
scrollbarVisible: self.options.scrollbarVisible,
sort: [{key: 'position', operator: '+'}],
sortable: true,
unique: 'id'
})
.bindEvent({
move: function(data) {
self.options.items.sort(function(a, b) {
return data.ids.indexOf(a.id) - data.ids.indexOf(b.id);
});
that.triggerEvent('sort', data);
}
});
function getItems() {
return self.options.items.map(function(item, position) {
return {id: item.id, title: item.title, position: position};
});
}
that.setElement(self.$list);
return that;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,305 @@
'use strict';
/*@
Ox.TreeList <f> Tree List
options <o> Options object
data <f|null> Data to be parsed as items (needs documentation)
expanded <b|false> If true, and data is not null, all items are expanded
icon <o|f|null> Image URL, or function that returns an image object
items <a|[]> Array of items
max <n|-1> Maximum number of items that can be selected, -1 unlimited
min <n|0> Minimum number of items that have to be selected
selected <a|[]> Selected ids
width <n|256> List width
self <o> Shared private variable
([options[, self]]) -> <o:Ox.List> Tree List Object
@*/
Ox.TreeList = function(options, self) {
// fixme: expanding the last item should cause some scroll
self = self || {};
var that = Ox.Element({}, self)
.defaults({
data: null,
expanded: false,
icon: null,
items: [],
max: 1,
min: 0,
selected: [],
width: 'auto'
})
.options(options || {})
.update({
data: function() {
self.options.items = getItems();
self.$list.options({items: parseItems()});
},
selected: function() {
//self.$list.options({selected: self.options.selected});
selectItem({ids: self.options.selected});
self.$list.scrollToSelection();
},
width: function() {
// ...
}
});
if (self.options.data) {
self.options.items = getItems();
}
that.setElement(
self.$list = Ox.List({
_tree: true,
construct: constructItem,
itemHeight: 16,
items: parseItems(),
itemWidth: self.options.width,
keys: ['expanded', 'id', 'items', 'level', 'title'],
max: self.options.max,
min: self.options.min,
unique: 'id'
})
.addClass('OxTableList OxTreeList')
.css({
width: self.options.width + 'px',
overflowY: 'scroll'
})
.bindEvent(function(data, event) {
if (event == 'anyclick') {
clickItem(data);
} else if (event == 'toggle') {
toggleItems(data);
}
that.triggerEvent(event, data);
})
);
self.options.selected.length && selectItem({ids: self.options.selected});
function clickItem(e) {
var $target = $(e.target),
$item, id, item;
if ($target.is('.OxToggle')) {
$item = $target.parent().parent();
id = $item.data('id');
item = getItemById(id);
toggleItem(item, !item.expanded);
}
}
function constructItem(data) {
var $item = $('<div>').css({
width: self.options.width == 'auto' ? '100%'
: self.options.width - Ox.UI.SCROLLBAR_SIZE + 'px'
}),
$cell = $('<div>').addClass('OxCell').css({width: '8px'}),
$icon = data.id ? getIcon(data.id, data.expanded || (
data.items ? false : null
)) : null,
padding = data.level * 16 - 8;
if (data.level) {
$('<div>')
.addClass('OxCell OxTarget')
.css({width: padding + 'px'})
.appendTo($item);
}
$cell.appendTo($item);
$icon && $icon.addClass(data.items ? 'OxToggle' : 'OxTarget').appendTo($cell);
$('<div>')
.addClass('OxCell OxTarget')
.css({
width: self.options.width - padding - 32 - Ox.UI.SCROLLBAR_SIZE + 'px'
})
.html(data.title || '')
.appendTo($item);
return $item;
}
function getIcon(id, expanded) {
var isFunction = Ox.isFunction(self.options.icon),
$icon = isFunction ? self.options.icon(id, expanded) : null;
if (!$icon) {
if (expanded === null) {
if (!$icon && self.options.icon && !isFunction) {
$icon = $('<img>').attr({src: self.options.icon});
}
} else {
$icon = $('<img>').attr({src: Ox.UI.getImageURL(
'symbol' + (expanded ? 'Down' : 'Right')
)});
}
}
return $icon;
}
function getItemById(id, items, level) {
var ret = null;
items = items || self.options.items;
level = level || 0;
Ox.forEach(items, function(item) {
if (item.id == id) {
ret = Ox.extend(item, {
level: level
});
return false; // break
}
if (item.items) {
ret = getItemById(id, item.items, level + 1);
if (ret) {
return false; // break
}
}
});
return ret;
}
function getItems() {
var items = [];
Ox.sort(Object.keys(self.options.data)).forEach(function(key) {
items.push(parseData(key, self.options.data[key]));
});
return items;
}
function getParent(id, items) {
var ret;
Ox.forEach(items, function(item) {
if (item.items) {
if (Ox.getObjectById(item.items, id)) {
ret = item.id;
} else {
ret = getParent(id, item.items);
}
if (ret) {
return false; // break
}
}
});
return ret;
}
function parseData(key, value) {
var ret = {
id: Ox.uid().toString(),
title: Ox.encodeHTMLEntities(key.toString()) + ': '
},
type = Ox.typeOf(value);
if (type == 'array' || type == 'object') {
ret.expanded = self.options.expanded;
ret.title += Ox.toTitleCase(type)
+ ' <span class="OxLight">[' + Ox.len(value) + ']</span>';
ret.items = type == 'array' ? value.map(function(v, i) {
return parseData(i, v);
}) : Ox.sort(Object.keys(value)).map(function(k) {
return parseData(k, value[k]);
});
} else {
ret.title += (
type == 'function'
? value.toString().split('{')[0]
: Ox.encodeHTMLEntities(JSON.stringify(value))
.replace(/(^&quot;|&quot;$)/g, '<span class="OxLight">"</span>')
);
}
return ret;
}
function parseItems(items, level) {
var ret = [];
items = items || self.options.items;
level = level || 0;
items.forEach(function(item, i) {
if (item.items && self.options.expanded) {
item.expanded = true;
}
var item_ = Ox.extend({level: level}, item, item.items ? {
items: item.expanded
? parseItems(item.items, level + 1)
: []
} : {});
ret.push(item_);
if (item.items) {
ret = ret.concat(item_.items);
}
});
return ret;
}
function selectItem(data) {
var id = data.ids[0], parent = id, parents = [];
while (parent = getParent(parent, self.options.items)) {
parents.push(parent);
}
parents = parents.reverse();
toggleItems({
expanded: true,
ids: parents
});
self.$list.options({selected: data.ids})
}
function toggleItem(item, expanded) {
var $img, pos;
item.expanded = expanded;
that.find('.OxItem').each(function() {
var $item = $(this);
if ($item.data('id') == item.id) {
$img = $item.find('.OxToggle');
pos = $item.data('position');
return false;
}
});
//self.$list.value(item.id, 'expanded', expanded);
$img.attr({
src: getIcon(item.id, expanded).attr('src')
});
if (expanded) {
self.$list.addItems(
pos + 1, parseItems(item.items, item.level + 1)
);
} else {
self.$list.removeItems(
pos + 1, parseItems(item.items, item.level + 1).length
);
}
}
function toggleItems(data) {
data.ids.forEach(function(id, i) {
var item = getItemById(id);
if (item.items && data.expanded != !!item.expanded) {
toggleItem(item, data.expanded);
}
});
}
/*@
gainFocus <f> gainFocus
@*/
that.gainFocus = function() {
self.$list.gainFocus();
return that;
};
/*@
hasFocus <f> hasFocus
@*/
that.hasFocus = function() {
return self.$list.hasFocus();
};
/*@
loseFocus <f> loseFocus
@*/
that.loseFocus = function() {
self.$list.loseFocus();
return that;
};
return that;
};

1641
source/UI/js/Map/Map.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,73 @@
'use strict';
/*@
Ox.MapImage <f> MapImage Object
options <o> Options object
height <n|360> image height (px)
place <o|null> Object with south, west, north and east properties
type <s|satellite> map type ('hybrid', 'roadmap', 'satellite', 'terrain')
width <n|640> image width (px)
self <o> shared private variable
([options[, self]]) -> <o:Ox.Element> MapImage Object
@*/
Ox.MapImage = function(options, self) {
var self = self || {},
that = Ox.Element('<img>', self)
.defaults({
backgroundColor: [0, 0, 0, 0],
borderColor: [0, 0, 0, 0],
borderWidth: 0,
height: 640,
markers: [],
place: null,
type: 'satellite',
width: 640
})
.options(options || {});
self.src = document.location.protocol
+ '//maps.google.com/maps/api/staticmap?sensor=false' +
'&size=' + self.options.width + 'x' + self.options.height +
'&maptype=' + self.options.type;
if (self.options.place) {
self.src += '&path=fillcolor:' + formatColor(self.options.backgroundColor)
+ '|color:0x' + formatColor(self.options.borderColor)
+ '|weight:' + self.options.borderWidth + '|'
+ [
['south', 'west'],
['north', 'west'],
['north', 'east'],
['south', 'east'],
['south', 'west']
].map(function(keys) {
return [
self.options.place[keys[0]],
self.options.place[keys[1]]
].join(',');
}).join('|');
} else {
self.src += '&center=0,0&zoom=2'
}
if (self.options.markers.length) {
self.src += '&markers='
+ self.options.markers.map(function(marker) {
return [marker.lat, marker.lng].join(',')
}).join('|');
}
that.attr({
src: self.src
});
function formatColor(color) {
return color.map(function(c) {
return Ox.pad(c.toString(16), 'left', 2, '0');
}).join('')
}
return that;
};

View file

@ -0,0 +1,326 @@
'use strict';
/*@
Ox.MapMarker <f> MapMarker
(options) -> <o> MapMarker object
options <o> Options object
color <a|[255, 0, 0]> marker color
map <o|null> map
place <o|null> place
size <n|16> size
@*/
Ox.MapMarker = function(options) {
options = Ox.extend({
map: null,
place: null
}, options);
var that = this,
areaSize = {
100: 10, // 10 x 10 m
10000: 12, // 100 x 100 m
1000000: 14, // 1 x 1 km
100000000: 16, // 10 x 10 km
10000000000: 18, // 100 x 100 km
1000000000000: 20, // 1,000 x 1,000 km
100000000000000: 22 // 10,000 x 10,000 km
},
themeData = Ox.Theme.getThemeData(),
typeColor = {};
[
'country', 'region', 'city', 'borough',
'street', 'building', 'feature'
].forEach(function(type) {
typeColor[type] = themeData[
'mapPlace' + Ox.toTitleCase(type) + 'Color'
];
});
Ox.forEach(options, function(val, key) {
that[key] = val;
});
that.marker = new google.maps.Marker({
raiseOnDrag: false,
shape: {coords: [8, 8, 8], type: 'circle'}
//title: that.place.name,
//zIndex: 1000
});
setOptions();
function click() {
var key = that.map.getKey(),
place, bounds, southWest, northEast;
if (!that.place.selected) {
if (
that.map.options('editable')
&& (key == 'meta' || key == 'shift')
) {
place = that.map.getSelectedPlace();
}
if (place) {
bounds = new google.maps.LatLngBounds(
new google.maps.LatLng(place.south, place.west),
new google.maps.LatLng(place.north, place.east)
);
bounds = bounds.union(that.place.bounds);
southWest = bounds.getSouthWest();
northEast = bounds.getNorthEast();
that.map.newPlace(new Ox.MapPlace({
// fixme: duplicated, see Ox.Map.js
alternativeNames: [],
countryCode: '',
editable: true,
geoname: '',
id: '_' + Ox.encodeBase32(Ox.uid()), // fixme: stupid
map: that.map,
name: '',
type: 'feature',
south: southWest.lat(),
west: southWest.lng(),
north: northEast.lat(),
east: northEast.lng()
}));
} else {
that.map.options({selected: that.place.id});
}
} else {
if (key == 'meta') {
that.map.options({selected: null});
} else {
that.map.panToPlace();
}
}
}
function dblclick() {
that.place.selected && that.map.zoomToPlace();
}
function dragstart(e) {
Ox.$body.addClass('OxDragging');
}
function drag(e) {
var northSouth = (that.place.north - that.place.south) / 2,
lat = Ox.limit(
e.latLng.lat(),
Ox.MIN_LATITUDE + northSouth,
Ox.MAX_LATITUDE - northSouth
),
lng = e.latLng.lng(),
span = Math.min(
that.place.sizeEastWest * Ox.getDegreesPerMeter(lat) / 2, 179.99999999
),
degreesPerMeter = Ox.getDegreesPerMeter(lat);
that.place.south += lat - that.place.lat;
that.place.north += lat - that.place.lat;
that.place.west = lng - span;
that.place.east = lng + span;
if (that.place.west < -180) {
that.place.west += 360;
} else if (that.place.east > 180) {
that.place.east -= 360;
}
Ox.Log('Map', 'west', that.place.west, 'east', that.place.east, 'span', span);
that.place.update();
that.marker.setOptions({
position: that.place.center
});
that.place.rectangle.update();
}
function dragend(e) {
Ox.$body.removeClass('OxDragging');
that.map.triggerEvent('changeplaceend', that.place);
}
function getMarkerImage(options, callback) {
// fixme: unused
options = Ox.extend({
background: [255, 0, 0],
editing: false,
result: false,
selected: false,
size: 16
}, options);
var background = options.result ? [255, 255, 0] : [255, 0, 0],
border = options.editing ? [128, 128, 255] :
options.selected ? [255, 255, 255] : [0, 0, 0],
c = Ox.canvas(options.size, options.size),
image,
r = options.size / 2;
if (Ox.isArray(background)) {
c.context.fillStyle = 'rgba(' + background.join(', ') + ', 0.5)';
c.context.arc(r, r, r - 2, 0, 360);
c.context.fill();
renderImage();
} else {
image = new Image();
image.onload = renderImage;
image.src = background;
}
function renderImage() {
//var i;
if (Ox.isString(background)) {
c.context.drawImage(image, 1, 1, options.size - 2, options.size - 2);
/*
c.imageData = c.context.getImageData(0, 0, options.size, options.size);
c.data = c.imageData.data;
for (i = 3; i < c.data.length; i += 1) {
c.data[i] = Math.round(c.data[i] * 0.5);
}
c.context.putImageData(c.imageData, 0, 0);
*/
}
c.context.beginPath();
c.context.lineWidth = 2;
c.context.strokeStyle = 'rgb(' + border.join(', ') + ')';
c.context.arc(r, r, r - 1, 0, 360);
c.context.stroke();
callback(new google.maps.MarkerImage(
c.canvas.toDataURL(),
new google.maps.Size(options.size, options.size),
new google.maps.Point(0, 0),
new google.maps.Point(r, r)
));
}
}
function mouseover(e) {
var offset = that.map.offset(),
xy = that.map.overlayView.getProjection()
.fromLatLngToContainerPixel(e.latLng);
that.tooltip.show(
offset.left + Math.round(xy.x) - 4,
offset.top + Math.round(xy.y) + 20
);
}
function mouseout() {
that.tooltip.hide();
}
function setOptions() {
// workaround to prevent marker from appearing twice
// after setting draggable from true to false (google maps bug)
var fix = that.marker.getDraggable() && !that.place.editing,
color = that.map.options('markerColor'),
size = that.map.options('markerSize');
//Ox.Log('Map', 'setOptions, that.map: ', that.map)
if (color == 'auto') {
that.color = typeColor[that.place.type];
} else if (Ox.isArray(color)) {
that.color = color;
} else {
that.color = color(that.place);
}
if (size == 'auto') {
that.size = 8;
Ox.forEach(areaSize, function(size, area) {
if (that.place.area >= area) {
that.size = size;
} else {
return false; // break
}
});
} else if (Ox.isNumber(size)) {
that.size = size;
} else {
that.size = size(that.place);
}
that.marker.setOptions({
// fixme: cursor remains pointer
cursor: that.place.editing ? 'move' : 'pointer',
draggable: that.place.editing,
icon: Ox.MapMarkerImage({
color: that.color,
mode: that.place.editing ? 'editing' :
that.place.selected ? 'selected' : 'normal',
size: that.size,
type: that.place.id[0] == '_' ? 'result' : 'place'
}),
position: that.place.center
});
if (fix) {
that.marker.setVisible(false);
setTimeout(function() {
that.marker.setVisible(true);
}, 0);
}
setTooltip();
}
function setTooltip() {
that.tooltip && that.tooltip.remove();
that.tooltip = Ox.Tooltip({
title: '<img src="'
+ Ox.getFlagByGeoname(that.place.geoname, 16)
+ '" style="float: left; width: 16px; height: 16px; margin: 1px 0 1px -1px; border-radius: 4px"/>'
+ '<div style="float: left; margin: 4px -1px 0 4px; font-size: 9px;">'
+ that.map.options('markerTooltip')(that.place) + '</div>'
})
.addClass('OxMapMarkerTooltip');
}
/*@
add <f> add to map
() -> <f> add to map, returns MapMarker
@*/
that.add = function() {
that.marker.setMap(that.map.map);
google.maps.event.addListener(that.marker, 'click', click);
google.maps.event.addListener(that.marker, 'dblclick', dblclick);
google.maps.event.addListener(that.marker, 'mouseover', mouseover);
google.maps.event.addListener(that.marker, 'mouseout', mouseout);
return that;
};
/*@
edit <f> edit marker
() -> <f> edit marker, returns MapMarker
@*/
that.edit = function() {
setOptions();
google.maps.event.addListener(that.marker, 'dragstart', dragstart);
google.maps.event.addListener(that.marker, 'drag', drag);
google.maps.event.addListener(that.marker, 'dragend', dragend);
return that;
};
/*@
remove <f> remove marker
() -> <f> remove marker from map, returns MapMarker
@*/
that.remove = function() {
that.marker.setMap(null);
google.maps.event.clearListeners(that.marker);
return that;
};
/*@
submit <f> submit marker
() -> <f> clear edit listeners, returns MapMarker
@*/
that.submit = function() {
google.maps.event.clearListeners(that.marker, 'dragstart');
google.maps.event.clearListeners(that.marker, 'drag');
google.maps.event.clearListeners(that.marker, 'dragend');
return that;
}
/*@
update <f> update marker
() -> <f> update marker, returns MapMarker
@*/
that.update = function() {
setOptions();
return that;
}
return that;
};

View file

@ -0,0 +1,65 @@
'use strict';
/*@
Ox.MapMarkerImage <f> MapMarkerImage Object
(options) -> <o> google.maps.MarkerImage
options <o> Options object
color <a|[255, 0, 0]> marker color
mode <s|normal> can be: normal, selected, editing
size <n|16> size
type <s|place> can be: place, result, rectangle
@*/
Ox.MapMarkerImage = (function() {
var cache = {};
return function(options) {
options = Ox.extend({
color: [255, 0, 0],
mode: 'normal', // normal, selected, editing
rectangle: false,
size: 16,
type: 'place' // place, result
}, options);
var index = [
options.type, options.mode, options.size, options.color.join(',')
].join(';'),
themeData = Ox.Theme.getThemeData();
if (!cache[index]) {
var color = options.rectangle ? [0, 0, 0, 0]
: options.color.concat(
[options.type == 'place' ? 0.75 : 0.25]
),
border = (
options.mode == 'normal' ? themeData.mapPlaceBorder
: options.mode == 'selected' ? themeData.mapPlaceSelectedBorder
: themeData.mapPlaceEditingBorder
).concat([options.type == 'result' ? 0.5 : 1]),
c = Ox.canvas(options.size, options.size),
image,
r = options.size / 2;
c.context.fillStyle = 'rgba(' + color.join(', ') + ')';
c.context.arc(r, r, r - 2, 0, 360);
c.context.fill();
c.context.beginPath();
c.context.lineWidth = 2;
c.context.strokeStyle = 'rgba(' + border.join(', ') + ')';
c.context.arc(r, r, r - 1, 0, 360);
c.context.stroke();
cache[index] = new google.maps.MarkerImage(
c.canvas.toDataURL(),
new google.maps.Size(options.size, options.size),
new google.maps.Point(0, 0),
new google.maps.Point(r, r)
);
}
return cache[index];
}
}());

View file

@ -0,0 +1,218 @@
'use strict';
/*@
Ox.MapPlace <f> MapPlace Object
(options) -> <o> MapPlace Object
options <o> Options object
east <n|0>
editing <b|false>
geoname <s|''>
map <o|null>
markerColor <a|[255> 0> 0]>
markerSize <n|16>
name <s|''>
north <n|0>
selected <b|false>
south <n|0>
type <s|''>
visible <b|false>
west <n|0>
@*/
Ox.MapPlace = function(options) {
options = Ox.extend({
east: 0,
editing: false,
geoname: '',
map: null,
name: '',
north: 0,
selected: false,
south: 0,
type: '',
visible: false,
west: 0
}, options);
var that = this;
Ox.forEach(options, function(val, key) {
that[key] = val;
});
update();
function update(updateMarker) {
that.points = {
ne: new google.maps.LatLng(that.north, that.east),
sw: new google.maps.LatLng(that.south, that.west)
};
that.bounds = new google.maps.LatLngBounds(that.points.sw, that.points.ne);
that.center = that.bounds.getCenter();
that.lat = that.center.lat();
that.lng = that.center.lng();
Ox.extend(that.points, {
e: new google.maps.LatLng(that.lat, that.east),
s: new google.maps.LatLng(that.south, that.lng),
se: new google.maps.LatLng(that.south, that.east),
n: new google.maps.LatLng(that.north, that.lng),
nw: new google.maps.LatLng(that.north, that.west),
w: new google.maps.LatLng(that.lat, that.west)
});
// fixme: use bounds.toSpan()
that.sizeNorthSouth = (that.north - that.south)
* Ox.EARTH_CIRCUMFERENCE / 360;
that.sizeEastWest = (that.east + (that.west > that.east ? 360 : 0) - that.west)
* Ox.getMetersPerDegree(that.lat);
that.area = Ox.getArea(
{lat: that.south, lng: that.west},
{lat: that.north, lng: that.east}
);
if (!that.marker) {
that.marker = new Ox.MapMarker({
map: that.map,
place: that
});
that.rectangle = new Ox.MapRectangle({
map: that.map,
place: that
});
} else if (updateMarker) {
that.marker.update();
that.rectangle.update();
}
}
function editable() {
return that.map.options('editable') && that.editable;
}
/*@
add <f> add
@*/
that.add = function() {
that.visible = true;
that.marker.add();
return that;
};
/*@
cancel <f> cancel
@*/
that.cancel = function() {
if (editable()) {
that.undo();
that.editing = false;
that.marker.update();
that.rectangle.deselect();
}
return that;
};
/*@
crossesDateline <f> crossesDateline
@*/
that.crossesDateline = function() {
return that.west > that.east;
}
/*@
deselect <f> dselect
@*/
that.deselect = function() {
that.editing && that.submit();
that.selected = false;
that.marker.update();
that.rectangle.remove();
return that;
};
/*@
edit <f> edit
@*/
that.edit = function() {
if (editable()) {
that.editing = true;
that.original = {
south: that.south,
west: that.west,
north: that.north,
east: that.east
};
that.marker.edit();
that.rectangle.select();
}
return that;
};
// fixme: make this an Ox.Element to get options handling for free?
that.options = function(options) {
options = Ox.makeObject(arguments);
Ox.forEach(options, function(value, key) {
that[key] = value;
});
update(true);
};
/*@
remove <f> remove
@*/
that.remove = function() {
that.editing && that.submit();
that.selected && that.deselect();
that.visible = false;
that.marker.remove();
return that;
};
/*@
select <f> select
@*/
that.select = function() {
that.selected = true;
!that.visible && that.add();
that.marker.update();
that.rectangle.add();
return that;
};
/*@
submit <f> submit
@*/
that.submit = function() {
if (editable()) {
that.editing = false;
that.marker.update();
that.rectangle.deselect();
}
return that;
};
/*@
update <f> update
@*/
that.update = function(updateMarker) {
update(updateMarker);
that.map.triggerEvent('changeplace', that);
return that;
};
/*@
undo <f> undo
@*/
that.undo = function() {
if (editable()) {
Ox.forEach(that.original, function(v, k) {
that[k] = v;
});
that.update();
that.marker.update();
that.rectangle.update();
}
return that;
};
return that;
};

View file

@ -0,0 +1,132 @@
'use strict';
/*@
Ox.MapRectangle <f> MapRectangle Object
(options) -> <o> MapRectangle Object
options <o> Options object
map <o|null> map
place <o|null> place
@*/
Ox.MapRectangle = function(options) {
options = Ox.extend({
map: null,
place: null
}, options);
var that = this,
themeData = Ox.Theme.getThemeData();
Ox.forEach(options, function(val, key) {
that[key] = val;
});
/*@
rectangle <f> google.maps.Rectangle
@*/
that.rectangle = new google.maps.Rectangle({
clickable: true,
bounds: that.place.bounds
});
/*@
markers <a> array of markers
@*/
that.markers = Ox.map(that.place.points, function(point, position) {
return new Ox.MapRectangleMarker({
map: that.map,
place: that.place,
position: position
});
});
setOptions();
function click() {
if (
that.map.options('editable')
&& that.place.editable
&& !that.place.editing
) {
that.place.edit();
} else if (that.map.getKey() == 'meta') {
that.place.submit();
} else if (that.map.getKey() == 'shift') {
that.map.zoomToPlace();
} else {
that.map.panToPlace();
}
}
function setOptions() {
var color = '#' + Ox.toHex(themeData[
that.place.editing
? 'mapPlaceEditingBorder'
: 'mapPlaceSelectedBorder'
]);
that.rectangle.setOptions({
bounds: that.place.bounds,
fillColor: color,
fillOpacity: that.place.editing ? 0.1 : 0,
strokeColor: color,
strokeOpacity: that.place.id[0] == '_' ? 0.5 : 1,
strokeWeight: 2
});
}
/*@
add <f> add
@*/
that.add = function() {
that.rectangle.setMap(that.map.map);
google.maps.event.addListener(that.rectangle, 'click', click);
return that;
};
/*@
deselect <f> deselect
@*/
that.deselect = function() {
setOptions();
Ox.Log('Map', 'MARKERS', that.markers)
Ox.forEach(that.markers, function(marker) {
marker.remove();
});
return that;
};
/*@
remove <f> remove
@*/
that.remove = function() {
that.rectangle.setMap(null);
google.maps.event.clearListeners(that.rectangle);
return that;
}
/*@
select <f> select
@*/
that.select = function() {
setOptions();
Ox.forEach(that.markers, function(marker) {
marker.add();
});
return that;
};
/*@
update <f> udpate
@*/
that.update = function() {
Ox.Log('Map', 'UPDATE...')
setOptions();
Ox.forEach(that.markers, function(marker) {
marker.update();
});
return that;
}
return that;
};

View file

@ -0,0 +1,123 @@
'use strict';
/*@
Ox.MapRectangleMarker <f> MapRectangleMarker Object
(options) -> <o> MapRectangleMarker Object
options <o> Options object
map <o|null> map
place <o|null> place
position <s|''>
@*/
Ox.MapRectangleMarker = function(options) {
options = Ox.extend({
map: null,
place: null,
position: ''
}, options);
var that = this;
Ox.forEach(options, function(val, key) {
that[key] = val;
});
that.markerImage = new google.maps.MarkerImage
that.marker = new google.maps.Marker({
cursor: that.position + '-resize',
draggable: true,
icon: Ox.MapMarkerImage({
mode: 'editing',
rectangle: true,
type: that.place.id[0] == '_' ? 'result' : 'place'
}),
position: that.place.points[that.position],
raiseOnDrag: false
});
function dragstart(e) {
Ox.$body.addClass('OxDragging');
that.drag = {
lat: e.latLng.lat(),
lng: e.latLng.lng()
};
}
function drag(e) {
// fixme: implement shift+drag (center stays the same)
Ox.Log('Map', e.pixel.x, e.pixel.y)
var lat = Ox.limit(e.latLng.lat(), Ox.MIN_LATITUDE, Ox.MAX_LATITUDE),
lng = e.latLng.lng();
that.drag = {
lat: lat,
lng: lng
};
if (that.position.indexOf('s') > -1) {
that.place.south = lat;
}
if (that.position.indexOf('n') > -1) {
that.place.north = lat;
}
if (that.position.indexOf('w') > -1) {
that.place.west = lng;
}
if (that.position.indexOf('e') > -1) {
that.place.east = lng;
}
//Ox.Log('Map', 'west', that.place.west, 'east', that.place.east);
//Ox.Log('Map', 'south', that.place.south, 'north', that.place.north);
that.place.update();
that.place.marker.update();
that.place.rectangle.update();
}
function dragend(e) {
var south;
Ox.$body.removeClass('OxDragging');
if (that.place.south > that.place.north) {
south = that.place.south;
that.place.south = that.place.north;
that.place.north = south;
that.place.update();
that.place.marker.update();
that.place.rectangle.update();
}
that.map.triggerEvent('changeplaceend', that.place);
}
/*@
add <f> add
@*/
that.add = function() {
that.marker.setMap(that.map.map);
google.maps.event.addListener(that.marker, 'dragstart', dragstart);
google.maps.event.addListener(that.marker, 'drag', drag);
google.maps.event.addListener(that.marker, 'dragend', dragend);
};
/*@
remove <f> remove
@*/
that.remove = function() {
that.marker.setMap(null);
google.maps.event.clearListeners(that.marker);
};
/*@
update <f> update
@*/
that.update = function() {
that.marker.setOptions({
icon: Ox.MapMarkerImage({
mode: 'editing',
rectangle: true,
type: that.place.id[0] == '_' ? 'result' : 'place'
}),
position: that.place.points[that.position]
});
};
return that;
};

View file

@ -0,0 +1,285 @@
'use strict';
/*@
Ox.MainMenu <f> MainMenu Object
options <o> Options object
extras <a|[]> extra menus
menus <a|[]> submenus
size <s|medium> can be small, medium, large
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Bar> MainMenu Object
@*/
Ox.MainMenu = function(options, self) {
self = self || {};
var that = 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
.on({
click: click,
mousemove: mousemove
});
self.focused = false;
self.selected = -1;
that.menus = [];
that.titles = [];
that.layer = $('<div>').addClass('OxLayer');
self.options.menus.forEach(function(menu, position) {
addMenu(menu, position);
});
if (self.options.extras.length) {
that.extras = $('<div>')
.addClass('OxExtras')
.appendTo(that);
self.options.extras.forEach(function(extra) {
extra.appendTo(that.extras);
});
}
function addMenu(menu, position) {
that.titles[position] = $('<div>')
.addClass('OxTitle')
.html(menu.title)
.data({position: position});
if (position == 0) {
if (that.titles.length == 1) {
that.titles[position].appendTo(that);
} else {
that.titles[position].insertBefore(that.titles[1]);
}
} else {
that.titles[position].insertAfter(that.titles[position - 1])
}
that.menus[position] = Ox.Menu(Ox.extend(menu, {
element: that.titles[position],
mainmenu: that,
size: self.options.size
}))
.bindEvent({
hide: onHideMenu
});
}
function click(event) {
var $element = getElement(event),
position = typeof $element.data('position') != 'undefined'
? $element.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 getElement(event) {
var $element = $(event.target);
return $element.is('img') ? $element.parent() : $element;
}
function mousemove(event) {
var $element = getElement(event),
focused,
position = typeof $element.data('position') != 'undefined'
? $element.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;
}
function removeMenu(position) {
that.titles[position].remove();
that.menus[position].remove();
}
that.addMenuAfter = function(id) {
};
that.addMenuBefore = function(id) {
};
/*@
checkItem <f> checkItem
@*/
that.checkItem = function(id) {
var ids = id.split('_'),
item = that.getItem(id);
if (item) {
if (item.options('group')) {
item.options('menu').checkItem(ids[ids.length - 1]);
} else {
item.options({checked: true});
}
}
return that;
};
/*@
disableItem <f> disableItem
@*/
that.disableItem = function(id) {
var item = that.getItem(id);
item && item.options({disabled: true});
return that;
};
/*@
enableItem <f> enableItem
@*/
that.enableItem = function(id) {
var item = that.getItem(id);
item && item.options({disabled: false});
return that;
};
/*@
getItem <f> getItem
@*/
that.getItem = function(id) {
var ids = id.split('_'),
item;
if (ids.length == 1) {
Ox.forEach(that.menus, function(menu) {
item = menu.getItem(id);
if (item) {
return false; // break
}
});
} else {
item = that.getMenu(ids.shift()).getItem(ids.join('_'));
}
Ox.Log('Menu', 'getItem', id, item);
return item;
};
/*@
getMenu <f> getMenu
@*/
that.getMenu = function(id) {
var ids = id.split('_'),
menu;
if (ids.length == 1) {
Ox.forEach(that.menus, function(v) {
if (v.options('id') == id) {
menu = v;
return false; // break
}
});
} else {
menu = that.getMenu(ids.shift()).getSubmenu(ids.join('_'));
}
return menu;
};
that.highlightMenu = function(id) {
var position = Ox.getIndexById(self.options.menus, id);
self.highlightTimeout && clearTimeout(self.highlightTimeout);
that.titles[position].addClass('OxHighlight');
self.highlightTimeout = setTimeout(function() {
that.titles[position].removeClass('OxHighlight');
delete self.highlightTimeout;
}, 500);
};
that.isSelected = function() {
return self.selected > -1;
};
that.removeMenu = function() {
};
/*@
replaceMenu <f> replace menu
(id, menu) -> <u> replace menu
@*/
that.replaceMenu = function(id, menu) {
var position = Ox.getIndexById(self.options.menus, id);
self.options.menus[position] = menu;
removeMenu(position);
addMenu(menu, position);
};
/*@
selectNextMenu <f> selectNextMenu
@*/
that.selectNextMenu = function() {
if (self.selected < self.options.menus.length - 1) {
clickTitle(self.selected + 1);
}
return that;
};
/*@
selectPreviousMenu <f> selectPreviousMenu
@*/
that.selectPreviousMenu = function() {
if (self.selected) {
clickTitle(self.selected - 1);
}
return that;
};
that.setItemKeyboard = function(id, keyboard) {
var item = that.getItem(id);
item && item.options({keyboard: keyboard});
return that;
};
/*@
setItemTitle <f> setItemTitle
(id, title) -> <o> set item title
@*/
that.setItemTitle = function(id, title) {
var item = that.getItem(id);
item && item.options({title: title});
return that;
};
/*@
uncheckItem <f> uncheckItem
@*/
that.uncheckItem = function(id) {
var item = that.getItem(id);
item && item.options({checked: false});
return that;
};
return that;
};

855
source/UI/js/Menu/Menu.js Normal file
View file

@ -0,0 +1,855 @@
'use strict';
/*@
Ox.Menu <f> Menu Object
options <o> Options object
edge <s> open to 'bottom' or 'right'
element <o> the element the menu is attached to
id <s> the menu id
items <a> array of menu items
mainmenu <o> the main menu this menu is part of, if any
offset <o> offset of the menu, in px
left <n> left
top <n> top
parent <o> the supermenu, if any
selected <b> the position of the selected item
size <s> 'large', 'medium' or 'small'
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Menu Object
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
click <!> click
change <!> change
select <!> select
deselect <!> deselect
@*/
Ox.Menu = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
edge: 'bottom',
element: null,
id: '',
items: [],
mainmenu: null,
maxWidth: 0,
offset: {
left: 0,
top: 0
},
parent: null,
selected: -1,
size: 'medium' // fixme: remove
})
.options(options || {})
.update({
items: function() {
renderItems(self.options.items);
},
selected: function() {
that.$content.find('.OxSelected').removeClass('OxSelected');
selectItem(self.options.selected);
}
})
.addClass(
'OxMenu Ox' + Ox.toTitleCase(self.options.edge) +
' Ox' + Ox.toTitleCase(self.options.size)
)
.on({
click: click,
mouseenter: mouseenter,
mouseleave: mouseleave,
mousemove: mousemove,
mousewheel: mousewheel
})
.bindEvent({
key_up: selectPreviousItem,
key_down: selectNextItem,
key_left: selectSupermenu,
key_right: selectSubmenu,
key_escape: hideMenu,
key_enter: clickSelectedItem
});
self.itemHeight = self.options.size == 'small'
? 12 : self.options.size == 'medium' ? 16 : 20;
self.scrollSpeed = 1;
// render
that.items = [];
that.submenus = {};
that.$scrollbars = [];
that.$top = $('<div>')
.addClass('OxTop')
.appendTo(that);
that.$scrollbars.up = renderScrollbar('up')
.appendTo(that);
that.$container = $('<div>')
.addClass('OxContainer')
.appendTo(that);
that.$content = $('<table>')
.addClass('OxContent')
.appendTo(that.$container);
renderItems(self.options.items);
that.$scrollbars.down = renderScrollbar('down')
.appendTo(that);
that.$bottom = $('<div>')
.addClass('OxBottom')
.appendTo(that);
function click(event) {
var item,
position,
$target = $(event.target),
$parent = $target.parent();
// necessary for highlight
if ($parent.is('.OxCell')) {
$target = $parent;
$parent = $target.parent();
}
if ($target.is('.OxCell')) {
position = $parent.data('position');
item = that.items[position];
if (!item.options('disabled')) {
clickItem(position);
} else {
that.hideMenu();
}
} else {
that.hideMenu();
}
}
function clickItem(position, files) {
var item = that.items[position],
group = item.options('group'),
menu = self.options.mainmenu || self.options.parent || that,
offset,
toggled;
that.hideMenu();
if (!item.options('items').length) {
if (self.options.parent) {
self.options.parent.hideMenu(true).triggerEvent('click', Ox.extend({
id: item.options('id'),
title: parseTitle(item.options('title')[0])
}, files ? {files: files} : {}));
}
if (item.options('checked') !== null) {
if (group) {
offset = self.optionGroupOffset[group];
toggled = self.optionGroup[group].toggle(position - offset);
if (toggled.length) {
toggled.forEach(function(pos) {
that.items[pos + offset].toggleChecked();
});
menu.triggerEvent('change', {
id: item.options('group'),
checked: self.optionGroup[group].checked().map(function(pos) {
return {
id: that.items[pos + offset].options('id'),
title: Ox.isString(that.items[pos + offset].options('title')[0])
? parseTitle(that.items[pos + offset].options('title')[0]) : ''
};
})
});
}
} else {
item.toggleChecked();
menu.triggerEvent('change', {
checked: item.options('checked'),
id: item.options('id'),
title: Ox.isString(item.options('title')[0])
? parseTitle(item.options('title')[0]) : ''
});
}
} else {
// if item.options.files && !files, the click happened outside
// the title - as a workaround, we don't trigger a click event.
if (!item.options('file') || files) {
menu.triggerEvent('click', Ox.extend({
id: item.options('id'),
title: parseTitle(item.options('title')[0])
}, files ? {files: files} : {}));
}
}
if (item.options('title').length == 2) {
item.toggleTitle();
}
}
}
function clickLayer() {
that.hideMenu();
}
function clickSelectedItem() {
// called on key.enter
if (self.options.selected > -1) {
clickItem(self.options.selected);
}
}
function getElement(id) {
// fixme: needed?
return $('#' + Ox.toCamelCase(options.id + '/' + id));
}
function getItemPositionById(id) {
// fixme: this exists in ox.js by now
var position;
Ox.forEach(that.items, function(item, i) {
if (item.options('id') == id) {
position = i;
return false; // break
}
});
return position;
}
function hideMenu() {
// called on key.escape
that.hideMenu();
}
function isFirstEnabledItem() {
var ret = true;
Ox.forEach(that.items, function(item, i) {
if (i < self.options.selected && !item.options('disabled')) {
ret = false;
return false; // break
}
});
return ret;
}
function isLastEnabledItem() {
var ret = true;
Ox.forEach(that.items, function(item, i) {
if (i > self.options.selected && !item.options('disabled')) {
ret = false;
return false; // break
}
});
return ret;
}
function mouseenter() {
that.gainFocus();
}
function mouseleave() {
if (
self.options.selected > -1
&& !that.items[self.options.selected].options('items').length
) {
selectItem(-1);
}
}
function mousemove(event) {
var item,
position,
$target = $(event.target),
$parent = $target.parent(),
$grandparent = $parent.parent();
if ($parent.is('.OxCell')) {
$target = $parent;
$parent = $target.parent();
} else if ($grandparent.is('.OxCell')) {
$target = $grandparent;
$parent = $target.parent();
}
if ($target.is('.OxCell')) {
position = $parent.data('position');
item = that.items[position];
if (!item.options('disabled') && position != self.options.selected) {
selectItem(position);
}
} else {
mouseleave();
}
}
function mousewheel(e, delta, deltaX, deltaY) {
var $scrollbar;
if (deltaY && !$(e.target).is('.OxScrollbar')) {
$scrollbar = that.$scrollbars[deltaY < 0 ? 'down' : 'up'];
Ox.loop(0, Math.abs(deltaY), function() {
if ($scrollbar.is(':visible')) {
$scrollbar.trigger('mouseenter').trigger('mouseleave');
}
});
}
}
function parseTitle(title) {
return Ox.decodeHTMLEntities(Ox.stripTags(title));
}
function renderItems(items) {
var offset = 0;
that.$content.empty();
scrollMenuUp();
self.optionGroup = {};
self.optionGroupOffset = {};
items.forEach(function(item, i) {
if (item.group) {
items[i] = item.items.map(function(v) {
return Ox.extend(v, {
group: item.group
});
});
self.optionGroup[item.group] = new Ox.OptionGroup(
items[i].filter(function(v) {
return 'id' in v;
}),
'min' in item ? item.min : 1,
'max' in item ? item.max : 1
);
self.optionGroupOffset[item.group] = offset;
offset += items[i].length;
} else if ('id' in item) {
offset += 1;
}
});
items = Ox.flatten(items);
that.items = [];
items.forEach(function(item) {
var position;
if ('id' in item) {
position = that.items.length;
that.items.push(
Ox.MenuItem(Ox.extend(Ox.clone(item), {
maxWidth: self.options.maxWidth,
menu: that,
position: position
}))
.data('position', position)
// fixme: jquery bug when passing {position: position}? does not return the object?
.appendTo(that.$content)
);
if (item.items) {
that.submenus[item.id] = Ox.Menu({
edge: 'right',
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,
size: self.options.size
});
}
} else {
that.$content.append(renderSpace());
that.$content.append(renderLine());
that.$content.append(renderSpace());
}
});
if (!that.is(':hidden')) {
that.hideMenu();
that.showMenu();
}
}
function renderLine() {
return $('<tr>').append(
$('<td>').addClass('OxLine').attr({colspan: 5})
);
}
function renderScrollbar(direction) {
var interval,
speed = direction == 'up' ? -1 : 1;
return $('<div/>', {
'class': 'OxScrollbar Ox' + Ox.toTitleCase(direction),
html: Ox.SYMBOLS['triangle_' + direction],
click: function() { // fixme: do we need to listen to click event?
return false;
},
mousedown: function() {
self.scrollSpeed = 2;
return false;
},
mouseenter: function() {
self.scrollSpeed = 1;
var $otherScrollbar = that.$scrollbars[direction == 'up' ? 'down' : 'up'];
$(this).addClass('OxSelected');
if ($otherScrollbar.is(':hidden')) {
$otherScrollbar.show();
that.$container.height(that.$container.height() - self.itemHeight);
if (direction == 'down') {
that.$content.css({
top: -self.itemHeight + 'px'
});
}
}
scrollMenu(speed);
interval = setInterval(function() {
scrollMenu(speed);
}, 100);
},
mouseleave: function() {
self.scrollSpeed = 1;
$(this).removeClass('OxSelected');
clearInterval(interval);
},
mouseup: function() {
self.scrollSpeed = 1;
return false;
}
});
}
function renderSpace() {
return $('<tr>').append(
$('<td>').addClass('OxSpace').attr({colspan: 5})
);
}
function scrollMenu(speed) {
var containerHeight = that.$container.height(),
contentHeight = that.$content.height(),
top = parseInt(that.$content.css('top'), 10) || 0,
min = containerHeight - contentHeight + self.itemHeight,
max = 0;
top += speed * self.scrollSpeed * -self.itemHeight;
if (top <= min) {
top = min;
that.$scrollbars.down.hide().trigger('mouseleave');
that.$container.height(containerHeight + self.itemHeight);
that.items[that.items.length - 1].trigger('mouseover');
} else if (top >= max - self.itemHeight) {
top = max;
that.$scrollbars.up.hide().trigger('mouseleave');
that.$container.height(containerHeight + self.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() + self.itemHeight);
}
}
}
function selectItem(position) {
var item;
if (self.options.selected > -1) {
item = that.items[self.options.selected];
if (item) {
item.removeClass('OxSelected');
if (item.options('file')) {
item.$button.blurButton();
that.bindEvent({key_enter: clickSelectedItem})
}
}
/* disabled
that.triggerEvent('deselect', {
id: item.options('id'),
title: Ox.parseTitle(item.options('title')[0])
});
*/
}
if (position > -1) {
item = that.items[position];
Ox.forEach(that.submenus, function(submenu, id) {
if (!submenu.is(':hidden')) {
submenu.hideMenu();
return false; // break
}
});
item.options('items').length && that.submenus[item.options('id')].showMenu();
item.addClass('OxSelected');
if (item.options('file')) {
item.$button.focusButton();
that.unbindEvent('key_enter');
}
that.triggerEvent('select', {
id: item.options('id'),
title: Ox.isString(item.options('title')[0])
? parseTitle(item.options('title')[0]) : ''
});
}
self.options.selected = position;
}
function selectNextItem() {
var offset,
selected = self.options.selected;
//Ox.Log('Menu', '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 + self.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() - self.itemHeight);
offset += self.itemHeight;
}
if (selected == that.items.length - 1) {
that.$scrollbars.down.hide();
that.$container.height(that.$container.height() + self.itemHeight);
} else {
that.$content.css({
top: ((parseInt(that.$content.css('top'), 10) || 0) - offset) + 'px'
});
}
}
}
}
function selectPreviousItem() {
var offset,
selected = self.options.selected;
//Ox.Log('Menu', '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() - self.itemHeight);
}
if (selected == 0) {
that.$scrollbars.up.hide();
that.$container.height(that.$container.height() + self.itemHeight);
}
that.$content.css({
top: ((parseInt(that.$content.css('top'), 10) || 0) - offset) + 'px'
});
}
}
}
function selectSubmenu() {
//Ox.Log('Menu', 'selectSubmenu', self.options.selected)
if (self.options.selected > -1) {
var submenu = that.submenus[that.items[self.options.selected].options('id')];
//Ox.Log('Menu', 'submenu', submenu, that.submenus);
if (submenu && submenu.hasEnabledItems()) {
submenu.gainFocus();
submenu.selectFirstItem();
} else if (self.options.mainmenu) {
self.options.mainmenu.selectNextMenu();
}
} else if (self.options.mainmenu) {
self.options.mainmenu.selectNextMenu();
}
}
function selectSupermenu() {
//Ox.Log('Menu', 'selectSupermenu', self.options.selected)
if (self.options.parent) {
self.options.selected > -1 && that.items[self.options.selected].trigger('mouseleave');
scrollMenuUp();
self.options.parent.gainFocus();
} else if (self.options.mainmenu) {
self.options.mainmenu.selectPreviousMenu();
}
}
/*@
addItem <f>
@*/
that.addItem = function(item, position) {
};
/*@
addItemAfter <f>
@*/
that.addItemAfter = function(item, id) {
};
/*@
addItemBefore <f> addItemBefore
@*/
that.addItemBefore = function(item, id) {
};
/*@
checkItem <f> checkItem
(id, checked) -> <u> check item, checked can be undefined/true or false
@*/
that.checkItem = function(id, checked) {
Ox.Log('Menu', 'checkItem id', id)
var group,
ids = id.split('_'),
item,
offset,
position,
toggled;
checked = Ox.isUndefined(checked) ? true : checked;
if (ids.length == 1) {
item = that.getItem(id);
group = item.options('group');
if (group) {
offset = self.optionGroupOffset[group];
position = getItemPositionById(id);
toggled = self.optionGroup[item.options('group')].toggle(position - offset);
if (toggled.length) {
toggled.forEach(function(pos) {
that.items[pos + offset].toggleChecked();
});
}
} else {
item.options({checked: checked});
}
} else {
that.submenus[ids.shift()].checkItem(ids.join('_'));
}
};
/*@
clickItem <f> clickItem
(position, files) -> <o> click item at position
@*/
that.clickItem = function(position, files) {
clickItem(position, files);
};
/*@
getItem <f> getItem
(id) -> <o> get item
@*/
that.getItem = function(id) {
//Ox.Log('Menu', 'getItem id', id)
var ids = id.split('_'),
item;
if (ids.length == 1) {
Ox.forEach(that.items, function(v) {
if (v.options('id') == id) {
item = v;
return false; // break
}
});
if (!item) {
Ox.forEach(that.submenus, function(submenu) {
item = submenu.getItem(id);
if (item) {
return false; // break
}
});
}
} else {
item = that.submenus[ids.shift()].getItem(ids.join('_'));
}
return item;
};
/*@
getSubmenu <f> getSubmenu
(id) -> <o> get submenu by id
@*/
that.getSubmenu = function(id) {
var ids = id.split('_'),
submenu;
if (ids.length == 1) {
submenu = that.submenus[id];
} else {
submenu = that.submenus[ids.shift()].getSubmenu(ids.join('_'));
}
//Ox.Log('Menu', 'getSubmenu', id, submenu);
return submenu;
}
/*@
hasEnabledItems <f> hasEditableItems
() -> <b> menu has editable items
@*/
that.hasEnabledItems = function() {
var ret = false;
Ox.forEach(that.items, function(item) {
if (!item.options('disabled')) {
ret = true;
return false; // break
}
});
return ret;
};
/*@
hideMenu <f> hideMenu
() -> <f> Menu Object
@*/
that.hideMenu = function(hideParent) {
if (that.is(':hidden')) {
return;
}
Ox.forEach(that.submenus, function(submenu) {
if (submenu.is(':visible')) {
submenu.hideMenu();
return false; // break
}
});
selectItem(-1);
scrollMenuUp();
that.$scrollbars.up.is(':visible') && that.$scrollbars.up.hide();
that.$scrollbars.down.is(':visible') && that.$scrollbars.down.hide();
if (self.options.parent) {
//self.options.element.removeClass('OxSelected');
self.options.parent.options({
selected: -1
});
hideParent && self.options.parent.hideMenu(true);
}
that.$layer && that.$layer.hide();
that.hide().loseFocus().triggerEvent('hide');
return that;
};
/*@
removeElement <f> removeElement
@*/
that.removeElement = function() {
Ox.forEach(that.submenus, function(submenu) {
submenu.remove();
});
return Ox.Element.prototype.removeElement.apply(that, arguments);
};
/*@
removeItem <f> removeItem
@*/
that.removeItem = function() {
};
/*@
selectFirstItem <f> selectFirstItem
@*/
that.selectFirstItem = function() {
selectNextItem();
return that;
};
that.setItemKeyboard = function(id, keyboard) {
var item = that.getItem(id);
item && item.options({keyboard: keyboard});
return that;
};
/*@
setItemTitle <f> setItemTitle
(id, title) -> <o> set item title
@*/
that.setItemTitle = function(id, title) {
var item = that.getItem(id);
item && item.options({title: title});
return that;
};
/*@
showMenu <f> showMenu
() -> <f> Menu Object
@*/
that.showMenu = function() {
if (!that.is(':hidden')) {
return;
}
that.parent().length == 0 && that.appendTo(Ox.$body);
that.css({
left: '-1000px',
top: '-1000px'
}).show();
var offset = self.options.element.offset(),
width = self.options.element.outerWidth(),
height = self.options.element.outerHeight(),
menuWidth = that.width(),
windowWidth = Ox.$window.width(),
windowHeight = Ox.$window.height(),
left = offset.left + self.options.offset.left + (self.options.edge == 'bottom' ? 0 : width),
right,
top = offset.top + self.options.offset.top + (self.options.edge == 'bottom' ? height : 0),
menuHeight = that.$content.outerHeight(), // fixme: why is outerHeight 0 when hidden?
menuMaxHeight = Math.floor(Ox.$window.height() - top - 16);
if (self.options.edge == 'bottom' && left + menuWidth > windowWidth) {
left = offset.left + width - menuWidth;
that.is('.OxRight') && that.removeClass('OxRight') && that.addClass('OxLeft');
}
if (self.options.parent) {
if (menuHeight > menuMaxHeight) {
top = Ox.limit(top - menuHeight + menuMaxHeight, self.options.parent.offset().top, top);
menuMaxHeight = Math.floor(windowHeight - top - 16);
}
}
that.css({
left: left + 'px',
top: top + 'px'
});
if (menuHeight > menuMaxHeight) {
that.$container.height(menuMaxHeight - self.itemHeight - 8); // margin
that.$scrollbars.down.show();
} else {
that.$container.height(menuHeight);
}
if (!self.options.parent) {
that.gainFocus();
that.$layer = Ox.Layer({type: 'menu'})
.css({top: self.options.mainmenu ? '20px' : 0})
.bindEvent({click: clickLayer})
.show();
}
return that;
//that.triggerEvent('show');
};
/*@
toggleMenu <f> toggleMenu
@*/
that.toggleMenu = function() {
return that.is(':hidden') ? that.showMenu() : that.hideMenu();
};
/*@
uncheckItem <f> uncheckItem
(id) -> <o> uncheck item
@*/
that.uncheckItem = function(id) {
that.checkItem(id, false);
};
return that;
};

View file

@ -0,0 +1,188 @@
'use strict';
/*@
Ox.MenuButton <f> Menu Button
options <o> Options object
disabled <b|false> If true, button is disabled
id <s|''> Element id
items <a|[]> Menu items
maxWidth <n|0> Maximum menu width
style <s|'rounded'> Style ('rounded' or 'square')
title <s|''> Menu title
tooltip <s|f|''> Tooltip title, or function that returns one
(e) -> <string> Tooltip title
e <object> Mouse event
type <s|'text'> Type ('text' or 'image')
width <s|n|'auto'> Width in px, or 'auto'
click <!> click
change <!> change
hide <!> hide
show <!> show
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> Menu Button
@*/
Ox.MenuButton = function(options, self) {
self = self || {};
var that = Ox.Element({
tooltip: options.tooltip || ''
}, self)
.defaults({
disabled: false,
id: '',
items: [],
maxWidth: 0,
overlap: 'none',
style: 'rounded',
title: '',
type: 'text',
width: 'auto'
})
.options(options || {})
.update({
items: function() {
self.$menu.options({items: self.options.items});
},
title: function() {
if (self.options.type == 'text') {
self.$title.html(self.options.title);
} else {
self.$button.options({title: self.options.title});
}
},
width: function() {
that.css({width: self.options.width - 2 + 'px'});
self.$title.css({width: self.options.width - 24 + 'px'});
}
})
.addClass(
'OxSelect Ox' + Ox.toTitleCase(self.options.style) + (
self.options.overlap != 'none'
? ' OxOverlap' + Ox.toTitleCase(self.options.overlap)
: ''
)
)
.css(self.options.width == 'auto' ? {} : {
width: self.options.width - 2 + 'px'
})
.bindEvent({
anyclick: function(e) {
showMenu($(e.target).is('.OxButton') ? 'button' : null);
},
});
if (self.options.type == 'text') {
self.$title = Ox.$('<div>')
.addClass('OxTitle')
.css({width: self.options.width - 24 + 'px'})
.html(self.options.title)
.appendTo(that);
}
self.$button = Ox.Button({
id: self.options.id + 'Button',
selectable: true,
overlap: self.options.overlap,
style: 'symbol',
title: self.options.type == 'text' || !self.options.title
? 'select' : self.options.title,
type: 'image'
})
.appendTo(that);
self.$menu = Ox.Menu({
edge: 'bottom',
element: self.$title || self.$button,
id: self.options.id + 'Menu',
items: self.options.items,
maxWidth: self.options.maxWidth
})
.bindEvent({
change: changeMenu,
click: clickMenu,
hide: hideMenu
});
self.options.type == 'image' && self.$menu.addClass('OxRight');
function clickMenu(data) {
that.triggerEvent('click', data);
}
function changeMenu(data) {
that.triggerEvent('change', data);
}
function hideMenu(data) {
that.loseFocus();
that.removeClass('OxSelected');
self.$button.options({value: false});
that.triggerEvent('hide');
}
function showMenu(from) {
that.gainFocus();
that.addClass('OxSelected');
from != 'button' && self.$button.options({value: true});
that.$tooltip && that.$tooltip.hide();
self.$menu.showMenu();
that.triggerEvent('show');
}
/*@
checkItem <f> checkItem
(id) -> <o> check item with id
@*/
that.checkItem = function(id) {
self.$menu.checkItem(id);
return that;
};
/*@
disableItem <f> disableItem
(id) -> <o> disable item with id
@*/
that.disableItem = function(id) {
self.$menu.getItem(id).options({disabled: true});
return that;
};
/*@
enableItem <f> enableItem
(id) -> <o> enable item
@*/
that.enableItem = function(id) {
self.$menu.getItem(id).options({disabled: false});
return that;
};
/*@
removeElement <f> removeElement
@*/
that.removeElement = function() {
self.$menu.remove();
return Ox.Element.prototype.removeElement.apply(that, arguments);
};
/*@
setItemTitle <f> setItemTitle
(id, title) -> <o> set item title
@*/
that.setItemTitle = function(id, title) {
self.$menu.setItemTitle(id, title);
return that;
};
/*@
uncheckItem <f> uncheck item
(id) -> <o> uncheck item with id
@*/
that.uncheckItem = function(id) {
self.$menu.uncheckItem(id);
return that;
};
return that;
};

View file

@ -0,0 +1,186 @@
'use strict';
/*@
Ox.MenuItem <f> MenuItem Object
options <o> Options object
bind <a|[]> fixme: what's this?
checked <f|null> If true, the item is checked
disabled <b|false> If true, the item is disabled
file <o|null> File selection options
group <s|''>
icon <s|''> icon
id <s|''> id
items <a|[]> items
keyboard <s|''> keyboard
menu <o|null> menu
position <n|0> position
title <a|[]> title
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> MenuItem Object
@*/
Ox.MenuItem = function(options, self) {
self = self || {};
var that = Ox.Element('<tr>', self)
.defaults({
bind: [], // fixme: what's this?
checked: null,
disabled: false,
file: null,
group: '',
icon: '',
id: '',
items: [],
keyboard: '',
maxWidth: 0,
menu: null, // fixme: is passing the menu to 100s of menu items really memory-neutral?
position: 0,
title: [],
type: ''
})
.options(Ox.extend(Ox.clone(options), {
keyboard: parseKeyboard(options.keyboard || self.defaults.keyboard),
title: Ox.makeArray(options.title || self.defaults.title)
}))
.update({
checked: function() {
that.$status.html(self.options.checked ? Ox.SYMBOLS.check : '')
},
disabled: function() {
that[
self.options.disabled ? 'addClass' : 'removeClass'
]('OxDisabled');
self.options.file && that.$button.options({
disabled: self.options.disabled
});
},
keyboard: function() {
self.options.keyboard = parseKeyboard(self.options.keyboard);
that.$modifiers.html(formatModifiers());
that.$key.html(formatKey());
},
title: function() {
self.options.title = Ox.makeArray(self.options.title);
that.$title.html(self.options.title[0]);
}
})
.addClass('OxItem' + (self.options.disabled ? ' OxDisabled' : ''))
/*
.attr({
id: Ox.toCamelCase(self.options.menu.options('id') + '/' + self.options.id)
})
*/
.data('group', self.options.group); // fixme: why?
if (self.options.group && self.options.checked === null) {
self.options.checked = false;
}
that.append(
that.$status = Ox.$('<td>')
.addClass('OxCell OxStatus')
.html(self.options.checked ? Ox.SYMBOLS.check : '')
)
.append(
that.$icon = $('<td>')
.addClass('OxCell OxIcon')
.append(
self.options.icon
? Ox.$('<img>').attr({src: self.options.icon})
: null
)
)
.append(
that.$title = $('<td>')
.addClass('OxCell OxTitle')
.css(
self.options.maxWidth
? {
maxWidth: self.options.maxWidth - 46,
textOverflow: 'ellipsis',
overflow: 'hidden'
}
: {}
)
.html(
self.options.file
? that.$button = Ox.FileButton(Ox.extend({
disabled: self.options.disabled,
title: self.options.title[0]
}, self.options.file)).bindEvent({
click: function(data) {
self.options.menu.clickItem(self.options.position, data.files);
}
})
: (
Ox.isString(self.options.title[0])
? self.options.title[0]
: Ox.$('<div>').html(self.options.title[0]).html()
)
)
)
.append(
that.$modifiers = Ox.$('<td>')
.addClass('OxCell OxModifiers')
.html(formatModifiers())
)
.append(
that.$key = Ox.$('<td>')
.addClass(
'OxCell Ox' + (self.options.items.length ? 'Submenu' : 'Key')
)
.html(
self.options.items.length
? Ox.SYMBOLS.triangle_right
: formatKey()
)
);
function formatKey() {
return Ox.SYMBOLS[self.options.keyboard.key]
|| self.options.keyboard.key.toUpperCase();
}
function formatModifiers() {
return self.options.keyboard.modifiers.map(function(modifier) {
return Ox.SYMBOLS[modifier];
}).join('');
}
function parseKeyboard(str) {
var modifiers = str.split(' '),
key = modifiers.pop();
return {
modifiers: modifiers,
key: key
};
}
that.toggle = function() {
// toggle id and title
};
/*@
toggleChecked <f> toggleChecked
@*/
that.toggleChecked = function() {
that.options({checked: !self.options.checked});
return that;
};
that.toggleDisabled = function() {
};
/*@
toggleTitle <f> toggleTitle
@*/
that.toggleTitle = function() {
that.options({title: Ox.clone(self.options.title).reverse()});
return that;
};
return that;
};

View file

@ -0,0 +1,177 @@
'use strict';
/*@
Ox.CollapsePanel <f> CollapsePanel Object
options <o> Options object
collapsed <b|false> collapsed state
extras <a|[]> panel extras
size <n|16> size
title <s> title
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> CollapsePanel Object
toggle <!> toggle
@*/
Ox.CollapsePanel = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
animate: true,
collapsed: false,
extras: [],
size: 16,
title: ''
})
.options(options)
.update({
collapsed: function() {
// will be toggled again in toggleCollapsed
self.options.collapsed = !self.options.collapsed;
toggleCollapsed();
},
title: function() {
self.$title.html(self.options.title);
}
})
.addClass('OxPanel OxCollapsePanel')
.bindEvent({
key_left: function() {
!self.options.collapsed && toggleCollapsed();
},
key_right: function() {
self.options.collapsed && toggleCollapsed();
}
});
var index = Ox.indexOf(self.options.extras, Ox.isEmpty);
self.extras = index > -1
? [
self.options.extras.slice(0, index),
self.options.extras.slice(index + 1)
]
: [
[],
self.options.extras
];
self.$titlebar = Ox.Bar({
orientation: 'horizontal',
size: self.options.size
})
.bindEvent({
anyclick: clickTitlebar,
doubleclick: doubleclickTitlebar
})
.appendTo(that);
self.$button = Ox.Button({
style: 'symbol',
type: 'image',
value: self.options.collapsed ? 'expand' : 'collapse',
values: [
{id: 'expand', title: 'right'},
{id: 'collapse', title: 'down'}
]
})
.bindEvent({
click: function() {
that.gainFocus();
toggleCollapsed(true);
}
})
.appendTo(self.$titlebar);
self.$extras = [];
if (self.extras[0].length) {
self.$extras[0] = Ox.Element()
.addClass('OxExtras')
.css({width: self.extras[0].length * 16 + 'px'})
.appendTo(self.$titlebar);
self.extras[0].forEach(function($extra) {
$extra.appendTo(self.$extras[0]);
});
}
self.$title = Ox.Element()
.addClass('OxTitle')
.css({
left: 16 + self.extras[0].length * 16 + 'px',
right: self.extras[1].length * 16 + 'px'
})
.html(self.options.title)
.appendTo(self.$titlebar);
if (self.extras[1].length) {
self.$extras[1] = Ox.Element()
.addClass('OxExtras')
.css({width: self.extras[1].length * 16 + 'px'})
.appendTo(self.$titlebar);
self.extras[1].forEach(function($extra) {
$extra.appendTo(self.$extras[1]);
});
}
that.$content = 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'
}).hide();
}
function clickTitlebar(e) {
if (!$(e.target).hasClass('OxButton')) {
that.gainFocus();
}
}
function doubleclickTitlebar(e) {
if (!$(e.target).hasClass('OxButton')) {
self.$button.trigger('click');
}
}
function toggleCollapsed(fromButton) {
// show/hide is needed in case the collapsed content
// grows vertically when shrinking the panel horizontally
var marginTop;
self.options.collapsed = !self.options.collapsed;
marginTop = self.options.collapsed ? -that.$content.height() : 0;
!fromButton && self.$button.toggle();
!self.options.collapsed && that.$content.css({
marginTop: -that.$content.height() + 'px'
}).show();
if (self.options.animate) {
that.$content.animate({
marginTop: marginTop + 'px'
}, 250, function() {
self.options.collapsed && that.$content.hide();
});
} else {
that.$content.css({
marginTop: marginTop + 'px'
});
self.options.collapsed && that.$content.hide();
}
that.triggerEvent('toggle', {
collapsed: self.options.collapsed
});
}
/*@
update <f> Update panel when in collapsed state
@*/
that.updatePanel = function() {
self.options.collapsed && that.$content.css({
marginTop: -that.$content.height()
});
};
return that;
};

View file

@ -0,0 +1,73 @@
'use strict';
Ox.SlidePanel = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
animate: 250,
elements: [],
orientation: 'horizontal',
selected: ''
})
.options(options || {})
.update({
selected: function() {
selectElement(self.options.selected);
},
size: updateElements
})
.addClass('OxSlidePanel');
if (!self.options.selected) {
self.options.selected = self.options.elements[0].id
}
self.elements = self.options.elements.length;
self.$content = Ox.Element()
.css(getContentCSS())
.appendTo(that);
updateElements();
self.options.elements.forEach(function(element, index) {
element.element.appendTo(self.$content);
});
function getContentCSS() {
return {
left: -Ox.getIndexById(self.options.elements, self.options.selected)
* self.options.size + 'px',
width: self.elements * self.options.size + 'px'
};
}
function getElementCSS(index) {
return {
left: index * self.options.size + 'px',
width: self.options.size + 'px'
};
}
function selectElement(id) {
self.$content.animate({
left: getContentCSS().left,
}, self.options.animate);
}
function updateElements() {
self.$content.css(getContentCSS());
self.options.elements.forEach(function(element, index) {
element.element.css(getElementCSS(index));
});
}
that.replaceElement = function(idOrIndex, element) {
var index = Ox.isNumber(idOrIndex) ? idOrIndex
: Ox.getIndexById(self.options.elements, idOrIndex);
self.options.elements[index].element.replaceWith(
self.options.elements[index].element = element.css(getElementCSS(index))
);
return that;
};
return that;
};

View file

@ -0,0 +1,316 @@
'use strict';
/*@
Ox.SplitPanel <f> SpliPanel Object
options <o> Options object
elements <[o]|[]> Array of two or three element objects
collapsible <b|false> If true, can be collapsed (if outer element)
collapsed <b|false> If true, is collapsed (if collapsible)
defaultSize <n|s|"auto"> Default size in px (restorable via reset)
element <o> Any Ox.Element
If any element is collapsible or resizable, all elements must
have an id.
resettable <b|false> If true, can be resetted (if outer element)
Note that reset fires on doubleclick, and if the element is also
collapsible, toggle now fires on singleclick, no longer on click.
Singleclick happens 250 ms later.
resizable <b|false> If true, can be resized (if outer element)
resize <[n]|[]> Min size, optional snappy points, and max size
size <n|s|"auto"> Size in px (one element must be "auto")
tooltip <b|s|false> If true, show tooltip, if string, append it
orientation <s|"horizontal"> orientation ("horizontal" or "vertical")
self <o> Shared private variable
([options[, self]]) -> <o:Ox.Element> SpliPanel Object
resize <!> resize
Fires on resize, on both elements being resized
resizeend <!> resizeend
Fires on resize, on both elements being resized
resizepause <!> resizepause
Fires on resize, on both elements being resized
toggle <!> toggle
Fires on collapse or expand, on the element being toggled
@*/
Ox.SplitPanel = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
elements: [],
orientation: 'horizontal'
})
.options(options || {})
.addClass('OxSplitPanel');
self.defaultSize = self.options.elements.map(function(element) {
return !Ox.isUndefined(element.defaultSize)
? element.defaultSize : element.size;
});
self.dimensions = Ox.UI.DIMENSIONS[self.options.orientation];
self.edges = Ox.UI.EDGES[self.options.orientation];
self.initialized = false;
self.length = self.options.elements.length;
self.$elements = [];
self.$resizebars = [];
self.options.elements.forEach(function(element, index) {
var elementIndices = index == 0 ? [0, 1] : index == 1 ? [1, 0] : [2, 1],
resizebarIndex = self.$resizebars.length;
self.options.elements[index] = Ox.extend({
collapsible: false,
collapsed: false,
defaultSize: 'auto',
resettable: false,
resizable: false,
resize: [],
size: 'auto',
tooltip: false
}, element);
// top and bottom (horizontal) or left and right (vertical)
self.edges.slice(2).forEach(function(edge) {
element.element.css(
edge, (parseInt(element.element.css(edge)) || 0) + 'px'
);
});
if (element.collapsed) {
// left/right (horizontal) or top/bottom (vertical)
that.css(self.edges[index == 0 ? 0 : 1], -element.size + 'px');
}
self.$elements[index] = element.element.appendTo(that);
if (element.collapsible || element.resizable) {
self.$resizebars[resizebarIndex] = Ox.Resizebar({
collapsed: element.collapsed,
collapsible: element.collapsible,
edge: self.edges[index == 0 ? 0 : 1],
orientation: self.options.orientation == 'horizontal'
? 'vertical' : 'horizontal',
resettable: element.resettable,
resizable: element.resizable,
resize: element.resize,
size: element.size,
tooltip: element.tooltip === true ? '' : element.tooltip
})
.bindEvent({
reset: function() {
that.resetElement(index);
},
resize: function(data) {
onResize(elementIndices, data.size);
triggerEvents(elementIndices, 'resize', data);
},
resizepause: function(data) {
triggerEvents(elementIndices, 'resizepause', data);
},
resizeend: function(data) {
triggerEvents(elementIndices, 'resizeend', data);
},
toggle: function(data) {
that.toggleElement(index);
}
})
[index == 0 ? 'insertAfter' : 'insertBefore'](self.$elements[index]);
}
});
setSizes();
function getSize(index) {
var element = self.options.elements[index];
return element.size + (element.collapsible || element.resizable);
}
function getVisibleSize(index) {
var element = self.options.elements[index];
return getSize(index) * !element.collapsed;
}
function onResize(elementIndices, size) {
var dimension = self.dimensions[0],
edge = self.edges[elementIndices[0] == 0 ? 0 : 1];
self.options.elements[elementIndices[0]].size = size;
elementIndices.forEach(function(elementIndex, index) {
self.$elements[elementIndex].css(
index == 0 ? dimension : edge,
(index == 0 ? size : size + 1) + 'px'
);
});
}
function setSizes(animate) {
// will animate if animate is truthy and call animate if it's a function
self.options.elements.forEach(function(element, index) {
var $resizebar,
css = {},
edges = self.edges.slice(0, 2).map(function(edge) {
// left/right (horizontal) or top/bottom (vertical)
var value = parseInt(self.$elements[index].css(edge));
return !self.initialized && value || 0;
});
if (element.size != 'auto') {
// width (horizontal) or height (vertical)
css[self.dimensions[0]] = element.size + 'px';
}
if (index == 0) {
// left (horizontal) or top (vertical)
css[self.edges[0]] = edges[0] + 'px';
// right (horizontal) or bottom (vertical)
if (element.size == 'auto') {
css[self.edges[1]] = getSize(1) + (
self.length == 3 ? getVisibleSize(2) : 0
) + 'px';
}
} else if (index == 1) {
// left (horizontal) or top (vertical)
if (self.options.elements[0].size != 'auto') {
css[self.edges[0]] = edges[0] + getSize(0) + 'px';
} else {
css[self.edges[0]] = 'auto'; // fixme: why is this needed?
}
// right (horizontal) or bottom (vertical)
css[self.edges[1]] = (self.length == 3 ? getSize(2) : 0) + 'px';
} else {
// left (horizontal) or top (vertical)
if (element.size == 'auto') {
css[self.edges[0]] = getVisibleSize(0) + getSize(1) + 'px';
} else {
css[self.edges[0]] = 'auto'; // fixme: why is this needed?
}
// right (horizontal) or bottom (vertical)
css[self.edges[1]] = edges[1] + 'px';
}
if (animate) {
self.$elements[index].animate(css, 250, function() {
index == 0 && Ox.isFunction(animate) && animate();
});
} else {
self.$elements[index].css(css);
}
if (element.collapsible || element.resizable) {
$resizebar = self.$resizebars[
index < 2 ? 0 : self.$resizebars.length - 1
];
// left or right (horizontal) or top or bottom (vertical)
css = Ox.extend(
{}, self.edges[index == 0 ? 0 : 1], element.size + 'px'
);
if (animate) {
$resizebar.animate(css, 250);
} else {
$resizebar.css(css);
}
$resizebar.options({size: element.size});
}
});
self.initialized = true;
}
function triggerEvents(elementIndices, event, data) {
elementIndices.forEach(function(elementIndex, index) {
var $element = self.$elements[elementIndex],
size = index == 0 ? data.size : $element[self.dimensions[0]]();
$element.triggerEvent(event, {size: size});
});
}
/*@
isCollapsed <f> Tests if an outer element is collapsed
(index) -> <b> True if collapsed
index <i> The element's index
@*/
that.isCollapsed = function(index) {
return self.options.elements[index].collapsed;
};
/*@
replaceElement <f> Replaces an element
(index, element) -> <f> replace element
index <n> The element's index
element <o> New element
@*/
that.replaceElement = function(index, element) {
// top and bottom (horizontal) or left and right (vertical)
self.edges.slice(2).forEach(function(edge) {
element.css(edge, (parseInt(element.css(edge)) || 0) + 'px');
});
self.$elements[index] = element;
self.options.elements[index].element.replaceWith(
self.options.elements[index].element = element
);
setSizes();
return that;
};
/*@
resetElement <f> Resets an outer element to its initial size
@*/
that.resetElement = function(index) {
var element = self.options.elements[index];
element.size = self.defaultSize[index];
setSizes(function() {
element.element.triggerEvent('resize', {
size: element.size
});
element = self.options.elements[index == 0 ? 1 : index - 1];
element.element.triggerEvent('resize', {
size: element.element[self.dimensions[0]]()
});
});
return that;
};
/*@
size <f> Get or set size of an element
(index) -> <i> Returns size
(index, size) -> <o> Sets size, returns SplitPanel
(index, size, callback) -> <o> Sets size with animation, returns SplitPanel
index <i> The element's index
size <i> New size, in px
callback <b|f> Callback function (passing true animates w/o callback)
@*/
that.resizeElement = that.size = function(index, size, callback) {
var element = self.options.elements[index];
if (arguments.length == 1) {
return element.element[self.dimensions[0]]()
* !that.isCollapsed(index);
} else {
element.size = size;
setSizes(callback);
return that;
}
};
/*@
toggleElement <f> Toggles collapsed state of an outer element
(index) -> <o> The SplitPanel
index <s|i> The element's index
@*/
that.toggleElement = function(index) {
if (self.toggling) {
return that;
}
var element = self.options.elements[index],
value = parseInt(that.css(self.edges[index == 0 ? 0 : 1]))
+ element.element[self.dimensions[0]]()
* (element.collapsed ? 1 : -1),
animate = Ox.extend({}, self.edges[index == 0 ? 0 : 1], value);
self.toggling = true;
that.animate(animate, 250, function() {
element.collapsed = !element.collapsed;
element.element.triggerEvent('toggle', {
collapsed: element.collapsed
});
self.$resizebars[index < 2 ? 0 : self.$resizebars.length - 1].options({
collapsed: element.collapsed
});
element = self.options.elements[index == 0 ? 1 : index - 1];
element.element.triggerEvent('resize', {
size: element.element[self.dimensions[0]]()
});
self.toggling = false;
});
return that;
};
return that;
};

View file

@ -0,0 +1,112 @@
'use strict';
/*@
Ox.TabPanel <f> Tabbed panel
options <o> Options
content <o|f> Content per tab
Either `({id1: $element1, id2: $element2}}` or `function(id) {
return $element; })`
size <n|24> Height of the tab bar
tabs [o] Tabs
id <s> Tab id
title <s> Tab title
self <o> Shared private variable
([options[, self]]) -> <o:Ox.SplitPanel> Panel
change <!> change
@*/
Ox.TabPanel = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
content: null,
size: 24,
tabs: []
})
.options(options || {})
.update({
content: function() {
self.$panel.replaceElement(1, getContent());
}
});
self.isObject = Ox.isObject(self.options.content);
self.selected = getSelected();
self.$bar = Ox.Bar({size: 24});
self.$tabs = Ox.ButtonGroup({
buttons: self.options.tabs,
id: 'tabs',
selectable: true,
value: self.selected
})
.css({top: (self.options.size - 16) / 2 + 'px'})
.bindEvent({
change: function(data) {
self.selected = data.value;
self.$panel.replaceElement(1, getContent());
that.triggerEvent('change', {selected: self.selected});
}
})
.appendTo(self.$bar);
self.$panel = Ox.SplitPanel({
elements: [
{
element: self.$bar,
size: self.options.size
},
{
element: getContent()
}
],
orientation: 'vertical'
})
.addClass('OxTabPanel');
that.setElement(self.$panel);
function getContent() {
return self.isObject
? self.options.content[self.selected]
: self.options.content(self.selected);
}
function getSelected() {
var selected = self.options.tabs.filter(function(tab) {
return tab.selected;
});
return (selected.length ? selected : self.options.tabs)[0].id;
}
//@ reloadPanel <f> reload panel
that.reloadPanel = function() {
self.$panel.replaceElement(1, getContent());
return that;
};
/*@
select <f> select
(id) -> <o> select panel
@*/
// FIXME: remove select (collides with a jquery method)
that.select = that.selectTab = function(id) {
if (Ox.getIndexById(self.options.tabs, id) > -1) {
self.$tabs.options({value: id});
}
return that;
};
/*@
selected <f> selected
() -> <b> return selected panel
@*/
that.selected = function() {
return self.selected;
};
return that;
};

Some files were not shown because too many files have changed in this diff Show more