├── src
├── templates
│ ├── required.tpl
│ ├── choice-dropdown.tpl
│ ├── choice-radio.tpl
│ ├── choice-checkbox.tpl
│ ├── element-paragraph-text.tpl
│ ├── element-email.tpl
│ ├── element-number.tpl
│ ├── element-single-line-text.tpl
│ ├── element-section-break.tpl
│ ├── settings-dropdown.tpl
│ ├── settings-choice-radio.tpl
│ ├── settings-choice-checkbox.tpl
│ ├── element-dropdown.tpl
│ ├── rules.tpl
│ ├── rule.tpl
│ ├── element-multiple-choice.tpl
│ ├── element-checkboxes.tpl
│ ├── formbuilder-base.tpl
│ └── formbuilder-fields.tpl
├── libraries
│ ├── tabs.jquery.min.js
│ ├── tabs.jquery.js
│ └── dust-js
│ │ ├── dust-core-0.3.0.min.js
│ │ ├── dust-helpers.js
│ │ └── dust-full-0.3.0.min.js
├── json
│ └── example.json
├── css
│ └── style.css
├── less
│ └── style.less
├── formBuilder.jquery.min.js
└── formBuilder.jquery.js
├── demo
├── submit.php
├── save.php
└── render.php
├── .gitignore
├── index.php
├── README.md
└── loaders
└── php
└── formLoader.php
/src/templates/required.tpl:
--------------------------------------------------------------------------------
1 | *
--------------------------------------------------------------------------------
/demo/submit.php:
--------------------------------------------------------------------------------
1 | ';
4 | print_r($_POST);
5 | echo '';
--------------------------------------------------------------------------------
/src/templates/choice-dropdown.tpl:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/templates/choice-radio.tpl:
--------------------------------------------------------------------------------
1 |
form_data = json_decode(str_replace('\\', '', $form_json));
33 | $this->action = $form_action;
34 | }
35 |
36 | /**
37 | * Render the form
38 | *
39 | * @return void
40 | * @access public
41 | **/
42 | public function render_form()
43 | {
44 | if( empty($this->form_data) || empty($this->action) ) {
45 | throw new Exception("Error Processing Request", 1);
46 | }
47 |
48 | $fields = '';
49 |
50 | foreach ($this->form_data->fields as $field) {
51 |
52 | // Single line text
53 | if($field->type == 'element-single-line-text' ) {
54 | $fields .= $this->element_single_line_text($field);
55 | }
56 |
57 | // Number
58 | if($field->type == 'element-number') {
59 | $fields .= $this->element_number($field);
60 | }
61 |
62 | // Paragraph text
63 | if($field->type == 'element-paragraph-text') {
64 | $fields .= $this->element_paragraph_text($field);
65 | }
66 |
67 | // Checkboxes
68 | if($field->type == 'element-checkboxes') {
69 | $fields .= $this->element_checkboxes($field);
70 | }
71 |
72 | // Multiple choice
73 | if($field->type == 'element-multiple-choice') {
74 | $fields .= $this->element_multiple_choice($field);
75 | }
76 |
77 | // Dropdown
78 | if($field->type == 'element-dropdown') {
79 | $fields .= $this->element_dropdown($field);
80 | }
81 |
82 | // Section break
83 | if($field->type == 'element-section-break') {
84 | $fields .= $this->element_section_break($field);
85 | }
86 |
87 | }
88 |
89 | $form = $this->open_form($fields);
90 | echo $form;
91 | }
92 |
93 | /**
94 | * Render the form header
95 | *
96 | * @param object $fields
97 | * @return string $html
98 | * @access private
99 | **/
100 | private function open_form($fields)
101 | {
102 | $html = sprintf('';
108 | return $html;
109 | }
110 |
111 | /**
112 | * Encode element title
113 | *
114 | * @param string $title
115 | * @return string $str
116 | * @access private
117 | **/
118 | private function encode_element_title($title)
119 | {
120 | $str = str_replace(' ', '_', strtolower($title));
121 | $str = preg_replace("/[^a-zA-Z0-9.-_]/", "", $str);
122 | $str = htmlentities($str, ENT_QUOTES, 'UTF-8');
123 | $str = html_entity_decode($str, ENT_QUOTES, 'UTF-8');
124 |
125 | return $str;
126 | }
127 |
128 | /**
129 | * Get formatted label for form element
130 | *
131 | * @param string $id
132 | * @param string $title
133 | * @param mixed $required
134 | * @return string
135 | * @access private
136 | **/
137 | private function make_label($id, $title, $required)
138 | {
139 | if( $required ) {
140 | $html = sprintf('', $id, $title);
141 | } else {
142 | $html = sprintf('', $id, $title);
143 | }
144 |
145 | return $html;
146 | }
147 |
148 | /**
149 | * Render single line text
150 | *
151 | * @param object $field
152 | * @return string $html
153 | * @access private
154 | **/
155 | private function element_single_line_text($field)
156 | {
157 | $id = $this->encode_element_title($field->title);
158 | $required = ($field->required) ? 'required' : FALSE;
159 |
160 | $html = '';
161 | $html .= $this->make_label($id, $field->title, $required);
162 | $html .= sprintf('', $id, $id, $required);
163 | $html .= '
';
164 |
165 | return $html;
166 | }
167 |
168 | /**
169 | * Render number
170 | *
171 | * @param object $field
172 | * @return string $html
173 | * @access private
174 | **/
175 | private function element_number($field)
176 | {
177 | $id = $this->encode_element_title($field->title);
178 | $required = ($field->required) ? 'required' : FALSE;
179 |
180 | $html = '';
181 | $html .= $this->make_label($id, $field->title, $required);
182 | $html .= sprintf('', $id, $id, $required);
183 | $html .= '
';
184 |
185 | return $html;
186 | }
187 |
188 | /**
189 | * Render paragraph text
190 | *
191 | * @param object $field
192 | * @return string $html
193 | * @access private
194 | **/
195 | private function element_paragraph_text($field)
196 | {
197 | $id = $this->encode_element_title($field->title);
198 | $required = ($field->required) ? 'required' : FALSE;
199 |
200 | $html = '';
201 | $html .= $this->make_label($id, $field->title, $required);
202 | $html .= sprintf('', $id, $id, $required);
203 | $html .= '
';
204 |
205 | return $html;
206 | }
207 |
208 | /**
209 | * Checkboxes
210 | *
211 | * @param object $field
212 | * @return string $html
213 | * @access private
214 | **/
215 | private function element_checkboxes($field)
216 | {
217 | error_log('message');
218 |
219 | $id = $this->encode_element_title($field->title);
220 | $required = ($field->required) ? 'required' : FALSE;
221 |
222 | $html = '';
235 |
236 | return $html;
237 | }
238 |
239 | /**
240 | * Mutliple choice
241 | *
242 | * @param object $field
243 | * @return string $html
244 | * @access private
245 | **/
246 | private function element_multiple_choice($field)
247 | {
248 | $id = $this->encode_element_title($field->title);
249 | $required = ($field->required) ? 'required' : FALSE;
250 |
251 | $html = '';
264 |
265 | return $html;
266 | }
267 |
268 | /**
269 | * Render dropdown
270 | *
271 | * @param object $field
272 | * @return string $html
273 | * @access private
274 | **/
275 | private function element_dropdown($field)
276 | {
277 | $id = $this->encode_element_title($field->title);
278 | $required = ($field->required) ? 'required' : FALSE;
279 |
280 | $html = '';
281 | $html .= $this->make_label($id, $field->title, $required);
282 | $html .= sprintf('
';
291 |
292 | return $html;
293 | }
294 |
295 | /**
296 | * Section break
297 | *
298 | * @param object $field
299 | * @return string $html
300 | * @access private
301 | **/
302 | private function element_section_break($field)
303 | {
304 | $html = '';
307 |
308 | return $html;
309 | }
310 |
311 | } // End formLoader.php
--------------------------------------------------------------------------------
/src/formBuilder.jquery.js:
--------------------------------------------------------------------------------
1 | /**
2 | * jQuery form builder
3 | * Copyright (c) 2014 (v3.0) Shlomi Nissan, 1ByteBeta (http://www.1bytebeta.com)
4 | * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
5 | */
6 |
7 | (function($) {
8 |
9 | $.fn.formBuilder = function(options) {
10 |
11 | // Set default settings
12 | var settings = $.extend({
13 | load_url: '/',
14 | save_url: '/'
15 | }, options);
16 |
17 |
18 | /*******************************************************/
19 | /* Fields and Tabs
20 | /*******************************************************/
21 |
22 |
23 | /*
24 | fieldAdd
25 | Adding a new form field on .new-element click
26 | */
27 | var fieldAdd = function() {
28 |
29 | // Bind new field buttons
30 | $('.new-element').click(function(){
31 |
32 | clearSelectedElements();
33 |
34 | var sortableElements = $('#sortable-elements');
35 | sortableElements.sortable({
36 | stop: function(){
37 | reorderElements();
38 | }
39 | });
40 |
41 | var tpl = $(this).data('type');
42 |
43 | var data = {
44 | 'label': 'Untitled',
45 | 'position': $('.form-element').length - 1
46 | };
47 |
48 | dust.render(tpl, data, function(err, out) {
49 |
50 | sortableElements.append(out);
51 | fieldSelect();
52 |
53 | var newElement = $('#element-'+data['position']);
54 | currentlySelected = newElement;
55 |
56 | currentlySelected.addClass('selected');
57 | tabs.showTab('#field-settings');
58 |
59 | bindSettings();
60 | repositionToolbox();
61 | isFieldOptions();
62 | });
63 |
64 | });
65 |
66 | }
67 |
68 |
69 | /*
70 | fieldSelect
71 | Show settings pan and bind fields on .form-element click
72 | */
73 | var fieldSelect = function() {
74 |
75 | $('.form-element').unbind();
76 |
77 | // Form element clicked
78 | $('.form-element').click(function(){
79 |
80 | // Remove selected class from all elements
81 | clearSelectedElements();
82 |
83 | // Add selected class to selected element
84 | $(this).addClass('selected');
85 |
86 | // View the settings base on element type
87 | if( $(this).data('type') == 'form-settings' ) {
88 |
89 | tabs.showTab('#form-settings');
90 |
91 | } else {
92 |
93 | tabs.showTab('#field-settings');
94 | currentlySelected = $(this);
95 | bindSettings();
96 | repositionToolbox();
97 | isFieldOptions();
98 | }
99 |
100 | });
101 | }
102 |
103 |
104 | /*
105 | tabSelect
106 | Adjust form fields based on tab selection
107 | */
108 | var tabSelect = function() {
109 |
110 | // Switch tabs
111 | $('.toolbox-tab').click(function(){
112 |
113 | clearSelectedElements();
114 |
115 | if( $(this).data('target') == '#form-settings' ) {
116 | $('#form-settings-element').addClass('selected');
117 | }
118 |
119 | if( $(this).data('target') == '#field-settings' ) {
120 |
121 | if(!currentlySelected){
122 | $('#element-0').addClass('selected');
123 | currentlySelected = $('#element-0');
124 | } else {
125 | currentlySelected.addClass('selected');
126 | }
127 |
128 | bindSettings();
129 | repositionToolbox();
130 | }
131 |
132 | });
133 |
134 | }
135 |
136 |
137 | /*******************************************************/
138 | /* Bind controls
139 | /*******************************************************/
140 |
141 | /*
142 | bindTextFields
143 | Binds textfields in the settings panel to form textfields
144 | */
145 | var bindTextFields = function() {
146 | // Bind controls
147 | $('.bind-control').each(function(){
148 |
149 | var target = $(this).data('bind');
150 |
151 | $(this).on("keyup", function() {
152 |
153 | if(currentlySelected == '') {
154 | $(target).html($(this).val());
155 | } else {
156 |
157 | if( currentlySelected.data('type') != 'element-dropdown' ) {
158 | currentlySelected.find(target).next('.choice-label').html($(this).val());
159 | } else {
160 |
161 | currentlySelected.find(target).html($(this).val());
162 | }
163 |
164 | }
165 |
166 |
167 | });
168 |
169 | });
170 | }
171 |
172 | /*
173 | bindButtons (checkboxes and radio buttons)
174 | Binds buttons from the settings pane to form elements
175 | */
176 | var bindButtons = function () {
177 | $('.option').unbind();
178 |
179 | $('.option').click(function(){
180 |
181 | var target = $(this).parent().next('input').data('bind');
182 | var value = ( $(currentlySelected).data('type') != 'element-dropdown' ) ? 'checked' : 'selected';
183 |
184 | $(currentlySelected).find(target).prop( value, function( i, val ) {
185 | return !val;
186 | });
187 |
188 | });
189 | }
190 |
191 |
192 | /*
193 | bindSettings
194 | Binds settings controls to form elements (labels, required, choices, etc)
195 | */
196 | var bindSettings = function() {
197 |
198 | // Field Label
199 | $('#field-label').val(currentlySelected.data('label'));
200 |
201 | $('#field-label').on("keyup", function() {
202 |
203 | currentlySelected.children('label').children('.label-title').html($(this).val());
204 | currentlySelected.data('label', $(this).val());
205 |
206 | });
207 |
208 | // Description
209 | if( currentlySelected.data('type') == 'element-section-break' ) {
210 | $('#description').val(currentlySelected.children('.description').html());
211 | }
212 |
213 | $('#description').on("keyup", function() {
214 |
215 | currentlySelected.children('.description').html($(this).val());
216 | currentlySelected.data('description', $(this).val());
217 |
218 | });
219 |
220 | // Choices
221 | if(currentlySelected.data('type') == 'element-multiple-choice' || currentlySelected.data('type') == 'element-checkboxes' || currentlySelected.data('type') == 'element-dropdown') {
222 |
223 | $('#field-choices').css('display', 'block');
224 | $('#field-choices').html('');
225 |
226 | var choices = [];
227 |
228 | var items = currentlySelected.children('.choices').children('.choice');
229 |
230 | if( currentlySelected.data('type') == 'element-dropdown' ) {
231 | items = currentlySelected.children('.choices').children('option');
232 | }
233 |
234 | items.each(function(i){
235 |
236 | if( currentlySelected.data('type') != 'element-dropdown' ) {
237 |
238 | // Radio buttons, checkboxes
239 |
240 | var checked = $(this).children('label').children('input').is(':checked') ? true : false;
241 | var bindingClass = $(this).children('label').children('input').attr('class');
242 | var title = $(this).children('label').children('.choice-label').html();
243 |
244 | } else {
245 |
246 | // Dropdown
247 |
248 | var title = $(this).val();
249 | var bindingClass = $(this).attr('class');
250 | var checked = $(this).is(':selected') ? true : false;
251 |
252 | }
253 |
254 |
255 |
256 | var data = {
257 | 'checked':checked,
258 | 'title': title,
259 | 'position': i+1,
260 | 'bindingClass': bindingClass,
261 | };
262 |
263 | choices.push(data);
264 |
265 | });
266 |
267 | var data = {
268 | "choices":choices
269 | }
270 |
271 | // Render the choices
272 |
273 | dust.render(currentlySelected.children('.choices').data('type'), data, function(err, out) {
274 |
275 | $('#field-choices').append(out);
276 | bindTextFields();
277 | bindButtons();
278 | controlMultipleChoice();
279 |
280 | });
281 |
282 | } else {
283 | $('#field-choices').css('display', 'none');
284 | }
285 |
286 | // Required
287 | if( currentlySelected.hasClass('required') ) {
288 | $('#required').prop("checked", true);
289 | } else {
290 | $('#required').prop("checked", false);
291 | }
292 |
293 | $('#required').unbind();
294 | $('#required').change(function(){
295 |
296 | currentlySelected.toggleClass('required');
297 |
298 | var data = {};
299 | if(this.checked) {
300 |
301 | dust.render('required', data, function(err, out) {
302 |
303 | currentlySelected.children('label').append(out);
304 |
305 | });
306 |
307 | } else {
308 | currentlySelected.children('label').children('.required-star').remove();
309 | }
310 |
311 | });
312 |
313 | }
314 |
315 | /*******************************************************/
316 | /* Controls
317 | /*******************************************************/
318 |
319 | /*
320 | controlSettings
321 | Attach settings control (Remove Field, Add Field)
322 | */
323 | var controlSettings = function() {
324 |
325 | // Remove field
326 | $('#control-remove-field').click(function(){
327 |
328 | if( currentlySelected != '' ) {
329 |
330 | if( $('.form-element').length > 2 ) {
331 | currentlySelected.remove();
332 | reorderElements();
333 | tabs.showTab('#add-field');
334 |
335 | clearSelectedElements();
336 | } else {
337 | alert('Unable to delete this field! You must have at least 1 field in your form.');
338 |
339 | }
340 |
341 | }
342 |
343 | });
344 |
345 | $('#control-add-field').click(function(){
346 | tabs.showTab('#add-field');
347 |
348 | clearSelectedElements();
349 |
350 | });
351 |
352 | }
353 |
354 | /*
355 | rules
356 | Attach rules
357 | */
358 |
359 | var rules = function() {
360 |
361 | $("#control-add-rule").unbind("click");
362 | $(".control-remove-rule").unbind("click");
363 |
364 | $("#control-add-rule").click(function(){
365 |
366 | var fields = [];
367 | var targets = [];
368 |
369 | // get existing fields
370 | $('.form-element').each(function(){
371 |
372 | var type = $(this).data("type");
373 | var label = $(this).children('label').children('.label-title').html();
374 |
375 | if( type == 'element-multiple-choice' ) { fields.push( { label: label } ); }
376 |
377 | if( label != undefined ) { targets.push( { label: label } ); }
378 |
379 | });
380 |
381 | if( fields.length == 0 ) {
382 |
383 | alert("You need to have at least one multiple choice field to create a new rule.");
384 |
385 | } else {
386 |
387 | var data = {
388 |
389 | fields: fields,
390 | targets: targets
391 |
392 | };
393 |
394 | dust.render('rule', data, function(err, out) {
395 |
396 | $('#rules').append(out);
397 | rules();
398 |
399 | });
400 |
401 | }
402 |
403 | });
404 |
405 | $(".control-remove-rule").click(function(){
406 |
407 | $(this).parent().remove();
408 |
409 | });
410 |
411 | $(".control-rule-field").change(function(){
412 |
413 | var htmlStr = "";
414 |
415 | var val = $(this).val();
416 |
417 | $('.form-element').each(function(){
418 |
419 | var label = $(this).children('label').children('.label-title').html();
420 |
421 | if( label == val ) {
422 |
423 | $(this).children('.choices').children('.choice').each(function(){
424 |
425 | var temp = $(this).children('label').children('.choice-label').html();
426 |
427 | htmlStr += "";
428 |
429 | });
430 |
431 | }
432 |
433 | });
434 |
435 | $(".control-rule-value").prop('disabled', false);
436 | $(".control-rule-value").html(htmlStr);
437 |
438 | });
439 |
440 | }
441 |
442 | /*
443 | controlMutlipleChoice
444 | Attach multiple choice controls (remove choice, ddd choice)
445 | */
446 | var controlMultipleChoice = function() {
447 |
448 | // Remove choice
449 | $('.remove-choice').unbind();
450 | $('.remove-choice').click(function(){
451 |
452 | if( $(this).parent().parent().children('.choice').length > 1 ) {
453 |
454 | // Delete choice from form
455 | var deleteItem = $(this).data('delete');
456 |
457 | if($(currentlySelected).data('type') == 'element-dropdown') {
458 | $(currentlySelected).find(deleteItem).remove();
459 | } else {
460 | $(currentlySelected).find(deleteItem).parent().parent().remove();
461 | }
462 |
463 | // Delete choice from settings
464 | $(this).parent().remove();
465 |
466 | // Bind new fields
467 | bindTextFields();
468 | controlMultipleChoice();
469 | }
470 |
471 | });
472 |
473 | // Add Choice
474 | $('.add-choice').unbind();
475 | $('.add-choice').click(function(){
476 |
477 | // Get the choice count
478 | var lastChoice = 2;
479 |
480 | // Dropdown
481 | if( currentlySelected.data('type') == 'element-dropdown' ) {
482 | var items = currentlySelected.children('.choices').children('option');
483 | } else {
484 | var items = currentlySelected.children('.choices').children('.choice');
485 | }
486 |
487 | console.log(items);
488 |
489 | $(items).each(function(i){
490 |
491 | if( currentlySelected.data('type') == 'element-dropdown' ) {
492 | var choiceString = $(this).attr('class');
493 | } else {
494 | var choiceString = $(this).find('input').attr('class');
495 | }
496 |
497 |
498 | var choiceSplit = choiceString.split('-');
499 | if( lastChoice < choiceSplit[1] ) {
500 | lastChoice = choiceSplit[1];
501 | }
502 |
503 | });
504 |
505 | lastChoice++;
506 |
507 | var choice = {
508 | 'bindingClass':'option-'+lastChoice,
509 | 'title':'Untitled'
510 | };
511 |
512 | var data = {
513 | "choices": choice
514 | }
515 |
516 | // Render a new choice in settings
517 | dust.render(currentlySelected.children('.choices').data('type'), data, function(err, out) {
518 |
519 | $('#field-choices').append(out);
520 |
521 | // Set template based on type
522 | if( currentlySelected.data('type') == 'element-multiple-choice' ) {
523 | template = 'choice-radio';
524 | }
525 |
526 | if( currentlySelected.data('type') == 'element-checkboxes' ) {
527 | template = 'choice-checkbox';
528 | }
529 |
530 | if( currentlySelected.data('type') == 'element-dropdown') {
531 | template = 'choice-dropdown';
532 | }
533 |
534 |
535 | var elementId = currentlySelected.attr('id').replace('element-','');
536 |
537 | // Load template
538 | data = {
539 | 'title': 'Untitled',
540 | 'value': 'untitled',
541 | 'lastChoice': lastChoice,
542 | 'elementId': elementId
543 | }
544 |
545 | dust.render(template, data, function(err, out) {
546 | currentlySelected.children('.choices').append(out);
547 | });
548 |
549 | // Bind new fields
550 | bindTextFields();
551 | bindButtons();
552 | controlMultipleChoice();
553 |
554 |
555 | });
556 |
557 | });
558 |
559 | }
560 |
561 |
562 |
563 | /*******************************************************/
564 | /* Helpers
565 | /*******************************************************/
566 |
567 | var isFieldOptions = function() {
568 | if( currentlySelected.data('type') == 'element-section-break' ) {
569 | $('#field-options').hide();
570 | $('#field-description').show();
571 | } else {
572 | $('#field-options').show();
573 | $('#field-description').hide();
574 | }
575 | }
576 |
577 | /*
578 | repositionToolbox
579 | Change the position of the toolbox based on active selection
580 | */
581 | var repositionToolbox = function() {
582 | topOffset = currentlySelected.position().top;
583 | toolboxOffset = 115;
584 | offset = topOffset - toolboxOffset;
585 | $('#field-settings').css('margin-top', offset + 'px');
586 | $('.left-col').css('height','100%');
587 | }
588 |
589 |
590 | /*
591 | clearSelectedElements
592 | Remove currently selected element
593 | */
594 | var clearSelectedElements = function() {
595 |
596 | // Remove selected class from all elements
597 | $('.form-element').each(function(){
598 | $(this).removeClass('selected');
599 | });
600 |
601 | }
602 |
603 | /*
604 | reorderElements
605 | Update element id based on position
606 | */
607 | var reorderElements = function() {
608 |
609 | $('#sortable-elements').sortable({
610 | stop: function(){
611 | reorderElements();
612 | }
613 | });
614 |
615 | $('#sortable-elements li').each(function(i){
616 | $(this).attr( 'id', 'element-'+ i );
617 | });
618 |
619 | fieldSelect();
620 | }
621 |
622 |
623 | /*
624 | serialize
625 | Serialize form elements into a JSON string
626 | */
627 | var serialize = function() {
628 |
629 | var formData = {};
630 |
631 | formData['title'] = $('#form-title').val();
632 | formData['description'] = $('#form-description').val();
633 |
634 |
635 | formData['fields'] = Array();
636 |
637 | // Get elements
638 |
639 | $('#sortable-elements li').each(function(i){
640 |
641 | var element = {
642 | 'title': $(this).data('label'),
643 | 'type': $(this).data('type'),
644 | 'required': $(this).hasClass('required') ? true : false,
645 | 'position': i+1,
646 | 'description': $(this).data('description')
647 | }
648 |
649 | // If element has multiple choices
650 | if( element['type'] == 'element-multiple-choice' || element['type'] == 'element-checkboxes' || element['type'] == 'element-dropdown' ) {
651 |
652 | var choices = [];
653 |
654 | if( element['type'] == 'element-dropdown') {
655 |
656 | // Collect choices for dropdown
657 | $(this).find('.choices').children('option').each(function(index){
658 |
659 | var choice = {
660 | 'title': $(this).val(),
661 | 'value': $(this).val(),
662 | 'checked': $(this).is(':selected') ? true : false,
663 | }
664 |
665 | choices.push(choice);
666 | element['choices'] = choices;
667 |
668 | });
669 |
670 | } else {
671 |
672 | // Collect choices for radio/checkboxes
673 | $(this).find('.choices').children('.choice').each(function(index){
674 |
675 | var choice = {
676 | 'title': $(this).children('label').children('.choice-label').html(),
677 | 'value': $(this).children('label').children('.choice-label').html(),
678 | 'checked': $(this).children('label').children('input').is(':checked') ? true : false,
679 | }
680 |
681 | choices.push(choice);
682 | element['choices'] = choices;
683 |
684 | });
685 |
686 | }
687 |
688 | }
689 |
690 | formData['fields'].push(element);
691 |
692 | });
693 |
694 | formData['rules'] = Array();
695 |
696 | // Get rules
697 |
698 | $('.section.rule').each(function(i){
699 |
700 | var rule = {
701 |
702 | 'field': $(this).find('.control-rule-field').val(),
703 | 'condition': $(this).find('.control-rule-condition').val(),
704 | 'value': $(this).find('.control-rule-value').val(),
705 | 'action': $(this).find('.control-rule-action').val(),
706 | 'target': $(this).find('.control-rule-target').val(),
707 |
708 | }
709 |
710 | formData['rules'].push(rule);
711 |
712 | });
713 |
714 |
715 | var serialized = JSON.stringify(formData);
716 |
717 | console.log(serialized);
718 |
719 | // Process the form data here...
720 | return serialized;
721 |
722 | }
723 |
724 |
725 | /*******************************************************/
726 | /* Entry Point
727 | /*******************************************************/
728 |
729 | // Globl vars
730 | var currentlySelected = '';
731 | var tabs = '';
732 |
733 |
734 | // Auto load templates
735 | dust.onLoad = function(name, callback) {
736 | $.ajax('src/templates/' + name + '.tpl', {
737 | success: function(data) {
738 | callback(undefined, data);
739 | },
740 | error: function(jqXHR, textStatus, errorThrown) {
741 | callback(textStatus, undefined);
742 | }
743 | });
744 | };
745 |
746 | var base = {};
747 |
748 | //
749 | var obj = this;
750 |
751 | dust.render('formbuilder-base', base, function(err, out) {
752 | obj.append(out);
753 | });
754 |
755 | // Get data
756 | $.getJSON( settings.load_url, function( data ) {
757 |
758 | // Load the base template
759 | base = {
760 | form: data,
761 | fieldSettings: false,
762 | formSettings: false
763 | };
764 |
765 | // Render the form
766 | dust.render('formbuilder-fields', base, function(err, out) {
767 |
768 | dust.render('rules', base.form, function(err, out) {
769 |
770 | $("#rules").append(out);
771 |
772 | });
773 |
774 | $('.loading').fadeOut(function(){
775 |
776 | $('#form-col').html(out);
777 | $('#form-elements').fadeIn();
778 |
779 | $('#form-title').val(base['form']['title']);
780 | $('#form-description').val(base['form']['description']);
781 |
782 | fieldAdd();
783 | fieldSelect();
784 | tabSelect();
785 |
786 | bindTextFields();
787 | controlSettings();
788 | rules();
789 | reorderElements();
790 |
791 | tabs = $('.nav-tabs').tabs();
792 |
793 | $('#save').click(function(e){
794 |
795 | var form_data = serialize();
796 |
797 | $.ajax({
798 |
799 | type: "POST",
800 | url: settings.save_url,
801 | data: {formData: form_data},
802 |
803 | success: function () {
804 |
805 | settings.onSaveForm.call();
806 |
807 | }
808 |
809 | });
810 |
811 | });
812 |
813 | });
814 |
815 | });
816 |
817 | });
818 |
819 | ///
820 |
821 | } // End plugin
822 |
823 | }(jQuery));
824 |
--------------------------------------------------------------------------------
/src/libraries/dust-js/dust-helpers.js:
--------------------------------------------------------------------------------
1 | (function(dust){
2 |
3 | //using the built in logging method of dust when accessible
4 | var _log = dust.log ? function(mssg) { dust.log(mssg, "INFO"); } : function() {};
5 |
6 | function isSelect(context) {
7 | var value = context.current();
8 | return typeof value === "object" && value.isSelect === true;
9 | }
10 |
11 | // Utility method : toString() equivalent for functions
12 | function jsonFilter(key, value) {
13 | if (typeof value === "function") {
14 | //to make sure all environments format functions the same way
15 | return value.toString()
16 | //remove all leading and trailing whitespace
17 | .replace(/(^\s+|\s+$)/mg, '')
18 | //remove new line characters
19 | .replace(/\n/mg, '')
20 | //replace , and 0 or more spaces with ", "
21 | .replace(/,\s*/mg, ', ')
22 | //insert space between ){
23 | .replace(/\)\{/mg, ') {')
24 | ;
25 | }
26 | return value;
27 | }
28 |
29 | // Utility method: to invoke the given filter operation such as eq/gt etc
30 | function filter(chunk, context, bodies, params, filterOp) {
31 | params = params || {};
32 | var body = bodies.block,
33 | actualKey,
34 | expectedValue,
35 | filterOpType = params.filterOpType || '';
36 | // when @eq, @lt etc are used as standalone helpers, key is required and hence check for defined
37 | if ( typeof params.key !== "undefined") {
38 | actualKey = dust.helpers.tap(params.key, chunk, context);
39 | }
40 | else if (isSelect(context)) {
41 | actualKey = context.current().selectKey;
42 | // supports only one of the blocks in the select to be selected
43 | if (context.current().isResolved) {
44 | filterOp = function() { return false; };
45 | }
46 | }
47 | else {
48 | _log("No key specified for filter in:" + filterOpType + " helper ");
49 | return chunk;
50 | }
51 | expectedValue = dust.helpers.tap(params.value, chunk, context);
52 | // coerce both the actualKey and expectedValue to the same type for equality and non-equality compares
53 | if (filterOp(coerce(expectedValue, params.type, context), coerce(actualKey, params.type, context))) {
54 | if (isSelect(context)) {
55 | context.current().isResolved = true;
56 | }
57 | // we want helpers without bodies to fail gracefully so check it first
58 | if(body) {
59 | return chunk.render(body, context);
60 | }
61 | else {
62 | _log("No key specified for filter in:" + filterOpType + " helper ");
63 | return chunk;
64 | }
65 | }
66 | else if (bodies['else']) {
67 | return chunk.render(bodies['else'], context);
68 | }
69 | return chunk;
70 | }
71 |
72 | function coerce (value, type, context) {
73 | if (value) {
74 | switch (type || typeof(value)) {
75 | case 'number': return +value;
76 | case 'string': return String(value);
77 | case 'boolean': {
78 | value = (value === 'false' ? false : value);
79 | return Boolean(value);
80 | }
81 | case 'date': return new Date(value);
82 | case 'context': return context.get(value);
83 | }
84 | }
85 |
86 | return value;
87 | }
88 |
89 | var helpers = {
90 |
91 | // Utility helping to resolve dust references in the given chunk
92 | // uses the Chunk.render method to resolve value
93 | /*
94 | Reference resolution rules:
95 | if value exists in JSON:
96 | "" or '' will evaluate to false, boolean false, null, or undefined will evaluate to false,
97 | numeric 0 evaluates to true, so does, string "0", string "null", string "undefined" and string "false".
98 | Also note that empty array -> [] is evaluated to false and empty object -> {} and non-empty object are evaluated to true
99 | The type of the return value is string ( since we concatenate to support interpolated references
100 |
101 | if value does not exist in JSON and the input is a single reference: {x}
102 | dust render emits empty string, and we then return false
103 |
104 | if values does not exist in JSON and the input is interpolated references : {x} < {y}
105 | dust render emits < and we return the partial output
106 |
107 | */
108 | "tap": function(input, chunk, context) {
109 | // return given input if there is no dust reference to resolve
110 | // dust compiles a string/reference such as {foo} to a function
111 | if (typeof input !== "function") {
112 | return input;
113 | }
114 |
115 | var dustBodyOutput = '',
116 | returnValue;
117 |
118 | //use chunk render to evaluate output. For simple functions result will be returned from render call,
119 | //for dust body functions result will be output via callback function
120 | returnValue = chunk.tap(function(data) {
121 | dustBodyOutput += data;
122 | return '';
123 | }).render(input, context);
124 |
125 | chunk.untap();
126 |
127 | //assume it's a simple function call if return result is not a chunk
128 | if (returnValue.constructor !== chunk.constructor) {
129 | //use returnValue as a result of tap
130 | return returnValue;
131 | } else if (dustBodyOutput === '') {
132 | return false;
133 | } else {
134 | return dustBodyOutput;
135 | }
136 | },
137 |
138 | "sep": function(chunk, context, bodies) {
139 | var body = bodies.block;
140 | if (context.stack.index === context.stack.of - 1) {
141 | return chunk;
142 | }
143 | if(body) {
144 | return bodies.block(chunk, context);
145 | }
146 | else {
147 | return chunk;
148 | }
149 | },
150 |
151 | "idx": function(chunk, context, bodies) {
152 | var body = bodies.block;
153 | if(body) {
154 | return bodies.block(chunk, context.push(context.stack.index));
155 | }
156 | else {
157 | return chunk;
158 | }
159 | },
160 |
161 | /**
162 | * contextDump helper
163 | * @param key specifies how much to dump.
164 | * "current" dumps current context. "full" dumps the full context stack.
165 | * @param to specifies where to write dump output.
166 | * Values can be "console" or "output". Default is output.
167 | */
168 | "contextDump": function(chunk, context, bodies, params) {
169 | var p = params || {},
170 | to = p.to || 'output',
171 | key = p.key || 'current',
172 | dump;
173 | to = dust.helpers.tap(to, chunk, context);
174 | key = dust.helpers.tap(key, chunk, context);
175 | if (key === 'full') {
176 | dump = JSON.stringify(context.stack, jsonFilter, 2);
177 | }
178 | else {
179 | dump = JSON.stringify(context.stack.head, jsonFilter, 2);
180 | }
181 | if (to === 'console') {
182 | _log(dump);
183 | return chunk;
184 | }
185 | else {
186 | return chunk.write(dump);
187 | }
188 | },
189 | /**
190 | if helper for complex evaluation complex logic expressions.
191 | Note : #1 if helper fails gracefully when there is no body block nor else block
192 | #2 Undefined values and false values in the JSON need to be handled specially with .length check
193 | for e.g @if cond=" '{a}'.length && '{b}'.length" is advised when there are chances of the a and b been
194 | undefined or false in the context
195 | #3 Use only when the default ? and ^ dust operators and the select fall short in addressing the given logic,
196 | since eval executes in the global scope
197 | #4 All dust references are default escaped as they are resolved, hence eval will block malicious scripts in the context
198 | Be mindful of evaluating a expression that is passed through the unescape filter -> |s
199 | @param cond, either a string literal value or a dust reference
200 | a string literal value, is enclosed in double quotes, e.g. cond="2>3"
201 | a dust reference is also enclosed in double quotes, e.g. cond="'{val}'' > 3"
202 | cond argument should evaluate to a valid javascript expression
203 | **/
204 |
205 | "if": function( chunk, context, bodies, params ){
206 | var body = bodies.block,
207 | skip = bodies['else'];
208 | if( params && params.cond){
209 | var cond = params.cond;
210 | cond = dust.helpers.tap(cond, chunk, context);
211 | // eval expressions with given dust references
212 | if(eval(cond)){
213 | if(body) {
214 | return chunk.render( bodies.block, context );
215 | }
216 | else {
217 | _log("Missing body block in the if helper!");
218 | return chunk;
219 | }
220 | }
221 | if(skip){
222 | return chunk.render( bodies['else'], context );
223 | }
224 | }
225 | // no condition
226 | else {
227 | _log("No condition given in the if helper!");
228 | }
229 | return chunk;
230 | },
231 |
232 | /**
233 | * math helper
234 | * @param key is the value to perform math against
235 | * @param method is the math method, is a valid string supported by math helper like mod, add, subtract
236 | * @param operand is the second value needed for operations like mod, add, subtract, etc.
237 | * @param round is a flag to assure that an integer is returned
238 | */
239 | "math": function ( chunk, context, bodies, params ) {
240 | //key and method are required for further processing
241 | if( params && typeof params.key !== "undefined" && params.method ){
242 | var key = params.key,
243 | method = params.method,
244 | // operand can be null for "abs", ceil and floor
245 | operand = params.operand,
246 | round = params.round,
247 | mathOut = null,
248 | operError = function(){
249 | _log("operand is required for this math method");
250 | return null;
251 | };
252 | key = dust.helpers.tap(key, chunk, context);
253 | operand = dust.helpers.tap(operand, chunk, context);
254 | // TODO: handle and tests for negatives and floats in all math operations
255 | switch(method) {
256 | case "mod":
257 | if(operand === 0 || operand === -0) {
258 | _log("operand for divide operation is 0/-0: expect Nan!");
259 | }
260 | mathOut = parseFloat(key) % parseFloat(operand);
261 | break;
262 | case "add":
263 | mathOut = parseFloat(key) + parseFloat(operand);
264 | break;
265 | case "subtract":
266 | mathOut = parseFloat(key) - parseFloat(operand);
267 | break;
268 | case "multiply":
269 | mathOut = parseFloat(key) * parseFloat(operand);
270 | break;
271 | case "divide":
272 | if(operand === 0 || operand === -0) {
273 | _log("operand for divide operation is 0/-0: expect Nan/Infinity!");
274 | }
275 | mathOut = parseFloat(key) / parseFloat(operand);
276 | break;
277 | case "ceil":
278 | mathOut = Math.ceil(parseFloat(key));
279 | break;
280 | case "floor":
281 | mathOut = Math.floor(parseFloat(key));
282 | break;
283 | case "round":
284 | mathOut = Math.round(parseFloat(key));
285 | break;
286 | case "abs":
287 | mathOut = Math.abs(parseFloat(key));
288 | break;
289 | default:
290 | _log("method passed is not supported");
291 | }
292 |
293 | if (mathOut !== null){
294 | if (round) {
295 | mathOut = Math.round(mathOut);
296 | }
297 | if (bodies && bodies.block) {
298 | // with bodies act like the select helper with mathOut as the key
299 | // like the select helper bodies['else'] is meaningless and is ignored
300 | return chunk.render(bodies.block, context.push({ isSelect: true, isResolved: false, selectKey: mathOut }));
301 | } else {
302 | // self closing math helper will return the calculated output
303 | return chunk.write(mathOut);
304 | }
305 | } else {
306 | return chunk;
307 | }
308 | }
309 | // no key parameter and no method
310 | else {
311 | _log("Key is a required parameter for math helper along with method/operand!");
312 | }
313 | return chunk;
314 | },
315 | /**
316 | select helper works with one of the eq/ne/gt/gte/lt/lte/default providing the functionality
317 | of branching conditions
318 | @param key, ( required ) either a string literal value or a dust reference
319 | a string literal value, is enclosed in double quotes, e.g. key="foo"
320 | a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
321 | @param type (optional), supported types are number, boolean, string, date, context, defaults to string
322 | **/
323 | "select": function(chunk, context, bodies, params) {
324 | var body = bodies.block;
325 | // key is required for processing, hence check for defined
326 | if( params && typeof params.key !== "undefined"){
327 | // returns given input as output, if the input is not a dust reference, else does a context lookup
328 | var key = dust.helpers.tap(params.key, chunk, context);
329 | // bodies['else'] is meaningless and is ignored
330 | if( body ) {
331 | return chunk.render(bodies.block, context.push({ isSelect: true, isResolved: false, selectKey: key }));
332 | }
333 | else {
334 | _log("Missing body block in the select helper ");
335 | return chunk;
336 | }
337 | }
338 | // no key
339 | else {
340 | _log("No key given in the select helper!");
341 | }
342 | return chunk;
343 | },
344 |
345 | /**
346 | eq helper compares the given key is same as the expected value
347 | It can be used standalone or in conjunction with select for multiple branching
348 | @param key, The actual key to be compared ( optional when helper used in conjunction with select)
349 | either a string literal value or a dust reference
350 | a string literal value, is enclosed in double quotes, e.g. key="foo"
351 | a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
352 | @param value, The expected value to compare to, when helper is used standalone or in conjunction with select
353 | @param type (optional), supported types are number, boolean, string, date, context, defaults to string
354 | Note : use type="number" when comparing numeric
355 | **/
356 | "eq": function(chunk, context, bodies, params) {
357 | if(params) {
358 | params.filterOpType = "eq";
359 | }
360 | return filter(chunk, context, bodies, params, function(expected, actual) { return actual === expected; });
361 | },
362 |
363 | /**
364 | ne helper compares the given key is not the same as the expected value
365 | It can be used standalone or in conjunction with select for multiple branching
366 | @param key, The actual key to be compared ( optional when helper used in conjunction with select)
367 | either a string literal value or a dust reference
368 | a string literal value, is enclosed in double quotes, e.g. key="foo"
369 | a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
370 | @param value, The expected value to compare to, when helper is used standalone or in conjunction with select
371 | @param type (optional), supported types are number, boolean, string, date, context, defaults to string
372 | Note : use type="number" when comparing numeric
373 | **/
374 | "ne": function(chunk, context, bodies, params) {
375 | if(params) {
376 | params.filterOpType = "ne";
377 | return filter(chunk, context, bodies, params, function(expected, actual) { return actual !== expected; });
378 | }
379 | return chunk;
380 | },
381 |
382 | /**
383 | lt helper compares the given key is less than the expected value
384 | It can be used standalone or in conjunction with select for multiple branching
385 | @param key, The actual key to be compared ( optional when helper used in conjunction with select)
386 | either a string literal value or a dust reference
387 | a string literal value, is enclosed in double quotes, e.g. key="foo"
388 | a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
389 | @param value, The expected value to compare to, when helper is used standalone or in conjunction with select
390 | @param type (optional), supported types are number, boolean, string, date, context, defaults to string
391 | Note : use type="number" when comparing numeric
392 | **/
393 | "lt": function(chunk, context, bodies, params) {
394 | if(params) {
395 | params.filterOpType = "lt";
396 | return filter(chunk, context, bodies, params, function(expected, actual) { return actual < expected; });
397 | }
398 | },
399 |
400 | /**
401 | lte helper compares the given key is less or equal to the expected value
402 | It can be used standalone or in conjunction with select for multiple branching
403 | @param key, The actual key to be compared ( optional when helper used in conjunction with select)
404 | either a string literal value or a dust reference
405 | a string literal value, is enclosed in double quotes, e.g. key="foo"
406 | a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
407 | @param value, The expected value to compare to, when helper is used standalone or in conjunction with select
408 | @param type (optional), supported types are number, boolean, string, date, context, defaults to string
409 | Note : use type="number" when comparing numeric
410 | **/
411 | "lte": function(chunk, context, bodies, params) {
412 | if(params) {
413 | params.filterOpType = "lte";
414 | return filter(chunk, context, bodies, params, function(expected, actual) { return actual <= expected; });
415 | }
416 | return chunk;
417 | },
418 |
419 |
420 | /**
421 | gt helper compares the given key is greater than the expected value
422 | It can be used standalone or in conjunction with select for multiple branching
423 | @param key, The actual key to be compared ( optional when helper used in conjunction with select)
424 | either a string literal value or a dust reference
425 | a string literal value, is enclosed in double quotes, e.g. key="foo"
426 | a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
427 | @param value, The expected value to compare to, when helper is used standalone or in conjunction with select
428 | @param type (optional), supported types are number, boolean, string, date, context, defaults to string
429 | Note : use type="number" when comparing numeric
430 | **/
431 | "gt": function(chunk, context, bodies, params) {
432 | // if no params do no go further
433 | if(params) {
434 | params.filterOpType = "gt";
435 | return filter(chunk, context, bodies, params, function(expected, actual) { return actual > expected; });
436 | }
437 | return chunk;
438 | },
439 |
440 | /**
441 | gte helper, compares the given key is greater than or equal to the expected value
442 | It can be used standalone or in conjunction with select for multiple branching
443 | @param key, The actual key to be compared ( optional when helper used in conjunction with select)
444 | either a string literal value or a dust reference
445 | a string literal value, is enclosed in double quotes, e.g. key="foo"
446 | a dust reference may or may not be enclosed in double quotes, e.g. key="{val}" and key=val are both valid
447 | @param value, The expected value to compare to, when helper is used standalone or in conjunction with select
448 | @param type (optional), supported types are number, boolean, string, date, context, defaults to string
449 | Note : use type="number" when comparing numeric
450 | **/
451 | "gte": function(chunk, context, bodies, params) {
452 | if(params) {
453 | params.filterOpType = "gte";
454 | return filter(chunk, context, bodies, params, function(expected, actual) { return actual >= expected; });
455 | }
456 | return chunk;
457 | },
458 |
459 | // to be used in conjunction with the select helper
460 | // TODO: fix the helper to do nothing when used standalone
461 | "default": function(chunk, context, bodies, params) {
462 | // does not require any params
463 | if(params) {
464 | params.filterOpType = "default";
465 | }
466 | return filter(chunk, context, bodies, params, function(expected, actual) { return true; });
467 | },
468 |
469 | /**
470 | * size helper prints the size of the given key
471 | * Note : size helper is self closing and does not support bodies
472 | * @param key, the element whose size is returned
473 | */
474 | "size": function( chunk, context, bodies, params ) {
475 | var key, value=0, nr, k;
476 | params = params || {};
477 | key = params.key;
478 | if (!key || key === true) { //undefined, null, "", 0
479 | value = 0;
480 | }
481 | else if(dust.isArray(key)) { //array
482 | value = key.length;
483 | }
484 | else if (!isNaN(parseFloat(key)) && isFinite(key)) { //numeric values
485 | value = key;
486 | }
487 | else if (typeof key === "object") { //object test
488 | //objects, null and array all have typeof ojbect...
489 | //null and array are already tested so typeof is sufficient http://jsperf.com/isobject-tests
490 | nr = 0;
491 | for(k in key){
492 | if(Object.hasOwnProperty.call(key,k)){
493 | nr++;
494 | }
495 | }
496 | value = nr;
497 | } else {
498 | value = (key + '').length; //any other value (strings etc.)
499 | }
500 | return chunk.write(value);
501 | }
502 |
503 |
504 | };
505 |
506 | for (var key in helpers) {
507 | dust.helpers[key] = helpers[key];
508 | }
509 |
510 | if(typeof exports !== 'undefined') {
511 | module.exports = dust;
512 | }
513 |
514 | })(typeof exports !== 'undefined' ? require('dustjs-linkedin') : dust);
--------------------------------------------------------------------------------
/src/libraries/dust-js/dust-full-0.3.0.min.js:
--------------------------------------------------------------------------------
1 | //
2 | // Dust - Asynchronous Templating v0.3.0
3 | // http://akdubya.github.com/dustjs
4 | //
5 | // Copyright (c) 2010, Aleksander Williams
6 | // Released under the MIT License.
7 | //
8 |
9 | var dust={};
10 | (function(o){function z(e,k,l){this.stack=e;this.global=k;this.blocks=l}function H(e,k,l,x){this.tail=k;this.isObject=!o.isArray(e)&&e&&typeof e==="object";this.head=e;this.index=l;this.of=x}function p(e){this.head=new B(this);this.callback=e;this.out=""}function J(){this.head=new B(this)}function B(e,k,l){this.root=e;this.next=k;this.data="";this.flushable=false;this.taps=l}function r(e,k){this.head=e;this.tail=k}o.cache={};o.register=function(e,k){if(e)o.cache[e]=k};o.render=function(e,k,l){l=(new p(l)).head;
11 | o.load(e,l,z.wrap(k)).end()};o.stream=function(e,k){var l=new J;o.nextTick(function(){o.load(e,l.head,z.wrap(k)).end()});return l};o.renderSource=function(e,k,l){return o.compileFn(e)(k,l)};o.compileFn=function(e,k){var l=o.loadSource(o.compile(e,k));return function(x,C){var E=C?new p(C):new J;o.nextTick(function(){l(E.head,z.wrap(x)).end()});return E}};o.load=function(e,k,l){var x=o.cache[e];if(x)return x(k,l);else{if(o.onLoad)return k.map(function(C){o.onLoad(e,function(E,M){if(E)return C.setError(E);
12 | o.cache[e]||o.loadSource(o.compile(M,e));o.cache[e](C,l).end()})});return k.setError(Error("Template Not Found: "+e))}};o.loadSource=function(e){return eval(e)};o.isArray=Array.isArray?Array.isArray:function(e){return Object.prototype.toString.call(e)=="[object Array]"};o.nextTick=function(e){setTimeout(e,0)};o.isEmpty=function(e){if(o.isArray(e)&&!e.length)return true;if(e===0)return false;return!e};o.filter=function(e,k,l){if(l)for(var x=0,C=l.length;x\"]/),q=/&/g,j=//g,t=/\"/g;o.escapeHtml=function(e){if(typeof e==="string"){if(!K.test(e))return e;return e.replace(q,"&").replace(j,"<").replace(w,">").replace(t,""")}return e};
21 | var y=/\\/g,A=/\r/g,F=/\u2028/g,L=/\u2029/g,N=/\n/g,V=/\f/g,I=/'/g,Q=/"/g,T=/\t/g;o.escapeJs=function(e){if(typeof e==="string")return e.replace(y,"\\\\").replace(Q,'\\"').replace(I,"\\'").replace(A,"\\r").replace(F,"\\u2028").replace(L,"\\u2029").replace(N,"\\n").replace(V,"\\f").replace(T,"\\t");return e}})(dust);if(typeof exports!=="undefined"){typeof process!=="undefined"&&require("./server")(dust);module.exports=dust}
22 | (function(o){function z(q,j){for(var w=[j[0]],t=1,y=j.length;tR){R=a;W=[]}W.push(n)}}function K(){var n="body@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=[];for(var c=
31 | q();c!==null;){b.push(c);c=q()}b=b!==null?["body"].concat(b):null;v[n]={nextPos:a,result:b};return b}function q(){var n="part@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=l();if(b!==null)b=b;else{b=j();if(b!==null)b=b;else{b="partial@"+a;var c=v[b];if(c){a=c.nextPos;b=c.result}else{c=h;h=false;var d=a,g=C();if(g!==null){if(p.substr(a,1)===">"){var f=">";a+=1}else{f=null;h&&r('">"')}if(f!==null){var i=I();i=i!==null?["literal",i]:null;if(i!==null)i=i;else{i=Q();i=i!==null?i:null}if(i!==null){var m=
32 | y();if(m!==null){if(p.substr(a,1)==="/"){var s="/";a+=1}else{s=null;h&&r('"/"')}if(s!==null){var u=E();if(u!==null)g=[g,f,i,m,s,u];else{g=null;a=d}}else{g=null;a=d}}else{g=null;a=d}}else{g=null;a=d}}else{g=null;a=d}}else{g=null;a=d}d=g!==null?["partial",g[2],g[3]]:null;(h=c)&&d===null&&r("partial");v[b]={nextPos:a,result:d};b=d}if(b!==null)b=b;else{b=L();if(b!==null)b=b;else{b=F();if(b!==null)b=b;else{b="buffer@"+a;if(c=v[b]){a=c.nextPos;b=c.result}else{c=h;h=false;d=a;g=M();if(g!==null){f=[];for(i=
33 | U();i!==null;){f.push(i);i=U()}if(f!==null)g=[g,f];else{g=null;a=d}}else{g=null;a=d}d=g!==null?["format",g[0],g[1].join("")]:null;if(d!==null)d=d;else{i=g=a;f=h;h=false;m=x();h=f;if(m===null)f="";else{f=null;a=i}if(f!==null){m=a;i=h;h=false;s=M();h=i;if(s===null)i="";else{i=null;a=m}if(i!==null){m=a;s=h;h=false;u=l();h=s;if(u===null)s="";else{s=null;a=m}if(s!==null){if(p.length>a){m=p.charAt(a);a++}else{m=null;h&&r("any character")}if(m!==null)f=[f,i,s,m];else{f=null;a=g}}else{f=null;a=g}}else{f=
34 | null;a=g}}else{f=null;a=g}g=f!==null?f[3]:null;if(g!==null)for(d=[];g!==null;){d.push(g);i=g=a;f=h;h=false;m=x();h=f;if(m===null)f="";else{f=null;a=i}if(f!==null){m=a;i=h;h=false;s=M();h=i;if(s===null)i="";else{i=null;a=m}if(i!==null){m=a;s=h;h=false;u=l();h=s;if(u===null)s="";else{s=null;a=m}if(s!==null){if(p.length>a){m=p.charAt(a);a++}else{m=null;h&&r("any character")}if(m!==null)f=[f,i,s,m];else{f=null;a=g}}else{f=null;a=g}}else{f=null;a=g}}else{f=null;a=g}g=f!==null?f[3]:null}else d=null;d=d!==
35 | null?["buffer",d.join("")]:null;d=d!==null?d:null}(h=c)&&d===null&&r("buffer");v[b]={nextPos:a,result:d};b=d}b=b!==null?b:null}}}}}v[n]={nextPos:a,result:b};return b}function j(){var n="section@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=h;h=false;var c=a,d=w();if(d!==null){var g=E();if(g!==null){var f=K();if(f!==null){var i=A();if(i!==null){var m=t();if(m!==null){var s=d[1].text===m.text?"":null;if(s!==null)d=[d,g,f,i,m,s];else{d=null;a=c}}else{d=null;a=c}}else{d=null;a=c}}else{d=null;a=c}}else{d=
36 | null;a=c}}else{d=null;a=c}c=d!==null?function(u,D,O){O.push(["param",["literal","block"],D]);u.push(O);return u}(d[0],d[2],d[3],d[4]):null;if(c!==null)c=c;else{c=a;d=w();if(d!==null){if(p.substr(a,1)==="/"){g="/";a+=1}else{g=null;h&&r('"/"')}if(g!==null){f=E();if(f!==null)d=[d,g,f];else{d=null;a=c}}else{d=null;a=c}}else{d=null;a=c}c=d!==null?function(u){u.push(["bodies"]);return u}(d[0]):null;c=c!==null?c:null}(h=b)&&c===null&&r("section");v[n]={nextPos:a,result:c};return c}function w(){var n="sec_tag_start@"+
37 | a,b=v[n];if(b){a=b.nextPos;return b.result}b=a;var c=C();if(c!==null){if(p.substr(a).match(/^[#?^<+@%]/)!==null){var d=p.charAt(a);a++}else{d=null;h&&r("[#?^<+@%]")}if(d!==null){var g=N();if(g!==null){var f=y();if(f!==null){var i;i="params@"+a;var m=v[i];if(m){a=m.nextPos;i=m.result}else{m=h;h=false;var s=[],u=a,D=U();if(D!==null){var O=I();if(O!==null){if(p.substr(a,1)==="="){var P="=";a+=1}else{P=null;h&&r('"="')}if(P!==null){var G=N();if(G!==null)G=G;else{G=Q();G=G!==null?G:null}if(G!==null)D=
38 | [D,O,P,G];else{D=null;a=u}}else{D=null;a=u}}else{D=null;a=u}}else{D=null;a=u}for(u=D!==null?["param",["literal",D[1]],D[3]]:null;u!==null;){s.push(u);u=a;D=U();if(D!==null){O=I();if(O!==null){if(p.substr(a,1)==="="){P="=";a+=1}else{P=null;h&&r('"="')}if(P!==null){G=N();if(G!==null)G=G;else{G=Q();G=G!==null?G:null}if(G!==null)D=[D,O,P,G];else{D=null;a=u}}else{D=null;a=u}}else{D=null;a=u}}else{D=null;a=u}u=D!==null?["param",["literal",D[1]],D[3]]:null}s=s!==null?["params"].concat(s):null;(h=m)&&s===
39 | null&&r("params");v[i]={nextPos:a,result:s};i=s}if(i!==null)c=[c,d,g,f,i];else{c=null;a=b}}else{c=null;a=b}}else{c=null;a=b}}else{c=null;a=b}}else{c=null;a=b}b=c!==null?[c[1],c[2],c[3],c[4]]:null;v[n]={nextPos:a,result:b};return b}function t(){var n="end_tag@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=h;h=false;var c=a,d=C();if(d!==null){if(p.substr(a,1)==="/"){var g="/";a+=1}else{g=null;h&&r('"/"')}if(g!==null){var f=N();if(f!==null){var i=E();if(i!==null)d=[d,g,f,i];else{d=null;a=c}}else{d=null;
40 | a=c}}else{d=null;a=c}}else{d=null;a=c}c=d!==null?d[2]:null;(h=b)&&c===null&&r("end tag");v[n]={nextPos:a,result:c};return c}function y(){var n="context@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=a;if(p.substr(a,1)===":"){var c=":";a+=1}else{c=null;h&&r('":"')}if(c!==null){var d=N();if(d!==null)c=[c,d];else{c=null;a=b}}else{c=null;a=b}b=c!==null?c[1]:null;b=b!==null?b:"";b=b!==null?b?["context",b]:["context"]:null;v[n]={nextPos:a,result:b};return b}function A(){var n="bodies@"+a,b=v[n];if(b){a=
41 | b.nextPos;return b.result}b=h;h=false;var c=[],d=a,g=C();if(g!==null){if(p.substr(a,1)===":"){var f=":";a+=1}else{f=null;h&&r('":"')}if(f!==null){var i=I();if(i!==null){var m=E();if(m!==null){var s=K();if(s!==null)g=[g,f,i,m,s];else{g=null;a=d}}else{g=null;a=d}}else{g=null;a=d}}else{g=null;a=d}}else{g=null;a=d}for(d=g!==null?["param",["literal",g[2]],g[4]]:null;d!==null;){c.push(d);d=a;g=C();if(g!==null){if(p.substr(a,1)===":"){f=":";a+=1}else{f=null;h&&r('":"')}if(f!==null){i=I();if(i!==null){m=
42 | E();if(m!==null){s=K();if(s!==null)g=[g,f,i,m,s];else{g=null;a=d}}else{g=null;a=d}}else{g=null;a=d}}else{g=null;a=d}}else{g=null;a=d}d=g!==null?["param",["literal",g[2]],g[4]]:null}c=c!==null?["bodies"].concat(c):null;(h=b)&&c===null&&r("bodies");v[n]={nextPos:a,result:c};return c}function F(){var n="reference@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=h;h=false;var c=a,d=C();if(d!==null){var g=N();if(g!==null){var f;f="filters@"+a;var i=v[f];if(i){a=i.nextPos;f=i.result}else{i=h;h=false;var m=
43 | [],s=a;if(p.substr(a,1)==="|"){var u="|";a+=1}else{u=null;h&&r('"|"')}if(u!==null){var D=I();if(D!==null)u=[u,D];else{u=null;a=s}}else{u=null;a=s}for(s=u!==null?u[1]:null;s!==null;){m.push(s);s=a;if(p.substr(a,1)==="|"){u="|";a+=1}else{u=null;h&&r('"|"')}if(u!==null){D=I();if(D!==null)u=[u,D];else{u=null;a=s}}else{u=null;a=s}s=u!==null?u[1]:null}m=m!==null?["filters"].concat(m):null;(h=i)&&m===null&&r("filters");v[f]={nextPos:a,result:m};f=m}if(f!==null){i=E();if(i!==null)d=[d,g,f,i];else{d=null;
44 | a=c}}else{d=null;a=c}}else{d=null;a=c}}else{d=null;a=c}c=d!==null?["reference",d[1],d[2]]:null;(h=b)&&c===null&&r("reference");v[n]={nextPos:a,result:c};return c}function L(){var n="special@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=h;h=false;var c=a,d=C();if(d!==null){if(p.substr(a,1)==="~"){var g="~";a+=1}else{g=null;h&&r('"~"')}if(g!==null){var f=I();if(f!==null){var i=E();if(i!==null)d=[d,g,f,i];else{d=null;a=c}}else{d=null;a=c}}else{d=null;a=c}}else{d=null;a=c}c=d!==null?["special",d[2]]:
45 | null;(h=b)&&c===null&&r("special");v[n]={nextPos:a,result:c};return c}function N(){var n="identifier@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=h;h=false;var c=V();c=c!==null?X(["path"].concat(c),n):null;if(c!==null)c=c;else{c=I();c=c!==null?X(["key",c],n):null;c=c!==null?c:null}(h=b)&&c===null&&r("identifier");v[n]={nextPos:a,result:c};return c}function V(){var n="path@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=h;h=false;var c=a,d=I();d=d!==null?d:"";if(d!==null){var g=a;if(p.substr(a,1)===
46 | "."){var f=".";a+=1}else{f=null;h&&r('"."')}if(f!==null){var i=I();if(i!==null)f=[f,i];else{f=null;a=g}}else{f=null;a=g}g=f!==null?f[1]:null;if(g!==null)for(var m=[];g!==null;){m.push(g);g=a;if(p.substr(a,1)==="."){f=".";a+=1}else{f=null;h&&r('"."')}if(f!==null){i=I();if(i!==null)f=[f,i];else{f=null;a=g}}else{f=null;a=g}g=f!==null?f[1]:null}else m=null;if(m!==null)d=[d,m];else{d=null;a=c}}else{d=null;a=c}c=d!==null?function(s,u){if(s){u.unshift(s);return[false,u]}return[true,u]}(d[0],d[1]):null;if(c!==
47 | null)c=c;else{if(p.substr(a,1)==="."){c=".";a+=1}else{c=null;h&&r('"."')}c=c!==null?[true,[]]:null;c=c!==null?c:null}(h=b)&&c===null&&r("path");v[n]={nextPos:a,result:c};return c}function I(){var n="key@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=h;h=false;var c=a;if(p.substr(a).match(/^[a-zA-Z_$]/)!==null){var d=p.charAt(a);a++}else{d=null;h&&r("[a-zA-Z_$]")}if(d!==null){var g=[];if(p.substr(a).match(/^[0-9a-zA-Z_$]/)!==null){var f=p.charAt(a);a++}else{f=null;h&&r("[0-9a-zA-Z_$]")}for(;f!==null;){g.push(f);
48 | if(p.substr(a).match(/^[0-9a-zA-Z_$]/)!==null){f=p.charAt(a);a++}else{f=null;h&&r("[0-9a-zA-Z_$]")}}if(g!==null)d=[d,g];else{d=null;a=c}}else{d=null;a=c}c=d!==null?d[0]+d[1].join(""):null;(h=b)&&c===null&&r("key");v[n]={nextPos:a,result:c};return c}function Q(){var n="inline@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=h;h=false;var c=a;if(p.substr(a,1)==='"'){var d='"';a+=1}else{d=null;h&&r('"\\""')}if(d!==null){if(p.substr(a,1)==='"'){var g='"';a+=1}else{g=null;h&&r('"\\""')}if(g!==null)d=[d,
49 | g];else{d=null;a=c}}else{d=null;a=c}c=d!==null?["literal",""]:null;if(c!==null)c=c;else{c=a;if(p.substr(a,1)==='"'){d='"';a+=1}else{d=null;h&&r('"\\""')}if(d!==null){g=e();if(g!==null){if(p.substr(a,1)==='"'){var f='"';a+=1}else{f=null;h&&r('"\\""')}if(f!==null)d=[d,g,f];else{d=null;a=c}}else{d=null;a=c}}else{d=null;a=c}c=d!==null?["literal",d[1]]:null;if(c!==null)c=c;else{c=a;if(p.substr(a,1)==='"'){d='"';a+=1}else{d=null;h&&r('"\\""')}if(d!==null){f=T();if(f!==null)for(g=[];f!==null;){g.push(f);
50 | f=T()}else g=null;if(g!==null){if(p.substr(a,1)==='"'){f='"';a+=1}else{f=null;h&&r('"\\""')}if(f!==null)d=[d,g,f];else{d=null;a=c}}else{d=null;a=c}}else{d=null;a=c}c=d!==null?["body"].concat(d[1]):null;c=c!==null?c:null}}(h=b)&&c===null&&r("inline");v[n]={nextPos:a,result:c};return c}function T(){var n="inline_part@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=L();if(b!==null)b=b;else{b=F();if(b!==null)b=b;else{b=e();b=b!==null?["buffer",b]:null;b=b!==null?b:null}}v[n]={nextPos:a,result:b};return b}
51 | function e(){var n="literal@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=h;h=false;var c=a,d=a,g=h;h=false;var f=x();h=g;if(f===null)g="";else{g=null;a=d}if(g!==null){f=a;d=h;h=false;var i=M();h=d;if(i===null)d="";else{d=null;a=f}if(d!==null){f=k();if(f!==null)f=f;else{if(p.substr(a).match(/^[^"]/)!==null){f=p.charAt(a);a++}else{f=null;h&&r('[^"]')}f=f!==null?f:null}if(f!==null)g=[g,d,f];else{g=null;a=c}}else{g=null;a=c}}else{g=null;a=c}c=g!==null?g[2]:null;if(c!==null)for(var m=[];c!==null;){m.push(c);
52 | d=c=a;g=h;h=false;f=x();h=g;if(f===null)g="";else{g=null;a=d}if(g!==null){f=a;d=h;h=false;i=M();h=d;if(i===null)d="";else{d=null;a=f}if(d!==null){f=k();if(f!==null)f=f;else{if(p.substr(a).match(/^[^"]/)!==null){f=p.charAt(a);a++}else{f=null;h&&r('[^"]')}f=f!==null?f:null}if(f!==null)g=[g,d,f];else{g=null;a=c}}else{g=null;a=c}}else{g=null;a=c}c=g!==null?g[2]:null}else m=null;m=m!==null?m.join(""):null;(h=b)&&m===null&&r("literal");v[n]={nextPos:a,result:m};return m}function k(){var n="esc@"+a,b=v[n];
53 | if(b){a=b.nextPos;return b.result}if(p.substr(a,2)==='\\"'){b='\\"';a+=2}else{b=null;h&&r('"\\\\\\""')}b=b!==null?'"':null;v[n]={nextPos:a,result:b};return b}function l(){var n="comment@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=h;h=false;var c=a;if(p.substr(a,2)==="{!"){var d="{!";a+=2}else{d=null;h&&r('"{!"')}if(d!==null){var g=[],f=a,i=a,m=h;h=false;if(p.substr(a,2)==="!}"){var s="!}";a+=2}else{s=null;h&&r('"!}"')}h=m;if(s===null)m="";else{m=null;a=i}if(m!==null){if(p.length>a){i=p.charAt(a);
54 | a++}else{i=null;h&&r("any character")}if(i!==null)i=[m,i];else{i=null;a=f}}else{i=null;a=f}for(f=i!==null?i[1]:null;f!==null;){g.push(f);i=f=a;m=h;h=false;if(p.substr(a,2)==="!}"){s="!}";a+=2}else{s=null;h&&r('"!}"')}h=m;if(s===null)m="";else{m=null;a=i}if(m!==null){if(p.length>a){i=p.charAt(a);a++}else{i=null;h&&r("any character")}if(i!==null)i=[m,i];else{i=null;a=f}}else{i=null;a=f}f=i!==null?i[1]:null}if(g!==null){if(p.substr(a,2)==="!}"){f="!}";a+=2}else{f=null;h&&r('"!}"')}if(f!==null)d=[d,g,
55 | f];else{d=null;a=c}}else{d=null;a=c}}else{d=null;a=c}c=d!==null?["comment",d[1].join("")]:null;(h=b)&&c===null&&r("comment");v[n]={nextPos:a,result:c};return c}function x(){var n="tag@"+a,b=v[n];if(b){a=b.nextPos;return b.result}b=a;var c=C();if(c!==null){if(p.substr(a).match(/^[#?^><+%:@\/~%]/)!==null){var d=p.charAt(a);a++}else{d=null;h&&r("[#?^><+%:@\\/~%]")}if(d!==null){var g=a,f=a,i=h;h=false;var m=E();h=i;if(m===null)i="";else{i=null;a=f}if(i!==null){f=a;m=h;h=false;var s=M();h=m;if(s===null)m=
56 | "";else{m=null;a=f}if(m!==null){if(p.length>a){f=p.charAt(a);a++}else{f=null;h&&r("any character")}if(f!==null)i=[i,m,f];else{i=null;a=g}}else{i=null;a=g}}else{i=null;a=g}if(i!==null)for(var u=[];i!==null;){u.push(i);f=g=a;i=h;h=false;m=E();h=i;if(m===null)i="";else{i=null;a=f}if(i!==null){f=a;m=h;h=false;s=M();h=m;if(s===null)m="";else{m=null;a=f}if(m!==null){if(p.length>a){f=p.charAt(a);a++}else{f=null;h&&r("any character")}if(f!==null)i=[i,m,f];else{i=null;a=g}}else{i=null;a=g}}else{i=null;a=g}}else u=
57 | null;if(u!==null){g=E();if(g!==null)c=[c,d,u,g];else{c=null;a=b}}else{c=null;a=b}}else{c=null;a=b}}else{c=null;a=b}if(c!==null)b=c;else{b=F();b=b!==null?b:null}v[n]={nextPos:a,result:b};return b}function C(){var n="ld@"+a,b=v[n];if(b){a=b.nextPos;return b.result}if(p.substr(a,1)==="{"){b="{";a+=1}else{b=null;h&&r('"{"')}v[n]={nextPos:a,result:b};return b}function E(){var n="rd@"+a,b=v[n];if(b){a=b.nextPos;return b.result}if(p.substr(a,1)==="}"){b="}";a+=1}else{b=null;h&&r('"}"')}v[n]={nextPos:a,result:b};
58 | return b}function M(){var n="eol@"+a,b=v[n];if(b){a=b.nextPos;return b.result}if(p.substr(a,1)==="\n"){b="\n";a+=1}else{b=null;h&&r('"\\n"')}if(b!==null)b=b;else{if(p.substr(a,2)==="\r\n"){b="\r\n";a+=2}else{b=null;h&&r('"\\r\\n"')}if(b!==null)b=b;else{if(p.substr(a,1)==="\r"){b="\r";a+=1}else{b=null;h&&r('"\\r"')}if(b!==null)b=b;else{if(p.substr(a,1)==="\u2028"){b="\u2028";a+=1}else{b=null;h&&r('"\\u2028"')}if(b!==null)b=b;else{if(p.substr(a,1)==="\u2029"){b="\u2029";a+=1}else{b=null;h&&r('"\\u2029"')}b=
59 | b!==null?b:null}}}}v[n]={nextPos:a,result:b};return b}function U(){var n="ws@"+a,b=v[n];if(b){a=b.nextPos;return b.result}if(p.substr(a).match(/^[\t\u000b\u000c \xA0\uFEFF]/)!==null){b=p.charAt(a);a++}else{b=null;h&&r("[\t\u000b\u000c \\xA0\\uFEFF]")}v[n]={nextPos:a,result:b};return b}function Y(){var n=function(c){c.sort();for(var d=null,g=[],f=0;f