├── LICENSE ├── README.md ├── src ├── css │ └── jquery-cruddy.css └── js │ └── jquery-cruddy.js └── tests └── index.html /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Askedio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | An easy to use, templated, CRUD plugin for JSON API. Fully extendable, easy to use. 3 | 4 | * [DOWNLOAD V1](https://github.com/Askedio/jQuery-Cruddy/archive/v1.zip) 5 | * [Live Demo](https://cruddy.io/app/) 6 | 7 |  8 | 9 | # Usage 10 | ~~~ 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | ~~~ 27 | 28 | # Template 29 | 30 | [Default Template Example](https://raw.githubusercontent.com/Askedio/jQuery-Cruddy/master/tests/index.html) 31 | 32 | data-href= 33 | 34 | Look for the data-href=, these are what will be used for your urls - adjust them to match your json api. 35 | 36 | # Settings 37 | ~~~ 38 | var pluginName = 'cruddy', 39 | defaults = { 40 | strict: true, /* force [send|receive] headers to be application/vnd.api+json */ 41 | slug: 'users', /* slug used to cache local settings like url,sort,search */ 42 | validation: 'validateLaravel', /* the function used to validate form errors */ 43 | listtype: 'listLaravel', /* the function used to generate lists */ 44 | autofocus: true, /* auto focus first visible input in modal when [edit|create] */ 45 | 46 | templates: { 47 | noresults : '#no-results', /* the jsrender template for no results */ 48 | create_edit: '#create-edit', /* the jsrender template for [create|edit] modal */ 49 | pagination: '#list-pagination', /* the jsrender template for the pagination */ 50 | row: '#row-item' /* the jsrender template for results */ 51 | }, 52 | 53 | selectors: { 54 | create: '.btn-create', /* the button used to trigger the create modal */ 55 | del: 'button[data-action="delete"]', /* the button used to delete a list item */ 56 | id: 'input[name="id"]', /* the (hidden) input used to as the id for the user */ 57 | limit: '[name="limit"]', /* the input used to define the list limit */ 58 | modal: '#modal-create-edit', /* the modal for [create|edit] */ 59 | refresh: '.btn-refresh', /* the button used to refresh list data */ 60 | pagination: '.pagination > li > span', /* the paginatied elements used for next/prev */ 61 | read: 'button[data-action="read"]', /* the button used to edit a list item */ 62 | search: '.search', /* the input used for searching the list */ 63 | search_field: 'input[name="q"]', /* .. */ 64 | sort: 'th[data-col]', /* the attibute to use for sorting the list */ 65 | table: '.table-list', /* the list table */ 66 | update: '.create-edit' /* the form used in [create|edit] modal */ 67 | }, 68 | 69 | styles: { 70 | tbody: 'table tbody', /* the lists tbody */ 71 | thead: 'thead', /* the lists thead */ 72 | pagination: 'list-pagination', /* the lists pagination container */ 73 | hide: 'hide', /* invisible elements */ 74 | form_group: 'form-group', /* form item groups */ 75 | em: 'em', /* used for icons [em|i] */ 76 | 77 | alert: { 78 | base: 'alert-control', /* base alert */ 79 | alert_success: 'alert-success', /* alert ok */ 80 | alert_danger: 'alert-danger' /* alert not ok */ 81 | }, 82 | 83 | refresh: { 84 | btn_refresh: 'btn-refresh', /* refresh btn */ 85 | fa_spin: 'fa-spin' /* spin */ 86 | }, 87 | 88 | sort: { 89 | em: 'em-sort', /* use this icon for sort arrow changes */ 90 | fa: 'fa-sort', /* base sort icon */ 91 | sort_asc: 'fa-sort-asc', /* asc icon */ 92 | sort_desc: 'fa-sort-desc' /* desc icon */ 93 | }, 94 | 95 | error: { 96 | has_error: 'has-error', /* form error */ 97 | help_block: 'help-block', /* form block */ 98 | strong: 'strong' /* element in the error */ 99 | } 100 | }, 101 | 102 | lang: { 103 | saved: 'Saved', /* alert save */ 104 | deleted: 'Deleted', /* alert deleted */ 105 | confirm: 'Are you sure?', /* confirm delete */ 106 | errors: { /* translate error codes to messages, new json api will be different */ 107 | default: 'Error', 108 | 500: 'Internal Server Error', 109 | 404: 'Not Found', 110 | 415: 'Unsupported Media Type', 111 | 406: 'Not Acceptable' 112 | } 113 | }, 114 | 115 | list: { 116 | direction: 'asc', /* default sort direction */ 117 | limit: 10, /* default limit */ 118 | sort: 'id', /* default sort id */ 119 | search: '' /* default search */ 120 | }, 121 | 122 | create: { /* define data your templates.create_edit requires */ 123 | attributes: { 124 | id: '', 125 | name: '', 126 | email: '' 127 | } 128 | }, 129 | ~~~ 130 | -------------------------------------------------------------------------------- /src/css/jquery-cruddy.css: -------------------------------------------------------------------------------- 1 | 2 | .alert{display:none} 3 | .panel-table .panel-body{ 4 | padding:0; 5 | } 6 | 7 | .panel-table .panel-body .table-bordered{ 8 | border-style: none; 9 | margin:0; 10 | } 11 | 12 | .panel-table .panel-body .table-bordered > thead > tr > th:first-of-type { 13 | text-align:center; 14 | width: 100px; 15 | } 16 | 17 | .panel-table .panel-body .table-bordered > thead > tr > th:last-of-type, 18 | .panel-table .panel-body .table-bordered > tbody > tr > td:last-of-type { 19 | border-right: 0px; 20 | } 21 | 22 | .panel-table .panel-body .table-bordered > thead > tr > th:first-of-type, 23 | .panel-table .panel-body .table-bordered > tbody > tr > td:first-of-type { 24 | border-left: 0px; 25 | } 26 | 27 | .panel-table .panel-body .table-bordered > tbody > tr:first-of-type > td{ 28 | border-bottom: 0px; 29 | } 30 | 31 | .panel-table .panel-body .table-bordered > thead > tr:first-of-type > th{ 32 | border-top: 0px; 33 | } 34 | 35 | .panel-table .panel-footer .col{ 36 | line-height: 34px; 37 | height: 34px; 38 | } 39 | 40 | .panel-table .panel-heading .col h3{ 41 | line-height: 30px; 42 | height: 30px; 43 | } 44 | 45 | .panel-table .panel-footer .pagination{ 46 | margin:0; 47 | } 48 | 49 | .panel-table .panel-body .table-bordered > tbody > tr > td{ 50 | line-height: 34px; 51 | } 52 | 53 | 54 | th[data-col],.pagination span{ 55 | cursor:pointer 56 | } 57 | -------------------------------------------------------------------------------- /src/js/jquery-cruddy.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 William Bowman, gcphost@gmail.com, Asked.io, Cruddy.io 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | /* TO-DO: 27 | * 28 | * cache selectors 29 | * clean up names of variables/functions 30 | * clean up 'helpers' to get lang, setting, selector, style this.setting('selectors.modal') 31 | * clean up css/id variables being used to be more uniform - cruddy-## or something 32 | * abstract API related variables, fields, results, write Laravel helper 33 | * 34 | * Switch form validation to trigger 35 | * "errors": [ 36 | * { 37 | * "status": "422", 38 | * "source": { "pointer": "/data/attributes/first-name" }, 39 | * "title": "Invalid Attribute", 40 | * "detail": "First name must contain at least three characters." 41 | * } 42 | * 43 | * BUGS/ISSUES 44 | * 45 | * POST/PATCH is {name:value} json array for body, not json api spec 46 | * My API spec errors are in transition, so are these 47 | * 48 | * 49 | */ 50 | 51 | 52 | 53 | 54 | ; 55 | (function ($, window, document, undefined) { 56 | 57 | 'use strict'; 58 | 59 | var pluginName = 'cruddy', 60 | defaults = { 61 | strict: true, /* force [send|receive] headers to be application/vnd.api+json */ 62 | slug: 'users', /* slug used to cache local settings like url,sort,search */ 63 | validation: 'validateLaravel', /* the function used to validate form errors */ 64 | listtype: 'listLaravel', /* the function used to generate lists */ 65 | autofocus: true, /* auto focus first visible input in modal when [edit|create] */ 66 | 67 | templates: { 68 | noresults : '#no-results', /* the jsrender template for no results */ 69 | create_edit: '#create-edit', /* the jsrender template for [create|edit] modal */ 70 | pagination: '#list-pagination', /* the jsrender template for the pagination */ 71 | row: '#row-item' /* the jsrender template for results */ 72 | }, 73 | 74 | selectors: { 75 | create: '.btn-create', /* the button used to trigger the create modal */ 76 | del: 'button[data-action="delete"]', /* the button used to delete a list item */ 77 | id: 'input[name="id"]', /* the (hidden) input used to as the id for the user */ 78 | limit: '[name="limit"]', /* the input used to define the list limit */ 79 | modal: '#modal-create-edit', /* the modal for [create|edit] */ 80 | refresh: '.btn-refresh', /* the button used to refresh list data */ 81 | pagination: '.pagination > li > span', /* the paginatied elements used for next/prev */ 82 | read: 'button[data-action="read"]', /* the button used to edit a list item */ 83 | search: '.search', /* the input used for searching the list */ 84 | search_field: 'input[name="q"]', /* .. */ 85 | sort: 'th[data-col]', /* the attibute to use for sorting the list */ 86 | table: '.table-list', /* the list table */ 87 | update: '.create-edit' /* the form used in [create|edit] modal */ 88 | }, 89 | 90 | styles: { 91 | tbody: 'table tbody', /* the lists tbody */ 92 | thead: 'thead', /* the lists thead */ 93 | pagination: 'list-pagination', /* the lists pagination container */ 94 | hide: 'hide', /* invisible elements */ 95 | form_group: 'form-group', /* form item groups */ 96 | em: 'em', /* used for icons [em|i] */ 97 | 98 | alert: { 99 | base: 'alert-control', /* base alert */ 100 | alert_success: 'alert-success', /* alert ok */ 101 | alert_danger: 'alert-danger' /* alert not ok */ 102 | }, 103 | 104 | refresh: { 105 | btn_refresh: 'btn-refresh', /* refresh btn */ 106 | fa_spin: 'fa-spin' /* spin */ 107 | }, 108 | 109 | sort: { 110 | em: 'em-sort', /* use this icon for sort arrow changes */ 111 | fa: 'fa-sort', /* base sort icon */ 112 | sort_asc: 'fa-sort-asc', /* asc icon */ 113 | sort_desc: 'fa-sort-desc' /* desc icon */ 114 | }, 115 | 116 | error: { 117 | has_error: 'has-error', /* form error */ 118 | help_block: 'help-block', /* form block */ 119 | strong: 'strong' /* element in the error */ 120 | } 121 | }, 122 | 123 | lang: { 124 | saved: 'Saved', /* alert save */ 125 | deleted: 'Deleted', /* alert deleted */ 126 | confirm: 'Are you sure?', /* confirm delete */ 127 | errors: { /* translate error codes to messages, new json api will be different */ 128 | default: 'Error', 129 | 500: 'Internal Server Error', 130 | 404: 'Not Found', 131 | 415: 'Unsupported Media Type', 132 | 406: 'Not Acceptable' 133 | } 134 | }, 135 | 136 | list: { 137 | direction: 'asc', /* default sort direction */ 138 | limit: 10, /* default limit */ 139 | sort: 'id', /* default sort id */ 140 | search: '' /* default search */ 141 | }, 142 | 143 | create: { /* define data your templates.create_edit requires */ 144 | attributes: { 145 | id: '', 146 | name: '', 147 | email: '' 148 | } 149 | }, 150 | 151 | onConfirm: function($this, string, $ele){ 152 | return confirm(string); 153 | } 154 | 155 | }; 156 | 157 | function Plugin(element, options) { 158 | this.element = element; 159 | this.settings = $.extend({}, defaults, options); 160 | this._defaults = defaults; 161 | this._name = pluginName; 162 | this.init(); 163 | } 164 | 165 | $.extend(Plugin.prototype, { 166 | /* start plugin */ 167 | init: function () { 168 | this.localSettings(); 169 | this.localStorage(); 170 | this.$element = $(this.element); 171 | this.bindEvents().render(); 172 | this.callback('onInit'); 173 | }, 174 | 175 | localSettings: function () { 176 | this.alert_timeout = false; 177 | this.list_url = $(this.element).find(this.settings.selectors.table).attr('data-href'); 178 | this.default_list_url = $(this.element).find(this.settings.selectors.table).attr('data-href'); 179 | return this; 180 | }, 181 | 182 | localStorage: function () { 183 | /* TO-DO: clean this up, object like other settings */ 184 | if (this.getLocalStorage('sort')) this.settings.list.sort = this.getLocalStorage('sort'); 185 | if (this.getLocalStorage('direction')) this.settings.list.direction = this.getLocalStorage('direction'); 186 | if (this.getLocalStorage('search')) { 187 | this.settings.list.search = this.getLocalStorage('search'); 188 | $(this.element).find(this.settings.selectors.search_field).val(this.settings.list.search); 189 | } 190 | if (this.getLocalStorage('limit')) { 191 | this.settings.list.limit = this.getLocalStorage('limit'); 192 | $(this.element).find(this.settings.selectors.limit).val(this.settings.list.limit); 193 | } 194 | if (this.getLocalStorage('list_url')) this.list_url = this.getLocalStorage('list_url'); 195 | return this; 196 | }, 197 | 198 | /* bound event functions */ 199 | bindEvents: function () { 200 | var plugin = this; 201 | /* refresh */ 202 | this.$element.on('click', plugin.settings.selectors.refresh, function (e) { 203 | plugin.refresh.call(plugin, $(this)); 204 | }); 205 | 206 | /* pagination */ 207 | $(this.element).on('click', plugin.settings.selectors.pagination, function (e) { 208 | plugin.pagination.call(plugin, $(this)); 209 | }); 210 | 211 | /* limit */ 212 | $(this.element).on('change', plugin.settings.selectors.limit, function (e) { 213 | e.preventDefault(); 214 | plugin.limit.call(plugin, $(this)); 215 | }); 216 | 217 | /* search */ 218 | $(this.element).on('submit', plugin.settings.selectors.search, function (e) { 219 | e.preventDefault(); 220 | plugin.search.call(plugin, $(this)); 221 | }); 222 | 223 | /* sort */ 224 | $(this.element).on('click', plugin.settings.selectors.sort, function () { 225 | plugin.sort.call(plugin, $(this)); 226 | }); 227 | 228 | /* create */ 229 | $(this.element).on('click', plugin.settings.selectors.create, function (e) { 230 | e.preventDefault(); 231 | plugin.create.call(plugin, $(this)); 232 | }); 233 | 234 | /* read */ 235 | $(this.element).on('click', plugin.settings.selectors.read, function (e) { 236 | e.preventDefault(); 237 | plugin.read.call(plugin, $(this).attr('data-href')); 238 | }); 239 | 240 | /* update */ 241 | $(this.element).on('submit', plugin.settings.selectors.update, function (e) { 242 | e.preventDefault(); 243 | plugin.update.call(plugin, $(this)); 244 | }); 245 | 246 | /* delete */ 247 | $(this.element).on('click', plugin.settings.selectors.del, function (e) { 248 | e.preventDefault(); 249 | plugin.del.call(plugin, $(this)); 250 | }); 251 | 252 | $(this.element).on('shown.bs.modal', '.modal', function () { 253 | plugin.autofocus(this); 254 | }); 255 | 256 | return this.callback('onBindEvents'); 257 | }, 258 | 259 | unbindEvents: function () { 260 | this.$element.off('.' + this._name); 261 | return this; 262 | }, 263 | 264 | /* bound event functions */ 265 | 266 | autofocus: function ($this) { 267 | if(!this.settings.autofocus) return this; 268 | setTimeout(function(){ 269 | $('input:text:visible:first', $this).focus(); 270 | }, 200); 271 | return this; 272 | }, 273 | 274 | refresh: function ($this) { 275 | return this.render().callback('onRefresh', $this); 276 | }, 277 | 278 | pagination: function ($this) { 279 | return this.setUrl($this.attr('data-href')).render().callback('onPagination', $this); 280 | }, 281 | 282 | limit: function ($this) { 283 | return this.setUrl().setLimit($this.val()).render().callback('onLimit', $this); 284 | }, 285 | 286 | search: function ($this) { 287 | return this.setUrl().setSearch($this.find(this.settings.selectors.search_field).val()).render().callback('onSearch', $this); 288 | }, 289 | 290 | sort: function ($this) { 291 | return this.setSort($this.attr('data-col')).sortStyle($this, this.settings.list.direction).setDirection(this.settings.list.direction == 'asc' ? 'desc' : 'asc').render().callback('onSort', $this); 292 | }, 293 | 294 | create: function ($this) { 295 | return this.triggerCreate($this).callback('onCreate', $this); 296 | }, 297 | 298 | /* ajax calls */ 299 | read: function (url) { 300 | var plugin = this; 301 | plugin.ajax({ 302 | url: url, 303 | success: function (data) { 304 | plugin.log(data); 305 | plugin.triggerRead(data); 306 | } 307 | }); 308 | return this.callback('onRead', url); 309 | }, 310 | 311 | update: function ($this) { 312 | /* TO-DO: move data to its own function */ 313 | var plugin = this, 314 | _url = this.updateUrl($this), 315 | _data = {}; 316 | $('input', $this).filter(function () { 317 | var _type = this.type; 318 | if(_type == 'text' || _type == 'textarea'){ 319 | if(this.value != this.defaultValue) _data[this.name] = this.value; 320 | } else if(_type = 'select'){ 321 | if(this.value != this.defaultSelected) _data[this.name] = this.value; 322 | } else if(_type = 'checkbox' || _type == 'radio') 323 | if(this.value != this.defaultChecked) _data[this.name] = this.value; 324 | }).serializeArray(); 325 | 326 | 327 | $(plugin.style('alert','base', true)).hide(); 328 | plugin.ajax({ 329 | method: _url.type, 330 | url: _url.url, 331 | data: JSON.stringify(_data), 332 | beforeSend: function () { 333 | plugin.removeErrors($this); 334 | }, 335 | success: function (data) { 336 | plugin.processUpdate($this, data); 337 | } 338 | }); 339 | return this.callback('onUpdate', $this); 340 | }, 341 | 342 | confirm: function (string) { 343 | return this.callback('onConfirm', string); 344 | }, 345 | 346 | del: function ($this) { 347 | var plugin = this; 348 | if (plugin.confirm(plugin.lang('confirm'), $this)) { 349 | plugin.ajax({ 350 | url: $this.attr('data-href'), 351 | method: 'DELETE', 352 | success: function (data) { 353 | plugin.log(data); 354 | plugin.render().success(plugin.lang('deleted')) 355 | } 356 | }); 357 | } 358 | return this.callback('onDel', $this); 359 | }, 360 | 361 | render: function () { 362 | var plugin = this; 363 | plugin.ajax({ 364 | url: this.listUrl(), 365 | beforeSend: function () { 366 | plugin.loading(); 367 | }, 368 | complete: function () { 369 | plugin.loaded(); 370 | }, 371 | success: function (data, status, xhr) { 372 | plugin.log(data); 373 | if(data.meta.total > 0){ 374 | plugin.renderTemplate(data, plugin.settings.templates.row).save(); 375 | } else plugin.renderTemplate(false, plugin.settings.templates.noresults).save(); 376 | } 377 | }); 378 | return this.callback('onRender'); 379 | }, 380 | 381 | /* plugin functions */ 382 | hasErrors: function (data, status, xhr) { 383 | var _error = false; 384 | if(this.settings.strict && xhr.getResponseHeader('Content-Type') != 'application/vnd.api+json'){ 385 | _error = this.lang('errors', 415); 386 | } else if(typeof data.errors !== 'undefined'){ 387 | _error = data.errors; 388 | } 389 | 390 | if(_error){ 391 | this.error(_error, false, true); 392 | return true; 393 | } 394 | }, 395 | 396 | ajax: function (options) { 397 | var plugin = this, 398 | defaults = { 399 | method: 'GET', 400 | dataType: 'json', 401 | error: function (xhr) { 402 | xhr.status == 403 403 | ? plugin.validation($('.create-edit'), xhr.responseJSON) 404 | : plugin.error(plugin.xhrError(xhr), false, true); 405 | }, 406 | headers: { 407 | 'Content-Type': 'application/vnd.api+json', 408 | 'Accept': 'application/vnd.api+json' 409 | } 410 | }; 411 | if(!this.settings.strict) defaults.headers = ''; 412 | 413 | var settings = $.extend({}, defaults, options); 414 | 415 | $.ajax({ 416 | method: settings.method, 417 | url: settings.url, 418 | dataType: settings.dataType, 419 | data: settings.data, 420 | beforeSend: settings.beforeSend, 421 | error: settings.error, 422 | complete: settings.complete, 423 | success: settings.success, 424 | headers: settings.headers 425 | }); 426 | 427 | return this.callback('onAjax', settings); 428 | }, 429 | 430 | callback: function (action, data) { 431 | if (typeof this.settings[action] === 'undefined') return this; 432 | var _function = this.settings[action]; 433 | if (typeof _function === 'function') return _function.call(this.element, this, data); 434 | return this; 435 | }, 436 | 437 | xhrError: function (xhr) { 438 | if(xhr.readyState == 0) xhr.status = 404; 439 | var _message = this.lang('errors', xhr.status); 440 | return _message ? _message : this.lang('errors', 'default'); 441 | }, 442 | 443 | save: function () { 444 | /* TO-DO: convert to object and loop */ 445 | this.saveLocalStorage('limit', this.settings.list.limit) 446 | this.saveLocalStorage('sort', this.settings.list.sort) 447 | this.saveLocalStorage('direction', this.settings.list.direction) 448 | this.saveLocalStorage('search', this.settings.list.search) 449 | this.saveLocalStorage('list_url', this.list_url) 450 | return this.callback('onSave'); 451 | }, 452 | 453 | /* visual */ 454 | style: function(base, sub, p) { 455 | var _style = sub 456 | ? this.settings.styles[base][sub] 457 | : this.settings.styles[base]; 458 | return p 459 | ? '.' + _style 460 | : _style; 461 | }, 462 | 463 | success: function (message, target) { 464 | return this.alert(this.style('alert','alert_success'), message, target).callback('onSuccess', { 465 | message: message, 466 | target: target 467 | }); 468 | }, 469 | 470 | error: function (message, target, close) { 471 | if(close) $('.modal').modal('hide'); 472 | return this.alert(this.style('alert','alert_danger'), message, target).callback('onError', { 473 | message: message, 474 | target: target 475 | }); 476 | }, 477 | 478 | alert: function (style, message, target) { 479 | if (target) { 480 | target = $(target).find(this.style('alert','base', true)); 481 | } else target = $(this.style('alert','base', true)); 482 | 483 | var _alert = target.removeClass( 484 | this.style('alert','alert_success') + ' ' + this.style('alert','alert_danger') 485 | ).addClass(style).html('' + message + '').show(); 486 | clearTimeout(this.alert_timeout); 487 | this.alert_timeout = setTimeout(function (){ 488 | _alert.hide(); 489 | }, 3000); 490 | return this.callback('onAlert', { 491 | style: style, 492 | message: message, 493 | target: target 494 | }); 495 | }, 496 | 497 | loading: function () { 498 | $(this.element).find(this.style('refresh','btn_refresh', true)).find(this.style('em')).addClass(this.style('refresh','fa_spin')); 499 | return this.callback('onLoading', $(this.element)); 500 | }, 501 | 502 | loaded: function () { 503 | $(this.element).find(this.style('refresh','btn_refresh', true)).find(this.style('em')).removeClass(this.style('refresh','fa_spin')); 504 | return this.callback('onLoaded', $(this.element)); 505 | }, 506 | 507 | sortStyle: function (_this, direction) { 508 | _this.parents(this.style('thead')).find(this.style('sort', 'em', true)).removeClass(this.style('sort', 'sort_asc') + ' ' + this.style('sort', 'sort_desc')).addClass(this.style('sort', 'fa')); 509 | _this.find(this.style('sort', 'em', true)).removeClass(this.style('sort', 'fa')).addClass(this.style('sort', 'fa') +'-' + this.settings.list.direction); 510 | return this.callback('onSortStyle', { 511 | 'this' : _this, 512 | direction: direction 513 | }); 514 | }, 515 | 516 | renderTemplate: function (data, tpl) { 517 | var _data = data ? data : false; 518 | $(this.element).find(this.style('tbody')).html($.templates(tpl).render(_data.data)); 519 | if(_data) $(this.element).find(this.style('pagination', '', true)).html($.templates(this.settings.templates.pagination).render(data)); 520 | return this.callback('onRenderTemplate', { 521 | data: data, 522 | tpl: tpl 523 | }); 524 | }, 525 | 526 | removeErrors: function (_this) { 527 | _this.find(this.style('error','has_error', true)).removeClass(this.style('error','has_error')); 528 | _this.find(this.style('error','has_error', true)).addClass(this.style('hide')); 529 | return this.callback('onRemoveErrors', _this); 530 | }, 531 | 532 | validation: function (_this, data) { 533 | this[this.settings.validation](_this, data); 534 | return this.callback('onValidation', { 535 | 'this': _this, 536 | data: data 537 | }); 538 | }, 539 | 540 | validateLaravel: function(_this, data) { 541 | var plugin = this; 542 | $(data.errors.detail).each(function (i, errors) { 543 | _this.find('[name="' + errors.field + '"]') 544 | .parents(plugin.style('form_group', '', true)) 545 | .addClass(plugin.style('error','has_error')) 546 | .find(plugin.style('error','help_block', true)) 547 | .removeClass(plugin.style('hide')) 548 | .find(plugin.style('error','strong')).html(errors.error); 549 | }); 550 | return this.callback('onValidateLaravel', { 551 | 'this': _this, 552 | data: data 553 | }); 554 | }, 555 | 556 | /* helpers */ 557 | setting: function(base, sub){ 558 | if(typeof this.settings[base] === 'undefined' || (sub && typeof this.settings[base][sub] === 'undefined')) return ''; 559 | return sub 560 | ? this.settings[base][sub] 561 | : this.settings[base]; 562 | }, 563 | 564 | lang: function(base, sub){ 565 | /* TO-DO: still unhappy here, lets do some splits on . (error.404, confirm, some.setting) and do a check loop to get the string */ 566 | if(sub != '' && typeof this.settings.lang[base][sub] !== 'undefined') return this.settings.lang[base][sub]; 567 | if(typeof this.settings.lang[base] !== 'undefined') return this.settings.lang[base]; 568 | return ''; 569 | }, 570 | 571 | saveLocalStorage: function (vr, vl) { 572 | localStorage.setItem(this.settings.slug + '_' + vr, vl); 573 | return this.callback('onSaveLocalStorage', { 574 | 'var': vr, 575 | 'val': vl 576 | }); 577 | }, 578 | 579 | getLocalStorage: function (vr) { 580 | return localStorage.getItem(this.settings.slug + '_' + vr); 581 | }, 582 | 583 | log: function (data) { 584 | return this.callback('onLog', data); 585 | }, 586 | 587 | processUpdate: function (_this, data) { 588 | var plugin = this; 589 | 590 | this.log(data); 591 | this.read($(_this).attr('action') + '/' + data.data.id).render(); 592 | 593 | setTimeout(function(){ 594 | plugin.success(plugin.lang('saved'), plugin.settings.selectors.modal); 595 | }, 700); 596 | 597 | return this.callback('onProcessUpdate', { 598 | 'this': _this, 599 | data: data 600 | }); 601 | }, 602 | 603 | updateUrl: function (_this) { 604 | var _url = _this.attr('action'), 605 | _patch = _this.find(this.settings.selectors.id).val(); 606 | 607 | if (_patch) { 608 | var _type = 'PATCH'; 609 | _url += '/' + _patch; 610 | } else var _type = 'POST'; 611 | 612 | this.callback('onUpdateUrl', _this); 613 | return { 614 | url: _url, 615 | type: _type 616 | }; 617 | }, 618 | 619 | listUrl: function () { 620 | return this[this.settings.listtype](); 621 | }, 622 | 623 | listLaravel: function () { 624 | /* TO-DO: move variables to settings so they can be defined, how to customize w/o new function? custom list? inline callbacks? */ 625 | var _url = this.list_url; 626 | _url += (_url.indexOf('?') > -1) ? '&' : '?page[number]=1'; 627 | if (this.settings.list.search) _url += '&search=' + encodeURIComponent(this.settings.list.search); 628 | _url += '&sort=' + (this.settings.list.direction == 'desc' ? '-' : '') + this.settings.list.sort; 629 | _url += '&page[size]=' + this.settings.list.limit; 630 | return _url; 631 | }, 632 | 633 | /* setters */ 634 | setUrl: function (url) { 635 | this.list_url = url ? url : this.default_list_url; 636 | return this.callback('onSetUrl', url); 637 | }, 638 | 639 | setLimit: function (_limit) { 640 | this.settings.list.limit = _limit; 641 | return this.callback('onSetLimit', _limit); 642 | }, 643 | 644 | setSearch: function (_search) { 645 | this.settings.list.search = _search; 646 | return this.callback('onSetSearch', _search); 647 | }, 648 | 649 | setSort: function (_sort) { 650 | this.settings.list.sort = _sort; 651 | return this.callback('onSetSort', _sort); 652 | }, 653 | 654 | setDirection: function (_direction) { 655 | this.settings.list.direction = _direction; 656 | return this.callback('onSetDirection', _direction); 657 | }, 658 | 659 | /* triggers */ 660 | triggerCreate: function (_this) { 661 | return this.doCreateRead(this.setting('create')).callback('onTriggerCreate', _this); 662 | }, 663 | 664 | triggerRead: function (data) { 665 | return this.doCreateRead(data.data).callback('onTriggerRead', data); 666 | }, 667 | 668 | doCreateRead: function(data){ 669 | $(this.element).find(this.setting('selectors', 'update')) 670 | .empty() 671 | .html( 672 | $.templates(this.setting('templates', 'create_edit')).render(data) 673 | ); 674 | return this.callback('onDoCreateRead', data); 675 | } 676 | 677 | }); 678 | 679 | $.fn[pluginName] = function (options) { 680 | return this.each(function () { 681 | if (!$.data(this, 'plugin_' + pluginName)) { 682 | $.data(this, 'plugin_' + 683 | pluginName, new Plugin(this, options)); 684 | } 685 | }); 686 | }; 687 | })(jQuery); 688 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 |A jQuery plugin to assist with JSON API driven CRUD tasks.
26 |128 | | ID | 129 |Name | 130 ||
---|---|---|---|
Loading... |