You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ranger.apache.org by sn...@apache.org on 2014/08/15 00:09:24 UTC
[45/51] [partial] ARGUS-2 : Uploading eclipse support files and
additional lib/js files
http://git-wip-us.apache.org/repos/asf/incubator-argus/blob/185f7c50/security-admin/src/main/webapp/libs/bower/backbone-forms/js/list.js
----------------------------------------------------------------------
diff --git a/security-admin/src/main/webapp/libs/bower/backbone-forms/js/list.js b/security-admin/src/main/webapp/libs/bower/backbone-forms/js/list.js
new file mode 100644
index 0000000..082e099
--- /dev/null
+++ b/security-admin/src/main/webapp/libs/bower/backbone-forms/js/list.js
@@ -0,0 +1,655 @@
+define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Backbone) {
+
+ ;(function(Form) {
+
+ /**
+ * List editor
+ *
+ * An array editor. Creates a list of other editor items.
+ *
+ * Special options:
+ * @param {String} [options.schema.itemType] The editor type for each item in the list. Default: 'Text'
+ * @param {String} [options.schema.confirmDelete] Text to display in a delete confirmation dialog. If falsey, will not ask for confirmation.
+ */
+ Form.editors.List = Form.editors.Base.extend({
+
+ events: {
+ 'click [data-action="add"]': function(event) {
+ event.preventDefault();
+ this.addItem(null, true);
+ }
+ },
+
+ initialize: function(options) {
+ options = options || {};
+
+ var editors = Form.editors;
+
+ editors.Base.prototype.initialize.call(this, options);
+
+ var schema = this.schema;
+ if (!schema) throw "Missing required option 'schema'";
+
+ this.template = options.template || this.constructor.template;
+
+ //Determine the editor to use
+ this.Editor = (function() {
+ var type = schema.itemType;
+
+ //Default to Text
+ if (!type) return editors.Text;
+
+ //Use List-specific version if available
+ if (editors.List[type]) return editors.List[type];
+
+ //Or whichever was passed
+ return editors[type];
+ })();
+
+ this.items = [];
+ },
+
+ render: function() {
+ var self = this,
+ value = this.value || [];
+
+ //Create main element
+ var $el = $($.trim(this.template()));
+
+ //Store a reference to the list (item container)
+ this.$list = $el.is('[data-items]') ? $el : $el.find('[data-items]');
+
+ //Add existing items
+ if (value.length) {
+ _.each(value, function(itemValue) {
+ self.addItem(itemValue);
+ });
+ }
+
+ //If no existing items create an empty one, unless the editor specifies otherwise
+ else {
+ if (!this.Editor.isAsync) this.addItem();
+ }
+
+ this.setElement($el);
+ this.$el.attr('id', this.id);
+ this.$el.attr('name', this.key);
+
+ if (this.hasFocus) this.trigger('blur', this);
+
+ return this;
+ },
+
+ /**
+ * Add a new item to the list
+ * @param {Mixed} [value] Value for the new item editor
+ * @param {Boolean} [userInitiated] If the item was added by the user clicking 'add'
+ */
+ addItem: function(value, userInitiated) {
+ var self = this,
+ editors = Form.editors;
+
+ //Create the item
+ var item = new editors.List.Item({
+ list: this,
+ form: this.form,
+ schema: this.schema,
+ value: value,
+ Editor: this.Editor,
+ key: this.key
+ }).render();
+
+ var _addItem = function() {
+ self.items.push(item);
+ self.$list.append(item.el);
+
+ item.editor.on('all', function(event) {
+ if (event === 'change') return;
+
+ // args = ["key:change", itemEditor, fieldEditor]
+ var args = _.toArray(arguments);
+ args[0] = 'item:' + event;
+ args.splice(1, 0, self);
+ // args = ["item:key:change", this=listEditor, itemEditor, fieldEditor]
+
+ editors.List.prototype.trigger.apply(this, args);
+ }, self);
+
+ item.editor.on('change', function() {
+ if (!item.addEventTriggered) {
+ item.addEventTriggered = true;
+ this.trigger('add', this, item.editor);
+ }
+ this.trigger('item:change', this, item.editor);
+ this.trigger('change', this);
+ }, self);
+
+ item.editor.on('focus', function() {
+ if (this.hasFocus) return;
+ this.trigger('focus', this);
+ }, self);
+ item.editor.on('blur', function() {
+ if (!this.hasFocus) return;
+ var self = this;
+ setTimeout(function() {
+ if (_.find(self.items, function(item) { return item.editor.hasFocus; })) return;
+ self.trigger('blur', self);
+ }, 0);
+ }, self);
+
+ if (userInitiated || value) {
+ item.addEventTriggered = true;
+ }
+
+ if (userInitiated) {
+ self.trigger('add', self, item.editor);
+ self.trigger('change', self);
+ }
+ };
+
+ //Check if we need to wait for the item to complete before adding to the list
+ if (this.Editor.isAsync) {
+ item.editor.on('readyToAdd', _addItem, this);
+ }
+
+ //Most editors can be added automatically
+ else {
+ _addItem();
+ item.editor.focus();
+ }
+
+ return item;
+ },
+
+ /**
+ * Remove an item from the list
+ * @param {List.Item} item
+ */
+ removeItem: function(item) {
+ //Confirm delete
+ var confirmMsg = this.schema.confirmDelete;
+ if (confirmMsg && !confirm(confirmMsg)) return;
+
+ var index = _.indexOf(this.items, item);
+
+ this.items[index].remove();
+ this.items.splice(index, 1);
+
+ if (item.addEventTriggered) {
+ this.trigger('remove', this, item.editor);
+ this.trigger('change', this);
+ }
+
+ if (!this.items.length && !this.Editor.isAsync) this.addItem();
+ },
+
+ getValue: function() {
+ var values = _.map(this.items, function(item) {
+ return item.getValue();
+ });
+
+ //Filter empty items
+ return _.without(values, undefined, '');
+ },
+
+ setValue: function(value) {
+ this.value = value;
+ this.render();
+ },
+
+ focus: function() {
+ if (this.hasFocus) return;
+
+ if (this.items[0]) this.items[0].editor.focus();
+ },
+
+ blur: function() {
+ if (!this.hasFocus) return;
+
+ var focusedItem = _.find(this.items, function(item) { return item.editor.hasFocus; });
+
+ if (focusedItem) focusedItem.editor.blur();
+ },
+
+ /**
+ * Override default remove function in order to remove item views
+ */
+ remove: function() {
+ _.invoke(this.items, 'remove');
+
+ Form.editors.Base.prototype.remove.call(this);
+ },
+
+ /**
+ * Run validation
+ *
+ * @return {Object|Null}
+ */
+ validate: function() {
+ if (!this.validators) return null;
+
+ //Collect errors
+ var errors = _.map(this.items, function(item) {
+ return item.validate();
+ });
+
+ //Check if any item has errors
+ var hasErrors = _.compact(errors).length ? true : false;
+ if (!hasErrors) return null;
+
+ //If so create a shared error
+ var fieldError = {
+ type: 'list',
+ message: 'Some of the items in the list failed validation',
+ errors: errors
+ };
+
+ return fieldError;
+ }
+ }, {
+
+ //STATICS
+ template: _.template('\
+ <div>\
+ <div data-items></div>\
+ <button type="button" data-action="add">Add</button>\
+ </div>\
+ ', null, Form.templateSettings)
+
+ });
+
+
+ /**
+ * A single item in the list
+ *
+ * @param {editors.List} options.list The List editor instance this item belongs to
+ * @param {Function} options.Editor Editor constructor function
+ * @param {String} options.key Model key
+ * @param {Mixed} options.value Value
+ * @param {Object} options.schema Field schema
+ */
+ Form.editors.List.Item = Form.editors.Base.extend({
+
+ events: {
+ 'click [data-action="remove"]': function(event) {
+ event.preventDefault();
+ this.list.removeItem(this);
+ },
+ 'keydown input[type=text]': function(event) {
+ if(event.keyCode !== 13) return;
+ event.preventDefault();
+ this.list.addItem();
+ this.list.$list.find("> li:last input").focus();
+ }
+ },
+
+ initialize: function(options) {
+ this.list = options.list;
+ this.schema = options.schema || this.list.schema;
+ this.value = options.value;
+ this.Editor = options.Editor || Form.editors.Text;
+ this.key = options.key;
+ this.template = options.template || this.constructor.template;
+ this.errorClassName = options.errorClassName || this.constructor.errorClassName;
+ this.form = options.form;
+ },
+
+ render: function() {
+ //Create editor
+ this.editor = new this.Editor({
+ key: this.key,
+ schema: this.schema,
+ value: this.value,
+ list: this.list,
+ item: this,
+ form: this.form
+ }).render();
+
+ //Create main element
+ var $el = $($.trim(this.template()));
+
+ $el.find('[data-editor]').append(this.editor.el);
+
+ //Replace the entire element so there isn't a wrapper tag
+ this.setElement($el);
+
+ return this;
+ },
+
+ getValue: function() {
+ return this.editor.getValue();
+ },
+
+ setValue: function(value) {
+ this.editor.setValue(value);
+ },
+
+ focus: function() {
+ this.editor.focus();
+ },
+
+ blur: function() {
+ this.editor.blur();
+ },
+
+ remove: function() {
+ this.editor.remove();
+
+ Backbone.View.prototype.remove.call(this);
+ },
+
+ validate: function() {
+ var value = this.getValue(),
+ formValues = this.list.form ? this.list.form.getValue() : {},
+ validators = this.schema.validators,
+ getValidator = this.getValidator;
+
+ if (!validators) return null;
+
+ //Run through validators until an error is found
+ var error = null;
+ _.every(validators, function(validator) {
+ error = getValidator(validator)(value, formValues);
+
+ return error ? false : true;
+ });
+
+ //Show/hide error
+ if (error){
+ this.setError(error);
+ } else {
+ this.clearError();
+ }
+
+ //Return error to be aggregated by list
+ return error ? error : null;
+ },
+
+ /**
+ * Show a validation error
+ */
+ setError: function(err) {
+ this.$el.addClass(this.errorClassName);
+ this.$el.attr('title', err.message);
+ },
+
+ /**
+ * Hide validation errors
+ */
+ clearError: function() {
+ this.$el.removeClass(this.errorClassName);
+ this.$el.attr('title', null);
+ }
+ }, {
+
+ //STATICS
+ template: _.template('\
+ <div>\
+ <span data-editor></span>\
+ <button type="button" data-action="remove">×</button>\
+ </div>\
+ ', null, Form.templateSettings),
+
+ errorClassName: 'error'
+
+ });
+
+
+ /**
+ * Base modal object editor for use with the List editor; used by Object
+ * and NestedModal list types
+ */
+ Form.editors.List.Modal = Form.editors.Base.extend({
+
+ events: {
+ 'click': 'openEditor'
+ },
+
+ /**
+ * @param {Object} options
+ * @param {Form} options.form The main form
+ * @param {Function} [options.schema.itemToString] Function to transform the value for display in the list.
+ * @param {String} [options.schema.itemType] Editor type e.g. 'Text', 'Object'.
+ * @param {Object} [options.schema.subSchema] Schema for nested form,. Required when itemType is 'Object'
+ * @param {Function} [options.schema.model] Model constructor function. Required when itemType is 'NestedModel'
+ */
+ initialize: function(options) {
+ options = options || {};
+
+ Form.editors.Base.prototype.initialize.call(this, options);
+
+ //Dependencies
+ if (!Form.editors.List.Modal.ModalAdapter) throw 'A ModalAdapter is required';
+
+ this.form = options.form;
+ if (!options.form) throw 'Missing required option: "form"';
+
+ //Template
+ this.template = options.template || this.constructor.template;
+ },
+
+ /**
+ * Render the list item representation
+ */
+ render: function() {
+ var self = this;
+
+ //New items in the list are only rendered when the editor has been OK'd
+ if (_.isEmpty(this.value)) {
+ this.openEditor();
+ }
+
+ //But items with values are added automatically
+ else {
+ this.renderSummary();
+
+ setTimeout(function() {
+ self.trigger('readyToAdd');
+ }, 0);
+ }
+
+ if (this.hasFocus) this.trigger('blur', this);
+
+ return this;
+ },
+
+ /**
+ * Renders the list item representation
+ */
+ renderSummary: function() {
+ this.$el.html($.trim(this.template({
+ summary: this.getStringValue()
+ })));
+ },
+
+ /**
+ * Function which returns a generic string representation of an object
+ *
+ * @param {Object} value
+ *
+ * @return {String}
+ */
+ itemToString: function(value) {
+ var createTitle = function(key) {
+ var context = { key: key };
+
+ return Form.Field.prototype.createTitle.call(context);
+ };
+
+ value = value || {};
+
+ //Pretty print the object keys and values
+ var parts = [];
+ _.each(this.nestedSchema, function(schema, key) {
+ var desc = schema.title ? schema.title : createTitle(key),
+ val = value[key];
+
+ if (_.isUndefined(val) || _.isNull(val)) val = '';
+
+ parts.push(desc + ': ' + val);
+ });
+
+ return parts.join('<br />');
+ },
+
+ /**
+ * Returns the string representation of the object value
+ */
+ getStringValue: function() {
+ var schema = this.schema,
+ value = this.getValue();
+
+ if (_.isEmpty(value)) return '[Empty]';
+
+ //If there's a specified toString use that
+ if (schema.itemToString) return schema.itemToString(value);
+
+ //Otherwise use the generic method or custom overridden method
+ return this.itemToString(value);
+ },
+
+ openEditor: function() {
+ var self = this,
+ ModalForm = this.form.constructor;
+
+ var form = this.modalForm = new ModalForm({
+ schema: this.nestedSchema,
+ data: this.value
+ });
+
+ var modal = this.modal = new Form.editors.List.Modal.ModalAdapter({
+ content: form,
+ animate: true
+ });
+
+ modal.open();
+
+ this.trigger('open', this);
+ this.trigger('focus', this);
+
+ modal.on('cancel', this.onModalClosed, this);
+
+ modal.on('ok', _.bind(this.onModalSubmitted, this));
+ },
+
+ /**
+ * Called when the user clicks 'OK'.
+ * Runs validation and tells the list when ready to add the item
+ */
+ onModalSubmitted: function() {
+ var modal = this.modal,
+ form = this.modalForm,
+ isNew = !this.value;
+
+ //Stop if there are validation errors
+ var error = form.validate();
+ if (error) return modal.preventClose();
+
+ //Store form value
+ this.value = form.getValue();
+
+ //Render item
+ this.renderSummary();
+
+ if (isNew) this.trigger('readyToAdd');
+
+ this.trigger('change', this);
+
+ this.onModalClosed();
+ },
+
+ /**
+ * Cleans up references, triggers events. To be called whenever the modal closes
+ */
+ onModalClosed: function() {
+ this.modal = null;
+ this.modalForm = null;
+
+ this.trigger('close', this);
+ this.trigger('blur', this);
+ },
+
+ getValue: function() {
+ return this.value;
+ },
+
+ setValue: function(value) {
+ this.value = value;
+ },
+
+ focus: function() {
+ if (this.hasFocus) return;
+
+ this.openEditor();
+ },
+
+ blur: function() {
+ if (!this.hasFocus) return;
+
+ if (this.modal) {
+ this.modal.trigger('cancel');
+ }
+ }
+ }, {
+ //STATICS
+ template: _.template('\
+ <div><%= summary %></div>\
+ '),
+
+ //The modal adapter that creates and manages the modal dialog.
+ //Defaults to BootstrapModal (http://github.com/powmedia/backbone.bootstrap-modal)
+ //Can be replaced with another adapter that implements the same interface.
+ ModalAdapter: Backbone.BootstrapModal,
+
+ //Make the wait list for the 'ready' event before adding the item to the list
+ isAsync: true
+ });
+
+
+ Form.editors.List.Object = Form.editors.List.Modal.extend({
+ initialize: function () {
+ Form.editors.List.Modal.prototype.initialize.apply(this, arguments);
+
+ var schema = this.schema;
+
+ if (!schema.subSchema) throw 'Missing required option "schema.subSchema"';
+
+ this.nestedSchema = schema.subSchema;
+ }
+ });
+
+
+ Form.editors.List.NestedModel = Form.editors.List.Modal.extend({
+ initialize: function() {
+ Form.editors.List.Modal.prototype.initialize.apply(this, arguments);
+
+ var schema = this.schema;
+
+ if (!schema.model) throw 'Missing required option "schema.model"';
+
+ var nestedSchema = schema.model.prototype.schema;
+
+ this.nestedSchema = (_.isFunction(nestedSchema)) ? nestedSchema() : nestedSchema;
+ },
+
+ /**
+ * Returns the string representation of the object value
+ */
+ getStringValue: function() {
+ var schema = this.schema,
+ value = this.getValue();
+
+ if (_.isEmpty(value)) return null;
+
+ //If there's a specified toString use that
+ if (schema.itemToString) return schema.itemToString(value);
+
+ //Otherwise use the model
+ return new (schema.model)(value).toString();
+ }
+ });
+
+})(Backbone.Form);
+
+
+});
http://git-wip-us.apache.org/repos/asf/incubator-argus/blob/185f7c50/security-admin/src/main/webapp/libs/bower/backbone-forms/js/list.min.js
----------------------------------------------------------------------
diff --git a/security-admin/src/main/webapp/libs/bower/backbone-forms/js/list.min.js b/security-admin/src/main/webapp/libs/bower/backbone-forms/js/list.min.js
new file mode 100644
index 0000000..ce7eb66
--- /dev/null
+++ b/security-admin/src/main/webapp/libs/bower/backbone-forms/js/list.min.js
@@ -0,0 +1 @@
+define(["jquery","underscore","backbone","backbone-forms"],function(e,t,n){(function(r){r.editors.List=r.editors.Base.extend({events:{'click [data-action="add"]':function(e){e.preventDefault(),this.addItem(null,!0)}},initialize:function(e){e=e||{};var t=r.editors;t.Base.prototype.initialize.call(this,e);var n=this.schema;if(!n)throw"Missing required option 'schema'";this.template=e.template||this.constructor.template,this.Editor=function(){var e=n.itemType;return e?t.List[e]?t.List[e]:t[e]:t.Text}(),this.items=[]},render:function(){var n=this,r=this.value||[],i=e(e.trim(this.template()));return this.$list=i.is("[data-items]")?i:i.find("[data-items]"),r.length?t.each(r,function(e){n.addItem(e)}):this.Editor.isAsync||this.addItem(),this.setElement(i),this.$el.attr("id",this.id),this.$el.attr("name",this.key),this.hasFocus&&this.trigger("blur",this),this},addItem:function(e,n){var i=this,s=r.editors,o=(new s.List.Item({list:this,form:this.form,schema:this.schema,value:e,Editor:this.Edi
tor,key:this.key})).render(),u=function(){i.items.push(o),i.$list.append(o.el),o.editor.on("all",function(e){if(e==="change")return;var n=t.toArray(arguments);n[0]="item:"+e,n.splice(1,0,i),s.List.prototype.trigger.apply(this,n)},i),o.editor.on("change",function(){o.addEventTriggered||(o.addEventTriggered=!0,this.trigger("add",this,o.editor)),this.trigger("item:change",this,o.editor),this.trigger("change",this)},i),o.editor.on("focus",function(){if(this.hasFocus)return;this.trigger("focus",this)},i),o.editor.on("blur",function(){if(!this.hasFocus)return;var e=this;setTimeout(function(){if(t.find(e.items,function(e){return e.editor.hasFocus}))return;e.trigger("blur",e)},0)},i);if(n||e)o.addEventTriggered=!0;n&&(i.trigger("add",i,o.editor),i.trigger("change",i))};return this.Editor.isAsync?o.editor.on("readyToAdd",u,this):(u(),o.editor.focus()),o},removeItem:function(e){var n=this.schema.confirmDelete;if(n&&!confirm(n))return;var r=t.indexOf(this.items,e);this.items[r].remove(),this.i
tems.splice(r,1),e.addEventTriggered&&(this.trigger("remove",this,e.editor),this.trigger("change",this)),!this.items.length&&!this.Editor.isAsync&&this.addItem()},getValue:function(){var e=t.map(this.items,function(e){return e.getValue()});return t.without(e,undefined,"")},setValue:function(e){this.value=e,this.render()},focus:function(){if(this.hasFocus)return;this.items[0]&&this.items[0].editor.focus()},blur:function(){if(!this.hasFocus)return;var e=t.find(this.items,function(e){return e.editor.hasFocus});e&&e.editor.blur()},remove:function(){t.invoke(this.items,"remove"),r.editors.Base.prototype.remove.call(this)},validate:function(){if(!this.validators)return null;var e=t.map(this.items,function(e){return e.validate()}),n=t.compact(e).length?!0:!1;if(!n)return null;var r={type:"list",message:"Some of the items in the list failed validation",errors:e};return r}},{template:t.template(' <div> <div data-items></div> <button type="button" data-action="add">Add</but
ton> </div> ',null,r.templateSettings)}),r.editors.List.Item=r.editors.Base.extend({events:{'click [data-action="remove"]':function(e){e.preventDefault(),this.list.removeItem(this)},"keydown input[type=text]":function(e){if(e.keyCode!==13)return;e.preventDefault(),this.list.addItem(),this.list.$list.find("> li:last input").focus()}},initialize:function(e){this.list=e.list,this.schema=e.schema||this.list.schema,this.value=e.value,this.Editor=e.Editor||r.editors.Text,this.key=e.key,this.template=e.template||this.constructor.template,this.errorClassName=e.errorClassName||this.constructor.errorClassName,this.form=e.form},render:function(){this.editor=(new this.Editor({key:this.key,schema:this.schema,value:this.value,list:this.list,item:this,form:this.form})).render();var t=e(e.trim(this.template()));return t.find("[data-editor]").append(this.editor.el),this.setElement(t),this},getValue:function(){return this.editor.getValue()},setValue:function(e){this.editor.setValue(e)},focus:
function(){this.editor.focus()},blur:function(){this.editor.blur()},remove:function(){this.editor.remove(),n.View.prototype.remove.call(this)},validate:function(){var e=this.getValue(),n=this.list.form?this.list.form.getValue():{},r=this.schema.validators,i=this.getValidator;if(!r)return null;var s=null;return t.every(r,function(t){return s=i(t)(e,n),s?!1:!0}),s?this.setError(s):this.clearError(),s?s:null},setError:function(e){this.$el.addClass(this.errorClassName),this.$el.attr("title",e.message)},clearError:function(){this.$el.removeClass(this.errorClassName),this.$el.attr("title",null)}},{template:t.template(' <div> <span data-editor></span> <button type="button" data-action="remove">×</button> </div> ',null,r.templateSettings),errorClassName:"error"}),r.editors.List.Modal=r.editors.Base.extend({events:{click:"openEditor"},initialize:function(e){e=e||{},r.editors.Base.prototype.initialize.call(this,e);if(!r.editors.List.Modal.ModalAdapter)throw"A
ModalAdapter is required";this.form=e.form;if(!e.form)throw'Missing required option: "form"';this.template=e.template||this.constructor.template},render:function(){var e=this;return t.isEmpty(this.value)?this.openEditor():(this.renderSummary(),setTimeout(function(){e.trigger("readyToAdd")},0)),this.hasFocus&&this.trigger("blur",this),this},renderSummary:function(){this.$el.html(e.trim(this.template({summary:this.getStringValue()})))},itemToString:function(e){var n=function(e){var t={key:e};return r.Field.prototype.createTitle.call(t)};e=e||{};var i=[];return t.each(this.nestedSchema,function(r,s){var o=r.title?r.title:n(s),u=e[s];if(t.isUndefined(u)||t.isNull(u))u="";i.push(o+": "+u)}),i.join("<br />")},getStringValue:function(){var e=this.schema,n=this.getValue();return t.isEmpty(n)?"[Empty]":e.itemToString?e.itemToString(n):this.itemToString(n)},openEditor:function(){var e=this,n=this.form.constructor,i=this.modalForm=new n({schema:this.nestedSchema,data:this.value}),s=this.modal=
new r.editors.List.Modal.ModalAdapter({content:i,animate:!0});s.open(),this.trigger("open",this),this.trigger("focus",this),s.on("cancel",this.onModalClosed,this),s.on("ok",t.bind(this.onModalSubmitted,this))},onModalSubmitted:function(){var e=this.modal,t=this.modalForm,n=!this.value,r=t.validate();if(r)return e.preventClose();this.value=t.getValue(),this.renderSummary(),n&&this.trigger("readyToAdd"),this.trigger("change",this),this.onModalClosed()},onModalClosed:function(){this.modal=null,this.modalForm=null,this.trigger("close",this),this.trigger("blur",this)},getValue:function(){return this.value},setValue:function(e){this.value=e},focus:function(){if(this.hasFocus)return;this.openEditor()},blur:function(){if(!this.hasFocus)return;this.modal&&this.modal.trigger("cancel")}},{template:t.template(" <div><%= summary %></div> "),ModalAdapter:n.BootstrapModal,isAsync:!0}),r.editors.List.Object=r.editors.List.Modal.extend({initialize:function(){r.editors.List.Modal.prototype.in
itialize.apply(this,arguments);var e=this.schema;if(!e.subSchema)throw'Missing required option "schema.subSchema"';this.nestedSchema=e.subSchema}}),r.editors.List.NestedModel=r.editors.List.Modal.extend({initialize:function(){r.editors.List.Modal.prototype.initialize.apply(this,arguments);var e=this.schema;if(!e.model)throw'Missing required option "schema.model"';var n=e.model.prototype.schema;this.nestedSchema=t.isFunction(n)?n():n},getStringValue:function(){var e=this.schema,n=this.getValue();return t.isEmpty(n)?null:e.itemToString?e.itemToString(n):(new e.model(n)).toString()}})})(n.Form)})
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-argus/blob/185f7c50/security-admin/src/main/webapp/libs/bower/backbone-forms/js/old.js
----------------------------------------------------------------------
diff --git a/security-admin/src/main/webapp/libs/bower/backbone-forms/js/old.js b/security-admin/src/main/webapp/libs/bower/backbone-forms/js/old.js
new file mode 100644
index 0000000..e3efe55
--- /dev/null
+++ b/security-admin/src/main/webapp/libs/bower/backbone-forms/js/old.js
@@ -0,0 +1,91 @@
+/**
+ * Include this template file after backbone-forms.amd.js to override the default templates
+ *
+ * 'data-*' attributes control where elements are placed
+ */
+define(['jquery', 'underscore', 'backbone', 'backbone-forms'], function($, _, Backbone) {
+ var Form = Backbone.Form;
+
+
+ /**
+ * Templates to match those used previous versions of Backbone Form, i.e. <= 0.11.0.
+ * NOTE: These templates are deprecated.
+ */
+ Form.template = _.template('\
+ <form class="bbf-form" data-fieldsets></form>\
+ ');
+
+
+ Form.Fieldset.template = _.template('\
+ <fieldset>\
+ <% if (legend) { %>\
+ <legend><%= legend %></legend>\
+ <% } %>\
+ <ul data-fields></ul>\
+ </fieldset>\
+ ');
+
+
+ Form.Field.template = _.template('\
+ <li class="bbf-field field-<%= key %>">\
+ <label for="<%= editorId %>"><%= title %></label>\
+ <div class="bbf-editor" data-editor></div>\
+ <div class="bbf-help"><%= help %></div>\
+ <div class="bbf-error" data-error></div>\
+ </li>\
+ ');
+
+
+ Form.NestedField.template = _.template('\
+ <li class="bbf-field bbf-nested-field field-<%= key %>">\
+ <label for="<%= editorId %>"><%= title %></label>\
+ <div class="bbf-editor" data-editor></div>\
+ <div class="bbf-help"><%= help %></div>\
+ <div class="bbf-error" data-error></div>\
+ </li>\
+ ');
+
+
+ Form.editors.Date.template = _.template('\
+ <div class="bbf-date">\
+ <select class="bbf-date" data-type="date"><%= dates %></select>\
+ <select class="bbf-month" data-type="month"><%= months %></select>\
+ <select class="bbf-year" data-type="year"><%= years %></select>\
+ </div>\
+ ');
+
+
+ Form.editors.DateTime.template = _.template('\
+ <div class="bbf-datetime">\
+ <div class="bbf-date-container" data-date></div>\
+ <select data-type="hour"><%= hours %></select>\
+ :\
+ <select data-type="min"><%= mins %></select>\
+ </div>\
+ ');
+
+
+ Form.editors.List.template = _.template('\
+ <div class="bbf-list">\
+ <ul data-items></ul>\
+ <div class="bbf-actions"><button type="button" data-action="add">Add</div>\
+ </div>\
+ ');
+
+
+ Form.editors.List.Item.template = _.template('\
+ <li>\
+ <button type="button" data-action="remove" class="bbf-remove">×</button>\
+ <div class="bbf-editor-container" data-editor></div>\
+ </li>\
+ ');
+
+
+ Form.editors.List.Modal.template = _.template('\
+ <div class="bbf-list-modal">\
+ <%= summary %>\
+ </div>\
+ ');
+
+
+});
http://git-wip-us.apache.org/repos/asf/incubator-argus/blob/185f7c50/security-admin/src/main/webapp/libs/bower/backbone-pageable/js/backbone-pageable.js
----------------------------------------------------------------------
diff --git a/security-admin/src/main/webapp/libs/bower/backbone-pageable/js/backbone-pageable.js b/security-admin/src/main/webapp/libs/bower/backbone-pageable/js/backbone-pageable.js
new file mode 100644
index 0000000..9accadd
--- /dev/null
+++ b/security-admin/src/main/webapp/libs/bower/backbone-pageable/js/backbone-pageable.js
@@ -0,0 +1,1327 @@
+/*
+ backbone-pageable 1.3.2
+ http://github.com/wyuenho/backbone-pageable
+
+ Copyright (c) 2013 Jimmy Yuen Ho Wong
+ Licensed under the MIT @license.
+*/
+
+(function (factory) {
+
+ // CommonJS
+ if (typeof exports == "object") {
+ module.exports = factory(require("underscore"), require("backbone"));
+ }
+ // AMD
+ else if (typeof define == "function" && define.amd) {
+ define(["underscore", "backbone"], factory);
+ }
+ // Browser
+ else if (typeof _ !== "undefined" && typeof Backbone !== "undefined") {
+ var oldPageableCollection = Backbone.PageableCollection;
+ var PageableCollection = factory(_, Backbone);
+
+ /**
+ __BROWSER ONLY__
+
+ If you already have an object named `PageableCollection` attached to the
+ `Backbone` module, you can use this to return a local reference to this
+ Backbone.PageableCollection class and reset the name
+ Backbone.PageableCollection to its previous definition.
+
+ // The left hand side gives you a reference to this
+ // Backbone.PageableCollection implementation, the right hand side
+ // resets Backbone.PageableCollection to your other
+ // Backbone.PageableCollection.
+ var PageableCollection = Backbone.PageableCollection.noConflict();
+
+ @static
+ @member Backbone.PageableCollection
+ @return {Backbone.PageableCollection}
+ */
+ Backbone.PageableCollection.noConflict = function () {
+ Backbone.PageableCollection = oldPageableCollection;
+ return PageableCollection;
+ };
+ }
+
+}(function (_, Backbone) {
+
+ "use strict";
+
+ var _extend = _.extend;
+ var _omit = _.omit;
+ var _clone = _.clone;
+ var _each = _.each;
+ var _pick = _.pick;
+ var _contains = _.contains;
+ var _isEmpty = _.isEmpty;
+ var _pairs = _.pairs;
+ var _invert = _.invert;
+ var _isArray = _.isArray;
+ var _isFunction = _.isFunction;
+ var _isObject = _.isObject;
+ var _keys = _.keys;
+ var _isUndefined = _.isUndefined;
+ var _result = _.result;
+ var ceil = Math.ceil;
+ var floor = Math.floor;
+ var max = Math.max;
+
+ var BBColProto = Backbone.Collection.prototype;
+
+ function finiteInt (val, name) {
+ if (!_.isNumber(val) || _.isNaN(val) || !_.isFinite(val) || ~~val !== val) {
+ throw new TypeError("`" + name + "` must be a finite integer");
+ }
+ return val;
+ }
+
+ function queryStringToParams (qs) {
+ var kvp, k, v, ls, params = {}, decode = decodeURIComponent;
+ var kvps = qs.split('&');
+ for (var i = 0, l = kvps.length; i < l; i++) {
+ var param = kvps[i];
+ kvp = param.split('='), k = kvp[0], v = kvp[1] || true;
+ k = decode(k), ls = params[k];
+ if (_isArray(ls)) ls.push(v);
+ else if (ls) params[k] = [ls, v];
+ else params[k] = v;
+ }
+ return params;
+ }
+
+ var PARAM_TRIM_RE = /[\s'"]/g;
+ var URL_TRIM_RE = /[<>\s'"]/g;
+
+ /**
+ Drop-in replacement for Backbone.Collection. Supports server-side and
+ client-side pagination and sorting. Client-side mode also support fully
+ multi-directional synchronization of changes between pages.
+
+ @class Backbone.PageableCollection
+ @extends Backbone.Collection
+ */
+ var PageableCollection = Backbone.PageableCollection = Backbone.Collection.extend({
+
+ /**
+ The container object to store all pagination states.
+
+ You can override the default state by extending this class or specifying
+ them in an `options` hash to the constructor.
+
+ @property {Object} state
+
+ @property {0|1} [state.firstPage=1] The first page index. Set to 0 if
+ your server API uses 0-based indices. You should only override this value
+ during extension, initialization or reset by the server after
+ fetching. This value should be read only at other times.
+
+ @property {number} [state.lastPage=null] The last page index. This value
+ is __read only__ and it's calculated based on whether `firstPage` is 0 or
+ 1, during bootstrapping, fetching and resetting. Please don't change this
+ value under any circumstances.
+
+ @property {number} [state.currentPage=null] The current page index. You
+ should only override this value during extension, initialization or reset
+ by the server after fetching. This value should be read only at other
+ times. Can be a 0-based or 1-based index, depending on whether
+ `firstPage` is 0 or 1. If left as default, it will be set to `firstPage`
+ on initialization.
+
+ @property {number} [state.pageSize=25] How many records to show per
+ page. This value is __read only__ after initialization, if you want to
+ change the page size after initialization, you must call #setPageSize.
+
+ @property {number} [state.totalPages=null] How many pages there are. This
+ value is __read only__ and it is calculated from `totalRecords`.
+
+ @property {number} [state.totalRecords=null] How many records there
+ are. This value is __required__ under server mode. This value is optional
+ for client mode as the number will be the same as the number of models
+ during bootstrapping and during fetching, either supplied by the server
+ in the metadata, or calculated from the size of the response.
+
+ @property {string} [state.sortKey=null] The model attribute to use for
+ sorting.
+
+ @property {-1|0|1} [state.order=-1] The order to use for sorting. Specify
+ -1 for ascending order or 1 for descending order. If 0, no client side
+ sorting will be done and the order query parameter will not be sent to
+ the server during a fetch.
+ */
+ state: {
+ firstPage: 1,
+ lastPage: null,
+ currentPage: null,
+ pageSize: 25,
+ totalPages: null,
+ totalRecords: null,
+ sortKey: null,
+ order: -1
+ },
+
+ /**
+ @property {"server"|"client"|"infinite"} [mode="server"] The mode of
+ operations for this collection. `"server"` paginates on the server-side,
+ `"client"` paginates on the client-side and `"infinite"` paginates on the
+ server-side for APIs that do not support `totalRecords`.
+ */
+ mode: "server",
+
+ /**
+ A translation map to convert Backbone.PageableCollection state attributes
+ to the query parameters accepted by your server API.
+
+ You can override the default state by extending this class or specifying
+ them in `options.queryParams` object hash to the constructor.
+
+ @property {Object} queryParams
+ @property {string} [queryParams.currentPage="page"]
+ @property {string} [queryParams.pageSize="per_page"]
+ @property {string} [queryParams.totalPages="total_pages"]
+ @property {string} [queryParams.totalRecords="total_entries"]
+ @property {string} [queryParams.sortKey="sort_by"]
+ @property {string} [queryParams.order="order"]
+ @property {string} [queryParams.directions={"-1": "asc", "1": "desc"}] A
+ map for translating a Backbone.PageableCollection#state.order constant to
+ the ones your server API accepts.
+ */
+ queryParams: {
+ currentPage: "page",
+ pageSize: "per_page",
+ totalPages: "total_pages",
+ totalRecords: "total_entries",
+ sortKey: "sort_by",
+ order: "order",
+ directions: {
+ "-1": "asc",
+ "1": "desc"
+ }
+ },
+
+ /**
+ __CLIENT MODE ONLY__
+
+ This collection is the internal storage for the bootstrapped or fetched
+ models. You can use this if you want to operate on all the pages.
+
+ @property {Backbone.Collection} fullCollection
+ */
+
+ /**
+ Given a list of models or model attributues, bootstraps the full
+ collection in client mode or infinite mode, or just the page you want in
+ server mode.
+
+ If you want to initialize a collection to a different state than the
+ default, you can specify them in `options.state`. Any state parameters
+ supplied will be merged with the default. If you want to change the
+ default mapping from #state keys to your server API's query parameter
+ names, you can specifiy an object hash in `option.queryParams`. Likewise,
+ any mapping provided will be merged with the default. Lastly, all
+ Backbone.Collection constructor options are also accepted.
+
+ See:
+
+ - Backbone.PageableCollection#state
+ - Backbone.PageableCollection#queryParams
+ - [Backbone.Collection#initialize](http://backbonejs.org/#Collection-constructor)
+
+ @param {Array.<Object>} [models]
+
+ @param {Object} [options]
+
+ @param {function(*, *): number} [options.comparator] If specified, this
+ comparator is set to the current page under server mode, or the #fullCollection
+ otherwise.
+
+ @param {boolean} [options.full] If `false` and either a
+ `options.comparator` or `sortKey` is defined, the comparator is attached
+ to the current page. Default is `true` under client or infinite mode and
+ the comparator will be attached to the #fullCollection.
+
+ @param {Object} [options.state] The state attributes overriding the defaults.
+
+ @param {string} [options.state.sortKey] The model attribute to use for
+ sorting. If specified instead of `options.comparator`, a comparator will
+ be automatically created using this value, and optionally a sorting order
+ specified in `options.state.order`. The comparator is then attached to
+ the new collection instance.
+
+ @param {-1|1} [options.state.order] The order to use for sorting. Specify
+ -1 for ascending order and 1 for descending order.
+
+ @param {Object} [options.queryParam]
+ */
+ constructor: function (models, options) {
+
+ Backbone.Collection.apply(this, arguments);
+
+ options = options || {};
+
+ var mode = this.mode = options.mode || this.mode || PageableProto.mode;
+
+ var queryParams = _extend({}, PageableProto.queryParams, this.queryParams,
+ options.queryParams || {});
+
+ queryParams.directions = _extend({},
+ PageableProto.queryParams.directions,
+ this.queryParams.directions,
+ queryParams.directions || {});
+
+ this.queryParams = queryParams;
+
+ var state = this.state = _extend({}, PageableProto.state, this.state,
+ options.state || {});
+
+ state.currentPage = state.currentPage == null ?
+ state.firstPage :
+ state.currentPage;
+
+ if (!_isArray(models)) models = models ? [models] : [];
+
+ if (mode != "server" && state.totalRecords == null && !_isEmpty(models)) {
+ state.totalRecords = models.length;
+ }
+
+ this.switchMode(mode, _extend({fetch: false,
+ resetState: false,
+ models: models}, options));
+
+ var comparator = options.comparator;
+
+ if (state.sortKey && !comparator) {
+ this.setSorting(state.sortKey, state.order, options);
+ }
+
+ if (mode != "server") {
+ var fullCollection = this.fullCollection;
+
+ if (comparator && options.full) {
+ delete this.comparator;
+ fullCollection.comparator = comparator;
+ }
+
+ if (options.full) fullCollection.sort();
+
+ // make sure the models in the current page and full collection have the
+ // same references
+ if (models && !_isEmpty(models)) {
+ this.getPage(state.currentPage);
+ models.splice.apply(models, [0, models.length].concat(this.models));
+ }
+ }
+
+ this._initState = _clone(this.state);
+ },
+
+ /**
+ Makes a Backbone.Collection that contains all the pages.
+
+ @private
+ @param {Array.<Object|Backbone.Model>} models
+ @param {Object} options Options for Backbone.Collection constructor.
+ @return {Backbone.Collection}
+ */
+ _makeFullCollection: function (models, options) {
+
+ var properties = ["url", "model", "sync", "comparator"];
+ var thisProto = this.constructor.prototype;
+ var i, length, prop;
+
+ var proto = {};
+ for (i = 0, length = properties.length; i < length; i++) {
+ prop = properties[i];
+ if (!_isUndefined(thisProto[prop])) {
+ proto[prop] = thisProto[prop];
+ }
+ }
+
+ var fullCollection = new (Backbone.Collection.extend(proto))(models, options);
+
+ for (i = 0, length = properties.length; i < length; i++) {
+ prop = properties[i];
+ if (this[prop] !== thisProto[prop]) {
+ fullCollection[prop] = this[prop];
+ }
+ }
+
+ return fullCollection;
+ },
+
+ /**
+ Factory method that returns a Backbone event handler that responses to
+ the `add`, `remove`, `reset`, and the `sort` events. The returned event
+ handler will synchronize the current page collection and the full
+ collection's models.
+
+ @private
+
+ @param {Backbone.PageableCollection} pageCol
+ @param {Backbone.Collection} fullCol
+
+ @return {function(string, Backbone.Model, Backbone.Collection, Object)}
+ Collection event handler
+ */
+ _makeCollectionEventHandler: function (pageCol, fullCol) {
+
+ return function collectionEventHandler (event, model, collection, options) {
+
+ var handlers = pageCol._handlers;
+ _each(_keys(handlers), function (event) {
+ var handler = handlers[event];
+ pageCol.off(event, handler);
+ fullCol.off(event, handler);
+ });
+
+ var state = _clone(pageCol.state);
+ var firstPage = state.firstPage;
+ var currentPage = firstPage === 0 ?
+ state.currentPage :
+ state.currentPage - 1;
+ var pageSize = state.pageSize;
+ var pageStart = currentPage * pageSize, pageEnd = pageStart + pageSize;
+
+ if (event == "add") {
+ var pageIndex, fullIndex, addAt, colToAdd, options = options || {};
+ if (collection == fullCol) {
+ fullIndex = fullCol.indexOf(model);
+ if (fullIndex >= pageStart && fullIndex < pageEnd) {
+ colToAdd = pageCol;
+ pageIndex = addAt = fullIndex - pageStart;
+ }
+ }
+ else {
+ pageIndex = pageCol.indexOf(model);
+ fullIndex = pageStart + pageIndex;
+ colToAdd = fullCol;
+ var addAt = !_isUndefined(options.at) ?
+ options.at + pageStart :
+ fullIndex;
+ }
+
+ ++state.totalRecords;
+ pageCol.state = pageCol._checkState(state);
+
+ if (colToAdd) {
+ colToAdd.add(model, _extend({}, options || {}, {at: addAt}));
+ var modelToRemove = pageIndex >= pageSize ?
+ model :
+ !_isUndefined(options.at) && addAt < pageEnd && pageCol.length > pageSize ?
+ pageCol.at(pageSize) :
+ null;
+ if (modelToRemove) {
+ var addHandlers = collection._events.add || [],
+ popOptions = {onAdd: true};
+ if (addHandlers.length) {
+ var lastAddHandler = addHandlers[addHandlers.length - 1];
+ var oldCallback = lastAddHandler.callback;
+ lastAddHandler.callback = function () {
+ try {
+ oldCallback.apply(this, arguments);
+ pageCol.remove(modelToRemove, popOptions);
+ }
+ finally {
+ lastAddHandler.callback = oldCallback;
+ }
+ };
+ }
+ else pageCol.remove(modelToRemove, popOptions);
+ }
+ }
+ }
+
+ // remove the model from the other collection as well
+ if (event == "remove") {
+ if (!options.onAdd) {
+ // decrement totalRecords and update totalPages and lastPage
+ if (!--state.totalRecords) {
+ state.totalRecords = null;
+ state.totalPages = null;
+ }
+ else {
+ var totalPages = state.totalPages = ceil(state.totalRecords / pageSize);
+ state.lastPage = firstPage === 0 ? totalPages - 1 : totalPages;
+ if (state.currentPage > totalPages) state.currentPage = state.lastPage;
+ }
+ pageCol.state = pageCol._checkState(state);
+
+ var nextModel, removedIndex = options.index;
+ if (collection == pageCol) {
+ if (nextModel = fullCol.at(pageEnd)) pageCol.push(nextModel);
+ fullCol.remove(model);
+ }
+ else if (removedIndex >= pageStart && removedIndex < pageEnd) {
+ pageCol.remove(model);
+ nextModel = fullCol.at(currentPage * (pageSize + removedIndex));
+ if (nextModel) pageCol.push(nextModel);
+ }
+ }
+ else delete options.onAdd;
+ }
+
+ if (event == "reset") {
+ options = collection;
+ collection = model;
+
+ // Reset that's not a result of getPage
+ if (collection === pageCol && options.from == null &&
+ options.to == null) {
+ var head = fullCol.models.slice(0, pageStart);
+ var tail = fullCol.models.slice(pageStart + pageCol.models.length);
+ fullCol.reset(head.concat(pageCol.models).concat(tail), options);
+ }
+ else if (collection === fullCol) {
+ if (!(state.totalRecords = fullCol.models.length)) {
+ state.totalRecords = null;
+ state.totalPages = null;
+ }
+ if (pageCol.mode == "client") {
+ state.lastPage = state.currentPage = state.firstPage;
+ }
+ pageCol.state = pageCol._checkState(state);
+ pageCol.reset(fullCol.models.slice(pageStart, pageEnd),
+ _extend({}, options, {parse: false}));
+ }
+ }
+
+ if (event == "sort") {
+ options = collection;
+ collection = model;
+ if (collection === fullCol) {
+ pageCol.reset(fullCol.models.slice(pageStart, pageEnd),
+ _extend({}, options, {parse: false}));
+ }
+ }
+
+ _each(_keys(handlers), function (event) {
+ var handler = handlers[event];
+ _each([pageCol, fullCol], function (col) {
+ col.on(event, handler);
+ var callbacks = col._events[event] || [];
+ callbacks.unshift(callbacks.pop());
+ });
+ });
+ };
+ },
+
+ /**
+ Sanity check this collection's pagination states. Only perform checks
+ when all the required pagination state values are defined and not null.
+ If `totalPages` is undefined or null, it is set to `totalRecords` /
+ `pageSize`. `lastPage` is set according to whether `firstPage` is 0 or 1
+ when no error occurs.
+
+ @private
+
+ @throws {TypeError} If `totalRecords`, `pageSize`, `currentPage` or
+ `firstPage` is not a finite integer.
+
+ @throws {RangeError} If `pageSize`, `currentPage` or `firstPage` is out
+ of bounds.
+
+ @return {Object} Returns the `state` object if no error was found.
+ */
+ _checkState: function (state) {
+
+ var mode = this.mode;
+ var links = this.links;
+ var totalRecords = state.totalRecords;
+ var pageSize = state.pageSize;
+ var currentPage = state.currentPage;
+ var firstPage = state.firstPage;
+ var totalPages = state.totalPages;
+
+ if (totalRecords != null && pageSize != null && currentPage != null &&
+ firstPage != null && (mode == "infinite" ? links : true)) {
+
+ totalRecords = finiteInt(totalRecords, "totalRecords");
+ pageSize = finiteInt(pageSize, "pageSize");
+ currentPage = finiteInt(currentPage, "currentPage");
+ firstPage = finiteInt(firstPage, "firstPage");
+
+ if (pageSize < 1) {
+ throw new RangeError("`pageSize` must be >= 1");
+ }
+
+ totalPages = state.totalPages = ceil(totalRecords / pageSize);
+
+ if (firstPage < 0 || firstPage > 1) {
+ throw new RangeError("`firstPage must be 0 or 1`");
+ }
+
+ state.lastPage = firstPage === 0 ? max(0, totalPages - 1) : totalPages;
+
+ if (mode == "infinite") {
+ if (!links[currentPage + '']) {
+ throw new RangeError("No link found for page " + currentPage);
+ }
+ }
+ else if (currentPage < firstPage ||
+ (totalPages > 0 &&
+ (firstPage ? currentPage > totalPages : currentPage >= totalPages))) {
+ throw new RangeError("`currentPage` must be firstPage <= currentPage " +
+ (firstPage ? ">" : ">=") +
+ " totalPages if " + firstPage + "-based. Got " +
+ currentPage + '.');
+ }
+ }
+
+ return state;
+ },
+
+ /**
+ Change the page size of this collection.
+
+ Under most if not all circumstances, you should call this method to
+ change the page size of a pageable collection because it will keep the
+ pagination state sane. By default, the method will recalculate the
+ current page number to one that will retain the current page's models
+ when increasing the page size. When decreasing the page size, this method
+ will retain the last models to the current page that will fit into the
+ smaller page size.
+
+ If `options.first` is true, changing the page size will also reset the
+ current page back to the first page instead of trying to be smart.
+
+ For server mode operations, changing the page size will trigger a #fetch
+ and subsequently a `reset` event.
+
+ For client mode operations, changing the page size will `reset` the
+ current page by recalculating the current page boundary on the client
+ side.
+
+ If `options.fetch` is true, a fetch can be forced if the collection is in
+ client mode.
+
+ @param {number} pageSize The new page size to set to #state.
+ @param {Object} [options] {@link #fetch} options.
+ @param {boolean} [options.first=false] Reset the current page number to
+ the first page if `true`.
+ @param {boolean} [options.fetch] If `true`, force a fetch in client mode.
+
+ @throws {TypeError} If `pageSize` is not a finite integer.
+ @throws {RangeError} If `pageSize` is less than 1.
+
+ @chainable
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+ from fetch or this.
+ */
+ setPageSize: function (pageSize, options) {
+ pageSize = finiteInt(pageSize, "pageSize");
+
+ options = options || {first: false};
+
+ var state = this.state;
+ var totalPages = ceil(state.totalRecords / pageSize);
+ var currentPage = totalPages ?
+ max(state.firstPage,
+ floor(totalPages *
+ (state.firstPage ?
+ state.currentPage :
+ state.currentPage + 1) /
+ state.totalPages)) :
+ state.firstPage;
+
+ state = this.state = this._checkState(_extend({}, state, {
+ pageSize: pageSize,
+ currentPage: options.first ? state.firstPage : currentPage,
+ totalPages: totalPages
+ }));
+
+ if(!options.fetch)
+ return;
+ return this.getPage(state.currentPage, _omit(options, ["first"]));
+ },
+
+ /**
+ Switching between client, server and infinite mode.
+
+ If switching from client to server mode, the #fullCollection is emptied
+ first and then deleted and a fetch is immediately issued for the current
+ page from the server. Pass `false` to `options.fetch` to skip fetching.
+
+ If switching to infinite mode, and if `options.models` is given for an
+ array of models, #links will be populated with a URL per page, using the
+ default URL for this collection.
+
+ If switching from server to client mode, all of the pages are immediately
+ refetched. If you have too many pages, you can pass `false` to
+ `options.fetch` to skip fetching.
+
+ If switching to any mode from infinite mode, the #links will be deleted.
+
+ @param {"server"|"client"|"infinite"} [mode] The mode to switch to.
+
+ @param {Object} [options]
+
+ @param {boolean} [options.fetch=true] If `false`, no fetching is done.
+
+ @param {boolean} [options.resetState=true] If 'false', the state is not
+ reset, but checked for sanity instead.
+
+ @chainable
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+ from fetch or this if `options.fetch` is `false`.
+ */
+ switchMode: function (mode, options) {
+
+ if (!_contains(["server", "client", "infinite"], mode)) {
+ throw new TypeError('`mode` must be one of "server", "client" or "infinite"');
+ }
+
+ options = options || {fetch: true, resetState: true};
+
+ var state = this.state = options.resetState ?
+ _clone(this._initState) :
+ this._checkState(_extend({}, this.state));
+
+ this.mode = mode;
+
+ var self = this;
+ var fullCollection = this.fullCollection;
+ var handlers = this._handlers = this._handlers || {}, handler;
+ if (mode != "server" && !fullCollection) {
+ fullCollection = this._makeFullCollection(options.models || []);
+ fullCollection.pageableCollection = this;
+ this.fullCollection = fullCollection;
+ var allHandler = this._makeCollectionEventHandler(this, fullCollection);
+ _each(["add", "remove", "reset", "sort"], function (event) {
+ handlers[event] = handler = _.bind(allHandler, {}, event);
+ self.on(event, handler);
+ fullCollection.on(event, handler);
+ });
+ fullCollection.comparator = this._fullComparator;
+ }
+ else if (mode == "server" && fullCollection) {
+ _each(_keys(handlers), function (event) {
+ handler = handlers[event];
+ self.off(event, handler);
+ fullCollection.off(event, handler);
+ });
+ delete this._handlers;
+ this._fullComparator = fullCollection.comparator;
+ delete this.fullCollection;
+ }
+
+ if (mode == "infinite") {
+ var links = this.links = {};
+ var firstPage = state.firstPage;
+ var totalPages = ceil(state.totalRecords / state.pageSize);
+ var lastPage = firstPage === 0 ? max(0, totalPages - 1) : totalPages || firstPage;
+ for (var i = state.firstPage; i <= lastPage; i++) {
+ links[i] = this.url;
+ }
+ }
+ else if (this.links) delete this.links;
+
+ return options.fetch ?
+ this.fetch(_omit(options, "fetch", "resetState")) :
+ this;
+ },
+
+ /**
+ @return {boolean} `true` if this collection can page backward, `false`
+ otherwise.
+ */
+ hasPrevious: function () {
+ var state = this.state;
+ var currentPage = state.currentPage;
+ if (this.mode != "infinite") return currentPage > state.firstPage;
+ return !!this.links[currentPage - 1];
+ },
+
+ /**
+ @return {boolean} `true` if this collection can page forward, `false`
+ otherwise.
+ */
+ hasNext: function () {
+ var state = this.state;
+ var currentPage = this.state.currentPage;
+ if (this.mode != "infinite") return currentPage < state.lastPage;
+ return !!this.links[currentPage + 1];
+ },
+
+ /**
+ Fetch the first page in server mode, or reset the current page of this
+ collection to the first page in client or infinite mode.
+
+ @param {Object} options {@link #getPage} options.
+
+ @chainable
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+ from fetch or this.
+ */
+ getFirstPage: function (options) {
+ return this.getPage("first", options);
+ },
+
+ /**
+ Fetch the previous page in server mode, or reset the current page of this
+ collection to the previous page in client or infinite mode.
+
+ @param {Object} options {@link #getPage} options.
+
+ @chainable
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+ from fetch or this.
+ */
+ getPreviousPage: function (options) {
+ return this.getPage("prev", options);
+ },
+
+ /**
+ Fetch the next page in server mode, or reset the current page of this
+ collection to the next page in client mode.
+
+ @param {Object} options {@link #getPage} options.
+
+ @chainable
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+ from fetch or this.
+ */
+ getNextPage: function (options) {
+ return this.getPage("next", options);
+ },
+
+ /**
+ Fetch the last page in server mode, or reset the current page of this
+ collection to the last page in client mode.
+
+ @param {Object} options {@link #getPage} options.
+
+ @chainable
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+ from fetch or this.
+ */
+ getLastPage: function (options) {
+ return this.getPage("last", options);
+ },
+
+ /**
+ Given a page index, set #state.currentPage to that index. If this
+ collection is in server mode, fetch the page using the updated state,
+ otherwise, reset the current page of this collection to the page
+ specified by `index` in client mode. If `options.fetch` is true, a fetch
+ can be forced in client mode before resetting the current page. Under
+ infinite mode, if the index is less than the current page, a reset is
+ done as in client mode. If the index is greater than the current page
+ number, a fetch is made with the results **appended** to #fullCollection.
+ The current page will then be reset after fetching.
+
+ @param {number|string} index The page index to go to, or the page name to
+ look up from #links in infinite mode.
+ @param {Object} [options] {@link #fetch} options or
+ [reset](http://backbonejs.org/#Collection-reset) options for client mode
+ when `options.fetch` is `false`.
+ @param {boolean} [options.fetch=false] If true, force a {@link #fetch} in
+ client mode.
+
+ @throws {TypeError} If `index` is not a finite integer under server or
+ client mode, or does not yield a URL from #links under infinite mode.
+
+ @throws {RangeError} If `index` is out of bounds.
+
+ @chainable
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+ from fetch or this.
+ */
+ getPage: function (index, options) {
+
+ var mode = this.mode, fullCollection = this.fullCollection;
+
+ options = options || {fetch: false};
+
+ var state = this.state,
+ firstPage = state.firstPage,
+ currentPage = state.currentPage,
+ lastPage = state.lastPage,
+ pageSize = state.pageSize;
+
+ var pageNum = index;
+ switch (index) {
+ case "first": pageNum = firstPage; break;
+ case "prev": pageNum = currentPage - 1; break;
+ case "next": pageNum = currentPage + 1; break;
+ case "last": pageNum = lastPage; break;
+ default: pageNum = finiteInt(index, "index");
+ }
+
+ this.state = this._checkState(_extend({}, state, {currentPage: pageNum}));
+
+ options.from = currentPage, options.to = pageNum;
+
+ var pageStart = (firstPage === 0 ? pageNum : pageNum - 1) * pageSize;
+ var pageModels = fullCollection && fullCollection.length ?
+ fullCollection.models.slice(pageStart, pageStart + pageSize) :
+ [];
+ if ((mode == "client" || (mode == "infinite" && !_isEmpty(pageModels))) &&
+ !options.fetch) {
+ return this.reset(pageModels, _omit(options, "fetch"));
+ }
+
+ if (mode == "infinite") options.url = this.links[pageNum];
+
+ return this.fetch(_omit(options, "fetch"));
+ },
+
+ /**
+ Fetch the page for the provided item offset in server mode, or reset the current page of this
+ collection to the page for the provided item offset in client mode.
+
+ @param {Object} options {@link #getPage} options.
+
+ @chainable
+ @return {XMLHttpRequest|Backbone.PageableCollection} The XMLHttpRequest
+ from fetch or this.
+ */
+ getPageByOffset: function (offset, options) {
+ if (offset < 0) {
+ throw new RangeError("`offset must be > 0`");
+ }
+ offset = finiteInt(offset);
+
+ var page = floor(offset / this.state.pageSize);
+ if (this.state.firstPage !== 0) page++;
+ if (page > this.state.lastPage) page = this.state.lastPage;
+ return this.getPage(page, options);
+ },
+
+ /**
+ Overidden to make `getPage` compatible with Zepto.
+
+ @param {string} method
+ @param {Backbone.Model|Backbone.Collection} model
+ @param {Object} [options]
+
+ @return {XMLHttpRequest}
+ */
+ sync: function (method, model, options) {
+ var self = this;
+ if (self.mode == "infinite") {
+ var success = options.success;
+ var currentPage = self.state.currentPage;
+ options.success = function (resp, status, xhr) {
+ var links = self.links;
+ var newLinks = self.parseLinks(resp, _extend({xhr: xhr}, options));
+ if (newLinks.first) links[self.state.firstPage] = newLinks.first;
+ if (newLinks.prev) links[currentPage - 1] = newLinks.prev;
+ if (newLinks.next) links[currentPage + 1] = newLinks.next;
+ if (success) success(resp, status, xhr);
+ };
+ }
+
+ return (BBColProto.sync || Backbone.sync).call(self, method, model, options);
+ },
+
+ /**
+ Parse pagination links from the server response. Only valid under
+ infinite mode.
+
+ Given a response body and a XMLHttpRequest object, extract pagination
+ links from them for infinite paging.
+
+ This default implementation parses the RFC 5988 `Link` header and extract
+ 3 links from it - `first`, `prev`, `next`. If a `previous` link is found,
+ it will be found in the `prev` key in the returned object hash. Any
+ subclasses overriding this method __must__ return an object hash having
+ only the keys above. If `first` is missing, the collection's default URL
+ is assumed to be the `first` URL. If `prev` or `next` is missing, it is
+ assumed to be `null`. An empty object hash must be returned if there are
+ no links found. If either the response or the header contains information
+ pertaining to the total number of records on the server, #state.totalRecords
+ must be set to that number. The default implementation uses the `last`
+ link from the header to calculate it.
+
+ @param {*} resp The deserialized response body.
+ @param {Object} [options]
+ @param {XMLHttpRequest} [options.xhr] The XMLHttpRequest object for this
+ response.
+ @return {Object}
+ */
+ parseLinks: function (resp, options) {
+ var links = {};
+ var linkHeader = options.xhr.getResponseHeader("Link");
+ if (linkHeader) {
+ var relations = ["first", "prev", "previous", "next", "last"];
+ _each(linkHeader.split(","), function (linkValue) {
+ var linkParts = linkValue.split(";");
+ var url = linkParts[0].replace(URL_TRIM_RE, '');
+ var params = linkParts.slice(1);
+ _each(params, function (param) {
+ var paramParts = param.split("=");
+ var key = paramParts[0].replace(PARAM_TRIM_RE, '');
+ var value = paramParts[1].replace(PARAM_TRIM_RE, '');
+ if (key == "rel" && _contains(relations, value)) {
+ if (value == "previous") links.prev = url;
+ else links[value] = url;
+ }
+ });
+ });
+
+ var last = links.last || '', qsi, qs;
+ if (qs = (qsi = last.indexOf('?')) ? last.slice(qsi + 1) : '') {
+ var params = queryStringToParams(qs);
+
+ var state = _clone(this.state);
+ var queryParams = this.queryParams;
+ var pageSize = state.pageSize;
+
+ var totalRecords = params[queryParams.totalRecords] * 1;
+ var pageNum = params[queryParams.currentPage] * 1;
+ var totalPages = params[queryParams.totalPages];
+
+ if (!totalRecords) {
+ if (pageNum) totalRecords = (state.firstPage === 0 ?
+ pageNum + 1 :
+ pageNum) * pageSize;
+ else if (totalPages) totalRecords = totalPages * pageSize;
+ }
+
+ if (totalRecords) state.totalRecords = totalRecords;
+
+ this.state = this._checkState(state);
+ }
+ }
+
+ delete links.last;
+
+ return links;
+ },
+
+ /**
+ Parse server response data.
+
+ This default implementation assumes the response data is in one of two
+ structures:
+
+ [
+ {}, // Your new pagination state
+ [{}, ...] // An array of JSON objects
+ ]
+
+ Or,
+
+ [{}] // An array of JSON objects
+
+ The first structure is the preferred form because the pagination states
+ may have been updated on the server side, sending them down again allows
+ this collection to update its states. If the response has a pagination
+ state object, it is checked for errors.
+
+ The second structure is the
+ [Backbone.Collection#parse](http://backbonejs.org/#Collection-parse)
+ default.
+
+ **Note:** this method has been further simplified since 1.1.7. While
+ existing #parse implementations will continue to work, new code is
+ encouraged to override #parseState and #parseRecords instead.
+
+ @param {Object} resp The deserialized response data from the server.
+ @param {Object} the options for the ajax request
+
+ @return {Array.<Object>} An array of model objects
+ */
+ parse: function (resp, options) {
+ var newState = this.parseState(resp, _clone(this.queryParams), _clone(this.state), options);
+ if (newState) this.state = this._checkState(_extend({}, this.state, newState));
+ return this.parseRecords(resp, options);
+ },
+
+ /**
+ Parse server response for server pagination state updates.
+
+ This default implementation first checks whether the response has any
+ state object as documented in #parse. If it exists, a state object is
+ returned by mapping the server state keys to this pageable collection
+ instance's query parameter keys using `queryParams`.
+
+ It is __NOT__ neccessary to return a full state object complete with all
+ the mappings defined in #queryParams. Any state object resulted is merged
+ with a copy of the current pageable collection state and checked for
+ sanity before actually updating. Most of the time, simply providing a new
+ `totalRecords` value is enough to trigger a full pagination state
+ recalculation.
+
+ parseState: function (resp, queryParams, state, options) {
+ return {totalRecords: resp.total_entries};
+ }
+
+ If you want to use header fields use:
+
+ parseState: function (resp, queryParams, state, options) {
+ return {totalRecords: options.xhr.getResponseHeader("X-total")};
+ }
+
+ This method __MUST__ return a new state object instead of directly
+ modifying the #state object. The behavior of directly modifying #state is
+ undefined.
+
+ @param {Object} resp The deserialized response data from the server.
+ @param {Object} queryParams A copy of #queryParams.
+ @param {Object} state A copy of #state.
+ @param {Object} [options] The options passed through from
+ `parse`. (backbone >= 0.9.10 only)
+
+ @return {Object} A new (partial) state object.
+ */
+ parseState: function (resp, queryParams, state, options) {
+ if (resp && resp.length === 2 && _isObject(resp[0]) && _isArray(resp[1])) {
+
+ var newState = _clone(state);
+ var serverState = resp[0];
+
+ _each(_pairs(_omit(queryParams, "directions")), function (kvp) {
+ var k = kvp[0], v = kvp[1];
+ var serverVal = serverState[v];
+ if (!_isUndefined(serverVal) && !_.isNull(serverVal)) newState[k] = serverState[v];
+ });
+
+ if (serverState.order) {
+ newState.order = _invert(queryParams.directions)[serverState.order] * 1;
+ }
+
+ return newState;
+ }
+ },
+
+ /**
+ Parse server response for an array of model objects.
+
+ This default implementation first checks whether the response has any
+ state object as documented in #parse. If it exists, the array of model
+ objects is assumed to be the second element, otherwise the entire
+ response is returned directly.
+
+ @param {Object} resp The deserialized response data from the server.
+ @param {Object} [options] The options passed through from the
+ `parse`. (backbone >= 0.9.10 only)
+
+ @return {Array.<Object>} An array of model objects
+ */
+ parseRecords: function (resp, options) {
+ if (resp && resp.length === 2 && _isObject(resp[0]) && _isArray(resp[1])) {
+ return resp[1];
+ }
+
+ return resp;
+ },
+
+ /**
+ Fetch a page from the server in server mode, or all the pages in client
+ mode. Under infinite mode, the current page is refetched by default and
+ then reset.
+
+ The query string is constructed by translating the current pagination
+ state to your server API query parameter using #queryParams. The current
+ page will reset after fetch.
+
+ @param {Object} [options] Accepts all
+ [Backbone.Collection#fetch](http://backbonejs.org/#Collection-fetch)
+ options.
+
+ @return {XMLHttpRequest}
+ */
+ fetch: function (options) {
+
+ options = options || {};
+
+ var state = this._checkState(this.state);
+
+ var mode = this.mode;
+
+ if (mode == "infinite" && !options.url) {
+ options.url = this.links[state.currentPage];
+ }
+
+ var data = options.data || {};
+
+ // dedup query params
+ var url = _result(options, "url") || _result(this, "url") || '';
+ var qsi = url.indexOf('?');
+ if (qsi != -1) {
+ _extend(data, queryStringToParams(url.slice(qsi + 1)));
+ url = url.slice(0, qsi);
+ }
+
+ options.url = url;
+ options.data = data;
+
+ // map params except directions
+ var queryParams = this.mode == "client" ?
+ _pick(this.queryParams, "sortKey", "order") :
+ _omit(_pick(this.queryParams, _keys(PageableProto.queryParams)),
+ "directions");
+
+ var i, kvp, k, v, kvps = _pairs(queryParams), thisCopy = _clone(this);
+ for (i = 0; i < kvps.length; i++) {
+ kvp = kvps[i], k = kvp[0], v = kvp[1];
+ v = _isFunction(v) ? v.call(thisCopy) : v;
+ if (state[k] != null && v != null) {
+ data[v] = state[k];
+ }
+ }
+
+ // fix up sorting parameters
+ if (state.sortKey && state.order) {
+ data[queryParams.order] = this.queryParams.directions[state.order + ""];
+ }
+ else if (!state.sortKey) delete data[queryParams.order];
+
+ // map extra query parameters
+ var extraKvps = _pairs(_omit(this.queryParams,
+ _keys(PageableProto.queryParams)));
+ for (i = 0; i < extraKvps.length; i++) {
+ kvp = extraKvps[i];
+ v = kvp[1];
+ v = _isFunction(v) ? v.call(thisCopy) : v;
+ if (v != null) data[kvp[0]] = v;
+ }
+
+ if (mode != "server") {
+ var self = this, fullCol = this.fullCollection;
+ var success = options.success;
+ options.success = function (col, resp, opts) {
+
+ // make sure the caller's intent is obeyed
+ opts = opts || {};
+ if (_isUndefined(options.silent)) delete opts.silent;
+ else opts.silent = options.silent;
+
+ var models = col.models;
+ if (mode == "client") fullCol.reset(models, opts);
+ else fullCol.add(models, _extend({at: fullCol.length}, opts));
+
+ if (success) success(col, resp, opts);
+ };
+
+ // silent the first reset from backbone
+ return BBColProto.fetch.call(self, _extend({}, options, {silent: true}));
+ }
+
+ return BBColProto.fetch.call(this, options);
+ },
+
+ /**
+ Convenient method for making a `comparator` sorted by a model attribute
+ identified by `sortKey` and ordered by `order`.
+
+ Like a Backbone.Collection, a Backbone.PageableCollection will maintain
+ the __current page__ in sorted order on the client side if a `comparator`
+ is attached to it. If the collection is in client mode, you can attach a
+ comparator to #fullCollection to have all the pages reflect the global
+ sorting order by specifying an option `full` to `true`. You __must__ call
+ `sort` manually or #fullCollection.sort after calling this method to
+ force a resort.
+
+ While you can use this method to sort the current page in server mode,
+ the sorting order may not reflect the global sorting order due to the
+ additions or removals of the records on the server since the last
+ fetch. If you want the most updated page in a global sorting order, it is
+ recommended that you set #state.sortKey and optionally #state.order, and
+ then call #fetch.
+
+ @protected
+
+ @param {string} [sortKey=this.state.sortKey] See `state.sortKey`.
+ @param {number} [order=this.state.order] See `state.order`.
+ @param {(function(Backbone.Model, string): Object) | string} [sortValue] See #setSorting.
+
+ See [Backbone.Collection.comparator](http://backbonejs.org/#Collection-comparator).
+ */
+ _makeComparator: function (sortKey, order, sortValue) {
+ var state = this.state;
+
+ sortKey = sortKey || state.sortKey;
+ order = order || state.order;
+
+ if (!sortKey || !order) return;
+
+ if (!sortValue) sortValue = function (model, attr) {
+ return model.get(attr);
+ };
+
+ return function (left, right) {
+ var l = sortValue(left, sortKey), r = sortValue(right, sortKey), t;
+ if (order === 1) t = l, l = r, r = t;
+ if (l === r) return 0;
+ else if (l < r) return -1;
+ return 1;
+ };
+ },
+
+ /**
+ Adjusts the sorting for this pageable collection.
+
+ Given a `sortKey` and an `order`, sets `state.sortKey` and
+ `state.order`. A comparator can be applied on the client side to sort in
+ the order defined if `options.side` is `"client"`. By default the
+ comparator is applied to the #fullCollection. Set `options.full` to
+ `false` to apply a comparator to the current page under any mode. Setting
+ `sortKey` to `null` removes the comparator from both the current page and
+ the full collection.
+
+ If a `sortValue` function is given, it will be passed the `(model,
+ sortKey)` arguments and is used to extract a value from the model during
+ comparison sorts. If `sortValue` is not given, `model.get(sortKey)` is
+ used for sorting.
+
+ @chainable
+
+ @param {string} sortKey See `state.sortKey`.
+ @param {number} [order=this.state.order] See `state.order`.
+ @param {Object} [options]
+ @param {"server"|"client"} [options.side] By default, `"client"` if
+ `mode` is `"client"`, `"server"` otherwise.
+ @param {boolean} [options.full=true]
+ @param {(function(Backbone.Model, string): Object) | string} [options.sortValue]
+ */
+ setSorting: function (sortKey, order, options) {
+
+ var state = this.state;
+
+ state.sortKey = sortKey;
+ state.order = order = order || state.order;
+
+ var fullCollection = this.fullCollection;
+
+ var delComp = false, delFullComp = false;
+
+ if (!sortKey) delComp = delFullComp = true;
+
+ var mode = this.mode;
+ options = _extend({side: mode == "client" ? mode : "server", full: true},
+ options);
+
+ var comparator = this._makeComparator(sortKey, order, options.sortValue);
+
+ var full = options.full, side = options.side;
+
+ if (side == "client") {
+ if (full) {
+ if (fullCollection) fullCollection.comparator = comparator;
+ delComp = true;
+ }
+ else {
+ this.comparator = comparator;
+ delFullComp = true;
+ }
+ }
+ else if (side == "server" && !full) {
+ this.comparator = comparator;
+ }
+
+ if (delComp) delete this.comparator;
+ if (delFullComp && fullCollection) delete fullCollection.comparator;
+
+ return this;
+ }
+
+ });
+
+ var PageableProto = PageableCollection.prototype;
+
+ return PageableCollection;
+
+}));