├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── MIT-LICENSE.txt ├── README.md ├── autocomplete-form-tagit.html ├── bower.json ├── complex-sample.html ├── controls ├── dialog.js ├── dialog.ts ├── jquery.jsform.bsfileUpload.js ├── jquery.responsiveDialog.js ├── jquery.sorTable.js ├── jquery.tree.css └── jquery.tree.js ├── js ├── jquery.jsForm-1.4.0.js ├── jquery.jsForm-1.5.0.js ├── jquery.jsForm-1.5.1.js ├── jquery.jsForm-1.5.2.js ├── jquery.jsForm-1.5.4.js ├── jquery.jsForm-1.5.5.js ├── jquery.jsForm-1.6.0.js ├── jquery.jsForm.d.ts ├── jquery.jsForm.js ├── jquery.jsForm.min.js └── jquery.jsForm.min.map ├── jsForm.jquery.json ├── lib ├── handlebars-1.0.rc.1.js ├── jquery.format-1.3.js ├── jquery.min.js ├── jquery.simpledateformat.js ├── jquery.tagit.css └── tag-it.js ├── package-lock.json ├── package.json ├── sample-conditional.html ├── sample-connect.html ├── sample-dataHandler.html ├── sample-multiselect.html ├── sample-object.html ├── sample.html ├── sortable-editable-sample.html ├── sortable-sample.html ├── src ├── jquery.jsForm.controls.js ├── jquery.jsForm.d.ts └── jquery.jsForm.js └── test ├── jquery.jsForm.performance.js ├── jquery.jsForm.test.js ├── performance.jquery.jsForm.html ├── qunit-1.10.0.css ├── qunit-1.10.0.js └── test.jquery.jsForm.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /.project 3 | /.settings 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 11, 3 | "boss": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "eqnull": true, 7 | "expr": true, 8 | "immed": true, 9 | "noarg": true, 10 | "onevar": true, 11 | "quotmark": "double", 12 | "smarttabs": true, 13 | "trailing": true, 14 | "undef": true, 15 | "unused": true, 16 | 17 | "node": true 18 | } 19 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function( grunt ) { 2 | 3 | "use strict"; 4 | 5 | var readOptionalJSON = function( filepath ) { 6 | var data = {}; 7 | try { 8 | data = grunt.file.readJSON( filepath ); 9 | } catch(e) {} 10 | return data; 11 | }, 12 | srcHintOptions = readOptionalJSON("src/.jshintrc"); 13 | 14 | grunt.initConfig({ 15 | pkg: grunt.file.readJSON("package.json"), 16 | // dst: readOptionalJSON("dist/.destination.json"), 17 | concat: { 18 | js: { 19 | src: ["src/jquery.jsForm.js", "src/jquery.jsForm.controls.js"], 20 | dest: "js/jquery.jsForm-<%= pkg.version %>.js" 21 | }, 22 | js2: { 23 | src: ["src/jquery.jsForm.js", "src/jquery.jsForm.controls.js"], 24 | dest: "js/jquery.jsForm.js" 25 | } 26 | 27 | }, 28 | jshint: { 29 | dist: { 30 | src: [ "js/jquery.jsForm-<%= pkg.version %>.js" ], 31 | options: srcHintOptions 32 | }, 33 | grunt: { 34 | src: [ "Gruntfile.js" ], 35 | options: { 36 | jshintrc: ".jshintrc" 37 | } 38 | }, 39 | options: { 40 | esversion: 11 41 | } 42 | // tests: { 43 | // TODO: Once .jshintignore is supported, use that instead. 44 | // issue located here: https://github.com/gruntjs/grunt-contrib-jshint/issues/1 45 | // src: [ "test/data/{test,testinit,testrunner}.js", "test/unit/**/*.js" ], 46 | // options: { 47 | // jshintrc: "test/.jshintrc" 48 | // } 49 | // } 50 | }, 51 | uglify: { 52 | all: { 53 | files: { 54 | "js/jquery.jsForm.min.js": [ "js/jquery.jsForm-<%= pkg.version %>.js" ] 55 | }, 56 | options: { 57 | banner: "/*!\n * jQuery.jsForm v<%= pkg.version %> | (c) 2015 <%=pkg.author.name%> <%=pkg.author.url%>\n * Usage: <%=pkg.homepage%>\n */\n", 58 | beautify: { 59 | ascii_only: true 60 | } 61 | } 62 | } 63 | } 64 | }); 65 | 66 | // Load grunt tasks from NPM packages 67 | grunt.loadNpmTasks("grunt-compare-size"); 68 | grunt.loadNpmTasks("grunt-contrib-jshint"); 69 | grunt.loadNpmTasks("grunt-contrib-uglify"); 70 | grunt.loadNpmTasks("grunt-contrib-concat"); 71 | 72 | 73 | // Short list as a high frequency watch task 74 | grunt.registerTask( "default", [ "concat:js", "concat:js2", "uglify", "jshint" ] ); 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2013 Niko Berger and Contributors, 2 | http://www.corinis.com 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jquery.jsForm 2 | ============= 3 | 4 | jQuery based form library that allows you to handle data within a javascript object (like from a JSON request) with plain html forms. 5 | 6 | This is a library allowing a binding between html and a javascript object as described in [MVVM](http://en.wikipedia.org/wiki/Model_View_ViewModel) similar to other libraries like [knockout.js](http://knockoutjs.com/) or [AngularJs](http://angularjs.org/). 7 | JsForm only takes care of the rendering of data in a html. The controller can be written in standard jQuery. This keeps the library clean and simple. 8 | 9 | 10 | The main features of this library are: 11 | 12 | * Use html markup to fill your forms/page with almost any js object dynamically 13 | * Update an existing js object with changes done within a form (=manipulate data without extra code) 14 | * Provide basic functions for formatting (i.e. date/time, money, byte) using html markup 15 | * Provide form validation functionality 16 | * handle collections (arrays) with subobjects 17 | * handle binaries (blobs) within json by converting them to data url 18 | * provides helper methods to handle array manipulation (add new entry/remove an entry) using only html markup 19 | * Can be used in connection with an autocomplete function to add new array objects 20 | * Compatible with [bootstrap](https://getbootstrap.com/), [jQuery UI](http://jqueryui.com/) and [jQuery Mobile](http://jquerymobile.com/) and other frameworks by working directly on the DOM 21 | * addon library for form controls and layouting (comes bundled in the minified version), internationalisation 22 | * unit tested using [QUnit](http://www.gargan.org/jsform/test/test.jquery.jsForm.html) 23 | * jslint clean 24 | * Minified+Gzipped: 7kb 25 | 26 | You can also check out some [Demos](http://www.gargan.org/jsform/index.jsp). 27 | 28 | # Libraries 29 | 30 | ## Required 31 | * [jQuery](http://jquery.com/) 1.9 or higher 32 | * [luxon](https://moment.github.io/luxon/) Luxon for Date/Time Formatting 33 | 34 | ## Optional 35 | 36 | Optional Libraries are used with jquery.jsForm.controls.js to allow various input methods: 37 | 38 | * [jQuery Format Plugin](http://www.asual.com/jquery/format/) 1.2 or higher- used when working with date/number formats 39 | * [clockpicker](https://weareoutman.github.io/clockpicker/) used to display a clock input using input class="time" 40 | * [flatpickr](https://flatpickr.js.org) date and dateTime control (class="date" or class="dateTime") 41 | * [datetimepicker](https://eonasdan.github.io/bootstrap-datetimepicker/) alternative datetime picker (class="date" or class="dateTime") 42 | * [Moment.js](http://momentjs.com/) allows for finer grained international date support (deprecated) -> use [luxon](https://moment.github.io/luxon/) 43 | 44 | # Download 45 | 46 | Current Version: 1.6.0 47 | 48 | * [Minified](https://github.com/corinis/jsForm/raw/master/js/jquery.jsForm.min.js) 49 | * [Combined Source](https://github.com/corinis/jsForm/raw/master/js/jquery.jsForm.js) 50 | 51 | On bower.io: 52 | ``` 53 | bower install jquery-jsform --save 54 | ``` 55 | 56 | # Documentation 57 | 58 | can be found in the github wiki: 59 | 60 | * [Dom Layout](https://github.com/corinis/jsForm/wiki/JsForm-Dom-Layout) tells you how you can/should structure your html 61 | * [API Documentation](https://github.com/corinis/jsForm/wiki/JsForm-Documentation) for the javascript options 62 | * [Form Controls and Validation](https://github.com/corinis/jsForm/wiki/Controls) additional formatting/validating controls 63 | 64 | # Custom UI Controls 65 | 66 | Additionally to the base form, I created some custom controls. There might be much more powerful versions out there, 67 | but the aim here is to have simple to use controls that are compatible with the json-approach of jsForm and also 68 | compatible to jquery-ui and bootstrap. 69 | 70 | * [Tree](https://github.com/corinis/jsForm/wiki/Tree) 71 | * [jquery.tree.js](https://raw.github.com/corinis/jsForm/master/controls/jquery.tree.js) 72 | * [jquery.tree.css](https://raw.github.com/corinis/jsForm/master/controls/jquery.tree.css) 73 | * [Sortable Table](https://github.com/corinis/jsForm/wiki/Sortable-Table) 74 | * [jquery.sorTable.js](https://raw.github.com/corinis/jsForm/master/controls/jquery.sorTable.js) also allows quick filtering 75 | * [jqueryUI Responsive Dialog](https://github.com/corinis/jsForm/wiki/Responsive-Dialog) 76 | * [jquery.responsiveDialog.js](https://github.com/corinis/jsForm/wiki/jquery.responsiveDialog.js) Plugin which extends the default jquery.ui dialog to be more responsive 77 | 78 | 79 | # Quickstart 80 | 81 | * Start with a simple html to show you the basic usage [download sample](https://raw.github.com/corinis/jsForm/master/sample.html) [view life](http://www.gargan.org/jsform/index.jsp). 82 | : 83 | 84 | ```html 85 | 86 | 87 | 88 | 89 | 110 | 111 | 112 |

Simple Form Test

113 |
114 | Name:
115 | active
116 |
117 | 122 |
123 | Cool
124 | Hot
125 | Warm
126 |
127 | Links 128 | 131 |
132 |
133 | Additional field: 134 |
135 | 136 | 137 | 138 | ``` 139 | 140 | ## Also check out the other samples: 141 | 142 | [view life demos](http://www.gargan.org/jsform/index.jsp) 143 | 144 | * [complex json with collections within collections](https://raw.github.com/corinis/jsForm/master/complex-sample.html) 145 | * [complex json with collections, sortable and editable](https://raw.github.com/corinis/jsForm/master/sortable-editable-sample.html) 146 | * [select from a given list of objects](https://raw.github.com/corinis/jsForm/master/sample-multiselect.html) 147 | * [show elements based on conditions within the data](https://raw.github.com/corinis/jsForm/master/sample-conditional.html) 148 | * [use the tagit control to select from a list of objects](https://raw.github.com/corinis/jsForm/master/autocomplete-form-tagit.html) 149 | 150 | -------------------------------------------------------------------------------- /autocomplete-form-tagit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 95 | 96 | 97 |

Autocomplete form with tag-it control

98 |
99 | Name:
100 | active
101 |
102 |
103 | 104 |
105 | 106 | 107 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-jsform", 3 | "version": "1.6.0", 4 | "homepage": "https://github.com/corinis/jsForm", 5 | "authors": [ 6 | "Niko " 7 | ], 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/corinis/jsForm.git" 11 | }, 12 | "bugs": "https://github.com/corinis/jsForm/issues", 13 | "description": "jQuery based form library that allows you to handle data within a js object (i.e. JSON) with html forms.\nYou can modify all fields/properties by simply creating a html form with the correct naming schema (MVVM Pattern).\n\nThe main features of this library are:\n Full standard html with data available in a js object\n Update an existing js object with changes done within a form\n Fully internationalizable with number format, currency and date formatting\n Provide basic functions for formatting (i.e. date/time, money) using html markup\n Provide form validation functionality\n handle collections (arrays) with subobjects\n provides helper methods to handle array manipulation (add new entry/remove an entry, sorting) using only html markup\n Can be used in connection with an autocomplete function to add new array objects", 14 | "main": [ 15 | "js/jquery.jsForm.min.js", 16 | "lib/jquery.format-1.3.js" 17 | ], 18 | "keywords": [ 19 | "jquery", 20 | "form", 21 | "json", 22 | "mvvm", 23 | "form2json", 24 | "json2form", 25 | "ajax" 26 | ], 27 | "license": "MIT", 28 | "dependencies": { 29 | "jquery": ">=1.9" 30 | }, 31 | "ignore": [ 32 | "**/.*", 33 | "*.html", 34 | "*.txt", 35 | "*.json", 36 | "*.md", 37 | "Gruntfile.js", 38 | "node_modules", 39 | "bower_components", 40 | "test", 41 | "src" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /complex-sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 49 | 50 | 51 |

Multi level array testform

52 |
53 | Name:
54 | active
55 | 60 |
61 |
62 | Group: groups.name 63 |
    64 |
  • 65 |
66 | 67 |
68 |
69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /controls/dialog.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /// 3 | /// 4 | /// 5 | /** 6 | * Documentation:(https://project.corinis.com/docs/pages/viewpage.action?pageId=99615166) 7 | * 8 | * Dialog Helper Class. 9 | * this works in combination with jqueryUI dialog and Connection Utils 10 | * to provide a generic CRUD dialog functionality 11 | * 12 | * This supports basic Dialogs and sub-dialogs. 13 | * 14 | * uses i18n to define button labels. 15 | * Use with following js: 16 | * ```js 17 | * subDialog = $("#subDialogId"); 18 | * Dialog.init($detail, { 19 | * service: "InfoService", 20 | * saveMethod: "persist", 21 | * create: { action: "#addMenuBtn", data: {} } 22 | * update: { action: "#changeMenuBtn", data: ()=>{ return selectedItem; } } 23 | * view: { action: "#changeMenuBtn", data: ()=>{ return selectedItem; } } 24 | * subDialogs: [{ 25 | * dlg: subDialog, 26 | * action: ".rawDetail" 27 | * }] 28 | * }); 29 | * ``` 30 | * Sample HTML: 31 | * ``` 32 | * 41 | * 47 | * ``` 48 | * Events 49 | * You can use the default dialog events to add extra actions: 50 | * ``` 51 | * $dlg.on("dialogopen", function(){ alert("just opened a dialog"); }); 52 | * $dlg.on("success", function(){ alert("just successfully saved"); }); 53 | * 54 | * ``` 55 | */ 56 | var Dialog; 57 | (function (Dialog) { 58 | class DialogConfig { 59 | constructor() { 60 | this.width = 600; 61 | this.responsive = 800; 62 | this.modal = true; 63 | } 64 | } 65 | class ActionConfig { 66 | } 67 | class SubDialogConfig { 68 | constructor() { 69 | this.width = 400; 70 | } 71 | } 72 | /** 73 | * Initializes the dialog overlay with the given ID and options. 74 | * 75 | * @param {any} id - the ID of the dialog 76 | * @param {object} options - optional settings for the dialog 77 | * @return {undefined} 78 | */ 79 | function init(id, options, replaceButton = false) { 80 | if (typeof Panel !== "undefined") { 81 | console.log("Using panel init ", id, options); 82 | Panel.init(id, options); 83 | return; 84 | } 85 | if (replaceButton) { 86 | setTimeout(() => { 87 | console.log("Replace button pane..."); 88 | replaceDefaultButtonPane($dlg); 89 | }, 100); 90 | } 91 | const $dlg = $(id); 92 | if (!$dlg.length) { 93 | console.log("Unable to find dialog: " + id, new Error().stack); 94 | return; 95 | } 96 | if (!options) { 97 | options = new DialogConfig(); 98 | } 99 | if ($dlg.attr("data-modal") === "false") { 100 | options.modal = false; 101 | } 102 | else { 103 | options.modal = true; 104 | } 105 | if ($dlg.attr("data-icon")) { 106 | options.icon = $dlg.attr("data-icon"); 107 | } 108 | if ($dlg.attr("data-color")) { 109 | options.color = $dlg.attr("data-color"); 110 | } 111 | if ($dlg.attr("data-width")) { 112 | options.width = Number($dlg.attr("data-width")); 113 | } 114 | if ($dlg.attr("data-responsive")) { 115 | options.responsive = Number($dlg.attr("data-responsive")); 116 | } 117 | if ($dlg.attr("data-buttonsave")) { 118 | options.buttonSave = $dlg.attr("data-buttonsave") === "true"; 119 | } 120 | /** 121 | * Save the form (and close) 122 | */ 123 | let save = function (close) { 124 | if ((options === null || options === void 0 ? void 0 : options.saveMethod) && !$dlg.hasClass("view")) { 125 | let data = $dlg.jsForm("get"); 126 | if ($dlg.data().onData) { 127 | data = $dlg.data().onData(data); 128 | } 129 | if (!data) { 130 | alert(i18n.dialog_validation_notOk); 131 | return; 132 | } 133 | if (typeof options.saveMethod === "function") { 134 | options.saveMethod(data, function (data) { 135 | let postResult = null; 136 | if ($dlg.data().onDone) { 137 | postResult = $dlg.data().onDone(data); 138 | } 139 | else if (options.onDone) { 140 | postResult = options.onDone(data); 141 | } 142 | $dlg.trigger("success", [data]); 143 | if (postResult === false || !close) { 144 | $dlg.jsForm("fill", data); 145 | return; 146 | } 147 | $dlg.dialog("close"); 148 | }); 149 | } 150 | else { 151 | Core.conn.execute(options.service, options.saveMethod, [data]).then(function (data) { 152 | let postResult = null; 153 | if ($dlg.data().onDone) { 154 | postResult = $dlg.data().onDone(data); 155 | } 156 | else if (options.onDone) { 157 | postResult = options.onDone(data); 158 | } 159 | $dlg.trigger("success", [data]); 160 | if (postResult === false || !close) { 161 | $dlg.jsForm("fill", data); 162 | return; 163 | } 164 | $dlg.dialog("close"); 165 | }); 166 | } 167 | } 168 | else if ($dlg.data().onDone) { 169 | let data = $dlg.jsForm("get"); 170 | // prepare data (if required) 171 | if ($dlg.data().onData) { 172 | data = $dlg.data().onData(data); 173 | } 174 | // call the onDone function 175 | let res = $dlg.data().onDone(data, (data) => { 176 | if (close || !data) { 177 | $dlg.dialog("close"); 178 | } 179 | else { 180 | $dlg.jsForm("fill", data); 181 | } 182 | }); 183 | if (res === false) { 184 | alert(i18n.dialog_validation_notOk); 185 | return; 186 | } 187 | else if (res === true) { 188 | // keep open, wait for callback 189 | } 190 | else { 191 | $dlg.dialog("close"); 192 | } 193 | } 194 | else { 195 | $dlg.dialog("close"); 196 | } 197 | }; 198 | // action integration: open dialog 199 | if (options) { 200 | if (options.create) { 201 | $(options.create.action).click(() => { 202 | if ($(this).hasClass("disabled")) { 203 | return; 204 | } 205 | $dlg.removeClass("view"); 206 | Dialog.open($dlg, options.create.data, options.create.onDone, options.create.onData); 207 | }); 208 | } 209 | if (options.update) { 210 | $(options.update.action).click(() => { 211 | if ($(this).hasClass("disabled")) { 212 | return; 213 | } 214 | $dlg.removeClass("view"); 215 | Dialog.open($dlg, options.update.data, options.update.onDone, options.update.onData); 216 | }); 217 | } 218 | if (options.view) { 219 | $(options.view.action).click(() => { 220 | if ($(this).hasClass("disabled")) { 221 | return; 222 | } 223 | $dlg.addClass("view"); 224 | Dialog.open($dlg, options.view.data, options.view.onDone, options.view.onData); 225 | }); 226 | } 227 | } 228 | let buttons = []; 229 | if (options.buttonSave) { 230 | buttons.push({ 231 | 'class': "ui-button-primary", 232 | text: i18n.dialog_save, 233 | responsive: { 234 | html: '', 235 | position: 1 236 | }, 237 | click: function () { 238 | save(false); 239 | } 240 | }); 241 | } 242 | buttons.push({ 243 | 'class': "ui-button-primary", 244 | text: i18n.dialog_ok, 245 | responsive: { 246 | html: '', 247 | position: 1 248 | }, 249 | click: function () { 250 | save(true); 251 | } 252 | }); 253 | buttons.push({ 254 | text: i18n.dialog_cancel, 255 | click: function () { 256 | $dlg.dialog("close"); 257 | } 258 | }); 259 | // default buttons 260 | if (options.buttons) { 261 | if (typeof options.buttons === "function") 262 | buttons = options.buttons($dlg, buttons); 263 | else 264 | buttons = options.buttons; 265 | } 266 | // fill default class 267 | if ((buttons === null || buttons === void 0 ? void 0 : buttons.length) > 1) { 268 | buttons.forEach(item => { 269 | if (!item.class && item.color) { 270 | item.class = 'btn btn-' + item.color; 271 | } 272 | }); 273 | } 274 | // check if overlay exists 275 | if (options.overlay && $(options.overlay).length === 0) { 276 | // remove overlay (not found) 277 | options.overlay = null; 278 | } 279 | $dlg.dialog({ 280 | autoOpen: false, 281 | width: options.width || 600, 282 | modal: options.modal, 283 | titleIcon: { 284 | background: options.color, 285 | icon: options.icon 286 | }, 287 | responsive: { 288 | overlay: options.overlay, 289 | limit: options.responsive || options.width || 600, 290 | left: { 291 | 'class': 'bg-icon active ' + options.color, 292 | text: '  ', 293 | click: function () { 294 | // reset changed fields 295 | if ($(this).jsForm) { 296 | $(this).jsForm("resetChanged"); 297 | } 298 | $(this).dialog("close"); 299 | } 300 | }, 301 | center: { 'class': 'has-icon' }, 302 | right: true 303 | }, 304 | buttons: buttons 305 | }); 306 | if (options.subDialogs) { 307 | options.subDialogs.forEach((curDlg, index, array) => { 308 | $(curDlg.action, $dlg).click(function () { 309 | if ($(this).hasClass("disabled")) { 310 | return; 311 | } 312 | curDlg.dlg.dialog("open"); 313 | curDlg.dlg.data().updateData = $(this).closest(".POJO").data(); 314 | curDlg.dlg.jsForm("fill", $(this).closest(".POJO").data().pojo); 315 | }); 316 | let saveDlg = function () { 317 | let data = curDlg.dlg.jsForm("get"); 318 | if (!data) { 319 | alert(i18n.dialog_validation_notOk); 320 | return; 321 | } 322 | // update original object 323 | curDlg.dlg.data().updateData.pojo = data; 324 | let updated = $dlg.jsForm("get"); 325 | $dlg.jsForm("fill", updated); 326 | }; 327 | curDlg.dlg.dialog({ 328 | autoOpen: false, 329 | width: curDlg.width || 400, 330 | modal: true, 331 | buttons: [ 332 | { 333 | 'class': "ui-button-primary", 334 | text: i18n.dialog_ok, 335 | click: function () { 336 | saveDlg(); 337 | curDlg.dlg.dialog("close"); 338 | } 339 | }, 340 | { 341 | text: i18n.dialog_cancel, 342 | click: function () { 343 | curDlg.dlg.dialog("close"); 344 | } 345 | } 346 | ] 347 | }); 348 | curDlg.dlg.jsForm(); 349 | }); 350 | } 351 | $dlg.jsForm(); 352 | let $form = $dlg.find("form"); 353 | if ($form) { 354 | $form.submit(function (ev) { 355 | ev.preventDefault(); 356 | save(false); 357 | }); 358 | } 359 | /** 360 | * prepare bootstrap tabs for js trigger 361 | * Fix for bootstrap bug 362 | */ 363 | $dlg.find("a.nav-link").each(function (idx, ele) { 364 | let tabTrigger = new bootstrap.Tab(ele); 365 | $(ele).on('click', function (event) { 366 | event.preventDefault(); 367 | tabTrigger.show(); 368 | }); 369 | }); 370 | } 371 | Dialog.init = init; 372 | /** 373 | * Opens a dialog with the specified ID, populates it with data, and sets up event handlers. 374 | * 375 | * @param {string} id - The ID of the dialog element. 376 | * @param {object} data - The data to populate the dialog with. 377 | * @param {Function} done - The callback function to execute when the dialog is closed. 378 | * @param {Function} onData - The callback function to execute when new data is received. 379 | */ 380 | function open(id, data, done, onData) { 381 | if (typeof Panel !== "undefined") { 382 | Panel.open(id, data, done, onData); 383 | return; 384 | } 385 | if (!data) { 386 | data = {}; 387 | } 388 | // save the callback 389 | const $dlg = $(id); 390 | $dlg.data().onDone = done; 391 | $dlg.data().onData = onData; 392 | // toggle view/edit mode 393 | $dlg.jsForm("preventEditing", false); 394 | if (typeof data === "function") { 395 | data((retrieveData) => { 396 | $dlg.data().pojo = retrieveData; 397 | $dlg.jsForm("fill", retrieveData); 398 | if ($dlg.hasClass("view")) { 399 | $dlg.jsForm("preventEditing", true); 400 | } 401 | $dlg.dialog("open"); 402 | }); 403 | } 404 | else { 405 | if (!data) { 406 | data = {}; 407 | } 408 | $dlg.data().pojo = data; 409 | $dlg.jsForm("fill", data); 410 | if ($dlg.hasClass("view")) { 411 | $dlg.jsForm("preventEditing", true); 412 | } 413 | $dlg.dialog("open"); 414 | } 415 | } 416 | Dialog.open = open; 417 | /** 418 | * Closes the overlay or modal identified by the given ID. 419 | * 420 | * @param {string} id - The ID of the overlay or modal to be closed. 421 | */ 422 | function close(id) { 423 | if (typeof Panel !== "undefined") { 424 | Panel.close(id); 425 | return; 426 | } 427 | const $dlg = $(id); 428 | $dlg.dialog("close"); 429 | } 430 | Dialog.close = close; 431 | /** 432 | * Helper functions 433 | */ 434 | /** 435 | * Replace default buttonpane if button div or/and button dropdown exists 436 | * @param dlg Dialog object contains HTMLDivElement 437 | */ 438 | function replaceDefaultButtonPane(dlg) { 439 | if ($(dlg).find('.dialog-buttons').length > 0 || $(dlg).find('.dropdown').length > 0) { 440 | if ($(dlg).parent().find('.ui-dialog-buttonpane').length > 0) { 441 | $(dlg).parent().find('.ui-dialog-buttonpane').empty(); 442 | if ($(dlg).find('.dialog-buttons').length === 1) { 443 | $(dlg).parent().find('.ui-dialog-buttonpane').append($(dlg).find('.dialog-buttons')); 444 | dispatchButtonEvents($(dlg).parent().find('.dialog-buttons'), dlg); 445 | } 446 | } 447 | else { 448 | $(dlg).parent().append('
'); 449 | replaceDefaultButtonPane(dlg); 450 | } 451 | } 452 | } 453 | /** 454 | * Dispatch button events for the given button container and dialog. 455 | * 456 | * @param {HTMLElement} buttonContainer - The container element that holds the buttons. 457 | * @param {HTMLElement} dlg - The dialog element. 458 | */ 459 | function dispatchButtonEvents(buttonContainer, dlg) { 460 | // Add click handler for event dispatching to every single element 461 | $(buttonContainer).children().each(function (index, element) { 462 | var _a; 463 | // For dropdown buttons set correct buttoncontainer 464 | if (element.nodeName === 'DIV') { 465 | dispatchButtonEvents($(dlg).parent().find('.dropdown-menu'), dlg); 466 | } 467 | // Check if button has data-event, dispatch event if element is not disabled 468 | if (element.hasAttribute('data-event') || ((_a = element === null || element === void 0 ? void 0 : element.children[0]) === null || _a === void 0 ? void 0 : _a.hasAttribute('data-event'))) { 469 | let el = element; // button or anchor 470 | if (!element.hasAttribute('data-event')) { 471 | el = element.children[0]; 472 | } 473 | $(el).on('click', function () { 474 | if (this.classList.contains('disabled')) 475 | return; 476 | let data = null; 477 | if (this.classList.contains('data')) { 478 | data = $('.detail').jsForm('get'); 479 | } 480 | //console.log('----- Dispatched event', $(this).data().event); 481 | dlg.trigger($(this).data().event, [dlg, data]); 482 | }); 483 | } 484 | }); 485 | } 486 | })(Dialog || (Dialog = {})); 487 | -------------------------------------------------------------------------------- /controls/dialog.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | /** 6 | * Documentation:(https://project.corinis.com/docs/pages/viewpage.action?pageId=99615166) 7 | * 8 | * Dialog Helper Class. 9 | * this works in combination with jqueryUI dialog and Connection Utils 10 | * to provide a generic CRUD dialog functionality 11 | * 12 | * This supports basic Dialogs and sub-dialogs. 13 | * 14 | * uses i18n to define button labels. 15 | * Use with following js: 16 | * ```js 17 | * subDialog = $("#subDialogId"); 18 | * Dialog.init($detail, { 19 | * service: "InfoService", 20 | * saveMethod: "persist", 21 | * create: { action: "#addMenuBtn", data: {} } 22 | * update: { action: "#changeMenuBtn", data: ()=>{ return selectedItem; } } 23 | * view: { action: "#changeMenuBtn", data: ()=>{ return selectedItem; } } 24 | * subDialogs: [{ 25 | * dlg: subDialog, 26 | * action: ".rawDetail" 27 | * }] 28 | * }); 29 | * ``` 30 | * Sample HTML: 31 | * ``` 32 | * 41 | * 47 | * ``` 48 | * Events 49 | * You can use the default dialog events to add extra actions: 50 | * ``` 51 | * $dlg.on("dialogopen", function(){ alert("just opened a dialog"); }); 52 | * $dlg.on("success", function(){ alert("just successfully saved"); }); 53 | * 54 | * ``` 55 | */ 56 | namespace Dialog { 57 | 58 | class DialogConfig { 59 | service?: string; 60 | saveMethod?: string | ((data: any, any) => void); 61 | // have an extra save button not only ok (=save+close) 62 | buttonSave? : boolean; 63 | buttons? : Array | (($detail: JQuery, buttons: Array)=>any); 64 | create?: ActionConfig; 65 | update?: ActionConfig; 66 | view?: ActionConfig; 67 | /** show the menu floating (true) or in the header(false) */ 68 | floatingMenu: false; 69 | onDone?:((data:any ) => boolean); 70 | icon?: string; 71 | color?: string; 72 | width?: number = 600; 73 | responsive?: number = 800; 74 | modal: boolean = true; 75 | subDialogs?: Array; 76 | overlay?: JQuery; 77 | } 78 | 79 | class ActionConfig { 80 | action: string; 81 | data: any; 82 | /** 83 | * before data is procesed 84 | */ 85 | onData?:((data: any) => any); 86 | /** 87 | * after data is processed 88 | */ 89 | onDone?:((data: any) => boolean); 90 | } 91 | 92 | class SubDialogConfig { 93 | action: string; 94 | dlg?: JQuery; 95 | width?: number = 400; 96 | } 97 | 98 | /** 99 | * Initializes the dialog overlay with the given ID and options. 100 | * 101 | * @param {any} id - the ID of the dialog 102 | * @param {object} options - optional settings for the dialog 103 | * @return {undefined} 104 | */ 105 | export function init(id: string | JQuery, options?: DialogConfig, replaceButton:boolean = false) { 106 | if(typeof Panel !== "undefined") { 107 | console.log("Using panel init ", id, options); 108 | Panel.init(id, options); 109 | return; 110 | } 111 | 112 | if(replaceButton) { 113 | setTimeout(() => { 114 | console.log("Replace button pane..."); 115 | replaceDefaultButtonPane($dlg); 116 | }, 100); 117 | } 118 | 119 | const $dlg = $(id); 120 | 121 | if(!$dlg.length) { 122 | console.log("Unable to find dialog: " + id, new Error().stack); 123 | return; 124 | } 125 | 126 | if(!options) { 127 | options = new DialogConfig (); 128 | } 129 | 130 | if($dlg.attr("data-modal") === "false") { 131 | options.modal = false; 132 | } else { 133 | options.modal = true; 134 | } 135 | 136 | if($dlg.attr("data-icon")) { 137 | options.icon = $dlg.attr("data-icon"); 138 | } 139 | if($dlg.attr("data-color")) { 140 | options.color = $dlg.attr("data-color"); 141 | } 142 | if($dlg.attr("data-width")) { 143 | options.width = Number($dlg.attr("data-width")); 144 | } 145 | if($dlg.attr("data-responsive")) { 146 | options.responsive = Number($dlg.attr("data-responsive")); 147 | } 148 | if($dlg.attr("data-buttonsave")) { 149 | options.buttonSave = $dlg.attr("data-buttonsave") === "true"; 150 | } 151 | 152 | /** 153 | * Save the form (and close) 154 | */ 155 | let save = function(close:boolean) { 156 | if(options?.saveMethod && !$dlg.hasClass("view")) { 157 | let data = $dlg.jsForm("get"); 158 | if($dlg.data().onData) { 159 | data = $dlg.data().onData(data); 160 | } 161 | if(!data) { 162 | alert(i18n.dialog_validation_notOk); 163 | return; 164 | } 165 | 166 | if(typeof options.saveMethod === "function") { 167 | options.saveMethod(data, function(data){ 168 | let postResult = null; 169 | if($dlg.data().onDone) { 170 | postResult = $dlg.data().onDone(data); 171 | } 172 | else if(options.onDone) { 173 | postResult = options.onDone(data); 174 | } 175 | $dlg.trigger("success", [data]); 176 | if(postResult === false || !close) { 177 | $dlg.jsForm("fill", data); 178 | return; 179 | } 180 | $dlg.dialog("close"); 181 | }); 182 | } else { 183 | Core.conn.execute(options.service, options.saveMethod, [data]).then(function(data){ 184 | let postResult = null; 185 | if($dlg.data().onDone) { 186 | postResult = $dlg.data().onDone(data); 187 | } 188 | else if(options.onDone) { 189 | postResult = options.onDone(data); 190 | } 191 | $dlg.trigger("success", [data]); 192 | 193 | if(postResult === false || !close) { 194 | $dlg.jsForm("fill", data); 195 | return; 196 | } 197 | $dlg.dialog("close"); 198 | }); 199 | } 200 | } else if($dlg.data().onDone) { 201 | let data = $dlg.jsForm("get"); 202 | // prepare data (if required) 203 | if($dlg.data().onData) { 204 | data = $dlg.data().onData(data); 205 | } 206 | // call the onDone function 207 | let res = $dlg.data().onDone(data, (data)=>{ 208 | if(close || !data) { 209 | $dlg.dialog("close"); 210 | } else { 211 | $dlg.jsForm("fill", data); 212 | } 213 | }); 214 | if(res === false) { 215 | alert(i18n.dialog_validation_notOk); 216 | return; 217 | } else if(res === true) { 218 | // keep open, wait for callback 219 | } else { 220 | $dlg.dialog("close"); 221 | } 222 | 223 | } else{ 224 | $dlg.dialog("close"); 225 | } 226 | }; 227 | 228 | // action integration: open dialog 229 | if(options) { 230 | if(options.create) { 231 | $(options.create.action).click(()=>{ 232 | if($(this).hasClass("disabled")) { 233 | return; 234 | } 235 | $dlg.removeClass("view"); 236 | Dialog.open($dlg, options.create.data, options.create.onDone, options.create.onData); 237 | }); 238 | } 239 | if(options.update) { 240 | $(options.update.action).click(()=>{ 241 | if($(this).hasClass("disabled")) { 242 | return; 243 | } 244 | $dlg.removeClass("view"); 245 | Dialog.open($dlg, options.update.data, options.update.onDone, options.update.onData); 246 | }); 247 | } 248 | if(options.view) { 249 | $(options.view.action).click(()=>{ 250 | if($(this).hasClass("disabled")) { 251 | return; 252 | } 253 | $dlg.addClass("view"); 254 | Dialog.open($dlg, options.view.data, options.view.onDone, options.view.onData); 255 | }); 256 | } 257 | } 258 | 259 | let buttons = [] 260 | 261 | 262 | if(options.buttonSave) { 263 | buttons.push({ 264 | 'class': "ui-button-primary", 265 | text: i18n.dialog_save, 266 | responsive: { 267 | html: '', 268 | position: 1 269 | }, 270 | click: function() { 271 | save(false); 272 | } 273 | }); 274 | } 275 | 276 | buttons.push({ 277 | 'class': "ui-button-primary", 278 | text: i18n.dialog_ok, 279 | responsive: { 280 | html: '', 281 | position: 1 282 | }, 283 | click: function() { 284 | save(true); 285 | } 286 | }); 287 | 288 | buttons.push({ 289 | text: i18n.dialog_cancel, 290 | click: function() { 291 | $dlg.dialog("close"); 292 | } 293 | }); 294 | 295 | 296 | // default buttons 297 | if(options.buttons) { 298 | if(typeof options.buttons === "function") 299 | buttons = options.buttons($dlg, buttons); 300 | else 301 | buttons = options.buttons; 302 | } 303 | 304 | // fill default class 305 | if(buttons?.length > 1) { 306 | buttons.forEach(item => { 307 | if(!item.class && item.color) { 308 | item.class = 'btn btn-' + item.color; 309 | } 310 | }); 311 | } 312 | 313 | 314 | // check if overlay exists 315 | if(options.overlay && $(options.overlay).length === 0) { 316 | // remove overlay (not found) 317 | options.overlay = null; 318 | } 319 | 320 | $dlg.dialog({ 321 | autoOpen: false, 322 | width: options.width || 600, 323 | modal: options.modal, 324 | titleIcon: { 325 | background: options.color, 326 | icon: options.icon 327 | }, 328 | responsive: { 329 | overlay: options.overlay, 330 | limit: options.responsive || options.width || 600, // become responsive and full screen when screen width is <= 600 pixel 331 | left: { 332 | 'class': 'bg-icon active ' + options.color, 333 | text: '  ', 334 | click: function() { 335 | // reset changed fields 336 | if($(this).jsForm) { 337 | $(this).jsForm("resetChanged"); 338 | } 339 | $(this).dialog("close"); 340 | } 341 | }, 342 | center: { 'class': 'has-icon' }, 343 | right: true 344 | }, 345 | buttons: buttons 346 | 347 | }); 348 | 349 | if(options.subDialogs) { 350 | options.subDialogs.forEach((curDlg, index, array) => { 351 | $(curDlg.action, $dlg).click(function(){ 352 | if($(this).hasClass("disabled")) { 353 | return; 354 | } 355 | curDlg.dlg.dialog("open"); 356 | curDlg.dlg.data().updateData = $(this).closest(".POJO").data(); 357 | curDlg.dlg.jsForm("fill", $(this).closest(".POJO").data().pojo); 358 | }); 359 | 360 | let saveDlg = function() { 361 | let data = curDlg.dlg.jsForm("get"); 362 | if(!data) { 363 | alert(i18n.dialog_validation_notOk); 364 | return; 365 | } 366 | // update original object 367 | curDlg.dlg.data().updateData.pojo = data; 368 | let updated = $dlg.jsForm("get"); 369 | $dlg.jsForm("fill", updated); 370 | }; 371 | 372 | curDlg.dlg.dialog({ 373 | autoOpen: false, 374 | width: curDlg.width || 400, 375 | modal: true, 376 | buttons: 377 | [ 378 | { 379 | 'class': "ui-button-primary", 380 | text: i18n.dialog_ok, 381 | click: function() { 382 | saveDlg(); 383 | curDlg.dlg.dialog("close"); 384 | } 385 | }, 386 | { 387 | text: i18n.dialog_cancel, 388 | click: function() { 389 | curDlg.dlg.dialog("close"); 390 | } 391 | } 392 | ] 393 | }); 394 | 395 | curDlg.dlg.jsForm(); 396 | }); 397 | } 398 | 399 | $dlg.jsForm(); 400 | let $form = $dlg.find("form"); 401 | if($form) { 402 | $form.submit(function(ev){ 403 | ev.preventDefault(); 404 | save(false); 405 | }); 406 | } 407 | 408 | /** 409 | * prepare bootstrap tabs for js trigger 410 | * Fix for bootstrap bug 411 | */ 412 | $dlg.find("a.nav-link").each(function (idx, ele) { 413 | let tabTrigger = new bootstrap.Tab(ele); 414 | $(ele).on('click', function (event) { 415 | event.preventDefault(); 416 | tabTrigger.show(); 417 | }); 418 | }); 419 | 420 | } 421 | 422 | /** 423 | * Opens a dialog with the specified ID, populates it with data, and sets up event handlers. 424 | * 425 | * @param {string} id - The ID of the dialog element. 426 | * @param {object} data - The data to populate the dialog with. 427 | * @param {Function} done - The callback function to execute when the dialog is closed. 428 | * @param {Function} onData - The callback function to execute when new data is received. 429 | */ 430 | export function open(id: string | JQuery, data: any, done?: ((data: any, cb: ((data: any)=>void)) => boolean), onData?: ((data: any) => any)) { 431 | if(typeof Panel !== "undefined") { 432 | Panel.open(id, data, done, onData); 433 | return; 434 | } 435 | 436 | if (!data) { 437 | data = {}; 438 | } 439 | 440 | // save the callback 441 | const $dlg = $(id); 442 | $dlg.data().onDone = done; 443 | $dlg.data().onData = onData; 444 | 445 | // toggle view/edit mode 446 | $dlg.jsForm("preventEditing", false); 447 | 448 | if(typeof data === "function") { 449 | data((retrieveData: any)=>{ 450 | $dlg.data().pojo = retrieveData; 451 | $dlg.jsForm("fill", retrieveData); 452 | if($dlg.hasClass("view")) { 453 | $dlg.jsForm("preventEditing", true); 454 | } 455 | $dlg.dialog("open"); 456 | }); 457 | } else { 458 | if(!data) { 459 | data = {}; 460 | } 461 | 462 | $dlg.data().pojo = data; 463 | $dlg.jsForm("fill", data); 464 | if($dlg.hasClass("view")) { 465 | $dlg.jsForm("preventEditing", true); 466 | } 467 | $dlg.dialog("open"); 468 | } 469 | } 470 | 471 | /** 472 | * Closes the overlay or modal identified by the given ID. 473 | * 474 | * @param {string} id - The ID of the overlay or modal to be closed. 475 | */ 476 | export function close(id: string | JQuery): void { 477 | if(typeof Panel !== "undefined") { 478 | Panel.close(id); 479 | return; 480 | } 481 | const $dlg = $(id); 482 | $dlg.dialog("close"); 483 | } 484 | 485 | 486 | /** 487 | * Helper functions 488 | */ 489 | 490 | 491 | /** 492 | * Replace default buttonpane if button div or/and button dropdown exists 493 | * @param dlg Dialog object contains HTMLDivElement 494 | */ 495 | function replaceDefaultButtonPane(dlg: object) { 496 | if ($(dlg).find('.dialog-buttons').length > 0 || $(dlg).find('.dropdown').length > 0) { 497 | if ($(dlg).parent().find('.ui-dialog-buttonpane').length > 0) { 498 | $(dlg).parent().find('.ui-dialog-buttonpane').empty(); 499 | 500 | if ($(dlg).find('.dialog-buttons').length === 1) { 501 | $(dlg).parent().find('.ui-dialog-buttonpane').append($(dlg).find('.dialog-buttons')); 502 | dispatchButtonEvents($(dlg).parent().find('.dialog-buttons') , dlg); 503 | } 504 | } 505 | else { 506 | $(dlg).parent().append('
'); 507 | replaceDefaultButtonPane(dlg); 508 | } 509 | } 510 | } 511 | 512 | /** 513 | * Dispatch button events for the given button container and dialog. 514 | * 515 | * @param {HTMLElement} buttonContainer - The container element that holds the buttons. 516 | * @param {HTMLElement} dlg - The dialog element. 517 | */ 518 | function dispatchButtonEvents(buttonContainer: JQuery, dlg: JQuery): void { 519 | // Add click handler for event dispatching to every single element 520 | $(buttonContainer).children().each(function(index, element) { 521 | 522 | // For dropdown buttons set correct buttoncontainer 523 | if(element.nodeName === 'DIV') { 524 | dispatchButtonEvents($(dlg).parent().find('.dropdown-menu'), dlg); 525 | } 526 | 527 | // Check if button has data-event, dispatch event if element is not disabled 528 | if (element.hasAttribute('data-event') || element?.children[0]?.hasAttribute('data-event')) { 529 | let el: any = element; // button or anchor 530 | if (!element.hasAttribute('data-event')) { 531 | el= element.children[0]; 532 | } 533 | 534 | $(el).on('click', function() { 535 | if (this.classList.contains('disabled')) 536 | return; 537 | 538 | let data = null; 539 | if (this.classList.contains('data')) { 540 | data = $('.detail').jsForm('get'); 541 | } 542 | //console.log('----- Dispatched event', $(this).data().event); 543 | dlg.trigger($(this).data().event, [dlg, data] ); 544 | }); 545 | } 546 | }); 547 | } 548 | 549 | } -------------------------------------------------------------------------------- /controls/jquery.jsform.bsfileUpload.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | 3 | let uploadModal = new bootstrap.Modal(document.getElementById('uploadDlg'), {}); 4 | 5 | $(".jsfileupload").on("click", function(){ 6 | let options = { 7 | title: $(this).data().title || "Upload", 8 | service: $(this).data().service, 9 | method: $(this).data().method || "method", 10 | params: $(this).data().params || [], 11 | ele: $(this) 12 | } 13 | 14 | $("#uploadDlg .modal-title").html(options.title); 15 | $("#uploadDlgLoadFile").data().options = options; 16 | 17 | // reset 18 | $("#uploadDlgStatus").hide(); 19 | $("#uploadDlgProgress").hide(); 20 | $("#uploadDlgBtn").show(); 21 | 22 | uploadModal.show(); 23 | }); 24 | 25 | $("#uploadDlgLoadFile").on("change", function(){ 26 | // read data 27 | $("#uploadDlgProgress").show(); 28 | $("#uploadDlgBtn").hide(); 29 | 30 | let options = $("#uploadDlgLoadFile").data().options; 31 | 32 | let done = 0; 33 | 34 | let allFiles = $('#uploadDlgLoadFile')[0].files 35 | 36 | let result = []; 37 | 38 | console.log("files", allFiles); 39 | for(let i = 0; i < allFiles.length; i++) 40 | { 41 | let files = allFiles[i]; 42 | let fd = new FormData(); 43 | let transData = { 44 | service: options.service, 45 | method: options.method, 46 | param: options.params, 47 | }; 48 | fd.append('data', JSON.stringify(transData)); 49 | fd.append('files[]',files); 50 | 51 | // upload 52 | $.ajax({ 53 | url: Core.conn.SERVICE_URL ? Core.conn.SERVICE_URL : '/service', 54 | type: 'post', 55 | data: fd, 56 | contentType: false, 57 | processData: false 58 | }).done(function(data, textStatus, _xhr) { 59 | done++; 60 | data = data.length > 0 ? data[0] : data; 61 | 62 | if(data.success === false || data.error) { 63 | $("#uploadDlgStatus").html("Error: " + data.longMessage); 64 | options.ele.trigger("error", [data.error]); 65 | return; 66 | } 67 | 68 | result.push(data.data); 69 | 70 | if(done == $('#uploadDlgLoadFile')[0].files.length) { 71 | options.ele.trigger("success", [result]); 72 | uploadModal.hide(); 73 | } 74 | }).fail(function(_xhr, textStatus, error) { 75 | done++; 76 | console.log("error", textStatus, error); 77 | $("#uploadDlgStatus").html("Error: " + textStatus); 78 | options.ele.trigger("error", [error]); 79 | }); 80 | } 81 | }); 82 | 83 | }); -------------------------------------------------------------------------------- /controls/jquery.responsiveDialog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Initialize a dialog for a detail window 3 | * 4 | * this is basically just a a wrapper for the jquery ui dialog. 5 | * an additional feature is, that on small screens (i.e. mobile) 6 | * it will instantiate a sliding panel (from the left) filling the whole screen. 7 | * - close will move to the left and turn into a back arrow 8 | * - other buttons will move to the top right (i.e. save) but with an icon 9 | * 10 | * You can force responsive dialog by setting $(document).data().mobile to true 11 | * # Usage 12 | * 13 | * Init the dialog as normal and add responsive configuration options. 14 | * Note: this works only when creating the dialog, not when setting the 15 | * options later: 16 | * 17 | * 18 | $detail.dialog({ 19 | width: 600, 20 | height: 450, 21 | overlay: $("#main"), 22 | savePosition: "uniqueId", 23 | responsive: { 24 | limit: 600, 25 | left: { 26 | 'class': 'red-style', 27 | text: ' Icon', 28 | click: function() { 29 | $(this).dialog("close"); 30 | } 31 | }, 32 | center: { 'class': 'green-background' }, 33 | right: true 34 | }, 35 | closeOnEscape: true, 36 | buttons: [ 37 | { 38 | text: i18n.dialog_ok, 39 | responsive: { 40 | html: '', 41 | position: 1 42 | }, 43 | click: function() { 44 | alert("OK"); 45 | } 46 | }, 47 | { 48 | text: i18n.dialog_cancel, 49 | responsive: false, 50 | click: function() { 51 | $(this).dialog("close"); 52 | } 53 | } 54 | ] 55 | }; 56 | */ 57 | $.widget("ui.dialog", $.ui.dialog, { 58 | windowResize: false, 59 | fullScreen: null, 60 | overlayTitlebar: false, 61 | resizeto: null, 62 | 63 | open: function() { 64 | 65 | var that = this; 66 | 67 | // a responsive dialog 68 | if(this.options.responsive) { 69 | var isResponsive = ($(window).width() <= this.options.responsive.limit) || $(document).data().mobile || this.options.overlay; 70 | 71 | if(this.options.responsive.overlay && !isResponsive) { 72 | this.uiDialogTitlebar.show(); 73 | this.uiDialogTitlepane.hide(); 74 | this.uiDialog.removeClass("ui-corner-all").addClass("resp-fullscreen"); 75 | this._setOption("draggable", false); 76 | 77 | if($(this.options.responsive.overlay).length > 0) { 78 | this.resizeto = $(this.options.responsive.overlay); 79 | } 80 | 81 | // create the right title bar 82 | if(!this.overlayTitlebar) { 83 | // move the buttons in the header instead of the "x" 84 | var buttonPane = this.uiDialogButtonPane.detach(); 85 | buttonPane.find(".ui-dialog-buttonset").addClass("btn-group"); 86 | buttonPane.css({ 87 | position: "absolute", 88 | top: 8, 89 | right: 5 90 | }); 91 | this.uiDialogTitlebar.css({ 92 | background: "#f5f5f5" 93 | }); 94 | 95 | buttonPane.removeClass("ui-dialog-buttonpane").show(); 96 | $("button", this.uiDialogTitlebar).hide(); 97 | this.uiDialogTitlebar.append(buttonPane); 98 | this.overlayTitlebar = true; 99 | } 100 | 101 | 102 | // set full screen flag 103 | this.fullScreen = "overlay"; 104 | 105 | if(!this.windowResize) { 106 | this.windowResize = true; 107 | $(window).resize(function() { 108 | that._position(); 109 | }); 110 | } 111 | 112 | } else { 113 | 114 | // create the right title bar 115 | if(this.overlayTitlebar) { 116 | // move the buttons in the header instead of the "x" 117 | var buttonPane = this.uiDialogButtonPane.detach(); 118 | buttonPane.css({ 119 | position: "block", 120 | top: "auto", 121 | right: "auto" 122 | }); 123 | this.uiDialogTitlebar.css({ 124 | background: "white" 125 | }); 126 | 127 | buttonPane.addClass("ui-dialog-buttonpane"); 128 | $("button", this.uiDialogTitlebar).show(); 129 | this.element.append(buttonPane); 130 | this.overlayTitlebar = false; 131 | } 132 | 133 | 134 | // reset changes if it WAS fullscreen 135 | if(this.fullScreen && !isResponsive) { 136 | this.uiDialogTitlebar.show(); 137 | this.uiDialogButtonPane.show(); 138 | this.uiDialogTitlepane.hide(); 139 | $(".titleIcon", this.uiDialogTitlebar).show(); 140 | this._setOption("draggable", true); 141 | this.uiDialog.addClass("ui-corner-all").removeClass("resp-fullscreen"); 142 | this.uiDialog.css("margin", "auto"); 143 | } 144 | 145 | if(isResponsive) { 146 | // set full screen flag 147 | this.fullScreen = "full"; 148 | 149 | // hide the default panes 150 | $(".titleIcon", this.uiDialogTitlepane).hide(); 151 | this.uiDialogTitlebar.hide(); 152 | this.uiDialogButtonPane.hide(); 153 | this.uiDialogTitlepane.show(); 154 | this.uiDialog.removeClass("ui-corner-all").addClass("resp-fullscreen");; 155 | this._setOption("draggable", false); 156 | this.uiDialog.css("margin", "-2px"); // remove extra border 157 | } 158 | } 159 | 160 | } 161 | 162 | // auto save position and size 163 | if(this.options.savePosition && !this.fullScreen) { 164 | var saveKey = this.options.savePosition; 165 | // set the default position and w/h 166 | var defaultPos = localStorage ? localStorage.getItem(saveKey) : null; 167 | if(!defaultPos) { 168 | defaultPos = { 169 | width: this.options.width, 170 | height: this.options.height, 171 | position: this.options.position 172 | } 173 | } else { 174 | defaultPos = JSON.parse(defaultPos); 175 | } 176 | 177 | 178 | if(defaultPos.position) { 179 | if(!defaultPos.position.of) 180 | defaultPos.position.of = window; 181 | } else { 182 | defaultPos.position = { 183 | my: "center", at: "center", of: window 184 | }; 185 | } 186 | 187 | this.options.width = defaultPos.width; 188 | this.options.height = defaultPos.height; 189 | this.options.position = defaultPos.position; 190 | 191 | // add drag and resize handler 192 | var savePos = function(event, ui) { 193 | if(this.fullScreen) { 194 | return; 195 | } 196 | var pos = { 197 | width: $(this).dialog("option", "width"), 198 | height: $(this).dialog("option", "height"), 199 | position: { 200 | my: "left top", 201 | at: "left+" + ui.position.left + " top+" + ui.position.top 202 | } 203 | } 204 | 205 | localStorage.setItem(saveKey, JSON.stringify(pos)); 206 | }; 207 | 208 | this.options.dragStop = savePos; 209 | this.options.resizeStop = savePos; 210 | } 211 | 212 | 213 | // invoke the parent widget 214 | var wasOpen = this._isOpen; 215 | var resp = this._super(); 216 | if(wasOpen) { 217 | this._trigger( "open" ); 218 | } 219 | 220 | return resp; 221 | }, 222 | 223 | _size: function() { 224 | if(this.fullScreen === "overlay") { 225 | // Reset content sizing 226 | this.element.show().css( { 227 | width: "auto", 228 | minHeight: 0, 229 | maxHeight: "none", 230 | height: 0 231 | } ); 232 | 233 | this.element.css({ 234 | height: "100%", 235 | width: "100%", 236 | position: "absolute" 237 | }); 238 | // check the size of what we want to overlay 239 | this.uiDialog.show().css({ 240 | width: "100%", 241 | height: "100%" 242 | }); 243 | } 244 | else if(this.fullScreen === "full") { 245 | 246 | // Reset content sizing 247 | this.element.show().css( { 248 | width: "auto", 249 | minHeight: 0, 250 | maxHeight: "none", 251 | height: 0 252 | } ); 253 | 254 | var nonContentHeight = this.uiDialog.css( { 255 | height: "auto", 256 | width: "100%", 257 | } ).outerHeight(); 258 | 259 | 260 | this.element.css({ 261 | height: "100%", 262 | width: "100%", 263 | position: "absolute", 264 | top: "48px" 265 | }); 266 | 267 | // full screen 268 | this.uiDialog.show().css({ 269 | width: "100%", 270 | height: "100%" 271 | }); 272 | } else { 273 | this._super(); 274 | } 275 | }, 276 | 277 | _position: function() { 278 | if(this.fullScreen === "overlay") { 279 | // calculate the new position 280 | // two possibilities: either left - or based on width 281 | if(this.options.responsive.overlay.position) { 282 | this.uiDialog.css({ 283 | left: this.options.responsive.overlay.position().left, 284 | height: $(window).height() - this.options.responsive.overlay.position().top, 285 | width: $(window).width() - this.options.responsive.overlay.position().left, 286 | top: this.options.responsive.overlay.position().top 287 | }); 288 | } 289 | else if(this.options.responsive.overlay.left) { 290 | this.uiDialog.css({ 291 | left: this.options.responsive.overlay.left, 292 | height: $(window).height() - this.options.responsive.overlay.top, 293 | width: $(window).width() - this.options.responsive.overlay.left, 294 | top: this.options.responsive.overlay.top 295 | }); 296 | } else { 297 | this.uiDialog.css({ 298 | left: $(window).width() - this.options.width, 299 | height: $(window).height() - this.options.responsive.overlay.top, 300 | width: this.options.width, 301 | top: this.options.responsive.overlay.top 302 | }); 303 | } 304 | } 305 | else if(this.fullScreen === "full") { 306 | this.uiDialog.position({my: "left top", at: "left top", of: window}) 307 | } else { 308 | this._super(); 309 | 310 | // shrink to avoid overwriting the window size (async to allow painting of content) 311 | var $ele = this.element; 312 | setTimeout(function(){ 313 | if($ele.height() > $(window).height() - 150) { 314 | $ele.height($(window).height() - 130); 315 | } 316 | }, 30); 317 | } 318 | }, 319 | 320 | _createButtons: function() { 321 | var that = this, 322 | buttons = this.options.buttons; 323 | 324 | // now go over over buttons and see if any have a pulldown 325 | $.each( buttons, function( name, props ) { 326 | if(!props["class"]) 327 | props["class"] = "btn btn-secondary"; 328 | else if(props["class"].indexOf("ui-button-primary") !== -1) { 329 | props["class"] = props["class"] + " btn btn-primary"; 330 | } else 331 | props["class"] = props["class"] + " btn btn-secondary"; 332 | 333 | if(!props.pulldown) { 334 | return; 335 | } 336 | var cur = props; 337 | if(!cur.id) { 338 | cur.id = "pd-" + new Date().getTime() - Math.random() * 10000; 339 | } 340 | 341 | // add the pulldown 342 | props.click = function() { 343 | if(that._pulldown) { 344 | that._pulldown.item.remove(); 345 | delete(that._pulldown); 346 | // we clicked on the same 347 | if(that._pulldown.id == cur.id) 348 | return; 349 | } 350 | 351 | // create the html 352 | var container = $("
") 353 | var pd = $(""); 354 | pd.css("min-width", $("#" + cur.id).outerWidth()); 355 | container.append(pd); 356 | $.each(cur.pulldown.items, function(){ 357 | var entry = $("
  • "); 358 | entry.append("
    " + this.html + "
    "); 359 | entry.click(this.click); 360 | entry.on("close", function(){ 361 | that._pulldown.item.remove(); 362 | delete that._pulldown; 363 | }); 364 | pd.append(entry) 365 | }); 366 | $(that.uiDialog).append(container); 367 | that._pulldown = { item: container, id: cur.id}; 368 | pd.menu({ 369 | select: function(event, ui) { 370 | $(ui.item).click(); 371 | } 372 | }); 373 | container.position({ 374 | my: "left top", 375 | at: "left bottom", 376 | of: "#" + cur.id, 377 | }); 378 | 379 | // call the open 380 | if(cur.pulldown.open) { 381 | $.proxy(cur.pulldown.open, pd); 382 | } 383 | } 384 | }); 385 | 386 | 387 | this._createPaneButtons(); 388 | this._super(); 389 | }, 390 | 391 | _createPaneButtons: function() { 392 | if(!this.options.responsive || !this.uiDialogTitlepaneRight) { 393 | return; 394 | } 395 | var that = this, 396 | buttons = this.options.buttons, 397 | config = this.options.responsive; 398 | if(!config) { 399 | return; 400 | } 401 | 402 | // remove existing buttons 403 | this.uiDialogTitlepaneRight.empty(); 404 | var buttonArr =[]; 405 | $.each( buttons, function( name, props ) { 406 | if(!props.responsive) { 407 | return; 408 | } 409 | 410 | 411 | var click, buttonOptions; 412 | 413 | props = $.isFunction( props ) ? 414 | { click: props, text: name } : 415 | props; 416 | 417 | // Default to a non-submitting button 418 | props = $.extend( { type: "button" }, props ); 419 | 420 | // Change the context for the click callback to be the main element 421 | click = props.click; 422 | buttonOptions = { 423 | icon: props.responsive.icon || props.icon, 424 | iconPosition: props.iconPosition, 425 | pos: props.responsive.position || 1, 426 | html: props.responsive.html 427 | }; 428 | 429 | delete props.click; 430 | delete props.responsive; 431 | delete props.icon; 432 | delete props.iconPosition; 433 | delete props.showLabel; 434 | 435 | // sort 436 | buttonArr.push({props: props, options: buttonOptions, click:click}) 437 | } ); 438 | 439 | buttonArr.sort(function(a,b){ 440 | return a.options.pos - b.options.pos 441 | }); 442 | 443 | $.each(buttonArr, function(){ 444 | var button = this; 445 | if(button.options.html) { 446 | $(button.options.html).appendTo( that.uiDialogTitlepaneRight ) 447 | .on( "click", function() { 448 | button.click.apply( that.element[ 0 ], arguments ); 449 | } ); 450 | } else { 451 | $( "", button.props ) 452 | .btn( button.options ) 453 | .appendTo( that.uiDialogTitlepaneRight ) 454 | .on( "click", function() { 455 | button.click.apply( that.element[ 0 ], arguments ); 456 | } ); 457 | } 458 | }); 459 | }, 460 | 461 | /** 462 | * create a combined title bar/button pane for responsive layout 463 | */ 464 | _createTitlepane: function() { 465 | var that = this; 466 | var config = this.options.responsive; 467 | if(!config) { 468 | return; 469 | } 470 | 471 | this.uiDialogTitlepane = $( '
    ' ); 472 | this.uiDialogTitlepane.hide(); 473 | this.uiDialogTitlepane.addClass("ui-dialog-titlebar ui-widget-header ui-helper-clearfix ui-dialog-titlepane resp-fullscreen" ); 474 | 475 | 476 | // four parts: left-buttons - title - rightbuttons - menu 477 | this.uiDialogTitlepaneLeft = $( "
    " ); 478 | if(config.left) { 479 | if(config.left['class']) { 480 | this.uiDialogTitlepaneLeft.addClass(config.left['class']); 481 | } 482 | if(config.left.text) { 483 | this.uiDialogTitlepaneLeft.html(config.left.text); 484 | } else { 485 | this.uiDialogTitlepaneLeft.html(" "); 486 | } 487 | if(config.left.click) { 488 | this.uiDialogTitlepaneLeft.on("click", function(){ 489 | config.left.click.apply( that.element[ 0 ], arguments ); 490 | }); 491 | } 492 | } 493 | 494 | this.uiDialogTitlepaneRight = $( "
    " ); 495 | if(config.right) { 496 | if(config.right['class']) { 497 | this.uiDialogTitlepaneRight.addClass(config.right['class']); 498 | } 499 | // content function (use append) 500 | if(config.right.content) { 501 | config.right.content(this.uiDialogTitlepaneRight); 502 | } else { 503 | // create based on the buttons 504 | this._createPaneButtons(); 505 | } 506 | } 507 | 508 | this.uiDialogTitlepaneTitle = $( "" ); 509 | if(config.center) { 510 | if(config.center['class']) { 511 | this.uiDialogTitlepaneTitle.addClass(config.center['class']); 512 | } 513 | if(config.center.text) { 514 | this.uiDialogTitlepaneTitle.html(config.center.text); 515 | } else { 516 | this._title(this.uiDialogTitlepaneTitle); 517 | } 518 | if(config.center.click) { 519 | this.uiDialogTitlepaneTitle.on("click", function(){ 520 | config.center.click.apply( that.element[ 0 ], arguments ); 521 | }); 522 | } 523 | } else { 524 | this._title(this.uiDialogTitlepaneTitle, config.title); 525 | } 526 | 527 | this.uiDialogTitlepaneLeft.css({ 528 | float:"left" 529 | }); 530 | 531 | this.uiDialogTitlepane.append(this.uiDialogTitlepaneLeft); 532 | 533 | this.uiDialogTitlepane.append(this.uiDialogTitlepaneTitle); 534 | 535 | // has to be within title pane to allow click events 536 | this.uiDialogTitlepane.append(this.uiDialogTitlepaneRight); 537 | 538 | // add on top 539 | this.uiDialogTitlepane.prependTo(this.uiDialog); 540 | }, 541 | 542 | _title: function(title, xtra) { 543 | this._super(title); 544 | var that = this; 545 | // add icon 546 | var opts = this.options.titleIcon; 547 | if(opts || xtra) { 548 | title.addClass("has-icon"); 549 | var bg = opts?opts.background:''; 550 | if(!bg) { 551 | bg = ''; 552 | } 553 | 554 | title.html( 555 | ' ' + 556 | (xtra?' ':'') + 557 | '' + 558 | '' 559 | + title.html()); 560 | 561 | if(xtra && xtra.click) { 562 | title.addClass("xtra"); 563 | title.children("span").click(function(){ 564 | xtra.click.apply( that.element[ 0 ], arguments ); 565 | }); 566 | } 567 | } 568 | }, 569 | 570 | _create: function() { 571 | this._super(); 572 | this._createTitlepane(); 573 | }, 574 | 575 | _init: function() { 576 | 577 | // check/fix the options 578 | if(this.options.responsive) { 579 | if(this.options.responsive === true) { 580 | this.options.responsive = {}; 581 | } 582 | var responsiveOpts = $.extend({}, { 583 | /** 584 | * the limit when the responsive full screen dialog should appear 585 | */ 586 | limit: 1000, 587 | /** 588 | * which effect (can be just the name or the options) to use when showing/hiding the screen 589 | */ 590 | effect: { effect: "slide" } 591 | }, this.options.responsive); 592 | this.options.responsive = responsiveOpts; 593 | 594 | if(this.options.responsive.overlay && !this.options.responsive.overlay.top) { 595 | this.options.responsive.overlay.top = 121; 596 | } 597 | } 598 | 599 | return this._super(); 600 | }, 601 | 602 | close: function() { 603 | return this._super(); 604 | } 605 | }); 606 | /** 607 | * btn override: 608 | * type: [outline-][primary, *secondary*, success, danger, warning, info, light, dark] 609 | */ 610 | $.widget("corinis.btn", $.ui.button, { 611 | options: { 612 | type: "secondary" 613 | }, 614 | _create: function() { 615 | this._super(); 616 | var type = this.options.type || "secondary"; 617 | if($(this).hasClass("ui-button-primary")) 618 | type = "primary"; 619 | this._addClass("btn btn-" + type); 620 | this._removeClass("ui-corner-all ui-widget ui-button"); 621 | } 622 | }); 623 | -------------------------------------------------------------------------------- /controls/jquery.sorTable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jquery.sorTable 3 | * ----------- 4 | * Add some basic functions like sorting and selecting of rows to an existing table. 5 | * 6 | * For this to work, there needs to be a header row that controls the sorting. This is either: 7 | * - "table>thead>tr>th","table>thead>tr>td" or "table>tr[eq:0]>td" 8 | * can be specified in the "headSelect" option 9 | * the rows sorted are either "table>tbody>tr", "table>tr" or "table>tr[pos()>0]" depending on the html layout (i.e. with a thead). 10 | * the can be specified in the "bodyRowSelect" option. the cells are ALWAYS td for the body! 11 | * 12 | * Every time you call refresh the body will be selected new, so you can use this even with dynamically added tables. 13 | * 14 | * Note that the header cells must be matched by body cells in order for sorting to work. If there is a different amount of body cells 15 | * there will be unintended effects. 16 | * 17 | * @version 1.0 18 | * @class 19 | * @author Niko Berger 20 | * @license MIT License GPL 21 | */ 22 | ;(function( $, window, undefined ){ 23 | "use strict"; 24 | 25 | const SORTABLE_INIT_FUNCTIONS = {}; // remember initialization functions 26 | const SORTABLE_MAP = {}; // remember all sorTables 27 | 28 | /** 29 | * @param element {Node} the cotnainer table that should be converted to a sorTable 30 | * @param options {object} the configuraton object 31 | * @constructor 32 | */ 33 | function SorTable (element, options) { 34 | // create the options 35 | this.options = $.extend({}, { 36 | /** 37 | * add icons in the header: add html snipped. 38 | * If empty, nothing will be shown 39 | */ 40 | icon: [' ', ' ', ' '], 41 | /** 42 | * selector to get the header cells 43 | */ 44 | headSelect: "thead>tr>th", 45 | /** 46 | * select the body 47 | */ 48 | bodySelect: "tbody", 49 | /** 50 | * the class in the head that specifies a row that is sortable 51 | */ 52 | classSortable: "sortable", 53 | /** 54 | * quickfilter element 55 | */ 56 | quickfilter: null, 57 | /** 58 | * remember the sort order in a cookie (non-null = name of the cookie) 59 | */ 60 | remember: null 61 | }, options); 62 | // the table to sort 63 | this.element = element; 64 | // the sorted last 65 | this.lastSort = null; 66 | 67 | this._init(); 68 | } 69 | 70 | /** 71 | * init and load the config 72 | * @private 73 | */ 74 | SorTable.prototype._init = function() { 75 | const that = this; 76 | $(this.options.headSelect, this.element).each(function(){ 77 | if(!$(this).hasClass(that.options.classSortable)) 78 | return; 79 | let sortIcon = null; 80 | if(that.options.icon) { 81 | sortIcon = $(''+that.options.icon[0]+''); 82 | $(this).append(sortIcon); 83 | } 84 | 85 | $(this).click(function(){ 86 | const curSort = that._sort($(this).prevAll().length, that.lastSort !== sortIcon ? 0 : $(this).data().sortOrder); 87 | $(this).data().sortOrder = curSort; 88 | if(sortIcon) { 89 | if(that.lastSort) that.lastSort.html(that.options.icon[0]); 90 | // remember the current row and set the icon correctly 91 | that.lastSort = sortIcon; 92 | sortIcon.html(that.options.icon[curSort]); 93 | } 94 | }); 95 | }); 96 | 97 | const qf = $(this.options.headSelect, this.element).find("input.quickfilter"); 98 | if(qf.length > 0) { 99 | qf.on("keyup change", function(){ 100 | const val = $(this).val().trim(); 101 | that._filter(val); 102 | }); 103 | } 104 | 105 | }; 106 | 107 | 108 | /** 109 | * filter the body table based on a value 110 | */ 111 | SorTable.prototype._filter = function(value) { 112 | const that = this; 113 | const $body = $(this.options.bodySelect, this.element); 114 | value = value.toLowerCase(); 115 | $($body).children("tr").each(function(){ 116 | if(!value || value === "") { 117 | $(this).show(); 118 | return; 119 | } 120 | // check the text content 121 | const content = that._rowVal($(this)).toLowerCase(); 122 | if(content.indexOf(value) === -1) 123 | $(this).hide(); 124 | else 125 | $(this).show(); 126 | }); 127 | }; 128 | 129 | /** 130 | * get the content of a cell 131 | */ 132 | SorTable.prototype._cellVal= function($cell) { 133 | 134 | let rowVal = $("select", $cell).val(); 135 | // no text - check input 136 | if(typeof rowVal === "undefined") { 137 | rowVal = $("input", $cell).val(); 138 | } 139 | if(typeof rowVal === "undefined") { 140 | rowVal = $cell.text().trim(); 141 | } 142 | return rowVal; 143 | } 144 | 145 | /** 146 | * get the content of a row 147 | */ 148 | SorTable.prototype._rowVal= function($row) { 149 | const that = this; 150 | let rowVal = ""; 151 | $row.children().each(function(){ 152 | rowVal += that._cellVal($(this)); 153 | }); 154 | return rowVal; 155 | } 156 | 157 | /** 158 | * repaint the sorTable with new data 159 | * @param pos the row to sort 160 | * @param order the current order of this row 161 | * @return 0-no sort; 1-down; 2-up 162 | * @private 163 | */ 164 | SorTable.prototype._sort = function(pos, order) { 165 | const that = this; 166 | const $body = $(this.options.bodySelect, this.element); 167 | let dataType = "string"; 168 | if(order == 1) 169 | order = 2; 170 | else 171 | order = 1; 172 | 173 | const he = $($(this.options.headSelect, this.element).get(pos)); 174 | if(he.hasClass("num") || he.hasClass("number")) 175 | dataType = "number"; 176 | else if(he.hasClass("trimnum")) 177 | dataType = "findnumber"; 178 | else if(he.hasClass("date")) 179 | dataType = "date"; 180 | 181 | console.log("sort "+pos+" by", dataType); 182 | 183 | // collect all tr 184 | const data = []; 185 | $($body).children("tr").each(function(){ 186 | const $cell = $($(this).children()[pos]); 187 | const rowVal = that._cellVal($cell); 188 | data.push({ 189 | val: rowVal, 190 | row: $(this).detach() 191 | }); 192 | }); 193 | 194 | let valFunc = he.data().sorter; 195 | if(!valFunc) 196 | switch(dataType){ 197 | case "number": 198 | valFunc = (a)=>{ 199 | return Number(a.val); 200 | }; break; 201 | case "findnumber": 202 | valFunc = (a)=>{ 203 | const aval = a.val.replace(/[^0-9.,]+/g, ""); 204 | return Number(aval); 205 | }; break; 206 | case "date": 207 | valFunc = (a)=>{ 208 | const aval = new Date(a); 209 | return aval.getTime(); 210 | }; break; 211 | default: 212 | valFunc = (a)=>{ 213 | return a.val; 214 | }; break; 215 | } 216 | 217 | // sort 218 | data.sort(function(a,b){ 219 | const aVal = valFunc(a); 220 | const bVal = valFunc(b); 221 | if (aVal < bVal) 222 | return order == 1 ? -1 : 1; 223 | if (aVal > bVal) 224 | return order == 1 ? 1 : -1; 225 | return 0; 226 | }); 227 | 228 | // readd 229 | $.each(data, function(){ 230 | $body.append(this.row); 231 | }); 232 | 233 | return order; 234 | }; 235 | 236 | SorTable.prototype.sort = function (col) { 237 | if(!isNaN(col)) { 238 | this._sort(col); 239 | return; 240 | } 241 | this._sort(col.prevAll().length); 242 | }; 243 | 244 | /** 245 | * destroy the jsform and its resources. 246 | * @private 247 | */ 248 | SorTable.prototype.destroy = function( ) { 249 | return $(this.element).each(function(){ 250 | $(window).unbind('.sorTable'); 251 | $(this).removeData('sorTable'); 252 | }); 253 | }; 254 | 255 | // init and call methods 256 | $.fn.sorTable = function ( method ) { 257 | // Method calling logic 258 | if ( typeof method === 'object' || ! method ) { 259 | return this.each(function () { 260 | if (!$(this).data('sorTable')) { 261 | $(this).data('sorTable', new SorTable( this, method )); 262 | } 263 | }); 264 | } else { 265 | const args = Array.prototype.slice.call( arguments, 1 ); 266 | let sorTable; 267 | // none found 268 | if(this.length === 0) { 269 | return null; 270 | } 271 | // only one - return directly 272 | if(this.length === 1) { 273 | sorTable = $(this).data('sorTable'); 274 | if (sorTable) { 275 | if(method.indexOf("_") !== 0 && sorTable[method]) { 276 | return sorTable[method].apply(sorTable, args); 277 | } 278 | 279 | $.error( 'Method ' + method + ' does not exist on jQuery.sorTable' ); 280 | return false; 281 | } 282 | } 283 | 284 | return this.each(function () { 285 | sorTable = $.data(this, 'sorTable'); 286 | if (sorTable) { 287 | if(method.indexOf("_") !== 0 && sorTable[method]) { 288 | return sorTable[method].apply(sorTable, args); 289 | } else { 290 | $.error( 'Method ' + method + ' does not exist on jQuery.sorTable' ); 291 | return false; 292 | } 293 | } 294 | }); 295 | } 296 | }; 297 | 298 | /** 299 | * global sorTable function for initialization 300 | */ 301 | $.sorTable = function ( name, initFunc ) { 302 | const sorTables = SORTABLE_MAP[name]; 303 | // initFunc is a function -> initialize 304 | if($.isFunction(initFunc)) { 305 | // call init if already initialized 306 | if(sorTables) { 307 | $.each(sorTables, function(){ 308 | initFunc(this, $(this.element)); 309 | }); 310 | } 311 | 312 | // remember for future initializations 313 | SORTABLE_INIT_FUNCTIONS[name] = initFunc; 314 | } else { 315 | // call init if already initialized 316 | if(sorTables) { 317 | const method = initFunc; 318 | const args = Array.prototype.slice.call( arguments, 2 ); 319 | $.each(portlets, function(){ 320 | this[method].apply(this, args); 321 | }); 322 | } 323 | } 324 | }; 325 | 326 | })( jQuery, window ); 327 | -------------------------------------------------------------------------------- /controls/jquery.tree.css: -------------------------------------------------------------------------------- 1 | /* tree */ 2 | .tree { 3 | display: block; 4 | list-style-type: none; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | .tree li, .tree ul { 10 | display: block; 11 | list-style-type: none; 12 | margin: 0; 13 | padding: 0; 14 | } 15 | 16 | .tree .control span{ 17 | cursor:pointer; 18 | display:inline-block; 19 | } 20 | 21 | .tree span { 22 | padding-left: 5px; 23 | padding-right: 5px; 24 | } 25 | 26 | .tree li { 27 | line-height: 20px; 28 | margin-left: 18px; 29 | min-height: 22px; 30 | min-width: 18px; 31 | margin-top: -6px; 32 | white-space: nowrap; 33 | overflow:visible; 34 | } 35 | 36 | .tree > ul > li { 37 | margin-left: 0; 38 | } 39 | .tree ins { 40 | display: inline-block; 41 | height: 18px; 42 | margin-bottom: -4px; 43 | padding: 0; 44 | text-decoration: none; 45 | width: 16px; 46 | position:relative; 47 | cursor: pointer; 48 | } 49 | 50 | .tree li.open > ul { 51 | display: block; 52 | } 53 | 54 | .tree li a { 55 | color: black; 56 | height: 16px; 57 | line-height: 16px; 58 | margin: 0; 59 | text-decoration: none; 60 | white-space: nowrap; 61 | padding: 0 2px; 62 | } 63 | 64 | .tree a > ins { 65 | height: 16px; 66 | width: 16px; 67 | left:0px; 68 | } 69 | -------------------------------------------------------------------------------- /controls/jquery.tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jquery.tree 3 | * ----------- 4 | * Simple Tree control 5 | * @version 1.0 6 | * @class 7 | * @author Niko Berger 8 | * @license MIT License GPL 9 | */ 10 | ;(function( $, window, undefined ){ 11 | "use strict"; 12 | 13 | var TREE_INIT_FUNCTIONS = {}, // remember initialization functions 14 | TREE_MAP = {}; // remember all trees 15 | 16 | /** 17 | * @param element {Node} the cotnainer node that should be converted to a tree 18 | * @param options {object} the configuraton object 19 | * @constructor 20 | */ 21 | function Tree (element, options) { 22 | // create the options 23 | this.options = $.extend({}, { 24 | /** 25 | * automatically prepend icons 26 | */ 27 | autoIcon: false, 28 | /** 29 | * open all levels 30 | */ 31 | open: false, 32 | /** 33 | * remember which part of the tree was open in a cookie (non-null = name of the cookie) 34 | */ 35 | remember: null, 36 | /** 37 | * field used for id (only required for remembering which node was "open") 38 | */ 39 | id: null, 40 | /** 41 | * field used as name, this can also be a function that renders the object 42 | */ 43 | name: "name", 44 | /** 45 | * field used for title 46 | */ 47 | title: null, 48 | /** 49 | * field to recurse into 50 | */ 51 | children: "children", 52 | /** 53 | * set to true to allow multiple elements to be selected 54 | */ 55 | multiple: false, 56 | /** 57 | * style when a node is active (null: disables activation) 58 | */ 59 | active: "ui-state-active", 60 | hover: "ui-state-hover", 61 | 62 | 63 | /** 64 | * the object used to fill/collect data 65 | */ 66 | data: null, 67 | 68 | /** 69 | * true to execute the loaddata automatically 70 | */ 71 | load: true, 72 | 73 | /** 74 | * callback function for data fetching 75 | */ 76 | loadData: null, 77 | 78 | /** 79 | * callback function when a node is selected 80 | */ 81 | select: null, 82 | /** 83 | * callback function when a node is double clicked 84 | */ 85 | dblclick: null, 86 | 87 | /** 88 | * callback function when a node is drag/dropped. if null drag/dropping is disabled 89 | */ 90 | drop: null, 91 | /** 92 | * optional "root" target which will be visible when dragging starts 93 | */ 94 | rootTarget: 'Root' 95 | }, options); 96 | 97 | this.element = element; 98 | 99 | if(this.options.load && this.options.loadData) { 100 | this.options.loadData(); 101 | } 102 | 103 | this._init(); 104 | } 105 | 106 | /** 107 | * init the portlet - load the config 108 | * @private 109 | */ 110 | Tree.prototype._init = function() { 111 | // fill everything 112 | this._repaint(); 113 | }; 114 | 115 | Tree.prototype._paint = function(basenode, data) { 116 | // return if empty 117 | if(!data || !data.length || data.length == 0) 118 | return; 119 | 120 | var that = this, config = this.options, $this = $(this.element); 121 | 122 | var root = $('
      '); 123 | basenode.append(root); 124 | $.each(data, function(){ 125 | var name = null, title = null; 126 | if($.isFunction(config.name)) { 127 | name = config.name(this); 128 | } else { 129 | name = this[config.name]; 130 | } 131 | 132 | if($.isFunction(config.title)) { 133 | title = config.title(this); 134 | } else { 135 | title = this[config.title]; 136 | } 137 | 138 | var node = $('
    • '+name+'
    • '); 139 | if(title !== null) { 140 | node.attr("title", title); 141 | } 142 | 143 | if(config.id) { 144 | node.data().id = this[config.id]; 145 | } else if(this.id) 146 | node.data().id = this.id; 147 | 148 | $("span", node).hover(function(){$(this).addClass(config.hover);}, function(){$(this).removeClass(config.hover);}).click(function(){ 149 | if(config.active) { 150 | // deactivate 151 | if(!config.multiple) { 152 | $this.find("." + config.active).removeClass(config.active); 153 | $(this).addClass(config.active); 154 | } 155 | else { 156 | if($(this).hasClass(config.active)) { 157 | $(this).removeClass(config.active); 158 | } else { 159 | $(this).addClass(config.active); 160 | } 161 | } 162 | } 163 | 164 | // trigger selection 165 | if($(this).hasClass(config.active)) { 166 | if(config.select) 167 | config.select($(this).parent().data().node); 168 | $this.data().selected = this; 169 | $this.trigger("select", this); 170 | } 171 | }).dblclick(function(){ 172 | // doubleclick: only one secelection 173 | if(config.active) { 174 | $this.find("." + config.active).removeClass(config.active); 175 | $(this).addClass(config.active); 176 | } 177 | // trigger selection 178 | if(config.dblclick) 179 | config.dblclick($(this).parent().data().node); 180 | $this.data().selected = $(this).parent().data().node; 181 | $this.trigger("dblclick", $(this).parent().data().node); 182 | }); 183 | node.data().node = this; 184 | root.append(node); 185 | that._paint(node, this[config.children]); 186 | }); 187 | }; 188 | 189 | /** 190 | * repaint the tree with new data 191 | * @private 192 | */ 193 | Tree.prototype._repaint = function() { 194 | this._clear(); 195 | 196 | if(this.options.data) { 197 | this._paint($(this.element), this.options.data); 198 | } 199 | 200 | this._enable(this.element, this.options.data); 201 | 202 | if(this.options.drop) { 203 | this._enableSorting(); 204 | } 205 | }; 206 | 207 | Tree.prototype._enableSorting = function() { 208 | var $this = $(this.element),config = this.options; 209 | 210 | // now add dragndrop capability 211 | $("li", $this).draggable({ 212 | opacity: 0.5, 213 | revert:true, 214 | start: function(){ 215 | if(config.rootNode) { 216 | config.rootNode.show(); 217 | } 218 | }, 219 | stop: function() { 220 | if(config.rootNode) { 221 | config.rootNode.hide(); 222 | } 223 | } 224 | }); 225 | 226 | if(config.rootTarget) { 227 | var node =$('
    • '); 228 | if($.isFunction(config.rootTarget)) 229 | node.children("span").append(config.rootTarget()); 230 | else 231 | node.children("span").append(config.rootTarget); 232 | console.log(node.html()); 233 | config.rootNode = node; 234 | node.hide(); 235 | $this.children("ul.tree").prepend(node); 236 | } 237 | 238 | // and drop them 239 | $("li span", $this).droppable({ 240 | tolerance: "pointer", 241 | hoverClass: "ui-state-hover", 242 | drop: function(event, ui){ 243 | var dropped = ui.draggable; 244 | var me = $(this).parent(); 245 | // reload after move 246 | var meObj = me.data().node; 247 | 248 | // make the change in the pojos 249 | var dropObj = dropped.data().node; 250 | 251 | config.drop(dropObj, meObj); 252 | } 253 | }); 254 | }; 255 | 256 | 257 | /** 258 | * applies the acutal tree functionality 259 | * @private 260 | */ 261 | Tree.prototype._enable = function(base, data) { 262 | var config = this.options; 263 | 264 | // make sure we have openers 265 | $(base).find("li").each(function(){ 266 | // avoid double initialization 267 | if($(this).hasClass("tree-item")) { 268 | return; 269 | } 270 | $(this).addClass("tree-item"); 271 | 272 | // cleanup 273 | if(config.autoIcon) { 274 | $(this).children("ul").filter( function() { 275 | return $.trim($(this).html()) == ''; 276 | }).remove(); 277 | } 278 | 279 | // either we have an ul or a method but do not know that there are no childs 280 | var opener = $(''); 281 | $(this).prepend(opener); 282 | if($(this).children("ul").length > 0 || ($(this).hasClass("portlet-fill") && $(this).data("hasChilds") !== false)) { 283 | opener.addClass("ui-icon ui-icon-triangle-1-e"); 284 | } else if(config.empty){ 285 | opener.addClass(config.empty); 286 | } 287 | 288 | // in case of ul with li -> open 289 | if($(this).children("ul").length > 0) { 290 | if($(this).find("li").length > 0) { 291 | $(this).addClass("open"); 292 | opener.addClass("ui-icon-triangle-1-se").removeClass("ui-icon-triangle-1-e"); 293 | } 294 | } 295 | 296 | opener.click(function(){ 297 | if($(this).parent().hasClass("open")){ 298 | $(this).parent().children("ul").hide(); 299 | $(this).parent().removeClass("open"); 300 | $(this).addClass("ui-icon-triangle-1-e").removeClass("ui-icon-triangle-1-se"); 301 | $(this).parent().trigger("tree.close", [$(this).parent()]); 302 | } else { 303 | $(this).parent().children("ul").show(); 304 | $(this).parent().addClass("open"); 305 | $(this).addClass("ui-icon-triangle-1-se").removeClass("ui-icon-triangle-1-e"); 306 | $(this).parent().trigger("tree.open", [$(this).parent()]); 307 | } 308 | }); 309 | 310 | // close ul (do not trigger for dynmic tress -> portlet-fill 311 | if($(this).children("ul").length > 0 && !$(this).parent().hasClass("open") && !config.open) { 312 | opener.trigger("click"); 313 | } 314 | }); 315 | }; 316 | 317 | 318 | 319 | /** 320 | * clear/reset a form. The prefix is normally predefined by init 321 | * @param form the form 322 | * @param prefix the optional prefix used to identify fields for this form 323 | */ 324 | Tree.prototype._clear = function() { 325 | $(this.element).html(""); 326 | }; 327 | 328 | 329 | /** 330 | * repaint the tree with given data 331 | * @param data {object} the data 332 | */ 333 | Tree.prototype.fill = function(pojo) { 334 | // clear first 335 | this.clear(); 336 | // set the new data 337 | this.options.data = pojo; 338 | // fill everything 339 | this._repaint(this.element, this.options.data); 340 | }; 341 | 342 | /** 343 | * Clear all fields in a form 344 | */ 345 | Tree.prototype.clear = function() { 346 | // clear first 347 | this._clear(); 348 | }; 349 | 350 | /** 351 | * @return all currently selected fields 352 | */ 353 | Tree.prototype.getAll = function() { 354 | var config = this.options, $this = $(this.element); 355 | 356 | if(!config.active) 357 | return $this.data().selected; 358 | 359 | var ret = []; 360 | $this.find("." + config.active).each(function(){ 361 | ret.push($(this).closest("li.tree-item").data().node); 362 | }); 363 | return ret; 364 | }; 365 | 366 | Tree.prototype.select = function(fields) { 367 | var config = this.options, $this = $(this.element); 368 | 369 | // only if we actually display the select 370 | if(!config.active) 371 | return; 372 | 373 | // deselect all 374 | $this.find("." + config.active).removeClass(config.active); 375 | 376 | // skip if we dont select anything (=clear) 377 | if(!fields) 378 | return; 379 | 380 | $.each(fields, function(){ 381 | var id = this; 382 | if(config.id) { 383 | id = this[config.id]; 384 | } else if(this.id) 385 | id = this.id; 386 | 387 | $("li.node").each(function(){ 388 | if($(this).data().id === id) { 389 | $(this).children("span").addClass(config.active); 390 | return false; 391 | } 392 | }); 393 | }); 394 | }; 395 | 396 | /** 397 | * call the loadData callback or use the current dat aobject to repaint the whole tree 398 | */ 399 | Tree.prototype.reload = function(){ 400 | if(this.options.loadData) 401 | this.options.loadData(); 402 | else { 403 | this.clear(); 404 | this._repaint(this.element, this.options.data); 405 | } 406 | }; 407 | 408 | /** 409 | * destroy the jsform and its resources. 410 | * @private 411 | */ 412 | Tree.prototype.destroy = function( ) { 413 | return $(this.element).each(function(){ 414 | $(window).unbind('.tree'); 415 | $(this).removeData('tree'); 416 | }); 417 | }; 418 | 419 | // init and call methods 420 | $.fn.tree = function ( method ) { 421 | // Method calling logic 422 | if ( typeof method === 'object' || ! method ) { 423 | return this.each(function () { 424 | if (!$(this).data('tree')) { 425 | $(this).data('tree', new Tree( this, method )); 426 | } 427 | }); 428 | } else { 429 | var args = Array.prototype.slice.call( arguments, 1 ), 430 | tree; 431 | // none found 432 | if(this.length === 0) { 433 | return null; 434 | } 435 | // only one - return directly 436 | if(this.length === 1) { 437 | tree = $(this).data('tree'); 438 | if (tree) { 439 | if(method.indexOf("_") !== 0 && tree[method]) { 440 | var ret = tree[method].apply(tree, args); 441 | return ret; 442 | } 443 | 444 | $.error( 'Method ' + method + ' does not exist on jQuery.tree' ); 445 | return false; 446 | } 447 | } 448 | 449 | return this.each(function () { 450 | tree = $.data(this, 'tree'); 451 | if (tree) { 452 | if(method.indexOf("_") !== 0 && tree[method]) { 453 | return tree[method].apply(tree, args); 454 | } else { 455 | $.error( 'Method ' + method + ' does not exist on jQuery.tree' ); 456 | return false; 457 | } 458 | } 459 | }); 460 | } 461 | }; 462 | 463 | /** 464 | * global tree function for initialization 465 | */ 466 | $.tree = function ( name, initFunc ) { 467 | var trees = TREE_MAP[name]; 468 | // initFunc is a function -> initialize 469 | if($.isFunction(initFunc)) { 470 | // call init if already initialized 471 | if(trees) { 472 | $.each(trees, function(){ 473 | initFunc(this, $(this.element)); 474 | }); 475 | } 476 | 477 | // remember for future initializations 478 | TREE_INIT_FUNCTIONS[name] = initFunc; 479 | } else { 480 | // call init if already initialized 481 | if(trees) { 482 | var method = initFunc; 483 | var args = Array.prototype.slice.call( arguments, 2 ); 484 | $.each(portlets, function(){ 485 | this[method].apply(this, args); 486 | }); 487 | } 488 | } 489 | }; 490 | 491 | })( jQuery, window ); 492 | -------------------------------------------------------------------------------- /js/jquery.jsForm.d.ts: -------------------------------------------------------------------------------- 1 | interface JQuery { 2 | /** 3 | * Initialize jsform with default configuration on a node 4 | */ 5 | jsForm(): JQuery; 6 | jsForm(func:any): JQuery; 7 | jsForm(func:string, opts:any): JQuery; 8 | } -------------------------------------------------------------------------------- /jsForm.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsForm", 3 | "title": "jQuery JSON Form", 4 | "description": "jQuery based form library that allows you to handle data within a js object (i.e. JSON) with html forms.\nYou can modify all fields/properties by simply creating a html form with the correct naming schema (MVVM Pattern).\n\nThe main features of this library are:\n Full standard html with data available in a js object\n Update an existing js object with changes done within a form\n Fully internationalizable with number format, currency and date formatting\n Provide basic functions for formatting (i.e. date/time, money) using html markup\n Provide form validation functionality\n handle collections (arrays) with subobjects\n provides helper methods to handle array manipulation (add new entry/remove an entry, sorting) using only html markup\n Can be used in connection with an autocomplete function to add new array objects", 5 | "keywords": [ 6 | "form", 7 | "json", 8 | "data", 9 | "mvvm" 10 | ], 11 | "version": "1.6.0", 12 | "author": { 13 | "name": "Niko Berger" 14 | }, 15 | "maintainers": [{ 16 | "name": "Niko Berger", 17 | "email": "niko.berger@corinis.com", 18 | "url": "http://www.corinis.com" 19 | }], 20 | "licenses": [{ 21 | "type": "MIT", 22 | "url": "https://github.com/corinis/jsForm/MIT-LICENSE.txt" 23 | }], 24 | "bugs": "https://github.com/corinis/jsForm/issues", 25 | "homepage": "https://github.com/corinis/jsForm/", 26 | "docs": "https://github.com/corinis/jsForm/", 27 | "dependencies": { 28 | "jquery": ">=1.7" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/jquery.format-1.3.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Format Plugin v${version} 3 | * http://www.asual.com/jquery/format/ 4 | * 5 | * Copyright (c) 2009-2011 Rostislav Hristov 6 | * Uses code by Matt Kruse 7 | * Dual licensed under the MIT or GPL Version 2 licenses. 8 | * http://jquery.org/license 9 | * 10 | * Date: ${timestamp} 11 | */ 12 | (function ($) { 13 | 14 | $.format = (function () { 15 | 16 | var UNDEFINED = 'undefined', 17 | TRUE = true, 18 | FALSE = false, 19 | _locale = { 20 | date: { 21 | format: 'MMM dd, yyyy h:mm:ss a', 22 | monthsFull: ['January','February','March','April','May','June', 23 | 'July','August','September','October','November','December'], 24 | monthsShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'], 25 | daysFull: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], 26 | daysShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'], 27 | shortDateFormat: 'M/d/yyyy h:mm a', 28 | longDateFormat: 'EEEE, MMMM dd, yyyy h:mm:ss a' 29 | }, 30 | number: { 31 | format: '#,##0.0#', 32 | groupingSeparator: ',', 33 | decimalSeparator: '.' 34 | } 35 | }; 36 | 37 | return { 38 | 39 | locale: function(value) { 40 | a = {a: 6}; 41 | if (value) { 42 | for (var p in value) { 43 | for (var v in value[p]) { 44 | _locale[p][v] = value[p][v]; 45 | } 46 | } 47 | } 48 | return _locale; 49 | }, 50 | 51 | date: function(value, format) { 52 | 53 | var i = 0, 54 | j = 0, 55 | l = 0, 56 | c = '', 57 | token = '', 58 | x, 59 | y; 60 | 61 | if (typeof value == 'string') { 62 | 63 | var getNumber = function (str, p, minlength, maxlength) { 64 | for (var x = maxlength; x >= minlength; x--) { 65 | var token = str.substring(p, p + x); 66 | if (token.length >= minlength && /^\d+$/.test(token)) { 67 | return token; 68 | } 69 | } 70 | return null; 71 | }; 72 | 73 | if (typeof format == UNDEFINED) { 74 | format = _locale.date.format; 75 | } 76 | 77 | var _strict = false, 78 | pos = 0, 79 | now = new Date(0, 0, 0, 0, 0, 0, 0), 80 | year = now.getYear(), 81 | month = now.getMonth() + 1, 82 | date = 1, 83 | hh = now.getHours(), 84 | mm = now.getMinutes(), 85 | ss = now.getSeconds(), 86 | SSS = now.getMilliseconds(), 87 | ampm = '', 88 | monthName, 89 | dayName; 90 | 91 | while (i < format.length) { 92 | token = ''; 93 | c = format.charAt(i); 94 | while ((format.charAt(i) == c) && (i < format.length)) { 95 | token += format.charAt(i++); 96 | } 97 | if (token.indexOf('MMMM') > - 1 && token.length > 4) { 98 | token = 'MMMM'; 99 | } 100 | if (token.indexOf('EEEE') > - 1 && token.length > 4) { 101 | token = 'EEEE'; 102 | } 103 | if (token == 'yyyy' || token == 'yy' || token == 'y') { 104 | if (token == 'yyyy') { 105 | x = 4; 106 | y = 4; 107 | } 108 | if (token == 'yy') { 109 | x = 2; 110 | y = 2; 111 | } 112 | if (token == 'y') { 113 | x = 2; 114 | y = 4; 115 | } 116 | year = getNumber(value, pos, x, y); 117 | if (year === null) { 118 | return 0; 119 | } 120 | pos += year.length; 121 | if (year.length == 2) { 122 | year = parseInt(year, 10); 123 | if (year > 70) { 124 | year = 1900 + year; 125 | } else { 126 | year = 2000 + year; 127 | } 128 | } 129 | } else if (token == 'MMMM'){ 130 | month = 0; 131 | for (j = 0, l = _locale.date.monthsFull.length; j < l; j++) { 132 | monthName = _locale.date.monthsFull[j]; 133 | if (value.substring(pos, pos + monthName.length).toLowerCase() == monthName.toLowerCase()) { 134 | month = j + 1; 135 | pos += monthName.length; 136 | break; 137 | } 138 | } 139 | if ((month < 1) || (month > 12)){ 140 | return 0; 141 | } 142 | } else if (token == 'MMM'){ 143 | month = 0; 144 | for (j = 0, l = _locale.date.monthsShort.length; j < l; j++) { 145 | monthName = _locale.date.monthsShort[j]; 146 | if (value.substring(pos, pos + monthName.length).toLowerCase() == monthName.toLowerCase()) { 147 | month = j + 1; 148 | pos += monthName.length; 149 | break; 150 | } 151 | } 152 | if ((month < 1) || (month > 12)){ 153 | return 0; 154 | } 155 | } else if (token == 'EEEE'){ 156 | for (j = 0, l = _locale.date.daysFull.length; j < l; j++) { 157 | dayName = _locale.date.daysFull[j]; 158 | if (value.substring(pos, pos + dayName.length).toLowerCase() == dayName.toLowerCase()) { 159 | pos += dayName.length; 160 | break; 161 | } 162 | } 163 | } else if (token == 'EEE'){ 164 | for (j = 0, l =_locale.date.daysShort.length; j < l; j++) { 165 | dayName = _locale.date.daysShort[j]; 166 | if (value.substring(pos, pos + dayName.length).toLowerCase() == dayName.toLowerCase()) { 167 | pos += dayName.length; 168 | break; 169 | } 170 | } 171 | } else if (token == 'MM' || token == 'M') { 172 | month = getNumber(value, pos, _strict ? token.length : 1, 2); 173 | if (month === null || (month < 1) || (month > 12)){ 174 | return 0; 175 | } 176 | pos += month.length; 177 | } else if (token == 'dd' || token == 'd') { 178 | date = getNumber(value, pos, _strict ? token.length : 1, 2); 179 | if (date === null || (date < 1) || (date > 31)){ 180 | return 0; 181 | } 182 | pos += date.length; 183 | } else if (token == 'hh' || token == 'h') { 184 | hh = getNumber(value, pos, _strict ? token.length : 1, 2); 185 | if (hh === null || (hh < 1) || (hh > 12)) { 186 | return 0; 187 | } 188 | pos += hh.length; 189 | } else if (token == 'HH' || token == 'H') { 190 | hh = getNumber(value, pos, _strict ? token.length : 1, 2); 191 | if(hh === null || (hh < 0) || (hh > 23)){ 192 | return 0; 193 | } 194 | pos += hh.length; 195 | } else if (token == 'KK' || token == 'K') { 196 | hh = getNumber(value, pos, _strict ? token.length : 1, 2); 197 | if (hh === null || (hh < 0) || (hh > 11)){ 198 | return 0; 199 | } 200 | pos += hh.length; 201 | } else if (token == 'kk' || token == 'k') { 202 | hh = getNumber(value, pos, _strict ? token.length : 1, 2); 203 | if (hh === null || (hh < 1) || (hh > 24)){ 204 | return 0; 205 | } 206 | pos += hh.length; 207 | hh--; 208 | } else if (token == 'mm' || token == 'm') { 209 | mm = getNumber(value, pos, _strict ? token.length : 1, 2); 210 | if (mm === null || (mm < 0) || ( mm > 59)) { 211 | return 0; 212 | } 213 | pos += mm.length; 214 | } else if (token == 'ss' || token == 's') { 215 | ss = getNumber(value, pos, _strict ? token.length : 1, 2); 216 | if (ss === null || (ss < 0) || (ss > 59)){ 217 | return 0; 218 | } 219 | pos += ss.length; 220 | } else if (token == 'SSS' || token == 'SS' || token == 'S') { 221 | SSS = getNumber(value, pos, _strict ? token.length : 1, 3); 222 | if (SSS === null || (SSS < 0) || (SSS > 999)){ 223 | return 0; 224 | } 225 | pos += SSS.length; 226 | } else if (token == 'a') { 227 | var ap = value.substring(pos, pos + 2).toLowerCase(); 228 | if (ap == 'am') { 229 | ampm = 'AM'; 230 | } else if (ap == 'pm') { 231 | ampm = 'PM'; 232 | } else { 233 | return 0; 234 | } 235 | pos += 2; 236 | } else { 237 | if (token != value.substring(pos, pos + token.length)) { 238 | return 0; 239 | } else { 240 | pos += token.length; 241 | } 242 | } 243 | } 244 | if (pos != value.length) { 245 | return 0; 246 | } 247 | if (month == 2) { 248 | if (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) { 249 | if (date > 29) { 250 | return 0; 251 | } 252 | } else { 253 | if (date > 28) { 254 | return 0; 255 | } 256 | } 257 | } 258 | if ((month == 4) || (month == 6) || (month == 9) || (month == 11)) { 259 | if (date > 30) { 260 | return 0; 261 | } 262 | } 263 | if (hh < 12 && ampm == 'PM') { 264 | hh = hh - 0 + 12; 265 | } else if (hh > 11 && ampm == 'AM') { 266 | hh -= 12; 267 | } 268 | 269 | return (new Date(year, month - 1, date, hh, mm, ss, SSS)); 270 | 271 | } else { 272 | 273 | var formatNumber = function (n, s) { 274 | if (typeof s == UNDEFINED || s == 2) { 275 | return (n >= 0 && n < 10 ? '0' : '') + n; 276 | } else { 277 | if (n >= 0 && n < 10) { 278 | return '00' + n; 279 | } 280 | if (n >= 10 && n <100) { 281 | return '0' + n; 282 | } 283 | return n; 284 | } 285 | }; 286 | 287 | if (typeof format == UNDEFINED) { 288 | format = _locale.date.format; 289 | } 290 | 291 | y = value.getYear(); 292 | if (y < 1000) { 293 | y = String(y + 1900); 294 | } 295 | 296 | var M = value.getMonth() + 1, 297 | d = value.getDate(), 298 | E = value.getDay(), 299 | H = value.getHours(), 300 | m = value.getMinutes(), 301 | s = value.getSeconds(), 302 | S = value.getMilliseconds(); 303 | 304 | value = { 305 | y: y, 306 | yyyy: y, 307 | yy: String(y).substring(2, 4), 308 | M: M, 309 | MM: formatNumber(M), 310 | MMM: _locale.date.monthsShort[M-1], 311 | MMMM: _locale.date.monthsFull[M-1], 312 | d: d, 313 | dd: formatNumber(d), 314 | EEE: _locale.date.daysShort[E], 315 | EEEE: _locale.date.daysFull[E], 316 | H: H, 317 | HH: formatNumber(H) 318 | }; 319 | 320 | if (H === 0) { 321 | value.h = 12; 322 | } else if (H > 12) { 323 | value.h = H - 12; 324 | } else { 325 | value.h = H; 326 | } 327 | 328 | value.hh = formatNumber(value.h); 329 | value.k = H !== 0 ? H : 24; 330 | value.kk = formatNumber(value.k); 331 | 332 | if (H > 11) { 333 | value.K = H - 12; 334 | } else { 335 | value.K = H; 336 | } 337 | 338 | value.KK = formatNumber(value.K); 339 | 340 | if (H > 11) { 341 | value.a = 'PM'; 342 | } else { 343 | value.a = 'AM'; 344 | } 345 | 346 | value.m = m; 347 | value.mm = formatNumber(m); 348 | value.s = s; 349 | value.ss = formatNumber(s); 350 | value.S = S; 351 | value.SS = formatNumber(S); 352 | value.SSS = formatNumber(S, 3); 353 | 354 | var result = ''; 355 | 356 | i = 0; 357 | c = ''; 358 | token = ''; 359 | s = false; 360 | 361 | while (i < format.length) { 362 | token = ''; 363 | c = format.charAt(i); 364 | if (c == '\'') { 365 | i++; 366 | if (format.charAt(i) == c) { 367 | result = result + c; 368 | i++; 369 | } else { 370 | s = !s; 371 | } 372 | } else { 373 | while (format.charAt(i) == c) { 374 | token += format.charAt(i++); 375 | } 376 | if (token.indexOf('MMMM') != -1 && token.length > 4) { 377 | token = 'MMMM'; 378 | } 379 | if (token.indexOf('EEEE') != -1 && token.length > 4) { 380 | token = 'EEEE'; 381 | } 382 | if (typeof value[token] != UNDEFINED && !s) { 383 | result = result + value[token]; 384 | } else { 385 | result = result + token; 386 | } 387 | } 388 | } 389 | return result; 390 | } 391 | }, 392 | 393 | number: function(value, format) { 394 | 395 | var groupingSeparator, 396 | groupingIndex, 397 | decimalSeparator, 398 | decimalIndex, 399 | roundFactor, 400 | result, 401 | i; 402 | 403 | if (typeof value == 'string') { 404 | 405 | groupingSeparator = _locale.number.groupingSeparator; 406 | decimalSeparator = _locale.number.decimalSeparator; 407 | decimalIndex = value.indexOf(decimalSeparator); 408 | 409 | roundFactor = 1; 410 | 411 | if (decimalIndex != -1) { 412 | roundFactor = Math.pow(10, value.length - decimalIndex - 1); 413 | } 414 | 415 | value = value.replace(new RegExp('[' + groupingSeparator + ']', 'g'), ''); 416 | value = value.replace(new RegExp('[' + decimalSeparator + ']'), '.'); 417 | 418 | return Math.round(value*roundFactor)/roundFactor; 419 | 420 | } else { 421 | 422 | if (typeof format == UNDEFINED || format.length < 1) { 423 | format = _locale.number.format; 424 | } 425 | 426 | groupingSeparator = ','; 427 | groupingIndex = format.lastIndexOf(groupingSeparator); 428 | decimalSeparator = '.'; 429 | decimalIndex = format.indexOf(decimalSeparator); 430 | 431 | var integer = '', 432 | fraction = '', 433 | negative = value < 0, 434 | minFraction = format.substr(decimalIndex + 1).replace(/#/g, '').length, 435 | maxFraction = format.substr(decimalIndex + 1).length, 436 | powFraction = 10; 437 | 438 | value = Math.abs(value); 439 | 440 | if (decimalIndex != -1) { 441 | fraction = _locale.number.decimalSeparator; 442 | if (maxFraction > 0) { 443 | roundFactor = 1000; 444 | powFraction = Math.pow(powFraction, maxFraction); 445 | var tempRound = Math.round(parseInt(value * powFraction * roundFactor - 446 | Math.round(value) * powFraction * roundFactor, 10) / roundFactor), 447 | tempFraction = String(tempRound < 0 ? Math.round(parseInt(value * powFraction * roundFactor - 448 | parseInt(value, 10) * powFraction * roundFactor, 10) / roundFactor) : tempRound), 449 | parts = value.toString().split('.'); 450 | if (typeof parts[1] != UNDEFINED) { 451 | for (i = 0; i < maxFraction; i++) { 452 | if (parts[1].substr(i, 1) == '0' && i < maxFraction - 1 && 453 | tempFraction.length != maxFraction) { 454 | tempFraction = '0' + tempFraction; 455 | } else { 456 | break; 457 | } 458 | } 459 | } 460 | for (i = 0; i < (maxFraction - fraction.length); i++) { 461 | tempFraction += '0'; 462 | } 463 | var symbol, 464 | formattedFraction = ''; 465 | for (i = 0; i < tempFraction.length; i++) { 466 | symbol = tempFraction.substr(i, 1); 467 | if (i >= minFraction && symbol == '0' && /^0*$/.test(tempFraction.substr(i+1))) { 468 | break; 469 | } 470 | formattedFraction += symbol; 471 | } 472 | fraction += formattedFraction; 473 | } 474 | if (fraction == _locale.number.decimalSeparator) { 475 | fraction = ''; 476 | } 477 | } 478 | 479 | if (decimalIndex !== 0) { 480 | if (fraction != '') { 481 | integer = String(parseInt(Math.round(value * powFraction) / powFraction, 10)); 482 | } else { 483 | integer = String(Math.round(value)); 484 | } 485 | var grouping = _locale.number.groupingSeparator, 486 | groupingSize = 0; 487 | if (groupingIndex != -1) { 488 | if (decimalIndex != -1) { 489 | groupingSize = decimalIndex - groupingIndex; 490 | } else { 491 | groupingSize = format.length - groupingIndex; 492 | } 493 | groupingSize--; 494 | } 495 | if (groupingSize > 0) { 496 | var count = 0, 497 | formattedInteger = ''; 498 | i = integer.length; 499 | while (i--) { 500 | if (count !== 0 && count % groupingSize === 0) { 501 | formattedInteger = grouping + formattedInteger; 502 | } 503 | formattedInteger = integer.substr(i, 1) + formattedInteger; 504 | count++; 505 | } 506 | integer = formattedInteger; 507 | } 508 | var maxInteger, maxRegExp = /#|,/g; 509 | if (decimalIndex != -1) { 510 | maxInteger = format.substr(0, decimalIndex).replace(maxRegExp, '').length; 511 | } else { 512 | maxInteger = format.replace(maxRegExp, '').length; 513 | } 514 | var tempInteger = integer.length; 515 | for (i = tempInteger; i < maxInteger; i++) { 516 | integer = '0' + integer; 517 | } 518 | } 519 | result = integer + fraction; 520 | return (negative ? '-' : '') + result; 521 | } 522 | } 523 | }; 524 | })(); 525 | 526 | }(jQuery)); -------------------------------------------------------------------------------- /lib/jquery.simpledateformat.js: -------------------------------------------------------------------------------- 1 | /** 2 | *

      jquery.simpledateformat.js

      3 | *

      Date and time patterns

      4 | *
        5 | *
      • yy = short year
      • 6 | *
      • yyyy = long year

      • 7 | *
      • M = month (1-12)

      • 8 | *
      • MM = month (01-12)

      • 9 | *
      • MMM = month abbreviation (Jan, Feb … Dec)

      • 10 | *
      • MMMM = long month (January, February … December)

      • 11 | *
      • d = day (1 - 31)

      • 12 | *
      • dd = day (01 - 31)

      • 13 | *
      • ddd = day of the week in words (Monday, Tuesday … Sunday)

      • 14 | *
      • h = hour in am/pm (0-12)

      • 15 | *
      • hh = hour in am/pm (00-12)

      • 16 | *
      • HH = hour in day (00-23)

      • 17 | *
      • mm = minute

      • 18 | *
      • ss = second

      • 19 | *
      • SSS = milliseconds

      • 20 | *
      • a = am/pm marker

      • 21 | *
      22 | *

      Usage

      23 | * $.simpledateformat.format(new Date(), "dd/MM/yyyy hh:mm:ss");
      24 | * $.simpledateformat.format(100, "hh:mm:ss");
      25 | * @based https://github.com/phstc/jquery-dateFormat 26 | * @license MIT License GPL 27 | * @contributor albertjan, christopherstott, cipa, dahdread, docchang, eemeyer, gwilson2151, jafin, jakemonO, jharting, kitto, larryzhao, leesolutions, nashg842, fuzzygroove, stuttufu, thiloplanz, Zyber17. 28 | */ 29 | "use strict"; 30 | 31 | ;(function ($) { 32 | 33 | var daysInWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; 34 | var shortMonthsInYear = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; 35 | var longMonthsInYear = ["January", "February", "March", "April", "May", "June", 36 | "July", "August", "September", "October", "November", "December"]; 37 | var shortMonthsToNumber = []; 38 | shortMonthsToNumber["Jan"] = "01"; 39 | shortMonthsToNumber["Feb"] = "02"; 40 | shortMonthsToNumber["Mar"] = "03"; 41 | shortMonthsToNumber["Apr"] = "04"; 42 | shortMonthsToNumber["May"] = "05"; 43 | shortMonthsToNumber["Jun"] = "06"; 44 | shortMonthsToNumber["Jul"] = "07"; 45 | shortMonthsToNumber["Aug"] = "08"; 46 | shortMonthsToNumber["Sep"] = "09"; 47 | shortMonthsToNumber["Oct"] = "10"; 48 | shortMonthsToNumber["Nov"] = "11"; 49 | shortMonthsToNumber["Dec"] = "12"; 50 | 51 | $.simpledateformat = (function () { 52 | function strDay(value) { 53 | return daysInWeek[parseInt(value, 10)] || value; 54 | } 55 | 56 | function strMonth(value) { 57 | var monthArrayIndex = parseInt(value, 10) - 1; 58 | return shortMonthsInYear[monthArrayIndex] || value; 59 | } 60 | 61 | function strLongMonth(value) { 62 | var monthArrayIndex = parseInt(value, 10) - 1; 63 | return longMonthsInYear[monthArrayIndex] || value; 64 | } 65 | 66 | var parseMonth = function (value) { 67 | return shortMonthsToNumber[value] || value; 68 | }; 69 | 70 | var parseTime = function (value) { 71 | var retValue = value; 72 | var millis = ""; 73 | if (retValue.indexOf(".") !== -1) { 74 | var delimited = retValue.split('.'); 75 | retValue = delimited[0]; 76 | millis = delimited[1]; 77 | } 78 | 79 | var values3 = retValue.split(":"); 80 | 81 | if (values3.length === 3) { 82 | return { 83 | time: retValue, 84 | hour: values3[0], 85 | minute: values3[1], 86 | second: values3[2], 87 | millis: millis 88 | }; 89 | } else { 90 | return { 91 | time: "", 92 | hour: "", 93 | minute: "", 94 | second: "", 95 | millis: "" 96 | }; 97 | } 98 | }; 99 | 100 | return { 101 | format: function (value, format) { 102 | /* 103 | value = new java.util.Date() 104 | 2009-12-18 10:54:50.546 105 | */ 106 | try { 107 | var date = null; 108 | var year = null; 109 | var month = null; 110 | var dayOfMonth = null; 111 | var dayOfWeek = null; 112 | var time = null; 113 | 114 | // convert number to date 115 | if (typeof value == "number"){ 116 | value = new Date(value); 117 | } 118 | 119 | if ($.isFunction(value.getFullYear)) { 120 | year = value.getFullYear(); 121 | month = value.getMonth() + 1; 122 | dayOfMonth = value.getDate(); 123 | dayOfWeek = value.getDay(); 124 | time = { 125 | time: "", 126 | hour: value.getHours(), 127 | minute: value.getMinutes(), 128 | second: value.getSeconds(), 129 | millis: value.getMilliseconds() 130 | }; 131 | } else if (value.search(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.?\d{0,3}[Z\-+]?(\d{2}:?\d{2})?/) != -1) { 132 | /* 2009-04-19T16:11:05+02:00 || 2009-04-19T16:11:05Z */ 133 | var values = value.split(/[T\+-]/); 134 | year = values[0]; 135 | month = values[1]; 136 | dayOfMonth = values[2]; 137 | time = parseTime(values[3].split(".")[0]); 138 | date = new Date(year, month - 1, dayOfMonth); 139 | dayOfWeek = date.getDay(); 140 | } else { 141 | var values = value.split(" "); 142 | switch (values.length) { 143 | case 6: 144 | /* Wed Jan 13 10:43:41 CET 2010 */ 145 | year = values[5]; 146 | month = parseMonth(values[1]); 147 | dayOfMonth = values[2]; 148 | time = parseTime(values[3]); 149 | date = new Date(year, month - 1, dayOfMonth); 150 | dayOfWeek = date.getDay(); 151 | break; 152 | case 2: 153 | /* 2009-12-18 10:54:50.546 */ 154 | var values2 = values[0].split("-"); 155 | year = values2[0]; 156 | month = values2[1]; 157 | dayOfMonth = values2[2]; 158 | time = parseTime(values[1]); 159 | date = new Date(year, month - 1, dayOfMonth); 160 | dayOfWeek = date.getDay(); 161 | break; 162 | case 7: 163 | /* Tue Mar 01 2011 12:01:42 GMT-0800 (PST) */ 164 | case 9: 165 | /*added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0800 (China Standard Time) */ 166 | case 10: 167 | /* added by Larry, for Fri Apr 08 2011 00:00:00 GMT+0200 (W. Europe Daylight Time) */ 168 | year = values[3]; 169 | month = parseMonth(values[1]); 170 | dayOfMonth = values[2]; 171 | time = parseTime(values[4]); 172 | date = new Date(year, month - 1, dayOfMonth); 173 | dayOfWeek = date.getDay(); 174 | break; 175 | case 1: 176 | /* added by Jonny, for 2012-02-07CET00:00:00 (Doctrine Entity -> Json Serializer) */ 177 | var values2 = values[0].split(""); 178 | year=values2[0]+values2[1]+values2[2]+values2[3]; 179 | month= values2[5]+values2[6]; 180 | dayOfMonth = values2[8]+values2[9]; 181 | time = parseTime(values2[13]+values2[14]+values2[15]+values2[16]+values2[17]+values2[18]+values2[19]+values2[20]); 182 | date = new Date(year, month - 1, dayOfMonth); 183 | dayOfWeek = date.getDay(); 184 | break; 185 | default: 186 | return value; 187 | } 188 | } 189 | 190 | var pattern = ""; 191 | var retValue = ""; 192 | var unparsedRest = ""; 193 | /* 194 | Issue 1 - variable scope issue in format.date 195 | Thanks jakemonO 196 | */ 197 | for (var i = 0; i < format.length; i++) { 198 | var currentPattern = format.charAt(i); 199 | 200 | pattern += currentPattern; 201 | unparsedRest = ""; 202 | switch (pattern) { 203 | case "ddd": 204 | retValue += strDay(dayOfWeek); 205 | pattern = ""; 206 | break; 207 | case "dd": 208 | if (format.charAt(i + 1) == "d") { 209 | break; 210 | } 211 | if (String(dayOfMonth).length === 1) { 212 | dayOfMonth = '0' + dayOfMonth; 213 | } 214 | retValue += dayOfMonth; 215 | pattern = ""; 216 | break; 217 | case "d": 218 | if (format.charAt(i + 1) == "d") { 219 | break; 220 | } 221 | retValue += parseInt(dayOfMonth, 10); 222 | pattern = ""; 223 | break; 224 | case "MMMM": 225 | retValue += strLongMonth(month); 226 | pattern = ""; 227 | break; 228 | case "MMM": 229 | if (format.charAt(i + 1) === "M") { 230 | break; 231 | } 232 | retValue += strMonth(month); 233 | pattern = ""; 234 | break; 235 | case "MM": 236 | if (format.charAt(i + 1) == "M") { 237 | break; 238 | } 239 | if (String(month).length === 1) { 240 | month = '0' + month; 241 | } 242 | retValue += month; 243 | pattern = ""; 244 | break; 245 | case "M": 246 | if (format.charAt(i + 1) == "M") { 247 | break; 248 | } 249 | retValue += parseInt(month, 10); 250 | pattern = ""; 251 | break; 252 | case "yyyy": 253 | retValue += year; 254 | pattern = ""; 255 | break; 256 | case "yy": 257 | if (format.charAt(i + 1) == "y" && 258 | format.charAt(i + 2) == "y") { 259 | break; 260 | } 261 | retValue += String(year).slice(-2); 262 | pattern = ""; 263 | break; 264 | case "HH": 265 | case "hh": 266 | /* time.hour is "00" as string == is used instead of === */ 267 | var hour = (time.hour == 0 ? 12 : time.hour < 13 ? time.hour : time.hour - 12); 268 | hour = hour < 10 ? '0' + hour : hour; 269 | retValue += hour; 270 | pattern = ""; 271 | break; 272 | case "h": 273 | if (format.charAt(i + 1) == "h") { 274 | break; 275 | } 276 | var hour = (time.hour == 0 ? 12 : time.hour < 13 ? time.hour : time.hour - 12); 277 | retValue += hour; 278 | // Fixing issue https://github.com/phstc/jquery-dateFormat/issues/21 279 | // retValue = parseInt(retValue, 10); 280 | pattern = ""; 281 | break; 282 | case "mm": 283 | retValue += (time.minute < 10 ? '0' : '') + time.minute; 284 | pattern = ""; 285 | break; 286 | case "ss": 287 | /* ensure only seconds are added to the return string */ 288 | retValue += (time.second < 10 ? '0' : '') + time.second.substring(0, 2); 289 | pattern = ""; 290 | break; 291 | case "SSS": 292 | retValue += time.millis.substring(0, 3); 293 | pattern = ""; 294 | break; 295 | case "a": 296 | retValue += time.hour >= 12 ? "PM" : "AM"; 297 | pattern = ""; 298 | break; 299 | case " ": 300 | retValue += currentPattern; 301 | pattern = ""; 302 | break; 303 | case ".": 304 | retValue += currentPattern; 305 | pattern = ""; 306 | break; 307 | case "/": 308 | retValue += currentPattern; 309 | pattern = ""; 310 | break; 311 | case ":": 312 | retValue += currentPattern; 313 | pattern = ""; 314 | break; 315 | default: 316 | if (pattern.length === 2 && pattern.indexOf("y") !== 0 && pattern != "SS") { 317 | retValue += pattern.substring(0, 1); 318 | pattern = pattern.substring(1, 2); 319 | } else if ((pattern.length === 3 && pattern.indexOf("yyy") === -1)) { 320 | pattern = ""; 321 | } else { 322 | unparsedRest = pattern; 323 | } 324 | } 325 | } 326 | retValue += unparsedRest; 327 | return retValue; 328 | } catch (e) { 329 | console.log(e); 330 | return value; 331 | } 332 | } 333 | }; 334 | }()); 335 | }(jQuery)); -------------------------------------------------------------------------------- /lib/jquery.tagit.css: -------------------------------------------------------------------------------- 1 | ul.tagit { 2 | padding: 1px 5px; 3 | overflow: auto; 4 | margin-left: inherit; /* usually we don't want the regular ul margins. */ 5 | margin-right: inherit; 6 | } 7 | ul.tagit li { 8 | display: block; 9 | float: left; 10 | margin: 2px 5px 2px 0; 11 | } 12 | ul.tagit li.tagit-choice { 13 | position: relative; 14 | line-height: inherit; 15 | } 16 | 17 | ul.tagit li.tagit-choice-read-only { 18 | padding: .2em .5em .2em .5em; 19 | } 20 | 21 | ul.tagit li.tagit-choice-editable { 22 | padding: .2em 18px .2em .5em; 23 | } 24 | 25 | ul.tagit li.tagit-new { 26 | padding: .25em 4px .25em 0; 27 | } 28 | 29 | ul.tagit li.tagit-choice a.tagit-label { 30 | cursor: pointer; 31 | text-decoration: none; 32 | } 33 | ul.tagit li.tagit-choice .tagit-close { 34 | cursor: pointer; 35 | position: absolute; 36 | right: .1em; 37 | top: 50%; 38 | margin-top: -8px; 39 | line-height: 17px; 40 | } 41 | 42 | /* used for some custom themes that don't need image icons */ 43 | ul.tagit li.tagit-choice .tagit-close .text-icon { 44 | display: none; 45 | } 46 | 47 | ul.tagit li.tagit-choice input { 48 | display: block; 49 | float: left; 50 | margin: 2px 5px 2px 0; 51 | } 52 | ul.tagit input[type="text"] { 53 | -moz-box-sizing: border-box; 54 | -webkit-box-sizing: border-box; 55 | box-sizing: border-box; 56 | 57 | -moz-box-shadow: none; 58 | -webkit-box-shadow: none; 59 | box-shadow: none; 60 | 61 | border: none; 62 | margin: 0; 63 | padding: 0; 64 | width: inherit; 65 | background-color: inherit; 66 | outline: none; 67 | } 68 | -------------------------------------------------------------------------------- /lib/tag-it.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI Tag-it! 3 | * 4 | * @version v2.0 (06/2011) 5 | * 6 | * Copyright 2011, Levy Carneiro Jr. 7 | * Released under the MIT license. 8 | * http://aehlke.github.com/tag-it/LICENSE 9 | * 10 | * Homepage: 11 | * http://aehlke.github.com/tag-it/ 12 | * 13 | * Authors: 14 | * Levy Carneiro Jr. 15 | * Martin Rehfeld 16 | * Tobias Schmidt 17 | * Skylar Challand 18 | * Alex Ehlke 19 | * 20 | * Maintainer: 21 | * Alex Ehlke - Twitter: @aehlke 22 | * 23 | * Dependencies: 24 | * jQuery v1.4+ 25 | * jQuery UI v1.8+ 26 | */ 27 | (function($) { 28 | 29 | $.widget('ui.tagit', { 30 | options: { 31 | allowDuplicates : false, 32 | caseSensitive : true, 33 | fieldName : 'tags', 34 | placeholderText : null, // Sets `placeholder` attr on input field. 35 | readOnly : false, // Disables editing. 36 | removeConfirmation: false, // Require confirmation to remove tags. 37 | tagLimit : null, // Max number of tags allowed (null for unlimited). 38 | 39 | // Used for autocomplete, unless you override `autocomplete.source`. 40 | availableTags : [], 41 | 42 | // Use to override or add any options to the autocomplete widget. 43 | // 44 | // By default, autocomplete.source will map to availableTags, 45 | // unless overridden. 46 | autocomplete: {}, 47 | 48 | // Shows autocomplete before the user even types anything. 49 | showAutocompleteOnFocus: false, 50 | 51 | // When enabled, quotes are unneccesary for inputting multi-word tags. 52 | allowSpaces: false, 53 | 54 | // The below options are for using a single field instead of several 55 | // for our form values. 56 | // 57 | // When enabled, will use a single hidden field for the form, 58 | // rather than one per tag. It will delimit tags in the field 59 | // with singleFieldDelimiter. 60 | // 61 | // The easiest way to use singleField is to just instantiate tag-it 62 | // on an INPUT element, in which case singleField is automatically 63 | // set to true, and singleFieldNode is set to that element. This 64 | // way, you don't need to fiddle with these options. 65 | singleField: false, 66 | 67 | // This is just used when preloading data from the field, and for 68 | // populating the field with delimited tags as the user adds them. 69 | singleFieldDelimiter: ',', 70 | 71 | // Set this to an input DOM node to use an existing form field. 72 | // Any text in it will be erased on init. But it will be 73 | // populated with the text of tags as they are created, 74 | // delimited by singleFieldDelimiter. 75 | // 76 | // If this is not set, we create an input node for it, 77 | // with the name given in settings.fieldName. 78 | singleFieldNode: null, 79 | 80 | // Whether to animate tag removals or not. 81 | animate: true, 82 | 83 | // Optionally set a tabindex attribute on the input that gets 84 | // created for tag-it. 85 | tabIndex: null, 86 | 87 | // Event callbacks. 88 | beforeTagAdded : null, 89 | afterTagAdded : null, 90 | 91 | beforeTagRemoved : null, 92 | afterTagRemoved : null, 93 | 94 | onTagClicked : null, 95 | onTagLimitExceeded : null, 96 | 97 | 98 | // DEPRECATED: 99 | // 100 | // /!\ These event callbacks are deprecated and WILL BE REMOVED at some 101 | // point in the future. They're here for backwards-compatibility. 102 | // Use the above before/after event callbacks instead. 103 | onTagAdded : null, 104 | onTagRemoved: null, 105 | // `autocomplete.source` is the replacement for tagSource. 106 | tagSource: null 107 | // Do not use the above deprecated options. 108 | }, 109 | 110 | _create: function() { 111 | // for handling static scoping inside callbacks 112 | var that = this; 113 | 114 | // There are 2 kinds of DOM nodes this widget can be instantiated on: 115 | // 1. UL, OL, or some element containing either of these. 116 | // 2. INPUT, in which case 'singleField' is overridden to true, 117 | // a UL is created and the INPUT is hidden. 118 | if (this.element.is('input')) { 119 | this.tagList = $('
        ').insertAfter(this.element); 120 | this.options.singleField = true; 121 | this.options.singleFieldNode = this.element; 122 | this.element.css('display', 'none'); 123 | } else { 124 | this.tagList = this.element.find('ul, ol').andSelf().last(); 125 | } 126 | 127 | this.tagInput = $('').addClass('ui-widget-content'); 128 | 129 | if (this.options.readOnly) this.tagInput.attr('disabled', 'disabled'); 130 | 131 | if (this.options.tabIndex) { 132 | this.tagInput.attr('tabindex', this.options.tabIndex); 133 | } 134 | 135 | if (this.options.placeholderText) { 136 | this.tagInput.attr('placeholder', this.options.placeholderText); 137 | } 138 | 139 | if (!this.options.autocomplete.source) { 140 | this.options.autocomplete.source = function(search, showChoices) { 141 | var filter = search.term.toLowerCase(); 142 | var choices = $.grep(this.options.availableTags, function(element) { 143 | // Only match autocomplete options that begin with the search term. 144 | // (Case insensitive.) 145 | return (element.toLowerCase().indexOf(filter) === 0); 146 | }); 147 | if (!this.options.allowDuplicates) { 148 | choices = this._subtractArray(choices, this.assignedTags()); 149 | } 150 | showChoices(choices); 151 | }; 152 | } 153 | 154 | if (this.options.showAutocompleteOnFocus) { 155 | this.tagInput.focus(function(event, ui) { 156 | that._showAutocomplete(); 157 | }); 158 | 159 | if (typeof this.options.autocomplete.minLength === 'undefined') { 160 | this.options.autocomplete.minLength = 0; 161 | } 162 | } 163 | 164 | // Bind autocomplete.source callback functions to this context. 165 | if ($.isFunction(this.options.autocomplete.source)) { 166 | this.options.autocomplete.source = $.proxy(this.options.autocomplete.source, this); 167 | } 168 | 169 | // DEPRECATED. 170 | if ($.isFunction(this.options.tagSource)) { 171 | this.options.tagSource = $.proxy(this.options.tagSource, this); 172 | } 173 | 174 | this.tagList 175 | .addClass('tagit') 176 | .addClass('ui-widget ui-widget-content ui-corner-all') 177 | // Create the input field. 178 | .append($('
      • ').append(this.tagInput)) 179 | .click(function(e) { 180 | var target = $(e.target); 181 | if (target.hasClass('tagit-label')) { 182 | var tag = target.closest('.tagit-choice'); 183 | if (!tag.hasClass('removed')) { 184 | that._trigger('onTagClicked', e, {tag: tag, tagLabel: that.tagLabel(tag)}); 185 | } 186 | } else { 187 | // Sets the focus() to the input field, if the user 188 | // clicks anywhere inside the UL. This is needed 189 | // because the input field needs to be of a small size. 190 | that.tagInput.focus(); 191 | } 192 | }); 193 | 194 | // Single field support. 195 | var addedExistingFromSingleFieldNode = false; 196 | if (this.options.singleField) { 197 | if (this.options.singleFieldNode) { 198 | // Add existing tags from the input field. 199 | var node = $(this.options.singleFieldNode); 200 | var tags = node.val().split(this.options.singleFieldDelimiter); 201 | node.val(''); 202 | $.each(tags, function(index, tag) { 203 | that.createTag(tag, null, true); 204 | addedExistingFromSingleFieldNode = true; 205 | }); 206 | } else { 207 | // Create our single field input after our list. 208 | this.options.singleFieldNode = $(''); 209 | this.tagList.after(this.options.singleFieldNode); 210 | } 211 | } 212 | 213 | // Add existing tags from the list, if any. 214 | if (!addedExistingFromSingleFieldNode) { 215 | this.tagList.children('li').each(function() { 216 | if (!$(this).hasClass('tagit-new')) { 217 | that.createTag($(this).text(), $(this).attr('class'), true); 218 | $(this).remove(); 219 | } 220 | }); 221 | } 222 | 223 | // Events. 224 | this.tagInput 225 | .keydown(function(event) { 226 | // Backspace is not detected within a keypress, so it must use keydown. 227 | if (event.which == $.ui.keyCode.BACKSPACE && that.tagInput.val() === '') { 228 | var tag = that._lastTag(); 229 | if (!that.options.removeConfirmation || tag.hasClass('remove')) { 230 | // When backspace is pressed, the last tag is deleted. 231 | that.removeTag(tag); 232 | } else if (that.options.removeConfirmation) { 233 | tag.addClass('remove ui-state-highlight'); 234 | } 235 | } else if (that.options.removeConfirmation) { 236 | that._lastTag().removeClass('remove ui-state-highlight'); 237 | } 238 | 239 | // Comma/Space/Enter are all valid delimiters for new tags, 240 | // except when there is an open quote or if setting allowSpaces = true. 241 | // Tab will also create a tag, unless the tag input is empty, 242 | // in which case it isn't caught. 243 | if ( 244 | event.which === $.ui.keyCode.COMMA || 245 | event.which === $.ui.keyCode.ENTER || 246 | ( 247 | event.which == $.ui.keyCode.TAB && 248 | that.tagInput.val() !== '' 249 | ) || 250 | ( 251 | event.which == $.ui.keyCode.SPACE && 252 | that.options.allowSpaces !== true && 253 | ( 254 | $.trim(that.tagInput.val()).replace( /^s*/, '' ).charAt(0) != '"' || 255 | ( 256 | $.trim(that.tagInput.val()).charAt(0) == '"' && 257 | $.trim(that.tagInput.val()).charAt($.trim(that.tagInput.val()).length - 1) == '"' && 258 | $.trim(that.tagInput.val()).length - 1 !== 0 259 | ) 260 | ) 261 | ) 262 | ) { 263 | // Enter submits the form if there's no text in the input. 264 | if (!(event.which === $.ui.keyCode.ENTER && that.tagInput.val() === '')) { 265 | event.preventDefault(); 266 | } 267 | 268 | // Autocomplete will create its own tag from a selection and close automatically. 269 | if (!that.tagInput.data('autocomplete-open')) { 270 | that.createTag(that._cleanedInput()); 271 | } 272 | } 273 | }).blur(function(e){ 274 | // Create a tag when the element loses focus. 275 | // If autocomplete is enabled and suggestion was clicked, don't add it. 276 | if (!that.tagInput.data('autocomplete-open')) { 277 | that.createTag(that._cleanedInput()); 278 | } 279 | }); 280 | 281 | // Autocomplete. 282 | if (this.options.availableTags || this.options.tagSource || this.options.autocomplete.source) { 283 | var autocompleteOptions = { 284 | select: function(event, ui) { 285 | that.createTag(ui.item); 286 | // Preventing the tag input to be updated with the chosen value. 287 | return false; 288 | }, 289 | open: function(event, ui) { 290 | that.tagInput.data('autocomplete-open', true); 291 | }, 292 | close: function(event, ui) { 293 | that.tagInput.data('autocomplete-open', false); 294 | } 295 | }; 296 | $.extend(autocompleteOptions, this.options.autocomplete); 297 | 298 | // tagSource is deprecated, but takes precedence here since autocomplete.source is set by default, 299 | // while tagSource is left null by default. 300 | autocompleteOptions.source = this.options.tagSource || autocompleteOptions.source; 301 | 302 | this.tagInput.autocomplete(autocompleteOptions); 303 | } 304 | }, 305 | 306 | _cleanedInput: function() { 307 | // Returns the contents of the tag input, cleaned and ready to be passed to createTag 308 | return $.trim(this.tagInput.val().replace(/^"(.*)"$/, '$1')); 309 | }, 310 | 311 | _lastTag: function() { 312 | return this.tagList.find('.tagit-choice:last:not(.removed)'); 313 | }, 314 | 315 | _tags: function() { 316 | return this.tagList.find('.tagit-choice:not(.removed)'); 317 | }, 318 | 319 | assignedTags: function() { 320 | // Returns an array of tag string values 321 | var that = this; 322 | var tags = []; 323 | if (this.options.singleField) { 324 | tags = $(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter); 325 | if (tags[0] === '') { 326 | tags = []; 327 | } 328 | } else { 329 | this._tags().each(function() { 330 | tags.push(that.tagLabel(this)); 331 | }); 332 | } 333 | return tags; 334 | }, 335 | 336 | /** 337 | * @return a list of objects (if available), otherwise this returns the same as assignedTags 338 | */ 339 | assignedObjects: function () { 340 | var that = this; 341 | var tags = []; 342 | this._tags().each(function() { 343 | if($(this).data().value) 344 | tags.push($(this).data().value); 345 | else 346 | tags.push(that.tagLabel(this)); 347 | }); 348 | return tags; 349 | }, 350 | 351 | _updateSingleTagsField: function(tags) { 352 | // Takes a list of tag string values, updates this.options.singleFieldNode.val to the tags delimited by this.options.singleFieldDelimiter 353 | $(this.options.singleFieldNode).val(tags.join(this.options.singleFieldDelimiter)).trigger('change'); 354 | }, 355 | 356 | _subtractArray: function(a1, a2) { 357 | var result = []; 358 | for (var i = 0; i < a1.length; i++) { 359 | if ($.inArray(a1[i], a2) == -1) { 360 | result.push(a1[i]); 361 | } 362 | } 363 | return result; 364 | }, 365 | 366 | tagLabel: function(tag) { 367 | // Returns the tag's string label. 368 | if (this.options.singleField) { 369 | return $(tag).find('.tagit-label:first').text(); 370 | } else { 371 | return $(tag).find('input:first').val(); 372 | } 373 | }, 374 | 375 | _showAutocomplete: function() { 376 | this.tagInput.autocomplete('search', ''); 377 | }, 378 | 379 | _findTagByLabel: function(name) { 380 | var that = this; 381 | var tag = null; 382 | this._tags().each(function(i) { 383 | if (that._formatStr(name) == that._formatStr(that.tagLabel(this))) { 384 | tag = $(this); 385 | return false; 386 | } 387 | }); 388 | return tag; 389 | }, 390 | 391 | _isNew: function(name) { 392 | return !this._findTagByLabel(name); 393 | }, 394 | 395 | _formatStr: function(str) { 396 | if (this.options.caseSensitive) { 397 | return str; 398 | } 399 | return $.trim(str.toLowerCase()); 400 | }, 401 | 402 | _effectExists: function(name) { 403 | return Boolean($.effects && ($.effects[name] || ($.effects.effect && $.effects.effect[name]))); 404 | }, 405 | 406 | /** 407 | * @param value either a string (the tagLabel) or an object: { label: "label", data: "attachdata" } 408 | */ 409 | createTag: function(value, additionalClass, duringInitialization) { 410 | var that = this; 411 | var data = null; 412 | // allow value to be an object - take the label then 413 | if($.isPlainObject(value)) { 414 | data = value.data; 415 | if(value.value) 416 | value = value.value; 417 | else 418 | value = value.label; 419 | } 420 | 421 | value = $.trim(value); 422 | 423 | if(this.options.preprocessTag) { 424 | value = this.options.preprocessTag(value, data); 425 | } 426 | 427 | if (value === '' || !value) { 428 | return false; 429 | } 430 | 431 | if (!this.options.allowDuplicates && !this._isNew(value)) { 432 | var existingTag = this._findTagByLabel(value); 433 | if (this._trigger('onTagExists', null, { 434 | existingTag: existingTag, 435 | duringInitialization: duringInitialization 436 | }) !== false) { 437 | if (this._effectExists('highlight')) { 438 | existingTag.effect('highlight'); 439 | } 440 | } 441 | return false; 442 | } 443 | 444 | if (this.options.tagLimit && this._tags().length >= this.options.tagLimit) { 445 | this._trigger('onTagLimitExceeded', null, {duringInitialization: duringInitialization}); 446 | return false; 447 | } 448 | 449 | var label = $(this.options.onTagClicked ? '' : '').text(value); 450 | 451 | // Create tag. 452 | var tag = $('
      • ') 453 | .addClass('tagit-choice ui-widget-content ui-state-default ui-corner-all') 454 | .addClass(additionalClass) 455 | .append(label); 456 | 457 | if(data !== null) { 458 | tag.data().value = data; 459 | } 460 | 461 | if (this.options.readOnly){ 462 | tag.addClass('tagit-choice-read-only'); 463 | } else { 464 | tag.addClass('tagit-choice-editable'); 465 | // Button for removing the tag. 466 | var removeTagIcon = $('') 467 | .addClass('ui-icon ui-icon-close'); 468 | var removeTag = $('\xd7') // \xd7 is an X 469 | .addClass('tagit-close') 470 | .append(removeTagIcon) 471 | .click(function(e) { 472 | // Removes a tag when the little 'x' is clicked. 473 | that.removeTag(tag); 474 | }); 475 | tag.append(removeTag); 476 | } 477 | 478 | // Unless options.singleField is set, each tag has a hidden input field inline. 479 | if (!this.options.singleField) { 480 | var escapedValue = label.html(); 481 | tag.append(''); 482 | } 483 | 484 | if (this._trigger('beforeTagAdded', null, { 485 | tag: tag, 486 | tagLabel: this.tagLabel(tag), 487 | duringInitialization: duringInitialization 488 | }) === false) { 489 | return; 490 | } 491 | 492 | if (this.options.singleField) { 493 | var tags = this.assignedTags(); 494 | tags.push(value); 495 | this._updateSingleTagsField(tags); 496 | } 497 | 498 | // DEPRECATED. 499 | this._trigger('onTagAdded', null, tag); 500 | 501 | this.tagInput.val(''); 502 | 503 | // Insert tag. 504 | this.tagInput.parent().before(tag); 505 | 506 | this._trigger('afterTagAdded', null, { 507 | tag: tag, 508 | tagLabel: this.tagLabel(tag), 509 | duringInitialization: duringInitialization 510 | }); 511 | 512 | this.element.trigger("change"); 513 | 514 | if (this.options.showAutocompleteOnFocus && !duringInitialization) { 515 | setTimeout(function () { that._showAutocomplete(); }, 0); 516 | } 517 | }, 518 | 519 | removeTag: function(tag, animate) { 520 | animate = typeof animate === 'undefined' ? this.options.animate : animate; 521 | 522 | tag = $(tag); 523 | 524 | // DEPRECATED. 525 | this._trigger('onTagRemoved', null, tag); 526 | 527 | if (this._trigger('beforeTagRemoved', null, {tag: tag, tagLabel: this.tagLabel(tag)}) === false) { 528 | return; 529 | } 530 | 531 | if (this.options.singleField) { 532 | var tags = this.assignedTags(); 533 | var removedTagLabel = this.tagLabel(tag); 534 | tags = $.grep(tags, function(el){ 535 | return el != removedTagLabel; 536 | }); 537 | this._updateSingleTagsField(tags); 538 | } 539 | 540 | if (animate) { 541 | tag.addClass('removed'); // Excludes this tag from _tags. 542 | var hide_args = this._effectExists('blind') ? ['blind', {direction: 'horizontal'}, 'fast'] : ['fast']; 543 | 544 | var thisTag = this; 545 | hide_args.push(function() { 546 | tag.remove(); 547 | thisTag._trigger('afterTagRemoved', null, {tag: tag, tagLabel: thisTag.tagLabel(tag)}); 548 | this.element.trigger("change"); 549 | }); 550 | 551 | tag.fadeOut('fast').hide.apply(tag, hide_args).dequeue(); 552 | } else { 553 | tag.remove(); 554 | this._trigger('afterTagRemoved', null, {tag: tag, tagLabel: this.tagLabel(tag)}); 555 | this.element.trigger("change"); 556 | } 557 | 558 | }, 559 | 560 | removeTagByLabel: function(tagLabel, animate) { 561 | var toRemove = this._findTagByLabel(tagLabel); 562 | if (!toRemove) { 563 | throw "No such tag exists with the name '" + tagLabel + "'"; 564 | } 565 | this.removeTag(toRemove, animate); 566 | }, 567 | 568 | removeAll: function() { 569 | // Removes all tags. 570 | var that = this; 571 | this._tags().each(function(index, tag) { 572 | that.removeTag(tag, false); 573 | }); 574 | } 575 | 576 | }); 577 | })(jQuery); 578 | 579 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery.jsForm", 3 | "title": "jQuery JSON Form - jsForm", 4 | "description": "jQuery based form library that allows you to handle data within a js object (i.e. JSON) with html forms.\nYou can modify all fields/properties by simply creating a html form with the correct naming schema (MVVM Pattern).\n\nThe main features of this library are:\n Full standard html with data available in a js object\n Update an existing js object with changes done within a form\n Fully internationalizable with number format, currency and date formatting\n Provide basic functions for formatting (i.e. date/time, money) using html markup\n Provide form validation functionality\n handle collections (arrays) with subobjects\n provides helper methods to handle array manipulation (add new entry/remove an entry, sorting) using only html markup\n Can be used in connection with an autocomplete function to add new array objects", 5 | "version": "1.6.0", 6 | "homepage": "https://github.com/corinis/jsForm", 7 | "author": { 8 | "name": "Niko Berger", 9 | "url": "http://www.gargan.org/" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/corinis/jsForm.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/corinis/jsForm/issues" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "MIT, GPL, LGPL", 21 | "url": "https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt" 22 | } 23 | ], 24 | "dependencies": { 25 | "grunt": "^1.4.1", 26 | "grunt-compare-size": "^0.4.2", 27 | "grunt-contrib-concat": "^1.0.1", 28 | "grunt-contrib-jshint": "^3.2.0", 29 | "grunt-contrib-uglify": "^5.0.1", 30 | "grunt-contrib-watch": "^1.1.0", 31 | "jshint": "^2.13.6" 32 | }, 33 | "keywords": [] 34 | } 35 | -------------------------------------------------------------------------------- /sample-conditional.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 34 | 38 | 39 | 40 |

        Conditional Form Test

        41 | This form is all about sho/hiding parts of the dom based on data and input 42 |
        43 | Name:
        44 | activate Age
        45 |
        46 | Age:
        47 |
        48 |
        49 | 54 |
        55 | links are only shown if name or active is set 56 | Links 57 |
          58 |
        • links.description Link:
        • 59 |
        60 |
        61 |
        62 | Additional field: 63 | Fill something in the addional field and apply conditionals to see quips 64 |
        65 | Quips 66 |
          67 |
        • 68 |
        69 |
        70 |
        71 |
        72 | 73 | 74 | -------------------------------------------------------------------------------- /sample-connect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 31 | 32 | 33 |

        Connect two Forms into one

        34 | In this example, the form is split into two dom elements, with no direct connection, but they are filled with one jsForm call and also put together correctly. 35 |
        36 | Name:
        37 | active
        38 | Age:
        39 | 44 |
        45 | Links 46 |
          47 |
        • links.description Link:
        • 48 |
        49 |
        50 |
        51 | Additional field: 52 | 53 |
        54 | 55 |

        The second part

        56 |
        57 |
        58 |
        59 | Quips 60 |
          61 |
        • 62 |
        63 |
        64 |
        65 |
        66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /sample-dataHandler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 49 | 50 | 51 |

        Simple Form Test

        52 |
        53 | Name:
        54 |
        55 |
        56 | Links 57 |
          58 |
        • links.description Link:
        • 59 |
        60 |
        61 |
        62 | Additional field: 63 | 64 |
        65 | 66 | 67 | -------------------------------------------------------------------------------- /sample-multiselect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 48 | 49 | 50 |

        Multi Select test

        51 |
        52 | Name:
        53 | 54 | 55 | 56 | 57 |
        58 | 59 | 60 |
          61 |
        • Austria
        • 62 |
        • Australia
        • 63 |
        • Germany
        • 64 |
        • UK
        • 65 |
        • USA
        • 66 |
        67 | 68 | 69 | Click to select: 70 |
          71 |
        • User 1
        • 72 |
        • User 2
        • 73 |
        • User 3
        • 74 |
        • User 4
        • 75 |
        • User 5
        • 76 |
        77 | 78 |
        79 | 80 | 81 | -------------------------------------------------------------------------------- /sample-object.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 76 | 77 | 78 | 79 |

        Simple Form Test - working with objects

        80 |
        81 | 82 |
          83 |
        • 84 | 85 |
          86 |
          87 | props: +
          88 |
          89 |
          90 | =props.value - 91 |
          92 |
          93 | 94 |
        • 95 |
        96 | 97 |
        98 |
        99 | groups.name
        100 | props: + 101 |
        102 |
        103 | =props.value - 104 |
        105 |
        106 |
        107 |
        108 |
        109 | 110 | 111 | -------------------------------------------------------------------------------- /sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 33 | 34 | 35 |

        Simple Form Test

        36 |
        37 | Name:
        38 | active
        39 | Age:
        40 |
        41 | 46 |
        47 | Cool
        48 | Hot
        49 | Warm
        50 |
        51 | Links 52 |
          53 |
        • links.description Link:
        • 54 |
        55 |
        56 |
        57 | Additional field: 58 | 59 |
        60 | Quips 61 |
          62 |
        • 63 |
        64 |
        65 |
        66 | 67 |
        68 | Favorite Colors 69 | Red 70 | Green 71 | Blue 72 |
        73 | 74 |
        75 | 76 | 77 | -------------------------------------------------------------------------------- /sortable-editable-sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 47 | 48 | 49 |

        Multi level array testform

        50 |
        51 | Name:
        52 | active
        53 | 58 |
        59 |
        60 | Group 61 | Name 62 |
          63 |
        • tasks.$idx
        • 64 |
        65 | 66 | 67 |
        68 |
        69 | 70 | 71 |
        72 | 73 | 74 | -------------------------------------------------------------------------------- /sortable-sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 47 | 48 | 49 |

        Multi level array testform

        50 |
        51 | Name:
        52 | active
        53 | 58 |
        59 |
        60 | Group: groups.name 61 |
          62 |
        • tasks.$idx
        • 63 |
        64 | 65 |
        66 |
        67 |
        68 | 69 | 70 | -------------------------------------------------------------------------------- /src/jquery.jsForm.d.ts: -------------------------------------------------------------------------------- 1 | interface JQuery { 2 | /** 3 | * Initialize jsform with default configuration on a node 4 | */ 5 | jsForm(): JQuery; 6 | jsForm(func:any): JQuery; 7 | jsForm(func:string, opts:any): JQuery; 8 | } -------------------------------------------------------------------------------- /test/jquery.jsForm.performance.js: -------------------------------------------------------------------------------- 1 | module('jquery.jsForm.js'); 2 | 3 | 4 | test("predefined form", function(){ 5 | // html code for basic form 6 | var basicForm = $('
        \n'+ 7 | '\n'+ 8 | '\n'+ 9 | '\n'+ 10 | '\n'+ 11 | '\n'+ 12 | '
        \n'+ 13 | '
        \n'+ 14 | ' \n'+ 15 | ' \n'+ 16 | ' \n'+ 17 | ' \n'+ 18 | ' \n'+ 19 | '
        '+ 20 | '
        \n'+ 21 | '
        \n'+ 22 | '
        \n'+ 23 | ' \n'+ 24 | ' \n'+ 25 | ' \n'+ 26 | ' \n'+ 27 | ' \n'+ 28 | '
        '+ 29 | '
        \n'+ 30 | '
        \n'+ 31 | '
        \n'+ 32 | ' \n'+ 33 | ' \n'+ 34 | ' \n'+ 35 | ' \n'+ 36 | ' \n'+ 37 | '
        '+ 38 | '
        \n'+ 39 | '
        '); 40 | 41 | $("body").append(basicForm); 42 | var REPEAT = 20; 43 | 44 | var data = {groups1:[], groups2:[], groups3:[]}; 45 | // basedata 46 | for(var i = 1; i <= 5; i++ ) { 47 | data["input" + i] = "LOREM IPSUM " + i; 48 | } 49 | //collection 50 | for(var j = 0; j < REPEAT; j++ ) { 51 | var obj = {}; 52 | for(var i = 1; i <= 5; i++ ) 53 | obj["input" + i] = "LOREM IPSUM " + i; 54 | 55 | data.groups1.push(obj); 56 | } 57 | //collection 58 | for(var j = 0; j < REPEAT; j++ ) { 59 | var obj = {}; 60 | for(var i = 1; i <= 5; i++ ) 61 | obj["input" + i] = "LOREM IPSUM DOLOR " + i; 62 | data.groups2.push(obj); 63 | } 64 | //collection 65 | for(var j = 0; j < REPEAT; j++ ) { 66 | var obj = {}; 67 | for(var i = 1; i <= 5; i++ ) 68 | obj["input" + i] = "val2"; 69 | data.groups3.push(obj); 70 | } 71 | 72 | var start = new Date().getTime(); 73 | 74 | // default init: prefix = data 75 | basicForm.jsForm({ 76 | data: data 77 | }); 78 | 79 | test("time for filling", function(){ 80 | ok(true, (new Date().getTime() - start) + "ms"); 81 | }); 82 | 83 | 84 | equal(basicForm.jsForm("equals", data), true, "equals"); 85 | 86 | var RUNS = 5; 87 | start = new Date().getTime(); 88 | 89 | for(var i = 0; i < RUNS; i++) { 90 | basicForm.jsForm("get"); 91 | } 92 | 93 | test("time for getting", function(){ 94 | ok(true, (new Date().getTime() - start)/RUNS + "ms"); 95 | }); 96 | 97 | 98 | start = new Date().getTime(); 99 | 100 | for(var i = 0; i < RUNS; i++) { 101 | basicForm.jsForm("changed"); 102 | } 103 | 104 | test("time for changed", function(){ 105 | ok(true, (new Date().getTime() - start)/RUNS + "ms"); 106 | }); 107 | 108 | basicForm.remove(); 109 | }); 110 | 111 | -------------------------------------------------------------------------------- /test/jquery.jsForm.test.js: -------------------------------------------------------------------------------- 1 | module('jquery.jsForm.js'); 2 | 3 | test("data formatting", function(){ 4 | equal($.jsFormControls.Format.humanTime(320), "320ms", "milliseconds"); 5 | equal($.jsFormControls.Format.humanTime(6000), "6s", "milliseconds"); 6 | equal($.jsFormControls.Format.humanTime(4320), "4s", "seconds"); 7 | equal($.jsFormControls.Format.humanTime(184200), "3m 4s", "minutest and seconds"); 8 | equal($.jsFormControls.Format.humanTime(5*3600000 + 32*60000 + 3580), "5h 32m", "hours and minutes"); 9 | equal($.jsFormControls.Format.decimal(51234.1234), "51,234.12", "decimal"); 10 | equal($.jsFormControls.Format.decimal(4.1234), "4.12", "decimal"); 11 | }); 12 | 13 | test("internationalization", function(){ 14 | // default: english 15 | equal($.jsFormControls.Format.decimal(1123456.54), "1,123,456.54", "default decimal"); 16 | //$.jsFormControls.Format.dateTime(cdata) 17 | //$.jsFormControls.Format.date(cdata) 18 | equal($.jsFormControls.Format.asNumber("1,123,456.54"), 1123456.54, "decimal to number"); 19 | 20 | // set english locale 21 | $(document).data("i18n", { 22 | date: { 23 | "format": "M/d/yy h:mm a", 24 | "monthsFull":["January","February","March","April","May","June","July","August","September","October","November","December"], 25 | "monthsShort": ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"], 26 | "daysFull": ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], 27 | "daysShort": ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"], 28 | "timeFormat": "h:mm a", 29 | "longDateFormat": "EEEE, MMMM d, yyyy", 30 | "shortDateFormat": "M/d/yy" 31 | }, 32 | number: { 33 | "format": "#,##0.###", 34 | "groupingSeparator": ".", 35 | "decimalSeparator": "," 36 | }, 37 | currency: { 38 | "prefix": "", 39 | "suffix": " €", 40 | "fractionDigits": 2 41 | } 42 | }); 43 | 44 | 45 | equal($.jsFormControls.Format.decimal(1123456.54), "1.123.456,54", "decimal (de)"); 46 | //$.jsFormControls.Format.dateTime(cdata) 47 | //$.jsFormControls.Format.date(cdata) 48 | equal($.jsFormControls.Format.currency(1123456.54), "1.123.456,54 €", "currency (€ suffix)"); 49 | equal($.jsFormControls.Format.asNumber("1.123.456,54 €"), 1123456.54, "currency number (€ suffix)"); 50 | 51 | // set german locale 52 | $(document).data("i18n", { 53 | date: { 54 | "format": "dd.MM.yy HH:mm", 55 | "monthsFull":["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"], 56 | "monthsShort": ["Jan","Feb","Mrz","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"], 57 | "daysFull": ["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"], 58 | "daysShort": ["So","Mo","Di","Mi","Do","Fr","Sa"], 59 | "timeFormat": "HH:mm", 60 | "longDateFormat": "EEEE, d. MMMM yyyy", 61 | "shortDateFormat": "dd.MM.yy" 62 | }, 63 | number: { 64 | "format": "#,##0.###", 65 | "groupingSeparator": ",", 66 | "decimalSeparator": "." 67 | }, 68 | currency: { 69 | "prefix": "$", 70 | "suffix": "", 71 | "fractionDigits": 2 72 | } 73 | }); 74 | equal($.jsFormControls.Format.decimal(1123456.54), "1,123,456.54", "decimal US"); 75 | //$.jsFormControls.Format.dateTime(cdata) 76 | //$.jsFormControls.Format.date(cdata) 77 | equal($.jsFormControls.Format.currency(1123456.54), "$1,123,456.54", "currency ($ prefix)"); 78 | equal($.jsFormControls.Format.asNumber("$1,123,456.54"), 1123456.54, "currency number ($ prefix)"); 79 | 80 | }); 81 | 82 | test("slickgrid parameter formatting", function(){ 83 | equal($.jsFormControls.Format.humanTime(10, 10, 320), "320ms", "human time"); 84 | }); 85 | 86 | 87 | test("basic form", function(){ 88 | // html code for basic form 89 | var basicForm = $('
        \n'+ 90 | '\n'+ 91 | '\n'+ 96 | '\n'+ 97 | '\n'+ 98 | '\n'+ 99 | 'data.field\n'+ 100 | '
        '); 101 | 102 | $("body").append(basicForm); 103 | 104 | var original = { 105 | input: "inputTest", 106 | checkbox: true, 107 | select: "opt2", 108 | textarea: "ta\ntest", 109 | field: 123456 110 | }; 111 | 112 | // default init: prefix = data 113 | basicForm.jsForm({ 114 | data: original 115 | }); 116 | 117 | equal($("input[name='data.input']", basicForm).val(), "inputTest", "input"); 118 | equal($("input[name='data.checkbox']", basicForm).is(":checked"), true, "checkbox"); 119 | equal($("select[name='data.select']", basicForm).val(), "opt2", "select"); 120 | equal($("textarea[name='data.textarea']", basicForm).val(), "ta\ntest", "textarea"); 121 | equal($("input[name='input']", basicForm).val(), "nochange", "ignore non-namespace input"); 122 | equal($("#simpleFormField", basicForm).html(), "123456", "field"); 123 | 124 | // make sure we get the same data out of the form 125 | equal(basicForm.jsForm("equals", original), true, "form hasn't changed the data"); 126 | 127 | // update the fields 128 | $("input[name='data.input']", basicForm).val("input2"); 129 | $("input[name='data.checkbox']", basicForm).click(); 130 | $("select[name='data.select']", basicForm).find('option[value="opt3"]').prop('selected', true); 131 | $("textarea[name='data.textarea']", basicForm).val("test\n2"); 132 | $("input[name='input']", basicForm).val("CHANGED"); 133 | $("#simpleFormField", basicForm).html("ASDF"); 134 | 135 | // compare the form with the original data 136 | equal(basicForm.jsForm("equals", original), false, "form has different data"); 137 | 138 | // get the pojo 139 | var pojo = basicForm.jsForm("get"); 140 | 141 | 142 | // check the pojo with the updated values 143 | equal(pojo.input,"input2", "input (updated)"); 144 | equal(pojo.checkbox, false, "checkbox (updated)"); 145 | equal(pojo.select, "opt3", "select (updated)"); 146 | equal(pojo.textarea, "test\n2", "textarea (updated)"); 147 | equal(pojo.field, 123456, "field (no change) (updated)"); 148 | 149 | // cleanup 150 | basicForm.remove(); 151 | }); 152 | 153 | 154 | test("collection test", function(){ 155 | // html code for basic form 156 | var basicForm = $('
        \n'+ 157 | '\n'+ 158 | '
        \n'+ 159 | '
        '+ 160 | 'groups.id\n'+ 161 | ' \n'+ 162 | ' X\n'+ 163 | '
        '+ 164 | '
        \n'+ 165 | 'N\n'+ 166 | '
        \n'+ 167 | '
        \n'+ 168 | '
        '); 169 | 170 | $("body").append(basicForm); 171 | 172 | var original = { 173 | input: "inputTest", 174 | test: "testValue", 175 | groups: [ 176 | { name: "group1", id: 1 }, 177 | { name: "group2", id: 2 }, 178 | { name: "group3", id: 3 } 179 | ], 180 | simpleString: ["test", "test2", "test3"], 181 | simpleNumber: [1, 2, 3] 182 | }; 183 | 184 | // default init: prefix = data 185 | basicForm.jsForm({ 186 | data: original 187 | }); 188 | 189 | equal($("input[name='data.input']", basicForm).val(), "inputTest", "simple input"); 190 | equal($("div.collection.group", basicForm).children().size(), 3, "3 repeats"); 191 | equal($("div.collection.number", basicForm).children().size(), 3, "3 repeats"); 192 | equal($("div.collection.string", basicForm).children().size(), 3, "3 repeats"); 193 | 194 | 195 | equal(basicForm.jsForm("equals", original), true, "form has not changed"); 196 | 197 | // update a field 198 | $("div.collection.group div", basicForm).eq(1).find("input").val("TEST2"); 199 | $("div.collection.string div", basicForm).eq(1).find("input").val("HELLO WORLD"); 200 | $("div.collection.number div", basicForm).eq(1).find("input").val("1000"); 201 | 202 | equal(basicForm.jsForm("equals", original), false, "form has different data"); 203 | 204 | // get object back and test 205 | var pojo = basicForm.jsForm("get"); 206 | 207 | equal(pojo.groups[1].name, "TEST2", "test change in collection"); 208 | equal(pojo.simpleString[1], "HELLO WORLD", "test change in collection"); 209 | equal(pojo.simpleNumber[1] === 1000, true, "test change in collection"); 210 | 211 | // add a new entry using "simple controls" 212 | $("span.add", basicForm).click(); 213 | equal($("div.collection.group", basicForm).children().size(), 4, "4 repeats"); 214 | 215 | pojo = basicForm.jsForm("get"); 216 | equal(pojo.groups.length, 3, "no data yet - 3 entries"); 217 | 218 | // enter some data 219 | $("div.collection.group div", basicForm).eq(3).find("input").val("newTest"); 220 | 221 | pojo = basicForm.jsForm("get"); 222 | equal(pojo.groups.length, 4, "with data - 4 entries"); 223 | 224 | // remove 225 | $("div.collection.group div", basicForm).eq(1).find("span.delete").click(); 226 | equal($("div.collection.group", basicForm).children().size(), 3, "3 repeats"); 227 | equal($("div.collection.group div", basicForm).eq(1).find("input").val(), "group3", "check if correct field is removed"); 228 | pojo = basicForm.jsForm("get"); 229 | equal(pojo.groups.length, 3, "remove entry"); 230 | 231 | basicForm.remove(); 232 | }); 233 | 234 | 235 | test("collection in collection test", function(){ 236 | // html code for basic form 237 | var basicForm = $('
        \n'+ 238 | '\n'+ 239 | '
        \n'+ 240 | '
        \n'+ 241 | ' \n'+ 242 | '
        \n'+ 243 | '
        '+ 244 | ' \n'+ 245 | ' \n'+ 246 | ' X\n'+ 247 | '
        '+ 248 | '
        '+ 249 | '
        '+ 250 | '
        \n'+ 251 | 'N\n'+ 252 | '
        '); 253 | 254 | $("body").append(basicForm); 255 | 256 | var original = { 257 | input: "inputTest", 258 | groups: [ 259 | { name: "group1", users: [{name: "user11"}, {name: "user12"}] }, 260 | { name: "group2"}, 261 | { name: "group3", users: [{name: "user31"}, {name: "user32"}] } 262 | ] 263 | }; 264 | 265 | // default init: prefix = data 266 | basicForm.jsForm({ 267 | data: original 268 | }); 269 | 270 | equal($("div.collection.group", basicForm).children().size(), 3, "3 repeats"); 271 | equal($("div.collection.group", basicForm).children().eq(0).find(".collection.users").children(".entry").size(), 2, "2 repeats (child)"); 272 | 273 | equal(basicForm.jsForm("equals", original), true, "form has not changed"); 274 | 275 | // update a field 276 | $("div.collection.group", basicForm).children().eq(0).find(".collection.users").children().eq(0).find("input[name='users.name']").val("TESTUSER"); 277 | $("div.collection.group", basicForm).children().eq(0).find(".collection.users").children().eq(1).find("input[name='users.value']").val("33"); 278 | 279 | equal(basicForm.jsForm("equals", original), false, "form has different data"); 280 | 281 | // get object back and test 282 | var pojo = basicForm.jsForm("get"); 283 | 284 | equal(pojo.groups[0].users[0].name, "TESTUSER", "test change in collection"); 285 | equal(pojo.groups[0].users[1].value, 33, "test change in collection"); 286 | 287 | basicForm.remove(); 288 | }); 289 | 290 | test("simple arrays and checkboxes", function(){ 291 | // html code for basic form 292 | var basicForm = $('
        \n'+ 293 | '\n'+ 294 | '\n'+ 295 | '\n'+ 296 | '\n'+ 297 | '
        '); 298 | 299 | $("body").append(basicForm); 300 | 301 | var original = { 302 | checks: ["a", "c"] 303 | }; 304 | 305 | // default init: prefix = data 306 | basicForm.jsForm({ 307 | data: original 308 | }); 309 | 310 | equal($("input.array[value='a']", basicForm).is(":checked"), true, "a checked"); 311 | equal($("input.array[value='b']", basicForm).is(":checked"), false, "b not checked"); 312 | equal($("input.array[value='c']", basicForm).is(":checked"), true, "c checked"); 313 | equal($("input.array[value='d']", basicForm).is(":checked"), false, "d not checked"); 314 | equal(basicForm.jsForm("equals", original), true, "form has not changed"); 315 | /* 316 | // update a field 317 | $("input.array[value='a']", basicForm).prop("checked", false); 318 | $("input.array[value='b']", basicForm).prop("checked", true); 319 | 320 | equal(basicForm.jsForm("equals", original), false, "form has different data"); 321 | 322 | // get object back and test 323 | var pojo = basicForm.jsForm("get"); 324 | 325 | equal(pojo.checks, ["b", "c"], "b and c checked"); 326 | */ 327 | basicForm.remove(); 328 | 329 | }); 330 | 331 | 332 | test("direct access simple arrays", function(){ 333 | // html code for basic form 334 | var basicForm = $('
        \n'+ 335 | '
          \n'+ 336 | '
        • \n' + 337 | '
        \b'+ 338 | '\n'+ 339 | '
          \n'+ 340 | '
        • \n' + 341 | '
        \b'+ 342 | '\n'+ 343 | '
        '); 344 | 345 | $("body").append(basicForm); 346 | 347 | var original = { 348 | steps: [10,20,30,40,50], 349 | test: { 350 | steps: [100,200,300,400,500] 351 | } 352 | }; 353 | 354 | // default init: prefix = data 355 | basicForm.jsForm({ 356 | data: original 357 | }); 358 | 359 | equal($("#list1", basicForm).children().length, 5, "5 items in list1"); 360 | equal($("#list2", basicForm).children().length, 5, "5 items in list2"); 361 | equal(basicForm.jsForm("changed"), false, "form has not changed"); 362 | equal(basicForm.jsForm("equals", original), true, "form data has not changed"); 363 | // add an item in each list 364 | $("#add1", basicForm).click(); 365 | $("#add2", basicForm).click(); 366 | equal($("#list1", basicForm).children().length, 6, "6 items in list1"); 367 | equal($("#list2", basicForm).children().length, 6, "6 items in list2"); 368 | 369 | $("#list1", basicForm).children().first().find("input").val("1111").change(); 370 | $("#list1", basicForm).children().last().find("input").val("1112").change(); 371 | $("#list2", basicForm).children().first().find("input").val("2221").change(); 372 | $("#list2", basicForm).children().last().find("input").val("2222").change(); 373 | var updated = basicForm.jsForm("get"); 374 | equal(updated.steps.length, 6, "6 steps"); 375 | equal(updated.steps[0], 1111, "first element: 1111"); 376 | equal(updated.steps[5], 1112, "last element: 1112"); 377 | equal(updated.test.steps.length, 6, "6 steps in test"); 378 | equal(updated.test.steps[0], 2221, "first element: 2221"); 379 | equal(updated.test.steps[5], 2222, "last element: 2222"); 380 | 381 | equal($("#list1", basicForm).children().first().find("input").hasClass("changed"), true, "first input changed"); 382 | equal($("#list1", basicForm).children().eq(2).find("input").hasClass("changed"), false, "second input not changed"); 383 | equal($("#list1", basicForm).children().last().find("input").hasClass("changed"), true, "last input changed"); 384 | equal($("#list2", basicForm).children().first().find("input").hasClass("changed"), true, "first input changed"); 385 | equal($("#list2", basicForm).children().last().find("input").hasClass("changed"), true, "last input changed"); 386 | 387 | equal(basicForm.jsForm("changed"), true, "form has changed"); 388 | equal(basicForm.jsForm("equals", original), false, "form data has changed"); 389 | // clean up 390 | basicForm.remove(); 391 | }); -------------------------------------------------------------------------------- /test/performance.jquery.jsForm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | performance.jquery.jsForm.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
        23 |
        24 | 25 |
        26 |

        Status Window

        27 |
        28 |
        29 | 30 | 31 | -------------------------------------------------------------------------------- /test/qunit-1.10.0.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.10.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | #qunit-tests ol { 115 | margin-top: 0.5em; 116 | padding: 0.5em; 117 | 118 | background-color: #fff; 119 | 120 | border-radius: 5px; 121 | -moz-border-radius: 5px; 122 | -webkit-border-radius: 5px; 123 | } 124 | 125 | #qunit-tests table { 126 | border-collapse: collapse; 127 | margin-top: .2em; 128 | } 129 | 130 | #qunit-tests th { 131 | text-align: right; 132 | vertical-align: top; 133 | padding: 0 .5em 0 0; 134 | } 135 | 136 | #qunit-tests td { 137 | vertical-align: top; 138 | } 139 | 140 | #qunit-tests pre { 141 | margin: 0; 142 | white-space: pre-wrap; 143 | word-wrap: break-word; 144 | } 145 | 146 | #qunit-tests del { 147 | background-color: #e0f2be; 148 | color: #374e0c; 149 | text-decoration: none; 150 | } 151 | 152 | #qunit-tests ins { 153 | background-color: #ffcaca; 154 | color: #500; 155 | text-decoration: none; 156 | } 157 | 158 | /*** Test Counts */ 159 | 160 | #qunit-tests b.counts { color: black; } 161 | #qunit-tests b.passed { color: #5E740B; } 162 | #qunit-tests b.failed { color: #710909; } 163 | 164 | #qunit-tests li li { 165 | padding: 5px; 166 | background-color: #fff; 167 | border-bottom: none; 168 | list-style-position: inside; 169 | } 170 | 171 | /*** Passing Styles */ 172 | 173 | #qunit-tests li li.pass { 174 | color: #3c510c; 175 | background-color: #fff; 176 | border-left: 10px solid #C6E746; 177 | } 178 | 179 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 180 | #qunit-tests .pass .test-name { color: #366097; } 181 | 182 | #qunit-tests .pass .test-actual, 183 | #qunit-tests .pass .test-expected { color: #999999; } 184 | 185 | #qunit-banner.qunit-pass { background-color: #C6E746; } 186 | 187 | /*** Failing Styles */ 188 | 189 | #qunit-tests li li.fail { 190 | color: #710909; 191 | background-color: #fff; 192 | border-left: 10px solid #EE5757; 193 | white-space: pre; 194 | } 195 | 196 | #qunit-tests > li:last-child { 197 | border-radius: 0 0 5px 5px; 198 | -moz-border-radius: 0 0 5px 5px; 199 | -webkit-border-bottom-right-radius: 5px; 200 | -webkit-border-bottom-left-radius: 5px; 201 | } 202 | 203 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 204 | #qunit-tests .fail .test-name, 205 | #qunit-tests .fail .module-name { color: #000000; } 206 | 207 | #qunit-tests .fail .test-actual { color: #EE5757; } 208 | #qunit-tests .fail .test-expected { color: green; } 209 | 210 | #qunit-banner.qunit-fail { background-color: #EE5757; } 211 | 212 | 213 | /** Result */ 214 | 215 | #qunit-testresult { 216 | padding: 0.5em 0.5em 0.5em 2.5em; 217 | 218 | color: #2b81af; 219 | background-color: #D2E0E6; 220 | 221 | border-bottom: 1px solid white; 222 | } 223 | #qunit-testresult .module-name { 224 | font-weight: bold; 225 | } 226 | 227 | /** Fixture */ 228 | 229 | #qunit-fixture { 230 | position: absolute; 231 | top: -10000px; 232 | left: -10000px; 233 | width: 1000px; 234 | height: 1000px; 235 | } 236 | -------------------------------------------------------------------------------- /test/test.jquery.jsForm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jquery.jsForm.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
        23 |
        24 | 25 |
        26 |

        Status Window

        27 |
        28 |
        29 | 30 | 31 | --------------------------------------------------------------------------------