// vim: et:ts=4:sw=4:sts=4:ft=javascript 'use strict'; /*@ Ox.AnnotationFolder AnnotationFolder Object () -> AnnotationFolder Object (options) -> AnnotationFolder Object (options, self) -> AnnotationFolder Object options Options object editable If true, annotations can be added id id items items title title type panel type width self shared private variable @*/ Ox.AnnotationFolder = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ clickLink: null, collapsed: false, editable: false, highlight: '', id: '', 'in': 0, item: '', items: [], out: 0, position: 0, range: 'all', selected: '', showInfo: false, showWidget: false, sort: 'position', title: '', type: 'text', users: 'all', widgetSize: 256, width: 0 }) .options(options || {}); self.annotations = getAnnotations(); self.points = getPoints(); self.position = self.options.position; self.sort = getSort(); self.widget = self.options.type == 'event' ? 'Calendar' : self.options.type == 'place' ? 'Map' : ''; if (self.widget) { self.$defineButton = Ox.Button({ disabled: !self.options.selected, id: 'define', style: 'symbol', title: 'click', tooltip: 'Define ' + Ox.toTitleCase(self.options.type), type: 'image' }) .bindEvent({ click: function() { var item = Ox.getObjectById(self.options.items, self.options.selected); that.triggerEvent('define', item.place ? { id: item.place.id } : { name: item.value }); } }); } self.$addButton = Ox.Button({ id: 'add', style: 'symbol', title: 'add', tooltip: 'Add ' + self.options.item, type: 'image' }) .bindEvent({ click: function() { that.triggerEvent('add', {value: ''}); } }); self.$infoButton = Ox.Button({ style: 'symbol', title: 'info', type: 'image' }) .bindEvent({ click: function() { that.triggerEvent('info'); } }); self.$panel = Ox.CollapsePanel({ collapsed: self.options.collapsed, extras: Ox.merge( self.widget ? [self.$defineButton] : [], [self.options.editable ? self.$addButton : self.$infoButton] ), size: 16, title: self.options.title }) .addClass('OxAnnotationFolder') .bindEvent({ toggle: toggleLayer }); that.setElement(self.$panel); that.$content = that.$element.$content; if (self.widget) { self.widgetSize = self.options.showWidget * self.options.widgetSize; self.$outer = Ox.Element() .css({ display: 'table-cell', width: self.options.width + 'px' }) .appendTo(that.$content); self.$inner = Ox.Element() .css({ height: self.widgetSize + 'px', overflow: 'hidden' }) .appendTo(self.$outer); if (options.type == 'event') { self.$widget = self.$calendar = Ox.Calendar({ events: getEvents(), height: self.widgetSize, showZoombar: true, width: self.options.width, zoomOnlyWhenFocused: true }) .css({ width: self.options.width + 'px', height: self.widgetSize + 'px' }) .bindEvent({ select: function(data) { if ( !data.id && self.options.selected && isDefined(Ox.getObjectById(self.options.items, self.options.selected)) ) { // only deselect annotation if the event deselect was not // caused by switching to an annotation without event self.$annotations.options({selected: ''}); } else if ( data.annotationIds && data.annotationIds.indexOf(self.options.selected) == -1 ) { // only select a new annotation if the currently selected // annotation does not match the selected event self.$annotations.options({selected: data.annotationIds[0]}); } } }) .appendTo(self.$inner); } else { // place self.$widget = self.$map = Ox.Map({ places: getPlaces(), showTypes: true, // FIXME: should be showZoombar zoombar: true, zoomOnlyWhenFocused: true // showLabels: true }) .css({ width: self.options.width + 'px', height: self.widgetSize + 'px' }) .bindEvent({ // FIXME: should be select, not selectplace selectplace: function(data) { if ( !data.id && self.options.selected && isDefined(Ox.getObjectById(self.options.items, self.options.selected)) ) { // only deselect annotation if the place deselect was not // caused by switching to an annotation without place self.$annotations.options({selected: ''}); } else if ( data.annotationIds && data.annotationIds.indexOf(self.options.selected) == -1 ) { // only select a new annotation if the currently selected // annotation does not match the selected place self.$annotations.options({selected: data.annotationIds[0]}); } } }) .appendTo(self.$inner); } self.$resizebar = Ox.Element({ tooltip: 'Drag to resize or click to toggle map' // fixme: update as w/ splitpanels }) .addClass('OxResizebar OxHorizontal') .css({ position: 'absolute', top: self.widgetSize + 'px', cursor: 'ns-resize' }) .append($('
').addClass('OxSpace')) .append($('
').addClass('OxLine')) .append($('
').addClass('OxSpace')) .bindEvent({ anyclick: toggleWidget, dragstart: dragstart, drag: drag, dragend: dragend }) .appendTo(self.$outer); } self.$annotations = Ox.ArrayEditable({ clickLink: self.options.clickLink, editable: self.options.editable, highlight: self.options.highlight, items: self.annotations, placeholder: 'No ' + self.options.title, selected: self.options.selected, separator: ';', sort: self.sort, submitOnBlur: false, tooltipText: self.options.showInfo ? '{user}, {date}' : '', width: self.options.width, maxHeight: self.options.type == 'text' ? Infinity : void 0, type: self.options.type == 'text' ? 'textarea' : 'input' }) //.css({marginTop: '256px'}) .bindEvent({ add: function(data) { if (self.editing) { // FIXME: changed value will not be saved! } that.triggerEvent('add', {value: data.value || ''}); }, blur: function() { // the video editor will, if it has not received focus, // call blurItem that.triggerEvent('blur'); }, change: changeAnnotation, 'delete': removeAnnotation, edit: function() { self.editing = true; that.triggerEvent('edit'); }, select: selectAnnotation, selectafter: function() { that.triggerEvent('selectafter'); }, selectbefore: function() { that.triggerEvent('selectbefore'); }, selectnone: function() { that.triggerEvent('selectnone'); }, submit: submitAnnotation }) .appendTo( ['event', 'place'].indexOf(self.options.type) > -1 ? self.$outer : that.$content ); [ '0', 'b', 'backslash', 'closebracket', 'comma', 'dot', 'equal', 'f', 'g', 'h', 'i', 'minus', 'n', 'o', 'openbracket', 'p', 'shift_0', 'shift_g', 'shift_i', 'shift_o', 'slash', 'space' ].forEach(function(key) { key = 'key_' + key; self.$annotations.bindEvent(key, function() { that.triggerEvent(key); }); }); self.options.selected && setTimeout(function() { // need timeout in order to trigger events if (self.options.collapsed) { self.$panel.options({collapsed: false}); } selectAnnotation({id: self.options.selected}); }, 0); showWarnings(); function changeAnnotation(data) { var item = Ox.getObjectById(self.options.items, data.id); item.value = data.value; that.triggerEvent('change', item); } function crossesPoint() { var sort = [self.position, self.options.position].sort(function(a, b) { return a - b }), positionA = sort[0], positionB = sort[1]; return self.points.some(function(point) { return point >= positionA && point <= positionB; }); } function dragstart() { if (self.options.showWidget) { self.drag = { startSize: self.options.widgetSize }; } } function drag(e) { if (self.options.showWidget) { self.options.widgetSize = Ox.limit( self.drag.startSize + e.clientDY, 128, 384 ); if (self.options.widgetSize >= 248 && self.options.widgetSize <= 264) { self.options.widgetSize = 256; } self.$resizebar.css({top: self.options.widgetSize + 'px'}); self.$inner.css({height: self.options.widgetSize + 'px'}); self.$widget.options({height: self.options.widgetSize}); } } function dragend(e) { if (self.options.showWidget) { self.options.type == 'event' ? self.$calendar.resizeCalendar() : self.$map.resizeMap(); that.triggerEvent('resizewidget', {size: self.options.widgetSize}); } } function getAnnotations() { return Ox.map(self.options.items, function(item) { return self.editing && item.id == self.options.selected || ( ( self.options.range == 'all' || ( self.options.range == 'selection' && item['in'] <= self.options.out && item.out >= self.options['in'] ) || ( self.options.range == 'position' && item['in'] <= self.options.position && item.out >= self.options.position ) ) && ( self.options.users == 'all' || self.options.users.indexOf(item.user) > -1 ) ) ? Ox.extend({ date: Ox.formatDate(item.modified.substr(0, 10), '%B %e, %Y') }, item) : null; }); } function getEvents() { var events = []; self.annotations.filter(function(item) { return isDefined(item); }).forEach(function(item) { var index = Ox.getIndexById(events, item.event.id); if (index == -1) { events.push(Ox.extend({ annotationIds: [item.id] }, item.event)) } else { events[index].annotationIds.push(item.id); } }); return events; } function getPlaces() { var places = []; self.annotations.filter(function(item) { return isDefined(item); }).forEach(function(item) { var index = Ox.getIndexById(places, item.place.id); if (index == -1) { places.push(Ox.extend({ annotationIds: [item.id] }, item.place)); } else { places[index].annotationIds.push(item.id); } }); return places; } function getPoints() { return Ox.unique(Ox.flatten( self.options.items.map(function(item) { return [item['in'], item.out]; }) )); } function getSort() { return ({ duration: ['+duration', '+in', '+value'], position: ['+in', '+duration', '+value'], text: ['+value', '+in', '+duration'] })[self.options.sort]; } function isDefined(item) { return !!item[self.options.type] && !!item[self.options.type].type; } function removeAnnotation(data) { var item; self.editing = false; if (self.widget) { item = Ox.getObjectById(self.options.items, data.id); if (isDefined(item)) { if (self.options.type == 'event') { self.$calendar.removeEvent(data.id); } else { self.$map.removePlace(data.id) .options({selected: ''}) // deselect resultPlace .triggerEvent('key_escape'); // remove resultPlace } } } that.triggerEvent('remove', {id: data.id}); } function selectAnnotation(data) { var item = Ox.getObjectById(self.options.items, data.id); self.options.selected = item ? data.id : ''; if (self.widget) { if (self.options.type == 'event') { self.$calendar.options({ selected: item && isDefined(item) ? item.event.id : '' }) .panToEvent(); } else { self.$map.options({ selected: item && isDefined(item) ? item.place.id : '' }) .panToPlace(); } } that.triggerEvent('select', Ox.extend(data, item ? { 'in': item['in'], out: item.out, layer: self.options.id } : {})); } function showWarnings() { if (self.widget && self.options.items.length) { self.$annotations.find('.OxEditableElement').each(function() { var $element = $(this); // We don't want to catch an eventual placeholder, // which is an EditableElement without .data('id') if ( $element.data('id') && !isDefined( Ox.getObjectById(self.options.items, $element.data('id')) ) ) { $element.addClass('OxWarning'); } }); } } function submitAnnotation(data) { var item = Ox.getObjectById(self.options.items, data.id); item.value = data.value; self.editing = false; //self.$annotations.options({items: self.options.items}); self.options.sort == 'text' && self.$annotations.reloadItems(); that.triggerEvent('submit', item); } function toggleLayer() { self.options.collapsed = !self.options.collapsed; if ( !self.options.collapsed && self.options.type == 'place' && self.options.showWidget ) { self.$map.resizeMap(); } if (self.options.collapsed) { self.editing && that.blurItem(); self.$annotations.loseFocus(); } that.triggerEvent('togglelayer', {collapsed: self.options.collapsed}); } function toggleWidget() { self.options.showWidget = !self.options.showWidget; self.widgetSize = self.options.showWidget * self.options.widgetSize; self.$resizebar.animate({top: self.widgetSize + 'px'}, 250); self.$inner.animate({height: self.widgetSize + 'px'}, 250); self.$widget.animate({height: self.widgetSize + 'px'}, 250, function() { self.$widget.options({height: self.widgetSize}); }); that.triggerEvent('togglewidget', {collapsed: !self.options.showWidget}); } function updateAnnotations() { self.annotations = getAnnotations(); self.$annotations.options({items: self.annotations}); showWarnings(); if (self.widget) { self.options.type == 'event' ? self.$calendar.options({events: getEvents()}) : self.$map.options({places: getPlaces()}); } } self.setOption = function(key, value) { if (key == 'highlight') { self.$annotations.options({highlight: value}); } if (['in', 'out'].indexOf(key) > -1 && self.editing) { var item = Ox.getObjectById(self.options.items, self.options.selected); item[key] = value; item.duration = self.options.out - self.options['in']; self.points = getPoints(); } if (key == 'in') { //fixme: array editable should support item updates while editing self.options.range == 'selection' && updateAnnotations(); } else if (key == 'out') { self.options.range == 'selection' && updateAnnotations(); } else if (key == 'position') { if (self.options.range == 'position') { crossesPoint() && updateAnnotations(); self.position = self.options.position; } } else if (key == 'range') { updateAnnotations(); } else if (key == 'selected') { if (value === '') { self.editing = false; } if (value && self.options.collapsed) { self.$panel.options({collapsed: false}); } self.$annotations.options({selected: value}); } else if (key == 'sort') { self.sort = getSort(); self.$annotations.options({sort: self.sort}); showWarnings(); } else if (key == 'users') { updateAnnotations(); } else if (key == 'width') { Ox.print('RESIZE!!!!') if (self.widget) { self.$outer.options({width: self.options.width}); self.$inner.options({width: self.options.width}); self.$widget.options({width: self.options.width}); } self.$annotations.options({ width: self.options.type == 'text' ? self.options.width + 8 : self.options.width }); } }; /*@ addItem addItem @*/ that.addItem = function(item) { Ox.Log('AF', 'FOLDER ADD ITEM', item) var pos = 0; self.options.items.splice(pos, 0, item); self.$panel.options({collapsed: false}); self.$annotations .addItem(pos, item) .options({selected: item.id}) .editItem(); showWarnings(); //selectAnnotation(item); //that.triggerEvent('edit'); return that; }; that.blurItem = function() { self.editing = false; self.$annotations.blurItem(); return that; }; that.editItem = function() { self.editing = true; self.$panel.options({collapsed: false}); self.$annotations.editItem(); return that; }; that.selectItem = function(position) { // selects the first (0) or last (-1) visible annotation if (self.annotations.length) { that.options({selected: self.annotations[ position == 0 ? 0 : self.annotations.length - 1 ].id}); self.$annotations.gainFocus(); } else { that.triggerEvent( position == 0 ? 'selectafter' : 'selectbefore' ); } }; /*@ removeItems removeItems @*/ /* that.removeItem = function(id) { var pos = Ox.getIndexById(self.options.items, id); self.options.items.splice(pos, 1); self.$annotations.removeItems && self.$annotations.removeItems([id]); }; */ that.updateItem = function(id, data) { var item = Ox.getObjectById(self.options.items, id); Ox.forEach(data, function(value, key) { item[key] = value; }); self.options.selected = item.id; self.$annotations.options({selected: self.options.selected}); if (self.widget) { self.options.type == 'event' ? self.$calendar.options({events: getEvents()}) : self.$map.options({places: getPlaces()}); // if updating has made the item match // an event or place, then select it isDefined(item) && self.$widget.options({ selected: item[self.options.type].id }); } return that; }; return that; };