├── 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 |
2 | 6 |
7 | -------------------------------------------------------------------------------- /src/templates/choice-checkbox.tpl: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 | -------------------------------------------------------------------------------- /src/templates/element-paragraph-text.tpl: -------------------------------------------------------------------------------- 1 |
  • 2 | 6 | 7 |
  • -------------------------------------------------------------------------------- /src/templates/element-email.tpl: -------------------------------------------------------------------------------- 1 |
  • 2 | 6 | 7 |
  • -------------------------------------------------------------------------------- /src/templates/element-number.tpl: -------------------------------------------------------------------------------- 1 |
  • 2 | 6 | 7 |
  • -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | heroku keys:add 2 | heroku keys:add.pub 3 | .DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | # Icon must end with two \r 7 | Icon 8 | # Thumbnails 9 | ._* 10 | # Files that might appear on external disk 11 | .Spotlight-V100 12 | .Trashes 13 | # Directories potentially created on remote AFP share 14 | .AppleDB 15 | .AppleDesktop 16 | Network Trash Folder 17 | Temporary Items 18 | .apdisk 19 | *.swp 20 | -------------------------------------------------------------------------------- /src/templates/element-single-line-text.tpl: -------------------------------------------------------------------------------- 1 |
  • 2 | 6 | 7 |
  • 8 | -------------------------------------------------------------------------------- /src/templates/element-section-break.tpl: -------------------------------------------------------------------------------- 1 |
  • 2 |
    3 | 4 |

    A description of the section goes here.

    5 |
  • -------------------------------------------------------------------------------- /demo/save.php: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 |   9 | 10 |   11 | 12 | 13 | 14 | {/choices} -------------------------------------------------------------------------------- /src/templates/settings-choice-radio.tpl: -------------------------------------------------------------------------------- 1 | {#choices} 2 |
    3 | 4 | 7 | 8 |   9 | 10 |   11 | 12 | 13 |
    14 | {/choices} -------------------------------------------------------------------------------- /src/templates/settings-choice-checkbox.tpl: -------------------------------------------------------------------------------- 1 | {#choices} 2 |
    3 | 4 | 7 | 8 |   9 | 10 |   11 | 12 | 13 |
    14 | {/choices} -------------------------------------------------------------------------------- /src/templates/element-dropdown.tpl: -------------------------------------------------------------------------------- 1 |
  • 2 | 6 | 11 |
  • -------------------------------------------------------------------------------- /demo/render.php: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | Render 27 | 28 | 29 | 30 |
    31 |
    32 | render_form(); ?> 33 |
    34 |
    35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/libraries/tabs.jquery.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tabs 3 | * Copyright (c) 2014 (v1.0) Shlomi Nissan, 1ByteBeta (http://www.1bytebeta.com) 4 | * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 5 | */(function(e){e.fn.tabs=function(t){var n=e.extend({},t),r=function(t){e(t).each(function(t){var n=e(this).data("target");e(this).hasClass("active")?e(n).css("display","block"):e(n).css("display","none")})},i=function(t,n){e(n).each(function(){e(this).removeClass("active")});e(n).each(function(){e(this).data("target")==t&&e(this).addClass("active")});r(n)},s=e(this).children("li");e(s).each(function(t){var n=e(this).data("target");e(n).css("display","none");r(s);e(this).click(function(){i(n,s)})});return{showTab:function(t){e(s).each(function(){e(this).removeClass("active")});e(s).each(function(){e(this).data("target")==t&&e(this).addClass("active")});e(s).each(function(t){var n=e(this).data("target");e(this).hasClass("active")?e(n).css("display","block"):e(n).css("display","none")})}}}})(jQuery); -------------------------------------------------------------------------------- /src/templates/rules.tpl: -------------------------------------------------------------------------------- 1 | {#rules} 2 | 3 |
    4 | 5 |
    6 | 7 | Field:   8 |   13 | 14 | Condition: 15 | 18 | 19 |
    20 | 21 | Value:   22 |   25 | 26 | Action:   27 | 30 | 31 |
    32 | 33 | Target:   34 | 37 | 38 |
    39 | 40 | 41 | 42 |
    43 | 44 | {/rules} 45 | -------------------------------------------------------------------------------- /src/templates/rule.tpl: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 | 5 | Field:   6 |   17 | 18 | Condition: 19 | 22 | 23 |
    24 | 25 | Value:   26 |   29 | 30 | Action:   31 | 35 | 36 |
    37 | 38 | Target:   39 | 49 | 50 |
    51 | 52 | 53 | 54 |
    55 | -------------------------------------------------------------------------------- /src/templates/element-multiple-choice.tpl: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | 7 | 8 |
    9 | 10 |
    11 | 15 |
    16 | 17 |
    18 | 22 |
    23 | 24 |
    25 | 29 |
    30 | 31 |
    32 | 33 |
  • -------------------------------------------------------------------------------- /src/templates/element-checkboxes.tpl: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | 7 | 8 |
    9 | 10 |
    11 | 15 |
    16 | 17 |
    18 | 22 |
    23 | 24 |
    25 | 29 |
    30 | 31 |
    32 | 33 |
  • -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | jQuery Form Builder v3.0 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 | 19 |
    20 | 21 | 32 | 33 |
    34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/libraries/tabs.jquery.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tabs 3 | * Copyright (c) 2014 (v1.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.tabs = function(options) { 10 | 11 | // Set default settings 12 | var settings = $.extend({ 13 | 14 | }, options); 15 | 16 | var showActiveTab = function(tabs) { 17 | 18 | $(tabs).each(function(i){ 19 | 20 | var pane = $(this).data('target'); 21 | 22 | if( $(this).hasClass('active') ) { 23 | 24 | $(pane).css('display', 'block'); 25 | 26 | } else { 27 | 28 | $(pane).css('display', 'none'); 29 | 30 | } 31 | 32 | }); 33 | } 34 | 35 | var showTab = function(tab, tabs) { 36 | 37 | $(tabs).each(function(){ 38 | $(this).removeClass('active'); 39 | }); 40 | 41 | $(tabs).each(function(){ 42 | if( $(this).data('target') == tab) { 43 | $(this).addClass('active'); 44 | } 45 | }); 46 | 47 | showActiveTab(tabs); 48 | 49 | } 50 | 51 | var tabs = $(this).children('li'); 52 | 53 | $(tabs).each(function(i){ 54 | 55 | var target = $(this).data('target'); 56 | $(target).css('display', 'none'); 57 | showActiveTab(tabs); 58 | 59 | $(this).click(function(){ 60 | showTab(target, tabs); 61 | }); 62 | 63 | }); 64 | 65 | return { 66 | 67 | showTab: function(tab) { 68 | 69 | $(tabs).each(function(){ 70 | $(this).removeClass('active'); 71 | }); 72 | 73 | $(tabs).each(function(){ 74 | if( $(this).data('target') == tab) { 75 | $(this).addClass('active'); 76 | } 77 | }); 78 | 79 | $(tabs).each(function(i){ 80 | 81 | var pane = $(this).data('target'); 82 | 83 | if( $(this).hasClass('active') ) { 84 | 85 | $(pane).css('display', 'block'); 86 | 87 | } else { 88 | 89 | $(pane).css('display', 'none'); 90 | 91 | } 92 | 93 | }); 94 | } 95 | 96 | } 97 | 98 | } 99 | 100 | }(jQuery)); 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | formbuilder 2 | =========== 3 | 4 | jQuery and HTML5 Form Builder. Crafted with love by [1Byte Beta](http://1bytebeta.com). 5 | 6 | Demo: http://www.1bytebeta.com/projects/formbuilder/ 7 | 8 | ## About 9 | 10 | I always admired Wufoo's form builder. It's much simpler than other services such as Google Forms. 11 | 12 | In both of our recent projects at 1Byte Beta, we needed to integrate a form builder. I found a couple of great solutions on GitHub but they didn't work the way I wanted them to work so I decided to start a new project. 13 | 14 | This form builder allow users to create simple web forms by dragging and dropping form elements. When users save the form, the plugin serialise the active state of the form to a JSON string so it can be saved and later rendered. Also included is a `formLoader` PHP class that convert the JSON string back into a usable web form. If you're not sure, take the demo for a test drive. 15 | 16 | ## Requirements 17 | 18 | I tried to keep it as light as possible. 19 | 20 | * `jquery` - The jam. 21 | * `dustjs` - Template engine for Javascript. 22 | * `tabs` - Minimal tabs plugin I wrote specifically for this plugin. 23 | 24 | ## How to use 25 | 26 | It's really easy to get started. First you need to include the required libraries at the bottom of your HTML page: 27 | 28 | ``` 29 | 30 | 31 | 32 | 33 | 34 | ``` 35 | 36 | Then, you can initialise the plugin using an empty div container. 37 | 38 | ``` 39 | 45 | ``` 46 | 47 | ## Options 48 | 49 | `load_url` - This URL is called on form init. It needs to return a JSON string containing the existing form structure that was previously generated by the form builder. 50 | 51 | `save_url` - This URL is called through AJAX when a user saves the form. It passes a parameter [formData] using POST request. 52 | 53 | ## Callbacks 54 | 55 | `onSaveForm` - Called when a user saves the form. 56 | 57 | ``` 58 | $('#formBuilder').formBuilder({ 59 | onSaveForm: function() { 60 | // The form has been saved! Create an alert and notify the user here... 61 | } 62 | }); 63 | ``` 64 | 65 | ## License 66 | The MIT License 67 | 68 | Copyright (c) 2008-2014 1Byte Beta hello@1bytebeta.com 69 | 70 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 71 | 72 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 73 | 74 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 75 | -------------------------------------------------------------------------------- /src/json/example.json: -------------------------------------------------------------------------------- 1 | {"title":"My Form","description":"This is my form. Please fill it out. It's awesome!","fields":[{"title":"Personal Details","type":"element-section-break","required":false,"position":1,"description":""},{"title":"Title","type":"element-single-line-text","required":true,"position":2},{"title":"First Name","type":"element-single-line-text","required":true,"position":3},{"title":"Last Name","type":"element-single-line-text","required":true,"position":4},{"title":"Department","type":"element-single-line-text","required":true,"position":5},{"title":"Position","type":"element-single-line-text","required":true,"position":6},{"title":"Country","type":"element-single-line-text","required":true,"position":7},{"title":"City","type":"element-single-line-text","required":true,"position":8},{"title":"Suburb","type":"element-single-line-text","required":true,"position":9},{"title":"Email","type":"element-single-line-text","required":true,"position":10},{"title":"Registration Type","type":"element-multiple-choice","required":true,"position":11,"choices":[{"title":"Option 1","value":"Option 1","checked":true},{"title":"Option 2","value":"Option 2","checked":false},{"title":"Option 3","value":"Option 3","checked":false}]},{"title":"Personal Requirements","type":"element-section-break","required":false,"position":12,"description":""},{"title":"Parking Required","type":"element-multiple-choice","required":false,"position":13,"choices":[{"title":"Yes","value":"Yes","checked":true},{"title":"No","value":"No","checked":false}]},{"title":"Dietary Requirements","type":"element-single-line-text","required":false,"position":14},{"title":"Flights","type":"element-section-break","required":false,"position":15,"description":""},{"title":"Flights Required","type":"element-multiple-choice","required":true,"position":16,"choices":[{"title":"Yes","value":"Yes","checked":true},{"title":"No","value":"No","checked":false}]},{"title":"Departure Airport","type":"element-single-line-text","required":true,"position":17},{"title":"Preferred Airline","type":"element-single-line-text","required":false,"position":18},{"title":"Preferred Flight Time","type":"element-dropdown","required":false,"position":19,"choices":[{"title":"Morning","value":"Morning","checked":true},{"title":"Afternoon","value":"Afternoon","checked":false},{"title":"Evening","value":"Evening","checked":false},{"title":"Night","value":"Night","checked":false}]},{"title":"Accomodation","type":"element-section-break","required":false,"position":20,"description":""},{"title":"Accomodation Required","type":"element-multiple-choice","required":true,"position":21,"choices":[{"title":"Yes","value":"Yes","checked":true},{"title":"No","value":"No","checked":false}]},{"title":"Check-in Date","type":"element-single-line-text","required":true,"position":22},{"title":"Check-out Date","type":"element-single-line-text","required":true,"position":23},{"title":"Special Requirements","type":"element-single-line-text","required":false,"position":24},{"title":"Passenger Details","type":"element-section-break","required":false,"position":25,"description":""},{"title":"Title","type":"element-single-line-text","required":true,"position":26},{"title":"First Name","type":"element-single-line-text","required":true,"position":27},{"title":"Middle","type":"element-single-line-text","required":false,"position":28},{"title":"Last Name","type":"element-single-line-text","required":true,"position":29},{"title":"Frequent Flyer Number","type":"element-single-line-text","required":false,"position":30},{"title":"Comments","type":"element-section-break","required":false,"position":31,"description":""},{"title":"Comments","type":"element-paragraph-text","required":false,"position":32}],"rules":[{"field":"Flights Required","condition":"Equal","value":"Yes","action":"Show","target":"Departure Airport"},{"field":"Flights Required","condition":"Equal","value":"Yes","action":"Show","target":"Preferred Airline"},{"field":"Flights Required","condition":"Equal","value":"Yes","action":"Show","target":"Preferred Flight Time"},{"field":"Accomodation Required","condition":"Equal","value":"Yes","action":"Show","target":"Check-in Date"},{"field":"Accomodation Required","condition":"Equal","value":"Yes","action":"Show","target":"Check-out Date"},{"field":"Accomodation Required","condition":"Equal","value":"Yes","action":"Show","target":"Special Requirements"}]} 2 | 3 | -------------------------------------------------------------------------------- /src/css/style.css: -------------------------------------------------------------------------------- 1 | /* Variables */ 2 | /* Mixins */ 3 | .animate { 4 | -moz-transition: all 0.1s ease-in; 5 | -webkit-transition: all 0.1s ease-in; 6 | -o-transition: all 0.1s ease-in; 7 | transition: all 0.1s ease-in; 8 | } 9 | /* Template */ 10 | #master-container { 11 | width: 1050px; 12 | margin: 0 auto; 13 | border: 1px solid #cdcdcd; 14 | } 15 | #form-container { 16 | float: left; 17 | width: 100%; 18 | overflow: hidden; 19 | clear: left; 20 | } 21 | #tabs-container { 22 | float: left; 23 | width: 100%; 24 | position: relative; 25 | border: 1px solid #cdcdcd; 26 | border-width: 0 1px 0 0; 27 | right: 60%; 28 | } 29 | .left-col { 30 | float: left; 31 | width: 40%; 32 | position: relative; 33 | left: 60%; 34 | overflow: hidden; 35 | } 36 | .right-col { 37 | float: left; 38 | width: 60%; 39 | position: relative; 40 | left: 60%; 41 | overflow: hidden; 42 | } 43 | /* Tabs */ 44 | .nav-tabs { 45 | margin: 0px; 46 | padding: 0px; 47 | border: 1px solid #cdcdcd; 48 | border-width: 0 0 1px 0; 49 | margin-top: 46px; 50 | position: relative; 51 | } 52 | .nav-tabs li:first-child { 53 | margin-left: 10px; 54 | } 55 | .nav-tabs li { 56 | list-style-type: none; 57 | display: inline-block; 58 | padding: 10px 15px; 59 | font-size: 14px; 60 | cursor: pointer; 61 | position: relative; 62 | } 63 | .nav-tabs li a { 64 | text-decoration: none; 65 | } 66 | .nav-tabs li.active { 67 | border: 1px solid #cdcdcd; 68 | border-radius: 3px; 69 | -moz-border-radius: 3px; 70 | -webkit-border-radius: 3px; 71 | border-bottom-color: #f1f1f1; 72 | padding-bottom: 12px; 73 | top: 1px; 74 | } 75 | .required-star { 76 | color: red; 77 | } 78 | /* Form Container */ 79 | #form-container hr { 80 | border: 1px solid #cdcdcd; 81 | border-width: 1px 0 0 0; 82 | } 83 | #form-container h3 { 84 | margin-top: 0px; 85 | margin-bottom: 0px; 86 | } 87 | /* Form Fields */ 88 | .right-col { 89 | margin-bottom: 20px; 90 | } 91 | .right-col .loading { 92 | width: 100%; 93 | text-align: center; 94 | margin-top: 17%; 95 | } 96 | .right-col #form-elements, 97 | .right-col #sortable-elements { 98 | padding: 0px; 99 | margin: 0px; 100 | } 101 | .right-col #form-elements li { 102 | list-style-type: none; 103 | } 104 | .right-col .form-element { 105 | position: relative; 106 | list-style-type: none; 107 | cursor: pointer; 108 | padding: 25px; 109 | } 110 | .right-col .form-element input, 111 | .right-col .form-element texarea, 112 | .right-col .form-element label { 113 | cursor: pointer; 114 | } 115 | .right-col .form-element h2, 116 | .right-col .form-element h5 { 117 | margin: 0px; 118 | padding: 0px; 119 | } 120 | .right-col .form-element h2 { 121 | margin-bottom: 5px; 122 | } 123 | .right-col #form-settings-element { 124 | margin-bottom: 10px; 125 | } 126 | .right-col #form-settings-element:hover { 127 | border: none; 128 | } 129 | .right-col #form-settings-element.selected { 130 | background-color: #c3dbf8; 131 | } 132 | .right-col #sortable-elements { 133 | padding: 0px; 134 | margin: 0px; 135 | margin-top: 25px; 136 | } 137 | .right-col #sortable-elements .form-element { 138 | width: 85%; 139 | margin: 10px 0 10px 25px; 140 | list-style-type: none; 141 | cursor: pointer; 142 | padding: 15px; 143 | border: 1px solid #cdcdcd; 144 | } 145 | .right-col #sortable-elements .form-element label { 146 | display: block; 147 | font-size: 14px; 148 | margin-bottom: 10px; 149 | } 150 | .right-col #sortable-elements .form-element.selected { 151 | background-color: #DCF7C0; 152 | border: 1px solid #7ED321; 153 | } 154 | .right-col #sortable-elements .form-element:hover { 155 | border: 1px dashed #7ED321; 156 | } 157 | .right-col #sortable-elements .section-break { 158 | margin: 25px !important; 159 | padding: 0px !important; 160 | border: none !important; 161 | width: 100%; 162 | left: 0px; 163 | } 164 | .right-col #sortable-elements .section-break label { 165 | font-size: 20px !important; 166 | padding: 10px; 167 | padding-bottom: 0px; 168 | margin-bottom: 0px !important; 169 | } 170 | .right-col #sortable-elements .section-break p { 171 | margin-top: 0px; 172 | padding: 10px; 173 | padding-top: 0px; 174 | } 175 | .right-col .button { 176 | margin: 20px 0 30px 25px; 177 | } 178 | /* Tabs toolbox */ 179 | .left-col .form-group label { 180 | font-weight: bold; 181 | font-size: 15px; 182 | margin-top: 10px; 183 | margin-bottom: 8px; 184 | display: block; 185 | } 186 | .left-col .col-sm-6 { 187 | width: 47%; 188 | float: left; 189 | margin-left: 7px; 190 | } 191 | .left-col .section { 192 | padding: 20px; 193 | border: 1px solid #cdcdcd; 194 | margin: 0 0 15px 0; 195 | border-radius: 3px; 196 | -moz-border-radius: 3px; 197 | -webkit-border-radius: 3px; 198 | } 199 | .left-col .new-element { 200 | margin-bottom: 3px; 201 | } 202 | .left-col .choice { 203 | margin-bottom: 5px; 204 | } 205 | .left-col textarea { 206 | width: 100%; 207 | } 208 | .left-col input[type='text'] { 209 | width: 100%; 210 | } 211 | /* Footer */ 212 | #footer { 213 | width: 600px; 214 | text-align: center; 215 | margin: 0 auto; 216 | font-size: 12px; 217 | } 218 | #footer p { 219 | margin-top: 30px; 220 | font-size: 14px; 221 | } 222 | -------------------------------------------------------------------------------- /src/less/style.less: -------------------------------------------------------------------------------- 1 | /* Variables */ 2 | 3 | @background-color: #F1F1F1; 4 | @border-color: #CDCDCD; 5 | @light-gray: #4A4A4A; 6 | @selected-blue: #C3DBF8; 7 | @selected-green: #D1E1A8; 8 | @button-blue: #4A90E2; 9 | 10 | /* Mixins */ 11 | 12 | .round-corners(@radius) { 13 | border-radius: @radius; 14 | -moz-border-radius: @radius; 15 | -webkit-border-radius: @radius; 16 | } 17 | 18 | .animate { 19 | -moz-transition: all 0.1s ease-in; 20 | -webkit-transition: all 0.1s ease-in; 21 | -o-transition: all 0.1s ease-in; 22 | transition: all 0.1s ease-in; 23 | } 24 | 25 | /* Template */ 26 | 27 | #master-container { 28 | width: 1050px; 29 | margin: 0 auto; 30 | border: 1px solid @border-color; 31 | } 32 | #form-container { 33 | float:left; 34 | width: 100%; 35 | overflow:hidden; 36 | clear: left; 37 | } 38 | #tabs-container { 39 | float:left; 40 | width:100%; 41 | position:relative; 42 | border: 1px solid @border-color; 43 | border-width: 0 1px 0 0; 44 | right:60%; 45 | } 46 | .left-col { 47 | float:left; 48 | width:40%; 49 | position:relative; 50 | left:60%; 51 | overflow:hidden; 52 | } 53 | .right-col { 54 | float:left; 55 | width:60%; 56 | position:relative; 57 | left:60%; 58 | overflow:hidden; 59 | } 60 | 61 | /* Tabs */ 62 | 63 | .nav-tabs { 64 | 65 | margin: 0px; 66 | padding: 0px; 67 | border: 1px solid @border-color; 68 | border-width: 0 0 1px 0; 69 | margin-top: 46px; 70 | position: relative; 71 | 72 | li:first-child { 73 | margin-left: 10px; 74 | } 75 | 76 | li { 77 | 78 | list-style-type: none; 79 | display: inline-block; 80 | padding: 10px 15px; 81 | font-size: 14px; 82 | cursor: pointer; 83 | 84 | a { 85 | text-decoration: none; 86 | } 87 | 88 | position: relative; 89 | 90 | } 91 | 92 | li.active { 93 | border: 1px solid @border-color; 94 | .round-corners(3px); 95 | border-bottom-color: @background-color; 96 | padding-bottom: 12px; 97 | top: 1px; 98 | } 99 | 100 | } 101 | 102 | .required-star { 103 | color: red; 104 | } 105 | 106 | /* Form Container */ 107 | 108 | #form-container { 109 | hr { 110 | border: 1px solid @border-color; 111 | border-width: 1px 0 0 0; 112 | } 113 | 114 | h3 { 115 | margin-top: 0px; 116 | margin-bottom: 0px; 117 | } 118 | 119 | } 120 | 121 | /* Form Fields */ 122 | 123 | .right-col { 124 | 125 | margin-bottom: 20px; 126 | 127 | .loading { 128 | width: 100%; 129 | text-align: center; 130 | margin-top: 17%; 131 | } 132 | 133 | #form-elements, #sortable-elements { 134 | padding: 0px; 135 | margin: 0px; 136 | } 137 | 138 | #form-elements li { 139 | list-style-type: none; 140 | } 141 | 142 | .form-element { 143 | 144 | position: relative; 145 | list-style-type: none; 146 | cursor: pointer; 147 | padding: 25px; 148 | 149 | input, texarea, label { 150 | cursor: pointer; 151 | } 152 | 153 | h2, h5 { 154 | margin: 0px; 155 | padding: 0px; 156 | } 157 | h2 { 158 | margin-bottom: 5px; 159 | } 160 | 161 | } 162 | 163 | #form-settings-element { 164 | margin-bottom: 10px; 165 | } 166 | #form-settings-element:hover { 167 | border: none; 168 | } 169 | #form-settings-element.selected { 170 | background-color: @selected-blue; 171 | } 172 | 173 | #sortable-elements { 174 | padding: 0px; 175 | margin: 0px; 176 | margin-top: 25px; 177 | 178 | .form-element { 179 | width: 85%; 180 | margin: 10px 0 10px 25px; 181 | list-style-type: none; 182 | cursor: pointer; 183 | padding: 15px; 184 | border: 1px solid @border-color; 185 | 186 | label { 187 | display: block; 188 | font-size: 14px; 189 | margin-bottom: 10px; 190 | } 191 | } 192 | 193 | .form-element.selected { 194 | background-color: #DCF7C0; 195 | border: 1px solid #7ED321; 196 | } 197 | 198 | .form-element:hover { 199 | border: 1px dashed #7ED321; 200 | } 201 | 202 | .section-break { 203 | 204 | margin: 25px !important; 205 | padding: 0px !important; 206 | border: none !important; 207 | width: 100%; 208 | left: 0px; 209 | 210 | label { 211 | font-size: 20px !important; 212 | padding: 10px; 213 | padding-bottom: 0px; 214 | margin-bottom: 0px !important; 215 | } 216 | 217 | p { 218 | margin-top: 0px; 219 | padding: 10px; 220 | padding-top: 0px; 221 | } 222 | 223 | } 224 | 225 | } 226 | 227 | .button { 228 | margin: 20px 0 30px 25px; 229 | } 230 | 231 | } 232 | 233 | /* Tabs toolbox */ 234 | 235 | .left-col { 236 | 237 | .form-group label { 238 | font-weight: bold; 239 | font-size: 15px; 240 | margin-top: 10px; 241 | margin-bottom: 8px; 242 | display: block; 243 | } 244 | 245 | .col-sm-6 { 246 | width: 47%; 247 | float: left; 248 | margin-left: 7px; 249 | } 250 | 251 | .section { 252 | padding: 20px; 253 | border: 1px solid @border-color; 254 | margin: 0 0 15px 0; 255 | .round-corners(3px); 256 | } 257 | 258 | .new-element { 259 | margin-bottom: 3px; 260 | } 261 | 262 | .choice { 263 | margin-bottom: 5px; 264 | } 265 | 266 | textarea { 267 | width: 100%; 268 | } 269 | 270 | input[type='text'] { 271 | width: 100%; 272 | } 273 | 274 | } 275 | 276 | /* Footer */ 277 | 278 | #footer { 279 | 280 | width: 600px; 281 | text-align: center; 282 | margin: 0 auto; 283 | 284 | p { 285 | margin-top: 30px; 286 | font-size: 14px; 287 | } 288 | 289 | font-size: 12px; 290 | 291 | } 292 | -------------------------------------------------------------------------------- /src/templates/formbuilder-base.tpl: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 |
    6 | 7 | 13 | 14 |
    15 | 16 |
    17 | 18 |
    19 | 20 | 21 | 22 | 23 |
    24 | 25 |
    26 | 27 | 28 | 29 |
    30 | 31 |
    32 | 33 | 42 | 43 |
    44 | 45 | 94 | 95 | 110 | 111 | 120 | 121 |
    122 | 123 |
    124 | 125 |
    126 | 127 |
    128 | Loading... 129 |
    130 | 131 |
    132 | 133 |
    134 | 135 |
    136 | 137 |
    138 | 139 |
    140 | 141 |
    142 | 143 |
    144 | -------------------------------------------------------------------------------- /src/libraries/dust-js/dust-core-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(d){function h(a,b,c){this.stack=a;this.global=b;this.blocks=c}function k(a,b,c,e){this.tail=b;this.isObject=!d.isArray(a)&&a&&typeof a==="object";this.head=a;this.index=c;this.of=e}function l(a){this.head=new f(this);this.callback=a;this.out=""}function j(){this.head=new f(this)}function f(a,b,c){this.root=a;this.next=b;this.data="";this.flushable=false;this.taps=c}function m(a,b){this.head=a;this.tail=b}d.cache={};d.register=function(a,b){if(a)d.cache[a]=b};d.render=function(a,b,c){c=(new l(c)).head; 11 | d.load(a,c,h.wrap(b)).end()};d.stream=function(a,b){var c=new j;d.nextTick(function(){d.load(a,c.head,h.wrap(b)).end()});return c};d.renderSource=function(a,b,c){return d.compileFn(a)(b,c)};d.compileFn=function(a,b){var c=d.loadSource(d.compile(a,b));return function(e,g){var i=g?new l(g):new j;d.nextTick(function(){c(i.head,h.wrap(e)).end()});return i}};d.load=function(a,b,c){var e=d.cache[a];if(e)return e(b,c);else{if(d.onLoad)return b.map(function(g){d.onLoad(a,function(i,n){if(i)return g.setError(i); 12 | d.cache[a]||d.loadSource(d.compile(n,a));d.cache[a](g,c).end()})});return b.setError(Error("Template Not Found: "+a))}};d.loadSource=function(a){return eval(a)};d.isArray=Array.isArray?Array.isArray:function(a){return Object.prototype.toString.call(a)=="[object Array]"};d.nextTick=function(a){setTimeout(a,0)};d.isEmpty=function(a){if(d.isArray(a)&&!a.length)return true;if(a===0)return false;return!a};d.filter=function(a,b,c){if(c)for(var e=0,g=c.length;e\"]/),p=/&/g,q=//g,s=/\"/g;d.escapeHtml=function(a){if(typeof a==="string"){if(!o.test(a))return a;return a.replace(p,"&").replace(q,"<").replace(r,">").replace(s,""")}return a}; 21 | var t=/\\/g,u=/\r/g,v=/\u2028/g,w=/\u2029/g,x=/\n/g,y=/\f/g,z=/'/g,A=/"/g,B=/\t/g;d.escapeJs=function(a){if(typeof a==="string")return a.replace(t,"\\\\").replace(A,'\\"').replace(z,"\\'").replace(u,"\\r").replace(v,"\\u2028").replace(w,"\\u2029").replace(x,"\\n").replace(y,"\\f").replace(B,"\\t");return a}})(dust);if(typeof exports!=="undefined"){typeof process!=="undefined"&&require("./server")(dust);module.exports=dust}; 22 | -------------------------------------------------------------------------------- /src/templates/formbuilder-fields.tpl: -------------------------------------------------------------------------------- 1 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /src/formBuilder.jquery.min.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 | */(function(e){e.fn.formBuilder=function(t){var n=e.extend({load_url:"/",save_url:"/"},t),r=function(){e(".new-element").click(function(){p();var t=e("#sortable-elements");t.sortable({stop:function(){d()}});var n=e(this).data("type"),r={label:"Untitled",position:e(".form-element").length-1};dust.render(n,r,function(n,s){t.append(s);i();var o=e("#element-"+r.position);m=o;m.addClass("selected");g.showTab("#field-settings");a();h();c()})})},i=function(){e(".form-element").unbind();e(".form-element").click(function(){p();e(this).addClass("selected");if(e(this).data("type")=="form-settings")g.showTab("#form-settings");else{g.showTab("#field-settings");m=e(this);a();h();c()}})},s=function(){e(".toolbox-tab").click(function(){p();e(this).data("target")=="#form-settings"&&e("#form-settings-element").addClass("selected");if(e(this).data("target")=="#field-settings"){if(!m){e("#element-0").addClass("selected");m=e("#element-0")}else m.addClass("selected");a();h()}})},o=function(){e(".bind-control").each(function(){var t=e(this).data("bind");e(this).on("keyup",function(){m==""?e(t).html(e(this).val()):m.data("type")!="element-dropdown"?m.find(t).next(".choice-label").html(e(this).val()):m.find(t).html(e(this).val())})})},u=function(){e(".option").unbind();e(".option").click(function(){var t=e(this).parent().next("input").data("bind"),n=e(m).data("type")!="element-dropdown"?"checked":"selected";e(m).find(t).prop(n,function(e,t){return!t})})},a=function(){e("#field-label").val(m.data("label"));e("#field-label").on("keyup",function(){m.children("label").children(".label-title").html(e(this).val());m.data("label",e(this).val())});m.data("type")=="element-section-break"&&e("#description").val(m.children(".description").html());e("#description").on("keyup",function(){m.children(".description").html(e(this).val());m.data("description",e(this).val())});if(m.data("type")=="element-multiple-choice"||m.data("type")=="element-checkboxes"||m.data("type")=="element-dropdown"){e("#field-choices").css("display","block");e("#field-choices").html('
    ');var t=[],n=m.children(".choices").children(".choice");m.data("type")=="element-dropdown"&&(n=m.children(".choices").children("option"));console.log(n);n.each(function(n){if(m.data("type")!="element-dropdown")var r=e(this).children("label").children("input").is(":checked")?!0:!1,i=e(this).children("label").children("input").attr("class"),s=e(this).children("label").children(".choice-label").html();else var s=e(this).val(),i=e(this).attr("class"),r=e(this).is(":selected")?!0:!1;var o={checked:r,title:s,position:n+1,bindingClass:i};t.push(o)});var r={choices:t};console.log(r);dust.render(m.children(".choices").data("type"),r,function(t,n){e("#field-choices").append(n);o();u();l()})}else e("#field-choices").css("display","none");m.hasClass("required")?e("#required").prop("checked",!0):e("#required").prop("checked",!1);e("#required").unbind();e("#required").change(function(){m.toggleClass("required");var e={};this.checked?dust.render("required",e,function(e,t){m.children("label").append(t)}):m.children("label").children(".required-star").remove()})},f=function(){e("#control-remove-field").click(function(){if(m!="")if(e(".form-element").length>2){m.remove();d();g.showTab("#add-field");p()}else alert("Unable to delete this field! You must have at least 1 field in your form.")});e("#control-add-field").click(function(){g.showTab("#add-field");p()})},l=function(){e(".remove-choice").unbind();e(".remove-choice").click(function(){if(e(this).parent().parent().children(".choice").length>1){var t=e(this).data("delete");e(m).data("type")=="element-dropdown"?e(m).find(t).remove():e(m).find(t).parent().parent().remove();e(this).parent().remove();o();l()}});e(".add-choice").unbind();e(".add-choice").click(function(){var t=2;if(m.data("type")=="element-dropdown")var n=m.children(".choices").children("option");else var n=m.children(".choices").children(".choice");console.log(n);e(n).each(function(n){if(m.data("type")=="element-dropdown")var r=e(this).attr("class");else var r=e(this).find("input").attr("class");var i=r.split("-");tform_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('
    ', $this->action); 103 | $html .= '
    '; 104 | $html .= sprintf('

    %s

    %s

    ', $this->form_data->title, $this->form_data->description); 105 | $html .= $fields; 106 | $html .= ''; 107 | $html .= '
    '; 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 = '
    '; 223 | $html .= $this->make_label($id, $field->title, $required); 224 | 225 | // Render choices 226 | for($i=0; $i < count($field->choices); $i++) { 227 | $checked = $field->choices[$i]->checked ? "checked" : ''; 228 | 229 | $html .= '
    '; 232 | } 233 | 234 | $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 = '
    '; 252 | $html .= $this->make_label($id, $field->title, $required); 253 | 254 | // Render choices 255 | for($i=0; $i < count($field->choices); $i++) { 256 | $checked = $field->choices[$i]->checked ? "checked" : ''; 257 | 258 | $html .= '
    '; 261 | } 262 | 263 | $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 = '
    '; 305 | $html .= sprintf('

    %s

    %s

    ', $field->title, $field->description); 306 | $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