forked from 0x2620/oxjs
better filesystem structure for modules and themes; 'minified' ui if debug option not set; dynamially generated map markers
This commit is contained in:
parent
358ee1bc96
commit
4489e88f44
596 changed files with 115093 additions and 17682 deletions
19
source/Ox.UI/js/Bar/Ox.Bar.js
Normal file
19
source/Ox.UI/js/Bar/Ox.Bar.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/**
|
||||
*/
|
||||
Ox.Bar = function(options, self) {
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
orientation: 'horizontal',
|
||||
size: 'medium' // can be int
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxBar Ox' + Ox.toTitleCase(self.options.orientation)),
|
||||
dimensions = Ox.UI.DIMENSIONS[self.options.orientation];
|
||||
self.options.size = Ox.isString(self.options.size) ?
|
||||
Ox.UI.getBarSize(self.options.size) : self.options.size;
|
||||
that.css(dimensions[0], '100%')
|
||||
.css(dimensions[1], self.options.size + 'px');
|
||||
return that;
|
||||
};
|
||||
144
source/Ox.UI/js/Bar/Ox.Resizebar.js
Normal file
144
source/Ox.UI/js/Bar/Ox.Resizebar.js
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/**
|
||||
*/
|
||||
Ox.Resizebar = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
collapsed: false,
|
||||
collapsible: true,
|
||||
edge: 'left',
|
||||
elements: [],
|
||||
orientation: 'horizontal',
|
||||
parent: null,
|
||||
resizable: true,
|
||||
resize: [],
|
||||
size: 0
|
||||
})
|
||||
.options(options || {}) // fixme: options function should be able to handle undefined, no need for || {}
|
||||
.addClass('OxResizebar Ox' + Ox.toTitleCase(self.options.orientation))
|
||||
/*
|
||||
.attr({
|
||||
draggable: 'true'
|
||||
})
|
||||
.bind('dragstart', function(e) {
|
||||
// e.originalEvent.dataTransfer.setDragImage($('<div>')[0], 0, 0);
|
||||
})
|
||||
.bind('drag', function(e) {
|
||||
Ox.print('dragging', e)
|
||||
})
|
||||
*/
|
||||
.bindEvent({
|
||||
anyclick: toggle,
|
||||
dragstart: dragstart,
|
||||
drag: drag,
|
||||
dragend: dragend
|
||||
})
|
||||
.append($('<div>').addClass('OxSpace'))
|
||||
.append($('<div>').addClass('OxLine'))
|
||||
.append($('<div>').addClass('OxSpace'));
|
||||
|
||||
$.extend(self, {
|
||||
clientXY: self.options.orientation == 'horizontal' ? 'clientY' : 'clientX',
|
||||
dimensions: Ox.UI.DIMENSIONS[self.options.orientation], // fixme: should orientation be the opposite orientation here?
|
||||
edges: Ox.UI.EDGES[self.options.orientation],
|
||||
leftOrTop: self.options.edge == 'left' || self.options.edge == 'top'
|
||||
});
|
||||
|
||||
function dragstart(event, e) {
|
||||
if (self.options.resizable && !self.options.collapsed) {
|
||||
Ox.print('DRAGSTART')
|
||||
self.drag = {
|
||||
startPos: e[self.clientXY],
|
||||
startSize: self.options.size
|
||||
}
|
||||
} else { Ox.print('NO DRAGSTART r !c', self.options.resizable, !self.options.collapsed) }
|
||||
}
|
||||
|
||||
function drag(event, e) {
|
||||
if (self.options.resizable && !self.options.collapsed) {
|
||||
var d = e[self.clientXY] - self.drag.startPos,
|
||||
size = self.options.size;
|
||||
self.options.size = Ox.limit(
|
||||
self.drag.startSize + d * (self.leftOrTop ? 1 : -1),
|
||||
self.options.resize[0],
|
||||
self.options.resize[self.options.resize.length - 1]
|
||||
);
|
||||
Ox.forEach(self.options.resize, function(v) {
|
||||
if (self.options.size >= v - 8 && self.options.size <= v + 8) {
|
||||
self.options.size = v;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (self.options.size != size) {
|
||||
that.css(self.edges[self.leftOrTop ? 2 : 3], self.options.size + 'px');
|
||||
// fixme: send {size: x}, not x
|
||||
if (self.leftOrTop) {
|
||||
self.options.elements[0]
|
||||
.css(self.dimensions[1], self.options.size + 'px')
|
||||
self.options.elements[1]
|
||||
.css(self.edges[2], (self.options.size + 1) + 'px')
|
||||
} else {
|
||||
self.options.elements[0]
|
||||
.css(self.edges[3], (self.options.size + 1) + 'px')
|
||||
self.options.elements[1]
|
||||
.css(self.dimensions[1], self.options.size + 'px')
|
||||
}
|
||||
triggerEvents('resize');
|
||||
self.options.parent.updateSize(self.leftOrTop ? 0 : 1, self.options.size); // fixme: listen to event instead?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dragend() {
|
||||
if (self.options.resizable && !self.options.collapsed) {
|
||||
self.options.size != self.drag.startSize && triggerEvents('resizeend');
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (self.options.collapsible) {
|
||||
// fixme: silly, pass a parameter
|
||||
self.options.parent.toggle(
|
||||
self.leftOrTop ? 0 :
|
||||
self.options.parent.options('elements').length - 1
|
||||
);
|
||||
self.options.collapsed = !self.options.collapsed;
|
||||
}
|
||||
/*
|
||||
//Ox.print('toggle');
|
||||
if (Ox.isUndefined(self.options.position)) {
|
||||
self.options.position = parseInt(self.options.parent.css(self.options.edge)) +
|
||||
(self.options.collapsed ? self.options.size : 0);
|
||||
}
|
||||
var size = self.options.position -
|
||||
(self.options.collapsed ? 0 : self.options.size),
|
||||
animate = {};
|
||||
//Ox.print('s.o.e', self.options.edge);
|
||||
animate[self.options.edge] = size;
|
||||
self.options.parent.animate(animate, 200, function() {
|
||||
var i = (self.options.edge == 'left' || self.options.edge == 'top') ? 0 : 1;
|
||||
self.options.collapsed = !self.options.collapsed;
|
||||
Ox.Event.trigger(self.ids[i], 'toggle', self.options.collapsed);
|
||||
Ox.Event.trigger(self.ids[1 - i], 'resize', self.options.elements[1 - i][self.dimensions[1]]());
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
function triggerEvents(event) {
|
||||
self.options.elements[0].triggerEvent(event,
|
||||
self.leftOrTop ?
|
||||
self.options.size :
|
||||
self.options.elements[0][self.dimensions[1]]()
|
||||
);
|
||||
self.options.elements[1].triggerEvent(event,
|
||||
self.leftOrTop ?
|
||||
self.options.elements[1][self.dimensions[1]]() :
|
||||
self.options.size
|
||||
);
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
28
source/Ox.UI/js/Bar/Ox.Tabbar.js
Normal file
28
source/Ox.UI/js/Bar/Ox.Tabbar.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/**
|
||||
*/
|
||||
Ox.Tabbar = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Bar({
|
||||
size: 20
|
||||
}, self)
|
||||
.defaults({
|
||||
selected: 0,
|
||||
tabs: []
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxTabbar');
|
||||
|
||||
Ox.ButtonGroup({
|
||||
buttons: self.options.tabs,
|
||||
group: true,
|
||||
selectable: true,
|
||||
selected: self.options.selected,
|
||||
size: 'medium',
|
||||
style: 'tab',
|
||||
}).appendTo(that);
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
11
source/Ox.UI/js/Bar/Ox.Toolbar.js
Normal file
11
source/Ox.UI/js/Bar/Ox.Toolbar.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/**
|
||||
fixme: no need for this
|
||||
*/
|
||||
Ox.Toolbar = function(options, self) {
|
||||
var self = self || {},
|
||||
that = new Ox.Bar({
|
||||
size: Ox.UI.getBarSize(options.size)
|
||||
}, self);
|
||||
return that;
|
||||
};
|
||||
710
source/Ox.UI/js/Calendar/Ox.Calendar.js
Normal file
710
source/Ox.UI/js/Calendar/Ox.Calendar.js
Normal file
|
|
@ -0,0 +1,710 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Calendar = function(options, self) {
|
||||
|
||||
self = self || {};
|
||||
var that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
date: new Date(),
|
||||
dates: [],
|
||||
height: 512,
|
||||
range: [100, 5101],
|
||||
width: 512,
|
||||
zoom: 8
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxCalendar')
|
||||
.css({
|
||||
width: self.options.width + 'px',
|
||||
height: self.options.height + 'px'
|
||||
});
|
||||
|
||||
self.maxZoom = 32;
|
||||
self.minLabelWidth = 80;
|
||||
self.overlayWidths = [Math.round(self.options.width / 16)];
|
||||
self.overlayWidths = [
|
||||
Math.floor((self.options.width - self.overlayWidths[0]) / 2),
|
||||
self.overlayWidths[0],
|
||||
Math.ceil((self.options.width - self.overlayWidths[0]) / 2),
|
||||
];
|
||||
self.units = [
|
||||
{
|
||||
id: 'millennium',
|
||||
seconds: 365242.5 * 86400,
|
||||
date: function(i) {
|
||||
return '01/01/' + (i + 1) + '000 UTC';
|
||||
},
|
||||
name: function(i) {
|
||||
return Ox.formatOrdinal(i + 2) + ' millennium';
|
||||
},
|
||||
value: function(date) {
|
||||
return Math.floor(date.getUTCFullYear() / 1000) - 1;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'century',
|
||||
seconds: 36524.25 * 86400,
|
||||
date: function(i) {
|
||||
return '01/01/' + (i + 19) + '00 UTC';
|
||||
},
|
||||
name: function(i) {
|
||||
return Ox.formatOrdinal(i + 20) + ' century';
|
||||
},
|
||||
value: function(date) {
|
||||
return Math.floor(date.getUTCFullYear() / 100) - 19;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'decade',
|
||||
seconds: 3652.425 * 86400,
|
||||
date: function(i) {
|
||||
return '01/01/' + (i + 197) + '0 UTC'
|
||||
},
|
||||
name: function(i) {
|
||||
return (i + 197) + '0s'
|
||||
},
|
||||
value: function(date) {
|
||||
return Math.floor(date.getUTCFullYear() / 10) - 197;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'year',
|
||||
seconds: 365.2425 * 86400,
|
||||
date: function(i) {
|
||||
return '01/01/' + (i + 1970) + ' UTC';
|
||||
},
|
||||
name: function(i) {
|
||||
return (i + 1970) + '';
|
||||
},
|
||||
value: function(date) {
|
||||
return date.getUTCFullYear() - 1970;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'month',
|
||||
seconds: 365.2425 / 12 * 86400,
|
||||
date: function(i) {
|
||||
return (Ox.mod(i, 12) + 1) + '/01/' + (Math.floor(i / 12) + 1970) + ' UTC';
|
||||
},
|
||||
name: function(i) {
|
||||
return Ox.SHORT_MONTHS[Ox.mod(i, 12)] + ' ' + Math.floor(i / 12 + 1970)
|
||||
},
|
||||
value: function(date) {
|
||||
return (date.getUTCFullYear() - 1970) * 12 + date.getUTCMonth();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'week',
|
||||
seconds: 7 * 86400,
|
||||
date: function(i) {
|
||||
return (i * 7 - 3) * 86400000;
|
||||
},
|
||||
name: function(i) {
|
||||
return Ox.formatDate(new Date((i * 7 - 3) * 86400000), '%a, %b %e');
|
||||
},
|
||||
value: function(date) {
|
||||
return Math.floor((date / 86400000 + 4) / 7);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'day',
|
||||
seconds: 86400,
|
||||
date: function(i) {
|
||||
return i * 86400000;
|
||||
},
|
||||
name: function(i) {
|
||||
return Ox.formatDate(new Date(i * 86400000), '%b %e, %Y', true);
|
||||
},
|
||||
value: function(date) {
|
||||
return Math.floor(date / 86400000);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'six_hours',
|
||||
seconds: 21600,
|
||||
date: function(i) {
|
||||
return i * 21600000;
|
||||
},
|
||||
name: function(i) {
|
||||
return Ox.formatDate(new Date(i * 21600000), '%b %e, %H:00', true);
|
||||
},
|
||||
value: function(date) {
|
||||
return Math.floor(date / 21600000);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'hour',
|
||||
seconds: 3600,
|
||||
date: function(i) {
|
||||
return i * 3600000;
|
||||
},
|
||||
name: function(i) {
|
||||
return Ox.formatDate(new Date(i * 3600000), '%b %e, %H:00', true);
|
||||
},
|
||||
value: function(date) {
|
||||
return Math.floor(date / 3600000);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'five_minutes',
|
||||
seconds: 300,
|
||||
date: function(i) {
|
||||
return i * 300000;
|
||||
},
|
||||
name: function(i) {
|
||||
return Ox.formatDate(new Date(i * 300000), '%b %e, %H:%M', true);
|
||||
},
|
||||
value: function(date) {
|
||||
return Math.floor(date / 300000);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'minute',
|
||||
seconds: 60,
|
||||
date: function(i) {
|
||||
return i * 60000;
|
||||
},
|
||||
name: function(i) {
|
||||
return Ox.formatDate(new Date(i * 60000), '%b %e, %H:%M', true);
|
||||
},
|
||||
value: function(date) {
|
||||
return Math.floor(date / 60000);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'five_seconds',
|
||||
seconds: 5,
|
||||
date: function(i) {
|
||||
return i * 5000;
|
||||
},
|
||||
name: function(i) {
|
||||
return Ox.formatDate(new Date(i * 5000), '%H:%M:%S', true);
|
||||
},
|
||||
value: function(date) {
|
||||
return Math.floor(date / 5000);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'second',
|
||||
seconds: 1,
|
||||
date: function(i) {
|
||||
return i * 1000;
|
||||
},
|
||||
name: function(i) {
|
||||
return Ox.formatDate(new Date(i * 1000), '%H:%M:%S', true);
|
||||
},
|
||||
value: function(date) {
|
||||
return Math.floor(date / 1000);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
self.$container = new Ox.Element()
|
||||
.addClass('OxCalendarContainer')
|
||||
.css({
|
||||
top: '24px',
|
||||
bottom: '40px'
|
||||
})
|
||||
.bind({
|
||||
mouseleave: mouseleave,
|
||||
mousemove: mousemove,
|
||||
mousewheel: mousewheel
|
||||
})
|
||||
.bindEvent({
|
||||
doubleclick: doubleclick,
|
||||
dragstart: dragstart,
|
||||
drag: drag,
|
||||
dragpause: dragpause,
|
||||
dragend: dragend,
|
||||
singleclick: singleclick
|
||||
})
|
||||
.appendTo(that);
|
||||
|
||||
self.$content = new Ox.Element()
|
||||
.addClass('OxCalendarContent')
|
||||
.appendTo(self.$container);
|
||||
|
||||
self.$background = new Ox.Element()
|
||||
.addClass('OxBackground')
|
||||
.appendTo(self.$content);
|
||||
|
||||
self.$scalebar = new Ox.Element()
|
||||
.addClass('OxTimeline')
|
||||
.css({
|
||||
posision: 'absolute',
|
||||
})
|
||||
.appendTo(self.$content);
|
||||
|
||||
self.$scrollbar = new Ox.Element()
|
||||
.addClass('OxTimeline')
|
||||
.css({
|
||||
posision: 'absolute',
|
||||
bottom: '40px'
|
||||
})
|
||||
.appendTo(that);
|
||||
self.$overlay = new Ox.Element()
|
||||
.addClass('OxOverlay')
|
||||
.css({
|
||||
bottom: '40px'
|
||||
})
|
||||
.append(
|
||||
$('<div>').css({
|
||||
width: self.overlayWidths[0] + 'px'
|
||||
})
|
||||
)
|
||||
.append(
|
||||
$('<div>').css({
|
||||
left: self.overlayWidths[0] + 'px',
|
||||
width: self.overlayWidths[1] + 'px'
|
||||
})
|
||||
)
|
||||
.append(
|
||||
$('<div>').css({
|
||||
left: (self.overlayWidths[0] + self.overlayWidths[1]) + 'px',
|
||||
width: self.overlayWidths[2] + 'px'
|
||||
})
|
||||
)
|
||||
.bindEvent({
|
||||
dragstart: dragstartScrollbar,
|
||||
drag: dragScrollbar,
|
||||
dragpause: dragpauseScrollbar,
|
||||
dragend: dragendScrollbar
|
||||
})
|
||||
.appendTo(that);
|
||||
|
||||
self.$zoombar = new Ox.Element()
|
||||
.css({
|
||||
position: 'absolute',
|
||||
bottom: 24 + 'px',
|
||||
height: '16px'
|
||||
})
|
||||
.appendTo(that);
|
||||
self.$zoomInput = new Ox.Range({
|
||||
arrows: true,
|
||||
max: self.maxZoom,
|
||||
min: 0,
|
||||
size: self.options.width,
|
||||
thumbSize: 32,
|
||||
thumbValue: true,
|
||||
value: self.options.zoom
|
||||
})
|
||||
.bindEvent({
|
||||
change: changeZoom
|
||||
})
|
||||
.appendTo(self.$zoombar);
|
||||
|
||||
self.$statusbar = new Ox.Bar({
|
||||
size: 24
|
||||
})
|
||||
.css({
|
||||
// fixme: no need to set position absolute with map statusbar
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
textAlign: 'center'
|
||||
})
|
||||
.appendTo(that);
|
||||
|
||||
self.$tooltip = new Ox.Tooltip({
|
||||
animate: false
|
||||
})
|
||||
.css({
|
||||
textAlign: 'center'
|
||||
});
|
||||
|
||||
renderCalendar();
|
||||
|
||||
function changeDate() {
|
||||
|
||||
}
|
||||
|
||||
function changeZoom(event, data) {
|
||||
self.options.zoom = data.value;
|
||||
renderCalendar();
|
||||
}
|
||||
|
||||
function doubleclick(event, e) {
|
||||
if ($(e.target).is(':not(.OxLine > .OxDate)')) {
|
||||
if (self.options.zoom < self.maxZoom) {
|
||||
self.options.date = new Date(
|
||||
(+self.options.date + +getMouseDate(e)) / 2
|
||||
);
|
||||
self.options.zoom++;
|
||||
}
|
||||
renderCalendar();
|
||||
}
|
||||
}
|
||||
|
||||
function dragstart(event, e) {
|
||||
if ($(e.target).is(':not(.OxLine > .OxDate)')) {
|
||||
self.drag = {x: e.clientX};
|
||||
}
|
||||
}
|
||||
|
||||
function drag(event, e) {
|
||||
if (self.drag) {
|
||||
///*
|
||||
self.$content.css({
|
||||
marginLeft: (e.clientX - self.drag.x) + 'px'
|
||||
});
|
||||
self.$scrollbar.css({
|
||||
marginLeft: Math.round((e.clientX - self.drag.x) / 16) + 'px'
|
||||
});
|
||||
//*/
|
||||
/*
|
||||
self.options.date = new Date(
|
||||
+self.options.date - e.clientDX * getSecondsPerPixel() * 1000
|
||||
);
|
||||
*/
|
||||
//self.drag = {x: e.clientX};
|
||||
}
|
||||
}
|
||||
|
||||
function dragpause(event, e) {
|
||||
if (self.drag) {
|
||||
dragafter(e);
|
||||
self.drag = {x: e.clientX};
|
||||
}
|
||||
}
|
||||
|
||||
function dragend(event, e) {
|
||||
if (self.drag) {
|
||||
dragafter(e);
|
||||
self.drag = null;
|
||||
}
|
||||
}
|
||||
|
||||
function dragafter(e) {
|
||||
self.options.date = new Date(
|
||||
+self.options.date - (e.clientX - self.drag.x) * getSecondsPerPixel() * 1000
|
||||
);
|
||||
self.$content.css({
|
||||
marginLeft: 0
|
||||
});
|
||||
self.$scrollbar.css({
|
||||
marginLeft: 0
|
||||
});
|
||||
renderCalendar();
|
||||
}
|
||||
|
||||
function dragstartScrollbar(event, e) {
|
||||
self.drag = {x: e.clientX};
|
||||
}
|
||||
|
||||
function dragScrollbar(event, e) {
|
||||
self.$content.css({
|
||||
marginLeft: ((e.clientX - self.drag.x) * 16) + 'px'
|
||||
});
|
||||
self.$scrollbar.css({
|
||||
marginLeft: (e.clientX - self.drag.x) + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
function dragpauseScrollbar(event, e) {
|
||||
dragafterScrollbar(e);
|
||||
self.drag = {x: e.clientX};
|
||||
}
|
||||
|
||||
function dragendScrollbar(event, e) {
|
||||
dragafterScrollbar(e);
|
||||
self.drag = null;
|
||||
}
|
||||
|
||||
function dragafterScrollbar(e) {
|
||||
self.options.date = new Date(
|
||||
+self.options.date + (self.drag.x - e.clientX) * getSecondsPerPixel() * 1000 * 16
|
||||
);
|
||||
// fixme: duplicated
|
||||
self.$content.css({
|
||||
marginLeft: 0
|
||||
});
|
||||
self.$scrollbar.css({
|
||||
marginLeft: 0
|
||||
});
|
||||
renderCalendar();
|
||||
}
|
||||
|
||||
function formatDate(date) {
|
||||
var isFullDays = Ox.formatDate(date.start, '%H:%M:%S') == '00:00:00' &&
|
||||
Ox.formatDate(date.stop, '%H:%M:%S') == '00:00:00',
|
||||
isOneDay = isFullDays && date.stop - date.start == 86400000, // fixme: wrong, DST
|
||||
isSameDay = Ox.formatDate(date.start, '%Y-%m-%d') ==
|
||||
Ox.formatDate(date.stop, '%Y-%m-%d'),
|
||||
isSameYear = date.start.getFullYear() == date.stop.getFullYear(),
|
||||
timeFormat = isFullDays ? '' : ', %H:%M:%S',
|
||||
str = Ox.formatDate(date.start, '%a, %b %e');
|
||||
if (isOneDay || isSameDay || !isSameYear) {
|
||||
str += Ox.formatDate(date.start, ', %Y' + timeFormat);
|
||||
}
|
||||
if (!isOneDay && !isSameDay) {
|
||||
str += Ox.formatDate(date.stop, ' - %a, %b %e, %Y' + timeFormat);
|
||||
}
|
||||
if (isSameDay) {
|
||||
str += Ox.formatDate(date.stop, ' - ' + timeFormat.replace(', ', ''));
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function getCalendarDate() {
|
||||
var ms = self.options.width * getSecondsPerPixel() * 1000;
|
||||
return {
|
||||
start: new Date(+self.options.date - ms / 2),
|
||||
stop: new Date(+self.options.date + ms / 2)
|
||||
};
|
||||
}
|
||||
|
||||
function getDateByName(name) {
|
||||
var date = {};
|
||||
Ox.forEach(self.options.dates, function(v) {
|
||||
if (v.name == name) {
|
||||
date = v;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return date;
|
||||
}
|
||||
|
||||
function getDateElement(date, zoom) {
|
||||
var left = getPosition(date.start, zoom),
|
||||
width = Math.max(getPosition(date.stop, zoom) - left, 1);
|
||||
return new Ox.Element()
|
||||
.addClass('OxDate')
|
||||
.css({
|
||||
left: left + 'px',
|
||||
width: width + 'px'
|
||||
})
|
||||
.data({
|
||||
name: date.name
|
||||
})
|
||||
.html(' ' + date.name);
|
||||
}
|
||||
|
||||
function getMouseDate(e) {
|
||||
return new Date(+self.options.date + (
|
||||
e.clientX - that.offset().left - self.options.width / 2 - 1
|
||||
) * getSecondsPerPixel() * 1000);
|
||||
}
|
||||
|
||||
function getPixelsPerSecond(zoom) {
|
||||
return Math.pow(2, (zoom || self.options.zoom) - (self.maxZoom - 4));
|
||||
}
|
||||
|
||||
function getPosition(date, zoom) {
|
||||
return Math.round(
|
||||
self.options.width / 2 +
|
||||
(date - self.options.date) / 1000 *
|
||||
getPixelsPerSecond(zoom || self.options.zoom)
|
||||
);
|
||||
}
|
||||
|
||||
function getSecondsPerPixel(zoom) {
|
||||
return 1 / getPixelsPerSecond(zoom);
|
||||
}
|
||||
|
||||
function getBackgroundElements(zoom) {
|
||||
// fixme: duplicated
|
||||
var $elements = [],
|
||||
units = getUnits(zoom),
|
||||
n, value, width;
|
||||
[1, 0].forEach(function(u) {
|
||||
var unit = units[u],
|
||||
value = unit.value(self.options.date),
|
||||
width = unit.seconds * getPixelsPerSecond(zoom),
|
||||
n = Math.ceil(self.options.width * 1.5/* * 16*/ / width);
|
||||
Ox.loop(-n, n + 1, function(i) {
|
||||
$elements.push(
|
||||
new Ox.Element()
|
||||
.addClass(
|
||||
u == 0 ? 'line' : Ox.mod(value + i, 2) == 0 ? 'even' : 'odd'
|
||||
)
|
||||
.css({
|
||||
left: getPosition(new Date(unit.date(value + i)), zoom) + 'px',
|
||||
width: (u == 0 ? 1 : width) + 'px'
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
return $elements;
|
||||
}
|
||||
|
||||
function getTimelineElements(zoom) {
|
||||
var $elements = [],
|
||||
unit = getUnits(zoom)[0],
|
||||
value = unit.value(self.options.date),
|
||||
width = unit.seconds * getPixelsPerSecond(zoom);
|
||||
n = Math.ceil(self.options.width * 1.5/* * 16*/ / width);
|
||||
Ox.loop(-n, n + 1, function(i) {
|
||||
$elements.push(
|
||||
getDateElement({
|
||||
name: unit.name(value + i),
|
||||
start: new Date(unit.date(value + i)),
|
||||
stop: new Date(unit.date(value + i + 1))
|
||||
}, zoom)
|
||||
.addClass(Ox.mod(value + i, 2) == 0 ? 'even' : 'odd')
|
||||
);
|
||||
});
|
||||
return $elements;
|
||||
}
|
||||
|
||||
function getUnits(zoom) {
|
||||
// returns array of 2 units
|
||||
// units[0] for timeline
|
||||
// units[1] for background
|
||||
var pixelsPerSecond = getPixelsPerSecond(zoom),
|
||||
units;
|
||||
self.units = self.units.reverse();
|
||||
Ox.forEach(self.units, function(v, i) {
|
||||
width = Math.round(v.seconds * pixelsPerSecond);
|
||||
if (width >= self.minLabelWidth) {
|
||||
units = [self.units[i], self.units[i - 1]];
|
||||
return false;
|
||||
}
|
||||
});
|
||||
self.units = self.units.reverse();
|
||||
return units;
|
||||
}
|
||||
|
||||
function mouseleave() {
|
||||
self.$tooltip.hide();
|
||||
}
|
||||
|
||||
function mousemove(e) {
|
||||
var $target = $(e.target),
|
||||
date, title;
|
||||
if ($target.is('.OxLine > .OxDate')) {
|
||||
date = getDateByName($target.data('name'));
|
||||
title = '<span class="OxBright">' + date.name + '</span><br/>' +
|
||||
formatDate(date);
|
||||
} else {
|
||||
title = Ox.formatDate(getMouseDate(e), '%a, %b %e, %Y, %H:%M:%S');
|
||||
}
|
||||
self.$tooltip.options({
|
||||
title: title
|
||||
})
|
||||
.show(e.clientX, e.clientY);
|
||||
}
|
||||
|
||||
function mousewheel(e, delta, deltaX, deltaY) {
|
||||
//Ox.print('mousewheel', delta, deltaX, deltaY);
|
||||
var deltaZ = 0;
|
||||
if (!self.mousewheel && deltaY && Math.abs(deltaY) > Math.abs(deltaX)) {
|
||||
if (deltaY < 0 && self.options.zoom > 0) {
|
||||
deltaZ = -1
|
||||
} else if (deltaY > 0 && self.options.zoom < self.maxZoom) {
|
||||
deltaZ = 1
|
||||
}
|
||||
if (deltaZ) {
|
||||
self.options.date = deltaZ == -1 ?
|
||||
new Date(2 * +self.options.date - +getMouseDate(e)) :
|
||||
new Date((+self.options.date + +getMouseDate(e)) / 2)
|
||||
self.options.zoom += deltaZ;
|
||||
self.$zoomInput.options({value: self.options.zoom});
|
||||
renderCalendar();
|
||||
}
|
||||
}
|
||||
self.mousewheel = true;
|
||||
setTimeout(function() {
|
||||
self.mousewheel = false;
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function overlaps(date0, date1) {
|
||||
return (
|
||||
date0.start >= date1.start && date0.start < date1.stop
|
||||
) || (
|
||||
date1.start >= date0.start && date1.start < date0.stop
|
||||
);
|
||||
}
|
||||
|
||||
function renderCalendar() {
|
||||
$('.OxBackground').empty();
|
||||
$('.OxDate').remove();
|
||||
renderBackground();
|
||||
renderTimelines();
|
||||
renderDates();
|
||||
self.$statusbar.html(
|
||||
Ox.formatDate(self.options.date, '%a, %b %e, %Y, %H:%M:%S (%s)', true)
|
||||
);
|
||||
}
|
||||
|
||||
function renderBackground() {
|
||||
getBackgroundElements(self.options.zoom).forEach(function($element) {
|
||||
$element.appendTo(self.$background);
|
||||
});
|
||||
}
|
||||
|
||||
function renderDates() {
|
||||
var calendarDate = getCalendarDate();
|
||||
lineDates = [];
|
||||
self.options.dates.filter(function(date) {
|
||||
// filter out dates outside the visible area
|
||||
return overlaps(date, calendarDate);
|
||||
}).sort(function(a, b) {
|
||||
// sort dates by duration, descending
|
||||
return (b.stop - b.start) - (a.stop - a.start);
|
||||
}).forEach(function(date, i) {
|
||||
var line = lineDates.length;
|
||||
// traverse lines
|
||||
Ox.forEach(lineDates, function(dates, line_) {
|
||||
var fits = true;
|
||||
// traverse dates in line
|
||||
Ox.forEach(dates, function(date_) {
|
||||
// if overlaps, check next line
|
||||
if (overlaps(date, date_)) {
|
||||
fits = false;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (fits) {
|
||||
line = line_;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (line == lineDates.length) {
|
||||
lineDates[line] = [];
|
||||
}
|
||||
lineDates[line].push(date);
|
||||
});
|
||||
$('.OxLine').remove();
|
||||
lineDates.forEach(function(dates, line) {
|
||||
var $line = new Ox.Element()
|
||||
.addClass('OxLine')
|
||||
.css({
|
||||
top: ((line + 1) * 16) + 'px'
|
||||
})
|
||||
.appendTo(self.$content);
|
||||
dates.sort(function(a, b) {
|
||||
// sort dates by start, ascending
|
||||
return a.start - b.start;
|
||||
}).forEach(function(date) {
|
||||
getDateElement(date).appendTo($line);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderTimelines() {
|
||||
Ox.print(self.options.zoom, Math.max(self.options.zoom - 4, 0))
|
||||
getTimelineElements(self.options.zoom).forEach(function($element) {
|
||||
$element.appendTo(self.$scalebar.$element);
|
||||
});
|
||||
getTimelineElements(Math.max(self.options.zoom - 4, 0)).forEach(function($element) {
|
||||
$element.appendTo(self.$scrollbar.$element);
|
||||
});
|
||||
}
|
||||
|
||||
function singleclick(event, e) {
|
||||
if ($(e.target).is(':not(.OxLine > .OxDate)')) {
|
||||
self.options.date = getMouseDate(e);
|
||||
renderCalendar();
|
||||
}
|
||||
}
|
||||
|
||||
self.onChange = function(key, val) {
|
||||
if (key == 'date') {
|
||||
|
||||
} else if (key == 'zoom') {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
26
source/Ox.UI/js/Calendar/Ox.CalendarDate.js
Normal file
26
source/Ox.UI/js/Calendar/Ox.CalendarDate.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.CalendarDate = function(options) {
|
||||
|
||||
var self = {},
|
||||
that = this;
|
||||
|
||||
['start', 'stop'].forEach(function(v) {
|
||||
var date = self.options[v];
|
||||
if (Ox.isString(date)) {
|
||||
date = new Date(self.options[v]);
|
||||
}
|
||||
});
|
||||
|
||||
self.duration = self.options.stop - self.options.start;
|
||||
|
||||
that.format = function() {
|
||||
|
||||
};
|
||||
|
||||
that.formatDuration = function() {
|
||||
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
129
source/Ox.UI/js/Core/Ox.App.js
Normal file
129
source/Ox.UI/js/Core/Ox.App.js
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/*
|
||||
============================================================================
|
||||
Application
|
||||
============================================================================
|
||||
*/
|
||||
|
||||
// fixme: get rid of launch, fire load event
|
||||
|
||||
Ox.App = (function() {
|
||||
|
||||
/***
|
||||
Ox.App
|
||||
Basic application instance that communicates with a JSON API.
|
||||
The JSON API must support at least the following actions:
|
||||
api returns all api methods
|
||||
init returns data (site, user, ...)
|
||||
Options
|
||||
timeout API timeout in msec
|
||||
type 'GET' or 'POST'
|
||||
url URL of the API
|
||||
Methods
|
||||
api[action] make a request
|
||||
api.cancel cancel a request
|
||||
options get or set options
|
||||
Events
|
||||
load app loaded
|
||||
***/
|
||||
|
||||
return function(options) {
|
||||
|
||||
options = options || {};
|
||||
var self = {
|
||||
options: Ox.extend({
|
||||
timeout: 60000,
|
||||
type: 'POST',
|
||||
url: '/api/',
|
||||
}, options || {}),
|
||||
time: new Date()
|
||||
},
|
||||
that = new Ox.Element({}, self);
|
||||
|
||||
that.api = {
|
||||
api: function(callback) {
|
||||
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.api(function(result) {
|
||||
Ox.forEach(result.data.actions, function(val, key) {
|
||||
that.api[key] = function(data, callback) {
|
||||
if (arguments.length == 1 && Ox.isFunction(data)) {
|
||||
callback = data;
|
||||
data = {};
|
||||
}
|
||||
return Ox.Request.send($.extend({
|
||||
url: self.options.url,
|
||||
data: {
|
||||
action: key,
|
||||
data: JSON.stringify(data)
|
||||
},
|
||||
callback: callback
|
||||
}, !val.cache ? {age: 0}: {}));
|
||||
};
|
||||
});
|
||||
that.api.init(getUserData(), function(result) {
|
||||
//Ox.UI.hideLoadingScreen();
|
||||
that.triggerEvent({
|
||||
load: result.data
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getUserData() {
|
||||
return {
|
||||
document: {
|
||||
referrer: document.referrer
|
||||
},
|
||||
history: {
|
||||
length: history.length
|
||||
},
|
||||
navigator: {
|
||||
cookieEnabled: navigator.cookieEnabled,
|
||||
plugins: $.map(navigator.plugins, function(plugin, i) {
|
||||
return plugin.name;
|
||||
}),
|
||||
userAgent: navigator.userAgent
|
||||
},
|
||||
screen: screen,
|
||||
time: (+new Date() - self.time) / 1000,
|
||||
window: {
|
||||
innerHeight: window.innerHeight,
|
||||
innerWidth: window.innerWidth,
|
||||
outerHeight: window.outerHeight,
|
||||
outerWidth: window.outerWidth,
|
||||
screenLeft: window.screenLeft,
|
||||
screenTop: window.screenTop
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
self.change = function(key, value) {
|
||||
|
||||
};
|
||||
|
||||
that.options = function() {
|
||||
return Ox.getset(self.options, Array.prototype.slice.call(arguments), self.change, that);
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
|
||||
}());
|
||||
23
source/Ox.UI/js/Core/Ox.Clipboard.js
Normal file
23
source/Ox.UI/js/Core/Ox.Clipboard.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Clipboard = function() {
|
||||
/***
|
||||
Ox.Clipboard
|
||||
Basic clipboard handler
|
||||
Methods
|
||||
copy(data) copy data to clipboard
|
||||
paste paste data from clipboard
|
||||
***/
|
||||
var clipboard = {};
|
||||
return {
|
||||
_print: function() {
|
||||
Ox.print(JSON.stringify(clipboard));
|
||||
},
|
||||
copy: function(data) {
|
||||
clipboard = data;
|
||||
Ox.print('copy', JSON.stringify(clipboard));
|
||||
},
|
||||
paste: function(type) {
|
||||
return clipboard;
|
||||
}
|
||||
};
|
||||
}();
|
||||
17
source/Ox.UI/js/Core/Ox.Container.js
Normal file
17
source/Ox.UI/js/Core/Ox.Container.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
// fixme: wouldn't it be better to let the elements be,
|
||||
// rather then $element, $content, and potentially others,
|
||||
// 0, 1, 2, etc, so that append would append 0, and appendTo
|
||||
// would append (length - 1)?
|
||||
|
||||
Ox.Container = function(options, self) {
|
||||
// fixme: to be deprecated
|
||||
var that = new Ox.Element('div', self)
|
||||
.options(options || {})
|
||||
.addClass('OxContainer');
|
||||
that.$content = new Ox.Element('div', self)
|
||||
.options(options || {})
|
||||
.addClass('OxContent')
|
||||
.appendTo(that);
|
||||
return that;
|
||||
};
|
||||
325
source/Ox.UI/js/Core/Ox.Element.js
Normal file
325
source/Ox.UI/js/Core/Ox.Element.js
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
// check out http://ejohn.org/apps/learn/#36 (-#38, making fns work w/o new)
|
||||
|
||||
Ox.Element = function() {
|
||||
|
||||
/***
|
||||
Basic element object
|
||||
***/
|
||||
|
||||
/*
|
||||
tooltip option can be any of the following:
|
||||
string
|
||||
function(e), returns string
|
||||
{mousemove: true, title: function(e)}
|
||||
*/
|
||||
|
||||
return function(options, self) {
|
||||
|
||||
if (!(this instanceof arguments.callee)) {
|
||||
return new arguments.callee(options, self);
|
||||
}
|
||||
|
||||
self = self || {};
|
||||
self.options = options || {};
|
||||
// allow for Ox.Element('tagname', self)
|
||||
if (typeof self.options == 'string') {
|
||||
self.options = {
|
||||
element: self.options
|
||||
};
|
||||
}
|
||||
if (!self.$eventHandler) {
|
||||
self.$eventHandler = $('<div>');
|
||||
}
|
||||
|
||||
var that = new Ox.JQueryElement(
|
||||
$('<' + (self.options.element || 'div') + '>')
|
||||
)
|
||||
.mousedown(mousedown);
|
||||
|
||||
/*
|
||||
self.options.tooltip && that.bind(Ox.extend({
|
||||
mouseenter: mouseenter,
|
||||
mouseleave: mouseleave
|
||||
}, self.options.tooltip.mousemove ? {
|
||||
mousemove: mousemove
|
||||
} : {}));
|
||||
*/
|
||||
|
||||
function mousedown(e) {
|
||||
/*
|
||||
better mouse events
|
||||
mousedown:
|
||||
trigger mousedown
|
||||
within 250 msec:
|
||||
mouseup: trigger anyclick ("click" would collide with click events of certain widgets)
|
||||
mouseup + mousedown: trigger doubleclick
|
||||
after 250 msec:
|
||||
mouseup + no mousedown within 250 msec: trigger singleclick
|
||||
no mouseup within 250 msec:
|
||||
trigger mouserepeat every 50 msec
|
||||
trigger dragstart
|
||||
mousemove: trigger drag
|
||||
no mousemove within 250 msec:
|
||||
trigger dragpause
|
||||
mouseup: trigger dragend
|
||||
*/
|
||||
var clientX, clientY,
|
||||
dragTimeout = 0,
|
||||
mouseInterval = 0;
|
||||
if (!self.mouseTimeout) {
|
||||
// first mousedown
|
||||
that.triggerEvent('mousedown', e);
|
||||
self.mouseup = false;
|
||||
self.mouseTimeout = setTimeout(function() {
|
||||
self.mouseTimeout = 0;
|
||||
if (self.mouseup) {
|
||||
// singleclick
|
||||
that.triggerEvent('singleclick', e);
|
||||
} else {
|
||||
// mouserepeat, drag
|
||||
clientX = e.clientX;
|
||||
clientY = e.clientY;
|
||||
that.triggerEvent('dragstart', e);
|
||||
mouserepeat();
|
||||
mouseInterval = setInterval(mouserepeat, 50);
|
||||
Ox.UI.$window.unbind('mouseup', mouseup)
|
||||
.mousemove(mousemove)
|
||||
.one('mouseup', function(e) {
|
||||
clearInterval(mouseInterval);
|
||||
clearTimeout(dragTimeout);
|
||||
Ox.UI.$window.unbind('mousemove', mousemove);
|
||||
that.triggerEvent('dragend', extend(e));
|
||||
});
|
||||
that.one('mouseleave', function() {
|
||||
clearInterval(mouseInterval);
|
||||
});
|
||||
}
|
||||
}, 250);
|
||||
} else {
|
||||
// second mousedown
|
||||
clearTimeout(self.mouseTimeout);
|
||||
self.mouseTimeout = 0;
|
||||
that.triggerEvent('doubleclick', e);
|
||||
}
|
||||
Ox.UI.$window.one('mouseup', mouseup);
|
||||
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() {
|
||||
that.triggerEvent('dragpause', e);
|
||||
}, 250);
|
||||
that.triggerEvent('drag', e);
|
||||
}
|
||||
function mouserepeat() {
|
||||
that.triggerEvent('mouserepeat');
|
||||
}
|
||||
function mouseup(e) {
|
||||
// only trigger on firse mouseup
|
||||
if (!self.mouseup) {
|
||||
that.triggerEvent('anyclick', e);
|
||||
self.mouseup = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
function mouseenter(e) {
|
||||
self.$tooltip = new Ox.Tooltip({
|
||||
title: Ox.isString(self.options.tooltip) ?
|
||||
self.options.tooltip : Ox.isFunction(self.options.tooltip) ?
|
||||
self.options.tooltip(e) : self.options.tooltip.title(e)
|
||||
}).show();
|
||||
}
|
||||
|
||||
function mouseleave(e) {
|
||||
self.$tooltip.hide();
|
||||
}
|
||||
|
||||
function mousemove(e) {
|
||||
self.$tooltip.options({
|
||||
title: self.options.tooltip.title(e)
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
self.onChange = function() {
|
||||
// self.onChange(key, value)
|
||||
// is called when an option changes
|
||||
// (to be implemented by widget)
|
||||
// fixme: rename to self.setOption
|
||||
};
|
||||
|
||||
that._leakSelf = function() { // fixme: remove
|
||||
return self;
|
||||
}
|
||||
|
||||
that.bindEvent = function() {
|
||||
/***
|
||||
binds a function to an event triggered by this object
|
||||
Usage
|
||||
bindEvent(event, fn) or bindEvent({event0: fn0, event1: fn1, ...})
|
||||
***/
|
||||
if (arguments.length == 1) {
|
||||
Ox.forEach(arguments[0], function(fn, event) {
|
||||
// Ox.print(that.id, 'bind', event);
|
||||
self.$eventHandler.bind('ox_' + event, fn);
|
||||
});
|
||||
} else {
|
||||
// Ox.print(that.id, 'bind', arguments[0]);
|
||||
self.$eventHandler.bind('ox_' + arguments[0], arguments[1]);
|
||||
}
|
||||
return that;
|
||||
}
|
||||
|
||||
that.bindEventOnce = function() {
|
||||
if (arguments.length == 1) {
|
||||
Ox.forEach(arguments[0], function(fn, event) {
|
||||
self.$eventHandler.one('ox_' + event, fn);
|
||||
});
|
||||
} else {
|
||||
self.$eventHandler.one('ox_' + arguments[0], arguments[1]);
|
||||
}
|
||||
return that;
|
||||
};
|
||||
|
||||
that.defaults = function(defaults) {
|
||||
/***
|
||||
sets the default options
|
||||
Usage
|
||||
that.defaults({key0: value0, key1: value1, ...})
|
||||
***/
|
||||
self.defaults = defaults;
|
||||
delete self.options; // fixme: hackish fix for that = Ox.Foo({...}, self).defaults({...}).options({...})
|
||||
return that;
|
||||
};
|
||||
|
||||
that.gainFocus = function() {
|
||||
/***
|
||||
make this object gain focus
|
||||
***/
|
||||
Ox.Focus.focus(that.id);
|
||||
return that;
|
||||
};
|
||||
|
||||
that.hasFocus = function() {
|
||||
/***
|
||||
returns true if this object has focus
|
||||
***/
|
||||
return Ox.Focus.focused() == that.id;
|
||||
};
|
||||
|
||||
that.loseFocus = function() {
|
||||
/***
|
||||
make this object lose focus
|
||||
***/
|
||||
Ox.Focus.blur(that.id);
|
||||
return that;
|
||||
};
|
||||
|
||||
that.options = function() { // fixme: use Ox.getset
|
||||
/***
|
||||
get or set options
|
||||
Usage
|
||||
that.options() returns self.options
|
||||
that.options('foo') returns self.options.foo
|
||||
that.options('foo', x) sets self.options.foo,
|
||||
returns that
|
||||
that.options({foo: x, bar: y}) sets self.options.foo
|
||||
and self.options.bar,
|
||||
returns that
|
||||
***/
|
||||
var args,
|
||||
length = arguments.length,
|
||||
oldOptions,
|
||||
ret;
|
||||
if (length == 0) {
|
||||
// options()
|
||||
ret = self.options;
|
||||
} else if (length == 1 && typeof arguments[0] == 'string') {
|
||||
// options(str)
|
||||
ret = self.options ? self.options[arguments[0]] : options[arguments[0]];
|
||||
} else {
|
||||
// options (str, val) or options({str: val, ...})
|
||||
// translate (str, val) to ({str: val})
|
||||
args = Ox.makeObject.apply(that, arguments || {});
|
||||
oldOptions = $.extend({}, self.options);
|
||||
// if options have not been set, extend defaults,
|
||||
// otherwise, extend options
|
||||
//self.options = $.extend(self.options, self.options ? {} : self.defaults, args);
|
||||
self.options = $.extend({}, self.defaults, self.options, args);
|
||||
//self.options = $.extend(self.options || self.defaults, args);
|
||||
Ox.forEach(args, function(val, key) {
|
||||
// key == 'id' && id && Ox.Event.changeId(id, value);
|
||||
/*!Ox.equals(value, oldOptions[key]) &&*/ self.onChange(key, val);
|
||||
});
|
||||
ret = that;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
that.removeElement = function() {
|
||||
/***
|
||||
remove this element, including its event handler
|
||||
***/
|
||||
that.loseFocus();
|
||||
delete self.$eventHandler;
|
||||
that.remove();
|
||||
delete Ox.UI.elements[that.id];
|
||||
return that;
|
||||
};
|
||||
|
||||
that.triggerEvent = function() {
|
||||
/***
|
||||
triggers an event
|
||||
Usage
|
||||
triggerEvent(event)
|
||||
triggerEvent(event, data)
|
||||
triggerEvent({event0: data0, event1: data1, ...})
|
||||
***/
|
||||
if (Ox.isObject(arguments[0])) {
|
||||
Ox.forEach(arguments[0], function(data, event) {
|
||||
if (['mousedown', 'mouserepeat', 'anyclick', 'singleclick', 'doubleclick', 'dragstart', 'drag', 'dragpause', 'dragend', 'playing'].indexOf(event) == -1) {
|
||||
Ox.print(that.id, self.options.id, 'trigger', event, data);
|
||||
}
|
||||
self.$eventHandler.trigger('ox_' + event, data);
|
||||
});
|
||||
} else {
|
||||
if (['mousedown', 'mouserepeat', 'anyclick', 'singleclick', 'doubleclick', 'dragstart', 'drag', 'dragpause', 'dragend', 'playing'].indexOf(arguments[0]) == -1) {
|
||||
Ox.print(that.id, self.options ? self.options.id : '', 'trigger', arguments[0], arguments[1] || {});
|
||||
}
|
||||
self.$eventHandler.trigger('ox_' + arguments[0], arguments[1] || {});
|
||||
}
|
||||
return that;
|
||||
};
|
||||
|
||||
that.unbindEvent = function() {
|
||||
/***
|
||||
unbinds a function from an event triggered by this element
|
||||
Usage
|
||||
unbindEvent(event, fn)
|
||||
unbindEvent({event0: fn0, event1: fn1, ...})
|
||||
***/
|
||||
if (arguments.length == 1) {
|
||||
Ox.forEach(arguments[0], function(fn, event) {
|
||||
// Ox.print(that.id, 'unbind', arguments[0]);
|
||||
self.$eventHandler.unbind('ox_' + event, fn);
|
||||
});
|
||||
} else {
|
||||
// Ox.print(that.id, 'unbind', arguments[0]);
|
||||
self.$eventHandler.unbind('ox_' + arguments[0], arguments[1]);
|
||||
}
|
||||
return that;
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
}
|
||||
|
||||
}();
|
||||
41
source/Ox.UI/js/Core/Ox.Focus.js
Normal file
41
source/Ox.UI/js/Core/Ox.Focus.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Focus = function() {
|
||||
/***
|
||||
Ox.Focus
|
||||
Basic focus handler
|
||||
Methods
|
||||
blur(id) blur element
|
||||
focus(id) focus element
|
||||
focused() return id of focused element, or null
|
||||
***/
|
||||
var stack = [];
|
||||
return {
|
||||
_print: function() {
|
||||
Ox.print(stack);
|
||||
},
|
||||
blur: function(id) {
|
||||
var index = stack.indexOf(id);
|
||||
if (index > -1 && index == stack.length - 1) {
|
||||
stack.length == 1 ? stack.pop() :
|
||||
stack.splice(stack.length - 2, 0, stack.pop());
|
||||
//$elements[id].removeClass('OxFocus');
|
||||
$('.OxFocus').removeClass('OxFocus'); // fixme: the above is better, and should work
|
||||
stack.length && Ox.UI.elements[stack[stack.length - 1]].addClass('OxFocus');
|
||||
Ox.print('blur', id, stack);
|
||||
}
|
||||
},
|
||||
focus: function(id) {
|
||||
var index = stack.indexOf(id);
|
||||
if (index == -1 || index < stack.length - 1) {
|
||||
index > -1 && stack.splice(index, 1);
|
||||
stack.push(id);
|
||||
$('.OxFocus').removeClass('OxFocus'); // fixme: see above
|
||||
Ox.UI.elements[id].addClass('OxFocus');
|
||||
Ox.print('focus', id, stack);
|
||||
}
|
||||
},
|
||||
focused: function() {
|
||||
return stack.length ? stack[stack.length - 1] : null;
|
||||
}
|
||||
};
|
||||
}();
|
||||
5
source/Ox.UI/js/Core/Ox.History.js
Normal file
5
source/Ox.UI/js/Core/Ox.History.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/***
|
||||
Ox.History
|
||||
***/
|
||||
|
||||
38
source/Ox.UI/js/Core/Ox.JQueryElement.js
Normal file
38
source/Ox.UI/js/Core/Ox.JQueryElement.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
|
||||
/***
|
||||
Basic jQuery element
|
||||
***/
|
||||
|
||||
Ox.JQueryElement = function($element) {
|
||||
var that = this;
|
||||
that.id = Ox.uid();
|
||||
that.ox = Ox.VERSION;
|
||||
that.$element = $element.data({
|
||||
oxid: that.id
|
||||
});
|
||||
Ox.UI.elements[that.id] = that;
|
||||
return that;
|
||||
};
|
||||
|
||||
Ox.forEach($('<div>'), function(val, key) {
|
||||
if (Ox.isFunction(val)) {
|
||||
Ox.JQueryElement.prototype[key] = function() {
|
||||
var args = arguments, id, ret, that = this;
|
||||
Ox.forEach(args, function(arg, i) {
|
||||
// if an ox object was passed
|
||||
// then pass its $element instead
|
||||
// so that we can do oxObj.jqFn(oxObj)
|
||||
if (arg && arg.ox) {
|
||||
args[i] = arg.$element;
|
||||
}
|
||||
});
|
||||
ret = that.$element[key].apply(that.$element, args);
|
||||
// if the $element of an ox object was returned
|
||||
// then return the ox object instead
|
||||
// so that we can do oxObj.jqFn().oxFn()
|
||||
return ret.jquery && Ox.UI.elements[id = ret.data('oxid')] ?
|
||||
Ox.UI.elements[id] : ret;
|
||||
};
|
||||
}
|
||||
});
|
||||
198
source/Ox.UI/js/Core/Ox.Keyboard.js
Normal file
198
source/Ox.UI/js/Core/Ox.Keyboard.js
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/***
|
||||
Ox.Keyboard
|
||||
***/
|
||||
|
||||
(function() {
|
||||
|
||||
var buffer = '',
|
||||
bufferTime = 0,
|
||||
bufferTimeout = 1000,
|
||||
// wrapped in function so it can be collapsed in text editor
|
||||
keyNames = (function() {
|
||||
return {
|
||||
0: 'section',
|
||||
8: 'backspace',
|
||||
9: 'tab',
|
||||
12: 'clear',
|
||||
13: 'enter',
|
||||
16: 'shift',
|
||||
17: 'control',
|
||||
18: 'alt',
|
||||
20: 'capslock',
|
||||
27: 'escape',
|
||||
32: 'space',
|
||||
33: 'pageup',
|
||||
34: 'pagedown',
|
||||
35: 'end',
|
||||
36: 'home',
|
||||
37: 'left',
|
||||
38: 'up',
|
||||
39: 'right',
|
||||
40: 'down',
|
||||
45: 'insert',
|
||||
46: 'delete',
|
||||
47: 'help',
|
||||
48: '0',
|
||||
49: '1',
|
||||
50: '2',
|
||||
51: '3',
|
||||
52: '4',
|
||||
53: '5',
|
||||
54: '6',
|
||||
55: '7',
|
||||
56: '8',
|
||||
57: '9',
|
||||
65: 'a',
|
||||
66: 'b',
|
||||
67: 'c',
|
||||
68: 'd',
|
||||
69: 'e',
|
||||
70: 'f',
|
||||
71: 'g',
|
||||
72: 'h',
|
||||
73: 'i',
|
||||
74: 'j',
|
||||
75: 'k',
|
||||
76: 'l',
|
||||
77: 'm',
|
||||
78: 'n',
|
||||
79: 'o',
|
||||
80: 'p',
|
||||
81: 'q',
|
||||
82: 'r',
|
||||
83: 's',
|
||||
84: 't',
|
||||
85: 'u',
|
||||
86: 'v',
|
||||
87: 'w',
|
||||
88: 'x',
|
||||
89: 'y',
|
||||
90: 'z',
|
||||
//91: 'meta.left',
|
||||
//92: 'meta.right',
|
||||
91: 'meta',
|
||||
//92: 'meta',
|
||||
93: 'meta',
|
||||
96: '0.numpad',
|
||||
97: '1.numpad',
|
||||
98: '2.numpad',
|
||||
99: '3.numpad',
|
||||
100: '4.numpad',
|
||||
101: '5.numpad',
|
||||
102: '6.numpad',
|
||||
103: '7.numpad',
|
||||
104: '8.numpad',
|
||||
105: '9.numpad',
|
||||
106: 'asterisk.numpad',
|
||||
107: 'plus.numpad',
|
||||
109: 'minus.numpad',
|
||||
108: 'enter.numpad',
|
||||
110: 'dot.numpad',
|
||||
111: 'slash.numpad',
|
||||
112: 'f1',
|
||||
113: 'f2',
|
||||
114: 'f3',
|
||||
115: 'f4',
|
||||
116: 'f5',
|
||||
117: 'f6',
|
||||
118: 'f7',
|
||||
119: 'f8',
|
||||
120: 'f9',
|
||||
121: 'f10',
|
||||
122: 'f11',
|
||||
123: 'f12',
|
||||
124: 'f13',
|
||||
125: 'f14',
|
||||
126: 'f15',
|
||||
127: 'f16',
|
||||
144: 'numlock',
|
||||
145: 'scrolllock',
|
||||
186: 'semicolon',
|
||||
187: 'equal',
|
||||
188: 'comma',
|
||||
189: 'minus',
|
||||
190: 'dot',
|
||||
191: 'slash',
|
||||
192: 'backtick',
|
||||
219: 'openbracket',
|
||||
220: 'backslash',
|
||||
221: 'closebracket',
|
||||
222: 'quote'
|
||||
// see dojo, for ex.
|
||||
};
|
||||
})(),
|
||||
modifierNames = {
|
||||
altKey: 'alt', // mac: option
|
||||
ctrlKey: 'control',
|
||||
// metaKey: 'meta', // mac: command
|
||||
shiftKey: 'shift'
|
||||
};
|
||||
|
||||
Ox.UI.ready(function() {
|
||||
// fixme: how to do this better?
|
||||
// in firefox on mac, keypress doesn't fire for up/down
|
||||
// if the cursor is at the start/end of an input element
|
||||
// on linux, it doesn't seem to fire if the input element has focus
|
||||
if ($.browser.mozilla) {
|
||||
Ox.UI.$document.keypress(keypress);
|
||||
Ox.UI.$document.keydown(function(event) {
|
||||
var $element = $('input:focus');
|
||||
if ($element.length) {
|
||||
if (
|
||||
(
|
||||
keyNames[event.keyCode] == 'up' &&
|
||||
$element[0].selectionStart + $element[0].selectionEnd == 0
|
||||
) || (
|
||||
keyNames[event.keyCode] == 'down' &&
|
||||
$element[0].selectionStart == $element.val().length &&
|
||||
$element[0].selectionEnd == $element.val().length
|
||||
)
|
||||
) {
|
||||
keypress(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Ox.UI.$document.keydown(keypress);
|
||||
}
|
||||
});
|
||||
|
||||
function keypress(event) {
|
||||
var focused = Ox.Focus.focused(),
|
||||
key,
|
||||
keys = [],
|
||||
//ret = true,
|
||||
time;
|
||||
Ox.forEach(modifierNames, function(v, k) {
|
||||
event[k] && keys.push(v);
|
||||
});
|
||||
// avoid pushing modifier twice
|
||||
if (keyNames[event.keyCode] && keys.indexOf(keyNames[event.keyCode]) == -1) {
|
||||
keys.push(keyNames[event.keyCode]);
|
||||
}
|
||||
key = keys.join('_');
|
||||
if (key.match(/^[\w\d\-]$|SPACE/)) {
|
||||
time = Ox.getTime();
|
||||
if (time - bufferTime > bufferTimeout) {
|
||||
buffer = '';
|
||||
}
|
||||
buffer += key == 'SPACE' ? ' ' : key;
|
||||
bufferTime = time;
|
||||
}
|
||||
focused !== null && Ox.UI.elements[focused].triggerEvent('key_' + key);
|
||||
// fixme: oxdb context browser suggests we should add left and right keys here
|
||||
if (['down', 'space', 'up'].indexOf(key) > -1 && !Ox.UI.elements[focused].hasClass('OxInput')) {
|
||||
// prevent chrome from scrolling
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
stack.forEach(function(v) {
|
||||
// fixme: we dont get the return value!
|
||||
ret = Ox.event.trigger(keyboard + Ox.toCamelCase(key) + '.' + v);
|
||||
return ret;
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
})();
|
||||
28
source/Ox.UI/js/Core/Ox.LoadingIcon.js
Normal file
28
source/Ox.UI/js/Core/Ox.LoadingIcon.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.LoadingIcon = function(options, self) {
|
||||
var self = self || {},
|
||||
that = new Ox.Element('img', self)
|
||||
.defaults({
|
||||
size: 'medium'
|
||||
})
|
||||
.options(options || {})
|
||||
.attr({
|
||||
src: Ox.UI.getImagePath('symbolLoading.svg')
|
||||
})
|
||||
.addClass(
|
||||
'OxLoadingIcon Ox' + Ox.toTitleCase(self.options.size)
|
||||
);
|
||||
that.start = function() {
|
||||
that.animate({
|
||||
opacity: 1
|
||||
}, 250);
|
||||
return that;
|
||||
};
|
||||
that.stop = function() {
|
||||
that.animate({
|
||||
opacity: 0
|
||||
}, 250);
|
||||
return that;
|
||||
}
|
||||
return that;
|
||||
}
|
||||
5
source/Ox.UI/js/Core/Ox.Progressbar.js
Normal file
5
source/Ox.UI/js/Core/Ox.Progressbar.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/**
|
||||
Ox.Progressbar
|
||||
*/
|
||||
|
||||
222
source/Ox.UI/js/Core/Ox.Request.js
Normal file
222
source/Ox.UI/js/Core/Ox.Request.js
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Request = function(options) {
|
||||
|
||||
/***
|
||||
Ox.Request
|
||||
Basic request handler
|
||||
Options
|
||||
timeout
|
||||
Methods
|
||||
cancel() cancel request
|
||||
clearCache() clear cache
|
||||
options() get or set options
|
||||
requests() return number of active requests
|
||||
send() send request
|
||||
***/
|
||||
|
||||
var cache = {},
|
||||
//dfd = $.Deferred(),
|
||||
pending = {},
|
||||
requests = {},
|
||||
self = {
|
||||
options: $.extend({
|
||||
timeout: 60000,
|
||||
type: 'POST',
|
||||
url: '/api/'
|
||||
}, options)
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
cancel: function() {
|
||||
if (arguments.length == 0) {
|
||||
// cancel all requests
|
||||
requests = {};
|
||||
} else if (Ox.isFunction(arguments[0])) {
|
||||
// cancel with function
|
||||
Ox.forEach(requests, function(req, id) {
|
||||
if (arguments[0](req)) {
|
||||
delete requests[id];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// cancel by id
|
||||
delete requests[arguments[0]];
|
||||
}
|
||||
},
|
||||
|
||||
clearCache: function() {
|
||||
cache = {};
|
||||
},
|
||||
|
||||
options: function(options) {
|
||||
return Ox.getset(self.options, options, $.noop(), this);
|
||||
},
|
||||
|
||||
requests: function() {
|
||||
return Ox.len(requests);
|
||||
},
|
||||
|
||||
send: function(options) {
|
||||
|
||||
var options = $.extend({
|
||||
age: -1,
|
||||
callback: null,
|
||||
id: Ox.uid(),
|
||||
timeout: self.options.timeout,
|
||||
type: self.options.type,
|
||||
url: self.options.url
|
||||
}, options),
|
||||
req = JSON.stringify({
|
||||
url: options.url,
|
||||
data: options.data
|
||||
});
|
||||
|
||||
if (pending[options.id]) {
|
||||
setTimeout(function() {
|
||||
Ox.Request.send(options);
|
||||
}, 0);
|
||||
} else {
|
||||
requests[options.id] = {
|
||||
url: options.url,
|
||||
data: options.data
|
||||
};
|
||||
if (cache[req] && (options.age == -1 || options.age > Ox.getTime() - cache[req].time)) {
|
||||
setTimeout(function() {
|
||||
callback && callback(cache[req].data);
|
||||
}, 0);
|
||||
} else {
|
||||
pending[options.id] = true;
|
||||
$.ajax({
|
||||
data: options.data,
|
||||
dataType: 'json',
|
||||
error: error,
|
||||
success: success,
|
||||
timeout: options.timeout,
|
||||
type: options.type,
|
||||
url: options.url
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function callback(data) {
|
||||
delete requests[options.id];
|
||||
//Ox.len(requests) == 0 && $body.trigger('requestStop');
|
||||
options.callback && options.callback(data);
|
||||
}
|
||||
|
||||
function debug(request) {
|
||||
var $iframe = $('<iframe>')
|
||||
.css({ // fixme: should go into a class
|
||||
width: 768,
|
||||
height: 384
|
||||
}),
|
||||
$dialog = new Ox.Dialog({
|
||||
title: 'Application Error',
|
||||
buttons: [
|
||||
new Ox.Button({
|
||||
title: 'Close'
|
||||
})
|
||||
.bindEvent({
|
||||
click: function() {
|
||||
$dialog.close();
|
||||
}
|
||||
})
|
||||
],
|
||||
content: $iframe,
|
||||
width: 800,
|
||||
height: 400
|
||||
})
|
||||
.open(),
|
||||
iframe = $iframe[0].contentDocument || $iframe[0].contentWindow.document;
|
||||
iframe.open();
|
||||
iframe.write(request.responseText);
|
||||
iframe.close();
|
||||
}
|
||||
|
||||
function error(request, status, error) {
|
||||
var data;
|
||||
if (arguments.length == 1) {
|
||||
data = arguments[0]
|
||||
} else {
|
||||
try {
|
||||
data = JSON.parse(request.responseText);
|
||||
} catch (err) {
|
||||
try {
|
||||
data = {
|
||||
status: {
|
||||
code: request.status,
|
||||
text: request.statusText
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
data = {
|
||||
status: {
|
||||
code: '500',
|
||||
text: 'Unknown Error'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data.status.code < 500) {
|
||||
callback(data);
|
||||
} else {
|
||||
var $dialog = new Ox.Dialog({
|
||||
title: 'Application Error',
|
||||
buttons: [
|
||||
new Ox.Button({
|
||||
id: 'details',
|
||||
title: 'Details'
|
||||
})
|
||||
.bindEvent({
|
||||
click: function() {
|
||||
$dialog.close(function() {
|
||||
debug(request);
|
||||
});
|
||||
}
|
||||
}),
|
||||
new Ox.Button({
|
||||
id: 'close',
|
||||
title: 'Close'
|
||||
})
|
||||
.bindEvent({
|
||||
click: function() {
|
||||
$dialog.close();
|
||||
}
|
||||
})
|
||||
],
|
||||
content: 'Sorry, we have encountered an application error while handling your request. To help us find out what went wrong, you may want to report this error to an administrator. Otherwise, please try again later.',
|
||||
keys: {enter: 'close', escape: 'close'},
|
||||
width: 400,
|
||||
height: 200
|
||||
})
|
||||
.open();
|
||||
// fixme: change this to Send / Don't Send
|
||||
/*Ox.print({
|
||||
request: request,
|
||||
status: status,
|
||||
error: error
|
||||
});*/
|
||||
}
|
||||
pending[options.id] = false;
|
||||
}
|
||||
|
||||
function success(data) {
|
||||
pending[options.id] = false;
|
||||
cache[req] = {
|
||||
data: data,
|
||||
time: Ox.getTime()
|
||||
};
|
||||
callback(data);
|
||||
}
|
||||
|
||||
// return dfd.promise();
|
||||
|
||||
return options.id;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}();
|
||||
104
source/Ox.UI/js/Core/Ox.Theme.js
Normal file
104
source/Ox.UI/js/Core/Ox.Theme.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
// fixme: this should be Ox.Theme, and provide Ox.Theme.set(), Ox.Theme.load, etc.
|
||||
/**
|
||||
if name is given as argument, switch to this theme.
|
||||
return current theme otherwise.
|
||||
|
||||
Ox.theme()
|
||||
get theme
|
||||
Ox.theme('foo')
|
||||
set theme to 'foo'
|
||||
*/
|
||||
|
||||
Ox.Theme = function(theme) {
|
||||
|
||||
return theme ? setTheme(theme) : getTheme();
|
||||
|
||||
function getTheme() {
|
||||
var theme = '';
|
||||
Ox.forEach(Ox.UI.$body.attr('class').split(' '), function(className) {
|
||||
if (Ox.startsWith(className, 'OxTheme')) {
|
||||
theme = className.replace('OxTheme', '').toLowerCase();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
Ox.print('getTheme', theme)
|
||||
return theme;
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
var currentTheme = getTheme();
|
||||
if (theme != currentTheme) {
|
||||
Ox.UI.$body
|
||||
.addClass('OxTheme' + Ox.toTitleCase(theme))
|
||||
.removeClass('OxTheme' + Ox.toTitleCase(currentTheme));
|
||||
$('img').add('input[type=image]').each(function() {
|
||||
var $this = $(this);
|
||||
$this.attr({
|
||||
src: $this.attr('src').replace(
|
||||
'/Ox.UI/themes/' + currentTheme + '/',
|
||||
'/Ox.UI/themes/' + theme + '/'
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
return theme;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
Ox.UI.ready(function() {
|
||||
|
||||
Ox.theme = function() {
|
||||
var length = arguments.length,
|
||||
classes = Ox.UI.$body.attr('class').split(' '),
|
||||
arg, theme;
|
||||
Ox.forEach(classes, function(v) {
|
||||
if (Ox.startsWith(v, 'OxTheme')) {
|
||||
theme = v.replace('OxTheme', '').toLowerCase();
|
||||
if (length == 1) {
|
||||
Ox.UI.$body.removeClass(v);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (length == 1) {
|
||||
arg = arguments[0]
|
||||
Ox.UI.$body.addClass('OxTheme' + Ox.toTitleCase(arg));
|
||||
if (theme) {
|
||||
$('img').each(function() {
|
||||
var $this = $(this);
|
||||
if (!$this.attr('src')) return; // fixme: remove, should't be neccessary
|
||||
$this.attr({
|
||||
src: $this.attr('src').replace(
|
||||
'/ox.ui.' + theme + '/', '/ox.ui.' + arg + '/'
|
||||
)
|
||||
});
|
||||
});
|
||||
$('input[type=image]').each(function() {
|
||||
var $this = $(this);
|
||||
$this.attr({
|
||||
src: $this.attr('src').replace(
|
||||
'/ox.ui.' + theme + '/', '/ox.ui.' + arg + '/'
|
||||
)
|
||||
});
|
||||
});
|
||||
$('.OxLoadingIcon').each(function() {
|
||||
var $this = $(this);
|
||||
$this.attr({
|
||||
src: $this.attr('src').replace(
|
||||
'/ox.ui.' + theme + '/', '/ox.ui.' + arg + '/'
|
||||
)
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
return theme;
|
||||
};
|
||||
|
||||
Ox.theme(Ox.UI.DEFAULT_THEME);
|
||||
|
||||
|
||||
});
|
||||
*/
|
||||
5
source/Ox.UI/js/Core/Ox.URL.js
Normal file
5
source/Ox.UI/js/Core/Ox.URL.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/***
|
||||
Ox.URL
|
||||
***/
|
||||
|
||||
164
source/Ox.UI/js/Form/Ox.Button.js
Normal file
164
source/Ox.UI/js/Form/Ox.Button.js
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Button = function(options, self) {
|
||||
|
||||
/**
|
||||
methods:
|
||||
toggleDisabled enable/disable button
|
||||
toggleSelected select/unselect button
|
||||
toggleTitle if more than one title was provided,
|
||||
toggle to next title.
|
||||
events:
|
||||
click non-selectable button was clicked
|
||||
deselect selectable button was deselected
|
||||
select selectable button was selected
|
||||
*/
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('input', self)
|
||||
.defaults({
|
||||
disabled: false,
|
||||
group: false,
|
||||
id: '',
|
||||
overlap: 'none',
|
||||
selectable: false,
|
||||
selected: false,
|
||||
size: 'medium',
|
||||
// fixme: 'default' or ''?
|
||||
style: 'default', // can be default, checkbox, symbol, or tab
|
||||
title: '',
|
||||
tooltip: '',
|
||||
type: 'text',
|
||||
width: 'auto'
|
||||
})
|
||||
.options(options || {})
|
||||
.attr({
|
||||
disabled: self.options.disabled ? 'disabled' : '',
|
||||
type: self.options.type == 'text' ? 'button' : 'image'
|
||||
})
|
||||
.addClass('OxButton Ox' + Ox.toTitleCase(self.options.size) +
|
||||
(self.options.disabled ? ' OxDisabled': '') +
|
||||
(self.options.selected ? ' OxSelected': '') +
|
||||
(self.options.style != 'default' ? ' Ox' + Ox.toTitleCase(self.options.style) : '') +
|
||||
(self.options.overlap != 'none' ? ' OxOverlap' + Ox.toTitleCase(self.options.overlap) : ''))
|
||||
.css(self.options.width == 'auto' ? {} : {
|
||||
width: (self.options.width - 14) + 'px'
|
||||
})
|
||||
.mousedown(mousedown)
|
||||
.click(click);
|
||||
|
||||
$.extend(self, Ox.isArray(self.options.title) ? {
|
||||
selectedTitle: Ox.setPropertyOnce(self.options.title, 'selected'),
|
||||
titles: self.options.title
|
||||
} : {
|
||||
selectedTitle: 0,
|
||||
titles: [{
|
||||
id: '',
|
||||
title: self.options.title
|
||||
}]
|
||||
});
|
||||
|
||||
setTitle(self.titles[self.selectedTitle].title);
|
||||
|
||||
if (self.options.tooltip) {
|
||||
self.tooltips = Ox.isArray(self.options.tooltip) ? self.options.tooltip : [self.options.tooltip];
|
||||
self.$tooltip = new Ox.Tooltip({
|
||||
title: self.tooltips[self.selectedTitle]
|
||||
});
|
||||
that.mouseenter(mouseenter)
|
||||
.mouseleave(mouseleave);
|
||||
}
|
||||
|
||||
function click() {
|
||||
if (!self.options.disabled) {
|
||||
var data = self.titles[self.selectedTitle];
|
||||
if (!self.options.selectable) {
|
||||
that.triggerEvent('click', data);
|
||||
} else {
|
||||
//self.options.selected = !self.options.selected;
|
||||
//that.toggleClass('OxSelected');
|
||||
if (self.options.group) {
|
||||
that.triggerEvent('select', data);
|
||||
} else {
|
||||
that.toggleSelected();
|
||||
//that.triggerEvent('change', {selected: self.options.selected});
|
||||
}
|
||||
}
|
||||
if (self.titles.length == 2) {
|
||||
that.toggleTitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mousedown(e) {
|
||||
if (self.options.type == 'image' && $.browser.safari) {
|
||||
// keep image from being draggable
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function mouseenter(e) {
|
||||
self.$tooltip.show(e.clientX, e.clientY);
|
||||
}
|
||||
|
||||
function mouseleave() {
|
||||
self.$tooltip.hide();
|
||||
}
|
||||
|
||||
function setTitle(title) {
|
||||
self.title = title;
|
||||
if (self.options.type == 'image') {
|
||||
that.attr({
|
||||
src: Ox.UI.getImagePath(
|
||||
'symbol' + title[0].toUpperCase() + title.substr(1) + '.svg'
|
||||
)
|
||||
});
|
||||
} else {
|
||||
that.val(title);
|
||||
}
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'disabled') {
|
||||
that.attr({
|
||||
disabled: value ? 'disabled' : ''
|
||||
})
|
||||
.toggleClass('OxDisabled');
|
||||
} else if (key == 'selected') {
|
||||
if (value != that.hasClass('OxSelected')) { // fixme: neccessary?
|
||||
that.toggleClass('OxSelected');
|
||||
}
|
||||
that.triggerEvent('change');
|
||||
} else if (key == 'title') {
|
||||
setTitle(value);
|
||||
} else if (key == 'width') {
|
||||
that.$element.css({
|
||||
width: (value - 14) + 'px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
that.toggleDisabled = function() {
|
||||
that.options({
|
||||
enabled: !self.options.disabled
|
||||
});
|
||||
//self.options.disabled = !self.options.disabled;
|
||||
}
|
||||
|
||||
that.toggleSelected = function() {
|
||||
that.options({
|
||||
selected: !self.options.selected
|
||||
});
|
||||
//self.options.selected = !self.options.selected;
|
||||
}
|
||||
|
||||
that.toggleTitle = function() {
|
||||
self.selectedTitle = 1 - self.selectedTitle;
|
||||
setTitle(self.titles[self.selectedTitle].title);
|
||||
self.$tooltip && self.$tooltip.options({
|
||||
title: self.tooltips[self.selectedTitle]
|
||||
});
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
75
source/Ox.UI/js/Form/Ox.ButtonGroup.js
Normal file
75
source/Ox.UI/js/Form/Ox.ButtonGroup.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.ButtonGroup = function(options, self) {
|
||||
|
||||
/**
|
||||
options
|
||||
buttons array of buttons
|
||||
max integer, maximum number of selected buttons, 0 for all
|
||||
min integer, minimum number of selected buttons, 0 for none
|
||||
selectable if true, buttons are selectable
|
||||
type string, 'image' or 'text'
|
||||
methods:
|
||||
events:
|
||||
change {id, value} selection within a group changed
|
||||
*/
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
buttons: [],
|
||||
max: 1,
|
||||
min: 1,
|
||||
selectable: false,
|
||||
size: 'medium',
|
||||
style: '',
|
||||
type: 'text',
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxButtonGroup');
|
||||
|
||||
if (self.options.selectable) {
|
||||
self.optionGroup = new Ox.OptionGroup(
|
||||
self.options.buttons,
|
||||
self.options.min,
|
||||
self.options.max,
|
||||
'selected'
|
||||
);
|
||||
self.options.buttons = self.optionGroup.init();
|
||||
}
|
||||
|
||||
self.$buttons = [];
|
||||
self.options.buttons.forEach(function(button, position) {
|
||||
var id = self.options.id + Ox.toTitleCase(button.id)
|
||||
self.$buttons[position] = Ox.Button({
|
||||
disabled: button.disabled,
|
||||
group: true,
|
||||
id: id,
|
||||
selectable: self.options.selectable,
|
||||
selected: button.selected,
|
||||
size: self.options.size,
|
||||
style: self.options.style,
|
||||
title: button.title,
|
||||
type: self.options.type
|
||||
})
|
||||
.bindEvent('select', function() {
|
||||
selectButton(position);
|
||||
})
|
||||
.appendTo(that);
|
||||
});
|
||||
|
||||
function selectButton(pos) {
|
||||
var toggled = self.optionGroup.toggle(pos);
|
||||
if (toggled.length) {
|
||||
toggled.forEach(function(pos, i) {
|
||||
self.$buttons[pos].toggleSelected();
|
||||
});
|
||||
that.triggerEvent('change', {
|
||||
selected: $.map(self.optionGroup.selected(), function(v, i) {
|
||||
return self.options.buttons[v].id;
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return that;
|
||||
};
|
||||
105
source/Ox.UI/js/Form/Ox.Checkbox.js
Normal file
105
source/Ox.UI/js/Form/Ox.Checkbox.js
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Checkbox = function(options, self) {
|
||||
|
||||
/**
|
||||
options
|
||||
disabled boolean, if true, checkbox is disabled
|
||||
id element id
|
||||
group boolean, if true, checkbox is part of a group
|
||||
checked boolean, if true, checkbox is checked
|
||||
title string, text on label
|
||||
width integer, width in px
|
||||
methods:
|
||||
toggleChecked function()
|
||||
toggles checked property
|
||||
returns that
|
||||
events:
|
||||
change triggered when checked property changes
|
||||
passes {checked, id, title}
|
||||
*/
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
disabled: false,
|
||||
id: '',
|
||||
group: false,
|
||||
checked: false,
|
||||
overlap: 'none',
|
||||
title: '',
|
||||
width: 'auto'
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxCheckbox' +
|
||||
(self.options.overlap == 'none' ? '' : ' OxOverlap' +
|
||||
Ox.toTitleCase(self.options.overlap))
|
||||
)
|
||||
.attr(self.options.disabled ? {
|
||||
disabled: 'disabled'
|
||||
} : {});
|
||||
|
||||
if (self.options.title) {
|
||||
self.options.width != 'auto' && that.css({
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
self.$title = new Ox.Label({
|
||||
disabled: self.options.disabled,
|
||||
id: self.options.id + 'Label',
|
||||
overlap: 'left',
|
||||
title: self.options.title,
|
||||
width: self.options.width - 16
|
||||
})
|
||||
.css({
|
||||
float: 'right'
|
||||
})
|
||||
.click(clickTitle)
|
||||
.appendTo(that);
|
||||
}
|
||||
|
||||
self.$button = new Ox.Button({
|
||||
disabled: self.options.disabled,
|
||||
id: self.options.id + 'Button',
|
||||
title: [
|
||||
{id: 'none', title: 'none', selected: !self.options.checked},
|
||||
{id: 'check', title: 'check', selected: self.options.checked}
|
||||
],
|
||||
type: 'image'
|
||||
})
|
||||
.addClass('OxCheckbox')
|
||||
.click(clickButton)
|
||||
.appendTo(that);
|
||||
|
||||
function clickButton() {
|
||||
self.options.checked = !self.options.checked;
|
||||
// click will have toggled the button,
|
||||
// if it is part of a group, we have to revert that
|
||||
self.options.group && that.toggleChecked();
|
||||
that.triggerEvent('change', {
|
||||
checked: self.options.checked,
|
||||
id: self.options.id,
|
||||
title: self.options.title
|
||||
});
|
||||
}
|
||||
|
||||
function clickTitle() {
|
||||
!self.options.disabled && self.$button.trigger('click');
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'checked') {
|
||||
that.toggleChecked();
|
||||
}
|
||||
};
|
||||
|
||||
that.checked = function() {
|
||||
return self.options.checked;
|
||||
}
|
||||
|
||||
that.toggleChecked = function() {
|
||||
self.$button.toggleTitle();
|
||||
return that;
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
71
source/Ox.UI/js/Form/Ox.CheckboxGroup.js
Normal file
71
source/Ox.UI/js/Form/Ox.CheckboxGroup.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.CheckboxGroup = function(options, self) {
|
||||
|
||||
/**
|
||||
options
|
||||
checkboxes [] array of checkboxes
|
||||
max 1 integer
|
||||
min 1 integer
|
||||
width integer, width in px
|
||||
events:
|
||||
change triggered when checked property changes
|
||||
passes {checked, id, title}
|
||||
*/
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
checkboxes: [],
|
||||
max: 1,
|
||||
min: 1,
|
||||
width: 256
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxCheckboxGroup');
|
||||
|
||||
self.optionGroup = new Ox.OptionGroup(
|
||||
self.options.checkboxes,
|
||||
self.options.min,
|
||||
self.options.max);
|
||||
self.options.checkboxes = self.optionGroup.init();
|
||||
|
||||
$.extend(self, {
|
||||
$checkboxes: [],
|
||||
checkboxWidth: $.map(Ox.divideInt(
|
||||
self.options.width + (self.options.checkboxes.length - 1) * 6,
|
||||
self.options.checkboxes.length
|
||||
), function(v, i) {
|
||||
return v + (i < self.options.checkboxes.length - 1 ? 10 : 0);
|
||||
})
|
||||
});
|
||||
self.options.checkboxes.forEach(function(checkbox, position) {
|
||||
var id = self.options.id + Ox.toTitleCase(checkbox.id)
|
||||
self.$checkboxes[position] = new Ox.Checkbox($.extend(checkbox, {
|
||||
group: true,
|
||||
id: id,
|
||||
width: self.checkboxWidth[position]
|
||||
}))
|
||||
.bindEvent('change', function() {
|
||||
change(position);
|
||||
})
|
||||
.appendTo(that);
|
||||
});
|
||||
|
||||
function change(pos) {
|
||||
var toggled = self.optionGroup.toggle(pos);
|
||||
//Ox.print('change', pos, 'toggled', toggled)
|
||||
if (toggled.length) {
|
||||
toggled.forEach(function(pos, i) {
|
||||
self.$checkboxes[pos].toggleChecked();
|
||||
});
|
||||
that.triggerEvent('change', {
|
||||
checked: $.map(self.optionGroup.checked(), function(v, i) {
|
||||
return self.options.checkboxes[v].id;
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
72
source/Ox.UI/js/Form/Ox.ColorInput.js
Normal file
72
source/Ox.UI/js/Form/Ox.ColorInput.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.ColorInput = function(options, self) {
|
||||
|
||||
var self = $.extend(self || {}, {
|
||||
options: $.extend({
|
||||
id: '',
|
||||
value: '0, 0, 0'
|
||||
}, options)
|
||||
}),
|
||||
that;
|
||||
|
||||
self.values = self.options.value.split(', ');
|
||||
self.$inputs = [];
|
||||
['red', 'green', 'blue'].forEach(function(v, i) {
|
||||
self.$inputs[i] = new Ox.Input({
|
||||
id: v,
|
||||
max: 255,
|
||||
type: 'integer',
|
||||
value: self.values[i],
|
||||
width: 36
|
||||
})
|
||||
.bindEvent('autovalidate', change);
|
||||
});
|
||||
self.$inputs[3] = new Ox.Label({
|
||||
id: 'color',
|
||||
width: 36
|
||||
})
|
||||
.css({
|
||||
background: 'rgb(' + self.options.value + ')'
|
||||
});
|
||||
self.$inputs[4] = new Ox.ColorPicker({
|
||||
id: 'picker'
|
||||
})
|
||||
.bindEvent('change', function(event, data) {
|
||||
//Ox.print('change function called');
|
||||
self.options.value = data.value;
|
||||
self.values = data.value.split(', ');
|
||||
Ox.range(3).forEach(function(i) {
|
||||
self.$inputs[i].options({
|
||||
value: self.values[i]
|
||||
});
|
||||
});
|
||||
})
|
||||
.options({
|
||||
width: 16 // this is just a hack to make the InputGroup layout work
|
||||
});
|
||||
|
||||
that = new Ox.InputGroup({
|
||||
id: self.options.id,
|
||||
inputs: self.$inputs,
|
||||
separators: [
|
||||
{title: ',', width: 8},
|
||||
{title: ',', width: 8},
|
||||
{title: '', width: 8},
|
||||
{title: '', width: 8}
|
||||
],
|
||||
value: self.options.value // fixme: it'd be nicer if this would be taken care of by passing self
|
||||
}, self)
|
||||
.bindEvent('change', change);
|
||||
|
||||
function change() {
|
||||
self.options.value = $.map(self.$inputs, function(v, i) {
|
||||
return v.options('value');
|
||||
}).join(', ');
|
||||
self.$inputs[3].css({
|
||||
background: 'rgb(' + self.options.value + ')'
|
||||
});
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
92
source/Ox.UI/js/Form/Ox.ColorPicker.js
Normal file
92
source/Ox.UI/js/Form/Ox.ColorPicker.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.ColorPicker = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
id: '',
|
||||
value: '0, 0, 0'
|
||||
})
|
||||
.options(options || {});
|
||||
|
||||
//Ox.print(self)
|
||||
self.$ranges = [];
|
||||
self.rgb = ['red', 'green', 'blue'];
|
||||
self.values = self.options.value.split(', ');
|
||||
|
||||
Ox.range(3).forEach(function(i) {
|
||||
self.$ranges[i] = new Ox.Range({
|
||||
arrows: true,
|
||||
id: self.options.id + Ox.toTitleCase(self.rgb[i]),
|
||||
max: 255,
|
||||
size: 328, // 256 + 16 + 40 + 16
|
||||
thumbSize: 40,
|
||||
thumbValue: true,
|
||||
trackColors: getColors(i),
|
||||
value: self.values[i]
|
||||
})
|
||||
.css({
|
||||
position: 'absolute',
|
||||
top: (i * 15) + 'px'
|
||||
})
|
||||
.bindEvent('change', function(event, data) {
|
||||
change(i, data.value);
|
||||
})
|
||||
.appendTo(that);
|
||||
// fixme: make self.$ranges[i].children() work
|
||||
if (i == 0) {
|
||||
self.$ranges[i].$element.children('input.OxOverlapRight').css({
|
||||
MozBorderRadius: 0,
|
||||
WebkitBorderRadius: 0
|
||||
});
|
||||
self.$ranges[i].$element.children('input.OxOverlapLeft').css({
|
||||
MozBorderRadius: '0 8px 0 0',
|
||||
WebkitBorderRadius: '0 8px 0 0'
|
||||
});
|
||||
} else {
|
||||
self.$ranges[i].$element.children('input').css({
|
||||
MozBorderRadius: 0,
|
||||
WebkitBorderRadius: 0
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
that = new Ox.Picker({
|
||||
element: that,
|
||||
elementHeight: 46,
|
||||
elementWidth: 328,
|
||||
id: self.options.id
|
||||
});
|
||||
|
||||
function change(index, value) {
|
||||
self.values[index] = value;
|
||||
self.options.value = self.values.join(', ');
|
||||
that.$label.css({
|
||||
background: 'rgb(' + self.options.value + ')'
|
||||
});
|
||||
Ox.range(3).forEach(function(i) {
|
||||
if (i != index) {
|
||||
self.$ranges[i].options({
|
||||
trackColors: getColors(i)
|
||||
});
|
||||
}
|
||||
});
|
||||
that.triggerEvent('change', {
|
||||
value: self.options.value
|
||||
});
|
||||
}
|
||||
|
||||
function getColors(index) {
|
||||
return [
|
||||
'rgb(' + $.map(Ox.range(3), function(v) {
|
||||
return v == index ? 0 : self.values[v];
|
||||
}).join(', ') + ')',
|
||||
'rgb(' + $.map(Ox.range(3), function(v) {
|
||||
return v == index ? 255 : self.values[v];
|
||||
}).join(', ') + ')'
|
||||
]
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
218
source/Ox.UI/js/Form/Ox.DateInput.js
Normal file
218
source/Ox.UI/js/Form/Ox.DateInput.js
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.DateInput = function(options, self) {
|
||||
|
||||
/**
|
||||
options:
|
||||
format: 'short'
|
||||
value: date value
|
||||
weekday: false
|
||||
width: {
|
||||
day: 32,
|
||||
month: options.format == 'long' ? 80 : (options.format == 'medium' ? 40 : 32),
|
||||
weekday: options.format == 'long' ? 80 : 40,
|
||||
year: 48
|
||||
}
|
||||
*/
|
||||
|
||||
var self = $.extend(self || {}, {
|
||||
options: $.extend({
|
||||
format: 'short',
|
||||
value: Ox.formatDate(new Date(), '%F'),
|
||||
weekday: false,
|
||||
width: {
|
||||
day: 32,
|
||||
month: options.format == 'long' ? 80 : (options.format == 'medium' ? 40 : 32),
|
||||
weekday: options.format == 'long' ? 80 : 40,
|
||||
year: 48
|
||||
}
|
||||
}, options)
|
||||
}),
|
||||
that;
|
||||
|
||||
$.extend(self, {
|
||||
date: new Date(self.options.value.replace(/-/g, '/')),
|
||||
formats: {
|
||||
day: '%d',
|
||||
month: self.options.format == 'short' ? '%m' :
|
||||
(self.options.format == 'medium' ? '%b' : '%B'),
|
||||
weekday: self.options.format == 'long' ? '%A' : '%a',
|
||||
year: '%Y'
|
||||
},
|
||||
months: self.options.format == 'long' ? Ox.MONTHS : $.map(Ox.MONTHS, function(v, i) {
|
||||
return v.substr(0, 3);
|
||||
}),
|
||||
weekdays: self.options.format == 'long' ? Ox.WEEKDAYS : $.map(Ox.WEEKDAYS, function(v, i) {
|
||||
return v.substr(0, 3);
|
||||
})
|
||||
});
|
||||
|
||||
self.$input = $.extend(self.options.weekday ? {
|
||||
weekday: new Ox.Input({
|
||||
autocomplete: self.weekdays,
|
||||
autocompleteReplace: true,
|
||||
autocompleteReplaceCorrect: true,
|
||||
id: 'weekday',
|
||||
value: Ox.formatDate(self.date, self.formats.weekday),
|
||||
width: self.options.width.weekday
|
||||
})
|
||||
.bindEvent('autocomplete', changeWeekday),
|
||||
} : {}, {
|
||||
day: new Ox.Input({
|
||||
autocomplete: $.map(Ox.range(1, Ox.getDaysInMonth(
|
||||
parseInt(Ox.formatDate(self.date, '%Y'), 10),
|
||||
parseInt(Ox.formatDate(self.date, '%m'), 10)
|
||||
) + 1), function(v, i) {
|
||||
return self.options.format == 'short' ? Ox.pad(v, 2) : v.toString();
|
||||
}),
|
||||
autocompleteReplace: true,
|
||||
autocompleteReplaceCorrect: true,
|
||||
id: 'day',
|
||||
value: Ox.formatDate(self.date, self.formats.day),
|
||||
textAlign: 'right',
|
||||
width: self.options.width.day
|
||||
})
|
||||
.bindEvent('autocomplete', changeDay),
|
||||
month: new Ox.Input({
|
||||
autocomplete: self.options.format == 'short' ? $.map(Ox.range(1, 13), function(v, i) {
|
||||
return Ox.pad(v, 2);
|
||||
}) : self.months,
|
||||
autocompleteReplace: true,
|
||||
autocompleteReplaceCorrect: true,
|
||||
id: 'month',
|
||||
value: Ox.formatDate(self.date, self.formats.month),
|
||||
textAlign: self.options.format == 'short' ? 'right' : 'left',
|
||||
width: self.options.width.month
|
||||
})
|
||||
.bindEvent('autocomplete', changeMonthOrYear),
|
||||
year: new Ox.Input({
|
||||
autocomplete: $.map($.merge(Ox.range(1900, 3000), Ox.range(1000, 1900)), function(v, i) {
|
||||
return v.toString();
|
||||
}),
|
||||
autocompleteReplace: true,
|
||||
autocompleteReplaceCorrect: true,
|
||||
id: 'year',
|
||||
value: Ox.formatDate(self.date, self.formats.year),
|
||||
textAlign: 'right',
|
||||
width: self.options.width.year
|
||||
})
|
||||
.bindEvent('autocomplete', changeMonthOrYear)
|
||||
});
|
||||
|
||||
that = new Ox.InputGroup($.extend(self.options, {
|
||||
id: self.options.id,
|
||||
inputs: $.merge(self.options.weekday ? [
|
||||
self.$input.weekday
|
||||
] : [], self.options.format == 'short' ? [
|
||||
self.$input.year, self.$input.month, self.$input.day
|
||||
] : [
|
||||
self.$input.month, self.$input.day, self.$input.year
|
||||
]),
|
||||
separators: $.merge(self.options.weekday ? [
|
||||
{title: self.options.format == 'short' ? '' : ',', width: 8},
|
||||
] : [], self.options.format == 'short' ? [
|
||||
{title: '-', width: 8}, {title: '-', width: 8}
|
||||
] : [
|
||||
{title: '', width: 8}, {title: ',', width: 8}
|
||||
]),
|
||||
width: 0
|
||||
}), self);
|
||||
|
||||
//Ox.print('SELF', self)
|
||||
|
||||
function changeDay() {
|
||||
self.options.weekday && self.$input.weekday.options({
|
||||
value: Ox.formatDate(new Date([
|
||||
self.$input.month.options('value'),
|
||||
self.$input.day.options('value'),
|
||||
self.$input.year.options('value')
|
||||
].join(' ')), self.formats.weekday)
|
||||
});
|
||||
setValue();
|
||||
}
|
||||
|
||||
function changeMonthOrYear() {
|
||||
var day = self.$input.day.options('value'),
|
||||
month = self.$input.month.options('value'),
|
||||
year = self.$input.year.options('value'),
|
||||
days = Ox.getDaysInMonth(year, self.options.format == 'short' ? parseInt(month, 10) : month);
|
||||
day = day <= days ? day : days;
|
||||
//Ox.print(year, month, 'day days', day, days)
|
||||
self.options.weekday && self.$input.weekday.options({
|
||||
value: Ox.formatDate(new Date([month, day, year].join(' ')), self.formats.weekday)
|
||||
});
|
||||
self.$input.day.options({
|
||||
autocomplete: $.map(Ox.range(1, days + 1), function(v, i) {
|
||||
return self.options.format == 'short' ? Ox.pad(v, 2) : v.toString();
|
||||
}),
|
||||
value: self.options.format == 'short' ? Ox.pad(day, 2) : day.toString()
|
||||
});
|
||||
setValue();
|
||||
}
|
||||
|
||||
function changeWeekday() {
|
||||
var date = getDateInWeek(
|
||||
self.$input.weekday.options('value'),
|
||||
self.$input.month.options('value'),
|
||||
self.$input.day.options('value'),
|
||||
self.$input.year.options('value')
|
||||
);
|
||||
self.$input.month.options({value: date.month});
|
||||
self.$input.day.options({
|
||||
autocomplete: $.map(Ox.range(1, Ox.getDaysInMonth(date.year, date.month) + 1), function(v, i) {
|
||||
return self.options.format == 'short' ? Ox.pad(v, 2) : v.toString();
|
||||
}),
|
||||
value: date.day
|
||||
});
|
||||
self.$input.year.options({value: date.year});
|
||||
setValue();
|
||||
}
|
||||
|
||||
function getDateInWeek(weekday, month, day, year) {
|
||||
//Ox.print([month, day, year].join(' '))
|
||||
var date = new Date([month, day, year].join(' '));
|
||||
date = Ox.getDateInWeek(date, weekday);
|
||||
return {
|
||||
day: Ox.formatDate(date, self.formats.day),
|
||||
month: Ox.formatDate(date, self.formats.month),
|
||||
year: Ox.formatDate(date, self.formats.year)
|
||||
};
|
||||
}
|
||||
|
||||
function setValue() {
|
||||
self.options.value = Ox.formatDate(new Date(self.options.format == 'short' ? [
|
||||
self.$input.year.options('value'),
|
||||
self.$input.month.options('value'),
|
||||
self.$input.day.options('value')
|
||||
].join('/') : [
|
||||
self.$input.month.options('value'),
|
||||
self.$input.day.options('value'),
|
||||
self.$input.year.options('value')
|
||||
].join(' ')), '%F');
|
||||
}
|
||||
|
||||
/*
|
||||
function normalize() {
|
||||
var year = that.getInputById('year').options('value'),
|
||||
month = that.getInputById('month').options('value'),
|
||||
day = that.getInputById('day').options('value')
|
||||
return {
|
||||
year: year,
|
||||
month: self.options.format == 'short' ? month :
|
||||
Ox.pad((format == 'medium' ? Ox.WEEKDAYS.map(function(v, i) {
|
||||
return v.substr(0, 3);
|
||||
}) : Ox.WEEKDAYS).indexOf(month), 2),
|
||||
day: Ox.pad(day, 2)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
that.serialize = function() {
|
||||
var normal = normalize();
|
||||
return [normal.year, normal.month, normal.day].join('-');
|
||||
}
|
||||
*/
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
49
source/Ox.UI/js/Form/Ox.DateTimeInput.js
Normal file
49
source/Ox.UI/js/Form/Ox.DateTimeInput.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.DateTimeInput = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
ampm: false,
|
||||
format: 'short',
|
||||
seconds: false,
|
||||
value: Ox.formatDate(new Date(), '%F %T'),
|
||||
weekday: false
|
||||
})
|
||||
.options(options || {});
|
||||
|
||||
self.values = self.options.value.split(' ');
|
||||
//Ox.print(self.values)
|
||||
|
||||
that = new Ox.InputGroup({
|
||||
inputs: [
|
||||
new Ox.DateInput({
|
||||
format: self.options.format,
|
||||
id: 'date',
|
||||
value: self.values[0],
|
||||
weekday: self.options.weekday
|
||||
}),
|
||||
new Ox.TimeInput({
|
||||
ampm: self.options.ampm,
|
||||
id: 'time',
|
||||
value: self.values[1],
|
||||
seconds: self.options.seconds
|
||||
})
|
||||
],
|
||||
separators: [
|
||||
{title: '', width: 8}
|
||||
],
|
||||
value: self.options.value
|
||||
})
|
||||
.bindEvent('change', setValue);
|
||||
|
||||
function setValue() {
|
||||
self.options.value = [
|
||||
self.options('inputs')[0].options('value'),
|
||||
self.options('inputs')[1].options('value')
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
404
source/Ox.UI/js/Form/Ox.Filter.js
Normal file
404
source/Ox.UI/js/Form/Ox.Filter.js
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Filter = function(options, self) {
|
||||
|
||||
/***
|
||||
Options:
|
||||
Methods:
|
||||
Events:
|
||||
***/
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
findKeys: [],
|
||||
query: {
|
||||
conditions: [],
|
||||
operator: '&'
|
||||
},
|
||||
sortKeys: [],
|
||||
viewKeys: []
|
||||
})
|
||||
.options(options || {});
|
||||
|
||||
Ox.print('Ox.Filter self.options', self.options)
|
||||
|
||||
$.extend(self, {
|
||||
conditionOperators: {
|
||||
date: [
|
||||
{id: '', title: 'is'},
|
||||
{id: '!', title: 'is not'},
|
||||
{id: '<', title: 'is before'},
|
||||
{id: '>', title: 'is after'},
|
||||
{id: '>&<', title: 'is between'},
|
||||
{id: '<|>', title: 'is not between'}
|
||||
],
|
||||
list: [
|
||||
{id: '', title: 'is'},
|
||||
{id: '!', title: 'is not'}
|
||||
],
|
||||
number: [
|
||||
{id: '', title: 'is'},
|
||||
{id: '!', title: 'is not'},
|
||||
{id: '<', title: 'is less than'},
|
||||
{id: '>', title: 'is greater than'},
|
||||
{id: '>&<', title: 'is between'},
|
||||
{id: '<|>', title: 'is not between'}
|
||||
],
|
||||
string: [
|
||||
{id: '=', title: 'is'},
|
||||
{id: '!=', title: 'is not'},
|
||||
{id: '^', title: 'begins with'},
|
||||
{id: '$', title: 'ends with'},
|
||||
{id: '', title: 'contains'},
|
||||
{id: '!', title: 'does not contain'}
|
||||
],
|
||||
text: [
|
||||
{id: '', title: 'contains'},
|
||||
{id: '!', title: 'does not contain'}
|
||||
]
|
||||
},
|
||||
operators: [
|
||||
{id: '&', title: 'all'},
|
||||
{id: '|', title: 'any'}
|
||||
]
|
||||
});
|
||||
|
||||
if (!self.options.query.conditions.length) {
|
||||
self.options.query.conditions = [{
|
||||
key: self.options.findKeys[0].id,
|
||||
value: '',
|
||||
operator: self.conditionOperators[
|
||||
getConditionType(self.options.findKeys[0].type)
|
||||
][0].id
|
||||
}];
|
||||
}
|
||||
|
||||
self.$operator = new Ox.FormElementGroup({
|
||||
elements: [
|
||||
new Ox.Label({
|
||||
title: 'Match',
|
||||
overlap: 'right',
|
||||
width: 48
|
||||
}),
|
||||
new Ox.FormElementGroup({
|
||||
elements: [
|
||||
new Ox.Select({
|
||||
items: self.operators,
|
||||
width: 48
|
||||
})
|
||||
.bindEvent({
|
||||
change: changeOperator
|
||||
}),
|
||||
new Ox.Label({
|
||||
overlap: 'left',
|
||||
title: 'of the following conditions',
|
||||
width: 160
|
||||
})
|
||||
],
|
||||
float: 'right',
|
||||
width: 208
|
||||
})
|
||||
],
|
||||
float: 'left',
|
||||
});
|
||||
|
||||
self.$buttons = [];
|
||||
self.$conditions = $.map(self.options.query.conditions, function(condition, i) {
|
||||
return constructCondition(condition, i);
|
||||
});
|
||||
|
||||
self.$limit = new Ox.InputGroup({
|
||||
inputs: [
|
||||
new Ox.Checkbox({
|
||||
width: 16
|
||||
}),
|
||||
new Ox.FormElementGroup({
|
||||
elements: [
|
||||
new Ox.Input({
|
||||
width: 56
|
||||
}),
|
||||
new Ox.Select({
|
||||
items: [
|
||||
{id: 'items', title: 'items'},
|
||||
{},
|
||||
{id: 'hours', title: 'hours'},
|
||||
{id: 'days', title: 'days'},
|
||||
{},
|
||||
{id: 'GB', title: 'GB'}
|
||||
],
|
||||
overlap: 'left',
|
||||
width: 64
|
||||
})
|
||||
],
|
||||
float: 'right',
|
||||
width: 120
|
||||
}),
|
||||
new Ox.Select({
|
||||
items: self.options.sortKeys,
|
||||
width: 128
|
||||
}),
|
||||
new Ox.FormElementGroup({
|
||||
elements: [
|
||||
new Ox.Select({
|
||||
items: [
|
||||
{id: 'ascending', title: 'ascending'},
|
||||
{id: 'descending', title: 'descending'}
|
||||
],
|
||||
width: 96
|
||||
}),
|
||||
new Ox.Label({
|
||||
overlap: 'left',
|
||||
title: 'order',
|
||||
width: 72
|
||||
})
|
||||
],
|
||||
float: 'right',
|
||||
width: 168
|
||||
})
|
||||
],
|
||||
separators: [
|
||||
{title: 'Limit to', width: 56},
|
||||
{title: 'sorted by', width: 64},
|
||||
{title: 'in', width: 32}
|
||||
]
|
||||
});
|
||||
|
||||
self.$view = new Ox.InputGroup({
|
||||
inputs: [
|
||||
new Ox.Checkbox({
|
||||
width: 16
|
||||
}),
|
||||
new Ox.Select({
|
||||
items: self.options.viewKeys,
|
||||
width: 128
|
||||
})
|
||||
],
|
||||
separators: [
|
||||
{title: 'By default, view', width: 112}
|
||||
]
|
||||
});
|
||||
|
||||
self.$save = new Ox.InputGroup({
|
||||
inputs: [
|
||||
new Ox.Checkbox({
|
||||
width: 16
|
||||
}),
|
||||
new Ox.Input({
|
||||
id: 'list',
|
||||
width: 128
|
||||
})
|
||||
],
|
||||
separators: [
|
||||
{title: 'Save as Smart List', width: 112}
|
||||
]
|
||||
});
|
||||
|
||||
self.$items = $.merge($.merge([self.$operator], self.$conditions), [self.$limit, self.$view, self.$save]);
|
||||
|
||||
self.$form = new Ox.Form({
|
||||
items: self.$items
|
||||
});
|
||||
that.$element = self.$form.$element;
|
||||
|
||||
function addCondition(pos) {
|
||||
var key = self.options.findKeys[0];
|
||||
self.options.query.conditions.splice(pos, 0, {
|
||||
key: key.id,
|
||||
value: '',
|
||||
operator: self.conditionOperators[key.type][0].id
|
||||
});
|
||||
self.$conditions.splice(pos, 0, constructCondition({}, pos));
|
||||
updateConditions();
|
||||
self.$form.addItem(pos + 1, self.$conditions[pos]);
|
||||
}
|
||||
|
||||
function addGroup(pos) {
|
||||
self.$form.addItem(pos + 1, constructGroup(pos))
|
||||
}
|
||||
|
||||
function changeConditionKey(pos, key) {
|
||||
Ox.print('changeConditionKey', pos, key);
|
||||
var oldOperator = self.options.query.conditions[pos].operator,
|
||||
oldType = Ox.getObjectById(
|
||||
self.options.findKeys, self.options.query.conditions[pos].key
|
||||
).type,
|
||||
newType = Ox.getObjectById(
|
||||
self.options.findKeys, key
|
||||
).type,
|
||||
oldConditionType = getConditionType(oldType),
|
||||
newConditionType = getConditionType(newType);
|
||||
changeConditionType = oldConditionType != newConditionType;
|
||||
Ox.print('old new', oldConditionType, newConditionType)
|
||||
self.options.query.conditions[pos].key = key;
|
||||
if (changeConditionType) {
|
||||
self.$conditions[pos].replaceElement(1, constructConditionOperator(pos, oldOperator));
|
||||
}
|
||||
}
|
||||
|
||||
function changeConditionOperator(pos, operator) {
|
||||
self.options.query.conditions[pos].operator = operator;
|
||||
}
|
||||
|
||||
function changeOperator(event, data) {
|
||||
self.options.query.operator = data.selected[0].id;
|
||||
}
|
||||
|
||||
function constructCondition(condition, pos) {
|
||||
var $condition;
|
||||
return $condition = new Ox.FormElementGroup({
|
||||
elements: [
|
||||
new Ox.Select({
|
||||
items: $.map(self.options.findKeys, function(key) {
|
||||
return {
|
||||
id: key.id,
|
||||
title: key.title
|
||||
};
|
||||
}),
|
||||
//items: $.extend({}, self.options.findKeys), // fixme: Ox.Menu messes with keys
|
||||
overlap: 'right',
|
||||
width: 128
|
||||
})
|
||||
.bindEvent({
|
||||
change: function(event, data) {
|
||||
Ox.print('event', event)
|
||||
changeConditionKey($condition.data('position'), data.selected[0].id);
|
||||
}
|
||||
}),
|
||||
constructConditionOperator(pos),
|
||||
new Ox.Input({
|
||||
width: 256
|
||||
}),
|
||||
new Ox.Button({
|
||||
disabled: self.options.query.conditions.length == 1,
|
||||
id: 'remove',
|
||||
title: 'remove',
|
||||
type: 'image'
|
||||
})
|
||||
.css({margin: '0 4px 0 8px'})
|
||||
.bindEvent({
|
||||
click: function() {
|
||||
removeCondition($condition.data('position'));
|
||||
}
|
||||
}),
|
||||
new Ox.Button({
|
||||
id: 'add',
|
||||
title: 'add',
|
||||
type: 'image'
|
||||
})
|
||||
.css({margin: '0 4px 0 4px'})
|
||||
.bindEvent({
|
||||
click: function() {
|
||||
Ox.print('add', $(this).parent().parent().data('position'))
|
||||
addCondition($condition.data('position') + 1)
|
||||
}
|
||||
}),
|
||||
new Ox.Button({
|
||||
id: 'addgroup',
|
||||
title: 'more',
|
||||
type: 'image'
|
||||
})
|
||||
.css({margin: '0 0 0 4px'})
|
||||
.bindEvent({
|
||||
click: function() {
|
||||
addGroup($condition.data('position') + 1)
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
.data({position: pos});
|
||||
}
|
||||
|
||||
function constructConditionOperator(pos, selected) {
|
||||
return new Ox.Select({
|
||||
items: $.map(self.conditionOperators[getConditionType(
|
||||
Ox.getObjectById(
|
||||
self.options.findKeys,
|
||||
self.options.query.conditions[pos].key
|
||||
).type
|
||||
)], function(operator) {
|
||||
return {
|
||||
checked: operator.id == selected, // fixme: should be "selected", not "checked"
|
||||
id: operator.operator,
|
||||
title: operator.title
|
||||
};
|
||||
}),
|
||||
overlap: 'right',
|
||||
width: 128
|
||||
})
|
||||
.bindEvent({
|
||||
change: function(event, data) {
|
||||
changeConditionOperator(/*$condition.data('position')*/ pos, data.selected[0].id)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function constructGroup() {
|
||||
// fixme: duplicated
|
||||
return new Ox.FormElementGroup({
|
||||
elements: [
|
||||
new Ox.Label({
|
||||
title: self.options.operator == '&' ? 'and' : 'or',
|
||||
overlap: 'right',
|
||||
width: 48
|
||||
}),
|
||||
new Ox.FormElementGroup({
|
||||
elements: [
|
||||
new Ox.Select({
|
||||
items: $.map(self.operators, function(operator) {
|
||||
Ox.print('!!!!', {
|
||||
checked: operator.id != self.options.operator,
|
||||
id: operator.id,
|
||||
title: operator.title
|
||||
});
|
||||
return {
|
||||
//checked: operator.id != self.options.operator,
|
||||
id: operator.id,
|
||||
title: operator.title
|
||||
}
|
||||
}),
|
||||
width: 48
|
||||
})
|
||||
.bindEvent({
|
||||
change: changeOperator
|
||||
}),
|
||||
new Ox.Label({
|
||||
overlap: 'left',
|
||||
title: 'of the following conditions',
|
||||
width: 160
|
||||
})
|
||||
],
|
||||
float: 'right',
|
||||
width: 208
|
||||
})
|
||||
],
|
||||
float: 'left',
|
||||
});
|
||||
}
|
||||
|
||||
function getConditionType(type) {
|
||||
type = Ox.isArray(type) ? type[0] : type;
|
||||
if (['float', 'integer', 'year'].indexOf(type) > -1) {
|
||||
type = 'number';
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
function removeCondition(pos) {
|
||||
self.options.query.conditions.splice(pos, 1);
|
||||
self.$conditions.splice(pos, 1);
|
||||
updateConditions();
|
||||
self.$form.removeItem(pos + 1);
|
||||
}
|
||||
|
||||
function updateConditions() {
|
||||
self.$conditions.forEach(function(condition, pos) {
|
||||
condition.data({position: pos});
|
||||
});
|
||||
self.$conditions[0].options('elements')[3].options({
|
||||
disabled: self.options.query.conditions.length == 1
|
||||
});
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
122
source/Ox.UI/js/Form/Ox.Form.js
Normal file
122
source/Ox.UI/js/Form/Ox.Form.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Form = function(options, self) {
|
||||
|
||||
/**
|
||||
*/
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
error: '',
|
||||
id: '',
|
||||
items: [],
|
||||
submit: null
|
||||
})
|
||||
.options(options || {}) // fixme: the || {} can be done once, in the options function
|
||||
.addClass('OxForm');
|
||||
|
||||
$.extend(self, {
|
||||
$items: [],
|
||||
$messages: [],
|
||||
formIsValid: false,
|
||||
itemIds: [],
|
||||
itemIsValid: []
|
||||
});
|
||||
|
||||
// fixme: form isn't necessarily empty/invalid
|
||||
self.options.items.forEach(function(item, i) {
|
||||
self.itemIds[i] = item.options('id') || item.id;
|
||||
self.itemIsValid[i] = !!item.value().length;
|
||||
that.append(self.$items[i] = new Ox.FormItem({element: item}));
|
||||
item.bindEvent({
|
||||
/*
|
||||
blur: function(event, data) {
|
||||
validate(i, data.valid);
|
||||
if (data.valid) {
|
||||
self.$messages[i].html('').hide();
|
||||
} else {
|
||||
self.$messages[i].html(data.message).show();
|
||||
}
|
||||
},
|
||||
*/
|
||||
autovalidate: function(event, data) {
|
||||
data.valid = !!data.value.length;
|
||||
validate(i, data.valid);
|
||||
data.valid && self.$items[i].setMessage('');
|
||||
},
|
||||
submit: function(event, data) {
|
||||
self.formIsValid && that.submit();
|
||||
},
|
||||
validate: function(event, data) {
|
||||
validate(i, data.valid);
|
||||
self.$items[i].setMessage(data.valid ? '' : data.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function getItemPositionById(id) {
|
||||
return self.itemIds.indexOf(id);
|
||||
}
|
||||
|
||||
function submitCallback(data) {
|
||||
data.forEach(function(v, i) {
|
||||
self.$items[i].setMessage(v.message);
|
||||
});
|
||||
}
|
||||
|
||||
function validate(pos, valid) {
|
||||
//Ox.print('FORM validate', pos, valid)
|
||||
self.itemIsValid[pos] = valid;
|
||||
if (Ox.every(self.itemIsValid) != self.formIsValid) {
|
||||
self.formIsValid = !self.formIsValid;
|
||||
that.triggerEvent('validate', {
|
||||
valid: self.formIsValid
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
that.addItem = function(pos, item) {
|
||||
Ox.print('addItem', pos)
|
||||
self.options.items.splice(pos, 0, item);
|
||||
self.$items.splice(pos, 0, new Ox.FormItem({element: item}));
|
||||
pos == 0 ?
|
||||
self.$items[pos].insertBefore(self.$items[0]) :
|
||||
self.$items[pos].insertAfter(self.$items[pos - 1]);
|
||||
}
|
||||
|
||||
that.removeItem = function(pos) {
|
||||
Ox.print('removeItem', pos);
|
||||
self.$items[pos].removeElement();
|
||||
self.options.items.splice(pos, 1);
|
||||
self.$items.splice(pos, 1);
|
||||
}
|
||||
|
||||
that.submit = function() {
|
||||
//Ox.print('---- that.values()', that.values())
|
||||
self.options.submit(that.values(), submitCallback);
|
||||
};
|
||||
|
||||
that.values = function() { // fixme: can this be private?
|
||||
/*
|
||||
get/set form values
|
||||
call without arguments to get current form values
|
||||
pass values as array to set values (not implemented)
|
||||
*/
|
||||
var values = {};
|
||||
if (arguments.length == 0) {
|
||||
self.$items.forEach(function($item, i) {
|
||||
values[self.itemIds[i]] = self.$items[i].value();
|
||||
});
|
||||
//Ox.print('VALUES', values)
|
||||
return values;
|
||||
} else {
|
||||
Ox.each(arguments[0], function(val, key) {
|
||||
|
||||
});
|
||||
return that;
|
||||
}
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
74
source/Ox.UI/js/Form/Ox.FormElementGroup.js
Normal file
74
source/Ox.UI/js/Form/Ox.FormElementGroup.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.FormElementGroup = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
id: '',
|
||||
elements: [],
|
||||
float: 'left',
|
||||
separators: [],
|
||||
width: 0
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxInputGroup');
|
||||
|
||||
(
|
||||
self.options.float == 'left' ?
|
||||
self.options.elements : self.options.elements.reverse()
|
||||
).forEach(function($element, i) {
|
||||
$element.css({
|
||||
float: self.options.float // fixme: make this a class
|
||||
})
|
||||
.bindEvent({
|
||||
validate: function(event, data) {
|
||||
that.triggerEvent({
|
||||
validate: data
|
||||
});
|
||||
}
|
||||
})
|
||||
.appendTo(that);
|
||||
});
|
||||
|
||||
/*
|
||||
if (self.options.width) {
|
||||
setWidths();
|
||||
} else {
|
||||
self.options.width = getWidth();
|
||||
}
|
||||
that.css({
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
*/
|
||||
|
||||
function getWidth() {
|
||||
|
||||
}
|
||||
|
||||
function setWidth() {
|
||||
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
|
||||
};
|
||||
|
||||
that.replaceElement = function(pos, element) {
|
||||
Ox.print('Ox.FormElementGroup replaceElement', pos, element)
|
||||
self.options.elements[pos].replaceWith(element.$element);
|
||||
self.options.elements[pos] = element;
|
||||
};
|
||||
|
||||
that.value = function() {
|
||||
return $.map(self.options.elements, function(element) {
|
||||
var ret = null;
|
||||
['checked', 'selected', 'value'].forEach(function(v) {
|
||||
element[v] && (ret = element[v]());
|
||||
});
|
||||
return ret;
|
||||
});
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
28
source/Ox.UI/js/Form/Ox.FormItem.js
Normal file
28
source/Ox.UI/js/Form/Ox.FormItem.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.FormItem = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
element: null,
|
||||
error: '',
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxFormItem')
|
||||
.append(self.options.element);
|
||||
|
||||
self.$message = new Ox.Element()
|
||||
.addClass('OxFormMessage')
|
||||
.appendTo(that);
|
||||
|
||||
that.setMessage = function(message) {
|
||||
self.$message.html(message)[message !== '' ? 'show' : 'hide']();
|
||||
}
|
||||
|
||||
that.value = function() {
|
||||
return self.options.element.value();
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
}
|
||||
1764
source/Ox.UI/js/Form/Ox.Input.js
Normal file
1764
source/Ox.UI/js/Form/Ox.Input.js
Normal file
File diff suppressed because it is too large
Load diff
134
source/Ox.UI/js/Form/Ox.InputGroup.js
Normal file
134
source/Ox.UI/js/Form/Ox.InputGroup.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.InputGroup = function(options, self) {
|
||||
|
||||
/***
|
||||
Ox.InputGroup
|
||||
Options:
|
||||
Methods:
|
||||
Events:
|
||||
***/
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
id: '',
|
||||
inputs: [],
|
||||
separators: [],
|
||||
width: 0
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxInputGroup')
|
||||
.click(click);
|
||||
|
||||
if (self.options.width) {
|
||||
setWidths();
|
||||
} else {
|
||||
self.options.width = getWidth();
|
||||
}
|
||||
that.css({
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
|
||||
$.extend(self, {
|
||||
//$input: [],
|
||||
$separator: []
|
||||
});
|
||||
|
||||
self.options.separators.forEach(function(v, i) {
|
||||
self.options.id == 'debug' && Ox.print('separator #' + i + ' ' + self.options.inputs[i].options('id') + ' ' + self.options.inputs[i].options('width'))
|
||||
self.$separator[i] = new Ox.Label({
|
||||
textAlign: 'center',
|
||||
title: v.title,
|
||||
width: v.width + 32
|
||||
})
|
||||
.addClass('OxSeparator')
|
||||
.css({
|
||||
marginLeft: (self.options.inputs[i].options('width') - (i == 0 ? 16 : 32)) + 'px'
|
||||
})
|
||||
.appendTo(that);
|
||||
});
|
||||
|
||||
self.options.inputs.forEach(function($input, i) {
|
||||
$input.options({
|
||||
id: self.options.id + Ox.toTitleCase($input.options('id')),
|
||||
parent: that
|
||||
})
|
||||
.css({
|
||||
marginLeft: -Ox.sum($.map(self.options.inputs, function(v_, i_) {
|
||||
return i_ > i ? self.options.inputs[i_ - 1].options('width') +
|
||||
self.options.separators[i_ - 1].width : (i_ == i ? 16 : 0);
|
||||
})) + 'px'
|
||||
})
|
||||
.bindEvent({
|
||||
change: change,
|
||||
submit: change,
|
||||
validate: validate
|
||||
})
|
||||
.appendTo(that);
|
||||
});
|
||||
|
||||
function change(event, data) {
|
||||
//Ox.print('InputGroup change')
|
||||
// fixme: would be good to pass a value here
|
||||
that.triggerEvent('change');
|
||||
}
|
||||
|
||||
function click(event) {
|
||||
if ($(event.target).hasClass('OxSeparator')) {
|
||||
self.options.inputs[0].focusInput();
|
||||
}
|
||||
}
|
||||
|
||||
function getWidth() {
|
||||
return Ox.sum($.map(self.options.inputs, function(v, i) {
|
||||
return v.options('width');
|
||||
})) + Ox.sum($.map(self.options.separators, function(v, i) {
|
||||
return v.width;
|
||||
})) + 2; // fixme: why + 2?
|
||||
}
|
||||
|
||||
function setWidths() {
|
||||
var length = self.options.inputs.length,
|
||||
inputWidths = Ox.divideInt(
|
||||
self.options.width - Ox.sum($.map(self.options.separators, function(v, i) {
|
||||
return v.width;
|
||||
})), length
|
||||
);
|
||||
self.options.inputs.forEach(function(v) {
|
||||
v.options({
|
||||
width: inputWidths[1]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function validate(event, data) {
|
||||
//Ox.print('INPUTGROUP TRIGGER VALIDATE')
|
||||
that.triggerEvent('validate', data);
|
||||
}
|
||||
|
||||
// fixme: is this used?
|
||||
that.getInputById = function(id) {
|
||||
var input = null;
|
||||
Ox.forEach(self.options.inputs, function(v, i) {
|
||||
//Ox.print(v, v.options('id'), id)
|
||||
if (v.options('id') == self.options.id + Ox.toTitleCase(id)) {
|
||||
input = v;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return input;
|
||||
};
|
||||
|
||||
that.value = function() {
|
||||
return $.map(self.options.inputs, function(input) {
|
||||
var ret = null;
|
||||
['checked', 'selected', 'value'].forEach(function(v) {
|
||||
input[v] && (ret = input[v]());
|
||||
});
|
||||
return ret;
|
||||
});
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
35
source/Ox.UI/js/Form/Ox.Label.js
Normal file
35
source/Ox.UI/js/Form/Ox.Label.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Label = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
disabled: false,
|
||||
id: '',
|
||||
overlap: 'none',
|
||||
textAlign: 'left',
|
||||
title: '',
|
||||
width: 'auto'
|
||||
})
|
||||
.options(options)
|
||||
.addClass(
|
||||
'OxLabel' + (self.options.disabled ? ' OxDisabled' : '') +
|
||||
(self.options.overlap != 'none' ?
|
||||
' OxOverlap' + Ox.toTitleCase(self.options.overlap) : '')
|
||||
)
|
||||
.css($.extend(self.options.width == 'auto' ? {} : {
|
||||
width: (self.options.width - 14) + 'px'
|
||||
}, {
|
||||
textAlign: self.options.textAlign
|
||||
}))
|
||||
.html(self.options.title);
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'title') {
|
||||
that.html(value);
|
||||
}
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
104
source/Ox.UI/js/Form/Ox.OptionGroup.js
Normal file
104
source/Ox.UI/js/Form/Ox.OptionGroup.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.OptionGroup = function(items, min, max, property) {
|
||||
|
||||
/*
|
||||
to be used by ButtonGroup, CheckboxGroup, Select and Menu
|
||||
*/
|
||||
|
||||
var property = property || 'checked'
|
||||
length = items.length,
|
||||
max = max == -1 ? length : max;
|
||||
|
||||
function getLastBefore(pos) {
|
||||
// returns the position of the last checked item before position pos
|
||||
var last = -1;
|
||||
/*Ox.print(items, items.length, length, $.merge(
|
||||
pos > 0 ? Ox.range(pos - 1, -1, -1) : [],
|
||||
pos < items.length - 1 ? Ox.range(items.length - 1, pos, -1) : []
|
||||
))*/
|
||||
// fixme: why is length not == items.length here?
|
||||
Ox.forEach($.merge(
|
||||
pos > 0 ? Ox.range(pos - 1, -1, -1) : [],
|
||||
pos < items.length - 1 ? Ox.range(items.length - 1, pos, -1) : []
|
||||
), function(v) {
|
||||
//Ox.print(pos, v)
|
||||
if (items[v][property]) {
|
||||
last = v;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return last;
|
||||
}
|
||||
|
||||
function getNumber() {
|
||||
// returns the number of checked items
|
||||
var num = 0;
|
||||
items.forEach(function(item) {
|
||||
if (item[property]) {
|
||||
num++;
|
||||
}
|
||||
})
|
||||
return num;
|
||||
}
|
||||
|
||||
this[property] = function() {
|
||||
// returns an array with the positions of all checked item
|
||||
var checked = [];
|
||||
items.forEach(function(item, i) {
|
||||
if (item[property]) {
|
||||
checked.push(i);
|
||||
}
|
||||
})
|
||||
return checked;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
this.toggle = function(pos) {
|
||||
var last,
|
||||
num = getNumber(),
|
||||
toggled = [];
|
||||
if (!items[pos][property]) { // check
|
||||
if (num >= max) {
|
||||
last = getLastBefore(pos);
|
||||
items[last][property] = false;
|
||||
toggled.push(last);
|
||||
}
|
||||
if (!items[pos][property]) {
|
||||
items[pos][property] = true;
|
||||
toggled.push(pos);
|
||||
}
|
||||
} else { // uncheck
|
||||
if (num > min) {
|
||||
items[pos][property] = false;
|
||||
toggled.push(pos);
|
||||
}
|
||||
}
|
||||
return toggled;
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
93
source/Ox.UI/js/Form/Ox.Picker.js
Normal file
93
source/Ox.UI/js/Form/Ox.Picker.js
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Picker = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
element: null,
|
||||
elementHeight: 128,
|
||||
elementWidth: 256,
|
||||
id: '',
|
||||
overlap: 'none'
|
||||
})
|
||||
.options(options || {});
|
||||
|
||||
self.$selectButton = new Ox.Button({
|
||||
overlap: self.options.overlap,
|
||||
title: 'select',
|
||||
type: 'image'
|
||||
})
|
||||
.click(showMenu)
|
||||
.appendTo(that);
|
||||
|
||||
self.$menu = new Ox.Element('div')
|
||||
.addClass('OxPicker')
|
||||
.css({
|
||||
width: self.options.elementWidth + 'px',
|
||||
height: (self.options.elementHeight + 24) + 'px'
|
||||
});
|
||||
|
||||
self.options.element
|
||||
.css({
|
||||
width: self.options.elementWidth + 'px',
|
||||
height: self.options.elementHeight + 'px'
|
||||
})
|
||||
.appendTo(self.$menu);
|
||||
|
||||
self.$bar = new Ox.Bar({
|
||||
orientation: 'horizontal',
|
||||
size: 24
|
||||
})
|
||||
.appendTo(self.$menu);
|
||||
|
||||
that.$label = new Ox.Label({
|
||||
width: self.options.elementWidth - 60
|
||||
})
|
||||
.appendTo(self.$bar);
|
||||
|
||||
self.$doneButton = new Ox.Button({
|
||||
title: 'Done',
|
||||
width: 48
|
||||
})
|
||||
.click(hideMenu)
|
||||
.appendTo(self.$bar);
|
||||
|
||||
self.$layer = $('<div>')
|
||||
.addClass('OxLayer')
|
||||
.click(hideMenu);
|
||||
|
||||
function hideMenu() {
|
||||
self.$menu.detach();
|
||||
self.$layer.detach();
|
||||
self.$selectButton
|
||||
.removeClass('OxSelected')
|
||||
.css({
|
||||
MozBorderRadius: '8px',
|
||||
WebkitBorderRadius: '8px'
|
||||
});
|
||||
that.triggerEvent('hide');
|
||||
};
|
||||
|
||||
function showMenu() {
|
||||
var offset = that.offset(),
|
||||
left = offset.left,
|
||||
top = offset.top + 15;
|
||||
self.$selectButton
|
||||
.addClass('OxSelected')
|
||||
.css({
|
||||
MozBorderRadius: '8px 8px 0 0',
|
||||
WebkitBorderRadius: '8px 8px 0 0'
|
||||
});
|
||||
self.$layer.appendTo(Ox.UI.$body);
|
||||
self.$menu
|
||||
.css({
|
||||
left: left + 'px',
|
||||
top: top + 'px'
|
||||
})
|
||||
.appendTo(Ox.UI.$body);
|
||||
that.triggerEvent('show');
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
35
source/Ox.UI/js/Form/Ox.PlaceInput.js
Normal file
35
source/Ox.UI/js/Form/Ox.PlaceInput.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.PlaceInput = function(options, self) {
|
||||
|
||||
var self = $.extend(self || {}, {
|
||||
options: $.extend({
|
||||
id: '',
|
||||
value: 'United States'
|
||||
}, options)
|
||||
}),
|
||||
that;
|
||||
|
||||
that = new Ox.FormElementGroup({
|
||||
id: self.options.id,
|
||||
elements: [
|
||||
new Ox.Input({
|
||||
id: 'input',
|
||||
value: self.options.value
|
||||
}),
|
||||
new Ox.PlacePicker({
|
||||
id: 'picker',
|
||||
overlap: 'left',
|
||||
value: self.options.value
|
||||
})
|
||||
],
|
||||
float: 'right'
|
||||
}, self)
|
||||
.bindEvent('change', change);
|
||||
|
||||
function change() {
|
||||
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
143
source/Ox.UI/js/Form/Ox.PlacePicker.js
Normal file
143
source/Ox.UI/js/Form/Ox.PlacePicker.js
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.PlacePicker = function(options, self) {
|
||||
|
||||
var self = $.extend(self || {}, {
|
||||
options: $.extend({
|
||||
id: '',
|
||||
value: 'United States'
|
||||
}, options)
|
||||
}),
|
||||
that;
|
||||
|
||||
self.$element = new Ox.Element('div')
|
||||
.css({
|
||||
width: '256px',
|
||||
height: '192px'
|
||||
})
|
||||
.append(
|
||||
self.$topBar = new Ox.Bar({
|
||||
size: 16
|
||||
})
|
||||
.css({
|
||||
MozBorderRadius: '0 8px 0 0',
|
||||
WebkitBorderRadius: '0 8px 0 0'
|
||||
})
|
||||
.append(
|
||||
self.$input = new Ox.Input({
|
||||
clear: true,
|
||||
id: self.options.id + 'Input',
|
||||
placeholder: 'Find',
|
||||
width: 256
|
||||
})
|
||||
.bindEvent('submit', findPlace)
|
||||
)
|
||||
)
|
||||
.append(
|
||||
self.$container = new Ox.Element('div')
|
||||
.css({
|
||||
width: '256px',
|
||||
height: '160px'
|
||||
})
|
||||
)
|
||||
.append(
|
||||
self.$bottomBar = new Ox.Bar({
|
||||
size: 16
|
||||
})
|
||||
.append(
|
||||
self.$range = new Ox.Range({
|
||||
arrows: true,
|
||||
id: self.options.id + 'Range',
|
||||
max: 22,
|
||||
size: 256,
|
||||
thumbSize: 32,
|
||||
thumbValue: true
|
||||
})
|
||||
.bindEvent('change', changeZoom)
|
||||
)
|
||||
);
|
||||
|
||||
self.$input.$element.children('input[type=text]').css({
|
||||
width: '230px',
|
||||
paddingLeft: '2px',
|
||||
MozBorderRadius: '0 8px 8px 0',
|
||||
WebkitBorderRadius: '0 8px 8px 0'
|
||||
});
|
||||
self.$input.$element.children('input[type=image]').css({
|
||||
MozBorderRadius: '0 8px 0 0',
|
||||
WebkitBorderRadius: '0 8px 0 0'
|
||||
});
|
||||
self.$range.$element.children('input').css({
|
||||
MozBorderRadius: 0,
|
||||
WebkitBorderRadius: 0
|
||||
});
|
||||
|
||||
that = new Ox.Picker({
|
||||
element: self.$element,
|
||||
elementHeight: 192,
|
||||
elementWidth: 256,
|
||||
id: self.options.id,
|
||||
overlap: self.options.overlap,
|
||||
value: self.options.value
|
||||
}, self)
|
||||
.bindEvent('show', showPicker);
|
||||
|
||||
that.$label.bind('click', clickLabel)
|
||||
|
||||
self.map = false;
|
||||
|
||||
function changeZoom(event, data) {
|
||||
//Ox.print('changeZoom')
|
||||
self.$map.zoom(data.value);
|
||||
}
|
||||
|
||||
function clickLabel() {
|
||||
var name = that.$label.html();
|
||||
if (name) {
|
||||
self.$input.options({
|
||||
value: name
|
||||
})
|
||||
.triggerEvent('submit', {
|
||||
value: name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function findPlace(event, data) {
|
||||
//Ox.print('findPlace', data);
|
||||
self.$map.find(data.value, function(place) {
|
||||
place && that.$label.html(place.geoname);
|
||||
})
|
||||
}
|
||||
|
||||
function onSelect(event, data) {
|
||||
that.$label.html(data.geoname);
|
||||
}
|
||||
|
||||
function onZoom(event, data) {
|
||||
self.$range.options({
|
||||
value: data.value
|
||||
});
|
||||
}
|
||||
|
||||
function showPicker() {
|
||||
if (!self.map) {
|
||||
self.$map = new Ox.Map({
|
||||
id: self.options.id + 'Map',
|
||||
places: [self.options.value]
|
||||
})
|
||||
.css({
|
||||
width: '256px',
|
||||
height: '160px'
|
||||
})
|
||||
.bindEvent({
|
||||
select: onSelect,
|
||||
zoom: onZoom
|
||||
})
|
||||
.appendTo(self.$container);
|
||||
self.map = true;
|
||||
}
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
249
source/Ox.UI/js/Form/Ox.Range.js
Normal file
249
source/Ox.UI/js/Form/Ox.Range.js
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Range = function(options, self) {
|
||||
|
||||
/**
|
||||
options
|
||||
arrows boolean if true, show arrows
|
||||
arrowStep number step when clicking arrows
|
||||
arrowSymbols array arrow symbols, like ['minus', 'plus']
|
||||
max number maximum value
|
||||
min number minimum value
|
||||
orientation string 'horizontal' or 'vertical'
|
||||
step number step between values
|
||||
size number width or height, in px
|
||||
thumbSize number minimum width or height of thumb, in px
|
||||
thumbValue boolean if true, display value on thumb
|
||||
trackGradient array colors
|
||||
trackImages string or array one or multiple track background image URLs
|
||||
trackStep number 0 (scroll here) or step when clicking track
|
||||
value number initial value
|
||||
valueNames array value names to display on thumb
|
||||
*/
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
arrows: false,
|
||||
arrowStep: 1,
|
||||
arrowSymbols: ['left', 'right'],
|
||||
max: 100,
|
||||
min: 0,
|
||||
orientation: 'horizontal',
|
||||
step: 1,
|
||||
size: 128,
|
||||
thumbSize: 16,
|
||||
thumbValue: false,
|
||||
trackColors: [],
|
||||
trackImages: [],
|
||||
trackStep: 0,
|
||||
value: 0,
|
||||
valueNames: null,
|
||||
})
|
||||
.options($.extend(options, {
|
||||
arrowStep: options.arrowStep ?
|
||||
options.arrowStep : options.step,
|
||||
trackImages: $.makeArray(options.trackImages || [])
|
||||
}))
|
||||
.addClass('OxRange')
|
||||
.css({
|
||||
width: self.options.size + 'px'
|
||||
});
|
||||
|
||||
$.extend(self, {
|
||||
trackColors: self.options.trackColors.length,
|
||||
trackImages: self.options.trackImages.length,
|
||||
values: (self.options.max - self.options.min + self.options.step) /
|
||||
self.options.step
|
||||
});
|
||||
setSizes();
|
||||
|
||||
if (self.options.arrows) {
|
||||
self.$arrows = [];
|
||||
Ox.range(0, 2).forEach(function(i) {
|
||||
self.$arrows[i] = new Ox.Button({
|
||||
overlap: i == 0 ? 'right' : 'left',
|
||||
title: self.options.arrowSymbols[i],
|
||||
type: 'image'
|
||||
})
|
||||
.addClass('OxArrow')
|
||||
.bindEvent({
|
||||
mousedown: function(event, e) {
|
||||
clickArrow(e, i, true);
|
||||
},
|
||||
mouserepeat: function(event, e) {
|
||||
clickArrow(e, i, false);
|
||||
}
|
||||
})
|
||||
.appendTo(that.$element);
|
||||
});
|
||||
}
|
||||
|
||||
self.$track = new Ox.Element()
|
||||
.addClass('OxTrack')
|
||||
.css($.extend({
|
||||
width: (self.trackSize - 2) + 'px'
|
||||
}, self.trackImages == 1 ? {
|
||||
background: 'rgb(0, 0, 0)'
|
||||
} : {}))
|
||||
.bindEvent({
|
||||
mousedown: clickTrack,
|
||||
drag: dragTrack
|
||||
})
|
||||
.appendTo(that.$element);
|
||||
|
||||
self.trackColors && setTrackColors();
|
||||
|
||||
if (self.trackImages) {
|
||||
self.$trackImages = $('<div>')
|
||||
.css({
|
||||
width: self.trackSize + 'px',
|
||||
marginRight: (-self.trackSize - 1) + 'px'
|
||||
})
|
||||
.appendTo(self.$track.$element);
|
||||
self.options.trackImages.forEach(function(v, i) {
|
||||
//Ox.print(self.trackImageWidths[i])
|
||||
$('<img>')
|
||||
.attr({
|
||||
src: v
|
||||
})
|
||||
.addClass(i == 0 ? 'OxFirstChild' : '')
|
||||
.addClass(i == self.trackImages - 1 ? 'OxLastChild' : '')
|
||||
.css({
|
||||
width: self.trackImageWidths[i] + 'px'
|
||||
})
|
||||
.mousedown(function(e) {
|
||||
e.preventDefault(); // prevent drag
|
||||
})
|
||||
.appendTo(self.$trackImages);
|
||||
//left += self.trackImageWidths[i];
|
||||
});
|
||||
}
|
||||
|
||||
self.$thumb = Ox.Button({
|
||||
id: self.options.id + 'Thumb',
|
||||
title: self.options.thumbValue ? (self.options.valueNames ?
|
||||
self.options.valueNames[self.options.value] :
|
||||
self.options.value) : '',
|
||||
width: self.thumbSize
|
||||
})
|
||||
.addClass('OxThumb')
|
||||
/*
|
||||
.css({
|
||||
border: '1px solid rgb(255, 255, 255)',
|
||||
background: 'rgba(0, 0, 0, 0)'
|
||||
})
|
||||
*/
|
||||
.appendTo(self.$track);
|
||||
|
||||
setThumb();
|
||||
|
||||
function clickArrow(e, i, animate) {
|
||||
// fixme: shift doesn't work, see menu scrolling
|
||||
setValue(self.options.value + self.options.arrowStep * (i == 0 ? -1 : 1) * (e.shiftKey ? 2 : 1), animate);
|
||||
}
|
||||
|
||||
function clickTrack(event, e) {
|
||||
// fixme: thumb ends up a bit too far on the right
|
||||
var isThumb = $(e.target).hasClass('OxThumb');
|
||||
self.drag = {
|
||||
left: self.$track.offset().left,
|
||||
offset: isThumb ? e.clientX - self.$thumb.offset().left - 8 /*self.thumbSize / 2*/ : 0
|
||||
};
|
||||
setValue(getVal(e.clientX - self.drag.left - self.drag.offset), !isThumb);
|
||||
}
|
||||
|
||||
function dragTrack(event, e) {
|
||||
setValue(getVal(e.clientX - self.drag.left - self.drag.offset))
|
||||
}
|
||||
|
||||
function getPx(val) {
|
||||
var pxPerVal = (self.trackSize - self.thumbSize) /
|
||||
(self.options.max - self.options.min);
|
||||
return Math.ceil((val - self.options.min) * pxPerVal);
|
||||
}
|
||||
|
||||
/*
|
||||
function getTime(oldValue, newValue) {
|
||||
return self.animationTime * Math.abs(oldValue - newValue) / (self.options.max - self.options.min);
|
||||
}
|
||||
*/
|
||||
|
||||
function getVal(px) {
|
||||
var px = self.trackSize / self.values >= 16 ? px : px - 8,
|
||||
valPerPx = (self.options.max - self.options.min) /
|
||||
(self.trackSize - self.thumbSize);
|
||||
return Ox.limit(self.options.min +
|
||||
Math.floor(px * valPerPx / self.options.step) * self.options.step,
|
||||
self.options.min, self.options.max);
|
||||
}
|
||||
|
||||
function setSizes() {
|
||||
self.trackSize = self.options.size - self.options.arrows * 32;
|
||||
self.thumbSize = Math.max(self.trackSize / self.values, self.options.thumbSize);
|
||||
self.trackImageWidths = self.trackImages == 1 ? [self.trackSize - 16] :
|
||||
Ox.divideInt(self.trackSize - 2, self.trackImages);
|
||||
self.trackColorsStart = self.thumbSize / 2 / self.options.size;
|
||||
self.trackColorsStep = (self.options.size - self.thumbSize) /
|
||||
(self.trackColors - 1) / self.options.size;
|
||||
self.$track && self.$track.css({
|
||||
width: (self.trackSize - 2) + 'px'
|
||||
});
|
||||
self.$thumb && self.$thumb.options({
|
||||
width: self.thumbSize
|
||||
});
|
||||
}
|
||||
|
||||
function setThumb(animate) {
|
||||
self.$thumb.stop().animate({
|
||||
marginLeft: (getPx(self.options.value) - 1) + 'px',
|
||||
//width: self.thumbSize + 'px'
|
||||
}, animate ? 200 : 0, function() {
|
||||
if (self.options.thumbValue) {
|
||||
self.$thumb.options({
|
||||
title: self.options.valueNames ?
|
||||
self.options.valueNames[self.options.value] :
|
||||
self.options.value
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setTrackColors() {
|
||||
self.$track.css({
|
||||
backgroundImage: $.browser.mozilla ?
|
||||
('-moz-linear-gradient(left, ' +
|
||||
self.options.trackColors[0] + ' 0%, ' + $.map(self.options.trackColors, function(v, i) {
|
||||
return v + ' ' + ((self.trackColorsStart + self.trackColorsStep * i) * 100) + '%';
|
||||
}).join(', ') + ', ' + self.options.trackColors[self.trackColors - 1] + ' 100%)') :
|
||||
('-webkit-gradient(linear, left top, right top, color-stop(0, ' +
|
||||
self.options.trackColors[0] + '), ' + $.map(self.options.trackColors, function(v, i) {
|
||||
return 'color-stop(' + (self.trackColorsStart + self.trackColorsStep * i) + ', ' + v + ')';
|
||||
}).join(', ') + ', color-stop(1, ' + self.options.trackColors[self.trackColors - 1] + '))')
|
||||
});
|
||||
}
|
||||
|
||||
function setValue(value, animate) {
|
||||
var value = Ox.limit(value, self.options.min, self.options.max);
|
||||
if (value != self.options.value) {
|
||||
//time = getTime(self.options.value, value);
|
||||
self.options.value = value;
|
||||
setThumb(animate);
|
||||
that.triggerEvent('change', {
|
||||
value: value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'size') {
|
||||
setSizes();
|
||||
} else if (key == 'trackColors') {
|
||||
setTrackColors();
|
||||
} else if (key == 'value') {
|
||||
setThumb();
|
||||
}
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
163
source/Ox.UI/js/Form/Ox.Select.js
Normal file
163
source/Ox.UI/js/Form/Ox.Select.js
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Select = function(options, self) {
|
||||
|
||||
// fixme: selected item needs attribute "checked", not "selected" ... that's strange
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self) // fixme: do we use 'div', or {}, or '', by default?
|
||||
.defaults({
|
||||
id: '',
|
||||
items: [],
|
||||
max: 1,
|
||||
min: 1,
|
||||
overlap: 'none', // can be none, left or right
|
||||
selectable: true,
|
||||
size: 'medium',
|
||||
title: '',
|
||||
type: 'text', // can be 'text' or 'image'
|
||||
width: 'auto'
|
||||
})
|
||||
// fixme: make default selection restorable
|
||||
// or allow for extra action items below options
|
||||
.options(options)
|
||||
.addClass(
|
||||
'OxSelect Ox' + Ox.toTitleCase(self.options.size) +
|
||||
(self.options.overlap == 'none' ? '' : ' OxOverlap' +
|
||||
Ox.toTitleCase(self.options.overlap))
|
||||
)
|
||||
.css(self.options.width == 'auto' ? {} : {
|
||||
width: self.options.width + 'px'
|
||||
})
|
||||
.bindEvent({
|
||||
key_escape: loseFocus,
|
||||
key_down: showMenu
|
||||
});
|
||||
|
||||
Ox.print('Ox.Select', self.options)
|
||||
|
||||
$.extend(self, {
|
||||
buttonId: self.options.id + 'Button',
|
||||
groupId: self.options.id + 'Group',
|
||||
menuId: self.options.id + 'Menu'
|
||||
});
|
||||
|
||||
if (self.options.selectable) {
|
||||
self.optionGroup = new Ox.OptionGroup(
|
||||
self.options.items,
|
||||
self.options.min,
|
||||
self.options.max
|
||||
);
|
||||
self.options.items = self.optionGroup.init();
|
||||
self.checked = self.optionGroup.checked();
|
||||
}
|
||||
|
||||
if (self.options.type == 'text') {
|
||||
self.$title = $('<div>')
|
||||
.addClass('OxTitle')
|
||||
.css({
|
||||
width: (self.options.width - 22) + 'px'
|
||||
})
|
||||
.html(
|
||||
self.options.title ? self.options.title :
|
||||
self.options.items[self.checked[0]].title
|
||||
)
|
||||
.click(showMenu)
|
||||
.appendTo(that.$element);
|
||||
}
|
||||
|
||||
self.$button = new Ox.Button({
|
||||
id: self.buttonId,
|
||||
style: 'symbol',
|
||||
title: 'select',
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent('click', showMenu)
|
||||
.appendTo(that);
|
||||
|
||||
self.$menu = new Ox.Menu({
|
||||
element: self.$title || self.$button,
|
||||
id: self.menuId,
|
||||
items: [self.options.selectable ? {
|
||||
group: self.groupId,
|
||||
items: self.options.items,
|
||||
max: self.options.max,
|
||||
min: self.options.min
|
||||
} : self.options.items],
|
||||
side: 'bottom',
|
||||
size: self.options.size
|
||||
})
|
||||
.bindEvent({
|
||||
change: changeMenu,
|
||||
click: clickMenu,
|
||||
hide: hideMenu
|
||||
});
|
||||
|
||||
self.options.type == 'image' && self.$menu.addClass('OxRight');
|
||||
|
||||
function clickMenu(event, data) {
|
||||
that.triggerEvent('click', data);
|
||||
}
|
||||
|
||||
function changeMenu(event, data) {
|
||||
//Ox.print('clickMenu: ', self.options.id, data)
|
||||
self.checked = self.optionGroup.checked();
|
||||
self.$title && self.$title.html(
|
||||
self.options.title ? self.options.title :
|
||||
data.checked[0].title
|
||||
);
|
||||
that.triggerEvent('change', {
|
||||
selected: data.checked
|
||||
});
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
//Ox.print('%% hideMenu that', that, 'self', self)
|
||||
that.removeClass('OxSelected');
|
||||
// self.$button.removeClass('OxSelected');
|
||||
//Ox.print('%% hideMenu end')
|
||||
}
|
||||
|
||||
function loseFocus() {
|
||||
that.loseFocus();
|
||||
}
|
||||
|
||||
function showMenu() {
|
||||
that.gainFocus();
|
||||
that.addClass('OxSelected');
|
||||
self.$menu.showMenu();
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
|
||||
};
|
||||
|
||||
that.selected = function() {
|
||||
return $.map(/*self.checked*/self.optionGroup.checked(), function(v) {
|
||||
return {
|
||||
id: self.options.items[v].id,
|
||||
title: self.options.items[v].title
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
that.selectItem = function(id) {
|
||||
//Ox.print('selectItem', id, Ox.getObjectById(self.options.items, id).title)
|
||||
self.options.type == 'text' && self.$title.html(
|
||||
Ox.getObjectById(self.options.items, id).title[0] // fixme: title should not have become an array
|
||||
);
|
||||
self.$menu.checkItem(id);
|
||||
self.checked = self.optionGroup.checked();
|
||||
};
|
||||
|
||||
/*
|
||||
that.width = function(val) {
|
||||
// fixme: silly hack, and won't work for css() ... remove!
|
||||
that.$element.width(val + 16);
|
||||
that.$button.width(val);
|
||||
//that.$symbol.width(val);
|
||||
return that;
|
||||
};
|
||||
*/
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
153
source/Ox.UI/js/Form/Ox.TimeInput.js
Normal file
153
source/Ox.UI/js/Form/Ox.TimeInput.js
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.TimeInput = function(options, self) {
|
||||
|
||||
// fixme: seconds get set even if options.seconds is false
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
ampm: false,
|
||||
seconds: false,
|
||||
milliseconds: false,
|
||||
value: Ox.formatDate(new Date(), '%T'),
|
||||
})
|
||||
.options(options || {});
|
||||
|
||||
if (self.options.milliseconds) {
|
||||
self.options.seconds = true;
|
||||
if (self.options.value.indexOf('.') == -1) {
|
||||
self.options.value += '.000';
|
||||
}
|
||||
}
|
||||
|
||||
self.date = getDate();
|
||||
self.values = getValues();
|
||||
|
||||
self.$input = {
|
||||
hours: new Ox.Input({
|
||||
autocomplete: $.map(self.options.ampm ? Ox.range(1, 13) : Ox.range(0, 24), function(v) {
|
||||
return Ox.pad(v, 2);
|
||||
}),
|
||||
autocompleteReplace: true,
|
||||
autocompleteReplaceCorrect: true,
|
||||
id: 'hours',
|
||||
textAlign: 'right',
|
||||
value: self.values.hours,
|
||||
width: 32
|
||||
}),
|
||||
minutes: new Ox.Input({
|
||||
autocomplete: $.map(Ox.range(0, 60), function(v) {
|
||||
return Ox.pad(v, 2);
|
||||
}),
|
||||
autocompleteReplace: true,
|
||||
autocompleteReplaceCorrect: true,
|
||||
id: 'minutes',
|
||||
textAlign: 'right',
|
||||
value: self.values.minutes,
|
||||
width: 32
|
||||
}),
|
||||
seconds: new Ox.Input({
|
||||
autocomplete: $.map(Ox.range(0, 60), function(v) {
|
||||
return Ox.pad(v, 2);
|
||||
}),
|
||||
autocompleteReplace: true,
|
||||
autocompleteReplaceCorrect: true,
|
||||
id: 'seconds',
|
||||
textAlign: 'right',
|
||||
value: self.values.seconds,
|
||||
width: 32
|
||||
}),
|
||||
milliseconds: new Ox.Input({
|
||||
autocomplete: $.map(Ox.range(0, 1000), function(v) {
|
||||
return Ox.pad(v, 3);
|
||||
}),
|
||||
autocompleteReplace: true,
|
||||
autocompleteReplaceCorrect: true,
|
||||
id: 'milliseconds',
|
||||
textAlign: 'right',
|
||||
value: self.values.milliseconds,
|
||||
width: 40
|
||||
}),
|
||||
ampm: new Ox.Input({
|
||||
autocomplete: ['AM', 'PM'],
|
||||
autocompleteReplace: true,
|
||||
autocompleteReplaceCorrect: true,
|
||||
id: 'ampm',
|
||||
value: self.values.ampm,
|
||||
width: 32
|
||||
})
|
||||
};
|
||||
|
||||
that = new Ox.InputGroup($.extend(self.options, {
|
||||
inputs: $.merge($.merge($.merge([
|
||||
self.$input.hours,
|
||||
self.$input.minutes,
|
||||
], self.options.seconds ? [
|
||||
self.$input.seconds
|
||||
] : []), self.options.milliseconds ? [
|
||||
self.$input.milliseconds
|
||||
] : []), self.options.ampm ? [
|
||||
self.$input.ampm
|
||||
] : []),
|
||||
separators: $.merge($.merge($.merge([
|
||||
{title: ':', width: 8},
|
||||
], self.options.seconds ? [
|
||||
{title: ':', width: 8}
|
||||
] : []), self.options.milliseconds ? [
|
||||
{title: '.', width: 8}
|
||||
] : []), self.options.ampm ? [
|
||||
{title: '', width: 8}
|
||||
] : []),
|
||||
//width: self.options.width || 128
|
||||
}), self)
|
||||
.bindEvent('change', setValue);
|
||||
|
||||
setValue();
|
||||
|
||||
function getDate() {
|
||||
return new Date('1970/01/01 ' + (
|
||||
self.options.milliseconds ?
|
||||
self.options.value.substr(0, self.options.value.length - 4) :
|
||||
self.options.value
|
||||
));
|
||||
}
|
||||
|
||||
function getValues() {
|
||||
self.date = getDate();
|
||||
return {
|
||||
ampm: Ox.formatDate(self.date, '%p'),
|
||||
hours: Ox.formatDate(self.date, self.options.ampm ? '%I' : '%H'),
|
||||
milliseconds: self.options.milliseconds ? self.options.value.substr(-3) : '000',
|
||||
minutes: Ox.formatDate(self.date, '%M'),
|
||||
seconds: Ox.formatDate(self.date, '%S')
|
||||
};
|
||||
}
|
||||
|
||||
function setValue() {
|
||||
self.options.value = Ox.formatDate(new Date('1970/01/01 ' + [
|
||||
self.$input.hours.options('value'),
|
||||
self.$input.minutes.options('value'),
|
||||
self.options.seconds ? self.$input.seconds.options('value') : '00'
|
||||
].join(':') + (self.options.ampm ? ' ' + self.$input.ampm.options('value') : '')),
|
||||
(self.options.seconds? '%T' : '%H:%M')) +
|
||||
(self.options.milliseconds ? '.' + self.$input.milliseconds.options('value') : '');
|
||||
//Ox.print('SETVALUE', self.options.value);
|
||||
}
|
||||
|
||||
function setValues() {
|
||||
self.values = getValues();
|
||||
Ox.forEach(self.$input, function(v, k) {
|
||||
self.$input[k].options({
|
||||
value: self.values[k]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'value') {
|
||||
setValues();
|
||||
}
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
156
source/Ox.UI/js/List/Ox.IconItem.js
Normal file
156
source/Ox.UI/js/List/Ox.IconItem.js
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.IconItem = function(options, self) {
|
||||
|
||||
//Ox.print('IconItem', options, self)
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
height: 128,
|
||||
id: '',
|
||||
info: '',
|
||||
size: 128,
|
||||
title: '',
|
||||
width: 128,
|
||||
url: ''
|
||||
})
|
||||
.options(options || {})
|
||||
|
||||
$.extend(self, {
|
||||
fontSize: self.options.size == 64 ? 6 : 9,
|
||||
height: self.options.size * 1.5,
|
||||
lineLength: self.options.size == 64 ? 15 : 23,
|
||||
lines: self.options.size == 64 ? 4 : 5,
|
||||
url: Ox.PATH + '/png/Ox.UI/transparent.png',
|
||||
width: self.options.size
|
||||
});
|
||||
self.title = formatText(self.options.title, self.lines - 1, self.lineLength);
|
||||
self.info = formatText(self.options.info, 5 - self.title.split('<br/>').length, self.lineLength);
|
||||
|
||||
that.css({
|
||||
width: self.width + 'px',
|
||||
height: self.height + 'px'
|
||||
});
|
||||
that.$icon = $('<div>')
|
||||
.addClass('OxIcon')
|
||||
.css({
|
||||
top: self.options.size == 64 ? -64 : -124,
|
||||
width: (self.options.size + 4) + 'px',
|
||||
height: (self.options.size + 4) + 'px'
|
||||
});
|
||||
that.$iconImage = $('<img>')
|
||||
.addClass('OxLoading OxTarget')
|
||||
.attr({
|
||||
src: self.url
|
||||
})
|
||||
.css({
|
||||
width: self.options.width + 'px',
|
||||
height: self.options.height + 'px'
|
||||
})
|
||||
.mousedown(mousedown)
|
||||
.mouseenter(mouseenter)
|
||||
.mouseleave(mouseleave);
|
||||
self.options.url && that.$iconImage.one('load', load);
|
||||
that.$textBox = $('<div>')
|
||||
.addClass('OxText')
|
||||
.css({
|
||||
top: (self.options.size / 2) + 'px',
|
||||
width: (self.options.size + 4) + 'px',
|
||||
height: (self.options.size == 64 ? 30 : 58) + 'px'
|
||||
})
|
||||
that.$text = $('<div>')
|
||||
.addClass('OxTarget')
|
||||
.css({
|
||||
fontSize: self.fontSize + 'px'
|
||||
})
|
||||
.html(
|
||||
self.title + '<br/><span class="OxInfo">' + self.info + '</span>'
|
||||
)
|
||||
.mouseenter(mouseenter)
|
||||
.mouseleave(mouseleave);
|
||||
that.$reflection = $('<div>')
|
||||
.addClass('OxReflection')
|
||||
.css({
|
||||
top: self.options.size + 'px',
|
||||
width: (self.options.size + 4) + 'px',
|
||||
height: (self.options.size / 2) + 'px'
|
||||
});
|
||||
that.$reflectionImage = $('<img>')
|
||||
.addClass('OxLoading')
|
||||
.attr({
|
||||
src: self.url
|
||||
})
|
||||
.css({
|
||||
width: self.options.width + 'px',
|
||||
height: self.options.height + 'px',
|
||||
// firefox is 1px off when centering images with odd width and scaleY(-1)
|
||||
paddingLeft: ($.browser.mozilla && self.options.width % 2 ? 1 : 0) + 'px'
|
||||
});
|
||||
that.$gradient = $('<div>')
|
||||
.css({
|
||||
//top: (-self.options.size / 2) + 'px',
|
||||
width: self.options.width + 'px',
|
||||
height: (self.options.size / 2) + 'px'
|
||||
});
|
||||
|
||||
that.append(
|
||||
that.$reflection.append(
|
||||
that.$reflectionImage
|
||||
).append(
|
||||
that.$gradient
|
||||
)
|
||||
).append(
|
||||
that.$textBox.append(
|
||||
that.$text
|
||||
)
|
||||
).append(
|
||||
that.$icon.append(
|
||||
that.$iconImage
|
||||
)
|
||||
);
|
||||
|
||||
function formatText(text, maxLines, maxLength) {
|
||||
var lines = Ox.wordwrap(text, maxLength, '<br/>', true, false).split('<br/>');
|
||||
return $.map(lines, function(line, i) {
|
||||
if (i < maxLines - 1) {
|
||||
return line;
|
||||
} else if (i == maxLines - 1) {
|
||||
return lines.length == maxLines ? line : Ox.truncate($.map(lines, function(line, i) {
|
||||
return i < maxLines - 1 ? null : line;
|
||||
}).join(' '), maxLength, '...', 'center');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}).join('<br/>');
|
||||
}
|
||||
|
||||
function load() {
|
||||
that.$iconImage.attr({
|
||||
src: self.options.url
|
||||
})
|
||||
.one('load', function() {
|
||||
that.$iconImage.removeClass('OxLoading');
|
||||
that.$reflectionImage
|
||||
.attr({
|
||||
src: self.options.url
|
||||
})
|
||||
.removeClass('OxLoading');
|
||||
});
|
||||
}
|
||||
|
||||
function mousedown(e) {
|
||||
// fixme: preventDefault keeps image from being draggable in safari - but also keeps the list from getting focus
|
||||
// e.preventDefault();
|
||||
}
|
||||
|
||||
function mouseenter() {
|
||||
that.addClass('OxHover');
|
||||
}
|
||||
|
||||
function mouseleave() {
|
||||
that.removeClass('OxHover');
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
139
source/Ox.UI/js/List/Ox.IconList.js
Normal file
139
source/Ox.UI/js/List/Ox.IconList.js
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.IconList = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
centerSelection: false,
|
||||
draggable: true,
|
||||
id: '',
|
||||
item: null,
|
||||
items: null,
|
||||
keys: [],
|
||||
max: -1,
|
||||
min: 0,
|
||||
orientation: 'both',
|
||||
selected: [],
|
||||
size: 128,
|
||||
sort: [],
|
||||
})
|
||||
.options(options || {});
|
||||
|
||||
$.extend(self, {
|
||||
itemHeight: self.options.size * 1.5,
|
||||
itemWidth: self.options.size
|
||||
});
|
||||
|
||||
that.$element = new Ox.List({
|
||||
centered: self.options.centered,
|
||||
construct: constructItem,
|
||||
draggable: self.options.draggable,
|
||||
id: self.options.id,
|
||||
itemHeight: self.itemHeight,
|
||||
items: self.options.items,
|
||||
itemWidth: self.itemWidth,
|
||||
keys: self.options.keys,
|
||||
orientation: self.options.orientation,
|
||||
keys: self.options.keys,
|
||||
max: self.options.max,
|
||||
min: self.options.min,
|
||||
selected: self.options.selected,
|
||||
size: self.options.size,
|
||||
sort: self.options.sort,
|
||||
type: 'icon',
|
||||
unique: self.options.unique
|
||||
}, $.extend({}, self)) // pass event handler
|
||||
.addClass('OxIconList Ox' + Ox.toTitleCase(self.options.orientation))
|
||||
.click(click)
|
||||
.dblclick(dblclick)
|
||||
.scroll(scroll);
|
||||
|
||||
updateKeys();
|
||||
|
||||
function click() {
|
||||
|
||||
}
|
||||
|
||||
function constructItem(data) {
|
||||
var data = !$.isEmptyObject(data) ?
|
||||
self.options.item(data, self.options.sort, self.options.size) :
|
||||
{height: 8, width: 5},
|
||||
ratio = data.width / data.height;
|
||||
return new Ox.IconItem($.extend(data, {
|
||||
height: Math.round(self.options.size / (ratio <= 1 ? 1 : ratio)),
|
||||
size: self.options.size,
|
||||
width: Math.round(self.options.size * (ratio >= 1 ? 1 : ratio))
|
||||
}));
|
||||
}
|
||||
|
||||
function dblclick() {
|
||||
|
||||
}
|
||||
|
||||
function scroll() {
|
||||
|
||||
}
|
||||
|
||||
function updateKeys() {
|
||||
self.options.keys = Ox.unique($.merge(self.options.keys, [self.options.sort[0].key]));
|
||||
that.$element.options({
|
||||
keys: self.options.keys
|
||||
});
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'items') {
|
||||
that.$element.options(key, value);
|
||||
} else if (key == 'paste') {
|
||||
that.$element.options(key, value);
|
||||
} else if (key == 'selected') {
|
||||
that.$element.options(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
that.closePreview = function() {
|
||||
that.$element.closePreview();
|
||||
};
|
||||
|
||||
that.paste = function(data) {
|
||||
that.$element.paste(data);
|
||||
return that;
|
||||
};
|
||||
|
||||
that.reloadList = function() {
|
||||
that.$element.reloadList();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.scrollToSelection = function() {
|
||||
that.$element.scrollToSelection();
|
||||
};
|
||||
|
||||
that.size = function() {
|
||||
that.$element.size();
|
||||
};
|
||||
|
||||
that.sortList = function(key, operator) {
|
||||
self.options.sort = [{
|
||||
key: key,
|
||||
operator: operator
|
||||
}];
|
||||
updateKeys();
|
||||
that.$element.sortList(key, operator);
|
||||
};
|
||||
|
||||
that.value = function(id, key, value) {
|
||||
// fixme: make this accept id, {k: v, ...}
|
||||
if (arguments.length == 1) {
|
||||
return that.$element.value(id);
|
||||
} else if (arguments.length == 2) {
|
||||
return that.$element.value(id, key);
|
||||
} else {
|
||||
that.$element.value(id, key, value);
|
||||
return that;
|
||||
}
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
55
source/Ox.UI/js/List/Ox.ItemInput.js
Normal file
55
source/Ox.UI/js/List/Ox.ItemInput.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.ItemInput = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
type: 'textarea',
|
||||
value: '',
|
||||
height: 300,
|
||||
width: 100
|
||||
})
|
||||
.options(options || {}),
|
||||
$input;
|
||||
|
||||
that.append(
|
||||
$input = new Ox.Input({
|
||||
height: self.options.height,
|
||||
style: 'square',
|
||||
type: self.options.type,
|
||||
value: self.options.value,
|
||||
width: self.options.width + 6
|
||||
})
|
||||
.bind({
|
||||
mousedown: function(e) {
|
||||
// keep mousedown from reaching list
|
||||
e.stopPropagation();
|
||||
}
|
||||
})
|
||||
)
|
||||
.append(new Ox.Element()
|
||||
.append(new Ox.Button({type: 'text', title: 'Cancel'})
|
||||
.css('width', '42%')
|
||||
.bindEvent({
|
||||
'click': function() {
|
||||
that.triggerEvent('cancel');
|
||||
}
|
||||
}))
|
||||
.append(new Ox.Button({type: 'text', title: 'Save'})
|
||||
.css('width', '42%')
|
||||
.bindEvent({
|
||||
'click': function() {
|
||||
that.triggerEvent('save', {
|
||||
value: $input.value()
|
||||
});
|
||||
}
|
||||
}))
|
||||
.css({
|
||||
'margin-top': self.options.height-8,
|
||||
'height': '16px',
|
||||
'text-align': 'right',
|
||||
})
|
||||
);
|
||||
Ox.print($input);
|
||||
return that;
|
||||
}
|
||||
1423
source/Ox.UI/js/List/Ox.List.js
Normal file
1423
source/Ox.UI/js/List/Ox.List.js
Normal file
File diff suppressed because it is too large
Load diff
42
source/Ox.UI/js/List/Ox.ListItem.js
Normal file
42
source/Ox.UI/js/List/Ox.ListItem.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.ListItem = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
construct: function() {},
|
||||
data: {},
|
||||
draggable: false,
|
||||
position: 0,
|
||||
unique: ''
|
||||
})
|
||||
.options(options || {});
|
||||
|
||||
constructItem();
|
||||
|
||||
function constructItem(update) {
|
||||
var $element = self.options.construct(self.options.data)
|
||||
.addClass('OxItem')
|
||||
.attr({
|
||||
draggable: self.options.draggable
|
||||
})
|
||||
.data({
|
||||
id: self.options.data[self.options.unique],
|
||||
position: self.options.position
|
||||
});
|
||||
if (update) {
|
||||
that.$element.hasClass('OxSelected') && $element.addClass('OxSelected');
|
||||
that.$element.replaceWith($element);
|
||||
}
|
||||
that.$element = $element;
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'data') {
|
||||
constructItem(true);
|
||||
}
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
7
source/Ox.UI/js/List/Ox.ListPage.js
Normal file
7
source/Ox.UI/js/List/Ox.ListPage.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.ListPage = function(options, self) {
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.addClass('OxPage');
|
||||
return that;
|
||||
};
|
||||
720
source/Ox.UI/js/List/Ox.TextList.js
Normal file
720
source/Ox.UI/js/List/Ox.TextList.js
Normal file
|
|
@ -0,0 +1,720 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.TextList = function(options, self) {
|
||||
|
||||
// fixme: rename to TableList
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
columns: [],
|
||||
columnsMovable: false,
|
||||
columnsRemovable: false,
|
||||
columnsResizable: false,
|
||||
columnsVisible: false,
|
||||
columnWidth: [40, 800],
|
||||
id: '',
|
||||
items: null, // function() {} {sort, range, keys, callback} or array
|
||||
max: -1,
|
||||
min: 0,
|
||||
pageLength: 100,
|
||||
scrollbarVisible: false,
|
||||
selected: [],
|
||||
sort: []
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxTextList');
|
||||
|
||||
Ox.print('Ox.TextList self.options', self.options)
|
||||
|
||||
self.options.columns.forEach(function(v) { // fixme: can this go into a generic ox.js function?
|
||||
// fixme: and can't these just remain undefined?
|
||||
if (Ox.isUndefined(v.align)) {
|
||||
v.align = 'left';
|
||||
}
|
||||
if (Ox.isUndefined(v.clickable)) {
|
||||
v.clickable = false;
|
||||
}
|
||||
if (Ox.isUndefined(v.editable)) {
|
||||
v.editable = false;
|
||||
}
|
||||
if (Ox.isUndefined(v.unique)) {
|
||||
v.unique = false;
|
||||
}
|
||||
if (Ox.isUndefined(v.visible)) {
|
||||
v.visible = false;
|
||||
}
|
||||
if (v.unique) {
|
||||
self.unique = v.id;
|
||||
}
|
||||
});
|
||||
|
||||
$.extend(self, {
|
||||
columnPositions: [],
|
||||
defaultColumnWidths: $.map(self.options.columns, function(v) {
|
||||
return v.defaultWidth || v.width;
|
||||
}),
|
||||
itemHeight: 16,
|
||||
page: 0,
|
||||
pageLength: 100,
|
||||
scrollLeft: 0,
|
||||
selectedColumn: getColumnIndexById(self.options.sort[0].key),
|
||||
visibleColumns: $.map(self.options.columns, function(v) {
|
||||
return v.visible ? v : null;
|
||||
})
|
||||
});
|
||||
// fixme: there might be a better way than passing both visible and position
|
||||
self.options.columns.forEach(function(v) {
|
||||
if (!Ox.isUndefined(v.position)) {
|
||||
self.visibleColumns[v.position] = v;
|
||||
}
|
||||
})
|
||||
$.extend(self, {
|
||||
columnWidths: $.map(self.visibleColumns, function(v, i) {
|
||||
return v.width;
|
||||
}),
|
||||
pageHeight: self.options.pageLength * self.itemHeight
|
||||
});
|
||||
|
||||
self.format = {};
|
||||
self.options.columns.forEach(function(v, i) {
|
||||
if (v.format) {
|
||||
self.format[v.id] = v.format;
|
||||
}
|
||||
});
|
||||
|
||||
// Head
|
||||
|
||||
if (self.options.columnsVisible) {
|
||||
that.$bar = new Ox.Bar({
|
||||
orientation: 'horizontal',
|
||||
size: 16
|
||||
}).appendTo(that);
|
||||
that.$head = new Ox.Container()
|
||||
.addClass('OxHead')
|
||||
.css({
|
||||
right: self.options.scrollbarVisible ? Ox.UI.SCROLLBAR_SIZE + 'px' : 0
|
||||
})
|
||||
.appendTo(that.$bar);
|
||||
that.$head.$content.addClass('OxTitles');
|
||||
constructHead();
|
||||
if (self.options.columnsRemovable) {
|
||||
that.$select = new Ox.Select({
|
||||
id: self.options.id + 'SelectColumns',
|
||||
items: $.map(self.options.columns, function(v, i) {
|
||||
return {
|
||||
checked: v.visible,
|
||||
disabled: v.removable === false,
|
||||
id: v.id,
|
||||
title: v.title
|
||||
}
|
||||
}),
|
||||
max: -1,
|
||||
min: 1,
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent('change', changeColumns)
|
||||
.appendTo(that.$bar.$element);
|
||||
}
|
||||
}
|
||||
|
||||
// Body
|
||||
|
||||
that.$body = new Ox.List({
|
||||
construct: constructItem,
|
||||
id: self.options.id,
|
||||
items: self.options.items,
|
||||
itemHeight: 16,
|
||||
items: self.options.items,
|
||||
itemWidth: getItemWidth(),
|
||||
format: self.format, // fixme: not needed, happens in TextList
|
||||
keys: $.map(self.visibleColumns, function(v) {
|
||||
return v.id;
|
||||
}),
|
||||
max: self.options.max,
|
||||
min: self.options.min,
|
||||
pageLength: self.options.pageLength,
|
||||
paste: self.options.paste,
|
||||
orientation: 'vertical',
|
||||
selected: self.options.selected,
|
||||
sort: self.options.sort,
|
||||
sortable: self.options.sortable,
|
||||
type: 'text',
|
||||
unique: self.unique
|
||||
}, $.extend({}, self)) // pass event handler
|
||||
.addClass('OxBody')
|
||||
.css({
|
||||
top: (self.options.columnsVisible ? 16 : 0) + 'px',
|
||||
overflowY: (self.options.scrollbarVisible ? 'scroll' : 'hidden')
|
||||
})
|
||||
.scroll(function() {
|
||||
var scrollLeft = $(this).scrollLeft();
|
||||
if (scrollLeft != self.scrollLeft) {
|
||||
self.scrollLeft = scrollLeft;
|
||||
that.$head && that.$head.scrollLeft(scrollLeft);
|
||||
}
|
||||
})
|
||||
.bindEvent({
|
||||
edit: function(event, data) {
|
||||
that.editCell(data.id, data.key);
|
||||
},
|
||||
select: function(event, data) {
|
||||
self.options.selected = data.ids;
|
||||
}
|
||||
})
|
||||
.appendTo(that);
|
||||
that.$body.$content.css({
|
||||
width: getItemWidth() + 'px'
|
||||
});
|
||||
|
||||
//Ox.print('s.vC', self.visibleColumns)
|
||||
|
||||
function addColumn(id) {
|
||||
//Ox.print('addColumn', id);
|
||||
var column, ids,
|
||||
index = 0;
|
||||
Ox.forEach(self.options.columns, function(v) {
|
||||
if (v.visible) {
|
||||
index++;
|
||||
} else if (v.id == id) {
|
||||
column = v;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
column.visible = true;
|
||||
self.visibleColumns.splice(index, 0, column);
|
||||
self.columnWidths.splice(index, 0, column.width);
|
||||
that.$head.$content.empty();
|
||||
constructHead();
|
||||
that.$body.options({
|
||||
keys: $.map(self.visibleColumns, function(v, i) {
|
||||
return v.id;
|
||||
})
|
||||
});
|
||||
that.$body.reloadPages();
|
||||
}
|
||||
|
||||
function changeColumns(event, data) {
|
||||
var add,
|
||||
ids = [];
|
||||
Ox.forEach(data.selected, function(column) {
|
||||
var index = getColumnIndexById(column.id);
|
||||
if (!self.options.columns[index].visible) {
|
||||
addColumn(column.id);
|
||||
add = true;
|
||||
return false;
|
||||
}
|
||||
ids.push(column.id);
|
||||
});
|
||||
if (!add) {
|
||||
Ox.forEach(self.visibleColumns, function(column) {
|
||||
if (ids.indexOf(column.id) == -1) {
|
||||
removeColumn(column.id);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
triggerColumnChangeEvent();
|
||||
}
|
||||
|
||||
function clickColumn(id) {
|
||||
Ox.print('clickColumn', id);
|
||||
var i = getColumnIndexById(id),
|
||||
isSelected = self.options.sort[0].key == self.options.columns[i].id;
|
||||
that.sortList(
|
||||
self.options.columns[i].id, isSelected ?
|
||||
(self.options.sort[0].operator == '+' ? '-' : '+') :
|
||||
self.options.columns[i].operator
|
||||
);
|
||||
}
|
||||
|
||||
function constructHead() {
|
||||
var offset = 0;
|
||||
that.$titles = [];
|
||||
self.columnOffsets = [];
|
||||
self.visibleColumns.forEach(function(v, i) {
|
||||
var $order, $resize, $left, $center, $right;
|
||||
offset += self.columnWidths[i];
|
||||
self.columnOffsets[i] = offset - self.columnWidths[i] / 2;
|
||||
that.$titles[i] = new Ox.Element()
|
||||
.addClass('OxTitle OxColumn' + Ox.toTitleCase(v.id))
|
||||
.css({
|
||||
width: (self.columnWidths[i] - 9) + 'px',
|
||||
textAlign: v.align
|
||||
})
|
||||
.html(v.title)
|
||||
.bindEvent({
|
||||
anyclick: function(event, e) {
|
||||
clickColumn(v.id);
|
||||
},
|
||||
dragstart: function(event, e) {
|
||||
dragstartColumn(v.id, e);
|
||||
},
|
||||
drag: function(event, e) {
|
||||
dragColumn(v.id, e);
|
||||
},
|
||||
dragend: function(event, e) {
|
||||
dragendColumn(v.id, e);
|
||||
}
|
||||
})
|
||||
.appendTo(that.$head.$content.$element);
|
||||
$order = $('<div>')
|
||||
.addClass('OxOrder')
|
||||
.html(Ox.UI.symbols['triangle_' + (
|
||||
v.operator == '+' ? 'up' : 'down'
|
||||
)])
|
||||
.click(function() {
|
||||
$(this).prev().trigger('click')
|
||||
})
|
||||
.appendTo(that.$head.$content.$element);
|
||||
$resize = new Ox.Element()
|
||||
.addClass('OxResize')
|
||||
.appendTo(that.$head.$content.$element);
|
||||
if (self.options.columnsResizable) {
|
||||
$resize.addClass('OxResizable')
|
||||
.bindEvent({
|
||||
doubleclick: function(event, e) {
|
||||
resetColumn(v.id, e);
|
||||
},
|
||||
dragstart: function(event, e) {
|
||||
dragstartResize(v.id, e);
|
||||
},
|
||||
drag: function(event, e) {
|
||||
dragResize(v.id, e);
|
||||
},
|
||||
dragend: function(event, e) {
|
||||
dragendResize(v.id, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
$left = $('<div>').addClass('OxLeft').appendTo($resize.$element);
|
||||
$center = $('<div>').addClass('OxCenter').appendTo($resize.$element);
|
||||
$right = $('<div>').addClass('OxRight').appendTo($resize.$element);
|
||||
});
|
||||
that.$head.$content.css({
|
||||
width: (Ox.sum(self.columnWidths) + 2) + 'px'
|
||||
});
|
||||
//Ox.print('s.sC', self.selectedColumn)
|
||||
//Ox.print('s.cO', self.columnOffsets)
|
||||
if (getColumnPositionById(self.options.columns[self.selectedColumn].id) > -1) { // fixme: save in var
|
||||
toggleSelected(self.options.columns[self.selectedColumn].id);
|
||||
that.$titles[getColumnPositionById(self.options.columns[self.selectedColumn].id)].css({
|
||||
width: (self.options.columns[self.selectedColumn].width - 25) + 'px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function constructItem(data) {
|
||||
var $item = $('<div>')
|
||||
.addClass('OxTarget')
|
||||
.css({
|
||||
width: getItemWidth() + 'px'
|
||||
});
|
||||
self.visibleColumns.forEach(function(v, i) {
|
||||
var clickable = Ox.isBoolean(v.clickable) ? v.clickable : v.clickable(data),
|
||||
editable = Ox.isBoolean(v.editable) ? v.editable : v.editable(data),
|
||||
$cell = $('<div>')
|
||||
.addClass(
|
||||
'OxCell OxColumn' + Ox.toTitleCase(v.id) +
|
||||
(clickable ? ' OxClickable' : '') +
|
||||
(editable ? ' OxEditable' : '')
|
||||
)
|
||||
.css({
|
||||
width: (self.columnWidths[i] - (self.options.columnsVisible ? 9 : 8)) + 'px',
|
||||
borderRightWidth: (self.options.columnsVisible ? 1 : 0) + 'px',
|
||||
textAlign: v.align
|
||||
})
|
||||
.html(v.id in data ? formatValue(data[v.id], v.format) : '')
|
||||
.appendTo($item);
|
||||
});
|
||||
function formatValue(value, format) {
|
||||
if (value === null) {
|
||||
value = '';
|
||||
} else if (format) {
|
||||
value = Ox.isObject(format) ?
|
||||
Ox['format' + Ox.toTitleCase(format.type)]
|
||||
.apply(this, $.merge([value], format.args)) :
|
||||
format(value);
|
||||
} else if (Ox.isArray(value)) {
|
||||
value = value.join(', ');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
//Math.random() < 0.01 && Ox.print('item', data, $item);
|
||||
return $item;
|
||||
}
|
||||
|
||||
function dragstartColumn(id, e) {
|
||||
self.drag = {
|
||||
startX: e.clientX,
|
||||
startPos: getColumnPositionById(id)
|
||||
}
|
||||
$.extend(self.drag, {
|
||||
stopPos: self.drag.startPos,
|
||||
offsets: $.map(self.visibleColumns, function(v, i) {
|
||||
return self.columnOffsets[i] - self.columnOffsets[self.drag.startPos]
|
||||
})
|
||||
});
|
||||
$('.OxColumn' + Ox.toTitleCase(id)).css({
|
||||
opacity: 0.25
|
||||
});
|
||||
that.$titles[self.drag.startPos].addClass('OxDrag').css({ // fixme: why does the class not work?
|
||||
cursor: 'move'
|
||||
});
|
||||
}
|
||||
|
||||
function dragColumn(id, e) {
|
||||
var d = e.clientX - self.drag.startX,
|
||||
pos = self.drag.stopPos;
|
||||
Ox.forEach(self.drag.offsets, function(v, i) {
|
||||
if (d < 0 && d < v) {
|
||||
self.drag.stopPos = i;
|
||||
return false;
|
||||
} else if (d > 0 && d > v) {
|
||||
self.drag.stopPos = i;
|
||||
}
|
||||
});
|
||||
if (self.drag.stopPos != pos) {
|
||||
moveColumn(id, self.drag.stopPos);
|
||||
}
|
||||
}
|
||||
|
||||
function dragendColumn(id, e) {
|
||||
var column = self.visibleColumns.splice(self.drag.stopPos, 1)[0],
|
||||
width = self.columnWidths.splice(self.drag.stopPos, 1)[0];
|
||||
self.visibleColumns.splice(self.drag.stopPos, 0, column);
|
||||
self.columnWidths.splice(self.drag.stopPos, 0, width);
|
||||
that.$head.$content.empty();
|
||||
constructHead();
|
||||
$('.OxColumn' + Ox.toTitleCase(id)).css({
|
||||
opacity: 1
|
||||
});
|
||||
that.$titles[self.drag.stopPos].removeClass('OxDrag').css({
|
||||
cursor: 'pointer'
|
||||
});
|
||||
that.$body.clearCache();
|
||||
triggerColumnChangeEvent();
|
||||
}
|
||||
|
||||
function dragstartResize(id, e) {
|
||||
var pos = getColumnPositionById(id);
|
||||
self.drag = {
|
||||
startX: e.clientX,
|
||||
startWidth: self.columnWidths[pos]
|
||||
};
|
||||
}
|
||||
|
||||
function dragResize(id, e) {
|
||||
var width = Ox.limit(
|
||||
self.drag.startWidth - self.drag.startX + e.clientX,
|
||||
self.options.columnWidth[0],
|
||||
self.options.columnWidth[1]
|
||||
);
|
||||
resizeColumn(id, width);
|
||||
}
|
||||
|
||||
function dragendResize(id, e) {
|
||||
var pos = getColumnPositionById(id);
|
||||
that.triggerEvent('columnresize', {
|
||||
id: id,
|
||||
width: self.columnWidths[pos]
|
||||
});
|
||||
}
|
||||
|
||||
function getCell(id, key) {
|
||||
Ox.print('getCell', id, key)
|
||||
var $item = getItem(id);
|
||||
return $($item.find('.OxCell.OxColumn' + Ox.toTitleCase(key))[0]);
|
||||
}
|
||||
|
||||
function getColumnIndexById(id) {
|
||||
return Ox.getPositionById(self.options.columns, id);
|
||||
}
|
||||
|
||||
function getColumnPositionById(id) {
|
||||
return Ox.getPositionById(self.visibleColumns, id);
|
||||
}
|
||||
|
||||
function getItem(id) {
|
||||
//Ox.print('getItem', id)
|
||||
var $item = null;
|
||||
$.each(that.find('.OxItem'), function(i, v) {
|
||||
$v = $(v);
|
||||
if ($v.data('id') == id) {
|
||||
$item = $v;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return $item;
|
||||
}
|
||||
|
||||
function getItemWidth() {
|
||||
return Math.max(
|
||||
Ox.sum(self.columnWidths),
|
||||
that.$element.width() -
|
||||
(self.options.scrollbarVisible ? Ox.UI.SCROLLBAR_SIZE : 0)
|
||||
);
|
||||
//return Ox.sum(self.columnWidths)
|
||||
}
|
||||
|
||||
function moveColumn(id, pos) {
|
||||
// fixme: column head should be one element, not three
|
||||
//Ox.print('moveColumn', id, pos)
|
||||
var startPos = getColumnPositionById(id),
|
||||
stopPos = pos,
|
||||
startClassName = '.OxColumn' + Ox.toTitleCase(id),
|
||||
stopClassName = '.OxColumn' + Ox.toTitleCase(self.visibleColumns[stopPos].id),
|
||||
insert = startPos < stopPos ? 'insertAfter' : 'insertBefore'
|
||||
$column = $('.OxTitle' + startClassName),
|
||||
$order = $column.next(),
|
||||
$resize = $order.next();
|
||||
//Ox.print(startClassName, insert, stopClassName)
|
||||
$column.detach()[insert](insert == 'insertAfter' ? $('.OxTitle' + stopClassName).next().next() : $('.OxTitle' + stopClassName));
|
||||
$order.detach().insertAfter($column);
|
||||
$resize.detach().insertAfter($order);
|
||||
$.each(that.$body.find('.OxItem'), function(i, v) {
|
||||
var $v = $(v);
|
||||
$v.children(startClassName).detach()[insert]($v.children(stopClassName));
|
||||
});
|
||||
var column = self.visibleColumns.splice(startPos, 1)[0],
|
||||
width = self.columnWidths.splice(startPos, 1)[0];
|
||||
self.visibleColumns.splice(stopPos, 0, column);
|
||||
self.columnWidths.splice(stopPos, 0, width);
|
||||
}
|
||||
|
||||
function removeColumn(id) {
|
||||
//Ox.print('removeColumn', id);
|
||||
var className = '.OxColumn' + Ox.toTitleCase(id),
|
||||
index = getColumnIndexById(id),
|
||||
itemWidth,
|
||||
position = getColumnPositionById(id),
|
||||
$column = $('.OxTitle' + className),
|
||||
$order = $column.next(),
|
||||
$resize = $order.next();
|
||||
self.options.columns[index].visible = false;
|
||||
self.visibleColumns.splice(position, 1);
|
||||
self.columnWidths.splice(position, 1);
|
||||
that.$head.$content.empty();
|
||||
constructHead();
|
||||
itemWidth = getItemWidth();
|
||||
$.each(that.$body.find('.OxItem'), function(i, v) {
|
||||
var $v = $(v);
|
||||
$v.children(className).remove();
|
||||
$v.css({
|
||||
width: itemWidth + 'px'
|
||||
});
|
||||
});
|
||||
that.$body.$content.css({
|
||||
width: itemWidth + 'px'
|
||||
});
|
||||
that.$body.options({
|
||||
keys: $.map(self.visibleColumns, function(v, i) {
|
||||
return v.id;
|
||||
})
|
||||
});
|
||||
//that.$body.clearCache();
|
||||
}
|
||||
|
||||
function resetColumn(id) {
|
||||
var width = self.defaultColumnWidths[getColumnIndexById(id)];
|
||||
resizeColumn(id, width);
|
||||
that.triggerEvent('columnresize', {
|
||||
id: id,
|
||||
width: width
|
||||
});
|
||||
}
|
||||
|
||||
function resizeColumn(id, width) {
|
||||
var i = getColumnIndexById(id),
|
||||
pos = getColumnPositionById(id);
|
||||
self.options.columns[i].width = width;
|
||||
self.columnWidths[pos] = width;
|
||||
if (self.options.columnsVisible) {
|
||||
that.$head.$content.css({
|
||||
width: (Ox.sum(self.columnWidths) + 2) + 'px'
|
||||
});
|
||||
that.$titles[pos].css({
|
||||
width: (width - 9 - (i == self.selectedColumn ? 16 : 0)) + 'px'
|
||||
});
|
||||
}
|
||||
that.find('.OxCell.OxColumn' + Ox.toTitleCase(self.options.columns[i].id)).css({
|
||||
width: (width - (self.options.columnsVisible ? 9 : 8)) + 'px'
|
||||
});
|
||||
setWidth();
|
||||
}
|
||||
|
||||
function setWidth() {
|
||||
var width = getItemWidth();
|
||||
that.$body.$content.find('.OxItem').css({ // fixme: can we avoid this lookup?
|
||||
width: width + 'px'
|
||||
});
|
||||
that.$body.$content.css({
|
||||
width: width + 'px' // fixme: check if scrollbar visible, and listen to resize/toggle event
|
||||
});
|
||||
}
|
||||
|
||||
function toggleSelected(id) {
|
||||
var pos = getColumnPositionById(id);
|
||||
if (pos > -1) {
|
||||
updateOrder(id);
|
||||
pos > 0 && that.$titles[pos].prev().children().eq(2).toggleClass('OxSelected');
|
||||
that.$titles[pos].toggleClass('OxSelected');
|
||||
that.$titles[pos].next().toggleClass('OxSelected');
|
||||
that.$titles[pos].next().next().children().eq(0).toggleClass('OxSelected');
|
||||
that.$titles[pos].css({
|
||||
width: (
|
||||
that.$titles[pos].width() + (that.$titles[pos].hasClass('OxSelected') ? -16 : 16)
|
||||
) + 'px'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function triggerColumnChangeEvent() {
|
||||
that.triggerEvent('columnchange', {
|
||||
ids: $.map(self.visibleColumns, function(v, i) {
|
||||
return v.id;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function updateOrder(id) {
|
||||
var pos = getColumnPositionById(id);
|
||||
//Ox.print(id, pos)
|
||||
that.$titles[pos].next().html(Ox.UI.symbols[
|
||||
'triangle_' + (self.options.sort[0].operator == '+' ? 'up' : 'down')
|
||||
]);
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'items') {
|
||||
//alert('request set!!')
|
||||
that.$body.options(key, value);
|
||||
} else if (key == 'paste') {
|
||||
that.$body.options(key, value);
|
||||
} else if (key == 'selected') {
|
||||
that.$body.options(key, value);
|
||||
}
|
||||
};
|
||||
|
||||
// fixme: doesn't work, doesn't return that
|
||||
that.closePreview = that.$body.closePreview;
|
||||
|
||||
that.editCell = function(id, key) {
|
||||
Ox.print('editCell', id, key)
|
||||
var $item = getItem(id),
|
||||
$cell = getCell(id, key),
|
||||
$input,
|
||||
html = $cell.html(),
|
||||
index = getColumnIndexById(key),
|
||||
column = self.options.columns[index],
|
||||
width = column.width - self.options.columnsVisible;
|
||||
$cell.empty()
|
||||
.addClass('OxEdit')
|
||||
.css({
|
||||
width: width + 'px'
|
||||
});
|
||||
$input = new Ox.Input({
|
||||
autovalidate: column.input ? column.input.autovalidate : null,
|
||||
style: 'square',
|
||||
value: html,
|
||||
width: width
|
||||
})
|
||||
.bind({
|
||||
mousedown: function(e) {
|
||||
// keep mousedown from reaching list
|
||||
e.stopPropagation();
|
||||
}
|
||||
})
|
||||
.bindEvent({
|
||||
blur: submit,
|
||||
})
|
||||
.appendTo($cell);
|
||||
//.focusInput();
|
||||
setTimeout($input.focusInput, 0); // fixme: strange
|
||||
function submit() {
|
||||
var value = $input.value();
|
||||
//$input.loseFocus().remove();
|
||||
// fixme: leaky, inputs remain in focus stack
|
||||
$cell.removeClass('OxEdit')
|
||||
.css({
|
||||
width: (width - 8) + 'px'
|
||||
})
|
||||
.html(value)
|
||||
that.triggerEvent('submit', {
|
||||
id: id,
|
||||
key: key,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
that.gainFocus = function() {
|
||||
that.$body.gainFocus();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.loseFocus = function() {
|
||||
that.$body.loseFocus();
|
||||
return that;
|
||||
}
|
||||
|
||||
that.paste = function(data) {
|
||||
that.$body.paste();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.reloadList = function() {
|
||||
that.$body.reloadList();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.resizeColumn = function(id, width) {
|
||||
resizeColumn(id, width);
|
||||
return that;
|
||||
}
|
||||
|
||||
that.size = function() {
|
||||
setWidth();
|
||||
that.$body.size();
|
||||
}
|
||||
|
||||
that.sortList = function(key, operator) {
|
||||
var isSelected = key == self.options.sort[0].key;
|
||||
self.options.sort = [{key: key, operator: operator}];
|
||||
if (self.options.columnsVisible) {
|
||||
if (isSelected) {
|
||||
updateOrder(self.options.columns[self.selectedColumn].id);
|
||||
} else {
|
||||
toggleSelected(self.options.columns[self.selectedColumn].id);
|
||||
self.selectedColumn = getColumnIndexById(key);
|
||||
toggleSelected(self.options.columns[self.selectedColumn].id);
|
||||
}
|
||||
}
|
||||
that.$body.sortList(self.options.sort[0].key, self.options.sort[0].operator);
|
||||
return that;
|
||||
};
|
||||
|
||||
that.value = function(id, key, value) {
|
||||
// fixme: make this accept id, {k: v, ...}
|
||||
var $item = getItem(id),
|
||||
//$cell = getCell(id, key),
|
||||
column = self.options.columns[getColumnIndexById(key)];
|
||||
if (arguments.length == 1) {
|
||||
return that.$body.value(id);
|
||||
} else if (arguments.length == 2) {
|
||||
return that.$body.value(id, key);
|
||||
} else {
|
||||
that.$body.value(id, key, value);
|
||||
/*
|
||||
$cell && $cell.html(column.format ? column.format(value) : value);
|
||||
if (column.unique) {
|
||||
that.$body.setId($item.data('id'), value);
|
||||
$item.data({id: value});
|
||||
}
|
||||
*/
|
||||
return that;
|
||||
}
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
197
source/Ox.UI/js/List/Ox.TreeList.js
Normal file
197
source/Ox.UI/js/List/Ox.TreeList.js
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.TreeList = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
data: null,
|
||||
items: [],
|
||||
max: -1,
|
||||
min: 0,
|
||||
selected: [],
|
||||
width: 256
|
||||
})
|
||||
.options(options || {});
|
||||
|
||||
if (self.options.data) {
|
||||
self.options.items = [];
|
||||
//Ox.print('d', self.options.data, 'i', self.options.items)
|
||||
Ox.forEach(self.options.data, function(value, key) {
|
||||
self.options.items.push(parseData(key, value));
|
||||
});
|
||||
//Ox.print('d', self.options.data, 'i', self.options.items)
|
||||
}
|
||||
|
||||
that.$element = new Ox.List({
|
||||
construct: constructItem,
|
||||
itemHeight: 16,
|
||||
items: parseItems(),
|
||||
itemWidth: self.options.width,
|
||||
max: self.options.max,
|
||||
min: self.options.min,
|
||||
unique: 'id',
|
||||
}, $.extend({}, self))
|
||||
.addClass('OxTextList OxTreeList')
|
||||
.css({
|
||||
width: self.options.width + 'px'
|
||||
})
|
||||
.click(clickItem)
|
||||
.bindEvent({
|
||||
toggle: toggleItems
|
||||
});
|
||||
|
||||
function clickItem(e) {
|
||||
var $target = $(e.target),
|
||||
$item, id, item;
|
||||
if ($target.hasClass('OxToggle')) {
|
||||
$item = $target.parent().parent();
|
||||
id = $item.data('id');
|
||||
item = getItemById(id);
|
||||
toggleItem(item, !item.expanded)
|
||||
}
|
||||
}
|
||||
|
||||
function constructItem(data) {
|
||||
var $item = $('<div>'),
|
||||
padding = (data.level + !data.items) * 16 - 8;
|
||||
if (data.level || !data.items) {
|
||||
$('<div>')
|
||||
.addClass('OxCell OxTarget')
|
||||
.css({
|
||||
width: padding + 'px',
|
||||
})
|
||||
.appendTo($item);
|
||||
}
|
||||
if (data.items) {
|
||||
$('<div>')
|
||||
.addClass('OxCell')
|
||||
.css({
|
||||
width: '8px',
|
||||
})
|
||||
.append(
|
||||
// fixme: need Ox.Icon()
|
||||
$('<img>')
|
||||
.addClass('OxToggle')
|
||||
.attr({
|
||||
src: Ox.UI.getImagePath(
|
||||
'symbol' + (data.expanded ? 'Down' : 'Right') + '.svg'
|
||||
)
|
||||
})
|
||||
.css({
|
||||
width: '10px',
|
||||
height: '10px',
|
||||
padding: '3px'
|
||||
})
|
||||
)
|
||||
.appendTo($item);
|
||||
}
|
||||
$('<div>')
|
||||
.addClass('OxCell OxTarget')
|
||||
.css({
|
||||
width: (self.options.width - padding - 32 + !data.items * 16) + 'px'
|
||||
})
|
||||
.html(data.title)
|
||||
.appendTo($item);
|
||||
return $item;
|
||||
}
|
||||
|
||||
function getItemById(id, items, level) {
|
||||
var items = items || self.options.items,
|
||||
level = level || 0,
|
||||
ret = null;
|
||||
Ox.forEach(items, function(item) {
|
||||
if (item.id == id) {
|
||||
ret = $.extend(item, {
|
||||
level: level
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (item.items) {
|
||||
ret = getItemById(id, item.items, level + 1);
|
||||
if (ret) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function parseData(key, value) {
|
||||
//Ox.print('parseData', key, value)
|
||||
var ret = {
|
||||
id: key,
|
||||
title: key.toString().split('.').pop()
|
||||
},
|
||||
type = Ox.typeOf(value);
|
||||
if (type == 'array' || type == 'object') {
|
||||
ret.title += ': ' + Ox.toTitleCase(Ox.typeOf(value));
|
||||
ret.items = Ox.map(Ox.sort(Ox.keys(value)), function(k) {
|
||||
return parseData(key + '.' + k, value[k]);
|
||||
});
|
||||
} else {
|
||||
ret.title += ': ' + (
|
||||
type == 'function' ?
|
||||
value.toString().split('{')[0] :
|
||||
JSON.stringify(value)
|
||||
)
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function parseItems(items, level) {
|
||||
var items = items || self.options.items,
|
||||
level = level || 0,
|
||||
ret = [];
|
||||
items.forEach(function(item, i) {
|
||||
var item_ = $.extend({
|
||||
level: level
|
||||
}, item, item.items ? {
|
||||
items: !!item.expanded ?
|
||||
parseItems(item.items, level + 1) : []
|
||||
} : {});
|
||||
ret.push(item_);
|
||||
item.items && $.merge(ret, item_.items);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function toggleItem(item, expanded) {
|
||||
var $img, $item, pos;
|
||||
item.expanded = expanded;
|
||||
$.each(that.$element.find('.OxItem'), function(i, v) {
|
||||
var $item = $(v);
|
||||
if ($item.data('id') == item.id) {
|
||||
$img = $item.find('img');
|
||||
pos = $item.data('position');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
Ox.print('i.e', item.expanded)
|
||||
$img.attr({
|
||||
src: Ox.UI.getImagePath(
|
||||
'symbol' + (item.expanded ? 'Down' : 'Right') + '.svg'
|
||||
)
|
||||
});
|
||||
item.expanded ?
|
||||
that.$element.addItems(pos + 1, parseItems(item.items, item.level + 1)) :
|
||||
that.$element.removeItems(pos + 1, parseItems(item.items, item.level + 1).length);
|
||||
}
|
||||
|
||||
function toggleItems(event, data) {
|
||||
data.ids.forEach(function(id, i) {
|
||||
var item = getItemById(id);
|
||||
if (item.items && data.expanded != !!item.expanded) {
|
||||
toggleItem(item, data.expanded);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'data') {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
370
source/Ox.UI/js/Map/Ox.ListMap.js
Normal file
370
source/Ox.UI/js/Map/Ox.ListMap.js
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.ListMap = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
addPlace: null,
|
||||
height: 256,
|
||||
labels: false,
|
||||
places: null,
|
||||
selected: [],
|
||||
width: 256
|
||||
})
|
||||
.addClass('OxListMap')
|
||||
.options(options || {})
|
||||
.css({
|
||||
width: self.options.width + 'px',
|
||||
height: self.options.height + 'px'
|
||||
});
|
||||
|
||||
self.columns = [
|
||||
{
|
||||
addable: false, // fixme: implement
|
||||
id: 'id',
|
||||
unique: true,
|
||||
visible: false
|
||||
},
|
||||
{
|
||||
editable: true,
|
||||
id: 'name',
|
||||
operator: '+',
|
||||
removable: false,
|
||||
title: 'Name',
|
||||
visible: true,
|
||||
width: 144
|
||||
},
|
||||
{
|
||||
editable: true,
|
||||
id: 'geoname',
|
||||
removable: false,
|
||||
operator: '+',
|
||||
title: 'Geoname',
|
||||
visible: true,
|
||||
width: 192
|
||||
},
|
||||
{
|
||||
format: function(value) {
|
||||
return $('<img>')
|
||||
.attr({
|
||||
// fixme: not the right place to do this
|
||||
src: Ox.PATH + 'Ox.Geo/png/icons/16/' + (value || 'NTHH') + '.png'
|
||||
})
|
||||
.css({
|
||||
width: '14px',
|
||||
height: '14px'
|
||||
});
|
||||
/*
|
||||
.css({
|
||||
width: '21px',
|
||||
height: '14px'
|
||||
})
|
||||
.load(function() {
|
||||
var $this = $(this);
|
||||
Ox.print($this.width() / $this.height())
|
||||
$this.css({
|
||||
width: Math.round(14 * $this.width() / $this.height()) + 'px',
|
||||
height: '14px',
|
||||
padding: '1px 0 0 1px'
|
||||
});
|
||||
});
|
||||
*/
|
||||
},
|
||||
id: 'countryCode',
|
||||
operator: '+',
|
||||
title: 'Flag',
|
||||
visible: true,
|
||||
width: 48
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
format: {type: 'area', args: [0]},
|
||||
id: 'size',
|
||||
operator: '-',
|
||||
title: 'Size',
|
||||
visible: true,
|
||||
width: 128
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
format: toFixed,
|
||||
id: 'lat',
|
||||
operator: '+',
|
||||
title: 'Latitude',
|
||||
visible: true,
|
||||
width: 96
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
format: toFixed,
|
||||
id: 'lng',
|
||||
operator: '+',
|
||||
title: 'Longitude',
|
||||
visible: true,
|
||||
width: 96
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
format: toFixed,
|
||||
id: 'south',
|
||||
operator: '+',
|
||||
title: 'South',
|
||||
visible: false,
|
||||
width: 96
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
id: 'west',
|
||||
operator: '+',
|
||||
title: 'West',
|
||||
visible: false,
|
||||
width: 96
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
format: toFixed,
|
||||
id: 'north',
|
||||
operator: '+',
|
||||
title: 'North',
|
||||
visible: false,
|
||||
width: 96
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
format: toFixed,
|
||||
id: 'east',
|
||||
operator: '+',
|
||||
title: 'East',
|
||||
visible: false,
|
||||
width: 96
|
||||
},
|
||||
{
|
||||
id: 'user',
|
||||
operator: '+',
|
||||
title: 'User',
|
||||
visible: false,
|
||||
width: 96
|
||||
},
|
||||
{
|
||||
format: 'date',
|
||||
id: 'created',
|
||||
operator: '-',
|
||||
title: 'Date Created',
|
||||
visible: false,
|
||||
width: 96,
|
||||
},
|
||||
{
|
||||
format: 'date',
|
||||
id: 'modified',
|
||||
operator: '-',
|
||||
title: 'Date Modified',
|
||||
visible: false,
|
||||
width: 96,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
id: 'matches',
|
||||
operator: '-',
|
||||
title: 'Matches',
|
||||
visible: false,
|
||||
width: 96,
|
||||
}
|
||||
];
|
||||
|
||||
self.$toolbar = new Ox.Bar({
|
||||
size: 24
|
||||
});
|
||||
|
||||
self.$findElement = new Ox.FormElementGroup({
|
||||
elements: [
|
||||
self.$findSelect = new Ox.Select({
|
||||
items: [
|
||||
{id: 'all', title: 'Find: All'},
|
||||
{id: 'name', title: 'Find: Name'},
|
||||
{id: 'geoname', title: 'Find: Geoname'},
|
||||
{id: 'country', title: 'Find: Country'}
|
||||
],
|
||||
overlap: 'right',
|
||||
width: 128
|
||||
}),
|
||||
self.$findInput = new Ox.Input({
|
||||
clear: true,
|
||||
width: 192
|
||||
})
|
||||
]
|
||||
})
|
||||
.css({float: 'right', margin: '4px'})
|
||||
.appendTo(self.$toolbar)
|
||||
|
||||
self.$list = new Ox.TextList({
|
||||
columns: self.columns,
|
||||
columnsRemovable: true,
|
||||
columnsVisible: true,
|
||||
items: self.options.places,
|
||||
pageLength: 100,
|
||||
scrollbarVisible: true,
|
||||
sort: [
|
||||
{key: 'name', operator: '+'}
|
||||
]
|
||||
})
|
||||
.bindEvent({
|
||||
'delete': removeItem,
|
||||
init: initList,
|
||||
load: function() {
|
||||
that.triggerEvent('loadlist');
|
||||
},
|
||||
open: openItem,
|
||||
select: selectItem
|
||||
});
|
||||
|
||||
self.$statusbar = new Ox.Bar({
|
||||
size: 24
|
||||
});
|
||||
|
||||
self.$status = new Ox.Element()
|
||||
.css({paddingTop: '4px', margin: 'auto', textAlign: 'center'})
|
||||
.appendTo(self.$statusbar);
|
||||
|
||||
self.mapResize = [
|
||||
Math.round(self.options.width * 0.25),
|
||||
Math.round(self.options.width * 0.5),
|
||||
Math.round(self.options.width * 0.75)
|
||||
];
|
||||
|
||||
if (Ox.isArray(self.options.places)) {
|
||||
init(self.options.places)
|
||||
} else {
|
||||
self.options.places({}, function(result) {
|
||||
Ox.print('$$$$', result.data.items)
|
||||
self.options.places({
|
||||
keys: self.columns.map(function(column) {
|
||||
return column.id
|
||||
}),
|
||||
range: [0, result.data.items]
|
||||
}, function(result) {
|
||||
Ox.print('DATA', result)
|
||||
init(result.data.items);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function init(places) {
|
||||
//Ox.print('PLACES', places)
|
||||
self.$map = new Ox.Map({
|
||||
clickable: true,
|
||||
editable: true,
|
||||
findPlaceholder: 'Find on Map',
|
||||
height: self.options.height,
|
||||
places: places,
|
||||
statusbar: true,
|
||||
toolbar: true,
|
||||
width: self.mapResize[1],
|
||||
zoombar: true
|
||||
})
|
||||
.bindEvent({
|
||||
addplace: function(event, data) {
|
||||
that.triggerEvent('addplace', data);
|
||||
},
|
||||
resize: function() {
|
||||
self.$map.resizeMap(); // fixme: don't need event
|
||||
},
|
||||
selectplace: selectPlace
|
||||
});
|
||||
that.$element.replaceWith(
|
||||
that.$element = new Ox.SplitPanel({
|
||||
elements: [
|
||||
{
|
||||
element: new Ox.SplitPanel({
|
||||
elements: [
|
||||
{
|
||||
element: self.$toolbar,
|
||||
size: 24
|
||||
},
|
||||
{
|
||||
element: self.$list
|
||||
},
|
||||
{
|
||||
element: self.$statusbar,
|
||||
size: 24
|
||||
}
|
||||
],
|
||||
orientation: 'vertical'
|
||||
})
|
||||
},
|
||||
{
|
||||
element: self.$map,
|
||||
resizable: true,
|
||||
resize: self.mapResize,
|
||||
size: self.mapResize[1]
|
||||
}
|
||||
],
|
||||
orientation: 'horizontal'
|
||||
}).$element
|
||||
);
|
||||
}
|
||||
|
||||
function initList(event, data) {
|
||||
self.$status.html(data.items + ' place' + (data.items == 1 ? '' : 's'))
|
||||
}
|
||||
|
||||
function openItem(event, data) {
|
||||
selectItem(event, data);
|
||||
self.$map.zoomToPlace(data.ids[0]);
|
||||
}
|
||||
|
||||
function removeItem(event, data) {
|
||||
var id = data.ids[0];
|
||||
that.triggerEvent('removeplace', {id: id});
|
||||
self.$map.removePlace(id);
|
||||
}
|
||||
|
||||
function selectItem(event, data) {
|
||||
Ox.print('selectItem', data.ids[0])
|
||||
var id = data.ids.length ? data.ids[0] : null;
|
||||
self.$map.options({selected: id});
|
||||
id && self.$map.panToPlace();
|
||||
}
|
||||
|
||||
function selectPlace(event, data) {
|
||||
Ox.print('selectPlace', data, data.id)
|
||||
data.id && data.id[0] != '_' && self.$list.options({
|
||||
selected: data.id ? [data.id] : []
|
||||
});
|
||||
}
|
||||
|
||||
function toFixed(val) {
|
||||
return val.toFixed(8);
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
Ox.print('ONCHANGE')
|
||||
if (key == 'height' || key == 'width') {
|
||||
Ox.print('ONCHANGE...')
|
||||
self.$map.options({
|
||||
height: self.options.height,
|
||||
width: self.options.width
|
||||
})
|
||||
} else if (key == 'selected') {
|
||||
self.$list.options({selected: value});
|
||||
}
|
||||
}
|
||||
|
||||
that.focusList = function() {
|
||||
self.$list.gainFocus();
|
||||
return that;
|
||||
}
|
||||
|
||||
that.reloadList = function() {
|
||||
self.$list.reloadList();
|
||||
return that;
|
||||
}
|
||||
|
||||
that.resizeMap = function() {
|
||||
Ox.print('Ox.ListMap.resizeMap()')
|
||||
self.$map.resizeMap();
|
||||
return that;
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
954
source/Ox.UI/js/Map/Ox.Map.js
Normal file
954
source/Ox.UI/js/Map/Ox.Map.js
Normal file
|
|
@ -0,0 +1,954 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
|
||||
Ox.Map = function(options, self) {
|
||||
|
||||
self = self || {};
|
||||
var that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
// fixme: isClickable? hasZoombar?
|
||||
clickable: false,
|
||||
editable: false,
|
||||
findPlaceholder: 'Find',
|
||||
labels: false,
|
||||
markers: 100,
|
||||
places: [],
|
||||
selected: null,
|
||||
statusbar: false,
|
||||
toolbar: false,
|
||||
zoombar: false
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxMap')
|
||||
.click(function(e) {
|
||||
!$(e.target).is('input') && that.gainFocus();
|
||||
})
|
||||
.bindEvent({
|
||||
key_0: function() {
|
||||
that.panToPlace()
|
||||
},
|
||||
key_down: function() {
|
||||
pan(0, 1);
|
||||
},
|
||||
key_enter: pressEnter,
|
||||
key_escape: pressEscape,
|
||||
key_equal: function() {
|
||||
zoom(1);
|
||||
},
|
||||
key_l: toggleLabels,
|
||||
key_left: function() {
|
||||
pan(-1, 0);
|
||||
},
|
||||
key_meta: function() {
|
||||
self.metaKey = true;
|
||||
$(document).one({
|
||||
keyup: function() {
|
||||
self.metaKey = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
key_minus: function() {
|
||||
zoom(-1);
|
||||
},
|
||||
key_right: function() {
|
||||
pan(1, 0);
|
||||
},
|
||||
key_shift: function() {
|
||||
self.shiftKey = true;
|
||||
$(document).one({
|
||||
keyup: function() {
|
||||
self.shiftKey = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
key_shift_down: function() {
|
||||
pan(0, 2);
|
||||
},
|
||||
key_shift_0: function() {
|
||||
that.zoomToPlace();
|
||||
},
|
||||
key_shift_equal: function() {
|
||||
zoom(2)
|
||||
},
|
||||
key_shift_left: function() {
|
||||
pan(-2, 0);
|
||||
},
|
||||
key_shift_minus: function() {
|
||||
zoom(-2);
|
||||
},
|
||||
key_shift_right: function() {
|
||||
pan(2, 0);
|
||||
},
|
||||
key_shift_up: function() {
|
||||
pan(0, -2);
|
||||
},
|
||||
key_up: function() {
|
||||
pan(0, -1);
|
||||
},
|
||||
key_z: undo
|
||||
});
|
||||
|
||||
self.mapHeight = getMapHeight();
|
||||
self.minZoom = getMinZoom();
|
||||
self.scaleMeters = [
|
||||
50000000, 20000000, 10000000,
|
||||
5000000, 2000000, 1000000,
|
||||
500000, 200000, 100000,
|
||||
50000, 20000, 10000,
|
||||
5000, 2000, 1000,
|
||||
500, 200, 100,
|
||||
50, 20, 10
|
||||
];
|
||||
|
||||
Ox.extend(self, {
|
||||
metaKey: false,
|
||||
resultPlace: null,
|
||||
shiftKey: false
|
||||
});
|
||||
|
||||
if (self.options.toolbar) {
|
||||
self.$toolbar = new Ox.Bar({
|
||||
size: 24
|
||||
})
|
||||
.appendTo(that);
|
||||
self.$labelsButton = new Ox.Button({
|
||||
title: 'Show Labels',
|
||||
width: 96
|
||||
})
|
||||
.css({float: 'left', margin: '4px'})
|
||||
.bindEvent({
|
||||
click: toggleLabels
|
||||
})
|
||||
.appendTo(self.$toolbar)
|
||||
self.$findInput = new Ox.Input({
|
||||
clear: true,
|
||||
placeholder: self.options.findPlaceholder,
|
||||
width: 192
|
||||
})
|
||||
.css({float: 'right', margin: '4px'})
|
||||
.bindEvent({
|
||||
submit: submitFind
|
||||
})
|
||||
.appendTo(self.$toolbar)
|
||||
}
|
||||
|
||||
self.$map = new Ox.Element('div')
|
||||
.css({
|
||||
left: 0,
|
||||
top: self.options.toolbar * 24 + 'px',
|
||||
right: 0,
|
||||
bottom: self.options.zoombar * 16 + self.options.statusbar * 24 + 'px'
|
||||
})
|
||||
.appendTo(that);
|
||||
|
||||
if (self.options.zoombar) {
|
||||
self.$zoombar = new Ox.Bar({
|
||||
size: 16
|
||||
})
|
||||
.css({
|
||||
bottom: self.options.statusbar * 24 + 'px'
|
||||
})
|
||||
.appendTo(that);
|
||||
}
|
||||
|
||||
if (self.options.statusbar) {
|
||||
self.$statusbar = new Ox.Bar({
|
||||
size: 24
|
||||
})
|
||||
.css({
|
||||
bottom: 0
|
||||
})
|
||||
.appendTo(that);
|
||||
self.$placeNameInput = new Ox.Input({
|
||||
placeholder: 'Name',
|
||||
width: 96
|
||||
})
|
||||
//.css({position: 'absolute', left: 4, top: 4})
|
||||
.css({float: 'left', margin: '4px 1px 4px 4px'})
|
||||
.appendTo(self.$statusbar);
|
||||
self.$placeGeonameInput = new Ox.Input({
|
||||
placeholder: 'Geoname',
|
||||
width: 96
|
||||
})
|
||||
//.css({position: 'absolute', left: 104, top: 4})
|
||||
.css({float: 'left', margin: '4px 1px 4px 4px'})
|
||||
.appendTo(self.$statusbar);
|
||||
self.$placeButton = new Ox.Button({
|
||||
title: 'New Place',
|
||||
width: 96
|
||||
})
|
||||
.css({float: 'right', margin: '4px 4px 4px 2px'})
|
||||
.bindEvent({
|
||||
click: clickPlaceButton
|
||||
})
|
||||
.appendTo(self.$statusbar);
|
||||
}
|
||||
|
||||
self.$navigationButtons = {
|
||||
'center': new Ox.Button({
|
||||
title: 'close',
|
||||
type: 'image'
|
||||
})
|
||||
.addClass('OxMapButton')
|
||||
.css({
|
||||
left: '24px',
|
||||
top: '24px'
|
||||
}),
|
||||
'east': new Ox.Button({
|
||||
title: 'right',
|
||||
type: 'image'
|
||||
})
|
||||
.addClass('OxMapButton')
|
||||
.css({
|
||||
left: '44px',
|
||||
top: '24px',
|
||||
}),
|
||||
'north': new Ox.Button({
|
||||
title: 'up',
|
||||
type: 'image'
|
||||
})
|
||||
.addClass('OxMapButton')
|
||||
.css({
|
||||
left: '24px',
|
||||
top: '4px',
|
||||
}),
|
||||
'south': new Ox.Button({
|
||||
title: 'down',
|
||||
type: 'image'
|
||||
})
|
||||
.addClass('OxMapButton')
|
||||
.css({
|
||||
left: '24px',
|
||||
top: '44px',
|
||||
}),
|
||||
'west': new Ox.Button({
|
||||
title: 'left',
|
||||
type: 'image'
|
||||
})
|
||||
.addClass('OxMapButton')
|
||||
.css({
|
||||
left: '4px',
|
||||
top: '24px',
|
||||
})
|
||||
};
|
||||
|
||||
self.$scaleLabel = new Ox.Label({
|
||||
textAlign: 'center',
|
||||
title: '...'
|
||||
})
|
||||
.addClass('OxMapLabel')
|
||||
.css({
|
||||
right: '4px',
|
||||
top: '4px'
|
||||
});
|
||||
|
||||
if (!window.googleCallback) {
|
||||
window.googleCallback = function() {
|
||||
delete window.googleCallback;
|
||||
initMap();
|
||||
};
|
||||
$.getScript('http://maps.google.com/maps/api/js?callback=googleCallback&sensor=false');
|
||||
} else {
|
||||
(function interval() {
|
||||
window.google ? initMap() : setTimeout(interval, 100);
|
||||
}());
|
||||
}
|
||||
|
||||
function addPlaceToMap(place) {
|
||||
// via find, click, or new place button
|
||||
var exists = false;
|
||||
if (!place) {
|
||||
var bounds = self.map.getBounds(),
|
||||
center = self.map.getCenter(),
|
||||
southwest = new google.maps.LatLngBounds(
|
||||
bounds.getSouthWest(), center
|
||||
).getCenter(),
|
||||
northeast = new google.maps.LatLngBounds(
|
||||
center, bounds.getNorthEast()
|
||||
).getCenter(),
|
||||
place = new Ox.MapPlace({
|
||||
countryCode: '',
|
||||
editable: true,
|
||||
geoname: '',
|
||||
id: '_' + Ox.uid(), // fixme: stupid
|
||||
lat: center.lat(),
|
||||
lng: center.lng(),
|
||||
map: that,
|
||||
name: '',
|
||||
south: southwest.lat(),
|
||||
west: southwest.lng(),
|
||||
north: northeast.lat(),
|
||||
east: northeast.lng()
|
||||
});
|
||||
}
|
||||
Ox.forEach(self.places, function(p, i) {
|
||||
if (place.bounds.equals(p.bounds)) {
|
||||
place = p;
|
||||
exists = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (!exists) {
|
||||
self.resultPlace && self.resultPlace.remove();
|
||||
self.resultPlace = place;
|
||||
place.add();
|
||||
}
|
||||
selectPlace(place.id);
|
||||
}
|
||||
|
||||
function addPlaceToPlaces() {
|
||||
var place = getSelectedPlace();
|
||||
if (self.options.selected == place.id) {
|
||||
self.options.selected = place.id.substr(1);
|
||||
}
|
||||
place.id = place.id.substr(1); // fixme: NOT SAFE!
|
||||
place.name = self.$placeNameInput.value();
|
||||
place.geoname = self.$placeGeonameInput.value();
|
||||
place.countryCode = Ox.getCountryCode(place.geoname);
|
||||
place.marker.update();
|
||||
self.places.push(place);
|
||||
self.resultPlace = null;
|
||||
that.triggerEvent('addplace', place)
|
||||
Ox.print('SSSS', self.options.selected)
|
||||
}
|
||||
|
||||
function boundsChanged() {
|
||||
setScale();
|
||||
self.boundsChanged = true;
|
||||
}
|
||||
|
||||
function canContain(outerBounds, innerBounds) {
|
||||
var outerSpan = outerBounds.toSpan(),
|
||||
innerSpan = innerBounds.toSpan();
|
||||
return outerSpan.lat() > innerSpan.lat() &&
|
||||
outerSpan.lng() > innerSpan.lng();
|
||||
}
|
||||
|
||||
function centerChanged() {
|
||||
self.center = self.map.getCenter();
|
||||
self.centerChanged = true;
|
||||
}
|
||||
|
||||
function changeZoom(event, data) {
|
||||
self.map.setZoom(data.value);
|
||||
}
|
||||
|
||||
function clickMap(event) {
|
||||
Ox.print('Ox.Map clickMap')
|
||||
if (self.options.clickable/* && !editing()*/) {
|
||||
getPlaceByLatLng(event.latLng, self.map.getBounds(), function(place) {
|
||||
if (place) {
|
||||
addPlaceToMap(place);
|
||||
//selectPlace(place.id);
|
||||
} else {
|
||||
selectPlace(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function clickPlaceButton() {
|
||||
var place = getSelectedPlace(),
|
||||
title = self.$placeButton.options('title');
|
||||
if (title == 'New Place') {
|
||||
addPlaceToMap();
|
||||
} else if (title == 'Add Place') {
|
||||
addPlaceToPlaces();
|
||||
} else if (title == 'Remove Place') {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function constructZoomInput() {
|
||||
Ox.print('constructZoomInput', self.minZoom, self.maxZoom)
|
||||
if (self.options.zoombar) {
|
||||
self.$zoomInput && self.$zoomInput.removeElement();
|
||||
self.$zoomInput = new Ox.Range({
|
||||
arrows: true,
|
||||
max: self.maxZoom,
|
||||
min: self.minZoom,
|
||||
size: that.width(),
|
||||
thumbSize: 32,
|
||||
thumbValue: true,
|
||||
value: self.map.getZoom()
|
||||
})
|
||||
.bindEvent({
|
||||
change: changeZoom
|
||||
})
|
||||
.appendTo(self.$zoombar);
|
||||
}
|
||||
}
|
||||
|
||||
function editing() {
|
||||
var place = getSelectedPlace();
|
||||
return place && place.editing;
|
||||
}
|
||||
|
||||
function getElevation(point, callback) {
|
||||
// fixme: unused
|
||||
if (arguments.length == 1) {
|
||||
callback = point;
|
||||
point = self.map.getCenter();
|
||||
}
|
||||
self.elevationService.getElevationForLocations({
|
||||
locations: [point]
|
||||
}, function(data) {
|
||||
callback(data.elevation);
|
||||
});
|
||||
}
|
||||
|
||||
function getMapHeight() {
|
||||
return self.options.height -
|
||||
self.options.statusbar * 24 -
|
||||
self.options.toolbar * 24 -
|
||||
self.options.zoombar * 16;
|
||||
}
|
||||
|
||||
function getMapType() {
|
||||
return self.options.labels ? 'HYBRID' : 'SATELLITE'
|
||||
}
|
||||
|
||||
function getMaxZoom(point, callback) {
|
||||
if (arguments.length == 1) {
|
||||
callback = point;
|
||||
point = self.map.getCenter();
|
||||
}
|
||||
self.maxZoomService.getMaxZoomAtLatLng(point, function(data) {
|
||||
callback(data.status == 'OK' ? data.zoom : null);
|
||||
});
|
||||
}
|
||||
|
||||
function getMinZoom() {
|
||||
return 0;
|
||||
return Math.ceil(
|
||||
Ox.log(self.mapHeight / Ox.MAP_TILE_SIZE, 2)
|
||||
);
|
||||
}
|
||||
|
||||
function getPlaceById(id) {
|
||||
var place = Ox.getObjectById(self.places, id);
|
||||
if (!place && self.resultPlace && self.resultPlace.id == id) {
|
||||
place = self.resultPlace;
|
||||
}
|
||||
Ox.print('getPlaceById', id, place)
|
||||
return place;
|
||||
}
|
||||
|
||||
function getPlaceByLatLng(latlng, bounds, callback) {
|
||||
Ox.print('ll b', latlng, bounds)
|
||||
var callback = arguments.length == 3 ? callback : bounds,
|
||||
bounds = arguments.length == 3 ? bounds : null;
|
||||
self.geocoder.geocode({
|
||||
latLng: latlng
|
||||
}, function(results, status) {
|
||||
Ox.print('results', results)
|
||||
var length = results.length;
|
||||
if (status == google.maps.GeocoderStatus.OK) {
|
||||
if (status != google.maps.GeocoderStatus.ZERO_RESULTS) {
|
||||
if (bounds) {
|
||||
Ox.forEach(results.reverse(), function(result, i) {
|
||||
if (
|
||||
i == length - 1 ||
|
||||
canContain(bounds, result.geometry.bounds || result.geometry.viewport)
|
||||
) {
|
||||
callback(new Ox.MapPlace(parseGeodata(results[i])));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback(new Ox.MapPlace(parseGeodata(results[0])));
|
||||
}
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
} else {
|
||||
//Ox.print('geocode failed:', status);
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getPlaceByName(name, callback) {
|
||||
self.geocoder.geocode({
|
||||
address: name
|
||||
}, function(results, status) {
|
||||
if (status == google.maps.GeocoderStatus.OK) {
|
||||
if (status != google.maps.GeocoderStatus.ZERO_RESULTS) {
|
||||
Ox.print('GEOCODER RESULT', results[0])
|
||||
callback(new Ox.MapPlace(parseGeodata(results[0])));
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
} else {
|
||||
Ox.print('geocode failed:', status);
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getPositionByName(name) {
|
||||
var position = -1;
|
||||
Ox.forEach(self.options.places, function(place, i) {
|
||||
if (place.name == name) {
|
||||
position = i;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return position;
|
||||
}
|
||||
|
||||
function getSelectedMarker() {
|
||||
// needed in case self.options.selected
|
||||
// is changed from outside
|
||||
var id = null;
|
||||
if (self.resultPlace && self.resultPlace.selected) {
|
||||
id = self.resultPlace.id;
|
||||
} else {
|
||||
Ox.forEach(self.places, function(place) {
|
||||
if (place.selected) {
|
||||
id = place.id;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
function getSelectedPlace() {
|
||||
return self.options.selected ?
|
||||
getPlaceById(self.options.selected) : null;
|
||||
}
|
||||
|
||||
function initMap() {
|
||||
var mapBounds;
|
||||
|
||||
updateFormElements();
|
||||
|
||||
self.elevationService = new google.maps.ElevationService();
|
||||
self.geocoder = new google.maps.Geocoder();
|
||||
self.maxZoomService = new google.maps.MaxZoomService();
|
||||
self.places = [];
|
||||
self.options.places.forEach(function(place, i) {
|
||||
var bounds = new google.maps.LatLngBounds(
|
||||
new google.maps.LatLng(place.south, place.west),
|
||||
new google.maps.LatLng(place.north, place.east)
|
||||
);
|
||||
if (Ox.isUndefined(place.id)) {
|
||||
place.id = Ox.uid();
|
||||
}
|
||||
mapBounds = i == 0 ? bounds : mapBounds.union(bounds);
|
||||
});
|
||||
self.center = mapBounds ? mapBounds.getCenter() : new google.maps.LatLng(0, 0);
|
||||
self.zoom = 1; // fixme: should depend on height
|
||||
that.map = self.map = new google.maps.Map(self.$map.$element[0], {
|
||||
center: self.center,
|
||||
disableDefaultUI: true,
|
||||
disableDoubleClickZoom: true,
|
||||
mapTypeId: google.maps.MapTypeId[getMapType()],
|
||||
zoom: self.zoom
|
||||
});
|
||||
google.maps.event.addListener(self.map, 'bounds_changed', boundsChanged);
|
||||
google.maps.event.addListener(self.map, 'center_changed', centerChanged);
|
||||
google.maps.event.addListener(self.map, 'click', clickMap);
|
||||
google.maps.event.addListener(self.map, 'idle', mapChanged);
|
||||
google.maps.event.addListener(self.map, 'zoom_changed', zoomChanged);
|
||||
google.maps.event.addListenerOnce(self.map, 'tilesloaded', tilesLoaded);
|
||||
mapBounds && self.map.fitBounds(mapBounds);
|
||||
/*
|
||||
setTimeout(function() {
|
||||
}, 1000);
|
||||
*/
|
||||
self.options.places.forEach(function(place, i) {
|
||||
self.places[i] = new Ox.MapPlace(Ox.extend({
|
||||
map: that
|
||||
}, place))/*.add()*/;
|
||||
});
|
||||
google.maps.event.trigger(self.map, 'resize');
|
||||
//that.gainFocus();
|
||||
that.triggerEvent('load');
|
||||
function tilesLoaded() {
|
||||
// fixme: can add earlier, use don't replace map contents option
|
||||
Ox.forEach(self.$navigationButtons, function(button) {
|
||||
button.appendTo(self.$map);
|
||||
});
|
||||
self.$scaleLabel.appendTo(self.$map);
|
||||
}
|
||||
}
|
||||
|
||||
function mapChanged() {
|
||||
// gets called after panning or zooming
|
||||
Ox.print('mapChanged');
|
||||
var bounds;
|
||||
if (self.boundsChanged) {
|
||||
bounds = self.map.getBounds();
|
||||
self.places.sort(function(a, b) {
|
||||
var sort = {
|
||||
a: a.selected ? Infinity :
|
||||
bounds.contains(a.center) ? a.size : -Infinity,
|
||||
b: b.selected ? Infinity :
|
||||
bounds.contains(b.center) ? b.size : -Infinity,
|
||||
};
|
||||
return sort.b - sort.a;
|
||||
}).forEach(function(place, i) {
|
||||
if (i < self.options.markers && !place.visible) {
|
||||
place.add();
|
||||
} else if (i >= self.options.markers && place.visible) {
|
||||
place.remove();
|
||||
}
|
||||
});
|
||||
self.boundsChanged = false;
|
||||
}
|
||||
if (self.centerChanged) {
|
||||
getMaxZoom(function(zoom) {
|
||||
if (zoom != self.maxZoom) {
|
||||
self.maxZoom = zoom;
|
||||
if (self.map.getZoom() > zoom) {
|
||||
self.map.setZoom(zoom);
|
||||
}
|
||||
constructZoomInput();
|
||||
}
|
||||
});
|
||||
self.centerChanged = false;
|
||||
}
|
||||
if (self.zoomChanged) {
|
||||
self.zoomChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
function pan(x, y) {
|
||||
self.map.panBy(x * self.$map.width() / 2, y * self.$map.height() / 2);
|
||||
};
|
||||
|
||||
function parseGeodata(data) {
|
||||
var bounds = data.geometry.bounds || data.geometry.viewport,
|
||||
place = {
|
||||
components: data.address_components,
|
||||
countryCode: getCountryCode(data.address_components),
|
||||
east: bounds.getNorthEast().lng(),
|
||||
editable: self.options.editable,
|
||||
fullGeoname: getFullGeoname(data.address_components),
|
||||
geoname: data.formatted_address,
|
||||
id: '_' + Ox.uid(),
|
||||
map: that,
|
||||
name: data.formatted_address.split(', ')[0],
|
||||
north: bounds.getNorthEast().lat(),
|
||||
south: bounds.getSouthWest().lat(),
|
||||
types: data.types.map(function(type) {
|
||||
return Ox.toTitleCase(type.replace(/_/g, ' '));
|
||||
}),
|
||||
west: bounds.getSouthWest().lng()
|
||||
};
|
||||
function getCountryCode(components) {
|
||||
countryCode = '';
|
||||
Ox.forEach(components, function(component) {
|
||||
if (component.types.indexOf('country') > -1) {
|
||||
countryCode = component.short_name;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return countryCode;
|
||||
}
|
||||
function getFullGeoname(components) {
|
||||
var country = false;
|
||||
return components.map(function(component, i) {
|
||||
var name = component.long_name;
|
||||
if (i && components[i - 1].types.indexOf('country') > -1) {
|
||||
country = true;
|
||||
}
|
||||
return !country && (
|
||||
i == 0 || name != components[i - 1].long_name
|
||||
) ? name : null;
|
||||
}).join(', ')
|
||||
}
|
||||
return place;
|
||||
}
|
||||
|
||||
function pressEnter() {
|
||||
var place = getSelectedPlace();
|
||||
if (place) {
|
||||
if (place.editing) {
|
||||
place.submit();
|
||||
} else {
|
||||
place.edit();
|
||||
}
|
||||
} else if (self.resultPlace) {
|
||||
selectPlace(self.resultPlace.id)
|
||||
}
|
||||
}
|
||||
|
||||
function pressEscape() {
|
||||
var place = getSelectedPlace();
|
||||
if (place) {
|
||||
if (place.editing) {
|
||||
place.cancel();
|
||||
} else {
|
||||
selectPlace(null);
|
||||
}
|
||||
} else if (self.resultPlace) {
|
||||
self.resultPlace.remove();
|
||||
self.resultPlace = null;
|
||||
}
|
||||
}
|
||||
|
||||
function removePlace(id) {
|
||||
|
||||
}
|
||||
|
||||
function reset() {
|
||||
//Ox.print(self.map.getZoom(), self.zoom);
|
||||
self.map.getZoom() == self.zoom ?
|
||||
self.map.panTo(self.center) :
|
||||
self.map.fitBounds(self.bounds);
|
||||
}
|
||||
|
||||
function resizeMap() {
|
||||
/*
|
||||
Ox.print('resizeMap', self.options.width, self.options.height);
|
||||
var center = self.map.getCenter();
|
||||
self.mapHeight = getMapHeight();
|
||||
self.minZoom = getMinZoom();
|
||||
that.css({
|
||||
height: self.options.height + 'px',
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
self.$map.css({
|
||||
height: self.mapHeight + 'px',
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
google.maps.event.trigger(self.map, 'resize');
|
||||
self.map.setCenter(center);
|
||||
*/
|
||||
}
|
||||
|
||||
function selectPlace(id) {
|
||||
var place,
|
||||
selected = getSelectedMarker();
|
||||
Ox.print('Ox.Map selectPlace()', id, selected);
|
||||
if (id != selected) {
|
||||
place = getPlaceById(selected);
|
||||
place && place.deselect();
|
||||
place = getPlaceById(id);
|
||||
place && place.select();
|
||||
self.options.selected = id;
|
||||
setStatus();
|
||||
that.triggerEvent('selectplace', place);
|
||||
}
|
||||
};
|
||||
|
||||
function getMetersPerPixel() {
|
||||
var mapWidth = self.$map.width(),
|
||||
span = self.map.getBounds().toSpan().lng();
|
||||
if (span >= 360) {
|
||||
span = 360 * mapWidth / Ox.MAP_TILE_SIZE;
|
||||
}
|
||||
return span * Ox.getMetersPerDegree(self.map.getCenter().lat()) / mapWidth;
|
||||
}
|
||||
|
||||
function setScale() {
|
||||
var metersPerPixel = getMetersPerPixel();
|
||||
Ox.forEach(self.scaleMeters, function(meters) {
|
||||
var scaleWidth = Math.round(meters / metersPerPixel);
|
||||
if (scaleWidth <= 256) {
|
||||
self.$scaleLabel
|
||||
.options({
|
||||
title: '\u2190 ' + (
|
||||
meters > 1000 ? Ox.formatNumber(meters / 1000) + ' k' : meters + ' '
|
||||
) + 'm \u2192'
|
||||
})
|
||||
.css({
|
||||
width: (scaleWidth - 10) + 'px'
|
||||
})
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setStatus() {
|
||||
Ox.print('setStatus()', self.options.selected)
|
||||
var disabled, place, title;
|
||||
if (self.options.statusbar) {
|
||||
place = getSelectedPlace();
|
||||
if (place) {
|
||||
title = place.id[0] == '_' ? 'Add Place' : 'Remove Place';
|
||||
} else {
|
||||
title = 'New Place';
|
||||
}
|
||||
disabled = place && !place.editable;
|
||||
self.$placeNameInput.options({
|
||||
disabled: disabled,
|
||||
value: place ? place.name : ''
|
||||
});
|
||||
self.$placeGeonameInput.options({
|
||||
disabled: disabled,
|
||||
value: place ? place.geoname : ''
|
||||
});
|
||||
self.$placeButton.options({
|
||||
disabled: disabled,
|
||||
title: title
|
||||
});
|
||||
}
|
||||
Ox.print('STATUS DONE');
|
||||
}
|
||||
|
||||
function submitFind(event, data) {
|
||||
that.findPlace(data.value, function(place) {
|
||||
setStatus(place);
|
||||
});
|
||||
}
|
||||
|
||||
function toggleLabels() {
|
||||
self.options.labels = !self.options.labels
|
||||
self.map.setMapTypeId(google.maps.MapTypeId[getMapType()]);
|
||||
self.$labelsButton.options({
|
||||
title: self.$labelsButton.options('title') == 'Show Labels' ?
|
||||
'Hide Labels' : 'Show Labels'
|
||||
});
|
||||
}
|
||||
|
||||
function undo() {
|
||||
Ox.print('Map undo')
|
||||
var place = getSelectedPlace();
|
||||
place.editing && place.undo();
|
||||
}
|
||||
|
||||
function updateFormElements() {
|
||||
var width = that.width();
|
||||
self.$zoomInput && constructZoomInput();
|
||||
self.$placeNameInput.options({
|
||||
width: Math.floor((width - 112) / 2)
|
||||
});
|
||||
self.$placeGeonameInput.options({
|
||||
width: Math.ceil((width - 112) / 2)
|
||||
});
|
||||
}
|
||||
|
||||
function zoom(z) {
|
||||
self.map.setZoom(self.map.getZoom() + z);
|
||||
}
|
||||
|
||||
function zoomChanged() {
|
||||
var zoom = self.map.getZoom();
|
||||
if (zoom < self.minZoom) {
|
||||
self.map.setZoom(self.minZoom);
|
||||
} else if (self.maxZoom && zoom > self.maxZoom) {
|
||||
self.map.setZoom(self.maxZoom);
|
||||
} else {
|
||||
self.zoomChanged = true;
|
||||
self.$zoomInput && self.$zoomInput.options({value: zoom});
|
||||
that.triggerEvent('zoom', {
|
||||
value: zoom
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function zoomToPlace() {
|
||||
Ox.print('zoomToPlace')
|
||||
if (self.options.selected !== null) {
|
||||
self.map.fitBounds(getPlaceById(self.options.selected).bounds);
|
||||
}
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
/*if (key == 'height' || key == 'width') {
|
||||
resizeMap();
|
||||
} else */if (key == 'places') {
|
||||
loadPlaces();
|
||||
} else if (key == 'selected') {
|
||||
selectPlace(value);
|
||||
} else if (key == 'type') {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
that.getKey = function() {
|
||||
var key = null;
|
||||
if (self.shiftKey) {
|
||||
key = 'shift'
|
||||
} else if (self.metaKey) {
|
||||
key = 'meta'
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
that.editPlace = function() {
|
||||
getPlaceById(self.options.selected).edit();
|
||||
return that;
|
||||
}
|
||||
|
||||
that.findPlace = function(name, callback) {
|
||||
getPlaceByName(name, function(place) {
|
||||
if (place) {
|
||||
addPlaceToMap(place);
|
||||
self.map.fitBounds(place.bounds);
|
||||
} else {
|
||||
self.$findInput.addClass('OxError');
|
||||
}
|
||||
callback(place);
|
||||
});
|
||||
};
|
||||
|
||||
that.panToPlace = function() {
|
||||
Ox.print('panToPlace:', self.options.selected)
|
||||
var place = getSelectedPlace();
|
||||
place && self.map.panTo(place.center);
|
||||
return that;
|
||||
};
|
||||
|
||||
that.removePlace = function(id) {
|
||||
return that;
|
||||
};
|
||||
|
||||
that.resizeMap = function() {
|
||||
|
||||
/*
|
||||
Ox.print('resizeMap', self.options.width, self.options.height);
|
||||
var center = self.map.getCenter();
|
||||
self.mapHeight = getMapHeight();
|
||||
self.minZoom = getMinZoom();
|
||||
that.css({
|
||||
height: self.options.height + 'px',
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
self.$map.css({
|
||||
height: self.mapHeight + 'px',
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
google.maps.event.trigger(self.map, 'resize');
|
||||
self.map.setCenter(center);
|
||||
*/
|
||||
|
||||
/*
|
||||
Ox.print('Ox.Map.resizeMap()');
|
||||
var center = self.map.getCenter();
|
||||
self.options.height = that.$element.height();
|
||||
self.options.width = that.$element.width();
|
||||
Ox.print(self.options.width, self.options.height)
|
||||
self.$map.css({
|
||||
height: self.mapHeight + 'px',
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
google.maps.event.trigger(self.map, 'resize');
|
||||
self.map.setCenter(center);
|
||||
self.options.zoombar && self.$zoomInput.options({
|
||||
size: self.options.width
|
||||
});
|
||||
*/
|
||||
updateFormElements();
|
||||
google.maps.event.trigger(self.map, 'resize');
|
||||
return that;
|
||||
}
|
||||
|
||||
that.zoomToPlace = function() {
|
||||
Ox.print('zoomToPlace')
|
||||
var place = getSelectedPlace();
|
||||
place && self.map.fitBounds(place.bounds);
|
||||
return that;
|
||||
};
|
||||
|
||||
that.zoom = function(value) {
|
||||
self.map.setZoom(value);
|
||||
return that;
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
68
source/Ox.UI/js/Map/Ox.MapImage.js
Normal file
68
source/Ox.UI/js/Map/Ox.MapImage.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.MapImage = function(options, self) {
|
||||
|
||||
/**
|
||||
options
|
||||
height image height (px)
|
||||
places array of either names (''), points ([0, 0]),
|
||||
or objects ({name, point, highlight})
|
||||
type map type ('hybrid', 'roadmap', 'satellite', 'terrain')
|
||||
width image width (px)
|
||||
*/
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('img', self)
|
||||
.defaults({
|
||||
height: 360,
|
||||
markerColorHighlight: 'yellow',
|
||||
markerColorNormal: 'blue',
|
||||
places: [],
|
||||
type: 'satellite',
|
||||
width: 640
|
||||
})
|
||||
.options(options || {})
|
||||
|
||||
$.extend(self, {
|
||||
markers: {
|
||||
highlight: [],
|
||||
normal: []
|
||||
},
|
||||
src: 'http://maps.google.com/maps/api/staticmap?sensor=false' +
|
||||
'&size=' + self.options.width + 'x' + self.options.height +
|
||||
'&maptype=' + self.options.type
|
||||
});
|
||||
|
||||
if (self.options.places.length) {
|
||||
self.options.places.forEach(function(place) {
|
||||
if (Ox.isString(place)) {
|
||||
self.markers.normal.push(place);
|
||||
} else if (Ox.isArray(place)) {
|
||||
self.markers.normal.push(place.join(','));
|
||||
} else {
|
||||
self.markers[place.highlight ? 'highlight' : 'normal']
|
||||
.push('point' in place ? place.point.join(',') : place.name)
|
||||
}
|
||||
});
|
||||
Ox.forEach(self.markers, function(markers, k) {
|
||||
if (markers.length) {
|
||||
self.src += '&markers=icon:' + 'http://dev.pan.do:8000' +
|
||||
Ox.PATH + 'png/Ox.UI/marker' +
|
||||
Ox.toTitleCase(self.options['markerColor' + Ox.toTitleCase(k)]) +
|
||||
'.png|' + markers.join('|')
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.src += '¢er=0,0&zoom=2'
|
||||
}
|
||||
|
||||
that.attr({
|
||||
src: self.src
|
||||
});
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
184
source/Ox.UI/js/Map/Ox.MapMarker.js
Normal file
184
source/Ox.UI/js/Map/Ox.MapMarker.js
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
|
||||
Ox.MapMarker = function(options) {
|
||||
|
||||
options = Ox.extend({
|
||||
color: [255, 0, 0],
|
||||
map: null,
|
||||
place: null,
|
||||
size: 16
|
||||
}, options);
|
||||
|
||||
var that = this;
|
||||
|
||||
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() {
|
||||
if (!that.place.selected) {
|
||||
that.map.options({selected: that.place.id});
|
||||
} else if (that.map.getKey() == 'meta') {
|
||||
that.map.options({selected: null});
|
||||
} else if (that.map.getKey() == 'shift') {
|
||||
that.map.zoomToPlace();
|
||||
} else {
|
||||
that.map.panToPlace();
|
||||
}
|
||||
}
|
||||
|
||||
function dragstart(e) {
|
||||
|
||||
}
|
||||
|
||||
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.print('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) {
|
||||
|
||||
}
|
||||
|
||||
function getMarkerImage(options, callback) {
|
||||
// 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 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;
|
||||
//Ox.print('setOptions, that.color', that.color)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
that.add = function() {
|
||||
that.marker.setMap(that.map.map);
|
||||
google.maps.event.addListener(that.marker, 'click', click);
|
||||
return that;
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
that.remove = function() {
|
||||
that.marker.setMap(null);
|
||||
google.maps.event.clearListeners(that.marker);
|
||||
return that;
|
||||
};
|
||||
|
||||
that.submit = function() {
|
||||
google.maps.event.clearListeners(that.marker, 'dragstart');
|
||||
google.maps.event.clearListeners(that.marker, 'drag');
|
||||
google.maps.event.clearListeners(that.marker, 'dragend');
|
||||
}
|
||||
|
||||
that.update = function() {
|
||||
setOptions();
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
49
source/Ox.UI/js/Map/Ox.MapMarkerImage.js
Normal file
49
source/Ox.UI/js/Map/Ox.MapMarkerImage.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
|
||||
Ox.MapMarkerImage = (function() {
|
||||
|
||||
var cache = {};
|
||||
|
||||
return function(options) {
|
||||
|
||||
options = Ox.extend({
|
||||
color: [255, 0, 0],
|
||||
mode: 'normal', // normal, selected, editing
|
||||
size: 16,
|
||||
type: 'place', // place, result, rectangle
|
||||
}, options);
|
||||
|
||||
var index = [
|
||||
options.type, options.mode, options.size, options.color.join(',')
|
||||
].join(';');
|
||||
|
||||
if (!cache[index]) {
|
||||
var color = options.type == 'place' ?
|
||||
Ox.merge(Ox.clone(options.color), [0.5]) : [0, 0, 0, 0],
|
||||
border = options.mode == 'normal' ? [0, 0, 0] :
|
||||
options.mode == 'selected' ? [255, 255, 255] : [128, 128, 255],
|
||||
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 = 'rgb(' + 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)
|
||||
);
|
||||
//Ox.print(options, 'index', index)
|
||||
}
|
||||
|
||||
return cache[index];
|
||||
|
||||
}
|
||||
|
||||
}());
|
||||
160
source/Ox.UI/js/Map/Ox.MapPlace.js
Normal file
160
source/Ox.UI/js/Map/Ox.MapPlace.js
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
|
||||
Ox.MapPlace = function(options) {
|
||||
|
||||
options = Ox.extend({
|
||||
east: 0,
|
||||
editing: false,
|
||||
geoname: '',
|
||||
map: null,
|
||||
markerColor: [255, 0, 0],
|
||||
markerSize: 16,
|
||||
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() {
|
||||
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.size = Ox.getArea(
|
||||
{lat: that.south, lng: that.west},
|
||||
{lat: that.north, lng: that.east}
|
||||
);
|
||||
if (!that.marker) {
|
||||
that.marker = new Ox.MapMarker({
|
||||
color: that.markerColor,
|
||||
map: that.map,
|
||||
place: that,
|
||||
size: that.markerSize
|
||||
});
|
||||
that.rectangle = new Ox.MapRectangle({
|
||||
map: that.map,
|
||||
place: that
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function editable() {
|
||||
return that.map.options('editable') && that.editable;
|
||||
}
|
||||
|
||||
that.add = function() {
|
||||
that.visible = true;
|
||||
that.marker.add();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.cancel = function() {
|
||||
if (editable()) {
|
||||
that.undo();
|
||||
that.editing = false;
|
||||
that.marker.update();
|
||||
that.rectangle.deselect();
|
||||
}
|
||||
return that;
|
||||
};
|
||||
|
||||
that.crossesDateline = function() {
|
||||
return that.west > that.east;
|
||||
}
|
||||
|
||||
that.deselect = function() {
|
||||
that.editing && that.submit();
|
||||
that.selected = false;
|
||||
that.marker.update();
|
||||
that.rectangle.remove();
|
||||
return that;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
that.remove = function() {
|
||||
that.editing && that.submit();
|
||||
that.selected && that.deselect();
|
||||
that.visible = false;
|
||||
that.marker.remove();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.select = function() {
|
||||
that.selected = true;
|
||||
!that.visible && that.add();
|
||||
that.marker.update();
|
||||
that.rectangle.add();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.submit = function() {
|
||||
if (editable()) {
|
||||
that.editing = false;
|
||||
that.marker.update();
|
||||
that.rectangle.deselect();
|
||||
}
|
||||
return that;
|
||||
};
|
||||
|
||||
that.update = function() {
|
||||
update();
|
||||
return that;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
89
source/Ox.UI/js/Map/Ox.MapRectangle.js
Normal file
89
source/Ox.UI/js/Map/Ox.MapRectangle.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.MapRectangle = function(options, self) {
|
||||
|
||||
var options = Ox.extend({
|
||||
map: null,
|
||||
place: null
|
||||
}, options),
|
||||
that = this;
|
||||
|
||||
Ox.forEach(options, function(val, key) {
|
||||
that[key] = val;
|
||||
});
|
||||
|
||||
that.rectangle = new google.maps.Rectangle({
|
||||
clickable: true,
|
||||
bounds: that.place.bounds,
|
||||
});
|
||||
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 = that.place.editing ? '#8080FF' : '#FFFFFF';
|
||||
that.rectangle.setOptions({
|
||||
fillColor: color,
|
||||
fillOpacity: that.place.editing ? 0.1 : 0,
|
||||
strokeColor: color,
|
||||
strokeOpacity: 1,
|
||||
strokeWeight: 2
|
||||
})
|
||||
}
|
||||
|
||||
that.add = function() {
|
||||
that.rectangle.setMap(that.map.map);
|
||||
google.maps.event.addListener(that.rectangle, 'click', click);
|
||||
return that;
|
||||
};
|
||||
|
||||
that.deselect = function() {
|
||||
setOptions();
|
||||
Ox.print('MARKERS', that.markers)
|
||||
Ox.forEach(that.markers, function(marker) {
|
||||
marker.remove();
|
||||
});
|
||||
};
|
||||
|
||||
that.remove = function() {
|
||||
that.rectangle.setMap(null);
|
||||
google.maps.event.clearListeners(that.rectangle);
|
||||
return that
|
||||
}
|
||||
|
||||
that.select = function() {
|
||||
setOptions();
|
||||
Ox.forEach(that.markers, function(marker) {
|
||||
marker.add();
|
||||
});
|
||||
};
|
||||
|
||||
that.update = function() {
|
||||
that.rectangle.setOptions({
|
||||
bounds: that.place.bounds
|
||||
});
|
||||
Ox.forEach(that.markers, function(marker) {
|
||||
marker.update();
|
||||
});
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
96
source/Ox.UI/js/Map/Ox.MapRectangleMarker.js
Normal file
96
source/Ox.UI/js/Map/Ox.MapRectangleMarker.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
|
||||
Ox.MapRectangleMarker = function(options, self) {
|
||||
|
||||
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',
|
||||
type: 'rectangle'
|
||||
}),
|
||||
position: that.place.points[that.position],
|
||||
raiseOnDrag: false
|
||||
});
|
||||
|
||||
function dragstart(e) {
|
||||
that.drag = {
|
||||
lat: e.latLng.lat(),
|
||||
lng: e.latLng.lng()
|
||||
};
|
||||
}
|
||||
|
||||
function drag(e) {
|
||||
// fixme: implement shift+drag (center stays the same)
|
||||
Ox.print(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.print('west', that.place.west, 'east', that.place.east);
|
||||
Ox.print('south', that.place.south, 'north', that.place.north);
|
||||
that.place.update();
|
||||
that.place.marker.update();
|
||||
that.place.rectangle.update();
|
||||
}
|
||||
|
||||
function dragend(e) {
|
||||
var south;
|
||||
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.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);
|
||||
};
|
||||
|
||||
that.remove = function() {
|
||||
that.marker.setMap(null);
|
||||
google.maps.event.clearListeners(that.marker);
|
||||
};
|
||||
|
||||
that.update = function() {
|
||||
that.marker.setOptions({
|
||||
position: that.place.points[that.position]
|
||||
});
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
185
source/Ox.UI/js/Menu/Ox.MainMenu.js
Normal file
185
source/Ox.UI/js/Menu/Ox.MainMenu.js
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/**
|
||||
*/
|
||||
Ox.MainMenu = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Bar({}, self)
|
||||
.defaults({
|
||||
extras: [],
|
||||
menus: [],
|
||||
size: 'medium'
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxMainMenu Ox' + Ox.toTitleCase(self.options.size)) // fixme: bar should accept small/medium/large ... like toolbar
|
||||
.click(click)
|
||||
.mousemove(mousemove);
|
||||
|
||||
self.focused = false;
|
||||
self.selected = -1;
|
||||
that.menus = [];
|
||||
that.titles = [];
|
||||
that.layer = $('<div>').addClass('OxLayer');
|
||||
|
||||
self.options.menus.forEach(function(menu, position) {
|
||||
that.titles[position] = $('<div>')
|
||||
.addClass('OxTitle')
|
||||
.html(menu.title)
|
||||
.data('position', position)
|
||||
.appendTo(that.$element);
|
||||
that.menus[position] = new Ox.Menu($.extend(menu, {
|
||||
element: that.titles[position],
|
||||
mainmenu: that,
|
||||
size: self.options.size
|
||||
}))
|
||||
.bindEvent({
|
||||
hide: onHideMenu
|
||||
});
|
||||
});
|
||||
|
||||
if (self.options.extras.length) {
|
||||
that.extras = $('<div>')
|
||||
.addClass('OxExtras')
|
||||
.appendTo(that.$element);
|
||||
self.options.extras.forEach(function(extra) {
|
||||
extra.css({
|
||||
float: 'left' // fixme: need class!
|
||||
}).appendTo(that.extras);
|
||||
});
|
||||
}
|
||||
|
||||
function click(event) {
|
||||
var $target = $(event.target),
|
||||
position = typeof $target.data('position') != 'undefined' ?
|
||||
$target.data('position') : -1;
|
||||
clickTitle(position);
|
||||
}
|
||||
|
||||
function clickTitle(position) {
|
||||
var selected = self.selected;
|
||||
if (self.selected > -1) {
|
||||
that.menus[self.selected].hideMenu();
|
||||
}
|
||||
if (position > -1) {
|
||||
if (position != selected) {
|
||||
self.focused = true;
|
||||
self.selected = position;
|
||||
that.titles[self.selected].addClass('OxSelected');
|
||||
that.menus[self.selected].showMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mousemove(event) {
|
||||
var $target = $(event.target),
|
||||
focused,
|
||||
position = typeof $target.data('position') != 'undefined' ?
|
||||
$target.data('position') : -1;
|
||||
if (self.focused && position != self.selected) {
|
||||
if (position > -1) {
|
||||
clickTitle(position);
|
||||
} else {
|
||||
focused = self.focused;
|
||||
that.menus[self.selected].hideMenu();
|
||||
self.focused = focused;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onHideMenu() {
|
||||
if (self.selected > -1) {
|
||||
that.titles[self.selected].removeClass('OxSelected');
|
||||
self.selected = -1;
|
||||
}
|
||||
self.focused = false;
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
|
||||
};
|
||||
|
||||
that.addMenuAfter = function(id) {
|
||||
|
||||
};
|
||||
|
||||
that.addMenuBefore = function(id) {
|
||||
|
||||
};
|
||||
|
||||
that.checkItem = function(id) {
|
||||
var ids = id.split('_'),
|
||||
itemId = ids.pop(),
|
||||
menuId = ids.join('_');
|
||||
that.getMenu(menuId).checkItem(itemId);
|
||||
};
|
||||
|
||||
that.disableItem = function(id) {
|
||||
that.getItem(id).options({
|
||||
disabled: true
|
||||
});
|
||||
};
|
||||
|
||||
that.enableItem = function(id) {
|
||||
Ox.print('ENABLE ITEM', id)
|
||||
that.getItem(id).options({
|
||||
disabled: false
|
||||
});
|
||||
};
|
||||
|
||||
that.getItem = function(id) {
|
||||
var ids = id.split('_'),
|
||||
item;
|
||||
if (ids.length == 1) {
|
||||
Ox.forEach(that.menus, function(menu) {
|
||||
item = menu.getItem(id);
|
||||
return !item;
|
||||
});
|
||||
} else {
|
||||
item = that.getMenu(ids.shift()).getItem(ids.join('_'));
|
||||
}
|
||||
Ox.print('getItem', id, item);
|
||||
return item;
|
||||
};
|
||||
|
||||
that.getMenu = function(id) {
|
||||
var ids = id.split('_'),
|
||||
menu;
|
||||
if (ids.length == 1) {
|
||||
Ox.forEach(that.menus, function(v) {
|
||||
if (v.options('id') == id) {
|
||||
menu = v;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
menu = that.getMenu(ids.shift()).getSubmenu(ids.join('_'));
|
||||
}
|
||||
//Ox.print('getMenu', id, menu);
|
||||
return menu;
|
||||
};
|
||||
|
||||
that.removeMenu = function() {
|
||||
|
||||
};
|
||||
|
||||
that.selectNextMenu = function() {
|
||||
if (self.selected < self.options.menus.length - 1) {
|
||||
clickTitle(self.selected + 1);
|
||||
}
|
||||
};
|
||||
|
||||
that.selectPreviousMenu = function() {
|
||||
if (self.selected) {
|
||||
clickTitle(self.selected - 1);
|
||||
}
|
||||
};
|
||||
|
||||
that.uncheckItem = function(id) {
|
||||
that.getItem(id).options({
|
||||
checked: false
|
||||
});
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
688
source/Ox.UI/js/Menu/Ox.Menu.js
Normal file
688
source/Ox.UI/js/Menu/Ox.Menu.js
Normal file
|
|
@ -0,0 +1,688 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/**
|
||||
options
|
||||
element the element the menu is attached to
|
||||
id the menu id
|
||||
items array of menu items
|
||||
mainmenu the main menu this menu is part of, if any
|
||||
offset offset of the menu, in px
|
||||
parent the supermenu, if any
|
||||
selected the position of the selected item
|
||||
side open to 'bottom' or 'right'
|
||||
size 'large', 'medium' or 'small'
|
||||
|
||||
events:
|
||||
change_groupId {id, value} checked item of a group has changed
|
||||
click_itemId item not belonging to a group was clicked
|
||||
click_menuId {id, value} item not belonging to a group was clicked
|
||||
deselect_menuId {id, value} item was deselected not needed, not implemented
|
||||
hide_menuId menu was hidden
|
||||
select_menuId {id, value} item was selected
|
||||
*/
|
||||
Ox.Menu = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.defaults({
|
||||
element: null,
|
||||
id: '',
|
||||
items: [],
|
||||
mainmenu: null,
|
||||
offset: {
|
||||
left: 0,
|
||||
top: 0
|
||||
},
|
||||
parent: null,
|
||||
selected: -1,
|
||||
side: 'bottom',
|
||||
size: 'medium',
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass(
|
||||
'OxMenu Ox' + Ox.toTitleCase(self.options.side) +
|
||||
' Ox' + Ox.toTitleCase(self.options.size)
|
||||
)
|
||||
.click(click)
|
||||
.mouseenter(mouseenter)
|
||||
.mouseleave(mouseleave)
|
||||
.mousemove(mousemove)
|
||||
.bindEvent({
|
||||
key_up: selectPreviousItem,
|
||||
key_down: selectNextItem,
|
||||
key_left: selectSupermenu,
|
||||
key_right: selectSubmenu,
|
||||
key_escape: hideMenu,
|
||||
key_enter: clickSelectedItem
|
||||
}),
|
||||
itemHeight = self.options.size == 'small' ? 12 : (self.options.size == 'medium' ? 16 : 20),
|
||||
// menuHeight,
|
||||
scrollSpeed = 1,
|
||||
$item; // fixme: used?
|
||||
// fixme: attach all private vars to self
|
||||
|
||||
// construct
|
||||
that.items = [];
|
||||
that.submenus = {};
|
||||
that.$scrollbars = [];
|
||||
that.$top = $('<div>')
|
||||
.addClass('OxTop')
|
||||
.appendTo(that.$element);
|
||||
that.$scrollbars.up = constructScrollbar('up')
|
||||
.appendTo(that.$element);
|
||||
that.$container = $('<div>')
|
||||
.addClass('OxContainer')
|
||||
.appendTo(that.$element);
|
||||
that.$content = $('<table>')
|
||||
.addClass('OxContent')
|
||||
.appendTo(that.$container);
|
||||
constructItems(self.options.items);
|
||||
that.$scrollbars.down = constructScrollbar('down')
|
||||
.appendTo(that.$element);
|
||||
that.$bottom = $('<div>')
|
||||
.addClass('OxBottom')
|
||||
.appendTo(that.$element);
|
||||
that.$layer = $('<div>')
|
||||
.addClass(self.options.mainmenu ? 'OxMainMenuLayer' : 'OxMenuLayer')
|
||||
.click(click);
|
||||
|
||||
function click(event) {
|
||||
var item,
|
||||
position,
|
||||
$target = $(event.target),
|
||||
$parent = $target.parent();
|
||||
// necessary for highlight
|
||||
if ($parent.is('.OxCell')) {
|
||||
$target = $parent;
|
||||
$parent = $target.parent();
|
||||
}
|
||||
if ($target.is('.OxCell')) {
|
||||
position = $parent.data('position');
|
||||
item = that.items[position];
|
||||
if (!item.options('disabled')) {
|
||||
clickItem(position);
|
||||
} else {
|
||||
that.hideMenu();
|
||||
}
|
||||
} else {
|
||||
that.hideMenu();
|
||||
}
|
||||
}
|
||||
|
||||
function clickItem(position) {
|
||||
var item = that.items[position],
|
||||
menu = self.options.mainmenu || self.options.parent || that,
|
||||
toggled;
|
||||
that.hideMenu();
|
||||
if (!item.options('items').length) {
|
||||
if (that.options('parent')) {
|
||||
that.options('parent').hideMenu().triggerEvent('click');
|
||||
}
|
||||
if (item.options('checked') !== null) {
|
||||
if (item.options('group')) {
|
||||
//Ox.print('has group', item.options('group'))
|
||||
toggled = self.optionGroups[item.options('group')].toggle(position);
|
||||
//Ox.print('toggled', toggled)
|
||||
if (toggled.length) {
|
||||
toggled.forEach(function(pos) {
|
||||
that.items[pos].toggleChecked();
|
||||
});
|
||||
//Ox.print('--triggering change event--');
|
||||
menu.triggerEvent('change', {
|
||||
id: item.options('group'),
|
||||
checked: $.map(self.optionGroups[item.options('group')].checked(), function(v, i) {
|
||||
return {
|
||||
id: that.items[v].options('id'),
|
||||
title: Ox.stripTags(that.items[v].options('title')[0])
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
} else {
|
||||
item.toggleChecked();
|
||||
menu.triggerEvent('change', {
|
||||
checked: item.options('checked'),
|
||||
id: item.options('id'),
|
||||
title: Ox.stripTags(item.options('title')[0])
|
||||
});
|
||||
}
|
||||
} else {
|
||||
menu.triggerEvent('click', {
|
||||
id: item.options('id'),
|
||||
title: Ox.stripTags(item.options('title')[0])
|
||||
});
|
||||
}
|
||||
if (item.options('title').length == 2) {
|
||||
item.toggleTitle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clickSelectedItem() {
|
||||
// called on key.enter
|
||||
if (self.options.selected > -1) {
|
||||
clickItem(self.options.selected);
|
||||
} else {
|
||||
that.hideMenu();
|
||||
}
|
||||
}
|
||||
|
||||
function constructItems(items) {
|
||||
|
||||
that.$content.empty();
|
||||
scrollMenuUp();
|
||||
|
||||
self.optionGroups = {};
|
||||
items.forEach(function(item, i) {
|
||||
if (item.group) {
|
||||
items[i] = $.map(item.items, function(v, i) {
|
||||
return $.extend(v, {
|
||||
group: item.group
|
||||
});
|
||||
});
|
||||
self.optionGroups[item.group] = new Ox.OptionGroup(
|
||||
items[i],
|
||||
'min' in item ? item.min : 1,
|
||||
'max' in item ? item.max : 1
|
||||
);
|
||||
}
|
||||
});
|
||||
items = Ox.flatten(items);
|
||||
|
||||
that.items = [];
|
||||
items.forEach(function(item) {
|
||||
var position;
|
||||
if ('id' in item) {
|
||||
that.items.push(new Ox.MenuItem($.extend(item, {
|
||||
menu: that,
|
||||
position: position = that.items.length
|
||||
})).data('position', position).appendTo(that.$content)); // fixme: jquery bug when passing {position: position}? does not return the object?;
|
||||
if (item.items) {
|
||||
that.submenus[item.id] = new Ox.Menu({
|
||||
element: that.items[position],
|
||||
id: Ox.toCamelCase(self.options.id + '/' + item.id),
|
||||
items: item.items,
|
||||
mainmenu: self.options.mainmenu,
|
||||
offset: {
|
||||
left: 0,
|
||||
top: -4
|
||||
},
|
||||
parent: that,
|
||||
side: 'right',
|
||||
size: self.options.size,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
that.$content.append(constructSpace());
|
||||
that.$content.append(constructLine());
|
||||
that.$content.append(constructSpace());
|
||||
}
|
||||
});
|
||||
|
||||
if (!that.is(':hidden')) {
|
||||
that.hideMenu();
|
||||
that.showMenu();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function constructLine() {
|
||||
return $('<tr>').append(
|
||||
$('<td>', {
|
||||
'class': 'OxLine',
|
||||
colspan: 5
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function constructScrollbar(direction) {
|
||||
var interval,
|
||||
speed = direction == 'up' ? -1 : 1;
|
||||
return $('<div/>', {
|
||||
'class': 'OxScrollbar Ox' + Ox.toTitleCase(direction),
|
||||
html: Ox.UI.symbols['triangle_' + direction],
|
||||
click: function() { // fixme: do we need to listen to click event?
|
||||
return false;
|
||||
},
|
||||
mousedown: function() {
|
||||
scrollSpeed = 2;
|
||||
return false;
|
||||
},
|
||||
mouseenter: function() {
|
||||
var $otherScrollbar = that.$scrollbars[direction == 'up' ? 'down' : 'up'];
|
||||
$(this).addClass('OxSelected');
|
||||
if ($otherScrollbar.is(':hidden')) {
|
||||
$otherScrollbar.show();
|
||||
that.$container.height(that.$container.height() - itemHeight);
|
||||
if (direction == 'down') {
|
||||
that.$content.css({
|
||||
top: -itemHeight + 'px'
|
||||
});
|
||||
}
|
||||
}
|
||||
scrollMenu(speed);
|
||||
interval = setInterval(function() {
|
||||
scrollMenu(speed);
|
||||
}, 100);
|
||||
},
|
||||
mouseleave: function() {
|
||||
$(this).removeClass('OxSelected');
|
||||
clearInterval(interval);
|
||||
},
|
||||
mouseup: function() {
|
||||
scrollSpeed = 1;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function constructSpace() {
|
||||
return $('<tr>').append(
|
||||
$('<td>', {
|
||||
'class': 'OxSpace',
|
||||
colspan: 5
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function getElement(id) {
|
||||
// fixme: needed?
|
||||
return $('#' + Ox.toCamelCase(options.id + '/' + id));
|
||||
}
|
||||
|
||||
function getItemPositionById(id) {
|
||||
var position;
|
||||
Ox.forEach(that.items, function(item, i) {
|
||||
if (item.options('id') == id) {
|
||||
position = i;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
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')) {
|
||||
return ret = false;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function isLastEnabledItem() {
|
||||
var ret = true;
|
||||
Ox.forEach(that.items, function(item, i) {
|
||||
if (i > self.options.selected && !item.options('disabled')) {
|
||||
return ret = false;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function mouseenter() {
|
||||
that.gainFocus();
|
||||
}
|
||||
|
||||
function mouseleave() {
|
||||
if (self.options.selected > -1 && !that.items[self.options.selected].options('items').length) {
|
||||
selectItem(-1);
|
||||
}
|
||||
}
|
||||
|
||||
function mousemove(event) {
|
||||
var item,
|
||||
position,
|
||||
$target = $(event.target);
|
||||
$parent = $target.parent();
|
||||
if ($parent.is('.OxCell')) {
|
||||
$target = $parent;
|
||||
$parent = $target.parent();
|
||||
}
|
||||
if ($target.is('.OxCell')) {
|
||||
position = $parent.data('position');
|
||||
item = that.items[position];
|
||||
if (!item.options('disabled') && position != self.options.selected) {
|
||||
selectItem(position);
|
||||
}
|
||||
} else {
|
||||
mouseleave();
|
||||
}
|
||||
}
|
||||
|
||||
function scrollMenu(speed) {
|
||||
var containerHeight = that.$container.height(),
|
||||
contentHeight = that.$content.height(),
|
||||
top = parseInt(that.$content.css('top')) || 0,
|
||||
min = containerHeight - contentHeight + itemHeight,
|
||||
max = 0;
|
||||
top += speed * scrollSpeed * -itemHeight;
|
||||
if (top <= min) {
|
||||
top = min;
|
||||
that.$scrollbars.down.hide().trigger('mouseleave');
|
||||
that.$container.height(containerHeight + itemHeight);
|
||||
that.items[that.items.length - 1].trigger('mouseover');
|
||||
} else if (top >= max - itemHeight) {
|
||||
top = max;
|
||||
that.$scrollbars.up.hide().trigger('mouseleave');
|
||||
that.$container.height(containerHeight + itemHeight);
|
||||
that.items[0].trigger('mouseover');
|
||||
}
|
||||
that.$content.css({
|
||||
top: top + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
function scrollMenuUp() {
|
||||
if (that.$scrollbars.up.is(':visible')) {
|
||||
that.$content.css({
|
||||
top: '0px'
|
||||
});
|
||||
that.$scrollbars.up.hide();
|
||||
if (that.$scrollbars.down.is(':hidden')) {
|
||||
that.$scrollbars.down.show();
|
||||
} else {
|
||||
that.$container.height(that.$container.height() + itemHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectItem(position) {
|
||||
var item;
|
||||
if (self.options.selected > -1) {
|
||||
//Ox.print('s.o.s', self.options.selected, that.items)
|
||||
item = that.items[self.options.selected]
|
||||
item.removeClass('OxSelected');
|
||||
/* disabled
|
||||
that.triggerEvent('deselect', {
|
||||
id: item.options('id'),
|
||||
title: Ox.stripTags(item.options('title')[0])
|
||||
});
|
||||
*/
|
||||
}
|
||||
if (position > -1) {
|
||||
item = that.items[position];
|
||||
Ox.forEach(that.submenus, function(submenu, id) {
|
||||
if (!submenu.is(':hidden')) {
|
||||
submenu.hideMenu();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
item.options('items').length && that.submenus[item.options('id')].showMenu(); // fixme: do we want to switch to this style?
|
||||
item.addClass('OxSelected');
|
||||
/* disabled
|
||||
that.triggerEvent('select', {
|
||||
id: item.options('id'),
|
||||
title: Ox.stripTags(item.options('title')[0])
|
||||
});
|
||||
*/
|
||||
}
|
||||
self.options.selected = position;
|
||||
}
|
||||
|
||||
function selectNextItem() {
|
||||
var offset,
|
||||
selected = self.options.selected;
|
||||
//Ox.print('sNI', selected)
|
||||
if (!isLastEnabledItem()) {
|
||||
if (selected == -1) {
|
||||
scrollMenuUp();
|
||||
} else {
|
||||
that.items[selected].removeClass('OxSelected');
|
||||
}
|
||||
do {
|
||||
selected++;
|
||||
} while (that.items[selected].options('disabled'))
|
||||
selectItem(selected);
|
||||
offset = that.items[selected].offset().top + itemHeight -
|
||||
that.$container.offset().top - that.$container.height();
|
||||
if (offset > 0) {
|
||||
if (that.$scrollbars.up.is(':hidden')) {
|
||||
that.$scrollbars.up.show();
|
||||
that.$container.height(that.$container.height() - itemHeight);
|
||||
offset += itemHeight;
|
||||
}
|
||||
if (selected == that.items.length - 1) {
|
||||
that.$scrollbars.down.hide();
|
||||
that.$container.height(that.$container.height() + itemHeight);
|
||||
} else {
|
||||
that.$content.css({
|
||||
top: ((parseInt(that.$content.css('top')) || 0) - offset) + 'px'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectPreviousItem() {
|
||||
var offset,
|
||||
selected = self.options.selected;
|
||||
//Ox.print('sPI', selected)
|
||||
if (selected > - 1) {
|
||||
if (!isFirstEnabledItem()) {
|
||||
that.items[selected].removeClass('OxSelected');
|
||||
do {
|
||||
selected--;
|
||||
} while (that.items[selected].options('disabled'))
|
||||
selectItem(selected);
|
||||
}
|
||||
offset = that.items[selected].offset().top - that.$container.offset().top;
|
||||
if (offset < 0) {
|
||||
if (that.$scrollbars.down.is(':hidden')) {
|
||||
that.$scrollbars.down.show();
|
||||
that.$container.height(that.$container.height() - itemHeight);
|
||||
}
|
||||
if (selected == 0) {
|
||||
that.$scrollbars.up.hide();
|
||||
that.$container.height(that.$container.height() + itemHeight);
|
||||
}
|
||||
that.$content.css({
|
||||
top: ((parseInt(that.$content.css('top')) || 0) - offset) + 'px'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectSubmenu() {
|
||||
//Ox.print('selectSubmenu', self.options.selected)
|
||||
if (self.options.selected > -1) {
|
||||
var submenu = that.submenus[that.items[self.options.selected].options('id')];
|
||||
//Ox.print('submenu', submenu, that.submenus);
|
||||
if (submenu && submenu.hasEnabledItems()) {
|
||||
submenu.gainFocus();
|
||||
submenu.selectFirstItem();
|
||||
} else if (self.options.mainmenu) {
|
||||
self.options.mainmenu.selectNextMenu();
|
||||
}
|
||||
} else if (self.options.mainmenu) {
|
||||
self.options.mainmenu.selectNextMenu();
|
||||
}
|
||||
}
|
||||
|
||||
function selectSupermenu() {
|
||||
//Ox.print('selectSupermenu', self.options.selected)
|
||||
if (self.options.parent) {
|
||||
self.options.selected > -1 && that.items[self.options.selected].trigger('mouseleave');
|
||||
scrollMenuUp();
|
||||
self.options.parent.gainFocus();
|
||||
} else if (self.options.mainmenu) {
|
||||
self.options.mainmenu.selectPreviousMenu();
|
||||
}
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'items') {
|
||||
constructItems(value);
|
||||
} else if (key == 'selected') {
|
||||
that.$content.find('.OxSelected').removeClass('OxSelected');
|
||||
selectItem(value);
|
||||
}
|
||||
}
|
||||
|
||||
that.addItem = function(item, position) {
|
||||
|
||||
};
|
||||
|
||||
that.addItemAfter = function(item, id) {
|
||||
|
||||
};
|
||||
|
||||
that.addItemBefore = function(item, id) {
|
||||
|
||||
};
|
||||
|
||||
that.checkItem = function(id) {
|
||||
var item = that.getItem(id);
|
||||
if (item.options('group')) {
|
||||
var position = getItemPositionById(id),
|
||||
toggled = self.optionGroups[item.options('group')].toggle(position);
|
||||
if (toggled.length) {
|
||||
toggled.forEach(function(pos) {
|
||||
that.items[pos].toggleChecked();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
item.options({
|
||||
checked: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
that.getItem = function(id) {
|
||||
//Ox.print('id', id)
|
||||
var ids = id.split('_'),
|
||||
item;
|
||||
if (ids.length == 1) {
|
||||
Ox.forEach(that.items, function(v) {
|
||||
if (v.options('id') == id) {
|
||||
item = v;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (!item) {
|
||||
Ox.forEach(that.submenus, function(submenu) {
|
||||
item = submenu.getItem(id);
|
||||
return !item;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
item = that.submenus[ids.shift()].getItem(ids.join('_'));
|
||||
}
|
||||
return item;
|
||||
};
|
||||
|
||||
that.getSubmenu = function(id) {
|
||||
var ids = id.split('_'),
|
||||
submenu;
|
||||
if (ids.length == 1) {
|
||||
submenu = that.submenus[id];
|
||||
} else {
|
||||
submenu = that.submenus[ids.shift()].getSubmenu(ids.join('_'));
|
||||
}
|
||||
//Ox.print('getSubmenu', id, submenu);
|
||||
return submenu;
|
||||
}
|
||||
|
||||
that.hasEnabledItems = function() {
|
||||
var ret = false;
|
||||
Ox.forEach(that.items, function(item) {
|
||||
if (!item.options('disabled')) {
|
||||
return ret = true;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
that.hideMenu = function() {
|
||||
if (that.is(':hidden')) {
|
||||
return;
|
||||
}
|
||||
Ox.forEach(that.submenus, function(submenu) {
|
||||
if (submenu.is(':visible')) {
|
||||
submenu.hideMenu();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
selectItem(-1);
|
||||
scrollMenuUp();
|
||||
that.$scrollbars.up.is(':visible') && that.$scrollbars.up.hide();
|
||||
that.$scrollbars.down.is(':visible') && that.$scrollbars.down.hide();
|
||||
if (self.options.parent) {
|
||||
//self.options.element.removeClass('OxSelected');
|
||||
self.options.parent.options({
|
||||
selected: -1
|
||||
});
|
||||
}
|
||||
that.hide()
|
||||
.loseFocus()
|
||||
.triggerEvent('hide');
|
||||
that.$layer.hide();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.removeItem = function() {
|
||||
|
||||
};
|
||||
|
||||
that.selectFirstItem = function() {
|
||||
selectNextItem();
|
||||
};
|
||||
|
||||
that.showMenu = function() {
|
||||
if (!that.is(':hidden')) {
|
||||
return;
|
||||
}
|
||||
if (!self.options.parent && !that.$layer.parent().length) {
|
||||
that.$layer.appendTo(Ox.UI.$body);
|
||||
}
|
||||
that.parent().length == 0 && that.appendTo(Ox.UI.$body);
|
||||
that.css({
|
||||
left: '-1000px',
|
||||
top: '-1000px',
|
||||
}).show();
|
||||
var offset = self.options.element.offset(),
|
||||
width = self.options.element.outerWidth(),
|
||||
height = self.options.element.outerHeight(),
|
||||
left = Ox.limit(
|
||||
offset.left + self.options.offset.left + (self.options.side == 'bottom' ? 0 : width),
|
||||
0, Ox.UI.$window.width() - that.width()
|
||||
),
|
||||
top = offset.top + self.options.offset.top + (self.options.side == 'bottom' ? height : 0),
|
||||
menuHeight = that.$content.outerHeight(); // fixme: why is outerHeight 0 when hidden?
|
||||
menuMaxHeight = Math.floor(Ox.UI.$window.height() - top - 16);
|
||||
if (self.options.parent) {
|
||||
if (menuHeight > menuMaxHeight) {
|
||||
top = Ox.limit(top - menuHeight + menuMaxHeight, self.options.parent.offset().top, top);
|
||||
menuMaxHeight = Math.floor(Ox.UI.$window.height() - top - 16);
|
||||
}
|
||||
}
|
||||
that.css({
|
||||
left: left + 'px',
|
||||
top: top + 'px'
|
||||
});
|
||||
if (menuHeight > menuMaxHeight) {
|
||||
that.$container.height(menuMaxHeight - itemHeight - 8); // margin
|
||||
that.$scrollbars.down.show();
|
||||
} else {
|
||||
that.$container.height(menuHeight);
|
||||
}
|
||||
if (!self.options.parent) {
|
||||
that.gainFocus();
|
||||
}
|
||||
that.$layer.show();
|
||||
return that;
|
||||
//that.triggerEvent('show');
|
||||
};
|
||||
|
||||
that.toggleMenu = function() {
|
||||
that.is(':hidden') ? that.showMenu() : that.hideMenu();
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
118
source/Ox.UI/js/Menu/Ox.MenuItem.js
Normal file
118
source/Ox.UI/js/Menu/Ox.MenuItem.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.MenuItem = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('tr', self)
|
||||
.defaults({
|
||||
bind: [], // fixme: what's this?
|
||||
checked: null,
|
||||
disabled: false,
|
||||
group: '',
|
||||
icon: '',
|
||||
id: '',
|
||||
items: [],
|
||||
keyboard: '',
|
||||
menu: null, // fixme: is passing the menu to 100s of menu items really memory-neutral?
|
||||
position: 0,
|
||||
title: [],
|
||||
})
|
||||
.options($.extend(options, {
|
||||
keyboard: parseKeyboard(options.keyboard || self.defaults.keyboard),
|
||||
title: Ox.toArray(options.title || self.defaults.title)
|
||||
}))
|
||||
.addClass('OxItem' + (self.options.disabled ? ' OxDisabled' : ''))
|
||||
/*
|
||||
.attr({
|
||||
id: Ox.toCamelCase(self.options.menu.options('id') + '/' + self.options.id)
|
||||
})
|
||||
*/
|
||||
.data('group', self.options.group); // fixme: why?
|
||||
|
||||
if (self.options.group && self.options.checked === null) {
|
||||
self.options.checked = false;
|
||||
}
|
||||
|
||||
// construct
|
||||
that.append(
|
||||
that.$status = $('<td>', {
|
||||
'class': 'OxCell OxStatus',
|
||||
html: self.options.checked ? Ox.UI.symbols.check : ''
|
||||
})
|
||||
)
|
||||
.append(
|
||||
that.$icon = $('<td>', {
|
||||
'class': 'OxCell OxIcon'
|
||||
})
|
||||
.append(self.options.icon ?
|
||||
$('<img>', {
|
||||
src: self.options.icon
|
||||
}) : null
|
||||
)
|
||||
)
|
||||
.append(
|
||||
that.$title = $('<td>', {
|
||||
'class': 'OxCell OxTitle',
|
||||
html: self.options.title[0]
|
||||
})
|
||||
)
|
||||
.append(
|
||||
$('<td>', {
|
||||
'class': 'OxCell OxModifiers',
|
||||
html: $.map(self.options.keyboard.modifiers, function(modifier) {
|
||||
return Ox.UI.symbols[modifier];
|
||||
}).join('')
|
||||
})
|
||||
)
|
||||
.append(
|
||||
$('<td>', {
|
||||
'class': 'OxCell Ox' + (self.options.items.length ? 'Submenu' : 'Key'),
|
||||
html: self.options.items.length ? Ox.UI.symbols.triangle_right :
|
||||
Ox.UI.symbols[self.options.keyboard.key] ||
|
||||
self.options.keyboard.key.toUpperCase()
|
||||
})
|
||||
);
|
||||
|
||||
function parseKeyboard(str) {
|
||||
var modifiers = str.split(' '),
|
||||
key = modifiers.pop();
|
||||
return {
|
||||
modifiers: modifiers,
|
||||
key: key
|
||||
};
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'checked') {
|
||||
that.$status.html(value ? Ox.UI.symbols.check : '')
|
||||
} else if (key == 'disabled') {
|
||||
that.toggleClass('OxDisabled'); // fixme: this will only work if onChange is only invoked on actual change
|
||||
} else if (key == 'title') {
|
||||
self.options.title = Ox.toArray(value);
|
||||
that.$title.html(self.options.title[0]);
|
||||
}
|
||||
}
|
||||
|
||||
that.toggle = function() {
|
||||
// toggle id and title
|
||||
};
|
||||
|
||||
that.toggleChecked = function() {
|
||||
that.options({
|
||||
checked: !self.options.checked
|
||||
});
|
||||
};
|
||||
|
||||
that.toggleDisabled = function() {
|
||||
|
||||
};
|
||||
|
||||
that.toggleTitle = function() {
|
||||
//Ox.print('s.o.t', self.options.title)
|
||||
that.options({
|
||||
title: self.options.title.reverse()
|
||||
});
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
84
source/Ox.UI/js/Panel/Ox.CollapsePanel.js
Normal file
84
source/Ox.UI/js/Panel/Ox.CollapsePanel.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/**
|
||||
*/
|
||||
Ox.CollapsePanel = function(options, self) {
|
||||
var self = self || {},
|
||||
that = new Ox.Panel({}, self)
|
||||
.defaults({
|
||||
collapsed: false,
|
||||
extras: [],
|
||||
size: 16,
|
||||
title: ''
|
||||
})
|
||||
.options(options)
|
||||
.addClass('OxCollapsePanel'),
|
||||
// fixme: the following should all be self.foo
|
||||
title = self.options.collapsed ?
|
||||
[{id: 'expand', title: 'right'}, {id: 'collapse', title: 'down'}] :
|
||||
[{id: 'collapse', title: 'down'}, {id: 'expand', title: 'right'}],
|
||||
$titlebar = new Ox.Bar({
|
||||
orientation: 'horizontal',
|
||||
size: self.options.size,
|
||||
})
|
||||
.dblclick(dblclickTitlebar)
|
||||
.appendTo(that),
|
||||
$switch = new Ox.Button({
|
||||
style: 'symbol',
|
||||
title: title,
|
||||
type: 'image',
|
||||
})
|
||||
.click(toggleCollapsed)
|
||||
.appendTo($titlebar),
|
||||
$title = new Ox.Element()
|
||||
.addClass('OxTitle')
|
||||
.html(self.options.title/*.toUpperCase()*/)
|
||||
.appendTo($titlebar),
|
||||
$extras;
|
||||
if (self.options.extras.length) {
|
||||
$extras = new Ox.Element()
|
||||
.addClass('OxExtras')
|
||||
.appendTo($titlebar);
|
||||
self.options.extras.forEach(function($extra) {
|
||||
$extra.appendTo($extras);
|
||||
});
|
||||
}
|
||||
that.$content = new Ox.Element()
|
||||
.addClass('OxContent')
|
||||
.appendTo(that);
|
||||
// fixme: doesn't work, content still empty
|
||||
// need to hide it if collapsed
|
||||
if (self.options.collapsed) {
|
||||
that.$content.css({
|
||||
marginTop: -that.$content.height() + 'px'
|
||||
});
|
||||
}
|
||||
function dblclickTitlebar(e) {
|
||||
if (!$(e.target).hasClass('OxButton')) {
|
||||
$switch.trigger('click');
|
||||
}
|
||||
}
|
||||
function toggleCollapsed() {
|
||||
var marginTop;
|
||||
self.options.collapsed = !self.options.collapsed;
|
||||
marginTop = self.options.collapsed ? -that.$content.height() : 0;
|
||||
that.$content.animate({
|
||||
marginTop: marginTop + 'px'
|
||||
}, 200);
|
||||
that.triggerEvent('toggle', {
|
||||
collapsed: self.options.collapsed
|
||||
});
|
||||
}
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'collapsed') {
|
||||
|
||||
} else if (key == 'title') {
|
||||
$title.html(self.options.title);
|
||||
}
|
||||
};
|
||||
that.update = function() { // fixme: used anywhere?
|
||||
self.options.collapsed && that.$content.css({
|
||||
marginTop: -that.$content.height()
|
||||
});
|
||||
};
|
||||
return that;
|
||||
};
|
||||
9
source/Ox.UI/js/Panel/Ox.Panel.js
Normal file
9
source/Ox.UI/js/Panel/Ox.Panel.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/**
|
||||
*/
|
||||
Ox.Panel = function(options, self) {
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self)
|
||||
.addClass('OxPanel');
|
||||
return that;
|
||||
};
|
||||
435
source/Ox.UI/js/Panel/Ox.SplitPanel.js
Normal file
435
source/Ox.UI/js/Panel/Ox.SplitPanel.js
Normal file
|
|
@ -0,0 +1,435 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
/**
|
||||
options:
|
||||
elements: [{ array of one, two or three elements
|
||||
collapsible: false, collapsible or not (only for outer elements)
|
||||
collapsed: false, collapsed or not (only for collapsible elements)
|
||||
element: {}, OxElement (if any element is resizable or
|
||||
collapsible, all OxElements must have an id)
|
||||
resizable: false, resizable or not (only for outer elements)
|
||||
resize: [], array of sizes (only for resizable elements,
|
||||
first value is min, last value is max,
|
||||
other values are 'snappy' points in between)
|
||||
size: 0 size in px (one element must have no size)
|
||||
}],
|
||||
orientation: '' 'horizontal' or 'vertical'
|
||||
events:
|
||||
resize
|
||||
toggle
|
||||
*/
|
||||
Ox.SplitPanel = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element({}, self) // fixme: Container
|
||||
.defaults({
|
||||
elements: [],
|
||||
orientation: 'horizontal'
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxSplitPanel');
|
||||
|
||||
$.extend(self, {
|
||||
dimensions: Ox.UI.DIMENSIONS[self.options.orientation],
|
||||
edges: Ox.UI.EDGES[self.options.orientation],
|
||||
length: self.options.elements.length,
|
||||
resizebarElements: [],
|
||||
$resizebars: []
|
||||
});
|
||||
|
||||
// create elements
|
||||
that.$elements = [];
|
||||
self.options.elements.forEach(function(v, i) {
|
||||
self.options.elements[i] = $.extend({
|
||||
collapsible: false,
|
||||
collapsed: false,
|
||||
resizable: false,
|
||||
resize: [],
|
||||
size: 'auto'
|
||||
}, v);
|
||||
that.$elements[i] = v.element
|
||||
.css(self.edges[2], (parseInt(v.element.css(self.edges[2])) || 0) + 'px')
|
||||
.css(self.edges[3], (parseInt(v.element.css(self.edges[3])) || 0) + 'px');
|
||||
//alert(v.element.css(self.edges[3]))
|
||||
});
|
||||
|
||||
// create resizebars
|
||||
self.options.elements.forEach(function(v, i) {
|
||||
//that.append(element)
|
||||
//Ox.print('V: ', v, that.$elements[i])
|
||||
var index = i == 0 ? 0 : 1;
|
||||
that.$elements[i].appendTo(that.$element); // fixme: that.$content
|
||||
if (v.collapsible || v.resizable) {
|
||||
//Ox.print('v.size', v.size)
|
||||
self.resizebarElements[index] = i < 2 ? [0, 1] : [1, 2];
|
||||
self.$resizebars[index] = new Ox.Resizebar({
|
||||
collapsible: v.collapsible,
|
||||
edge: self.edges[index],
|
||||
elements: [
|
||||
that.$elements[self.resizebarElements[index][0]],
|
||||
that.$elements[self.resizebarElements[index][1]]
|
||||
],
|
||||
id: v.element.options('id'),
|
||||
orientation: self.options.orientation == 'horizontal' ? 'vertical' : 'horizontal',
|
||||
parent: that, // fixme: that.$content
|
||||
resizable: v.resizable,
|
||||
resize: v.resize,
|
||||
size: v.size
|
||||
});
|
||||
self.$resizebars[index][i == 0 ? 'insertAfter' : 'insertBefore'](that.$elements[i]);
|
||||
}
|
||||
});
|
||||
|
||||
self.options.elements.forEach(function(v, i) {
|
||||
v.collapsed && that.css(
|
||||
self.edges[i == 0 ? 0 : 1], -self.options.elements[i].size + 'px'
|
||||
);
|
||||
});
|
||||
|
||||
setSizes(true);
|
||||
|
||||
function getPositionById(id) {
|
||||
var position = -1;
|
||||
Ox.forEach(self.options.elements, function(element, i) {
|
||||
if (element.element.options('id') == id) {
|
||||
position = i;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
//Ox.print('getPositionById', id, position);
|
||||
return position;
|
||||
}
|
||||
|
||||
function getSize(element) {
|
||||
return element.size + (element.collapsible || element.resizable);
|
||||
//return (element.size + (element.collapsible || element.resizable)) * !element.collapsed;
|
||||
}
|
||||
|
||||
function getVisibleSize(element) {
|
||||
return getSize(element) * !element.collapsed;
|
||||
}
|
||||
|
||||
function setSizes(init) {
|
||||
self.options.elements.forEach(function(v, i) {
|
||||
// fixme: maybe we can add a conditional here, since init
|
||||
// is about elements that are collapsed splitpanels
|
||||
var edges = [
|
||||
(init && parseInt(that.$elements[i].css(self.edges[0]))) || 0,
|
||||
(init && parseInt(that.$elements[i].css(self.edges[1]))) || 0
|
||||
];
|
||||
v.size != 'auto' && that.$elements[i].css(self.dimensions[0], v.size + 'px');
|
||||
if (i == 0) {
|
||||
that.$elements[i].css(
|
||||
self.edges[0], edges[0] + 'px'
|
||||
);
|
||||
that.$elements[i].css(
|
||||
self.edges[1], (getSize(self.options.elements[1]) + (length == 3 ? getSize(self.options.elements[2]) : 0)) + 'px'
|
||||
);
|
||||
} else if (i == 1) {
|
||||
that.$elements[i].css(
|
||||
self.edges[0], self.options.elements[0].size == 'auto' ? 'auto' :
|
||||
edges[0] + getSize(self.options.elements[0]) + 'px'
|
||||
);
|
||||
(self.options.elements[0].size != 'auto' || v.size != 'auto') && that.$elements[i].css(
|
||||
self.edges[1], (self.length == 3 ? getSize(self.options.elements[2]) : 0) + 'px'
|
||||
);
|
||||
} else {
|
||||
that.$elements[i].css(
|
||||
self.edges[0], (self.options.elements[1].size == 'auto' || v.size == 'auto') ? 'auto' :
|
||||
(getVisibleSize(self.options.elements[0]) + getVisibleSize(self.options.elements[1])) + 'px'
|
||||
);
|
||||
that.$elements[i].css(
|
||||
self.edges[1], edges[1] + 'px'
|
||||
);
|
||||
}
|
||||
if (v.collapsible || v.resizable) {
|
||||
self.$resizebars[i == 0 ? 0 : 1].css(self.edges[i == 0 ? 0 : 1], v.size);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
that.isCollapsed = function(id) {
|
||||
var pos = Ox.isNumber(id) ? id : getPositionById(id);
|
||||
return self.options.elements[pos].collapsed;
|
||||
};
|
||||
|
||||
that.replaceElement = function(id, element) {
|
||||
// one can pass pos instead of id
|
||||
var pos = Ox.isNumber(id) ? id : getPositionById(id);
|
||||
//Ox.print('replace', pos, element);
|
||||
//Ox.print('element', self.options.elements[pos].element, element)
|
||||
that.$elements[pos] = element
|
||||
.css(self.edges[2], (parseInt(element.css(self.edges[2])) || 0) + 'px')
|
||||
.css(self.edges[3], (parseInt(element.css(self.edges[3])) || 0) + 'px');
|
||||
//alert(element.css(self.edges[3]))
|
||||
self.options.elements[pos].element.replaceWith(element.$element.$element || element.$element);
|
||||
self.options.elements[pos].element = element;
|
||||
setSizes();
|
||||
self.$resizebars.forEach(function($resizebar, i) {
|
||||
$resizebar.options({
|
||||
elements: [
|
||||
that.$elements[self.resizebarElements[i][0]],
|
||||
that.$elements[self.resizebarElements[i][1]]
|
||||
]
|
||||
});
|
||||
});
|
||||
//Ox.print(self.options.elements[pos])
|
||||
return that;
|
||||
};
|
||||
|
||||
that.replaceElements = function(elements) {
|
||||
elements.forEach(function(element, i) {
|
||||
if (Ox.isNumber(element.size)) {
|
||||
that.size(i, element.size);
|
||||
if (element.collapsible || element.resizable) {
|
||||
self.$resizebars[i == 0 ? 0 : 1].options({
|
||||
collapsible: element.collapsible,
|
||||
resizable: element.resizable,
|
||||
size: element.size
|
||||
});
|
||||
}
|
||||
}
|
||||
that.replace(i, element.element);
|
||||
});
|
||||
self.options.elements = elements;
|
||||
self.$resizebars.forEach(function($resizebar, i) {
|
||||
$resizebar.options({
|
||||
elements: [
|
||||
that.$elements[self.resizebarElements[i][0]],
|
||||
that.$elements[self.resizebarElements[i][1]]
|
||||
]
|
||||
});
|
||||
});
|
||||
return that;
|
||||
}
|
||||
|
||||
that.size = function(id, size) {
|
||||
// one can pass pos instead of id
|
||||
var pos = Ox.isNumber(id) ? id : getPositionById(id),
|
||||
element = self.options.elements[pos];
|
||||
if (arguments.length == 1) {
|
||||
return element.element[self.dimensions[0]]() * !that.isCollapsed(pos);
|
||||
} else {
|
||||
element.size = size;
|
||||
setSizes();
|
||||
return that;
|
||||
}
|
||||
};
|
||||
|
||||
that.toggle = function(id) {
|
||||
// one can pass pos instead of id
|
||||
var pos = Ox.isNumber(id) ? id : getPositionById(id),
|
||||
element = self.options.elements[pos],
|
||||
value = parseInt(that.css(self.edges[pos == 0 ? 0 : 1])) +
|
||||
element.element[self.dimensions[0]]() *
|
||||
(element.collapsed ? 1 : -1),
|
||||
animate = {};
|
||||
animate[self.edges[pos == 0 ? 0 : 1]] = value;
|
||||
that.animate(animate, 200, function() { // fixme: 250?
|
||||
element.collapsed = !element.collapsed;
|
||||
element.element.triggerEvent('toggle', {
|
||||
'collapsed': element.collapsed
|
||||
});
|
||||
element = self.options.elements[pos == 0 ? 1 : pos - 1];
|
||||
element.element.triggerEvent(
|
||||
'resize',
|
||||
element.element[self.dimensions[0]]()
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
that.updateSize = function(pos, size) {
|
||||
// this is called from resizebar
|
||||
var pos = pos == 0 ? 0 : self.options.elements.length - 1; // fixme: silly that 0 or 1 is passed, and not pos
|
||||
self.options.elements[pos].size = size;
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
|
||||
Ox.SplitPanel_ = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
elements: [],
|
||||
orientation: 'horizontal'
|
||||
})
|
||||
.options(options)
|
||||
.addClass(
|
||||
'OxSplitPanel_ Ox' + Ox.toTitleCase(self.options.orientation)
|
||||
);
|
||||
|
||||
Ox.extend(self, {
|
||||
$separators: [],
|
||||
clientXY: self.options.orientation == 'horizontal' ? 'clientX' : 'clientY',
|
||||
dimensions: Ox.UI.DIMENSIONS[self.options.orientation],
|
||||
edges: Ox.UI.EDGES[self.options.orientation]
|
||||
});
|
||||
|
||||
self.options.elements.forEach(function(element, i) {
|
||||
self.options.elements[i] = Ox.extend({
|
||||
collapsible: false,
|
||||
collapsed: false,
|
||||
resizable: false,
|
||||
resize: [],
|
||||
size: 'auto'
|
||||
}, element);
|
||||
});
|
||||
|
||||
self.autoPercent = (100 - self.options.elements.reduce(function(val, element) {
|
||||
return val + (Ox.endsWith(element.size, '%') ? parseFloat(element.size) : 0);
|
||||
}, 0)) / self.options.elements.filter(function(element) {
|
||||
return element.size == 'auto';
|
||||
}).length + '%';
|
||||
|
||||
self.options.elements.forEach(function(element, i) {
|
||||
var flex, index = i == 0 ? 0 : 1;
|
||||
if (Ox.isNumber(element.size)) {
|
||||
element.element.css(self.dimensions[0], element.size + 'px');
|
||||
} else {
|
||||
flex = (
|
||||
element.size == 'auto' ? self.autoPercent : element.size
|
||||
).replace('%', '');
|
||||
element.element.css({
|
||||
boxFlex: flex,
|
||||
MozBoxFlex: flex,
|
||||
WebkitBoxFlex: flex
|
||||
});
|
||||
}
|
||||
element.element.appendTo(that);
|
||||
if (element.collapsible || element.resizable) {
|
||||
self.$separators.push(
|
||||
Ox.Element()
|
||||
.addClass('OxSeparator')
|
||||
.bindEvent({
|
||||
anyclick: function() {
|
||||
that.toggle(i);
|
||||
},
|
||||
dragstart: function(event, e) {
|
||||
dragstart(i, e);
|
||||
},
|
||||
drag: function(event, e) {
|
||||
drag(i, e);
|
||||
},
|
||||
dragend: function(event, e) {
|
||||
dragend(i, e);
|
||||
},
|
||||
})
|
||||
.append($('<div>').addClass('OxSpace'))
|
||||
.append($('<div>').addClass('OxLine'))
|
||||
.append($('<div>').addClass('OxSpace'))
|
||||
['insert' + (index ? 'Before' : 'After')](element.element)
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function dragstart(pos, e) {
|
||||
var element = self.options.elements[pos],
|
||||
size = element.element[self.dimensions[0]]();
|
||||
if (element.resizable && !element.collapsed) {
|
||||
self.drag = {
|
||||
size: size,
|
||||
startPos: e[self.clientXY],
|
||||
startSize: size
|
||||
};
|
||||
Ox.print('self.drag', self.drag)
|
||||
}
|
||||
}
|
||||
|
||||
function drag(pos, e) {
|
||||
var data = {},
|
||||
element = self.options.elements[pos],
|
||||
index = pos == 0 ? 0 : 1;
|
||||
if (element.resizable && !element.collapsed) {
|
||||
var d = e[self.clientXY] - self.drag.startPos,
|
||||
size = Ox.limit(
|
||||
self.drag.startSize + d * (index ? -1 : 1),
|
||||
element.resize[0],
|
||||
element.resize[element.resize.length - 1]
|
||||
);
|
||||
Ox.forEach(element.resize, function(v) {
|
||||
if (size >= v - 8 && size <= v + 8) {
|
||||
size = v;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (size != self.drag.size) {
|
||||
self.drag.size = size;
|
||||
data[self.dimensions[0]] = size;
|
||||
element.element
|
||||
.css(self.dimensions[0], size + 'px')
|
||||
.triggerEvent('resize', data);
|
||||
triggerEvents('resize', pos);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function dragend(pos, e) {
|
||||
var data = {},
|
||||
element = self.options.elements[pos];
|
||||
if (element.resizable && !element.collapsed) {
|
||||
data[self.dimensions[0]] = self.drag.size
|
||||
element.element.triggerEvent('resizeend', data);
|
||||
triggerEvents('resizeend', pos);
|
||||
}
|
||||
}
|
||||
|
||||
function triggerEvents(event, pos) {
|
||||
var data = {};
|
||||
self.options.elements.forEach(function(element, i) {
|
||||
if (i != pos && element.size == 'auto') {
|
||||
data[self.dimensions[0]] = element.element[self.dimensions[0]]();
|
||||
element.element.triggerEvent(event, data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
that.replaceElement = function(pos, element) {
|
||||
var $element = self.options.elements[pos].element,
|
||||
size = self.options.elements[pos].size;
|
||||
$element.replaceWith(self.options.elements[pos].element = element);
|
||||
if (size == 'auto') {
|
||||
$element.css(self.boxFlexCSS);
|
||||
} else {
|
||||
$element.css(self.dimensions[0], size + 'px')
|
||||
}
|
||||
return that;
|
||||
};
|
||||
|
||||
that.size = function(pos, size) {
|
||||
var element = self.options.elements[pos],
|
||||
ret;
|
||||
if (Ox.isUndefined(size)) {
|
||||
ret = element.element[self.dimensions[0]]();
|
||||
} else {
|
||||
element.size = size;
|
||||
element.element.css(self.dimensions[0], size + 'px')
|
||||
ret = that;
|
||||
}
|
||||
return that;
|
||||
}
|
||||
|
||||
that.toggle = function(pos) {
|
||||
var css = {},
|
||||
element = self.options.elements[pos],
|
||||
flex,
|
||||
index = pos == 0 ? 0 : 1,
|
||||
size = element.element[self.dimensions[0]]();
|
||||
if (element.collapsible) {
|
||||
element.collapsed = !element.collapsed;
|
||||
css['margin' + Ox.toTitleCase(self.edges[0][index])] =
|
||||
element.collapsed ? -size : 0;
|
||||
Ox.print('css', css);
|
||||
that.animate(css, 250, function() {
|
||||
element.element.triggerEvent('toggle', {collapsed: element.collapsed});
|
||||
triggerEvents('resize', pos);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
4
source/Ox.UI/js/Panel/Ox.TabPanel.js
Normal file
4
source/Ox.UI/js/Panel/Ox.TabPanel.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.TabPanel = function(options, self) {
|
||||
|
||||
};
|
||||
118
source/Ox.UI/js/Video/Ox.AnnotationPanel.js
Normal file
118
source/Ox.UI/js/Video/Ox.AnnotationPanel.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.AnnotationPanel = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
id: '',
|
||||
items: [],
|
||||
title: '',
|
||||
type: 'text',
|
||||
width: 0
|
||||
})
|
||||
.options(options || {});
|
||||
|
||||
self.selected = -1;
|
||||
|
||||
that.$element = new Ox.CollapsePanel({
|
||||
collapsed: false,
|
||||
extras: [
|
||||
new Ox.Button({
|
||||
id: 'add',
|
||||
style: 'symbol',
|
||||
title: 'Add',
|
||||
type: 'image'
|
||||
}).bindEvent({
|
||||
click: function(event, data) {
|
||||
that.triggerEvent('add', {value: ''});
|
||||
}
|
||||
})
|
||||
],
|
||||
size: 16,
|
||||
title: self.options.title
|
||||
})
|
||||
.addClass('OxAnnotationPanel')
|
||||
.bindEvent({
|
||||
toggle: togglePanel
|
||||
});
|
||||
that.$content = that.$element.$content;
|
||||
|
||||
self.$annotations = new Ox.List({
|
||||
construct: function(data) {
|
||||
return new Ox.Element('div')
|
||||
.addClass('OxAnnotation OxEditable OxTarget')
|
||||
.html(Ox.parseHTML(data.value));
|
||||
},
|
||||
items: $.map(self.options.items, function(v, i) {
|
||||
return {
|
||||
id: v.id || i + '',
|
||||
value: v.value
|
||||
};
|
||||
}),
|
||||
unique: 'id'
|
||||
})
|
||||
.bindEvent({
|
||||
open: function(event, data) {
|
||||
if (data.ids.length == 1) {
|
||||
var pos = Ox.getPositionById(self.$annotations.options('items'), data.ids[0]);
|
||||
self.$annotations.editItem(pos);
|
||||
|
||||
}
|
||||
},
|
||||
'delete': function(event, data) {
|
||||
that.triggerEvent('delete', data);
|
||||
},
|
||||
select: selectAnnotation,
|
||||
submit: updateAnnotation
|
||||
})
|
||||
.appendTo(that.$content);
|
||||
|
||||
/*
|
||||
self.$annotations = new Ox.Element('div')
|
||||
.appendTo(that.$content);
|
||||
self.$annotation = [];
|
||||
self.options.items.forEach(function(item, i) {
|
||||
self.$annotation[i] = new Ox.Element('div')
|
||||
.addClass('OxAnnotation')
|
||||
.html(item.value.replace(/\n/g, '<br/>'))
|
||||
.click(function() {
|
||||
clickAnnotation(i);
|
||||
})
|
||||
.appendTo(self.$annotations);
|
||||
});
|
||||
*/
|
||||
function selectAnnotation(event, data) {
|
||||
var item = Ox.getObjectById(self.options.items, data.ids[0]);
|
||||
that.triggerEvent('select', {
|
||||
'in': item['in'],
|
||||
'out': item.out,
|
||||
'layer': self.options.id
|
||||
});
|
||||
}
|
||||
function updateAnnotation(event, data) {
|
||||
var item = Ox.getObjectById(self.options.items, data.id);
|
||||
item.value = data.value;
|
||||
that.triggerEvent('submit', item);
|
||||
}
|
||||
|
||||
function togglePanel() {
|
||||
|
||||
}
|
||||
|
||||
that.addItem = function(item) {
|
||||
var pos = 0;
|
||||
self.options.items.splice(pos, 0, item);
|
||||
self.$annotations.addItems(pos, [item]);
|
||||
self.$annotations.editItem(pos);
|
||||
}
|
||||
|
||||
that.removeItems = function(ids) {
|
||||
self.$annotations.removeItems(ids);
|
||||
}
|
||||
that.deselectItems = function() {
|
||||
if(self.$annotations.options('selected'))
|
||||
self.$annotations.options('selected',[]);
|
||||
}
|
||||
return that;
|
||||
|
||||
};
|
||||
364
source/Ox.UI/js/Video/Ox.BlockTimeline.js
Normal file
364
source/Ox.UI/js/Video/Ox.BlockTimeline.js
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.BlockTimeline = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
cuts: [],
|
||||
duration: 0,
|
||||
find: '',
|
||||
matches: [],
|
||||
points: [0, 0],
|
||||
position: 0,
|
||||
subtitles: [],
|
||||
videoId: '',
|
||||
width: 0
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxTimelineSmall')
|
||||
.mousedown(mousedown)
|
||||
.mouseleave(mouseleave)
|
||||
.mousemove(mousemove)
|
||||
.bindEvent({
|
||||
drag: function(event, e) {
|
||||
mousedown(e);
|
||||
}
|
||||
});
|
||||
|
||||
$.extend(self, {
|
||||
$images: [],
|
||||
$lines: [],
|
||||
$markerPoint: [],
|
||||
$selection: [],
|
||||
$subtitles: [],
|
||||
$tooltip: new Ox.Tooltip({
|
||||
animate: false
|
||||
}).css({
|
||||
textAlign: 'center'
|
||||
}),
|
||||
hasSubtitles: self.options.subtitles.length,
|
||||
height: 16,
|
||||
lines: Math.ceil(self.options.duration / self.options.width),
|
||||
margin: 8
|
||||
});
|
||||
|
||||
that.css({
|
||||
width: (self.options.width + self.margin) + 'px',
|
||||
height: ((self.height + self.margin) * self.lines + 4) + 'px'
|
||||
});
|
||||
|
||||
getTimelineImageURL(function(url) {
|
||||
|
||||
self.timelineImageURL = url;
|
||||
|
||||
Ox.range(0, self.lines).forEach(function(i) {
|
||||
addLine(i);
|
||||
});
|
||||
|
||||
self.$markerPosition = $('<img>')
|
||||
.addClass('OxMarkerPosition')
|
||||
.attr({
|
||||
src: Ox.PATH + 'png/Ox.UI/videoMarkerPlay.png'
|
||||
})
|
||||
.css({
|
||||
position: 'absolute',
|
||||
width: '9px',
|
||||
height: '5px',
|
||||
zIndex: 10
|
||||
})
|
||||
.appendTo(that.$element);
|
||||
|
||||
setPosition();
|
||||
|
||||
['in', 'out'].forEach(function(v, i) {
|
||||
var titleCase = Ox.toTitleCase(v);
|
||||
self.$markerPoint[i] = $('<img>')
|
||||
.addClass('OxMarkerPoint' + titleCase)
|
||||
.attr({
|
||||
src: Ox.PATH + 'png/Ox.UI/videoMarker' + titleCase + '.png'
|
||||
})
|
||||
.appendTo(that.$element);
|
||||
setMarkerPoint(i);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function addLine(i) {
|
||||
// fixme: get URLs once, not once for every line
|
||||
self.$lines[i] = new Ox.Element('div')
|
||||
.css({
|
||||
top: i * (self.height + self.margin) + 'px',
|
||||
width: self.options.width + 'px'
|
||||
})
|
||||
.appendTo(that);
|
||||
self.$images[i] = $('<img>')
|
||||
.addClass('OxTimelineSmallImage')
|
||||
.attr({
|
||||
src: self.timelineImageURL
|
||||
})
|
||||
.css({
|
||||
marginLeft: (-i * self.options.width) + 'px'
|
||||
})
|
||||
.appendTo(self.$lines[i].$element)
|
||||
if (self.hasSubtitles) {
|
||||
self.subtitlesImageURL = getSubtitlesImageURL();
|
||||
self.$subtitles[i] = $('<img>')
|
||||
.addClass('OxTimelineSmallSubtitles')
|
||||
.attr({
|
||||
src: self.subtitlesImageURL
|
||||
})
|
||||
.css({
|
||||
marginLeft: (-i * self.options.width) + 'px'
|
||||
})
|
||||
.appendTo(self.$lines[i].$element);
|
||||
}
|
||||
if (self.options.points[0] != self.options.points[1]) {
|
||||
addSelection[i];
|
||||
}
|
||||
}
|
||||
|
||||
function addSelection(i) {
|
||||
self.selectionImageURL = getSelectionImageURL();
|
||||
self.$selection[i] && self.$selection[i].remove();
|
||||
self.$selection[i] = $('<img>')
|
||||
.addClass('OxTimelineSmallSelection')
|
||||
.attr({
|
||||
src: self.selectionImageURL
|
||||
})
|
||||
.css({
|
||||
marginLeft: (-i * self.options.width) + 'px'
|
||||
})
|
||||
.appendTo(self.$lines[i].$element);
|
||||
}
|
||||
|
||||
function getPosition(e) {
|
||||
//FIXME: this might still be broken in opera according to http://acko.net/blog/mouse-handling-and-absolute-positions-in-javascript
|
||||
return (e.offsetX ? e.offsetX : e.clientX - $(e.target).offset().left);
|
||||
}
|
||||
|
||||
function getSelectionImageURL() {
|
||||
var height = 18,
|
||||
width = Math.ceil(self.options.duration),
|
||||
$canvas = $('<canvas>')
|
||||
.attr({
|
||||
height: height,
|
||||
width: width
|
||||
}),
|
||||
canvas = $canvas[0],
|
||||
context = canvas.getContext('2d'),
|
||||
imageData = context.createImageData(width, height),
|
||||
data = imageData.data,
|
||||
points = $.map(self.options.points, function(v, i) {
|
||||
return Math.round(v) + i;
|
||||
}),
|
||||
top = 0,
|
||||
bottom = 18;
|
||||
Ox.range(points[0], points[1]).forEach(function(x) {
|
||||
Ox.range(top, bottom).forEach(function(y) {
|
||||
var color = (y == top || y == bottom - 1) ? [255, 255, 255, 255] : [255, 255, 255, 64],
|
||||
index = x * 4 + y * 4 * width;
|
||||
data[index] = color[0];
|
||||
data[index + 1] = color[1];
|
||||
data[index + 2] = color[2];
|
||||
data[index + 3] = color[3]
|
||||
});
|
||||
});
|
||||
context.putImageData(imageData, 0, 0);
|
||||
return canvas.toDataURL();
|
||||
}
|
||||
|
||||
function getSubtitle(position) {
|
||||
var subtitle = null;
|
||||
Ox.forEach(self.options.subtitles, function(v) {
|
||||
if (v['in'] <= position && v['out'] >= position) {
|
||||
subtitle = v;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
function getSubtitlesImageURL() {
|
||||
var height = 18,
|
||||
width = Math.ceil(self.options.duration),
|
||||
$canvas = $('<canvas>')
|
||||
.attr({
|
||||
height: height,
|
||||
width: width
|
||||
}),
|
||||
canvas = $canvas[0],
|
||||
context = canvas.getContext('2d'),
|
||||
imageData = context.createImageData(width, height),
|
||||
data = imageData.data;
|
||||
self.options.subtitles.forEach(function(v) {
|
||||
//var color = self.options.matches.indexOf(i) > -1 ? [255, 255, 0] : [255, 255, 255]
|
||||
var inPoint = Math.round(v['in']),
|
||||
outPoint = Math.round(v.out) + 1,
|
||||
lines = v.value.split('\n').length,
|
||||
bottom = 15,
|
||||
top = bottom - lines - 2;
|
||||
Ox.range(inPoint, outPoint).forEach(function(x) {
|
||||
Ox.range(top, bottom).forEach(function(y) {
|
||||
var color = (y == top || y == bottom - 1) ? [0, 0, 0] : [255, 255, 255],
|
||||
index = x * 4 + y * 4 * width;
|
||||
data[index] = color[0];
|
||||
data[index + 1] = color[1];
|
||||
data[index + 2] = color[2];
|
||||
data[index + 3] = 128
|
||||
});
|
||||
});
|
||||
});
|
||||
context.putImageData(imageData, 0, 0);
|
||||
return canvas.toDataURL();
|
||||
}
|
||||
|
||||
function getTimelineImageURL(callback) {
|
||||
var height = 16,
|
||||
images = Math.ceil(self.options.duration / 3600),
|
||||
loaded = 0,
|
||||
width = Math.ceil(self.options.duration),
|
||||
$canvas = $('<canvas>')
|
||||
.attr({
|
||||
height: height,
|
||||
width: width
|
||||
}),
|
||||
canvas = $canvas[0],
|
||||
context = canvas.getContext('2d');
|
||||
Ox.range(images).forEach(function(i) {
|
||||
var $img = $('<img>')
|
||||
.attr({
|
||||
src: '/' + self.options.videoId + '/timelines/timeline.16.' + i + '.png'
|
||||
})
|
||||
.load(function() {
|
||||
context.drawImage($img[0], i * 3600, 0);
|
||||
//Ox.print('loaded, images', loaded, images, $img[0])
|
||||
if (++loaded == images) {
|
||||
//Ox.print('callback', canvas.toDataURL().length)
|
||||
callback(canvas.toDataURL());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function mousedown(e) {
|
||||
var $target = $(e.target);
|
||||
if (
|
||||
$target.hasClass('OxTimelineSmallImage') ||
|
||||
$target.hasClass('OxTimelineSmallSubtitles') ||
|
||||
$target.hasClass('OxTimelineSmallSelection')
|
||||
) {
|
||||
self.options.position = getPosition(e);
|
||||
setPosition();
|
||||
that.triggerEvent('change', {
|
||||
position: self.options.position
|
||||
});
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function mouseleave(e) {
|
||||
self.$tooltip.hide();
|
||||
}
|
||||
|
||||
function mousemove(e) {
|
||||
var $target = $(e.target),
|
||||
position,
|
||||
subtitle;
|
||||
if (
|
||||
$target.hasClass('OxTimelineSmallImage') ||
|
||||
$target.hasClass('OxTimelineSmallSubtitles') ||
|
||||
$target.hasClass('OxTimelineSmallSelection')
|
||||
) {
|
||||
position = getPosition(e),
|
||||
subtitle = getSubtitle(position);
|
||||
self.$tooltip.options({
|
||||
title: subtitle ?
|
||||
'<span class=\'OxBright\'>' +
|
||||
Ox.highlight(subtitle.value, self.options.find).replace(/\n/g, '<br/>') + '</span><br/>' +
|
||||
Ox.formatDuration(subtitle['in'], 3) + ' - ' + Ox.formatDuration(subtitle['out'], 3) :
|
||||
Ox.formatDuration(position, 3)
|
||||
})
|
||||
.show(e.clientX, e.clientY);
|
||||
} else {
|
||||
self.$tooltip.hide();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setMarker() {
|
||||
self.$markerPosition
|
||||
.css({
|
||||
left: (self.options.position % self.options.width) + 'px',
|
||||
top: (parseInt(self.options.position / self.options.width) * (self.height + self.margin) + 2) + 'px',
|
||||
});
|
||||
}
|
||||
|
||||
function setMarkerPoint(i) {
|
||||
var position = Math.round(self.options.points[i]);
|
||||
self.$markerPoint[i]
|
||||
.css({
|
||||
left: (position % self.options.width) + 'px',
|
||||
top: (parseInt(position / self.options.width) * (self.height + self.margin) + 16) + 'px',
|
||||
});
|
||||
}
|
||||
|
||||
function setPosition() {
|
||||
self.options.position = Ox.limit(self.options.position, 0, self.options.duration);
|
||||
setMarker();
|
||||
}
|
||||
|
||||
function setWidth() {
|
||||
self.lines = Math.ceil(self.options.duration / self.options.width);
|
||||
that.css({
|
||||
width: (self.options.width + self.margin) + 'px',
|
||||
height: ((self.height + self.margin) * self.lines + 4) + 'px'
|
||||
});
|
||||
Ox.range(self.lines).forEach(function(i) {
|
||||
if (self.$lines[i]) {
|
||||
self.$lines[i].css({
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
self.$images[i].css({
|
||||
marginLeft: (-i * self.options.width) + 'px'
|
||||
});
|
||||
if (self.hasSubtitles) {
|
||||
self.$subtitles[i].css({
|
||||
marginLeft: (-i * self.options.width) + 'px'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
addLine(i);
|
||||
}
|
||||
});
|
||||
while (self.$lines.length > self.lines) {
|
||||
self.$lines[self.$lines.length - 1].removeElement();
|
||||
self.$lines.pop();
|
||||
}
|
||||
setMarker();
|
||||
setMarkerPoint(0);
|
||||
setMarkerPoint(1);
|
||||
}
|
||||
|
||||
function updateSelection() {
|
||||
self.$lines.forEach(function($line, i) {
|
||||
addSelection(i);
|
||||
});
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
//Ox.print('onChange:', key, value)
|
||||
if (key == 'points') {
|
||||
//Ox.print('key', key, 'value', value)
|
||||
setMarkerPoint(0);
|
||||
setMarkerPoint(1);
|
||||
updateSelection()
|
||||
} else if (key == 'position') {
|
||||
setPosition();
|
||||
} else if (key == 'width') {
|
||||
setWidth();
|
||||
}
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
183
source/Ox.UI/js/Video/Ox.FilesView.js
Normal file
183
source/Ox.UI/js/Video/Ox.FilesView.js
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
// fixme: this is not necessarily part of OxUI
|
||||
|
||||
/*
|
||||
============================================================================
|
||||
Pan.do/ra
|
||||
============================================================================
|
||||
*/
|
||||
|
||||
Ox.FilesView = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
id: ''
|
||||
})
|
||||
.options(options || {});
|
||||
|
||||
self.$toolbar = new Ox.Bar({
|
||||
size: 24
|
||||
});
|
||||
|
||||
self.$orderButton = new Ox.Button({
|
||||
title: 'Change Order of Users...'
|
||||
})
|
||||
.css({
|
||||
float: 'left',
|
||||
margin: '4px'
|
||||
})
|
||||
.appendTo(self.$toolbar);
|
||||
|
||||
self.$moveButton = new Ox.Button({
|
||||
disabled: 'true',
|
||||
title: 'Move Selected Files...'
|
||||
})
|
||||
.css({
|
||||
float: 'right',
|
||||
margin: '4px'
|
||||
})
|
||||
.appendTo(self.$toolbar);
|
||||
|
||||
self.$filesList = new Ox.TextList({
|
||||
columns: [
|
||||
{
|
||||
align: 'left',
|
||||
id: 'users',
|
||||
operator: '+',
|
||||
title: 'Users',
|
||||
visible: true,
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
id: 'folder',
|
||||
operator: '+',
|
||||
title: 'Folder',
|
||||
visible: true,
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
id: 'name',
|
||||
operator: '+',
|
||||
title: 'Name',
|
||||
visible: true,
|
||||
width: 360
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
id: 'type',
|
||||
operator: '+',
|
||||
title: 'Type',
|
||||
visible: true,
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
id: 'part',
|
||||
operator: '+',
|
||||
title: 'Part',
|
||||
visible: true,
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
format: {type: 'value', args: ['B']},
|
||||
id: 'size',
|
||||
operator: '-',
|
||||
title: 'Size',
|
||||
visible: true,
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
format: {type: 'resolution', args: ['px']},
|
||||
id: 'resolution',
|
||||
operator: '-',
|
||||
title: 'Resolution',
|
||||
visible: true,
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
format: {type: 'duration', args: [0, 'short']},
|
||||
id: 'duration',
|
||||
operator: '-',
|
||||
title: 'Duration',
|
||||
visible: true,
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
id: 'oshash',
|
||||
operator: '+',
|
||||
title: 'Hash',
|
||||
unique: true,
|
||||
visible: false,
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
id: 'instances',
|
||||
operator: '+',
|
||||
title: 'Instances',
|
||||
visible: false,
|
||||
width: 120
|
||||
}
|
||||
],
|
||||
columnsMovable: true,
|
||||
columnsRemovable: true,
|
||||
columnsResizable: true,
|
||||
columnsVisible: true,
|
||||
id: 'files',
|
||||
items: function(data, callback) {
|
||||
pandora.api.findFiles($.extend(data, {
|
||||
query: {
|
||||
conditions: [{
|
||||
key: 'id',
|
||||
value: self.options.id,
|
||||
operator: '='
|
||||
}]
|
||||
}
|
||||
}), callback);
|
||||
},
|
||||
scrollbarVisible: true,
|
||||
sort: [{key: 'name', operator:'+'}]
|
||||
})
|
||||
.bindEvent({
|
||||
open: openFiles,
|
||||
select: selectFiles
|
||||
});
|
||||
|
||||
self.$instancesList = new Ox.Element()
|
||||
.html('No files selected');
|
||||
|
||||
that.$element = new Ox.SplitPanel({
|
||||
elements: [
|
||||
{
|
||||
element: self.$toolbar,
|
||||
size: 24
|
||||
},
|
||||
{
|
||||
element: self.$filesList
|
||||
},
|
||||
{
|
||||
element: self.$instancesList,
|
||||
size: 80
|
||||
}
|
||||
],
|
||||
orientation: 'vertical'
|
||||
});
|
||||
|
||||
function openFiles(event, data) {
|
||||
//alert(JSON.stringify(self.$filesList.value(data.ids[0], 'instances')))
|
||||
}
|
||||
|
||||
function selectFiles(event, data) {
|
||||
//alert(JSON.stringify(self.$filesList.value(data.ids[0], 'instances')))
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
101
source/Ox.UI/js/Video/Ox.Flipbook.js
Normal file
101
source/Ox.UI/js/Video/Ox.Flipbook.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
// fixme: rename!
|
||||
|
||||
Ox.Flipbook = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
frame = $('<img>').css({
|
||||
'position': 'absolute',
|
||||
'width': '100%',
|
||||
'height': 'auto'
|
||||
})
|
||||
.hide(),
|
||||
icon = $('<img>').css({
|
||||
'position': 'absolute',
|
||||
'width': '100%',
|
||||
'height': 'auto'
|
||||
}),
|
||||
frames = {},
|
||||
timestamp = $('<div>').css({
|
||||
'position': 'absolute',
|
||||
'text-align': 'center',
|
||||
'width': '100%'
|
||||
})
|
||||
.hide(),
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
frames: {},
|
||||
duration: 0,
|
||||
icon: ''
|
||||
})
|
||||
.options(options || {})
|
||||
.append(icon)
|
||||
.append(frame)
|
||||
.append(timestamp)
|
||||
.mouseover(function() {
|
||||
frame.show();
|
||||
timestamp.show();
|
||||
icon.hide();
|
||||
})
|
||||
.mousemove(function(event) {
|
||||
var position = getPosition(event),
|
||||
image = getFrame(position),
|
||||
frameHeight = image?image.height:that.height();
|
||||
frame.attr('src', image.src);
|
||||
timestamp.html(Ox.formatDuration(position, 'short'));
|
||||
|
||||
var height = (that.height() - frameHeight)/2;
|
||||
frame.css({'top': height + 'px'});
|
||||
timestamp.css({'top': (frameHeight + height) + 'px'});
|
||||
})
|
||||
.mouseout(function() {
|
||||
frame.hide();
|
||||
timestamp.hide();
|
||||
icon.show();
|
||||
})
|
||||
.mousedown(function(event) {
|
||||
that.triggerEvent('click', {
|
||||
'position': getPosition(event)
|
||||
});
|
||||
});
|
||||
|
||||
function getPosition(event) {
|
||||
var position = Math.floor(event.clientX - that.offset().left);
|
||||
position = (position / that.width()) * self.options.duration;
|
||||
return position;
|
||||
}
|
||||
|
||||
function getFrame(position) {
|
||||
var frame;
|
||||
Ox.forEach(frames, function(img, i) {
|
||||
if (!frame || i <= position) {
|
||||
frame = img;
|
||||
}
|
||||
});
|
||||
return frame;
|
||||
}
|
||||
|
||||
function cacheFrames() {
|
||||
Ox.forEach(self.options.frames, function(src, i) {
|
||||
frames[i] = new Image();
|
||||
frames[i].onload = function() {
|
||||
frameHeight = frames[i].height / frames[i].width * that.width();
|
||||
};
|
||||
frames[i].src = src;
|
||||
});
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'frames') {
|
||||
cacheFrames();
|
||||
} else if (key == 'icon') {
|
||||
icon.attr('src', value);
|
||||
}
|
||||
};
|
||||
|
||||
if(options.icon) {
|
||||
icon.attr('src', options.icon);
|
||||
}
|
||||
cacheFrames();
|
||||
return that;
|
||||
};
|
||||
213
source/Ox.UI/js/Video/Ox.LargeTimeline.js
Normal file
213
source/Ox.UI/js/Video/Ox.LargeTimeline.js
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.LargeTimeline = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
cuts: [],
|
||||
duration: 0,
|
||||
find: '',
|
||||
matches: [],
|
||||
points: [0, 0],
|
||||
position: 0,
|
||||
style: 'default',
|
||||
subtitles: [],
|
||||
videoId: '',
|
||||
width: 0
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxTimelineLarge')
|
||||
.mouseleave(mouseleave)
|
||||
.mousemove(mousemove)
|
||||
.bindEvent({
|
||||
anyclick: click,
|
||||
dragstart: dragstart,
|
||||
drag: drag
|
||||
});
|
||||
|
||||
$.extend(self, {
|
||||
$cuts: [],
|
||||
$markerPoint: [],
|
||||
$subtitles: [],
|
||||
$tiles: {},
|
||||
$tooltip: new Ox.Tooltip({
|
||||
animate: false
|
||||
}),
|
||||
center: parseInt(self.options.width / 2),
|
||||
element: that.$element[0],
|
||||
fps: 25,
|
||||
height: 64,
|
||||
tileWidth: 1500
|
||||
});
|
||||
self.tiles = self.options.duration * self.fps / self.tileWidth;
|
||||
|
||||
self.$timeline = $('<div>')
|
||||
.css({
|
||||
left: self.center + 'px'
|
||||
})
|
||||
.appendTo(that.$element)
|
||||
|
||||
self.options.subtitles.forEach(function(v, i) {
|
||||
self.$subtitles[i] = $('<div>')
|
||||
.addClass('OxSubtitle' + (self.options.matches.indexOf(i) > -1 ? ' OxHighlight' : ''))
|
||||
.css({
|
||||
left: (v['in'] * self.fps) + 'px',
|
||||
width: (((v['out'] - v['in']) * self.fps) - 2) + 'px'
|
||||
})
|
||||
.html(Ox.highlight(v.value, self.options.find))
|
||||
.appendTo(self.$timeline)
|
||||
});
|
||||
|
||||
self.options.cuts.forEach(function(v, i) {
|
||||
self.$cuts[i] = $('<img>')
|
||||
.addClass('OxCut')
|
||||
.attr({
|
||||
src: Ox.PATH + 'png/Ox.UI/videoMarkerCut.png'
|
||||
})
|
||||
.css({
|
||||
left: (v * self.fps) + 'px'
|
||||
})
|
||||
.appendTo(self.$timeline)
|
||||
});
|
||||
|
||||
self.$markerPosition = $('<img>')
|
||||
.addClass('OxMarkerPosition')
|
||||
.attr({
|
||||
src: Ox.PATH + 'png/Ox.UI/videoMarkerPlay.png'
|
||||
})
|
||||
.appendTo(that.$element);
|
||||
setMarker();
|
||||
|
||||
['In', 'Out'].forEach(function(v, i) {
|
||||
self.$markerPoint[i] = $('<img>')
|
||||
.addClass('OxMarkerPoint' + v)
|
||||
.attr({
|
||||
src: Ox.PATH + 'png/Ox.UI/videoMarker' + v + '.png'
|
||||
})
|
||||
.appendTo(self.$timeline);
|
||||
setMarkerPoint(i);
|
||||
});
|
||||
|
||||
setWidth();
|
||||
setPosition();
|
||||
|
||||
function click(event, e) {
|
||||
self.options.position = Ox.limit(
|
||||
getPosition(e), 0, self.options.duration
|
||||
);
|
||||
setPosition();
|
||||
triggerChangeEvent();
|
||||
}
|
||||
|
||||
function dragstart(event, e) {
|
||||
self.drag = {x: e.clientX};
|
||||
}
|
||||
|
||||
function drag(event, e) {
|
||||
self.options.position = Ox.limit(
|
||||
self.options.position + (self.drag.x - e.clientX) / self.fps,
|
||||
0, self.options.duration
|
||||
);
|
||||
self.drag.x = e.clientX;
|
||||
setPosition();
|
||||
triggerChangeEvent();
|
||||
}
|
||||
|
||||
function getPosition(e) {
|
||||
return self.options.position + (e.clientX - that.offset().left - self.center - 1) / self.fps
|
||||
}
|
||||
|
||||
function mouseleave(e) {
|
||||
self.clientX = 0;
|
||||
self.clientY = 0;
|
||||
self.$tooltip.hide();
|
||||
}
|
||||
|
||||
function mousemove(e) {
|
||||
self.clientX = e.clientX;
|
||||
self.clientY = e.clientY;
|
||||
updateTooltip();
|
||||
}
|
||||
|
||||
function setMarkerPoint(i) {
|
||||
self.$markerPoint[i].css({
|
||||
left: (self.options.points[i] * self.fps) + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
function setMarker() {
|
||||
self.$markerPosition.css({
|
||||
left: (self.center - 4) + 'px',
|
||||
});
|
||||
}
|
||||
|
||||
function setPosition() {
|
||||
self.tile = parseInt(self.options.position * self.fps / self.tileWidth);
|
||||
self.$timeline.css({
|
||||
marginLeft: (-self.options.position * self.fps) + 'px'
|
||||
});
|
||||
Ox.range(
|
||||
Math.max(self.tile - 1, 0), Math.min(self.tile + 2, self.tiles)
|
||||
).forEach(function(v) {
|
||||
if (!self.$tiles[v]) {
|
||||
self.$tiles[v] = $('<img>')
|
||||
.attr({
|
||||
src: '/' + self.options.videoId + '/timelines/' + (
|
||||
self.options.style == 'default' ? 'timeline' : self.options.style
|
||||
) + '.64.' + v + '.png'
|
||||
})
|
||||
.css({
|
||||
left: (v * self.tileWidth) + 'px'
|
||||
})
|
||||
.appendTo(self.$timeline);
|
||||
}
|
||||
});
|
||||
if (self.clientX && self.clientY) {
|
||||
updateTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
function setWidth() {
|
||||
self.center = parseInt(self.options.width / 2);
|
||||
that.css({
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
self.$timeline.css({
|
||||
left: self.center + 'px'
|
||||
});
|
||||
setMarker();
|
||||
}
|
||||
|
||||
function triggerChangeEvent() {
|
||||
that.triggerEvent('change', {
|
||||
position: self.options.position
|
||||
});
|
||||
}
|
||||
|
||||
function updateTooltip() {
|
||||
var position = getPosition(self);
|
||||
if (position >= 0 && position <= self.options.duration) {
|
||||
self.$tooltip
|
||||
.options({
|
||||
title: Ox.formatDuration(position, 3)
|
||||
})
|
||||
.show(self.clientX, self.clientY);
|
||||
} else {
|
||||
self.$tooltip.hide();
|
||||
}
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'points') {
|
||||
setMarkerPoint(0);
|
||||
setMarkerPoint(1);
|
||||
} else if (key == 'position') {
|
||||
setPosition();
|
||||
} else if (key == 'width') {
|
||||
setWidth();
|
||||
}
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
192
source/Ox.UI/js/Video/Ox.SmallTimeline.js
Normal file
192
source/Ox.UI/js/Video/Ox.SmallTimeline.js
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.SmallTimeline = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
duration: 0,
|
||||
find: '',
|
||||
matches: [],
|
||||
points: [0, 0],
|
||||
position: 0,
|
||||
subtitles: [],
|
||||
videoId: '',
|
||||
width: 0
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxTimelineSmall')
|
||||
.mousedown(mousedown)
|
||||
.mouseleave(mouseleave)
|
||||
.mousemove(mousemove)
|
||||
.bindEvent({
|
||||
drag: function(event, e) {
|
||||
mousedown(e);
|
||||
}
|
||||
});
|
||||
|
||||
$.extend(self, {
|
||||
$images: [],
|
||||
$markerPoint: [],
|
||||
$subtitles: [],
|
||||
$tooltip: new Ox.Tooltip({
|
||||
animate: false
|
||||
}).css({
|
||||
textAlign: 'center'
|
||||
}),
|
||||
hasSubtitles: self.options.subtitles.length,
|
||||
height: 16,
|
||||
margin: 8
|
||||
});
|
||||
|
||||
that.css({
|
||||
width: (self.options.width + self.margin) + 'px',
|
||||
height: (self.height + self.margin) + 'px'
|
||||
});
|
||||
|
||||
self.$line = $('<img>')
|
||||
.addClass('OxTimelineSmallImage')
|
||||
.attr({
|
||||
src: '/' + self.options.videoId + '/timelines/timeline.16.0.png'
|
||||
})
|
||||
.css({
|
||||
position: 'absolute',
|
||||
left: '4px',
|
||||
top: '4px',
|
||||
width: self.options.width,
|
||||
height: '16px'
|
||||
})
|
||||
.appendTo(that.$element);
|
||||
|
||||
self.$markerPosition = $('<img>')
|
||||
.addClass('OxMarkerPosition')
|
||||
.attr({
|
||||
src: Ox.PATH + 'png/Ox.UI/videoMarkerPlay.png'
|
||||
})
|
||||
.css({
|
||||
position: 'absolute',
|
||||
width: '9px',
|
||||
height: '5px',
|
||||
zIndex: 10
|
||||
})
|
||||
.appendTo(that.$element);
|
||||
|
||||
setPosition();
|
||||
|
||||
['in', 'out'].forEach(function(v, i) {
|
||||
var titleCase = Ox.toTitleCase(v);
|
||||
self.$markerPoint[i] = $('<img>')
|
||||
.addClass('OxMarkerPoint' + titleCase)
|
||||
.attr({
|
||||
src: Ox.PATH + 'png/Ox.UI/videoMarker' + titleCase + '.png'
|
||||
})
|
||||
.appendTo(that.$element);
|
||||
setMarkerPoint(i);
|
||||
});
|
||||
|
||||
function getPosition(e) {
|
||||
return e.offsetX / self.options.width * self.options.duration;
|
||||
}
|
||||
|
||||
function getSubtitle(position) {
|
||||
var subtitle = null;
|
||||
Ox.forEach(self.options.subtitles, function(v) {
|
||||
if (v['in'] <= position && v['out'] >= position) {
|
||||
subtitle = v;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
function mousedown(e) {
|
||||
var $target = $(e.target);
|
||||
if (
|
||||
$target.hasClass('OxTimelineSmallImage') ||
|
||||
$target.hasClass('OxTimelineSmallSubtitles')
|
||||
) {
|
||||
self.options.position = getPosition(e);
|
||||
setPosition();
|
||||
that.triggerEvent('change', {
|
||||
position: self.options.position
|
||||
});
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function mouseleave(e) {
|
||||
self.$tooltip.hide();
|
||||
}
|
||||
|
||||
function mousemove(e) {
|
||||
var $target = $(e.target),
|
||||
position,
|
||||
subtitle;
|
||||
if (
|
||||
$target.hasClass('OxTimelineSmallImage') ||
|
||||
$target.hasClass('OxTimelineSmallSubtitles')
|
||||
) {
|
||||
position = getPosition(e),
|
||||
subtitle = getSubtitle(position);
|
||||
self.$tooltip.options({
|
||||
title: subtitle ?
|
||||
'<span class=\'OxBright\'>' +
|
||||
Ox.highlight(subtitle.value, self.options.find).replace(/\n/g, '<br/>') + '</span><br/>' +
|
||||
Ox.formatDuration(subtitle['in'], 3) + ' - ' + Ox.formatDuration(subtitle['out'], 3) :
|
||||
Ox.formatDuration(position, 3)
|
||||
})
|
||||
.show(e.clientX, e.clientY);
|
||||
} else {
|
||||
self.$tooltip.hide();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setMarker() {
|
||||
self.$markerPosition
|
||||
.css({
|
||||
left: parseInt(
|
||||
self.options.position / self.options.duration * self.options.width
|
||||
) + 'px',
|
||||
top: '2px',
|
||||
});
|
||||
}
|
||||
|
||||
function setMarkerPoint(i) {
|
||||
var position = self.options.points[i];
|
||||
self.$markerPoint[i]
|
||||
.css({
|
||||
left: (position % self.options.width) + 'px',
|
||||
top: (parseInt(position / self.options.width) * (self.height + self.margin) + 16) + 'px',
|
||||
});
|
||||
}
|
||||
|
||||
function setPosition() {
|
||||
self.options.position = Ox.limit(self.options.position, 0, self.options.duration);
|
||||
setMarker();
|
||||
}
|
||||
|
||||
function setWidth() {
|
||||
self.$line.css({
|
||||
width: self.options.width + 'px',
|
||||
});
|
||||
setMarker();
|
||||
setMarkerPoint(0);
|
||||
setMarkerPoint(1);
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
//Ox.print('onChange:', key, value)
|
||||
if (key == 'points') {
|
||||
//Ox.print('key', key, 'value', value)
|
||||
setMarkerPoint(0);
|
||||
setMarkerPoint(1);
|
||||
} else if (key == 'position') {
|
||||
setPosition();
|
||||
} else if (key == 'width') {
|
||||
setWidth();
|
||||
}
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
414
source/Ox.UI/js/Video/Ox.VideoEditorPlayer.js
Normal file
414
source/Ox.UI/js/Video/Ox.VideoEditorPlayer.js
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.VideoEditorPlayer = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
duration: 0,
|
||||
find: '',
|
||||
height: 0,
|
||||
points: [0, 0],
|
||||
position: 0,
|
||||
posterFrame: 0,
|
||||
size: 'small',
|
||||
subtitles: [],
|
||||
type: 'play',
|
||||
url: '',
|
||||
width: 0
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxVideoPlayer')
|
||||
.css({
|
||||
height: (self.options.height + 16) + 'px',
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
|
||||
self.controlsHeight = 16;
|
||||
|
||||
if (self.options.type == 'play') {
|
||||
self.$video = new Ox.VideoElement({
|
||||
height: self.options.height,
|
||||
paused: true,
|
||||
points: self.options.points,
|
||||
position: self.options.position,
|
||||
url: self.options.url,
|
||||
width: self.options.width
|
||||
})
|
||||
.bindEvent({
|
||||
paused: paused,
|
||||
playing: playing
|
||||
})
|
||||
.appendTo(that);
|
||||
self.video = self.$video.$element[0];
|
||||
} else {
|
||||
self.$video = $('<img>')
|
||||
.css({
|
||||
height: self.options.height + 'px',
|
||||
width: self.options.width + 'px'
|
||||
})
|
||||
.appendTo(that.$element)
|
||||
}
|
||||
|
||||
self.$subtitle = $('<div>')
|
||||
.addClass('OxSubtitle')
|
||||
.appendTo(that.$element);
|
||||
|
||||
setSubtitleSize();
|
||||
|
||||
self.$markerFrame = $('<div>')
|
||||
.addClass('OxMarkerFrame')
|
||||
.append(
|
||||
$('<div>')
|
||||
.addClass('OxFrame')
|
||||
.css({
|
||||
width: Math.floor((self.options.width - self.options.height) / 2) + 'px',
|
||||
height: self.options.height + 'px'
|
||||
})
|
||||
)
|
||||
.append(
|
||||
$('<div>')
|
||||
.addClass('OxPoster')
|
||||
.css({
|
||||
width: (self.options.height - 2) + 'px',
|
||||
height: (self.options.height - 2) + 'px'
|
||||
})
|
||||
)
|
||||
.append(
|
||||
$('<div>')
|
||||
.addClass('OxFrame')
|
||||
.css({
|
||||
width: Math.ceil((self.options.width - self.options.height) / 2) + 'px',
|
||||
height: self.options.height + 'px'
|
||||
})
|
||||
)
|
||||
.hide()
|
||||
.appendTo(that.$element);
|
||||
|
||||
self.$markerPoint = {};
|
||||
['in', 'out'].forEach(function(point, i) {
|
||||
self.$markerPoint[point] = {};
|
||||
['top', 'bottom'].forEach(function(edge) {
|
||||
var titleCase = Ox.toTitleCase(point) + Ox.toTitleCase(edge);
|
||||
self.$markerPoint[point][edge] = $('<img>')
|
||||
.addClass('OxMarkerPoint OxMarker' + titleCase)
|
||||
.attr({
|
||||
src: Ox.PATH + 'png/Ox.UI/videoMarker' + titleCase + '.png'
|
||||
})
|
||||
.hide()
|
||||
.appendTo(that.$element);
|
||||
if (self.options.points[i] == self.options.position) {
|
||||
self.$markerPoint[point][edge].show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
self.$controls = new Ox.Bar({
|
||||
size: self.controlsHeight
|
||||
})
|
||||
.css({
|
||||
marginTop: '-2px'
|
||||
})
|
||||
.appendTo(that);
|
||||
|
||||
if (self.options.type == 'play') {
|
||||
// fixme: $buttonPlay etc. ?
|
||||
self.$playButton = new Ox.Button({
|
||||
id: self.options.id + 'Play',
|
||||
title: [
|
||||
{id: 'play', title: 'play'},
|
||||
{id: 'pause', title: 'pause'}
|
||||
],
|
||||
tooltip: ['Play', 'Pause'],
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent('click', togglePlay)
|
||||
.appendTo(self.$controls);
|
||||
self.$playInToOutButton = new Ox.Button({
|
||||
id: self.options.id + 'PlayInToOut',
|
||||
title: 'PlayInToOut',
|
||||
tooltip: 'Play In to Out',
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent('click', function() {
|
||||
that.playInToOut();
|
||||
})
|
||||
.appendTo(self.$controls);
|
||||
self.$muteButton = new Ox.Button({
|
||||
id: self.options.id + 'Mute',
|
||||
title: [
|
||||
{id: 'mute', title: 'mute'},
|
||||
{id: 'unmute', title: 'unmute'}
|
||||
],
|
||||
tooltip: ['Mute', 'Unmute'],
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent('click', toggleMute)
|
||||
.appendTo(self.$controls);
|
||||
self.$sizeButton = new Ox.Button({
|
||||
id: self.options.id + 'Size',
|
||||
title: self.options.size == 'small' ? [
|
||||
{id: 'large', title: 'grow'},
|
||||
{id: 'small', title: 'shrink'}
|
||||
] : [
|
||||
{id: 'small', title: 'shrink'},
|
||||
{id: 'large', title: 'grow'}
|
||||
],
|
||||
tooltip: ['Larger', 'Smaller'],
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent('click', toggleSize)
|
||||
.appendTo(self.$controls);
|
||||
} else {
|
||||
self.$goToPointButton = new Ox.Button({
|
||||
id: self.options.id + 'GoTo' + Ox.toTitleCase(self.options.type),
|
||||
title: 'GoTo' + Ox.toTitleCase(self.options.type),
|
||||
tooltip: 'Go to ' + Ox.toTitleCase(self.options.type) + ' Point',
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent('click', goToPoint)
|
||||
.appendTo(self.$controls);
|
||||
self.$setPointButton = new Ox.Button({
|
||||
id: self.options.id + 'Set' + Ox.toTitleCase(self.options.type),
|
||||
title: 'Set' + Ox.toTitleCase(self.options.type),
|
||||
tooltip: 'Set ' + Ox.toTitleCase(self.options.type) + ' Point',
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent('click', setPoint)
|
||||
.appendTo(self.$controls);
|
||||
}
|
||||
|
||||
self.$positionInput = new Ox.TimeInput({
|
||||
milliseconds: true,
|
||||
seconds: true,
|
||||
value: Ox.formatDuration(self.options.position, 3)
|
||||
})
|
||||
.css({
|
||||
float: 'right',
|
||||
})
|
||||
.appendTo(self.$controls)
|
||||
|
||||
// fixme: strange positioning hack
|
||||
self.$positionInput.css({
|
||||
width: '98px'
|
||||
});
|
||||
$.browser.mozilla && self.$positionInput.css({
|
||||
marginTop: '-19px'
|
||||
});
|
||||
// fixme: children doesnt work w/o $element
|
||||
self.$positionInput.$element.children('.OxLabel').each(function(i, element) {
|
||||
$(this).css({
|
||||
width: '22px',
|
||||
marginLeft: (i == 0 ? 8 : 0) + 'px',
|
||||
background: 'rgb(32, 32, 32)'
|
||||
});
|
||||
});
|
||||
self.$positionInput.$element.children('div.OxInput').each(function(i) {
|
||||
var marginLeft = [-82, -58, -34, -10];
|
||||
$(this).css({
|
||||
marginLeft: marginLeft[i] + 'px'
|
||||
});
|
||||
});
|
||||
|
||||
if (self.options.type == 'play') {
|
||||
self.$loadingIcon = new Ox.LoadingIcon()
|
||||
.appendTo(that)
|
||||
.start();
|
||||
self.loadingInterval = setInterval(function() {
|
||||
if (self.video.readyState) {
|
||||
clearInterval(self.loadingInterval);
|
||||
self.$loadingIcon.stop();
|
||||
setPosition();
|
||||
}
|
||||
}, 50);
|
||||
} else {
|
||||
setPosition();
|
||||
}
|
||||
|
||||
function getSubtitle() {
|
||||
var subtitle = '';
|
||||
Ox.forEach(self.options.subtitles, function(v) {
|
||||
if (v['in'] <= self.options.position && v['out'] > self.options.position) {
|
||||
subtitle = v.value;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
function goToPoint() {
|
||||
that.triggerEvent('change', {
|
||||
position: self.options.points[self.options.type == 'in' ? 0 : 1]
|
||||
});
|
||||
}
|
||||
|
||||
function paused(event, data) {
|
||||
self.$playButton.toggleTitle();
|
||||
}
|
||||
|
||||
function playing(event, data) {
|
||||
self.options.position = data.position;
|
||||
setMarkers();
|
||||
setSubtitle();
|
||||
self.$positionInput.options({
|
||||
value: Ox.formatDuration(self.options.position, 3)
|
||||
});
|
||||
that.triggerEvent('playing', {
|
||||
position: self.options.position
|
||||
});
|
||||
}
|
||||
|
||||
function setHeight() {
|
||||
that.css({
|
||||
height: (self.options.height + 16) + 'px'
|
||||
});
|
||||
self.options.type == 'play' ? self.$video.options({
|
||||
height: self.options.height
|
||||
}) : self.$video.css({
|
||||
height: self.options.height + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
function setMarkers() {
|
||||
self.options.position == self.options.posterFrame ?
|
||||
self.$markerFrame.show() : self.$markerFrame.hide();
|
||||
Ox.forEach(self.$markerPoint, function(markers, point) {
|
||||
Ox.forEach(markers, function(marker) {
|
||||
self.options.position == self.options.points[point == 'in' ? 0 : 1] ?
|
||||
marker.show() : marker.hide();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function setPoint() {
|
||||
var data = {};
|
||||
self.options.points[self.options.type == 'in' ? 0 : 1] = self.options.position;
|
||||
setMarkers();
|
||||
data[self.options.type] = self.options.position;
|
||||
that.triggerEvent('set', data);
|
||||
}
|
||||
|
||||
function setPosition() {
|
||||
var position = Ox.limit(
|
||||
self.options.position - (self.options.type == 'out' ? 0.01 : 0),
|
||||
0, self.options.duration - 0.01
|
||||
),
|
||||
url;
|
||||
if (self.options.type == 'play') {
|
||||
self.$video.position(self.options.position);
|
||||
} else {
|
||||
self.$loadingIcon && self.$loadingIcon.stop();
|
||||
url = self.options.url(position);
|
||||
if (self.$video.attr('src') != url) {
|
||||
self.$loadingIcon = new Ox.LoadingIcon()
|
||||
.appendTo(that)
|
||||
.start();
|
||||
self.$video.attr({
|
||||
src: url
|
||||
})
|
||||
.load(self.$loadingIcon.stop);
|
||||
}
|
||||
}
|
||||
setMarkers();
|
||||
setSubtitle();
|
||||
self.$positionInput.options({
|
||||
value: Ox.formatDuration(self.options.position, 3)
|
||||
});
|
||||
}
|
||||
|
||||
function setSubtitle() {
|
||||
var subtitle = getSubtitle();
|
||||
if (subtitle != self.subtitle) {
|
||||
self.subtitle = subtitle;
|
||||
self.$subtitle.html(Ox.highlight(self.subtitle, self.options.find).replace(/\n/g, '<br/>'));
|
||||
}
|
||||
}
|
||||
|
||||
function setSubtitleSize() {
|
||||
self.$subtitle.css({
|
||||
bottom: parseInt(self.controlsHeight + self.options.height / 16) + 'px',
|
||||
width: self.options.width + 'px',
|
||||
fontSize: parseInt(self.options.height / 20) + 'px',
|
||||
WebkitTextStroke: (self.options.height / 1000) + 'px rgb(0, 0, 0)'
|
||||
});
|
||||
}
|
||||
|
||||
function setWidth() {
|
||||
that.css({
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
self.options.type == 'play' ? self.$video.options({
|
||||
width: self.options.width
|
||||
}) : self.$video.css({
|
||||
width: self.options.width + 'px'
|
||||
});
|
||||
setSubtitleSize();
|
||||
}
|
||||
|
||||
function toggleMute() {
|
||||
self.$video.toggleMute();
|
||||
}
|
||||
|
||||
function togglePlay() {
|
||||
self.video.paused ? that.play() : that.pause();
|
||||
}
|
||||
|
||||
function toggleSize(event, data) {
|
||||
self.options.size = data.id
|
||||
that.triggerEvent('togglesize', {
|
||||
size: self.options.size
|
||||
});
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'height') {
|
||||
setHeight();
|
||||
} else if (key == 'points') {
|
||||
setMarkers();
|
||||
} else if (key == 'position') {
|
||||
setPosition();
|
||||
} else if (key == 'posterFrame') {
|
||||
setMarkers();
|
||||
} else if (key == 'width') {
|
||||
setWidth();
|
||||
}
|
||||
}
|
||||
|
||||
that.mute = function() {
|
||||
self.$video.mute();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.pause = function() {
|
||||
self.$video.pause();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.play = function() {
|
||||
self.$video.play();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.playInToOut = function() {
|
||||
self.$video.paused() && self.$playButton.toggleTitle();
|
||||
self.$video.playInToOut();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.toggleMute = function() {
|
||||
self.$muteButton.trigger('click');
|
||||
return that;
|
||||
}
|
||||
|
||||
that.togglePlay = function() {
|
||||
self.$playButton.trigger('click');
|
||||
return that;
|
||||
}
|
||||
|
||||
that.unmute = function() {
|
||||
self.$video.unmute();
|
||||
return that;
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
159
source/Ox.UI/js/Video/Ox.VideoElement.js
Normal file
159
source/Ox.UI/js/Video/Ox.VideoElement.js
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.VideoElement = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('video', self)
|
||||
.defaults({
|
||||
fps: 25,
|
||||
height: 0,
|
||||
loop: false,
|
||||
muted: false,
|
||||
paused: false,
|
||||
playInToOut: false,
|
||||
points: [0, 0],
|
||||
position: 0,
|
||||
poster: '',
|
||||
url: '',
|
||||
width: 0
|
||||
})
|
||||
.options(options || {})
|
||||
.attr({
|
||||
poster: self.options.poster,
|
||||
preload: 'auto',
|
||||
src: self.options.url
|
||||
})
|
||||
.css({
|
||||
height: self.options.height + 'px',
|
||||
width: self.options.width + 'px'
|
||||
})
|
||||
.bind({
|
||||
ended: ended,
|
||||
loadedmetadata: function() {
|
||||
self.video.currentTime = self.options.position;
|
||||
}
|
||||
});
|
||||
|
||||
$.extend(self, {
|
||||
millisecondsPerFrame: 1000 / self.options.fps,
|
||||
video: that.$element[0]
|
||||
});
|
||||
|
||||
function ended() {
|
||||
that.pause()
|
||||
.triggerEvent('paused', {
|
||||
position: self.options.position
|
||||
});
|
||||
}
|
||||
|
||||
function playing() {
|
||||
var event = 'playing';
|
||||
self.options.position = Math.round(self.video.currentTime * self.options.fps) / self.options.fps;
|
||||
if (self.options.playInToOut && self.options.position >= self.options.points[1]) {
|
||||
event = 'paused';
|
||||
that.position(self.options.points[1]).pause();
|
||||
}
|
||||
that.triggerEvent(event, {
|
||||
position: self.options.position
|
||||
});
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'height') {
|
||||
that.size(self.options.width, value);
|
||||
} else if (key == 'muted') {
|
||||
that[value ? 'mute' : 'unmute']();
|
||||
} else if (key == 'paused') {
|
||||
that[value ? 'pause' : 'play']();
|
||||
} else if (key == 'points') {
|
||||
that.points(value);
|
||||
} else if (key == 'width') {
|
||||
that.size(value, self.options.height);
|
||||
}
|
||||
};
|
||||
|
||||
that.mute = function() {
|
||||
self.options.muted = true;
|
||||
self.video.muted = true;
|
||||
return that;
|
||||
};
|
||||
|
||||
that.muted = function() {
|
||||
return self.options.muted;
|
||||
}
|
||||
|
||||
that.pause = function() {
|
||||
self.options.paused = true;
|
||||
self.options.playInToOut = false;
|
||||
self.video.pause();
|
||||
clearInterval(self.playInterval);
|
||||
return that;
|
||||
};
|
||||
|
||||
that.paused = function() {
|
||||
return self.options.paused;
|
||||
}
|
||||
|
||||
that.play = function() {
|
||||
self.options.paused = false;
|
||||
self.video.play();
|
||||
self.playInterval = setInterval(playing, self.millisecondsPerFrame);
|
||||
return that;
|
||||
};
|
||||
|
||||
that.playInToOut = function() {
|
||||
self.options.playInToOut = true;
|
||||
that.position(self.options.points[0]);
|
||||
self.options.paused && that.play();
|
||||
return that;
|
||||
};
|
||||
|
||||
that.points = function(points) {
|
||||
self.options.points = points;
|
||||
}
|
||||
|
||||
that.position = function(pos) {
|
||||
if (arguments.length == 0) {
|
||||
return self.video.currentTime;
|
||||
} else {
|
||||
self.options.position = pos;
|
||||
self.video.currentTime = self.options.position;
|
||||
return that;
|
||||
}
|
||||
};
|
||||
|
||||
that.size = function(width, height) {
|
||||
// fixme: why options? use css!
|
||||
if (arguments.length == 0) {
|
||||
return {
|
||||
width: self.options.width,
|
||||
height: self.options.height
|
||||
};
|
||||
} else {
|
||||
self.options.width = width;
|
||||
self.options.height = height;
|
||||
that.css({
|
||||
width: width + 'px',
|
||||
height: height + 'px'
|
||||
});
|
||||
return that;
|
||||
}
|
||||
};
|
||||
|
||||
that.toggleMute = function() {
|
||||
self.video.muted = !self.video.muted;
|
||||
return that;
|
||||
}
|
||||
|
||||
that.togglePlay = function() {
|
||||
self.options.paused = !self.options.paused;
|
||||
that[self.options.paused ? 'pause' : 'play']();
|
||||
}
|
||||
|
||||
that.unmute = function() {
|
||||
self.video.muted = false;
|
||||
return that;
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
422
source/Ox.UI/js/Video/Ox.VideoPanelPlayer.js
Normal file
422
source/Ox.UI/js/Video/Ox.VideoPanelPlayer.js
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.VideoPanelPlayer = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
annotationsSize: 256,
|
||||
duration: 0,
|
||||
height: 0,
|
||||
loop: false,
|
||||
muted: false,
|
||||
paused: false,
|
||||
playInToOut: false,
|
||||
points: [0, 0],
|
||||
position: 0,
|
||||
poster: '',
|
||||
showAnnotations: true,
|
||||
showControls: true,
|
||||
subtitles: [],
|
||||
videoHeight: 0,
|
||||
videoSize: 'fit',
|
||||
videoWidth: 0,
|
||||
videoURL: '',
|
||||
width: 0
|
||||
})
|
||||
.options(options || {})
|
||||
.css({
|
||||
height: self.options.height + 'px',
|
||||
width: self.options.width + 'px'
|
||||
})
|
||||
.bindEvent({
|
||||
resize: resizeElement,
|
||||
key_shift_a: function() {
|
||||
that.toggleAnnotations();
|
||||
},
|
||||
key_shift_c: function() {
|
||||
that.toggleControls();
|
||||
},
|
||||
key_shift_s: function() {
|
||||
that.toggleSize();
|
||||
},
|
||||
key_space: function() {
|
||||
that.togglePlay();
|
||||
}
|
||||
});
|
||||
|
||||
$.extend(self, {
|
||||
fullscreen: false,
|
||||
videoCSS: getVideoCSS()
|
||||
});
|
||||
//alert(JSON.stringify([self.playerHeight, self.playerWidth, self.videoCSS]))
|
||||
|
||||
self.$player = new Ox.Element()
|
||||
.css({
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'hidden'
|
||||
})
|
||||
.bind({
|
||||
mousedown: that.gainFocus
|
||||
})
|
||||
.bindEvent({
|
||||
resize: resizeVideo
|
||||
});
|
||||
|
||||
self.$video = new Ox.VideoElement({
|
||||
height: self.videoCSS.height,
|
||||
paused: true,
|
||||
points: self.options.points,
|
||||
position: self.options.position,
|
||||
url: self.options.videoURL,
|
||||
width: self.videoCSS.width
|
||||
})
|
||||
.css(self.videoCSS)
|
||||
.bindEvent({
|
||||
paused: paused,
|
||||
playing: playing
|
||||
})
|
||||
.appendTo(self.$player);
|
||||
|
||||
self.$controls = new Ox.Element()
|
||||
.bindEvent({
|
||||
toggle: toggleControls
|
||||
});
|
||||
|
||||
self.$buttons = new Ox.Element()
|
||||
.css({
|
||||
float: 'left',
|
||||
width: '16px',
|
||||
margin: '4px'
|
||||
})
|
||||
.appendTo(self.$controls);
|
||||
|
||||
self.$button = {
|
||||
play: new Ox.Button({
|
||||
id: 'play',
|
||||
title: [
|
||||
{id: 'play', title: 'play'},
|
||||
{id: 'pause', title: 'pause'}
|
||||
],
|
||||
tooltip: ['Play', 'Pause'],
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent({
|
||||
click: self.$video.togglePlay
|
||||
}),
|
||||
mute: new Ox.Button({
|
||||
id: 'mute',
|
||||
title: [
|
||||
{id: 'mute', title: 'mute'},
|
||||
{id: 'unmute', title: 'unmute'}
|
||||
],
|
||||
tooltip: ['Mute', 'Unmute'],
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent({
|
||||
click: self.$video.toggleMute
|
||||
}),
|
||||
size: new Ox.Button({
|
||||
id: 'size',
|
||||
title: self.options.videoSize == 'fit' ? [
|
||||
{id: 'fill', title: 'fill'},
|
||||
{id: 'fit', title: 'fit'}
|
||||
] : [
|
||||
{id: 'fit', title: 'fit'},
|
||||
{id: 'fill', title: 'fill'}
|
||||
],
|
||||
tooltip: self.options.videoSize == 'fit' ? [
|
||||
'Fill Screen', 'Fit to Screen'
|
||||
] : [
|
||||
'Fit to Screen', 'Fill Screen'
|
||||
],
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent({
|
||||
click: toggleSize
|
||||
}),
|
||||
fullscreen: new Ox.Button({
|
||||
id: 'size',
|
||||
title: [
|
||||
{id: 'grow', title: 'grow'},
|
||||
{id: 'shrink', title: 'shrink'}
|
||||
],
|
||||
tooltip: [
|
||||
'Enter Fullscreen', 'Exit Fullscreen'
|
||||
],
|
||||
type: 'image'
|
||||
})
|
||||
.bindEvent({
|
||||
click: toggleFullscreen
|
||||
})
|
||||
}
|
||||
var i = 0;
|
||||
Ox.forEach(self.$button, function($button) {
|
||||
$button.css({
|
||||
position: 'absolute',
|
||||
left: '8px',
|
||||
top: (8 + i++ * 24) + 'px'
|
||||
})
|
||||
.appendTo(self.$buttons);
|
||||
});
|
||||
|
||||
self.$timelines = new Ox.Element()
|
||||
.css({
|
||||
float: 'left',
|
||||
margin: '4px'
|
||||
})
|
||||
.appendTo(self.$controls);
|
||||
|
||||
self.$timeline = {
|
||||
large: new Ox.LargeTimeline({
|
||||
duration: self.options.duration,
|
||||
position: self.options.position,
|
||||
subtitles: self.options.subtitles,
|
||||
videoId: self.options.videoId,
|
||||
width: getTimelineWidth()
|
||||
})
|
||||
.css({
|
||||
top: '4px'
|
||||
})
|
||||
.bindEvent({
|
||||
change: changeLargeTimeline
|
||||
}),
|
||||
small: new Ox.SmallTimeline({
|
||||
duration: self.options.duration,
|
||||
position: self.options.position,
|
||||
subtitles: self.options.subtitles,
|
||||
videoId: self.options.videoId,
|
||||
width: getTimelineWidth()
|
||||
})
|
||||
.css({
|
||||
top: '76px'
|
||||
})
|
||||
.bindEvent({
|
||||
change: changeSmallTimeline
|
||||
})
|
||||
};
|
||||
Ox.forEach(self.$timeline, function($timeline) {
|
||||
$timeline.appendTo(self.$timelines);
|
||||
});
|
||||
|
||||
self.$panel = new Ox.SplitPanel({
|
||||
elements: [
|
||||
{
|
||||
element: self.$player
|
||||
},
|
||||
{
|
||||
collapsed: !self.options.showControls,
|
||||
collapsible: true,
|
||||
element: self.$controls,
|
||||
size: 104
|
||||
}
|
||||
],
|
||||
orientation: 'vertical'
|
||||
})
|
||||
.bindEvent({
|
||||
resize: resizePanel
|
||||
});
|
||||
|
||||
self.$annotations = new Ox.Element()
|
||||
.bindEvent({
|
||||
resize: resizeAnnotations,
|
||||
resizeend: resizeendAnnotations,
|
||||
toggle: toggleAnnotations
|
||||
});
|
||||
|
||||
that.$element = new Ox.SplitPanel({
|
||||
elements: [
|
||||
{
|
||||
element: self.$panel
|
||||
},
|
||||
{
|
||||
collapsed: !self.options.showAnnotations,
|
||||
collapsible: true,
|
||||
element: self.$annotations,
|
||||
resizable: true,
|
||||
resize: [192, 256, 320, 384],
|
||||
size: self.options.annotationsSize
|
||||
}
|
||||
],
|
||||
orientation: 'horizontal'
|
||||
});
|
||||
|
||||
function changeLargeTimeline(event, data) {
|
||||
self.options.position = data.position;
|
||||
self.$video.position(self.options.position);
|
||||
self.$timeline.small.options({
|
||||
position: self.options.position
|
||||
});
|
||||
}
|
||||
|
||||
function changeSmallTimeline(event, data) {
|
||||
self.options.position = data.position;
|
||||
self.$video.position(self.options.position);
|
||||
self.$timeline.large.options({
|
||||
position: self.options.position
|
||||
});
|
||||
}
|
||||
|
||||
function getPlayerHeight() {
|
||||
return self.options.height -
|
||||
self.options.showControls * 104 - 1;
|
||||
}
|
||||
|
||||
function getPlayerWidth() {
|
||||
return self.options.width -
|
||||
(self.options.showAnnotations && !self.fullscreen) *
|
||||
self.options.annotationsSize - 1;
|
||||
}
|
||||
|
||||
function getTimelineWidth() {
|
||||
return self.options.width -
|
||||
(self.options.showAnnotations && !self.fullscreen) *
|
||||
self.options.annotationsSize - 40
|
||||
}
|
||||
|
||||
function getVideoCSS() {
|
||||
var width = getPlayerWidth(),
|
||||
height = getPlayerHeight(),
|
||||
ratio = width / height,
|
||||
videoRatio = self.options.videoWidth / self.options.videoHeight,
|
||||
isWide = ratio < videoRatio;
|
||||
return self.options.videoSize == 'fit' ? {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: (isWide ? width : Math.round(height * videoRatio)) + 'px',
|
||||
height: (isWide ? Math.round(width / videoRatio) : height) + 'px',
|
||||
margin: 'auto'
|
||||
} : {
|
||||
width: (isWide ? Math.round(height * videoRatio) : width) + 'px',
|
||||
height: (isWide ? height : Math.round(width / videoRatio)) + 'px',
|
||||
margin: [
|
||||
isWide ? '0' : Math.floor((height - width / videoRatio) / 2) + 'px',
|
||||
isWide ? Math.ceil((width - height * videoRatio) / 2) + 'px' : '0',
|
||||
isWide ? '0' : Math.ceil((height - width / videoRatio) / 2) + 'px',
|
||||
isWide ? Math.floor((width - height * videoRatio) / 2) + 'px' : '0'
|
||||
].join(' ')
|
||||
};
|
||||
}
|
||||
|
||||
function paused() {
|
||||
|
||||
}
|
||||
|
||||
function playing(event, data) {
|
||||
self.options.position = data.position;
|
||||
self.$timeline.large.options({
|
||||
position: self.options.position
|
||||
});
|
||||
self.$timeline.small.options({
|
||||
position: self.options.position
|
||||
});
|
||||
}
|
||||
|
||||
function resizeAnnotations(event, data) {
|
||||
self.options.annotationsSize = data;
|
||||
resizeVideoAndControls();
|
||||
}
|
||||
|
||||
function resizeendAnnotations(event, data) {
|
||||
self.options.annotationsSize = data;
|
||||
that.triggerEvent('change', {
|
||||
annotationsSize: self.options.annotationsSize
|
||||
});
|
||||
}
|
||||
|
||||
function resizeControls() {
|
||||
self.$timeline.large.options({
|
||||
width: getTimelineWidth()
|
||||
});
|
||||
self.$timeline.small.options({
|
||||
width: getTimelineWidth()
|
||||
});
|
||||
}
|
||||
|
||||
function resizeElement(event, data) {
|
||||
// called on browser toggle
|
||||
self.options.height = data;
|
||||
resizeVideo();
|
||||
}
|
||||
|
||||
function resizePanel(event, data) {
|
||||
// called on annotations toggle
|
||||
resizeVideoAndControls();
|
||||
}
|
||||
|
||||
function resizeVideoAndControls() {
|
||||
resizeVideo();
|
||||
resizeControls();
|
||||
}
|
||||
|
||||
function resizeVideo() {
|
||||
self.videoCSS = getVideoCSS();
|
||||
self.$video.css(self.videoCSS);
|
||||
};
|
||||
|
||||
function toggleAnnotations(event, data) {
|
||||
self.options.showAnnotations = !data.collapsed;
|
||||
that.triggerEvent('change', {
|
||||
showAnnotations: self.options.showAnnotations
|
||||
});
|
||||
}
|
||||
|
||||
function toggleControls(event, data) {
|
||||
self.options.showControls = !data.collapsed;
|
||||
that.triggerEvent('change', {
|
||||
showControls: self.options.showControls
|
||||
});
|
||||
}
|
||||
|
||||
function toggleFullscreen() {
|
||||
self.fullscreen = !self.fullscreen;
|
||||
self.options.showAnnotations && that.$element.toggle(1);
|
||||
self.fullscreen && self.options.showControls && self.$panel.toggle(1);
|
||||
that.triggerEvent((self.fullscreen ? 'enter' : 'exit') + 'fullscreen', {});
|
||||
}
|
||||
|
||||
function toggleSize() {
|
||||
self.options.videoSize = self.options.videoSize == 'fit' ? 'fill' : 'fit';
|
||||
resizeVideo();
|
||||
that.triggerEvent('change', {
|
||||
videoSize: self.options.videoSize
|
||||
});
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'height') {
|
||||
resizeVideo();
|
||||
} else if (key == 'position') {
|
||||
self.$video.position(value);
|
||||
} else if (key == 'width') {
|
||||
resizeVideoAndControls();
|
||||
}
|
||||
}
|
||||
|
||||
that.toggleAnnotations = function() {
|
||||
that.$element.toggle(1);
|
||||
//that.toggleAnnotations(null, !self.options.showAnnotations);
|
||||
};
|
||||
|
||||
that.toggleControls = function() {
|
||||
self.$panel.toggle(1);
|
||||
//that.toggleControls(null, !self.options.showControls);
|
||||
};
|
||||
|
||||
that.toggleMute = function() {
|
||||
self.$button.mute.trigger('click');
|
||||
};
|
||||
|
||||
that.togglePlay = function() {
|
||||
self.$button.play.trigger('click');
|
||||
};
|
||||
|
||||
that.toggleSize = function() {
|
||||
self.$button.size.trigger('click');
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
}
|
||||
589
source/Ox.UI/js/Video/Ox.VideoPlayer.js
Normal file
589
source/Ox.UI/js/Video/Ox.VideoPlayer.js
Normal file
|
|
@ -0,0 +1,589 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.VideoEditor = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
annotationsSize: 0,
|
||||
cuts: [],
|
||||
duration: 0,
|
||||
find: '',
|
||||
frameURL: function() {},
|
||||
fps: 25, // fixme: doesn't get handed through to player
|
||||
height: 0,
|
||||
largeTimeline: true,
|
||||
layers: [],
|
||||
matches: [],
|
||||
points: [0, 0],
|
||||
position: 0,
|
||||
posterFrame: 0,
|
||||
showAnnotations: false,
|
||||
subtitles: [],
|
||||
videoHeight: 0,
|
||||
videoId: '',
|
||||
videoWidth: 0,
|
||||
videoSize: 'small',
|
||||
videoURL: '',
|
||||
width: 0
|
||||
})
|
||||
.options(options || {})
|
||||
.mousedown(function() {
|
||||
that.gainFocus();
|
||||
})
|
||||
.bindEvent({
|
||||
key_shift_0: function() {
|
||||
movePositionBy(-self.options.position);
|
||||
},
|
||||
key_alt_left: function() {
|
||||
},
|
||||
key_alt_right: function() {
|
||||
},
|
||||
key_alt_shift_left: function() {
|
||||
},
|
||||
key_alt_shift_right: function() {
|
||||
},
|
||||
key_backslash: function() {
|
||||
select('subtitle');
|
||||
},
|
||||
key_closebracket: function() {
|
||||
movePositionTo('subtitle', 1);
|
||||
},
|
||||
key_comma: function() {
|
||||
movePositionTo('cut', -1);
|
||||
},
|
||||
key_dot: function() {
|
||||
movePositionTo('cut', 1);
|
||||
},
|
||||
key_down: function() {
|
||||
movePositionBy(self.sizes.timeline[0].width);
|
||||
},
|
||||
key_i: function() {
|
||||
setPoint('in');
|
||||
},
|
||||
key_left: function() {
|
||||
movePositionBy(-1);
|
||||
},
|
||||
key_m: toggleMute,
|
||||
key_o: function() {
|
||||
setPoint('out');
|
||||
},
|
||||
key_openbracket: function() {
|
||||
movePositionTo('subtitle', -1);
|
||||
},
|
||||
key_p: playInToOut,
|
||||
key_right: function() {
|
||||
movePositionBy(1);
|
||||
},
|
||||
key_s: function() {
|
||||
// toggleSize
|
||||
},
|
||||
key_shift_comma: function() {
|
||||
movePositionTo('match', -1)
|
||||
},
|
||||
key_shift_dot: function() {
|
||||
movePositionTo('match', 1)
|
||||
},
|
||||
key_shift_down: function() {
|
||||
movePositionBy(self.options.duration);
|
||||
},
|
||||
key_shift_left: function() {
|
||||
movePositionBy(-0.04);
|
||||
//movePositionBy(-60);
|
||||
},
|
||||
key_shift_i: function() {
|
||||
goToPoint('in');
|
||||
},
|
||||
key_shift_o: function() {
|
||||
goToPoint('out');
|
||||
},
|
||||
key_shift_right: function() {
|
||||
movePositionBy(0.04);
|
||||
//movePositionBy(60);
|
||||
},
|
||||
key_shift_up: function() {
|
||||
movePositionBy(-self.options.duration);
|
||||
},
|
||||
key_slash: function() {
|
||||
select('cut');
|
||||
},
|
||||
key_space: togglePlay,
|
||||
key_up: function() {
|
||||
movePositionBy(-self.sizes.timeline[0].width);
|
||||
}
|
||||
});
|
||||
|
||||
$.extend(self, {
|
||||
$player: [],
|
||||
$timeline: [],
|
||||
controlsHeight: 16,
|
||||
margin: 8,
|
||||
videoRatio: self.options.videoWidth / self.options.videoHeight
|
||||
});
|
||||
|
||||
self.$editor = new Ox.Element()
|
||||
.addClass('OxVideoEditor')
|
||||
.click(function() {
|
||||
that.gainFocus()
|
||||
});
|
||||
|
||||
self.sizes = getSizes();
|
||||
|
||||
['play', 'in', 'out'].forEach(function(type, i) {
|
||||
self.$player[i] = new Ox.VideoEditorPlayer({
|
||||
duration: self.options.duration,
|
||||
find: self.options.find,
|
||||
height: self.sizes.player[i].height,
|
||||
id: 'player' + Ox.toTitleCase(type),
|
||||
points: self.options.points,
|
||||
position: type == 'play' ? self.options.position : self.options.points[type == 'in' ? 0 : 1],
|
||||
posterFrame: self.options.posterFrame,
|
||||
subtitles: self.options.subtitles,
|
||||
type: type,
|
||||
url: type == 'play' ? self.options.videoURL : self.options.frameURL,
|
||||
width: self.sizes.player[i].width
|
||||
})
|
||||
.css({
|
||||
left: self.sizes.player[i].left + 'px',
|
||||
top: self.sizes.player[i].top + 'px'
|
||||
})
|
||||
.bindEvent(type == 'play' ? {
|
||||
playing: changePlayer,
|
||||
togglesize: toggleSize
|
||||
} : {
|
||||
change: function() {
|
||||
goToPoint(type);
|
||||
},
|
||||
set: function() {
|
||||
setPoint(type);
|
||||
}
|
||||
})
|
||||
.appendTo(self.$editor);
|
||||
});
|
||||
|
||||
self.$timeline[0] = new Ox.LargeTimeline({
|
||||
cuts: self.options.cuts,
|
||||
duration: self.options.duration,
|
||||
find: self.options.find,
|
||||
id: 'timelineLarge',
|
||||
matches: self.options.matches,
|
||||
points: self.options.points,
|
||||
position: self.options.position,
|
||||
subtitles: self.options.subtitles,
|
||||
videoId: self.options.videoId,
|
||||
width: self.sizes.timeline[0].width
|
||||
})
|
||||
.css({
|
||||
left: self.sizes.timeline[0].left + 'px',
|
||||
top: self.sizes.timeline[0].top + 'px'
|
||||
})
|
||||
.bindEvent('change', changeTimelineLarge)
|
||||
.bindEvent('changeEnd', changeTimelineLarge)
|
||||
.appendTo(self.$editor);
|
||||
|
||||
self.$timeline[1] = new Ox.BlockTimeline({
|
||||
cuts: self.options.cuts,
|
||||
duration: self.options.duration,
|
||||
find: self.options.find,
|
||||
id: 'timelineSmall',
|
||||
matches: self.options.matches,
|
||||
points: self.options.points,
|
||||
position: self.options.position,
|
||||
subtitles: self.options.subtitles,
|
||||
videoId: self.options.videoId,
|
||||
width: self.sizes.timeline[1].width
|
||||
})
|
||||
.css({
|
||||
left: self.sizes.timeline[1].left + 'px',
|
||||
top: self.sizes.timeline[1].top + 'px'
|
||||
})
|
||||
.bindEvent('change', changeTimelineSmall)
|
||||
.appendTo(self.$editor);
|
||||
|
||||
self.$annotations = new Ox.Element()
|
||||
.css({
|
||||
overflowY: 'auto'
|
||||
})
|
||||
.bindEvent({
|
||||
resize: resizeAnnotations,
|
||||
toggle: toggleAnnotations
|
||||
});
|
||||
self.$annotationPanel = [];
|
||||
|
||||
self.options.layers.forEach(function(layer, i) {
|
||||
self.$annotationPanel[i] = new Ox.AnnotationPanel(
|
||||
$.extend({
|
||||
width: self.options.annotationSize
|
||||
}, layer)
|
||||
)
|
||||
.bindEvent({
|
||||
add: function(event, data) {
|
||||
data.layer = layer.id;
|
||||
data['in'] = self.options.points[0];
|
||||
data.out = self.options.points[1];
|
||||
that.triggerEvent('addAnnotation', data);
|
||||
},
|
||||
'delete': function(event, data) {
|
||||
data.layer = layer.id;
|
||||
that.triggerEvent('removeAnnotations', data);
|
||||
},
|
||||
select: function(event, data) {
|
||||
self.options.layers.forEach(function(l, j) { // fixme: l? j?
|
||||
if(l.id != layer.id) {
|
||||
self.$annotationPanel[j].deselectItems();
|
||||
}
|
||||
});
|
||||
selectAnnotation(event, data);
|
||||
},
|
||||
submit: updateAnnotation
|
||||
});
|
||||
self.$annotationPanel[i]
|
||||
.appendTo(self.$annotations);
|
||||
});
|
||||
|
||||
that.$element = new Ox.SplitPanel({
|
||||
elements: [
|
||||
{
|
||||
element: self.$editor
|
||||
},
|
||||
{
|
||||
collapsed: !self.options.showAnnotations,
|
||||
collapsible: true,
|
||||
element: self.$annotations,
|
||||
resizable: true,
|
||||
resize: [192, 256, 320, 384],
|
||||
size: self.options.annotationsSize
|
||||
}
|
||||
],
|
||||
orientation: 'horizontal'
|
||||
});
|
||||
|
||||
function changePlayer(event, data) {
|
||||
self.options.position = data.position;
|
||||
self.$timeline[0].options({
|
||||
position: data.position
|
||||
});
|
||||
self.$timeline[1].options({
|
||||
position: data.position
|
||||
});
|
||||
}
|
||||
|
||||
function changeTimelineLarge(event, data) {
|
||||
self.options.position = data.position;
|
||||
self.$player[0].options({
|
||||
position: data.position
|
||||
});
|
||||
self.$timeline[1].options({
|
||||
position: data.position
|
||||
});
|
||||
}
|
||||
|
||||
function changeTimelineSmall(event, data) {
|
||||
self.options.position = data.position;
|
||||
self.$player[0].options({
|
||||
position: data.position
|
||||
});
|
||||
self.$timeline[0].options({
|
||||
position: data.position
|
||||
});
|
||||
}
|
||||
|
||||
function getNextPosition(type, direction) {
|
||||
var found = false,
|
||||
position = 0,
|
||||
positions;
|
||||
if (type == 'cut') {
|
||||
positions = self.options.cuts;
|
||||
} else if (type == 'match') {
|
||||
positions = $.map(self.options.matches, function(v, i) {
|
||||
return self.options.subtitles[v]['in'];
|
||||
});
|
||||
} else if (type == 'subtitle') {
|
||||
positions = $.map(self.options.subtitles, function(v, i) {
|
||||
return v['in'];
|
||||
});
|
||||
}
|
||||
direction == -1 && positions.reverse();
|
||||
Ox.forEach(positions, function(v) {
|
||||
if (direction == 1 ? v > self.options.position : v < self.options.position) {
|
||||
position = v;
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
direction == -1 && positions.reverse();
|
||||
if (!found) {
|
||||
position = positions[direction == 1 ? 0 : positions.length - 1];
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
function getPoints(type) {
|
||||
var found = false,
|
||||
points,
|
||||
positions = [];
|
||||
if (type == 'cut') {
|
||||
positions = self.options.cuts;
|
||||
} else if (type == 'match') {
|
||||
// ...
|
||||
} else if (type == 'subtitle') {
|
||||
self.options.subtitles.forEach(function(v, i) {
|
||||
positions.push(v['in']);
|
||||
positions.push(v.out);
|
||||
});
|
||||
}
|
||||
positions.indexOf(0) == -1 && positions.unshift(0);
|
||||
positions.indexOf(self.options.duration) == -1 &&
|
||||
positions.push(self.options.duration);
|
||||
Ox.forEach(positions, function(v, i) {
|
||||
if (v > self.options.position) {
|
||||
points = [positions[i - 1], positions[i]];
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return points;
|
||||
}
|
||||
|
||||
function getSizes(scrollbarIsVisible) {
|
||||
//Ox.print('getSizes', scrollbarIsVisible)
|
||||
var scrollbarWidth = Ox.UI.SCROLLBAR_SIZE,
|
||||
contentWidth = self.options.width -
|
||||
(self.options.showAnnotations * self.options.annotationsSize) - 1 -
|
||||
(scrollbarIsVisible ? scrollbarWidth : 0),
|
||||
height,
|
||||
lines,
|
||||
size = {
|
||||
player: [],
|
||||
timeline: []
|
||||
},
|
||||
width, widths;
|
||||
if (self.options.videoSize == 'small') {
|
||||
width = 0;
|
||||
widths = Ox.divideInt(contentWidth - 4 * self.margin, 3);
|
||||
[1, 0, 2].forEach(function(v, i) {
|
||||
size.player[v] = {
|
||||
left: (i + 0.5) * self.margin + width,
|
||||
top: self.margin / 2,
|
||||
width: widths[i],
|
||||
height: Math.round(widths[1] / self.videoRatio)
|
||||
}
|
||||
width += widths[i];
|
||||
});
|
||||
} else {
|
||||
size.player[0] = {
|
||||
left: self.margin / 2,
|
||||
top: self.margin / 2,
|
||||
width: Math.round((contentWidth - 3 * self.margin + (self.controlsHeight + self.margin) / 2 * self.videoRatio) * 2/3),
|
||||
}
|
||||
size.player[0].height = Math.round(size.player[0].width / self.videoRatio);
|
||||
size.player[1] = {
|
||||
left: size.player[0].left + size.player[0].width + self.margin,
|
||||
top: size.player[0].top,
|
||||
width: contentWidth - 3 * self.margin - size.player[0].width
|
||||
}
|
||||
size.player[1].height = Math.ceil(size.player[1].width / self.videoRatio)
|
||||
size.player[2] = {
|
||||
left: size.player[1].left,
|
||||
top: size.player[0].top + size.player[1].height + self.controlsHeight + self.margin,
|
||||
width: size.player[1].width,
|
||||
height: size.player[0].height - size.player[1].height - self.controlsHeight - self.margin
|
||||
}
|
||||
}
|
||||
size.timeline[0] = {
|
||||
left: self.margin / 2,
|
||||
top: size.player[0].height + self.controlsHeight + 1.5 * self.margin,
|
||||
width: contentWidth - 2 * self.margin,
|
||||
height: 64
|
||||
}
|
||||
size.timeline[1] = {
|
||||
left: size.timeline[0].left,
|
||||
top: size.timeline[0].top + size.timeline[0].height + self.margin,
|
||||
width: size.timeline[0].width
|
||||
}
|
||||
lines = Math.ceil(self.options.duration / size.timeline[1].width);
|
||||
height = getHeight();
|
||||
self.$editor.css({
|
||||
overflowY: (scrollbarIsVisible && height <= self.options.height) ? 'scroll' : 'auto'
|
||||
});
|
||||
return (!scrollbarIsVisible && height > self.options.height) ? getSizes(true) : size;
|
||||
function getHeight() {
|
||||
return size.player[0].height + self.controlsHeight +
|
||||
size.timeline[0].height + lines * 16 +
|
||||
(lines + 3) * self.margin;
|
||||
}
|
||||
}
|
||||
|
||||
function goToPoint(point) {
|
||||
self.options.position = self.options.points[point == 'in' ? 0 : 1];
|
||||
setPosition();
|
||||
that.triggerEvent('change', {
|
||||
position: self.options.position
|
||||
});
|
||||
}
|
||||
|
||||
function movePositionBy(sec) {
|
||||
self.options.position = Ox.limit(self.options.position + sec, 0, self.options.duration);
|
||||
setPosition();
|
||||
that.triggerEvent('change', {
|
||||
position: self.options.position
|
||||
});
|
||||
}
|
||||
|
||||
function movePositionTo(type, direction) {
|
||||
self.options.position = getNextPosition(type, direction);
|
||||
setPosition();
|
||||
that.triggerEvent('change', {
|
||||
position: self.options.position
|
||||
});
|
||||
}
|
||||
|
||||
function playInToOut() {
|
||||
self.$player[0].playInToOut();
|
||||
}
|
||||
|
||||
function resizeAnnotations(event, data) {
|
||||
self.options.annotationsSize = data;
|
||||
setSizes();
|
||||
}
|
||||
|
||||
function resizeEditor(event, data) {
|
||||
var width = data - 2 * margin + 100;
|
||||
resizeVideoPlayers(width);
|
||||
$timelineLarge.options({
|
||||
width: width
|
||||
});
|
||||
$timelineSmall.options({
|
||||
width: width
|
||||
});
|
||||
}
|
||||
|
||||
function resizePlayers() {
|
||||
self.$player.forEach(function(v, i) {
|
||||
v.options({
|
||||
width: size[i].width,
|
||||
height: size[i].height
|
||||
})
|
||||
.css({
|
||||
left: size[i].left + 'px',
|
||||
top: size[i].top + 'px',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function selectAnnotation(event, data) {
|
||||
self.options.position = data['in']
|
||||
self.options.points = [data['in'], data.out];
|
||||
setPosition();
|
||||
setPoints();
|
||||
}
|
||||
function updateAnnotation(event, data) {
|
||||
data['in'] = self.options.points[0];
|
||||
data.out = self.options.points[1];
|
||||
that.triggerEvent('updateAnnotation', data);
|
||||
}
|
||||
|
||||
function select(type) {
|
||||
self.options.points = getPoints(type);
|
||||
setPoints();
|
||||
}
|
||||
|
||||
function setPoint(point) {
|
||||
self.options.points[point == 'in' ? 0 : 1] = self.options.position;
|
||||
if (self.options.points[1] < self.options.points[0]) {
|
||||
self.options.points[point == 'in' ? 1 : 0] = self.options.position;
|
||||
}
|
||||
setPoints();
|
||||
}
|
||||
|
||||
function setPoints() {
|
||||
self.$player.forEach(function(v, i) {
|
||||
v.options($.extend({
|
||||
points: self.options.points
|
||||
}, i ? {
|
||||
position: self.options.points[i - 1]
|
||||
} : {}));
|
||||
});
|
||||
self.$timeline.forEach(function(v) {
|
||||
v.options({
|
||||
points: self.options.points
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setPosition() {
|
||||
self.$player[0].options({
|
||||
position: self.options.position
|
||||
});
|
||||
self.$timeline.forEach(function(v) {
|
||||
v.options({
|
||||
position: self.options.position
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setSizes() {
|
||||
self.sizes = getSizes();
|
||||
self.$player.forEach(function(v, i) {
|
||||
v.options({
|
||||
height: self.sizes.player[i].height,
|
||||
width: self.sizes.player[i].width
|
||||
})
|
||||
.css({
|
||||
left: self.sizes.player[i].left + 'px',
|
||||
top: self.sizes.player[i].top + 'px'
|
||||
});
|
||||
});
|
||||
self.$timeline.forEach(function(v, i) {
|
||||
v.options({
|
||||
width: self.sizes.timeline[i].width
|
||||
})
|
||||
.css({
|
||||
left: self.sizes.timeline[i].left + 'px',
|
||||
top: self.sizes.timeline[i].top + 'px'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toggleAnnotations(event, data) {
|
||||
self.options.showAnnotations = !data.collapsed;
|
||||
setSizes();
|
||||
}
|
||||
|
||||
function toggleMute() {
|
||||
self.$player[0].toggleMute();
|
||||
}
|
||||
|
||||
function togglePlay() {
|
||||
self.$player[0].togglePlay();
|
||||
}
|
||||
|
||||
function toggleSize(event, data) {
|
||||
self.options.videoSize = data.size
|
||||
setSizes();
|
||||
that.triggerEvent('togglesize', {
|
||||
size: self.options.videoSize
|
||||
});
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'width' || key == 'height') {
|
||||
//Ox.print('XXXX setSizes', key, value, self.options.width, self.options.height)
|
||||
setSizes();
|
||||
} else if (key == 'position') {
|
||||
self.$player[0].position(value);
|
||||
}
|
||||
};
|
||||
|
||||
that.addAnnotation = function(layer, item) {
|
||||
var i = Ox.getPositionById(self.options.layers, layer);
|
||||
self.$annotationPanel[i].addItem(item);
|
||||
}
|
||||
|
||||
that.removeAnnotations = function(layer, ids) {
|
||||
var i = Ox.getPositionById(self.options.layers, layer);
|
||||
self.$annotationPanel[i].removeItems(ids);
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
388
source/Ox.UI/js/Window/Ox.Dialog.js
Normal file
388
source/Ox.UI/js/Window/Ox.Dialog.js
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Dialog = function(options, self) {
|
||||
|
||||
// fixme: dialog should be derived from a generic draggable
|
||||
// fixme: buttons should have a close attribute, or the dialog a close id
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
title: '',
|
||||
buttons: [],
|
||||
content: null,
|
||||
height: 216,
|
||||
keys: {},
|
||||
minHeight: 144,
|
||||
minWidth: 256,
|
||||
movable: true,
|
||||
padding: 16,
|
||||
resizable: true,
|
||||
width: 384
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxDialog')
|
||||
.bindEvent({
|
||||
key_enter: function() {
|
||||
keypress('enter');
|
||||
},
|
||||
key_escape: function() {
|
||||
//Ox.print('KEY ESCAPE')
|
||||
keypress('escape');
|
||||
}
|
||||
});
|
||||
|
||||
$.extend(self, {
|
||||
initialWidth: self.options.width,
|
||||
initialHeight: self.options.height
|
||||
})
|
||||
|
||||
that.$titlebar = new Ox.Bar({
|
||||
size: 'medium'
|
||||
})
|
||||
.addClass('OxTitleBar')
|
||||
.appendTo(that);
|
||||
self.options.movable && that.$titlebar
|
||||
.dblclick(center)
|
||||
.bindEvent({
|
||||
dragstart: dragstart,
|
||||
drag: drag
|
||||
});
|
||||
|
||||
that.$title = new Ox.Element()
|
||||
.addClass('OxTitle')
|
||||
.html(self.options.title)
|
||||
.appendTo(that.$titlebar);
|
||||
|
||||
that.$content = new Ox.Element()
|
||||
.addClass('OxContent')
|
||||
.css({
|
||||
padding: self.options.padding + 'px',
|
||||
overflow: 'auto'
|
||||
})
|
||||
.append(self.options.content)
|
||||
.appendTo(that);
|
||||
|
||||
that.$buttonsbar = new Ox.Bar({})
|
||||
.addClass('OxButtonsBar')
|
||||
.appendTo(that);
|
||||
loadButtons();
|
||||
|
||||
//that.$buttons[0].focus();
|
||||
|
||||
that.$layer = new Ox.Element() // fixme: Layer widget that would handle click?
|
||||
.addClass('OxLayer')
|
||||
.mousedown(mousedownLayer)
|
||||
.mouseup(mouseupLayer);
|
||||
|
||||
function center() {
|
||||
var documentHeight = Ox.UI.$document.height();
|
||||
that.css({
|
||||
left: 0,
|
||||
top: Math.max(parseInt(-documentHeight / 10), self.options.height - documentHeight + 40) + 'px',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
margin: 'auto'
|
||||
});
|
||||
}
|
||||
|
||||
function dragstart(event, e) {
|
||||
self.drag = {
|
||||
bodyWidth: Ox.UI.$body.width(),
|
||||
bodyHeight: Ox.UI.$document.height(),
|
||||
elementWidth: that.width(),
|
||||
offset: that.offset(),
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
};
|
||||
that.css({
|
||||
margin: 0
|
||||
});
|
||||
}
|
||||
|
||||
function drag(event, e) {
|
||||
var left = Ox.limit(
|
||||
self.drag.offset.left - self.drag.x + e.clientX,
|
||||
24 - self.drag.elementWidth, self.drag.bodyWidth - 24
|
||||
//0, self.drag.documentWidth - self.drag.elementWidth
|
||||
),
|
||||
top = Ox.limit(
|
||||
self.drag.offset.top - self.drag.y + e.clientY,
|
||||
24, self.drag.bodyHeight - 24
|
||||
//24, self.drag.documentHeight - self.drag.elementHeight
|
||||
);
|
||||
that.css({
|
||||
left: left + 'px',
|
||||
top: top + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
function dragstartResize(event, e) {
|
||||
self.drag = {
|
||||
documentWidth: Ox.UI.$document.width(),
|
||||
documentHeight: Ox.UI.$document.height(),
|
||||
elementWidth: that.width(),
|
||||
elementHeight: that.height(),
|
||||
offset: that.offset(),
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
};
|
||||
$.extend(self.drag, {
|
||||
ratio: self.drag.elementWidth / self.drag.elementHeight
|
||||
});
|
||||
that.css({
|
||||
left: self.drag.offset.left,
|
||||
top: self.drag.offset.top,
|
||||
margin: 0
|
||||
});
|
||||
}
|
||||
|
||||
function dragResize(event, e) {
|
||||
if (!e.shiftKey) {
|
||||
self.drag.ratio = self.options.width / self.options.height;
|
||||
}
|
||||
self.options.width = Ox.limit(
|
||||
self.drag.elementWidth - self.drag.x + e.clientX,
|
||||
self.options.minWidth,
|
||||
Math.min(
|
||||
self.drag.documentWidth,
|
||||
self.drag.documentWidth - self.drag.offset.left
|
||||
)
|
||||
);
|
||||
self.options.height = Ox.limit(
|
||||
self.drag.elementHeight - self.drag.y + e.clientY,
|
||||
self.options.minHeight,
|
||||
Math.min(
|
||||
self.drag.documentHeight,
|
||||
self.drag.documentHeight - self.drag.offset.top
|
||||
)
|
||||
);
|
||||
if (e.shiftKey) {
|
||||
self.options.height = Ox.limit(
|
||||
self.options.width / self.drag.ratio,
|
||||
self.options.minHeight,
|
||||
Math.min(
|
||||
self.drag.documentHeight,
|
||||
self.drag.documentHeight - self.drag.offset.top
|
||||
)
|
||||
);
|
||||
self.options.width = self.options.height * self.drag.ratio;
|
||||
}
|
||||
that.width(self.options.width);
|
||||
that.height(self.options.height);
|
||||
that.$content.height(self.options.height - 48 - 2 * self.options.padding); // fixme: this should happen automatically
|
||||
}
|
||||
|
||||
function dragendResize(event, e) {
|
||||
triggerResizeEvent();
|
||||
}
|
||||
|
||||
function getButtonById(id) {
|
||||
var ret = null;
|
||||
//Ox.print('that.$buttons', that.$buttons, id)
|
||||
Ox.forEach(that.$buttons, function(button) {
|
||||
if (button.options('id') == id) {
|
||||
ret = button;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function keypress(key) {
|
||||
var id = self.options.keys[key];
|
||||
//Ox.print('X', key, self.options.keys)
|
||||
id && getButtonById(id).$element.trigger('click');
|
||||
}
|
||||
|
||||
function loadButtons() {
|
||||
/*Ox.print('loadButtons', $.map(self.options.buttons, function(v) {
|
||||
return v;
|
||||
}));*/
|
||||
if (that.$buttons) {
|
||||
that.$buttons.forEach(function($button) {
|
||||
$button.removeElement();
|
||||
});
|
||||
that.$resize.removeElement();
|
||||
// that.$buttonsbar.empty();
|
||||
}
|
||||
that.$buttons = [];
|
||||
if (!Ox.isArray(self.options.buttons[0])) {
|
||||
self.options.buttons = [[], self.options.buttons];
|
||||
}
|
||||
self.options.buttons[0].forEach(function(button, i) {
|
||||
that.$buttons[i] = button
|
||||
.addClass('OxLeft')
|
||||
.appendTo(that.$buttonsbar);
|
||||
});
|
||||
if (self.options.resizable) {
|
||||
that.$resize = new Ox.Element()
|
||||
.addClass('OxResize')
|
||||
.dblclick(reset)
|
||||
.bindEvent({
|
||||
dragstart: dragstartResize,
|
||||
drag: dragResize,
|
||||
dragend: dragendResize
|
||||
})
|
||||
.appendTo(that.$buttonsbar);
|
||||
}
|
||||
self.options.buttons[1].reverse().forEach(function(button) {
|
||||
that.$buttons[that.$buttons.length] = button
|
||||
.addClass('OxRight')
|
||||
.appendTo(that.$buttonsbar);
|
||||
});
|
||||
}
|
||||
|
||||
function mousedownLayer() {
|
||||
that.$layer.stop().animate({
|
||||
opacity: 0.5
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function mouseupLayer() {
|
||||
that.$layer.stop().animate({
|
||||
opacity: 0
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
$.extend(self.options, {
|
||||
height: self.initialHeight,
|
||||
width: self.initialWidth
|
||||
});
|
||||
that/*.css({
|
||||
left: Math.max(that.offset().left, 24 - that.width())
|
||||
})*/
|
||||
.width(self.options.width)
|
||||
.height(self.options.height);
|
||||
that.$content.height(self.options.height - 48 - 2 * self.options.padding); // fixme: this should happen automatically
|
||||
triggerResizeEvent();
|
||||
}
|
||||
|
||||
function triggerResizeEvent() {
|
||||
that.triggerEvent('resize', {
|
||||
width: self.options.width,
|
||||
height: self.options.height
|
||||
});
|
||||
}
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'buttons') {
|
||||
loadButtons();
|
||||
/*
|
||||
that.$buttonsbar.children().animate({
|
||||
opacity: 0
|
||||
}, 100, function() {
|
||||
loadButtons();
|
||||
that.$buttonsbar.children().animate({
|
||||
opacity: 1
|
||||
}, 100);
|
||||
});
|
||||
*/
|
||||
} else if (key == 'content') {
|
||||
that.$content.html(value);
|
||||
} else if (key == 'height' || key == 'width') {
|
||||
that.animate({
|
||||
height: self.options.height + 'px',
|
||||
width: self.options.width + 'px'
|
||||
}, 100);
|
||||
that.$content.height(self.options.height - 48 - 2 * self.options.padding); // fixme: this should happen automatically
|
||||
} else if (key == 'title') {
|
||||
that.$title.animate({
|
||||
opacity: 0
|
||||
}, 100, function() {
|
||||
that.$title.html(value).animate({
|
||||
opacity: 1
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
that.center = function() {
|
||||
|
||||
};
|
||||
|
||||
that.close = function(callback) {
|
||||
callback = callback || function() {};
|
||||
that.animate({
|
||||
opacity: 0
|
||||
}, 200, function() {
|
||||
that.$buttons.forEach(function($button) {
|
||||
$button.removeElement();
|
||||
});
|
||||
that.loseFocus();
|
||||
that.$layer.removeElement();
|
||||
that.removeElement();
|
||||
callback();
|
||||
});
|
||||
Ox.UI.$window.unbind('mouseup', mouseupLayer)
|
||||
return that;
|
||||
};
|
||||
|
||||
that.content = function($element) {
|
||||
that.$content.empty().append($element);
|
||||
return that;
|
||||
}
|
||||
|
||||
that.disable = function() {
|
||||
// to be used on submit of form, like login
|
||||
that.$layer.addClass('OxFront');
|
||||
return that;
|
||||
};
|
||||
|
||||
that.disableButton = function(id) {
|
||||
getButtonById(id).options({
|
||||
disabled: true
|
||||
});
|
||||
return that;
|
||||
};
|
||||
|
||||
that.enable = function() {
|
||||
that.$layer.removeClass('OxFront');
|
||||
return that;
|
||||
};
|
||||
|
||||
that.enableButton = function(id) {
|
||||
getButtonById(id).options({
|
||||
disabled: false
|
||||
});
|
||||
return that;
|
||||
};
|
||||
|
||||
that.open = function() {
|
||||
//Ox.print('before open')
|
||||
that.$layer.appendTo(Ox.UI.$body);
|
||||
that.css({
|
||||
opacity: 0
|
||||
}).appendTo(Ox.UI.$body).animate({
|
||||
opacity: 1
|
||||
}, 200);
|
||||
center();
|
||||
reset();
|
||||
// fixme: the following line prevents preview-style dialog
|
||||
that.gainFocus();
|
||||
Ox.UI.$window.bind('mouseup', mouseupLayer)
|
||||
//Ox.print('after open')
|
||||
return that;
|
||||
};
|
||||
|
||||
that.size = function(width, height, callback) {
|
||||
$.extend(self, {
|
||||
initialWidth: width,
|
||||
initialHeight: height
|
||||
});
|
||||
$.extend(self.options, {
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
// fixme: duplicated
|
||||
that.animate({
|
||||
height: self.options.height + 'px',
|
||||
width: self.options.width + 'px'
|
||||
}, 100, function() {
|
||||
that.$content.height(self.options.height - 48 - 2 * self.options.padding); // fixme: this should happen automatically
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
57
source/Ox.UI/js/Window/Ox.Tooltip.js
Normal file
57
source/Ox.UI/js/Window/Ox.Tooltip.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Tooltip = function(options, self) {
|
||||
|
||||
var self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
animate: true,
|
||||
title: ''
|
||||
})
|
||||
.options(options || {})
|
||||
.addClass('OxTooltip')
|
||||
.html(self.options.title);
|
||||
|
||||
self.options.animate && that.css({
|
||||
opacity: 0
|
||||
});
|
||||
|
||||
self.onChange = function(key, value) {
|
||||
if (key == 'title') {
|
||||
that.html(value);
|
||||
}
|
||||
};
|
||||
|
||||
that.hide = function() {
|
||||
if (self.options.animate) {
|
||||
that.animate({
|
||||
opacity: 0
|
||||
}, 250, function() {
|
||||
that.removeElement();
|
||||
});
|
||||
} else {
|
||||
that.removeElement();
|
||||
}
|
||||
return that;
|
||||
};
|
||||
|
||||
that.show = function(x, y) {
|
||||
var left, top, width, height;
|
||||
$('.OxTooltip').remove(); // fixme: don't use DOM
|
||||
that.appendTo(Ox.UI.$body);
|
||||
width = that.width();
|
||||
height = that.height();
|
||||
left = Ox.limit(x - width / 2, 0, Ox.UI.$document.width() - width);
|
||||
top = y > Ox.UI.$document.height() - height - 16 ? y - 32 : y + 16;
|
||||
that.css({
|
||||
left: left + 'px',
|
||||
top: top + 'px'
|
||||
});
|
||||
self.options.animate && that.animate({
|
||||
opacity: 1
|
||||
}, 250);
|
||||
return that;
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
54
source/Ox.UI/js/Window/Ox.Window.js
Normal file
54
source/Ox.UI/js/Window/Ox.Window.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// vim: et:ts=4:sw=4:sts=4:ft=js
|
||||
Ox.Window = function(options, self) {
|
||||
|
||||
self = self || {},
|
||||
that = new Ox.Element('div', self)
|
||||
.defaults({
|
||||
draggable: true,
|
||||
fullscreenable: true, // fixme: silly name
|
||||
height: 225,
|
||||
resizeable: true,
|
||||
scaleable: true,
|
||||
width: 400
|
||||
})
|
||||
.options(options || {})
|
||||
|
||||
self.center = function() {
|
||||
|
||||
};
|
||||
|
||||
self.drag = function() {
|
||||
|
||||
};
|
||||
|
||||
self.fullscreen = function() {
|
||||
|
||||
};
|
||||
|
||||
self.onChange = function() {
|
||||
|
||||
};
|
||||
|
||||
self.reset = function() {
|
||||
|
||||
};
|
||||
|
||||
self.resize = function() {
|
||||
|
||||
};
|
||||
|
||||
self.scale = function() {
|
||||
|
||||
};
|
||||
|
||||
that.close = function() {
|
||||
|
||||
};
|
||||
|
||||
that.open = function() {
|
||||
|
||||
};
|
||||
|
||||
return that;
|
||||
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue