'use strict'; /*@ Ox.AnnotationFolder AnnotationFolder Object ([options[, self]]) -> AnnotationFolder Object add add blur blur change change edit edit info info insert insert key_* key_* open open remove remove selectafter selectafter selectbefore selectbefore selectnone selectnone select select submit submit togglelayer togglelayer togglewidget togglewidget 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 || {}) .update(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({animate: false}); self.$panel.options({collapsed: false}); self.$panel.options({animate: true}); } 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') { 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 }); } }); if (self.options.selected) { self.options.collapsed = false; } 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' : ''; 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: [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, getSortValue: self.options.type == 'text' ? function(value) { return Ox.stripTags(value); } : null, 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 ? function(item) { return Ox.encodeHTMLEntities(item.user) + ', ' + Ox.formatDate(item.modified.slice(0, 10), '%B %e, %Y'); } : '', 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'); }, insert: function(data) { that.triggerEvent('insert', data); }, open: function() { that.triggerEvent('open'); }, 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', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'b', 'backslash', 'closebracket', 'comma', 'dot', 'equal', 'f', 'g', 'h', 'i', 'minus', 'n', 'o', 'openbracket', 'p', 'shift_0', 'shift_equal', 'shift_g', 'shift_i', 'shift_minus', '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 positions = [self.position, self.options.position].sort(); return self.points.some(function(point) { return point >= positions[0] && point <= positions[1]; }); } 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.filter(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 ) ); }); } 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.options({selected: ''}) .options({events: getEvents()}); } else { self.$map.options({selected: ''}) .options({places: getPlaces()}); } } } showWarnings(); 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'); } else { $element.removeClass('OxWarning'); } }); } } function submitAnnotation(data) { var item = Ox.getObjectById(self.options.items, data.id); item.value = data.value; self.editing = false; 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()}); } } /*@ addItem addItem @*/ that.addItem = function(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(); self.points = getPoints(); //selectAnnotation(item); //that.triggerEvent('edit'); return that; }; /*@ blurItem blur item () -> blur selected item @*/ that.blurItem = function() { self.editing = false; self.$annotations.blurItem(); return that; }; /*@ editItem edit item () -> edit selected item @*/ that.editItem = function() { self.editing = true; self.$panel.options({collapsed: false}); self.$annotations.editItem(); return that; }; /*@ gainFocus gain focus () -> gain focus @*/ that.gainFocus = function() { self.$annotations.gainFocus(); return that; }; /*@ removeItem remove item () -> remove selected item @*/ that.removeItem = function() { self.$annotations.removeItem(); }; /*@ selectItem select item (position) -> select item at position @*/ 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' ); } }; /*@ updateItem update item (id, data) -> update item @*/ that.updateItem = function(id, data) { var item = Ox.getObjectById(self.options.items, id); Ox.forEach(data, function(value, key) { item[key] = value; }); if (id != item.id) { self.$annotations.find('.OxEditableElement').each(function() { var $element = $(this); if ($element.data('id') == self.options.selected) { $element.data({id: item.id}); } }); self.options.selected = item.id; } if (self.$widget) { // update may have made the item match, // or no longer match, an event or place if (isDefined(item)) { self.$widget.options( self.options.type == 'event' ? {events: getEvents()} : {places: getPlaces()} ) .options({ selected: item[self.options.type].id }); self.$widget[ self.options.type == 'event' ? 'panToEvent' : 'panToPlace' ](); } else { self.$widget.options({ selected: '' }) .options( self.options.type == 'event' ? {events: getEvents()} : {places: getPlaces()} ); } } if (id != item.id) { self.$annotations.options({selected: self.options.selected}); } showWarnings(); return that; }; return that; };