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(options) { this.options = options || {}; _.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).attr('multiple', 'multiple'); 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(options) { this.options = options || {}; _.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.prop('selected', true).attr('selected', 'selected'); } return this; } }) }); Ngg.Views.Chosen = Backbone.View.extend({ tagName: 'span', initialize: function(options) { this.options = options || {}; this.collection = this.options.collection; 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); // Ensure that values are pre-populated if (this.options.multiple) { var selected = []; _.each(this.collection.selected_ids(), function(id){ selected.push(id.toString()); }); if (selected.length == 0) selected = ''; this.select_tag.$el.select2('val', selected); } // For IE, ensure that the text field has a width this.$el.find('.select2-input').width(this.options.width-20); 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 = _.extend({}, igw_data.sec_token, { 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 (typeof(_) == 'undefined') return; 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 }, to_shortcode: function(){ retval = null; var get_shortcode_attr= function(object, key){ var val = object[key]; if (_.isArray(val)) { val = val.length > 0 ? val.join(',') : null; } if (val) { val = val.toString().replace('[', '['); val = val.toString().replace(']', ']'); return key + '="' + val +'"'; } }; // Convert the displayed gallery to a JSON object var display_type = Ngg.DisplayTab.instance.display_types.find_by_name_or_alias(this.get('display_type')); var obj = this.toJSON(); obj.display_type = display_type.get_shortcode_value(); // Convert the displayed gallery to a shortcode var snippet = '[ngg_images'; var val = null; if ((val = get_shortcode_attr(obj, 'source'))) snippet += ' ' + val; if ((val = get_shortcode_attr(obj, 'container_ids'))) snippet += ' ' + val; if ((val = get_shortcode_attr(obj, 'entity_ids'))) snippet += ' ' + val; if ((val = get_shortcode_attr(obj, 'exclusions'))) snippet += ' ' + val; if ((val = get_shortcode_attr(obj, 'sortorder'))) snippet += ' ' + val; for (var key in obj) { var skipped = [ 'source', 'container_ids', 'entity_ids', 'exclusions', 'sortorder', '__defaults_set', 'id_field' ]; if (skipped.indexOf(key) > -1) continue; else if (key == 'display_settings') { for (var display_key in obj[key]) { if ((val = get_shortcode_attr(obj[key], display_key))) snippet += ' ' + val; } } else { val = get_shortcode_attr(obj, key); if (val) snippet += ' ' + val; } } snippet += ']'; return snippet; } }); /** * 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; }, find_by_name_or_alias: function(name) { return this.find(function(source) { return source.get('name') == name || (_.isArray(source.get('aliases')) && source.get('aliases').indexOf(name) > -1); }); } }); /** * Ngg.DisplayTab.Models.Gallery * Represents an individual gallery entity **/ Ngg.DisplayTab.Models.Gallery = Backbone.Model.extend({ idAttribute: igw_data.gallery_primary_key, 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; }, get_shortcode_value: function(){ var retval = this.id; // TODO: Uncomment for a later version // var aliases = this.get('aliases'); // if (_.isArray(aliases) && aliases.length > 0) { // retval = aliases[0]; // } return retval; } }); /** * 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; }, find_by_name_or_alias: function(name){ return this.find(function(display_type){ return display_type.get('name') == name || (_.isArray(display_type.get('aliases')) && display_type.get('aliases').indexOf(name) > -1); }); } }); /** * 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 }); var template = _.template('
"+igw_data.i18n.no_entities+"
"); }, 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(options) { this.options = options || {}; _.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(options) { this.options = options || {}; _.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(options) { this.options = options || {}; _.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(options) { this.options = options || {}; _.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(options) { this.options = options || {}; _.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(options) { this.options = options || {}; _.each(this.options, function(value, key){ this[key] = value; }, this); this.initTime = new Date().getTime(); 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 preview_item = $('').addClass('preview_item'); var image_container = $('').addClass('image_container'); var alt_text = this.model.alttext().replace(/\\&/g, '&').replace(/\\'/g, "'"); var timestamp = this.initTime; image_container.attr({ title: alt_text, style: "background-image: url('"+this.model.get('thumb_url')+"?timestamp"+timestamp+"')" }); this.$el.append(preview_item).addClass('ui-state-default'); preview_item.append(image_container); // Add exclude checkbox var exclude_container = $('').addClass('exclude_container'); var exclude_label = $(''); exclude_label.append(igw_data.i18n.exclude_question); var exclude_checkbox = new this.ExcludeCheckbox({model: this.model}); exclude_label.append(exclude_checkbox.render().el); exclude_container.append(exclude_label); preview_item.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(options) { this.options = options || {}; _.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: igw_data.i18n.select_gallery, multiple: true, width: 500 }); var html = $('