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
JavaScript
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