1AUI.add( 2 'com-placecube-digitalplace-communities-userconnections-taglib', 3 4 function (A) { 5 6 var UserConnectionsTaglib = { 7 8 openViewInModal: function (params) { 9 var selector = params.selector; 10 var dialogId = params.dialogId; 11 var dialogTitle = params.dialogTitle; 12 var renderURL = params.renderURL; 13 14 $(document).on('click', selector, function (e) { 15 var modalDialogId = dialogId + '_Dialog'; 16 Liferay.Util.openWindow( 17 { 18 dialog: { 19 cache: true, 20 centered: true, 21 constrain: true, 22 cssClass: 'userconnections-taglib', 23 destroyOnHide: true, 24 plugins: [Liferay.WidgetZIndex], 25 after: { 26 destroy: () => { 27 window.location.reload(); 28 } 29 }, 30 }, 31 dialogIframe: { 32 bodyCssClass: 'userconnections-taglib' 33 }, 34 id: modalDialogId, 35 title: dialogTitle, 36 uri: renderURL 37 } 38 ); 39 }); 40 } 41 }; 42 43 A.UserConnectionsTaglib = UserConnectionsTaglib; 44 }, 45 '', 46 { 47 requires: [ 48 'aui-base', 49 'liferay-portlet-url', 50 'liferay-util-window' 51 ] 52 } 53); 54 55 56/*! X-editable - v1.5.3 57* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery 58* http://github.com/vitalets/x-editable 59* Copyright (c) 2018 Vitaliy Potapov; Licensed MIT */ 60/** 61Form with single input element, two buttons and two states: normal/loading. 62Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown. 63Editableform is linked with one of input types, e.g. 'text', 'select' etc. 64 65@class editableform 66@uses text 67@uses textarea 68**/ 69(function ($) { 70 "use strict"; 71 72 var EditableForm = function (div, options) { 73 this.options = $.extend({}, $.fn.editableform.defaults, options); 74 this.$div = $(div); //div, containing form. Not form tag. Not editable-element. 75 if(!this.options.scope) { 76 this.options.scope = this; 77 } 78 //nothing shown after init 79 }; 80 81 EditableForm.prototype = { 82 constructor: EditableForm, 83 initInput: function() { //called once 84 //take input from options (as it is created in editable-element) 85 this.input = this.options.input; 86 87 //set initial value 88 //todo: may be add check: typeof str === 'string' ? 89 this.value = this.input.str2value(this.options.value); 90 91 //prerender: get input.$input 92 this.input.prerender(); 93 }, 94 initTemplate: function() { 95 this.$form = $($.fn.editableform.template); 96 }, 97 initButtons: function() { 98 var $btn = this.$form.find('.editable-buttons'); 99 $btn.append($.fn.editableform.buttons); 100 if(this.options.showbuttons === 'bottom') { 101 $btn.addClass('editable-buttons-bottom'); 102 } 103 }, 104 /** 105 Renders editableform 106 107 @method render 108 **/ 109 render: function() { 110 //init loader 111 this.$loading = $($.fn.editableform.loading); 112 this.$div.empty().append(this.$loading); 113 114 //init form template and buttons 115 this.initTemplate(); 116 if(this.options.showbuttons) { 117 this.initButtons(); 118 } else { 119 this.$form.find('.editable-buttons').remove(); 120 } 121 122 //show loading state 123 this.showLoading(); 124 125 //flag showing is form now saving value to server. 126 //It is needed to wait when closing form. 127 this.isSaving = false;
128 129 /** 130 Fired when rendering starts 131 @event rendering 132 @param {Object} event event object 133 **/ 134 this.$div.triggerHandler('rendering'); 135 136 //init input 137 this.initInput(); 138 139 //append input to form 140 this.$form.find('div.editable-input').append(this.input.$tpl); 141 142 //append form to container 143 this.$div.append(this.$form); 144 145 //render input 146 $.when(this.input.render()) 147 .then($.proxy(function () { 148 //setup input to submit automatically when no buttons shown 149 if(!this.options.showbuttons) { 150 this.input.autosubmit(); 151 } 152 153 //attach 'cancel' handler 154 this.$form.find('.editable-cancel').click($.proxy(this.cancel, this)); 155 156 if(this.input.error) { 157 this.error(this.input.error); 158 this.$form.find('.editable-submit').attr('disabled', true); 159 this.input.$input.attr('disabled', true); 160 //prevent form from submitting 161 this.$form.submit(function(e){ e.preventDefault(); }); 162 } else { 163 this.error(false); 164 this.input.$input.removeAttr('disabled'); 165 this.$form.find('.editable-submit').removeAttr('disabled'); 166 var value = (this.value === null || this.value === undefined || this.value === '') ? this.options.defaultValue : this.value; 167 this.input.value2input(value); 168 //attach submit handler 169 this.$form.submit($.proxy(this.submit, this)); 170 } 171 172 /** 173 Fired when form is rendered 174 @event rendered 175 @param {Object} event event object 176 **/ 177 this.$div.triggerHandler('rendered'); 178 179 this.showForm(); 180 181 //call postrender method to perform actions required visibility of form 182 if(this.input.postrender) { 183 this.input.postrender(); 184 } 185 }, this)); 186 }, 187 cancel: function() { 188 /** 189 Fired when form was cancelled by user 190 @event cancel 191 @param {Object} event event object 192 **/ 193 this.$div.triggerHandler('cancel'); 194 }, 195 showLoading: function() { 196 var w, h; 197 if(this.$form) { 198 //set loading size equal to form 199 w = this.$form.outerWidth(); 200 h = this.$form.outerHeight(); 201 if(w) { 202 this.$loading.width(w); 203 } 204 if(h) { 205 this.$loading.height(h); 206 } 207 this.$form.hide(); 208 } else { 209 //stretch loading to fill container width 210 w = this.$loading.parent().width(); 211 if(w) { 212 this.$loading.width(w); 213 } 214 } 215 this.$loading.show(); 216 }, 217 218 showForm: function(activate) { 219 this.$loading.hide(); 220 this.$form.show(); 221 if(activate !== false) { 222 this.input.activate(); 223 } 224 /** 225 Fired when form is shown 226 @event show 227 @param {Object} event event object 228 **/ 229 this.$div.triggerHandler('show'); 230 }, 231 232 error: function(msg) { 233 var $group = this.$form.find('.control-group'), 234 $block = this.$form.find('.editable-error-block'), 235 lines; 236 237 if(msg === false) { 238 $group.removeClass($.fn.editableform.errorGroupClass); 239 $block.removeClass($.fn.editableform.errorBlockClass).empty().hide(); 240 } else { 241 //convert newline to <br> for more pretty error display 242 if(msg) { 243 lines = (''+msg).split('\n'); 244 for (var i = 0; i < lines.length; i++) { 245 lines[i] = $('<div>').text(lines[i]).html(); 246 } 247 msg = lines.join('<br>'); 248 } 249 $group.addClass($.fn.editableform.errorGroupClass); 250 $block.addClass($.fn.editableform.errorBlockClass).html(msg).show(); 251 } 252 }, 253 254 submit: function(e) { 255 e.stopPropagation(); 256 e.preventDefault(); 257 258 //get new value from input 259 var newValue = this.input.input2value(); 260 261 //validation: if validate returns string or truthy value - means error 262 //if returns object like {newValue: '...'} => submitted value is reassigned to it 263 var error = this.validate(newValue);
264 if ($.type(error) === 'object' && error.newValue !== undefined) { 265 newValue = error.newValue; 266 this.input.value2input(newValue); 267 if(typeof error.msg === 'string') { 268 this.error(error.msg); 269 this.showForm(); 270 return; 271 } 272 } else if (error) { 273 this.error(error); 274 this.showForm(); 275 return; 276 } 277 278 //if value not changed --> trigger 'nochange' event and return 279 /*jslint eqeq: true*/ 280 if (!this.options.savenochange && this.input.value2str(newValue) === this.input.value2str(this.value)) { 281 /*jslint eqeq: false*/ 282 /** 283 Fired when value not changed but form is submitted. Requires savenochange = false. 284 @event nochange 285 @param {Object} event event object 286 **/ 287 this.$div.triggerHandler('nochange'); 288 return; 289 } 290 291 //convert value for submitting to server 292 var submitValue = this.input.value2submit(newValue); 293 294 this.isSaving = true; 295 296 //sending data to server 297 $.when(this.save(submitValue)) 298 .done($.proxy(function(response) { 299 this.isSaving = false; 300 301 //run success callback 302 var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null; 303 304 //if success callback returns false --> keep form open and do not activate input 305 if(res === false) { 306 this.error(false); 307 this.showForm(false); 308 return; 309 } 310 311 //if success callback returns string --> keep form open, show error and activate input 312 if(typeof res === 'string') { 313 this.error(res); 314 this.showForm(); 315 return; 316 } 317 318 //if success callback returns object like {newValue: <something>} --> use that value instead of submitted 319 //it is useful if you want to chnage value in url-function 320 if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) { 321 newValue = res.newValue; 322 } 323 324 //clear error message 325 this.error(false); 326 this.value = newValue; 327 /** 328 Fired when form is submitted 329 @event save 330 @param {Object} event event object 331 @param {Object} params additional params 332 @param {mixed} params.newValue raw new value 333 @param {mixed} params.submitValue submitted value as string 334 @param {Object} params.response ajax response 335 336 @example 337 $('#form-div').on('save'), function(e, params){ 338 if(params.newValue === 'username') {...} 339 }); 340 **/ 341 this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response}); 342 }, this)) 343 .fail($.proxy(function(xhr) { 344 this.isSaving = false; 345 346 var msg; 347 if(typeof this.options.error === 'function') { 348 msg = this.options.error.call(this.options.scope, xhr, newValue); 349 } else { 350 msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!'; 351 } 352 353 this.error(msg); 354 this.showForm(); 355 }, this)); 356 }, 357 358 save: function(submitValue) { 359 //try parse composite pk defined as json string in data-pk 360 this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true); 361 362 var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk, 363 /* 364 send on server in following cases: 365 1. url is function 366 2. url is string AND (pk defined OR send option = always) 367 */ 368 send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))), 369 params; 370 371 if (send) { //send to server 372 this.showLoading(); 373 374 //standard params 375 params = { 376 name: this.options.name || '', 377 value: submitValue, 378 pk: pk 379 }; 380 381 //additional params 382 if(typeof this.options.params === 'function') { 383 params = this.options.params.call(this.options.scope, params); 384 } else { 385 //try parse json in single quotes (from data-params attribute) 386 this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true); 387 $.extend(params, this.options.params); 388 } 389 390 if(typeof this.options.url === 'function') { //user's function 391 return this.options.url.call(this.options.scope, params); 392 } else { 393 //send ajax to server and return deferred object 394 return $.ajax($.extend({ 395 url : this.options.url, 396 data : params, 397 type : 'POST' 398 }, this.options.ajaxOptions)); 399 } 400 } 401 }, 402 403 validate: function (value) { 404 if (value === undefined) { 405 value = this.value; 406 } 407 if (typeof this.options.validate === 'function') { 408 return this.options.validate.call(this.options.scope, value); 409 } 410 }, 411 412 option: function(key, value) { 413 if(key in this.options) { 414 this.options[key] = value; 415 } 416 417 if(key === 'value') { 418 this.setValue(value); 419 } 420 421 //do not pass option to input as it is passed in editable-element 422 }, 423 424 setValue: function(value, convertStr) { 425 if(convertStr) { 426 this.value = this.input.str2value(value); 427 } else { 428 this.value = value; 429 } 430 431 //if form is visible, update input 432 if(this.$form && this.$form.is(':visible')) { 433 this.input.value2input(this.value); 434 } 435 } 436 }; 437 438 /* 439 Initialize editableform. Applied to jQuery object. 440 441 @method $().editableform(options) 442 @params {Object} options 443 @example 444 var $form = $('<div>').editableform({ 445 type: 'text', 446 name: 'username', 447 url: '/post', 448 value: 'vitaliy' 449 }); 450 451 //to display form you should call 'render' method 452 $form.editableform('render'); 453 */ 454 $.fn.editableform = function (option) { 455 var args = arguments; 456 return this.each(function () { 457 var $this = $(this), 458 data = $this.data('editableform'), 459 options = typeof option === 'object' && option; 460 if (!data) { 461 $this.data('editableform', (data = new EditableForm(this, options))); 462 } 463 464 if (typeof option === 'string') { //call method 465 data[option].apply(data, Array.prototype.slice.call(args, 1)); 466 } 467 }); 468 }; 469 470 //keep link to constructor to allow inheritance 471 $.fn.editableform.Constructor = EditableForm; 472 473 //defaults 474 $.fn.editableform.defaults = { 475 /* see also defaults for input */ 476 477 /** 478 Type of input. Can be <code>
478text|textarea|select|date|checklist</code> 479 480 @property type 481 @type string 482 @default 'text' 483 **/ 484 type: 'text', 485 /** 486 Url for submit, e.g. <code>'/post'</code> 487 If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks. 488 489 @property url 490 @type string|function 491 @default null 492 @example 493 url: function(params) { 494 var d = new $.Deferred; 495 if(params.value === 'abc') { 496 return d.reject('error message'); //returning error via deferred object 497 } else { 498 //async saving data in js model 499 someModel.asyncSaveMethod({ 500 ..., 501 success: function(){ 502 d.resolve(); 503 } 504 }); 505 return d.promise(); 506 } 507 } 508 **/ 509 url:null, 510 /** 511 Additional params for submit. If defined as <code>object</code> - it is **appended** to original ajax data (pk, name and value). 512 If defined as <code>function</code> - returned object **overwrites** original ajax data. 513 @example 514 params: function(params) { 515 //originally params contain pk, name and value 516 params.a = 1; 517 return params; 518 } 519 520 @property params 521 @type object|function 522 @default null 523 **/ 524 params:null, 525 /** 526 Name of field. Will be submitted on server. Can be taken from <code>id</code> attribute 527 528 @property name 529 @type string 530 @default null 531 **/ 532 name: null, 533 /** 534 Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>. 535 Can be calculated dynamically via function. 536 537 @property pk 538 @type string|object|function 539 @default null 540 **/ 541 pk: null, 542 /** 543 Initial value. If not defined - will be taken from element's content. 544 For __select__ type should be defined (as it is ID of shown text). 545 546 @property value 547 @type string|object 548 @default null 549 **/ 550 value: null, 551 /** 552 Value that will be displayed in input if original field value is empty (`null|undefined|''`). 553 554 @property defaultValue 555 @type string|object 556 @default null 557 @since 1.4.6 558 **/ 559 defaultValue: null, 560 /** 561 Strategy for sending data on server. Can be `auto|always|never`. 562 When 'auto' data will be sent on server **only if pk and url defined**, otherwise new value will be stored locally. 563 564 @property send 565 @type string 566 @default 'auto' 567 **/ 568 send: 'auto', 569 /** 570 Function for client-side validation. If returns string - means validation not passed and string showed as error. 571 Since 1.5.1 you can modify submitted value by returning object from `validate`: 572 `{newValue: '...'}` or `{newValue: '...', msg: '...'}` 573 574 @property validate 575 @type function 576 @default null 577 @example 578 validate: function(value) { 579 if($.trim(value) == '') { 580 return 'This field is required'; 581 } 582 } 583 **/ 584 validate: null, 585 /** 586 Success callback. Called when value successfully sent on server and **response status = 200**. 587 Usefull to work with json response. For example, if your backend response can be <code>{success: true}</code> 588 or `{success: false, msg: "server error"}` you can check it inside this callback. 589 If it returns **string** - means error occured and string is shown as error message. 590 If it returns **object like** `{newValue: <something>}` - it overwrites value, submitted by user 591 (useful when server changes value). 592 Otherwise newValue simply rendered into element. 593 594 @property success 595 @type function 596 @default null 597 @example 598 success: function(response, newValue) { 599 if(!response.success) return response.msg; 600 } 601 **/ 602 success: null, 603 /** 604 Error callback. Called when request failed (response status != 200). 605 Usefull when you want to parse error response and display a custom message. 606 Must return **string** - the message to be displayed in the error block. 607 608 @property error 609 @type function 610 @default null 611 @since 1.4.4 612 @example 613 error: function(response, newValue) { 614 if(response.status === 500) { 615 return 'Service unavailable. Please try later.'; 616 } else { 617 return response.responseText; 618 } 619 } 620 **/ 621 error: null, 622 /** 623 Additional options for submit ajax request. 624 List of values: http://api.jquery.com/jQuery.ajax 625 626 @property ajaxOptions 627 @type object 628 @default null 629 @since 1.1.1 630 @example 631 ajaxOptions: { 632 type: 'put', 633 dataType: 'json' 634 } 635 **/ 636 ajaxOptions: null, 637 /** 638 Where to show buttons: left(true)|bottom|false 639 Form without buttons is auto-submitted. 640 641 @property showbuttons 642 @type boolean|string 643 @default true 644 @since 1.1.1 645 **/ 646 showbuttons: true, 647 /** 648 Scope for callback methods (success, validate). 649 If <code>null</code> means editableform instance itself.
650 651 @property scope 652 @type DOMElement|object 653 @default null 654 @since 1.2.0 655 @private 656 **/ 657 scope: null, 658 /** 659 Whether to save or cancel value when it was not changed but form was submitted 660 661 @property savenochange 662 @type boolean 663 @default false 664 @since 1.2.0 665 **/ 666 savenochange: false 667 }; 668 669 /* 670 Note: following params could redefined in engine: bootstrap or jqueryui: 671 Classes 'control-group' and 'editable-error-block' must always present! 672 */ 673 $.fn.editableform.template = '<form class="form-inline editableform">'+ 674 '<div class="control-group">' + 675 '<div><div class="editable-input"></div><div class="editable-buttons"></div></div>'+ 676 '<div class="editable-error-block"></div>' + 677 '</div>' + 678 '</form>'; 679 680 //loading div 681 $.fn.editableform.loading = '<div class="editableform-loading"></div>'; 682 683 //buttons 684 $.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+ 685 '<button type="button" class="editable-cancel">cancel</button>'; 686 687 //error class attached to control-group 688 $.fn.editableform.errorGroupClass = null; 689 690 //error class attached to editable-error-block 691 $.fn.editableform.errorBlockClass = 'editable-error'; 692 693 //engine 694 $.fn.editableform.engine = 'jquery'; 695}(window.jQuery)); 696 697/** 698* EditableForm utilites 699*/ 700(function ($) { 701 "use strict"; 702 703 //utils 704 $.fn.editableutils = { 705 /** 706 * classic JS inheritance function 707 */ 708 inherit: function (Child, Parent) { 709 var F = function() { }; 710 F.prototype = Parent.prototype; 711 Child.prototype = new F(); 712 Child.prototype.constructor = Child; 713 Child.superclass = Parent.prototype; 714 }, 715 716 /** 717 * set caret position in input 718 * see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area 719 */ 720 setCursorPosition: function(elem, pos) { 721 if (elem.setSelectionRange) { 722 try { elem.setSelectionRange(pos, pos); } catch (e) {} 723 } else if (elem.createTextRange) { 724 var range = elem.createTextRange(); 725 range.collapse(true); 726 range.moveEnd('character', pos); 727 range.moveStart('character', pos); 728 range.select(); 729 } 730 }, 731 732 /** 733 * function to parse JSON in *single* quotes. (jquery automatically parse only double quotes) 734 * That allows such code as: <a data-source="{'a': 'b', 'c': 'd'}"> 735 * safe = true --> means no exception will be thrown 736 * for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery 737 */ 738 tryParseJson: function(s, safe) { 739 if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) { 740 if (safe) { 741 try { 742 /*jslint evil: true*/ 743 s = (new Function('return ' + s))(); 744 /*jslint evil: false*/ 745 } catch (e) {} finally { 746 return s; 747 } 748 } else { 749 /*jslint evil: true*/ 750 s = (new Function('return ' + s))(); 751 /*jslint evil: false*/ 752 } 753 } 754 return s; 755 }, 756 757 /** 758 * slice object by specified keys 759 */ 760 sliceObj: function(obj, keys, caseSensitive /* default: false */) { 761 var key, keyLower, newObj = {}; 762 763 if (!$.isArray(keys) || !keys.length) { 764 return newObj; 765 } 766 767 for (var i = 0; i < keys.length; i++) { 768 key = keys[i]; 769 if (obj.hasOwnProperty(key)) { 770 newObj[key] = obj[key]; 771 } 772 773 if(caseSensitive === true) { 774 continue; 775 } 776 777 //when getting data-* attributes via $.data() it's converted to lowercase. 778 //details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery 779 //workaround is code below. 780 keyLower = key.toLowerCase(); 781 if (obj.hasOwnProperty(keyLower)) { 782 newObj[key] = obj[keyLower]; 783 } 784 } 785 786 return newObj; 787 }, 788 789 /* 790 exclude complex objects from $.data() before pass to config 791 */ 792 getConfigData: function($element) { 793 var data = {}; 794 $.each($element[0].dataset, function(k, v) { 795 if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) { 796 data[k] = v; 797 } 798 }); 799 return data; 800 }, 801 802 /* 803 returns keys of object 804 */ 805 objectKeys: function(o) { 806 if (Object.keys) { 807 return Object.keys(o); 808 } else { 809 if (o !== Object(o)) { 810 throw new TypeError('Object.keys called on a non-object'); 811 } 812 var k=[], p; 813 for (p in o) { 814 if (Object.prototype.hasOwnProperty.call(o,p)) { 815 k.push(p); 816 } 817 } 818 return k; 819 } 820 821 }, 822 823 /** 824 method to escape html. 825 **/ 826 escape: function(str) { 827 return $('<div>').text(str).html(); 828 }, 829 830 /* 831 returns array items from sourceData having value property equal or inArray of 'value' 832 */ 833 itemsByValue: function(value, sourceData, valueProp) { 834 if(!sourceData || value === null) { 835 return []; 836 } 837 838 if (typeof(valueProp) !== "function") { 839 var idKey = valueProp || 'value'; 840 valueProp = function (e) { return e[idKey]; }; 841 } 842 843 var isValArray = $.isArray(value),
844 result = [], 845 that = this; 846 847 $.each(sourceData, function(i, o) { 848 if(o.children) { 849 result = result.concat(that.itemsByValue(value, o.children, valueProp)); 850 } else { 851 /*jslint eqeq: true*/ 852 if(isValArray) { 853 if($.grep(value, function(v){ return v == (o && typeof o === 'object' ? valueProp(o) : o); }).length) { 854 result.push(o); 855 } 856 } else { 857 var itemValue = (o && (typeof o === 'object')) ? valueProp(o) : o; 858 if(value == itemValue) { 859 result.push(o); 860 } 861 } 862 /*jslint eqeq: false*/ 863 } 864 }); 865 866 return result; 867 }, 868 869 /* 870 Returns input by options: type, mode. 871 */ 872 createInput: function(options) { 873 var TypeConstructor, typeOptions, input, 874 type = options.type; 875 876 //`date` is some kind of virtual type that is transformed to one of exact types 877 //depending on mode and core lib 878 if(type === 'date') { 879 //inline 880 if(options.mode === 'inline') { 881 if($.fn.editabletypes.datefield) { 882 type = 'datefield'; 883 } else if($.fn.editabletypes.dateuifield) { 884 type = 'dateuifield'; 885 } 886 //popup 887 } else { 888 if($.fn.editabletypes.date) { 889 type = 'date'; 890 } else if($.fn.editabletypes.dateui) { 891 type = 'dateui'; 892 } 893 } 894 895 //if type still `date` and not exist in types, replace with `combodate` that is base input 896 if(type === 'date' && !$.fn.editabletypes.date) { 897 type = 'combodate'; 898 } 899 } 900 901 //`datetime` should be datetimefield in 'inline' mode 902 if(type === 'datetime' && options.mode === 'inline') { 903 type = 'datetimefield'; 904 } 905 906 //change wysihtml5 to textarea for jquery UI and plain versions 907 if(type === 'wysihtml5' && !$.fn.editabletypes[type]) { 908 type = 'textarea'; 909 } 910 911 //create input of specified type. Input will be used for converting value, not in form 912 if(typeof $.fn.editabletypes[type] === 'function') { 913 TypeConstructor = $.fn.editabletypes[type]; 914 typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults)); 915 input = new TypeConstructor(typeOptions); 916 return input; 917 } else { 918 $.error('Unknown type: '+ type); 919 return false; 920 } 921 }, 922 923 //see http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr 924 supportsTransitions: function () { 925 var b = document.body || document.documentElement, 926 s = b.style, 927 p = 'transition', 928 v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms']; 929 930 if(typeof s[p] === 'string') { 931 return true; 932 } 933 934 // Tests for vendor specific prop 935 p = p.charAt(0).toUpperCase() + p.substr(1); 936 for(var i=0; i<v.length; i++) { 937 if(typeof s[v[i] + p] === 'string') { 938 return true; 939 } 940 } 941 return false; 942 } 943 944 }; 945}(window.jQuery)); 946 947/** 948Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br> 949This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
950Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br> 951Applied as jQuery method. 952 953@class editableContainer 954@uses editableform 955**/ 956(function ($) { 957 "use strict"; 958 959 var Popup = function (element, options) { 960 this.init(element, options); 961 }; 962 963 var Inline = function (element, options) { 964 this.init(element, options); 965 }; 966 967 //methods 968 Popup.prototype = { 969 containerName: null, //method to call container on element 970 containerDataName: null, //object name in element's .data() 971 innerCss: null, //tbd in child class 972 containerClass: 'editable-container editable-popup', //css class applied to container element 973 defaults: {}, //container itself defaults 974 975 init: function(element, options) { 976 this.$element = $(element); 977 //since 1.4.1 container do not use data-* directly as they already merged into options. 978 this.options = $.extend({}, $.fn.editableContainer.defaults, options); 979 this.splitOptions(); 980 981 //set scope of form callbacks to element 982 this.formOptions.scope = this.$element[0]; 983 984 this.initContainer(); 985 986 //flag to hide container, when saving value will finish 987 this.delayedHide = false;
988 989 //bind 'destroyed' listener to destroy container when element is removed from dom 990 this.$element.on('destroyed', $.proxy(function(){ 991 this.destroy(); 992 }, this)); 993 994 //attach document handler to close containers on click / escape 995 if(!$(document).data('editable-handlers-attached')) { 996 //close all on escape 997 $(document).on('keyup.editable', function (e) { 998 if (e.which === 27) { 999 $('.editable-open').editableContainer('hide', 'cancel'); 1000 //todo: return focus on element 1001 } 1002 }); 1003 1004 //close containers when click outside 1005 //(mousedown could be better than click, it closes everything also on drag drop) 1006 $(document).on('click.editable', function(e) { 1007 var $target = $(e.target), i, 1008 exclude_classes = ['.editable-container', 1009 '.ui-datepicker-header', 1010 '.datepicker', //in inline mode datepicker is rendered into body 1011 '.modal-backdrop', 1012 '.bootstrap-wysihtml5-insert-image-modal', 1013 '.bootstrap-wysihtml5-insert-link-modal' 1014 ]; 1015 1016 // select2 has extra body click in IE 1017 // see: https://github.com/ivaynberg/select2/issues/1058 1018 if ($('.select2-drop-mask').is(':visible')) { 1019 return; 1020 } 1021 1022 //check if element is detached. It occurs when clicking in bootstrap datepicker 1023 if (!$.contains(document.documentElement, e.target)) { 1024 return; 1025 } 1026 1027 //for some reason FF 20 generates extra event (click) in select2 widget with e.target = document 1028 //we need to filter it via construction below. See https://github.com/vitalets/x-editable/issues/199 1029 //Possibly related to http://stackoverflow.com/questions/10119793/why-does-firefox-react-differently-from-webkit-and-ie-to-click-event-on-selec 1030 if($target.is(document)) { 1031 return; 1032 } 1033 1034 //if click inside one of exclude classes --> no nothing 1035 for(i=0; i<exclude_classes.length; i++) { 1036 if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) { 1037 return; 1038 } 1039 } 1040 1041 //close all open containers (except one - target) 1042 Popup.prototype.closeOthers(e.target); 1043 }); 1044 1045 $(document).data('editable-handlers-attached', true); 1046 } 1047 }, 1048 1049 //split options on containerOptions and formOptions 1050 splitOptions: function() { 1051 this.containerOptions = {}; 1052 this.formOptions = {}; 1053 1054 if(!$.fn[this.containerName]) { 1055 throw new Error(this.containerName + ' not found. Have you included corresponding js file?'); 1056 } 1057 1058 //keys defined in container defaults go to container, others go to form 1059 for(var k in this.options) { 1060 if(k in this.defaults) { 1061 this.containerOptions[k] = this.options[k]; 1062 } else { 1063 this.formOptions[k] = this.options[k]; 1064 } 1065 } 1066 }, 1067 1068 /* 1069 Returns jquery object of container 1070 @method tip() 1071 */ 1072 tip: function() { 1073 return this.container() ? this.container().$tip : null; 1074 }, 1075 1076 /* returns container object */ 1077 container: function() { 1078 var container; 1079 //first, try get it by `containerDataName` 1080 if(this.containerDataName) { 1081 if(container = this.$element.data(this.containerDataName)) { 1082 return container; 1083 } 1084 }
1085 //second, try `containerName` 1086 container = this.$element.data(this.containerName); 1087 return container; 1088 }, 1089 1090 /* call native method of underlying container, e.g. this.$element.popover('method') */ 1091 call: function() { 1092 this.$element[this.containerName].apply(this.$element, arguments); 1093 }, 1094 1095 initContainer: function(){ 1096 this.call(this.containerOptions); 1097 }, 1098 1099 renderForm: function() { 1100 this.$form 1101 .editableform(this.formOptions) 1102 .on({ 1103 save: $.proxy(this.save, this), //click on submit button (value changed) 1104 nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed) 1105 cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on cancel button 1106 show: $.proxy(function() { 1107 if(this.delayedHide) { 1108 this.hide(this.delayedHide.reason); 1109 this.delayedHide = false;
1110 } else { 1111 this.setPosition(); 1112 } 1113 }, this), //re-position container every time form is shown (occurs each time after loading state) 1114 rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown 1115 resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed 1116 rendered: $.proxy(function(){ 1117 /** 1118 Fired when container is shown and form is rendered (for select will wait for loading dropdown options). 1119 **Note:** Bootstrap popover has own `shown` event that now cannot be separated from x-editable's one. 1120 The workaround is to check `arguments.length` that is always `2` for x-editable. 1121 1122 @event shown 1123 @param {Object} event event object 1124 @example 1125 $('#username').on('shown', function(e, editable) { 1126 editable.input.$input.val('overwriting value of input..'); 1127 }); 1128 **/ 1129 /* 1130 TODO: added second param mainly to distinguish from bootstrap's shown event. It's a hotfix that will be solved in future versions via namespaced events. 1131 */ 1132 this.$element.triggerHandler('shown', $(this.options.scope).data('editable')); 1133 }, this) 1134 }) 1135 .editableform('render'); 1136 }, 1137 1138 /** 1139 Shows container with form 1140 @method show() 1141 @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true. 1142 **/ 1143 /* Note: poshytip owerwrites this method totally! */ 1144 show: function (closeAll) { 1145 this.$element.addClass('editable-open'); 1146 if(closeAll !== false) { 1147 //close all open containers (except this) 1148 this.closeOthers(this.$element[0]); 1149 } 1150 1151 //show container itself 1152 this.innerShow(); 1153 this.tip().addClass(this.containerClass); 1154 1155 /* 1156 Currently, form is re-rendered on every show. 1157 The main reason is that we dont know, what will container do with content when closed: 1158 remove(), detach() or just hide() - it depends on container. 1159 1160 Detaching form itself before hide and re-insert before show is good solution, 1161 but visually it looks ugly --> container changes size before hide. 1162 */ 1163 1164 //if form already exist - delete previous data 1165 if(this.$form) { 1166 //todo: destroy prev data! 1167 //this.$form.destroy(); 1168 } 1169 1170 this.$form = $('<div>'); 1171 1172 //insert form into container body 1173 if(this.tip().is(this.innerCss)) { 1174 //for inline container 1175 this.tip().append(this.$form); 1176 } else { 1177 this.tip().find(this.innerCss).append(this.$form); 1178 } 1179 1180 //render form 1181 this.renderForm(); 1182 }, 1183 1184 /** 1185 Hides container with form 1186 @method hide() 1187 @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code> 1188 **/ 1189 hide: function(reason) { 1190 if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) { 1191 return; 1192 } 1193 1194 //if form is saving value, schedule hide 1195 if(this.$form.data('editableform').isSaving) { 1196 this.delayedHide = {reason: reason}; 1197 return; 1198 } else { 1199 this.delayedHide = false;
1200 } 1201 1202 this.$element.removeClass('editable-open'); 1203 this.innerHide(); 1204 1205 /** 1206 Fired when container was hidden. It occurs on both save or cancel. 1207 **Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one. 1208 The workaround is to check `arguments.length` that is always `2` for x-editable. 1209 1210 @event hidden 1211 @param {object} event event object 1212 @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|manual</code> 1213 @example 1214 $('#username').on('hidden', function(e, reason) { 1215 if(reason === 'save' || reason === 'cancel') { 1216 //auto-open next editable 1217 $(this).closest('tr').next().find('.editable').editable('show'); 1218 } 1219 }); 1220 **/ 1221 this.$element.triggerHandler('hidden', reason || 'manual'); 1222 }, 1223 1224 /* internal show method. To be overwritten in child classes */ 1225 innerShow: function () { 1226 1227 }, 1228 1229 /* internal hide method. To be overwritten in child classes */ 1230 innerHide: function () { 1231 1232 }, 1233 1234 /** 1235 Toggles container visibility (show / hide) 1236 @method toggle() 1237 @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true. 1238 **/ 1239 toggle: function(closeAll) { 1240 if(this.container() && this.tip() && this.tip().is(':visible')) { 1241 this.hide(); 1242 } else { 1243 this.show(closeAll); 1244 } 1245 }, 1246 1247 /* 1248 Updates the position of container when content changed. 1249 @method setPosition() 1250 */ 1251 setPosition: function() { 1252 //tbd in child class 1253 }, 1254 1255 save: function(e, params) { 1256 /** 1257 Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance 1258 1259 @event save 1260 @param {Object} event event object 1261 @param {Object} params additional params 1262 @param {mixed} params.newValue submitted value 1263 @param {Object} params.response ajax response 1264 @example 1265 $('#username').on('save', function(e, params) { 1266 //assuming server response: '{success: true}' 1267 var pk = $(this).data('editableContainer').options.pk; 1268 if(params.response && params.response.success) { 1269 alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!'); 1270 } else { 1271 alert('error!'); 1272 } 1273 }); 1274 **/ 1275 this.$element.triggerHandler('save', params); 1276 1277 //hide must be after trigger, as saving value may require methods of plugin, applied to input 1278 this.hide('save'); 1279 }, 1280 1281 /** 1282 Sets new option 1283 1284 @method option(key, value) 1285 @param {string} key 1286 @param {mixed} value 1287 **/ 1288 option: function(key, value) { 1289 this.options[key] = value; 1290 if(key in this.containerOptions) { 1291 this.containerOptions[key] = value; 1292 this.setContainerOption(key, value); 1293 } else { 1294 this.formOptions[key] = value; 1295 if(this.$form) { 1296 this.$form.editableform('option', key, value); 1297 } 1298 } 1299 }, 1300 1301 setContainerOption: function(key, value) { 1302 this.call('option', key, value); 1303 }, 1304 1305 /** 1306 Destroys the container instance 1307 @method destroy() 1308 **/ 1309 destroy: function() { 1310 this.hide(); 1311 this.innerDestroy(); 1312 this.$element.off('destroyed'); 1313 this.$element.removeData('editableContainer'); 1314 }, 1315
1316 /* to be overwritten in child classes */ 1317 innerDestroy: function() { 1318 1319 }, 1320 1321 /* 1322 Closes other containers except one related to passed element. 1323 Other containers can be cancelled or submitted (depends on onblur option) 1324 */ 1325 closeOthers: function(element) { 1326 $('.editable-open').each(function(i, el){ 1327 //do nothing with passed element and it's children 1328 if(el === element || $(el).find(element).length) { 1329 return; 1330 } 1331 1332 //otherwise cancel or submit all open containers 1333 var $el = $(el), 1334 ec = $el.data('editableContainer'); 1335 1336 if(!ec) { 1337 return; 1338 } 1339 1340 if(ec.options.onblur === 'cancel') { 1341 $el.data('editableContainer').hide('onblur'); 1342 } else if(ec.options.onblur === 'submit') { 1343 $el.data('editableContainer').tip().find('form').submit(); 1344 } 1345 }); 1346 1347 }, 1348 1349 /** 1350 Activates input of visible container (e.g. set focus) 1351 @method activate() 1352 **/ 1353 activate: function() { 1354 if(this.tip && this.tip().is(':visible') && this.$form) { 1355 this.$form.data('editableform').input.activate(); 1356 } 1357 } 1358 1359 }; 1360 1361 /** 1362 jQuery method to initialize editableContainer. 1363 1364 @method $().editableContainer(options) 1365 @params {Object} options 1366 @example 1367 $('#edit').editableContainer({ 1368 type: 'text', 1369 url: '/post', 1370 pk: 1, 1371 value: 'hello' 1372 }); 1373 **/ 1374 $.fn.editableContainer = function (option) { 1375 var args = arguments; 1376 return this.each(function () { 1377 var $this = $(this), 1378 dataKey = 'editableContainer', 1379 data = $this.data(dataKey), 1380 options = typeof option === 'object' && option, 1381 Constructor = (options.mode === 'inline') ? Inline : Popup; 1382 1383 if (!data) { 1384 $this.data(dataKey, (data = new Constructor(this, options))); 1385 } 1386 1387 if (typeof option === 'string') { //call method 1388 data[option].apply(data, Array.prototype.slice.call(args, 1)); 1389 } 1390 }); 1391 }; 1392 1393 //store constructors 1394 $.fn.editableContainer.Popup = Popup; 1395 $.fn.editableContainer.Inline = Inline; 1396 1397 //defaults 1398 $.fn.editableContainer.defaults = { 1399 /** 1400 Initial value of form input 1401 1402 @property value 1403 @type mixed 1404 @default null 1405 @private 1406 **/ 1407 value: null, 1408 /** 1409 Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container. 1410 1411 @property placement 1412 @type string 1413 @default 'top' 1414 **/ 1415 placement: 'top', 1416 /** 1417 Whether to hide container on save/cancel. 1418 1419 @property autohide 1420 @type boolean 1421 @default true 1422 @private 1423 **/ 1424 autohide: true, 1425 /** 1426 Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>. 1427 Setting <code>ignore</code> allows to have several containers open. 1428 1429 @property onblur 1430 @type string 1431 @default 'cancel' 1432 @since 1.1.1 1433 **/ 1434 onblur: 'cancel', 1435 1436 /** 1437 Animation speed (inline mode only) 1438 @property anim 1439 @type string 1440 @default false 1441 **/ 1442 anim: false, 1443 1444 /** 1445 Mode of editable, can be `popup` or `inline` 1446 1447 @property mode 1448 @type string 1449 @default 'popup' 1450 @since 1.4.0 1451 **/ 1452 mode: 'popup' 1453 }; 1454 1455 /* 1456 * workaround to have 'destroyed' event to destroy popover when element is destroyed 1457 * see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom 1458 */ 1459 jQuery.event.special.destroyed = { 1460 remove: function(o) { 1461 if (o.handler) { 1462 o.handler(); 1463 } 1464 } 1465 }; 1466 1467}(window.jQuery)); 1468 1469/** 1470* Editable Inline 1471* --------------------- 1472*/ 1473(function ($) { 1474 "use strict"; 1475 1476 //copy prototype from EditableContainer 1477 //extend methods 1478 $.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, { 1479 containerName: 'editableform', 1480 innerCss: '.editable-inline', 1481 containerClass: 'editable-container editable-inline', //css class applied to container element 1482 1483 initContainer: function(){ 1484 //container is <span> element 1485 this.$tip = $('<span></span>'); 1486 1487 //convert anim to miliseconds (int) 1488 if(!this.options.anim) { 1489 this.options.anim = 0; 1490 } 1491 }, 1492 1493 splitOptions: function() { 1494 //all options are passed to form 1495 this.containerOptions = {}; 1496 this.formOptions = this.options; 1497 }, 1498 1499 tip: function() { 1500 return this.$tip; 1501 }, 1502 1503 innerShow: function () { 1504 this.$element.hide(); 1505 this.tip().insertAfter(this.$element).show(); 1506 }, 1507 1508 innerHide: function () { 1509 this.$tip.hide(this.options.anim, $.proxy(function() { 1510 this.$element.show(); 1511 this.innerDestroy(); 1512 }, this)); 1513 }, 1514 1515 innerDestroy: function() { 1516 if(this.tip()) { 1517 this.tip().empty().remove(); 1518 } 1519 } 1520 }); 1521 1522}(window.jQuery)); 1523/**
1524Makes editable any HTML element on the page. Applied as jQuery method. 1525 1526@class editable 1527@uses editableContainer 1528**/ 1529(function ($) { 1530 "use strict"; 1531 1532 var Editable = function (element, options) { 1533 this.$element = $(element); 1534 //data-* has more priority over js options: because dynamically created elements may change data-* 1535 this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element)); 1536 if(this.options.selector) { 1537 this.initLive(); 1538 } else { 1539 this.init(); 1540 } 1541 1542 //check for transition support 1543 if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) { 1544 this.options.highlight = false; 1545 } 1546 }; 1547 1548 Editable.prototype = { 1549 constructor: Editable, 1550 init: function () { 1551 var isValueByText = false, 1552 doAutotext, finalize; 1553 1554 //name 1555 this.options.name = this.options.name || this.$element.attr('id'); 1556 1557 //create input of specified type. Input needed already here to convert value for initial display (e.g. show text by id for select) 1558 //also we set scope option to have access to element inside input specific callbacks (e. g. source as function) 1559 this.options.scope = this.$element[0]; 1560 this.input = $.fn.editableutils.createInput(this.options); 1561 if(!this.input) { 1562 return; 1563 } 1564 1565 //set value from settings or by element's text 1566 if (this.options.value === undefined || this.options.value === null) { 1567 this.value = this.input.html2value($.trim(this.$element.html())); 1568 isValueByText = true; 1569 } else { 1570 /* 1571 value can be string when received from 'data-value' attribute 1572 for complext objects value can be set as json string in data-value attribute, 1573 e.g. data-value="{city: 'Moscow', street: 'Lenina'}" 1574 */ 1575 this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true); 1576 if(typeof this.options.value === 'string') { 1577 this.value = this.input.str2value(this.options.value); 1578 } else { 1579 this.value = this.options.value; 1580 } 1581 } 1582 1583 //add 'editable' class to every editable element 1584 this.$element.addClass('editable'); 1585 1586 //specifically for "textarea" add class .editable-pre-line to keep linebreaks 1587 if(this.input.type === 'textarea') { 1588 this.$element.addClass('editable-pre-line'); 1589 } 1590 1591 //attach handler activating editable. In disabled mode it just prevent default action (useful for links) 1592 if(this.options.toggle !== 'manual') { 1593 this.$element.addClass('editable-click'); 1594 this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){ 1595 //prevent following link if editable enabled 1596 if(!this.options.disabled) { 1597 e.preventDefault(); 1598 } 1599 1600 //stop propagation not required because in document click handler it checks event target 1601 //e.stopPropagation(); 1602 1603 if(this.options.toggle === 'mouseenter') { 1604 //for hover only show container 1605 this.show(); 1606 } else { 1607 //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener 1608 var closeAll = (this.options.toggle !== 'click'); 1609 this.toggle(closeAll); 1610 } 1611 }, this)); 1612 } else { 1613 this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually 1614 } 1615 1616 //if display is function it's far more convinient to have autotext = always to render correctly on init 1617 //see https://github.com/vitalets/x-editable-yii/issues/34 1618 if(typeof this.options.display === 'function') { 1619 this.options.autotext = 'always'; 1620 } 1621 1622 //check conditions for autotext: 1623 switch(this.options.autotext) { 1624 case 'always': 1625 doAutotext = true; 1626 break; 1627 case 'auto': 1628 //if element text is empty and value is defined and value not generated by text --> run autotext 1629 doAutotext = !$.trim(this.$element.text()).length && this.value !== null && this.value !== undefined && !isValueByText; 1630 break; 1631 default: 1632 doAutotext = false;
1633 } 1634 1635 //depending on autotext run render() or just finilize init 1636 $.when(doAutotext ? this.render() : true).then($.proxy(function() { 1637 if(this.options.disabled) { 1638 this.disable(); 1639 } else { 1640 this.enable(); 1641 } 1642 /** 1643 Fired when element was initialized by `$().editable()` method. 1644 Please note that you should setup `init` handler **before** applying `editable`. 1645 1646 @event init 1647 @param {Object} event event object 1648 @param {Object} editable editable instance (as here it cannot accessed via data('editable')) 1649 @since 1.2.0 1650 @example 1651 $('#username').on('init', function(e, editable) { 1652 alert('initialized ' + editable.options.name); 1653 }); 1654 $('#username').editable(); 1655 **/ 1656 this.$element.triggerHandler('init', this); 1657 }, this)); 1658 }, 1659 1660 /* 1661 Initializes parent element for live editables 1662 */ 1663 initLive: function() { 1664 //store selector 1665 var selector = this.options.selector; 1666 //modify options for child elements 1667 this.options.selector = false; 1668 this.options.autotext = 'never'; 1669 //listen toggle events 1670 this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){ 1671 var $target = $(e.target).closest(selector); 1672 if(!$target.data('editable')) { 1673 //if delegated element initially empty, we need to clear it's text (that was manually set to `empty` by user) 1674 //see https://github.com/vitalets/x-editable/issues/137 1675 if($target.hasClass(this.options.emptyclass)) { 1676 $target.empty(); 1677 } 1678 $target.editable(this.options).trigger(e); 1679 } 1680 }, this)); 1681 }, 1682 1683 /* 1684 Renders value into element's text. 1685 Can call custom display method from options. 1686 Can return deferred object. 1687 @method render() 1688 @param {mixed} response server response (if exist) to pass into display function 1689 */ 1690 render: function(response) { 1691 //do not display anything 1692 if(this.options.display === false) { 1693 return; 1694 } 1695 1696 //if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded 1697 if(this.input.value2htmlFinal) { 1698 return this.input.value2html(this.value, this.$element[0], this.options.display, response); 1699 //if display method defined --> use it 1700 } else if(typeof this.options.display === 'function') { 1701 return this.options.display.call(this.$element[0], this.value, response); 1702 //else use input's original value2html() method 1703 } else { 1704 return this.input.value2html(this.value, this.$element[0]); 1705 } 1706 }, 1707 1708 /** 1709 Enables editable 1710 @method enable() 1711 **/ 1712 enable: function() { 1713 this.options.disabled = false; 1714 this.$element.removeClass('editable-disabled'); 1715 this.handleEmpty(this.isEmpty); 1716 if(this.options.toggle !== 'manual') { 1717 if(this.$element.attr('tabindex') === '-1') { 1718 this.$element.removeAttr('tabindex'); 1719 } 1720 } 1721 }, 1722 1723 /** 1724 Disables editable 1725 @method disable() 1726 **/ 1727 disable: function() { 1728 this.options.disabled = true; 1729 this.hide(); 1730 this.$element.addClass('editable-disabled'); 1731 this.handleEmpty(this.isEmpty);
1732 //do not stop focus on this element 1733 this.$element.attr('tabindex', -1); 1734 }, 1735 1736 /** 1737 Toggles enabled / disabled state of editable element 1738 @method toggleDisabled() 1739 **/ 1740 toggleDisabled: function() { 1741 if(this.options.disabled) { 1742 this.enable(); 1743 } else { 1744 this.disable(); 1745 } 1746 }, 1747 1748 /** 1749 Sets new option 1750 1751 @method option(key, value) 1752 @param {string|object} key option name or object with several options 1753 @param {mixed} value option new value 1754 @example 1755 $('.editable').editable('option', 'pk', 2); 1756 **/ 1757 option: function(key, value) { 1758 //set option(s) by object 1759 if(key && typeof key === 'object') { 1760 $.each(key, $.proxy(function(k, v){ 1761 this.option($.trim(k), v); 1762 }, this)); 1763 return; 1764 } 1765 1766 //set option by string 1767 this.options[key] = value; 1768 1769 //disabled 1770 if(key === 'disabled') { 1771 return value ? this.disable() : this.enable(); 1772 } 1773 1774 //value 1775 if(key === 'value') { 1776 this.setValue(value); 1777 } 1778 1779 //transfer new option to container! 1780 if(this.container) { 1781 this.container.option(key, value); 1782 } 1783 1784 //pass option to input directly (as it points to the same in form) 1785 if(this.input.option) { 1786 this.input.option(key, value); 1787 } 1788 1789 }, 1790 1791 /* 1792 * set emptytext if element is empty 1793 */ 1794 handleEmpty: function (isEmpty) { 1795 //do not handle empty if we do not display anything 1796 if(this.options.display === false) { 1797 return; 1798 } 1799 1800 /* 1801 isEmpty may be set directly as param of method. 1802 It is required when we enable/disable field and can't rely on content 1803 as node content is text: "Empty" that is not empty %) 1804 */ 1805 if(isEmpty !== undefined) { 1806 this.isEmpty = isEmpty; 1807 } else { 1808 //detect empty 1809 //for some inputs we need more smart check 1810 //e.g. wysihtml5 may have <br>, <p></p>, <img> 1811 if(typeof(this.input.isEmpty) === 'function') { 1812 this.isEmpty = this.input.isEmpty(this.$element); 1813 } else { 1814 this.isEmpty = $.trim(this.$element.html()) === ''; 1815 } 1816 } 1817 1818 //emptytext shown only for enabled 1819 if(!this.options.disabled) { 1820 if (this.isEmpty) { 1821 this.$element.html(this.options.emptytext); 1822 if(this.options.emptyclass) { 1823 this.$element.addClass(this.options.emptyclass); 1824 } 1825 } else if(this.options.emptyclass) { 1826 this.$element.removeClass(this.options.emptyclass); 1827 } 1828 } else { 1829 //below required if element disable property was changed 1830 if(this.isEmpty) { 1831 this.$element.empty(); 1832 if(this.options.emptyclass) { 1833 this.$element.removeClass(this.options.emptyclass); 1834 } 1835 } 1836 } 1837 }, 1838 1839 /** 1840 Shows container with form 1841 @method show() 1842 @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true. 1843 **/ 1844 show: function (closeAll) { 1845 if(this.options.disabled) { 1846 return; 1847 } 1848 1849 //init editableContainer: popover, tooltip, inline, etc.. 1850 if(!this.container) { 1851 var containerOptions = $.extend({}, this.options, { 1852 value: this.value, 1853 input: this.input //pass input to form (as it is already created) 1854 }); 1855 this.$element.editableContainer(containerOptions); 1856 //listen `save` event 1857 this.$element.on("save.internal", $.proxy(this.save, this)); 1858 this.container = this.$element.data('editableContainer'); 1859 } else if(this.container.tip().is(':visible')) { 1860 return; 1861 } 1862 1863 //show container 1864 this.container.show(closeAll); 1865 }, 1866 1867 /** 1868 Hides container with form 1869 @method hide() 1870 **/ 1871 hide: function () { 1872 if(this.container) { 1873 this.container.hide(); 1874 } 1875 }, 1876 1877 /** 1878 Toggles container visibility (show / hide)
1879 @method toggle() 1880 @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true. 1881 **/ 1882 toggle: function(closeAll) { 1883 if(this.container && this.container.tip().is(':visible')) { 1884 this.hide(); 1885 } else { 1886 this.show(closeAll); 1887 } 1888 }, 1889 1890 /* 1891 * called when form was submitted 1892 */ 1893 save: function(e, params) { 1894 //mark element with unsaved class if needed 1895 if(this.options.unsavedclass) { 1896 /* 1897 Add unsaved css to element if: 1898 - url is not user's function 1899 - value was not sent to server 1900 - params.response === undefined, that means data was not sent 1901 - value changed 1902 */ 1903 var sent = false; 1904 sent = sent || typeof this.options.url === 'function'; 1905 sent = sent || this.options.display === false; 1906 sent = sent || params.response !== undefined; 1907 sent = sent || (this.options.savenochange && this.input.value2str(this.value) !== this.input.value2str(params.newValue)); 1908 1909 if(sent) { 1910 this.$element.removeClass(this.options.unsavedclass); 1911 } else { 1912 this.$element.addClass(this.options.unsavedclass); 1913 } 1914 } 1915
1916 //highlight when saving 1917 if(this.options.highlight) { 1918 var $e = this.$element, 1919 bgColor = $e.css('background-color'); 1920 1921 $e.css('background-color', this.options.highlight); 1922 setTimeout(function(){ 1923 if(bgColor === 'transparent') { 1924 bgColor = ''; 1925 } 1926 $e.css('background-color', bgColor); 1927 $e.addClass('editable-bg-transition'); 1928 setTimeout(function(){ 1929 $e.removeClass('editable-bg-transition'); 1930 }, 1700); 1931 }, 10); 1932 } 1933 1934 //set new value 1935 this.setValue(params.newValue, false, params.response); 1936 1937 /** 1938 Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance 1939 1940 @event save 1941 @param {Object} event event object 1942 @param {Object} params additional params 1943 @param {mixed} params.newValue submitted value 1944 @param {Object} params.response ajax response 1945 @example 1946 $('#username').on('save', function(e, params) { 1947 alert('Saved value: ' + params.newValue); 1948 }); 1949 **/ 1950 //event itself is triggered by editableContainer. Description here is only for documentation 1951 }, 1952 1953 validate: function () { 1954 if (typeof this.options.validate === 'function') { 1955 return this.options.validate.call(this, this.value); 1956 } 1957 }, 1958 1959 /** 1960 Sets new value of editable 1961 @method setValue(value, convertStr) 1962 @param {mixed} value new value 1963 @param {boolean} convertStr whether to convert value from string to internal format 1964 **/ 1965 setValue: function(value, convertStr, response) { 1966 if(convertStr) { 1967 this.value = this.input.str2value(value); 1968 } else { 1969 this.value = value; 1970 } 1971 if(this.container) { 1972 this.container.option('value', this.value); 1973 } 1974 $.when(this.render(response)) 1975 .then($.proxy(function() { 1976 this.handleEmpty(); 1977 }, this)); 1978 }, 1979 1980 /** 1981 Activates input of visible container (e.g. set focus) 1982 @method activate() 1983 **/ 1984 activate: function() { 1985 if(this.container) { 1986 this.container.activate(); 1987 } 1988 }, 1989 1990 /** 1991 Removes editable feature from element 1992 @method destroy() 1993 **/ 1994 destroy: function() { 1995 this.disable(); 1996 1997 if(this.container) { 1998 this.container.destroy(); 1999 } 2000 2001 this.input.destroy(); 2002 2003 if(this.options.toggle !== 'manual') { 2004 this.$element.removeClass('editable-click'); 2005 this.$element.off(this.options.toggle + '.editable'); 2006 } 2007 2008 this.$element.off("save.internal"); 2009 2010 this.$element.removeClass('editable editable-open editable-disabled'); 2011 this.$element.removeData('editable'); 2012 } 2013 }; 2014 2015 /* EDITABLE PLUGIN DEFINITION 2016 * ======================= */ 2017 2018 /** 2019 jQuery method to initialize editable element. 2020 2021 @method $().editable(options) 2022 @params {Object} options 2023 @example 2024 $('#username').editable({ 2025 type: 'text', 2026 url: '/post', 2027 pk: 1 2028 }); 2029 **/ 2030 $.fn.editable = function (option) { 2031 //special API methods returning non-jquery object 2032 var result = {}, args = arguments, datakey = 'editable'; 2033 switch (option) { 2034 /** 2035 Runs client-side validation for all matched editables 2036 2037 @method validate() 2038 @returns {Object} validation errors map 2039 @example 2040 $('#username, #fullname').editable('validate'); 2041 // possible result: 2042 { 2043 username: "username is required", 2044 fullname: "fullname should be minimum 3 letters length" 2045 } 2046 **/ 2047 case 'validate': 2048 this.each(function () { 2049 var $this = $(this), data = $this.data(datakey), error; 2050 if (data && (error = data.validate())) { 2051 result[data.options.name] = error; 2052 } 2053 }); 2054 return result; 2055 2056 /** 2057 Returns current values of editable elements. 2058 Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements. 2059 If value of some editable is `null` or `undefined` it is excluded from result object. 2060 When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object. 2061 2062 @method getValue() 2063 @param {bool} isSingle whether to return just value of single element 2064 @returns {Object} object of element names and values 2065 @example 2066 $('#username, #fullname').editable('getValue'); 2067 //result: 2068 { 2069 username: "superuser", 2070 fullname: "John" 2071 } 2072 //isSingle = true 2073 $('#username').editable('getValue', true); 2074 //result "superuser" 2075 **/ 2076 case 'getValue': 2077 if(arguments.length === 2 && arguments[1] === true) { //isSingle = true 2078 result = this.eq(0).data(datakey).value; 2079 } else { 2080 this.each(function () { 2081 var $this = $(this), data = $this.data(datakey); 2082 if (data && data.value !== undefined && data.value !== null) { 2083 result[data.options.name] = data.input.value2submit(data.value); 2084 } 2085 }); 2086 } 2087 return result; 2088 2089 /** 2090 This method collects values from several editable elements and submit them all to server. 2091 Internally it runs client-side validation for all fields and submits only in case of success. 2092 See <a href="#newrecord">creating new records</a> for details. 2093 Since 1.5.1 `submit` can be applied to single element to send data programmatically. In that case 2094 `url`, `success` and `error` is taken from initial options and you can just call `$('#username').editable('submit')`. 2095 2096 @method submit(options) 2097 @param {object} options 2098 @param {object} options.url url to submit data 2099 @param {object} options.data additional data to submit 2100 @param {object} options.ajaxOptions additional ajax options 2101 @param {function} options.error(obj) error handler 2102 @param {function} options.success(obj,config) success handler 2103 @returns {Object} jQuery object 2104 **/ 2105 case 'submit': //collects value, validate and submit to server for creating new record 2106 var config = arguments[1] || {}, 2107 $elems = this, 2108 errors = this.editable('validate'); 2109 2110 // validation ok 2111 if($.isEmptyObject(errors)) { 2112 var ajaxOptions = {}; 2113 2114 // for single element use url, success etc from options 2115 if($elems.length === 1) { 2116 var editable = $elems.data('editable'); 2117 //standard params 2118 var params = { 2119 name: editable.options.name || '', 2120 value: editable.input.value2submit(editable.value), 2121 pk: (typeof editable.options.pk === 'function') ? 2122 editable.options.pk.call(editable.options.scope) : 2123 editable.options.pk 2124 }; 2125 2126 //additional params 2127 if(typeof editable.options.params === 'function') { 2128 params = editable.options.params.call(editable.options.scope, params); 2129 } else { 2130 //try parse json in single quotes (from data-params attribute) 2131 editable.options.params = $.fn.editableutils.tryParseJson(editable.options.params, true); 2132 $.extend(params, editable.options.params); 2133 } 2134 2135 ajaxOptions = { 2136 url: editable.options.url, 2137 data: params, 2138 type: 'POST' 2139 }; 2140 2141 // use success / error from options 2142 config.success = config.success || editable.options.success; 2143 config.error = config.error || editable.options.error; 2144 2145 // multiple elements 2146 } else { 2147 var values = this.editable('getValue'); 2148 2149 ajaxOptions = { 2150 url: config.url, 2151 data: values, 2152 type: 'POST' 2153 }; 2154 } 2155 2156 // ajax success callabck (response 200 OK) 2157 ajaxOptions.success = typeof config.success === 'function' ? function(response) { 2158 config.success.call($elems, response, config); 2159 } : $.noop; 2160 2161 // ajax error callabck 2162 ajaxOptions.error = typeof config.error === 'function' ? function() { 2163 config.error.apply($elems, arguments); 2164 } : $.noop; 2165 2166 // extend ajaxOptions 2167 if(config.ajaxOptions) { 2168 $.extend(ajaxOptions, config.ajaxOptions); 2169 } 2170 2171 // extra data 2172 if(config.data) { 2173 $.extend(ajaxOptions.data, config.data); 2174 } 2175 2176 // perform ajax request 2177 $.ajax(ajaxOptions); 2178 } else { //client-side validation error 2179 if(typeof config.error === 'function') { 2180 config.error.call($elems, errors); 2181 } 2182 } 2183 return this; 2184 } 2185 2186 //return jquery object 2187 return this.each(function () { 2188 var $this = $(this), 2189 data = $this.data(datakey), 2190 options = typeof option === 'object' && option; 2191 2192 //for delegated targets do not store `editable` object for element 2193 //it's allows several different selectors. 2194 //see: https://github.com/vitalets/x-editable/issues/312 2195 if(options && options.selector) { 2196 data = new Editable(this, options); 2197 return; 2198 } 2199 2200 if (!data) { 2201 $this.data(datakey, (data = new Editable(this, options))); 2202 } 2203 2204 if (typeof option === 'string') { //call method 2205 data[option].apply(data, Array.prototype.slice.call(args, 1)); 2206 } 2207 }); 2208 }; 2209 2210 2211 $.fn.editable.defaults = { 2212 /** 2213 Type of input. Can be <code>
2213text|textarea|select|date|checklist</code> and more 2214 2215 @property type 2216 @type string 2217 @default 'text' 2218 **/ 2219 type: 'text', 2220 /** 2221 Sets disabled state of editable 2222 2223 @property disabled 2224 @type boolean 2225 @default false 2226 **/ 2227 disabled: false, 2228 /** 2229 How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>. 2230 When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable. 2231 **Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element, 2232 you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document. 2233 2234 @example 2235 $('#edit-button').click(function(e) { 2236 e.stopPropagation(); 2237 $('#username').editable('toggle'); 2238 }); 2239 2240 @property toggle 2241 @type string 2242 @default 'click' 2243 **/ 2244 toggle: 'click', 2245 /** 2246 Text shown when element is empty. 2247 2248 @property emptytext 2249 @type string 2250 @default 'Empty' 2251 **/ 2252 emptytext: 'Empty', 2253 /** 2254 Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Useful for select and date. 2255 For example, if dropdown list is <code>{1: 'a', 2: 'b'}</code> and element's value set to <code>1</code>, it's html will be automatically set to <code>'a'</code>. 2256 <code>auto</code> - text will be automatically set only if element is empty. 2257 <code>always|never</code> - always(never) try to set element's text. 2258 2259 @property autotext 2260 @type string 2261 @default 'auto' 2262 **/ 2263 autotext: 'auto', 2264 /** 2265 Initial value of input. If not set, taken from element's text. 2266 Note, that if element's text is empty - text is automatically generated from value and can be customized (see `autotext` option). 2267 For example, to display currency sign: 2268 @example 2269 <a id="price" data-type="text" data-value="100"></a> 2270 <script> 2271 $('#price').editable({ 2272 ... 2273 display: function(value) { 2274 $(this).text(value + '$'); 2275 } 2276 }) 2277 </script> 2278 2279 @property value 2280 @type mixed 2281 @default element's text 2282 **/ 2283 value: null, 2284 /** 2285 Callback to perform custom displaying of value in element's text.
2286 If `null`, default input's display used. 2287 If `false`, no displaying methods will be called, element's text will never change. 2288 Runs under element's scope. 2289 _**Parameters:**_ 2290 2291 * `value` current value to be displayed 2292 * `response` server response (if display called after ajax submit), since 1.4.0 2293 2294 For _inputs with source_ (select, checklist) parameters are different: 2295 2296 * `value` current value to be displayed 2297 * `sourceData` array of items for current input (e.g. dropdown items) 2298 * `response` server response (if display called after ajax submit), since 1.4.0 2299 2300 To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`. 2301 2302 @property display 2303 @type function|boolean 2304 @default null 2305 @since 1.2.0 2306 @example 2307 display: function(value, sourceData) { 2308 //display checklist as comma-separated values 2309 var html = [], 2310 checked = $.fn.editableutils.itemsByValue(value, sourceData); 2311 2312 if(checked.length) { 2313 $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); }); 2314 $(this).html(html.join(', ')); 2315 } else { 2316 $(this).empty(); 2317 } 2318 } 2319 **/ 2320 display: null, 2321 /** 2322 Css class applied when editable text is empty. 2323 2324 @property emptyclass 2325 @type string 2326 @since 1.4.1 2327 @default editable-empty 2328 **/ 2329 emptyclass: 'editable-empty', 2330 /** 2331 Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`). 2332 You may set it to `null` if you work with editables locally and submit them together. 2333 2334 @property unsavedclass 2335 @type string 2336 @since 1.4.1 2337 @default editable-unsaved 2338 **/ 2339 unsavedclass: 'editable-unsaved', 2340 /** 2341 If selector is provided, editable will be delegated to the specified targets. 2342 Usefull for dynamically generated DOM elements. 2343 **Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options, 2344 as they actually become editable only after first click. 2345 You should manually set class `editable-click` to these elements. 2346 Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element: 2347 2348 @property selector 2349 @type string 2350 @since 1.4.1 2351 @default null 2352 @example 2353 <div id="user"> 2354 <!-- empty --> 2355 <a href="#" data-name="username" data-type="text" class="editable-click editable-empty" data-value="" title="Username">Empty</a> 2356 <!-- non-empty --> 2357 <a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" class="editable-click" title="Group">Operator</a> 2358 </div> 2359 2360 <script> 2361 $('#user').editable({ 2362 selector: 'a', 2363 url: '/post', 2364 pk: 1 2365 }); 2366 </script> 2367 **/ 2368 selector: null, 2369 /** 2370 Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers. 2371 2372 @property highlight 2373 @type string|boolean 2374 @since 1.4.5 2375 @default #FFFF80 2376 **/ 2377 highlight: '#FFFF80' 2378 }; 2379 2380}(window.jQuery)); 2381 2382/** 2383AbstractInput - base class for all editable inputs. 2384It defines interface to be implemented by any input type. 2385To create your own input you can inherit from this class. 2386 2387@class abstractinput 2388**/ 2389(function ($) { 2390 "use strict"; 2391 2392 //types 2393 $.fn.editabletypes = {}; 2394 2395 var AbstractInput = function () { }; 2396 2397 AbstractInput.prototype = { 2398 /** 2399 Initializes input 2400 2401 @method init() 2402 **/ 2403 init: function(type, options, defaults) { 2404 this.type = type; 2405 this.options = $.extend({}, defaults, options); 2406 }, 2407 2408 /* 2409 this method called before render to init $tpl that is inserted in DOM 2410 */ 2411 prerender: function() { 2412 this.$tpl = $(this.options.tpl); //whole tpl as jquery object 2413 this.$input = this.$tpl; //control itself, can be changed in render method 2414 this.$clear = null; //clear button 2415 this.error = null; //error message, if input cannot be rendered 2416 }, 2417 2418 /** 2419 Renders input from tpl. Can return jQuery deferred object.
2420 Can be overwritten in child objects 2421 2422 @method render() 2423 **/ 2424 render: function() { 2425 2426 }, 2427 2428 /** 2429 Sets element's html by value. 2430 2431 @method value2html(value, element) 2432 @param {mixed} value 2433 @param {DOMElement} element 2434 **/ 2435 value2html: function(value, element) { 2436 $(element)[this.options.escape ? 'text' : 'html']($.trim(value)); 2437 }, 2438 2439 /** 2440 Converts element's html to value 2441 2442 @method html2value(html) 2443 @param {string} html 2444 @returns {mixed} 2445 **/ 2446 html2value: function(html) { 2447 return $('<div>').html(html).text(); 2448 }, 2449 2450 /** 2451 Converts value to string (for internal compare). For submitting to server used value2submit(). 2452 2453 @method value2str(value) 2454 @param {mixed} value 2455 @returns {string} 2456 **/ 2457 value2str: function(value) { 2458 return String(value); 2459 }, 2460 2461 /** 2462 Converts string received from server into value. Usually from `data-value` attribute. 2463 2464 @method str2value(str) 2465 @param {string} str 2466 @returns {mixed} 2467 **/ 2468 str2value: function(str) { 2469 return str; 2470 }, 2471 2472 /** 2473 Converts value for submitting to server. Result can be string or object. 2474 2475 @method value2submit(value) 2476 @param {mixed} value 2477 @returns {mixed} 2478 **/ 2479 value2submit: function(value) { 2480 return value; 2481 }, 2482 2483 /** 2484 Sets value of input. 2485 2486 @method value2input(value) 2487 @param {mixed} value 2488 **/ 2489 value2input: function(value) { 2490 this.$input.val(value); 2491 }, 2492 2493 /** 2494 Returns value of input. Value can be object (e.g. datepicker) 2495 2496 @method input2value() 2497 **/ 2498 input2value: function() { 2499 return this.$input.val(); 2500 }, 2501 2502 /** 2503 Activates input. For text it sets focus. 2504 2505 @method activate() 2506 **/ 2507 activate: function() { 2508 if(this.$input.is(':visible')) { 2509 this.$input.focus(); 2510 } 2511 }, 2512 2513 /** 2514 Creates input. 2515 2516 @method clear() 2517 **/ 2518 clear: function() { 2519 this.$input.val(null); 2520 }, 2521 2522 /** 2523 method to escape html. 2524 **/ 2525 escape: function(str) { 2526 return $('<div>').text(str).html(); 2527 }, 2528 2529 /** 2530 attach handler to automatically submit form when value changed (useful when buttons not shown) 2531 **/ 2532 autosubmit: function() { 2533 2534 }, 2535 2536 /** 2537 Additional actions when destroying element 2538 **/ 2539 destroy: function() { 2540 }, 2541 2542 // -------- helper functions -------- 2543 setClass: function() { 2544 if(this.options.inputclass) { 2545 this.$input.addClass(this.options.inputclass); 2546 } 2547 }, 2548 2549 setAttr: function(attr) { 2550 if (this.options[attr] !== undefined && this.options[attr] !== null) { 2551 this.$input.attr(attr, this.options[attr]); 2552 } 2553 }, 2554 2555 option: function(key, value) { 2556 this.options[key] = value; 2557 } 2558 2559 }; 2560 2561 AbstractInput.defaults = { 2562 /** 2563 HTML template of input. Normally you should not change it. 2564 2565 @property tpl 2566 @type string 2567 @default '' 2568 **/ 2569 tpl: '', 2570 /** 2571 CSS class automatically applied to input 2572 2573 @property inputclass 2574 @type string 2575 @default null 2576 **/ 2577 inputclass: null, 2578 2579 /** 2580 If `true` - html will be escaped in content of element via $.text() method. 2581 If `false` - html will not be escaped, $.html() used. 2582 When you use own `display` function, this option obviosly has no effect. 2583 2584 @property escape 2585 @type boolean 2586 @since 1.5.0 2587 @default true 2588 **/ 2589 escape: true, 2590 2591 //scope for external methods (e.g. source defined as function) 2592 //for internal use only 2593 scope: null, 2594 2595 //need to re-declare showbuttons here to get it's value from common config (passed only options existing in defaults) 2596 showbuttons: true 2597 }; 2598 2599 $.extend($.fn.editabletypes, {abstractinput: AbstractInput}); 2600 2601}(window.jQuery)); 2602 2603/** 2604List - abstract class for inputs that have source option loaded from js array or via ajax 2605 2606@class list 2607@extends abstractinput 2608**/ 2609(function ($) { 2610 "use strict"; 2611 2612 var List = function (options) { 2613 2614 }; 2615 2616 $.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput); 2617 2618 $.extend(List.prototype, { 2619 render: function () { 2620 var deferred = $.Deferred(); 2621 2622 this.error = null; 2623 this.onSourceReady(function () { 2624 this.renderList(); 2625 deferred.resolve(); 2626 }, function () { 2627 this.error = this.options.sourceError; 2628 deferred.resolve(); 2629 }); 2630 2631 return deferred.promise(); 2632 }, 2633 2634 html2value: function (html) { 2635 return null; //can't set value by text 2636 }, 2637 2638 value2html: function (value, element, display, response) { 2639 var deferred = $.Deferred(), 2640 success = function () { 2641 if(typeof display === 'function') { 2642 //custom display method 2643 display.call(element, value, this.sourceData, response); 2644 } else { 2645 this.value2htmlFinal(value, element); 2646 } 2647 deferred.resolve(); 2648 }; 2649 2650 //for null value just call success without loading source 2651 if(value === null) { 2652 success.call(this); 2653 } else { 2654 this.onSourceReady(success, function () { deferred.resolve(); }); 2655 } 2656 2657 return deferred.promise(); 2658 }, 2659 2660 // ------------- additional functions ------------ 2661 2662 onSourceReady: function (success, error) { 2663 //run source if it function 2664 var source; 2665 if ($.isFunction(this.options.source)) { 2666 source = this.options.source.call(this.options.scope); 2667 this.sourceData = null; 2668 //note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed 2669 } else { 2670 source = this.options.source; 2671 } 2672 2673 //if allready loaded just call success 2674 if(this.options.sourceCache && $.isArray(this.sourceData)) { 2675 success.call(this); 2676 return; 2677 } 2678 2679 //try parse json in single quotes (for double quotes jquery does automatically) 2680 try { 2681 source = $.fn.editableutils.tryParseJson(source, false); 2682 } catch (e) { 2683 error.call(this); 2684 return; 2685 } 2686 2687 //loading from url 2688 if (typeof source === 'string') { 2689 //try to get sourceData from cache 2690 if(this.options.sourceCache) { 2691 var cacheID = source, 2692 cache; 2693 2694 if (!$(document).data(cacheID)) { 2695 $(document).data(cacheID, {}); 2696 } 2697 cache = $(document).data(cacheID); 2698 2699 //check for cached data 2700 if (cache.loading === false && cache.sourceData) { //take source from cache 2701 this.sourceData = cache.sourceData; 2702 this.doPrepend(); 2703 success.call(this); 2704 return; 2705 } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later 2706 cache.callbacks.push($.proxy(function () { 2707 this.sourceData = cache.sourceData; 2708 this.doPrepend(); 2709 success.call(this); 2710 }, this)); 2711 2712 //also collecting error callbacks 2713 cache.err_callbacks.push($.proxy(error, this)); 2714 return; 2715 } else { //no cache yet, activate it 2716 cache.loading = true; 2717 cache.callbacks = []; 2718 cache.err_callbacks = []; 2719 } 2720 } 2721
2722 //ajaxOptions for source. Can be overwritten bt options.sourceOptions 2723 var ajaxOptions = $.extend({ 2724 url: source, 2725 type: 'get', 2726 cache: false, 2727 dataType: 'json', 2728 success: $.proxy(function (data) { 2729 if(cache) { 2730 cache.loading = false; 2731 } 2732 this.sourceData = this.makeArray(data); 2733 if($.isArray(this.sourceData)) { 2734 if(cache) { 2735 //store result in cache 2736 cache.sourceData = this.sourceData; 2737 //run success callbacks for other fields waiting for this source 2738 $.each(cache.callbacks, function () { this.call(); }); 2739 } 2740 this.doPrepend(); 2741 success.call(this); 2742 } else { 2743 error.call(this); 2744 if(cache) { 2745 //run error callbacks for other fields waiting for this source 2746 $.each(cache.err_callbacks, function () { this.call(); }); 2747 } 2748 } 2749 }, this), 2750 error: $.proxy(function () { 2751 error.call(this); 2752 if(cache) { 2753 cache.loading = false; 2754 //run error callbacks for other fields 2755 $.each(cache.err_callbacks, function () { this.call(); }); 2756 } 2757 }, this) 2758 }, this.options.sourceOptions); 2759 2760 //loading sourceData from server 2761 $.ajax(ajaxOptions); 2762 2763 } else { //options as json/array 2764 this.sourceData = this.makeArray(source); 2765 2766 if($.isArray(this.sourceData)) { 2767 this.doPrepend(); 2768 success.call(this); 2769 } else { 2770 error.call(this); 2771 } 2772 } 2773 }, 2774 2775 doPrepend: function () { 2776 if(this.options.prepend === null || this.options.prepend === undefined) { 2777 return; 2778 } 2779 2780 if(!$.isArray(this.prependData)) { 2781 //run prepend if it is function (once) 2782 if ($.isFunction(this.options.prepend)) { 2783 this.options.prepend = this.options.prepend.call(this.options.scope); 2784 } 2785 2786 //try parse json in single quotes 2787 this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true); 2788 2789 //convert prepend from string to object 2790 if (typeof this.options.prepend === 'string') { 2791 this.options.prepend = {'': this.options.prepend}; 2792 } 2793 2794 this.prependData = this.makeArray(this.options.prepend); 2795 } 2796 2797 if($.isArray(this.prependData) && $.isArray(this.sourceData)) { 2798 this.sourceData = this.prependData.concat(this.sourceData); 2799 } 2800 }, 2801 2802 /* 2803 renders input list 2804 */ 2805 renderList: function() { 2806 // this method should be overwritten in child class 2807 }, 2808 2809 /* 2810 set element's html by value 2811 */ 2812 value2htmlFinal: function(value, element) { 2813 // this method should be overwritten in child class 2814 }, 2815 2816 /** 2817 * convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}] 2818 */ 2819 makeArray: function(data) { 2820 var count, obj, result = [], item, iterateItem; 2821 if(!data || typeof data === 'string') { 2822 return null; 2823 } 2824 2825 if($.isArray(data)) { //array 2826 /* 2827 function to iterate inside item of array if item is object. 2828 Caclulates count of keys in item and store in obj. 2829 */ 2830 iterateItem = function (k, v) { 2831 obj = {value: k, text: v}; 2832 if(count++ >= 2) { 2833 return false;// exit from `each` if item has more than one key. 2834 } 2835 }; 2836 2837 for(var i = 0; i < data.length; i++) { 2838 item = data[i]; 2839 if(typeof item === 'object') { 2840 count = 0; //count of keys inside item 2841 $.each(item, iterateItem); 2842 //case: [{val1: 'text1'}, {val2: 'text2} ...] 2843 if(count === 1) { 2844 result.push(obj); 2845 //case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...] 2846 } else if(count > 1) { 2847 //removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text') 2848 if(item.children) { 2849 item.children = this.makeArray(item.children); 2850 } 2851 result.push(item); 2852 } 2853 } else { 2854 //case: ['text1', 'text2' ...] 2855 result.push({value: item, text: item}); 2856 } 2857 } 2858 } else { //case: {val1: 'text1', val2: 'text2, ...} 2859 $.each(data, function (k, v) { 2860 result.push({value: k, text: v}); 2861 }); 2862 } 2863 return result; 2864 }, 2865 2866 option: function(key, value) { 2867 this.options[key] = value; 2868 if(key === 'source') { 2869 this.sourceData = null; 2870 } 2871 if(key === 'prepend') { 2872 this.prependData = null; 2873 } 2874 } 2875 2876 }); 2877 2878 List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { 2879 /** 2880 Source data for list. 2881 If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]` 2882 For compability, object format is also supported: `{"1": "text1", "2"
2882: "text2" ...}` but it does not guarantee elements order. 2883 2884 If **string** - considered ajax url to load items. In that case results will be cached for fields with the same source and name. See also `sourceCache` option. 2885 2886 If **function**, it should return data in format above (since 1.4.0). 2887 2888 Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only): 2889 @example 2890 [ 2891 {text: "group1", children: [ 2892 {value: 1, text: "text1"}, 2893 {value: 2, text: "text2"} 2894 ]}, 2895 ... 2896 ] 2897 2898 2899 @property source 2900 @type string | array | object | function 2901 @default null 2902 **/ 2903 source: null, 2904 /** 2905 Data automatically prepended to the beginning of dropdown list. 2906 2907 @property prepend 2908 @type string | array | object | function 2909 @default false 2910 **/ 2911 prepend: false, 2912 /** 2913 Error message when list cannot be loaded (e.g. ajax error) 2914 2915 @property sourceError 2916 @type string 2917 @default Error when loading list 2918 **/ 2919 sourceError: 'Error when loading list', 2920 /** 2921 if `true` and source is **string url** - results will be cached for fields with the same source. 2922 Usefull for editable column in grid to prevent extra requests. 2923 2924 @property sourceCache 2925 @type boolean 2926 @default true 2927 @since 1.2.0 2928 **/ 2929 sourceCache: true, 2930 /** 2931 Additional ajax options to be used in $.ajax() when loading list from server. 2932 Useful to send extra parameters or change request method. 2933 2934 @property sourceOptions 2935 @type object|function 2936 @default null 2937 @since 1.5.0 2938 @example 2939 sourceOptions: { 2940 data: {param: 123}, 2941 type: 'post' 2942 } 2943 2944 **/ 2945 sourceOptions: null 2946 }); 2947 2948 $.fn.editabletypes.list = List; 2949 2950}(window.jQuery)); 2951 2952/** 2953Text input 2954 2955@class text 2956@extends abstractinput 2957@final 2958@example 2959<a href="#" id="username" data-type="text" data-pk="1">awesome</a> 2960<script> 2961$(function(){ 2962 $('#username').editable({ 2963 url: '/post', 2964 title: 'Enter username' 2965 }); 2966}); 2967</script> 2968**/ 2969(function ($) { 2970 "use strict"; 2971 2972 var Text = function (options) { 2973 this.init('text', options, Text.defaults); 2974 }; 2975 2976 $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput); 2977 2978 $.extend(Text.prototype, { 2979 render: function() { 2980 this.renderClear(); 2981 this.setClass(); 2982 this.setAttr('placeholder'); 2983 }, 2984 2985 activate: function() { 2986 if(this.$input.is(':visible')) { 2987 this.$input.focus(); 2988// if (this.$input.is('input,textarea') && !this.$input.is('[type="checkbox"],[type="range"],[type="number"],[type="email"]')) { 2989 if (this.$input.is('input,textarea') && !this.$input.is('[type="checkbox"],[type="range"]')) { 2990 $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length); 2991 } 2992 2993 if(this.toggleClear) { 2994 this.toggleClear(); 2995 } 2996 } 2997 }, 2998 2999 //render clear button 3000 renderClear: function() { 3001 if (this.options.clear) { 3002 this.$clear = $('<span class="editable-clear-x"></span>'); 3003 this.$input.after(this.$clear) 3004 .css('padding-right', 24) 3005 .keyup($.proxy(function(e) { 3006 //arrows, enter, tab, etc 3007 if(~$.inArray(e.keyCode, [40,38,9,13,27])) { 3008 return; 3009 } 3010 3011 clearTimeout(this.t); 3012 var that = this; 3013 this.t = setTimeout(function() { 3014 that.toggleClear(e); 3015 }, 100); 3016 3017 }, this)) 3018 .parent().css('position', 'relative'); 3019 3020 this.$clear.click($.proxy(this.clear, this)); 3021 } 3022 }, 3023 3024 postrender: function() { 3025 /* 3026 //now `clear` is positioned via css 3027 if(this.$clear) { 3028 //can position clear button only here, when form is shown and height can be calculated 3029// var h = this.$input.outerHeight(true) || 20, 3030 var h = this.$clear.parent().height(), 3031 delta = (h - this.$clear.height()) / 2; 3032 3033 //this.$clear.css({bottom: delta, right: delta}); 3034 } 3035 */ 3036 }, 3037 3038 //show / hide clear button 3039 toggleClear: function(e) { 3040 if(!this.$clear) { 3041 return; 3042 } 3043 3044 var len = this.$input.val().length, 3045 visible = this.$clear.is(':visible'); 3046 3047 if(len && !visible) { 3048 this.$clear.show(); 3049 } 3050 3051 if(!len && visible) { 3052 this.$clear.hide(); 3053 } 3054 }, 3055 3056 clear: function() { 3057 this.$clear.hide(); 3058 this.$input.val('').focus(); 3059 } 3060 }); 3061 3062 Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { 3063 /** 3064 @property tpl 3065 @default <input type="text"> 3066 **/ 3067 tpl: '<input type="text">', 3068 /** 3069 Placeholder attribute of input. Shown when input is empty. 3070 3071 @property placeholder 3072 @type string 3073 @default null 3074 **/ 3075 placeholder: null, 3076 3077 /** 3078 Whether to show `clear` button 3079 3080 @property clear 3081 @type boolean 3082 @default true 3083 **/ 3084 clear: true 3085 }); 3086 3087 $.fn.editabletypes.text = Text; 3088 3089}(window.jQuery)); 3090 3091/** 3092Textarea input 3093 3094@class textarea 3095@extends abstractinput 3096@final 3097@example 3098<a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a> 3099<script> 3100$(function(){ 3101 $('#comments').editable({ 3102 url: '/post', 3103 title: 'Enter comments', 3104 rows: 10 3105 }); 3106}); 3107</script> 3108**/ 3109(function ($) { 3110 "use strict"; 3111 3112 var Textarea = function (options) { 3113 this.init('textarea', options, Textarea.defaults); 3114 }; 3115 3116 $.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput); 3117 3118 $.extend(Textarea.prototype, { 3119 render: function () { 3120 this.setClass(); 3121 this.setAttr('placeholder'); 3122 this.setAttr('rows'); 3123 3124 //ctrl + enter
3125 this.$input.keydown(function (e) { 3126 if (e.ctrlKey && e.which === 13) { 3127 $(this).closest('form').submit(); 3128 } 3129 }); 3130 }, 3131 3132 //using `white-space: pre-wrap` solves \n <--> BR conversion very elegant! 3133 /* 3134 value2html: function(value, element) { 3135 var html = '', lines; 3136 if(value) { 3137 lines = value.split("\n"); 3138 for (var i = 0; i < lines.length; i++) { 3139 lines[i] = $('<div>').text(lines[i]).html(); 3140 } 3141 html = lines.join('<br>'); 3142 } 3143 $(element).html(html); 3144 }, 3145 3146 html2value: function(html) { 3147 if(!html) { 3148 return ''; 3149 } 3150 3151 var regex = new RegExp(String.fromCharCode(10), 'g'); 3152 var lines = html.split(/<br\s*\/?>/i); 3153 for (var i = 0; i < lines.length; i++) { 3154 var text = $('<div>').html(lines[i]).text(); 3155 3156 // Remove newline characters (\n) to avoid them being converted by value2html() method 3157 // thus adding extra <br> tags 3158 text = text.replace(regex, ''); 3159 3160 lines[i] = text; 3161 } 3162 return lines.join("\n"); 3163 }, 3164 */ 3165 activate: function() { 3166 $.fn.editabletypes.text.prototype.activate.call(this); 3167 } 3168 }); 3169 3170 Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { 3171 /** 3172 @property tpl 3173 @default <textarea></textarea> 3174 **/ 3175 tpl:'<textarea></textarea>', 3176 /** 3177 @property inputclass 3178 @default input-large 3179 **/ 3180 inputclass: 'input-large', 3181 /** 3182 Placeholder attribute of input. Shown when input is empty. 3183 3184 @property placeholder 3185 @type string 3186 @default null 3187 **/ 3188 placeholder: null, 3189 /** 3190 Number of rows in textarea 3191 3192 @property rows 3193 @type integer 3194 @default 7 3195 **/ 3196 rows: 7 3197 }); 3198 3199 $.fn.editabletypes.textarea = Textarea; 3200 3201}(window.jQuery)); 3202 3203/** 3204Select (dropdown) 3205 3206@class select 3207@extends list 3208@final 3209@example 3210<a href="#" id="status" data-type="select" data-pk="1" data-url="/post" data-title="Select status"></a> 3211<script> 3212$(function(){ 3213 $('#status').editable({ 3214 value: 2, 3215 source: [ 3216 {value: 1, text: 'Active'}, 3217 {value: 2, text: 'Blocked'}, 3218 {value: 3, text: 'Deleted'} 3219 ] 3220 }); 3221}); 3222</script> 3223**/ 3224(function ($) { 3225 "use strict"; 3226 3227 var Select = function (options) { 3228 this.init('select', options, Select.defaults); 3229 }; 3230 3231 $.fn.editableutils.inherit(Select, $.fn.editabletypes.list); 3232 3233 $.extend(Select.prototype, { 3234 renderList: function() { 3235 this.$input.empty(); 3236 var escape = this.options.escape; 3237 3238 var fillItems = function($el, data) { 3239 var attr; 3240 if($.isArray(data)) { 3241 for(var i=0; i<data.length; i++) { 3242 attr = {}; 3243 if(data[i].children) { 3244 attr.label = data[i].text; 3245 $el.append(fillItems($('<optgroup>', attr), data[i].children)); 3246 } else { 3247 attr.value = data[i].value; 3248 if(data[i].disabled) { 3249 attr.disabled = true; 3250 } 3251 var $option = $('<option>', attr); 3252 $option[escape ? 'text' : 'html'](data[i].text); 3253 $el.append($option); 3254 } 3255 } 3256 } 3257 return $el; 3258 }; 3259 3260 fillItems(this.$input, this.sourceData); 3261 3262 this.setClass(); 3263 3264 //enter submit 3265 this.$input.on('keydown.editable', function (e) { 3266 if (e.which === 13) { 3267 $(this).closest('form').submit(); 3268 } 3269 }); 3270 }, 3271 3272 value2htmlFinal: function(value, element) { 3273 var text = '', 3274 items = $.fn.editableutils.itemsByValue(value, this.sourceData); 3275 3276 if(items.length) { 3277 text = items[0].text; 3278 } 3279 3280 //$(element).text(text); 3281 $.fn.editabletypes.abstractinput.prototype.value2html.call(this, text, element); 3282 }, 3283 3284 autosubmit: function() { 3285 this.$input.off('keydown.editable').on('change.editable', function(){ 3286 $(this).closest('form').submit(); 3287 }); 3288 } 3289 }); 3290 3291 Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, { 3292 /** 3293 @property tpl 3294 @default <select></select> 3295 **/ 3296 tpl:'<select></select>' 3297 }); 3298 3299 $.fn.editabletypes.select = Select; 3300 3301}(window.jQuery)); 3302 3303/** 3304List of checkboxes. 3305Internally value stored as javascript array of values. 3306 3307@class checklist 3308@extends list 3309@final 3310@example 3311<a href="#" id="options" data-type="checklist" data-pk="1" data-url="/post" data-title="Select options"></a> 3312<script> 3313$(function(){ 3314 $('#options').editable({ 3315 value: [2, 3], 3316 source: [ 3317 {value: 1, text: 'option1'}, 3318 {value: 2, text: 'option2'}, 3319 {value: 3, text: 'option3'} 3320 ] 3321 }); 3322}); 3323</script> 3324**/ 3325(function ($) { 3326 "use strict"; 3327 3328 var Checklist = function (options) { 3329 this.init('checklist', options, Checklist.defaults); 3330 }; 3331 3332 $.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list); 3333 3334 $.extend(Checklist.prototype, { 3335 renderList: function() { 3336 var $label, $div; 3337 3338 this.$tpl.empty(); 3339 3340 if(!$.isArray(this.sourceData)) { 3341 return; 3342 } 3343 3344 for(var i=0; i<this.sourceData.length; i++) { 3345 $label = $('<label>').append($('<input>', { 3346 type: 'checkbox', 3347 value: this.sourceData[i].value 3348 })); 3349 var $option = $('<span>'); 3350 $option[this.options.escape ? 'text' : 'html'](' '+this.sourceData[i].text); 3351 $label.append($option); 3352 3353 $('<div>').append($label).appendTo(this.$tpl); 3354 } 3355
3356 this.$input = this.$tpl.find('input[type="checkbox"]'); 3357 this.setClass(); 3358 }, 3359 3360 value2str: function(value) { 3361 return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : ''; 3362 }, 3363 3364 //parse separated string 3365 str2value: function(str) { 3366 var reg, value = null; 3367 if(typeof str === 'string' && str.length) { 3368 reg = new RegExp('\\s*'+$.trim(this.options.separator)+'\\s*'); 3369 value = str.split(reg); 3370 } else if($.isArray(str)) { 3371 value = str; 3372 } else { 3373 value = [str]; 3374 } 3375 return value; 3376 }, 3377 3378 //set checked on required checkboxes 3379 value2input: function(value) { 3380 this.$input.prop('checked', false); 3381 if($.isArray(value) && value.length) { 3382 this.$input.each(function(i, el) { 3383 var $el = $(el); 3384 // cannot use $.inArray as it performs strict comparison 3385 $.each(value, function(j, val){ 3386 /*jslint eqeq: true*/ 3387 if($el.val() == val) { 3388 /*jslint eqeq: false*/ 3389 $el.prop('checked', true); 3390 } 3391 }); 3392 }); 3393 } 3394 }, 3395 3396 input2value: function() { 3397 var checked = []; 3398 this.$input.filter(':checked').each(function(i, el) { 3399 checked.push($(el).val()); 3400 }); 3401 return checked; 3402 }, 3403 3404 //collect text of checked boxes 3405 value2htmlFinal: function(value, element) { 3406 var html = [], 3407 checked = $.fn.editableutils.itemsByValue(value, this.sourceData), 3408 escape = this.options.escape; 3409 3410 if(checked.length) { 3411 $.each(checked, function(i, v) { 3412 var text = escape ? $.fn.editableutils.escape(v.text) : v.text; 3413 html.push(text); 3414 }); 3415 $(element).html(html.join('<br>')); 3416 } else { 3417 $(element).empty(); 3418 } 3419 }, 3420 3421 activate: function() { 3422 this.$input.first().focus(); 3423 }, 3424 3425 autosubmit: function() { 3426 this.$input.on('keydown', function(e){ 3427 if (e.which === 13) { 3428 $(this).closest('form').submit(); 3429 } 3430 }); 3431 } 3432 }); 3433 3434 Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, { 3435 /** 3436 @property tpl 3437 @default <div></div> 3438 **/ 3439 tpl:'<div class="editable-checklist"></div>', 3440 3441 /** 3442 @property inputclass 3443 @type string 3444 @default null 3445 **/ 3446 inputclass: null, 3447 3448 /** 3449 Separator of values when reading from `data-value` attribute 3450 3451 @property separator 3452 @type string 3453 @default ',' 3454 **/ 3455 separator: ',' 3456 }); 3457 3458 $.fn.editabletypes.checklist = Checklist; 3459 3460}(window.jQuery)); 3461 3462/** 3463HTML5 input types. 3464Following types are supported: 3465 3466* password 3467* email 3468* url 3469* tel 3470* number 3471* range 3472* time 3473 3474Learn more about html5 inputs: 3475http://www.w3.org/wiki/HTML5_form_additions 3476To check browser compatibility please see: 3477https://developer.mozilla.org/en-US/docs/HTML/Element/Input 3478 3479@class html5types 3480@extends text 3481@final 3482@since 1.3.0 3483@example 3484<a href="#" id="email" data-type="email" data-pk="1">[email protected]</a> 3485<script> 3486$(function(){ 3487 $('#email').editable({ 3488 url: '/post', 3489 title: 'Enter email' 3490 }); 3491}); 3492</script> 3493**/ 3494 3495/** 3496@property tpl 3497@default depends on type 3498**/ 3499 3500/* 3501Password 3502*/ 3503(function ($) { 3504 "use strict"; 3505 3506 var Password = function (options) { 3507 this.init('password', options, Password.defaults); 3508 }; 3509 $.fn.editableutils.inherit(Password, $.fn.editabletypes.text); 3510 $.extend(Password.prototype, { 3511 //do not display password, show '[hidden]' instead 3512 value2html: function(value, element) { 3513 if(value) { 3514 $(element).text('[hidden]'); 3515 } else { 3516 $(element).empty(); 3517 } 3518 }, 3519 //as password not displayed, should not set value by html 3520 html2value: function(html) { 3521 return null; 3522 } 3523 }); 3524 Password.defaults = $.extend({}, $.fn.editabletypes.text.defaults, { 3525 tpl: '<input type="password">' 3526 }); 3527 $.fn.editabletypes.password = Password; 3528}(window.jQuery)); 3529 3530 3531/* 3532Email 3533*/ 3534(function ($) { 3535 "use strict"; 3536 3537 var Email = function (options) { 3538 this.init('email', options, Email.defaults); 3539 }; 3540 $.fn.editableutils.inherit(Email, $.fn.editabletypes.text); 3541 Email.defaults = $.extend({}, $.fn.editabletypes.text.defaults, { 3542 tpl: '<input type="email">' 3543 }); 3544 $.fn.editabletypes.email = Email; 3545}(window.jQuery)); 3546 3547 3548/* 3549Url 3550*/ 3551(function ($) { 3552 "use strict"; 3553 3554 var Url = function (options) { 3555 this.init('url', options, Url.defaults); 3556 }; 3557 $.fn.editableutils.inherit(Url, $.fn.editabletypes.text); 3558 Url.defaults = $.extend({}, $.fn.editabletypes.text.defaults, { 3559 tpl: '<input type="url">' 3560 }); 3561 $.fn.editabletypes.url = Url; 3562}(window.jQuery)); 3563 3564 3565/* 3566Tel 3567*/ 3568(function ($) { 3569 "use strict"; 3570 3571 var Tel = function (options) { 3572 this.init('tel', options, Tel.defaults); 3573 }; 3574 $.fn.editableutils.inherit(Tel, $.fn.editabletypes.text); 3575 Tel.defaults = $.extend({}, $.fn.editabletypes.text.defaults, { 3576 tpl: '<input type="tel">' 3577 }); 3578 $.fn.editabletypes.tel = Tel; 3579}(window.jQuery)); 3580 3581 3582/* 3583Number 3584*/ 3585(function ($) { 3586 "use strict"; 3587 3588 var NumberInput = function (options) { 3589 this.init('number', options, NumberInput.defaults); 3590 }; 3591 $.fn.editableutils.inherit(NumberInput, $.fn.editabletypes.text); 3592 $.extend(NumberInput.prototype, { 3593 render: function () { 3594 NumberInput.superclass.render.call(this); 3595 this.setAttr('min'); 3596 this.setAttr('max'); 3597 this.setAttr('step'); 3598 }, 3599 postrender: function() { 3600 if(this.$clear) {
3601 //increase right ffset for up/down arrows 3602 this.$clear.css({right: 24}); 3603 /* 3604 //can position clear button only here, when form is shown and height can be calculated 3605 var h = this.$input.outerHeight(true) || 20, 3606 delta = (h - this.$clear.height()) / 2; 3607 3608 //add 12px to offset right for up/down arrows 3609 this.$clear.css({top: delta, right: delta + 16}); 3610 */ 3611 } 3612 } 3613 }); 3614 NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, { 3615 tpl: '<input type="number">', 3616 inputclass: 'input-mini', 3617 min: null, 3618 max: null, 3619 step: null 3620 }); 3621 $.fn.editabletypes.number = NumberInput; 3622}(window.jQuery)); 3623 3624 3625/* 3626Range (inherit from number) 3627*/ 3628(function ($) { 3629 "use strict"; 3630 3631 var Range = function (options) { 3632 this.init('range', options, Range.defaults); 3633 }; 3634 $.fn.editableutils.inherit(Range, $.fn.editabletypes.number); 3635 $.extend(Range.prototype, { 3636 render: function () { 3637 this.$input = this.$tpl.filter('input'); 3638 3639 this.setClass(); 3640 this.setAttr('min'); 3641 this.setAttr('max'); 3642 this.setAttr('step'); 3643 3644 this.$input.on('input', function(){ 3645 $(this).siblings('output').text($(this).val()); 3646 }); 3647 }, 3648 activate: function() { 3649 this.$input.focus(); 3650 } 3651 }); 3652 Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, { 3653 tpl: '<input type="range"><output style="width: 30px; display: inline-block"></output>', 3654 inputclass: 'input-medium' 3655 }); 3656 $.fn.editabletypes.range = Range; 3657}(window.jQuery)); 3658 3659/* 3660Time 3661*/ 3662(function ($) { 3663 "use strict"; 3664 3665 var Time = function (options) { 3666 this.init('time', options, Time.defaults); 3667 }; 3668 //inherit from abstract, as inheritance from text gives selection error. 3669 $.fn.editableutils.inherit(Time, $.fn.editabletypes.abstractinput); 3670 $.extend(Time.prototype, { 3671 render: function() { 3672 this.setClass(); 3673 } 3674 }); 3675 Time.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { 3676 tpl: '<input type="time">' 3677 }); 3678 $.fn.editabletypes.time = Time; 3679}(window.jQuery)); 3680 3681/** 3682Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2. 3683Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options. 3684 3685You should manually download and include select2 distributive: 3686 3687 <link href="select2/select2.css" rel="stylesheet" type="text/css"></link> 3688 <script src="select2/select2.js"></script> 3689 3690To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css): 3691 3692 <link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link> 3693 3694**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source. 3695You need initially put both `data-value` and element's text youself: 3696 3697 <a href="#" data-type="select2" data-value="1">Text1</a> 3698 3699 3700@class select2 3701@extends abstractinput 3702@since 1.4.1 3703@final 3704@example 3705<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a> 3706<script> 3707$(function(){ 3708 //local source 3709 $('#country').editable({ 3710 source: [ 3711 {id: 'gb', text: 'Great Britain'}, 3712 {id: 'us', text: 'United States'}, 3713 {id: 'ru', text: 'Russia'} 3714 ], 3715 select2: { 3716 multiple: true 3717 } 3718 }); 3719 //remote source (simple) 3720 $('#country').editable({ 3721 source: '/getCountries', 3722 select2: { 3723 placeholder: 'Select Country', 3724 minimumInputLength: 1 3725 } 3726 }); 3727 //remote source (advanced) 3728 $('#country').editable({ 3729 select2: { 3730 placeholder: 'Select Country', 3731 allowClear: true, 3732 minimumInputLength: 3, 3733 id: function (item) { 3734 return item.CountryId; 3735 }, 3736 ajax: { 3737 url: '/getCountries', 3738 dataType: 'json', 3739 data: function (term, page) { 3740 return { query: term }; 3741 }, 3742 results: function (data, page) { 3743 return { results: data }; 3744 } 3745 }, 3746 formatResult: function (item) { 3747 return item.CountryName; 3748 }, 3749 formatSelection: function (item) { 3750 return item.CountryName; 3751 }, 3752 initSelection: function (element, callback) { 3753 return $.get('/getCountryById', { query: element.val() }, function (data) { 3754 callback(data); 3755 }); 3756 } 3757 } 3758 }); 3759}); 3760</script> 3761**/ 3762(function ($) { 3763 "use strict"; 3764 3765 var Constructor = function (options) { 3766 this.init('select2', options, Constructor.defaults); 3767 3768 options.select2 = options.select2 || {}; 3769 3770 this.sourceData = null; 3771 3772 //placeholder 3773 if(options.placeholder) { 3774 options.select2.placeholder = options.placeholder; 3775 } 3776 3777 //if not `tags` mode, use source 3778 if(!options.select2.tags && options.source) { 3779 var source = options.source; 3780 //if source is function, call it (once!) 3781 if ($.isFunction(options.source)) { 3782 source = options.source.call(options.scope); 3783 } 3784 3785 if (typeof source === 'string') { 3786 options.select2.ajax = options.select2.ajax || {}; 3787 //some default ajax params 3788 if(!options.select2.ajax.data) { 3789 options.select2.ajax.data = function(term) {return { query:term };}; 3790 } 3791 if(!options.select2.ajax.results) { 3792 options.select2.ajax.results = function(data) { return {results:data };}; 3793 } 3794 options.select2.ajax.url = source; 3795 } else { 3796 //check format and convert x-editable format to select2 format (if needed) 3797 this.sourceData = this.convertSource(source); 3798 options.select2.data = this.sourceData; 3799 } 3800 } 3801 3802 //overriding objects in config (as by default jQuery extend() is not recursive) 3803 this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2); 3804 3805 //detect whether it is multi-valued 3806 this.isMultiple = this.options.select2.tags || this.options.select2.multiple; 3807 this.isRemote = ('ajax' in this.options.select2); 3808 3809 //store function returning ID of item 3810 //should be here as used inautotext for local source 3811 this.idFunc = this.options.select2.id; 3812 if (typeof(this.idFunc) !== "function") { 3813 var idKey = this.idFunc || 'id'; 3814 this.idFunc = function (e) { return e[idKey]; }; 3815 } 3816 3817 //store function that renders text in select2 3818 this.formatSelection = this.options.select2.formatSelection; 3819 if (typeof(this.formatSelection) !== "function") { 3820 this.formatSelection = function (e) { return e.text; }; 3821 } 3822 }; 3823 3824 $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput); 3825 3826 $.extend(Constructor.prototype, { 3827 render: function() { 3828 this.setClass(); 3829 3830 //can not apply select2 here as it calls initSelection 3831 //over input that does not have correct value yet. 3832 //apply select2 only in value2input 3833 //this.$input.select2(this.options.select2); 3834 3835 //when data is loaded via ajax, we need to know when it's done to populate listData 3836 if(this.isRemote) { 3837 //listen to loaded event to populate data 3838 this.$input.on('select2-loaded', $.proxy(function(e) { 3839 this.sourceData = e.items.results; 3840 }, this)); 3841 } 3842 3843 //trigger resize of editableform to re-position container in multi-valued mode 3844 if(this.isMultiple) { 3845 this.$input.on('change', function() { 3846 $(this).closest('form').parent().triggerHandler('resize'); 3847 }); 3848 } 3849 }, 3850 3851 value2html: function(value, element) { 3852 var text = '', data, 3853 that = this; 3854 3855 if(this.options.select2.tags) { //in tags mode just assign value 3856 data = value; 3857 //data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc); 3858 } else if(this.sourceData) { 3859 data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc); 3860 } else { 3861 //can not get list of possible values 3862 //(e.g. autotext for select2 with ajax source) 3863 } 3864 3865 //data may be array (when multiple values allowed) 3866 if($.isArray(data)) { 3867 //collect selected data and show with separator 3868 text = []; 3869 $.each(data, function(k, v){ 3870 text.push(v && typeof v === 'object' ? that.formatSelection(v) : v); 3871 }); 3872 } else if(data) { 3873 text = that.formatSelection(data); 3874 } 3875 3876 text = $.isArray(text) ? text.join(this.options.viewseparator) : text; 3877 3878 //$(element).text(text); 3879 Constructor.superclass.value2html.call(this, text, element); 3880 }, 3881 3882 html2value: function(html) { 3883 return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null; 3884 }, 3885 3886 value2input: function(value) { 3887 // if value array => join it anyway 3888 if($.isArray(value)) { 3889 value = value.join(this.getSeparator()); 3890 } 3891 3892 //for remote source just set value, text is updated by initSelection 3893 if(!this.$input.data('select2')) { 3894 this.$input.val(value); 3895 this.$input.select2(this.options.select2); 3896 } else { 3897 //second argument needed to separate initial change from user's click (for autosubmit) 3898 this.$input.val(value).trigger('change', true); 3899 3900 //Uncaught Error: cannot call val() if initSelection() is not defined 3901 //this.$input.select2('val', value); 3902 } 3903 3904 // if defined remote source AND no multiple mode AND no user's initSelection provided --> 3905 // we should somehow get text for provided id. 3906 // The solution is to use element's text as text for that id (exclude empty) 3907 if(this.isRemote && !this.isMultiple && !this.options.select2.initSelection) { 3908 // customId and customText are methods to extract `id` and `text` from data object 3909 // we can use this workaround only if user did not define these methods 3910 // otherwise we cant construct data object 3911 var customId = this.options.select2.id, 3912 customText = this.options.select2.formatSelection; 3913 3914 if(!customId && !customText) { 3915 var $el = $(this.options.scope); 3916 if (!$el.data('editable').isEmpty) { 3917 var data = {id: value, text: $el.text()}; 3918 this.$input.select2('data', data); 3919 } 3920 } 3921 } 3922 }, 3923 3924 input2value: function() { 3925 return this.$input.select2('val'); 3926 }, 3927 3928 str2value: function(str, separator) { 3929 if(typeof str !== 'string' || !this.isMultiple) { 3930 return str; 3931 } 3932
3933 separator = separator || this.getSeparator(); 3934 3935 var val, i, l; 3936 3937 if (str === null || str.length < 1) { 3938 return null; 3939 } 3940 val = str.split(separator); 3941 for (i = 0, l = val.length; i < l; i = i + 1) { 3942 val[i] = $.trim(val[i]); 3943 } 3944 3945 return val; 3946 }, 3947 3948 autosubmit: function() { 3949 this.$input.on('change', function(e, isInitial){ 3950 if(!isInitial) { 3951 $(this).closest('form').submit(); 3952 } 3953 }); 3954 }, 3955 3956 getSeparator: function() { 3957 return this.options.select2.separator || $.fn.select2.defaults.separator; 3958 }, 3959 3960 /* 3961 Converts source from x-editable format: {value: 1, text: "1"} to 3962 select2 format: {id: 1, text: "1"} 3963 */ 3964 convertSource: function(source) { 3965 if($.isArray(source) && source.length && source[0].value !== undefined) { 3966 for(var i = 0; i<source.length; i++) { 3967 if(source[i].value !== undefined) { 3968 source[i].id = source[i].value; 3969 delete source[i].value; 3970 } 3971 } 3972 } 3973 return source; 3974 }, 3975 3976 activate: function() { 3977 this.$input.select2('open'); 3978 }, 3979 3980 destroy: function() { 3981 if(this.$input) { 3982 if(this.$input.data('select2')) { 3983 this.$input.select2('destroy'); 3984 } 3985 } 3986 } 3987 3988 }); 3989 3990 Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { 3991 /** 3992 @property tpl 3993 @default <input type="hidden"> 3994 **/ 3995 tpl:'<input type="hidden">', 3996 /** 3997 Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2). 3998 3999 @property select2 4000 @type object 4001 @default null 4002 **/ 4003 select2: null, 4004 /** 4005 Placeholder attribute of select 4006 4007 @property placeholder 4008 @type string 4009 @default null 4010 **/ 4011 placeholder: null, 4012 /** 4013 Source data for select. It will be assigned to select2 `data` property and kept here just for convenience. 4014 Please note, that format is different from simple `select` input: use 'id' instead of 'value'. 4015 E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`. 4016 4017 @property source 4018 @type array|string|function 4019 @default null 4020 **/ 4021 source: null, 4022 /** 4023 Separator used to display tags. 4024 4025 @property viewseparator 4026 @type string 4027 @default ', ' 4028 **/ 4029 viewseparator: ', ' 4030 }); 4031 4032 $.fn.editabletypes.select2 = Constructor; 4033 4034}(window.jQuery)); 4035 4036/** 4037* Combodate - 1.1.0 4038* Dropdown date and time picker. 4039* Converts text input into dropdowns to pick day, month, year, hour, minute and second. 4040* Uses momentjs as datetime library http://momentjs.com. 4041* For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang 4042* 4043* Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight 4044* In combodate: 4045* 12:00 pm --> 12:00 (24-h format, midday) 4046* 12:00 am --> 00:00 (24-h format, midnight, start of day) 4047* 4048* Differs from momentjs parse rules: 4049* 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change) 4050* 00:00 am, 12:00 am --> 00:00 (24-h format, day not change) 4051* 4052* 4053* Author: Vitaliy Potapov 4054* Project page: http://github.com/vitalets/combodate 4055* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License. 4056**/ 4057(function ($) { 4058 4059 var Combodate = function (element, options) { 4060 this.$element = $(element); 4061 if(!this.$element.is('input')) { 4062 $.error('Combodate should be applied to INPUT element'); 4063 return; 4064 } 4065 this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data()); 4066 this.init(); 4067 }; 4068 4069 Combodate.prototype = { 4070 constructor: Combodate, 4071 init: function () { 4072 this.map = { 4073 //key regexp moment.method 4074 day: ['D', 'date'], 4075 month: ['M', 'month'], 4076 year: ['Y', 'year'], 4077 hour: ['[Hh]', 'hours'], 4078 minute: ['m', 'minutes'], 4079 second: ['s', 'seconds'], 4080 ampm: ['[Aa]', ''] 4081 }; 4082 4083 this.$widget = $('<span class="combodate"></span>').html(this.getTemplate()); 4084 4085 this.initCombos(); 4086 4087 // internal momentjs instance 4088 this.datetime = null; 4089 4090 //update original input on change 4091 this.$widget.on('change', 'select', $.proxy(function(e) { 4092 this.$element.val(this.getValue()).change(); 4093 // update days count if month or year changes 4094 if (this.options.smartDays) { 4095 if ($(e.target).is('.month') || $(e.target).is('.year')) { 4096 this.fillCombo('day'); 4097 } 4098 } 4099 }, this)); 4100 4101 this.$widget.find('select').css('width', 'auto'); 4102 4103 // hide original input and insert widget 4104 this.$element.hide().after(this.$widget); 4105 4106 // set initial value 4107 this.setValue(this.$element.val() || this.options.value); 4108 }, 4109 4110 /* 4111 Replace tokens in template with <select> elements 4112 */ 4113 getTemplate: function() { 4114 var tpl = this.options.template; 4115 var inputDisabled = this.$element.prop('disabled'); 4116 var customClass = this.options.customClass; 4117 4118 //first pass 4119 $.each(this.map, function(k, v) { 4120 v = v[0]; 4121 var r = new RegExp(v+'+'), 4122 token = v.length > 1 ? v.substring(1, 2) : v; 4123 4124 tpl = tpl.replace(r, '{'+token+'}'); 4125 }); 4126 4127 //replace spaces with 4128 tpl = tpl.replace(/ /g, ' '); 4129 4130 //second pass 4131 $.each(this.map, function(k, v) { 4132 v = v[0]; 4133 var token = v.length > 1 ? v.substring(1, 2) : v; 4134 4135 tpl = tpl.replace('{'+token+'}', '<select class="'+k+' '+customClass +'"'+ 4136 (inputDisabled ? ' disabled="disabled"' : '')+'></select>'); 4137 }); 4138 4139 return tpl; 4140 }, 4141 4142 /* 4143 Initialize combos that presents in template 4144 */ 4145 initCombos: function() { 4146 for (var k in this.map) { 4147 var $c = this.$widget.find('.'+k); 4148 // set properties like this.$day, this.$month etc. 4149 this['$'+k] = $c.length ? $c : null; 4150 // fill with items 4151 this.fillCombo(k); 4152 } 4153 }, 4154 4155 /* 4156 Fill combo with items 4157 */ 4158 fillCombo: function(k) { 4159 var $combo = this['$'+k]; 4160 if (!$combo) { 4161 return; 4162 } 4163
4164 // define method name to fill items, e.g `fillDays` 4165 var f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1); 4166 var items = this[f](); 4167 var value = $combo.val(); 4168 4169 $combo.empty(); 4170 for(var i=0; i<items.length; i++) { 4171 $combo.append('<option value="'+items[i][0]+'">'+items[i][1]+'</option>'); 4172 } 4173 4174 $combo.val(value); 4175 }, 4176 4177 /* 4178 Initialize items of combos. Handles `firstItem` option 4179 */ 4180 fillCommon: function(key) { 4181 var values = [], 4182 relTime; 4183 4184 if(this.options.firstItem === 'name') { 4185 //need both to support moment ver < 2 and >= 2 4186 if (moment.localeData) { 4187 relTime = moment.localeData()._relativeTime; 4188 } else { 4189 relTime = moment.relativeTime || moment.langData()._relativeTime; 4190 } 4191 var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key]; 4192 //take last entry (see momentjs lang files structure) 4193 header = header.split(' ').reverse()[0]; 4194 values.push(['', header]); 4195 } else if(this.options.firstItem === 'empty') { 4196 values.push(['', '']); 4197 } 4198 return values; 4199 }, 4200 4201 4202 /* 4203 fill day 4204 */ 4205 fillDay: function() { 4206 var items = this.fillCommon('d'), name, i, 4207 twoDigit = this.options.template.indexOf('DD') !== -1, 4208 daysCount = 31; 4209 4210 // detect days count (depends on month and year) 4211 // originally https://github.com/vitalets/combodate/pull/7 4212 if (this.options.smartDays && this.$month && this.$year) { 4213 var month = parseInt(this.$month.val(), 10); 4214 var year = parseInt(this.$year.val(), 10); 4215 4216 if (!isNaN(month) && !isNaN(year)) { 4217 daysCount = moment([year, month]).daysInMonth(); 4218 } 4219 } 4220 4221 for (i = 1; i <= daysCount; i++) { 4222 name = twoDigit ? this.leadZero(i) : i; 4223 items.push([i, name]); 4224 } 4225 return items; 4226 }, 4227 4228 /* 4229 fill month 4230 */ 4231 fillMonth: function() { 4232 var items = this.fillCommon('M'), name, i, 4233 longNamesNum = this.options.template.indexOf('MMMMMM') !== -1, 4234 shortNamesNum = this.options.template.indexOf('MMMMM') !== -1, 4235 longNames = this.options.template.indexOf('MMMM') !== -1, 4236 shortNames = this.options.template.indexOf('MMM') !== -1, 4237 twoDigit = this.options.template.indexOf('MM') !== -1; 4238 4239 for(i=0; i<=11; i++) { 4240 if (longNamesNum) { 4241 name = moment().date(1).month(i).format('MM - MMMM'); 4242 } else if (shortNamesNum) { 4243 name = moment().date(1).month(i).format('MM - MMM'); 4244 } else if(longNames) { 4245 //see https://github.com/timrwood/momentjs.com/pull/36 4246 name = moment().date(1).month(i).format('MMMM'); 4247 } else if(shortNames) { 4248 name = moment().date(1).month(i).format('MMM'); 4249 } else if(twoDigit) { 4250 name = this.leadZero(i+1); 4251 } else { 4252 name = i+1; 4253 } 4254 items.push([i, name]); 4255 } 4256 return items; 4257 }, 4258 4259 /* 4260 fill year 4261 */ 4262 fillYear: function() { 4263 var items = [], name, i, 4264 longNames = this.options.template.indexOf('YYYY') !== -1; 4265 4266 for(i=this.options.maxYear; i>=this.options.minYear; i--) { 4267 name = longNames ? i : (i+'').substring(2); 4268 items[this.options.yearDescending ? 'push' : 'unshift']([i, name]); 4269 } 4270 4271 items = this.fillCommon('y').concat(items); 4272 4273 return items; 4274 }, 4275 4276 /* 4277 fill hour 4278 */ 4279 fillHour: function() { 4280 var items = this.fillCommon('h'), name, i, 4281 h12 = this.options.template.indexOf('h') !== -1, 4282 h24 = this.options.template.indexOf('H') !== -1, 4283 twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1, 4284 min = h12 ? 1 : 0, 4285 max = h12 ? 12 : 23; 4286 4287 for(i=min; i<=max; i++) { 4288 name = twoDigit ? this.leadZero(i) : i; 4289 items.push([i, name]); 4290 } 4291 return items; 4292 }, 4293 4294 /* 4295 fill minute 4296 */ 4297 fillMinute: function() { 4298 var items = this.fillCommon('m'), name, i, 4299 twoDigit = this.options.template.indexOf('mm') !== -1; 4300 4301 for(i=0; i<=59; i+= this.options.minuteStep) { 4302 name = twoDigit ? this.leadZero(i) : i; 4303 items.push([i, name]); 4304 } 4305 return items; 4306 }, 4307 4308 /* 4309 fill second 4310 */ 4311 fillSecond: function() { 4312 var items = this.fillCommon('s'), name, i, 4313 twoDigit = this.options.template.indexOf('ss') !== -1; 4314 4315 for(i=0; i<=59; i+= this.options.secondStep) { 4316 name = twoDigit ? this.leadZero(i) : i; 4317 items.push([i, name]); 4318 } 4319 return items; 4320 }, 4321 4322 /* 4323 fill ampm 4324 */ 4325 fillAmpm: function() { 4326 var ampmL = this.options.template.indexOf('a') !== -1, 4327 ampmU = this.options.template.indexOf('A') !== -1, 4328 items = [ 4329 ['am', ampmL ? 'am' : 'AM'], 4330 ['pm', ampmL ? 'pm' : 'PM'] 4331 ]; 4332 return items; 4333 }, 4334 4335 /* 4336 Returns current date value from combos. 4337 If format not specified - `options.format` used. 4338 If format = `null` - Moment object returned. 4339 */ 4340 getValue: function(format) { 4341 var dt, values = {}, 4342 that = this, 4343 notSelected = false;
4344 4345 //getting selected values 4346 $.each(this.map, function(k, v) { 4347 if(k === 'ampm') { 4348 return; 4349 } 4350 4351 // if combo exists, use it's value, otherwise use default 4352 if (that['$'+k]) { 4353 values[k] = parseInt(that['$'+k].val(), 10); 4354 } else { 4355 var defaultValue; 4356 if (that.datetime) { 4357 defaultValue = that.datetime[v[1]](); 4358 } else { 4359 defaultValue = k === 'day' ? 1 : 0; 4360 } 4361 values[k] = defaultValue; 4362 } 4363 4364 if(isNaN(values[k])) { 4365 notSelected = true; 4366 return false; 4367 } 4368 }); 4369 4370 //if at least one visible combo not selected - return empty string 4371 if(notSelected) { 4372 return ''; 4373 } 4374 4375 //convert hours 12h --> 24h 4376 if(this.$ampm) { 4377 //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day) 4378 if(values.hour === 12) { 4379 values.hour = this.$ampm.val() === 'am' ? 0 : 12; 4380 } else { 4381 values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12; 4382 } 4383 } 4384 4385 dt = moment([ 4386 values.year, 4387 values.month, 4388 values.day, 4389 values.hour, 4390 values.minute, 4391 values.second 4392 ]); 4393 4394 //highlight invalid date 4395 this.highlight(dt); 4396 4397 format = format === undefined ? this.options.format : format; 4398 if(format === null) { 4399 return dt.isValid() ? dt : null; 4400 } else { 4401 return dt.isValid() ? dt.format(format) : ''; 4402 } 4403 }, 4404 4405 setValue: function(value) { 4406 if(!value) { 4407 return; 4408 } 4409 4410 // parse in strict mode (third param `true`) 4411 var dt = typeof value === 'string' ? moment(value, this.options.format, true) : moment(value), 4412 that = this, 4413 values = {}; 4414 4415 //function to find nearest value in select options 4416 function getNearest($select, value) { 4417 var delta = {}; 4418 $select.children('option').each(function(i, opt){ 4419 var optValue = $(opt).attr('value'), 4420 distance; 4421 4422 if(optValue === '') return; 4423 distance = Math.abs(optValue - value); 4424 if(typeof delta.distance === 'undefined' || distance < delta.distance) { 4425 delta = {value: optValue, distance: distance}; 4426 } 4427 }); 4428 return delta.value; 4429 } 4430 4431 if(dt.isValid()) { 4432 //read values from date object 4433 $.each(this.map, function(k, v) { 4434 if(k === 'ampm') { 4435 return; 4436 } 4437 values[k] = dt[v[1]](); 4438 }); 4439 4440 if(this.$ampm) { 4441 //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day) 4442 if(values.hour >= 12) { 4443 values.ampm = 'pm'; 4444 if(values.hour > 12) { 4445 values.hour -= 12; 4446 } 4447 } else { 4448 values.ampm = 'am'; 4449 if(values.hour === 0) { 4450 values.hour = 12; 4451 } 4452 } 4453 } 4454 4455 $.each(values, function(k, v) { 4456 //call val() for each existing combo, e.g. this.$hour.val() 4457 if(that['$'+k]) { 4458 4459 if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) { 4460 v = getNearest(that['$'+k], v); 4461 } 4462 4463 if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) { 4464 v = getNearest(that['$'+k], v); 4465 } 4466 4467 that['$'+k].val(v); 4468 } 4469 }); 4470 4471 // update days count 4472 if (this.options.smartDays) { 4473 this.fillCombo('day'); 4474 } 4475 4476 this.$element.val(dt.format(this.options.format)).change(); 4477 this.datetime = dt; 4478 } else { 4479 this.datetime = null; 4480 } 4481 }, 4482 4483 /*
4484 highlight combos if date is invalid 4485 */ 4486 highlight: function(dt) { 4487 if(!dt.isValid()) { 4488 if(this.options.errorClass) { 4489 this.$widget.addClass(this.options.errorClass); 4490 } else { 4491 //store original border color 4492 if(!this.borderColor) { 4493 this.borderColor = this.$widget.find('select').css('border-color'); 4494 } 4495 this.$widget.find('select').css('border-color', 'red'); 4496 } 4497 } else { 4498 if(this.options.errorClass) { 4499 this.$widget.removeClass(this.options.errorClass); 4500 } else { 4501 this.$widget.find('select').css('border-color', this.borderColor); 4502 } 4503 } 4504 }, 4505 4506 leadZero: function(v) { 4507 return v <= 9 ? '0' + v : v; 4508 }, 4509 4510 destroy: function() { 4511 this.$widget.remove(); 4512 this.$element.removeData('combodate').show(); 4513 } 4514 4515 //todo: clear method 4516 }; 4517 4518 $.fn.combodate = function ( option ) { 4519 var d, args = Array.apply(null, arguments); 4520 args.shift(); 4521 4522 //getValue returns date as string / object (not jQuery object) 4523 if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) { 4524 return d.getValue.apply(d, args); 4525 } 4526 4527 return this.each(function () { 4528 var $this = $(this), 4529 data = $this.data('combodate'), 4530 options = typeof option == 'object' && option; 4531 if (!data) { 4532 $this.data('combodate', (data = new Combodate(this, options))); 4533 } 4534 if (typeof option == 'string' && typeof data[option] == 'function') { 4535 data[option].apply(data, args); 4536 } 4537 }); 4538 }; 4539 4540 $.fn.combodate.defaults = { 4541 //in this format value stored in original input 4542 format: 'DD-MM-YYYY HH:mm', 4543 //in this format items in dropdowns are displayed 4544 template: 'D / MMM / YYYY H : mm', 4545 //initial value, can be `new Date()` 4546 value: null, 4547 minYear: 1970, 4548 maxYear: new Date().getFullYear(), 4549 yearDescending: true, 4550 minuteStep: 5, 4551 secondStep: 1, 4552 firstItem: 'empty', //'name', 'empty', 'none' 4553 errorClass: null, 4554 customClass: '', 4555 roundTime: true, // whether to round minutes and seconds if step > 1 4556 smartDays: false // whether days in combo depend on selected month: 31, 30, 28 4557 }; 4558 4559}(window.jQuery)); 4560 4561/** 4562Combodate input - dropdown date and time picker. 4563Based on [combodate](http://vitalets.github.com/combodate) plugin (included). To use it you should manually include [momentjs](http://momentjs.com). 4564 4565 <script src="js/moment.min.js"></script> 4566 4567Allows to input: 4568 4569* only date 4570* only time 4571* both date and time 4572 4573Please note, that format is taken from momentjs and **not compatible** with bootstrap-datepicker / jquery UI datepicker. 4574Internally value stored as `momentjs` object. 4575 4576@class combodate 4577@extends abstractinput 4578@final 4579@since 1.4.0 4580@example 4581<a href="#" id="dob" data-type="combodate" data-pk="1" data-url="/post" data-value="1984-05-15" data-title="Select date"></a> 4582<script> 4583$(function(){ 4584 $('#dob').editable({ 4585 format: 'YYYY-MM-DD', 4586 viewformat: 'DD.MM.YYYY', 4587 template: 'D / MMMM / YYYY', 4588 combodate: { 4589 minYear: 2000, 4590 maxYear: new Date().getFullYear(), 4591 minuteStep: 1 4592 } 4593 } 4594 }); 4595}); 4596</script> 4597**/ 4598 4599/*global moment*/ 4600 4601(function ($) { 4602 "use strict"; 4603 4604 var Constructor = function (options) { 4605 this.init('combodate', options, Constructor.defaults); 4606 4607 //by default viewformat equals to format 4608 if(!this.options.viewformat) { 4609 this.options.viewformat = this.options.format; 4610 } 4611 4612 //try parse combodate config defined as json string in data-combodate 4613 options.combodate = $.fn.editableutils.tryParseJson(options.combodate, true); 4614 4615 //overriding combodate config (as by default jQuery extend() is not recursive) 4616 this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, { 4617 format: this.options.format, 4618 template: this.options.template 4619 }); 4620 }; 4621 4622 $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput); 4623 4624 $.extend(Constructor.prototype, { 4625 render: function () { 4626 this.$input.combodate(this.options.combodate); 4627 4628 if($.fn.editableform.engine === 'bs3') { 4629 this.$input.siblings().find('select').addClass('form-control'); 4630 } 4631 4632 if(this.options.inputclass) { 4633 this.$input.siblings().find('select').addClass(this.options.inputclass); 4634 } 4635 //"clear" link 4636 /* 4637 if(this.options.clear) { 4638 this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){ 4639 e.preventDefault(); 4640 e.stopPropagation(); 4641 this.clear(); 4642 }, this)); 4643 4644 this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear)); 4645 } 4646 */ 4647 }, 4648 4649 value2html: function(value, element) { 4650 var text = value ? value.format(this.options.viewformat) : ''; 4651 //$(element).text(text); 4652 Constructor.superclass.value2html.call(this, text, element); 4653 }, 4654 4655 html2value: function(html) { 4656 return html ? moment(html, this.options.viewformat) : null; 4657 }, 4658 4659 value2str: function(value) { 4660 return value ? value.format(this.options.format) : ''; 4661 }, 4662 4663 str2value: function(str) { 4664 return str ? moment(str, this.options.format) : null; 4665 }, 4666 4667 value2submit: function(value) { 4668 return this.value2str(value); 4669 }, 4670 4671 value2input: function(value) { 4672 this.$input.combodate('setValue', value); 4673 }, 4674 4675 input2value: function() { 4676 return this.$input.combodate('getValue', null); 4677 }, 4678 4679 activate: function() { 4680 this.$input.siblings('.combodate').find('select').eq(0).focus(); 4681 }, 4682 4683 /* 4684 clear: function() { 4685 this.$input.data('datepicker').date = null; 4686 this.$input.find('.active').removeClass('active'); 4687 }, 4688 */ 4689 4690 autosubmit: function() { 4691 4692 } 4693 4694 }); 4695 4696 Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { 4697 /** 4698 @property tpl 4699 @default <input type="text"> 4700 **/ 4701 tpl:'<input type="text">', 4702 /** 4703 @property inputclass 4704 @default null 4705 **/ 4706 inputclass: null, 4707 /** 4708 Format used for sending value to server. Also applied when convert
4708ing date from <code>data-value</code> attribute.<br> 4709 See list of tokens in [momentjs docs](http://momentjs.com/docs/#/parsing/string-format) 4710 4711 @property format 4712 @type string 4713 @default YYYY-MM-DD 4714 **/ 4715 format:'YYYY-MM-DD', 4716 /** 4717 Format used for displaying date. Also applied when converting date from element's text on init. 4718 If not specified equals to `format`. 4719 4720 @property viewformat 4721 @type string 4722 @default null 4723 **/ 4724 viewformat: null, 4725 /** 4726 Template used for displaying dropdowns. 4727 4728 @property template 4729 @type string 4730 @default D / MMM / YYYY 4731 **/ 4732 template: 'D / MMM / YYYY', 4733 /** 4734 Configuration of combodate. 4735 Full list of options: http://vitalets.github.com/combodate/#docs 4736 4737 @property combodate 4738 @type object 4739 @default null 4740 **/ 4741 combodate: null 4742 4743 /* 4744 (not implemented yet) 4745 Text shown as clear date button. 4746 If <code>false</code> clear button will not be rendered. 4747 4748 @property clear 4749 @type boolean|string 4750 @default 'x clear' 4751 */ 4752 //clear: '× clear' 4753 }); 4754 4755 $.fn.editabletypes.combodate = Constructor; 4756 4757}(window.jQuery)); 4758 4759/* 4760Editableform based on Twitter Bootstrap 3 4761*/ 4762(function ($) { 4763 "use strict"; 4764 4765 //store parent methods 4766 var pInitInput = $.fn.editableform.Constructor.prototype.initInput; 4767 4768 $.extend($.fn.editableform.Constructor.prototype, { 4769 initTemplate: function() { 4770 this.$form = $($.fn.editableform.template); 4771 this.$form.find('.control-group').addClass('form-group'); 4772 this.$form.find('.editable-error-block').addClass('help-block'); 4773 }, 4774 initInput: function() { 4775 pInitInput.apply(this); 4776 4777 var emptyInputClass = this.input.options.inputclass === null || this.input.options.inputclass === false; 4778 var defaultClass = 'form-control-sm'; 4779 4780 //bs3 add `form-control` class to standard inputs 4781 var stdtypes = 'text,select,textarea,password,email,url,tel,number,range,time,typeaheadjs'.split(','); 4782 if(~$.inArray(this.input.type, stdtypes)) { 4783 this.input.$input.addClass('form-control'); 4784 if(emptyInputClass) { 4785 this.input.options.inputclass = defaultClass; 4786 this.input.$input.addClass(defaultClass); 4787 } 4788 } 4789 4790 //apply size class also to buttons (to fit size of control) 4791 var $btn = this.$form.find('.editable-buttons'); 4792 var classes = emptyInputClass ? [defaultClass] : this.input.options.inputclass.split(' '); 4793 for(var i=0; i<classes.length; i++) { 4794 // `btn-sm` is default now 4795 /* 4796 if(classes[i].toLowerCase() === 'input-sm') { 4797 $btn.find('button').addClass('btn-sm'); 4798 } 4799 */ 4800 if(classes[i].toLowerCase() === 'input-lg') { 4801 $btn.find('button').removeClass('btn-sm').addClass('btn-lg'); 4802 } 4803 } 4804 } 4805 }); 4806 4807 //buttons 4808 $.fn.editableform.buttons = 4809 '<button type="submit" class="btn btn-primary btn-sm editable-submit">'+ 4810 '<i class="icon-ok" aria-hidden="true"></i>'+ 4811 '</button>'+ 4812 '<button type="button" class="btn btn-default btn-sm editable-cancel">'+ 4813 '<i class="icon-remove" aria-hidden="true"></i>'+ 4814 '</button>'; 4815 4816 //error classes 4817 $.fn.editableform.errorGroupClass = 'has-error'; 4818 $.fn.editableform.errorBlockClass = null; 4819 //engine 4820 $.fn.editableform.engine = 'bs4'; 4821}(window.jQuery)); 4822/** 4823* Editable Popover for Bootstrap 4 based on Popper.js 4824* --------------------- 4825* requires bootstrap-popover.js 4826*/ 4827(function ($) { 4828 "use strict"; 4829 4830 //extend methods 4831 $.extend($.fn.editableContainer.Popup.prototype, { 4832 containerName: 'popover',
4833 containerDataName: 'bs.popover', 4834 innerCss: '.popover-body', 4835 defaults: $.fn.popover.Constructor.DEFAULTS, 4836 4837 initContainer: function(){ 4838 $.extend(this.containerOptions, { 4839 trigger: 'manual', 4840 selector: false, 4841 content: ' ', 4842 template: this.defaults.template 4843 }); 4844 4845 //as template property is used in inputs, hide it from popover 4846 var t; 4847 if(this.$element.data('template')) { 4848 t = this.$element.data('template'); 4849 this.$element.removeData('template'); 4850 } 4851 4852 this.call(this.containerOptions); 4853 4854 if(t) { 4855 //restore data('template') 4856 this.$element.data('template', t); 4857 } 4858 }, 4859 4860 /* show */ 4861 innerShow: function () { 4862 this.call('show'); 4863 }, 4864 4865 /* hide */ 4866 innerHide: function () { 4867 this.call('hide'); 4868 }, 4869 4870 /* destroy */ 4871 innerDestroy: function() { 4872 this.call('dispose'); 4873 }, 4874 4875 setContainerOption: function(key, value) { 4876 this.container().options[key] = value; 4877 }, 4878 4879 setPosition: function () { 4880 (function() {}).call(this.container()); 4881 }, 4882 4883 tip: function() { 4884 return this.container() ? $(this.container().tip) : null; 4885 } 4886 }); 4887 4888}(window.jQuery)); 4889 4890/* ========================================================= 4891 * bootstrap-datepicker.js 4892 * http://www.eyecon.ro/bootstrap-datepicker 4893 * ========================================================= 4894 * Copyright 2012 Stefan Petre 4895 * Improvements by Andrew Rowls 4896 * 4897 * Licensed under the Apache License, Version 2.0 (the "License"); 4898 * you may not use this file except in compliance with the License. 4899 * You may obtain a copy of the License at 4900 * 4901 * http://www.apache.org/licenses/LICENSE-2.0 4902 * 4903 * Unless required by applicable law or agreed to in writing, software 4904 * distributed under the License is distributed on an "AS IS" BASIS, 4905 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4906 * See the License for the specific language governing permissions and 4907 * limitations under the License. 4908 * ========================================================= */ 4909 4910(function( $ ) { 4911 4912 function UTCDate(){ 4913 return new Date(Date.UTC.apply(Date, arguments)); 4914 } 4915 function UTCToday(){ 4916 var today = new Date(); 4917 return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate()); 4918 } 4919 4920 // Picker object 4921 4922 var Datepicker = function(element, options) { 4923 var that = this; 4924 4925 this._process_options(options); 4926 4927 this.element = $(element); 4928 this.isInline = false; 4929 this.isInput = this.element.is('input'); 4930 this.component = this.element.is('.date') ? this.element.find('.add-on, .btn') : false; 4931 this.hasInput = this.component && this.element.find('input').length; 4932 if(this.component && this.component.length === 0) 4933 this.component = false; 4934 4935 this.picker = $(DPGlobal.template); 4936 this._buildEvents(); 4937 this._attachEvents(); 4938 4939 if(this.isInline) { 4940 this.picker.addClass('datepicker-inline').appendTo(this.element); 4941 } else { 4942 this.picker.addClass('datepicker-dropdown dropdown-menu'); 4943 } 4944 4945 if (this.o.rtl){ 4946 this.picker.addClass('datepicker-rtl'); 4947 this.picker.find('.prev i, .next i') 4948 .toggleClass('icon-arrow-left icon-arrow-right'); 4949 } 4950 4951 4952 this.viewMode = this.o.startView; 4953 4954 if (this.o.calendarWeeks) 4955 this.picker.find('tfoot th.today') 4956 .attr('colspan', function(i, val){ 4957 return parseInt(val) + 1; 4958 }); 4959 4960 this._allow_update = false; 4961 4962 this.setStartDate(this.o.startDate); 4963 this.setEndDate(this.o.endDate); 4964 this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled); 4965 4966 this.fillDow(); 4967 this.fillMonths(); 4968 4969 this._allow_update = true; 4970 4971 this.update(); 4972 this.showMode(); 4973 4974 if(this.isInline) { 4975 this.show(); 4976 } 4977 }; 4978 4979 Datepicker.prototype = { 4980 constructor: Datepicker, 4981 4982 _process_options: function(opts){ 4983 // Store raw options for reference 4984 this._o = $.extend({}, this._o, opts); 4985 // Processed options 4986 var o = this.o = $.extend({}, this._o); 4987 4988 // Check if "de-DE" style date is available, if not language should 4989 // fallback to 2 letter code eg "de" 4990 var lang = o.language; 4991 if (!dates[lang]) { 4992 lang = lang.split('-')[0]; 4993 if (!dates[lang]) 4994 lang = defaults.language; 4995 } 4996 o.language = lang; 4997 4998 switch(o.startView){ 4999 case 2: 5000 case 'decade': 5001 o.startView = 2; 5002 break; 5003 case 1: 5004 case 'year': 5005 o.startView = 1; 5006 break; 5007 default: 5008 o.startView = 0; 5009 } 5010 5011 switch (o.minViewMode) { 5012 case 1: 5013 case 'months': 5014 o.minViewMode = 1; 5015 break; 5016 case 2: 5017 case 'years': 5018 o.minViewMode = 2; 5019 break; 5020 default: 5021 o.minViewMode = 0; 5022 } 5023 5024 o.startView = Math.max(o.startView, o.minViewMode); 5025 5026 o.weekStart %= 7; 5027 o.weekEnd = ((o.weekStart + 6) % 7); 5028 5029 var format = DPGlobal.parseFormat(o.format) 5030 if (o.startDate !== -Infinity) { 5031 o.startDate = DPGlobal.parseDate(o.startDate, format, o.language); 5032 } 5033 if (o.endDate !== Infinity) { 5034 o.endDate = DPGlobal.parseDate(o.endDate, format, o.language); 5035 } 5036 5037 o.daysOfWeekDisabled = o.daysOfWeekDisabled||[]; 5038 if (!$.isArray(o.daysOfWeekDisabled)) 5039 o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/); 5040 o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function (d) { 5041 return parseInt(d, 10); 5042 }); 5043 }, 5044 _events: [], 5045 _secondaryEvents: [], 5046 _applyEvents: function(evs){ 5047 for (var i=0, el, ev; i<evs.length; i++){ 5048 el = evs[i][0]; 5049 ev = evs[i][1]; 5050 el.on(ev); 5051 } 5052 }, 5053 _unapplyEvents: function(evs){ 5054 for (var i=0, el, ev; i<evs.length; i++){ 5055 el = evs[i][0]; 5056 ev = evs[i][1]; 5057 el.off(ev); 5058 } 5059 }, 5060 _buildEvents: function(){ 5061 if (this.isInput) { // single input 5062 this._events = [ 5063 [this.element, { 5064 focus: $.proxy(this.show, this), 5065 keyup: $.proxy(this.update, this), 5066 keydown: $.proxy(this.keydown, this) 5067 }] 5068 ]; 5069 } 5070 else if (this.component && this.hasInput){ // component: input + button 5071 this._events = [ 5072 // For components that are not readonly, allow keyboard nav 5073 [this.element.find('input'), { 5074 focus: $.proxy(this.show, this), 5075 keyup: $.proxy(this.update, this), 5076 keydown: $.proxy(this.keydown, this) 5077 }], 5078 [this.component, { 5079 click: $.proxy(this.show, this) 5080 }] 5081 ]; 5082 } 5083 else if (this.element.is('div')) { // inline datepicker 5084 this.isInline = true; 5085 } 5086 else { 5087 this._events = [ 5088 [this.element, { 5089 click: $.proxy(this.show, this) 5090 }] 5091 ]; 5092 } 5093 5094 this._secondaryEvents = [ 5095 [this.picker, { 5096 click: $.proxy(this.click, this) 5097 }], 5098 [$(window), { 5099 resize: $.proxy(this.place, this) 5100 }], 5101 [$(document), { 5102 mousedown: $.proxy(function (e) { 5103 // Clicked outside the datepicker, hide it 5104 if (!( 5105 this.element.is(e.target) || 5106 this.element.find(e.target).size() || 5107 this.picker.is(e.target) || 5108 this.picker.find(e.target).size() 5109 )) { 5110 this.hide(); 5111 } 5112 }, this) 5113 }] 5114 ]; 5115 }, 5116 _attachEvents: function(){ 5117 this._detachEvents(); 5118 this._applyEvents(this._events); 5119 }, 5120 _detachEvents: function(){ 5121 this._unapplyEvents(this._events); 5122 }, 5123 _attachSecondaryEvents: function(){ 5124 this._detachSecondaryEvents(); 5125 this._applyEvents(this._secondaryEvents); 5126 }, 5127 _detachSecondaryEvents: function(){ 5128 this._unapplyEvents(this._secondaryEvents); 5129 }, 5130 _trigger: function(event, altdate){ 5131 var date = altdate || this.date, 5132 local_date = new Date(date.getTime() + (date.getTimezoneOffset()*60000)); 5133 5134 this.element.trigger({ 5135 type: event, 5136 date: local_date, 5137 format: $.proxy(function(altformat){ 5138 var format = altformat || this.o.format; 5139 return DPGlobal.formatDate(date, format, this.o.language); 5140 }, this) 5141 }); 5142 }, 5143 5144 show: function(e) { 5145 if (!this.isInline) 5146 this.picker.appendTo('body'); 5147 this.picker.show(); 5148 this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); 5149 this.place(); 5150 this._attachSecondaryEvents(); 5151 if (e) { 5152 e.preventDefault(); 5153 } 5154 this._trigger('show'); 5155 }, 5156 5157 hide: function(e){ 5158 if(this.isInline) return; 5159 if (!this.picker.is(':visible')) return; 5160 this.picker.hide().detach(); 5161 this._detachSecondaryEvents(); 5162 this.viewMode = this.o.startView; 5163 this.showMode(); 5164 5165 if ( 5166 this.o.forceParse && 5167 ( 5168 this.isInput && this.element.val() || 5169 this.hasInput && this.element.find('input').val() 5170 ) 5171 ) 5172 this.setValue(); 5173 this._trigger('hide'); 5174 }, 5175 5176 remove: function() { 5177 this.hide(); 5178 this._detachEvents(); 5179 this._detachSecondaryEvents(); 5180 this.picker.remove(); 5181 delete this.element.data().datepicker; 5182 if (!this.isInput) { 5183 delete this.element.data().date; 5184 } 5185 }, 5186 5187 getDate: function() { 5188 var d = this.getUTCDate(); 5189 return new Date(d.getTime() + (d.getTimezoneOffset()*60000)); 5190 }, 5191 5192 getUTCDate: function() { 5193 return this.date; 5194 }, 5195 5196 setDate: function(d) { 5197 this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000))); 5198 }, 5199 5200 setUTCDate: function(d) { 5201 this.date = d; 5202 this.setValue(); 5203 }, 5204 5205 setValue: function() { 5206 var formatted = this.getFormattedDate(); 5207 if (!this.isInput) { 5208 if (this.component){ 5209 this.element.find('input').val(formatted); 5210 } 5211 } else { 5212 this.element.val(formatted); 5213 } 5214 }, 5215 5216 getFormattedDate: function(format) { 5217 if (format === undefined) 5218 format = this.o.format; 5219 return DPGlobal.formatDate(this.date, format, this.o.language); 5220 }, 5221 5222 setStartDate: function(startDate){ 5223 this._process_options({startDate: startDate}); 5224 this.update(); 5225 this.updateNavArrows(); 5226 }, 5227
5228 setEndDate: function(endDate){ 5229 this._process_options({endDate: endDate}); 5230 this.update(); 5231 this.updateNavArrows(); 5232 }, 5233 5234 setDaysOfWeekDisabled: function(daysOfWeekDisabled){ 5235 this._process_options({daysOfWeekDisabled: daysOfWeekDisabled}); 5236 this.update(); 5237 this.updateNavArrows(); 5238 }, 5239 5240 place: function(){ 5241 if(this.isInline) return; 5242 var zIndex = parseInt(this.element.parents().filter(function() { 5243 return $(this).css('z-index') != 'auto'; 5244 }).first().css('z-index'))+10; 5245 var offset = this.component ? this.component.parent().offset() : this.element.offset(); 5246 var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(true); 5247 this.picker.css({ 5248 top: offset.top + height, 5249 left: offset.left, 5250 zIndex: zIndex 5251 }); 5252 }, 5253 5254 _allow_update: true, 5255 update: function(){ 5256 if (!this._allow_update) return; 5257 5258 var date, fromArgs = false; 5259 if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) { 5260 date = arguments[0]; 5261 fromArgs = true; 5262 } else { 5263 date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val(); 5264 delete this.element.data().date; 5265 } 5266 5267 this.date = DPGlobal.parseDate(date, this.o.format, this.o.language); 5268 5269 if(fromArgs) this.setValue(); 5270 5271 if (this.date < this.o.startDate) { 5272 this.viewDate = new Date(this.o.startDate); 5273 } else if (this.date > this.o.endDate) { 5274 this.viewDate = new Date(this.o.endDate); 5275 } else { 5276 this.viewDate = new Date(this.date); 5277 } 5278 this.fill(); 5279 }, 5280 5281 fillDow: function(){ 5282 var dowCnt = this.o.weekStart, 5283 html = '<tr>'; 5284 if(this.o.calendarWeeks){ 5285 var cell = '<th class="cw"> </th>'; 5286 html += cell; 5287 this.picker.find('.datepicker-days thead tr:first-child').prepend(cell); 5288 } 5289 while (dowCnt < this.o.weekStart + 7) { 5290 html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>'; 5291 } 5292 html += '</tr>'; 5293 this.picker.find('.datepicker-days thead').append(html); 5294 }, 5295 5296 fillMonths: function(){ 5297 var html = '', 5298 i = 0; 5299 while (i < 12) { 5300 html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>'; 5301 } 5302 this.picker.find('.datepicker-months td').html(html); 5303 }, 5304 5305 setRange: function(range){ 5306 if (!range || !range.length) 5307 delete this.range; 5308 else 5309 this.range = $.map(range, function(d){ return d.valueOf(); }); 5310 this.fill(); 5311 }, 5312 5313 getClassNames: function(date){ 5314 var cls = [], 5315 year = this.viewDate.getUTCFullYear(), 5316 month = this.viewDate.getUTCMonth(), 5317 currentDate = this.date.valueOf(), 5318 today = new Date(); 5319 if (date.getUTCFullYear() < year || (date.getUTCFullYear() == year && date.getUTCMonth() < month)) { 5320 cls.push('old'); 5321 } else if (date.getUTCFullYear() > year || (date.getUTCFullYear() == year && date.getUTCMonth() > month)) { 5322 cls.push('new'); 5323 } 5324 // Compare internal UTC date with local today, not UTC today 5325 if (this.o.todayHighlight && 5326 date.getUTCFullYear() == today.getFullYear() && 5327 date.getUTCMonth() == today.getMonth() && 5328 date.getUTCDate() == today.getDate()) { 5329 cls.push('today'); 5330 } 5331 if (currentDate && date.valueOf() == currentDate) { 5332 cls.push('active'); 5333 } 5334 if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate || 5335 $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1) { 5336 cls.push('disabled'); 5337 } 5338 if (this.range){ 5339 if (date > this.range[0] && date < this.range[this.range.length-1]){ 5340 cls.push('range'); 5341 } 5342 if ($.inArray(date.valueOf(), this.range) != -1){ 5343 cls.push('selected'); 5344 } 5345 } 5346 return cls; 5347 }, 5348 5349 fill: function() { 5350 var d = new Date(this.viewDate), 5351 year = d.getUTCFullYear(), 5352 month = d.getUTCMonth(), 5353 startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity, 5354 startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity, 5355 endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity, 5356 endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity, 5357 currentDate = this.date && this.date.valueOf(), 5358 tooltip; 5359 this.picker.find('.datepicker-days thead th.datepicker-switch') 5360 .text(dates[this.o.language].months[month]+' '+year); 5361 this.picker.find('tfoot th.today') 5362 .text(dates[this.o.language].today) 5363 .toggle(this.o.todayBtn !== false); 5364 this.picker.find('tfoot th.clear') 5365 .text(dates[this.o.language].clear) 5366 .toggle(this.o.clearBtn !== false); 5367 this.updateNavArrows(); 5368 this.fillMonths(); 5369 var prevMonth = UTCDate(year, month-1, 28,0,0,0,0), 5370 day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); 5371 prevMonth.setUTCDate(day); 5372 prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7); 5373 var nextMonth = new Date(prevMonth); 5374 nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); 5375 nextMonth = nextMonth.valueOf(); 5376 var html = []; 5377 var clsName; 5378 while(prevMonth.valueOf() < nextMonth) { 5379 if (prevMonth.getUTCDay() == this.o.weekStart) { 5380 html.push('<tr>'); 5381 if(this.o.calendarWeeks){ 5382 // ISO 8601: First week contains first thursday. 5383 // ISO also states week starts on Monday, but we can be more abstract here. 5384 var 5385 // Start of current week: based on weekstart/current date 5386 ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5), 5387 // Thursday of this week 5388 th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5), 5389 // First Thursday of year, year from thursday 5390 yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5), 5391 // Calendar week: ms between thursdays, div ms per day, div 7 days 5392 calWeek = (th - yth) / 864e5 / 7 + 1; 5393 html.push('<td class="cw">'+ calWeek +'</td>'); 5394 5395 } 5396 } 5397 clsName = this.getClassNames(prevMonth); 5398 clsName.push('day'); 5399 5400 var before = this.o.beforeShowDay(prevMonth); 5401 if (before === undefined) 5402 before = {}; 5403 else if (typeof(before) === 'boolean') 5404 before = {enabled: before}; 5405 else if (typeof(before) === 'string') 5406 before = {classes: before}; 5407 if (before.enabled === false) 5408 clsName.push('disabled'); 5409 if (before.classes) 5410 clsName = clsName.concat(before.classes.split(/\s+/)); 5411 if (before.tooltip) 5412 tooltip = before.tooltip; 5413 5414 clsName = $.unique(clsName); 5415 html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>'); 5416 if (prevMonth.getUTCDay() == this.o.weekEnd) { 5417 html.push('</tr>'); 5418 } 5419 prevMonth.setUTCDate(prevMonth.getUTCDate()+1); 5420 } 5421 this.picker.find('.datepicker-days tbody').empty().append(html.join('')); 5422 var currentYear = this.date && this.date.getUTCFullYear(); 5423 5424 var months = this.picker.find('.datepicker-months') 5425 .find('th:eq(1)') 5426 .text(year) 5427 .end() 5428 .find('span').removeClass('active'); 5429 if (currentYear && currentYear == year) { 5430 months.eq(this.date.getUTCMonth()).addClass('active'); 5431 } 5432 if (year < startYear || year > endYear) { 5433 months.addClass('disabled'); 5434 } 5435 if (year == startYear) { 5436 months.slice(0, startMonth).addClass('disabled'); 5437 } 5438 if (year == endYear) { 5439 months.slice(endMonth+1).addClass('disabled'); 5440 } 5441 5442 html = ''; 5443 year = parseInt(year/10, 10) * 10; 5444 var yearCont = this.picker.find('.datepicker-years') 5445 .find('th:eq(1)') 5446 .text(year + '-' + (year + 9)) 5447 .end() 5448 .find('td'); 5449 year -= 1;
5450 for (var i = -1; i < 11; i++) { 5451 html += '<span class="year'+(i == -1 ? ' old' : i == 10 ? ' new' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>'; 5452 year += 1; 5453 } 5454 yearCont.html(html); 5455 }, 5456 5457 updateNavArrows: function() { 5458 if (!this._allow_update) return; 5459 5460 var d = new Date(this.viewDate), 5461 year = d.getUTCFullYear(), 5462 month = d.getUTCMonth(); 5463 switch (this.viewMode) { 5464 case 0: 5465 if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()) { 5466 this.picker.find('.prev').css({visibility: 'hidden'}); 5467 } else { 5468 this.picker.find('.prev').css({visibility: 'visible'}); 5469 } 5470 if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()) { 5471 this.picker.find('.next').css({visibility: 'hidden'}); 5472 } else { 5473 this.picker.find('.next').css({visibility: 'visible'}); 5474 } 5475 break; 5476 case 1: 5477 case 2: 5478 if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()) { 5479 this.picker.find('.prev').css({visibility: 'hidden'}); 5480 } else { 5481 this.picker.find('.prev').css({visibility: 'visible'}); 5482 } 5483 if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()) { 5484 this.picker.find('.next').css({visibility: 'hidden'}); 5485 } else { 5486 this.picker.find('.next').css({visibility: 'visible'}); 5487 } 5488 break; 5489 } 5490 }, 5491 5492 click: function(e) { 5493 e.preventDefault(); 5494 var target = $(e.target).closest('span, td, th'); 5495 if (target.length == 1) { 5496 switch(target[0].nodeName.toLowerCase()) { 5497 case 'th': 5498 switch(target[0].className) { 5499 case 'datepicker-switch': 5500 this.showMode(1); 5501 break; 5502 case 'prev': 5503 case 'next': 5504 var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1); 5505 switch(this.viewMode){ 5506 case 0: 5507 this.viewDate = this.moveMonth(this.viewDate, dir); 5508 break; 5509 case 1: 5510 case 2: 5511 this.viewDate = this.moveYear(this.viewDate, dir); 5512 break; 5513 } 5514 this.fill(); 5515 break; 5516 case 'today': 5517 var date = new Date(); 5518 date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); 5519 5520 this.showMode(-2); 5521 var which = this.o.todayBtn == 'linked' ? null : 'view'; 5522 this._setDate(date, which); 5523 break; 5524 case 'clear': 5525 var element; 5526 if (this.isInput) 5527 element = this.element; 5528 else if (this.component) 5529 element = this.element.find('input'); 5530 if (element) 5531 element.val("").change(); 5532 this._trigger('changeDate'); 5533 this.update(); 5534 if (this.o.autoclose) 5535 this.hide(); 5536 break; 5537 } 5538 break; 5539 case 'span': 5540 if (!target.is('.disabled')) { 5541 this.viewDate.setUTCDate(1); 5542 if (target.is('.month')) { 5543 var day = 1; 5544 var month = target.parent().find('span').index(target); 5545 var year = this.viewDate.getUTCFullYear(); 5546 this.viewDate.setUTCMonth(month); 5547 this._trigger('changeMonth', this.viewDate); 5548 if (this.o.minViewMode === 1) { 5549 this._setDate(UTCDate(year, month, day,0,0,0,0)); 5550 } 5551 } else { 5552 var year = parseInt(target.text(), 10)||0; 5553 var day = 1; 5554 var month = 0; 5555 this.viewDate.setUTCFullYear(year); 5556 this._trigger('changeYear', this.viewDate); 5557 if (this.o.minViewMode === 2) { 5558 this._setDate(UTCDate(year, month, day,0,0,0,0)); 5559 } 5560 } 5561 this.showMode(-1); 5562 this.fill(); 5563 } 5564 break; 5565 case 'td': 5566 if (target.is('.day') && !target.is('.disabled')){ 5567 var day = parseInt(target.text(), 10)||1; 5568 var year = this.viewDate.getUTCFullYear(), 5569 month = this.viewDate.getUTCMonth(); 5570 if (target.is('.old')) { 5571 if (month === 0) { 5572 month = 11; 5573 year -= 1; 5574 } else { 5575 month -= 1; 5576 } 5577 } else if (target.is('.new')) { 5578 if (month == 11) {
5579 month = 0; 5580 year += 1; 5581 } else { 5582 month += 1; 5583 } 5584 } 5585 this._setDate(UTCDate(year, month, day,0,0,0,0)); 5586 } 5587 break; 5588 } 5589 } 5590 }, 5591 5592 _setDate: function(date, which){ 5593 if (!which || which == 'date') 5594 this.date = new Date(date); 5595 if (!which || which == 'view') 5596 this.viewDate = new Date(date); 5597 this.fill(); 5598 this.setValue(); 5599 this._trigger('changeDate'); 5600 var element; 5601 if (this.isInput) { 5602 element = this.element; 5603 } else if (this.component){ 5604 element = this.element.find('input'); 5605 } 5606 if (element) { 5607 element.change(); 5608 if (this.o.autoclose && (!which || which == 'date')) { 5609 this.hide(); 5610 } 5611 } 5612 }, 5613 5614 moveMonth: function(date, dir){ 5615 if (!dir) return date; 5616 var new_date = new Date(date.valueOf()), 5617 day = new_date.getUTCDate(), 5618 month = new_date.getUTCMonth(), 5619 mag = Math.abs(dir), 5620 new_month, test; 5621 dir = dir > 0 ? 1 : -1; 5622 if (mag == 1){ 5623 test = dir == -1 5624 // If going back one month, make sure month is not current month 5625 // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) 5626 ? function(){ return new_date.getUTCMonth() == month; } 5627 // If going forward one month, make sure month is as expected 5628 // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) 5629 : function(){ return new_date.getUTCMonth() != new_month; }; 5630 new_month = month + dir; 5631 new_date.setUTCMonth(new_month); 5632 // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 5633 if (new_month < 0 || new_month > 11) 5634 new_month = (new_month + 12) % 12; 5635 } else { 5636 // For magnitudes >1, move one month at a time... 5637 for (var i=0; i<mag; i++) 5638 // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)... 5639 new_date = this.moveMonth(new_date, dir); 5640 // ...then reset the day, keeping it in the new month 5641 new_month = new_date.getUTCMonth(); 5642 new_date.setUTCDate(day); 5643 test = function(){ return new_month != new_date.getUTCMonth(); }; 5644 } 5645 // Common date-resetting loop -- if date is beyond end of month, make it 5646 // end of month 5647 while (test()){ 5648 new_date.setUTCDate(--day); 5649 new_date.setUTCMonth(new_month); 5650 } 5651 return new_date; 5652 }, 5653 5654 moveYear: function(date, dir){ 5655 return this.moveMonth(date, dir*12); 5656 }, 5657 5658 dateWithinRange: function(date){ 5659 return date >= this.o.startDate && date <= this.o.endDate; 5660 }, 5661 5662 keydown: function(e){ 5663 if (this.picker.is(':not(:visible)')){ 5664 if (e.keyCode == 27) // allow escape to hide and re-show picker 5665 this.show(); 5666 return; 5667 } 5668 var dateChanged = false, 5669 dir, day, month, 5670 newDate, newViewDate; 5671 switch(e.keyCode){ 5672 case 27: // escape 5673 this.hide(); 5674 e.preventDefault(); 5675 break; 5676 case 37: // left 5677 case 39: // right 5678 if (!this.o.keyboardNavigation) break; 5679 dir = e.keyCode == 37 ? -1 : 1; 5680 if (e.ctrlKey){ 5681 newDate = this.moveYear(this.date, dir); 5682 newViewDate = this.moveYear(this.viewDate, dir); 5683 } else if (e.shiftKey){ 5684 newDate = this.moveMonth(this.date, dir); 5685 newViewDate = this.moveMonth(this.viewDate, dir); 5686 } else { 5687 newDate = new Date(this.date); 5688 newDate.setUTCDate(this.date.getUTCDate() + dir); 5689 newViewDate = new Date(this.viewDate); 5690 newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir); 5691 } 5692 if (this.dateWithinRange(newDate)){ 5693 this.date = newDate; 5694 this.viewDate = newViewDate; 5695 this.setValue(); 5696 this.update(); 5697 e.preventDefault(); 5698 dateChanged = true; 5699 } 5700 break; 5701 case 38: // up 5702 case 40: // down 5703 if (!this.o.keyboardNavigation) break; 5704 dir = e.keyCode == 38 ? -1 : 1; 5705 if (e.ctrlKey){ 5706 newDate = this.moveYear(this.date, dir); 5707 newViewDate = this.moveYear(this.viewDate, dir); 5708 } else if (e.shiftKey){ 5709 newDate = this.moveMonth(this.date, dir); 5710 newViewDate = this.moveMonth(this.viewDate, dir); 5711 } else { 5712 newDate = new Date(this.date); 5713 newDate.setUTCDate(this.date.getUTCDate() + dir * 7); 5714 newViewDate = new Date(this.viewDate); 5715 newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7); 5716 } 5717 if (this.dateWithinRange(newDate)){ 5718 this.date = newDate; 5719 this.viewDate = newViewDate; 5720 this.setValue(); 5721 this.update(); 5722 e.preventDefault(); 5723 dateChanged = true; 5724 } 5725 break; 5726 case 13: // enter 5727 this.hide(); 5728 e.preventDefault(); 5729 break; 5730 case 9: // tab 5731 this.hide(); 5732 break; 5733 } 5734 if (dateChanged){ 5735 this._trigger('changeDate'); 5736 var element; 5737 if (this.isInput) { 5738 element = this.element; 5739 } else if (this.component){ 5740 element = this.element.find('input'); 5741 } 5742 if (element) { 5743 element.change(); 5744 } 5745 } 5746 }, 5747 5748 showMode: function(dir) { 5749 if (dir) { 5750 this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir)); 5751 } 5752 /* 5753 vitalets: fixing bug of very special conditions: 5754 jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover. 5755 Method show() does not set display css correctly and datepicker is not shown. 5756 Changed to .css('display', 'block') solve the problem. 5757 See https://github.com/vitalets/x-editable/issues/37 5758 5759 In jquery 1.7.2+ everything works fine. 5760 */ 5761 //this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); 5762 this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block'); 5763 this.updateNavArrows(); 5764 } 5765 }; 5766 5767 var DateRangePicker = function(element, options){ 5768 this.element = $(element); 5769 this.inputs = $.map(options.inputs, function(i){ return i.jquery ? i[0] : i; }); 5770 delete options.inputs; 5771 5772 $(this.inputs) 5773 .datepicker(options) 5774 .bind('changeDate', $.proxy(this.dateUpdated, this)); 5775 5776 this.pickers = $.map(this.inputs, function(i){ return $(i).data('datepicker'); }); 5777 this.updateDates(); 5778 }; 5779 DateRangePicker.prototype = { 5780 updateDates: function(){ 5781 this.dates = $.map(this.pickers, function(i){ return i.date; }); 5782 this.updateRanges(); 5783 }, 5784 updateRanges: function(){ 5785 var range = $.map(this.dates, function(d){ return d.valueOf(); }); 5786 $.each(this.pickers, function(i, p){ 5787 p.setRange(range); 5788 }); 5789 }, 5790 dateUpdated: function(e){ 5791 var dp = $(e.target).data('datepicker'), 5792 new_date = dp.getUTCDate(), 5793 i = $.inArray(e.target, this.inputs), 5794 l = this.inputs.length; 5795 if (i == -1) return; 5796 5797 if (new_date < this.dates[i]){ 5798 // Date being moved earlier/left 5799 while (i>=0 && new_date < this.dates[i]){ 5800 this.pickers[i--].setUTCDate(new_date); 5801 } 5802 } 5803 else if (new_date > this.dates[i]){ 5804 // Date being moved later/right 5805 while (i<l && new_date > this.dates[i]){ 5806 this.pickers[i++].setUTCDate(new_date); 5807 } 5808 } 5809 this.updateDates(); 5810 }, 5811 remove: function(){ 5812 $.map(this.pickers, function(p){ p.remove(); }); 5813 delete this.element.data().datepicker; 5814 } 5815 }; 5816 5817 function opts_from_el(el, prefix){ 5818 // Derive options from element data-attrs 5819 var data = $(el).data(), 5820 out = {}, inkey, 5821 replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'), 5822 prefix = new RegExp('^' + prefix.toLowerCase()); 5823 for (var key in data) 5824 if (prefix.test(key)){ 5825 inkey = key.replace(replace, function(_,a){ return a.toLowerCase(); }); 5826 out[inkey] = data[key]; 5827 } 5828 return out; 5829 } 5830 5831 function opts_from_locale(lang){ 5832 // Derive options from locale plugins 5833 var out = {}; 5834 // Check if "de-DE" style date is available, if not language should 5835 // fallback to 2 letter code eg "de" 5836 if (!dates[lang]) { 5837 lang = lang.split('-')[0] 5838 if (!dates[lang]) 5839 return; 5840 } 5841 var d = dates[lang]; 5842 $.each(locale_opts, function(i,k){ 5843 if (k in d) 5844 out[k] = d[k]; 5845 }); 5846 return out; 5847 } 5848 5849 var old = $.fn.datepicker; 5850 var datepicker = $.fn.datepicker = function ( option ) { 5851 var args = Array.apply(null, arguments); 5852 args.shift(); 5853 var internal_return, 5854 this_return; 5855 this.each(function () { 5856 var $this = $(this), 5857 data = $this.data('datepicker'), 5858 options = typeof option == 'object' && option; 5859 if (!data) { 5860 var elopts = opts_from_el(this, 'date'), 5861 // Preliminary otions 5862 xopts = $.extend({}, defaults, elopts, options), 5863 locopts = opts_from_locale(xopts.language), 5864 // Options priority: js args, data-attrs, locales, defaults 5865 opts = $.extend({}, defaults, locopts, elopts, options); 5866 if ($this.is('.input-daterange') || opts.inputs){ 5867 var ropts = { 5868 inputs: opts.inputs || $this.find('input').toArray() 5869 }; 5870 $this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts)))); 5871 } 5872 else{ 5873 $this.data('datepicker', (data = new Datepicker(this, opts))); 5874 } 5875 } 5876 if (typeof option == 'string' && typeof data[option] == 'function') { 5877 internal_return = data[option].apply(data, args); 5878 if (internal_return !== undefined) 5879 return false;
5880 } 5881 }); 5882 if (internal_return !== undefined) 5883 return internal_return; 5884 else 5885 return this; 5886 }; 5887 5888 var defaults = $.fn.datepicker.defaults = { 5889 autoclose: false, 5890 beforeShowDay: $.noop, 5891 calendarWeeks: false, 5892 clearBtn: false, 5893 daysOfWeekDisabled: [], 5894 endDate: Infinity, 5895 forceParse: true, 5896 format: 'mm/dd/yyyy', 5897 keyboardNavigation: true, 5898 language: 'en', 5899 minViewMode: 0, 5900 rtl: false, 5901 startDate: -Infinity, 5902 startView: 0, 5903 todayBtn: false, 5904 todayHighlight: false, 5905 weekStart: 0 5906 }; 5907 var locale_opts = $.fn.datepicker.locale_opts = [ 5908 'format', 5909 'rtl', 5910 'weekStart' 5911 ]; 5912 $.fn.datepicker.Constructor = Datepicker; 5913 var dates = $.fn.datepicker.dates = { 5914 en: { 5915 days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], 5916 daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], 5917 daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], 5918 months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], 5919 monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 5920 today: "Today", 5921 clear: "Clear" 5922 } 5923 }; 5924 5925 var DPGlobal = { 5926 modes: [ 5927 { 5928 clsName: 'days', 5929 navFnc: 'Month', 5930 navStep: 1 5931 }, 5932 { 5933 clsName: 'months', 5934 navFnc: 'FullYear', 5935 navStep: 1 5936 }, 5937 { 5938 clsName: 'years', 5939 navFnc: 'FullYear', 5940 navStep: 10 5941 }], 5942 isLeapYear: function (year) { 5943 return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); 5944 }, 5945 getDaysInMonth: function (year, month) { 5946 return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; 5947 }, 5948 validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g, 5949 nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g, 5950 parseFormat: function(format){ 5951 // IE treats \0 as a string end in inputs (truncating the value), 5952 // so it's a bad format delimiter, anyway 5953 var separators = format.replace(this.validParts, '\0').split('\0'), 5954 parts = format.match(this.validParts); 5955 if (!separators || !separators.length || !parts || parts.length === 0){ 5956 throw new Error("Invalid date format."); 5957 } 5958 return {separators: separators, parts: parts}; 5959 }, 5960 parseDate: function(date, format, language) { 5961 if (date instanceof Date) return date; 5962 if (typeof format === 'string') 5963 format = DPGlobal.parseFormat(format); 5964 if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) { 5965 var part_re = /([\-+]\d+)([dmwy])/, 5966 parts = date.match(/([\-+]\d+)([dmwy])/g), 5967 part, dir; 5968 date = new Date(); 5969 for (var i=0; i<parts.length; i++) { 5970 part = part_re.exec(parts[i]); 5971 dir = parseInt(part[1]); 5972 switch(part[2]){ 5973 case 'd': 5974 date.setUTCDate(date.getUTCDate() + dir); 5975 break; 5976 case 'm': 5977 date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir); 5978 break; 5979 case 'w': 5980 date.setUTCDate(date.getUTCDate() + dir * 7); 5981 break; 5982 case 'y': 5983 date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir); 5984 break; 5985 } 5986 } 5987 return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0); 5988 } 5989 var parts = date && date.match(this.nonpunctuation) || [], 5990 date = new Date(), 5991 parsed = {}, 5992 setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'], 5993 setters_map = { 5994 yyyy: function(d,v){ return d.setUTCFullYear(v); }, 5995 yy: function(d,v){ return d.setUTCFullYear(2000+v); }, 5996 m: function(d,v){ 5997 v -= 1; 5998 while (v<0) v += 12; 5999 v %= 12; 6000 d.setUTCMonth(v); 6001 while (d.getUTCMonth() != v) 6002 d.setUTCDate(d.getUTCDate()-1); 6003 return d; 6004 }, 6005 d: function(d,v){ return d.setUTCDate(v); } 6006 }, 6007 val, filtered, part; 6008 setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; 6009 setters_map['dd'] = setters_map['d']; 6010 date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); 6011 var fparts = format.parts.slice(); 6012 // Remove noop parts 6013 if (parts.length != fparts.length) { 6014 fparts = $(fparts).filter(function(i,p){ 6015 return $.inArray(p, setters_order) !== -1; 6016 }).toArray(); 6017 } 6018 // Process remainder 6019 if (parts.length == fparts.length) { 6020 for (var i=0, cnt = fparts.length; i < cnt; i++) { 6021 val = parseInt(parts[i], 10); 6022 part = fparts[i]; 6023 if (isNaN(val)) { 6024 switch(part) { 6025 case 'MM': 6026 filtered = $(dates[language].months).filter(function(){ 6027 var m = this.slice(0, parts[i].length), 6028 p = parts[i].slice(0, m.length); 6029 return m == p; 6030 }); 6031 val = $.inArray(filtered[0], dates[language].months) + 1; 6032 break; 6033 case 'M': 6034 filtered = $(dates[language].monthsShort).filter(function(){ 6035 var m = this.slice(0, parts[i].length), 6036 p = parts[i].slice(0, m.length); 6037 return m == p; 6038 }); 6039 val = $.inArray(filtered[0], dates[language].monthsShort) + 1; 6040 break; 6041 } 6042 } 6043 parsed[part] = val; 6044 } 6045 for (var i=0, s; i<setters_order.length; i++){ 6046 s = setters_order[i]; 6047 if (s in parsed && !isNaN(parsed[s])) 6048 setters_map[s](date, parsed[s]); 6049 } 6050 } 6051 return date; 6052 }, 6053 formatDate: function(date, format, language){ 6054 if (typeof format === 'string') 6055 format = DPGlobal.parseFormat(format); 6056 var val = { 6057 d: date.getUTCDate(), 6058 D: dates[language].daysShort[date.getUTCDay()], 6059 DD: dates[language].days[date.getUTCDay()], 6060 m: date.getUTCMonth() + 1, 6061 M: dates[language].monthsShort[date.getUTCMonth()], 6062 MM: dates[language].months[date.getUTCMonth()], 6063 yy: date.getUTCFullYear().toString().substring(2), 6064 yyyy: date.getUTCFullYear() 6065 }; 6066 val.dd = (val.d < 10 ? '0' : '') + val.d; 6067 val.mm = (val.m < 10 ? '0' : '') + val.m; 6068 var date = [], 6069 seps = $.extend([], format.separators); 6070 for (var i=0, cnt = format.parts.length; i <= cnt; i++) { 6071 if (seps.length) 6072 date.push(seps.shift()); 6073 date.push(val[format.parts[i]]); 6074 } 6075 return date.join(''); 6076 }, 6077 headTemplate: '<thead>'+ 6078 '<tr>'+ 6079 '<th class="prev"><i class="icon-arrow-left"/></th>'+ 6080 '<th colspan="5" class="datepicker-switch"></th>'+ 6081 '<th class="next"><i class="icon-arrow-right"/></th>'+ 6082 '</tr>'+ 6083 '</thead>', 6084 contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>', 6085 footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>' 6086 }; 6087 DPGlobal.template = '<div class="datepicker">'+ 6088 '<div class="datepicker-days">'+ 6089 '<table class=" table-condensed">'+ 6090 DPGlobal.headTemplate+ 6091 '<tbody></tbody>'+ 6092 DPGlobal.footTemplate+ 6093 '</table>'+ 6094 '</div>'+ 6095 '<div class="datepicker-months">'+ 6096 '<table class="table-condensed">'+ 6097 DPGlobal.headTemplate+ 6098 DPGlobal.contTemplate+ 6099 DPGlobal.footTemplate+ 6100 '</table>'+ 6101 '</div>'+ 6102 '<div class="datepicker-years">'+ 6103 '<table class="table-condensed">'+ 6104 DPGlobal.headTemplate+ 6105 DPGlobal.contTemplate+ 6106 DPGlobal.footTemplate+ 6107 '</table>'+ 6108 '</div>'+ 6109 '</div>'; 6110 6111 $.fn.datepicker.DPGlobal = DPGlobal; 6112 6113 6114 /* DATEPICKER NO CONFLICT 6115 * =================== */ 6116 6117 $.fn.datepicker.noConflict = function(){ 6118 $.fn.datepicker = old; 6119 return this; 6120 }; 6121 6122 6123 /* DATEPICKER DATA-API 6124 * ================== */ 6125 6126 $(document).on( 6127 'focus.datepicker.data-api click.datepicker.data-api',
6128 '[data-provide="datepicker"]', 6129 function(e){ 6130 var $this = $(this); 6131 if ($this.data('datepicker')) return; 6132 e.preventDefault(); 6133 // component click requires us to explicitly show it 6134 datepicker.call($this, 'show'); 6135 } 6136 ); 6137 $(function(){ 6138 //$('[data-provide="datepicker-inline"]').datepicker(); 6139 //vit: changed to support noConflict() 6140 datepicker.call($('[data-provide="datepicker-inline"]')); 6141 }); 6142 6143}( window.jQuery )); 6144 6145/** 6146Bootstrap-datepicker. 6147Description and examples: https://github.com/eternicode/bootstrap-datepicker. 6148For **i18n** you should include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales 6149and set `language` option. 6150Since 1.4.0 date has different appearance in **popup** and **inline** modes. 6151 6152@class date 6153@extends abstractinput 6154@final 6155@example 6156<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-title="Select date">15/05/1984</a> 6157<script> 6158$(function(){ 6159 $('#dob').editable({ 6160 format: 'yyyy-mm-dd', 6161 viewformat: 'dd/mm/yyyy', 6162 datepicker: { 6163 weekStart: 1 6164 } 6165 } 6166 }); 6167}); 6168</script> 6169**/ 6170(function ($) { 6171 "use strict"; 6172 6173 //store bootstrap-datepicker as bdateicker to exclude conflict with jQuery UI one 6174 $.fn.bdatepicker = $.fn.datepicker.noConflict(); 6175 if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name 6176 $.fn.datepicker = $.fn.bdatepicker; 6177 } 6178 6179 var Date = function (options) { 6180 this.init('date', options, Date.defaults); 6181 this.initPicker(options, Date.defaults); 6182 }; 6183 6184 $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput); 6185 6186 $.extend(Date.prototype, { 6187 initPicker: function(options, defaults) { 6188 //'format' is set directly from settings or data-* attributes 6189 6190 //by default viewformat equals to format 6191 if(!this.options.viewformat) { 6192 this.options.viewformat = this.options.format; 6193 } 6194 6195 //try parse datepicker config defined as json string in data-datepicker 6196 options.datepicker = $.fn.editableutils.tryParseJson(options.datepicker, true); 6197 6198 //overriding datepicker config (as by default jQuery extend() is not recursive) 6199 //since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only 6200 this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, { 6201 format: this.options.viewformat 6202 }); 6203 6204 //language 6205 this.options.datepicker.language = this.options.datepicker.language || 'en'; 6206 6207 //store DPglobal 6208 this.dpg = $.fn.bdatepicker.DPGlobal; 6209 6210 //store parsed formats 6211 this.parsedFormat = this.dpg.parseFormat(this.options.format); 6212 this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat); 6213 }, 6214 6215 render: function () { 6216 this.$input.bdatepicker(this.options.datepicker); 6217 6218 //"clear" link 6219 if(this.options.clear) { 6220 this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){ 6221 e.preventDefault(); 6222 e.stopPropagation(); 6223 this.clear(); 6224 }, this)); 6225 6226 this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear)); 6227 } 6228 }, 6229 6230 value2html: function(value, element) { 6231 var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : ''; 6232 Date.superclass.value2html.call(this, text, element); 6233 }, 6234 6235 html2value: function(html) { 6236 return this.parseDate(html, this.parsedViewFormat); 6237 }, 6238 6239 value2str: function(value) { 6240 return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : ''; 6241 }, 6242 6243 str2value: function(str) { 6244 return this.parseDate(str, this.parsedFormat); 6245 }, 6246 6247 value2submit: function(value) { 6248 return this.value2str(value); 6249 }, 6250 6251 value2input: function(value) { 6252 this.$input.bdatepicker('update', value); 6253 }, 6254 6255 input2value: function() { 6256 return this.$input.data('datepicker').date; 6257 }, 6258 6259 activate: function() { 6260 }, 6261 6262 clear: function() { 6263 this.$input.data('datepicker').date = null; 6264 this.$input.find('.active').removeClass('active'); 6265 if(!this.options.showbuttons) { 6266 this.$input.closest('form').submit(); 6267 } 6268 }, 6269 6270 autosubmit: function() { 6271 this.$input.on('mouseup', '.day', function(e){ 6272 if($(e.currentTarget).is('.old') || $(e.currentTarget).is('.new')) { 6273 return; 6274 } 6275 var $form = $(this).closest('form'); 6276 setTimeout(function() { 6277 $form.submit(); 6278 }, 200); 6279 }); 6280 //changedate is not suitable as it triggered when showing datepicker. see #149 6281 /* 6282 this.$input.on('changeDate', function(e){ 6283 var $form = $(this).closest('form'); 6284 setTimeout(function() { 6285 $form.submit(); 6286 }, 200); 6287 }); 6288 */ 6289 }, 6290 6291 /* 6292 For incorrect date bootstrap-datepicker returns current date that is not suitable 6293 for datefield. 6294 This function returns null for incorrect date. 6295 */ 6296 parseDate: function(str, format) { 6297 var date = null, formattedBack; 6298 if(str) { 6299 date = this.dpg.parseDate(str, format, this.options.datepicker.language); 6300 if(typeof str === 'string') { 6301 formattedBack = this.dpg.formatDate(date, format, this.options.datepicker.language); 6302 if(str !== formattedBack) { 6303 date = null; 6304 } 6305 } 6306 } 6307 return date; 6308 } 6309 6310 }); 6311 6312 Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { 6313 /** 6314 @property tpl 6315 @default <div></div> 6316 **/ 6317 tpl:'<div class="editable-date well"></div>', 6318 /** 6319 @property inputclass 6320 @default null 6321 **/ 6322 inputclass: null, 6323 /** 6324 Format used for sending value to server. Also applied when convert
6324ing date from <code>data-value</code> attribute.<br> 6325 Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code> 6326 6327 @property format 6328 @type string 6329 @default yyyy-mm-dd 6330 **/ 6331 format:'yyyy-mm-dd', 6332 /** 6333 Format used for displaying date. Also applied when converting date from element's text on init. 6334 If not specified equals to <code>format</code> 6335 6336 @property viewformat 6337 @type string 6338 @default null 6339 **/ 6340 viewformat: null, 6341 /** 6342 Configuration of datepicker. 6343 Full list of options: http://bootstrap-datepicker.readthedocs.org/en/latest/options.html 6344 6345 @property datepicker 6346 @type object 6347 @default { 6348 weekStart: 0, 6349 startView: 0, 6350 minViewMode: 0, 6351 autoclose: false 6352 } 6353 **/ 6354 datepicker:{ 6355 weekStart: 0, 6356 startView: 0, 6357 minViewMode: 0, 6358 autoclose: false 6359 }, 6360 /** 6361 Text shown as clear date button. 6362 If <code>false</code> clear button will not be rendered. 6363 6364 @property clear 6365 @type boolean|string 6366 @default 'x clear' 6367 **/ 6368 clear: '× clear' 6369 }); 6370 6371 $.fn.editabletypes.date = Date; 6372 6373}(window.jQuery)); 6374 6375/** 6376Bootstrap datefield input - modification for inline mode. 6377Shows normal <input type="text"> and binds popup datepicker. 6378Automatically shown in inline mode. 6379 6380@class datefield 6381@extends date 6382 6383@since 1.4.0 6384**/ 6385(function ($) { 6386 "use strict"; 6387 6388 var DateField = function (options) { 6389 this.init('datefield', options, DateField.defaults); 6390 this.initPicker(options, DateField.defaults); 6391 }; 6392 6393 $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date); 6394 6395 $.extend(DateField.prototype, { 6396 render: function () { 6397 this.$input = this.$tpl.find('input'); 6398 this.setClass(); 6399 this.setAttr('placeholder'); 6400 6401 //bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js) 6402 this.$input.bdatepicker(this.options.datepicker); 6403 6404 //need to disable original event handlers 6405 this.$input.off('focus keydown'); 6406 6407 //update value of datepicker 6408 this.$input.keyup($.proxy(function(){ 6409 this.$tpl.removeData('date'); 6410 this.$tpl.bdatepicker('update'); 6411 }, this)); 6412 6413 }, 6414 6415 value2input: function(value) { 6416 this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : ''); 6417 this.$tpl.bdatepicker('update'); 6418 }, 6419 6420 input2value: function() { 6421 return this.html2value(this.$input.val()); 6422 }, 6423 6424 activate: function() { 6425 $.fn.editabletypes.text.prototype.activate.call(this); 6426 }, 6427 6428 autosubmit: function() { 6429 //reset autosubmit to empty 6430 } 6431 }); 6432 6433 DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, { 6434 /** 6435 @property tpl 6436 **/ 6437 tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>', 6438 /** 6439 @property inputclass 6440 @default 'input-small' 6441 **/ 6442 inputclass: 'input-small', 6443 6444 /* datepicker config */ 6445 datepicker: { 6446 weekStart: 0, 6447 startView: 0, 6448 minViewMode: 0, 6449 autoclose: true 6450 } 6451 }); 6452 6453 $.fn.editabletypes.datefield = DateField; 6454 6455}(window.jQuery)); 6456 6457/** 6458Bootstrap-datetimepicker. 6459Based on [smalot bootstrap-datetimepicker plugin](https://github.com/smalot/bootstrap-datetimepicker). 6460Before usage you should manually include dependent js and css: 6461 6462 <link href="css/datetimepicker.css" rel="stylesheet" type="text/css"></link> 6463 <script src="js/bootstrap-datetimepicker.js"></script> 6464 6465For **i18n** you should include js file from here: https://github.com/smalot/bootstrap-datetimepicker/tree/master/js/locales 6466and set `language` option. 6467 6468@class datetime 6469@extends abstractinput 6470@final 6471@since 1.4.4 6472@example 6473<a href="#" id="last_seen" data-type="datetime" data-pk="1" data-url="/post" title="Select date & time">15/03/2013 12:45</a> 6474<script> 6475$(function(){ 6476 $('#last_seen').editable({ 6477 format: 'yyyy-mm-dd hh:ii', 6478 viewformat: 'dd/mm/yyyy hh:ii', 6479 datetimepicker: { 6480 weekStart: 1 6481 } 6482 } 6483 }); 6484}); 6485</script> 6486**/ 6487(function ($) { 6488 "use strict"; 6489 6490 var DateTime = function (options) { 6491 this.init('datetime', options, DateTime.defaults); 6492 this.initPicker(options, DateTime.defaults); 6493 }; 6494 6495 $.fn.editableutils.inherit(DateTime, $.fn.editabletypes.abstractinput); 6496 6497 $.extend(DateTime.prototype, { 6498 initPicker: function(options, defaults) { 6499 //'format' is set directly from settings or data-* attributes 6500 6501 //by default viewformat equals to format 6502 if(!this.options.viewformat) { 6503 this.options.viewformat = this.options.format; 6504 } 6505 6506 //try parse datetimepicker config defined as json string in data-datetimepicker 6507 options.datetimepicker = $.fn.editableutils.tryParseJson(options.datetimepicker, true); 6508 6509 //overriding datetimepicker config (as by default jQuery extend() is not recursive) 6510 //since 1.4 datetimepicker internally uses viewformat instead of format. Format is for submit only 6511 this.options.datetimepicker = $.extend({}, defaults.datetimepicker, options.datetimepicker, { 6512 format: this.options.viewformat 6513 }); 6514 6515 //language 6516 this.options.datetimepicker.language = this.options.datetimepicker.language || 'en'; 6517 6518 //store DPglobal 6519 this.dpg = $.fn.datetimepicker.DPGlobal; 6520 6521 //store parsed formats 6522 this.parsedFormat = this.dpg.parseFormat(this.options.format, this.options.formatType); 6523 this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat, this.options.formatType); 6524 }, 6525 6526 render: function () { 6527 this.$input.datetimepicker(this.options.datetimepicker); 6528 6529 //adjust container position when viewMode changes 6530 //see https://github.com/smalot/bootstrap-datetimepicker/pull/80 6531 this.$input.on('changeMode', function(e) { 6532 var f = $(this).closest('form').parent(); 6533 //timeout here, otherwise container changes position before form has new size 6534 setTimeout(function(){ 6535 f.triggerHandler('resize'); 6536 }, 0); 6537 }); 6538 6539 //"clear" link 6540 if(this.options.clear) { 6541 this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){ 6542 e.preventDefault(); 6543 e.stopPropagation(); 6544 this.clear(); 6545 }, this)); 6546 6547 this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear)); 6548 } 6549 }, 6550 6551 value2html: function(value, element) { 6552 //formatDate works with UTCDate! 6553 var text = value ? this.dpg.formatDate(this.toUTC(value), this.parsedViewFormat, this.options.datetimepicker.language, this.options.formatType) : ''; 6554 if(element) { 6555 DateTime.superclass.value2html.call(this, text, element); 6556 } else { 6557 return text; 6558 } 6559 }, 6560 6561 html2value: function(html) { 6562 //parseDate return utc date! 6563 var value = this.parseDate(html, this.parsedViewFormat); 6564 return value ? this.fromUTC(value) : null; 6565 }, 6566 6567 value2str: function(value) { 6568 //formatDate works with UTCDate! 6569 return value ? this.dpg.formatDate(this.toUTC(value), this.parsedFormat, this.options.datetimepicker.language, this.options.formatType) : ''; 6570 }, 6571 6572 str2value: function(str) { 6573 //parseDate return utc date! 6574 var value = this.parseDate(str, this.parsedFormat); 6575 return value ? this.fromUTC(value) : null; 6576 }, 6577 6578 value2submit: function(value) { 6579 return this.value2str(value); 6580 }, 6581 6582 value2input: function(value) { 6583 if(value) { 6584 this.$input.data('datetimepicker').setDate(value); 6585 } 6586 }, 6587 6588 input2value: function() { 6589 //date may be cleared, in that case getDate() triggers error 6590 var dt = this.$input.data('datetimepicker'); 6591 return dt.date ? dt.getDate() : null; 6592 }, 6593 6594 activate: function() { 6595 }, 6596 6597 clear: function() { 6598 this.$input.data('datetimepicker').date = null; 6599 this.$input.find('.active').removeClass('active'); 6600 if(!this.options.showbuttons) { 6601 this.$input.closest('form').submit(); 6602 } 6603 }, 6604 6605 autosubmit: function() { 6606 this.$input.on('mouseup', '.minute', function(e){ 6607 var $form = $(this).closest('form'); 6608 setTimeout(function() { 6609 $form.submit(); 6610 }, 200); 6611 }); 6612 }, 6613 6614 //convert date from local to utc 6615 toUTC: function(value) { 6616 return value ? new Date(value.valueOf() - value.getTimezoneOffset() * 60000) : value; 6617 }, 6618 6619 //convert date from utc to local 6620 fromUTC: function(value) { 6621 return value ? new Date(value.valueOf() + value.getTimezoneOffset() * 60000) : value; 6622 }, 6623 6624 /* 6625 For incorrect date bootstrap-datetimepicker returns current date that is not suitable 6626 for datetimefield. 6627 This function returns null for incorrect date. 6628 */ 6629 parseDate: function(str, format) { 6630 var date = null, formattedBack; 6631 if(str) { 6632 date = this.dpg.parseDate(str, format, this.options.datetimepicker.language, this.options.formatType); 6633 if(typeof str === 'string') { 6634 formattedBack = this.dpg.formatDate(date, format, this.options.datetimepicker.language, this.options.formatType); 6635 if(str !== formattedBack) { 6636 date = null; 6637 } 6638 } 6639 } 6640 return date; 6641 } 6642 6643 }); 6644 6645 DateTime.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { 6646 /** 6647 @property tpl 6648 @default <div></div> 6649 **/ 6650 tpl:'<div class="editable-date well"></div>', 6651 /** 6652 @property inputclass 6653 @default null 6654 **/ 6655 inputclass: null, 6656 /** 6657 Format used for sending value to server. Also applied when convert
6657ing date from <code>data-value</code> attribute.<br> 6658 Possible tokens are: <code>d, dd, m, mm, yy, yyyy, h, i</code> 6659 6660 @property format 6661 @type string 6662 @default yyyy-mm-dd hh:ii 6663 **/ 6664 format:'yyyy-mm-dd hh:ii', 6665 formatType:'standard', 6666 /** 6667 Format used for displaying date. Also applied when converting date from element's text on init. 6668 If not specified equals to <code>format</code> 6669 6670 @property viewformat 6671 @type string 6672 @default null 6673 **/ 6674 viewformat: null, 6675 /** 6676 Configuration of datetimepicker. 6677 Full list of options: https://github.com/smalot/bootstrap-datetimepicker 6678 6679 @property datetimepicker 6680 @type object 6681 @default { } 6682 **/ 6683 datetimepicker:{ 6684 todayHighlight: false, 6685 autoclose: false 6686 }, 6687 /** 6688 Text shown as clear date button. 6689 If <code>false</code> clear button will not be rendered. 6690 6691 @property clear 6692 @type boolean|string 6693 @default 'x clear' 6694 **/ 6695 clear: '× clear' 6696 }); 6697 6698 $.fn.editabletypes.datetime = DateTime; 6699 6700}(window.jQuery)); 6701/** 6702Bootstrap datetimefield input - datetime input for inline mode. 6703Shows normal <input type="text"> and binds popup datetimepicker. 6704Automatically shown in inline mode. 6705 6706@class datetimefield 6707@extends datetime 6708 6709**/ 6710(function ($) { 6711 "use strict"; 6712 6713 var DateTimeField = function (options) { 6714 this.init('datetimefield', options, DateTimeField.defaults); 6715 this.initPicker(options, DateTimeField.defaults); 6716 }; 6717 6718 $.fn.editableutils.inherit(DateTimeField, $.fn.editabletypes.datetime); 6719 6720 $.extend(DateTimeField.prototype, { 6721 render: function () { 6722 this.$input = this.$tpl.find('input'); 6723 this.setClass(); 6724 this.setAttr('placeholder'); 6725 6726 this.$tpl.datetimepicker(this.options.datetimepicker); 6727 6728 //need to disable original event handlers 6729 this.$input.off('focus keydown'); 6730 6731 //update value of datepicker 6732 this.$input.keyup($.proxy(function(){ 6733 this.$tpl.removeData('date'); 6734 this.$tpl.datetimepicker('update'); 6735 }, this)); 6736 6737 }, 6738 6739 value2input: function(value) { 6740 this.$input.val(this.value2html(value)); 6741 this.$tpl.datetimepicker('update'); 6742 }, 6743 6744 input2value: function() { 6745 return this.html2value(this.$input.val()); 6746 }, 6747 6748 activate: function() { 6749 $.fn.editabletypes.text.prototype.activate.call(this); 6750 }, 6751 6752 autosubmit: function() { 6753 //reset autosubmit to empty 6754 } 6755 }); 6756 6757 DateTimeField.defaults = $.extend({}, $.fn.editabletypes.datetime.defaults, { 6758 /** 6759 @property tpl 6760 **/ 6761 tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>', 6762 /** 6763 @property inputclass 6764 @default 'input-medium' 6765 **/ 6766 inputclass: 'input-medium', 6767 6768 /* datetimepicker config */ 6769 datetimepicker:{ 6770 todayHighlight: false, 6771 autoclose: true 6772 } 6773 }); 6774 6775 $.fn.editabletypes.datetimefield = DateTimeField; 6776 6777}(window.jQuery)); 6778AUI.add( 6779 6780 'com-placecube-digitalplace-communities-utilities', 6781 6782 function(A) { 6783 6784 var DPCUtilities = { 6785 6786 scrollToSection: function(cssSelector, offset = 20) { 6787 $('html,body').animate({ 6788 'scrollTop': $(cssSelector).offset().top - offset 6789 }, 500); 6790 } 6791 }; 6792 6793 A.DPCUtilities = DPCUtilities; 6794 }, 6795 '', 6796 { 6797 requires: [ 6798 'aui-base', 6799 'aui-node' 6800 ] 6801 } 6802); 6803var userId = Liferay.ThemeDisplay.getUserId(); 6804var companyId = Liferay.ThemeDisplay.getCompanyId(); 6805 6806var adminRoleName = "Administrator"; 6807window.onload = function () { 6808 if (document.body.classList.contains("dialog-iframe-popup") || 6809 (document.body.classList.contains("has-control-menu") && !document.body.classList.contains("non-company-admin") && !document.body.classList.contains("company-admin")) 6810 ) { 6811 Liferay.Service( 6812 '/role/has-user-role', 6813 {
6814 userId: userId, 6815 companyId: companyId, 6816 name: adminRoleName, 6817 inherited: false 6818 }, 6819 function (obj) { 6820 if (obj) { 6821 document.body.classList.add("company-admin"); 6822 } else { 6823 document.body.classList.add("non-company-admin"); 6824 } 6825 document.body.onload = function () { 6826 if (obj) { 6827 document.body.classList.add("company-admin"); 6828 } else { 6829 document.body.classList.add("non-company-admin"); 6830 } 6831 } 6832 } 6833 ); 6834 } 6835} 6836AUI.add( 6837 'com-placecube-digitalplace-communities-text-toggle', 6838 A => { 6839 const MessageToggle = function (parentDiv, baseSelector, seeMoreSelector, seeLessSelector) { 6840 const instance = this; 6841 6842 instance.parentDiv = parentDiv; 6843 6844 const toggleTruncate = function (button, showFull) { 6845 const buttonsParent = button.ancestor(baseSelector) 6846 if (showFull) { 6847 buttonsParent.one(baseSelector + "-truncated").replaceClass('', 'd-none') 6848 buttonsParent.one(baseSelector + "-full").replaceClass('d-none', '') 6849 } else { 6850 buttonsParent.one(baseSelector + "-truncated").replaceClass('d-none', '') 6851 buttonsParent.one(baseSelector + "-full").replaceClass('', 'd-none') 6852 } 6853 } 6854 6855 instance.showMoreButtons = instance.parentDiv.all(seeMoreSelector) 6856 instance.showMoreButtons.each(function () { 6857 this.on('click', event => { 6858 toggleTruncate(event.target, true) 6859 }) 6860 }) 6861 6862 instance.showLessButtons = instance.parentDiv.all(seeLessSelector) 6863 instance.showLessButtons.each(function () { 6864 this.on('click', event => { 6865 toggleTruncate(event.target, false) 6866 }) 6867 }) 6868 }; 6869 6870 Liferay.namespace('DPC').MessageToggle = MessageToggle; 6871 }, 6872 '', 6873 { 6874 requires: [ 6875 'aui-io-request' 6876 ] 6877 } 6878); 6879AUI.add( 6880 6881 'com-placecube-digitalplace-communities-editable-fields', 6882 6883 function(A) { 6884 6885 var EditableFields = { 6886 6887 initEditableFields: function(portletSelector, portletId, resourceId, mode) { 6888 6889 var resourceURL = Liferay.PortletURL.createResourceURL(); 6890 resourceURL.setParameter("p_p_id", portletId) 6891 resourceURL.setResourceId(resourceId); 6892 6893 var baseFieldConfig = { 6894 pk: Liferay.ThemeDisplay.getUserId(), 6895 url: resourceURL.toString(), 6896 params: function(params) { 6897 params.fieldType = $(this).editable().data('fieldtype'); 6898 params.required = $(this).editable().data('required'); 6899 params.fieldName = $(this).editable().data('fieldname'); 6900 return params; 6901 }, 6902 success: function(response) { 6903 if (response) { 6904 return response; 6905 } 6906 }, 6907 error: function(response) { 6908 if (response.responseText) { 6909 return response.responseText; 6910 } else { 6911 return 'Your\x20request\x20failed\x20to\x20complete\x2e'; 6912 } 6913 } 6914 } 6915 6916 var tagFieldConfig = $.extend({}, baseFieldConfig, { 6917 inputclass: 'input-large', 6918 select2: { 6919 tags: true, 6920 formatResult: function() { 6921 // disable formatting 6922 } 6923 } 6924 }); 6925 6926 $.fn.editable.defaults.mode = mode; 6927 6928 // X-editable init event handling 6929 $(portletSelector +' .editable-field').on('init', function(e) { 6930 var fieldLink = $(e.target); 6931 var fieldContainer = fieldLink.closest('.editable-fields'); 6932 var fieldEmptyText = fieldLink.attr("data-emptytext") 6933 if (!fieldLink.text() || fieldLink.text() === fieldEmptyText) { 6934 fieldContainer.addClass("empty-field"); 6935 } else { 6936 fieldContainer.removeClass("empty-field"); 6937 } 6938 }); 6939 6940 6941 // X-editable / Select2 init event handling 6942 $(portletSelector +' .editable-tag-field').editable(tagFieldConfig); 6943 6944 // X-editable update event handling 6945 $(portletSelector +' .editable-field').editable(baseFieldConfig); 6946 6947 // X-editable post save event handling 6948 $(portletSelector +' .editable-field').on('save', function(e, params) { 6949 var fieldContainer = $(e.target).closest('.editable-fields'); 6950 6951 if (!params.newValue && !fieldContainer.hasClass("empty-field")) { 6952 fieldContainer.addClass("empty-field"); 6953 } else if (params.newValue && fieldContainer.hasClass("empty-field")) { 6954 fieldContainer.removeClass("empty-field"); 6955 } 6956 }); 6957 } 6958 }; 6959 6960 A.EditableFields = EditableFields; 6961 }, 6962 '', 6963 { 6964 requires: [ 6965 'aui-base', 6966 'liferay-portlet-url', 6967 'aui-node' 6968 ] 6969 } 6970); 6971;(function () { 6972
6973 Liferay.provide(window, 'updateFieldFacetSelection', function (namespace, element, baseRemoveFilterUrl, baseUpdateFilterSelectionUrl, redirectUrl) { 6974 var A = AUI(); 6975 6976 var id = A.one(element).get('id'); 6977 var oldField = A.one("#" + id + "_old"); 6978 6979 var optionSelectedValue = A.one(element).val(); 6980 6981 var selectedElement = A.one(element).one("option[value=" + optionSelectedValue + "]"); 6982 var value = selectedElement.get('value'); 6983 var label = selectedElement.get('label'); 6984 6985 var updateFilterSelectionUrl = baseUpdateFilterSelectionUrl + "&" + namespace + "filterValue=" + value + "&" + namespace + "filterLabel=" + label; 6986 var removeFilterUrl = baseRemoveFilterUrl + "&" + namespace + "filterValue=" + oldField.val(); 6987 6988 $.ajax({ 6989 type: "POST", 6990 url: removeFilterUrl, 6991 success : function(data) { 6992 $.ajax({ 6993 type: "POST", 6994 url: updateFilterSelectionUrl, 6995 success : function(data) { 6996 oldField.val(optionSelectedValue); 6997 window.location.href = redirectUrl; 6998 }, 6999 error: function(jqXHR, textStatus, errorThrown){ 7000 console.info("Update filter selection call failed: " + textStatus); 7001 } 7002 }); 7003 }, 7004 error: function(jqXHR, textStatus, errorThrown){ 7005 console.info("Clear all filters call failed: " + textStatus); 7006 } 7007 7008 }); 7009 }); 7010 7011})() 7012AUI.add( 7013 'com-placecube-ddl-editor-autofill', 7014 function (A) { 7015 var DDLEditorAutofill = { 7016 init: function (ddlAutofillFields, namespace) { 7017 const fieldsToAutofill = JSON.parse(ddlAutofillFields); 7018 fieldsToAutofill.forEach((item) => { 7019 const fieldName = item.fieldName; 7020 const inputElement = document.querySelector('input[id*="'+namespace+fieldName+'"]'); 7021 if (inputElement) { 7022 $('div[data-fieldname="'+fieldName+'"]').hide(); 7023 inputElement.value = item.fieldValue; 7024 } 7025 }); 7026 } 7027 }; 7028 A.DDLEditorAutofill = DDLEditorAutofill; 7029 }, 7030 '', { 7031 requires: [ 7032 'aui-base' 7033 ] 7034}); 7035AUI.add( 7036 'com-placecube-field-filter-builder', 7037 function (A) { 7038 7039 var groupTemplate; 7040 7041 var optionTemplate; 7042 7043 var hiddenFormField; 7044 7045 var ns; 7046 7047 var FieldFilterBuilder = { 7048 7049 init: function (params) { 7050 groupTemplate = params.groupTemplate; 7051 optionTemplate = params.optionTemplate; 7052 hiddenFormField = params.hiddenFormField; 7053 ns = params.ns; 7054 7055 FieldFilterBuilder.buildForm(params.jsonFilterConfig); 7056 7057 $('#' + ns + 'addFilterGroupBtn').click(function (e) { 7058 FieldFilterBuilder.createFilterGroup(); 7059 }); 7060 7061 $('#' + ns + 'filterGroups').on("click", "button.add-filter", function (event) { 7062 FieldFilterBuilder.createFilterFromClick(event.currentTarget); 7063 }); 7064 7065 $('#' + ns + 'filterGroups').on("click", "button.add-option", function (event) { 7066 FieldFilterBuilder.createFilterOptionFromClick(event.currentTarget); 7067 }); 7068 7069 $('#' + ns + 'filterGroups').on("click", "a.remove-group", function (event) { 7070 FieldFilterBuilder.removeItem(event, event.currentTarget, '.filter-group'); 7071 }); 7072 7073 $('#' + ns + 'filterGroups').on("click", "a.remove-option", function (event) { 7074 FieldFilterBuilder.removeItem(event, event.currentTarget, '.filter-option'); 7075 }); 7076 7077 $('#' + ns + 'filterGroups').on("click", "a.remove-filter", function (event) { 7078 FieldFilterBuilder.removeItem(event, event.currentTarget, '.field-filter'); 7079 }); 7080 7081 $('#' + ns + 'filterGroups').on("change", ".filter-field-selector", function (event) { 7082 FieldFilterBuilder.updateAvailableInputs(event.currentTarget); 7083 }); 7084 }, 7085 7086 createFilterGroup: function () { 7087 7088 var templateHtml = groupTemplate.html().trim(); 7089 7090 var newFilterGroup = document.createElement('div'); 7091 $(newFilterGroup).addClass('filter-group'); 7092 newFilterGroup.innerHTML = templateHtml; 7093 7094 var filterGroups = document.getElementById(ns + 'filterGroups'); 7095 filterGroups.appendChild(newFilterGroup); 7096 return newFilterGroup; 7097 }, 7098 7099 createFilterOptionFromClick: function (element) { 7100 var filterValuesDiv = $(element).siblings('.filter-options').first().get(0); 7101 return FieldFilterBuilder.createFilterOption(filterValuesDiv); 7102 }, 7103 7104 createFilterOption: function (filterValuesDiv) { 7105 7106 var templateHtml = optionTemplate.html().trim(); 7107 7108 var newFilterOption = document.createElement('div'); 7109 $(newFilterOption).addClass('filter-option'); 7110 newFilterOption.innerHTML = templateHtml; 7111 7112 filterValuesDiv.appendChild(newFilterOption); 7113 return newFilterOption; 7114 7115 }, 7116 7117 createFilterFromClick: function (element) { 7118 7119 var filterValuesDiv = $(element).siblings('.filters').first().get(0); 7120 7121 return FieldFilterBuilder.createFilter(filterValuesDiv); 7122 }, 7123 7124 createFilter: function (filterValuesDiv) { 7125 7126 var templateHtml = $('#' + ns + 'field-filter-template').html().trim(); 7127 7128 var newFilterValue = document.createElement('div'); 7129 $(newFilterValue).addClass('field-filter'); 7130 newFilterValue.innerHTML = templateHtml; 7131 7132 filterValuesDiv.appendChild(newFilterValue); 7133 return newFilterValue; 7134 }, 7135 7136 formToJson: function () { 7137 7138 var groups = []; 7139 7140 $('.filter-group').each(function () { 7141 var group = {}; 7142 group.filterGroupName = $(this).find('[name="' + ns + 'filterGroupName"]').val(); 7143 7144 if (!group.filterGroupName) { 7145 return; 7146 } 7147 7148 group.filterPreselectedOptionName = $(this).find('[name="' + ns + 'filterPreselectedOptionName"]').val(); 7149 7150 var options = []; 7151 group.options = options; 7152 groups.push(group); 7153 $(this).find('.filter-option').each(function () { 7154 7155 var option = {}; 7156 var filters = []; 7157 option.filters = filters; 7158 7159 option.filterOptionName = $(this).find('[name="' + ns + 'filterOptionName"]').val(); 7160 if (!option.filterOptionName) { 7161 return; 7162 } 7163 7164 $(this).find('.field-filter').each(function () { 7165 var filter = {}; 7166 7167 filter.fieldName = $(this).find('[name="' + ns + 'fieldName"]').val(); 7168 filter.filterOperator = $(this).find('[name="' + ns + 'filterOperator"]').val(); 7169 filter.fieldValue = $(this).find('[name="' + ns + 'fieldValue"]:not(:disabled)').val(); 7170 filter.numDays = $(this).find('[name="' + ns + 'numDays"]').val(); 7171 filter.fieldType = $(this).find('[name="' + ns + 'fieldType"]').val(); 7172 7173 if (filter.fieldName && filter.filterOperator) { 7174 filters.push(filter); 7175 } 7176 }); 7177 7178 if (filters.length > 0) { 7179 options.push(option); 7180 } 7181 7182 }); 7183 }); 7184 7185 7186 hiddenFormField.val(JSON.stringify(groups)); 7187 }, 7188 7189 removeItem: function (event, element, selector) { 7190 event.preventDefault(); 7191 var nodeToRemove = $(element).parents(selector).first().get(0); 7192 nodeToRemove.parentNode.removeChild(nodeToRemove); 7193 }, 7194 7195 updateAvailableInputs: function (target) { 7196 var optionSelectedValue = $(target).val(); 7197 7198 if (optionSelectedValue != "") { 7199 var selectedElement = $(target).find("option[value=" + optionSelectedValue + "]"); 7200 var tableRow = $(target).closest("tr"); 7201 7202 var type = selectedElement.attr('data-type'); 7203 7204 tableRow.find("input.field-type").val(type); 7205 7206 FieldFilterBuilder.showAndHideFilterFields(tableRow, type); 7207 } 7208 }, 7209 7210 showAndHideFilterFields: function (filterElement, type) { 7211 if (type == "date") { 7212 filterElement.find(".date-field-wrapper").show(); 7213 filterElement.find(".date-field-wrapper input").prop('disabled', false); 7214 filterElement.find(".num-days-cell").show(); 7215 filterElement.find(".number-field-wrapper").hide(); 7216 filterElement.find(".number-field-wrapper input").prop('disabled', true); 7217 } 7218 else { 7219 filterElement.find(".number-field-wrapper").show(); 7220 filterElement.find(".number-field-wrapper input").prop('disabled', false); 7221 filterElement.find(".date-field-wrapper").hide(); 7222 filterElement.find(".date-field-wrapper input").prop('disabled', true); 7223 filterElement.find(".num-days-cell").hide(); 7224 } 7225 }, 7226 7227 buildForm: function (jsonConfigData) { 7228 7229 var existingFilterGroups = JSON.parse(jsonConfigData); 7230 7231 for (var i = 0; i < existingFilterGroups.length; i++) { 7232 var filterGroup = existingFilterGroups[i]; 7233 var options = filterGroup.options; 7234 7235 var filterGroupElement = FieldFilterBuilder.createFilterGroup(); 7236 $(filterGroupElement).find('[name="' + ns + 'filterGroupName"]').val(filterGroup.filterGroupName); 7237 $(filterGroupElement).find('[name="' + ns + 'filterPreselectedOptionName"]').val(filterGroup.filterPreselectedOptionName); 7238 7239 for (var x = 0; x < options.length; x++) { 7240 var option = options[x]; 7241 var filterOptionElement = FieldFilterBuilder.createFilterOption($(filterGroupElement).find('.filter-options').get(0)); 7242 $(filterOptionElement).find('[name="' + ns + 'filterOptionName"]').val(option.filterOptionName); 7243 7244 var filters = option.filters; 7245 var filtersElement = ($(filterOptionElement).find('.filters').get(0)); 7246 for (var y = 0; y < filters.length; y++) {
7247 var filter = filters[y]; 7248 var filterElement = FieldFilterBuilder.createFilter(filtersElement); 7249 $(filterElement).find('[name="' + ns + 'fieldName"]').val(filter.fieldName); 7250 $(filterElement).find('[name="' + ns + 'filterOperator"]').val(filter.filterOperator); 7251 $(filterElement).find('[name="' + ns + 'fieldValue"]').val(filter.fieldValue); 7252 $(filterElement).find('[name="' + ns + 'numDays"]').val(filter.numDays); 7253 $(filterElement).find('[name="' + ns + 'fieldType"]').val(filter.fieldType); 7254 7255 FieldFilterBuilder.showAndHideFilterFields($(filterElement), filter.fieldType); 7256 } 7257 7258 } 7259 7260 } 7261 } 7262 }; 7263 7264 A.FieldFilterBuilder = FieldFilterBuilder; 7265 7266 }, 7267 '', { 7268 requires: [ 7269 'aui-base' 7270 ] 7271}); 7272var namespace; 7273 7274function loadScript(GOOGLE_API_KEY, portletNamespace) { 7275 namespace = portletNamespace; 7276 if (!(window.google && window.google.maps)) { 7277 var script = document.createElement('script'); 7278 script.type = 'text/javascript'; 7279 script.src = 'https://maps.googleapis.com/maps/api/js?key=' + GOOGLE_API_KEY + '&callback=initAutocomplete&libraries=places&v=weekly'; //& needed 7280 document.body.appendChild(script); 7281 } else { 7282 initAutocomplete(); 7283 } 7284 7285} 7286 7287function initAutocomplete() { 7288 var autocomplete = new google.maps.places.Autocomplete(document.querySelector('#' + namespace + 'autocomplete'), { 7289 componentRestrictions: { country: ['uk'] }, 7290 fields: ['formatted_address', 'geometry', 'name'], 7291 types: ['geocode'], 7292 }); 7293 autocomplete.addListener("place_changed", () => { 7294 var place = autocomplete.getPlace(); 7295 if (!place.geometry || !place.geometry.location) { 7296 return; 7297 } 7298 if (place.geometry.location) { 7299 document.getElementById(namespace + "latitude").value = place.geometry.location.lat(); 7300 document.getElementById(namespace + "longitude").value = place.geometry.location.lng(); 7301 document.getElementById(namespace + "formattedAddress").value = place.formatted_address; 7302 document.getElementById(namespace + "location-search-form").submit(); 7303 } 7304 7305 }); 7306} 7307 7308function clearCurrentLocation() { 7309 document.getElementById(namespace + "formattedAddress").value = ''; 7310 document.getElementById(namespace + "location-search-form").submit(); 7311} 7312 7313function getCurrentLocation() { 7314 if (navigator.geolocation) { 7315 navigator.geolocation.getCurrentPosition(showPosition); 7316 } else { 7317 window.alert("Geolocation is not supported by this browser."); 7318 } 7319} 7320 7321function showPosition(position) { 7322 document.getElementById(namespace + "latitude").value = position.coords.latitude; 7323 document.getElementById(namespace + "longitude").value = position.coords.longitude; 7324 document.getElementById(namespace + "formattedAddress").value = "Current location"; 7325 document.getElementById(namespace + "location-search-form").submit(); 7326} 7327AUI.add( 7328 7329 'com-placecube-digitalplace-communities-group-taglib', 7330 7331 function(A) { 7332 7333 var GroupTaglib = { 7334 7335 openViewInModal: function (params){ 7336 var selector = params.selector; 7337 var dialogId = params.dialogId; 7338 var dialogTitle = params.dialogTitle; 7339 var renderURL = params.renderURL; 7340 7341 $(document).on('click', selector, function(e){ 7342 var modalDialogId = dialogId+'_Dialog'; 7343 Liferay.Util.openWindow( 7344 { 7345 dialog: { 7346 cache: true, 7347 centered: true, 7348 constrain: true, 7349 cssClass: 'group-taglib', 7350 destroyOnHide: true, 7351 plugins: [Liferay.WidgetZIndex], 7352 after: { 7353 destroy: () => { 7354 window.location.reload(); 7355 } 7356 }, 7357 }, 7358 dialogIframe: { 7359 bodyCssClass: 'group-taglib' 7360 }, 7361 id: modalDialogId, 7362 title: dialogTitle, 7363 uri: renderURL 7364 } 7365 ); 7366 }); 7367 }, 7368 7369 closeModalAndRedirectToPage: function(params){ 7370 var redirectURL = params.redirectURL; 7371 7372 window.onload = function() { 7373 setTimeout(function() { 7374 parent.window.location.href=redirectURL; 7375 }, 4000); 7376 }; 7377 } 7378 }; 7379 7380 A.GroupTaglib = GroupTaglib; 7381 }, 7382 '', 7383 { 7384 requires: [ 7385 'aui-base', 7386 'liferay-portlet-url', 7387 'liferay-util-window' 7388 ] 7389 } 7390); 7391 7392
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.