Skip to content

Instantly share code, notes, and snippets.

@jshirley
Created August 20, 2012 23:11
Show Gist options
  • Save jshirley/3409070 to your computer and use it in GitHub Desktop.
Save jshirley/3409070 to your computer and use it in GitHub Desktop.
YUI.add('handlebars-helpers', function(Y) {
var NS = Y.namespace('TDP'),
Handlebars = Y.Handlebars,
Lang = Y.Lang,
isString = Lang.isString,
isArray = Lang.isArray,
isObject = Lang.isObject,
isFunction = Lang.isFunction,
Aeach = Y.Array.each,
form_templates = {
'basic_field' : Handlebars.compile(
'<div class="control-group{{#classNames}} {{.}}{{/classNames}}">' +
'<label class="control-label" for="{{id}}">{{label}}</label>' +
'<div class="controls">' +
'<input type="{{type}}" name="{{name}}" value="{{value}}" class="{{#inputClassNames}}{{.}} {{/inputClassNames}}">' +
'{{#help_inline}}<p class="help-inline">{{{.}}}</p>{{/help_inline}}' +
'{{#help_block}}<p class="help-block">{{{.}}}</p>{{/help_block}}' +
'</div>' +
'</div>'),
'textarea_field' : Handlebars.compile(
'<div class="control-group{{#classNames}} {{.}}{{/classNames}}">' +
'<label class="control-label" for="{{id}}">{{label}}</label>' +
'<div class="controls">' +
'<textarea name="{{name}}">{{{value}}}</textarea>' +
'{{#help_inline}}<p class="help-inline">{{{.}}}</p>{{/help_inline}}' +
'{{#help_block}}<p class="help-block">{{{.}}}</p>{{/help_block}}' +
'</div>' +
'</div>'),
'select_field' : Handlebars.compile(
'<div class="control-group{{#classNames}} {{.}}{{/classNames}}">' +
'<label class="control-label" for="{{id}}">{{label}}</label>' +
'<div class="controls">' +
'<select name="{{name}}">{{#options}}<option value="{{id}}"{{#selected}} selected{{/selected}}>{{name}}</option>{{/options}}</select>' +
'{{#help_inline}}<p class="help-inline">{{{.}}}</p>{{/help_inline}}' +
'{{#help_block}}<p class="help-block">{{{.}}}</p>{{/help_block}}' +
'</div>' +
'</div>'),
'options_field' : Handlebars.compile(
'<div class="control-group{{#classNames}} {{.}}{{/classNames}}">' +
'<label class="control-label" for="{{id}}">{{label}}</label>' +
'<div class="controls">' +
'{{#options}}<label class="{{type}}"><input type="{{type}}" name="{{name}}" value="{{value}}" {{#if checked}}checked{{/if}}>{{label}}</label>{{/options}}' +
'{{#help_inline}}<p class="help-inline">{{{.}}}</p>{{/help_inline}}' +
'{{#help_block}}<p class="help-block">{{{.}}}</p>{{/help_block}}' +
'</div>' +
'</div>')
};
// Some extensions require a global Handlebars object, so let's violate
// our sandbox. It's bad form.
if ( typeof window !== 'undefined' ) {
window.Handlebars = Handlebars;
}
Handlebars.registerHelper('input_field', function(cfg) {
var template = form_templates[ cfg.template || 'basic_field' ];
if ( ! isString( cfg.id ) ) {
cfg.id = YUI().guid('hb_');
}
if ( ! isString( cfg.value ) ) {
cfg.value = '';
}
Aeach( [ 'error', 'warning', 'success' ], function(level) {
if ( isString( cfg[level] ) ) {
if ( isArray( cfg.classNames ) ) {
cfg.classNames.push(level);
} else {
if ( cfg.classNames ) {
cfg.classNames = [ level, cfg.classNames ];
} else {
cfg.classNames = [ level ];
}
}
if ( cfg.help_block && !isArray( cfg.help_block ) ) {
cfg.help_block = [ cfg.help_block ];
}
else if ( !cfg.help_block ) {
cfg.help_block = [];
}
cfg.help_block.push( cfg[level] );
}
});
if ( cfg.required ) {
if ( cfg.help_inline && !isArray( cfg.help_inline ) ) {
cfg.help_inline = [ cfg.help_inline ];
}
else if ( !cfg.help_inline ) {
cfg.help_inline = [];
}
cfg.help_inline.unshift('<i class="icon-asterisk"></i>');
}
return template(cfg);
});
Handlebars.registerHelper('text_field', function(label, name, value, id) {
var cfg = {
type : 'text',
};
if ( isObject(label) ) {
cfg = Y.merge(cfg, label);
}
else {
cfg.label = label;
cfg.name = name;
cfg.value = value;
cfg.id = id;
}
return Handlebars.helpers.input_field(cfg);
});
Handlebars.registerHelper('select_field', function(cfg) {
if ( !isString(cfg.template) ) {
cfg.template = 'select_field';
}
return Handlebars.helpers.input_field(cfg);
});
Handlebars.registerHelper('textarea_field', function(label, name, value, id) {
var cfg = {
type : 'text',
};
if ( isObject(label) ) {
cfg = Y.merge(cfg, label);
}
else {
cfg.label = label;
cfg.name = name;
cfg.value = value;
cfg.id = id;
}
cfg.template = 'textarea_field';
return Handlebars.helpers.input_field(cfg);
});
Handlebars.registerHelper('options_field', function(cfg) {
if ( !isObject(cfg) ) {
Y.log('Invalid configuration passed into options_field, expected an object and got ' + typeof cfg);
return '';
}
cfg.template = 'options_field';
Aeach( cfg.options, function(option) {
if ( option.type === 'radio' || option.type === 'checkbox' ) {
// Don't ask... I should learn how to code?
} else {
option.type = 'checkbox';
}
// Convert to a boolean from any truthy value
option.checked = option.checked ? true : false;
});
return Handlebars.helpers.input_field(cfg);
});
Handlebars.registerHelper('password_field', function(label, name, value, id) {
if ( typeof id !== 'string' ) {
id = YUI().guid('hb_');
}
return '<div class="clearfix">' +
'<label for="' + id + '">' + label + '</label>' +
'<div class="input">' +
'<input type="password" name="' + name + '">' +
'</div>' +
'</div>';
});
Handlebars.registerHelper('render_form', function(form) {
var buffer = '',
form_buttons = "\n",
name_map = {};
if ( ! Y.Lang.isObject(form) ) {
return 'No form specified, nothing to render.';
}
if ( !form.action ) {
form.action = '#';
}
if ( !form.method ) {
form.method = 'post';
}
buffer = '<form method="' + form.method + '" action="' + form.action + '">';
if ( ! isObject(form) ) {
return 'Invalid form passed in, I need an object!';
}
Aeach( form.fields, function(field) {
var method = ( field.type || 'text' ) + '_field',
fn = Handlebars.helpers[method],
template = form_templates[method],
val;
if ( field.type === 'password' ) {
method = 'text_field';
fn = Handlebars.helpers[method];
template = form_templates[method];
}
if ( isFunction(fn) || template ) {
val = isObject(form.values) ? form.values[field.name] : null;
if ( val && !field.value ) {
if ( field.type === 'select' ) {
Aeach( field.options, function(option) {
if ( typeof option.selected === 'undefined' && option.id === val ) {
option.selected = true;
}
});
}
else if ( field.type === 'options' ) {
// Options can be different, so we have to check at each level...
Aeach( field.options, function(option) {
if ( typeof option.checked === 'undefined' && option.value === val ) {
option.checked = true;
}
});
} else {
field.value = form.values[field.name];
}
}
// Options can be nested, so we have to check each option in the group, too
else if ( form.values && field.type === 'options' ) {
// Options can be different, so we have to check at each level...
Aeach( field.options, function(option) {
var val = form.values[option.name]
if ( val && typeof option.checked === 'undefined' && option.value === val ) {
option.checked = true;
}
});
}
if ( !fn ) {
field.template = method;
fn = Handlebars.helpers['input_field'];
}
buffer += fn.apply(this, [ field ]);
}
});
if ( typeof form.buttons === 'undefined' ) {
form.buttons = [ { classNames : [ 'btn', 'btn-primary' ], label : 'Submit' } ];
}
if ( isArray(form.buttons) ) {
Aeach( form.buttons, function(button) {
if ( !button.classNames ) {
button.classNames = [ 'btn' ];
}
else if ( !isArray( button.classNames ) ) {
button.classNames = [ button.classNames ];
}
if ( button.link ) {
form_buttons += '<a class="' + button.classNames.join(' ') + '" href="' + button.link + '">' + button.label + "</a>\n";
} else {
form_buttons += '<button class="' + button.classNames.join(' ') + '">' + button.label + "</button>\n";
}
});
}
buffer += '<div class="form-actions">' + form_buttons + '</div>';
return buffer;
});
}, '0.1.0', {
requires: [
'handlebars'
]
});
var template = Y.Handlebars.compile('{{{ render_form form }}}');
template({
form : {
action : '/login',
fields : [
{ label : 'Email Address', name : 'email' },
{ label : 'Password', type : 'password', name : 'password' }
]
}
});
/* This is an extension to any View to decorate a form, it expects a Message::Stack style serialization.
var MyView = Y.Base.create('myView', Y.View, [ Y.TDP.FormView ], { /* view stuff */ });
*/
YUI.add('tdp-view-form', function(Y) {
var FormView,
Handlebars = Y.Handlebars,
JSONparse = Y.JSON.parse,
Lang = Y.Lang,
Aeach = Y.Array.each,
sub = Lang.sub,
isArray = Lang.isArray,
isObject = Lang.isObject,
isValue = Lang.isValue,
isString = Lang.isString,
isNumber = Lang.isNumber,
isFunction = Lang.isFunction,
subject_map = {
// If this is the autocomplete, it will be tag_input
'category_pk1' : 'tag_input'
};
FormView = function() { };
FormView.FIELD_SELECTOR = 'div.control-group';
FormView.INPUT_SELECTOR = '*';
FormView.LABEL_SELECTOR = 'label';
FormView.GLOBAL_MESSAGE_SELECTOR = 'div.alert';
FormView.prototype = {
message_template : Handlebars.compile('<div class="alert alert-{{level}}"><a class="close" data-dismiss="alert" href="#">&times;</a>{{msgid}}</div>'),
message_listener : null,
initializer : function() {
},
clearMessages : function() { },
parseMessages : function(text) {
var container = this.get('container'),
dialog = this.get('dialog'),
scope = this.get('scope') || this.name,
local = [],
global = [],
data;
if ( dialog ) {
container = dialog.get('contentBox');
}
if ( isString(text) ) {
data = JSONparse(text);
}
else if ( isObject(text) ) {
data = text;
}
Y.log('Parsing messages from');
Y.log(data);
this.resetMessages();
if ( !data || !data.messages ) {
return;
}
if ( isObject(data.messages) ) {
Y.log('Finding messages in scope: ' + scope);
local = data.messages[ scope ] || [];
if ( isArray( data.messages.global ) ) {
global = data.messages.global;
}
}
else if ( isArray(data.messages) ) {
local = data.messages;
}
Aeach( local, function(message) {
var subject = subject_map[message.subject] ? subject_map[message.subject] : message.subject,
elements = container.all(FormView.FIELD_SELECTOR + ' ' + FormView.INPUT_SELECTOR + '[name=' + subject + ']');
elements.each( function(el) {
var field = el.ancestor(FormView.FIELD_SELECTOR);
help = field.one('.help-block');
el.once('focus', function() {
field.removeClass('error');
field.all('.form-message').remove({ destroy : true });
});
field.addClass('error');
if ( help ) {
help.setHTML('<span class="form-message">' + message.message + '</span>');
}
else {
el.get('parentNode').append('<p class="help-block"><span class="form-message">' + message.message + '</span></p>');
}
});
});
Aeach( global, this.addGlobalMessage, this );
},
resetMessages : function() {
var container = this.get('container'),
fields = container.all( FormView.FIELD_SELECTOR ),
help = container.all('.help-block .form-message'),
global = container.all( FormView.GLOBAL_MESSAGE_SELECTOR );
fields.removeClass('error');
help.remove({ destroy : true });
global.remove({ destroy : true });
//global.transition({ opacity : 0, height : 0, duration : 0.35 }, function() { this.remove({ destroy : true }); });
},
addGlobalMessage : function(message) {
var container = this.get('container'),
template = this.message_template;
if ( !this.message_listener ) {
this.message_listener = container.delegate('click', this.removeGlobalMessage, 'div.alert .close', this);
}
// Insert before the very first element found.
container.insert( template(message), container.one('*') );
},
removeGlobalMessage : function(e) {
Y.log('removeGlobalMessage');
e.preventDefault();
var alert = e.currentTarget.ancestor( FormView.GLOBAL_MESSAGE_SELECTOR );
if ( alert ) {
alert.transition({ height : 0, opacity : 0, duration : 0.35 }, function() { this.remove({ destroy : true }); });
}
},
_formatFields : function() { },
initializer : function(config) {
//isValue(config.scope) && (this.scope = config.scope);
},
serializeForm : function(form) {
var attrs = {};
form.all('input,textarea,select').each(function(el) {
var type = el.get('tagName') === 'INPUT' ?
el.get('type').toUpperCase() : el.get('tagName'),
name = el.get('name'),
value = el.get('value');
if ( !name ) {
return;
}
if ( type === 'SUBMIT' || type === 'CANCEL' || type === 'BUTTON' ) {
/* Do nothing, no processing necessary */
}
else if ( type === 'RADIO' || type === 'CHECKBOX' ) {
if ( el.get('checked') ) {
if ( isArray(attrs[name]) ) {
attrs[name].push(value);
}
else if ( typeof attrs[name] !== 'undefined' ) {
attrs[name] = [ attrs[name], value ];
} else {
attrs[name] = value;
}
}
}
else {
attrs[name] = value;
}
});
return attrs;
},
prompt : function(message, cb) {
if ( !isFunction(cb) ) { cb = function() { }; }
var panel = new Y.Panel({
bodyContent : message,
modal : true,
centered : true,
render : true,
zIndex : 100,
buttons : [
{ value : 'Cancel', classNames : [ 'btn' ], action : function() { panel.destroy(); } },
{ value : 'Ok', classNames : [ 'btn', 'btn-primary' ], action : function() { cb(); panel.destroy(); } }
]
}),
primary = panel.get('boundingBox').one('.btn-primary');
if ( primary ) {
primary.focus();
}
},
alert : function(message, cb) {
if ( !isFunction(cb) ) { cb = function() { }; }
var panel = new Y.Panel({
bodyContent : message,
modal : true,
centered : true,
render : true,
zIndex : 100,
buttons : [
{ value : 'Ok', classNames : [ 'btn', 'btn-primary' ], action : function() { cb(); panel.destroy(); } }
]
}),
primary = panel.get('boundingBox').one('.btn-primary');
if ( primary ) {
primary.focus();
}
}
};
Y.namespace('TDP').FormView = FormView;
}, 'tdp-view-form', {
requires : [ 'node', 'event', 'json' ]
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment