Skip to content

Instantly share code, notes, and snippets.

@jshirley
Created May 29, 2012 17:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jshirley/2829556 to your computer and use it in GitHub Desktop.
Save jshirley/2829556 to your computer and use it in GitHub Desktop.
Bootstrap Form Macros. Use these in combination with Twitter Bootstrap CSS, Catalyst::Plugin::MessageStack and Data::Manager
[%~
# Some illustrious documentation to get you started:
#
# The macros defined are to generate markup to save people from copy and pasting
# markup. They copy and paste hashes instead, which should be safer (in theory)
#
# The macros for forms are:
# * text_field - A simple text field, pass in type to override input type
# * password_field - A password field, equivalent to
# text_field({ type => 'password', ... })
# * textarea_field - A textarea field
# * readonly_field - A display-only field, with a hidden input
# * select_field - A <select> field, with options passed in the `array`
# parameter.
# * checkboxes - A group of fields, displayed in nice fashion. If a
# radio is needed, pass type => 'radio'.
# * checkbox - A single checkbox field
# If a radio is needed, pass type => 'radio'.
# * form_actions - Use this method to cap off the form actions and end
# a form.
#
# These use Twitter Bootstrap markup structure for the markup. Without the
# bootstrap CSS, things will not look correct. You can read more about the
# Bootstrap forms at http://twitter.github.com/bootstrap/base-css.html#forms
#
# The basic structure is a containing div and the label and input is in a
# separate div.
#
# Additionally, there is a pretty_date formatter that will display dates
# in a human friendly format. Things like, '24 minutes ago' versus a silly
# date string.
#
# It is recommended to look at /guide/form to see this stuff in action.
#
# Integration with Catalyst::Plugin::MessageStack and Data::Manager. This again
# is better demonstrated looking at /guide/form.
#
# To automatically look inside the `messages` and `results` objects in the
# stash, simply set `context.scope` to the current scope of the results.
# After that, messaging and sticky values should co-exist happily.
USE time_dir = Time.Duration;
MACRO system_localize(str) BLOCK;
IF str.match('^\@'); c.loc(str); ELSE; str | html; END;
END;
MACRO pretty_date(dt) BLOCK;
SET now_secs = now.epoch;
SET then_secs = dt.epoch || now_secs;
IF(now_secs > then_secs);
time_dir.ago(now_secs - then_secs);
ELSE;
time_dir.from_now(then_secs - now_secs);
END;
END;
MACRO form_actions(actions) BLOCK;
IF !actions; actions = [ { } ]; END;
%]
<div class="form-actions">
[% FOREACH action IN actions;
IF action.type == 'cancel' %]
<button class="btn"><i class="icon-remove"></i> [% c.loc(action.label || 'Cancel') %]</button>
[% ELSE %]
<button class="btn btn-primary"><i class="icon-[% action.icon || 'ok' %] icon-white"></i> [% c.loc(action.label || 'Save') %]</button>
[% END %]
[% END %]
</div>[%
END;
MACRO label_field(info) BLOCK;
IF !info.error && stack && stack.count;
error_name = info.name;
IF context.scope;
error_name = error_name.replace("${context.scope}.", "");
END;
info.error = c.loc( stack.for_subject(error_name).for_level('error').first_message.id );
END %]
<label class="control-label" for="[% info.id %]"
[%~ IF info.tooltip %] data-tooltip="[% info.tooltip | html %]"[% END ~%]
>
[%~ c.loc(info.label, info.params) ~%]
</label>
[% END;
MACRO readonly_field(info) BLOCK;
IF info.filter && info.value;
f = info.filter;
IF f == "join";
info.value = info.value.join(', ');
ELSE;
TRY; info.value = FILTER $f; info.value; END; CATCH; "Error!"; END;
END;
END;
%]
<div class="control-group[% IF info.error %] error[% ELSIF info.warning %] warning[% ELSIF info.success %] success[% END %]">
[% label_field(info) %]
<div class="controls ro">
<input type="hidden" id="form_[% info.name %]" name="[% info.name %]" value="[% info.value | html %]"/><span>
<span class="uneditable-input">[% info.value %]</span>
[% IF info.link || info.required || info.error || info.success || info.warning %]
<span class="help-inline">
[% IF info.required %]<span class="req">[% c.loc('FIELD REQUIRED SPLAT') %]</span>[% END; info.error; info.warning; info.success %]
[%~ IF info.link %]<a href="[% info.link %]">[%~ END ~%]
</span>
[% END %]
[% IF info.support %]<p class="help-block">[% c.loc(info.support) %]</p>[% END %]
[%~ IF info.link ~%]</a>[%~ END ~%]</span>
</div>
</div>
[% END;
MACRO select_field(info) BLOCK;
IF !info.defined('value_method'); info.value_method = 'value'; END;
IF !info.defined('label_method'); info.label_method = 'label'; END;
IF !info.value && results.${context.scope};
value_name = info.name;
IF context.scope; value_name = value_name.replace("${context.scope}.", ""); END;
info.value = results.${context.scope}.get_original_value(value_name);
END;
IF !info.error && messages.for_scope(context.scope);
value_name = info.name;
IF context.scope; value_name = value_name.replace("^${context.scope}.", ""); END;
info.error = messages.for_scope(context.scope).for_subject(value_name).first_message.id;
END;
%]
[%~ IF info.dynamic_array =%]<script type="text/javascript">if ( typeof dynamic_forms === 'undefined' ) dynamic_forms = {}; dynamic_forms['[% info.value_from %]'] = { 'source': '[% info.dynamic_array %]', 'update': '[% info.name %]', 'value': "[% info.value || html %]" };</script>[% IF info.options.size == 0; info.options.push(c.loc('Please select [_1]', [ info.value_from ] ) ); END; END %]
<div class="control-group[% IF info.error %] error[% ELSIF info.warning %] warning[% ELSIF info.success %] success[% END %]">
[% label_field(info) %]
<div class="select controls[% IF info.container_classes; " "; info.container_classes.join(" "); END %]">
<[% IF info.type == 'radio'; "div"; ELSE; "select"; END %] id="form_[% info.name %]" name="[% info.name %]"
[%~ IF info.classes %] class="[% info.classes.join(' ') %]"[%~ END ~%]
[%~ IF info.disabled %] disabled="disabled"[%~ END ~%]
[%~ FOREACH datum IN info.data.pairs %] data-[% datum.key %]="[% datum.value %]"[%~ END ~%]>
[% IF info.type != "radio" && info.default_option %]
[% IF info.type == 'radio' %]
[% ELSE %]
<option value="[% info.default_option.0 | html %]">[% info.default_option.1 %]</option>
[% END %]
[% END %]
[% IF info.optgroups;
info.options = [];
DEFAULT info.children = 'children';
FOREACH parent IN info.optgroups;
info.options.push({ optgroup => parent.${info.label_method} });
FOREACH child IN parent.children;
info.options.push(child);
END;
END;
END %]
[% has_optgroup = 0;
FOREACH item = info.options;
IF item.optgroup; has_optgroup = 1;
IF !loop.first %]</optgroup>[% END;
%]<optgroup label="[% item.optgroup | html %]">[%
ELSE;
IF info.defined('value_method') && item.keys.defined;
SET this_value = item.${info.value_method};
ELSE;
SET this_value = item;
END;
IF info.defined('label_method') && item.keys.defined;
SET this_name = item.${info.label_method};
ELSE;
SET this_name = item;
END;
IF info.defined('elide');
this_name = string_trunc.elide(this_name, info.elide);
END;
%][% IF info.type == "radio" %]<label><input type="radio" name="[% info.name %]" value="[% this_value | html %]"[% IF info.value == this_value %] checked="checked"[% END %]> [% c.loc(this_name) %]</label>[% info.radio_trailing %]
[% ELSE %]<option value="[% this_value | html %]" [% IF this_value == info.value %]selected="selected"[% END %]>[% c.loc(this_name) %]</option>[% END %]
[% END; # Not an optgroup %]
[% END;
IF has_optgroup %]</optgroup>[% END %]
[% IF info.type != 'radio' %]</select>[% END %]
[% IF info.type == 'radio' %]
[% IF info.required %]<span class="req">[% c.loc('FIELD REQUIRED SPLAT') %]</span>[% END %]
</div>
[% ELSE %]
[% IF info.required || info.error || info.success || info.warning %]
[% END %]
<span class="help-inline">[% IF info.required %]<span class="req">[% c.loc('FIELD REQUIRED SPLAT') %]</span>[% END; info.error; info.warning; info.success %]</span>
[% END %]
[% IF info.support %]<p class="help-block">[% c.loc(info.support) %]</p>[% END %]
</div>
</div>
[% END;
MACRO password_field(info) BLOCK;
info.type = 'password';
info.value = ''; # Force values to not be set.
text_field(info);
END;
MACRO datetime_field(info) BLOCK;
IF !info.defined('type'); info.type = 'date'; END;
IF !info.defined('value');
IF results.${context.scope};
value_name = info.name;
IF context.scope; value_name = value_name.replace("^${context.scope}.", ""); END;
info.value = results.${context.scope}.get_original_value(value_name);
END;
IF !info.defined('value') && info.default_value;
info.value = info.default_value;
END;
END;
IF !info.error && messages.for_scope(context.scope);
value_name = info.name;
IF context.scope; value_name = value_name.replace("^${context.scope}.", ""); END;
message = messages.for_scope(context.scope).for_subject(value_name).first_message;
IF message; info.${message.level} = c.loc(message.id); END;
END;
IF info.filter && info.value;
f = info.filter;
IF f == "join";
info.value = info.value.join(', ');
ELSE;
TRY; info.value = FILTER $f; info.value; END; CATCH; "Error applying filter to ${info.name}!"; END;
END;
END;
IF info.defined('value') && info.defined('timezone');
info.value = set_timezone(info.value, info.timezone);
END;
DEFAULT info.id = "form_" _ info.name.replace('\.', '_');
%]
<div class="control-group[% IF info.error %] error[% ELSIF info.warning %] warning[% ELSIF info.success %] success[% END %]">
[% label_field(info) %]
<div class="[% info.type || 'datetime' %] controls row">
[%~ IF info.prepend || info.append ~%]<div class="[% IF info.prepend %]input-prepend[% END; IF info.append %] input-append[% END %]">
[%~ IF info.prepend ~%]<span class="add-on">[% info.prepend %]</span>[%~ END ~%]
[%~ END ~%]
<input style="width: 90px;" [%~ ~%]
type="[% info.type || 'date' %]"[%~ ~%]
id="[% info.id %]" name="[% info.name %].date"[%~ =%]
[% IF info.pattern %] pattern="[% info.pattern %]" [% END =%]
value="[% info.value | html %]"
[%~ IF info.classes %] class="[% info.classes.join(" ") %]"[% END ~%]
[% # The unicode char below is purely to make the placeholder junk work. %]
[%~ IF info.hint %] placeholder="&#x25E6; [% c.loc(info.hint) %]"[% END ~%]
[%~ IF info.autocomplete == 0 %] autocomplete="false"[% END ~%]
[%~ IF info.disabled == 1 %] disabled="disabled"[% END ~%]
[%~ IF info.tabindex %] tabindex="[% info.tabindex %]"[% END ~%]
[%~ IF info.maxlength %] maxlength="[% info.maxlength %]"[% END ~%]>
[%~ IF info.append ~%]<span class="add-on">[% info.append %]</span>[%~ END ~%]
[%~ IF info.prepend || info.append ~%]</div>[%~ END ~%]
<select name="[% info.name %].hour" style="width: 5em;">
[% FOREACH i IN [0..23] %]
<option value="[% i | format("%02d") %]"[% IF info.value.hour && info.value.hour == i %] selected="selected"[% END %]>
[% IF info.military_time %]
[% i | format("%02d") %]
[% ELSE;
IF i == 0; c.loc("[_1]AM", 12);
ELSIF i < 12; c.loc("[_1]AM", i);
ELSIF i == 12; c.loc("[_1]PM", 12);
ELSE; c.loc("[_1]PM", i - 12);
END %]
[% END %]
</option>
[% END %]
</select>
<select name="[% info.name %].minute" style="width: 5em;">
[% FOR i IN [0, 15, 30, 45] %]
<option value="[% i | format("%02d") %]"[% IF info.value.minute && info.value.minute >= i && info.value.minute < i + 15 %] selected="selected"[% END %]>[% i | format("%02d") %]</option>
[% END %]
</select>
[% IF info.required || info.error || info.success || info.warning %]
<span class="help-inline">[% IF info.required %]<span class="req">[% c.loc('FIELD REQUIRED SPLAT') %]</span>[% END; info.error; info.warning; info.success %]</span>
[% END %]
[% IF info.support %]<p class="help-block">[% c.loc(info.support) %]</p>[% END %]
</div>
</div>
[% END;
MACRO text_field(info) BLOCK;
IF !info.defined('value');
IF results.${context.scope};
value_name = info.name;
IF context.scope; value_name = value_name.replace("^${context.scope}.", ""); END;
info.value = results.${context.scope}.get_original_value(value_name);
END;
IF !info.defined('value') && info.default_value;
info.value = info.default_value;
END;
END;
IF !info.error && messages.for_scope(context.scope);
value_name = info.name;
IF context.scope; value_name = value_name.replace("^${context.scope}.", ""); END;
message = messages.for_scope(context.scope).for_subject(value_name).first_message;
IF message; info.${message.level} = c.loc(message.id); END;
END;
IF info.filter && info.value;
f = info.filter;
IF f == "join";
info.value = info.value.join(', ');
ELSE;
TRY; info.value = FILTER $f; info.value; END; CATCH; "Error applying filter to ${info.name}!"; END;
END;
END;
DEFAULT info.id = "form_" _ info.name.replace('\.', '_');
%]
<div class="control-group[% IF info.error %] error[% ELSIF info.warning %] warning[% ELSIF info.success %] success[% END %]">
[% label_field(info) %]
<div class="[% info.type || 'text' %] controls">
[%~ IF info.prepend || info.append ~%]<div class="[% IF info.prepend %]input-prepend[% END; IF info.append %] input-append[% END %]">
[%~ IF info.prepend ~%]<span class="add-on">[% info.prepend %]</span>[%~ END ~%]
[%~ END ~%]
<input type="[% info.type || 'text' %]" id="[% info.id %]" name="[% info.name %]"
[% IF info.pattern %] pattern="[% info.pattern %]" [% END %]
value="[% info.value | html %]"
[%~ IF info.classes %] class="[% info.classes.join(" ") %]"[% END ~%]
[% # The unicode char below is purely to make the placeholder junk work. %]
[%~ IF info.hint %] placeholder="&#x25E6; [% c.loc(info.hint) %]"[% END ~%]
[%~ IF info.autocomplete == 0 %] autocomplete="false"[% END ~%]
[%~ IF info.disabled == 1 %] disabled="disabled"[% END ~%]
[%~ IF info.tabindex %] tabindex="[% info.tabindex %]"[% END ~%]
[%~ IF info.maxlength %] maxlength="[% info.maxlength %]"[% END ~%]>
[%~ IF info.append ~%]<span class="add-on">[% info.append %]</span>[%~ END ~%]
[%~ IF info.prepend || info.append ~%]</div>[%~ END ~%]
[% IF info.required || info.error || info.success || info.warning %]
<span class="help-inline">[% IF info.required %]<span class="req">[% c.loc('FIELD REQUIRED SPLAT') %]</span>[% END; info.error; info.warning; info.success %]</span>
[% END %]
[% IF info.support %]<p class="help-block">[% c.loc(info.support) %]</p>[% END %]
</div>
</div>
[% END;
MACRO textarea_field(info) BLOCK;
IF !info.defined('value');
IF results.${context.scope};
value_name = info.name;
IF context.scope; value_name = value_name.replace("^${context.scope}.", ""); END;
info.value = results.${context.scope}.get_original_value(value_name);
END;
IF !info.defined('value') && info.default_value;
info.value = info.default_value;
END;
END;
IF !info.error && messages.for_scope(context.scope);
value_name = info.name;
IF context.scope; value_name = value_name.replace("^${context.scope}.", ""); END;
message = messages.for_scope(context.scope).for_subject(value_name).first_message;
IF message; info.${message.level} = c.loc(message.id); END;
END;
%]
<div class="control-group[% IF info.error %] error[% ELSIF info.warning %] warning[% ELSIF info.success %] success[% END %]">
[% label_field(info) %]
<div class="[% info.type || 'textarea' %] controls">
<textarea [%= ~%]
id="form_[% info.name %]" name="[% info.name %]"
[% IF info.hint %] placeholder="[% c.loc(info.hint) %]"[% END %]
[%~ IF info.classes %] class="[% info.classes.join(' ') %]"[% END ~%]
[%~ IF info.disabled == 1 %] disabled="disabled"[% END ~%]
[%~ IF info.autocomplete == 0 %] autocomplete="false"[% END ~%]
[%~ IF info.tabindex %] tabindex="[% info.tabindex %]"[% END ~%]
[%~ IF info.rows %] rows="[% info.rows %]"[% END ~%]
[%~ IF info.cols %] cols="[% info.cols %]"[% END ~%]
>
[%~ info.value | html ~%]
</textarea>
[% IF info.error || info.success || info.warning %]
<span class="help-block">[% IF info.required %]<span class="req">[% c.loc('FIELD REQUIRED SPLAT') %]</span>[% END; info.error; info.warning; info.success %]</span>
[% END %]
[% IF info.support %]<p class="help-block">[% c.loc(info.support) %]</p>[% END %]
</div>
</div>
[% END;
MACRO checkboxes(info) BLOCK;
IF !info.defined('value') && !info.defined('selected');
temp = [];
IF results.${context.scope};
value_name = info.name;
IF context.scope; value_name = value_name.replace("^${context.scope}.", ""); END;
temp = results.${context.scope}.get_original_value(value_name);
END;
IF temp.size == 0 && ( info.defined('default_value') || info.defined('default_selected') );
temp = info.default_selected || info.default_value;
END;
info.selected = {};
FOREACH v IN temp;
info.selected.$v = v;
END;
END;
IF !info.error && messages.for_scope(context.scope);
value_name = info.name;
IF context.scope; value_name = value_name.replace("^${context.scope}.", ""); END;
IF message; info.${message.level} = c.loc(message.id); END;
END;
%]
<div class="control-group[% IF info.error %] error[% ELSIF info.warning %] warning[% ELSIF info.success %] success[% END %]">
[% label_field(info) %]
<div class="checkboxes controls">
[% FOREACH item = info.options;
IF info.defined('value_method');
SET this_value = item.${info.value_method};
ELSE;
SET this_value = item;
END;
IF info.defined('value_method');
SET this_name = item.${info.label_method};
ELSE;
SET this_name = item;
END;
%]<label class="[% IF info.type == 'radio'; "radio"; ELSE; "checkbox"; END %][% IF info.inline %] inline[% END %]"><input type="[% info.type || 'checkbox' %]" name="[% info.name %]" value="[% this_value | html %]"[% IF info.value == this_value || info.selected.defined(this_value) %] checked="checked"[% END %]> [% this_name %]</label>
[% END %]
[% IF info.required || info.error || info.success || info.warning %]
<p class="help-block">
[% IF info.required %]<span class="req">[% c.loc('FIELD REQUIRED SPLAT') %]</span>[% END %]
[% info.error; info.warning; info.success %]</span>
</p>
[% END %]
</div>
</div>
[% END ~%]
[%~ MACRO checkbox(info) BLOCK;
IF !info.defined('checked') && results.${context.scope};
value_name = info.name;
IF context.scope; value_name = value_name.replace("${context.scope}.", ""); END;
info.checked = results.${context.scope}.get_original_value(value_name);
END;
IF !info.defined('checked');
IF results.${context.scope};
value_name = info.name;
IF context.scope; value_name = value_name.replace("^${context.scope}.", ""); END;
info.checked = results.${context.scope}.get_original_value(value_name);
END;
IF !info.defined('checked') && info.default_checked;
info.checked = info.default_checked;
END;
END;
IF !info.error && messages.for_scope(context.scope);
value_name = info.name;
IF context.scope; value_name = value_name.replace("^${context.scope}.", ""); END;
IF message; info.${message.level} = c.loc(message.id); END;
END;
%]
<div class="control-group[% IF info.error %] error[% ELSIF info.warning %] warning[% ELSIF info.success %] success[% END %]">
[% label_field(info) %]
<div class="[% info.type || 'text' %] controls">
<input type="[% info.type || 'checkbox' %]" id="[% info.id %]" name="[% info.name %]"
[% IF info.pattern %] pattern="[% info.pattern %]" [% END %]
value="[% info.value | html %]"
[%~ IF info.classes %] class="[% info.classes.join(" ") %]"[% END ~%]
[%~ IF info.disabled == 1 %] disabled="disabled"[% END ~%]
[%~ IF info.checked == 1 %] checked="checked"[% END ~%]
[%~ IF info.tabindex %] tabindex="[% info.tabindex %]"[% END ~%]
[%~ IF info.maxlength %] maxlength="[% info.maxlength %]"[% END ~%]>
[% IF info.required || info.error || info.success || info.warning %]
<span class="help-inline">[% IF info.required %]<span class="req">[% c.loc('FIELD REQUIRED SPLAT') %]</span>[% END; info.error; info.warning; info.success %]</span>
[% END %]
[% IF info.support %]<p class="help-block">[% c.loc(info.support) %]</p>[% END %]
</div>
</div>
[% END ~%]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment