bootstrap-x-editable-rails 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/README.md +33 -0
- data/X-EDITABLE-LICENSE-MIT +22 -0
- data/bootstrap-x-editable-rails.gemspec +19 -0
- data/lib/bootstrap-x-editable-rails.rb +12 -0
- data/lib/bootstrap-x-editable-rails/engine.rb +6 -0
- data/lib/bootstrap-x-editable-rails/railtie.rb +5 -0
- data/lib/bootstrap-x-editable-rails/version.rb +5 -0
- data/vendor/assets/images/loading.gif +0 -0
- data/vendor/assets/javascripts/bootstrap-editable-inline.js +3769 -0
- data/vendor/assets/javascripts/bootstrap-editable.js +3801 -0
- data/vendor/assets/stylesheets/bootstrap-editable.scss +434 -0
- metadata +106 -0
@@ -0,0 +1,3801 @@
|
|
1
|
+
/*! X-editable - v1.3.0
|
2
|
+
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
|
3
|
+
* http://github.com/vitalets/x-editable
|
4
|
+
* Copyright (c) 2012 Vitaliy Potapov; Licensed MIT */
|
5
|
+
|
6
|
+
/**
|
7
|
+
Form with single input element, two buttons and two states: normal/loading.
|
8
|
+
Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown.
|
9
|
+
Editableform is linked with one of input types, e.g. 'text', 'select' etc.
|
10
|
+
|
11
|
+
@class editableform
|
12
|
+
@uses text
|
13
|
+
@uses textarea
|
14
|
+
**/
|
15
|
+
(function ($) {
|
16
|
+
|
17
|
+
var EditableForm = function (div, options) {
|
18
|
+
this.options = $.extend({}, $.fn.editableform.defaults, options);
|
19
|
+
this.$div = $(div); //div, containing form. Not form tag! Not editable-element.
|
20
|
+
if(!this.options.scope) {
|
21
|
+
this.options.scope = this;
|
22
|
+
}
|
23
|
+
this.initInput();
|
24
|
+
};
|
25
|
+
|
26
|
+
EditableForm.prototype = {
|
27
|
+
constructor: EditableForm,
|
28
|
+
initInput: function() { //called once
|
29
|
+
var TypeConstructor, typeOptions;
|
30
|
+
|
31
|
+
//create input of specified type
|
32
|
+
if(typeof $.fn.editabletypes[this.options.type] === 'function') {
|
33
|
+
TypeConstructor = $.fn.editabletypes[this.options.type];
|
34
|
+
typeOptions = $.fn.editableutils.sliceObj(this.options, $.fn.editableutils.objectKeys(TypeConstructor.defaults));
|
35
|
+
this.input = new TypeConstructor(typeOptions);
|
36
|
+
} else {
|
37
|
+
$.error('Unknown type: '+ this.options.type);
|
38
|
+
return;
|
39
|
+
}
|
40
|
+
|
41
|
+
this.value = this.input.str2value(this.options.value);
|
42
|
+
},
|
43
|
+
initTemplate: function() {
|
44
|
+
this.$form = $($.fn.editableform.template);
|
45
|
+
},
|
46
|
+
initButtons: function() {
|
47
|
+
this.$form.find('.editable-buttons').append($.fn.editableform.buttons);
|
48
|
+
},
|
49
|
+
/**
|
50
|
+
Renders editableform
|
51
|
+
|
52
|
+
@method render
|
53
|
+
**/
|
54
|
+
render: function() {
|
55
|
+
this.$loading = $($.fn.editableform.loading);
|
56
|
+
this.$div.empty().append(this.$loading);
|
57
|
+
this.showLoading();
|
58
|
+
|
59
|
+
//init form template and buttons
|
60
|
+
this.initTemplate();
|
61
|
+
if(this.options.showbuttons) {
|
62
|
+
this.initButtons();
|
63
|
+
} else {
|
64
|
+
this.$form.find('.editable-buttons').remove();
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
Fired when rendering starts
|
69
|
+
@event rendering
|
70
|
+
@param {Object} event event object
|
71
|
+
**/
|
72
|
+
this.$div.triggerHandler('rendering');
|
73
|
+
|
74
|
+
//render input
|
75
|
+
$.when(this.input.render())
|
76
|
+
.then($.proxy(function () {
|
77
|
+
//input
|
78
|
+
this.$form.find('div.editable-input').append(this.input.$input);
|
79
|
+
|
80
|
+
//automatically submit inputs when no buttons shown
|
81
|
+
if(!this.options.showbuttons) {
|
82
|
+
this.input.autosubmit();
|
83
|
+
}
|
84
|
+
|
85
|
+
//"clear" link
|
86
|
+
if(this.input.$clear) {
|
87
|
+
this.$form.find('div.editable-input').append($('<div class="editable-clear">').append(this.input.$clear));
|
88
|
+
}
|
89
|
+
|
90
|
+
//append form to container
|
91
|
+
this.$div.append(this.$form);
|
92
|
+
|
93
|
+
//attach 'cancel' handler
|
94
|
+
this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
|
95
|
+
|
96
|
+
if(this.input.error) {
|
97
|
+
this.error(this.input.error);
|
98
|
+
this.$form.find('.editable-submit').attr('disabled', true);
|
99
|
+
this.input.$input.attr('disabled', true);
|
100
|
+
//prevent form from submitting
|
101
|
+
this.$form.submit(function(e){ e.preventDefault(); });
|
102
|
+
} else {
|
103
|
+
this.error(false);
|
104
|
+
this.input.$input.removeAttr('disabled');
|
105
|
+
this.$form.find('.editable-submit').removeAttr('disabled');
|
106
|
+
this.input.value2input(this.value);
|
107
|
+
//attach submit handler
|
108
|
+
this.$form.submit($.proxy(this.submit, this));
|
109
|
+
}
|
110
|
+
|
111
|
+
/**
|
112
|
+
Fired when form is rendered
|
113
|
+
@event rendered
|
114
|
+
@param {Object} event event object
|
115
|
+
**/
|
116
|
+
this.$div.triggerHandler('rendered');
|
117
|
+
|
118
|
+
this.showForm();
|
119
|
+
}, this));
|
120
|
+
},
|
121
|
+
cancel: function() {
|
122
|
+
/**
|
123
|
+
Fired when form was cancelled by user
|
124
|
+
@event cancel
|
125
|
+
@param {Object} event event object
|
126
|
+
**/
|
127
|
+
this.$div.triggerHandler('cancel');
|
128
|
+
},
|
129
|
+
showLoading: function() {
|
130
|
+
var w;
|
131
|
+
if(this.$form) {
|
132
|
+
//set loading size equal to form
|
133
|
+
this.$loading.width(this.$form.outerWidth());
|
134
|
+
this.$loading.height(this.$form.outerHeight());
|
135
|
+
this.$form.hide();
|
136
|
+
} else {
|
137
|
+
//stretch loading to fill container width
|
138
|
+
w = this.$loading.parent().width();
|
139
|
+
if(w) {
|
140
|
+
this.$loading.width(w);
|
141
|
+
}
|
142
|
+
}
|
143
|
+
this.$loading.show();
|
144
|
+
},
|
145
|
+
|
146
|
+
showForm: function(activate) {
|
147
|
+
this.$loading.hide();
|
148
|
+
this.$form.show();
|
149
|
+
if(activate !== false) {
|
150
|
+
this.input.activate();
|
151
|
+
}
|
152
|
+
/**
|
153
|
+
Fired when form is shown
|
154
|
+
@event show
|
155
|
+
@param {Object} event event object
|
156
|
+
**/
|
157
|
+
this.$div.triggerHandler('show');
|
158
|
+
},
|
159
|
+
|
160
|
+
error: function(msg) {
|
161
|
+
var $group = this.$form.find('.control-group'),
|
162
|
+
$block = this.$form.find('.editable-error-block');
|
163
|
+
|
164
|
+
if(msg === false) {
|
165
|
+
$group.removeClass($.fn.editableform.errorGroupClass);
|
166
|
+
$block.removeClass($.fn.editableform.errorBlockClass).empty().hide();
|
167
|
+
} else {
|
168
|
+
$group.addClass($.fn.editableform.errorGroupClass);
|
169
|
+
$block.addClass($.fn.editableform.errorBlockClass).text(msg).show();
|
170
|
+
}
|
171
|
+
},
|
172
|
+
|
173
|
+
submit: function(e) {
|
174
|
+
e.stopPropagation();
|
175
|
+
e.preventDefault();
|
176
|
+
|
177
|
+
var error,
|
178
|
+
newValue = this.input.input2value(); //get new value from input
|
179
|
+
|
180
|
+
//validation
|
181
|
+
if (error = this.validate(newValue)) {
|
182
|
+
this.error(error);
|
183
|
+
this.showForm();
|
184
|
+
return;
|
185
|
+
}
|
186
|
+
|
187
|
+
//if value not changed --> trigger 'nochange' event and return
|
188
|
+
/*jslint eqeq: true*/
|
189
|
+
if (!this.options.savenochange && this.input.value2str(newValue) == this.input.value2str(this.value)) {
|
190
|
+
/*jslint eqeq: false*/
|
191
|
+
/**
|
192
|
+
Fired when value not changed but form is submitted. Requires savenochange = false.
|
193
|
+
@event nochange
|
194
|
+
@param {Object} event event object
|
195
|
+
**/
|
196
|
+
this.$div.triggerHandler('nochange');
|
197
|
+
return;
|
198
|
+
}
|
199
|
+
|
200
|
+
//sending data to server
|
201
|
+
$.when(this.save(newValue))
|
202
|
+
.done($.proxy(function(response) {
|
203
|
+
//run success callback
|
204
|
+
var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
|
205
|
+
|
206
|
+
//if success callback returns false --> keep form open and do not activate input
|
207
|
+
if(res === false) {
|
208
|
+
this.error(false);
|
209
|
+
this.showForm(false);
|
210
|
+
return;
|
211
|
+
}
|
212
|
+
|
213
|
+
//if success callback returns string --> keep form open, show error and activate input
|
214
|
+
if(typeof res === 'string') {
|
215
|
+
this.error(res);
|
216
|
+
this.showForm();
|
217
|
+
return;
|
218
|
+
}
|
219
|
+
|
220
|
+
//if success callback returns object like {newValue: <something>} --> use that value instead of submitted
|
221
|
+
if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
|
222
|
+
newValue = res.newValue;
|
223
|
+
}
|
224
|
+
|
225
|
+
//clear error message
|
226
|
+
this.error(false);
|
227
|
+
this.value = newValue;
|
228
|
+
/**
|
229
|
+
Fired when form is submitted
|
230
|
+
@event save
|
231
|
+
@param {Object} event event object
|
232
|
+
@param {Object} params additional params
|
233
|
+
@param {mixed} params.newValue submitted value
|
234
|
+
@param {Object} params.response ajax response
|
235
|
+
|
236
|
+
@example
|
237
|
+
$('#form-div').on('save'), function(e, params){
|
238
|
+
if(params.newValue === 'username') {...}
|
239
|
+
});
|
240
|
+
**/
|
241
|
+
this.$div.triggerHandler('save', {newValue: newValue, response: response});
|
242
|
+
}, this))
|
243
|
+
.fail($.proxy(function(xhr) {
|
244
|
+
this.error(typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!');
|
245
|
+
this.showForm();
|
246
|
+
}, this));
|
247
|
+
},
|
248
|
+
|
249
|
+
save: function(newValue) {
|
250
|
+
//convert value for submitting to server
|
251
|
+
var submitValue = this.input.value2submit(newValue);
|
252
|
+
|
253
|
+
//try parse composite pk defined as json string in data-pk
|
254
|
+
this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true);
|
255
|
+
|
256
|
+
var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
|
257
|
+
send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk)))),
|
258
|
+
params;
|
259
|
+
|
260
|
+
if (send) { //send to server
|
261
|
+
this.showLoading();
|
262
|
+
|
263
|
+
//standard params
|
264
|
+
params = {
|
265
|
+
name: this.options.name || '',
|
266
|
+
value: submitValue,
|
267
|
+
pk: pk
|
268
|
+
};
|
269
|
+
|
270
|
+
//additional params
|
271
|
+
if(typeof this.options.params === 'function') {
|
272
|
+
params = this.options.params.call(this.options.scope, params);
|
273
|
+
} else {
|
274
|
+
//try parse json in single quotes (from data-params attribute)
|
275
|
+
this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true);
|
276
|
+
$.extend(params, this.options.params);
|
277
|
+
}
|
278
|
+
|
279
|
+
if(typeof this.options.url === 'function') { //user's function
|
280
|
+
return this.options.url.call(this.options.scope, params);
|
281
|
+
} else {
|
282
|
+
//send ajax to server and return deferred object
|
283
|
+
return $.ajax($.extend({
|
284
|
+
url : this.options.url,
|
285
|
+
data : params,
|
286
|
+
type : 'POST'
|
287
|
+
}, this.options.ajaxOptions));
|
288
|
+
}
|
289
|
+
}
|
290
|
+
},
|
291
|
+
|
292
|
+
validate: function (value) {
|
293
|
+
if (value === undefined) {
|
294
|
+
value = this.value;
|
295
|
+
}
|
296
|
+
if (typeof this.options.validate === 'function') {
|
297
|
+
return this.options.validate.call(this.options.scope, value);
|
298
|
+
}
|
299
|
+
},
|
300
|
+
|
301
|
+
option: function(key, value) {
|
302
|
+
this.options[key] = value;
|
303
|
+
if(key === 'value') {
|
304
|
+
this.setValue(value);
|
305
|
+
}
|
306
|
+
},
|
307
|
+
|
308
|
+
setValue: function(value, convertStr) {
|
309
|
+
if(convertStr) {
|
310
|
+
this.value = this.input.str2value(value);
|
311
|
+
} else {
|
312
|
+
this.value = value;
|
313
|
+
}
|
314
|
+
}
|
315
|
+
};
|
316
|
+
|
317
|
+
/*
|
318
|
+
Initialize editableform. Applied to jQuery object.
|
319
|
+
|
320
|
+
@method $().editableform(options)
|
321
|
+
@params {Object} options
|
322
|
+
@example
|
323
|
+
var $form = $('<div>').editableform({
|
324
|
+
type: 'text',
|
325
|
+
name: 'username',
|
326
|
+
url: '/post',
|
327
|
+
value: 'vitaliy'
|
328
|
+
});
|
329
|
+
|
330
|
+
//to display form you should call 'render' method
|
331
|
+
$form.editableform('render');
|
332
|
+
*/
|
333
|
+
$.fn.editableform = function (option) {
|
334
|
+
var args = arguments;
|
335
|
+
return this.each(function () {
|
336
|
+
var $this = $(this),
|
337
|
+
data = $this.data('editableform'),
|
338
|
+
options = typeof option === 'object' && option;
|
339
|
+
if (!data) {
|
340
|
+
$this.data('editableform', (data = new EditableForm(this, options)));
|
341
|
+
}
|
342
|
+
|
343
|
+
if (typeof option === 'string') { //call method
|
344
|
+
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
345
|
+
}
|
346
|
+
});
|
347
|
+
};
|
348
|
+
|
349
|
+
//keep link to constructor to allow inheritance
|
350
|
+
$.fn.editableform.Constructor = EditableForm;
|
351
|
+
|
352
|
+
//defaults
|
353
|
+
$.fn.editableform.defaults = {
|
354
|
+
/* see also defaults for input */
|
355
|
+
|
356
|
+
/**
|
357
|
+
Type of input. Can be <code>text|textarea|select|date|checklist</code>
|
358
|
+
|
359
|
+
@property type
|
360
|
+
@type string
|
361
|
+
@default 'text'
|
362
|
+
**/
|
363
|
+
type: 'text',
|
364
|
+
/**
|
365
|
+
Url for submit, e.g. <code>'/post'</code>
|
366
|
+
If function - it will be called instead of ajax. Function can return deferred object to run fail/done callbacks.
|
367
|
+
|
368
|
+
@property url
|
369
|
+
@type string|function
|
370
|
+
@default null
|
371
|
+
@example
|
372
|
+
url: function(params) {
|
373
|
+
if(params.value === 'abc') {
|
374
|
+
var d = new $.Deferred;
|
375
|
+
return d.reject('field cannot be "abc"'); //returning error via deferred object
|
376
|
+
} else {
|
377
|
+
someModel.set(params.name, params.value); //save data in some js model
|
378
|
+
}
|
379
|
+
}
|
380
|
+
**/
|
381
|
+
url:null,
|
382
|
+
/**
|
383
|
+
Additional params for submit. If defined as <code>object</code> - it is **appended** to original ajax data (pk, name and value).
|
384
|
+
If defined as <code>function</code> - returned object **overwrites** original ajax data.
|
385
|
+
@example
|
386
|
+
params: function(params) {
|
387
|
+
//originally params contain pk, name and value
|
388
|
+
params.a = 1;
|
389
|
+
return params;
|
390
|
+
}
|
391
|
+
|
392
|
+
@property params
|
393
|
+
@type object|function
|
394
|
+
@default null
|
395
|
+
**/
|
396
|
+
params:null,
|
397
|
+
/**
|
398
|
+
Name of field. Will be submitted on server. Can be taken from <code>id</code> attribute
|
399
|
+
|
400
|
+
@property name
|
401
|
+
@type string
|
402
|
+
@default null
|
403
|
+
**/
|
404
|
+
name: null,
|
405
|
+
/**
|
406
|
+
Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>.
|
407
|
+
Can be calculated dynamically via function.
|
408
|
+
|
409
|
+
@property pk
|
410
|
+
@type string|object|function
|
411
|
+
@default null
|
412
|
+
**/
|
413
|
+
pk: null,
|
414
|
+
/**
|
415
|
+
Initial value. If not defined - will be taken from element's content.
|
416
|
+
For __select__ type should be defined (as it is ID of shown text).
|
417
|
+
|
418
|
+
@property value
|
419
|
+
@type string|object
|
420
|
+
@default null
|
421
|
+
**/
|
422
|
+
value: null,
|
423
|
+
/**
|
424
|
+
Strategy for sending data on server. Can be <code>auto|always|never</code>.
|
425
|
+
When 'auto' data will be sent on server only if pk defined, otherwise new value will be stored in element.
|
426
|
+
|
427
|
+
@property send
|
428
|
+
@type string
|
429
|
+
@default 'auto'
|
430
|
+
**/
|
431
|
+
send: 'auto',
|
432
|
+
/**
|
433
|
+
Function for client-side validation. If returns string - means validation not passed and string showed as error.
|
434
|
+
|
435
|
+
@property validate
|
436
|
+
@type function
|
437
|
+
@default null
|
438
|
+
@example
|
439
|
+
validate: function(value) {
|
440
|
+
if($.trim(value) == '') {
|
441
|
+
return 'This field is required';
|
442
|
+
}
|
443
|
+
}
|
444
|
+
**/
|
445
|
+
validate: null,
|
446
|
+
/**
|
447
|
+
Success callback. Called when value successfully sent on server and **response status = 200**.
|
448
|
+
Useful to work with json response. For example, if your backend response can be <code>{success: true}</code>
|
449
|
+
or <code>{success: false, msg: "server error"}</code> you can check it inside this callback.
|
450
|
+
If it returns **string** - means error occured and string is shown as error message.
|
451
|
+
If it returns **object like** <code>{newValue: <something>}</code> - it overwrites value, submitted by user.
|
452
|
+
Otherwise newValue simply rendered into element.
|
453
|
+
|
454
|
+
@property success
|
455
|
+
@type function
|
456
|
+
@default null
|
457
|
+
@example
|
458
|
+
success: function(response, newValue) {
|
459
|
+
if(!response.success) return response.msg;
|
460
|
+
}
|
461
|
+
**/
|
462
|
+
success: null,
|
463
|
+
/**
|
464
|
+
Additional options for ajax request.
|
465
|
+
List of values: http://api.jquery.com/jQuery.ajax
|
466
|
+
|
467
|
+
@property ajaxOptions
|
468
|
+
@type object
|
469
|
+
@default null
|
470
|
+
@since 1.1.1
|
471
|
+
**/
|
472
|
+
ajaxOptions: null,
|
473
|
+
/**
|
474
|
+
Whether to show buttons or not.
|
475
|
+
Form without buttons can be auto-submitted by input or by onblur = 'submit'.
|
476
|
+
@example
|
477
|
+
ajaxOptions: {
|
478
|
+
method: 'PUT',
|
479
|
+
dataType: 'xml'
|
480
|
+
}
|
481
|
+
|
482
|
+
@property showbuttons
|
483
|
+
@type boolean
|
484
|
+
@default true
|
485
|
+
@since 1.1.1
|
486
|
+
**/
|
487
|
+
showbuttons: true,
|
488
|
+
/**
|
489
|
+
Scope for callback methods (success, validate).
|
490
|
+
If <code>null</code> means editableform instance itself.
|
491
|
+
|
492
|
+
@property scope
|
493
|
+
@type DOMElement|object
|
494
|
+
@default null
|
495
|
+
@since 1.2.0
|
496
|
+
@private
|
497
|
+
**/
|
498
|
+
scope: null,
|
499
|
+
/**
|
500
|
+
Whether to save or cancel value when it was not changed but form was submitted
|
501
|
+
|
502
|
+
@property savenochange
|
503
|
+
@type boolean
|
504
|
+
@default false
|
505
|
+
@since 1.2.0
|
506
|
+
**/
|
507
|
+
savenochange: false
|
508
|
+
};
|
509
|
+
|
510
|
+
/*
|
511
|
+
Note: following params could redefined in engine: bootstrap or jqueryui:
|
512
|
+
Classes 'control-group' and 'editable-error-block' must always present!
|
513
|
+
*/
|
514
|
+
$.fn.editableform.template = '<form class="form-inline editableform">'+
|
515
|
+
'<div class="control-group">' +
|
516
|
+
'<div><div class="editable-input"></div><div class="editable-buttons"></div></div>'+
|
517
|
+
'<div class="editable-error-block"></div>' +
|
518
|
+
'</div>' +
|
519
|
+
'</form>';
|
520
|
+
|
521
|
+
//loading div
|
522
|
+
$.fn.editableform.loading = '<div class="editableform-loading"></div>';
|
523
|
+
|
524
|
+
//buttons
|
525
|
+
$.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+
|
526
|
+
'<button type="button" class="editable-cancel">cancel</button>';
|
527
|
+
|
528
|
+
//error class attached to control-group
|
529
|
+
$.fn.editableform.errorGroupClass = null;
|
530
|
+
|
531
|
+
//error class attached to editable-error-block
|
532
|
+
$.fn.editableform.errorBlockClass = 'editable-error';
|
533
|
+
}(window.jQuery));
|
534
|
+
/**
|
535
|
+
* EditableForm utilites
|
536
|
+
*/
|
537
|
+
(function ($) {
|
538
|
+
//utils
|
539
|
+
$.fn.editableutils = {
|
540
|
+
/**
|
541
|
+
* classic JS inheritance function
|
542
|
+
*/
|
543
|
+
inherit: function (Child, Parent) {
|
544
|
+
var F = function() { };
|
545
|
+
F.prototype = Parent.prototype;
|
546
|
+
Child.prototype = new F();
|
547
|
+
Child.prototype.constructor = Child;
|
548
|
+
Child.superclass = Parent.prototype;
|
549
|
+
},
|
550
|
+
|
551
|
+
/**
|
552
|
+
* set caret position in input
|
553
|
+
* see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
|
554
|
+
*/
|
555
|
+
setCursorPosition: function(elem, pos) {
|
556
|
+
if (elem.setSelectionRange) {
|
557
|
+
elem.setSelectionRange(pos, pos);
|
558
|
+
} else if (elem.createTextRange) {
|
559
|
+
var range = elem.createTextRange();
|
560
|
+
range.collapse(true);
|
561
|
+
range.moveEnd('character', pos);
|
562
|
+
range.moveStart('character', pos);
|
563
|
+
range.select();
|
564
|
+
}
|
565
|
+
},
|
566
|
+
|
567
|
+
/**
|
568
|
+
* function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)
|
569
|
+
* That allows such code as: <a data-source="{'a': 'b', 'c': 'd'}">
|
570
|
+
* safe = true --> means no exception will be thrown
|
571
|
+
* for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery
|
572
|
+
*/
|
573
|
+
tryParseJson: function(s, safe) {
|
574
|
+
if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {
|
575
|
+
if (safe) {
|
576
|
+
try {
|
577
|
+
/*jslint evil: true*/
|
578
|
+
s = (new Function('return ' + s))();
|
579
|
+
/*jslint evil: false*/
|
580
|
+
} catch (e) {} finally {
|
581
|
+
return s;
|
582
|
+
}
|
583
|
+
} else {
|
584
|
+
/*jslint evil: true*/
|
585
|
+
s = (new Function('return ' + s))();
|
586
|
+
/*jslint evil: false*/
|
587
|
+
}
|
588
|
+
}
|
589
|
+
return s;
|
590
|
+
},
|
591
|
+
|
592
|
+
/**
|
593
|
+
* slice object by specified keys
|
594
|
+
*/
|
595
|
+
sliceObj: function(obj, keys, caseSensitive /* default: false */) {
|
596
|
+
var key, keyLower, newObj = {};
|
597
|
+
|
598
|
+
if (!$.isArray(keys) || !keys.length) {
|
599
|
+
return newObj;
|
600
|
+
}
|
601
|
+
|
602
|
+
for (var i = 0; i < keys.length; i++) {
|
603
|
+
key = keys[i];
|
604
|
+
if (obj.hasOwnProperty(key)) {
|
605
|
+
newObj[key] = obj[key];
|
606
|
+
}
|
607
|
+
|
608
|
+
if(caseSensitive === true) {
|
609
|
+
continue;
|
610
|
+
}
|
611
|
+
|
612
|
+
//when getting data-* attributes via $.data() it's converted to lowercase.
|
613
|
+
//details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery
|
614
|
+
//workaround is code below.
|
615
|
+
keyLower = key.toLowerCase();
|
616
|
+
if (obj.hasOwnProperty(keyLower)) {
|
617
|
+
newObj[key] = obj[keyLower];
|
618
|
+
}
|
619
|
+
}
|
620
|
+
|
621
|
+
return newObj;
|
622
|
+
},
|
623
|
+
|
624
|
+
/**
|
625
|
+
* exclude complex objects from $.data() before pass to config
|
626
|
+
*/
|
627
|
+
getConfigData: function($element) {
|
628
|
+
var data = {};
|
629
|
+
$.each($element.data(), function(k, v) {
|
630
|
+
if(typeof v !== 'object' || (v && typeof v === 'object' && v.constructor === Object)) {
|
631
|
+
data[k] = v;
|
632
|
+
}
|
633
|
+
});
|
634
|
+
return data;
|
635
|
+
},
|
636
|
+
|
637
|
+
objectKeys: function(o) {
|
638
|
+
if (Object.keys) {
|
639
|
+
return Object.keys(o);
|
640
|
+
} else {
|
641
|
+
if (o !== Object(o)) {
|
642
|
+
throw new TypeError('Object.keys called on a non-object');
|
643
|
+
}
|
644
|
+
var k=[], p;
|
645
|
+
for (p in o) {
|
646
|
+
if (Object.prototype.hasOwnProperty.call(o,p)) {
|
647
|
+
k.push(p);
|
648
|
+
}
|
649
|
+
}
|
650
|
+
return k;
|
651
|
+
}
|
652
|
+
|
653
|
+
},
|
654
|
+
|
655
|
+
/**
|
656
|
+
method to escape html.
|
657
|
+
**/
|
658
|
+
escape: function(str) {
|
659
|
+
return $('<div>').text(str).html();
|
660
|
+
}
|
661
|
+
};
|
662
|
+
}(window.jQuery));
|
663
|
+
/**
|
664
|
+
Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>
|
665
|
+
This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
|
666
|
+
Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br>
|
667
|
+
Applied as jQuery method.
|
668
|
+
|
669
|
+
@class editableContainer
|
670
|
+
@uses editableform
|
671
|
+
**/
|
672
|
+
(function ($) {
|
673
|
+
|
674
|
+
var EditableContainer = function (element, options) {
|
675
|
+
this.init(element, options);
|
676
|
+
};
|
677
|
+
|
678
|
+
//methods
|
679
|
+
EditableContainer.prototype = {
|
680
|
+
containerName: null, //tbd in child class
|
681
|
+
innerCss: null, //tbd in child class
|
682
|
+
init: function(element, options) {
|
683
|
+
this.$element = $(element);
|
684
|
+
//todo: what is in priority: data or js?
|
685
|
+
this.options = $.extend({}, $.fn.editableContainer.defaults, $.fn.editableutils.getConfigData(this.$element), options);
|
686
|
+
this.splitOptions();
|
687
|
+
this.initContainer();
|
688
|
+
|
689
|
+
//bind 'destroyed' listener to destroy container when element is removed from dom
|
690
|
+
this.$element.on('destroyed', $.proxy(function(){
|
691
|
+
this.destroy();
|
692
|
+
}, this));
|
693
|
+
|
694
|
+
//attach document handlers (once)
|
695
|
+
if(!$(document).data('editable-handlers-attached')) {
|
696
|
+
//close all on escape
|
697
|
+
$(document).on('keyup.editable', function (e) {
|
698
|
+
if (e.which === 27) {
|
699
|
+
$('.editable-open').editableContainer('hide');
|
700
|
+
//todo: return focus on element
|
701
|
+
}
|
702
|
+
});
|
703
|
+
|
704
|
+
//close containers when click outside
|
705
|
+
$(document).on('click.editable', function(e) {
|
706
|
+
var $target = $(e.target);
|
707
|
+
|
708
|
+
//if click inside some editableContainer --> no nothing
|
709
|
+
if($target.is('.editable-container') || $target.parents('.editable-container').length || $target.parents('.ui-datepicker-header').length) {
|
710
|
+
return;
|
711
|
+
} else {
|
712
|
+
//close all open containers (except one)
|
713
|
+
EditableContainer.prototype.closeOthers(e.target);
|
714
|
+
}
|
715
|
+
});
|
716
|
+
|
717
|
+
$(document).data('editable-handlers-attached', true);
|
718
|
+
}
|
719
|
+
},
|
720
|
+
|
721
|
+
//split options on containerOptions and formOptions
|
722
|
+
splitOptions: function() {
|
723
|
+
this.containerOptions = {};
|
724
|
+
this.formOptions = {};
|
725
|
+
var cDef = $.fn[this.containerName].defaults;
|
726
|
+
for(var k in this.options) {
|
727
|
+
if(k in cDef) {
|
728
|
+
this.containerOptions[k] = this.options[k];
|
729
|
+
} else {
|
730
|
+
this.formOptions[k] = this.options[k];
|
731
|
+
}
|
732
|
+
}
|
733
|
+
},
|
734
|
+
|
735
|
+
initContainer: function(){
|
736
|
+
this.call(this.containerOptions);
|
737
|
+
},
|
738
|
+
|
739
|
+
initForm: function() {
|
740
|
+
this.formOptions.scope = this.$element[0]; //set scope of form callbacks to element
|
741
|
+
this.$form = $('<div>')
|
742
|
+
.editableform(this.formOptions)
|
743
|
+
.on({
|
744
|
+
save: $.proxy(this.save, this),
|
745
|
+
cancel: $.proxy(function(){ this.hide('cancel'); }, this),
|
746
|
+
nochange: $.proxy(function(){ this.hide('nochange'); }, this),
|
747
|
+
show: $.proxy(this.setPosition, this), //re-position container every time form is shown (occurs each time after loading state)
|
748
|
+
rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
|
749
|
+
rendered: $.proxy(function(){
|
750
|
+
/**
|
751
|
+
Fired when container is shown and form is rendered (for select will wait for loading dropdown options)
|
752
|
+
|
753
|
+
@event shown
|
754
|
+
@param {Object} event event object
|
755
|
+
@example
|
756
|
+
$('#username').on('shown', function() {
|
757
|
+
var $tip = $(this).data('editableContainer').tip();
|
758
|
+
$tip.find('input').val('overwriting value of input..');
|
759
|
+
});
|
760
|
+
**/
|
761
|
+
this.$element.triggerHandler('shown');
|
762
|
+
}, this)
|
763
|
+
});
|
764
|
+
return this.$form;
|
765
|
+
},
|
766
|
+
|
767
|
+
/*
|
768
|
+
Returns jquery object of container
|
769
|
+
@method tip()
|
770
|
+
*/
|
771
|
+
tip: function() {
|
772
|
+
return this.container().$tip;
|
773
|
+
},
|
774
|
+
|
775
|
+
container: function() {
|
776
|
+
return this.$element.data(this.containerName);
|
777
|
+
},
|
778
|
+
|
779
|
+
call: function() {
|
780
|
+
this.$element[this.containerName].apply(this.$element, arguments);
|
781
|
+
},
|
782
|
+
|
783
|
+
/**
|
784
|
+
Shows container with form
|
785
|
+
@method show()
|
786
|
+
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
787
|
+
**/
|
788
|
+
show: function (closeAll) {
|
789
|
+
this.$element.addClass('editable-open');
|
790
|
+
if(closeAll !== false) {
|
791
|
+
//close all open containers (except this)
|
792
|
+
this.closeOthers(this.$element[0]);
|
793
|
+
}
|
794
|
+
|
795
|
+
this.innerShow();
|
796
|
+
},
|
797
|
+
|
798
|
+
/* internal show method. To be overwritten in child classes */
|
799
|
+
innerShow: function () {
|
800
|
+
this.call('show');
|
801
|
+
this.tip().addClass('editable-container');
|
802
|
+
this.initForm();
|
803
|
+
this.tip().find(this.innerCss).empty().append(this.$form);
|
804
|
+
this.$form.editableform('render');
|
805
|
+
},
|
806
|
+
|
807
|
+
/**
|
808
|
+
Hides container with form
|
809
|
+
@method hide()
|
810
|
+
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
|
811
|
+
**/
|
812
|
+
hide: function(reason) {
|
813
|
+
if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
|
814
|
+
return;
|
815
|
+
}
|
816
|
+
this.$element.removeClass('editable-open');
|
817
|
+
this.innerHide();
|
818
|
+
/**
|
819
|
+
Fired when container was hidden. It occurs on both save or cancel.
|
820
|
+
|
821
|
+
@event hidden
|
822
|
+
@param {object} event event object
|
823
|
+
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
|
824
|
+
@example
|
825
|
+
$('#username').on('hidden', function(e, reason) {
|
826
|
+
if(reason === 'save' || reason === 'cancel') {
|
827
|
+
//auto-open next editable
|
828
|
+
$(this).closest('tr').next().find('.editable').editable('show');
|
829
|
+
}
|
830
|
+
});
|
831
|
+
**/
|
832
|
+
this.$element.triggerHandler('hidden', reason);
|
833
|
+
},
|
834
|
+
|
835
|
+
/* internal hide method. To be overwritten in child classes */
|
836
|
+
innerHide: function () {
|
837
|
+
this.call('hide');
|
838
|
+
},
|
839
|
+
|
840
|
+
/**
|
841
|
+
Toggles container visibility (show / hide)
|
842
|
+
@method toggle()
|
843
|
+
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
844
|
+
**/
|
845
|
+
toggle: function(closeAll) {
|
846
|
+
if(this.tip && this.tip().is(':visible')) {
|
847
|
+
this.hide();
|
848
|
+
} else {
|
849
|
+
this.show(closeAll);
|
850
|
+
}
|
851
|
+
},
|
852
|
+
|
853
|
+
/*
|
854
|
+
Updates the position of container when content changed.
|
855
|
+
@method setPosition()
|
856
|
+
*/
|
857
|
+
setPosition: function() {
|
858
|
+
//tbd in child class
|
859
|
+
},
|
860
|
+
|
861
|
+
save: function(e, params) {
|
862
|
+
this.hide('save');
|
863
|
+
/**
|
864
|
+
Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance
|
865
|
+
|
866
|
+
@event save
|
867
|
+
@param {Object} event event object
|
868
|
+
@param {Object} params additional params
|
869
|
+
@param {mixed} params.newValue submitted value
|
870
|
+
@param {Object} params.response ajax response
|
871
|
+
@example
|
872
|
+
$('#username').on('save', function(e, params) {
|
873
|
+
//assuming server response: '{success: true}'
|
874
|
+
var pk = $(this).data('editableContainer').options.pk;
|
875
|
+
if(params.response && params.response.success) {
|
876
|
+
alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
|
877
|
+
} else {
|
878
|
+
alert('error!');
|
879
|
+
}
|
880
|
+
});
|
881
|
+
**/
|
882
|
+
this.$element.triggerHandler('save', params);
|
883
|
+
},
|
884
|
+
|
885
|
+
/**
|
886
|
+
Sets new option
|
887
|
+
|
888
|
+
@method option(key, value)
|
889
|
+
@param {string} key
|
890
|
+
@param {mixed} value
|
891
|
+
**/
|
892
|
+
option: function(key, value) {
|
893
|
+
this.options[key] = value;
|
894
|
+
if(key in this.containerOptions) {
|
895
|
+
this.containerOptions[key] = value;
|
896
|
+
this.setContainerOption(key, value);
|
897
|
+
} else {
|
898
|
+
this.formOptions[key] = value;
|
899
|
+
if(this.$form) {
|
900
|
+
this.$form.editableform('option', key, value);
|
901
|
+
}
|
902
|
+
}
|
903
|
+
},
|
904
|
+
|
905
|
+
setContainerOption: function(key, value) {
|
906
|
+
this.call('option', key, value);
|
907
|
+
},
|
908
|
+
|
909
|
+
/**
|
910
|
+
Destroys the container instance
|
911
|
+
@method destroy()
|
912
|
+
**/
|
913
|
+
destroy: function() {
|
914
|
+
this.call('destroy');
|
915
|
+
},
|
916
|
+
|
917
|
+
/*
|
918
|
+
Closes other containers except one related to passed element.
|
919
|
+
Other containers can be cancelled or submitted (depends on onblur option)
|
920
|
+
*/
|
921
|
+
closeOthers: function(element) {
|
922
|
+
$('.editable-open').each(function(i, el){
|
923
|
+
//do nothing with passed element and it's children
|
924
|
+
if(el === element || $(el).find(element).length) {
|
925
|
+
return;
|
926
|
+
}
|
927
|
+
|
928
|
+
//otherwise cancel or submit all open containers
|
929
|
+
var $el = $(el),
|
930
|
+
ec = $el.data('editableContainer');
|
931
|
+
|
932
|
+
if(!ec) {
|
933
|
+
return;
|
934
|
+
}
|
935
|
+
|
936
|
+
if(ec.options.onblur === 'cancel') {
|
937
|
+
$el.data('editableContainer').hide('onblur');
|
938
|
+
} else if(ec.options.onblur === 'submit') {
|
939
|
+
$el.data('editableContainer').tip().find('form').submit();
|
940
|
+
}
|
941
|
+
});
|
942
|
+
|
943
|
+
},
|
944
|
+
|
945
|
+
/**
|
946
|
+
Activates input of visible container (e.g. set focus)
|
947
|
+
@method activate()
|
948
|
+
**/
|
949
|
+
activate: function() {
|
950
|
+
if(this.tip && this.tip().is(':visible') && this.$form) {
|
951
|
+
this.$form.data('editableform').input.activate();
|
952
|
+
}
|
953
|
+
}
|
954
|
+
|
955
|
+
};
|
956
|
+
|
957
|
+
/**
|
958
|
+
jQuery method to initialize editableContainer.
|
959
|
+
|
960
|
+
@method $().editableContainer(options)
|
961
|
+
@params {Object} options
|
962
|
+
@example
|
963
|
+
$('#edit').editableContainer({
|
964
|
+
type: 'text',
|
965
|
+
url: '/post',
|
966
|
+
pk: 1,
|
967
|
+
value: 'hello'
|
968
|
+
});
|
969
|
+
**/
|
970
|
+
$.fn.editableContainer = function (option) {
|
971
|
+
var args = arguments;
|
972
|
+
return this.each(function () {
|
973
|
+
var $this = $(this),
|
974
|
+
dataKey = 'editableContainer',
|
975
|
+
data = $this.data(dataKey),
|
976
|
+
options = typeof option === 'object' && option;
|
977
|
+
|
978
|
+
if (!data) {
|
979
|
+
$this.data(dataKey, (data = new EditableContainer(this, options)));
|
980
|
+
}
|
981
|
+
|
982
|
+
if (typeof option === 'string') { //call method
|
983
|
+
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
984
|
+
}
|
985
|
+
});
|
986
|
+
};
|
987
|
+
|
988
|
+
//store constructor
|
989
|
+
$.fn.editableContainer.Constructor = EditableContainer;
|
990
|
+
|
991
|
+
//defaults
|
992
|
+
$.fn.editableContainer.defaults = {
|
993
|
+
/**
|
994
|
+
Initial value of form input
|
995
|
+
|
996
|
+
@property value
|
997
|
+
@type mixed
|
998
|
+
@default null
|
999
|
+
@private
|
1000
|
+
**/
|
1001
|
+
value: null,
|
1002
|
+
/**
|
1003
|
+
Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container.
|
1004
|
+
|
1005
|
+
@property placement
|
1006
|
+
@type string
|
1007
|
+
@default 'top'
|
1008
|
+
**/
|
1009
|
+
placement: 'top',
|
1010
|
+
/**
|
1011
|
+
Whether to hide container on save/cancel.
|
1012
|
+
|
1013
|
+
@property autohide
|
1014
|
+
@type boolean
|
1015
|
+
@default true
|
1016
|
+
@private
|
1017
|
+
**/
|
1018
|
+
autohide: true,
|
1019
|
+
/**
|
1020
|
+
Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>.
|
1021
|
+
Setting <code>ignore</code> allows to have several containers open.
|
1022
|
+
|
1023
|
+
@property onblur
|
1024
|
+
@type string
|
1025
|
+
@default 'cancel'
|
1026
|
+
@since 1.1.1
|
1027
|
+
**/
|
1028
|
+
onblur: 'cancel'
|
1029
|
+
};
|
1030
|
+
|
1031
|
+
/*
|
1032
|
+
* workaround to have 'destroyed' event to destroy popover when element is destroyed
|
1033
|
+
* see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
|
1034
|
+
*/
|
1035
|
+
jQuery.event.special.destroyed = {
|
1036
|
+
remove: function(o) {
|
1037
|
+
if (o.handler) {
|
1038
|
+
o.handler();
|
1039
|
+
}
|
1040
|
+
}
|
1041
|
+
};
|
1042
|
+
|
1043
|
+
}(window.jQuery));
|
1044
|
+
|
1045
|
+
/**
|
1046
|
+
Makes editable any HTML element on the page. Applied as jQuery method.
|
1047
|
+
|
1048
|
+
@class editable
|
1049
|
+
@uses editableContainer
|
1050
|
+
**/
|
1051
|
+
(function ($) {
|
1052
|
+
|
1053
|
+
var Editable = function (element, options) {
|
1054
|
+
this.$element = $(element);
|
1055
|
+
this.options = $.extend({}, $.fn.editable.defaults, $.fn.editableutils.getConfigData(this.$element), options);
|
1056
|
+
this.init();
|
1057
|
+
};
|
1058
|
+
|
1059
|
+
Editable.prototype = {
|
1060
|
+
constructor: Editable,
|
1061
|
+
init: function () {
|
1062
|
+
var TypeConstructor,
|
1063
|
+
isValueByText = false,
|
1064
|
+
doAutotext,
|
1065
|
+
finalize;
|
1066
|
+
|
1067
|
+
//editableContainer must be defined
|
1068
|
+
if(!$.fn.editableContainer) {
|
1069
|
+
$.error('You must define $.fn.editableContainer via including corresponding file (e.g. editable-popover.js)');
|
1070
|
+
return;
|
1071
|
+
}
|
1072
|
+
|
1073
|
+
//name
|
1074
|
+
this.options.name = this.options.name || this.$element.attr('id');
|
1075
|
+
|
1076
|
+
//create input of specified type. Input will be used for converting value, not in form
|
1077
|
+
if(typeof $.fn.editabletypes[this.options.type] === 'function') {
|
1078
|
+
TypeConstructor = $.fn.editabletypes[this.options.type];
|
1079
|
+
this.typeOptions = $.fn.editableutils.sliceObj(this.options, $.fn.editableutils.objectKeys(TypeConstructor.defaults));
|
1080
|
+
this.input = new TypeConstructor(this.typeOptions);
|
1081
|
+
} else {
|
1082
|
+
$.error('Unknown type: '+ this.options.type);
|
1083
|
+
return;
|
1084
|
+
}
|
1085
|
+
|
1086
|
+
//set value from settings or by element's text
|
1087
|
+
if (this.options.value === undefined || this.options.value === null) {
|
1088
|
+
this.value = this.input.html2value($.trim(this.$element.html()));
|
1089
|
+
isValueByText = true;
|
1090
|
+
} else {
|
1091
|
+
/*
|
1092
|
+
value can be string when received from 'data-value' attribute
|
1093
|
+
for complext objects value can be set as json string in data-value attribute,
|
1094
|
+
e.g. data-value="{city: 'Moscow', street: 'Lenina'}"
|
1095
|
+
*/
|
1096
|
+
this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true);
|
1097
|
+
if(typeof this.options.value === 'string') {
|
1098
|
+
this.value = this.input.str2value(this.options.value);
|
1099
|
+
} else {
|
1100
|
+
this.value = this.options.value;
|
1101
|
+
}
|
1102
|
+
}
|
1103
|
+
|
1104
|
+
//add 'editable' class to every editable element
|
1105
|
+
this.$element.addClass('editable');
|
1106
|
+
|
1107
|
+
//attach handler activating editable. In disabled mode it just prevent default action (useful for links)
|
1108
|
+
if(this.options.toggle !== 'manual') {
|
1109
|
+
this.$element.addClass('editable-click');
|
1110
|
+
this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
|
1111
|
+
e.preventDefault();
|
1112
|
+
//stop propagation not required anymore because in document click handler it checks event target
|
1113
|
+
//e.stopPropagation();
|
1114
|
+
|
1115
|
+
if(this.options.toggle === 'mouseenter') {
|
1116
|
+
//for hover only show container
|
1117
|
+
this.show();
|
1118
|
+
} else {
|
1119
|
+
//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
|
1120
|
+
var closeAll = (this.options.toggle !== 'click');
|
1121
|
+
this.toggle(closeAll);
|
1122
|
+
}
|
1123
|
+
}, this));
|
1124
|
+
} else {
|
1125
|
+
this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
|
1126
|
+
}
|
1127
|
+
|
1128
|
+
//check conditions for autotext:
|
1129
|
+
//if value was generated by text or value is empty, no sense to run autotext
|
1130
|
+
doAutotext = !isValueByText && this.value !== null && this.value !== undefined;
|
1131
|
+
doAutotext &= (this.options.autotext === 'always') || (this.options.autotext === 'auto' && !this.$element.text().length);
|
1132
|
+
$.when(doAutotext ? this.render() : true).then($.proxy(function() {
|
1133
|
+
if(this.options.disabled) {
|
1134
|
+
this.disable();
|
1135
|
+
} else {
|
1136
|
+
this.enable();
|
1137
|
+
}
|
1138
|
+
/**
|
1139
|
+
Fired when element was initialized by editable method.
|
1140
|
+
|
1141
|
+
@event init
|
1142
|
+
@param {Object} event event object
|
1143
|
+
@param {Object} editable editable instance
|
1144
|
+
@since 1.2.0
|
1145
|
+
**/
|
1146
|
+
this.$element.triggerHandler('init', this);
|
1147
|
+
}, this));
|
1148
|
+
},
|
1149
|
+
|
1150
|
+
/*
|
1151
|
+
Renders value into element's text.
|
1152
|
+
Can call custom display method from options.
|
1153
|
+
Can return deferred object.
|
1154
|
+
@method render()
|
1155
|
+
*/
|
1156
|
+
render: function() {
|
1157
|
+
//do not display anything
|
1158
|
+
if(this.options.display === false) {
|
1159
|
+
return;
|
1160
|
+
}
|
1161
|
+
//if it is input with source, we pass callback in third param to be called when source is loaded
|
1162
|
+
if(this.input.options.hasOwnProperty('source')) {
|
1163
|
+
return this.input.value2html(this.value, this.$element[0], this.options.display);
|
1164
|
+
//if display method defined --> use it
|
1165
|
+
} else if(typeof this.options.display === 'function') {
|
1166
|
+
return this.options.display.call(this.$element[0], this.value);
|
1167
|
+
//else use input's original value2html() method
|
1168
|
+
} else {
|
1169
|
+
return this.input.value2html(this.value, this.$element[0]);
|
1170
|
+
}
|
1171
|
+
},
|
1172
|
+
|
1173
|
+
/**
|
1174
|
+
Enables editable
|
1175
|
+
@method enable()
|
1176
|
+
**/
|
1177
|
+
enable: function() {
|
1178
|
+
this.options.disabled = false;
|
1179
|
+
this.$element.removeClass('editable-disabled');
|
1180
|
+
this.handleEmpty();
|
1181
|
+
if(this.options.toggle !== 'manual') {
|
1182
|
+
if(this.$element.attr('tabindex') === '-1') {
|
1183
|
+
this.$element.removeAttr('tabindex');
|
1184
|
+
}
|
1185
|
+
}
|
1186
|
+
},
|
1187
|
+
|
1188
|
+
/**
|
1189
|
+
Disables editable
|
1190
|
+
@method disable()
|
1191
|
+
**/
|
1192
|
+
disable: function() {
|
1193
|
+
this.options.disabled = true;
|
1194
|
+
this.hide();
|
1195
|
+
this.$element.addClass('editable-disabled');
|
1196
|
+
this.handleEmpty();
|
1197
|
+
//do not stop focus on this element
|
1198
|
+
this.$element.attr('tabindex', -1);
|
1199
|
+
},
|
1200
|
+
|
1201
|
+
/**
|
1202
|
+
Toggles enabled / disabled state of editable element
|
1203
|
+
@method toggleDisabled()
|
1204
|
+
**/
|
1205
|
+
toggleDisabled: function() {
|
1206
|
+
if(this.options.disabled) {
|
1207
|
+
this.enable();
|
1208
|
+
} else {
|
1209
|
+
this.disable();
|
1210
|
+
}
|
1211
|
+
},
|
1212
|
+
|
1213
|
+
/**
|
1214
|
+
Sets new option
|
1215
|
+
|
1216
|
+
@method option(key, value)
|
1217
|
+
@param {string|object} key option name or object with several options
|
1218
|
+
@param {mixed} value option new value
|
1219
|
+
@example
|
1220
|
+
$('.editable').editable('option', 'pk', 2);
|
1221
|
+
**/
|
1222
|
+
option: function(key, value) {
|
1223
|
+
//set option(s) by object
|
1224
|
+
if(key && typeof key === 'object') {
|
1225
|
+
$.each(key, $.proxy(function(k, v){
|
1226
|
+
this.option($.trim(k), v);
|
1227
|
+
}, this));
|
1228
|
+
return;
|
1229
|
+
}
|
1230
|
+
|
1231
|
+
//set option by string
|
1232
|
+
this.options[key] = value;
|
1233
|
+
|
1234
|
+
//disabled
|
1235
|
+
if(key === 'disabled') {
|
1236
|
+
if(value) {
|
1237
|
+
this.disable();
|
1238
|
+
} else {
|
1239
|
+
this.enable();
|
1240
|
+
}
|
1241
|
+
return;
|
1242
|
+
}
|
1243
|
+
|
1244
|
+
//value
|
1245
|
+
if(key === 'value') {
|
1246
|
+
this.setValue(value);
|
1247
|
+
}
|
1248
|
+
|
1249
|
+
//transfer new option to container!
|
1250
|
+
if(this.container) {
|
1251
|
+
this.container.option(key, value);
|
1252
|
+
}
|
1253
|
+
},
|
1254
|
+
|
1255
|
+
/*
|
1256
|
+
* set emptytext if element is empty (reverse: remove emptytext if needed)
|
1257
|
+
*/
|
1258
|
+
handleEmpty: function () {
|
1259
|
+
//do not handle empty if we do not display anything
|
1260
|
+
if(this.options.display === false) {
|
1261
|
+
return;
|
1262
|
+
}
|
1263
|
+
|
1264
|
+
var emptyClass = 'editable-empty';
|
1265
|
+
//emptytext shown only for enabled
|
1266
|
+
if(!this.options.disabled) {
|
1267
|
+
if ($.trim(this.$element.text()) === '') {
|
1268
|
+
this.$element.addClass(emptyClass).text(this.options.emptytext);
|
1269
|
+
} else {
|
1270
|
+
this.$element.removeClass(emptyClass);
|
1271
|
+
}
|
1272
|
+
} else {
|
1273
|
+
//below required if element disable property was changed
|
1274
|
+
if(this.$element.hasClass(emptyClass)) {
|
1275
|
+
this.$element.empty();
|
1276
|
+
this.$element.removeClass(emptyClass);
|
1277
|
+
}
|
1278
|
+
}
|
1279
|
+
},
|
1280
|
+
|
1281
|
+
/**
|
1282
|
+
Shows container with form
|
1283
|
+
@method show()
|
1284
|
+
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
1285
|
+
**/
|
1286
|
+
show: function (closeAll) {
|
1287
|
+
if(this.options.disabled) {
|
1288
|
+
return;
|
1289
|
+
}
|
1290
|
+
|
1291
|
+
//init editableContainer: popover, tooltip, inline, etc..
|
1292
|
+
if(!this.container) {
|
1293
|
+
var containerOptions = $.extend({}, this.options, {
|
1294
|
+
value: this.value
|
1295
|
+
});
|
1296
|
+
this.$element.editableContainer(containerOptions);
|
1297
|
+
this.$element.on("save.internal", $.proxy(this.save, this));
|
1298
|
+
this.container = this.$element.data('editableContainer');
|
1299
|
+
} else if(this.container.tip().is(':visible')) {
|
1300
|
+
return;
|
1301
|
+
}
|
1302
|
+
|
1303
|
+
//show container
|
1304
|
+
this.container.show(closeAll);
|
1305
|
+
},
|
1306
|
+
|
1307
|
+
/**
|
1308
|
+
Hides container with form
|
1309
|
+
@method hide()
|
1310
|
+
**/
|
1311
|
+
hide: function () {
|
1312
|
+
if(this.container) {
|
1313
|
+
this.container.hide();
|
1314
|
+
}
|
1315
|
+
},
|
1316
|
+
|
1317
|
+
/**
|
1318
|
+
Toggles container visibility (show / hide)
|
1319
|
+
@method toggle()
|
1320
|
+
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
1321
|
+
**/
|
1322
|
+
toggle: function(closeAll) {
|
1323
|
+
if(this.container && this.container.tip().is(':visible')) {
|
1324
|
+
this.hide();
|
1325
|
+
} else {
|
1326
|
+
this.show(closeAll);
|
1327
|
+
}
|
1328
|
+
},
|
1329
|
+
|
1330
|
+
/*
|
1331
|
+
* called when form was submitted
|
1332
|
+
*/
|
1333
|
+
save: function(e, params) {
|
1334
|
+
//if url is not user's function and value was not sent to server and value changed --> mark element with unsaved css.
|
1335
|
+
if(typeof this.options.url !== 'function' && this.options.display !== false && params.response === undefined && this.input.value2str(this.value) !== this.input.value2str(params.newValue)) {
|
1336
|
+
this.$element.addClass('editable-unsaved');
|
1337
|
+
} else {
|
1338
|
+
this.$element.removeClass('editable-unsaved');
|
1339
|
+
}
|
1340
|
+
|
1341
|
+
// this.hide();
|
1342
|
+
this.setValue(params.newValue);
|
1343
|
+
|
1344
|
+
/**
|
1345
|
+
Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance
|
1346
|
+
|
1347
|
+
@event save
|
1348
|
+
@param {Object} event event object
|
1349
|
+
@param {Object} params additional params
|
1350
|
+
@param {mixed} params.newValue submitted value
|
1351
|
+
@param {Object} params.response ajax response
|
1352
|
+
@example
|
1353
|
+
$('#username').on('save', function(e, params) {
|
1354
|
+
//assuming server response: '{success: true}'
|
1355
|
+
var pk = $(this).data('editable').options.pk;
|
1356
|
+
if(params.response && params.response.success) {
|
1357
|
+
alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
|
1358
|
+
} else {
|
1359
|
+
alert('error!');
|
1360
|
+
}
|
1361
|
+
});
|
1362
|
+
**/
|
1363
|
+
//event itself is triggered by editableContainer. Description here is only for documentation
|
1364
|
+
},
|
1365
|
+
|
1366
|
+
validate: function () {
|
1367
|
+
if (typeof this.options.validate === 'function') {
|
1368
|
+
return this.options.validate.call(this, this.value);
|
1369
|
+
}
|
1370
|
+
},
|
1371
|
+
|
1372
|
+
/**
|
1373
|
+
Sets new value of editable
|
1374
|
+
@method setValue(value, convertStr)
|
1375
|
+
@param {mixed} value new value
|
1376
|
+
@param {boolean} convertStr whether to convert value from string to internal format
|
1377
|
+
**/
|
1378
|
+
setValue: function(value, convertStr) {
|
1379
|
+
if(convertStr) {
|
1380
|
+
this.value = this.input.str2value(value);
|
1381
|
+
} else {
|
1382
|
+
this.value = value;
|
1383
|
+
}
|
1384
|
+
if(this.container) {
|
1385
|
+
this.container.option('value', this.value);
|
1386
|
+
}
|
1387
|
+
$.when(this.render())
|
1388
|
+
.then($.proxy(function() {
|
1389
|
+
this.handleEmpty();
|
1390
|
+
}, this));
|
1391
|
+
},
|
1392
|
+
|
1393
|
+
/**
|
1394
|
+
Activates input of visible container (e.g. set focus)
|
1395
|
+
@method activate()
|
1396
|
+
**/
|
1397
|
+
activate: function() {
|
1398
|
+
if(this.container) {
|
1399
|
+
this.container.activate();
|
1400
|
+
}
|
1401
|
+
}
|
1402
|
+
};
|
1403
|
+
|
1404
|
+
/* EDITABLE PLUGIN DEFINITION
|
1405
|
+
* ======================= */
|
1406
|
+
|
1407
|
+
/**
|
1408
|
+
jQuery method to initialize editable element.
|
1409
|
+
|
1410
|
+
@method $().editable(options)
|
1411
|
+
@params {Object} options
|
1412
|
+
@example
|
1413
|
+
$('#username').editable({
|
1414
|
+
type: 'text',
|
1415
|
+
url: '/post',
|
1416
|
+
pk: 1
|
1417
|
+
});
|
1418
|
+
**/
|
1419
|
+
$.fn.editable = function (option) {
|
1420
|
+
//special API methods returning non-jquery object
|
1421
|
+
var result = {}, args = arguments, datakey = 'editable';
|
1422
|
+
switch (option) {
|
1423
|
+
/**
|
1424
|
+
Runs client-side validation for all matched editables
|
1425
|
+
|
1426
|
+
@method validate()
|
1427
|
+
@returns {Object} validation errors map
|
1428
|
+
@example
|
1429
|
+
$('#username, #fullname').editable('validate');
|
1430
|
+
// possible result:
|
1431
|
+
{
|
1432
|
+
username: "username is required",
|
1433
|
+
fullname: "fullname should be minimum 3 letters length"
|
1434
|
+
}
|
1435
|
+
**/
|
1436
|
+
case 'validate':
|
1437
|
+
this.each(function () {
|
1438
|
+
var $this = $(this), data = $this.data(datakey), error;
|
1439
|
+
if (data && (error = data.validate())) {
|
1440
|
+
result[data.options.name] = error;
|
1441
|
+
}
|
1442
|
+
});
|
1443
|
+
return result;
|
1444
|
+
|
1445
|
+
/**
|
1446
|
+
Returns current values of editable elements. If value is <code>null</code> or <code>undefined</code> it will not be returned
|
1447
|
+
@method getValue()
|
1448
|
+
@returns {Object} object of element names and values
|
1449
|
+
@example
|
1450
|
+
$('#username, #fullname').editable('validate');
|
1451
|
+
// possible result:
|
1452
|
+
{
|
1453
|
+
username: "superuser",
|
1454
|
+
fullname: "John"
|
1455
|
+
}
|
1456
|
+
**/
|
1457
|
+
case 'getValue':
|
1458
|
+
this.each(function () {
|
1459
|
+
var $this = $(this), data = $this.data(datakey);
|
1460
|
+
if (data && data.value !== undefined && data.value !== null) {
|
1461
|
+
result[data.options.name] = data.input.value2submit(data.value);
|
1462
|
+
}
|
1463
|
+
});
|
1464
|
+
return result;
|
1465
|
+
|
1466
|
+
/**
|
1467
|
+
This method collects values from several editable elements and submit them all to server.
|
1468
|
+
Internally it runs client-side validation for all fields and submits only in case of success.
|
1469
|
+
See <a href="#newrecord">creating new records</a> for details.
|
1470
|
+
|
1471
|
+
@method submit(options)
|
1472
|
+
@param {object} options
|
1473
|
+
@param {object} options.url url to submit data
|
1474
|
+
@param {object} options.data additional data to submit
|
1475
|
+
@param {object} options.ajaxOptions additional ajax options
|
1476
|
+
@param {function} options.error(obj) error handler
|
1477
|
+
@param {function} options.success(obj,config) success handler
|
1478
|
+
@returns {Object} jQuery object
|
1479
|
+
**/
|
1480
|
+
case 'submit': //collects value, validate and submit to server for creating new record
|
1481
|
+
var config = arguments[1] || {},
|
1482
|
+
$elems = this,
|
1483
|
+
errors = this.editable('validate'),
|
1484
|
+
values;
|
1485
|
+
|
1486
|
+
if($.isEmptyObject(errors)) {
|
1487
|
+
values = this.editable('getValue');
|
1488
|
+
if(config.data) {
|
1489
|
+
$.extend(values, config.data);
|
1490
|
+
}
|
1491
|
+
|
1492
|
+
$.ajax($.extend({
|
1493
|
+
url: config.url,
|
1494
|
+
data: values,
|
1495
|
+
type: 'POST'
|
1496
|
+
}, config.ajaxOptions))
|
1497
|
+
.success(function(response) {
|
1498
|
+
//successful response 200 OK
|
1499
|
+
if(typeof config.success === 'function') {
|
1500
|
+
config.success.call($elems, response, config);
|
1501
|
+
}
|
1502
|
+
})
|
1503
|
+
.error(function(){ //ajax error
|
1504
|
+
if(typeof config.error === 'function') {
|
1505
|
+
config.error.apply($elems, arguments);
|
1506
|
+
}
|
1507
|
+
});
|
1508
|
+
} else { //client-side validation error
|
1509
|
+
if(typeof config.error === 'function') {
|
1510
|
+
config.error.call($elems, errors);
|
1511
|
+
}
|
1512
|
+
}
|
1513
|
+
return this;
|
1514
|
+
}
|
1515
|
+
|
1516
|
+
//return jquery object
|
1517
|
+
return this.each(function () {
|
1518
|
+
var $this = $(this),
|
1519
|
+
data = $this.data(datakey),
|
1520
|
+
options = typeof option === 'object' && option;
|
1521
|
+
|
1522
|
+
if (!data) {
|
1523
|
+
$this.data(datakey, (data = new Editable(this, options)));
|
1524
|
+
}
|
1525
|
+
|
1526
|
+
if (typeof option === 'string') { //call method
|
1527
|
+
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
1528
|
+
}
|
1529
|
+
});
|
1530
|
+
};
|
1531
|
+
|
1532
|
+
|
1533
|
+
$.fn.editable.defaults = {
|
1534
|
+
/**
|
1535
|
+
Type of input. Can be <code>text|textarea|select|date|checklist</code> and more
|
1536
|
+
|
1537
|
+
@property type
|
1538
|
+
@type string
|
1539
|
+
@default 'text'
|
1540
|
+
**/
|
1541
|
+
type: 'text',
|
1542
|
+
/**
|
1543
|
+
Sets disabled state of editable
|
1544
|
+
|
1545
|
+
@property disabled
|
1546
|
+
@type boolean
|
1547
|
+
@default false
|
1548
|
+
**/
|
1549
|
+
disabled: false,
|
1550
|
+
/**
|
1551
|
+
How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>.
|
1552
|
+
When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable.
|
1553
|
+
**Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element,
|
1554
|
+
you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document.
|
1555
|
+
|
1556
|
+
@example
|
1557
|
+
$('#edit-button').click(function(e) {
|
1558
|
+
e.stopPropagation();
|
1559
|
+
$('#username').editable('toggle');
|
1560
|
+
});
|
1561
|
+
|
1562
|
+
@property toggle
|
1563
|
+
@type string
|
1564
|
+
@default 'click'
|
1565
|
+
**/
|
1566
|
+
toggle: 'click',
|
1567
|
+
/**
|
1568
|
+
Text shown when element is empty.
|
1569
|
+
|
1570
|
+
@property emptytext
|
1571
|
+
@type string
|
1572
|
+
@default 'Empty'
|
1573
|
+
**/
|
1574
|
+
emptytext: 'Empty',
|
1575
|
+
/**
|
1576
|
+
Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Useful for select and date.
|
1577
|
+
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>.
|
1578
|
+
<code>auto</code> - text will be automatically set only if element is empty.
|
1579
|
+
<code>always|never</code> - always(never) try to set element's text.
|
1580
|
+
|
1581
|
+
@property autotext
|
1582
|
+
@type string
|
1583
|
+
@default 'auto'
|
1584
|
+
**/
|
1585
|
+
autotext: 'auto',
|
1586
|
+
/**
|
1587
|
+
Initial value of input. Taken from <code>data-value</code> or element's text.
|
1588
|
+
|
1589
|
+
@property value
|
1590
|
+
@type mixed
|
1591
|
+
@default element's text
|
1592
|
+
**/
|
1593
|
+
value: null,
|
1594
|
+
/**
|
1595
|
+
Callback to perform custom displaying of value in element's text.
|
1596
|
+
If <code>null</code>, default input's value2html() will be called.
|
1597
|
+
If <code>false</code>, no displaying methods will be called, element's text will no change.
|
1598
|
+
Runs under element's scope.
|
1599
|
+
Second parameter __sourceData__ is passed for inputs with source (select, checklist).
|
1600
|
+
|
1601
|
+
@property display
|
1602
|
+
@type function|boolean
|
1603
|
+
@default null
|
1604
|
+
@since 1.2.0
|
1605
|
+
@example
|
1606
|
+
display: function(value, sourceData) {
|
1607
|
+
var escapedValue = $('<div>').text(value).html();
|
1608
|
+
$(this).html('<b>'+escapedValue+'</b>');
|
1609
|
+
}
|
1610
|
+
**/
|
1611
|
+
display: null
|
1612
|
+
};
|
1613
|
+
|
1614
|
+
}(window.jQuery));
|
1615
|
+
|
1616
|
+
/**
|
1617
|
+
AbstractInput - base class for all editable inputs.
|
1618
|
+
It defines interface to be implemented by any input type.
|
1619
|
+
To create your own input you can inherit from this class.
|
1620
|
+
|
1621
|
+
@class abstractinput
|
1622
|
+
**/
|
1623
|
+
(function ($) {
|
1624
|
+
|
1625
|
+
//types
|
1626
|
+
$.fn.editabletypes = {};
|
1627
|
+
|
1628
|
+
var AbstractInput = function () { };
|
1629
|
+
|
1630
|
+
AbstractInput.prototype = {
|
1631
|
+
/**
|
1632
|
+
Initializes input
|
1633
|
+
|
1634
|
+
@method init()
|
1635
|
+
**/
|
1636
|
+
init: function(type, options, defaults) {
|
1637
|
+
this.type = type;
|
1638
|
+
this.options = $.extend({}, defaults, options);
|
1639
|
+
this.$input = null;
|
1640
|
+
this.$clear = null;
|
1641
|
+
this.error = null;
|
1642
|
+
},
|
1643
|
+
|
1644
|
+
/**
|
1645
|
+
Renders input from tpl. Can return jQuery deferred object.
|
1646
|
+
|
1647
|
+
@method render()
|
1648
|
+
**/
|
1649
|
+
render: function() {
|
1650
|
+
this.$input = $(this.options.tpl);
|
1651
|
+
if(this.options.inputclass) {
|
1652
|
+
this.$input.addClass(this.options.inputclass);
|
1653
|
+
}
|
1654
|
+
|
1655
|
+
if (this.options.placeholder) {
|
1656
|
+
this.$input.attr('placeholder', this.options.placeholder);
|
1657
|
+
}
|
1658
|
+
},
|
1659
|
+
|
1660
|
+
/**
|
1661
|
+
Sets element's html by value.
|
1662
|
+
|
1663
|
+
@method value2html(value, element)
|
1664
|
+
@param {mixed} value
|
1665
|
+
@param {DOMElement} element
|
1666
|
+
**/
|
1667
|
+
value2html: function(value, element) {
|
1668
|
+
$(element).text(value);
|
1669
|
+
},
|
1670
|
+
|
1671
|
+
/**
|
1672
|
+
Converts element's html to value
|
1673
|
+
|
1674
|
+
@method html2value(html)
|
1675
|
+
@param {string} html
|
1676
|
+
@returns {mixed}
|
1677
|
+
**/
|
1678
|
+
html2value: function(html) {
|
1679
|
+
return $('<div>').html(html).text();
|
1680
|
+
},
|
1681
|
+
|
1682
|
+
/**
|
1683
|
+
Converts value to string (for internal compare). For submitting to server used value2submit().
|
1684
|
+
|
1685
|
+
@method value2str(value)
|
1686
|
+
@param {mixed} value
|
1687
|
+
@returns {string}
|
1688
|
+
**/
|
1689
|
+
value2str: function(value) {
|
1690
|
+
return value;
|
1691
|
+
},
|
1692
|
+
|
1693
|
+
/**
|
1694
|
+
Converts string received from server into value.
|
1695
|
+
|
1696
|
+
@method str2value(str)
|
1697
|
+
@param {string} str
|
1698
|
+
@returns {mixed}
|
1699
|
+
**/
|
1700
|
+
str2value: function(str) {
|
1701
|
+
return str;
|
1702
|
+
},
|
1703
|
+
|
1704
|
+
/**
|
1705
|
+
Converts value for submitting to server
|
1706
|
+
|
1707
|
+
@method value2submit(value)
|
1708
|
+
@param {mixed} value
|
1709
|
+
@returns {mixed}
|
1710
|
+
**/
|
1711
|
+
value2submit: function(value) {
|
1712
|
+
return value;
|
1713
|
+
},
|
1714
|
+
|
1715
|
+
/**
|
1716
|
+
Sets value of input.
|
1717
|
+
|
1718
|
+
@method value2input(value)
|
1719
|
+
@param {mixed} value
|
1720
|
+
**/
|
1721
|
+
value2input: function(value) {
|
1722
|
+
this.$input.val(value);
|
1723
|
+
},
|
1724
|
+
|
1725
|
+
/**
|
1726
|
+
Returns value of input. Value can be object (e.g. datepicker)
|
1727
|
+
|
1728
|
+
@method input2value()
|
1729
|
+
**/
|
1730
|
+
input2value: function() {
|
1731
|
+
return this.$input.val();
|
1732
|
+
},
|
1733
|
+
|
1734
|
+
/**
|
1735
|
+
Activates input. For text it sets focus.
|
1736
|
+
|
1737
|
+
@method activate()
|
1738
|
+
**/
|
1739
|
+
activate: function() {
|
1740
|
+
if(this.$input.is(':visible')) {
|
1741
|
+
this.$input.focus();
|
1742
|
+
}
|
1743
|
+
},
|
1744
|
+
|
1745
|
+
/**
|
1746
|
+
Creates input.
|
1747
|
+
|
1748
|
+
@method clear()
|
1749
|
+
**/
|
1750
|
+
clear: function() {
|
1751
|
+
this.$input.val(null);
|
1752
|
+
},
|
1753
|
+
|
1754
|
+
/**
|
1755
|
+
method to escape html.
|
1756
|
+
**/
|
1757
|
+
escape: function(str) {
|
1758
|
+
return $('<div>').text(str).html();
|
1759
|
+
},
|
1760
|
+
|
1761
|
+
/**
|
1762
|
+
attach handler to automatically submit form when value changed (useful when buttons not shown)
|
1763
|
+
**/
|
1764
|
+
autosubmit: function() {
|
1765
|
+
|
1766
|
+
}
|
1767
|
+
};
|
1768
|
+
|
1769
|
+
AbstractInput.defaults = {
|
1770
|
+
/**
|
1771
|
+
HTML template of input. Normally you should not change it.
|
1772
|
+
|
1773
|
+
@property tpl
|
1774
|
+
@type string
|
1775
|
+
@default ''
|
1776
|
+
**/
|
1777
|
+
tpl: '',
|
1778
|
+
/**
|
1779
|
+
CSS class automatically applied to input
|
1780
|
+
|
1781
|
+
@property inputclass
|
1782
|
+
@type string
|
1783
|
+
@default input-medium
|
1784
|
+
**/
|
1785
|
+
inputclass: 'input-medium',
|
1786
|
+
/**
|
1787
|
+
Name attribute of input
|
1788
|
+
|
1789
|
+
@property name
|
1790
|
+
@type string
|
1791
|
+
@default null
|
1792
|
+
**/
|
1793
|
+
name: null
|
1794
|
+
};
|
1795
|
+
|
1796
|
+
$.extend($.fn.editabletypes, {abstractinput: AbstractInput});
|
1797
|
+
|
1798
|
+
}(window.jQuery));
|
1799
|
+
|
1800
|
+
/**
|
1801
|
+
List - abstract class for inputs that have source option loaded from js array or via ajax
|
1802
|
+
|
1803
|
+
@class list
|
1804
|
+
@extends abstractinput
|
1805
|
+
**/
|
1806
|
+
(function ($) {
|
1807
|
+
|
1808
|
+
var List = function (options) {
|
1809
|
+
|
1810
|
+
};
|
1811
|
+
|
1812
|
+
$.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput);
|
1813
|
+
|
1814
|
+
$.extend(List.prototype, {
|
1815
|
+
render: function () {
|
1816
|
+
List.superclass.render.call(this);
|
1817
|
+
var deferred = $.Deferred();
|
1818
|
+
this.error = null;
|
1819
|
+
this.sourceData = null;
|
1820
|
+
this.prependData = null;
|
1821
|
+
this.onSourceReady(function () {
|
1822
|
+
this.renderList();
|
1823
|
+
deferred.resolve();
|
1824
|
+
}, function () {
|
1825
|
+
this.error = this.options.sourceError;
|
1826
|
+
deferred.resolve();
|
1827
|
+
});
|
1828
|
+
|
1829
|
+
return deferred.promise();
|
1830
|
+
},
|
1831
|
+
|
1832
|
+
html2value: function (html) {
|
1833
|
+
return null; //can't set value by text
|
1834
|
+
},
|
1835
|
+
|
1836
|
+
value2html: function (value, element, display) {
|
1837
|
+
var deferred = $.Deferred();
|
1838
|
+
this.onSourceReady(function () {
|
1839
|
+
if(typeof display === 'function') {
|
1840
|
+
//custom display method
|
1841
|
+
display.call(element, value, this.sourceData);
|
1842
|
+
} else {
|
1843
|
+
this.value2htmlFinal(value, element);
|
1844
|
+
}
|
1845
|
+
deferred.resolve();
|
1846
|
+
}, function () {
|
1847
|
+
//do nothing with element
|
1848
|
+
deferred.resolve();
|
1849
|
+
});
|
1850
|
+
|
1851
|
+
return deferred.promise();
|
1852
|
+
},
|
1853
|
+
|
1854
|
+
// ------------- additional functions ------------
|
1855
|
+
|
1856
|
+
onSourceReady: function (success, error) {
|
1857
|
+
//if allready loaded just call success
|
1858
|
+
if($.isArray(this.sourceData)) {
|
1859
|
+
success.call(this);
|
1860
|
+
return;
|
1861
|
+
}
|
1862
|
+
|
1863
|
+
// try parse json in single quotes (for double quotes jquery does automatically)
|
1864
|
+
try {
|
1865
|
+
this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false);
|
1866
|
+
} catch (e) {
|
1867
|
+
error.call(this);
|
1868
|
+
return;
|
1869
|
+
}
|
1870
|
+
|
1871
|
+
//loading from url
|
1872
|
+
if (typeof this.options.source === 'string') {
|
1873
|
+
//try to get from cache
|
1874
|
+
if(this.options.sourceCache) {
|
1875
|
+
var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''),
|
1876
|
+
cache;
|
1877
|
+
|
1878
|
+
if (!$(document).data(cacheID)) {
|
1879
|
+
$(document).data(cacheID, {});
|
1880
|
+
}
|
1881
|
+
cache = $(document).data(cacheID);
|
1882
|
+
|
1883
|
+
//check for cached data
|
1884
|
+
if (cache.loading === false && cache.sourceData) { //take source from cache
|
1885
|
+
this.sourceData = cache.sourceData;
|
1886
|
+
success.call(this);
|
1887
|
+
return;
|
1888
|
+
} else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
|
1889
|
+
cache.callbacks.push($.proxy(function () {
|
1890
|
+
this.sourceData = cache.sourceData;
|
1891
|
+
success.call(this);
|
1892
|
+
}, this));
|
1893
|
+
|
1894
|
+
//also collecting error callbacks
|
1895
|
+
cache.err_callbacks.push($.proxy(error, this));
|
1896
|
+
return;
|
1897
|
+
} else { //no cache yet, activate it
|
1898
|
+
cache.loading = true;
|
1899
|
+
cache.callbacks = [];
|
1900
|
+
cache.err_callbacks = [];
|
1901
|
+
}
|
1902
|
+
}
|
1903
|
+
|
1904
|
+
//loading sourceData from server
|
1905
|
+
$.ajax({
|
1906
|
+
url: this.options.source,
|
1907
|
+
type: 'get',
|
1908
|
+
cache: false,
|
1909
|
+
data: this.options.name ? {name: this.options.name} : {},
|
1910
|
+
dataType: 'json',
|
1911
|
+
success: $.proxy(function (data) {
|
1912
|
+
if(cache) {
|
1913
|
+
cache.loading = false;
|
1914
|
+
}
|
1915
|
+
this.sourceData = this.makeArray(data);
|
1916
|
+
if($.isArray(this.sourceData)) {
|
1917
|
+
this.doPrepend();
|
1918
|
+
success.call(this);
|
1919
|
+
if(cache) {
|
1920
|
+
//store result in cache
|
1921
|
+
cache.sourceData = this.sourceData;
|
1922
|
+
$.each(cache.callbacks, function () { this.call(); }); //run success callbacks for other fields
|
1923
|
+
}
|
1924
|
+
} else {
|
1925
|
+
error.call(this);
|
1926
|
+
if(cache) {
|
1927
|
+
$.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields
|
1928
|
+
}
|
1929
|
+
}
|
1930
|
+
}, this),
|
1931
|
+
error: $.proxy(function () {
|
1932
|
+
error.call(this);
|
1933
|
+
if(cache) {
|
1934
|
+
cache.loading = false;
|
1935
|
+
//run error callbacks for other fields
|
1936
|
+
$.each(cache.err_callbacks, function () { this.call(); });
|
1937
|
+
}
|
1938
|
+
}, this)
|
1939
|
+
});
|
1940
|
+
} else { //options as json/array
|
1941
|
+
this.sourceData = this.makeArray(this.options.source);
|
1942
|
+
if($.isArray(this.sourceData)) {
|
1943
|
+
this.doPrepend();
|
1944
|
+
success.call(this);
|
1945
|
+
} else {
|
1946
|
+
error.call(this);
|
1947
|
+
}
|
1948
|
+
}
|
1949
|
+
},
|
1950
|
+
|
1951
|
+
doPrepend: function () {
|
1952
|
+
if(this.options.prepend === null || this.options.prepend === undefined) {
|
1953
|
+
return;
|
1954
|
+
}
|
1955
|
+
|
1956
|
+
if(!$.isArray(this.prependData)) {
|
1957
|
+
//try parse json in single quotes
|
1958
|
+
this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);
|
1959
|
+
if (typeof this.options.prepend === 'string') {
|
1960
|
+
this.options.prepend = {'': this.options.prepend};
|
1961
|
+
}
|
1962
|
+
this.prependData = this.makeArray(this.options.prepend);
|
1963
|
+
}
|
1964
|
+
|
1965
|
+
if($.isArray(this.prependData) && $.isArray(this.sourceData)) {
|
1966
|
+
this.sourceData = this.prependData.concat(this.sourceData);
|
1967
|
+
}
|
1968
|
+
},
|
1969
|
+
|
1970
|
+
/*
|
1971
|
+
renders input list
|
1972
|
+
*/
|
1973
|
+
renderList: function() {
|
1974
|
+
// this method should be overwritten in child class
|
1975
|
+
},
|
1976
|
+
|
1977
|
+
/*
|
1978
|
+
set element's html by value
|
1979
|
+
*/
|
1980
|
+
value2htmlFinal: function(value, element) {
|
1981
|
+
// this method should be overwritten in child class
|
1982
|
+
},
|
1983
|
+
|
1984
|
+
/**
|
1985
|
+
* convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}]
|
1986
|
+
*/
|
1987
|
+
makeArray: function(data) {
|
1988
|
+
var count, obj, result = [], iterateEl;
|
1989
|
+
if(!data || typeof data === 'string') {
|
1990
|
+
return null;
|
1991
|
+
}
|
1992
|
+
|
1993
|
+
if($.isArray(data)) { //array
|
1994
|
+
iterateEl = function (k, v) {
|
1995
|
+
obj = {value: k, text: v};
|
1996
|
+
if(count++ >= 2) {
|
1997
|
+
return false;// exit each if object has more than one value
|
1998
|
+
}
|
1999
|
+
};
|
2000
|
+
|
2001
|
+
for(var i = 0; i < data.length; i++) {
|
2002
|
+
if(typeof data[i] === 'object') {
|
2003
|
+
count = 0;
|
2004
|
+
$.each(data[i], iterateEl);
|
2005
|
+
if(count === 1) {
|
2006
|
+
result.push(obj);
|
2007
|
+
} else if(count > 1 && data[i].hasOwnProperty('value') && data[i].hasOwnProperty('text')) {
|
2008
|
+
result.push(data[i]);
|
2009
|
+
} else {
|
2010
|
+
//data contains incorrect objects
|
2011
|
+
}
|
2012
|
+
} else {
|
2013
|
+
result.push({value: data[i], text: data[i]});
|
2014
|
+
}
|
2015
|
+
}
|
2016
|
+
} else { //object
|
2017
|
+
$.each(data, function (k, v) {
|
2018
|
+
result.push({value: k, text: v});
|
2019
|
+
});
|
2020
|
+
}
|
2021
|
+
return result;
|
2022
|
+
},
|
2023
|
+
|
2024
|
+
//search for item by particular value
|
2025
|
+
itemByVal: function(val) {
|
2026
|
+
if($.isArray(this.sourceData)) {
|
2027
|
+
for(var i=0; i<this.sourceData.length; i++){
|
2028
|
+
/*jshint eqeqeq: false*/
|
2029
|
+
if(this.sourceData[i].value == val) {
|
2030
|
+
/*jshint eqeqeq: true*/
|
2031
|
+
return this.sourceData[i];
|
2032
|
+
}
|
2033
|
+
}
|
2034
|
+
}
|
2035
|
+
}
|
2036
|
+
|
2037
|
+
});
|
2038
|
+
|
2039
|
+
List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
2040
|
+
/**
|
2041
|
+
Source data for list. If string - considered ajax url to load items. Otherwise should be an array.
|
2042
|
+
Array format is: <code>[{value: 1, text: "text"}, {...}]</code><br>
|
2043
|
+
For compability it also supports format <code>{value1: "text1", value2: "text2" ...}</code> but it does not guarantee elements order.
|
2044
|
+
If source is **string**, results will be cached for fields with the same source and name. See also <code>sourceCache</code> option.
|
2045
|
+
|
2046
|
+
@property source
|
2047
|
+
@type string|array|object
|
2048
|
+
@default null
|
2049
|
+
**/
|
2050
|
+
source:null,
|
2051
|
+
/**
|
2052
|
+
Data automatically prepended to the beginning of dropdown list.
|
2053
|
+
|
2054
|
+
@property prepend
|
2055
|
+
@type string|array|object
|
2056
|
+
@default false
|
2057
|
+
**/
|
2058
|
+
prepend:false,
|
2059
|
+
/**
|
2060
|
+
Error message when list cannot be loaded (e.g. ajax error)
|
2061
|
+
|
2062
|
+
@property sourceError
|
2063
|
+
@type string
|
2064
|
+
@default Error when loading list
|
2065
|
+
**/
|
2066
|
+
sourceError: 'Error when loading list',
|
2067
|
+
/**
|
2068
|
+
if <code>true</code> and source is **string url** - results will be cached for fields with the same source and name.
|
2069
|
+
Usefull for editable grids.
|
2070
|
+
|
2071
|
+
@property sourceCache
|
2072
|
+
@type boolean
|
2073
|
+
@default true
|
2074
|
+
@since 1.2.0
|
2075
|
+
**/
|
2076
|
+
sourceCache: true
|
2077
|
+
});
|
2078
|
+
|
2079
|
+
$.fn.editabletypes.list = List;
|
2080
|
+
|
2081
|
+
}(window.jQuery));
|
2082
|
+
/**
|
2083
|
+
Text input
|
2084
|
+
|
2085
|
+
@class text
|
2086
|
+
@extends abstractinput
|
2087
|
+
@final
|
2088
|
+
@example
|
2089
|
+
<a href="#" id="username" data-type="text" data-pk="1">awesome</a>
|
2090
|
+
<script>
|
2091
|
+
$(function(){
|
2092
|
+
$('#username').editable({
|
2093
|
+
url: '/post',
|
2094
|
+
title: 'Enter username'
|
2095
|
+
});
|
2096
|
+
});
|
2097
|
+
</script>
|
2098
|
+
**/
|
2099
|
+
(function ($) {
|
2100
|
+
var Text = function (options) {
|
2101
|
+
this.init('text', options, Text.defaults);
|
2102
|
+
};
|
2103
|
+
|
2104
|
+
$.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
|
2105
|
+
|
2106
|
+
$.extend(Text.prototype, {
|
2107
|
+
activate: function() {
|
2108
|
+
if(this.$input.is(':visible')) {
|
2109
|
+
this.$input.focus();
|
2110
|
+
$.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
|
2111
|
+
}
|
2112
|
+
}
|
2113
|
+
});
|
2114
|
+
|
2115
|
+
Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
2116
|
+
/**
|
2117
|
+
@property tpl
|
2118
|
+
@default <input type="text">
|
2119
|
+
**/
|
2120
|
+
tpl: '<input type="text">',
|
2121
|
+
/**
|
2122
|
+
Placeholder attribute of input. Shown when input is empty.
|
2123
|
+
|
2124
|
+
@property placeholder
|
2125
|
+
@type string
|
2126
|
+
@default null
|
2127
|
+
**/
|
2128
|
+
placeholder: null
|
2129
|
+
});
|
2130
|
+
|
2131
|
+
$.fn.editabletypes.text = Text;
|
2132
|
+
|
2133
|
+
}(window.jQuery));
|
2134
|
+
|
2135
|
+
/**
|
2136
|
+
Textarea input
|
2137
|
+
|
2138
|
+
@class textarea
|
2139
|
+
@extends abstractinput
|
2140
|
+
@final
|
2141
|
+
@example
|
2142
|
+
<a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a>
|
2143
|
+
<script>
|
2144
|
+
$(function(){
|
2145
|
+
$('#comments').editable({
|
2146
|
+
url: '/post',
|
2147
|
+
title: 'Enter comments'
|
2148
|
+
});
|
2149
|
+
});
|
2150
|
+
</script>
|
2151
|
+
**/
|
2152
|
+
(function ($) {
|
2153
|
+
|
2154
|
+
var Textarea = function (options) {
|
2155
|
+
this.init('textarea', options, Textarea.defaults);
|
2156
|
+
};
|
2157
|
+
|
2158
|
+
$.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);
|
2159
|
+
|
2160
|
+
$.extend(Textarea.prototype, {
|
2161
|
+
render: function () {
|
2162
|
+
Textarea.superclass.render.call(this);
|
2163
|
+
|
2164
|
+
//ctrl + enter
|
2165
|
+
this.$input.keydown(function (e) {
|
2166
|
+
if (e.ctrlKey && e.which === 13) {
|
2167
|
+
$(this).closest('form').submit();
|
2168
|
+
}
|
2169
|
+
});
|
2170
|
+
},
|
2171
|
+
|
2172
|
+
value2html: function(value, element) {
|
2173
|
+
var html = '', lines;
|
2174
|
+
if(value) {
|
2175
|
+
lines = value.split("\n");
|
2176
|
+
for (var i = 0; i < lines.length; i++) {
|
2177
|
+
lines[i] = $('<div>').text(lines[i]).html();
|
2178
|
+
}
|
2179
|
+
html = lines.join('<br>');
|
2180
|
+
}
|
2181
|
+
$(element).html(html);
|
2182
|
+
},
|
2183
|
+
|
2184
|
+
html2value: function(html) {
|
2185
|
+
if(!html) {
|
2186
|
+
return '';
|
2187
|
+
}
|
2188
|
+
var lines = html.split(/<br\s*\/?>/i);
|
2189
|
+
for (var i = 0; i < lines.length; i++) {
|
2190
|
+
lines[i] = $('<div>').html(lines[i]).text();
|
2191
|
+
}
|
2192
|
+
return lines.join("\n");
|
2193
|
+
},
|
2194
|
+
|
2195
|
+
activate: function() {
|
2196
|
+
if(this.$input.is(':visible')) {
|
2197
|
+
$.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
|
2198
|
+
this.$input.focus();
|
2199
|
+
}
|
2200
|
+
}
|
2201
|
+
});
|
2202
|
+
|
2203
|
+
Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
2204
|
+
/**
|
2205
|
+
@property tpl
|
2206
|
+
@default <textarea></textarea>
|
2207
|
+
**/
|
2208
|
+
tpl:'<textarea></textarea>',
|
2209
|
+
/**
|
2210
|
+
@property inputclass
|
2211
|
+
@default input-large
|
2212
|
+
**/
|
2213
|
+
inputclass: 'input-large',
|
2214
|
+
/**
|
2215
|
+
Placeholder attribute of input. Shown when input is empty.
|
2216
|
+
|
2217
|
+
@property placeholder
|
2218
|
+
@type string
|
2219
|
+
@default null
|
2220
|
+
**/
|
2221
|
+
placeholder: null
|
2222
|
+
});
|
2223
|
+
|
2224
|
+
$.fn.editabletypes.textarea = Textarea;
|
2225
|
+
|
2226
|
+
}(window.jQuery));
|
2227
|
+
|
2228
|
+
/**
|
2229
|
+
Select (dropdown)
|
2230
|
+
|
2231
|
+
@class select
|
2232
|
+
@extends list
|
2233
|
+
@final
|
2234
|
+
@example
|
2235
|
+
<a href="#" id="status" data-type="select" data-pk="1" data-url="/post" data-original-title="Select status"></a>
|
2236
|
+
<script>
|
2237
|
+
$(function(){
|
2238
|
+
$('#status').editable({
|
2239
|
+
value: 2,
|
2240
|
+
source: [
|
2241
|
+
{value: 1, text: 'Active'},
|
2242
|
+
{value: 2, text: 'Blocked'},
|
2243
|
+
{value: 3, text: 'Deleted'}
|
2244
|
+
]
|
2245
|
+
}
|
2246
|
+
});
|
2247
|
+
});
|
2248
|
+
</script>
|
2249
|
+
**/
|
2250
|
+
(function ($) {
|
2251
|
+
|
2252
|
+
var Select = function (options) {
|
2253
|
+
this.init('select', options, Select.defaults);
|
2254
|
+
};
|
2255
|
+
|
2256
|
+
$.fn.editableutils.inherit(Select, $.fn.editabletypes.list);
|
2257
|
+
|
2258
|
+
$.extend(Select.prototype, {
|
2259
|
+
renderList: function() {
|
2260
|
+
if(!$.isArray(this.sourceData)) {
|
2261
|
+
return;
|
2262
|
+
}
|
2263
|
+
|
2264
|
+
for(var i=0; i<this.sourceData.length; i++) {
|
2265
|
+
this.$input.append($('<option>', {value: this.sourceData[i].value}).text(this.sourceData[i].text));
|
2266
|
+
}
|
2267
|
+
|
2268
|
+
//enter submit
|
2269
|
+
this.$input.on('keydown.editable', function (e) {
|
2270
|
+
if (e.which === 13) {
|
2271
|
+
$(this).closest('form').submit();
|
2272
|
+
}
|
2273
|
+
});
|
2274
|
+
},
|
2275
|
+
|
2276
|
+
value2htmlFinal: function(value, element) {
|
2277
|
+
var text = '', item = this.itemByVal(value);
|
2278
|
+
if(item) {
|
2279
|
+
text = item.text;
|
2280
|
+
}
|
2281
|
+
Select.superclass.constructor.superclass.value2html(text, element);
|
2282
|
+
},
|
2283
|
+
|
2284
|
+
autosubmit: function() {
|
2285
|
+
this.$input.off('keydown.editable').on('change.editable', function(){
|
2286
|
+
$(this).closest('form').submit();
|
2287
|
+
});
|
2288
|
+
}
|
2289
|
+
});
|
2290
|
+
|
2291
|
+
Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
|
2292
|
+
/**
|
2293
|
+
@property tpl
|
2294
|
+
@default <select></select>
|
2295
|
+
**/
|
2296
|
+
tpl:'<select></select>'
|
2297
|
+
});
|
2298
|
+
|
2299
|
+
$.fn.editabletypes.select = Select;
|
2300
|
+
|
2301
|
+
}(window.jQuery));
|
2302
|
+
/**
|
2303
|
+
List of checkboxes.
|
2304
|
+
Internally value stored as javascript array of values.
|
2305
|
+
|
2306
|
+
@class checklist
|
2307
|
+
@extends list
|
2308
|
+
@final
|
2309
|
+
@example
|
2310
|
+
<a href="#" id="options" data-type="checklist" data-pk="1" data-url="/post" data-original-title="Select options"></a>
|
2311
|
+
<script>
|
2312
|
+
$(function(){
|
2313
|
+
$('#options').editable({
|
2314
|
+
value: [2, 3],
|
2315
|
+
source: [
|
2316
|
+
{value: 1, text: 'option1'},
|
2317
|
+
{value: 2, text: 'option2'},
|
2318
|
+
{value: 3, text: 'option3'}
|
2319
|
+
]
|
2320
|
+
}
|
2321
|
+
});
|
2322
|
+
});
|
2323
|
+
</script>
|
2324
|
+
**/
|
2325
|
+
(function ($) {
|
2326
|
+
|
2327
|
+
var Checklist = function (options) {
|
2328
|
+
this.init('checklist', options, Checklist.defaults);
|
2329
|
+
};
|
2330
|
+
|
2331
|
+
$.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list);
|
2332
|
+
|
2333
|
+
$.extend(Checklist.prototype, {
|
2334
|
+
renderList: function() {
|
2335
|
+
var $label, $div;
|
2336
|
+
if(!$.isArray(this.sourceData)) {
|
2337
|
+
return;
|
2338
|
+
}
|
2339
|
+
|
2340
|
+
for(var i=0; i<this.sourceData.length; i++) {
|
2341
|
+
$label = $('<label>').append($('<input>', {
|
2342
|
+
type: 'checkbox',
|
2343
|
+
value: this.sourceData[i].value,
|
2344
|
+
name: this.options.name
|
2345
|
+
}))
|
2346
|
+
.append($('<span>').text(' '+this.sourceData[i].text));
|
2347
|
+
|
2348
|
+
$('<div>').append($label).appendTo(this.$input);
|
2349
|
+
}
|
2350
|
+
},
|
2351
|
+
|
2352
|
+
value2str: function(value) {
|
2353
|
+
return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : '';
|
2354
|
+
},
|
2355
|
+
|
2356
|
+
//parse separated string
|
2357
|
+
str2value: function(str) {
|
2358
|
+
var reg, value = null;
|
2359
|
+
if(typeof str === 'string' && str.length) {
|
2360
|
+
reg = new RegExp('\\s*'+$.trim(this.options.separator)+'\\s*');
|
2361
|
+
value = str.split(reg);
|
2362
|
+
} else if($.isArray(str)) {
|
2363
|
+
value = str;
|
2364
|
+
}
|
2365
|
+
return value;
|
2366
|
+
},
|
2367
|
+
|
2368
|
+
//set checked on required checkboxes
|
2369
|
+
value2input: function(value) {
|
2370
|
+
var $checks = this.$input.find('input[type="checkbox"]');
|
2371
|
+
$checks.removeAttr('checked');
|
2372
|
+
if($.isArray(value) && value.length) {
|
2373
|
+
$checks.each(function(i, el) {
|
2374
|
+
var $el = $(el);
|
2375
|
+
// cannot use $.inArray as it performs strict comparison
|
2376
|
+
$.each(value, function(j, val){
|
2377
|
+
/*jslint eqeq: true*/
|
2378
|
+
if($el.val() == val) {
|
2379
|
+
/*jslint eqeq: false*/
|
2380
|
+
$el.attr('checked', 'checked');
|
2381
|
+
}
|
2382
|
+
});
|
2383
|
+
});
|
2384
|
+
}
|
2385
|
+
},
|
2386
|
+
|
2387
|
+
input2value: function() {
|
2388
|
+
var checked = [];
|
2389
|
+
this.$input.find('input:checked').each(function(i, el) {
|
2390
|
+
checked.push($(el).val());
|
2391
|
+
});
|
2392
|
+
return checked;
|
2393
|
+
},
|
2394
|
+
|
2395
|
+
//collect text of checked boxes
|
2396
|
+
value2htmlFinal: function(value, element) {
|
2397
|
+
var html = [],
|
2398
|
+
/*jslint eqeq: true*/
|
2399
|
+
checked = $.grep(this.sourceData, function(o){
|
2400
|
+
return $.grep(value, function(v){ return v == o.value; }).length;
|
2401
|
+
});
|
2402
|
+
/*jslint eqeq: false*/
|
2403
|
+
if(checked.length) {
|
2404
|
+
$.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
|
2405
|
+
$(element).html(html.join('<br>'));
|
2406
|
+
} else {
|
2407
|
+
$(element).empty();
|
2408
|
+
}
|
2409
|
+
},
|
2410
|
+
|
2411
|
+
activate: function() {
|
2412
|
+
this.$input.find('input[type="checkbox"]').first().focus();
|
2413
|
+
},
|
2414
|
+
|
2415
|
+
autosubmit: function() {
|
2416
|
+
this.$input.find('input[type="checkbox"]').on('keydown', function(e){
|
2417
|
+
if (e.which === 13) {
|
2418
|
+
$(this).closest('form').submit();
|
2419
|
+
}
|
2420
|
+
});
|
2421
|
+
}
|
2422
|
+
});
|
2423
|
+
|
2424
|
+
Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
|
2425
|
+
/**
|
2426
|
+
@property tpl
|
2427
|
+
@default <div></div>
|
2428
|
+
**/
|
2429
|
+
tpl:'<div></div>',
|
2430
|
+
|
2431
|
+
/**
|
2432
|
+
@property inputclass
|
2433
|
+
@type string
|
2434
|
+
@default editable-checklist
|
2435
|
+
**/
|
2436
|
+
inputclass: 'editable-checklist',
|
2437
|
+
|
2438
|
+
/**
|
2439
|
+
Separator of values when reading from 'data-value' string
|
2440
|
+
|
2441
|
+
@property separator
|
2442
|
+
@type string
|
2443
|
+
@default ', '
|
2444
|
+
**/
|
2445
|
+
separator: ','
|
2446
|
+
});
|
2447
|
+
|
2448
|
+
$.fn.editabletypes.checklist = Checklist;
|
2449
|
+
|
2450
|
+
}(window.jQuery));
|
2451
|
+
|
2452
|
+
/**
|
2453
|
+
HTML5 input types.
|
2454
|
+
Following types are supported:
|
2455
|
+
|
2456
|
+
* password
|
2457
|
+
* email
|
2458
|
+
* url
|
2459
|
+
* tel
|
2460
|
+
* number
|
2461
|
+
* range
|
2462
|
+
|
2463
|
+
Learn more about html5 inputs:
|
2464
|
+
http://www.w3.org/wiki/HTML5_form_additions
|
2465
|
+
To check browser compatibility please see:
|
2466
|
+
https://developer.mozilla.org/en-US/docs/HTML/Element/Input
|
2467
|
+
|
2468
|
+
@class html5types
|
2469
|
+
@extends text
|
2470
|
+
@final
|
2471
|
+
@since 1.3.0
|
2472
|
+
@example
|
2473
|
+
<a href="#" id="email" data-type="email" data-pk="1">admin@example.com</a>
|
2474
|
+
<script>
|
2475
|
+
$(function(){
|
2476
|
+
$('#email').editable({
|
2477
|
+
url: '/post',
|
2478
|
+
title: 'Enter email'
|
2479
|
+
});
|
2480
|
+
});
|
2481
|
+
</script>
|
2482
|
+
**/
|
2483
|
+
|
2484
|
+
/**
|
2485
|
+
@property tpl
|
2486
|
+
@default depends on type
|
2487
|
+
**/
|
2488
|
+
|
2489
|
+
/*
|
2490
|
+
Password
|
2491
|
+
*/
|
2492
|
+
(function ($) {
|
2493
|
+
var Password = function (options) {
|
2494
|
+
this.init('password', options, Password.defaults);
|
2495
|
+
};
|
2496
|
+
$.fn.editableutils.inherit(Password, $.fn.editabletypes.text);
|
2497
|
+
$.extend(Password.prototype, {
|
2498
|
+
//do not display password, show '[hidden]' instead
|
2499
|
+
value2html: function(value, element) {
|
2500
|
+
if(value) {
|
2501
|
+
$(element).text('[hidden]');
|
2502
|
+
} else {
|
2503
|
+
$(element).empty();
|
2504
|
+
}
|
2505
|
+
},
|
2506
|
+
//as password not displayed, should not set value by html
|
2507
|
+
html2value: function(html) {
|
2508
|
+
return null;
|
2509
|
+
}
|
2510
|
+
});
|
2511
|
+
Password.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
2512
|
+
tpl: '<input type="password">'
|
2513
|
+
});
|
2514
|
+
$.fn.editabletypes.password = Password;
|
2515
|
+
}(window.jQuery));
|
2516
|
+
|
2517
|
+
|
2518
|
+
/*
|
2519
|
+
Email
|
2520
|
+
*/
|
2521
|
+
(function ($) {
|
2522
|
+
var Email = function (options) {
|
2523
|
+
this.init('email', options, Email.defaults);
|
2524
|
+
};
|
2525
|
+
$.fn.editableutils.inherit(Email, $.fn.editabletypes.text);
|
2526
|
+
Email.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
2527
|
+
tpl: '<input type="email">'
|
2528
|
+
});
|
2529
|
+
$.fn.editabletypes.email = Email;
|
2530
|
+
}(window.jQuery));
|
2531
|
+
|
2532
|
+
|
2533
|
+
/*
|
2534
|
+
Url
|
2535
|
+
*/
|
2536
|
+
(function ($) {
|
2537
|
+
var Url = function (options) {
|
2538
|
+
this.init('url', options, Url.defaults);
|
2539
|
+
};
|
2540
|
+
$.fn.editableutils.inherit(Url, $.fn.editabletypes.text);
|
2541
|
+
Url.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
2542
|
+
tpl: '<input type="url">'
|
2543
|
+
});
|
2544
|
+
$.fn.editabletypes.url = Url;
|
2545
|
+
}(window.jQuery));
|
2546
|
+
|
2547
|
+
|
2548
|
+
/*
|
2549
|
+
Tel
|
2550
|
+
*/
|
2551
|
+
(function ($) {
|
2552
|
+
var Tel = function (options) {
|
2553
|
+
this.init('tel', options, Tel.defaults);
|
2554
|
+
};
|
2555
|
+
$.fn.editableutils.inherit(Tel, $.fn.editabletypes.text);
|
2556
|
+
Tel.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
2557
|
+
tpl: '<input type="tel">'
|
2558
|
+
});
|
2559
|
+
$.fn.editabletypes.tel = Tel;
|
2560
|
+
}(window.jQuery));
|
2561
|
+
|
2562
|
+
|
2563
|
+
/*
|
2564
|
+
Number
|
2565
|
+
*/
|
2566
|
+
(function ($) {
|
2567
|
+
var NumberInput = function (options) {
|
2568
|
+
this.init('number', options, NumberInput.defaults);
|
2569
|
+
};
|
2570
|
+
$.fn.editableutils.inherit(NumberInput, $.fn.editabletypes.text);
|
2571
|
+
$.extend(NumberInput.prototype, {
|
2572
|
+
render: function () {
|
2573
|
+
NumberInput.superclass.render.call(this);
|
2574
|
+
|
2575
|
+
if (this.options.min !== null) {
|
2576
|
+
this.$input.attr('min', this.options.min);
|
2577
|
+
}
|
2578
|
+
|
2579
|
+
if (this.options.max !== null) {
|
2580
|
+
this.$input.attr('max', this.options.max);
|
2581
|
+
}
|
2582
|
+
|
2583
|
+
if (this.options.step !== null) {
|
2584
|
+
this.$input.attr('step', this.options.step);
|
2585
|
+
}
|
2586
|
+
}
|
2587
|
+
});
|
2588
|
+
NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
2589
|
+
tpl: '<input type="number">',
|
2590
|
+
inputclass: 'input-mini',
|
2591
|
+
min: null,
|
2592
|
+
max: null,
|
2593
|
+
step: null
|
2594
|
+
});
|
2595
|
+
$.fn.editabletypes.number = NumberInput;
|
2596
|
+
}(window.jQuery));
|
2597
|
+
|
2598
|
+
|
2599
|
+
/*
|
2600
|
+
Range (inherit from number)
|
2601
|
+
*/
|
2602
|
+
(function ($) {
|
2603
|
+
var Range = function (options) {
|
2604
|
+
this.init('range', options, Range.defaults);
|
2605
|
+
};
|
2606
|
+
$.fn.editableutils.inherit(Range, $.fn.editabletypes.number);
|
2607
|
+
$.extend(Range.prototype, {
|
2608
|
+
render: function () {
|
2609
|
+
this.$input = $(this.options.tpl);
|
2610
|
+
var $slider = this.$input.filter('input');
|
2611
|
+
if(this.options.inputclass) {
|
2612
|
+
$slider.addClass(this.options.inputclass);
|
2613
|
+
}
|
2614
|
+
if (this.options.min !== null) {
|
2615
|
+
$slider.attr('min', this.options.min);
|
2616
|
+
}
|
2617
|
+
|
2618
|
+
if (this.options.max !== null) {
|
2619
|
+
$slider.attr('max', this.options.max);
|
2620
|
+
}
|
2621
|
+
|
2622
|
+
if (this.options.step !== null) {
|
2623
|
+
$slider.attr('step', this.options.step);
|
2624
|
+
}
|
2625
|
+
|
2626
|
+
$slider.on('input', function(){
|
2627
|
+
$(this).siblings('output').text($(this).val());
|
2628
|
+
});
|
2629
|
+
},
|
2630
|
+
activate: function() {
|
2631
|
+
this.$input.filter('input').focus();
|
2632
|
+
}
|
2633
|
+
});
|
2634
|
+
Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, {
|
2635
|
+
tpl: '<input type="range"><output style="width: 30px; display: inline-block"></output>',
|
2636
|
+
inputclass: 'input-medium'
|
2637
|
+
});
|
2638
|
+
$.fn.editabletypes.range = Range;
|
2639
|
+
}(window.jQuery));
|
2640
|
+
/*
|
2641
|
+
Editableform based on Twitter Bootstrap
|
2642
|
+
*/
|
2643
|
+
(function ($) {
|
2644
|
+
|
2645
|
+
$.extend($.fn.editableform.Constructor.prototype, {
|
2646
|
+
initTemplate: function() {
|
2647
|
+
this.$form = $($.fn.editableform.template);
|
2648
|
+
this.$form.find('.editable-error-block').addClass('help-block');
|
2649
|
+
}
|
2650
|
+
});
|
2651
|
+
|
2652
|
+
//buttons
|
2653
|
+
$.fn.editableform.buttons = '<button type="submit" class="btn btn-primary editable-submit"><i class="icon-ok icon-white"></i></button>'+
|
2654
|
+
'<button type="button" class="btn editable-cancel"><i class="icon-remove"></i></button>';
|
2655
|
+
|
2656
|
+
//error classes
|
2657
|
+
$.fn.editableform.errorGroupClass = 'error';
|
2658
|
+
$.fn.editableform.errorBlockClass = null;
|
2659
|
+
|
2660
|
+
}(window.jQuery));
|
2661
|
+
/**
|
2662
|
+
* Editable Popover
|
2663
|
+
* ---------------------
|
2664
|
+
* requires bootstrap-popover.js
|
2665
|
+
*/
|
2666
|
+
(function ($) {
|
2667
|
+
|
2668
|
+
//extend methods
|
2669
|
+
$.extend($.fn.editableContainer.Constructor.prototype, {
|
2670
|
+
containerName: 'popover',
|
2671
|
+
//for compatibility with bootstrap <= 2.2.1 (content inserted into <p> instead of directly .popover-content)
|
2672
|
+
innerCss: $($.fn.popover.defaults.template).find('p').length ? '.popover-content p' : '.popover-content',
|
2673
|
+
|
2674
|
+
initContainer: function(){
|
2675
|
+
$.extend(this.containerOptions, {
|
2676
|
+
trigger: 'manual',
|
2677
|
+
selector: false,
|
2678
|
+
content: ' '
|
2679
|
+
});
|
2680
|
+
this.call(this.containerOptions);
|
2681
|
+
},
|
2682
|
+
|
2683
|
+
setContainerOption: function(key, value) {
|
2684
|
+
this.container().options[key] = value;
|
2685
|
+
},
|
2686
|
+
|
2687
|
+
/**
|
2688
|
+
* move popover to new position. This function mainly copied from bootstrap-popover.
|
2689
|
+
*/
|
2690
|
+
/*jshint laxcomma: true*/
|
2691
|
+
setPosition: function () {
|
2692
|
+
|
2693
|
+
(function() {
|
2694
|
+
var $tip = this.tip()
|
2695
|
+
, inside
|
2696
|
+
, pos
|
2697
|
+
, actualWidth
|
2698
|
+
, actualHeight
|
2699
|
+
, placement
|
2700
|
+
, tp;
|
2701
|
+
|
2702
|
+
placement = typeof this.options.placement === 'function' ?
|
2703
|
+
this.options.placement.call(this, $tip[0], this.$element[0]) :
|
2704
|
+
this.options.placement;
|
2705
|
+
|
2706
|
+
inside = /in/.test(placement);
|
2707
|
+
|
2708
|
+
$tip
|
2709
|
+
// .detach()
|
2710
|
+
//vitalets: remove any placement class because otherwise they dont influence on re-positioning of visible popover
|
2711
|
+
.removeClass('top right bottom left')
|
2712
|
+
.css({ top: 0, left: 0, display: 'block' });
|
2713
|
+
// .insertAfter(this.$element);
|
2714
|
+
|
2715
|
+
pos = this.getPosition(inside);
|
2716
|
+
|
2717
|
+
actualWidth = $tip[0].offsetWidth;
|
2718
|
+
actualHeight = $tip[0].offsetHeight;
|
2719
|
+
|
2720
|
+
switch (inside ? placement.split(' ')[1] : placement) {
|
2721
|
+
case 'bottom':
|
2722
|
+
tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2};
|
2723
|
+
break;
|
2724
|
+
case 'top':
|
2725
|
+
tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2};
|
2726
|
+
break;
|
2727
|
+
case 'left':
|
2728
|
+
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth};
|
2729
|
+
break;
|
2730
|
+
case 'right':
|
2731
|
+
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width};
|
2732
|
+
break;
|
2733
|
+
}
|
2734
|
+
|
2735
|
+
$tip
|
2736
|
+
.offset(tp)
|
2737
|
+
.addClass(placement)
|
2738
|
+
.addClass('in');
|
2739
|
+
|
2740
|
+
}).call(this.container());
|
2741
|
+
/*jshint laxcomma: false*/
|
2742
|
+
}
|
2743
|
+
});
|
2744
|
+
|
2745
|
+
//defaults
|
2746
|
+
/*
|
2747
|
+
$.fn.editableContainer.defaults = $.extend({}, $.fn.popover.defaults, $.fn.editableContainer.defaults, {
|
2748
|
+
|
2749
|
+
});
|
2750
|
+
*/
|
2751
|
+
|
2752
|
+
}(window.jQuery));
|
2753
|
+
/**
|
2754
|
+
Bootstrap-datepicker.
|
2755
|
+
Description and examples: http://vitalets.github.com/bootstrap-datepicker.
|
2756
|
+
For localization you can include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
|
2757
|
+
|
2758
|
+
@class date
|
2759
|
+
@extends abstractinput
|
2760
|
+
@final
|
2761
|
+
@example
|
2762
|
+
<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a>
|
2763
|
+
<script>
|
2764
|
+
$(function(){
|
2765
|
+
$('#dob').editable({
|
2766
|
+
format: 'yyyy-mm-dd',
|
2767
|
+
viewformat: 'dd/mm/yyyy',
|
2768
|
+
datepicker: {
|
2769
|
+
weekStart: 1
|
2770
|
+
}
|
2771
|
+
}
|
2772
|
+
});
|
2773
|
+
});
|
2774
|
+
</script>
|
2775
|
+
**/
|
2776
|
+
(function ($) {
|
2777
|
+
|
2778
|
+
var Date = function (options) {
|
2779
|
+
this.init('date', options, Date.defaults);
|
2780
|
+
|
2781
|
+
//set popular options directly from settings or data-* attributes
|
2782
|
+
var directOptions = $.fn.editableutils.sliceObj(this.options, ['format']);
|
2783
|
+
|
2784
|
+
//overriding datepicker config (as by default jQuery extend() is not recursive)
|
2785
|
+
this.options.datepicker = $.extend({}, Date.defaults.datepicker, directOptions, options.datepicker);
|
2786
|
+
|
2787
|
+
//by default viewformat equals to format
|
2788
|
+
if(!this.options.viewformat) {
|
2789
|
+
this.options.viewformat = this.options.datepicker.format;
|
2790
|
+
}
|
2791
|
+
|
2792
|
+
//language
|
2793
|
+
this.options.datepicker.language = this.options.datepicker.language || 'en';
|
2794
|
+
|
2795
|
+
//store DPglobal
|
2796
|
+
this.dpg = $.fn.datepicker.DPGlobal;
|
2797
|
+
|
2798
|
+
//store parsed formats
|
2799
|
+
this.parsedFormat = this.dpg.parseFormat(this.options.datepicker.format);
|
2800
|
+
this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
|
2801
|
+
};
|
2802
|
+
|
2803
|
+
$.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
|
2804
|
+
|
2805
|
+
$.extend(Date.prototype, {
|
2806
|
+
render: function () {
|
2807
|
+
Date.superclass.render.call(this);
|
2808
|
+
this.$input.datepicker(this.options.datepicker);
|
2809
|
+
|
2810
|
+
if(this.options.clear) {
|
2811
|
+
this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
|
2812
|
+
e.preventDefault();
|
2813
|
+
e.stopPropagation();
|
2814
|
+
this.clear();
|
2815
|
+
}, this));
|
2816
|
+
}
|
2817
|
+
},
|
2818
|
+
|
2819
|
+
value2html: function(value, element) {
|
2820
|
+
var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
|
2821
|
+
Date.superclass.value2html(text, element);
|
2822
|
+
},
|
2823
|
+
|
2824
|
+
html2value: function(html) {
|
2825
|
+
return html ? this.dpg.parseDate(html, this.parsedViewFormat, this.options.datepicker.language) : null;
|
2826
|
+
},
|
2827
|
+
|
2828
|
+
value2str: function(value) {
|
2829
|
+
return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
|
2830
|
+
},
|
2831
|
+
|
2832
|
+
str2value: function(str) {
|
2833
|
+
return str ? this.dpg.parseDate(str, this.parsedFormat, this.options.datepicker.language) : null;
|
2834
|
+
},
|
2835
|
+
|
2836
|
+
value2submit: function(value) {
|
2837
|
+
return this.value2str(value);
|
2838
|
+
},
|
2839
|
+
|
2840
|
+
value2input: function(value) {
|
2841
|
+
this.$input.datepicker('update', value);
|
2842
|
+
},
|
2843
|
+
|
2844
|
+
input2value: function() {
|
2845
|
+
return this.$input.data('datepicker').date;
|
2846
|
+
},
|
2847
|
+
|
2848
|
+
activate: function() {
|
2849
|
+
},
|
2850
|
+
|
2851
|
+
clear: function() {
|
2852
|
+
this.$input.data('datepicker').date = null;
|
2853
|
+
this.$input.find('.active').removeClass('active');
|
2854
|
+
},
|
2855
|
+
|
2856
|
+
autosubmit: function() {
|
2857
|
+
this.$input.on('changeDate', function(e){
|
2858
|
+
var $form = $(this).closest('form');
|
2859
|
+
setTimeout(function() {
|
2860
|
+
$form.submit();
|
2861
|
+
}, 200);
|
2862
|
+
});
|
2863
|
+
}
|
2864
|
+
|
2865
|
+
});
|
2866
|
+
|
2867
|
+
Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
2868
|
+
/**
|
2869
|
+
@property tpl
|
2870
|
+
@default <div></div>
|
2871
|
+
**/
|
2872
|
+
tpl:'<div></div>',
|
2873
|
+
/**
|
2874
|
+
@property inputclass
|
2875
|
+
@default editable-date well
|
2876
|
+
**/
|
2877
|
+
inputclass: 'editable-date well',
|
2878
|
+
/**
|
2879
|
+
Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
|
2880
|
+
Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code>
|
2881
|
+
|
2882
|
+
@property format
|
2883
|
+
@type string
|
2884
|
+
@default yyyy-mm-dd
|
2885
|
+
**/
|
2886
|
+
format:'yyyy-mm-dd',
|
2887
|
+
/**
|
2888
|
+
Format used for displaying date. Also applied when converting date from element's text on init.
|
2889
|
+
If not specified equals to <code>format</code>
|
2890
|
+
|
2891
|
+
@property viewformat
|
2892
|
+
@type string
|
2893
|
+
@default null
|
2894
|
+
**/
|
2895
|
+
viewformat: null,
|
2896
|
+
/**
|
2897
|
+
Configuration of datepicker.
|
2898
|
+
Full list of options: http://vitalets.github.com/bootstrap-datepicker
|
2899
|
+
|
2900
|
+
@property datepicker
|
2901
|
+
@type object
|
2902
|
+
@default {
|
2903
|
+
weekStart: 0,
|
2904
|
+
startView: 0,
|
2905
|
+
autoclose: false
|
2906
|
+
}
|
2907
|
+
**/
|
2908
|
+
datepicker:{
|
2909
|
+
weekStart: 0,
|
2910
|
+
startView: 0,
|
2911
|
+
autoclose: false
|
2912
|
+
},
|
2913
|
+
/**
|
2914
|
+
Text shown as clear date button.
|
2915
|
+
If <code>false</code> clear button will not be rendered.
|
2916
|
+
|
2917
|
+
@property clear
|
2918
|
+
@type boolean|string
|
2919
|
+
@default 'x clear'
|
2920
|
+
**/
|
2921
|
+
clear: '× clear'
|
2922
|
+
});
|
2923
|
+
|
2924
|
+
$.fn.editabletypes.date = Date;
|
2925
|
+
|
2926
|
+
}(window.jQuery));
|
2927
|
+
|
2928
|
+
/* =========================================================
|
2929
|
+
* bootstrap-datepicker.js
|
2930
|
+
* http://www.eyecon.ro/bootstrap-datepicker
|
2931
|
+
* =========================================================
|
2932
|
+
* Copyright 2012 Stefan Petre
|
2933
|
+
* Improvements by Andrew Rowls
|
2934
|
+
*
|
2935
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
2936
|
+
* you may not use this file except in compliance with the License.
|
2937
|
+
* You may obtain a copy of the License at
|
2938
|
+
*
|
2939
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
2940
|
+
*
|
2941
|
+
* Unless required by applicable law or agreed to in writing, software
|
2942
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
2943
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
2944
|
+
* See the License for the specific language governing permissions and
|
2945
|
+
* limitations under the License.
|
2946
|
+
* ========================================================= */
|
2947
|
+
|
2948
|
+
!function( $ ) {
|
2949
|
+
|
2950
|
+
function UTCDate(){
|
2951
|
+
return new Date(Date.UTC.apply(Date, arguments));
|
2952
|
+
}
|
2953
|
+
function UTCToday(){
|
2954
|
+
var today = new Date();
|
2955
|
+
return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
|
2956
|
+
}
|
2957
|
+
|
2958
|
+
// Picker object
|
2959
|
+
|
2960
|
+
var Datepicker = function(element, options) {
|
2961
|
+
var that = this;
|
2962
|
+
|
2963
|
+
this.element = $(element);
|
2964
|
+
this.language = options.language||this.element.data('date-language')||"en";
|
2965
|
+
this.language = this.language in dates ? this.language : "en";
|
2966
|
+
this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
|
2967
|
+
this.isInline = false;
|
2968
|
+
this.isInput = this.element.is('input');
|
2969
|
+
this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
|
2970
|
+
this.hasInput = this.component && this.element.find('input').length;
|
2971
|
+
if(this.component && this.component.length === 0)
|
2972
|
+
this.component = false;
|
2973
|
+
|
2974
|
+
if (this.isInput) { //single input
|
2975
|
+
this.element.on({
|
2976
|
+
focus: $.proxy(this.show, this),
|
2977
|
+
keyup: $.proxy(this.update, this),
|
2978
|
+
keydown: $.proxy(this.keydown, this)
|
2979
|
+
});
|
2980
|
+
} else if(this.component && this.hasInput) { //component: input + button
|
2981
|
+
// For components that are not readonly, allow keyboard nav
|
2982
|
+
this.element.find('input').on({
|
2983
|
+
focus: $.proxy(this.show, this),
|
2984
|
+
keyup: $.proxy(this.update, this),
|
2985
|
+
keydown: $.proxy(this.keydown, this)
|
2986
|
+
});
|
2987
|
+
|
2988
|
+
this.component.on('click', $.proxy(this.show, this));
|
2989
|
+
} else if(this.element.is('div')) { //inline datepicker
|
2990
|
+
this.isInline = true;
|
2991
|
+
} else {
|
2992
|
+
this.element.on('click', $.proxy(this.show, this));
|
2993
|
+
}
|
2994
|
+
|
2995
|
+
this.picker = $(DPGlobal.template)
|
2996
|
+
.appendTo(this.isInline ? this.element : 'body')
|
2997
|
+
.on({
|
2998
|
+
click: $.proxy(this.click, this),
|
2999
|
+
mousedown: $.proxy(this.mousedown, this)
|
3000
|
+
});
|
3001
|
+
|
3002
|
+
if(this.isInline) {
|
3003
|
+
this.picker.addClass('datepicker-inline');
|
3004
|
+
} else {
|
3005
|
+
this.picker.addClass('dropdown-menu');
|
3006
|
+
}
|
3007
|
+
|
3008
|
+
$(document).on('mousedown', function (e) {
|
3009
|
+
// Clicked outside the datepicker, hide it
|
3010
|
+
if ($(e.target).closest('.datepicker').length == 0) {
|
3011
|
+
that.hide();
|
3012
|
+
}
|
3013
|
+
});
|
3014
|
+
|
3015
|
+
this.autoclose = false;
|
3016
|
+
if ('autoclose' in options) {
|
3017
|
+
this.autoclose = options.autoclose;
|
3018
|
+
} else if ('dateAutoclose' in this.element.data()) {
|
3019
|
+
this.autoclose = this.element.data('date-autoclose');
|
3020
|
+
}
|
3021
|
+
|
3022
|
+
this.keyboardNavigation = true;
|
3023
|
+
if ('keyboardNavigation' in options) {
|
3024
|
+
this.keyboardNavigation = options.keyboardNavigation;
|
3025
|
+
} else if ('dateKeyboardNavigation' in this.element.data()) {
|
3026
|
+
this.keyboardNavigation = this.element.data('date-keyboard-navigation');
|
3027
|
+
}
|
3028
|
+
|
3029
|
+
switch(options.startView || this.element.data('date-start-view')){
|
3030
|
+
case 2:
|
3031
|
+
case 'decade':
|
3032
|
+
this.viewMode = this.startViewMode = 2;
|
3033
|
+
break;
|
3034
|
+
case 1:
|
3035
|
+
case 'year':
|
3036
|
+
this.viewMode = this.startViewMode = 1;
|
3037
|
+
break;
|
3038
|
+
case 0:
|
3039
|
+
case 'month':
|
3040
|
+
default:
|
3041
|
+
this.viewMode = this.startViewMode = 0;
|
3042
|
+
break;
|
3043
|
+
}
|
3044
|
+
|
3045
|
+
this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false);
|
3046
|
+
this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false);
|
3047
|
+
|
3048
|
+
this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7);
|
3049
|
+
this.weekEnd = ((this.weekStart + 6) % 7);
|
3050
|
+
this.startDate = -Infinity;
|
3051
|
+
this.endDate = Infinity;
|
3052
|
+
this.setStartDate(options.startDate||this.element.data('date-startdate'));
|
3053
|
+
this.setEndDate(options.endDate||this.element.data('date-enddate'));
|
3054
|
+
this.fillDow();
|
3055
|
+
this.fillMonths();
|
3056
|
+
this.update();
|
3057
|
+
this.showMode();
|
3058
|
+
|
3059
|
+
if(this.isInline) {
|
3060
|
+
this.show();
|
3061
|
+
}
|
3062
|
+
};
|
3063
|
+
|
3064
|
+
Datepicker.prototype = {
|
3065
|
+
constructor: Datepicker,
|
3066
|
+
|
3067
|
+
show: function(e) {
|
3068
|
+
this.picker.show();
|
3069
|
+
this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
|
3070
|
+
this.update();
|
3071
|
+
this.place();
|
3072
|
+
$(window).on('resize', $.proxy(this.place, this));
|
3073
|
+
if (e ) {
|
3074
|
+
e.stopPropagation();
|
3075
|
+
e.preventDefault();
|
3076
|
+
}
|
3077
|
+
this.element.trigger({
|
3078
|
+
type: 'show',
|
3079
|
+
date: this.date
|
3080
|
+
});
|
3081
|
+
},
|
3082
|
+
|
3083
|
+
hide: function(e){
|
3084
|
+
if(this.isInline) return;
|
3085
|
+
this.picker.hide();
|
3086
|
+
$(window).off('resize', this.place);
|
3087
|
+
this.viewMode = this.startViewMode;
|
3088
|
+
this.showMode();
|
3089
|
+
if (!this.isInput) {
|
3090
|
+
$(document).off('mousedown', this.hide);
|
3091
|
+
}
|
3092
|
+
if (e && e.currentTarget.value)
|
3093
|
+
this.setValue();
|
3094
|
+
this.element.trigger({
|
3095
|
+
type: 'hide',
|
3096
|
+
date: this.date
|
3097
|
+
});
|
3098
|
+
},
|
3099
|
+
|
3100
|
+
getDate: function() {
|
3101
|
+
var d = this.getUTCDate();
|
3102
|
+
return new Date(d.getTime() + (d.getTimezoneOffset()*60000))
|
3103
|
+
},
|
3104
|
+
|
3105
|
+
getUTCDate: function() {
|
3106
|
+
return this.date;
|
3107
|
+
},
|
3108
|
+
|
3109
|
+
setDate: function(d) {
|
3110
|
+
this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000)));
|
3111
|
+
},
|
3112
|
+
|
3113
|
+
setUTCDate: function(d) {
|
3114
|
+
this.date = d;
|
3115
|
+
this.setValue();
|
3116
|
+
},
|
3117
|
+
|
3118
|
+
setValue: function() {
|
3119
|
+
var formatted = this.getFormattedDate();
|
3120
|
+
if (!this.isInput) {
|
3121
|
+
if (this.component){
|
3122
|
+
this.element.find('input').prop('value', formatted);
|
3123
|
+
}
|
3124
|
+
this.element.data('date', formatted);
|
3125
|
+
} else {
|
3126
|
+
this.element.prop('value', formatted);
|
3127
|
+
}
|
3128
|
+
},
|
3129
|
+
|
3130
|
+
getFormattedDate: function(format) {
|
3131
|
+
if(format == undefined) format = this.format;
|
3132
|
+
return DPGlobal.formatDate(this.date, format, this.language);
|
3133
|
+
},
|
3134
|
+
|
3135
|
+
setStartDate: function(startDate){
|
3136
|
+
this.startDate = startDate||-Infinity;
|
3137
|
+
if (this.startDate !== -Infinity) {
|
3138
|
+
this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language);
|
3139
|
+
}
|
3140
|
+
this.update();
|
3141
|
+
this.updateNavArrows();
|
3142
|
+
},
|
3143
|
+
|
3144
|
+
setEndDate: function(endDate){
|
3145
|
+
this.endDate = endDate||Infinity;
|
3146
|
+
if (this.endDate !== Infinity) {
|
3147
|
+
this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language);
|
3148
|
+
}
|
3149
|
+
this.update();
|
3150
|
+
this.updateNavArrows();
|
3151
|
+
},
|
3152
|
+
|
3153
|
+
place: function(){
|
3154
|
+
if(this.isInline) return;
|
3155
|
+
var zIndex = parseInt(this.element.parents().filter(function() {
|
3156
|
+
return $(this).css('z-index') != 'auto';
|
3157
|
+
}).first().css('z-index'))+10;
|
3158
|
+
var offset = this.component ? this.component.offset() : this.element.offset();
|
3159
|
+
this.picker.css({
|
3160
|
+
top: offset.top + this.height,
|
3161
|
+
left: offset.left,
|
3162
|
+
zIndex: zIndex
|
3163
|
+
});
|
3164
|
+
},
|
3165
|
+
|
3166
|
+
update: function(){
|
3167
|
+
var date, fromArgs = false;
|
3168
|
+
if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
|
3169
|
+
date = arguments[0];
|
3170
|
+
fromArgs = true;
|
3171
|
+
} else {
|
3172
|
+
date = this.isInput ? this.element.prop('value') : this.element.data('date') || this.element.find('input').prop('value');
|
3173
|
+
}
|
3174
|
+
|
3175
|
+
this.date = DPGlobal.parseDate(date, this.format, this.language);
|
3176
|
+
|
3177
|
+
if(fromArgs) this.setValue();
|
3178
|
+
|
3179
|
+
if (this.date < this.startDate) {
|
3180
|
+
this.viewDate = new Date(this.startDate);
|
3181
|
+
} else if (this.date > this.endDate) {
|
3182
|
+
this.viewDate = new Date(this.endDate);
|
3183
|
+
} else {
|
3184
|
+
this.viewDate = new Date(this.date);
|
3185
|
+
}
|
3186
|
+
this.fill();
|
3187
|
+
},
|
3188
|
+
|
3189
|
+
fillDow: function(){
|
3190
|
+
var dowCnt = this.weekStart;
|
3191
|
+
var html = '<tr>';
|
3192
|
+
while (dowCnt < this.weekStart + 7) {
|
3193
|
+
html += '<th class="dow">'+dates[this.language].daysMin[(dowCnt++)%7]+'</th>';
|
3194
|
+
}
|
3195
|
+
html += '</tr>';
|
3196
|
+
this.picker.find('.datepicker-days thead').append(html);
|
3197
|
+
},
|
3198
|
+
|
3199
|
+
fillMonths: function(){
|
3200
|
+
var html = '';
|
3201
|
+
var i = 0
|
3202
|
+
while (i < 12) {
|
3203
|
+
html += '<span class="month">'+dates[this.language].monthsShort[i++]+'</span>';
|
3204
|
+
}
|
3205
|
+
this.picker.find('.datepicker-months td').html(html);
|
3206
|
+
},
|
3207
|
+
|
3208
|
+
fill: function() {
|
3209
|
+
var d = new Date(this.viewDate),
|
3210
|
+
year = d.getUTCFullYear(),
|
3211
|
+
month = d.getUTCMonth(),
|
3212
|
+
startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
|
3213
|
+
startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
|
3214
|
+
endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
|
3215
|
+
endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
|
3216
|
+
currentDate = this.date && this.date.valueOf(),
|
3217
|
+
today = new Date();
|
3218
|
+
this.picker.find('.datepicker-days thead th:eq(1)')
|
3219
|
+
.text(dates[this.language].months[month]+' '+year);
|
3220
|
+
this.picker.find('tfoot th.today')
|
3221
|
+
.text(dates[this.language].today)
|
3222
|
+
.toggle(this.todayBtn !== false);
|
3223
|
+
this.updateNavArrows();
|
3224
|
+
this.fillMonths();
|
3225
|
+
var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
|
3226
|
+
day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
|
3227
|
+
prevMonth.setUTCDate(day);
|
3228
|
+
prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7);
|
3229
|
+
var nextMonth = new Date(prevMonth);
|
3230
|
+
nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
|
3231
|
+
nextMonth = nextMonth.valueOf();
|
3232
|
+
var html = [];
|
3233
|
+
var clsName;
|
3234
|
+
while(prevMonth.valueOf() < nextMonth) {
|
3235
|
+
if (prevMonth.getUTCDay() == this.weekStart) {
|
3236
|
+
html.push('<tr>');
|
3237
|
+
}
|
3238
|
+
clsName = '';
|
3239
|
+
if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
|
3240
|
+
clsName += ' old';
|
3241
|
+
} else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
|
3242
|
+
clsName += ' new';
|
3243
|
+
}
|
3244
|
+
// Compare internal UTC date with local today, not UTC today
|
3245
|
+
if (this.todayHighlight &&
|
3246
|
+
prevMonth.getUTCFullYear() == today.getFullYear() &&
|
3247
|
+
prevMonth.getUTCMonth() == today.getMonth() &&
|
3248
|
+
prevMonth.getUTCDate() == today.getDate()) {
|
3249
|
+
clsName += ' today';
|
3250
|
+
}
|
3251
|
+
if (currentDate && prevMonth.valueOf() == currentDate) {
|
3252
|
+
clsName += ' active';
|
3253
|
+
}
|
3254
|
+
if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate) {
|
3255
|
+
clsName += ' disabled';
|
3256
|
+
}
|
3257
|
+
html.push('<td class="day'+clsName+'">'+prevMonth.getUTCDate() + '</td>');
|
3258
|
+
if (prevMonth.getUTCDay() == this.weekEnd) {
|
3259
|
+
html.push('</tr>');
|
3260
|
+
}
|
3261
|
+
prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
|
3262
|
+
}
|
3263
|
+
this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
|
3264
|
+
var currentYear = this.date && this.date.getUTCFullYear();
|
3265
|
+
|
3266
|
+
var months = this.picker.find('.datepicker-months')
|
3267
|
+
.find('th:eq(1)')
|
3268
|
+
.text(year)
|
3269
|
+
.end()
|
3270
|
+
.find('span').removeClass('active');
|
3271
|
+
if (currentYear && currentYear == year) {
|
3272
|
+
months.eq(this.date.getUTCMonth()).addClass('active');
|
3273
|
+
}
|
3274
|
+
if (year < startYear || year > endYear) {
|
3275
|
+
months.addClass('disabled');
|
3276
|
+
}
|
3277
|
+
if (year == startYear) {
|
3278
|
+
months.slice(0, startMonth).addClass('disabled');
|
3279
|
+
}
|
3280
|
+
if (year == endYear) {
|
3281
|
+
months.slice(endMonth+1).addClass('disabled');
|
3282
|
+
}
|
3283
|
+
|
3284
|
+
html = '';
|
3285
|
+
year = parseInt(year/10, 10) * 10;
|
3286
|
+
var yearCont = this.picker.find('.datepicker-years')
|
3287
|
+
.find('th:eq(1)')
|
3288
|
+
.text(year + '-' + (year + 9))
|
3289
|
+
.end()
|
3290
|
+
.find('td');
|
3291
|
+
year -= 1;
|
3292
|
+
for (var i = -1; i < 11; i++) {
|
3293
|
+
html += '<span class="year'+(i == -1 || i == 10 ? ' old' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
|
3294
|
+
year += 1;
|
3295
|
+
}
|
3296
|
+
yearCont.html(html);
|
3297
|
+
},
|
3298
|
+
|
3299
|
+
updateNavArrows: function() {
|
3300
|
+
var d = new Date(this.viewDate),
|
3301
|
+
year = d.getUTCFullYear(),
|
3302
|
+
month = d.getUTCMonth();
|
3303
|
+
switch (this.viewMode) {
|
3304
|
+
case 0:
|
3305
|
+
if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) {
|
3306
|
+
this.picker.find('.prev').css({visibility: 'hidden'});
|
3307
|
+
} else {
|
3308
|
+
this.picker.find('.prev').css({visibility: 'visible'});
|
3309
|
+
}
|
3310
|
+
if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) {
|
3311
|
+
this.picker.find('.next').css({visibility: 'hidden'});
|
3312
|
+
} else {
|
3313
|
+
this.picker.find('.next').css({visibility: 'visible'});
|
3314
|
+
}
|
3315
|
+
break;
|
3316
|
+
case 1:
|
3317
|
+
case 2:
|
3318
|
+
if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
|
3319
|
+
this.picker.find('.prev').css({visibility: 'hidden'});
|
3320
|
+
} else {
|
3321
|
+
this.picker.find('.prev').css({visibility: 'visible'});
|
3322
|
+
}
|
3323
|
+
if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
|
3324
|
+
this.picker.find('.next').css({visibility: 'hidden'});
|
3325
|
+
} else {
|
3326
|
+
this.picker.find('.next').css({visibility: 'visible'});
|
3327
|
+
}
|
3328
|
+
break;
|
3329
|
+
}
|
3330
|
+
},
|
3331
|
+
|
3332
|
+
click: function(e) {
|
3333
|
+
e.stopPropagation();
|
3334
|
+
e.preventDefault();
|
3335
|
+
var target = $(e.target).closest('span, td, th');
|
3336
|
+
if (target.length == 1) {
|
3337
|
+
switch(target[0].nodeName.toLowerCase()) {
|
3338
|
+
case 'th':
|
3339
|
+
switch(target[0].className) {
|
3340
|
+
case 'switch':
|
3341
|
+
this.showMode(1);
|
3342
|
+
break;
|
3343
|
+
case 'prev':
|
3344
|
+
case 'next':
|
3345
|
+
var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
|
3346
|
+
switch(this.viewMode){
|
3347
|
+
case 0:
|
3348
|
+
this.viewDate = this.moveMonth(this.viewDate, dir);
|
3349
|
+
break;
|
3350
|
+
case 1:
|
3351
|
+
case 2:
|
3352
|
+
this.viewDate = this.moveYear(this.viewDate, dir);
|
3353
|
+
break;
|
3354
|
+
}
|
3355
|
+
this.fill();
|
3356
|
+
break;
|
3357
|
+
case 'today':
|
3358
|
+
var date = new Date();
|
3359
|
+
date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
|
3360
|
+
|
3361
|
+
this.showMode(-2);
|
3362
|
+
var which = this.todayBtn == 'linked' ? null : 'view';
|
3363
|
+
this._setDate(date, which);
|
3364
|
+
break;
|
3365
|
+
}
|
3366
|
+
break;
|
3367
|
+
case 'span':
|
3368
|
+
if (!target.is('.disabled')) {
|
3369
|
+
this.viewDate.setUTCDate(1);
|
3370
|
+
if (target.is('.month')) {
|
3371
|
+
var month = target.parent().find('span').index(target);
|
3372
|
+
this.viewDate.setUTCMonth(month);
|
3373
|
+
this.element.trigger({
|
3374
|
+
type: 'changeMonth',
|
3375
|
+
date: this.viewDate
|
3376
|
+
});
|
3377
|
+
} else {
|
3378
|
+
var year = parseInt(target.text(), 10)||0;
|
3379
|
+
this.viewDate.setUTCFullYear(year);
|
3380
|
+
this.element.trigger({
|
3381
|
+
type: 'changeYear',
|
3382
|
+
date: this.viewDate
|
3383
|
+
});
|
3384
|
+
}
|
3385
|
+
this.showMode(-1);
|
3386
|
+
this.fill();
|
3387
|
+
}
|
3388
|
+
break;
|
3389
|
+
case 'td':
|
3390
|
+
if (target.is('.day') && !target.is('.disabled')){
|
3391
|
+
var day = parseInt(target.text(), 10)||1;
|
3392
|
+
var year = this.viewDate.getUTCFullYear(),
|
3393
|
+
month = this.viewDate.getUTCMonth();
|
3394
|
+
if (target.is('.old')) {
|
3395
|
+
if (month == 0) {
|
3396
|
+
month = 11;
|
3397
|
+
year -= 1;
|
3398
|
+
} else {
|
3399
|
+
month -= 1;
|
3400
|
+
}
|
3401
|
+
} else if (target.is('.new')) {
|
3402
|
+
if (month == 11) {
|
3403
|
+
month = 0;
|
3404
|
+
year += 1;
|
3405
|
+
} else {
|
3406
|
+
month += 1;
|
3407
|
+
}
|
3408
|
+
}
|
3409
|
+
this._setDate(UTCDate(year, month, day,0,0,0,0));
|
3410
|
+
}
|
3411
|
+
break;
|
3412
|
+
}
|
3413
|
+
}
|
3414
|
+
},
|
3415
|
+
|
3416
|
+
_setDate: function(date, which){
|
3417
|
+
if (!which || which == 'date')
|
3418
|
+
this.date = date;
|
3419
|
+
if (!which || which == 'view')
|
3420
|
+
this.viewDate = date;
|
3421
|
+
this.fill();
|
3422
|
+
this.setValue();
|
3423
|
+
this.element.trigger({
|
3424
|
+
type: 'changeDate',
|
3425
|
+
date: this.date
|
3426
|
+
});
|
3427
|
+
var element;
|
3428
|
+
if (this.isInput) {
|
3429
|
+
element = this.element;
|
3430
|
+
} else if (this.component){
|
3431
|
+
element = this.element.find('input');
|
3432
|
+
}
|
3433
|
+
if (element) {
|
3434
|
+
element.change();
|
3435
|
+
if (this.autoclose) {
|
3436
|
+
this.hide();
|
3437
|
+
}
|
3438
|
+
}
|
3439
|
+
},
|
3440
|
+
|
3441
|
+
moveMonth: function(date, dir){
|
3442
|
+
if (!dir) return date;
|
3443
|
+
var new_date = new Date(date.valueOf()),
|
3444
|
+
day = new_date.getUTCDate(),
|
3445
|
+
month = new_date.getUTCMonth(),
|
3446
|
+
mag = Math.abs(dir),
|
3447
|
+
new_month, test;
|
3448
|
+
dir = dir > 0 ? 1 : -1;
|
3449
|
+
if (mag == 1){
|
3450
|
+
test = dir == -1
|
3451
|
+
// If going back one month, make sure month is not current month
|
3452
|
+
// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
|
3453
|
+
? function(){ return new_date.getUTCMonth() == month; }
|
3454
|
+
// If going forward one month, make sure month is as expected
|
3455
|
+
// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
|
3456
|
+
: function(){ return new_date.getUTCMonth() != new_month; };
|
3457
|
+
new_month = month + dir;
|
3458
|
+
new_date.setUTCMonth(new_month);
|
3459
|
+
// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
|
3460
|
+
if (new_month < 0 || new_month > 11)
|
3461
|
+
new_month = (new_month + 12) % 12;
|
3462
|
+
} else {
|
3463
|
+
// For magnitudes >1, move one month at a time...
|
3464
|
+
for (var i=0; i<mag; i++)
|
3465
|
+
// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
|
3466
|
+
new_date = this.moveMonth(new_date, dir);
|
3467
|
+
// ...then reset the day, keeping it in the new month
|
3468
|
+
new_month = new_date.getUTCMonth();
|
3469
|
+
new_date.setUTCDate(day);
|
3470
|
+
test = function(){ return new_month != new_date.getUTCMonth(); };
|
3471
|
+
}
|
3472
|
+
// Common date-resetting loop -- if date is beyond end of month, make it
|
3473
|
+
// end of month
|
3474
|
+
while (test()){
|
3475
|
+
new_date.setUTCDate(--day);
|
3476
|
+
new_date.setUTCMonth(new_month);
|
3477
|
+
}
|
3478
|
+
return new_date;
|
3479
|
+
},
|
3480
|
+
|
3481
|
+
moveYear: function(date, dir){
|
3482
|
+
return this.moveMonth(date, dir*12);
|
3483
|
+
},
|
3484
|
+
|
3485
|
+
dateWithinRange: function(date){
|
3486
|
+
return date >= this.startDate && date <= this.endDate;
|
3487
|
+
},
|
3488
|
+
|
3489
|
+
keydown: function(e){
|
3490
|
+
if (this.picker.is(':not(:visible)')){
|
3491
|
+
if (e.keyCode == 27) // allow escape to hide and re-show picker
|
3492
|
+
this.show();
|
3493
|
+
return;
|
3494
|
+
}
|
3495
|
+
var dateChanged = false,
|
3496
|
+
dir, day, month,
|
3497
|
+
newDate, newViewDate;
|
3498
|
+
switch(e.keyCode){
|
3499
|
+
case 27: // escape
|
3500
|
+
this.hide();
|
3501
|
+
e.preventDefault();
|
3502
|
+
break;
|
3503
|
+
case 37: // left
|
3504
|
+
case 39: // right
|
3505
|
+
if (!this.keyboardNavigation) break;
|
3506
|
+
dir = e.keyCode == 37 ? -1 : 1;
|
3507
|
+
if (e.ctrlKey){
|
3508
|
+
newDate = this.moveYear(this.date, dir);
|
3509
|
+
newViewDate = this.moveYear(this.viewDate, dir);
|
3510
|
+
} else if (e.shiftKey){
|
3511
|
+
newDate = this.moveMonth(this.date, dir);
|
3512
|
+
newViewDate = this.moveMonth(this.viewDate, dir);
|
3513
|
+
} else {
|
3514
|
+
newDate = new Date(this.date);
|
3515
|
+
newDate.setUTCDate(this.date.getUTCDate() + dir);
|
3516
|
+
newViewDate = new Date(this.viewDate);
|
3517
|
+
newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
|
3518
|
+
}
|
3519
|
+
if (this.dateWithinRange(newDate)){
|
3520
|
+
this.date = newDate;
|
3521
|
+
this.viewDate = newViewDate;
|
3522
|
+
this.setValue();
|
3523
|
+
this.update();
|
3524
|
+
e.preventDefault();
|
3525
|
+
dateChanged = true;
|
3526
|
+
}
|
3527
|
+
break;
|
3528
|
+
case 38: // up
|
3529
|
+
case 40: // down
|
3530
|
+
if (!this.keyboardNavigation) break;
|
3531
|
+
dir = e.keyCode == 38 ? -1 : 1;
|
3532
|
+
if (e.ctrlKey){
|
3533
|
+
newDate = this.moveYear(this.date, dir);
|
3534
|
+
newViewDate = this.moveYear(this.viewDate, dir);
|
3535
|
+
} else if (e.shiftKey){
|
3536
|
+
newDate = this.moveMonth(this.date, dir);
|
3537
|
+
newViewDate = this.moveMonth(this.viewDate, dir);
|
3538
|
+
} else {
|
3539
|
+
newDate = new Date(this.date);
|
3540
|
+
newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
|
3541
|
+
newViewDate = new Date(this.viewDate);
|
3542
|
+
newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
|
3543
|
+
}
|
3544
|
+
if (this.dateWithinRange(newDate)){
|
3545
|
+
this.date = newDate;
|
3546
|
+
this.viewDate = newViewDate;
|
3547
|
+
this.setValue();
|
3548
|
+
this.update();
|
3549
|
+
e.preventDefault();
|
3550
|
+
dateChanged = true;
|
3551
|
+
}
|
3552
|
+
break;
|
3553
|
+
case 13: // enter
|
3554
|
+
this.hide();
|
3555
|
+
e.preventDefault();
|
3556
|
+
break;
|
3557
|
+
case 9: // tab
|
3558
|
+
this.hide();
|
3559
|
+
break;
|
3560
|
+
}
|
3561
|
+
if (dateChanged){
|
3562
|
+
this.element.trigger({
|
3563
|
+
type: 'changeDate',
|
3564
|
+
date: this.date
|
3565
|
+
});
|
3566
|
+
var element;
|
3567
|
+
if (this.isInput) {
|
3568
|
+
element = this.element;
|
3569
|
+
} else if (this.component){
|
3570
|
+
element = this.element.find('input');
|
3571
|
+
}
|
3572
|
+
if (element) {
|
3573
|
+
element.change();
|
3574
|
+
}
|
3575
|
+
}
|
3576
|
+
},
|
3577
|
+
|
3578
|
+
showMode: function(dir) {
|
3579
|
+
if (dir) {
|
3580
|
+
this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir));
|
3581
|
+
}
|
3582
|
+
/*
|
3583
|
+
vitalets: fixing bug of very special conditions:
|
3584
|
+
jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
|
3585
|
+
Method show() does not set display css correctly and datepicker is not shown.
|
3586
|
+
Changed to .css('display', 'block') solve the problem.
|
3587
|
+
See https://github.com/vitalets/x-editable/issues/37
|
3588
|
+
|
3589
|
+
In jquery 1.7.2+ everything works fine.
|
3590
|
+
*/
|
3591
|
+
//this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
|
3592
|
+
this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
|
3593
|
+
this.updateNavArrows();
|
3594
|
+
}
|
3595
|
+
};
|
3596
|
+
|
3597
|
+
$.fn.datepicker = function ( option ) {
|
3598
|
+
var args = Array.apply(null, arguments);
|
3599
|
+
args.shift();
|
3600
|
+
return this.each(function () {
|
3601
|
+
var $this = $(this),
|
3602
|
+
data = $this.data('datepicker'),
|
3603
|
+
options = typeof option == 'object' && option;
|
3604
|
+
if (!data) {
|
3605
|
+
$this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
|
3606
|
+
}
|
3607
|
+
if (typeof option == 'string' && typeof data[option] == 'function') {
|
3608
|
+
data[option].apply(data, args);
|
3609
|
+
}
|
3610
|
+
});
|
3611
|
+
};
|
3612
|
+
|
3613
|
+
$.fn.datepicker.defaults = {
|
3614
|
+
};
|
3615
|
+
$.fn.datepicker.Constructor = Datepicker;
|
3616
|
+
var dates = $.fn.datepicker.dates = {
|
3617
|
+
en: {
|
3618
|
+
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
|
3619
|
+
daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
|
3620
|
+
daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
|
3621
|
+
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
|
3622
|
+
monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
3623
|
+
today: "Today"
|
3624
|
+
}
|
3625
|
+
}
|
3626
|
+
|
3627
|
+
var DPGlobal = {
|
3628
|
+
modes: [
|
3629
|
+
{
|
3630
|
+
clsName: 'days',
|
3631
|
+
navFnc: 'Month',
|
3632
|
+
navStep: 1
|
3633
|
+
},
|
3634
|
+
{
|
3635
|
+
clsName: 'months',
|
3636
|
+
navFnc: 'FullYear',
|
3637
|
+
navStep: 1
|
3638
|
+
},
|
3639
|
+
{
|
3640
|
+
clsName: 'years',
|
3641
|
+
navFnc: 'FullYear',
|
3642
|
+
navStep: 10
|
3643
|
+
}],
|
3644
|
+
isLeapYear: function (year) {
|
3645
|
+
return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
|
3646
|
+
},
|
3647
|
+
getDaysInMonth: function (year, month) {
|
3648
|
+
return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
|
3649
|
+
},
|
3650
|
+
validParts: /dd?|mm?|MM?|yy(?:yy)?/g,
|
3651
|
+
nonpunctuation: /[^ -\/:-@\[-`{-~\t\n\r]+/g,
|
3652
|
+
parseFormat: function(format){
|
3653
|
+
// IE treats \0 as a string end in inputs (truncating the value),
|
3654
|
+
// so it's a bad format delimiter, anyway
|
3655
|
+
var separators = format.replace(this.validParts, '\0').split('\0'),
|
3656
|
+
parts = format.match(this.validParts);
|
3657
|
+
if (!separators || !separators.length || !parts || parts.length == 0){
|
3658
|
+
throw new Error("Invalid date format.");
|
3659
|
+
}
|
3660
|
+
return {separators: separators, parts: parts};
|
3661
|
+
},
|
3662
|
+
parseDate: function(date, format, language) {
|
3663
|
+
if (date instanceof Date) return date;
|
3664
|
+
if (/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(date)) {
|
3665
|
+
var part_re = /([-+]\d+)([dmwy])/,
|
3666
|
+
parts = date.match(/([-+]\d+)([dmwy])/g),
|
3667
|
+
part, dir;
|
3668
|
+
date = new Date();
|
3669
|
+
for (var i=0; i<parts.length; i++) {
|
3670
|
+
part = part_re.exec(parts[i]);
|
3671
|
+
dir = parseInt(part[1]);
|
3672
|
+
switch(part[2]){
|
3673
|
+
case 'd':
|
3674
|
+
date.setUTCDate(date.getUTCDate() + dir);
|
3675
|
+
break;
|
3676
|
+
case 'm':
|
3677
|
+
date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
|
3678
|
+
break;
|
3679
|
+
case 'w':
|
3680
|
+
date.setUTCDate(date.getUTCDate() + dir * 7);
|
3681
|
+
break;
|
3682
|
+
case 'y':
|
3683
|
+
date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
|
3684
|
+
break;
|
3685
|
+
}
|
3686
|
+
}
|
3687
|
+
return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
|
3688
|
+
}
|
3689
|
+
var parts = date && date.match(this.nonpunctuation) || [],
|
3690
|
+
date = new Date(),
|
3691
|
+
parsed = {},
|
3692
|
+
setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
|
3693
|
+
setters_map = {
|
3694
|
+
yyyy: function(d,v){ return d.setUTCFullYear(v); },
|
3695
|
+
yy: function(d,v){ return d.setUTCFullYear(2000+v); },
|
3696
|
+
m: function(d,v){
|
3697
|
+
v -= 1;
|
3698
|
+
while (v<0) v += 12;
|
3699
|
+
v %= 12;
|
3700
|
+
d.setUTCMonth(v);
|
3701
|
+
while (d.getUTCMonth() != v)
|
3702
|
+
d.setUTCDate(d.getUTCDate()-1);
|
3703
|
+
return d;
|
3704
|
+
},
|
3705
|
+
d: function(d,v){ return d.setUTCDate(v); }
|
3706
|
+
},
|
3707
|
+
val, filtered, part;
|
3708
|
+
setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
|
3709
|
+
setters_map['dd'] = setters_map['d'];
|
3710
|
+
date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
|
3711
|
+
if (parts.length == format.parts.length) {
|
3712
|
+
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
|
3713
|
+
val = parseInt(parts[i], 10);
|
3714
|
+
part = format.parts[i];
|
3715
|
+
if (isNaN(val)) {
|
3716
|
+
switch(part) {
|
3717
|
+
case 'MM':
|
3718
|
+
filtered = $(dates[language].months).filter(function(){
|
3719
|
+
var m = this.slice(0, parts[i].length),
|
3720
|
+
p = parts[i].slice(0, m.length);
|
3721
|
+
return m == p;
|
3722
|
+
});
|
3723
|
+
val = $.inArray(filtered[0], dates[language].months) + 1;
|
3724
|
+
break;
|
3725
|
+
case 'M':
|
3726
|
+
filtered = $(dates[language].monthsShort).filter(function(){
|
3727
|
+
var m = this.slice(0, parts[i].length),
|
3728
|
+
p = parts[i].slice(0, m.length);
|
3729
|
+
return m == p;
|
3730
|
+
});
|
3731
|
+
val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
|
3732
|
+
break;
|
3733
|
+
}
|
3734
|
+
}
|
3735
|
+
parsed[part] = val;
|
3736
|
+
}
|
3737
|
+
for (var i=0, s; i<setters_order.length; i++){
|
3738
|
+
s = setters_order[i];
|
3739
|
+
if (s in parsed)
|
3740
|
+
setters_map[s](date, parsed[s])
|
3741
|
+
}
|
3742
|
+
}
|
3743
|
+
return date;
|
3744
|
+
},
|
3745
|
+
formatDate: function(date, format, language){
|
3746
|
+
var val = {
|
3747
|
+
d: date.getUTCDate(),
|
3748
|
+
m: date.getUTCMonth() + 1,
|
3749
|
+
M: dates[language].monthsShort[date.getUTCMonth()],
|
3750
|
+
MM: dates[language].months[date.getUTCMonth()],
|
3751
|
+
yy: date.getUTCFullYear().toString().substring(2),
|
3752
|
+
yyyy: date.getUTCFullYear()
|
3753
|
+
};
|
3754
|
+
val.dd = (val.d < 10 ? '0' : '') + val.d;
|
3755
|
+
val.mm = (val.m < 10 ? '0' : '') + val.m;
|
3756
|
+
var date = [],
|
3757
|
+
seps = $.extend([], format.separators);
|
3758
|
+
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
|
3759
|
+
if (seps.length)
|
3760
|
+
date.push(seps.shift())
|
3761
|
+
date.push(val[format.parts[i]]);
|
3762
|
+
}
|
3763
|
+
return date.join('');
|
3764
|
+
},
|
3765
|
+
headTemplate: '<thead>'+
|
3766
|
+
'<tr>'+
|
3767
|
+
'<th class="prev"><i class="icon-arrow-left"/></th>'+
|
3768
|
+
'<th colspan="5" class="switch"></th>'+
|
3769
|
+
'<th class="next"><i class="icon-arrow-right"/></th>'+
|
3770
|
+
'</tr>'+
|
3771
|
+
'</thead>',
|
3772
|
+
contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
|
3773
|
+
footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr></tfoot>'
|
3774
|
+
};
|
3775
|
+
DPGlobal.template = '<div class="datepicker">'+
|
3776
|
+
'<div class="datepicker-days">'+
|
3777
|
+
'<table class=" table-condensed">'+
|
3778
|
+
DPGlobal.headTemplate+
|
3779
|
+
'<tbody></tbody>'+
|
3780
|
+
DPGlobal.footTemplate+
|
3781
|
+
'</table>'+
|
3782
|
+
'</div>'+
|
3783
|
+
'<div class="datepicker-months">'+
|
3784
|
+
'<table class="table-condensed">'+
|
3785
|
+
DPGlobal.headTemplate+
|
3786
|
+
DPGlobal.contTemplate+
|
3787
|
+
DPGlobal.footTemplate+
|
3788
|
+
'</table>'+
|
3789
|
+
'</div>'+
|
3790
|
+
'<div class="datepicker-years">'+
|
3791
|
+
'<table class="table-condensed">'+
|
3792
|
+
DPGlobal.headTemplate+
|
3793
|
+
DPGlobal.contTemplate+
|
3794
|
+
DPGlobal.footTemplate+
|
3795
|
+
'</table>'+
|
3796
|
+
'</div>'+
|
3797
|
+
'</div>';
|
3798
|
+
|
3799
|
+
$.fn.datepicker.DPGlobal = DPGlobal;
|
3800
|
+
|
3801
|
+
}( window.jQuery );
|