;(function() {
var Form = Backbone.Form,
Base = Form.editors.Base,
createTemplate = Form.helpers.createTemplate,
triggerCancellableEvent = Form.helpers.triggerCancellableEvent,
exports = {};
/**
* Additional editors that depend on jQuery UI
*/
//DATE
exports['jqueryui.Date'] = Base.extend({
className: 'bbf-jui-date',
initialize: function(options) {
Base.prototype.initialize.call(this, options);
//Cast to Date
if (this.value && !_.isDate(this.value)) {
this.value = new Date(this.value);
}
//Set default date
if (!this.value) {
var date = new Date();
date.setSeconds(0);
date.setMilliseconds(0);
this.value = date;
}
},
render: function() {
var $el = this.$el;
$el.html('');
var input = $('input', $el);
input.datepicker({
dateFormat: 'dd/mm/yy',
showButtonPanel: true
});
this._observeDatepickerEvents();
//Make sure setValue of this object is called, not of any objects extending it (e.g. DateTime)
exports['jqueryui.Date'].prototype.setValue.call(this, this.value);
return this;
},
/**
* @return {Date} Selected date
*/
getValue: function() {
var input = $('input', this.el),
date = input.datepicker('getDate');
return date;
},
setValue: function(value) {
$('input', this.el).datepicker('setDate', value);
},
focus: function() {
if (this.hasFocus) return;
this.$('input').datepicker('show');
},
blur: function() {
if (!this.hasFocus) return;
this.$('input').datepicker('hide');
},
_observeDatepickerEvents: function() {
var self = this;
this.$('input').datepicker('option', 'onSelect', function() {
self.trigger('change', self);
})
this.$('input').datepicker('option', 'onClose', function() {
if (!self.hasFocus) return;
self.trigger('blur', self);
});
this.$('input').datepicker('option', 'beforeShow', function() {
if (self.hasFocus) return {};
self.trigger('focus', self);
return {};
});
}
});
//DATETIME
exports['jqueryui.DateTime'] = exports['jqueryui.Date'].extend({
className: 'bbf-jui-datetime',
template: createTemplate(' : '),
render: function() {
function pad(n) {
return n < 10 ? '0' + n : n
}
//Render the date element first
exports['jqueryui.Date'].prototype.render.call(this);
//Setup hour options
var hours = _.range(0, 24),
hoursOptions = [];
_.each(hours, function(hour) {
hoursOptions.push('');
});
//Setup minute options
var minsInterval = this.schema.minsInterval || 15,
mins = _.range(0, 60, minsInterval),
minsOptions = [];
_.each(mins, function(min) {
minsOptions.push('');
});
//Render time selects
this.$el.append(this.template({
hours: hoursOptions.join(),
mins: minsOptions.join()
}));
this._observeDatepickerEvents();
//Store references to selects
this.$hours = $('select:eq(0)', this.el);
this.$mins = $('select:eq(1)', this.el);
//Set time
this.setValue(this.value);
return this;
},
/**
* @return {Date} Selected datetime
*/
getValue: function() {
var input = $('input', this.el),
date = input.datepicker('getDate');
date.setHours(this.$hours.val());
date.setMinutes(this.$mins.val());
date.setMilliseconds(0);
return date;
},
setValue: function(date) {
exports['jqueryui.Date'].prototype.setValue.call(this, date);
this.$hours.val(date.getHours());
this.$mins.val(date.getMinutes());
}
});
//LIST
exports['jqueryui.List'] = Base.extend({
className: 'bbf-jui-list',
//Note: The extra div around the
is used to limit the drag area
template: createTemplate('\
\
\
'),
itemTemplate: createTemplate('\
- \
{{text}}\
\
\
\
\
\
'),
editorTemplate: createTemplate('\
\
'),
events: {
'click .bbf-list-add': 'addNewItem',
'click .bbf-list-edit': 'editItem',
'click .bbf-list-del': 'deleteItem'
},
initialize: function(options) {
Base.prototype.initialize.call(this, options);
if (!this.schema) throw "Missing required option 'schema'";
this.schema.listType = this.schema.listType || 'Text';
if (this.schema.listType == 'NestedModel' && !this.schema.model)
throw "Missing required option 'schema.model'";
},
render: function() {
var $el = this.$el;
//Main element
$el.html(this.template());
//Create list
var self = this,
data = this.value || [],
schema = this.schema,
itemToString = this.itemToString,
itemTemplate = this.itemTemplate,
listEl = $('ul', $el);
_.each(data, function(itemData) {
var text = itemToString.call(self, itemData);
//Create DOM element
var li = $(itemTemplate({
id: itemData.id || '',
text: text
}));
//Attach data
$.data(li[0], 'data', itemData);
listEl.append(li);
});
//Make sortable
if (schema.sortable !== false) {
listEl.sortable({
axis: 'y',
cursor: 'move',
containment: 'parent'
});
$el.addClass('bbf-list-sortable');
}
//jQuery UI buttonize
$('button.bbf-list-add', $el).button({
text: false,
icons: { primary: 'ui-icon-plus' }
});
$('button.bbf-list-edit', $el).button({
text: false,
icons: { primary: 'ui-icon-pencil' }
});
$('button.bbf-list-del', $el).button({
text: false,
icons: { primary: 'ui-icon-trash' }
});
if (this.hasFocus) this.trigger('blur', this);
return this;
},
/**
* Formats an item for display in the list
* For example objects, dates etc. can have a custom
* itemToString method which says how it should be formatted.
*/
itemToString: function(data) {
if (!data) return data;
var schema = this.schema;
//If there's a specified toString use that
if (schema.itemToString) return schema.itemToString(data);
//Otherwise check if it's NestedModel with it's own toString() method
if (this.schema.listType == 'NestedModel') {
var model = new (this.schema.model)(data);
return model.toString();
}
//Last resort, just return the data as is
return data;
},
/**
* Add a new item to the list if it is completed in the editor
*/
addNewItem: function(event) {
if (event) event.preventDefault();
var self = this;
this.openEditor(null, function(value, editor) {
//Fire 'addItem' cancellable event
triggerCancellableEvent(self, 'addItem', [value, editor], function() {
var text = self.itemToString(value);
//Create DOM element
var li = $(self.itemTemplate({
id: value.id || '',
text: text
}));
//Store data
$.data(li[0], 'data', value);
$('ul', self.el).append(li);
//jQuery UI buttonize
$('button.bbf-list-edit', this.el).button({
text: false,
icons: { primary: 'ui-icon-pencil' }
});
$('button.bbf-list-del', this.el).button({
text: false,
icons: { primary: 'ui-icon-trash' }
});
self.trigger('add', self, value);
self.trigger('item:change', self, editor);
self.trigger('change', self);
});
});
},
/**
* Edit an existing item in the list
*/
editItem: function(event) {
event.preventDefault();
var self = this,
li = $(event.target).closest('li'),
originalValue = $.data(li[0], 'data');
this.openEditor(originalValue, function(newValue, editor) {
//Fire 'editItem' cancellable event
triggerCancellableEvent(self, 'editItem', [newValue, editor], function() {
//Update display
$('.bbf-list-text', li).html(self.itemToString(newValue));
//Store data
$.data(li[0], 'data', newValue);
self.trigger('item:change', self, editor);
self.trigger('change', self);
});
});
},
deleteItem: function(event) {
event.preventDefault();
var self = this,
li = $(event.target).closest('li'),
data = $.data(li[0], 'data');
var confirmDelete = (this.schema.confirmDelete) ? this.schema.confirmDelete : false,
confirmMsg = this.schema.confirmDeleteMsg || 'Are you sure?';
function remove() {
triggerCancellableEvent(self, 'removeItem', [data], function() {
li.remove();
self.trigger('remove', self, data);
self.trigger('change', self);
});
}
if (this.schema.confirmDelete) {
if (confirm(confirmMsg)) remove();
} else {
remove();
}
},
/**
* Opens the sub editor dialog
* @param {Mixed} Data (if editing existing list item, null otherwise)
* @param {Function} Save callback. receives: value
*/
openEditor: function(data, callback) {
var self = this,
schema = this.schema,
listType = schema.listType || 'Text';
var editor = Form.helpers.createEditor(listType, {
key: '',
schema: schema,
value: data
}).render();
var container = this.editorContainer = $(this.editorTemplate());
$('.bbf-list-editor', container).html(editor.el);
var saveAndClose = function() {
var errs = editor.validate();
if (errs) return;
callback(editor.getValue(), editor);
container.dialog('close');
}
var handleEnterPressed = function(event) {
if (event.keyCode != 13) return;
saveAndClose();
}
$(container).dialog({
resizable: false,
modal: true,
width: 500,
title: data ? 'Edit item' : 'New item',
buttons: {
'OK': saveAndClose,
'Cancel': function() {
container.dialog('close');
}
},
close: function() {
self.editorContainer = null;
$(document).unbind('keydown', handleEnterPressed);
editor.remove();
container.remove();
self.trigger('item:close', self, editor);
self.trigger('item:blur', self, editor);
self.trigger('blur', self);
}
});
this.trigger('item:open', this, editor);
this.trigger('item:focus', this, editor);
this.trigger('focus', this);
//Save and close dialog on Enter keypress
$(document).bind('keydown', handleEnterPressed);
},
getValue: function() {
var data = [];
$('li', this.el).each(function(index, li) {
data.push($.data(li, 'data'));
});
return data;
},
setValue: function(value) {
this.value = value;
this.render();
},
focus: function() {
if (this.hasFocus) return;
var item = this.$('li .bbf-list-edit').first();
if (item.length > 0) {
item.click();
}
else {
this.addNewItem();
}
},
blur: function() {
if (!this.hasFocus) return;
if (this.editorContainer) this.editorContainer.dialog('close');
}
});
//Exports
_.extend(Form.editors, exports);
})();