// vim: et:ts=4:sw=4:sts=4:ft=javascript /*@ Ox.ListCalendar 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: [], sort: [{key: 'name', operator: '+'}], width: 256 }) .options(options || {}) .addClass('OxListCalendar') .css({ width: self.options.width + 'px', height: self.options.height + 'px' }); self.durationCache = {}; Ox.print('EVENT[0]', self.options.events[0]) 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] = 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.selected[0].id, 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), height: self.options.height, width: self.options.width - 514, zoom: 4 }) .bindEvent({ resize: function(data) { // triggered by SplitPanel $element.resizeCalendar(); }, select: selectEvent }); self.$eventTitlebar = Ox.Bar({ size: 24 }); self.$eventTitle = $('
') .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.print('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.selected[0].id); } 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.print('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.print(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.print('TIME TO SET LIST OPTIONS:', +new Date() - time0); self.$calendar.addEvent(event); selectEvent(event); self.$nameInput.focusInput(); } else { alert(result.status.text); } }); } function editEvent(key, value) { var id = self.selectedEvent, index = Ox.getPositionById(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); if (['name', 'type', 'start', 'end'].indexOf(key) > -1) { self.$calendar.editEvent(id, key, value); } if (key == 'name') { self.$eventName.options({title: value}); } else if (['start', 'end'].indexOf(key) > -1) { self.$durationInput.options({ value: Ox.formatDateRangeDuration( self.$startInput.options('value'), self.$endInput.options('value') || Ox.formatDate(new Date(), '%Y-%m-%d %H%:%M:%S'), true ) }); } } else { alert(result.status.text); } }); } 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.getPositionById(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.print('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(); 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(); } else if (key == 'width') { self.$calendar.resizeCalendar(); } }; return that; };