Skip to content

Instantly share code, notes, and snippets.

@drmikecrowe
Created October 22, 2014 12:34
Show Gist options
  • Save drmikecrowe/4e65f00fd93f1b643f09 to your computer and use it in GitHub Desktop.
Save drmikecrowe/4e65f00fd93f1b643f09 to your computer and use it in GitHub Desktop.
Complex rendering solution for https://github.com/caolan/forms

NOT for the faint of heart :)

Notes:

  • This was for sails/waterline, so I pushed as much as I could into the view layer so I wouldn't have to re-lift sails. Anything in the controller or server required a server restart.
  • This is also totally geared for Bootstrap/horizontal forms. However, it can handle any rendering, just change formDefaults.json
  • The project was already using lodash (though underline might work as well), so I do use that for simplicity. You will need that to use this
  • This also has the added benefit of allowing you to pass in default values for fields
  • I was also integrating JQuery form validation (client-side), so I needed a client error message. So, the system can handle error_message (which it sends down for the first time), or error (from forms itself). It sends down the most relevant (error, if present, then error_message)
  • You can also hide HTML tags by making the tag '@' (I tried '', but ended up needing something else)

Files:

  • formDefaults.json: The template to use to render the form widget
  • FormService.js: A custom rendering function to apply this template to a form
  • userFormParams.json: An example form for a user
  • UserController.js: A snipped of the edit controller to render the form
  • edit.ejs: The view rendering the form

Here was one example of a field in a form:

This was using Labelauty to style buttons.

    "dueDaymap": {
        "label": "Due Days:",
        "classes": ["form-control", "labelauty-cb", "labelauty-days"],
        "label_class": "col-sm-1",
        "inner_class": "col-sm-11 labelauty-holder labelauty-holder labelauty-holder-dueDaymap",
        "choices": {
            "Su": "Sun",
            "Mo": "Mon",
            "Tu": "Tue",
            "We": "Wed",
            "Th": "Thu",
            "Fr": "Fri",
            "Sa": "Sat"
        },
        "value": ["Su","Mo","Tu","We","Th","Fr","Sa"],
        "error_message": "Please select 1 day minimum",
        "error_class": "dueDaymap-error alert alert-danger text-center"
    },
<form role="form" id="user" action="<%= url %>" method="post" class="form-horizontal">
<div class="row">
<div class="page-header"><h1>User</h1></div>
<div class="clearfix"></div>
<%- form.fields['firstname'].toHTML('firstname', render) %>
<%- form.fields['lastname'].toHTML('lastname', render) %>
<%- form.fields['username'].toHTML('username', render) %>
<%- form.fields['email'].toHTML('email', render) %>
<%- form.fields['phone'].toHTML('phone', render) %>
<%- form.fields['accountAffiliation'].toHTML('accountAffiliation', render) %>
<%- form.fields['id'].toHTML('id', render) %>
</div>
<div class="clearfix"></div>
<div class="btn-group">
<input data-key="submit" class="btn btn-primary" type="submit" id="submitButton"
name="submitButton"
value="Submit">
<input data-key="cancel" class="btn btn-warning" type="button" value="Cancel"
id="cancelButton" onclick="window.location='<%= abort %>'"/>
</div>
</form>
{
"outer_open": "<{OUTER_TAG} class='{OUTER_CLASS} {NAME}-holder'>",
"outer_close": "</{OUTER_TAG}>",
"inner_open": "<{INNER_TAG} class='{INNER_CLASS}'>",
"inner_close": "</{INNER_TAG}>",
"label_template": "<{LABEL_TAG} class='{LABEL_CLASS}' for='{NAME}'>{LABEL}</{LABEL_TAG}>",
"help_template": "<{HELP_TAG} class='{HELP_CLASS}'>{HELP}</{HELP_TAG}>",
"error_template": "<{ERROR_TAG} class='{ERROR_CLASS} hidden'><b>{ERROR}</b></{ERROR_TAG}>",
"template": "{OUTER_OPEN}{LABEL_TEMPLATE}{INNER_OPEN}{WIDGET}{HELP_TEMPLATE}{INNER_CLOSE}{ERROR_TEMPLATE}{OUTER_CLOSE}",
"outer_tag": "div",
"outer_class": "form-group",
"label_tag": "label",
"label_class": "col-sm-5 control-label",
"inner_tag": "div",
"inner_class": "col-sm-7",
"help_tag": "span",
"help_class": "help-block",
"error_tag": "div",
"error_class": "alert alert-danger text-center"
}
var forms = require('forms');
var fields = forms. fields,
validators = forms.validators,
widgets = forms.widgets;
var _ = require('lodash');
var fs = require('fs');
function repl(str,arr) {
_.forOwn(arr,function(value,key) {
var search = '{'+key+'}';
while ( str.indexOf(search)>-1 ) {
str = str.replace(search,value);
}
});
return str;
}
module.exports = {
render: function (name, object_init) {
// NOTE: I load this at runtime
var file = __dirname + '/../../views/partials/forms/formDefaults.json';
var json = fs.readFileSync(file).toString();
var defaults = JSON.parse(json);
var object = _.defaults(object_init, defaults);
object.widget.classes = object.widget.classes || [];
object.widget.classes.push('form-control');
var widget = object.widget.toHTML(name, object);
var hidden = (_.has(object, 'hidden') && object.hidden || object.widget.type == 'hidden ');
var outer_hidden = hidden || (_.has(object, 'outer_tag') && object.outer_tag == '@');
var label_hidden = hidden || (_.has(object, 'label_tag') && object.label_tag == '@');
var help_hidden = hidden || (!_.has(object, 'help') || (_.has(object, 'help_tag') && object.help_tag == '@'));
var error_hidden = hidden || (!(_.has(object, 'error') || _.has(object, 'error_message')) || (_.has(object, 'error_tag') && object.error_tag == '@'));
if (_.has(object, 'hidden') && object.hidden) {
object.outer_tag = object.label_template = '@';
}
// IF you have some really bizarre requirements, you can tweak the widget here
var template_replace = {
OUTER_OPEN: !outer_hidden ? object.outer_open : '',
OUTER_CLOSE: !outer_hidden ? object.outer_close : '',
LABEL_TEMPLATE: !label_hidden ? object.label_template : '',
INNER_OPEN: object.inner_open,
INNER_CLOSE: object.inner_close,
HELP_TEMPLATE: !help_hidden ? object.help_template : '',
ERROR_TEMPLATE: !error_hidden ? object.error_template : ''
};
var tag_replace = {
OUTER_TAG: object.outer_tag,
OUTER_CLASS: object.outer_class,
LABEL_TAG: object.label_tag,
LABEL_CLASS: object.label_class,
HELP_TAG: object.help_tag,
HELP_CLASS: object.help_class,
ERROR_TAG: object.error_tag,
ERROR_CLASS: object.error_class,
INNER_TAG: object.inner_tag,
INNER_CLASS: object.inner_class
};
var data_replace = {
LABEL: object.labelText(name),
HELP_TAG: object.help_tag,
HELP_CLASS: object.inner_class,
HELP: object.help,
ERROR_TAG: object.error_tag,
ERROR_CLASS: object.error_class,
ERROR: _.has(object, 'error') ? object.error : _.has(object, 'error_message') ? object.error_message : '',
WIDGET: widget,
NAME: name
};
var t = object.template;
t = repl(t, template_replace);
t = repl(t, tag_replace);
t = repl(t, data_replace);
return t;
}
};
edit: function(req, res) {
if (req.param('id')) {
User.findOne({
id: req.param('id')
}, function(err, user) {
if (err) return res.serverError("Error uc22.");
if (user) {
/* user.asFormDefault() returns:
{
id: { value: '542aa1f248fd080e00021878' },
firstname: { value: 'Mike' },
lastname: { value: 'Crowe' },
username: { value: 'drmikecrowe' },
email: { value: '[email protected]' },
accountAffiliation: { value: 'Just Me' }
}
*/
var form = userForm.form(user.asFormDefault());
var returninfo = {
url: '/user/update',
abort: '/user',
form: form,
render: FormsService.render
};
return res.view(returninfo);
}
});
} else {
return res.serverError("Error uc23.");
}
},
var forms = require('forms');
var fields = forms.fields,
validators = forms.validators,
widgets = forms.widgets;
var fs = require('fs');
function repl(str, arr) {
_.forOwn(arr, function (value, key) {
var search = '{' + key + '}';
while (str.indexOf(search) > -1) {
str = str.replace(search, value);
}
});
return str;
}
module.exports = {
form: function (def) {
def = def || {};
// NOTE: I load this at runtime
var file = __dirname + '/../../../views/user/partials/userFormParams.json';
var json = fs.readFileSync(file).toString();
var config = JSON.parse(json);
var data = _.merge(config, def);
var schema = {
id: fields.string({ widget: widgets.hidden(), value: data.id, hidden: true}),
firstname: fields.string(data.firstname),
lastname: fields.string(data.lastname),
username: fields.string(data.username),
email: fields.string(data.email),
phone: fields.string(data.phone),
accountAffiliation: fields.string(data.accountAffiliation)
};
return forms.create(schema);
}
};
{
"firstname": {
"label": "First Name",
"label_class": "col-sm-2"
},
"lastname": {
"label": "Last Name",
"label_class": "col-sm-2"
},
"username": {
"label": "Username",
"label_class": "col-sm-2"
},
"email": {
"label": "Email",
"label_class": "col-sm-2"
},
"phone": {
"label": "Phone Number",
"label_class": "col-sm-2"
},
"accountAffiliation": {
"label": "Account",
"label_class": "col-sm-2"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment