mirror of
https://github.com/wahyd4/cdnjs.git
synced 2026-08-09 12:56:05 +10:00
Added backbone-forms version 0.12.0.
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* Bootstrap Modal wrapper for use with Backbone.
|
||||
*
|
||||
* Takes care of instantiation, manages multiple modals,
|
||||
* adds several options and removes the element from the DOM when closed
|
||||
*
|
||||
* @author Charles Davison <charlie@powmedia.co.uk>
|
||||
*
|
||||
* Events:
|
||||
* shown: Fired when the modal has finished animating in
|
||||
* hidden: Fired when the modal has finished animating out
|
||||
* cancel: The user dismissed the modal
|
||||
* ok: The user clicked OK
|
||||
*/
|
||||
(function($, _, Backbone) {
|
||||
|
||||
//Set custom template settings
|
||||
var _interpolateBackup = _.templateSettings;
|
||||
_.templateSettings = {
|
||||
interpolate: /\{\{(.+?)\}\}/g,
|
||||
evaluate: /<%([\s\S]+?)%>/g
|
||||
}
|
||||
|
||||
var template = _.template('\
|
||||
<% if (title) { %>\
|
||||
<div class="modal-header">\
|
||||
<% if (allowCancel) { %>\
|
||||
<a class="close">×</a>\
|
||||
<% } %>\
|
||||
<h3>{{title}}</h3>\
|
||||
</div>\
|
||||
<% } %>\
|
||||
<div class="modal-body">{{content}}</div>\
|
||||
<div class="modal-footer">\
|
||||
<% if (allowCancel) { %>\
|
||||
<% if (cancelText) { %>\
|
||||
<a href="#" class="btn cancel">{{cancelText}}</a>\
|
||||
<% } %>\
|
||||
<% } %>\
|
||||
<a href="#" class="btn ok btn-primary">{{okText}}</a>\
|
||||
</div>\
|
||||
');
|
||||
|
||||
//Reset to users' template settings
|
||||
_.templateSettings = _interpolateBackup;
|
||||
|
||||
|
||||
var Modal = Backbone.View.extend({
|
||||
|
||||
className: 'modal',
|
||||
|
||||
events: {
|
||||
'click .close': function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.trigger('cancel');
|
||||
|
||||
if (this.options.content && this.options.content.trigger) {
|
||||
this.options.content.trigger('cancel', this);
|
||||
}
|
||||
},
|
||||
'click .cancel': function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.trigger('cancel');
|
||||
|
||||
if (this.options.content && this.options.content.trigger) {
|
||||
this.options.content.trigger('cancel', this);
|
||||
}
|
||||
},
|
||||
'click .ok': function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.trigger('ok');
|
||||
|
||||
if (this.options.content && this.options.content.trigger) {
|
||||
this.options.content.trigger('ok', this);
|
||||
}
|
||||
|
||||
if (this.options.okCloses) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an instance of a Bootstrap Modal
|
||||
*
|
||||
* @see http://twitter.github.com/bootstrap/javascript.html#modals
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {String|View} [options.content] Modal content. Default: none
|
||||
* @param {String} [options.title] Title. Default: none
|
||||
* @param {String} [options.okText] Text for the OK button. Default: 'OK'
|
||||
* @param {String} [options.cancelText] Text for the cancel button. Default: 'Cancel'. If passed a falsey value, the button will be removed
|
||||
* @param {Boolean} [options.allowCancel Whether the modal can be closed, other than by pressing OK. Default: true
|
||||
* @param {Boolean} [options.escape] Whether the 'esc' key can dismiss the modal. Default: true, but false if options.cancellable is true
|
||||
* @param {Boolean} [options.animate] Whether to animate in/out. Default: false
|
||||
* @param {Function} [options.template] Compiled underscore template to override the default one
|
||||
*/
|
||||
initialize: function(options) {
|
||||
this.options = _.extend({
|
||||
title: null,
|
||||
okText: 'OK',
|
||||
focusOk: true,
|
||||
okCloses: true,
|
||||
cancelText: 'Cancel',
|
||||
allowCancel: true,
|
||||
escape: true,
|
||||
animate: false,
|
||||
template: template
|
||||
}, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the DOM element
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
render: function() {
|
||||
var $el = this.$el,
|
||||
options = this.options,
|
||||
content = options.content;
|
||||
|
||||
//Create the modal container
|
||||
$el.html(options.template(options));
|
||||
|
||||
var $content = this.$content = $el.find('.modal-body')
|
||||
|
||||
//Insert the main content if it's a view
|
||||
if (content.$el) {
|
||||
content.render();
|
||||
$el.find('.modal-body').html(content.$el);
|
||||
}
|
||||
|
||||
if (options.animate) $el.addClass('fade');
|
||||
|
||||
this.isRendered = true;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders and shows the modal
|
||||
*
|
||||
* @param {Function} [cb] Optional callback that runs only when OK is pressed.
|
||||
*/
|
||||
open: function(cb) {
|
||||
if (!this.isRendered) this.render();
|
||||
|
||||
var self = this,
|
||||
$el = this.$el;
|
||||
|
||||
//Create it
|
||||
$el.modal(_.extend({
|
||||
keyboard: this.options.allowCancel,
|
||||
backdrop: this.options.allowCancel ? true : 'static'
|
||||
}, this.options.modalOptions));
|
||||
|
||||
//Focus OK button
|
||||
$el.one('shown', function() {
|
||||
if (self.options.focusOk) {
|
||||
$el.find('.btn.ok').focus();
|
||||
}
|
||||
|
||||
if (self.options.content && self.options.content.trigger) {
|
||||
self.options.content.trigger('shown', self);
|
||||
}
|
||||
|
||||
self.trigger('shown');
|
||||
});
|
||||
|
||||
//Adjust the modal and backdrop z-index; for dealing with multiple modals
|
||||
var numModals = Modal.count,
|
||||
$backdrop = $('.modal-backdrop:eq('+numModals+')'),
|
||||
backdropIndex = parseInt($backdrop.css('z-index'),10),
|
||||
elIndex = parseInt($backdrop.css('z-index'), 10);
|
||||
|
||||
$backdrop.css('z-index', backdropIndex + numModals);
|
||||
this.$el.css('z-index', elIndex + numModals);
|
||||
|
||||
if (this.options.allowCancel) {
|
||||
$backdrop.one('click', function() {
|
||||
if (self.options.content && self.options.content.trigger) {
|
||||
self.options.content.trigger('cancel', self);
|
||||
}
|
||||
|
||||
self.trigger('cancel');
|
||||
});
|
||||
|
||||
$(document).one('keyup.dismiss.modal', function (e) {
|
||||
e.which == 27 && self.trigger('cancel');
|
||||
|
||||
if (self.options.content && self.options.content.trigger) {
|
||||
e.which == 27 && self.options.content.trigger('shown', self);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.on('cancel', function() {
|
||||
self.close();
|
||||
});
|
||||
|
||||
Modal.count++;
|
||||
|
||||
//Run callback on OK if provided
|
||||
if (cb) {
|
||||
self.on('ok', cb);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the modal
|
||||
*/
|
||||
close: function() {
|
||||
var self = this,
|
||||
$el = this.$el;
|
||||
|
||||
//Check if the modal should stay open
|
||||
if (this._preventClose) {
|
||||
this._preventClose = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$el.one('hidden', function onHidden(e) {
|
||||
// Ignore events propagated from interior objects, like bootstrap tooltips
|
||||
if(e.target !== e.currentTarget){
|
||||
return $el.one('hidden', onHidden);
|
||||
}
|
||||
self.remove();
|
||||
|
||||
if (self.options.content && self.options.content.trigger) {
|
||||
self.options.content.trigger('hidden', self);
|
||||
}
|
||||
|
||||
self.trigger('hidden');
|
||||
});
|
||||
|
||||
$el.modal('hide');
|
||||
|
||||
Modal.count--;
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop the modal from closing.
|
||||
* Can be called from within a 'close' or 'ok' event listener.
|
||||
*/
|
||||
preventClose: function() {
|
||||
this._preventClose = true;
|
||||
}
|
||||
}, {
|
||||
//STATICS
|
||||
|
||||
//The number of modals on display
|
||||
count: 0
|
||||
});
|
||||
|
||||
|
||||
//EXPORTS
|
||||
//CommonJS
|
||||
if (typeof require == 'function' && typeof module !== 'undefined' && exports) {
|
||||
module.exports = Modal;
|
||||
}
|
||||
|
||||
//AMD / RequireJS
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
return define(function() {
|
||||
Backbone.BootstrapModal = Modal;
|
||||
})
|
||||
}
|
||||
|
||||
//Regular; add to Backbone.Bootstrap.Modal
|
||||
else {
|
||||
Backbone.BootstrapModal = Modal;
|
||||
}
|
||||
|
||||
})(jQuery, _, Backbone);
|
||||
@@ -0,0 +1 @@
|
||||
(function(e,t,n){var r=t.templateSettings;t.templateSettings={interpolate:/\{\{(.+?)\}\}/g,evaluate:/<%([\s\S]+?)%>/g};var i=t.template(' <% if (title) { %> <div class="modal-header"> <% if (allowCancel) { %> <a class="close">×</a> <% } %> <h3>{{title}}</h3> </div> <% } %> <div class="modal-body">{{content}}</div> <div class="modal-footer"> <% if (allowCancel) { %> <% if (cancelText) { %> <a href="#" class="btn cancel">{{cancelText}}</a> <% } %> <% } %> <a href="#" class="btn ok btn-primary">{{okText}}</a> </div> ');t.templateSettings=r;var s=n.View.extend({className:"modal",events:{"click .close":function(e){e.preventDefault(),this.trigger("cancel"),this.options.content&&this.options.content.trigger&&this.options.content.trigger("cancel",this)},"click .cancel":function(e){e.preventDefault(),this.trigger("cancel"),this.options.content&&this.options.content.trigger&&this.options.content.trigger("cancel",this)},"click .ok":function(e){e.preventDefault(),this.trigger("ok"),this.options.content&&this.options.content.trigger&&this.options.content.trigger("ok",this),this.options.okCloses&&this.close()}},initialize:function(e){this.options=t.extend({title:null,okText:"OK",focusOk:!0,okCloses:!0,cancelText:"Cancel",allowCancel:!0,escape:!0,animate:!1,template:i},e)},render:function(){var e=this.$el,t=this.options,n=t.content;e.html(t.template(t));var r=this.$content=e.find(".modal-body");return n.$el&&(n.render(),e.find(".modal-body").html(n.$el)),t.animate&&e.addClass("fade"),this.isRendered=!0,this},open:function(n){this.isRendered||this.render();var r=this,i=this.$el;i.modal(t.extend({keyboard:this.options.allowCancel,backdrop:this.options.allowCancel?!0:"static"},this.options.modalOptions)),i.one("shown",function(){r.options.focusOk&&i.find(".btn.ok").focus(),r.options.content&&r.options.content.trigger&&r.options.content.trigger("shown",r),r.trigger("shown")});var o=s.count,u=e(".modal-backdrop:eq("+o+")"),a=parseInt(u.css("z-index"),10),f=parseInt(u.css("z-index"),10);return u.css("z-index",a+o),this.$el.css("z-index",f+o),this.options.allowCancel&&(u.one("click",function(){r.options.content&&r.options.content.trigger&&r.options.content.trigger("cancel",r),r.trigger("cancel")}),e(document).one("keyup.dismiss.modal",function(e){e.which==27&&r.trigger("cancel"),r.options.content&&r.options.content.trigger&&e.which==27&&r.options.content.trigger("shown",r)})),this.on("cancel",function(){r.close()}),s.count++,n&&r.on("ok",n),this},close:function(){var e=this,t=this.$el;if(this._preventClose){this._preventClose=!1;return}t.one("hidden",function n(r){if(r.target!==r.currentTarget)return t.one("hidden",n);e.remove(),e.options.content&&e.options.content.trigger&&e.options.content.trigger("hidden",e),e.trigger("hidden")}),t.modal("hide"),s.count--},preventClose:function(){this._preventClose=!0}},{count:0});typeof require=="function"&&typeof module!="undefined"&&exports&&(module.exports=s);if(typeof define=="function"&&define.amd)return define(function(){n.BootstrapModal=s});n.BootstrapModal=s})(jQuery,_,Backbone)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,650 @@
|
||||
;(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);
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* Bootstrap Modal wrapper for use with Backbone.
|
||||
*
|
||||
* Takes care of instantiation, manages multiple modals,
|
||||
* adds several options and removes the element from the DOM when closed
|
||||
*
|
||||
* @author Charles Davison <charlie@powmedia.co.uk>
|
||||
*
|
||||
* Events:
|
||||
* shown: Fired when the modal has finished animating in
|
||||
* hidden: Fired when the modal has finished animating out
|
||||
* cancel: The user dismissed the modal
|
||||
* ok: The user clicked OK
|
||||
*/
|
||||
(function($, _, Backbone) {
|
||||
|
||||
//Set custom template settings
|
||||
var _interpolateBackup = _.templateSettings;
|
||||
_.templateSettings = {
|
||||
interpolate: /\{\{(.+?)\}\}/g,
|
||||
evaluate: /<%([\s\S]+?)%>/g
|
||||
}
|
||||
|
||||
var template = _.template('\
|
||||
<% if (title) { %>\
|
||||
<div class="modal-header">\
|
||||
<% if (allowCancel) { %>\
|
||||
<a class="close">×</a>\
|
||||
<% } %>\
|
||||
<h3>{{title}}</h3>\
|
||||
</div>\
|
||||
<% } %>\
|
||||
<div class="modal-body">{{content}}</div>\
|
||||
<div class="modal-footer">\
|
||||
<% if (allowCancel) { %>\
|
||||
<% if (cancelText) { %>\
|
||||
<a href="#" class="btn cancel">{{cancelText}}</a>\
|
||||
<% } %>\
|
||||
<% } %>\
|
||||
<a href="#" class="btn ok btn-primary">{{okText}}</a>\
|
||||
</div>\
|
||||
');
|
||||
|
||||
//Reset to users' template settings
|
||||
_.templateSettings = _interpolateBackup;
|
||||
|
||||
|
||||
var Modal = Backbone.View.extend({
|
||||
|
||||
className: 'modal',
|
||||
|
||||
events: {
|
||||
'click .close': function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.trigger('cancel');
|
||||
|
||||
if (this.options.content && this.options.content.trigger) {
|
||||
this.options.content.trigger('cancel', this);
|
||||
}
|
||||
},
|
||||
'click .cancel': function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.trigger('cancel');
|
||||
|
||||
if (this.options.content && this.options.content.trigger) {
|
||||
this.options.content.trigger('cancel', this);
|
||||
}
|
||||
},
|
||||
'click .ok': function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
this.trigger('ok');
|
||||
|
||||
if (this.options.content && this.options.content.trigger) {
|
||||
this.options.content.trigger('ok', this);
|
||||
}
|
||||
|
||||
if (this.options.okCloses) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an instance of a Bootstrap Modal
|
||||
*
|
||||
* @see http://twitter.github.com/bootstrap/javascript.html#modals
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {String|View} [options.content] Modal content. Default: none
|
||||
* @param {String} [options.title] Title. Default: none
|
||||
* @param {String} [options.okText] Text for the OK button. Default: 'OK'
|
||||
* @param {String} [options.cancelText] Text for the cancel button. Default: 'Cancel'. If passed a falsey value, the button will be removed
|
||||
* @param {Boolean} [options.allowCancel Whether the modal can be closed, other than by pressing OK. Default: true
|
||||
* @param {Boolean} [options.escape] Whether the 'esc' key can dismiss the modal. Default: true, but false if options.cancellable is true
|
||||
* @param {Boolean} [options.animate] Whether to animate in/out. Default: false
|
||||
* @param {Function} [options.template] Compiled underscore template to override the default one
|
||||
*/
|
||||
initialize: function(options) {
|
||||
this.options = _.extend({
|
||||
title: null,
|
||||
okText: 'OK',
|
||||
focusOk: true,
|
||||
okCloses: true,
|
||||
cancelText: 'Cancel',
|
||||
allowCancel: true,
|
||||
escape: true,
|
||||
animate: false,
|
||||
template: template
|
||||
}, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the DOM element
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
render: function() {
|
||||
var $el = this.$el,
|
||||
options = this.options,
|
||||
content = options.content;
|
||||
|
||||
//Create the modal container
|
||||
$el.html(options.template(options));
|
||||
|
||||
var $content = this.$content = $el.find('.modal-body')
|
||||
|
||||
//Insert the main content if it's a view
|
||||
if (content.$el) {
|
||||
content.render();
|
||||
$el.find('.modal-body').html(content.$el);
|
||||
}
|
||||
|
||||
if (options.animate) $el.addClass('fade');
|
||||
|
||||
this.isRendered = true;
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders and shows the modal
|
||||
*
|
||||
* @param {Function} [cb] Optional callback that runs only when OK is pressed.
|
||||
*/
|
||||
open: function(cb) {
|
||||
if (!this.isRendered) this.render();
|
||||
|
||||
var self = this,
|
||||
$el = this.$el;
|
||||
|
||||
//Create it
|
||||
$el.modal(_.extend({
|
||||
keyboard: this.options.allowCancel,
|
||||
backdrop: this.options.allowCancel ? true : 'static'
|
||||
}, this.options.modalOptions));
|
||||
|
||||
//Focus OK button
|
||||
$el.one('shown', function() {
|
||||
if (self.options.focusOk) {
|
||||
$el.find('.btn.ok').focus();
|
||||
}
|
||||
|
||||
if (self.options.content && self.options.content.trigger) {
|
||||
self.options.content.trigger('shown', self);
|
||||
}
|
||||
|
||||
self.trigger('shown');
|
||||
});
|
||||
|
||||
//Adjust the modal and backdrop z-index; for dealing with multiple modals
|
||||
var numModals = Modal.count,
|
||||
$backdrop = $('.modal-backdrop:eq('+numModals+')'),
|
||||
backdropIndex = parseInt($backdrop.css('z-index'),10),
|
||||
elIndex = parseInt($backdrop.css('z-index'), 10);
|
||||
|
||||
$backdrop.css('z-index', backdropIndex + numModals);
|
||||
this.$el.css('z-index', elIndex + numModals);
|
||||
|
||||
if (this.options.allowCancel) {
|
||||
$backdrop.one('click', function() {
|
||||
if (self.options.content && self.options.content.trigger) {
|
||||
self.options.content.trigger('cancel', self);
|
||||
}
|
||||
|
||||
self.trigger('cancel');
|
||||
});
|
||||
|
||||
$(document).one('keyup.dismiss.modal', function (e) {
|
||||
e.which == 27 && self.trigger('cancel');
|
||||
|
||||
if (self.options.content && self.options.content.trigger) {
|
||||
e.which == 27 && self.options.content.trigger('shown', self);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.on('cancel', function() {
|
||||
self.close();
|
||||
});
|
||||
|
||||
Modal.count++;
|
||||
|
||||
//Run callback on OK if provided
|
||||
if (cb) {
|
||||
self.on('ok', cb);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the modal
|
||||
*/
|
||||
close: function() {
|
||||
var self = this,
|
||||
$el = this.$el;
|
||||
|
||||
//Check if the modal should stay open
|
||||
if (this._preventClose) {
|
||||
this._preventClose = false;
|
||||
return;
|
||||
}
|
||||
|
||||
$el.one('hidden', function onHidden(e) {
|
||||
// Ignore events propagated from interior objects, like bootstrap tooltips
|
||||
if(e.target !== e.currentTarget){
|
||||
return $el.one('hidden', onHidden);
|
||||
}
|
||||
self.remove();
|
||||
|
||||
if (self.options.content && self.options.content.trigger) {
|
||||
self.options.content.trigger('hidden', self);
|
||||
}
|
||||
|
||||
self.trigger('hidden');
|
||||
});
|
||||
|
||||
$el.modal('hide');
|
||||
|
||||
Modal.count--;
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop the modal from closing.
|
||||
* Can be called from within a 'close' or 'ok' event listener.
|
||||
*/
|
||||
preventClose: function() {
|
||||
this._preventClose = true;
|
||||
}
|
||||
}, {
|
||||
//STATICS
|
||||
|
||||
//The number of modals on display
|
||||
count: 0
|
||||
});
|
||||
|
||||
|
||||
//EXPORTS
|
||||
//CommonJS
|
||||
if (typeof require == 'function' && typeof module !== 'undefined' && exports) {
|
||||
module.exports = Modal;
|
||||
}
|
||||
|
||||
//AMD / RequireJS
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
return define(function() {
|
||||
Backbone.BootstrapModal = Modal;
|
||||
})
|
||||
}
|
||||
|
||||
//Regular; add to Backbone.Bootstrap.Modal
|
||||
else {
|
||||
Backbone.BootstrapModal = Modal;
|
||||
}
|
||||
|
||||
})(jQuery, _, Backbone);
|
||||
+1
@@ -0,0 +1 @@
|
||||
(function(e,t,n){var r=t.templateSettings;t.templateSettings={interpolate:/\{\{(.+?)\}\}/g,evaluate:/<%([\s\S]+?)%>/g};var i=t.template(' <% if (title) { %> <div class="modal-header"> <% if (allowCancel) { %> <a class="close">×</a> <% } %> <h3>{{title}}</h3> </div> <% } %> <div class="modal-body">{{content}}</div> <div class="modal-footer"> <% if (allowCancel) { %> <% if (cancelText) { %> <a href="#" class="btn cancel">{{cancelText}}</a> <% } %> <% } %> <a href="#" class="btn ok btn-primary">{{okText}}</a> </div> ');t.templateSettings=r;var s=n.View.extend({className:"modal",events:{"click .close":function(e){e.preventDefault(),this.trigger("cancel"),this.options.content&&this.options.content.trigger&&this.options.content.trigger("cancel",this)},"click .cancel":function(e){e.preventDefault(),this.trigger("cancel"),this.options.content&&this.options.content.trigger&&this.options.content.trigger("cancel",this)},"click .ok":function(e){e.preventDefault(),this.trigger("ok"),this.options.content&&this.options.content.trigger&&this.options.content.trigger("ok",this),this.options.okCloses&&this.close()}},initialize:function(e){this.options=t.extend({title:null,okText:"OK",focusOk:!0,okCloses:!0,cancelText:"Cancel",allowCancel:!0,escape:!0,animate:!1,template:i},e)},render:function(){var e=this.$el,t=this.options,n=t.content;e.html(t.template(t));var r=this.$content=e.find(".modal-body");return n.$el&&(n.render(),e.find(".modal-body").html(n.$el)),t.animate&&e.addClass("fade"),this.isRendered=!0,this},open:function(n){this.isRendered||this.render();var r=this,i=this.$el;i.modal(t.extend({keyboard:this.options.allowCancel,backdrop:this.options.allowCancel?!0:"static"},this.options.modalOptions)),i.one("shown",function(){r.options.focusOk&&i.find(".btn.ok").focus(),r.options.content&&r.options.content.trigger&&r.options.content.trigger("shown",r),r.trigger("shown")});var o=s.count,u=e(".modal-backdrop:eq("+o+")"),a=parseInt(u.css("z-index"),10),f=parseInt(u.css("z-index"),10);return u.css("z-index",a+o),this.$el.css("z-index",f+o),this.options.allowCancel&&(u.one("click",function(){r.options.content&&r.options.content.trigger&&r.options.content.trigger("cancel",r),r.trigger("cancel")}),e(document).one("keyup.dismiss.modal",function(e){e.which==27&&r.trigger("cancel"),r.options.content&&r.options.content.trigger&&e.which==27&&r.options.content.trigger("shown",r)})),this.on("cancel",function(){r.close()}),s.count++,n&&r.on("ok",n),this},close:function(){var e=this,t=this.$el;if(this._preventClose){this._preventClose=!1;return}t.one("hidden",function n(r){if(r.target!==r.currentTarget)return t.one("hidden",n);e.remove(),e.options.content&&e.options.content.trigger&&e.options.content.trigger("hidden",e),e.trigger("hidden")}),t.modal("hide"),s.count--},preventClose:function(){this._preventClose=!0}},{count:0});typeof require=="function"&&typeof module!="undefined"&&exports&&(module.exports=s);if(typeof define=="function"&&define.amd)return define(function(){n.BootstrapModal=s});n.BootstrapModal=s})(jQuery,_,Backbone)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,43 @@
|
||||
/* Date */
|
||||
.bbf-date .bbf-date {
|
||||
width: 4em
|
||||
}
|
||||
|
||||
.bbf-date .bbf-month {
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
.bbf-date .bbf-year {
|
||||
width: 5em;
|
||||
}
|
||||
|
||||
|
||||
/* DateTime */
|
||||
.bbf-datetime select {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
|
||||
/* List */
|
||||
.bbf-list .bbf-add {
|
||||
margin-top: -10px
|
||||
}
|
||||
|
||||
.bbf-list li {
|
||||
margin-bottom: 5px
|
||||
}
|
||||
|
||||
.bbf-list .bbf-del {
|
||||
margin-left: 4px
|
||||
}
|
||||
|
||||
|
||||
/* List.Modal */
|
||||
.bbf-list-modal {
|
||||
cursor: pointer;
|
||||
border: 1px solid #ccc;
|
||||
width: 208px;
|
||||
border-radius: 3px;
|
||||
padding: 4px;
|
||||
color: #555;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Include this template file after backbone-forms.amd.js to override the default templates
|
||||
*
|
||||
* 'data-*' attributes control where elements are placed
|
||||
*/
|
||||
;(function(Form) {
|
||||
|
||||
|
||||
/**
|
||||
* Bootstrap templates for Backbone Forms
|
||||
*/
|
||||
Form.template = _.template('\
|
||||
<form class="form-horizontal" data-fieldsets></form>\
|
||||
');
|
||||
|
||||
|
||||
Form.Fieldset.template = _.template('\
|
||||
<fieldset data-fields>\
|
||||
<% if (legend) { %>\
|
||||
<legend><%= legend %></legend>\
|
||||
<% } %>\
|
||||
</fieldset>\
|
||||
');
|
||||
|
||||
|
||||
Form.Field.template = _.template('\
|
||||
<div class="control-group field-<%= key %>">\
|
||||
<label class="control-label" for="<%= editorId %>"><%= title %></label>\
|
||||
<div class="controls">\
|
||||
<span data-editor></span>\
|
||||
<div class="help-inline" data-error></div>\
|
||||
<div class="help-block"><%= help %></div>\
|
||||
</div>\
|
||||
</div>\
|
||||
');
|
||||
|
||||
|
||||
Form.NestedField.template = _.template('\
|
||||
<div class="field-<%= key %>">\
|
||||
<div title="<%= title %>" class="input-xlarge">\
|
||||
<span data-editor></span>\
|
||||
<div class="help-inline" data-error></div>\
|
||||
</div>\
|
||||
<div class="help-block"><%= help %></div>\
|
||||
</div>\
|
||||
');
|
||||
|
||||
|
||||
Form.editors.List.template = _.template('\
|
||||
<div class="bbf-list">\
|
||||
<ul class="unstyled clearfix" data-items></ul>\
|
||||
<button class="btn bbf-add" data-action="add">Add</button>\
|
||||
</div>\
|
||||
');
|
||||
|
||||
|
||||
Form.editors.List.Item.template = _.template('\
|
||||
<li class="clearfix">\
|
||||
<div class="pull-left" data-editor></div>\
|
||||
<button type="button" class="btn bbf-del" data-action="remove">×</button>\
|
||||
</li>\
|
||||
');
|
||||
|
||||
|
||||
})(Backbone.Form);
|
||||
@@ -0,0 +1,650 @@
|
||||
;(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);
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,140 @@
|
||||
/* Form */
|
||||
.bbf-form {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
/* Field */
|
||||
.bbf-field {
|
||||
margin: 1em 0;
|
||||
list-style-type: none;
|
||||
position: relative;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.bbf-field label {
|
||||
float: left;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.bbf-field .bbf-editor {
|
||||
margin-left: 25%;
|
||||
width: 74%;
|
||||
}
|
||||
|
||||
.bbf-field input, .bbf-field textarea, .bbf-field select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bbf-field .bbf-help {
|
||||
margin-left: 25%;
|
||||
width: 74%;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.bbf-field .bbf-error {
|
||||
margin-left: 25%;
|
||||
width: 74%;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.bbf-field.bbf-error .bbf-editor {
|
||||
outline: 1px solid red;
|
||||
}
|
||||
|
||||
|
||||
/* Radio */
|
||||
.bbf-radio {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.bbf-radio input {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.bbf-radio label {
|
||||
float: none;
|
||||
}
|
||||
|
||||
|
||||
/* Checkboxes */
|
||||
.bbf-checkboxes {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.bbf-checkboxes input {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.bbf-checkboxes label {
|
||||
float: none;
|
||||
}
|
||||
|
||||
|
||||
/* List */
|
||||
.bbf-list ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bbf-list .bbf-error {
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
.bbf-list li {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bbf-list li .bbf-editor-container {
|
||||
margin-right: 2em;
|
||||
}
|
||||
|
||||
.bbf-list li .bbf-remove {
|
||||
float: right;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
.bbf-list .bbf-actions {
|
||||
text-align: center;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* List.Modal */
|
||||
.bbf-list-modal {
|
||||
cursor: pointer;
|
||||
border: 1px solid #ccc;
|
||||
width: 208px;
|
||||
border-radius: 3px;
|
||||
padding: 4px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Date */
|
||||
.bbf-date .bbf-date {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.bbf-date .bbf-month {
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
.bbf-date .bbf-year {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
|
||||
/* DateTime */
|
||||
.bbf-datetime .bbf-date-container {
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.bbf-datetime select {
|
||||
width: 4em;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Include this template file after backbone-forms.amd.js to override the default templates
|
||||
*
|
||||
* 'data-*' attributes control where elements are placed
|
||||
*/
|
||||
;(function(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>\
|
||||
');
|
||||
|
||||
|
||||
})(Backbone.Form);
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "backbone-forms",
|
||||
"filename": "backbone-forms.min.js",
|
||||
"version": "0.12.0",
|
||||
"description": "Form framework for BackboneJS with nested forms, editable lists and validation",
|
||||
"homepage": "https://github.com/powmedia/backbone-forms",
|
||||
"keywords": [
|
||||
"forms",
|
||||
"backbone"
|
||||
],
|
||||
"devDependencies": {
|
||||
"backbone.js": "1.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/powmedia/backbone-forms"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user