123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730 |
- /*
- * Fuel UX Repeater
- * https://github.com/ExactTarget/fuelux
- *
- * Copyright (c) 2014 ExactTarget
- * Licensed under the BSD New license.
- */
- // -- BEGIN UMD WRAPPER PREFACE --
- // For more information on UMD visit:
- // https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
- (function (factory) {
- if (typeof define === 'function' && define.amd) {
- // if AMD loader is available, register as an anonymous module.
- define(['jquery', 'fuelux/combobox', 'fuelux/infinite-scroll', 'fuelux/search', 'fuelux/selectlist'], factory);
- } else {
- // OR use browser globals if AMD is not present
- factory(jQuery);
- }
- }(function ($) {
- // -- END UMD WRAPPER PREFACE --
- // -- BEGIN MODULE CODE HERE --
- var old = $.fn.repeater;
- // REPEATER CONSTRUCTOR AND PROTOTYPE
- var Repeater = function (element, options) {
- var self = this;
- var $btn, currentView;
- this.$element = $(element);
- this.$canvas = this.$element.find('.repeater-canvas');
- this.$count = this.$element.find('.repeater-count');
- this.$end = this.$element.find('.repeater-end');
- this.$filters = this.$element.find('.repeater-filters');
- this.$loader = this.$element.find('.repeater-loader');
- this.$pageSize = this.$element.find('.repeater-itemization .selectlist');
- this.$nextBtn = this.$element.find('.repeater-next');
- this.$pages = this.$element.find('.repeater-pages');
- this.$prevBtn = this.$element.find('.repeater-prev');
- this.$primaryPaging = this.$element.find('.repeater-primaryPaging');
- this.$search = this.$element.find('.repeater-search').find('.search');
- this.$secondaryPaging = this.$element.find('.repeater-secondaryPaging');
- this.$start = this.$element.find('.repeater-start');
- this.$viewport = this.$element.find('.repeater-viewport');
- this.$views = this.$element.find('.repeater-views');
- this.currentPage = 0;
- this.currentView = null;
- this.infiniteScrollingCallback = function () {};
- this.infiniteScrollingCont = null;
- this.infiniteScrollingEnabled = false;
- this.infiniteScrollingEnd = null;
- this.infiniteScrollingOptions = {};
- this.lastPageInput = 0;
- this.options = $.extend({}, $.fn.repeater.defaults, options);
- this.pageIncrement = 0;// store direction navigated
- this.resizeTimeout = {};
- this.stamp = new Date().getTime() + (Math.floor(Math.random() * 100) + 1);
- this.storedDataSourceOpts = null;
- this.viewOptions = {};
- this.viewType = null;
- this.$filters.selectlist();
- this.$pageSize.selectlist();
- this.$primaryPaging.find('.combobox').combobox();
- this.$search.search();
- this.$filters.on('changed.fu.selectlist', function (e, value) {
- self.$element.trigger('filtered.fu.repeater', value);
- self.render({
- clearInfinite: true,
- pageIncrement: null
- });
- });
- this.$nextBtn.on('click.fu.repeater', $.proxy(this.next, this));
- this.$pageSize.on('changed.fu.selectlist', function (e, value) {
- self.$element.trigger('pageSizeChanged.fu.repeater', value);
- self.render({
- pageIncrement: null
- });
- });
- this.$prevBtn.on('click.fu.repeater', $.proxy(this.previous, this));
- this.$primaryPaging.find('.combobox').on('changed.fu.combobox', function (evt, data) {
- self.$element.trigger('pageChanged.fu.repeater', [data.text, data]);
- self.pageInputChange(data.text);
- });
- this.$search.on('searched.fu.search cleared.fu.search', function (e, value) {
- self.$element.trigger('searchChanged.fu.repeater', value);
- self.render({
- clearInfinite: true,
- pageIncrement: null
- });
- });
- this.$secondaryPaging.on('blur.fu.repeater', function (e) {
- self.pageInputChange(self.$secondaryPaging.val());
- });
- this.$secondaryPaging.on('keyup', function (e) {
- if (e.keyCode === 13) {
- self.pageInputChange(self.$secondaryPaging.val());
- }
- });
- this.$views.find('input').on('change.fu.repeater', $.proxy(this.viewChanged, this));
- // ID needed since event is bound to instance
- $(window).on('resize.fu.repeater.' + this.stamp, function (event) {
- clearTimeout(self.resizeTimeout);
- self.resizeTimeout = setTimeout(function () {
- self.resize();
- self.$element.trigger('resized.fu.repeater');
- }, 75);
- });
- this.$loader.loader();
- this.$loader.loader('pause');
- if (this.options.defaultView !== -1) {
- currentView = this.options.defaultView;
- } else {
- $btn = this.$views.find('label.active input');
- currentView = ($btn.length > 0) ? $btn.val() : 'list';
- }
- this.setViewOptions(currentView);
- this.initViewTypes(function () {
- self.resize();
- self.$element.trigger('resized.fu.repeater');
- self.render({
- changeView: currentView
- });
- });
- };
- Repeater.prototype = {
- constructor: Repeater,
- clear: function (options) {
- var viewChanged, viewTypeObj;
- function scan (cont) {
- var keep = [];
- cont.children().each(function () {
- var item = $(this);
- var pres = item.attr('data-preserve');
- if (pres === 'deep') {
- item.detach();
- keep.push(item);
- } else if (pres === 'shallow') {
- scan(item);
- item.detach();
- keep.push(item);
- }
- });
- cont.empty();
- cont.append(keep);
- }
- options = options || {};
- if (!options.preserve) {
- //Just trash everything because preserve is false
- this.$canvas.empty();
- } else if (!this.infiniteScrollingEnabled || options.clearInfinite) {
- //Preserve clear only if infiniteScrolling is disabled or if specifically told to do so
- scan(this.$canvas);
- } //Otherwise don't clear because infiniteScrolling is enabled
- //If viewChanged and current viewTypeObj has a cleared function, call it
- viewChanged = (options.viewChanged !== undefined) ? options.viewChanged : false;
- viewTypeObj = $.fn.repeater.viewTypes[this.viewType] || {};
- if (!viewChanged && viewTypeObj.cleared) {
- viewTypeObj.cleared.call(this, {
- options: options
- });
- }
- },
- clearPreservedDataSourceOptions: function () {
- this.storedDataSourceOpts = null;
- },
- destroy: function () {
- var markup;
- // set input value attrbute in markup
- this.$element.find('input').each(function () {
- $(this).attr('value', $(this).val());
- });
- // empty elements to return to original markup
- this.$canvas.empty();
- markup = this.$element[0].outerHTML;
- // destroy components and remove leftover
- this.$element.find('.combobox').combobox('destroy');
- this.$element.find('.selectlist').selectlist('destroy');
- this.$element.find('.search').search('destroy');
- if (this.infiniteScrollingEnabled) {
- $(this.infiniteScrollingCont).infinitescroll('destroy');
- }
- this.$element.remove();
- // any external events
- $(window).off('resize.fu.repeater.' + this.stamp);
- return markup;
- },
- getDataOptions: function (options) {
- var dataSourceOptions = {};
- var opts = {};
- var val, viewDataOpts;
- options = options || {};
- opts.filter = (this.$filters.length > 0) ? this.$filters.selectlist('selectedItem') : {
- text: 'All',
- value: 'all'
- };
- opts.view = this.currentView;
- if (!this.infiniteScrollingEnabled) {
- opts.pageSize = (this.$pageSize.length > 0) ? parseInt(this.$pageSize.selectlist('selectedItem').value, 10) : 25;
- }
- if (options.pageIncrement !== undefined) {
- if (options.pageIncrement === null) {
- this.currentPage = 0;
- } else {
- this.currentPage += options.pageIncrement;
- }
- }
- opts.pageIndex = this.currentPage;
- val = (this.$search.length > 0) ? this.$search.find('input').val() : '';
- if (val !== '') {
- opts.search = val;
- }
- if (options.dataSourceOptions) {
- dataSourceOptions = options.dataSourceOptions;
- if (options.preserveDataSourceOptions) {
- this.storedDataSourceOpts = (this.storedDataSourceOpts) ? $.extend(this.storedDataSourceOpts, dataSourceOptions) : dataSourceOptions;
- }
- }
- if (this.storedDataSourceOpts) {
- dataSourceOptions = $.extend(this.storedDataSourceOpts, dataSourceOptions);
- }
- viewDataOpts = $.fn.repeater.viewTypes[this.viewType] || {};
- viewDataOpts = viewDataOpts.dataOptions;
- if (viewDataOpts) {
- viewDataOpts = viewDataOpts.call(this, opts);
- opts = $.extend(viewDataOpts, dataSourceOptions);
- } else {
- opts = $.extend(opts, dataSourceOptions);
- }
- return opts;
- },
- infiniteScrolling: function (enable, options) {
- var itemization = this.$element.find('.repeater-itemization');
- var pagination = this.$element.find('.repeater-pagination');
- var cont, data;
- options = options || {};
- if (enable) {
- this.infiniteScrollingEnabled = true;
- this.infiniteScrollingEnd = options.end;
- delete options.dataSource;
- delete options.end;
- this.infiniteScrollingOptions = options;
- itemization.hide();
- pagination.hide();
- } else {
- cont = this.infiniteScrollingCont;
- data = cont.data();
- delete data.infinitescroll;
- cont.off('scroll');
- cont.removeClass('infinitescroll');
- this.infiniteScrollingCont = null;
- this.infiniteScrollingEnabled = false;
- this.infiniteScrollingEnd = null;
- this.infiniteScrollingOptions = {};
- itemization.show();
- pagination.show();
- }
- },
- infiniteScrollPaging: function (data, options) {
- var end = (this.infiniteScrollingEnd !== true) ? this.infiniteScrollingEnd : undefined;
- var page = data.page;
- var pages = data.pages;
- this.currentPage = (page !== undefined) ? page : NaN;
- if ((this.currentPage + 1) >= pages) {
- this.infiniteScrollingCont.infinitescroll('end', end);
- }
- },
- initInfiniteScrolling: function () {
- var cont = this.$canvas.find('[data-infinite="true"]:first');
- var opts, self;
- cont = (cont.length < 1) ? this.$canvas : cont;
- if (cont.data('fu.infinitescroll')) {
- cont.infinitescroll('enable');
- } else {
- self = this;
- opts = $.extend({}, this.infiniteScrollingOptions);
- opts.dataSource = function (helpers, callback) {
- self.infiniteScrollingCallback = callback;
- self.render({
- pageIncrement: 1
- });
- };
- cont.infinitescroll(opts);
- this.infiniteScrollingCont = cont;
- }
- },
- initViewTypes: function (callback) {
- var self = this;
- var viewTypes = [];
- var i, viewTypesLength;
- function init (index) {
- function next () {
- index++;
- if (index < viewTypesLength) {
- init(index);
- } else {
- callback();
- }
- }
- if (viewTypes[index].initialize) {
- viewTypes[index].initialize.call(self, {}, function () {
- next();
- });
- } else {
- next();
- }
- }
- for (i in $.fn.repeater.viewTypes) {
- viewTypes.push($.fn.repeater.viewTypes[i]);
- }
- viewTypesLength = viewTypes.length;
- if (viewTypesLength > 0) {
- init(0);
- } else {
- callback();
- }
- },
- itemization: function (data) {
- this.$count.html(data.count || '');
- this.$end.html(data.end || '');
- this.$start.html(data.start || '');
- },
- next: function (e) {
- var d = 'disabled';
- this.$nextBtn.attr(d, d);
- this.$prevBtn.attr(d, d);
- this.pageIncrement = 1;
- this.$element.trigger('nextClicked.fu.repeater');
- this.render({
- pageIncrement: this.pageIncrement
- });
- },
- pageInputChange: function (val) {
- var pageInc;
- if (val !== this.lastPageInput) {
- this.lastPageInput = val;
- val = parseInt(val, 10) - 1;
- pageInc = val - this.currentPage;
- this.$element.trigger('pageChanged.fu.repeater', val);
- this.render({
- pageIncrement: pageInc
- });
- }
- },
- pagination: function (data) {
- var act = 'active';
- var dsbl = 'disabled';
- var page = data.page;
- var pages = data.pages;
- var dropMenu, i, l;
- this.currentPage = (page !== undefined) ? page : NaN;
- this.$primaryPaging.removeClass(act);
- this.$secondaryPaging.removeClass(act);
- if (pages <= this.viewOptions.dropPagingCap) {
- this.$primaryPaging.addClass(act);
- dropMenu = this.$primaryPaging.find('.dropdown-menu');
- dropMenu.empty();
- for (i = 0; i < pages; i++) {
- l = i + 1;
- dropMenu.append('<li data-value="' + l + '"><a href="#">' + l + '</a></li>');
- }
- this.$primaryPaging.find('input.form-control').val(this.currentPage + 1);
- } else {
- this.$secondaryPaging.addClass(act);
- this.$secondaryPaging.val(this.currentPage + 1);
- }
- this.lastPageInput = this.currentPage + 1 + '';
- this.$pages.html(pages);
- // this is not the last page
- if ((this.currentPage + 1) < pages) {
- this.$nextBtn.removeAttr(dsbl);
- } else {
- this.$nextBtn.attr(dsbl, dsbl);
- }
- // this is not the first page
- if ((this.currentPage - 1) >= 0) {
- this.$prevBtn.removeAttr(dsbl);
- } else {
- this.$prevBtn.attr(dsbl, dsbl);
- }
- // return focus to next/previous buttons after navigating
- if (this.pageIncrement !== 0) {
- if (this.pageIncrement > 0) {
- if (this.$nextBtn.is(':disabled')) {
- // if you can't focus, go the other way
- this.$prevBtn.focus();
- } else {
- this.$nextBtn.focus();
- }
- } else {
- if (this.$prevBtn.is(':disabled')) {
- // if you can't focus, go the other way
- this.$nextBtn.focus();
- } else {
- this.$prevBtn.focus();
- }
- }
- }
- },
- previous: function () {
- var d = 'disabled';
- this.$nextBtn.attr(d, d);
- this.$prevBtn.attr(d, d);
- this.pageIncrement = -1;
- this.$element.trigger('previousClicked.fu.repeater');
- this.render({
- pageIncrement: this.pageIncrement
- });
- },
- render: function (options) {
- var self = this;
- var viewChanged = false;
- var viewTypeObj = $.fn.repeater.viewTypes[this.viewType] || {};
- var dataOptions, prevView;
- options = options || {};
- if (options.changeView && (this.currentView !== options.changeView)) {
- prevView = this.currentView;
- this.currentView = options.changeView;
- this.viewType = this.currentView.split('.')[0];
- this.setViewOptions(this.currentView);
- this.$element.attr('data-currentview', this.currentView);
- this.$element.attr('data-viewtype', this.viewType);
- viewChanged = true;
- options.viewChanged = viewChanged;
- this.$element.trigger('viewChanged.fu.repeater', this.currentView);
- if (this.infiniteScrollingEnabled) {
- self.infiniteScrolling(false);
- }
- viewTypeObj = $.fn.repeater.viewTypes[this.viewType] || {};
- if (viewTypeObj.selected) {
- viewTypeObj.selected.call(this, {
- prevView: prevView
- });
- }
- }
- options.preserve = (options.preserve !== undefined) ? options.preserve : !viewChanged;
- this.clear(options);
- if (!this.infiniteScrollingEnabled || (this.infiniteScrollingEnabled && viewChanged)) {
- this.$loader.show().loader('play');
- }
- dataOptions = this.getDataOptions(options);
- this.viewOptions.dataSource(dataOptions, function (data) {
- if (self.infiniteScrollingEnabled) {
- self.infiniteScrollingCallback({});
- } else {
- self.itemization(data);
- self.pagination(data);
- }
- self.runRenderer(viewTypeObj, data, function () {
- if (self.infiniteScrollingEnabled) {
- if (viewChanged || options.clearInfinite) {
- self.initInfiniteScrolling();
- }
- self.infiniteScrollPaging(data, options);
- }
- self.$loader.hide().loader('pause');
- self.$element.trigger('rendered.fu.repeater', {
- data: data,
- options: dataOptions,
- renderOptions: options
- });
- //for maintaining support of 'loaded' event
- self.$element.trigger('loaded.fu.repeater', dataOptions);
- });
- });
- },
- resize: function () {
- var staticHeight = (this.viewOptions.staticHeight === -1) ? this.$element.attr('data-staticheight') : this.viewOptions.staticHeight;
- var viewTypeObj = {};
- var height, viewportMargins;
- if (this.viewType) {
- viewTypeObj = $.fn.repeater.viewTypes[this.viewType] || {};
- }
- if (staticHeight !== undefined && staticHeight !== false && staticHeight !== 'false') {
- this.$canvas.addClass('scrolling');
- viewportMargins = {
- bottom: this.$viewport.css('margin-bottom'),
- top: this.$viewport.css('margin-top')
- };
- height = ((staticHeight === 'true' || staticHeight === true) ? this.$element.height() : parseInt(staticHeight, 10)) -
- this.$element.find('.repeater-header').outerHeight() -
- this.$element.find('.repeater-footer').outerHeight() -
- ((viewportMargins.bottom === 'auto') ? 0 : parseInt(viewportMargins.bottom, 10)) -
- ((viewportMargins.top === 'auto') ? 0 : parseInt(viewportMargins.top, 10));
- this.$viewport.outerHeight(height);
- } else {
- this.$canvas.removeClass('scrolling');
- }
- if (viewTypeObj.resize) {
- viewTypeObj.resize.call(this, {
- height: this.$element.outerHeight(),
- width: this.$element.outerWidth()
- });
- }
- },
- runRenderer: function (viewTypeObj, data, callback) {
- var $container, i, l, response, repeat, subset;
- function addItem ($parent, resp) {
- var action;
- if (resp) {
- action = (resp.action) ? resp.action : 'append';
- if (action !== 'none' && resp.item !== undefined) {
- $parent = (resp.container !== undefined) ? $(resp.container) : $parent;
- $parent[action](resp.item);
- }
- }
- }
- if (!viewTypeObj.render) {
- if (viewTypeObj.before) {
- response = viewTypeObj.before.call(this, {
- container: this.$canvas,
- data: data
- });
- addItem(this.$canvas, response);
- }
- $container = this.$canvas.find('[data-container="true"]:last');
- $container = ($container.length > 0) ? $container : this.$canvas;
- if (viewTypeObj.renderItem) {
- repeat = viewTypeObj.repeat || 'data.items';
- repeat = repeat.split('.');
- if (repeat[0] === 'data' || repeat[0] === 'this') {
- subset = (repeat[0] === 'this') ? this : data;
- repeat.shift();
- } else {
- repeat = [];
- subset = [];
- if (window.console && window.console.warn) {
- window.console.warn('WARNING: Repeater plugin "repeat" value must start with either "data" or "this"');
- }
- }
- for (i = 0, l = repeat.length; i < l; i++) {
- if (subset[repeat[i]] !== undefined){
- subset = subset[repeat[i]];
- } else {
- subset = [];
- if (window.console && window.console.warn) {
- window.console.warn('WARNING: Repeater unable to find property to iterate renderItem on.');
- }
- break;
- }
- }
- for (i = 0, l = subset.length; i < l; i++) {
- response = viewTypeObj.renderItem.call(this, {
- container: $container,
- data: data,
- index: i,
- subset: subset
- });
- addItem($container, response);
- }
- }
- if (viewTypeObj.after) {
- response = viewTypeObj.after.call(this, {
- container: this.$canvas,
- data: data
- });
- addItem(this.$canvas, response);
- }
- callback();
- } else {
- viewTypeObj.render.call(this, {
- container: this.$canvas,
- data: data
- }, function(){
- callback();
- });
- }
- },
- setViewOptions: function (curView) {
- var opts = {};
- var viewName = curView.split('.')[1];
- if (viewName && this.options.views) {
- opts = this.options.views[viewName] || this.options.views[curView] || {};
- } else {
- opts = {};
- }
- this.viewOptions = $.extend({}, this.options, opts);
- },
- viewChanged: function (e) {
- var $selected = $(e.target);
- var val = $selected.val();
- this.render({
- changeView: val,
- pageIncrement: null
- });
- }
- };
- // REPEATER PLUGIN DEFINITION
- $.fn.repeater = function (option) {
- var args = Array.prototype.slice.call(arguments, 1);
- var methodReturn;
- var $set = this.each(function () {
- var $this = $(this);
- var data = $this.data('fu.repeater');
- var options = typeof option === 'object' && option;
- if (!data) {
- $this.data('fu.repeater', (data = new Repeater(this, options)));
- }
- if (typeof option === 'string') {
- methodReturn = data[option].apply(data, args);
- }
- });
- return (methodReturn === undefined) ? $set : methodReturn;
- };
- $.fn.repeater.defaults = {
- dataSource: function (options, callback) {},
- defaultView: -1, //should be a string value. -1 means it will grab the active view from the view controls
- dropPagingCap: 10,
- staticHeight: -1, //normally true or false. -1 means it will look for data-staticheight on the element
- views: null //can be set to an object to configure multiple views of the same type
- };
- $.fn.repeater.viewTypes = {};
- $.fn.repeater.Constructor = Repeater;
- $.fn.repeater.noConflict = function () {
- $.fn.repeater = old;
- return this;
- };
- // -- BEGIN UMD WRAPPER AFTERWORD --
- }));
- // -- END UMD WRAPPER AFTERWORD --
|