remove unneeded Ox. prefix from path and file names
This commit is contained in:
parent
4138e4e558
commit
51696562f1
1365 changed files with 43 additions and 43 deletions
286
source/UI/UI.js
Normal file
286
source/UI/UI.js
Normal 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
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
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
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
4
source/UI/jquery/jquery-1.7.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
84
source/UI/jquery/jquery.mousewheel.js
Normal file
84
source/UI/jquery/jquery.mousewheel.js
Normal 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);
|
||||
118
source/UI/js/Audio/AudioElement.js
Normal file
118
source/UI/js/Audio/AudioElement.js
Normal 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;
|
||||
|
||||
};
|
||||
390
source/UI/js/Audio/AudioPlayer.js
Normal file
390
source/UI/js/Audio/AudioPlayer.js
Normal 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: ' 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(' · ');
|
||||
}
|
||||
|
||||
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: ' ' + 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
28
source/UI/js/Bar/Bar.js
Normal 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;
|
||||
|
||||
};
|
||||
243
source/UI/js/Bar/Progressbar.js
Normal file
243
source/UI/js/Bar/Progressbar.js
Normal 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;
|
||||
|
||||
};
|
||||
194
source/UI/js/Bar/Resizebar.js
Normal file
194
source/UI/js/Bar/Resizebar.js
Normal 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;
|
||||
|
||||
};
|
||||
34
source/UI/js/Bar/Tabbar.js
Normal file
34
source/UI/js/Bar/Tabbar.js
Normal 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;
|
||||
|
||||
};
|
||||
1375
source/UI/js/Calendar/Calendar.js
Normal file
1375
source/UI/js/Calendar/Calendar.js
Normal file
File diff suppressed because it is too large
Load diff
832
source/UI/js/Calendar/CalendarEditor.js
Normal file
832
source/UI/js/Calendar/CalendarEditor.js
Normal 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;
|
||||
|
||||
};
|
||||
291
source/UI/js/Code/DocPage.js
Normal file
291
source/UI/js/Code/DocPage.js
Normal 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> '
|
||||
+ '<' + item.types.join('></code> or <code><') + '> </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>> '
|
||||
+ Ox.encodeHTMLEntities(test.statement)
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/\n/g, '<br>\n ')
|
||||
+ '</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;
|
||||
|
||||
};
|
||||
401
source/UI/js/Code/DocPanel.js
Normal file
401
source/UI/js/Code/DocPanel.js
Normal 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;
|
||||
|
||||
};
|
||||
225
source/UI/js/Code/ExamplePage.js
Normal file
225
source/UI/js/Code/ExamplePage.js
Normal 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;
|
||||
|
||||
};
|
||||
181
source/UI/js/Code/ExamplePanel.js
Normal file
181
source/UI/js/Code/ExamplePanel.js
Normal 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;
|
||||
|
||||
};
|
||||
95
source/UI/js/Code/SourceViewer.js
Normal file
95
source/UI/js/Code/SourceViewer.js
Normal 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;
|
||||
|
||||
};
|
||||
147
source/UI/js/Code/SyntaxHighlighter.js
Normal file
147
source/UI/js/Code/SyntaxHighlighter.js
Normal 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(' ', self.options.tabSize - self.options.showTabs),
|
||||
whitespace = self.options.showWhitespace ? '\u00B7' : ' ';
|
||||
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(' ', 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
82
source/UI/js/Core/API.js
Normal 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
107
source/UI/js/Core/App.js
Normal 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;
|
||||
|
||||
};
|
||||
70
source/UI/js/Core/Clipboard.js
Normal file
70
source/UI/js/Core/Clipboard.js
Normal 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);
|
||||
}
|
||||
};
|
||||
};
|
||||
26
source/UI/js/Core/Container.js
Normal file
26
source/UI/js/Core/Container.js
Normal 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;
|
||||
};
|
||||
20
source/UI/js/Core/Cookies.js
Normal file
20
source/UI/js/Core/Cookies.js
Normal 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;
|
||||
}
|
||||
}
|
||||
843
source/UI/js/Core/Element.js
Normal file
843
source/UI/js/Core/Element.js
Normal 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
502
source/UI/js/Core/Event.js
Normal 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);
|
||||
|
||||
}());
|
||||
66
source/UI/js/Core/Focus.js
Normal file
66
source/UI/js/Core/Focus.js
Normal 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;
|
||||
|
||||
}());
|
||||
147
source/UI/js/Core/Fullscreen.js
Normal file
147
source/UI/js/Core/Fullscreen.js
Normal 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;
|
||||
|
||||
}());
|
||||
52
source/UI/js/Core/GarbageCollection.js
Normal file
52
source/UI/js/Core/GarbageCollection.js
Normal 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;
|
||||
|
||||
}());
|
||||
73
source/UI/js/Core/History.js
Normal file
73
source/UI/js/Core/History.js
Normal 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]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
89
source/UI/js/Core/LoadingIcon.js
Normal file
89
source/UI/js/Core/LoadingIcon.js
Normal 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;
|
||||
|
||||
};
|
||||
82
source/UI/js/Core/LoadingScreen.js
Normal file
82
source/UI/js/Core/LoadingScreen.js
Normal 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;
|
||||
|
||||
};
|
||||
226
source/UI/js/Core/Request.js
Normal file
226
source/UI/js/Core/Request.js
Normal 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
192
source/UI/js/Core/Theme.js
Normal 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
137
source/UI/js/Core/UI.js
Normal 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
1117
source/UI/js/Core/URL.js
Normal file
File diff suppressed because it is too large
Load diff
437
source/UI/js/Form/ArrayEditable.js
Normal file
437
source/UI/js/Form/ArrayEditable.js
Normal 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;
|
||||
|
||||
};
|
||||
198
source/UI/js/Form/ArrayInput.js
Normal file
198
source/UI/js/Form/ArrayInput.js
Normal 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
198
source/UI/js/Form/Button.js
Normal 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;
|
||||
|
||||
};
|
||||
157
source/UI/js/Form/ButtonGroup.js
Normal file
157
source/UI/js/Form/ButtonGroup.js
Normal 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;
|
||||
|
||||
};
|
||||
123
source/UI/js/Form/Checkbox.js
Normal file
123
source/UI/js/Form/Checkbox.js
Normal 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;
|
||||
|
||||
};
|
||||
119
source/UI/js/Form/CheckboxGroup.js
Normal file
119
source/UI/js/Form/CheckboxGroup.js
Normal 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;
|
||||
|
||||
};
|
||||
88
source/UI/js/Form/ColorInput.js
Normal file
88
source/UI/js/Form/ColorInput.js
Normal 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;
|
||||
|
||||
};
|
||||
117
source/UI/js/Form/ColorPicker.js
Normal file
117
source/UI/js/Form/ColorPicker.js
Normal 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;
|
||||
|
||||
};
|
||||
216
source/UI/js/Form/DateInput.js
Normal file
216
source/UI/js/Form/DateInput.js
Normal 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;
|
||||
|
||||
};
|
||||
70
source/UI/js/Form/DateTimeInput.js
Normal file
70
source/UI/js/Form/DateTimeInput.js
Normal 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;
|
||||
|
||||
};
|
||||
301
source/UI/js/Form/Editable.js
Normal file
301
source/UI/js/Form/Editable.js
Normal 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 ? ' '
|
||||
: self.options.type == 'input'
|
||||
? value.replace(/ /g, ' ')
|
||||
: value.replace(/\n$/, '\n ')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/(^ | $)/, ' ')
|
||||
.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;
|
||||
|
||||
};
|
||||
229
source/UI/js/Form/EditableContent.js
Normal file
229
source/UI/js/Form/EditableContent.js
Normal 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;
|
||||
|
||||
};
|
||||
140
source/UI/js/Form/FileButton.js
Normal file
140
source/UI/js/Form/FileButton.js
Normal 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;
|
||||
|
||||
}
|
||||
337
source/UI/js/Form/FileInput.js
Normal file
337
source/UI/js/Form/FileInput.js
Normal 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
907
source/UI/js/Form/Filter.js
Normal 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
184
source/UI/js/Form/Form.js
Normal 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;
|
||||
|
||||
};
|
||||
128
source/UI/js/Form/FormElementGroup.js
Normal file
128
source/UI/js/Form/FormElementGroup.js
Normal 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;
|
||||
|
||||
};
|
||||
53
source/UI/js/Form/FormItem.js
Normal file
53
source/UI/js/Form/FormItem.js
Normal 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;
|
||||
|
||||
};
|
||||
203
source/UI/js/Form/FormPanel.js
Normal file
203
source/UI/js/Form/FormPanel.js
Normal 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
983
source/UI/js/Form/Input.js
Normal 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;
|
||||
|
||||
};
|
||||
161
source/UI/js/Form/InputGroup.js
Normal file
161
source/UI/js/Form/InputGroup.js
Normal 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;
|
||||
|
||||
};
|
||||
222
source/UI/js/Form/InsertHTMLDialog.js
Normal file
222
source/UI/js/Form/InsertHTMLDialog.js
Normal 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;
|
||||
|
||||
};
|
||||
54
source/UI/js/Form/Label.js
Normal file
54
source/UI/js/Form/Label.js
Normal 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;
|
||||
|
||||
};
|
||||
172
source/UI/js/Form/ObjectArrayInput.js
Normal file
172
source/UI/js/Form/ObjectArrayInput.js
Normal 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;
|
||||
|
||||
};
|
||||
69
source/UI/js/Form/ObjectInput.js
Normal file
69
source/UI/js/Form/ObjectInput.js
Normal 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;
|
||||
|
||||
};
|
||||
124
source/UI/js/Form/OptionGroup.js
Normal file
124
source/UI/js/Form/OptionGroup.js
Normal 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
110
source/UI/js/Form/Picker.js
Normal 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;
|
||||
|
||||
};
|
||||
44
source/UI/js/Form/PlaceInput.js
Normal file
44
source/UI/js/Form/PlaceInput.js
Normal 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;
|
||||
|
||||
};
|
||||
155
source/UI/js/Form/PlacePicker.js
Normal file
155
source/UI/js/Form/PlacePicker.js
Normal 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
295
source/UI/js/Form/Range.js
Normal 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
272
source/UI/js/Form/Select.js
Normal 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;
|
||||
|
||||
};
|
||||
149
source/UI/js/Form/SelectInput.js
Normal file
149
source/UI/js/Form/SelectInput.js
Normal 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;
|
||||
|
||||
};
|
||||
251
source/UI/js/Form/Spreadsheet.js
Normal file
251
source/UI/js/Form/Spreadsheet.js
Normal 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;
|
||||
|
||||
};
|
||||
173
source/UI/js/Form/TimeInput.js
Normal file
173
source/UI/js/Form/TimeInput.js
Normal 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;
|
||||
|
||||
};
|
||||
84
source/UI/js/Image/ImageElement.js
Normal file
84
source/UI/js/Image/ImageElement.js
Normal 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;
|
||||
|
||||
};
|
||||
578
source/UI/js/Image/ImageViewer.js
Normal file
578
source/UI/js/Image/ImageViewer.js
Normal 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
285
source/UI/js/List/Chart.js
vendored
Normal 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;
|
||||
|
||||
};
|
||||
212
source/UI/js/List/ColumnList.js
Normal file
212
source/UI/js/List/ColumnList.js
Normal 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;
|
||||
|
||||
};
|
||||
156
source/UI/js/List/CustomList.js
Normal file
156
source/UI/js/List/CustomList.js
Normal 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;
|
||||
|
||||
};
|
||||
226
source/UI/js/List/IconItem.js
Normal file
226
source/UI/js/List/IconItem.js
Normal 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;
|
||||
|
||||
};
|
||||
308
source/UI/js/List/IconList.js
Normal file
308
source/UI/js/List/IconList.js
Normal 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;
|
||||
|
||||
};
|
||||
303
source/UI/js/List/InfoList.js
Normal file
303
source/UI/js/List/InfoList.js
Normal 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
1863
source/UI/js/List/List.js
Normal file
File diff suppressed because it is too large
Load diff
55
source/UI/js/List/ListItem.js
Normal file
55
source/UI/js/List/ListItem.js
Normal 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;
|
||||
|
||||
};
|
||||
64
source/UI/js/List/SortList.js
Normal file
64
source/UI/js/List/SortList.js
Normal 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;
|
||||
|
||||
};
|
||||
1264
source/UI/js/List/TableList.js
Normal file
1264
source/UI/js/List/TableList.js
Normal file
File diff suppressed because it is too large
Load diff
305
source/UI/js/List/TreeList.js
Normal file
305
source/UI/js/List/TreeList.js
Normal 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(/(^"|"$)/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
1641
source/UI/js/Map/Map.js
Normal file
File diff suppressed because it is too large
Load diff
1170
source/UI/js/Map/MapEditor.js
Normal file
1170
source/UI/js/Map/MapEditor.js
Normal file
File diff suppressed because it is too large
Load diff
73
source/UI/js/Map/MapImage.js
Normal file
73
source/UI/js/Map/MapImage.js
Normal 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 += '¢er=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;
|
||||
|
||||
};
|
||||
326
source/UI/js/Map/MapMarker.js
Normal file
326
source/UI/js/Map/MapMarker.js
Normal 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;
|
||||
|
||||
};
|
||||
65
source/UI/js/Map/MapMarkerImage.js
Normal file
65
source/UI/js/Map/MapMarkerImage.js
Normal 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];
|
||||
|
||||
}
|
||||
|
||||
}());
|
||||
218
source/UI/js/Map/MapPlace.js
Normal file
218
source/UI/js/Map/MapPlace.js
Normal 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;
|
||||
|
||||
};
|
||||
132
source/UI/js/Map/MapRectangle.js
Normal file
132
source/UI/js/Map/MapRectangle.js
Normal 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;
|
||||
|
||||
};
|
||||
123
source/UI/js/Map/MapRectangleMarker.js
Normal file
123
source/UI/js/Map/MapRectangleMarker.js
Normal 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;
|
||||
|
||||
};
|
||||
285
source/UI/js/Menu/MainMenu.js
Normal file
285
source/UI/js/Menu/MainMenu.js
Normal 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
855
source/UI/js/Menu/Menu.js
Normal 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;
|
||||
|
||||
};
|
||||
188
source/UI/js/Menu/MenuButton.js
Normal file
188
source/UI/js/Menu/MenuButton.js
Normal 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;
|
||||
|
||||
};
|
||||
186
source/UI/js/Menu/MenuItem.js
Normal file
186
source/UI/js/Menu/MenuItem.js
Normal 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;
|
||||
|
||||
};
|
||||
177
source/UI/js/Panel/CollapsePanel.js
Normal file
177
source/UI/js/Panel/CollapsePanel.js
Normal 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;
|
||||
|
||||
};
|
||||
73
source/UI/js/Panel/SlidePanel.js
Normal file
73
source/UI/js/Panel/SlidePanel.js
Normal 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;
|
||||
|
||||
};
|
||||
316
source/UI/js/Panel/SplitPanel.js
Normal file
316
source/UI/js/Panel/SplitPanel.js
Normal 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;
|
||||
|
||||
};
|
||||
112
source/UI/js/Panel/TabPanel.js
Normal file
112
source/UI/js/Panel/TabPanel.js
Normal 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
Loading…
Add table
Add a link
Reference in a new issue