jQuery(function($){ /***************************************************************************** ** NGG DEFINITION ***/ /** Setup a namespace for NextGEN-offered Backbone components **/ var Ngg = { Models: {}, Views: {} }; /***************************************************************************** ** NGG MODELS ***/ /** * Ngg.Models.SelectableItems * A collection of items that can be selectable. Commonly used with the * Ngg.Views.SelectTag widget (view) **/ Ngg.Models.SelectableItems = Backbone.Collection.extend({ selected: function(){ return this.filter(function(item){ return item.get('selected') == true; }); }, deselect_all: function(){ this.each(function(item){ item.set('selected', false); }); }, selected_ids: function(){ return _.pluck(this.selected(), 'id'); }, select: function(ids){ if (!_.isArray(ids)) ids = [ids]; this.each(function(item){ if (_.indexOf(ids, item.id) >= 0) { item.set('selected', true); } }); this.trigger('selected'); } }); /***************************************************************************** ** NGG VIEWS ***/ /** * Ngg.Views.SelectTag * Used to render a Select tag (drop-down list) **/ Ngg.Views.SelectTag = Backbone.View.extend({ tagName: 'select', collection: null, multiple: false, value_field: 'id', text_field: 'title', initialize: function(){ _.each(this.options, function(value, key){ this[key] = value; }, this); this.collection.on('add', this.render_new_option, this); this.collection.on('remove', this.remove_existing_option, this); this.collection.on('reset', this.empty_list, this); }, events: { 'change': 'selection_changed' }, empty_list: function(){ this.$el.empty(); }, render_new_option: function(item){ this.$el.append(new this.Option({ model: item, value_field: this.value_field, text_field: this.text_field }).render().el); }, remove_existing_option: function(item){ this.$el.find("option[value='"+item.id+"']").remove(); }, /** * After a selection has changed, set the 'selected' property for each item in the * collection * @triggers 'selected' **/ selection_changed: function(){ // Get selected options from DOM var selections = _.map(this.$el.find(':selected'), function(element){ return $(element).val(); }); // Set the 'selected' attribute for each item in the collection this.collection.each(function(item){ if (_.indexOf(selections, item.id) >= 0 || _.indexOf(selections, item.id.toString()) >= 0) item.set('selected', true); else item.set('selected', false); }); this.collection.trigger('selected'); }, render: function(){ this.$el.empty(); if (this.options.include_blank) { this.$el.append(""); } this.collection.each(function(item){ var option = new this.Option({ model: item, value_field: this.value_field, text_field: this.text_field }); this.$el.append(option.render().el); }, this); if (this.multiple) this.$el.prop('multiple', true); if (this.width) this.$el.width(this.width); return this; }, /** * Represents an option in the Select drop-down **/ Option: Backbone.View.extend({ tagName: 'option', model: null, initialize: function(){ _.each(this.options, function(value, key){ this[key] = value; }, this); this.model.on('change', this.render, this); }, render: function(){ var self = this; this.$el.html(this.model.get(this.text_field).replace(/\\&/g, '&').replace(/\\'/g, "'")); this.$el.prop({ value: this.value_field == 'id' ? this.model.id : this.model.get(this.value_field), }); if (self.model.get('selected') == true) { this.$el.attr('selected', 'selected'); } return this; } }) }); Ngg.Views.Chosen = Backbone.View.extend({ tagName: 'span', initialize: function(){ this.collection = this.options.collection; if (!this.options.multiple) this.options.include_blank = true; this.select_tag = new Ngg.Views.SelectTag(this.options); this.collection.on('change', this.selection_changed, this); }, selection_changed: function(e){ if (_.isUndefined(e.changed['selected'])) this.render(); }, render: function(){ this.$el.append(this.select_tag.render().$el); if (this.options.width) this.select_tag.$el.width(this.options.width); // Configure select2 options this.select2_opts = { placeholder: this.options.placeholder }; // Create the select2 drop-down if (this.$el.parent().length == 0) { $('body').append(this.$el); this.select_tag.$el.select2(this.select2_opts); var container = this.select_tag.$el.select2('container').detach(); this.$el.append(container); this.$el.detach(); } else this.select_tag.$el.select2(this.select2_opts); // Hack for multi-select elements if (this.options.multiple && this.collection.selected().length == 0) this.select_tag.$el.select2('val', ''); // For IE, ensure that the text field has a width this.$el.find('.select2-input').width(this.options.width-20); // For IE8, ensure that the selection is being displayed if (!this.options.multiple) { var selected_value = this.$el.find('.select2-choice span:first'); if (selected_value.text().length == 0 && this.collection.selected().length > 0) { var selected_item = this.collection.selected().pop(); selected_value.text(selected_item.get(this.select_tag.text_field)); } } else { var selected_values = this.$el.find('.select2-search-choice'); if (this.collection.selected().length > 0 && selected_values.length == 0) { this.select_tag.$el.select2('val', ''); var data = []; var value_field = this.select_tag.value_field; _.each(this.collection.selected(), function(item){ var value = value_field == 'id' ? item.id : item.get(value_field); data.push({ id: value, text: item.get(this.select_tag.text_field) }); }, this); this.select_tag.$el.select2('data', data); } } return this; } }); /***************************************************************************** ** DISPLAY TAB DEFINITION ***/ /** * Setup a namespace **/ Ngg.DisplayTab = { Models: {}, Views: {}, App: {} }; /***************************************************************************** * MODEL CLASSES **/ /** * A collection that can fetch it's entities from the server **/ Ngg.Models.Remote_Collection = Ngg.Models.SelectableItems.extend({ fetch_limit: 5000, in_progress: false, fetch_url: photocrati_ajax.url, action: '', extra_data: {}, _create_request: function(limit, offset) { var request = ; request = _.extend(request, { action: this.action, limit: limit ? limit : this.fetch_limit, offset: offset ? offset : 0 }); for (var index in this.extra_data) { var value = this.extra_data[index]; if (typeof(request[index]) == 'undefined') { request[index] = {}; } if (typeof(value['toJSON']) != 'undefined') { value = value.toJSON(); } request[index] = _.extend(request[index], value); } return request; }, _add_item: function(item) { this.push(item); }, fetch: function(limit, offset){ // Request the entities from the server var self = this; this.in_progress = true; $.post(this.fetch_url, this._create_request(limit, offset), function(response){ if (!_.isObject(response)) response = JSON.parse(response); if (response.items) { _.each(response.items, function(item){ self._add_item(item); }); // Continue fetching ? if (response.total >= response.limit+response.offset) { self.fetch(response.limit, response.offset+response.limit); } else { self.in_progress = false; self.trigger('finished_fetching'); } } }); } }); /** * Ngg.DisplayTab.Models.Displayed_Gallery * Represents the displayed gallery being edited or created by the Display Tab **/ Ngg.DisplayTab.Models.Displayed_Gallery = Backbone.Model.extend({ defaults: { source: null, container_ids: [], entity_ids: [], display_type: null, display_settings: {}, exclusions: [], sortorder: [], slug: null } }); /** * Ngg.DisplayTab.Models.Source * Represents an individual source used to collect displayable entities from **/ Ngg.DisplayTab.Models.Source = Backbone.Model.extend({ idAttribute: 'name', defaults: { title: '', name: '', selected: false } }); /** * Ngg.DisplayTab.Models.Source_Collection * Used as a collection of all the available sources for entities **/ Ngg.DisplayTab.Models.Source_Collection = Ngg.Models.SelectableItems.extend({ model: Ngg.DisplayTab.Models.Source, selected_value: function(){ var retval = null; var selected = this.selected(); if (selected.length > 0) { retval = selected[0].get('name'); } return retval; } }); /** * Ngg.DisplayTab.Models.Gallery * Represents an individual gallery entity **/ Ngg.DisplayTab.Models.Gallery = Backbone.Model.extend({ idAttribute: '', defaults: { title: '', name: '' } }); /** * Ngg.DisplayTab.Models.Gallery_Collection * Collection of gallery objects **/ Ngg.DisplayTab.Models.Gallery_Collection = Ngg.Models.Remote_Collection.extend({ model: Ngg.DisplayTab.Models.Gallery, action: 'get_existing_galleries' }); /** * Ngg.DisplayTab.Models.Album * Represents an individual Album object **/ Ngg.DisplayTab.Models.Album = Backbone.Model.extend({ defaults: { title: '', name: '' } }); /** * Ngg.DisplayTab.Models.Album_Collection * Used as a collection of album objects **/ Ngg.DisplayTab.Models.Album_Collection = Ngg.Models.Remote_Collection.extend({ model: Ngg.DisplayTab.Models.Album, action: 'get_existing_albums' }); /** * Ngg.DisplayTab.Models.Tag * Represents an individual tag object **/ Ngg.DisplayTab.Models.Tag = Backbone.Model.extend({ defaults: { title: '' } }); /** * Ngg.DisplayTab.Models.Tag_Collection * Represents a collection of tag objects **/ Ngg.DisplayTab.Models.Tag_Collection = Ngg.Models.Remote_Collection.extend({ model: Ngg.DisplayTab.Models.Tag, /* selected_ids: function(){ return this.selected().map(function(item){ return item.get('name'); }); }, */ action: 'get_existing_image_tags' }); /** * Ngg.DisplayTab.Models.Display_Type * Represents an individual display type **/ Ngg.DisplayTab.Models.Display_Type = Backbone.Model.extend({ idAttribute: 'name', defaults: { title: '' }, is_compatible_with_source: function(source){ var success = true; for (index in source.get('returns')) { var returned_entity_type = source.get('returns')[index]; if (_.indexOf(this.get('entity_types'), returned_entity_type) < 0) { success = false; break; } } return success; } }); /** * Ngg.DisplayTab.Models.Display_Type_Collection * Represents a collection of display type objects **/ Ngg.DisplayTab.Models.Display_Type_Collection = Ngg.Models.SelectableItems.extend({ model: Ngg.DisplayTab.Models.Display_Type, selected_value: function(){ var retval = null; var selected = this.selected(); if (selected.length > 0) { return selected[0].get('name'); } return retval; } }); /** * Ngg.DisplayTab.Models.Entity * Represents an entity to display on the front-end **/ Ngg.DisplayTab.Models.Entity = Backbone.Model.extend({ entity_id: function(){ return this.get(this.get('id_field')); }, is_excluded: function() { current_value = this.get('exclude'); if (_.isUndefined(current_value)) return false; else if (_.isBoolean(current_value)) return current_value; else return parseInt(current_value) == 0 ? false : true; }, is_included: function(){ return !this.is_excluded(); }, is_gallery: function(){ retval = false; if (this.get('is_gallery') == true) retval = true; return retval; }, is_album: function(){ retval = false; if (this.get('is_album') == true) retval = true; return retval; }, is_image: function(){ return !this.is_album() && !this.is_gallery(); }, alttext: function(){ if (this.is_image()) { return this.get('alttext'); } else if (this.is_gallery()) { return this.get('title'); } else if (this.is_album()) { return this.get('name'); } } }); /** * Ngg.DisplayTab.Models.Entity_Collection * Represents a collection of entities **/ Ngg.DisplayTab.Models.Entity_Collection = Ngg.Models.Remote_Collection.extend({ model: Ngg.DisplayTab.Models.Entity, action: 'get_displayed_gallery_entities', _add_item: function(item){ item.exclude = parseInt(item.exclude) == 1 ? true : false; item.is_gallery = parseInt(item.is_gallery) == 1 ? true : false; item.is_album = parseInt(item.is_album) == 1 ? true : false; this.push(item); }, entity_ids: function(){ return this.map(function(item){ return item.entity_id(); }); }, included_ids: function(){ return _.compact(this.map(function(item){ if (item.is_included()) return item.entity_id(); })); }, excluded_ids: function() { return _.compact(this.map(function(item) { if (!item.is_included()) { return item.entity_id(); } })); } }); Ngg.DisplayTab.Models.SortOrder = Backbone.Model.extend({ }); Ngg.DisplayTab.Models.SortOrder_Options = Ngg.Models.SelectableItems.extend({ model: Ngg.DisplayTab.Models.SortOrder }); Ngg.DisplayTab.Models.SortDirection = Backbone.Model.extend({ }); Ngg.DisplayTab.Models.SortDirection_Options = Backbone.Collection.extend({ model: Ngg.DisplayTab.Models.SortDirection }); Ngg.DisplayTab.Models.Slug = Backbone.Model.extend({}); /***************************************************************************** * VIEW CLASSES **/ /** * Ngg.DisplayTab.Views.Source_Config * Used to populate the source configuration tab **/ Ngg.DisplayTab.Views.Source_Config = Backbone.View.extend({ el: '#source_configuration', selected_view: null, /** * Bind to the "sources" collection to know when a selection has been made * and determine what sub-view to render **/ initialize: function(){ this.sources = Ngg.DisplayTab.instance.sources; this.sources.on('selected', this.render, this); _.bindAll(this, 'render'); this.render(); }, render: function(){ var chosen = new Ngg.Views.Chosen({ id: 'source_select', collection: this.sources, placeholder: 'Select a source', width: 500 }); this.$el.html('
No entities to display for this source.
"); }, render: function(){ this.$el.empty(); if (this.entities.length > 0 && this.displayed_gallery.get('container_ids').length > 0) { // Render header rows this.$el.append(new this.RefreshButton({ entities: this.entities }).render().el); this.$el.append(new this.SortButtons({ entities: this.entities, displayed_gallery: this.displayed_gallery, sources: this.sources }).render().el); this.$el.append(new this.ExcludeButtons({ entities: this.entities }).render().el); this.$el.append(this.entity_list); // Activate jQuery Sortable for the entity list this.entity_list.sortable({ placeholder: 'placeholder', forcePlaceholderSize: true, containment: 'parent', opacity: 0.7, revert: true, dropOnEmpty: true, start: function(e, ui){ ui.placeholder.css({ height: ui.item.height() }); return true; }, stop: function(e, ui) { ui.item.trigger('drop', ui.item.index()); } }); this.entity_list.disableSelection(); } else { this.render_no_images_notice(); } return this; }, RefreshButton: Backbone.View.extend({ className: 'refresh_button', tagName: 'input', label: 'Refresh', events: { click: 'clicked' }, clicked: function(){ this.entities.reset(); }, initialize: function(){ _.each(this.options, function(value, key){ this[key] = value; }, this); }, render: function(){ this.$el.attr({ value: this.label, type: 'button' }); return this; } }), ExcludeButtons: Backbone.View.extend({ className: 'header_row', initialize: function(){ _.each(this.options, function(value, key){ this[key] = value; }, this); }, render: function(){ this.$el.empty(); this.$el.append('Exclude:'); var all_button = new this.Button({ value: true, text: 'All', entities: this.entities }); this.$el.append(all_button.render().el); this.$el.append('|'); var none_button = new this.Button({ value: false, text: 'None', entities: this.entities }); this.$el.append(none_button.render().el); return this; }, Button: Backbone.View.extend({ tagName: 'a', value: 1, text: '', events: { click: 'clicked' }, initialize: function(){ _.each(this.options, function(value, key){ this[key] = value; }, this); }, clicked: function(e){ e.preventDefault(); this.entities.each(function(item){ item.set('exclude', this.value); }, this); }, render: function(){ this.$el.text(this.text).attr('href', '#'); return this; } }) }), SortButtons: Backbone.View.extend({ className: 'header_row', initialize: function(){ _.each(this.options, function(value, key){ this[key] = value; }, this); this.sortorder_options = new Ngg.DisplayTab.Models.SortOrder_Options(); this.sortorder_options.on('change:selected', this.sortoption_changed, this); // Create sort directions and listen for selection changes this.sortdirection_options = new Ngg.DisplayTab.Models.SortDirection_Options([ { value: 'ASC', title: 'Ascending', selected: this.displayed_gallery.get('order_direction') == 'ASC' }, { value: 'DESC', title: 'Descending', selected: this.displayed_gallery.get('order_direction') == 'DESC' } ]); this.sortdirection_options.on('change:selected', this.sortdirection_changed, this); this.displayed_gallery.on('change:order_by', this.displayed_gallery_order_changed, this); this.displayed_gallery.on('change.order_direction', this.displayed_gallery_order_dir_changed, this); }, populate_sorting_fields: function(){ // We display difference sorting buttons depending on what type of entities we're dealing with. var entity_types = this.sources.selected().pop().get('returns'); if (_.indexOf(entity_types, 'image') !== -1) { this.fill_image_sortorder_options(); } else { this.fill_gallery_sortorder_options(); } }, create_sortorder_option: function(name, title){ return new Ngg.DisplayTab.Models.SortOrder({ name: name, title: title, value: name, selected: this.displayed_gallery.get('order_by') == name }); }, fill_image_sortorder_options: function(){ this.sortorder_options.reset(); this.sortorder_options.push(this.create_sortorder_option('', 'None')); this.sortorder_options.push(this.create_sortorder_option('sortorder', 'Custom')); this.sortorder_options.push(this.create_sortorder_option(Ngg.DisplayTab.instance.image_key, 'Image ID')); this.sortorder_options.push(this.create_sortorder_option('filename', 'Filename')); this.sortorder_options.push(this.create_sortorder_option('alttext', 'Alt/Title Text')); this.sortorder_options.push(this.create_sortorder_option('imagedate', 'Date/Time')); }, fill_gallery_sortorder_options: function(){ this.sortorder_options.reset(); this.sortorder_options.push(this.create_sortorder_option('', 'None')); this.sortorder_options.push(this.create_sortorder_option('sortorder' ,'Custom')); this.sortorder_options.push(this.create_sortorder_option('name', 'Name')); this.sortorder_options.push(this.create_sortorder_option('galdesc', 'Description')); }, displayed_gallery_order_changed: function(e){ this.sortorder_options.findWhere({value: e.get('order_by')}).set('selected', true); }, displayed_gallery_order_dir_changed: function(e){ this.sortdirection_options.findWhere({value: e.get('order_direction')}).set('selected', true); }, sortoption_changed: function(model){ this.sortorder_options.each(function(item){ item.set('selected', model.get('value') == item.get('value') ? true : false, {silent: true}); }); this.displayed_gallery.set('sortorder', []); var sort_by = model.get('value'); // If "None" was selected, then clear the "sortorder" property if (model.get('value').length == 0) { sort_by = 'sortorder'; } // Change the "sort by" parameter this.displayed_gallery.set('order_by', sort_by); this.entities.reset(); this.$el.find('a.sortorder').each(function(){ var $item = $(this); if ($item.attr('value') == model.get('value')) $item.addClass('selected'); else $item.removeClass('selected'); }); }, sortdirection_changed: function(model){ this.sortdirection_options.each(function(item){ item.set('selected', model.get('value') == item.get('value') ? true : false, {silent: true}); }); this.displayed_gallery.set('order_direction', model.get('value')); this.entities.reset(); this.$el.find('a.sortdirection').each(function(){ var $item = $(this); if ($item.attr('value') == model.get('value')) $item.addClass('selected'); else $item.removeClass('selected'); }); }, render: function(){ this.$el.empty(); this.populate_sorting_fields(); this.$el.append('Sort By:'); this.sortorder_options.each(function(item, index){ var button = new this.Button({model: item, className: 'sortorder'}); this.$el.append(button.render().el); if (this.sortorder_options.length-1 > index) { this.$el.append('|'); } }, this); this.$el.append('Order By:'); this.sortdirection_options.each(function(item, index){ var button = new this.Button({model: item, className: 'sortdirection'}); this.$el.append(button.render().el); if (this.sortdirection_options.length-1 > index) { this.$el.append('|'); } }, this); return this; }, Button: Backbone.View.extend({ tagName: 'a', initialize: function(){ _.each(this.options, function(value, key){ this[key] = value; }, this); }, events: { click: 'clicked' }, clicked: function(e){ e.preventDefault(); this.model.set('selected', true); }, render: function(){ this.$el.prop({ value: this.model.get('value'), href: '#' }); this.$el.text(this.model.get('title')); if (this.model.get('selected')) this.$el.addClass('selected'); return this; } }) }), // Individual entity in the preview area EntityElement: Backbone.View.extend({ tagName: 'li', events: { drop: 'item_dropped' }, initialize: function(){ _.each(this.options, function(value, key){ this[key] = value; }, this); this.model.on('change', this.render, this); if (this.model.get('sortorder') == 0) { this.model.set('sortorder', -1, {silent: true}); } this.id = this.model.get('id_field')+'_'+this.model.entity_id() }, item_dropped: function(e, index){ Ngg.DisplayTab.instance.displayed_gallery.set('order_by', 'sortorder'); //Ngg.DisplayTab.instance.displayed_gallery.set('order_direction', 'ASC'); this.model.set('sortorder', index); }, render: function(){ this.$el.empty(); var image_container = $('').addClass('image_container'); var alt_text = this.model.alttext().replace(/\\&/g, '&').replace(/\\'/g, "'"); var timestamp = new Date().getTime(); image_container.attr({ title: alt_text, style: "background-image: url('"+this.model.get('thumb_url')+"?timestamp"+timestamp+"')" }).css({ width: this.model.get('max_width'), height: this.model.get('max_height'), 'max-width': this.model.get('max_width'), 'max-height': this.model.get('max_height') }); this.$el.append(image_container).addClass('ui-state-default'); // Add exclude checkbox var exclude_container = $('').addClass('exclude_container'); exclude_container.append('Exclude?'); var exclude_checkbox = new this.ExcludeCheckbox({model: this.model}); exclude_container.append(exclude_checkbox.render().el); image_container.append(exclude_container); return this; }, ExcludeCheckbox: Backbone.View.extend({ tagName: 'input', events: { 'change': 'entity_excluded' }, type_set: false, entity_excluded: function(e){ this.model.set('exclude', e.target.checked); }, initialize: function(){ _.each(this.options, function(value, key){ this[key] = value; }, this); this.model.on('change:exclude', this.render, this); }, render: function(){ if (!this.type_set) { this.$el.attr('type', 'checkbox'); this.type_set = true; } if (this.model.is_excluded()) this.$el.prop('checked', true); else this.$el.prop('checked', false); return this; } }) }) }); // Additional source configuration views. These will be rendered dynamically by PHP. // Adapters will add them. Ngg.DisplayTab.Views.GalleriesSource = Backbone.View.extend({ tagName: 'tbody', initialize: function(){ this.galleries = Ngg.DisplayTab.instance.galleries; }, render: function(){ var select = new Ngg.Views.Chosen({ collection: this.galleries, placeholder: 'Select a gallery', multiple: true, width: 500 }); var html = $('