UNPKG

backbone-list-controller

Version:

A powerful Backbone.js class that renders a collection of models as a list with infinite scrolling, sorting, filtering, and search field.

1,022 lines (1,021 loc) 81.7 kB
var SortableCollection = Backbone.Collection.extend({ constructor: function() { this.setupFilters(), Backbone.Collection.prototype.constructor.apply(this, arguments); }, defaultDesc: !1, dbSort: !1, _key: function() { return _.isFunction(this.key) ? this.key() : this.key; }, localStoreKey: function(a) { return "list:" + this._key() + (a ? ":" + a : ""); }, sortKey: function(a) { return void 0 === a ? _.store("list:" + this._key() + ":sort") || this.defaultSort : void _.store("list:" + this._key() + ":sort", a); }, sortDesc: function(a) { return void 0 !== a ? _.store("list:" + this._key() + ":sort:desc", a) : void 0 !== _.store("list:" + this._key() + ":sort:desc") ? _.store("list:" + this._key() + ":sort:desc") : this.defaultDesc; }, sortBy: function() { if (this.dbSort) return this.models; var a = _.sortBy(this.models, this.comparator, this); return this.sortDesc() && a.reverse(), a; }, comparator: function(a) { if (this.dbSort) return !0; var b = this.sortKey(), c = null; if (this.sorts && this.sorts[b] ? c = this.sorts[b].call(this, a, b, this.sortDesc()) : "title" === b ? c = _.sortString(a.get(this.sortKey())) : a[b] && _.isFunction(a[b]) ? c = a[b]() : a.has(b) && (c = a.get(b)), this.rootComparator) { _.isArray(c) || (c = [ c ]); var d = this.rootComparator(a); _.isArray(d) || (d = [ d ]), c = d.concat(c); } return c; }, changeSort: function(a) { this.sortKey() == a && this.sortDesc(!this.sortDesc()), this.sortKey(a), this.trigger("sort:change"), this.sort(); }, fetch: function(a) { var b = {}; this.filters && (b = this.filterVals(null, "db"), b.filters = this.filterVals(null, "db"), b.sortKey = this.sortKey(), b.sortDesc = this.sortDesc(), b.optionalFilters = this.optionalFilters()), a = a || {}, a.data = a.data ? _.extend(a.data, b) : b, this.url ? Backbone.Collection.prototype.fetch.call(this, a) : a.success(this, []); }, refresh: function(a) { return a = 0 == a ? !1 : !0, this.dbSort && a ? void this.trigger("reset") : (this.trigger("spin"), void this.fetch({ update: a, add: !1, remove: !1, merge: !0, success: function(b, c) { a && b.reset(c), this.trigger("fetch:success", b, c), this.trigger("spin", !1); }.bind(this), error: function(b, c) { this.trigger("fetch:failed", this, c.statusText, this.refresh.bind(this, a)), this.trigger("spin", !1); }.bind(this) })); }, setupFilters: function() { if (!this._setupFiltersDone && (this._setupFiltersDone = !0, this.filters)) { var a = this; _.each(this.filters, function(b) { if (!b.key) return console.error("!! SortableCollection: filters must have a key specified; this one does not:", b); if (b.localStorage !== !1) var c = _.store(a.localStoreKey(b.key)); c ? _.isObject(c) ? (b.val = c.val, b.optional = c.optional) : b.val = c : _.isFunction(b.val) && (b.val = b.val()); }); } }, applyFilter: function(a, b, c) { if (c = c || {}, this.filters) { var d = this.getFilter(a); return d ? (d.val = b, d.localStore !== !1 && _.store(this.localStoreKey(a), { val: d.val, optional: d.optional }), c.silent !== !0 && (this.trigger("filter:change:" + a, b), this.trigger("filter:change", a, b)), d.db && c.fetch !== !1 && this.refresh(), d) : console.error("!! SortableCollection: no filter was found for key: “" + a + "”", this.filters); } }, toggleFilterOptional: function(a, b) { if (b = b || {}, !this.filters) return console.error("!! SortableCollection: no filters are specifed"); var c = this.getFilter(a); return c ? (c.optional = !c.optional, c.localStore !== !1 && _.store(this.localStoreKey(a), { val: c.val, optional: c.optional }), b.silent !== !0 && (this.trigger("filter:change:" + a, c.optional), this.trigger("filter:change", a, c.optional)), void (c.db && b.fetch !== !1 && this.refresh())) : console.error("!! SortableCollection: no filter was found for key: “" + a + "”", this.filters); }, setFilters: function(a, b) { var c = !1; _.each(a, function(a, b) { filter = this.applyFilter(b, a, { silent: !0, fetch: !1 }), c || (c = filter && 1 == filter.db); }, this), b !== !1 && c && this.refresh(); }, getFiltered: function() { return this; }, getFilter: function(a) { return this.filters ? _.findWhere(this.filters, { key: a }) : null; }, filterVals: function(a, b) { void 0 == a && (a = this.filters); var c = {}; return a && _.each(a, function(a) { ("db" !== b || a.db !== !1) && (c[a.key] = a.val); }), c; }, filterOptionals: function(a) { void 0 == a && (a = this.filters); var b = {}; return a && _.each(a, function(a) { b[a.key] = a.optional; }), b; }, optionalFilters: function(a) { void 0 == a && (a = this.filters); var b = []; return a && _.each(a, function(a) { a.optional && b.push(a.key); }), b; } }), FilterView = Backbone.View.extend({ events: { "keyup input.filter": "filterCollectionFromInput" }, filteredCollection: this.collection, filterTerm: "", filterResult: null, filterMinScore: .7, filterResultCollection: function(a) { return new Backbone.Collection(a); }, foreachFilterNotInUse: function(a) { var b = this; a && _.each(this.filters, function(c, d) { b.activeFilters && void 0 !== b.activeFilters[d] || a.call(b, c, d); }); }, foreachFilterInUse: function(a) { var b = this; a && _.each(this.filters, function(c, d) { b.activeFilters && void 0 !== b.activeFilters[d] && a.call(b, c, d); }); }, filterComparator: null, defaultFilterMethods: { text: function(a, b, c) { return _.isArray(b) ? _.contains(b, a.get(c)) : a.get(c) == b; }, number: function(a, b, c) { return Number(a.get(c)) == b; }, "int": function(a, b, c) { return parseInt(a.get(c)) == b; }, array: function(a, b, c) { return _.indexOf(b, a.get(c)) > -1; }, model_id: function(a, b, c) { return _.isArray(b) ? _.contains(b, a.get(c).id) : a.get(c) && parseInt(a.get(c).id) == b; }, starts_with: function(a, b, c) { return (a.get(c) || "").match(RegExp("^" + b)); }, ends_with: function(a, b, c) { return (a.get(c) || "").match(RegExp(b + "$")); }, contains: function(a, b, c) { return (a.get(c) || "").match(RegExp(b)); } }, setActiveFilters: function(a) { _.each(a, function(a, b) { _.isObject(a) && !_.isArray(a) ? this.applyFilter(a.key, { val: a.val, optional: a.optional }, !1) : this.applyFilter(b, { val: a }, !1); }, this); }, applyFilter: function(a, b, c) { b = _.isObject(b) ? b : { val: b }; var d = b.val; void 0 === d || null === d || "" === d || "All" === d || "all" === d ? this._removeActiveFilter(a) : this._addActiveFilter(a, b), this.trigger("filter:change"), this.refilter(c); }, toggleFilter: function(a, b, c) { b = _.isObject(b) ? b : { val: b }, this.activeFilters[a] ? this._removeActiveFilter(a) : this._addActiveFilter(a, b), this.trigger("filter:change"), this.refilter(c); }, toggleFilterOptional: function(a, b) { var c = this.activeFilters[a]; c && (this.activeFilters[a].optional = !c.optional, this.trigger("filter:change"), this.refilter(b)); }, refilter: function(a) { return 0 == this.collection.length ? void (this.filteredCollection = this.collection) : (this._filterCollectionWithActiveFilters(), void (a !== !1 && this.filterCollection())); }, _addActiveFilter: function(a, b) { this.activeFilters = this.activeFilters || {}, this.activeFilters[a] = b, this.$el.addClass("is-filtered").attr("data-filtered-" + a, b.val); }, _removeActiveFilter: function(a) { this.activeFilters = this.activeFilters || {}, delete this.activeFilters[a], this.$el.attr("data-filtered-" + a, null), 0 === _.size(this.activeFilters) && this.$el.removeClass("is-filtered"); }, _filterCollectionWithActiveFilters: function() { this.filteredCollection = this.collection, this.filteredCollection = this.filteredCollection.filter(function(a) { var b = [], c = []; return _.each(this.activeFilters, function(d, e) { var f = d.val, g = d.optional === !0, h = this._filterFn(e); if (h) { var i = h.call(this, a, f, e, this); g ? c.push(i) : b.push(i); } }, this), (0 === b.length || -1 == _.indexOf(b, !1)) && (0 === c.length || _.indexOf(c, !0) > -1); }, this), this.filteredCollection = this.filterResultCollection(this.filteredCollection), this.filterComparator && (this.filteredCollection = this.filteredCollection.sortBy(this.filterComparator, this), this.filteredCollection = this.filterResultCollection(this.filteredCollection)); }, _filterFn: function(a) { var b = this.filters[a] || null; return b ? (_.isFunction(b) || _.isString(b) || (b = b.filterBy || null), _.isString(b) && (this.defaultFilterMethods[b] ? b = this.defaultFilterMethods[b] : console.warn("FilterView: “%s” is not valid default filter method. Available defaults:", b, _.keys(this.defaultFilterMethods))), b) : b; }, filterContexts: { "default": function() { return [ 1 ]; } }, filterCollectionFromInput: function(a) { var b = a.target.value; this.filterTerm !== b && (a.target.setAttribute("value", b), this.filterTerm = b, this.filterCollection()); }, filterCollection: function() { var a = this.filterTerm; if ("" != a && this.filterContexts["default"]) { this.$el.addClass("filtered"), this.trigger("filter:start", this.filterTerm); var b = "default"; _.each(this.filterContexts, function(c, d) { var e = new RegExp("^" + d + "(.*)"); e.test(a) && (b = d, a = a.match(e)[1].trim()); }); var c = this.filteredCollection.filter(function(c) { var d = this.filterContexts[b], e = d(a, c), f = _.max(e); return c.set("score", f, { silent: !0 }), f > this.filterMinScore; }, this); c = _.sortBy(c, function(a) { return a.get("score"); }), c = c.reverse(), this.filterResult = this.filterResultCollection(c); } else this.clearFilter(), this.filterContexts["default"] || console.warn("FilterView: no “default” filter context. Please add one."); this.collection.trigger("reset"); }, clearFilter: function() { this.filterTerm = "", this.filterResult = null, this.$el.removeClass("filtered"), this.trigger("filter:done"); }, getCollection: function() { return !this.filteredCollection && _.size(this.activeFilters) > 0 ? this._filterCollectionWithActiveFilters() : this.filteredCollection || (this.filteredCollection = this.collection), this.filterResult || this.filteredCollection; } }); InfiniteListView = Backbone.View.extend({ tagName: "ul", className: "list", noResultsMsg: "No Results", events: { "click .load-more": "endReached" }, initialize: function(a, b) { this._views = [], this.lastRow = 0, this.enabled = !0, this.opts = _.extend({ maxRows: 30, context: window }, b), this.maxRows = this.opts.maxRows, this.$el.appendTo(a), this.opts.className && this.$el.addClass(this.opts.className); }, removeItem: function(a, b) { b = void 0 != b ? b : !0; var c = this.el.childNodes[a], d = this._views[a]; this._views.splice(a, 1), c ? (this.lastRow--, d.remove(b, this.attachWaypoint.bind(this))) : console.warn("Could not find element to remove at index", a); }, addMore: function(a) { var b = this.lastRow; if (b >= a.length && a.length > 0) return void this.$endOfList.html("<p>End of list • " + a.length + " results</p>"); if (this.$endOfList || (this.$endOfList = $('<li class="end-of-list clearfix"></li>').appendTo(this.$el)), this.$endOfList.html("").detach(), 0 === a.length) return $("<p>" + this.noResultsMsg + "</p>").appendTo(this.$endOfList), void this.$endOfList.appendTo(this.$el); for (b; b < this.lastRow + this.maxRows; b++) { var c = a.at(b); c && this.trigger("addOne", c); } this.lastRow = b, b >= a.length ? $("<p>End of list • " + a.length + " results</p>").appendTo(this.$endOfList) : $('<p class="load-more">Load more</p>').appendTo(this.$endOfList), this.$endOfList.appendTo(this.$el), _.defer(this.attachWaypoint.bind(this)); }, addOne: function(a) { this._views.push(a), this.$el.append(a.render().el); }, endReached: function() { this.$endOfList.html("<p>Loading more results...</p>"), this.trigger("endReached"); }, attachWaypoint: function() { if (this.enabled && 0 != this.$el.parents("body").length) { var a = this; !this.context && _.isString(this.opts.context) && (this.context = document.querySelector(this.opts.context)), this.__waypoint && this.__waypoint.destroy(), this.__waypoint = new Waypoint({ element: this.$endOfList[0], offset: function() { return a.context.offsetHeight; }, context: $(this.opts.context), handler: function() { this.destroy(), a.endReached(); } }); } }, clear: function() { this.$el.html(""), this._views = [], this.lastRow = 0; }, enable: function() { this.enabled = !0; }, disable: function() { this.enabled = !1; } }), ListController = Backbone.View.extend({ _className: "list-controller", maxRows: 30, listStyle: "simple", reuseListViews: !1, displayList: !0, scrollContext: "#main .inner", noResultsMsg: "No Results", filter: !0, filterMinScore: .7, filterDelay: 200, filterPlaceholder: "Filter...", filterInputStyle: "right auto-hide", filterDropdownW: 120, filterComparator: null, fetchOnRender: !1, refreshBtn: !1, allowFilterQueueing: !0, viewBtn: !1, defaultView: "list", compareViewLimit: null, sorts: [], filterContexts: {}, filterResultCollection: function(a) { return new Backbone.Collection(a); }, constructor: function(a) { this.checkSetupTimeout = setTimeout(this.__checkSetup.bind(this), 0), Backbone.View.prototype.constructor.call(this, a); }, __checkSetup: function() { this.checkSetupTimeout && this.__setup(); }, __events: { "keyup input.filter": "filterCollectionFromInput", "click a.change-desc": "changeDesc", "click .manual-refresh": "manualRefresh", "click .apply-queued-filters": "applyQueuedFilters" }, __listenerEvents: [ [ "sort:change", "_addAll" ], [ "sort:change", "_renderSortSelect" ] ], __setup: function() { if (clearTimeout(this.checkSetupTimeout), this.events = _.extend({}, this.events || {}, this.__events), this.setupFilters && this.setupFilters(), this.$el.addClass(this._className), this.$top = $('<div class="filter-bar top clearfix"></div>').appendTo(this.$el), this.displayList !== !1) { var a = this.options.scrollContext || this.scrollContext; this.list = new InfiniteListView(this.$el, { maxRows: this.maxRows, context: a, className: this.listStyle + " list clearfix" }), this.setView(), this.listenTo(this.list, "addOne", this._addOne), this.listenTo(this.list, "endReached", this._addMore); } _.isFunction(this.filterContexts["default"]) || (this.filterContexts["default"] = this._defaultFilterContext), _.each(this.__listenerEvents, function(a) { var b = a[0], c = a[1]; this[c] && this.listenTo(this, b, this[c]); }.bind(this)), (this.collection || !this.collection && !this.collectionSwitcher) && this.changeCollection(this.collection), this.trigger("setup:done"); }, changeCollection: function(a) { !a || a instanceof SortableCollection ? a || (a = new SortableCollection()) : console.warn("!! ListController: “collection” is not an instance of SortableCollection", this), this.collection && (this.collection.getFiltered = null, this.stopListening(this.collection)), this.collection = a, this.collection.getFiltered = this.getCollection.bind(this), this.listenTo(this.collection, "spin", this.spin), this.listenTo(this.collection, "reset", this._addAllFromReset), this.listenTo(this.collection, "add", this._addAll), this.listenTo(this.collection, "remove", this._removeItem), this.listenTo(this.collection, "fetch:failed", this._fetchFailed); }, spin: function(a) { this.trigger("spin", a !== !1), a && this.updateCount("Loading..."), a !== !1 && this.$el.removeClass("invalid-data"); }, render: function() { return this._renderTop(), this.clearFilter(), !this.collection && this.collectionSwitcher ? this.promptCollectionSwitcherPick() : this.fetchOnRender ? this.autoFetchOnRender() : 0 == this.collection.length ? this.collection.trigger("reset") : _.defer(_.bind(function() { this.collection.trigger("reset"); }, this)), this.trigger("render"), this.delegateEvents(), this; }, promptCollectionSwitcherPick: function() { console.log("promptCollectionSwitcherPick: finish me"); }, autoFetchOnRender: function(a) { a = a || !1, this.collection.refresh(a); }, manualRefresh: function() { this.autoFetchOnRender(!0); }, _fetchFailed: function(a, b, c) { this.$el.addClass("invalid-data"), "Invalid User" != b && new Modal({ title: b, msg: "What would you like to do?", theme: "ios7", btns: [ { label: "Retry", className: "green btn-primary md-close", onClick: function() { c(); }, eventKey: "enter" }, { label: "Reset Filters", className: "btn-primary md-close", onClick: this.resetFilters.bind(this) }, "cancel" ] }); }, _addOne: function(a) { if (this.trigger("addOne", a), _.isString(this.listView) && (this.listView = _.getObjectByName(this.listView)), !this.listView) return void console.error("! ListController: a “listView” must be defined"); var b = { model: a }; if (this.listViewData && (b = _.extend(this.listViewData(this.lastModel, a), b)), this._addOneDivider(a), 1 == this.reuseListViews) var c = this.list.subview("list-view-" + a.id) || this.list.subview("list-view-" + a.id, new this.listView(b)); else var c = this.list.subview("list-view-" + a.id, new this.listView(b)); c.parentView = this, c.controller = this, this.lastModel = a, this.list.addOne(c), a._selected ? c.$el.addClass("selected") : c.$el.removeClass("selected"), this.close && this.listenTo(c, "panel:close", this.close); }, _addOneDivider: function(a) { if (this.listViewDivider && !this.filterTerm) { var b = _.isFunction(this.listViewDivider) ? this.listViewDivider() : a[this.listViewDivider] && _.isFunction(a[this.listViewDivider]) ? a[this.listViewDivider].call(a) : a.get(this.listViewDivider); b != this.__lastLabelDivider && (this.list.$el.append('<li class="list-divider">' + b + "</li>"), this.__lastLabelDivider = b); } }, _addMore: function() { this.list.addMore(this.getCollection()), this.trigger("addMore"); }, refreshList: function() { this._addAll(); }, _addAllFromReset: function(a, b) { a && b && this.list.cleanupSubviews(!0), this._addAll(); }, _addAll: function() { this.displayList && (this.list.clear(), this.lastModel = null, this.__lastLabelDivider = null, this.list.noResultsMsg = this.filterTerm ? "No Results for <i>“" + this.filterTerm + "”</i>" : _.isFunction(this.noResultsMsg) ? this.noResultsMsg() : this.noResultsMsg, this.refilter(!1), this.trigger("addAll"), this.compareViewLimit && this.inCompareMode() && this.getCollection().length > this.compareViewLimit && this.setView("list"), this._addMore(), this.updateCount()); }, _removeItem: function(a) { var b = this.getCollection().indexOf(a); this.list.removeItem(b, !1), this.filteredCollection.remove(a), _.defer(function() { this.updateCount(); }.bind(this)); }, _renderTop: function() { this.$top.html(""); var a = this.bulkActions || this.allowBulkSelect && this.allowDownload; this.refreshBtn && this.$top.append('<a class="btn right icon-only icon-arrows-ccw manual-refresh" title="Refresh results"></a>'), this.viewBtn && this.displayList && this._appendViewBtn(), this.allowDownload && this._appendDownloadBtn && this._appendDownloadBtn(), this.filter && (this.$filter = $('<input type="text" placeholder="' + this.filterPlaceholder + '" class="filter ' + this.filterInputStyle + '">').val(this.filterTerm).appendTo(this.$top)), this.$count = $('<span class="count no-selection ' + (a ? "btn" : "") + '" title="' + (a ? "Toggle bulk select" : "") + '"></span>').appendTo(this.$top), this.renderTop && this.renderTop(), a && this._appendBulkSelectActions && this._appendBulkSelectActions(), this.sorts && this.sorts.length > 0 && this.$top.append(this._renderSortSelect()), this.filters && this._renderFilterSelects && this.$top.append(this._renderFilterSelects()), this.header && this._renderHeader(); }, _renderHeader: function() { var a = ""; _.each(this.header, function(b) { b = _.extend({ label: "", className: "" }, _.isString(b) ? { label: b } : b); var c = _.underscored(_.stripTags(b.label)); a += '<div class="col ' + c + " header-" + c + " " + b.className + '">' + b.label + "</div>"; }), this.$top.addClass("has-header"), this.$header = $('<div class="header-bar clearfix">' + a + "</div>").appendTo(this.$top); }, _appendViewBtn: function() { { var a = [ { label: "List", val: "list" }, { label: "Grid", val: "grid" }, { label: "Compare", val: "compare" } ], b = { align: "bottomLeft", w: 100, onClick: this.onSetView.bind(this) }; $('<a class="btn right icon-only icon-columns switch-view" title="Switch view: List / Compare / Grid"></a>').dropdown(a, b).appendTo(this.$top); } }, inListMode: function() { return "list" == this.viewKey(); }, inCompareMode: function() { return "compare" == this.viewKey(); }, inGridMode: function() { return "grid" == this.viewKey(); }, viewKey: function() { return _.store("ListController:view:" + this.className) || this.defaultView; }, onSetView: function(a) { var b = this.setView(a.val), c = _.capitalize(a.val) + " View", d = "There are too many results for the <b>" + a.val.toUpperCase() + "</b> view.<br><br>It is limited to " + this.compareViewLimit + " results"; b != a.val && ("undefined" == typeof Modal ? alert(c + "\n\n" + _.stripTags(d)) : Modal.alert(c, d)); }, setView: function(a) { var b = this.viewKey(), c = a ? a : b; return this.viewBtn || (c = this.defaultView), this.compareViewLimit && "compare" == c && this.getCollection().length > this.compareViewLimit && (c = "compare" == b ? "list" : b), this.list.$el.removeClass("mode-" + b), this.list.$el.addClass("mode-" + c), _.store("ListController:view:" + this.className, c), c != b && this.trigger("view:change", c, this), c; }, _renderSortSelect: function() { if (this.sorts && 0 != this.sorts.length) { this.$sortSelect ? this.$sortSelect.html("") : this.$sortSelect = $('<div class="sorts"></div>'); var a = _.findWhere(this.sorts, { val: this.collection.sortKey() }), b = a && a.label ? a.label : "No sort", c = this.collection.sortDesc() ? "icon-sort-name-down" : "icon-sort-name-up"; return $btn = $('<span class="btn-group" title="Change sort"><a class="btn ' + c + ' change-desc">' + b + '</a><a class="btn icon-only icon-down-open change-sort"></a></span>').appendTo(this.$sortSelect), $btn.find(".change-sort").dropdown(this.sorts, { align: "bottom", w: 120, onClick: this._onChangeSort.bind(this) }), this.$sortSelect; } }, downloadCSV: function() { this.getCollection().saveToCSV(this.downloadName); }, changeDesc: function() { this.changeSort(this.collection.sortKey()); }, changeSort: function(a) { this.collection.changeSort(a), this.trigger("sort:change"); }, _onChangeSort: function(a) { this.changeSort(a.val); }, activeFilter: function(a) { return this.activeFilters && this.activeFilters[a]; }, activeFilterVal: function(a, b) { return this.activeFilter(a) ? this.activeFilter(a).val : b; }, activeFilterString: function(a, b) { var c = []; return _.each(this.activeFilters, function(a, b) { var d = this.filters[b].label || _.titleize(_.humanize(b)); c.push("<em>" + d + ":</em> <b>" + this._renderActiveFilter(a, b, !0) + "</b>"); }, this), c = c.join(a || ", "), b === !1 && (c = _.stripTags(c)), c; }, isQueueing: function() { return this.__queueing === !0; }, toggleQueueUpFilters: function() { this.isQueueing() ? this.applyQueuedFilters() : this.beginQueueingFilters(); }, beginQueueingFilters: function() { this.__queueing = !0, this.$el.addClass("queueing-filters"), this.trigger("filter:queue:begin"); }, applyQueuedFilters: function() { this.__queueing = !1, this.$el.removeClass("queueing-filters"), this.trigger("filter:queue:apply"), this.manualRefresh(); }, _onFilterChange: function(a, b, c) { var d = c.val, e = this.filters[a], f = this.activeFilters[a]; if (!d && c.filters) return void this.resetFiltersTo(c.filters); if (e.multi) if (void 0 === d || null === d || "" === d) ; else if (_.metaKey()) { var g = f ? [].concat(f.val) : [], h = _.indexOf(g, d); h > -1 ? g.splice(h, 1) : g.push(d), d = g; } else d = [ d ]; this.changeFilter(a, d, b); }, changeFilter: function(a, b, c) { var d = this; if (this.collectionSwitcherKey == a) { var e = this.collectionSwitcher.get(b), f = e ? e.switcherCollection() : null; !f || f instanceof Backbone.Collection ? (this.spin(), this.changeCollection(f), f ? this.autoFetchOnRender() : this.manualRefresh()) : f && console.warn("!! Cannot switch to non Backbone.Collection:", b, e, f); } this.trigger("filter:changing", a, b), _.defer(function() { d.collection.applyFilter(a, b, { fetch: !d.isQueueing() }), d._applyFilter(a, { val: b, optional: c }, !d.isQueueing()); }); }, toggleFilter: function(a, b, c) { this.activeFilters && this.activeFilters[a] && (b = null), this.changeFilter(a, b, c); }, _onFilterOptionalToggle: function(a) { this.collection.toggleFilterOptional(a, { fetch: !this.isQueueing() }), this._toggleFilterOptional(a, !this.isQueueing()); }, resetFilters: function() { this.resetFiltersTo({}); }, resetFiltersTo: function(a) { var b = {}; this.foreachFilterInUse(function(a, c) { if (a.values) { var d = a.values; _.isFunction(d) && (d = d.call(a)), d[a.defaultValIndex || 0] && (b[c] = d[a.defaultValIndex || 0].val); } }), b = _.extend(b, a), this.collection.setFilters(b, !this.isQueueing()), this.setActiveFilters(b), this.trigger("sort:change"); }, updateCount: function(a) { if (this.$count) { var b = a || this.getCollection().length; !a && this.isBulkSelectOn && (b = this.getSelected().length + " of " + b, this.$count.attr("data-selected", this.getSelected().length)), this.$count.html(b); } }, focusFilter: function() { this.$filter && this.$filter.focus(); }, scrollTo: function(a, b) { var a = a instanceof Backbone.View ? a.el : a; b = b || 0, this.list.el.scrollTop = a.offsetTop + b; }, filteredCollection: this.collection, filterTerm: "", filterResult: null, foreachFilterNotInUse: function(a) { var b = this; a && _.each(this.filters, function(c, d) { b.activeFilters && void 0 !== b.activeFilters[d] || a.call(b, c, d); }); }, foreachFilterInUse: function(a) { var b = this; a && _.each(this.filters, function(c, d) { b.activeFilters && void 0 !== b.activeFilters[d] && a.call(b, c, d); }); }, setActiveFilters: function(a) { this.activeFilters = {}, _.each(a, function(a, b) { _.isObject(a) && !_.isArray(a) ? this._applyFilter(a.key, { val: a.val, optional: a.optional }, !1) : this._applyFilter(b, { val: a }, !1); }, this); }, _applyFilter: function(a, b, c) { b = _.isObject(b) ? b : { val: b }; var d = b.val; void 0 === d || null === d || "" === d ? this._removeActiveFilter(a) : this._addActiveFilter(a, b), this.trigger("filter:change"), this.refilter(c); }, _toggleFilterOptional: function(a, b) { var c = this.activeFilters[a]; c && (this.activeFilters[a].optional = !c.optional, this.trigger("filter:change"), this.refilter(b)); }, refilter: function(a) { return 0 == this.collection.length ? void (this.filteredCollection = this.collection) : (this._filterCollectionWithActiveFilters(), void (a !== !1 && this.filterCollection())); }, _addActiveFilter: function(a, b) { this.activeFilters = this.activeFilters || {}, this.activeFilters[a] = b, this.$el.addClass("is-filtered").attr("data-filtered-" + a, b.val); }, _removeActiveFilter: function(a) { this.activeFilters = this.activeFilters || {}, delete this.activeFilters[a], this.$el.attr("data-filtered-" + a, null), 0 === _.size(this.activeFilters) && this.$el.removeClass("is-filtered"); }, _filterCollectionWithActiveFilters: function() { this.filteredCollection = this.collection, this.filteredCollection = this.filteredCollection.filter(function(a) { var b = [], c = []; return _.each(this.activeFilters, function(d, e) { var f = d.val, g = d.optional === !0, h = this._filterFn(e); if (h) { var i = h.call(this, a, f, e, this); g ? c.push(i) : b.push(i); } }, this), (0 === b.length || -1 == _.indexOf(b, !1)) && (0 === c.length || _.indexOf(c, !0) > -1); }, this), this.filteredCollection = this.filterResultCollection(this.filteredCollection), this.filterComparator && (this.filteredCollection = this.filteredCollection.sortBy(this.filterComparator, this), this.filteredCollection = this.filterResultCollection(this.filteredCollection)); }, _filterFn: function(a) { var b = this.filters[a] || null; return b ? (_.isFunction(b) || _.isString(b) || (b = b.filterBy || null), _.isString(b) && (this._defaultFilterMethods[b] ? b = this._defaultFilterMethods[b] : console.warn("FilterView: “%s” is not valid default filter method. Available defaults:", b, _.keys(this._defaultFilterMethods))), b) : b; }, filterCollectionFromInput: function(a) { var b = a.target.value; this.filterTerm !== b && (a.target.setAttribute("value", b), this.filterTerm = b, clearTimeout(this._filterCollectionFromInputTimeout), this._filterCollectionFromInputTimeout = setTimeout(function() { this.filterCollection(), this.trigger("filter:search", b); }.bind(this), this.filterDelay)); }, filterCollection: function() { var a = this.filterTerm; if ("" != a && this.filterContexts["default"]) { this.$el.addClass("filtered"), this.trigger("filter:start", this.filterTerm); var b = "default"; _.each(this.filterContexts, function(c, d) { var e = new RegExp("^" + d + "(.*)"); e.test(a) && (b = d, a = a.match(e)[1].trim()); }); var c = this.filteredCollection.filter(function(c) { var d = this.filterContexts[b], e = d(a, c), f = _.max(e); return c.set("score", f, { silent: !0 }), f > this.filterMinScore; }, this); c = _.sortBy(c, function(a) { return a.get("score"); }), c = c.reverse(), this.filterResult = this.filterResultCollection(c); } else this.clearFilter(), this.filterContexts["default"] || console.warn("FilterView: no “default” filter context. Please add one."); this.collection.trigger("reset"); }, clearFilter: function() { this.filterTerm = "", this.filterResult = null, this.$el.removeClass("filtered"), this.trigger("filter:done"); }, getCollection: function() { return !this.filteredCollection && _.size(this.activeFilters) > 0 ? this._filterCollectionWithActiveFilters() : this.filteredCollection || (this.filteredCollection = this.collection), this.filterResult || this.filteredCollection; }, getSelected: function() { return this.filterResultCollection(this.getCollection().filter(function(a) { return a._selected; })); }, getCurrentCollection: function() { return this.isBulkSelectOn ? this.getSelected() : this.getCollection(); } }), ListController.permission = function(a) { return User.can(a); }, ListController.prototype._defaultFilterContext = function(a, b) { var c = []; return b.has("book_id") && c.push(_.score(b.get("book_id"), a)), b.has("title") && c.push(_.score(b.get("title"), a)), b.has("name") && c.push(_.score(b.get("name"), a)), b.has("label") && c.push(_.score(b.get("label"), a)), c.length > 0 ? c : [ 1 ]; }, ListController.prototype._defaultFilterMethods = { text: function(a, b, c) { var d = _.isFunction(a[c]) ? a[c].call(a) : a.get(c); return _.isArray(b) ? _.contains(b, d) : d == b; }, number: function(a, b, c) { return Number(a.get(c)) == b; }, "int": function(a, b, c) { return parseInt(a.get(c)) == b; }, array: function(a, b, c) { return _.indexOf(b, a.get(c)) > -1; }, model_id: function(a, b, c) { return _.isArray(b) ? _.contains(b, a.get(c).id) : a.get(c) && parseInt(a.get(c).id) == b; }, starts_with: function(a, b, c) { return (a.get(c) || "").match(RegExp("^" + b)); }, ends_with: function(a, b, c) { return (a.get(c) || "").match(RegExp(b + "$")); }, contains: function(a, b, c) { return (a.get(c) || "").match(RegExp(b)); }, string_operator: function(model, filterVal, filterKey) { var modelVal = parseFloat(model.has(filterKey) ? model.get(filterKey) || 0 : model[filterKey] ? model[filterKey].call(model) : 0); if ("true" == filterVal) return modelVal > 0; if ("false" == filterVal) return !modelVal; var range = filterVal.match(/([0-9]+)[,-]{1}([0-9]+)/); return range ? modelVal >= range[1] && modelVal <= range[2] : (filterVal.match(/^[<=>]/) ? filterVal.match(/^[=][0-9]/) && (filterVal = "=" + filterVal) : filterVal = "==" + filterVal, matches = filterVal.match(/^([<=>]*)(.*)/), operator = matches[1], filterVal = numeral(matches[2]).value(), eval(modelVal + operator + filterVal)); } }, DBListController = ListController.extend({ filter: !1, __setup: function() { this.collection || console.error("!! DBListController: a collection is needed"), this._model = new DBListControllerModel({ collection: this.collection }), ListController.prototype.__setup.apply(this, arguments), this.listenTo(this.collection, "filter:change", this.clearNumbers), this.listenTo(this._model, "page:change", this.doAddMore); }, clearNumbers: function() { this.collection.length = 0, this._model.startAt = 0, this._model.perPage = this.list.maxRows; }, _addMore: function() { 0 !== this.collection.length && this._model.startAt >= this.collection.length || (this._model.startAt = this.list.lastRow, this._model.perPage = this.list.maxRows, this._model.startAt > 0 && this.collection.length > 0 && this.collection.length < this._model.startAt ? this.list.addMore(this.collection) : this._model.fetch()); }, autoFetchOnRender: function(a) { this._model.fetch(a); }, manualRefresh: function() { this.clearNumbers(), this.list.clear(), this.autoFetchOnRender(!0); }, doAddMore: function() { this.list.addMore(this.collection), this.updateCount(), this.spin(!1); } }), DBListControllerModel = Backbone.Model.extend({ initialize: function() { this.startAt = 0, this.perPage = 30, this.collection = this.get("collection") || new SortableCollection(); }, url: function() { return _.isFunction(this.collection.url) ? this.collection.url() : this.collection.url; }, fetch: function(a) { opts = {}, opts.data = { filters: this.collection.filters ? this.collection.filterVals(_.where(this.collection.filters, { db: !0 })) : null, sortKey: this.collection.sortKey(), sortDesc: this.collection.sortDesc(), startAt: this.startAt, perPage: this.perPage }, 0 == this.startAt && this.collection.trigger("spin"), opts.success = function(a, b) { this.addModels(b), this.collection.trigger("spin", !1); }.bind(this), opts.error = function(b, c) { this.collection.trigger("fetch:failed", this, c.statusText, this.fetch.bind(this, a)), this.collection.trigger("spin", !1); }.bind(this), Backbone.Model.prototype.fetch.call(this, opts); }, addModels: function(a) { if (void 0 == a.results) return console.error("No “results” key was found"); var b = a.results || a; 0 == this.startAt && this.collection.reset([], { silent: !0 }), this.collection.add(b, { merge: !0, silent: !0 }), a.results && a.count && (this.collection.length = a.count), (0 == b.length || this.collection.size() < this.startAt * this.perPage + this.perPage) && (this.collection.length = this.collection.size()), this.trigger("page:change"); } }), function() { var a = { setupFilters: function() { _.each(this.filters, function(a, b) { "divider" !== b && !b.match(/^divider/) && a.use && (ListController.Filters && ListController.Filters[a.use] ? this.filters[b] = _.extend({}, ListController.Filters[a.use], a) : console.error("ListController: “" + a.use + "” filter is not a globally defined filter. Please define it in ListController.Filters")); }.bind(this)); }, _renderFilterSelects: function() { return this.filters && _.find(this.filters, function(a) { return void 0 !== a.values; }) ? (this.$filterSelects ? this.$filterSelects.html("") : this.$filterSelects = $('<div class="filters"></div>'), _.each(this.activeFilters, this._renderActiveFilter, this), this._renderAddFilterBtn(), this.allowFilterQueueing && this.$filterSelects.append('<a class="btn primary apply-queued-filters" title="Apply Filters">Apply Filters</a>'), this.$filterSelects) : void 0; }, _renderAddFilterBtn: function() { var a = this, b = [], c = ""; this.foreachFilterNotInUse(function(d, e) { if ("divider" === e || e.match(/^divider/)) return c.match(/^divider/) || b.push(null === d ? "divider" : { divider: d }), void (c = e); if (c = e, (!d.permission || ListController.permission(d.permission)) && d.manual !== !0) { var f = d.label || _.humanize(e), g = a._onFilterChange.bind(a, e, !1), h = { filter: d, view: "function" == typeof d.values ? d.values.bind(d, this) : d.values, align: "rightBottom", w: d.w || 120, collection: "function" == typeof d.collection ? d.collection.bind(d, this) : d.collection, autoFetch: d.autoFetch || !1, description: d.description || "", border: d.border || null, onClick: g }; a.collectionSwitcherKey && a.collectionSwitcherKey == e && (a.collectionSwitcher && a.collectionSwitcher instanceof Backbone.Collection || console.warn("!! Filter", d, 'has "collectionSwitcher" set but no "collectionSwitcher" was found on the ListController'), h.collection = d.collection = a.collectionSwitcher, h.autoFetch = void 0 !== d.autoFetch ? d.autoFetch : !0), b.push({ label: f, dropdown: h }); } }), b.length > 0 && "divider" != b[0] && !b[0].divider && b.unshift({ divider: " " }), (this.filterPresets || this.allowPresets) && b.unshift({ label: "", title: "Filter Presets", val: "filter-presets", icon: "equalizer", dropdown: { view: this.presetMenu, w: 200, context: this, align: "rightBottom" } }), this.allowFilterQueueing && b.unshift({ label: "", val: "queue-filters", title: "Queue Filters: select desired filters before making request for the data.", icon: "layers", onClick: this.toggleQueueUpFilters.bind(this) }), b.unshift({ label: "", title: "Reset filters", val: "reset-filters", icon: "erase", onClick: this.resetFilters.bind(this) }), $('<a class="btn icon-only icon-filter" title="Apply new filter"></a>').appendTo(this.$filterSelects).dropdown(b, { align: "bottom", w: this.filterDropdownW, searchThreshold: 40 }); }, _renderActiveFilter: function(a, b, c) { var d = a.val, e = this.collection.getFilter(b).optional, f = this.filters[b]; if (f) { var g = _.isFunction(f.values) ? f.values.call(f, this) : f.values; if (_.isArray(d)) { var h = []; _.each(g, function(a) { _.contains(d, a.val) ? h.push(a.label) : null; }); var i = h.length > 0 ? { label: h.join(", ") } : null; } else var i = _.find(g, function(a) { return a.val == d; }); { this.collection.getFilter(b); } if (!i || f.alwaysUseManualVal) { var j = "Manual Filter"; if (f.manualVal) j = _.isFunction(f.manualVal) ? f.manualVal(this.collection.getFilter(b)) : f.manualVal; else { var k = this.collection.getFilter(b), l = k ? k.val : !1; j = _.keyToText(b) + ": " + l; } i = { label: j }; } var m = f.label || _.humanize(b), n = f.icon ? "icon-" + f.icon : "", o = f.prefix ? f.prefix : "", p = f.multi ? "multi" : "", g = f.values ? "function" == typeof f.values ? f.values.bind(f, this) : f.values : f.manual ? [ { label: "Clear", val: null } ] : null, q = e ? "optional" : "", r = [ n, p, q ].join(" "); if (this.collectionSwitcherKey == b && (r += " collection-switcher-btn"), f.optional && _.isArray(g) && (g = [].concat(g), g.push({ divider: "Options" }, { label: e ? "Make Required" : "Make Optional", val: !e, onClick: this._onFilterOptionalToggle.bind(this, b) })), 1 == c) return i.label; var s = this._onFilterChange.bind(this, b, e), t = { align: "bottom", w: f.w || 120, collection: "function" == typeof f.collection ? f.collection.bind(f, this) : f.collection, autoFetch: f.autoFetch || !1, description: f.description || "", selected: d, onClick: s, listController: this }; !t.collection || t.collection instanceof Backbone.Collection || (t.collection = t.collection()), this.collectionSwitcherKey && this.collectionSwitcherKey == b && (this.collectionSwitcher && this.collectionSwitcher instanceof Backbone.Collection || console.warn("!! Filter", f, 'has "collectionSwitcher" set but no "collectionSwitcher" was found on the ListController'), t.collection = f.collection = this.collectionSwitcher, t.autoFetch = void 0 !== f.autoFetch ? f.autoFetch : !0), $('<a class="btn filter-' + b + " " + r + '" title="Change ' + m + '"><span>' + o + i.label + "</span></a>").appendTo(this.$filterSelects).dropdown(g, t); } } }; _.extend(ListController.prototype, a), ListController.prototype.__listenerEvents.push([ "filter:change", "_renderFilterSelects" ]); }(), function() { var a = { allowDownload: !1, downloadName: null, resultExportDefaults: [ "sendToOnixGenerator", "divider", "saveToCSV" ], resultExports: { saveToCSV: { label: "Save list as CSV", title: "Download simple CSV file with the data in the current list", icon: "file-excel", onClick: function() { this.getCurrentCollection().saveToCSV(this.parentView ? _.stripTags(this.parentView.title) : "", { title: this.parentView ? _.stripTags(this.parentView.title) : "", description: this.activeFilterString(", ", !1) }); } }, digitalMetadata: { label: "Digital Metadata CSV", title: "Download legacy Digital Metadata CSV. Use with macros to format for specific partners.", icon: "file-excel", onClick: function() { var a = this.getCurrentCollection().pluck("book_id"); return a.length > 1500 ? Modal.alert("No can do", "You‘re trying to export too many books. Please refin