oxjs/source/Ox.UI/js/Calendar/Ox.ListCalendar.js

637 lines
20 KiB
JavaScript

// vim: et:ts=4:sw=4:sts=4:ft=javascript
'use strict';
/*@
Ox.ListCalendar <f> ListCalendar object
@*/
Ox.ListCalendar = function(options, self) {
self = self || {};
var that = Ox.Element({}, self)
.defaults({
addEvent: null,
editPlace: null,
events: [],
height: 256,
pageLength: 100,
removePlace: null,
selected: [],
showControls: false,
sort: [{key: 'name', operator: '+'}],
width: 256
})
.options(options || {})
.addClass('OxListCalendar')
.css({
width: self.options.width + 'px',
height: self.options.height + 'px'
});
self.durationCache = {};
self.columns = [
{
id: 'name',
operator: '+',
removable: false,
title: 'Name',
visible: true,
width: 144
},
{
editable: false,
format: function(value) {
return value.join('; ');
},
id: 'alternativeNames',
operator: '+',
title: 'Alternative Names',
visible: true,
width: 144
},
{
format: function(value) {
return Ox.toTitleCase(value);
},
id: 'type',
operator: '+',
title: 'Type',
visible: true,
width: 64
},
{
id: 'start',
map: function(value) {
return Ox.parseDate(value);
},
operator: '-',
title: 'Start',
visible: true,
width: 144
},
{
id: 'end',
map: function(value) {
return Ox.parseDate(value);
},
operator: '-',
title: 'End',
visible: true,
width: 144
},
{
format: function(value, data) {
// return Ox.formatDateRangeDuration(data.start, data.end, true);
var key = data.start + ' - ' + data.end;
if (!self.durationCache[key]) {
self.durationCache[key] = data.start
? Ox.formatDateRangeDuration(data.start, data.end, true)
: '';
}
return self.durationCache[key];
},
id: 'id',
map: function(value, data) {
return Ox.parseDate(data.end) - Ox.parseDate(data.start);
},
operator: '-',
title: 'Duration',
unique: true,
visible: true,
width: 256
},
{
id: 'user',
operator: '+',
title: 'User',
visible: false,
width: 96
},
{
format: function(value) {
return value.replace('T', ' ').replace('Z', '');
},
id: 'created',
operator: '-',
title: 'Date Created',
visible: false,
width: 128,
},
{
format: function(value) {
return value.replace('T', ' ').replace('Z', '');
},
id: 'modified',
operator: '-',
title: 'Date Modified',
visible: false,
width: 128,
},
{
align: 'right',
id: 'matches',
operator: '-',
title: 'Matches',
visible: false,
width: 64,
}
];
self.$listToolbar = Ox.Bar({
size: 24
});
self.$findElement = Ox.FormElementGroup({
elements: [
self.$findSelect = Ox.Select({
items: [
{id: 'all', title: 'Find: All'},
{id: 'name', title: 'Find: Name'},
{id: 'alternativeNames', title: 'Find: Alternative Names'},
],
overlap: 'right',
type: 'image'
})
.bindEvent({
change: function(data) {
var key = data.value,
value = self.$findInput.value();
value && updateList(key, value);
}
}),
self.$findInput = Ox.Input({
clear: true,
placeholder: 'Find in List',
width: 234
})
.bindEvent({
submit: function(data) {
var key = self.$findSelect.value(),
value = data.value;
updateList(key, value);
}
})
]
})
.css({float: 'right', margin: '4px'})
.bindEvent({
change: function(data) {
}
})
.appendTo(self.$listToolbar);
self.$list = Ox.TextList({
columns: self.columns,
columnsRemovable: true,
columnsVisible: true,
// we have to clone so that when self.options.events changes,
// self.$list.options({items: self.options.events}) still
// registers as a change
items: Ox.clone(self.options.events, true),
pageLength: self.options.pageLength,
scrollbarVisible: true,
sort: self.options.sort
})
.bindEvent({
'delete': removeEvent,
init: initList, // fixme: won't fire from static list
key_0: function() {
self.$calendar.panToEvent();
},
key_equal: function() {
self.$calendar.zoom(1);
},
key_minus: function() {
self.$calendar.zoom(-1);
},
key_shift_0: function() {
self.$calendar.zoomToPlace();
},
load: function() {
that.triggerEvent('loadlist');
},
open: openItem,
select: selectItem
});
self.$listStatusbar = Ox.Bar({
size: 16
});
self.$status = Ox.Element()
.css({paddingTop: '2px', margin: 'auto', fontSize: '9px', textAlign: 'center'})
.html(
Ox.formatNumber(self.options.events.length) + ' Event' + (
self.options.events.length == 1 ? '' : 's'
)
)
.appendTo(self.$listStatusbar);
self.$calendar = Ox.Calendar({
date: new Date(0),
//events: Ox.clone(self.options.events, true),
events: self.options.events.filter(function(event) {
return !!event.start;
}),
height: self.options.height,
showControls: self.options.showControls,
showToolbar: true,
showZoombar: true,
width: self.options.width - 514,
zoom: 4
})
.bindEvent({
resize: function(data) {
// triggered by SplitPanel
self.$calendar.resizeCalendar();
},
select: selectEvent
});
self.$eventTitlebar = Ox.Bar({
size: 24
});
self.$eventTitle = $('<div>')
.hide()
.appendTo(self.$eventTitlebar);
self.$eventName = Ox.Label({
title: '',
width: 228
})
.css({float: 'left', margin: '4px'})
.appendTo(self.$eventTitle)
.bindEvent({
singleclick: function() {
self.$calendar.panToEvent();
},
doubleclick: function() {
self.$calendar.zoomToEvent();
}
});
self.$deselectEventButton = Ox.Button({
title: 'close',
tooltip: 'Done',
type: 'image'
})
.css({float: 'left', margin: '4px 4px 4px 0'})
.bindEvent({
click: function() {
self.$calendar.options({selected: null});
}
})
.appendTo(self.$eventTitle);
self.$eventForm = Ox.Form({
items: [
self.$nameInput = Ox.Input({
id: 'name',
label: 'Name',
labelWidth: 64,
width: 240
}),
self.$alternativeNamesInput = Ox.ArrayInput({
id: 'alternativeNames',
label: 'Alternative Names',
max: 10,
values: [],
width: 240
}),
Ox.Select({
id: 'type',
items: [
{id: 'date', title: 'Date'},
{id: 'place', title: 'Place'},
{id: 'person', title: 'Person'},
{id: 'other', title: 'Other'}
],
label: 'Type',
labelWidth: 64,
width: 240
}),
self.$startInput = Ox.Input({
id: 'start',
label: 'Start',
labelWidth: 64,
width: 240
}),
self.$endInput = Ox.Input({
id: 'end',
label: 'End',
labelWidth: 64,
width: 240
}),
self.$durationInput = Ox.Input({
disabled: true,
id: 'durationText',
label: 'Duration',
labelWidth: 64,
width: 240
})
],
width: 240
})
.css({margin: '8px'})
.hide()
.bindEvent({
change: function(data) {
Ox.Log('Calendar', 'CHANGE', data);
var exists = false, values;
if (['name', 'alternativeNames'].indexOf(data.id) > -1) {
exists = '';
values = data.id == 'name' ? [data.data.value] : data.data.value;
Ox.forEach(self.options.events, function(event) {
Ox.forEach(values, function(value) {
if (
event.name == data.data.value
|| event.alternativeNames.indexOf(data.data.value) > -1
) {
exists = value;
return false;
}
});
return !exists;
});
}
if (data.id == 'name') {
if (!exists) {
// FIXME: can we change this to data.value?
editEvent('name', data.data.value);
} else {
self.$nameInput.addClass('OxError');
}
} else if (data.id == 'alternativeNames') {
if (!exists) {
editEvent('alternativeNames', data.data.value);
} else {
self.$alternativeNamesInput.setErrors([exists]);
}
} else if (data.id == 'type') {
editEvent('type', data.data.value);
} else if (data.id == 'start') {
editEvent('start', data.data.value);
} else if (data.id == 'end') {
editEvent('end', data.data.value);
}
}
});
self.$eventStatusbar = Ox.Bar({
size: 24
});
self.$newEventButton = Ox.Button({
title: 'New Event',
width: 96
})
.css({float: 'left', margin: '4px 2px 4px 4px'})
.bindEvent({
click: addEvent
})
.appendTo(self.$eventStatusbar);
self.$removeEventButton = Ox.Button({
title: 'Remove Event',
width: 96
})
.css({float: 'right', margin: '4px 4px 4px 2px'})
.bindEvent({
click: removeEvent
})
.hide()
.appendTo(self.$eventStatusbar);
that.$element = Ox.SplitPanel({
elements: [
{
collapsible: true,
element: Ox.SplitPanel({
elements: [
{
element: self.$listToolbar,
size: 24
},
{
element: self.$list
},
{
element: self.$listStatusbar,
size: 16
}
],
orientation: 'vertical'
}),
resizable: true,
resize: [256, 384, 512],
size: 256
},
{
element: self.$calendar,
},
{
collapsible: true,
element: Ox.SplitPanel({
elements: [
{
element: self.$eventTitlebar,
size: 24
},
{
element: self.$eventForm
},
{
element: self.$eventStatusbar,
size: 24
}
],
orientation: 'vertical'
})
.bindEvent({
resize: function(data) {
self.$placeTitleName.options({width: data.size - 48});
// fixme: pass width through form
self.$placeFormItems.forEach(function($item) {
$item.options({width: data.size - 16});
});
}
}),
resizable: true,
resize: [204, 256, 384],
size: 256
}
],
orientation: 'horizontal'
});
function addEvent() {
Ox.Log('Calendar', 'ADD', self.$calendar.getBounds())
var bounds = self.$calendar.getBounds(),
middle = +self.$calendar.options('date'),
startTime = +new Date((+bounds.startTime + middle) / 2),
endTime = +new Date((+bounds.endTime + middle) / 2),
event = {},
i = 1;
event.name = 'Untitled';
while (nameExists(event.name)) {
event.name = 'Untitled [' + (++i) + ']';
};
event.alternativeNames = [];
event.type = 'other';
event.start = Ox.formatDate(startTime, '%Y-%m-%d %H:%M:%S', true);
event.end = Ox.formatDate(endTime, '%Y-%m-%d %H:%M:%S', true);
Ox.Log('Calendar', event);
self.options.addEvent(event, function(result) {
if (result.status.code == '200') {
event.id = result.data.id;
self.options.events.push(event);
var time0 = +new Date()
self.$list.options({items: Ox.clone(self.options.events, true)});
Ox.Log('Calendar', 'TIME TO SET LIST OPTIONS:', +new Date() - time0);
self.$calendar.addEvent(event);
selectEvent(event);
self.$nameInput.focusInput(true);
} else {
// FIXME
alert(result.status.text);
}
});
}
function editEvent(key, value) {
var id = self.selectedEvent,
index = Ox.getIndexById(self.options.events, id),
data = {id: id};
data[key] = value;
self.options.editEvent(data, function(result) {
if (result.status.code == 200) {
self.options.events[index][key] = value;
self.$list.value(id, key, value);
self.$calendar.editEvent(id, key, value);
if (key == 'name') {
self.$eventName.options({title: value});
} else if (['start', 'end'].indexOf(key) > -1) {
self.$durationInput.value(
Ox.formatDateRangeDuration(
self.$startInput.value(),
self.$endInput.value()
|| Ox.formatDate(new Date(), '%Y-%m-%d %H%:%M:%S'),
true
)
);
}
} else {
// ...
}
});
}
function initList(data) {
self.$status.html(
Ox.formatNumber(data.items) + ' Event' + (
data.items == 1 ? '' : 's'
)
);
}
function nameExists(name) {
var exists = false;
Ox.forEach(self.options.events, function(event) {
if (
event.name == name
|| event.alternativeNames.indexOf(name) > -1
) {
exists = true;
return false;
}
});
return exists;
}
function openItem(data) {
selectItem(data);
self.$calendar.zoomToEvent(data.ids[0]);
}
function removeEvent() {
var id = self.selectedEvent,
index = Ox.getIndexById(self.options.events, id);
self.options.removeEvent({id: id}, function(result) {
if (result.status.code == '200') {
self.options.events.splice(index, 1);
var time0 = +new Date();
self.$list.options({items: Ox.clone(self.options.events, true)});
Ox.Log('Calendar', 'TIME TO SET LIST OPTIONS:', +new Date() - time0);
self.$calendar.removeEvent();
selectEvent({});
} else {
alert(result.status.text);
}
});
}
function selectEvent(event) {
self.$list.options({
selected: event.id ? [event.id] : []
});
if (event.id) {
var end = event
self.selectedEvent = event.id;
self.$eventName.options({title: event.name});
self.$eventTitle.show();
Ox.print('VALUES:', Ox.extend({}, event, {
end: event.current ? '' : event.end
}))
self.$eventForm.values(Ox.extend({}, event, {
end: event.current ? '' : event.end
})).show();
self.$removeEventButton.show();
} else {
self.selectedEvent = null;
self.$eventTitle.hide();
self.$eventForm.hide();
self.$removeEventButton.hide();
}
}
function selectItem(data) {
var id = data.ids.length ? data.ids[0] : null;
self.$calendar.options({selected: id});
id && self.$calendar.panToEvent();
}
function updateList(key, value) {
var events;
if (value === '') {
events = Ox.clone(self.options.events);
} else {
events = [];
self.options.events.forEach(function(event) {
if ((
['all', 'name'].indexOf(key) > -1
&& event.name.toLowerCase().indexOf(value) > -1
) || (
['all', 'alternativeNames'].indexOf(key) > -1
&& event.alternativeNames.join('\n').toLowerCase().indexOf(value) > -1
)) {
events.push(event)
}
});
}
self.$list.options({items: events});
}
self.setOption = function(key, value) {
if (key == 'height') {
// fixme: should be .resizeList
self.$list.size();
self.$calendar.resizeCalendar();
} else if (key == 'width') {
self.$calendar.resizeCalendar();
}
};
return that;
};