ab_admin 0.3.4 → 0.3.5
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 +2 -0
- data/Gemfile +4 -2
- data/app/assets/javascripts/ab_admin/components/google_translate.js.coffee +1 -1
- data/app/assets/javascripts/ab_admin/components/init_nested_filelds.js.coffee +2 -2
- data/app/assets/javascripts/ab_admin/core/batch_actions.js.coffee +3 -3
- data/app/assets/javascripts/ab_admin/core/init.js.coffee +5 -3
- data/app/assets/javascripts/ab_admin/core/search_form.js.coffee +2 -2
- data/app/assets/javascripts/ab_admin/core/ui_utils.js.coffee +14 -2
- data/app/assets/javascripts/ab_admin/core/utils.js.coffee +4 -3
- data/app/assets/javascripts/ab_admin/main.js +3 -1
- data/app/assets/stylesheets/ab_admin/bootstrap_and_overrides.css.scss +5 -4
- data/app/assets/stylesheets/ab_admin/components/_text_styles.css.scss +47 -0
- data/app/assets/stylesheets/ab_admin/main.css.scss +1 -1
- data/app/controllers/admin/base_controller.rb +1 -1
- data/app/views/admin/base/edit.js.erb +1 -1
- data/app/views/admin/base/new.js.erb +1 -1
- data/app/views/admin/base/update.js.erb +1 -1
- data/app/views/admin/fileupload/_container.html.slim +4 -2
- data/app/views/admin/fileupload/_file.html.slim +1 -1
- data/app/views/admin/fileupload/_ftmpl.html.slim +1 -1
- data/app/views/admin/manager/_search_form.html.slim +1 -1
- data/app/views/admin/shared/_batch_actions.html.slim +9 -10
- data/app/views/layouts/admin/application.html.slim +3 -5
- data/config/locales/ru.yml +3 -2
- data/features/menu.feature +7 -2
- data/features/step_definitions/menu_steps.rb +7 -0
- data/lib/ab_admin.rb +3 -3
- data/lib/ab_admin/controllers/head_options.rb +2 -2
- data/lib/ab_admin/menu_builder.rb +2 -1
- data/lib/ab_admin/models/attachment_file.rb +1 -2
- data/lib/ab_admin/models/user.rb +1 -1
- data/lib/ab_admin/version.rb +1 -1
- data/lib/ab_admin/views/admin_helpers.rb +7 -4
- data/lib/ab_admin/views/admin_navigation_helpers.rb +4 -4
- data/lib/ab_admin/views/form_builder.rb +2 -2
- data/lib/generators/template.rb +0 -1
- data/spec/ab_admin_spec.rb +2 -2
- data/vendor/assets/images/ab_admin/clear.png +0 -0
- data/vendor/assets/images/ab_admin/loading.gif +0 -0
- data/vendor/assets/javascripts/ab_admin/bootstrap-editable-inline.js +2895 -0
- data/vendor/assets/javascripts/ab_admin/bootstrap-editable.js +4523 -0
- data/vendor/assets/javascripts/ab_admin/jquery_nested_form.js.coffee +19 -1
- data/vendor/assets/javascripts/jquery/jquery-ui.min.js +15 -0
- data/vendor/assets/javascripts/jquery/jquery.min.js +4 -0
- data/vendor/assets/stylesheets/ab_admin/bootstrap-editable.scss +461 -0
- metadata +12 -4
@@ -0,0 +1,4523 @@
|
|
1
|
+
// !!! removed datepicker
|
2
|
+
/*! X-editable - v1.4.1
|
3
|
+
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
|
4
|
+
* http://github.com/vitalets/x-editable
|
5
|
+
* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
|
6
|
+
|
7
|
+
/**
|
8
|
+
Form with single input element, two buttons and two states: normal/loading.
|
9
|
+
Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown.
|
10
|
+
Editableform is linked with one of input types, e.g. 'text', 'select' etc.
|
11
|
+
|
12
|
+
@class editableform
|
13
|
+
@uses text
|
14
|
+
@uses textarea
|
15
|
+
**/
|
16
|
+
(function ($) {
|
17
|
+
|
18
|
+
var EditableForm = function (div, options) {
|
19
|
+
this.options = $.extend({}, $.fn.editableform.defaults, options);
|
20
|
+
this.$div = $(div); //div, containing form. Not form tag. Not editable-element.
|
21
|
+
if(!this.options.scope) {
|
22
|
+
this.options.scope = this;
|
23
|
+
}
|
24
|
+
//nothing shown after init
|
25
|
+
};
|
26
|
+
|
27
|
+
EditableForm.prototype = {
|
28
|
+
constructor: EditableForm,
|
29
|
+
initInput: function() { //called once
|
30
|
+
//take input from options (as it is created in editable-element)
|
31
|
+
this.input = this.options.input;
|
32
|
+
|
33
|
+
//set initial value
|
34
|
+
//todo: may be add check: typeof str === 'string' ?
|
35
|
+
this.value = this.input.str2value(this.options.value);
|
36
|
+
},
|
37
|
+
initTemplate: function() {
|
38
|
+
this.$form = $($.fn.editableform.template);
|
39
|
+
},
|
40
|
+
initButtons: function() {
|
41
|
+
this.$form.find('.editable-buttons').append($.fn.editableform.buttons);
|
42
|
+
},
|
43
|
+
/**
|
44
|
+
Renders editableform
|
45
|
+
|
46
|
+
@method render
|
47
|
+
**/
|
48
|
+
render: function() {
|
49
|
+
//init loader
|
50
|
+
this.$loading = $($.fn.editableform.loading);
|
51
|
+
this.$div.empty().append(this.$loading);
|
52
|
+
|
53
|
+
//init form template and buttons
|
54
|
+
this.initTemplate();
|
55
|
+
if(this.options.showbuttons) {
|
56
|
+
this.initButtons();
|
57
|
+
} else {
|
58
|
+
this.$form.find('.editable-buttons').remove();
|
59
|
+
}
|
60
|
+
|
61
|
+
//show loading state
|
62
|
+
this.showLoading();
|
63
|
+
|
64
|
+
/**
|
65
|
+
Fired when rendering starts
|
66
|
+
@event rendering
|
67
|
+
@param {Object} event event object
|
68
|
+
**/
|
69
|
+
this.$div.triggerHandler('rendering');
|
70
|
+
|
71
|
+
//init input
|
72
|
+
this.initInput();
|
73
|
+
|
74
|
+
//append input to form
|
75
|
+
this.input.prerender();
|
76
|
+
this.$form.find('div.editable-input').append(this.input.$tpl);
|
77
|
+
|
78
|
+
//append form to container
|
79
|
+
this.$div.append(this.$form);
|
80
|
+
|
81
|
+
//render input
|
82
|
+
$.when(this.input.render())
|
83
|
+
.then($.proxy(function () {
|
84
|
+
//setup input to submit automatically when no buttons shown
|
85
|
+
if(!this.options.showbuttons) {
|
86
|
+
this.input.autosubmit();
|
87
|
+
}
|
88
|
+
|
89
|
+
//attach 'cancel' handler
|
90
|
+
this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
|
91
|
+
|
92
|
+
if(this.input.error) {
|
93
|
+
this.error(this.input.error);
|
94
|
+
this.$form.find('.editable-submit').attr('disabled', true);
|
95
|
+
this.input.$input.attr('disabled', true);
|
96
|
+
//prevent form from submitting
|
97
|
+
this.$form.submit(function(e){ e.preventDefault(); });
|
98
|
+
} else {
|
99
|
+
this.error(false);
|
100
|
+
this.input.$input.removeAttr('disabled');
|
101
|
+
this.$form.find('.editable-submit').removeAttr('disabled');
|
102
|
+
this.input.value2input(this.value);
|
103
|
+
//attach submit handler
|
104
|
+
this.$form.submit($.proxy(this.submit, this));
|
105
|
+
}
|
106
|
+
|
107
|
+
/**
|
108
|
+
Fired when form is rendered
|
109
|
+
@event rendered
|
110
|
+
@param {Object} event event object
|
111
|
+
**/
|
112
|
+
this.$div.triggerHandler('rendered');
|
113
|
+
|
114
|
+
this.showForm();
|
115
|
+
|
116
|
+
//call postrender method to perform actions required visibility of form
|
117
|
+
if(this.input.postrender) {
|
118
|
+
this.input.postrender();
|
119
|
+
}
|
120
|
+
}, this));
|
121
|
+
},
|
122
|
+
cancel: function() {
|
123
|
+
/**
|
124
|
+
Fired when form was cancelled by user
|
125
|
+
@event cancel
|
126
|
+
@param {Object} event event object
|
127
|
+
**/
|
128
|
+
this.$div.triggerHandler('cancel');
|
129
|
+
},
|
130
|
+
showLoading: function() {
|
131
|
+
var w, h;
|
132
|
+
if(this.$form) {
|
133
|
+
//set loading size equal to form
|
134
|
+
w = this.$form.outerWidth();
|
135
|
+
h = this.$form.outerHeight();
|
136
|
+
if(w) {
|
137
|
+
this.$loading.width(w);
|
138
|
+
}
|
139
|
+
if(h) {
|
140
|
+
this.$loading.height(h);
|
141
|
+
}
|
142
|
+
this.$form.hide();
|
143
|
+
} else {
|
144
|
+
//stretch loading to fill container width
|
145
|
+
w = this.$loading.parent().width();
|
146
|
+
if(w) {
|
147
|
+
this.$loading.width(w);
|
148
|
+
}
|
149
|
+
}
|
150
|
+
this.$loading.show();
|
151
|
+
},
|
152
|
+
|
153
|
+
showForm: function(activate) {
|
154
|
+
this.$loading.hide();
|
155
|
+
this.$form.show();
|
156
|
+
if(activate !== false) {
|
157
|
+
this.input.activate();
|
158
|
+
}
|
159
|
+
/**
|
160
|
+
Fired when form is shown
|
161
|
+
@event show
|
162
|
+
@param {Object} event event object
|
163
|
+
**/
|
164
|
+
this.$div.triggerHandler('show');
|
165
|
+
},
|
166
|
+
|
167
|
+
error: function(msg) {
|
168
|
+
var $group = this.$form.find('.control-group'),
|
169
|
+
$block = this.$form.find('.editable-error-block'),
|
170
|
+
lines;
|
171
|
+
|
172
|
+
if(msg === false) {
|
173
|
+
$group.removeClass($.fn.editableform.errorGroupClass);
|
174
|
+
$block.removeClass($.fn.editableform.errorBlockClass).empty().hide();
|
175
|
+
} else {
|
176
|
+
//convert newline to <br> for more pretty error display
|
177
|
+
if(msg) {
|
178
|
+
lines = msg.split("\n");
|
179
|
+
for (var i = 0; i < lines.length; i++) {
|
180
|
+
lines[i] = $('<div>').text(lines[i]).html();
|
181
|
+
}
|
182
|
+
msg = lines.join('<br>');
|
183
|
+
}
|
184
|
+
$group.addClass($.fn.editableform.errorGroupClass);
|
185
|
+
$block.addClass($.fn.editableform.errorBlockClass).html(msg).show();
|
186
|
+
}
|
187
|
+
},
|
188
|
+
|
189
|
+
submit: function(e) {
|
190
|
+
e.stopPropagation();
|
191
|
+
e.preventDefault();
|
192
|
+
|
193
|
+
var error,
|
194
|
+
newValue = this.input.input2value(); //get new value from input
|
195
|
+
|
196
|
+
//validation
|
197
|
+
if (error = this.validate(newValue)) {
|
198
|
+
this.error(error);
|
199
|
+
this.showForm();
|
200
|
+
return;
|
201
|
+
}
|
202
|
+
|
203
|
+
//if value not changed --> trigger 'nochange' event and return
|
204
|
+
/*jslint eqeq: true*/
|
205
|
+
if (!this.options.savenochange && this.input.value2str(newValue) == this.input.value2str(this.value)) {
|
206
|
+
/*jslint eqeq: false*/
|
207
|
+
/**
|
208
|
+
Fired when value not changed but form is submitted. Requires savenochange = false.
|
209
|
+
@event nochange
|
210
|
+
@param {Object} event event object
|
211
|
+
**/
|
212
|
+
this.$div.triggerHandler('nochange');
|
213
|
+
return;
|
214
|
+
}
|
215
|
+
|
216
|
+
//sending data to server
|
217
|
+
$.when(this.save(newValue))
|
218
|
+
.done($.proxy(function(response) {
|
219
|
+
//run success callback
|
220
|
+
var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
|
221
|
+
|
222
|
+
//if success callback returns false --> keep form open and do not activate input
|
223
|
+
if(res === false) {
|
224
|
+
this.error(false);
|
225
|
+
this.showForm(false);
|
226
|
+
return;
|
227
|
+
}
|
228
|
+
|
229
|
+
//if success callback returns string --> keep form open, show error and activate input
|
230
|
+
if(typeof res === 'string') {
|
231
|
+
this.error(res);
|
232
|
+
this.showForm();
|
233
|
+
return;
|
234
|
+
}
|
235
|
+
|
236
|
+
//if success callback returns object like {newValue: <something>} --> use that value instead of submitted
|
237
|
+
//it is usefull if you want to chnage value in url-function
|
238
|
+
if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
|
239
|
+
newValue = res.newValue;
|
240
|
+
}
|
241
|
+
|
242
|
+
//clear error message
|
243
|
+
this.error(false);
|
244
|
+
this.value = newValue;
|
245
|
+
/**
|
246
|
+
Fired when form is submitted
|
247
|
+
@event save
|
248
|
+
@param {Object} event event object
|
249
|
+
@param {Object} params additional params
|
250
|
+
@param {mixed} params.newValue submitted value
|
251
|
+
@param {Object} params.response ajax response
|
252
|
+
|
253
|
+
@example
|
254
|
+
$('#form-div').on('save'), function(e, params){
|
255
|
+
if(params.newValue === 'username') {...}
|
256
|
+
});
|
257
|
+
**/
|
258
|
+
this.$div.triggerHandler('save', {newValue: newValue, response: response});
|
259
|
+
}, this))
|
260
|
+
.fail($.proxy(function(xhr) {
|
261
|
+
this.error(typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!');
|
262
|
+
this.showForm();
|
263
|
+
}, this));
|
264
|
+
},
|
265
|
+
|
266
|
+
save: function(newValue) {
|
267
|
+
//convert value for submitting to server
|
268
|
+
var submitValue = this.input.value2submit(newValue);
|
269
|
+
|
270
|
+
//try parse composite pk defined as json string in data-pk
|
271
|
+
this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true);
|
272
|
+
|
273
|
+
var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
|
274
|
+
send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk)))),
|
275
|
+
params;
|
276
|
+
|
277
|
+
if (send) { //send to server
|
278
|
+
this.showLoading();
|
279
|
+
|
280
|
+
//standard params
|
281
|
+
params = {
|
282
|
+
name: this.options.name || '',
|
283
|
+
value: submitValue,
|
284
|
+
pk: pk
|
285
|
+
};
|
286
|
+
|
287
|
+
//additional params
|
288
|
+
if(typeof this.options.params === 'function') {
|
289
|
+
params = this.options.params.call(this.options.scope, params);
|
290
|
+
} else {
|
291
|
+
//try parse json in single quotes (from data-params attribute)
|
292
|
+
this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true);
|
293
|
+
$.extend(params, this.options.params);
|
294
|
+
}
|
295
|
+
|
296
|
+
if(typeof this.options.url === 'function') { //user's function
|
297
|
+
return this.options.url.call(this.options.scope, params);
|
298
|
+
} else {
|
299
|
+
//send ajax to server and return deferred object
|
300
|
+
return $.ajax($.extend({
|
301
|
+
url : this.options.url,
|
302
|
+
data : params,
|
303
|
+
type : 'POST'
|
304
|
+
}, this.options.ajaxOptions));
|
305
|
+
}
|
306
|
+
}
|
307
|
+
},
|
308
|
+
|
309
|
+
validate: function (value) {
|
310
|
+
if (value === undefined) {
|
311
|
+
value = this.value;
|
312
|
+
}
|
313
|
+
if (typeof this.options.validate === 'function') {
|
314
|
+
return this.options.validate.call(this.options.scope, value);
|
315
|
+
}
|
316
|
+
},
|
317
|
+
|
318
|
+
option: function(key, value) {
|
319
|
+
if(key in this.options) {
|
320
|
+
this.options[key] = value;
|
321
|
+
}
|
322
|
+
|
323
|
+
if(key === 'value') {
|
324
|
+
this.setValue(value);
|
325
|
+
}
|
326
|
+
|
327
|
+
//do not pass option to input as it is passed in editable-element
|
328
|
+
},
|
329
|
+
|
330
|
+
setValue: function(value, convertStr) {
|
331
|
+
if(convertStr) {
|
332
|
+
this.value = this.input.str2value(value);
|
333
|
+
} else {
|
334
|
+
this.value = value;
|
335
|
+
}
|
336
|
+
|
337
|
+
//if form is visible, update input
|
338
|
+
if(this.$form && this.$form.is(':visible')) {
|
339
|
+
this.input.value2input(this.value);
|
340
|
+
}
|
341
|
+
}
|
342
|
+
};
|
343
|
+
|
344
|
+
/*
|
345
|
+
Initialize editableform. Applied to jQuery object.
|
346
|
+
|
347
|
+
@method $().editableform(options)
|
348
|
+
@params {Object} options
|
349
|
+
@example
|
350
|
+
var $form = $('<div>').editableform({
|
351
|
+
type: 'text',
|
352
|
+
name: 'username',
|
353
|
+
url: '/post',
|
354
|
+
value: 'vitaliy'
|
355
|
+
});
|
356
|
+
|
357
|
+
//to display form you should call 'render' method
|
358
|
+
$form.editableform('render');
|
359
|
+
*/
|
360
|
+
$.fn.editableform = function (option) {
|
361
|
+
var args = arguments;
|
362
|
+
return this.each(function () {
|
363
|
+
var $this = $(this),
|
364
|
+
data = $this.data('editableform'),
|
365
|
+
options = typeof option === 'object' && option;
|
366
|
+
if (!data) {
|
367
|
+
$this.data('editableform', (data = new EditableForm(this, options)));
|
368
|
+
}
|
369
|
+
|
370
|
+
if (typeof option === 'string') { //call method
|
371
|
+
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
372
|
+
}
|
373
|
+
});
|
374
|
+
};
|
375
|
+
|
376
|
+
//keep link to constructor to allow inheritance
|
377
|
+
$.fn.editableform.Constructor = EditableForm;
|
378
|
+
|
379
|
+
//defaults
|
380
|
+
$.fn.editableform.defaults = {
|
381
|
+
/* see also defaults for input */
|
382
|
+
|
383
|
+
/**
|
384
|
+
Type of input. Can be <code>text|textarea|select|date|checklist</code>
|
385
|
+
|
386
|
+
@property type
|
387
|
+
@type string
|
388
|
+
@default 'text'
|
389
|
+
**/
|
390
|
+
type: 'text',
|
391
|
+
/**
|
392
|
+
Url for submit, e.g. <code>'/post'</code>
|
393
|
+
If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks.
|
394
|
+
|
395
|
+
@property url
|
396
|
+
@type string|function
|
397
|
+
@default null
|
398
|
+
@example
|
399
|
+
url: function(params) {
|
400
|
+
var d = new $.Deferred;
|
401
|
+
if(params.value === 'abc') {
|
402
|
+
return d.reject('error message'); //returning error via deferred object
|
403
|
+
} else {
|
404
|
+
//async saving data in js model
|
405
|
+
someModel.asyncSaveMethod({
|
406
|
+
...,
|
407
|
+
success: function(){
|
408
|
+
d.resolve();
|
409
|
+
}
|
410
|
+
});
|
411
|
+
return d.promise();
|
412
|
+
}
|
413
|
+
}
|
414
|
+
**/
|
415
|
+
url:null,
|
416
|
+
/**
|
417
|
+
Additional params for submit. If defined as <code>object</code> - it is **appended** to original ajax data (pk, name and value).
|
418
|
+
If defined as <code>function</code> - returned object **overwrites** original ajax data.
|
419
|
+
@example
|
420
|
+
params: function(params) {
|
421
|
+
//originally params contain pk, name and value
|
422
|
+
params.a = 1;
|
423
|
+
return params;
|
424
|
+
}
|
425
|
+
|
426
|
+
@property params
|
427
|
+
@type object|function
|
428
|
+
@default null
|
429
|
+
**/
|
430
|
+
params:null,
|
431
|
+
/**
|
432
|
+
Name of field. Will be submitted on server. Can be taken from <code>id</code> attribute
|
433
|
+
|
434
|
+
@property name
|
435
|
+
@type string
|
436
|
+
@default null
|
437
|
+
**/
|
438
|
+
name: null,
|
439
|
+
/**
|
440
|
+
Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>.
|
441
|
+
Can be calculated dynamically via function.
|
442
|
+
|
443
|
+
@property pk
|
444
|
+
@type string|object|function
|
445
|
+
@default null
|
446
|
+
**/
|
447
|
+
pk: null,
|
448
|
+
/**
|
449
|
+
Initial value. If not defined - will be taken from element's content.
|
450
|
+
For __select__ type should be defined (as it is ID of shown text).
|
451
|
+
|
452
|
+
@property value
|
453
|
+
@type string|object
|
454
|
+
@default null
|
455
|
+
**/
|
456
|
+
value: null,
|
457
|
+
/**
|
458
|
+
Strategy for sending data on server. Can be <code>auto|always|never</code>.
|
459
|
+
When 'auto' data will be sent on server only if pk defined, otherwise new value will be stored in element.
|
460
|
+
|
461
|
+
@property send
|
462
|
+
@type string
|
463
|
+
@default 'auto'
|
464
|
+
**/
|
465
|
+
send: 'auto',
|
466
|
+
/**
|
467
|
+
Function for client-side validation. If returns string - means validation not passed and string showed as error.
|
468
|
+
|
469
|
+
@property validate
|
470
|
+
@type function
|
471
|
+
@default null
|
472
|
+
@example
|
473
|
+
validate: function(value) {
|
474
|
+
if($.trim(value) == '') {
|
475
|
+
return 'This field is required';
|
476
|
+
}
|
477
|
+
}
|
478
|
+
**/
|
479
|
+
validate: null,
|
480
|
+
/**
|
481
|
+
Success callback. Called when value successfully sent on server and **response status = 200**.
|
482
|
+
Useful to work with json response. For example, if your backend response can be <code>{success: true}</code>
|
483
|
+
or <code>{success: false, msg: "server error"}</code> you can check it inside this callback.
|
484
|
+
If it returns **string** - means error occured and string is shown as error message.
|
485
|
+
If it returns **object like** <code>{newValue: <something>}</code> - it overwrites value, submitted by user.
|
486
|
+
Otherwise newValue simply rendered into element.
|
487
|
+
|
488
|
+
@property success
|
489
|
+
@type function
|
490
|
+
@default null
|
491
|
+
@example
|
492
|
+
success: function(response, newValue) {
|
493
|
+
if(!response.success) return response.msg;
|
494
|
+
}
|
495
|
+
**/
|
496
|
+
success: null,
|
497
|
+
/**
|
498
|
+
Additional options for ajax request.
|
499
|
+
List of values: http://api.jquery.com/jQuery.ajax
|
500
|
+
|
501
|
+
@property ajaxOptions
|
502
|
+
@type object
|
503
|
+
@default null
|
504
|
+
@since 1.1.1
|
505
|
+
@example
|
506
|
+
ajaxOptions: {
|
507
|
+
type: 'put',
|
508
|
+
dataType: 'json'
|
509
|
+
}
|
510
|
+
**/
|
511
|
+
ajaxOptions: null,
|
512
|
+
/**
|
513
|
+
Whether to show buttons or not.
|
514
|
+
Form without buttons is auto-submitted.
|
515
|
+
|
516
|
+
@property showbuttons
|
517
|
+
@type boolean
|
518
|
+
@default true
|
519
|
+
@since 1.1.1
|
520
|
+
**/
|
521
|
+
showbuttons: true,
|
522
|
+
/**
|
523
|
+
Scope for callback methods (success, validate).
|
524
|
+
If <code>null</code> means editableform instance itself.
|
525
|
+
|
526
|
+
@property scope
|
527
|
+
@type DOMElement|object
|
528
|
+
@default null
|
529
|
+
@since 1.2.0
|
530
|
+
@private
|
531
|
+
**/
|
532
|
+
scope: null,
|
533
|
+
/**
|
534
|
+
Whether to save or cancel value when it was not changed but form was submitted
|
535
|
+
|
536
|
+
@property savenochange
|
537
|
+
@type boolean
|
538
|
+
@default false
|
539
|
+
@since 1.2.0
|
540
|
+
**/
|
541
|
+
savenochange: false
|
542
|
+
};
|
543
|
+
|
544
|
+
/*
|
545
|
+
Note: following params could redefined in engine: bootstrap or jqueryui:
|
546
|
+
Classes 'control-group' and 'editable-error-block' must always present!
|
547
|
+
*/
|
548
|
+
$.fn.editableform.template = '<form class="form-inline editableform">'+
|
549
|
+
'<div class="control-group">' +
|
550
|
+
'<div><div class="editable-input"></div><div class="editable-buttons"></div></div>'+
|
551
|
+
'<div class="editable-error-block"></div>' +
|
552
|
+
'</div>' +
|
553
|
+
'</form>';
|
554
|
+
|
555
|
+
//loading div
|
556
|
+
$.fn.editableform.loading = '<div class="editableform-loading"></div>';
|
557
|
+
|
558
|
+
//buttons
|
559
|
+
$.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+
|
560
|
+
'<button type="button" class="editable-cancel">cancel</button>';
|
561
|
+
|
562
|
+
//error class attached to control-group
|
563
|
+
$.fn.editableform.errorGroupClass = null;
|
564
|
+
|
565
|
+
//error class attached to editable-error-block
|
566
|
+
$.fn.editableform.errorBlockClass = 'editable-error';
|
567
|
+
}(window.jQuery));
|
568
|
+
/**
|
569
|
+
* EditableForm utilites
|
570
|
+
*/
|
571
|
+
(function ($) {
|
572
|
+
//utils
|
573
|
+
$.fn.editableutils = {
|
574
|
+
/**
|
575
|
+
* classic JS inheritance function
|
576
|
+
*/
|
577
|
+
inherit: function (Child, Parent) {
|
578
|
+
var F = function() { };
|
579
|
+
F.prototype = Parent.prototype;
|
580
|
+
Child.prototype = new F();
|
581
|
+
Child.prototype.constructor = Child;
|
582
|
+
Child.superclass = Parent.prototype;
|
583
|
+
},
|
584
|
+
|
585
|
+
/**
|
586
|
+
* set caret position in input
|
587
|
+
* see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
|
588
|
+
*/
|
589
|
+
setCursorPosition: function(elem, pos) {
|
590
|
+
if (elem.setSelectionRange) {
|
591
|
+
elem.setSelectionRange(pos, pos);
|
592
|
+
} else if (elem.createTextRange) {
|
593
|
+
var range = elem.createTextRange();
|
594
|
+
range.collapse(true);
|
595
|
+
range.moveEnd('character', pos);
|
596
|
+
range.moveStart('character', pos);
|
597
|
+
range.select();
|
598
|
+
}
|
599
|
+
},
|
600
|
+
|
601
|
+
/**
|
602
|
+
* function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)
|
603
|
+
* That allows such code as: <a data-source="{'a': 'b', 'c': 'd'}">
|
604
|
+
* safe = true --> means no exception will be thrown
|
605
|
+
* for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery
|
606
|
+
*/
|
607
|
+
tryParseJson: function(s, safe) {
|
608
|
+
if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {
|
609
|
+
if (safe) {
|
610
|
+
try {
|
611
|
+
/*jslint evil: true*/
|
612
|
+
s = (new Function('return ' + s))();
|
613
|
+
/*jslint evil: false*/
|
614
|
+
} catch (e) {} finally {
|
615
|
+
return s;
|
616
|
+
}
|
617
|
+
} else {
|
618
|
+
/*jslint evil: true*/
|
619
|
+
s = (new Function('return ' + s))();
|
620
|
+
/*jslint evil: false*/
|
621
|
+
}
|
622
|
+
}
|
623
|
+
return s;
|
624
|
+
},
|
625
|
+
|
626
|
+
/**
|
627
|
+
* slice object by specified keys
|
628
|
+
*/
|
629
|
+
sliceObj: function(obj, keys, caseSensitive /* default: false */) {
|
630
|
+
var key, keyLower, newObj = {};
|
631
|
+
|
632
|
+
if (!$.isArray(keys) || !keys.length) {
|
633
|
+
return newObj;
|
634
|
+
}
|
635
|
+
|
636
|
+
for (var i = 0; i < keys.length; i++) {
|
637
|
+
key = keys[i];
|
638
|
+
if (obj.hasOwnProperty(key)) {
|
639
|
+
newObj[key] = obj[key];
|
640
|
+
}
|
641
|
+
|
642
|
+
if(caseSensitive === true) {
|
643
|
+
continue;
|
644
|
+
}
|
645
|
+
|
646
|
+
//when getting data-* attributes via $.data() it's converted to lowercase.
|
647
|
+
//details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery
|
648
|
+
//workaround is code below.
|
649
|
+
keyLower = key.toLowerCase();
|
650
|
+
if (obj.hasOwnProperty(keyLower)) {
|
651
|
+
newObj[key] = obj[keyLower];
|
652
|
+
}
|
653
|
+
}
|
654
|
+
|
655
|
+
return newObj;
|
656
|
+
},
|
657
|
+
|
658
|
+
/*
|
659
|
+
exclude complex objects from $.data() before pass to config
|
660
|
+
*/
|
661
|
+
getConfigData: function($element) {
|
662
|
+
var data = {};
|
663
|
+
$.each($element.data(), function(k, v) {
|
664
|
+
if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {
|
665
|
+
data[k] = v;
|
666
|
+
}
|
667
|
+
});
|
668
|
+
return data;
|
669
|
+
},
|
670
|
+
|
671
|
+
/*
|
672
|
+
returns keys of object
|
673
|
+
*/
|
674
|
+
objectKeys: function(o) {
|
675
|
+
if (Object.keys) {
|
676
|
+
return Object.keys(o);
|
677
|
+
} else {
|
678
|
+
if (o !== Object(o)) {
|
679
|
+
throw new TypeError('Object.keys called on a non-object');
|
680
|
+
}
|
681
|
+
var k=[], p;
|
682
|
+
for (p in o) {
|
683
|
+
if (Object.prototype.hasOwnProperty.call(o,p)) {
|
684
|
+
k.push(p);
|
685
|
+
}
|
686
|
+
}
|
687
|
+
return k;
|
688
|
+
}
|
689
|
+
|
690
|
+
},
|
691
|
+
|
692
|
+
/**
|
693
|
+
method to escape html.
|
694
|
+
**/
|
695
|
+
escape: function(str) {
|
696
|
+
return $('<div>').text(str).html();
|
697
|
+
},
|
698
|
+
|
699
|
+
/*
|
700
|
+
returns array items from sourceData having value property equal or inArray of 'value'
|
701
|
+
*/
|
702
|
+
itemsByValue: function(value, sourceData, valueProp) {
|
703
|
+
if(!sourceData || value === null) {
|
704
|
+
return [];
|
705
|
+
}
|
706
|
+
|
707
|
+
valueProp = valueProp || 'value';
|
708
|
+
|
709
|
+
var isValArray = $.isArray(value),
|
710
|
+
result = [],
|
711
|
+
that = this;
|
712
|
+
|
713
|
+
$.each(sourceData, function(i, o) {
|
714
|
+
if(o.children) {
|
715
|
+
result = result.concat(that.itemsByValue(value, o.children));
|
716
|
+
} else {
|
717
|
+
/*jslint eqeq: true*/
|
718
|
+
if(isValArray) {
|
719
|
+
if($.grep(value, function(v){ return v == (o && typeof o === 'object' ? o[valueProp] : o); }).length) {
|
720
|
+
result.push(o);
|
721
|
+
}
|
722
|
+
} else {
|
723
|
+
if(value == (o && typeof o === 'object' ? o[valueProp] : o)) {
|
724
|
+
result.push(o);
|
725
|
+
}
|
726
|
+
}
|
727
|
+
/*jslint eqeq: false*/
|
728
|
+
}
|
729
|
+
});
|
730
|
+
|
731
|
+
return result;
|
732
|
+
},
|
733
|
+
|
734
|
+
/*
|
735
|
+
Returns input by options: type, mode.
|
736
|
+
*/
|
737
|
+
createInput: function(options) {
|
738
|
+
var TypeConstructor, typeOptions, input,
|
739
|
+
type = options.type;
|
740
|
+
|
741
|
+
//`date` is some kind of virtual type that is transformed to one of exact types
|
742
|
+
//depending on mode and core lib
|
743
|
+
if(type === 'date') {
|
744
|
+
//inline
|
745
|
+
if(options.mode === 'inline') {
|
746
|
+
if($.fn.editabletypes.datefield) {
|
747
|
+
type = 'datefield';
|
748
|
+
} else if($.fn.editabletypes.dateuifield) {
|
749
|
+
type = 'dateuifield';
|
750
|
+
}
|
751
|
+
//popup
|
752
|
+
} else {
|
753
|
+
if($.fn.editabletypes.date) {
|
754
|
+
type = 'date';
|
755
|
+
} else if($.fn.editabletypes.dateui) {
|
756
|
+
type = 'dateui';
|
757
|
+
}
|
758
|
+
}
|
759
|
+
|
760
|
+
//if type still `date` and not exist in types, replace with `combodate` that is base input
|
761
|
+
if(type === 'date' && !$.fn.editabletypes.date) {
|
762
|
+
type = 'combodate';
|
763
|
+
}
|
764
|
+
}
|
765
|
+
|
766
|
+
//change wysihtml5 to textarea for jquery UI and plain versions
|
767
|
+
if(type === 'wysihtml5' && !$.fn.editabletypes[type]) {
|
768
|
+
type = 'textarea';
|
769
|
+
}
|
770
|
+
|
771
|
+
//create input of specified type. Input will be used for converting value, not in form
|
772
|
+
if(typeof $.fn.editabletypes[type] === 'function') {
|
773
|
+
TypeConstructor = $.fn.editabletypes[type];
|
774
|
+
typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults));
|
775
|
+
input = new TypeConstructor(typeOptions);
|
776
|
+
return input;
|
777
|
+
} else {
|
778
|
+
$.error('Unknown type: '+ type);
|
779
|
+
return false;
|
780
|
+
}
|
781
|
+
}
|
782
|
+
|
783
|
+
};
|
784
|
+
}(window.jQuery));
|
785
|
+
|
786
|
+
/**
|
787
|
+
Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>
|
788
|
+
This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
|
789
|
+
Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br>
|
790
|
+
Applied as jQuery method.
|
791
|
+
|
792
|
+
@class editableContainer
|
793
|
+
@uses editableform
|
794
|
+
**/
|
795
|
+
(function ($) {
|
796
|
+
|
797
|
+
var Popup = function (element, options) {
|
798
|
+
this.init(element, options);
|
799
|
+
};
|
800
|
+
|
801
|
+
var Inline = function (element, options) {
|
802
|
+
this.init(element, options);
|
803
|
+
};
|
804
|
+
|
805
|
+
//methods
|
806
|
+
Popup.prototype = {
|
807
|
+
containerName: null, //tbd in child class
|
808
|
+
innerCss: null, //tbd in child class
|
809
|
+
init: function(element, options) {
|
810
|
+
this.$element = $(element);
|
811
|
+
//since 1.4.1 container do not use data-* directly as they already merged into options.
|
812
|
+
this.options = $.extend({}, $.fn.editableContainer.defaults, options);
|
813
|
+
this.splitOptions();
|
814
|
+
|
815
|
+
//set scope of form callbacks to element
|
816
|
+
this.formOptions.scope = this.$element[0];
|
817
|
+
|
818
|
+
this.initContainer();
|
819
|
+
|
820
|
+
//bind 'destroyed' listener to destroy container when element is removed from dom
|
821
|
+
this.$element.on('destroyed', $.proxy(function(){
|
822
|
+
this.destroy();
|
823
|
+
}, this));
|
824
|
+
|
825
|
+
//attach document handler to close containers on click / escape
|
826
|
+
if(!$(document).data('editable-handlers-attached')) {
|
827
|
+
//close all on escape
|
828
|
+
$(document).on('keyup.editable', function (e) {
|
829
|
+
if (e.which === 27) {
|
830
|
+
$('.editable-open').editableContainer('hide');
|
831
|
+
//todo: return focus on element
|
832
|
+
}
|
833
|
+
});
|
834
|
+
|
835
|
+
//close containers when click outside
|
836
|
+
$(document).on('click.editable', function(e) {
|
837
|
+
var $target = $(e.target), i,
|
838
|
+
exclude_classes = ['.editable-container',
|
839
|
+
'.ui-datepicker-header',
|
840
|
+
'.modal-backdrop',
|
841
|
+
'.bootstrap-wysihtml5-insert-image-modal',
|
842
|
+
'.bootstrap-wysihtml5-insert-link-modal'];
|
843
|
+
|
844
|
+
//if click inside one of exclude classes --> no nothing
|
845
|
+
for(i=0; i<exclude_classes.length; i++) {
|
846
|
+
if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) {
|
847
|
+
return;
|
848
|
+
}
|
849
|
+
}
|
850
|
+
|
851
|
+
//close all open containers (except one - target)
|
852
|
+
Popup.prototype.closeOthers(e.target);
|
853
|
+
});
|
854
|
+
|
855
|
+
$(document).data('editable-handlers-attached', true);
|
856
|
+
}
|
857
|
+
},
|
858
|
+
|
859
|
+
//split options on containerOptions and formOptions
|
860
|
+
splitOptions: function() {
|
861
|
+
this.containerOptions = {};
|
862
|
+
this.formOptions = {};
|
863
|
+
var cDef = $.fn[this.containerName].defaults;
|
864
|
+
//keys defined in container defaults go to container, others go to form
|
865
|
+
for(var k in this.options) {
|
866
|
+
if(k in cDef) {
|
867
|
+
this.containerOptions[k] = this.options[k];
|
868
|
+
} else {
|
869
|
+
this.formOptions[k] = this.options[k];
|
870
|
+
}
|
871
|
+
}
|
872
|
+
},
|
873
|
+
|
874
|
+
/*
|
875
|
+
Returns jquery object of container
|
876
|
+
@method tip()
|
877
|
+
*/
|
878
|
+
tip: function() {
|
879
|
+
return this.container() ? this.container().$tip : null;
|
880
|
+
},
|
881
|
+
|
882
|
+
/* returns container object */
|
883
|
+
container: function() {
|
884
|
+
return this.$element.data(this.containerName);
|
885
|
+
},
|
886
|
+
|
887
|
+
call: function() {
|
888
|
+
this.$element[this.containerName].apply(this.$element, arguments);
|
889
|
+
},
|
890
|
+
|
891
|
+
initContainer: function(){
|
892
|
+
this.call(this.containerOptions);
|
893
|
+
},
|
894
|
+
|
895
|
+
renderForm: function() {
|
896
|
+
this.$form
|
897
|
+
.editableform(this.formOptions)
|
898
|
+
.on({
|
899
|
+
save: $.proxy(this.save, this), //click on submit button (value changed)
|
900
|
+
nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)
|
901
|
+
cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on calcel button
|
902
|
+
show: $.proxy(this.setPosition, this), //re-position container every time form is shown (occurs each time after loading state)
|
903
|
+
rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
|
904
|
+
resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed
|
905
|
+
rendered: $.proxy(function(){
|
906
|
+
/**
|
907
|
+
Fired when container is shown and form is rendered (for select will wait for loading dropdown options)
|
908
|
+
|
909
|
+
@event shown
|
910
|
+
@param {Object} event event object
|
911
|
+
@example
|
912
|
+
$('#username').on('shown', function() {
|
913
|
+
var $tip = $(this).data('editableContainer').tip();
|
914
|
+
$tip.find('input').val('overwriting value of input..');
|
915
|
+
});
|
916
|
+
**/
|
917
|
+
this.$element.triggerHandler('shown');
|
918
|
+
}, this)
|
919
|
+
})
|
920
|
+
.editableform('render');
|
921
|
+
},
|
922
|
+
|
923
|
+
/**
|
924
|
+
Shows container with form
|
925
|
+
@method show()
|
926
|
+
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
927
|
+
**/
|
928
|
+
/* Note: poshytip owerwrites this method totally! */
|
929
|
+
show: function (closeAll) {
|
930
|
+
this.$element.addClass('editable-open');
|
931
|
+
if(closeAll !== false) {
|
932
|
+
//close all open containers (except this)
|
933
|
+
this.closeOthers(this.$element[0]);
|
934
|
+
}
|
935
|
+
|
936
|
+
//show container itself
|
937
|
+
this.innerShow();
|
938
|
+
this.tip().addClass('editable-container');
|
939
|
+
|
940
|
+
/*
|
941
|
+
Currently, form is re-rendered on every show.
|
942
|
+
The main reason is that we dont know, what container will do with content when closed:
|
943
|
+
remove(), detach() or just hide().
|
944
|
+
|
945
|
+
Detaching form itself before hide and re-insert before show is good solution,
|
946
|
+
but visually it looks ugly, as container changes size before hide.
|
947
|
+
*/
|
948
|
+
|
949
|
+
//if form already exist - delete previous data
|
950
|
+
if(this.$form) {
|
951
|
+
//todo: destroy prev data!
|
952
|
+
//this.$form.destroy();
|
953
|
+
}
|
954
|
+
|
955
|
+
this.$form = $('<div>');
|
956
|
+
|
957
|
+
//insert form into container body
|
958
|
+
if(this.tip().is(this.innerCss)) {
|
959
|
+
//for inline container
|
960
|
+
this.tip().append(this.$form);
|
961
|
+
} else {
|
962
|
+
this.tip().find(this.innerCss).append(this.$form);
|
963
|
+
}
|
964
|
+
|
965
|
+
//render form
|
966
|
+
this.renderForm();
|
967
|
+
},
|
968
|
+
|
969
|
+
/**
|
970
|
+
Hides container with form
|
971
|
+
@method hide()
|
972
|
+
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
|
973
|
+
**/
|
974
|
+
hide: function(reason) {
|
975
|
+
if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
|
976
|
+
return;
|
977
|
+
}
|
978
|
+
|
979
|
+
this.$element.removeClass('editable-open');
|
980
|
+
this.innerHide();
|
981
|
+
|
982
|
+
/**
|
983
|
+
Fired when container was hidden. It occurs on both save or cancel.
|
984
|
+
|
985
|
+
@event hidden
|
986
|
+
@param {object} event event object
|
987
|
+
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
|
988
|
+
@example
|
989
|
+
$('#username').on('hidden', function(e, reason) {
|
990
|
+
if(reason === 'save' || reason === 'cancel') {
|
991
|
+
//auto-open next editable
|
992
|
+
$(this).closest('tr').next().find('.editable').editable('show');
|
993
|
+
}
|
994
|
+
});
|
995
|
+
**/
|
996
|
+
this.$element.triggerHandler('hidden', reason);
|
997
|
+
},
|
998
|
+
|
999
|
+
/* internal show method. To be overwritten in child classes */
|
1000
|
+
innerShow: function () {
|
1001
|
+
|
1002
|
+
},
|
1003
|
+
|
1004
|
+
/* internal hide method. To be overwritten in child classes */
|
1005
|
+
innerHide: function () {
|
1006
|
+
|
1007
|
+
},
|
1008
|
+
|
1009
|
+
/**
|
1010
|
+
Toggles container visibility (show / hide)
|
1011
|
+
@method toggle()
|
1012
|
+
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
1013
|
+
**/
|
1014
|
+
toggle: function(closeAll) {
|
1015
|
+
if(this.container() && this.tip() && this.tip().is(':visible')) {
|
1016
|
+
this.hide();
|
1017
|
+
} else {
|
1018
|
+
this.show(closeAll);
|
1019
|
+
}
|
1020
|
+
},
|
1021
|
+
|
1022
|
+
/*
|
1023
|
+
Updates the position of container when content changed.
|
1024
|
+
@method setPosition()
|
1025
|
+
*/
|
1026
|
+
setPosition: function() {
|
1027
|
+
//tbd in child class
|
1028
|
+
},
|
1029
|
+
|
1030
|
+
save: function(e, params) {
|
1031
|
+
/**
|
1032
|
+
Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance
|
1033
|
+
|
1034
|
+
@event save
|
1035
|
+
@param {Object} event event object
|
1036
|
+
@param {Object} params additional params
|
1037
|
+
@param {mixed} params.newValue submitted value
|
1038
|
+
@param {Object} params.response ajax response
|
1039
|
+
@example
|
1040
|
+
$('#username').on('save', function(e, params) {
|
1041
|
+
//assuming server response: '{success: true}'
|
1042
|
+
var pk = $(this).data('editableContainer').options.pk;
|
1043
|
+
if(params.response && params.response.success) {
|
1044
|
+
alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
|
1045
|
+
} else {
|
1046
|
+
alert('error!');
|
1047
|
+
}
|
1048
|
+
});
|
1049
|
+
**/
|
1050
|
+
this.$element.triggerHandler('save', params);
|
1051
|
+
|
1052
|
+
//hide must be after trigger, as saving value may require methods od plugin, applied to input
|
1053
|
+
this.hide('save');
|
1054
|
+
},
|
1055
|
+
|
1056
|
+
/**
|
1057
|
+
Sets new option
|
1058
|
+
|
1059
|
+
@method option(key, value)
|
1060
|
+
@param {string} key
|
1061
|
+
@param {mixed} value
|
1062
|
+
**/
|
1063
|
+
option: function(key, value) {
|
1064
|
+
this.options[key] = value;
|
1065
|
+
if(key in this.containerOptions) {
|
1066
|
+
this.containerOptions[key] = value;
|
1067
|
+
this.setContainerOption(key, value);
|
1068
|
+
} else {
|
1069
|
+
this.formOptions[key] = value;
|
1070
|
+
if(this.$form) {
|
1071
|
+
this.$form.editableform('option', key, value);
|
1072
|
+
}
|
1073
|
+
}
|
1074
|
+
},
|
1075
|
+
|
1076
|
+
setContainerOption: function(key, value) {
|
1077
|
+
this.call('option', key, value);
|
1078
|
+
},
|
1079
|
+
|
1080
|
+
/**
|
1081
|
+
Destroys the container instance
|
1082
|
+
@method destroy()
|
1083
|
+
**/
|
1084
|
+
destroy: function() {
|
1085
|
+
this.hide();
|
1086
|
+
this.innerDestroy();
|
1087
|
+
this.$element.off('destroyed');
|
1088
|
+
this.$element.removeData('editableContainer');
|
1089
|
+
},
|
1090
|
+
|
1091
|
+
/* to be overwritten in child classes */
|
1092
|
+
innerDestroy: function() {
|
1093
|
+
|
1094
|
+
},
|
1095
|
+
|
1096
|
+
/*
|
1097
|
+
Closes other containers except one related to passed element.
|
1098
|
+
Other containers can be cancelled or submitted (depends on onblur option)
|
1099
|
+
*/
|
1100
|
+
closeOthers: function(element) {
|
1101
|
+
$('.editable-open').each(function(i, el){
|
1102
|
+
//do nothing with passed element and it's children
|
1103
|
+
if(el === element || $(el).find(element).length) {
|
1104
|
+
return;
|
1105
|
+
}
|
1106
|
+
|
1107
|
+
//otherwise cancel or submit all open containers
|
1108
|
+
var $el = $(el),
|
1109
|
+
ec = $el.data('editableContainer');
|
1110
|
+
|
1111
|
+
if(!ec) {
|
1112
|
+
return;
|
1113
|
+
}
|
1114
|
+
|
1115
|
+
if(ec.options.onblur === 'cancel') {
|
1116
|
+
$el.data('editableContainer').hide('onblur');
|
1117
|
+
} else if(ec.options.onblur === 'submit') {
|
1118
|
+
$el.data('editableContainer').tip().find('form').submit();
|
1119
|
+
}
|
1120
|
+
});
|
1121
|
+
|
1122
|
+
},
|
1123
|
+
|
1124
|
+
/**
|
1125
|
+
Activates input of visible container (e.g. set focus)
|
1126
|
+
@method activate()
|
1127
|
+
**/
|
1128
|
+
activate: function() {
|
1129
|
+
if(this.tip && this.tip().is(':visible') && this.$form) {
|
1130
|
+
this.$form.data('editableform').input.activate();
|
1131
|
+
}
|
1132
|
+
}
|
1133
|
+
|
1134
|
+
};
|
1135
|
+
|
1136
|
+
/**
|
1137
|
+
jQuery method to initialize editableContainer.
|
1138
|
+
|
1139
|
+
@method $().editableContainer(options)
|
1140
|
+
@params {Object} options
|
1141
|
+
@example
|
1142
|
+
$('#edit').editableContainer({
|
1143
|
+
type: 'text',
|
1144
|
+
url: '/post',
|
1145
|
+
pk: 1,
|
1146
|
+
value: 'hello'
|
1147
|
+
});
|
1148
|
+
**/
|
1149
|
+
$.fn.editableContainer = function (option) {
|
1150
|
+
var args = arguments;
|
1151
|
+
return this.each(function () {
|
1152
|
+
var $this = $(this),
|
1153
|
+
dataKey = 'editableContainer',
|
1154
|
+
data = $this.data(dataKey),
|
1155
|
+
options = typeof option === 'object' && option,
|
1156
|
+
Constructor = (options.mode === 'inline') ? Inline : Popup;
|
1157
|
+
|
1158
|
+
if (!data) {
|
1159
|
+
$this.data(dataKey, (data = new Constructor(this, options)));
|
1160
|
+
}
|
1161
|
+
|
1162
|
+
if (typeof option === 'string') { //call method
|
1163
|
+
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
1164
|
+
}
|
1165
|
+
});
|
1166
|
+
};
|
1167
|
+
|
1168
|
+
//store constructors
|
1169
|
+
$.fn.editableContainer.Popup = Popup;
|
1170
|
+
$.fn.editableContainer.Inline = Inline;
|
1171
|
+
|
1172
|
+
//defaults
|
1173
|
+
$.fn.editableContainer.defaults = {
|
1174
|
+
/**
|
1175
|
+
Initial value of form input
|
1176
|
+
|
1177
|
+
@property value
|
1178
|
+
@type mixed
|
1179
|
+
@default null
|
1180
|
+
@private
|
1181
|
+
**/
|
1182
|
+
value: null,
|
1183
|
+
/**
|
1184
|
+
Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container.
|
1185
|
+
|
1186
|
+
@property placement
|
1187
|
+
@type string
|
1188
|
+
@default 'top'
|
1189
|
+
**/
|
1190
|
+
placement: 'top',
|
1191
|
+
/**
|
1192
|
+
Whether to hide container on save/cancel.
|
1193
|
+
|
1194
|
+
@property autohide
|
1195
|
+
@type boolean
|
1196
|
+
@default true
|
1197
|
+
@private
|
1198
|
+
**/
|
1199
|
+
autohide: true,
|
1200
|
+
/**
|
1201
|
+
Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>.
|
1202
|
+
Setting <code>ignore</code> allows to have several containers open.
|
1203
|
+
|
1204
|
+
@property onblur
|
1205
|
+
@type string
|
1206
|
+
@default 'cancel'
|
1207
|
+
@since 1.1.1
|
1208
|
+
**/
|
1209
|
+
onblur: 'cancel',
|
1210
|
+
|
1211
|
+
/**
|
1212
|
+
Animation speed (inline mode)
|
1213
|
+
@property anim
|
1214
|
+
@type string
|
1215
|
+
@default 'fast'
|
1216
|
+
**/
|
1217
|
+
anim: 'fast',
|
1218
|
+
|
1219
|
+
/**
|
1220
|
+
Mode of editable, can be `popup` or `inline`
|
1221
|
+
|
1222
|
+
@property mode
|
1223
|
+
@type string
|
1224
|
+
@default 'popup'
|
1225
|
+
@since 1.4.0
|
1226
|
+
**/
|
1227
|
+
mode: 'popup'
|
1228
|
+
};
|
1229
|
+
|
1230
|
+
/*
|
1231
|
+
* workaround to have 'destroyed' event to destroy popover when element is destroyed
|
1232
|
+
* see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
|
1233
|
+
*/
|
1234
|
+
jQuery.event.special.destroyed = {
|
1235
|
+
remove: function(o) {
|
1236
|
+
if (o.handler) {
|
1237
|
+
o.handler();
|
1238
|
+
}
|
1239
|
+
}
|
1240
|
+
};
|
1241
|
+
|
1242
|
+
}(window.jQuery));
|
1243
|
+
|
1244
|
+
/**
|
1245
|
+
* Editable Inline
|
1246
|
+
* ---------------------
|
1247
|
+
*/
|
1248
|
+
(function ($) {
|
1249
|
+
|
1250
|
+
//copy prototype from EditableContainer
|
1251
|
+
//extend methods
|
1252
|
+
$.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, {
|
1253
|
+
containerName: 'editableform',
|
1254
|
+
innerCss: '.editable-inline',
|
1255
|
+
|
1256
|
+
initContainer: function(){
|
1257
|
+
//container is <span> element
|
1258
|
+
this.$tip = $('<span></span>').addClass('editable-inline');
|
1259
|
+
|
1260
|
+
//convert anim to miliseconds (int)
|
1261
|
+
if(!this.options.anim) {
|
1262
|
+
this.options.anim = 0;
|
1263
|
+
}
|
1264
|
+
},
|
1265
|
+
|
1266
|
+
splitOptions: function() {
|
1267
|
+
//all options are passed to form
|
1268
|
+
this.containerOptions = {};
|
1269
|
+
this.formOptions = this.options;
|
1270
|
+
},
|
1271
|
+
|
1272
|
+
tip: function() {
|
1273
|
+
return this.$tip;
|
1274
|
+
},
|
1275
|
+
|
1276
|
+
innerShow: function () {
|
1277
|
+
this.$element.hide();
|
1278
|
+
this.tip().insertAfter(this.$element).show();
|
1279
|
+
},
|
1280
|
+
|
1281
|
+
innerHide: function () {
|
1282
|
+
this.$tip.hide(this.options.anim, $.proxy(function() {
|
1283
|
+
this.$element.show();
|
1284
|
+
this.innerDestroy();
|
1285
|
+
}, this));
|
1286
|
+
},
|
1287
|
+
|
1288
|
+
innerDestroy: function() {
|
1289
|
+
if(this.tip()) {
|
1290
|
+
this.tip().empty().remove();
|
1291
|
+
}
|
1292
|
+
}
|
1293
|
+
});
|
1294
|
+
|
1295
|
+
}(window.jQuery));
|
1296
|
+
/**
|
1297
|
+
Makes editable any HTML element on the page. Applied as jQuery method.
|
1298
|
+
|
1299
|
+
@class editable
|
1300
|
+
@uses editableContainer
|
1301
|
+
**/
|
1302
|
+
(function ($) {
|
1303
|
+
|
1304
|
+
var Editable = function (element, options) {
|
1305
|
+
this.$element = $(element);
|
1306
|
+
//data-* has more priority over js options: because dynamically created elements may change data-*
|
1307
|
+
this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element));
|
1308
|
+
if(this.options.selector) {
|
1309
|
+
this.initLive();
|
1310
|
+
} else {
|
1311
|
+
this.init();
|
1312
|
+
}
|
1313
|
+
};
|
1314
|
+
|
1315
|
+
Editable.prototype = {
|
1316
|
+
constructor: Editable,
|
1317
|
+
init: function () {
|
1318
|
+
var isValueByText = false,
|
1319
|
+
doAutotext, finalize;
|
1320
|
+
|
1321
|
+
//name
|
1322
|
+
this.options.name = this.options.name || this.$element.attr('id');
|
1323
|
+
|
1324
|
+
//create input of specified type. Input will be used for converting value, not in form
|
1325
|
+
this.input = $.fn.editableutils.createInput(this.options);
|
1326
|
+
if(!this.input) {
|
1327
|
+
return;
|
1328
|
+
}
|
1329
|
+
|
1330
|
+
//set value from settings or by element's text
|
1331
|
+
if (this.options.value === undefined || this.options.value === null) {
|
1332
|
+
this.value = this.input.html2value($.trim(this.$element.html()));
|
1333
|
+
isValueByText = true;
|
1334
|
+
} else {
|
1335
|
+
/*
|
1336
|
+
value can be string when received from 'data-value' attribute
|
1337
|
+
for complext objects value can be set as json string in data-value attribute,
|
1338
|
+
e.g. data-value="{city: 'Moscow', street: 'Lenina'}"
|
1339
|
+
*/
|
1340
|
+
this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true);
|
1341
|
+
if(typeof this.options.value === 'string') {
|
1342
|
+
this.value = this.input.str2value(this.options.value);
|
1343
|
+
} else {
|
1344
|
+
this.value = this.options.value;
|
1345
|
+
}
|
1346
|
+
}
|
1347
|
+
|
1348
|
+
//add 'editable' class to every editable element
|
1349
|
+
this.$element.addClass('editable');
|
1350
|
+
|
1351
|
+
//attach handler activating editable. In disabled mode it just prevent default action (useful for links)
|
1352
|
+
if(this.options.toggle !== 'manual') {
|
1353
|
+
this.$element.addClass('editable-click');
|
1354
|
+
this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
|
1355
|
+
//prevent following link
|
1356
|
+
e.preventDefault();
|
1357
|
+
|
1358
|
+
//stop propagation not required because in document click handler it checks event target
|
1359
|
+
//e.stopPropagation();
|
1360
|
+
|
1361
|
+
if(this.options.toggle === 'mouseenter') {
|
1362
|
+
//for hover only show container
|
1363
|
+
this.show();
|
1364
|
+
} else {
|
1365
|
+
//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
|
1366
|
+
var closeAll = (this.options.toggle !== 'click');
|
1367
|
+
this.toggle(closeAll);
|
1368
|
+
}
|
1369
|
+
}, this));
|
1370
|
+
} else {
|
1371
|
+
this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
|
1372
|
+
}
|
1373
|
+
|
1374
|
+
//check conditions for autotext:
|
1375
|
+
//if value was generated by text or value is empty, no sense to run autotext
|
1376
|
+
doAutotext = !isValueByText && this.value !== null && this.value !== undefined;
|
1377
|
+
doAutotext &= (this.options.autotext === 'always') || (this.options.autotext === 'auto' && !this.$element.text().length);
|
1378
|
+
$.when(doAutotext ? this.render() : true).then($.proxy(function() {
|
1379
|
+
if(this.options.disabled) {
|
1380
|
+
this.disable();
|
1381
|
+
} else {
|
1382
|
+
this.enable();
|
1383
|
+
}
|
1384
|
+
/**
|
1385
|
+
Fired when element was initialized by editable method.
|
1386
|
+
|
1387
|
+
@event init
|
1388
|
+
@param {Object} event event object
|
1389
|
+
@param {Object} editable editable instance (as here it cannot accessed via data('editable'))
|
1390
|
+
@since 1.2.0
|
1391
|
+
@example
|
1392
|
+
$('#username').on('init', function(e, editable) {
|
1393
|
+
alert('initialized ' + editable.options.name);
|
1394
|
+
});
|
1395
|
+
**/
|
1396
|
+
this.$element.triggerHandler('init', this);
|
1397
|
+
}, this));
|
1398
|
+
},
|
1399
|
+
|
1400
|
+
/*
|
1401
|
+
Initializes parent element for live editables
|
1402
|
+
*/
|
1403
|
+
initLive: function() {
|
1404
|
+
//store selector
|
1405
|
+
var selector = this.options.selector;
|
1406
|
+
//modify options for child elements
|
1407
|
+
this.options.selector = false;
|
1408
|
+
this.options.autotext = 'never';
|
1409
|
+
//listen toggle events
|
1410
|
+
this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){
|
1411
|
+
var $target = $(e.target);
|
1412
|
+
if(!$target.data('editable')) {
|
1413
|
+
$target.editable(this.options).trigger(e);
|
1414
|
+
}
|
1415
|
+
}, this));
|
1416
|
+
},
|
1417
|
+
|
1418
|
+
/*
|
1419
|
+
Renders value into element's text.
|
1420
|
+
Can call custom display method from options.
|
1421
|
+
Can return deferred object.
|
1422
|
+
@method render()
|
1423
|
+
@param {mixed} response server response (if exist) to pass into display function
|
1424
|
+
*/
|
1425
|
+
render: function(response) {
|
1426
|
+
//do not display anything
|
1427
|
+
if(this.options.display === false) {
|
1428
|
+
return;
|
1429
|
+
}
|
1430
|
+
|
1431
|
+
//if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded
|
1432
|
+
if(this.input.value2htmlFinal) {
|
1433
|
+
return this.input.value2html(this.value, this.$element[0], this.options.display, response);
|
1434
|
+
//if display method defined --> use it
|
1435
|
+
} else if(typeof this.options.display === 'function') {
|
1436
|
+
return this.options.display.call(this.$element[0], this.value, response);
|
1437
|
+
//else use input's original value2html() method
|
1438
|
+
} else {
|
1439
|
+
return this.input.value2html(this.value, this.$element[0]);
|
1440
|
+
}
|
1441
|
+
},
|
1442
|
+
|
1443
|
+
/**
|
1444
|
+
Enables editable
|
1445
|
+
@method enable()
|
1446
|
+
**/
|
1447
|
+
enable: function() {
|
1448
|
+
this.options.disabled = false;
|
1449
|
+
this.$element.removeClass('editable-disabled');
|
1450
|
+
this.handleEmpty(this.isEmpty);
|
1451
|
+
if(this.options.toggle !== 'manual') {
|
1452
|
+
if(this.$element.attr('tabindex') === '-1') {
|
1453
|
+
this.$element.removeAttr('tabindex');
|
1454
|
+
}
|
1455
|
+
}
|
1456
|
+
},
|
1457
|
+
|
1458
|
+
/**
|
1459
|
+
Disables editable
|
1460
|
+
@method disable()
|
1461
|
+
**/
|
1462
|
+
disable: function() {
|
1463
|
+
this.options.disabled = true;
|
1464
|
+
this.hide();
|
1465
|
+
this.$element.addClass('editable-disabled');
|
1466
|
+
this.handleEmpty(this.isEmpty);
|
1467
|
+
//do not stop focus on this element
|
1468
|
+
this.$element.attr('tabindex', -1);
|
1469
|
+
},
|
1470
|
+
|
1471
|
+
/**
|
1472
|
+
Toggles enabled / disabled state of editable element
|
1473
|
+
@method toggleDisabled()
|
1474
|
+
**/
|
1475
|
+
toggleDisabled: function() {
|
1476
|
+
if(this.options.disabled) {
|
1477
|
+
this.enable();
|
1478
|
+
} else {
|
1479
|
+
this.disable();
|
1480
|
+
}
|
1481
|
+
},
|
1482
|
+
|
1483
|
+
/**
|
1484
|
+
Sets new option
|
1485
|
+
|
1486
|
+
@method option(key, value)
|
1487
|
+
@param {string|object} key option name or object with several options
|
1488
|
+
@param {mixed} value option new value
|
1489
|
+
@example
|
1490
|
+
$('.editable').editable('option', 'pk', 2);
|
1491
|
+
**/
|
1492
|
+
option: function(key, value) {
|
1493
|
+
//set option(s) by object
|
1494
|
+
if(key && typeof key === 'object') {
|
1495
|
+
$.each(key, $.proxy(function(k, v){
|
1496
|
+
this.option($.trim(k), v);
|
1497
|
+
}, this));
|
1498
|
+
return;
|
1499
|
+
}
|
1500
|
+
|
1501
|
+
//set option by string
|
1502
|
+
this.options[key] = value;
|
1503
|
+
|
1504
|
+
//disabled
|
1505
|
+
if(key === 'disabled') {
|
1506
|
+
return value ? this.disable() : this.enable();
|
1507
|
+
}
|
1508
|
+
|
1509
|
+
//value
|
1510
|
+
if(key === 'value') {
|
1511
|
+
this.setValue(value);
|
1512
|
+
}
|
1513
|
+
|
1514
|
+
//transfer new option to container!
|
1515
|
+
if(this.container) {
|
1516
|
+
this.container.option(key, value);
|
1517
|
+
}
|
1518
|
+
|
1519
|
+
//pass option to input directly (as it points to the same in form)
|
1520
|
+
if(this.input.option) {
|
1521
|
+
this.input.option(key, value);
|
1522
|
+
}
|
1523
|
+
|
1524
|
+
},
|
1525
|
+
|
1526
|
+
/*
|
1527
|
+
* set emptytext if element is empty
|
1528
|
+
*/
|
1529
|
+
handleEmpty: function (isEmpty) {
|
1530
|
+
//do not handle empty if we do not display anything
|
1531
|
+
if(this.options.display === false) {
|
1532
|
+
return;
|
1533
|
+
}
|
1534
|
+
|
1535
|
+
this.isEmpty = isEmpty !== undefined ? isEmpty : $.trim(this.$element.text()) === '';
|
1536
|
+
|
1537
|
+
//emptytext shown only for enabled
|
1538
|
+
if(!this.options.disabled) {
|
1539
|
+
if (this.isEmpty) {
|
1540
|
+
this.$element.text(this.options.emptytext);
|
1541
|
+
if(this.options.emptyclass) {
|
1542
|
+
this.$element.addClass(this.options.emptyclass);
|
1543
|
+
}
|
1544
|
+
} else if(this.options.emptyclass) {
|
1545
|
+
this.$element.removeClass(this.options.emptyclass);
|
1546
|
+
}
|
1547
|
+
} else {
|
1548
|
+
//below required if element disable property was changed
|
1549
|
+
if(this.isEmpty) {
|
1550
|
+
this.$element.empty();
|
1551
|
+
if(this.options.emptyclass) {
|
1552
|
+
this.$element.removeClass(this.options.emptyclass);
|
1553
|
+
}
|
1554
|
+
}
|
1555
|
+
}
|
1556
|
+
},
|
1557
|
+
|
1558
|
+
/**
|
1559
|
+
Shows container with form
|
1560
|
+
@method show()
|
1561
|
+
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
1562
|
+
**/
|
1563
|
+
show: function (closeAll) {
|
1564
|
+
if(this.options.disabled) {
|
1565
|
+
return;
|
1566
|
+
}
|
1567
|
+
|
1568
|
+
//init editableContainer: popover, tooltip, inline, etc..
|
1569
|
+
if(!this.container) {
|
1570
|
+
var containerOptions = $.extend({}, this.options, {
|
1571
|
+
value: this.value,
|
1572
|
+
input: this.input //pass input to form (as it is already created)
|
1573
|
+
});
|
1574
|
+
this.$element.editableContainer(containerOptions);
|
1575
|
+
//listen `save` event
|
1576
|
+
this.$element.on("save.internal", $.proxy(this.save, this));
|
1577
|
+
this.container = this.$element.data('editableContainer');
|
1578
|
+
} else if(this.container.tip().is(':visible')) {
|
1579
|
+
return;
|
1580
|
+
}
|
1581
|
+
|
1582
|
+
//show container
|
1583
|
+
this.container.show(closeAll);
|
1584
|
+
},
|
1585
|
+
|
1586
|
+
/**
|
1587
|
+
Hides container with form
|
1588
|
+
@method hide()
|
1589
|
+
**/
|
1590
|
+
hide: function () {
|
1591
|
+
if(this.container) {
|
1592
|
+
this.container.hide();
|
1593
|
+
}
|
1594
|
+
},
|
1595
|
+
|
1596
|
+
/**
|
1597
|
+
Toggles container visibility (show / hide)
|
1598
|
+
@method toggle()
|
1599
|
+
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
1600
|
+
**/
|
1601
|
+
toggle: function(closeAll) {
|
1602
|
+
if(this.container && this.container.tip().is(':visible')) {
|
1603
|
+
this.hide();
|
1604
|
+
} else {
|
1605
|
+
this.show(closeAll);
|
1606
|
+
}
|
1607
|
+
},
|
1608
|
+
|
1609
|
+
/*
|
1610
|
+
* called when form was submitted
|
1611
|
+
*/
|
1612
|
+
save: function(e, params) {
|
1613
|
+
//mark element with unsaved class if needed
|
1614
|
+
if(this.options.unsavedclass) {
|
1615
|
+
/*
|
1616
|
+
Add unsaved css to element if:
|
1617
|
+
- url is not user's function
|
1618
|
+
- value was not sent to server
|
1619
|
+
- params.response === undefined, that means data was not sent
|
1620
|
+
- value changed
|
1621
|
+
*/
|
1622
|
+
var sent = false;
|
1623
|
+
sent = sent || typeof this.options.url === 'function';
|
1624
|
+
sent = sent || this.options.display === false;
|
1625
|
+
sent = sent || params.response !== undefined;
|
1626
|
+
sent = sent || (this.options.savenochange && this.input.value2str(this.value) !== this.input.value2str(params.newValue));
|
1627
|
+
|
1628
|
+
if(sent) {
|
1629
|
+
this.$element.removeClass(this.options.unsavedclass);
|
1630
|
+
} else {
|
1631
|
+
this.$element.addClass(this.options.unsavedclass);
|
1632
|
+
}
|
1633
|
+
}
|
1634
|
+
|
1635
|
+
//set new value
|
1636
|
+
this.setValue(params.newValue, false, params.response);
|
1637
|
+
|
1638
|
+
/**
|
1639
|
+
Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance
|
1640
|
+
|
1641
|
+
@event save
|
1642
|
+
@param {Object} event event object
|
1643
|
+
@param {Object} params additional params
|
1644
|
+
@param {mixed} params.newValue submitted value
|
1645
|
+
@param {Object} params.response ajax response
|
1646
|
+
@example
|
1647
|
+
$('#username').on('save', function(e, params) {
|
1648
|
+
alert('Saved value: ' + params.newValue);
|
1649
|
+
});
|
1650
|
+
**/
|
1651
|
+
//event itself is triggered by editableContainer. Description here is only for documentation
|
1652
|
+
},
|
1653
|
+
|
1654
|
+
validate: function () {
|
1655
|
+
if (typeof this.options.validate === 'function') {
|
1656
|
+
return this.options.validate.call(this, this.value);
|
1657
|
+
}
|
1658
|
+
},
|
1659
|
+
|
1660
|
+
/**
|
1661
|
+
Sets new value of editable
|
1662
|
+
@method setValue(value, convertStr)
|
1663
|
+
@param {mixed} value new value
|
1664
|
+
@param {boolean} convertStr whether to convert value from string to internal format
|
1665
|
+
**/
|
1666
|
+
setValue: function(value, convertStr, response) {
|
1667
|
+
if(convertStr) {
|
1668
|
+
this.value = this.input.str2value(value);
|
1669
|
+
} else {
|
1670
|
+
this.value = value;
|
1671
|
+
}
|
1672
|
+
if(this.container) {
|
1673
|
+
this.container.option('value', this.value);
|
1674
|
+
}
|
1675
|
+
$.when(this.render(response))
|
1676
|
+
.then($.proxy(function() {
|
1677
|
+
this.handleEmpty();
|
1678
|
+
}, this));
|
1679
|
+
},
|
1680
|
+
|
1681
|
+
/**
|
1682
|
+
Activates input of visible container (e.g. set focus)
|
1683
|
+
@method activate()
|
1684
|
+
**/
|
1685
|
+
activate: function() {
|
1686
|
+
if(this.container) {
|
1687
|
+
this.container.activate();
|
1688
|
+
}
|
1689
|
+
},
|
1690
|
+
|
1691
|
+
/**
|
1692
|
+
Removes editable feature from element
|
1693
|
+
@method destroy()
|
1694
|
+
**/
|
1695
|
+
destroy: function() {
|
1696
|
+
if(this.container) {
|
1697
|
+
this.container.destroy();
|
1698
|
+
}
|
1699
|
+
|
1700
|
+
if(this.options.toggle !== 'manual') {
|
1701
|
+
this.$element.removeClass('editable-click');
|
1702
|
+
this.$element.off(this.options.toggle + '.editable');
|
1703
|
+
}
|
1704
|
+
|
1705
|
+
this.$element.off("save.internal");
|
1706
|
+
|
1707
|
+
this.$element.removeClass('editable');
|
1708
|
+
this.$element.removeClass('editable-open');
|
1709
|
+
this.$element.removeData('editable');
|
1710
|
+
}
|
1711
|
+
};
|
1712
|
+
|
1713
|
+
/* EDITABLE PLUGIN DEFINITION
|
1714
|
+
* ======================= */
|
1715
|
+
|
1716
|
+
/**
|
1717
|
+
jQuery method to initialize editable element.
|
1718
|
+
|
1719
|
+
@method $().editable(options)
|
1720
|
+
@params {Object} options
|
1721
|
+
@example
|
1722
|
+
$('#username').editable({
|
1723
|
+
type: 'text',
|
1724
|
+
url: '/post',
|
1725
|
+
pk: 1
|
1726
|
+
});
|
1727
|
+
**/
|
1728
|
+
$.fn.editable = function (option) {
|
1729
|
+
//special API methods returning non-jquery object
|
1730
|
+
var result = {}, args = arguments, datakey = 'editable';
|
1731
|
+
switch (option) {
|
1732
|
+
/**
|
1733
|
+
Runs client-side validation for all matched editables
|
1734
|
+
|
1735
|
+
@method validate()
|
1736
|
+
@returns {Object} validation errors map
|
1737
|
+
@example
|
1738
|
+
$('#username, #fullname').editable('validate');
|
1739
|
+
// possible result:
|
1740
|
+
{
|
1741
|
+
username: "username is required",
|
1742
|
+
fullname: "fullname should be minimum 3 letters length"
|
1743
|
+
}
|
1744
|
+
**/
|
1745
|
+
case 'validate':
|
1746
|
+
this.each(function () {
|
1747
|
+
var $this = $(this), data = $this.data(datakey), error;
|
1748
|
+
if (data && (error = data.validate())) {
|
1749
|
+
result[data.options.name] = error;
|
1750
|
+
}
|
1751
|
+
});
|
1752
|
+
return result;
|
1753
|
+
|
1754
|
+
/**
|
1755
|
+
Returns current values of editable elements. If value is <code>null</code> or <code>undefined</code> it will not be returned
|
1756
|
+
@method getValue()
|
1757
|
+
@returns {Object} object of element names and values
|
1758
|
+
@example
|
1759
|
+
$('#username, #fullname').editable('validate');
|
1760
|
+
// possible result:
|
1761
|
+
{
|
1762
|
+
username: "superuser",
|
1763
|
+
fullname: "John"
|
1764
|
+
}
|
1765
|
+
**/
|
1766
|
+
case 'getValue':
|
1767
|
+
this.each(function () {
|
1768
|
+
var $this = $(this), data = $this.data(datakey);
|
1769
|
+
if (data && data.value !== undefined && data.value !== null) {
|
1770
|
+
result[data.options.name] = data.input.value2submit(data.value);
|
1771
|
+
}
|
1772
|
+
});
|
1773
|
+
return result;
|
1774
|
+
|
1775
|
+
/**
|
1776
|
+
This method collects values from several editable elements and submit them all to server.
|
1777
|
+
Internally it runs client-side validation for all fields and submits only in case of success.
|
1778
|
+
See <a href="#newrecord">creating new records</a> for details.
|
1779
|
+
|
1780
|
+
@method submit(options)
|
1781
|
+
@param {object} options
|
1782
|
+
@param {object} options.url url to submit data
|
1783
|
+
@param {object} options.data additional data to submit
|
1784
|
+
@param {object} options.ajaxOptions additional ajax options
|
1785
|
+
@param {function} options.error(obj) error handler
|
1786
|
+
@param {function} options.success(obj,config) success handler
|
1787
|
+
@returns {Object} jQuery object
|
1788
|
+
**/
|
1789
|
+
case 'submit': //collects value, validate and submit to server for creating new record
|
1790
|
+
var config = arguments[1] || {},
|
1791
|
+
$elems = this,
|
1792
|
+
errors = this.editable('validate'),
|
1793
|
+
values;
|
1794
|
+
|
1795
|
+
if($.isEmptyObject(errors)) {
|
1796
|
+
values = this.editable('getValue');
|
1797
|
+
if(config.data) {
|
1798
|
+
$.extend(values, config.data);
|
1799
|
+
}
|
1800
|
+
|
1801
|
+
$.ajax($.extend({
|
1802
|
+
url: config.url,
|
1803
|
+
data: values,
|
1804
|
+
type: 'POST'
|
1805
|
+
}, config.ajaxOptions))
|
1806
|
+
.success(function(response) {
|
1807
|
+
//successful response 200 OK
|
1808
|
+
if(typeof config.success === 'function') {
|
1809
|
+
config.success.call($elems, response, config);
|
1810
|
+
}
|
1811
|
+
})
|
1812
|
+
.error(function(){ //ajax error
|
1813
|
+
if(typeof config.error === 'function') {
|
1814
|
+
config.error.apply($elems, arguments);
|
1815
|
+
}
|
1816
|
+
});
|
1817
|
+
} else { //client-side validation error
|
1818
|
+
if(typeof config.error === 'function') {
|
1819
|
+
config.error.call($elems, errors);
|
1820
|
+
}
|
1821
|
+
}
|
1822
|
+
return this;
|
1823
|
+
}
|
1824
|
+
|
1825
|
+
//return jquery object
|
1826
|
+
return this.each(function () {
|
1827
|
+
var $this = $(this),
|
1828
|
+
data = $this.data(datakey),
|
1829
|
+
options = typeof option === 'object' && option;
|
1830
|
+
|
1831
|
+
if (!data) {
|
1832
|
+
$this.data(datakey, (data = new Editable(this, options)));
|
1833
|
+
}
|
1834
|
+
|
1835
|
+
if (typeof option === 'string') { //call method
|
1836
|
+
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
1837
|
+
}
|
1838
|
+
});
|
1839
|
+
};
|
1840
|
+
|
1841
|
+
|
1842
|
+
$.fn.editable.defaults = {
|
1843
|
+
/**
|
1844
|
+
Type of input. Can be <code>text|textarea|select|date|checklist</code> and more
|
1845
|
+
|
1846
|
+
@property type
|
1847
|
+
@type string
|
1848
|
+
@default 'text'
|
1849
|
+
**/
|
1850
|
+
type: 'text',
|
1851
|
+
/**
|
1852
|
+
Sets disabled state of editable
|
1853
|
+
|
1854
|
+
@property disabled
|
1855
|
+
@type boolean
|
1856
|
+
@default false
|
1857
|
+
**/
|
1858
|
+
disabled: false,
|
1859
|
+
/**
|
1860
|
+
How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>.
|
1861
|
+
When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable.
|
1862
|
+
**Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element,
|
1863
|
+
you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document.
|
1864
|
+
|
1865
|
+
@example
|
1866
|
+
$('#edit-button').click(function(e) {
|
1867
|
+
e.stopPropagation();
|
1868
|
+
$('#username').editable('toggle');
|
1869
|
+
});
|
1870
|
+
|
1871
|
+
@property toggle
|
1872
|
+
@type string
|
1873
|
+
@default 'click'
|
1874
|
+
**/
|
1875
|
+
toggle: 'click',
|
1876
|
+
/**
|
1877
|
+
Text shown when element is empty.
|
1878
|
+
|
1879
|
+
@property emptytext
|
1880
|
+
@type string
|
1881
|
+
@default 'Empty'
|
1882
|
+
**/
|
1883
|
+
emptytext: 'Empty',
|
1884
|
+
/**
|
1885
|
+
Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Useful for select and date.
|
1886
|
+
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>.
|
1887
|
+
<code>auto</code> - text will be automatically set only if element is empty.
|
1888
|
+
<code>always|never</code> - always(never) try to set element's text.
|
1889
|
+
|
1890
|
+
@property autotext
|
1891
|
+
@type string
|
1892
|
+
@default 'auto'
|
1893
|
+
**/
|
1894
|
+
autotext: 'auto',
|
1895
|
+
/**
|
1896
|
+
Initial value of input. If not set, taken from element's text.
|
1897
|
+
|
1898
|
+
@property value
|
1899
|
+
@type mixed
|
1900
|
+
@default element's text
|
1901
|
+
**/
|
1902
|
+
value: null,
|
1903
|
+
/**
|
1904
|
+
Callback to perform custom displaying of value in element's text.
|
1905
|
+
If `null`, default input's display used.
|
1906
|
+
If `false`, no displaying methods will be called, element's text will never change.
|
1907
|
+
Runs under element's scope.
|
1908
|
+
_Parameters:_
|
1909
|
+
|
1910
|
+
* `value` current value to be displayed
|
1911
|
+
* `response` server response (if display called after ajax submit), since 1.4.0
|
1912
|
+
|
1913
|
+
For **inputs with source** (select, checklist) parameters are different:
|
1914
|
+
|
1915
|
+
* `value` current value to be displayed
|
1916
|
+
* `sourceData` array of items for current input (e.g. dropdown items)
|
1917
|
+
* `response` server response (if display called after ajax submit), since 1.4.0
|
1918
|
+
|
1919
|
+
To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`.
|
1920
|
+
|
1921
|
+
@property display
|
1922
|
+
@type function|boolean
|
1923
|
+
@default null
|
1924
|
+
@since 1.2.0
|
1925
|
+
@example
|
1926
|
+
display: function(value, sourceData) {
|
1927
|
+
//display checklist as comma-separated values
|
1928
|
+
var html = [],
|
1929
|
+
checked = $.fn.editableutils.itemsByValue(value, sourceData);
|
1930
|
+
|
1931
|
+
if(checked.length) {
|
1932
|
+
$.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
|
1933
|
+
$(this).html(html.join(', '));
|
1934
|
+
} else {
|
1935
|
+
$(this).empty();
|
1936
|
+
}
|
1937
|
+
}
|
1938
|
+
**/
|
1939
|
+
display: null,
|
1940
|
+
/**
|
1941
|
+
Css class applied when editable text is empty.
|
1942
|
+
|
1943
|
+
@property emptyclass
|
1944
|
+
@type string
|
1945
|
+
@since 1.4.1
|
1946
|
+
@default editable-empty
|
1947
|
+
**/
|
1948
|
+
emptyclass: 'editable-empty',
|
1949
|
+
/**
|
1950
|
+
Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`).
|
1951
|
+
You may set it to `null` if you work with editables locally and submit them together.
|
1952
|
+
|
1953
|
+
@property unsavedclass
|
1954
|
+
@type string
|
1955
|
+
@since 1.4.1
|
1956
|
+
@default editable-unsaved
|
1957
|
+
**/
|
1958
|
+
unsavedclass: 'editable-unsaved',
|
1959
|
+
/**
|
1960
|
+
If a css selector is provided, editable will be delegated to the specified targets.
|
1961
|
+
Usefull for dynamically generated DOM elements.
|
1962
|
+
**Please note**, that delegated targets can't use `emptytext` and `autotext` options,
|
1963
|
+
as they are initialized after first click.
|
1964
|
+
|
1965
|
+
@property selector
|
1966
|
+
@type string
|
1967
|
+
@since 1.4.1
|
1968
|
+
@default null
|
1969
|
+
@example
|
1970
|
+
<div id="user">
|
1971
|
+
<a href="#" data-name="username" data-type="text" title="Username">awesome</a>
|
1972
|
+
<a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" title="Group">Operator</a>
|
1973
|
+
</div>
|
1974
|
+
|
1975
|
+
<script>
|
1976
|
+
$('#user').editable({
|
1977
|
+
selector: 'a',
|
1978
|
+
url: '/post',
|
1979
|
+
pk: 1
|
1980
|
+
});
|
1981
|
+
</script>
|
1982
|
+
**/
|
1983
|
+
selector: null
|
1984
|
+
};
|
1985
|
+
|
1986
|
+
}(window.jQuery));
|
1987
|
+
|
1988
|
+
/**
|
1989
|
+
AbstractInput - base class for all editable inputs.
|
1990
|
+
It defines interface to be implemented by any input type.
|
1991
|
+
To create your own input you can inherit from this class.
|
1992
|
+
|
1993
|
+
@class abstractinput
|
1994
|
+
**/
|
1995
|
+
(function ($) {
|
1996
|
+
|
1997
|
+
//types
|
1998
|
+
$.fn.editabletypes = {};
|
1999
|
+
|
2000
|
+
var AbstractInput = function () { };
|
2001
|
+
|
2002
|
+
AbstractInput.prototype = {
|
2003
|
+
/**
|
2004
|
+
Initializes input
|
2005
|
+
|
2006
|
+
@method init()
|
2007
|
+
**/
|
2008
|
+
init: function(type, options, defaults) {
|
2009
|
+
this.type = type;
|
2010
|
+
this.options = $.extend({}, defaults, options);
|
2011
|
+
},
|
2012
|
+
|
2013
|
+
/*
|
2014
|
+
this method called before render to init $tpl that is inserted in DOM
|
2015
|
+
*/
|
2016
|
+
prerender: function() {
|
2017
|
+
this.$tpl = $(this.options.tpl); //whole tpl as jquery object
|
2018
|
+
this.$input = this.$tpl; //control itself, can be changed in render method
|
2019
|
+
this.$clear = null; //clear button
|
2020
|
+
this.error = null; //error message, if input cannot be rendered
|
2021
|
+
},
|
2022
|
+
|
2023
|
+
/**
|
2024
|
+
Renders input from tpl. Can return jQuery deferred object.
|
2025
|
+
Can be overwritten in child objects
|
2026
|
+
|
2027
|
+
@method render()
|
2028
|
+
**/
|
2029
|
+
render: function() {
|
2030
|
+
|
2031
|
+
},
|
2032
|
+
|
2033
|
+
/**
|
2034
|
+
Sets element's html by value.
|
2035
|
+
|
2036
|
+
@method value2html(value, element)
|
2037
|
+
@param {mixed} value
|
2038
|
+
@param {DOMElement} element
|
2039
|
+
**/
|
2040
|
+
value2html: function(value, element) {
|
2041
|
+
$(element).text(value);
|
2042
|
+
},
|
2043
|
+
|
2044
|
+
/**
|
2045
|
+
Converts element's html to value
|
2046
|
+
|
2047
|
+
@method html2value(html)
|
2048
|
+
@param {string} html
|
2049
|
+
@returns {mixed}
|
2050
|
+
**/
|
2051
|
+
html2value: function(html) {
|
2052
|
+
return $('<div>').html(html).text();
|
2053
|
+
},
|
2054
|
+
|
2055
|
+
/**
|
2056
|
+
Converts value to string (for internal compare). For submitting to server used value2submit().
|
2057
|
+
|
2058
|
+
@method value2str(value)
|
2059
|
+
@param {mixed} value
|
2060
|
+
@returns {string}
|
2061
|
+
**/
|
2062
|
+
value2str: function(value) {
|
2063
|
+
return value;
|
2064
|
+
},
|
2065
|
+
|
2066
|
+
/**
|
2067
|
+
Converts string received from server into value. Usually from `data-value` attribute.
|
2068
|
+
|
2069
|
+
@method str2value(str)
|
2070
|
+
@param {string} str
|
2071
|
+
@returns {mixed}
|
2072
|
+
**/
|
2073
|
+
str2value: function(str) {
|
2074
|
+
return str;
|
2075
|
+
},
|
2076
|
+
|
2077
|
+
/**
|
2078
|
+
Converts value for submitting to server. Result can be string or object.
|
2079
|
+
|
2080
|
+
@method value2submit(value)
|
2081
|
+
@param {mixed} value
|
2082
|
+
@returns {mixed}
|
2083
|
+
**/
|
2084
|
+
value2submit: function(value) {
|
2085
|
+
return value;
|
2086
|
+
},
|
2087
|
+
|
2088
|
+
/**
|
2089
|
+
Sets value of input.
|
2090
|
+
|
2091
|
+
@method value2input(value)
|
2092
|
+
@param {mixed} value
|
2093
|
+
**/
|
2094
|
+
value2input: function(value) {
|
2095
|
+
this.$input.val(value);
|
2096
|
+
},
|
2097
|
+
|
2098
|
+
/**
|
2099
|
+
Returns value of input. Value can be object (e.g. datepicker)
|
2100
|
+
|
2101
|
+
@method input2value()
|
2102
|
+
**/
|
2103
|
+
input2value: function() {
|
2104
|
+
return this.$input.val();
|
2105
|
+
},
|
2106
|
+
|
2107
|
+
/**
|
2108
|
+
Activates input. For text it sets focus.
|
2109
|
+
|
2110
|
+
@method activate()
|
2111
|
+
**/
|
2112
|
+
activate: function() {
|
2113
|
+
if(this.$input.is(':visible')) {
|
2114
|
+
this.$input.focus();
|
2115
|
+
}
|
2116
|
+
},
|
2117
|
+
|
2118
|
+
/**
|
2119
|
+
Creates input.
|
2120
|
+
|
2121
|
+
@method clear()
|
2122
|
+
**/
|
2123
|
+
clear: function() {
|
2124
|
+
this.$input.val(null);
|
2125
|
+
},
|
2126
|
+
|
2127
|
+
/**
|
2128
|
+
method to escape html.
|
2129
|
+
**/
|
2130
|
+
escape: function(str) {
|
2131
|
+
return $('<div>').text(str).html();
|
2132
|
+
},
|
2133
|
+
|
2134
|
+
/**
|
2135
|
+
attach handler to automatically submit form when value changed (useful when buttons not shown)
|
2136
|
+
**/
|
2137
|
+
autosubmit: function() {
|
2138
|
+
|
2139
|
+
},
|
2140
|
+
|
2141
|
+
// -------- helper functions --------
|
2142
|
+
setClass: function() {
|
2143
|
+
if(this.options.inputclass) {
|
2144
|
+
this.$input.addClass(this.options.inputclass);
|
2145
|
+
}
|
2146
|
+
},
|
2147
|
+
|
2148
|
+
setAttr: function(attr) {
|
2149
|
+
if (this.options[attr]) {
|
2150
|
+
this.$input.attr(attr, this.options[attr]);
|
2151
|
+
}
|
2152
|
+
},
|
2153
|
+
|
2154
|
+
option: function(key, value) {
|
2155
|
+
this.options[key] = value;
|
2156
|
+
}
|
2157
|
+
|
2158
|
+
};
|
2159
|
+
|
2160
|
+
AbstractInput.defaults = {
|
2161
|
+
/**
|
2162
|
+
HTML template of input. Normally you should not change it.
|
2163
|
+
|
2164
|
+
@property tpl
|
2165
|
+
@type string
|
2166
|
+
@default ''
|
2167
|
+
**/
|
2168
|
+
tpl: '',
|
2169
|
+
/**
|
2170
|
+
CSS class automatically applied to input
|
2171
|
+
|
2172
|
+
@property inputclass
|
2173
|
+
@type string
|
2174
|
+
@default input-medium
|
2175
|
+
**/
|
2176
|
+
inputclass: 'input-medium'
|
2177
|
+
};
|
2178
|
+
|
2179
|
+
$.extend($.fn.editabletypes, {abstractinput: AbstractInput});
|
2180
|
+
|
2181
|
+
}(window.jQuery));
|
2182
|
+
|
2183
|
+
/**
|
2184
|
+
List - abstract class for inputs that have source option loaded from js array or via ajax
|
2185
|
+
|
2186
|
+
@class list
|
2187
|
+
@extends abstractinput
|
2188
|
+
**/
|
2189
|
+
(function ($) {
|
2190
|
+
|
2191
|
+
var List = function (options) {
|
2192
|
+
|
2193
|
+
};
|
2194
|
+
|
2195
|
+
$.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput);
|
2196
|
+
|
2197
|
+
$.extend(List.prototype, {
|
2198
|
+
render: function () {
|
2199
|
+
var deferred = $.Deferred();
|
2200
|
+
|
2201
|
+
this.error = null;
|
2202
|
+
this.onSourceReady(function () {
|
2203
|
+
this.renderList();
|
2204
|
+
deferred.resolve();
|
2205
|
+
}, function () {
|
2206
|
+
this.error = this.options.sourceError;
|
2207
|
+
deferred.resolve();
|
2208
|
+
});
|
2209
|
+
|
2210
|
+
return deferred.promise();
|
2211
|
+
},
|
2212
|
+
|
2213
|
+
html2value: function (html) {
|
2214
|
+
return null; //can't set value by text
|
2215
|
+
},
|
2216
|
+
|
2217
|
+
value2html: function (value, element, display, response) {
|
2218
|
+
var deferred = $.Deferred(),
|
2219
|
+
success = function () {
|
2220
|
+
if(typeof display === 'function') {
|
2221
|
+
//custom display method
|
2222
|
+
display.call(element, value, this.sourceData, response);
|
2223
|
+
} else {
|
2224
|
+
this.value2htmlFinal(value, element);
|
2225
|
+
}
|
2226
|
+
deferred.resolve();
|
2227
|
+
};
|
2228
|
+
|
2229
|
+
//for null value just call success without loading source
|
2230
|
+
if(value === null) {
|
2231
|
+
success.call(this);
|
2232
|
+
} else {
|
2233
|
+
this.onSourceReady(success, function () { deferred.resolve(); });
|
2234
|
+
}
|
2235
|
+
|
2236
|
+
return deferred.promise();
|
2237
|
+
},
|
2238
|
+
|
2239
|
+
// ------------- additional functions ------------
|
2240
|
+
|
2241
|
+
onSourceReady: function (success, error) {
|
2242
|
+
//if allready loaded just call success
|
2243
|
+
if($.isArray(this.sourceData)) {
|
2244
|
+
success.call(this);
|
2245
|
+
return;
|
2246
|
+
}
|
2247
|
+
|
2248
|
+
// try parse json in single quotes (for double quotes jquery does automatically)
|
2249
|
+
try {
|
2250
|
+
this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false);
|
2251
|
+
} catch (e) {
|
2252
|
+
error.call(this);
|
2253
|
+
return;
|
2254
|
+
}
|
2255
|
+
|
2256
|
+
//loading from url
|
2257
|
+
if (typeof this.options.source === 'string') {
|
2258
|
+
//try to get from cache
|
2259
|
+
if(this.options.sourceCache) {
|
2260
|
+
var cacheID = this.options.source,
|
2261
|
+
cache;
|
2262
|
+
|
2263
|
+
if (!$(document).data(cacheID)) {
|
2264
|
+
$(document).data(cacheID, {});
|
2265
|
+
}
|
2266
|
+
cache = $(document).data(cacheID);
|
2267
|
+
|
2268
|
+
//check for cached data
|
2269
|
+
if (cache.loading === false && cache.sourceData) { //take source from cache
|
2270
|
+
this.sourceData = cache.sourceData;
|
2271
|
+
this.doPrepend();
|
2272
|
+
success.call(this);
|
2273
|
+
return;
|
2274
|
+
} else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
|
2275
|
+
cache.callbacks.push($.proxy(function () {
|
2276
|
+
this.sourceData = cache.sourceData;
|
2277
|
+
this.doPrepend();
|
2278
|
+
success.call(this);
|
2279
|
+
}, this));
|
2280
|
+
|
2281
|
+
//also collecting error callbacks
|
2282
|
+
cache.err_callbacks.push($.proxy(error, this));
|
2283
|
+
return;
|
2284
|
+
} else { //no cache yet, activate it
|
2285
|
+
cache.loading = true;
|
2286
|
+
cache.callbacks = [];
|
2287
|
+
cache.err_callbacks = [];
|
2288
|
+
}
|
2289
|
+
}
|
2290
|
+
|
2291
|
+
//loading sourceData from server
|
2292
|
+
$.ajax({
|
2293
|
+
url: this.options.source,
|
2294
|
+
type: 'get',
|
2295
|
+
cache: false,
|
2296
|
+
dataType: 'json',
|
2297
|
+
success: $.proxy(function (data) {
|
2298
|
+
if(cache) {
|
2299
|
+
cache.loading = false;
|
2300
|
+
}
|
2301
|
+
this.sourceData = this.makeArray(data);
|
2302
|
+
if($.isArray(this.sourceData)) {
|
2303
|
+
if(cache) {
|
2304
|
+
//store result in cache
|
2305
|
+
cache.sourceData = this.sourceData;
|
2306
|
+
//run success callbacks for other fields waiting for this source
|
2307
|
+
$.each(cache.callbacks, function () { this.call(); });
|
2308
|
+
}
|
2309
|
+
this.doPrepend();
|
2310
|
+
success.call(this);
|
2311
|
+
} else {
|
2312
|
+
error.call(this);
|
2313
|
+
if(cache) {
|
2314
|
+
//run error callbacks for other fields waiting for this source
|
2315
|
+
$.each(cache.err_callbacks, function () { this.call(); });
|
2316
|
+
}
|
2317
|
+
}
|
2318
|
+
}, this),
|
2319
|
+
error: $.proxy(function () {
|
2320
|
+
error.call(this);
|
2321
|
+
if(cache) {
|
2322
|
+
cache.loading = false;
|
2323
|
+
//run error callbacks for other fields
|
2324
|
+
$.each(cache.err_callbacks, function () { this.call(); });
|
2325
|
+
}
|
2326
|
+
}, this)
|
2327
|
+
});
|
2328
|
+
} else { //options as json/array/function
|
2329
|
+
if ($.isFunction(this.options.source)) {
|
2330
|
+
this.sourceData = this.makeArray(this.options.source());
|
2331
|
+
} else {
|
2332
|
+
this.sourceData = this.makeArray(this.options.source);
|
2333
|
+
}
|
2334
|
+
|
2335
|
+
if($.isArray(this.sourceData)) {
|
2336
|
+
this.doPrepend();
|
2337
|
+
success.call(this);
|
2338
|
+
} else {
|
2339
|
+
error.call(this);
|
2340
|
+
}
|
2341
|
+
}
|
2342
|
+
},
|
2343
|
+
|
2344
|
+
doPrepend: function () {
|
2345
|
+
if(this.options.prepend === null || this.options.prepend === undefined) {
|
2346
|
+
return;
|
2347
|
+
}
|
2348
|
+
|
2349
|
+
if(!$.isArray(this.prependData)) {
|
2350
|
+
//try parse json in single quotes
|
2351
|
+
this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);
|
2352
|
+
if (typeof this.options.prepend === 'string') {
|
2353
|
+
this.options.prepend = {'': this.options.prepend};
|
2354
|
+
}
|
2355
|
+
if (typeof this.options.prepend === 'function') {
|
2356
|
+
this.prependData = this.makeArray(this.options.prepend());
|
2357
|
+
} else {
|
2358
|
+
this.prependData = this.makeArray(this.options.prepend);
|
2359
|
+
}
|
2360
|
+
}
|
2361
|
+
|
2362
|
+
if($.isArray(this.prependData) && $.isArray(this.sourceData)) {
|
2363
|
+
this.sourceData = this.prependData.concat(this.sourceData);
|
2364
|
+
}
|
2365
|
+
},
|
2366
|
+
|
2367
|
+
/*
|
2368
|
+
renders input list
|
2369
|
+
*/
|
2370
|
+
renderList: function() {
|
2371
|
+
// this method should be overwritten in child class
|
2372
|
+
},
|
2373
|
+
|
2374
|
+
/*
|
2375
|
+
set element's html by value
|
2376
|
+
*/
|
2377
|
+
value2htmlFinal: function(value, element) {
|
2378
|
+
// this method should be overwritten in child class
|
2379
|
+
},
|
2380
|
+
|
2381
|
+
/**
|
2382
|
+
* convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}]
|
2383
|
+
*/
|
2384
|
+
makeArray: function(data) {
|
2385
|
+
var count, obj, result = [], item, iterateItem;
|
2386
|
+
if(!data || typeof data === 'string') {
|
2387
|
+
return null;
|
2388
|
+
}
|
2389
|
+
|
2390
|
+
if($.isArray(data)) { //array
|
2391
|
+
/*
|
2392
|
+
function to iterate inside item of array if item is object.
|
2393
|
+
Caclulates count of keys in item and store in obj.
|
2394
|
+
*/
|
2395
|
+
iterateItem = function (k, v) {
|
2396
|
+
obj = {value: k, text: v};
|
2397
|
+
if(count++ >= 2) {
|
2398
|
+
return false;// exit from `each` if item has more than one key.
|
2399
|
+
}
|
2400
|
+
};
|
2401
|
+
|
2402
|
+
for(var i = 0; i < data.length; i++) {
|
2403
|
+
item = data[i];
|
2404
|
+
if(typeof item === 'object') {
|
2405
|
+
count = 0; //count of keys inside item
|
2406
|
+
$.each(item, iterateItem);
|
2407
|
+
//case: [{val1: 'text1'}, {val2: 'text2} ...]
|
2408
|
+
if(count === 1) {
|
2409
|
+
result.push(obj);
|
2410
|
+
//case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...]
|
2411
|
+
} else if(count > 1) {
|
2412
|
+
//removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text')
|
2413
|
+
if(item.children) {
|
2414
|
+
item.children = this.makeArray(item.children);
|
2415
|
+
}
|
2416
|
+
result.push(item);
|
2417
|
+
}
|
2418
|
+
} else {
|
2419
|
+
//case: ['text1', 'text2' ...]
|
2420
|
+
result.push({value: item, text: item});
|
2421
|
+
}
|
2422
|
+
}
|
2423
|
+
} else { //case: {val1: 'text1', val2: 'text2, ...}
|
2424
|
+
$.each(data, function (k, v) {
|
2425
|
+
result.push({value: k, text: v});
|
2426
|
+
});
|
2427
|
+
}
|
2428
|
+
return result;
|
2429
|
+
},
|
2430
|
+
|
2431
|
+
option: function(key, value) {
|
2432
|
+
this.options[key] = value;
|
2433
|
+
if(key === 'source') {
|
2434
|
+
this.sourceData = null;
|
2435
|
+
}
|
2436
|
+
if(key === 'prepend') {
|
2437
|
+
this.prependData = null;
|
2438
|
+
}
|
2439
|
+
}
|
2440
|
+
|
2441
|
+
});
|
2442
|
+
|
2443
|
+
List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
2444
|
+
/**
|
2445
|
+
Source data for list.
|
2446
|
+
If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`
|
2447
|
+
For compability, object format is also supported: `{"1": "text1", "2": "text2" ...}` but it does not guarantee elements order.
|
2448
|
+
|
2449
|
+
If **string** - considered ajax url to load items. In that case results will be cached for fields with the same source and name. See also `sourceCache` option.
|
2450
|
+
|
2451
|
+
If **function**, it should return data in format above (since 1.4.0).
|
2452
|
+
|
2453
|
+
Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only).
|
2454
|
+
`[{text: "group1", children: [{value: 1, text: "text1"}, {value: 2, text: "text2"}]}, ...]`
|
2455
|
+
|
2456
|
+
|
2457
|
+
@property source
|
2458
|
+
@type string | array | object | function
|
2459
|
+
@default null
|
2460
|
+
**/
|
2461
|
+
source: null,
|
2462
|
+
/**
|
2463
|
+
Data automatically prepended to the beginning of dropdown list.
|
2464
|
+
|
2465
|
+
@property prepend
|
2466
|
+
@type string | array | object | function
|
2467
|
+
@default false
|
2468
|
+
**/
|
2469
|
+
prepend: false,
|
2470
|
+
/**
|
2471
|
+
Error message when list cannot be loaded (e.g. ajax error)
|
2472
|
+
|
2473
|
+
@property sourceError
|
2474
|
+
@type string
|
2475
|
+
@default Error when loading list
|
2476
|
+
**/
|
2477
|
+
sourceError: 'Error when loading list',
|
2478
|
+
/**
|
2479
|
+
if <code>true</code> and source is **string url** - results will be cached for fields with the same source.
|
2480
|
+
Usefull for editable column in grid to prevent extra requests.
|
2481
|
+
|
2482
|
+
@property sourceCache
|
2483
|
+
@type boolean
|
2484
|
+
@default true
|
2485
|
+
@since 1.2.0
|
2486
|
+
**/
|
2487
|
+
sourceCache: true
|
2488
|
+
});
|
2489
|
+
|
2490
|
+
$.fn.editabletypes.list = List;
|
2491
|
+
|
2492
|
+
}(window.jQuery));
|
2493
|
+
|
2494
|
+
/**
|
2495
|
+
Text input
|
2496
|
+
|
2497
|
+
@class text
|
2498
|
+
@extends abstractinput
|
2499
|
+
@final
|
2500
|
+
@example
|
2501
|
+
<a href="#" id="username" data-type="text" data-pk="1">awesome</a>
|
2502
|
+
<script>
|
2503
|
+
$(function(){
|
2504
|
+
$('#username').editable({
|
2505
|
+
url: '/post',
|
2506
|
+
title: 'Enter username'
|
2507
|
+
});
|
2508
|
+
});
|
2509
|
+
</script>
|
2510
|
+
**/
|
2511
|
+
(function ($) {
|
2512
|
+
var Text = function (options) {
|
2513
|
+
this.init('text', options, Text.defaults);
|
2514
|
+
};
|
2515
|
+
|
2516
|
+
$.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
|
2517
|
+
|
2518
|
+
$.extend(Text.prototype, {
|
2519
|
+
render: function() {
|
2520
|
+
this.renderClear();
|
2521
|
+
this.setClass();
|
2522
|
+
this.setAttr('placeholder');
|
2523
|
+
},
|
2524
|
+
|
2525
|
+
activate: function() {
|
2526
|
+
if(this.$input.is(':visible')) {
|
2527
|
+
this.$input.focus();
|
2528
|
+
$.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
|
2529
|
+
if(this.toggleClear) {
|
2530
|
+
this.toggleClear();
|
2531
|
+
}
|
2532
|
+
}
|
2533
|
+
},
|
2534
|
+
|
2535
|
+
//render clear button
|
2536
|
+
renderClear: function() {
|
2537
|
+
if (this.options.clear) {
|
2538
|
+
this.$clear = $('<span class="editable-clear-x"></span>');
|
2539
|
+
this.$input.after(this.$clear)
|
2540
|
+
.css('padding-right', 20)
|
2541
|
+
.keyup($.proxy(this.toggleClear, this))
|
2542
|
+
.parent().css('position', 'relative');
|
2543
|
+
|
2544
|
+
this.$clear.click($.proxy(this.clear, this));
|
2545
|
+
}
|
2546
|
+
},
|
2547
|
+
|
2548
|
+
postrender: function() {
|
2549
|
+
if(this.$clear) {
|
2550
|
+
//can position clear button only here, when form is shown and height can be calculated
|
2551
|
+
var h = this.$input.outerHeight() || 20,
|
2552
|
+
delta = (h - this.$clear.height()) / 2;
|
2553
|
+
|
2554
|
+
//workaround for plain-popup
|
2555
|
+
if(delta < 3) {
|
2556
|
+
delta = 3;
|
2557
|
+
}
|
2558
|
+
|
2559
|
+
this.$clear.css({top: delta, right: delta});
|
2560
|
+
}
|
2561
|
+
},
|
2562
|
+
|
2563
|
+
//show / hide clear button
|
2564
|
+
toggleClear: function() {
|
2565
|
+
if(!this.$clear) {
|
2566
|
+
return;
|
2567
|
+
}
|
2568
|
+
|
2569
|
+
if(this.$input.val().length) {
|
2570
|
+
this.$clear.show();
|
2571
|
+
} else {
|
2572
|
+
this.$clear.hide();
|
2573
|
+
}
|
2574
|
+
},
|
2575
|
+
|
2576
|
+
clear: function() {
|
2577
|
+
this.$clear.hide();
|
2578
|
+
this.$input.val('').focus();
|
2579
|
+
}
|
2580
|
+
});
|
2581
|
+
|
2582
|
+
Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
2583
|
+
/**
|
2584
|
+
@property tpl
|
2585
|
+
@default <input type="text">
|
2586
|
+
**/
|
2587
|
+
tpl: '<input type="text">',
|
2588
|
+
/**
|
2589
|
+
Placeholder attribute of input. Shown when input is empty.
|
2590
|
+
|
2591
|
+
@property placeholder
|
2592
|
+
@type string
|
2593
|
+
@default null
|
2594
|
+
**/
|
2595
|
+
placeholder: null,
|
2596
|
+
|
2597
|
+
/**
|
2598
|
+
Whether to show `clear` button
|
2599
|
+
|
2600
|
+
@property clear
|
2601
|
+
@type boolean
|
2602
|
+
@default true
|
2603
|
+
**/
|
2604
|
+
clear: true
|
2605
|
+
});
|
2606
|
+
|
2607
|
+
$.fn.editabletypes.text = Text;
|
2608
|
+
|
2609
|
+
}(window.jQuery));
|
2610
|
+
|
2611
|
+
/**
|
2612
|
+
Textarea input
|
2613
|
+
|
2614
|
+
@class textarea
|
2615
|
+
@extends abstractinput
|
2616
|
+
@final
|
2617
|
+
@example
|
2618
|
+
<a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a>
|
2619
|
+
<script>
|
2620
|
+
$(function(){
|
2621
|
+
$('#comments').editable({
|
2622
|
+
url: '/post',
|
2623
|
+
title: 'Enter comments',
|
2624
|
+
rows: 10
|
2625
|
+
});
|
2626
|
+
});
|
2627
|
+
</script>
|
2628
|
+
**/
|
2629
|
+
(function ($) {
|
2630
|
+
|
2631
|
+
var Textarea = function (options) {
|
2632
|
+
this.init('textarea', options, Textarea.defaults);
|
2633
|
+
};
|
2634
|
+
|
2635
|
+
$.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);
|
2636
|
+
|
2637
|
+
$.extend(Textarea.prototype, {
|
2638
|
+
render: function () {
|
2639
|
+
this.setClass();
|
2640
|
+
this.setAttr('placeholder');
|
2641
|
+
this.setAttr('rows');
|
2642
|
+
|
2643
|
+
//ctrl + enter
|
2644
|
+
this.$input.keydown(function (e) {
|
2645
|
+
if (e.ctrlKey && e.which === 13) {
|
2646
|
+
$(this).closest('form').submit();
|
2647
|
+
}
|
2648
|
+
});
|
2649
|
+
},
|
2650
|
+
|
2651
|
+
value2html: function(value, element) {
|
2652
|
+
var html = '', lines;
|
2653
|
+
if(value) {
|
2654
|
+
lines = value.split("\n");
|
2655
|
+
for (var i = 0; i < lines.length; i++) {
|
2656
|
+
lines[i] = $('<div>').text(lines[i]).html();
|
2657
|
+
}
|
2658
|
+
html = lines.join('<br>');
|
2659
|
+
}
|
2660
|
+
$(element).html(html);
|
2661
|
+
},
|
2662
|
+
|
2663
|
+
html2value: function(html) {
|
2664
|
+
if(!html) {
|
2665
|
+
return '';
|
2666
|
+
}
|
2667
|
+
|
2668
|
+
var regex = new RegExp(String.fromCharCode(10), 'g');
|
2669
|
+
var lines = html.split(/<br\s*\/?>/i);
|
2670
|
+
for (var i = 0; i < lines.length; i++) {
|
2671
|
+
var text = $('<div>').html(lines[i]).text();
|
2672
|
+
|
2673
|
+
// Remove newline characters (\n) to avoid them being converted by value2html() method
|
2674
|
+
// thus adding extra <br> tags
|
2675
|
+
text = text.replace(regex, '');
|
2676
|
+
|
2677
|
+
lines[i] = text;
|
2678
|
+
}
|
2679
|
+
return lines.join("\n");
|
2680
|
+
},
|
2681
|
+
|
2682
|
+
activate: function() {
|
2683
|
+
$.fn.editabletypes.text.prototype.activate.call(this);
|
2684
|
+
}
|
2685
|
+
});
|
2686
|
+
|
2687
|
+
Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
2688
|
+
/**
|
2689
|
+
@property tpl
|
2690
|
+
@default <textarea></textarea>
|
2691
|
+
**/
|
2692
|
+
tpl:'<textarea></textarea>',
|
2693
|
+
/**
|
2694
|
+
@property inputclass
|
2695
|
+
@default input-large
|
2696
|
+
**/
|
2697
|
+
inputclass: 'input-large',
|
2698
|
+
/**
|
2699
|
+
Placeholder attribute of input. Shown when input is empty.
|
2700
|
+
|
2701
|
+
@property placeholder
|
2702
|
+
@type string
|
2703
|
+
@default null
|
2704
|
+
**/
|
2705
|
+
placeholder: null,
|
2706
|
+
/**
|
2707
|
+
Number of rows in textarea
|
2708
|
+
|
2709
|
+
@property rows
|
2710
|
+
@type integer
|
2711
|
+
@default 7
|
2712
|
+
**/
|
2713
|
+
rows: 7
|
2714
|
+
});
|
2715
|
+
|
2716
|
+
$.fn.editabletypes.textarea = Textarea;
|
2717
|
+
|
2718
|
+
}(window.jQuery));
|
2719
|
+
|
2720
|
+
/**
|
2721
|
+
Select (dropdown)
|
2722
|
+
|
2723
|
+
@class select
|
2724
|
+
@extends list
|
2725
|
+
@final
|
2726
|
+
@example
|
2727
|
+
<a href="#" id="status" data-type="select" data-pk="1" data-url="/post" data-original-title="Select status"></a>
|
2728
|
+
<script>
|
2729
|
+
$(function(){
|
2730
|
+
$('#status').editable({
|
2731
|
+
value: 2,
|
2732
|
+
source: [
|
2733
|
+
{value: 1, text: 'Active'},
|
2734
|
+
{value: 2, text: 'Blocked'},
|
2735
|
+
{value: 3, text: 'Deleted'}
|
2736
|
+
]
|
2737
|
+
}
|
2738
|
+
});
|
2739
|
+
});
|
2740
|
+
</script>
|
2741
|
+
**/
|
2742
|
+
(function ($) {
|
2743
|
+
|
2744
|
+
var Select = function (options) {
|
2745
|
+
this.init('select', options, Select.defaults);
|
2746
|
+
};
|
2747
|
+
|
2748
|
+
$.fn.editableutils.inherit(Select, $.fn.editabletypes.list);
|
2749
|
+
|
2750
|
+
$.extend(Select.prototype, {
|
2751
|
+
renderList: function() {
|
2752
|
+
this.$input.empty();
|
2753
|
+
|
2754
|
+
var fillItems = function($el, data) {
|
2755
|
+
if($.isArray(data)) {
|
2756
|
+
for(var i=0; i<data.length; i++) {
|
2757
|
+
if(data[i].children) {
|
2758
|
+
$el.append(fillItems($('<optgroup>', {label: data[i].text}), data[i].children));
|
2759
|
+
} else {
|
2760
|
+
$el.append($('<option>', {value: data[i].value}).text(data[i].text));
|
2761
|
+
}
|
2762
|
+
}
|
2763
|
+
}
|
2764
|
+
return $el;
|
2765
|
+
};
|
2766
|
+
|
2767
|
+
fillItems(this.$input, this.sourceData);
|
2768
|
+
|
2769
|
+
this.setClass();
|
2770
|
+
|
2771
|
+
//enter submit
|
2772
|
+
this.$input.on('keydown.editable', function (e) {
|
2773
|
+
if (e.which === 13) {
|
2774
|
+
$(this).closest('form').submit();
|
2775
|
+
}
|
2776
|
+
});
|
2777
|
+
},
|
2778
|
+
|
2779
|
+
value2htmlFinal: function(value, element) {
|
2780
|
+
var text = '',
|
2781
|
+
items = $.fn.editableutils.itemsByValue(value, this.sourceData);
|
2782
|
+
|
2783
|
+
if(items.length) {
|
2784
|
+
text = items[0].text;
|
2785
|
+
}
|
2786
|
+
|
2787
|
+
$(element).text(text);
|
2788
|
+
},
|
2789
|
+
|
2790
|
+
autosubmit: function() {
|
2791
|
+
this.$input.off('keydown.editable').on('change.editable', function(){
|
2792
|
+
$(this).closest('form').submit();
|
2793
|
+
});
|
2794
|
+
}
|
2795
|
+
});
|
2796
|
+
|
2797
|
+
Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
|
2798
|
+
/**
|
2799
|
+
@property tpl
|
2800
|
+
@default <select></select>
|
2801
|
+
**/
|
2802
|
+
tpl:'<select></select>'
|
2803
|
+
});
|
2804
|
+
|
2805
|
+
$.fn.editabletypes.select = Select;
|
2806
|
+
|
2807
|
+
}(window.jQuery));
|
2808
|
+
/**
|
2809
|
+
List of checkboxes.
|
2810
|
+
Internally value stored as javascript array of values.
|
2811
|
+
|
2812
|
+
@class checklist
|
2813
|
+
@extends list
|
2814
|
+
@final
|
2815
|
+
@example
|
2816
|
+
<a href="#" id="options" data-type="checklist" data-pk="1" data-url="/post" data-original-title="Select options"></a>
|
2817
|
+
<script>
|
2818
|
+
$(function(){
|
2819
|
+
$('#options').editable({
|
2820
|
+
value: [2, 3],
|
2821
|
+
source: [
|
2822
|
+
{value: 1, text: 'option1'},
|
2823
|
+
{value: 2, text: 'option2'},
|
2824
|
+
{value: 3, text: 'option3'}
|
2825
|
+
]
|
2826
|
+
}
|
2827
|
+
});
|
2828
|
+
});
|
2829
|
+
</script>
|
2830
|
+
**/
|
2831
|
+
(function ($) {
|
2832
|
+
|
2833
|
+
var Checklist = function (options) {
|
2834
|
+
this.init('checklist', options, Checklist.defaults);
|
2835
|
+
};
|
2836
|
+
|
2837
|
+
$.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list);
|
2838
|
+
|
2839
|
+
$.extend(Checklist.prototype, {
|
2840
|
+
renderList: function() {
|
2841
|
+
var $label, $div;
|
2842
|
+
|
2843
|
+
this.$tpl.empty();
|
2844
|
+
|
2845
|
+
if(!$.isArray(this.sourceData)) {
|
2846
|
+
return;
|
2847
|
+
}
|
2848
|
+
|
2849
|
+
for(var i=0; i<this.sourceData.length; i++) {
|
2850
|
+
$label = $('<label>').append($('<input>', {
|
2851
|
+
type: 'checkbox',
|
2852
|
+
value: this.sourceData[i].value
|
2853
|
+
}))
|
2854
|
+
.append($('<span>').text(' '+this.sourceData[i].text));
|
2855
|
+
|
2856
|
+
$('<div>').append($label).appendTo(this.$tpl);
|
2857
|
+
}
|
2858
|
+
|
2859
|
+
this.$input = this.$tpl.find('input[type="checkbox"]');
|
2860
|
+
this.setClass();
|
2861
|
+
},
|
2862
|
+
|
2863
|
+
value2str: function(value) {
|
2864
|
+
return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : '';
|
2865
|
+
},
|
2866
|
+
|
2867
|
+
//parse separated string
|
2868
|
+
str2value: function(str) {
|
2869
|
+
var reg, value = null;
|
2870
|
+
if(typeof str === 'string' && str.length) {
|
2871
|
+
reg = new RegExp('\\s*'+$.trim(this.options.separator)+'\\s*');
|
2872
|
+
value = str.split(reg);
|
2873
|
+
} else if($.isArray(str)) {
|
2874
|
+
value = str;
|
2875
|
+
}
|
2876
|
+
return value;
|
2877
|
+
},
|
2878
|
+
|
2879
|
+
//set checked on required checkboxes
|
2880
|
+
value2input: function(value) {
|
2881
|
+
this.$input.prop('checked', false);
|
2882
|
+
if($.isArray(value) && value.length) {
|
2883
|
+
this.$input.each(function(i, el) {
|
2884
|
+
var $el = $(el);
|
2885
|
+
// cannot use $.inArray as it performs strict comparison
|
2886
|
+
$.each(value, function(j, val){
|
2887
|
+
/*jslint eqeq: true*/
|
2888
|
+
if($el.val() == val) {
|
2889
|
+
/*jslint eqeq: false*/
|
2890
|
+
$el.prop('checked', true);
|
2891
|
+
}
|
2892
|
+
});
|
2893
|
+
});
|
2894
|
+
}
|
2895
|
+
},
|
2896
|
+
|
2897
|
+
input2value: function() {
|
2898
|
+
var checked = [];
|
2899
|
+
this.$input.filter(':checked').each(function(i, el) {
|
2900
|
+
checked.push($(el).val());
|
2901
|
+
});
|
2902
|
+
return checked;
|
2903
|
+
},
|
2904
|
+
|
2905
|
+
//collect text of checked boxes
|
2906
|
+
value2htmlFinal: function(value, element) {
|
2907
|
+
var html = [],
|
2908
|
+
checked = $.fn.editableutils.itemsByValue(value, this.sourceData);
|
2909
|
+
|
2910
|
+
if(checked.length) {
|
2911
|
+
$.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
|
2912
|
+
$(element).html(html.join('<br>'));
|
2913
|
+
} else {
|
2914
|
+
$(element).empty();
|
2915
|
+
}
|
2916
|
+
},
|
2917
|
+
|
2918
|
+
activate: function() {
|
2919
|
+
this.$input.first().focus();
|
2920
|
+
},
|
2921
|
+
|
2922
|
+
autosubmit: function() {
|
2923
|
+
this.$input.on('keydown', function(e){
|
2924
|
+
if (e.which === 13) {
|
2925
|
+
$(this).closest('form').submit();
|
2926
|
+
}
|
2927
|
+
});
|
2928
|
+
}
|
2929
|
+
});
|
2930
|
+
|
2931
|
+
Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
|
2932
|
+
/**
|
2933
|
+
@property tpl
|
2934
|
+
@default <div></div>
|
2935
|
+
**/
|
2936
|
+
tpl:'<div class="editable-checklist"></div>',
|
2937
|
+
|
2938
|
+
/**
|
2939
|
+
@property inputclass
|
2940
|
+
@type string
|
2941
|
+
@default null
|
2942
|
+
**/
|
2943
|
+
inputclass: null,
|
2944
|
+
|
2945
|
+
/**
|
2946
|
+
Separator of values when reading from `data-value` attribute
|
2947
|
+
|
2948
|
+
@property separator
|
2949
|
+
@type string
|
2950
|
+
@default ','
|
2951
|
+
**/
|
2952
|
+
separator: ','
|
2953
|
+
});
|
2954
|
+
|
2955
|
+
$.fn.editabletypes.checklist = Checklist;
|
2956
|
+
|
2957
|
+
}(window.jQuery));
|
2958
|
+
|
2959
|
+
/**
|
2960
|
+
HTML5 input types.
|
2961
|
+
Following types are supported:
|
2962
|
+
|
2963
|
+
* password
|
2964
|
+
* email
|
2965
|
+
* url
|
2966
|
+
* tel
|
2967
|
+
* number
|
2968
|
+
* range
|
2969
|
+
|
2970
|
+
Learn more about html5 inputs:
|
2971
|
+
http://www.w3.org/wiki/HTML5_form_additions
|
2972
|
+
To check browser compatibility please see:
|
2973
|
+
https://developer.mozilla.org/en-US/docs/HTML/Element/Input
|
2974
|
+
|
2975
|
+
@class html5types
|
2976
|
+
@extends text
|
2977
|
+
@final
|
2978
|
+
@since 1.3.0
|
2979
|
+
@example
|
2980
|
+
<a href="#" id="email" data-type="email" data-pk="1">admin@example.com</a>
|
2981
|
+
<script>
|
2982
|
+
$(function(){
|
2983
|
+
$('#email').editable({
|
2984
|
+
url: '/post',
|
2985
|
+
title: 'Enter email'
|
2986
|
+
});
|
2987
|
+
});
|
2988
|
+
</script>
|
2989
|
+
**/
|
2990
|
+
|
2991
|
+
/**
|
2992
|
+
@property tpl
|
2993
|
+
@default depends on type
|
2994
|
+
**/
|
2995
|
+
|
2996
|
+
/*
|
2997
|
+
Password
|
2998
|
+
*/
|
2999
|
+
(function ($) {
|
3000
|
+
var Password = function (options) {
|
3001
|
+
this.init('password', options, Password.defaults);
|
3002
|
+
};
|
3003
|
+
$.fn.editableutils.inherit(Password, $.fn.editabletypes.text);
|
3004
|
+
$.extend(Password.prototype, {
|
3005
|
+
//do not display password, show '[hidden]' instead
|
3006
|
+
value2html: function(value, element) {
|
3007
|
+
if(value) {
|
3008
|
+
$(element).text('[hidden]');
|
3009
|
+
} else {
|
3010
|
+
$(element).empty();
|
3011
|
+
}
|
3012
|
+
},
|
3013
|
+
//as password not displayed, should not set value by html
|
3014
|
+
html2value: function(html) {
|
3015
|
+
return null;
|
3016
|
+
}
|
3017
|
+
});
|
3018
|
+
Password.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
3019
|
+
tpl: '<input type="password">'
|
3020
|
+
});
|
3021
|
+
$.fn.editabletypes.password = Password;
|
3022
|
+
}(window.jQuery));
|
3023
|
+
|
3024
|
+
|
3025
|
+
/*
|
3026
|
+
Email
|
3027
|
+
*/
|
3028
|
+
(function ($) {
|
3029
|
+
var Email = function (options) {
|
3030
|
+
this.init('email', options, Email.defaults);
|
3031
|
+
};
|
3032
|
+
$.fn.editableutils.inherit(Email, $.fn.editabletypes.text);
|
3033
|
+
Email.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
3034
|
+
tpl: '<input type="email">'
|
3035
|
+
});
|
3036
|
+
$.fn.editabletypes.email = Email;
|
3037
|
+
}(window.jQuery));
|
3038
|
+
|
3039
|
+
|
3040
|
+
/*
|
3041
|
+
Url
|
3042
|
+
*/
|
3043
|
+
(function ($) {
|
3044
|
+
var Url = function (options) {
|
3045
|
+
this.init('url', options, Url.defaults);
|
3046
|
+
};
|
3047
|
+
$.fn.editableutils.inherit(Url, $.fn.editabletypes.text);
|
3048
|
+
Url.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
3049
|
+
tpl: '<input type="url">'
|
3050
|
+
});
|
3051
|
+
$.fn.editabletypes.url = Url;
|
3052
|
+
}(window.jQuery));
|
3053
|
+
|
3054
|
+
|
3055
|
+
/*
|
3056
|
+
Tel
|
3057
|
+
*/
|
3058
|
+
(function ($) {
|
3059
|
+
var Tel = function (options) {
|
3060
|
+
this.init('tel', options, Tel.defaults);
|
3061
|
+
};
|
3062
|
+
$.fn.editableutils.inherit(Tel, $.fn.editabletypes.text);
|
3063
|
+
Tel.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
3064
|
+
tpl: '<input type="tel">'
|
3065
|
+
});
|
3066
|
+
$.fn.editabletypes.tel = Tel;
|
3067
|
+
}(window.jQuery));
|
3068
|
+
|
3069
|
+
|
3070
|
+
/*
|
3071
|
+
Number
|
3072
|
+
*/
|
3073
|
+
(function ($) {
|
3074
|
+
var NumberInput = function (options) {
|
3075
|
+
this.init('number', options, NumberInput.defaults);
|
3076
|
+
};
|
3077
|
+
$.fn.editableutils.inherit(NumberInput, $.fn.editabletypes.text);
|
3078
|
+
$.extend(NumberInput.prototype, {
|
3079
|
+
render: function () {
|
3080
|
+
NumberInput.superclass.render.call(this);
|
3081
|
+
this.setAttr('min');
|
3082
|
+
this.setAttr('max');
|
3083
|
+
this.setAttr('step');
|
3084
|
+
}
|
3085
|
+
});
|
3086
|
+
NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
|
3087
|
+
tpl: '<input type="number">',
|
3088
|
+
inputclass: 'input-mini',
|
3089
|
+
min: null,
|
3090
|
+
max: null,
|
3091
|
+
step: null
|
3092
|
+
});
|
3093
|
+
$.fn.editabletypes.number = NumberInput;
|
3094
|
+
}(window.jQuery));
|
3095
|
+
|
3096
|
+
|
3097
|
+
/*
|
3098
|
+
Range (inherit from number)
|
3099
|
+
*/
|
3100
|
+
(function ($) {
|
3101
|
+
var Range = function (options) {
|
3102
|
+
this.init('range', options, Range.defaults);
|
3103
|
+
};
|
3104
|
+
$.fn.editableutils.inherit(Range, $.fn.editabletypes.number);
|
3105
|
+
$.extend(Range.prototype, {
|
3106
|
+
render: function () {
|
3107
|
+
this.$input = this.$tpl.filter('input');
|
3108
|
+
|
3109
|
+
this.setClass();
|
3110
|
+
this.setAttr('min');
|
3111
|
+
this.setAttr('max');
|
3112
|
+
this.setAttr('step');
|
3113
|
+
|
3114
|
+
this.$input.on('input', function(){
|
3115
|
+
$(this).siblings('output').text($(this).val());
|
3116
|
+
});
|
3117
|
+
},
|
3118
|
+
activate: function() {
|
3119
|
+
this.$input.focus();
|
3120
|
+
}
|
3121
|
+
});
|
3122
|
+
Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, {
|
3123
|
+
tpl: '<input type="range"><output style="width: 30px; display: inline-block"></output>',
|
3124
|
+
inputclass: 'input-medium'
|
3125
|
+
});
|
3126
|
+
$.fn.editabletypes.range = Range;
|
3127
|
+
}(window.jQuery));
|
3128
|
+
/**
|
3129
|
+
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
|
3130
|
+
Please see [original docs](http://ivaynberg.github.com/select2) for detailed description and options.
|
3131
|
+
You should manually include select2 distributive:
|
3132
|
+
|
3133
|
+
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
|
3134
|
+
<script src="select2/select2.js"></script>
|
3135
|
+
|
3136
|
+
@class select2
|
3137
|
+
@extends abstractinput
|
3138
|
+
@since 1.4.1
|
3139
|
+
@final
|
3140
|
+
@example
|
3141
|
+
<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-original-title="Select country"></a>
|
3142
|
+
<script>
|
3143
|
+
$(function(){
|
3144
|
+
$('#country').editable({
|
3145
|
+
source: [
|
3146
|
+
{id: 'gb', text: 'Great Britain'},
|
3147
|
+
{id: 'us', text: 'United States'},
|
3148
|
+
{id: 'ru', text: 'Russia'}
|
3149
|
+
],
|
3150
|
+
select2: {
|
3151
|
+
multiple: true
|
3152
|
+
}
|
3153
|
+
});
|
3154
|
+
});
|
3155
|
+
</script>
|
3156
|
+
**/
|
3157
|
+
(function ($) {
|
3158
|
+
|
3159
|
+
var Constructor = function (options) {
|
3160
|
+
this.init('select2', options, Constructor.defaults);
|
3161
|
+
|
3162
|
+
options.select2 = options.select2 || {};
|
3163
|
+
|
3164
|
+
var that = this,
|
3165
|
+
mixin = {
|
3166
|
+
placeholder: options.placeholder
|
3167
|
+
};
|
3168
|
+
|
3169
|
+
//detect whether it is multi-valued
|
3170
|
+
this.isMultiple = options.select2.tags || options.select2.multiple;
|
3171
|
+
|
3172
|
+
//if not `tags` mode, we need define init set data from source
|
3173
|
+
if(!options.select2.tags) {
|
3174
|
+
if(options.source) {
|
3175
|
+
mixin.data = options.source;
|
3176
|
+
}
|
3177
|
+
|
3178
|
+
//this function can be defaulted in seletc2. See https://github.com/ivaynberg/select2/issues/710
|
3179
|
+
mixin.initSelection = function (element, callback) {
|
3180
|
+
var val = that.str2value(element.val()),
|
3181
|
+
data = $.fn.editableutils.itemsByValue(val, mixin.data, 'id');
|
3182
|
+
|
3183
|
+
//for single-valued mode should not use array. Take first element instead.
|
3184
|
+
if($.isArray(data) && data.length && !that.isMultiple) {
|
3185
|
+
data = data[0];
|
3186
|
+
}
|
3187
|
+
|
3188
|
+
callback(data);
|
3189
|
+
};
|
3190
|
+
}
|
3191
|
+
|
3192
|
+
//overriding objects in config (as by default jQuery extend() is not recursive)
|
3193
|
+
this.options.select2 = $.extend({}, Constructor.defaults.select2, mixin, options.select2);
|
3194
|
+
};
|
3195
|
+
|
3196
|
+
$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
|
3197
|
+
|
3198
|
+
$.extend(Constructor.prototype, {
|
3199
|
+
render: function() {
|
3200
|
+
this.setClass();
|
3201
|
+
//apply select2
|
3202
|
+
this.$input.select2(this.options.select2);
|
3203
|
+
|
3204
|
+
//trigger resize of editableform to re-position container in multi-valued mode
|
3205
|
+
if(this.isMultiple) {
|
3206
|
+
this.$input.on('change', function() {
|
3207
|
+
$(this).closest('form').parent().triggerHandler('resize');
|
3208
|
+
});
|
3209
|
+
}
|
3210
|
+
|
3211
|
+
},
|
3212
|
+
|
3213
|
+
value2html: function(value, element) {
|
3214
|
+
var text = '', data;
|
3215
|
+
if(this.$input) { //when submitting form
|
3216
|
+
data = this.$input.select2('data');
|
3217
|
+
} else { //on init (autotext)
|
3218
|
+
//here select2 instance not created yet and data may be even not loaded.
|
3219
|
+
//we can check data/tags property of select config and if exist lookup text
|
3220
|
+
if(this.options.select2.tags) {
|
3221
|
+
data = value;
|
3222
|
+
} else if(this.options.select2.data) {
|
3223
|
+
data = $.fn.editableutils.itemsByValue(value, this.options.select2.data, 'id');
|
3224
|
+
}
|
3225
|
+
}
|
3226
|
+
|
3227
|
+
if($.isArray(data)) {
|
3228
|
+
//collect selected data and show with separator
|
3229
|
+
text = [];
|
3230
|
+
$.each(data, function(k, v){
|
3231
|
+
text.push(v && typeof v === 'object' ? v.text : v);
|
3232
|
+
});
|
3233
|
+
} else if(data) {
|
3234
|
+
text = data.text;
|
3235
|
+
}
|
3236
|
+
|
3237
|
+
text = $.isArray(text) ? text.join(this.options.viewseparator) : text;
|
3238
|
+
|
3239
|
+
$(element).text(text);
|
3240
|
+
},
|
3241
|
+
|
3242
|
+
html2value: function(html) {
|
3243
|
+
return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
|
3244
|
+
},
|
3245
|
+
|
3246
|
+
value2input: function(value) {
|
3247
|
+
this.$input.val(value).trigger('change');
|
3248
|
+
},
|
3249
|
+
|
3250
|
+
input2value: function() {
|
3251
|
+
return this.$input.select2('val');
|
3252
|
+
},
|
3253
|
+
|
3254
|
+
str2value: function(str, separator) {
|
3255
|
+
if(typeof str !== 'string' || !this.isMultiple) {
|
3256
|
+
return str;
|
3257
|
+
}
|
3258
|
+
|
3259
|
+
separator = separator || this.options.select2.separator || $.fn.select2.defaults.separator;
|
3260
|
+
|
3261
|
+
var val, i, l;
|
3262
|
+
|
3263
|
+
if (str === null || str.length < 1) {
|
3264
|
+
return null;
|
3265
|
+
}
|
3266
|
+
val = str.split(separator);
|
3267
|
+
for (i = 0, l = val.length; i < l; i = i + 1) {
|
3268
|
+
val[i] = $.trim(val[i]);
|
3269
|
+
}
|
3270
|
+
|
3271
|
+
return val;
|
3272
|
+
}
|
3273
|
+
|
3274
|
+
});
|
3275
|
+
|
3276
|
+
Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
3277
|
+
/**
|
3278
|
+
@property tpl
|
3279
|
+
@default <input type="hidden">
|
3280
|
+
**/
|
3281
|
+
tpl:'<input type="hidden">',
|
3282
|
+
/**
|
3283
|
+
Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
|
3284
|
+
|
3285
|
+
@property select2
|
3286
|
+
@type object
|
3287
|
+
@default null
|
3288
|
+
**/
|
3289
|
+
select2: null,
|
3290
|
+
/**
|
3291
|
+
Placeholder attribute of select
|
3292
|
+
|
3293
|
+
@property placeholder
|
3294
|
+
@type string
|
3295
|
+
@default null
|
3296
|
+
**/
|
3297
|
+
placeholder: null,
|
3298
|
+
/**
|
3299
|
+
Source data for select. It will be assigned to select2 `data` property and kept here just for convenience.
|
3300
|
+
Please note, that format is different from simple `select` input: use 'id' instead of 'value'.
|
3301
|
+
E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.
|
3302
|
+
|
3303
|
+
@property source
|
3304
|
+
@type array
|
3305
|
+
@default null
|
3306
|
+
**/
|
3307
|
+
source: null,
|
3308
|
+
/**
|
3309
|
+
Separator used to display tags.
|
3310
|
+
|
3311
|
+
@property viewseparator
|
3312
|
+
@type string
|
3313
|
+
@default ', '
|
3314
|
+
**/
|
3315
|
+
viewseparator: ', '
|
3316
|
+
});
|
3317
|
+
|
3318
|
+
$.fn.editabletypes.select2 = Constructor;
|
3319
|
+
|
3320
|
+
}(window.jQuery));
|
3321
|
+
/**
|
3322
|
+
* Combodate - 1.0.1
|
3323
|
+
* Dropdown date and time picker.
|
3324
|
+
* Converts text input into dropdowns to pick day, month, year, hour, minute and second.
|
3325
|
+
* Uses momentjs as datetime library http://momentjs.com.
|
3326
|
+
* For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang
|
3327
|
+
*
|
3328
|
+
* Author: Vitaliy Potapov
|
3329
|
+
* Project page: http://github.com/vitalets/combodate
|
3330
|
+
* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
|
3331
|
+
**/
|
3332
|
+
(function ($) {
|
3333
|
+
|
3334
|
+
var Combodate = function (element, options) {
|
3335
|
+
this.$element = $(element);
|
3336
|
+
if(!this.$element.is('input')) {
|
3337
|
+
$.error('Combodate should be applied to INPUT element');
|
3338
|
+
return;
|
3339
|
+
}
|
3340
|
+
this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data());
|
3341
|
+
this.init();
|
3342
|
+
};
|
3343
|
+
|
3344
|
+
Combodate.prototype = {
|
3345
|
+
constructor: Combodate,
|
3346
|
+
init: function () {
|
3347
|
+
this.map = {
|
3348
|
+
//key regexp moment.method
|
3349
|
+
day: ['D', 'date'],
|
3350
|
+
month: ['M', 'month'],
|
3351
|
+
year: ['Y', 'year'],
|
3352
|
+
hour: ['[Hh]', 'hours'],
|
3353
|
+
minute: ['m', 'minutes'],
|
3354
|
+
second: ['s', 'seconds'],
|
3355
|
+
ampm: ['[Aa]', '']
|
3356
|
+
};
|
3357
|
+
|
3358
|
+
this.$widget = $('<span class="combodate"></span>').html(this.getTemplate());
|
3359
|
+
|
3360
|
+
this.initCombos();
|
3361
|
+
|
3362
|
+
//update original input on change
|
3363
|
+
this.$widget.on('change', 'select', $.proxy(function(){
|
3364
|
+
this.$element.val(this.getValue());
|
3365
|
+
}, this));
|
3366
|
+
|
3367
|
+
this.$widget.find('select').css('width', 'auto');
|
3368
|
+
|
3369
|
+
//hide original input and insert widget
|
3370
|
+
this.$element.hide().after(this.$widget);
|
3371
|
+
|
3372
|
+
//set initial value
|
3373
|
+
this.setValue(this.$element.val() || this.options.value);
|
3374
|
+
},
|
3375
|
+
|
3376
|
+
/*
|
3377
|
+
Replace tokens in template with <select> elements
|
3378
|
+
*/
|
3379
|
+
getTemplate: function() {
|
3380
|
+
var tpl = this.options.template;
|
3381
|
+
|
3382
|
+
//first pass
|
3383
|
+
$.each(this.map, function(k, v) {
|
3384
|
+
v = v[0];
|
3385
|
+
var r = new RegExp(v+'+'),
|
3386
|
+
token = v.length > 1 ? v.substring(1, 2) : v;
|
3387
|
+
|
3388
|
+
tpl = tpl.replace(r, '{'+token+'}');
|
3389
|
+
});
|
3390
|
+
|
3391
|
+
//replace spaces with
|
3392
|
+
tpl = tpl.replace(/ /g, ' ');
|
3393
|
+
|
3394
|
+
//second pass
|
3395
|
+
$.each(this.map, function(k, v) {
|
3396
|
+
v = v[0];
|
3397
|
+
var token = v.length > 1 ? v.substring(1, 2) : v;
|
3398
|
+
|
3399
|
+
tpl = tpl.replace('{'+token+'}', '<select class="'+k+'"></select>');
|
3400
|
+
});
|
3401
|
+
|
3402
|
+
return tpl;
|
3403
|
+
},
|
3404
|
+
|
3405
|
+
/*
|
3406
|
+
Initialize combos that presents in template
|
3407
|
+
*/
|
3408
|
+
initCombos: function() {
|
3409
|
+
var that = this;
|
3410
|
+
$.each(this.map, function(k, v) {
|
3411
|
+
var $c = that.$widget.find('.'+k), f, items;
|
3412
|
+
if($c.length) {
|
3413
|
+
that['$'+k] = $c; //set properties like this.$day, this.$month etc.
|
3414
|
+
f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1); //define method name to fill items, e.g `fillDays`
|
3415
|
+
items = that[f]();
|
3416
|
+
that['$'+k].html(that.renderItems(items));
|
3417
|
+
}
|
3418
|
+
});
|
3419
|
+
},
|
3420
|
+
|
3421
|
+
/*
|
3422
|
+
Initialize items of combos. Handles `firstItem` option
|
3423
|
+
*/
|
3424
|
+
initItems: function(key) {
|
3425
|
+
var values = [];
|
3426
|
+
if(this.options.firstItem === 'name') {
|
3427
|
+
var header = typeof moment.relativeTime[key] === 'function' ? moment.relativeTime[key](1, true, key, false) : moment.relativeTime[key];
|
3428
|
+
//take last entry (see momentjs lang files structure)
|
3429
|
+
header = header.split(' ').reverse()[0];
|
3430
|
+
values.push(['', header]);
|
3431
|
+
} else if(this.options.firstItem === 'empty') {
|
3432
|
+
values.push(['', '']);
|
3433
|
+
}
|
3434
|
+
return values;
|
3435
|
+
},
|
3436
|
+
|
3437
|
+
/*
|
3438
|
+
render items to string of <option> tags
|
3439
|
+
*/
|
3440
|
+
renderItems: function(items) {
|
3441
|
+
var str = [];
|
3442
|
+
for(var i=0; i<items.length; i++) {
|
3443
|
+
str.push('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');
|
3444
|
+
}
|
3445
|
+
return str.join("\n");
|
3446
|
+
},
|
3447
|
+
|
3448
|
+
/*
|
3449
|
+
fill day
|
3450
|
+
*/
|
3451
|
+
fillDay: function() {
|
3452
|
+
var items = this.initItems('d'), name, i,
|
3453
|
+
twoDigit = this.options.template.indexOf('DD') !== -1;
|
3454
|
+
|
3455
|
+
for(i=1; i<=31; i++) {
|
3456
|
+
name = twoDigit ? this.leadZero(i) : i;
|
3457
|
+
items.push([i, name]);
|
3458
|
+
}
|
3459
|
+
return items;
|
3460
|
+
},
|
3461
|
+
|
3462
|
+
/*
|
3463
|
+
fill month
|
3464
|
+
*/
|
3465
|
+
fillMonth: function() {
|
3466
|
+
var items = this.initItems('M'), name, i,
|
3467
|
+
longNames = this.options.template.indexOf('MMMM') !== -1,
|
3468
|
+
shortNames = this.options.template.indexOf('MMM') !== -1,
|
3469
|
+
twoDigit = this.options.template.indexOf('MM') !== -1;
|
3470
|
+
|
3471
|
+
for(i=0; i<=11; i++) {
|
3472
|
+
if(longNames) {
|
3473
|
+
name = moment.months[i];
|
3474
|
+
} else if(shortNames) {
|
3475
|
+
name = moment.monthsShort[i];
|
3476
|
+
} else if(twoDigit) {
|
3477
|
+
name = this.leadZero(i+1);
|
3478
|
+
} else {
|
3479
|
+
name = i+1;
|
3480
|
+
}
|
3481
|
+
items.push([i, name]);
|
3482
|
+
}
|
3483
|
+
return items;
|
3484
|
+
},
|
3485
|
+
|
3486
|
+
/*
|
3487
|
+
fill year
|
3488
|
+
*/
|
3489
|
+
fillYear: function() {
|
3490
|
+
var items = this.initItems('y'), name, i,
|
3491
|
+
longNames = this.options.template.indexOf('YYYY') !== -1;
|
3492
|
+
|
3493
|
+
for(i=this.options.maxYear; i>=this.options.minYear; i--) {
|
3494
|
+
name = longNames ? i : (i+'').substring(2);
|
3495
|
+
items.push([i, name]);
|
3496
|
+
}
|
3497
|
+
return items;
|
3498
|
+
},
|
3499
|
+
|
3500
|
+
/*
|
3501
|
+
fill hour
|
3502
|
+
*/
|
3503
|
+
fillHour: function() {
|
3504
|
+
var items = this.initItems('h'), name, i,
|
3505
|
+
h12 = this.options.template.indexOf('h') !== -1,
|
3506
|
+
h24 = this.options.template.indexOf('H') !== -1,
|
3507
|
+
twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
|
3508
|
+
max = h12 ? 12 : 23;
|
3509
|
+
|
3510
|
+
for(i=0; i<=max; i++) {
|
3511
|
+
name = twoDigit ? this.leadZero(i) : i;
|
3512
|
+
items.push([i, name]);
|
3513
|
+
}
|
3514
|
+
return items;
|
3515
|
+
},
|
3516
|
+
|
3517
|
+
/*
|
3518
|
+
fill minute
|
3519
|
+
*/
|
3520
|
+
fillMinute: function() {
|
3521
|
+
var items = this.initItems('m'), name, i,
|
3522
|
+
twoDigit = this.options.template.indexOf('mm') !== -1;
|
3523
|
+
|
3524
|
+
for(i=0; i<=59; i+= this.options.minuteStep) {
|
3525
|
+
name = twoDigit ? this.leadZero(i) : i;
|
3526
|
+
items.push([i, name]);
|
3527
|
+
}
|
3528
|
+
return items;
|
3529
|
+
},
|
3530
|
+
|
3531
|
+
/*
|
3532
|
+
fill second
|
3533
|
+
*/
|
3534
|
+
fillSecond: function() {
|
3535
|
+
var items = this.initItems('s'), name, i,
|
3536
|
+
twoDigit = this.options.template.indexOf('ss') !== -1;
|
3537
|
+
|
3538
|
+
for(i=0; i<=59; i+= this.options.secondStep) {
|
3539
|
+
name = twoDigit ? this.leadZero(i) : i;
|
3540
|
+
items.push([i, name]);
|
3541
|
+
}
|
3542
|
+
return items;
|
3543
|
+
},
|
3544
|
+
|
3545
|
+
/*
|
3546
|
+
fill ampm
|
3547
|
+
*/
|
3548
|
+
fillAmpm: function() {
|
3549
|
+
var ampmL = this.options.template.indexOf('a') !== -1,
|
3550
|
+
ampmU = this.options.template.indexOf('A') !== -1,
|
3551
|
+
items = [
|
3552
|
+
['am', ampmL ? 'am' : 'AM'],
|
3553
|
+
['pm', ampmL ? 'pm' : 'PM']
|
3554
|
+
];
|
3555
|
+
return items;
|
3556
|
+
},
|
3557
|
+
|
3558
|
+
/*
|
3559
|
+
Returns current date value.
|
3560
|
+
If format not specified - `options.format` used.
|
3561
|
+
If format = `null` - Moment object returned.
|
3562
|
+
*/
|
3563
|
+
getValue: function(format) {
|
3564
|
+
var dt, values = {},
|
3565
|
+
that = this,
|
3566
|
+
notSelected = false;
|
3567
|
+
|
3568
|
+
//getting selected values
|
3569
|
+
$.each(this.map, function(k, v) {
|
3570
|
+
if(k === 'ampm') {
|
3571
|
+
return;
|
3572
|
+
}
|
3573
|
+
var def = k === 'day' ? 1 : 0;
|
3574
|
+
|
3575
|
+
values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def;
|
3576
|
+
|
3577
|
+
if(isNaN(values[k])) {
|
3578
|
+
notSelected = true;
|
3579
|
+
return false;
|
3580
|
+
}
|
3581
|
+
});
|
3582
|
+
|
3583
|
+
//if at least one visible combo not selected - return empty string
|
3584
|
+
if(notSelected) {
|
3585
|
+
return '';
|
3586
|
+
}
|
3587
|
+
|
3588
|
+
//convert hours if 12h format
|
3589
|
+
if(this.$ampm) {
|
3590
|
+
values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
|
3591
|
+
if(values.hour === 24) {
|
3592
|
+
values.hour = 0;
|
3593
|
+
}
|
3594
|
+
}
|
3595
|
+
|
3596
|
+
dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
|
3597
|
+
|
3598
|
+
//highlight invalid date
|
3599
|
+
this.highlight(dt);
|
3600
|
+
|
3601
|
+
format = format === undefined ? this.options.format : format;
|
3602
|
+
if(format === null) {
|
3603
|
+
return dt.isValid() ? dt : null;
|
3604
|
+
} else {
|
3605
|
+
return dt.isValid() ? dt.format(format) : '';
|
3606
|
+
}
|
3607
|
+
},
|
3608
|
+
|
3609
|
+
setValue: function(value) {
|
3610
|
+
if(!value) {
|
3611
|
+
return;
|
3612
|
+
}
|
3613
|
+
|
3614
|
+
var dt = typeof value === 'string' ? moment(value, this.options.format) : moment(value),
|
3615
|
+
that = this,
|
3616
|
+
values = {};
|
3617
|
+
|
3618
|
+
if(dt.isValid()) {
|
3619
|
+
//read values from date object
|
3620
|
+
$.each(this.map, function(k, v) {
|
3621
|
+
if(k === 'ampm') {
|
3622
|
+
return;
|
3623
|
+
}
|
3624
|
+
values[k] = dt[v[1]]();
|
3625
|
+
});
|
3626
|
+
|
3627
|
+
if(this.$ampm) {
|
3628
|
+
if(values.hour > 12) {
|
3629
|
+
values.hour -= 12;
|
3630
|
+
values.ampm = 'pm';
|
3631
|
+
} else {
|
3632
|
+
values.ampm = 'am';
|
3633
|
+
}
|
3634
|
+
}
|
3635
|
+
|
3636
|
+
$.each(values, function(k, v) {
|
3637
|
+
if(that['$'+k]) {
|
3638
|
+
that['$'+k].val(v);
|
3639
|
+
}
|
3640
|
+
});
|
3641
|
+
|
3642
|
+
this.$element.val(dt.format(this.options.format));
|
3643
|
+
}
|
3644
|
+
},
|
3645
|
+
|
3646
|
+
/*
|
3647
|
+
highlight combos if date is invalid
|
3648
|
+
*/
|
3649
|
+
highlight: function(dt) {
|
3650
|
+
if(!dt.isValid()) {
|
3651
|
+
if(this.options.errorClass) {
|
3652
|
+
this.$widget.addClass(this.options.errorClass);
|
3653
|
+
} else {
|
3654
|
+
//store original border color
|
3655
|
+
if(!this.borderColor) {
|
3656
|
+
this.borderColor = this.$widget.find('select').css('border-color');
|
3657
|
+
}
|
3658
|
+
this.$widget.find('select').css('border-color', 'red');
|
3659
|
+
}
|
3660
|
+
} else {
|
3661
|
+
if(this.options.errorClass) {
|
3662
|
+
this.$widget.removeClass(this.options.errorClass);
|
3663
|
+
} else {
|
3664
|
+
this.$widget.find('select').css('border-color', this.borderColor);
|
3665
|
+
}
|
3666
|
+
}
|
3667
|
+
},
|
3668
|
+
|
3669
|
+
leadZero: function(v) {
|
3670
|
+
return v <= 9 ? '0' + v : v;
|
3671
|
+
},
|
3672
|
+
|
3673
|
+
destroy: function() {
|
3674
|
+
this.$widget.remove();
|
3675
|
+
this.$element.removeData('combodate').show();
|
3676
|
+
}
|
3677
|
+
|
3678
|
+
//todo: clear method
|
3679
|
+
};
|
3680
|
+
|
3681
|
+
$.fn.combodate = function ( option ) {
|
3682
|
+
var d, args = Array.apply(null, arguments);
|
3683
|
+
args.shift();
|
3684
|
+
|
3685
|
+
//getValue returns date as string / object (not jQuery object)
|
3686
|
+
if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) {
|
3687
|
+
return d.getValue.apply(d, args);
|
3688
|
+
}
|
3689
|
+
|
3690
|
+
return this.each(function () {
|
3691
|
+
var $this = $(this),
|
3692
|
+
data = $this.data('combodate'),
|
3693
|
+
options = typeof option == 'object' && option;
|
3694
|
+
if (!data) {
|
3695
|
+
$this.data('combodate', (data = new Combodate(this, options)));
|
3696
|
+
}
|
3697
|
+
if (typeof option == 'string' && typeof data[option] == 'function') {
|
3698
|
+
data[option].apply(data, args);
|
3699
|
+
}
|
3700
|
+
});
|
3701
|
+
};
|
3702
|
+
|
3703
|
+
$.fn.combodate.defaults = {
|
3704
|
+
//in this format value stored in original input
|
3705
|
+
format: 'DD-MM-YYYY HH:mm',
|
3706
|
+
//in this format items in dropdowns are displayed
|
3707
|
+
template: 'D / MMM / YYYY H : mm',
|
3708
|
+
//initial value, can be `new Date()`
|
3709
|
+
value: null,
|
3710
|
+
minYear: 1970,
|
3711
|
+
maxYear: 2015,
|
3712
|
+
minuteStep: 5,
|
3713
|
+
secondStep: 1,
|
3714
|
+
firstItem: 'empty', //'name', 'empty', 'none'
|
3715
|
+
errorClass: null
|
3716
|
+
};
|
3717
|
+
|
3718
|
+
}(window.jQuery));
|
3719
|
+
/**
|
3720
|
+
Combodate input - dropdown date and time picker.
|
3721
|
+
Based on [combodate](http://vitalets.github.com/combodate) plugin. To use it you should manually include [momentjs](http://momentjs.com).
|
3722
|
+
|
3723
|
+
<script src="js/moment.min.js"></script>
|
3724
|
+
|
3725
|
+
Allows to input:
|
3726
|
+
|
3727
|
+
* only date
|
3728
|
+
* only time
|
3729
|
+
* both date and time
|
3730
|
+
|
3731
|
+
Please note, that format is taken from momentjs and **not compatible** with bootstrap-datepicker / jquery UI datepicker.
|
3732
|
+
Internally value stored as `momentjs` object.
|
3733
|
+
|
3734
|
+
@class combodate
|
3735
|
+
@extends abstractinput
|
3736
|
+
@final
|
3737
|
+
@since 1.4.0
|
3738
|
+
@example
|
3739
|
+
<a href="#" id="dob" data-type="combodate" data-pk="1" data-url="/post" data-value="1984-05-15" data-original-title="Select date"></a>
|
3740
|
+
<script>
|
3741
|
+
$(function(){
|
3742
|
+
$('#dob').editable({
|
3743
|
+
format: 'YYYY-MM-DD',
|
3744
|
+
viewformat: 'DD.MM.YYYY',
|
3745
|
+
template: 'D / MMMM / YYYY',
|
3746
|
+
combodate: {
|
3747
|
+
minYear: 2000,
|
3748
|
+
maxYear: 2015,
|
3749
|
+
minuteStep: 1
|
3750
|
+
}
|
3751
|
+
}
|
3752
|
+
});
|
3753
|
+
});
|
3754
|
+
</script>
|
3755
|
+
**/
|
3756
|
+
|
3757
|
+
/*global moment*/
|
3758
|
+
|
3759
|
+
(function ($) {
|
3760
|
+
|
3761
|
+
var Constructor = function (options) {
|
3762
|
+
this.init('combodate', options, Constructor.defaults);
|
3763
|
+
|
3764
|
+
//by default viewformat equals to format
|
3765
|
+
if(!this.options.viewformat) {
|
3766
|
+
this.options.viewformat = this.options.format;
|
3767
|
+
}
|
3768
|
+
|
3769
|
+
//overriding combodate config (as by default jQuery extend() is not recursive)
|
3770
|
+
this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, {
|
3771
|
+
format: this.options.format,
|
3772
|
+
template: this.options.template
|
3773
|
+
});
|
3774
|
+
};
|
3775
|
+
|
3776
|
+
$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
|
3777
|
+
|
3778
|
+
$.extend(Constructor.prototype, {
|
3779
|
+
render: function () {
|
3780
|
+
this.$input.combodate(this.options.combodate);
|
3781
|
+
|
3782
|
+
//"clear" link
|
3783
|
+
/*
|
3784
|
+
if(this.options.clear) {
|
3785
|
+
this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
|
3786
|
+
e.preventDefault();
|
3787
|
+
e.stopPropagation();
|
3788
|
+
this.clear();
|
3789
|
+
}, this));
|
3790
|
+
|
3791
|
+
this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
|
3792
|
+
}
|
3793
|
+
*/
|
3794
|
+
},
|
3795
|
+
|
3796
|
+
value2html: function(value, element) {
|
3797
|
+
var text = value ? value.format(this.options.viewformat) : '';
|
3798
|
+
$(element).text(text);
|
3799
|
+
},
|
3800
|
+
|
3801
|
+
html2value: function(html) {
|
3802
|
+
return html ? moment(html, this.options.viewformat) : null;
|
3803
|
+
},
|
3804
|
+
|
3805
|
+
value2str: function(value) {
|
3806
|
+
return value ? value.format(this.options.format) : '';
|
3807
|
+
},
|
3808
|
+
|
3809
|
+
str2value: function(str) {
|
3810
|
+
return str ? moment(str, this.options.format) : null;
|
3811
|
+
},
|
3812
|
+
|
3813
|
+
value2submit: function(value) {
|
3814
|
+
return this.value2str(value);
|
3815
|
+
},
|
3816
|
+
|
3817
|
+
value2input: function(value) {
|
3818
|
+
this.$input.combodate('setValue', value);
|
3819
|
+
},
|
3820
|
+
|
3821
|
+
input2value: function() {
|
3822
|
+
return this.$input.combodate('getValue', null);
|
3823
|
+
},
|
3824
|
+
|
3825
|
+
activate: function() {
|
3826
|
+
this.$input.siblings('.combodate').find('select').eq(0).focus();
|
3827
|
+
},
|
3828
|
+
|
3829
|
+
/*
|
3830
|
+
clear: function() {
|
3831
|
+
this.$input.data('datepicker').date = null;
|
3832
|
+
this.$input.find('.active').removeClass('active');
|
3833
|
+
},
|
3834
|
+
*/
|
3835
|
+
|
3836
|
+
autosubmit: function() {
|
3837
|
+
|
3838
|
+
}
|
3839
|
+
|
3840
|
+
});
|
3841
|
+
|
3842
|
+
Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
3843
|
+
/**
|
3844
|
+
@property tpl
|
3845
|
+
@default <input type="text">
|
3846
|
+
**/
|
3847
|
+
tpl:'<input type="text">',
|
3848
|
+
/**
|
3849
|
+
@property inputclass
|
3850
|
+
@default null
|
3851
|
+
**/
|
3852
|
+
inputclass: null,
|
3853
|
+
/**
|
3854
|
+
Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
|
3855
|
+
See list of tokens in [momentjs docs](http://momentjs.com/docs/#/parsing/string-format)
|
3856
|
+
|
3857
|
+
@property format
|
3858
|
+
@type string
|
3859
|
+
@default YYYY-MM-DD
|
3860
|
+
**/
|
3861
|
+
format:'YYYY-MM-DD',
|
3862
|
+
/**
|
3863
|
+
Format used for displaying date. Also applied when converting date from element's text on init.
|
3864
|
+
If not specified equals to `format`.
|
3865
|
+
|
3866
|
+
@property viewformat
|
3867
|
+
@type string
|
3868
|
+
@default null
|
3869
|
+
**/
|
3870
|
+
viewformat: null,
|
3871
|
+
/**
|
3872
|
+
Template used for displaying dropdowns.
|
3873
|
+
|
3874
|
+
@property template
|
3875
|
+
@type string
|
3876
|
+
@default D / MMM / YYYY
|
3877
|
+
**/
|
3878
|
+
template: 'D / MMM / YYYY',
|
3879
|
+
/**
|
3880
|
+
Configuration of combodate.
|
3881
|
+
Full list of options: http://vitalets.github.com/combodate/#docs
|
3882
|
+
|
3883
|
+
@property combodate
|
3884
|
+
@type object
|
3885
|
+
@default null
|
3886
|
+
**/
|
3887
|
+
combodate: null
|
3888
|
+
|
3889
|
+
/*
|
3890
|
+
(not implemented yet)
|
3891
|
+
Text shown as clear date button.
|
3892
|
+
If <code>false</code> clear button will not be rendered.
|
3893
|
+
|
3894
|
+
@property clear
|
3895
|
+
@type boolean|string
|
3896
|
+
@default 'x clear'
|
3897
|
+
*/
|
3898
|
+
//clear: '× clear'
|
3899
|
+
});
|
3900
|
+
|
3901
|
+
$.fn.editabletypes.combodate = Constructor;
|
3902
|
+
|
3903
|
+
}(window.jQuery));
|
3904
|
+
|
3905
|
+
/*
|
3906
|
+
Editableform based on Twitter Bootstrap
|
3907
|
+
*/
|
3908
|
+
(function ($) {
|
3909
|
+
|
3910
|
+
$.extend($.fn.editableform.Constructor.prototype, {
|
3911
|
+
initTemplate: function() {
|
3912
|
+
this.$form = $($.fn.editableform.template);
|
3913
|
+
this.$form.find('.editable-error-block').addClass('help-block');
|
3914
|
+
}
|
3915
|
+
});
|
3916
|
+
|
3917
|
+
//buttons
|
3918
|
+
$.fn.editableform.buttons = '<button type="submit" class="btn btn-primary editable-submit"><i class="icon-ok icon-white"></i></button>'+
|
3919
|
+
'<button type="button" class="btn editable-cancel"><i class="icon-remove"></i></button>';
|
3920
|
+
|
3921
|
+
//error classes
|
3922
|
+
$.fn.editableform.errorGroupClass = 'error';
|
3923
|
+
$.fn.editableform.errorBlockClass = null;
|
3924
|
+
|
3925
|
+
}(window.jQuery));
|
3926
|
+
/**
|
3927
|
+
* Editable Popover
|
3928
|
+
* ---------------------
|
3929
|
+
* requires bootstrap-popover.js
|
3930
|
+
*/
|
3931
|
+
(function ($) {
|
3932
|
+
|
3933
|
+
//extend methods
|
3934
|
+
$.extend($.fn.editableContainer.Popup.prototype, {
|
3935
|
+
containerName: 'popover',
|
3936
|
+
//for compatibility with bootstrap <= 2.2.1 (content inserted into <p> instead of directly .popover-content)
|
3937
|
+
innerCss: $($.fn.popover.defaults.template).find('p').length ? '.popover-content p' : '.popover-content',
|
3938
|
+
|
3939
|
+
initContainer: function(){
|
3940
|
+
$.extend(this.containerOptions, {
|
3941
|
+
trigger: 'manual',
|
3942
|
+
selector: false,
|
3943
|
+
content: ' ',
|
3944
|
+
template: $.fn.popover.defaults.template
|
3945
|
+
});
|
3946
|
+
|
3947
|
+
//as template property is used in inputs, hide it from popover
|
3948
|
+
var t;
|
3949
|
+
if(this.$element.data('template')) {
|
3950
|
+
t = this.$element.data('template');
|
3951
|
+
this.$element.removeData('template');
|
3952
|
+
}
|
3953
|
+
|
3954
|
+
this.call(this.containerOptions);
|
3955
|
+
|
3956
|
+
if(t) {
|
3957
|
+
//restore data('template')
|
3958
|
+
this.$element.data('template', t);
|
3959
|
+
}
|
3960
|
+
},
|
3961
|
+
|
3962
|
+
/* show */
|
3963
|
+
innerShow: function () {
|
3964
|
+
this.call('show');
|
3965
|
+
},
|
3966
|
+
|
3967
|
+
/* hide */
|
3968
|
+
innerHide: function () {
|
3969
|
+
this.call('hide');
|
3970
|
+
},
|
3971
|
+
|
3972
|
+
/* destroy */
|
3973
|
+
innerDestroy: function() {
|
3974
|
+
this.call('destroy');
|
3975
|
+
},
|
3976
|
+
|
3977
|
+
setContainerOption: function(key, value) {
|
3978
|
+
this.container().options[key] = value;
|
3979
|
+
},
|
3980
|
+
|
3981
|
+
/**
|
3982
|
+
* move popover to new position. This function mainly copied from bootstrap-popover.
|
3983
|
+
*/
|
3984
|
+
/*jshint laxcomma: true*/
|
3985
|
+
setPosition: function () {
|
3986
|
+
|
3987
|
+
(function() {
|
3988
|
+
var $tip = this.tip()
|
3989
|
+
, inside
|
3990
|
+
, pos
|
3991
|
+
, actualWidth
|
3992
|
+
, actualHeight
|
3993
|
+
, placement
|
3994
|
+
, tp;
|
3995
|
+
|
3996
|
+
placement = typeof this.options.placement === 'function' ?
|
3997
|
+
this.options.placement.call(this, $tip[0], this.$element[0]) :
|
3998
|
+
this.options.placement;
|
3999
|
+
|
4000
|
+
inside = /in/.test(placement);
|
4001
|
+
|
4002
|
+
$tip
|
4003
|
+
// .detach()
|
4004
|
+
//vitalets: remove any placement class because otherwise they dont influence on re-positioning of visible popover
|
4005
|
+
.removeClass('top right bottom left')
|
4006
|
+
.css({ top: 0, left: 0, display: 'block' });
|
4007
|
+
// .insertAfter(this.$element);
|
4008
|
+
|
4009
|
+
pos = this.getPosition(inside);
|
4010
|
+
|
4011
|
+
actualWidth = $tip[0].offsetWidth;
|
4012
|
+
actualHeight = $tip[0].offsetHeight;
|
4013
|
+
|
4014
|
+
switch (inside ? placement.split(' ')[1] : placement) {
|
4015
|
+
case 'bottom':
|
4016
|
+
tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2};
|
4017
|
+
break;
|
4018
|
+
case 'top':
|
4019
|
+
tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2};
|
4020
|
+
break;
|
4021
|
+
case 'left':
|
4022
|
+
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth};
|
4023
|
+
break;
|
4024
|
+
case 'right':
|
4025
|
+
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width};
|
4026
|
+
break;
|
4027
|
+
}
|
4028
|
+
|
4029
|
+
$tip
|
4030
|
+
.offset(tp)
|
4031
|
+
.addClass(placement)
|
4032
|
+
.addClass('in');
|
4033
|
+
|
4034
|
+
}).call(this.container());
|
4035
|
+
/*jshint laxcomma: false*/
|
4036
|
+
}
|
4037
|
+
});
|
4038
|
+
|
4039
|
+
}(window.jQuery));
|
4040
|
+
/**
|
4041
|
+
Bootstrap-datepicker.
|
4042
|
+
Description and examples: https://github.com/eternicode/bootstrap-datepicker.
|
4043
|
+
For **i18n** you should include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
|
4044
|
+
and set `language` option.
|
4045
|
+
Since 1.4.0 date has different appearance in **popup** and **inline** modes.
|
4046
|
+
|
4047
|
+
@class date
|
4048
|
+
@extends abstractinput
|
4049
|
+
@final
|
4050
|
+
@example
|
4051
|
+
<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a>
|
4052
|
+
<script>
|
4053
|
+
$(function(){
|
4054
|
+
$('#dob').editable({
|
4055
|
+
format: 'yyyy-mm-dd',
|
4056
|
+
viewformat: 'dd/mm/yyyy',
|
4057
|
+
datepicker: {
|
4058
|
+
weekStart: 1
|
4059
|
+
}
|
4060
|
+
}
|
4061
|
+
});
|
4062
|
+
});
|
4063
|
+
</script>
|
4064
|
+
**/
|
4065
|
+
(function ($) {
|
4066
|
+
|
4067
|
+
var Date = function (options) {
|
4068
|
+
this.init('date', options, Date.defaults);
|
4069
|
+
this.initPicker(options, Date.defaults);
|
4070
|
+
};
|
4071
|
+
|
4072
|
+
$.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
|
4073
|
+
|
4074
|
+
$.extend(Date.prototype, {
|
4075
|
+
initPicker: function(options, defaults) {
|
4076
|
+
//'format' is set directly from settings or data-* attributes
|
4077
|
+
|
4078
|
+
//by default viewformat equals to format
|
4079
|
+
if(!this.options.viewformat) {
|
4080
|
+
this.options.viewformat = this.options.format;
|
4081
|
+
}
|
4082
|
+
|
4083
|
+
//overriding datepicker config (as by default jQuery extend() is not recursive)
|
4084
|
+
//since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
|
4085
|
+
this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
|
4086
|
+
format: this.options.viewformat
|
4087
|
+
});
|
4088
|
+
|
4089
|
+
//language
|
4090
|
+
this.options.datepicker.language = this.options.datepicker.language || 'en';
|
4091
|
+
|
4092
|
+
//store DPglobal
|
4093
|
+
this.dpg = $.fn.datepicker.DPGlobal;
|
4094
|
+
|
4095
|
+
//store parsed formats
|
4096
|
+
this.parsedFormat = this.dpg.parseFormat(this.options.format);
|
4097
|
+
this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
|
4098
|
+
},
|
4099
|
+
|
4100
|
+
render: function () {
|
4101
|
+
this.$input.datepicker(this.options.datepicker);
|
4102
|
+
|
4103
|
+
//"clear" link
|
4104
|
+
if(this.options.clear) {
|
4105
|
+
this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
|
4106
|
+
e.preventDefault();
|
4107
|
+
e.stopPropagation();
|
4108
|
+
this.clear();
|
4109
|
+
}, this));
|
4110
|
+
|
4111
|
+
this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
|
4112
|
+
}
|
4113
|
+
},
|
4114
|
+
|
4115
|
+
value2html: function(value, element) {
|
4116
|
+
var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
|
4117
|
+
Date.superclass.value2html(text, element);
|
4118
|
+
},
|
4119
|
+
|
4120
|
+
html2value: function(html) {
|
4121
|
+
return html ? this.dpg.parseDate(html, this.parsedViewFormat, this.options.datepicker.language) : null;
|
4122
|
+
},
|
4123
|
+
|
4124
|
+
value2str: function(value) {
|
4125
|
+
return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
|
4126
|
+
},
|
4127
|
+
|
4128
|
+
str2value: function(str) {
|
4129
|
+
return str ? this.dpg.parseDate(str, this.parsedFormat, this.options.datepicker.language) : null;
|
4130
|
+
},
|
4131
|
+
|
4132
|
+
value2submit: function(value) {
|
4133
|
+
return this.value2str(value);
|
4134
|
+
},
|
4135
|
+
|
4136
|
+
value2input: function(value) {
|
4137
|
+
this.$input.datepicker('update', value);
|
4138
|
+
},
|
4139
|
+
|
4140
|
+
input2value: function() {
|
4141
|
+
return this.$input.data('datepicker').date;
|
4142
|
+
},
|
4143
|
+
|
4144
|
+
activate: function() {
|
4145
|
+
},
|
4146
|
+
|
4147
|
+
clear: function() {
|
4148
|
+
this.$input.data('datepicker').date = null;
|
4149
|
+
this.$input.find('.active').removeClass('active');
|
4150
|
+
},
|
4151
|
+
|
4152
|
+
autosubmit: function() {
|
4153
|
+
this.$input.on('changeDate', function(e){
|
4154
|
+
var $form = $(this).closest('form');
|
4155
|
+
setTimeout(function() {
|
4156
|
+
$form.submit();
|
4157
|
+
}, 200);
|
4158
|
+
});
|
4159
|
+
}
|
4160
|
+
|
4161
|
+
});
|
4162
|
+
|
4163
|
+
Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
|
4164
|
+
/**
|
4165
|
+
@property tpl
|
4166
|
+
@default <div></div>
|
4167
|
+
**/
|
4168
|
+
tpl:'<div class="editable-date well"></div>',
|
4169
|
+
/**
|
4170
|
+
@property inputclass
|
4171
|
+
@default null
|
4172
|
+
**/
|
4173
|
+
inputclass: null,
|
4174
|
+
/**
|
4175
|
+
Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
|
4176
|
+
Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code>
|
4177
|
+
|
4178
|
+
@property format
|
4179
|
+
@type string
|
4180
|
+
@default yyyy-mm-dd
|
4181
|
+
**/
|
4182
|
+
format:'yyyy-mm-dd',
|
4183
|
+
/**
|
4184
|
+
Format used for displaying date. Also applied when converting date from element's text on init.
|
4185
|
+
If not specified equals to <code>format</code>
|
4186
|
+
|
4187
|
+
@property viewformat
|
4188
|
+
@type string
|
4189
|
+
@default null
|
4190
|
+
**/
|
4191
|
+
viewformat: null,
|
4192
|
+
/**
|
4193
|
+
Configuration of datepicker.
|
4194
|
+
Full list of options: http://vitalets.github.com/bootstrap-datepicker
|
4195
|
+
|
4196
|
+
@property datepicker
|
4197
|
+
@type object
|
4198
|
+
@default {
|
4199
|
+
weekStart: 0,
|
4200
|
+
startView: 0,
|
4201
|
+
autoclose: false
|
4202
|
+
}
|
4203
|
+
**/
|
4204
|
+
datepicker:{
|
4205
|
+
weekStart: 0,
|
4206
|
+
startView: 0,
|
4207
|
+
autoclose: false
|
4208
|
+
},
|
4209
|
+
/**
|
4210
|
+
Text shown as clear date button.
|
4211
|
+
If <code>false</code> clear button will not be rendered.
|
4212
|
+
|
4213
|
+
@property clear
|
4214
|
+
@type boolean|string
|
4215
|
+
@default 'x clear'
|
4216
|
+
**/
|
4217
|
+
clear: '× clear'
|
4218
|
+
});
|
4219
|
+
|
4220
|
+
$.fn.editabletypes.date = Date;
|
4221
|
+
|
4222
|
+
}(window.jQuery));
|
4223
|
+
|
4224
|
+
/**
|
4225
|
+
Bootstrap datefield input - modification for inline mode.
|
4226
|
+
Shows normal <input type="text"> and binds popup datepicker.
|
4227
|
+
Automatically shown in inline mode.
|
4228
|
+
|
4229
|
+
@class datefield
|
4230
|
+
@extends date
|
4231
|
+
|
4232
|
+
@since 1.4.0
|
4233
|
+
**/
|
4234
|
+
(function ($) {
|
4235
|
+
|
4236
|
+
var DateField = function (options) {
|
4237
|
+
this.init('datefield', options, DateField.defaults);
|
4238
|
+
this.initPicker(options, DateField.defaults);
|
4239
|
+
};
|
4240
|
+
|
4241
|
+
$.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);
|
4242
|
+
|
4243
|
+
$.extend(DateField.prototype, {
|
4244
|
+
render: function () {
|
4245
|
+
this.$input = this.$tpl.find('input');
|
4246
|
+
this.setClass();
|
4247
|
+
this.setAttr('placeholder');
|
4248
|
+
|
4249
|
+
this.$tpl.datepicker(this.options.datepicker);
|
4250
|
+
|
4251
|
+
//need to disable original event handlers
|
4252
|
+
this.$input.off('focus keydown');
|
4253
|
+
|
4254
|
+
//update value of datepicker
|
4255
|
+
this.$input.keyup($.proxy(function(){
|
4256
|
+
this.$tpl.removeData('date');
|
4257
|
+
this.$tpl.datepicker('update');
|
4258
|
+
}, this));
|
4259
|
+
|
4260
|
+
},
|
4261
|
+
|
4262
|
+
value2input: function(value) {
|
4263
|
+
this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '');
|
4264
|
+
this.$tpl.datepicker('update');
|
4265
|
+
},
|
4266
|
+
|
4267
|
+
input2value: function() {
|
4268
|
+
return this.html2value(this.$input.val());
|
4269
|
+
},
|
4270
|
+
|
4271
|
+
activate: function() {
|
4272
|
+
$.fn.editabletypes.text.prototype.activate.call(this);
|
4273
|
+
},
|
4274
|
+
|
4275
|
+
autosubmit: function() {
|
4276
|
+
//reset autosubmit to empty
|
4277
|
+
}
|
4278
|
+
});
|
4279
|
+
|
4280
|
+
DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, {
|
4281
|
+
/**
|
4282
|
+
@property tpl
|
4283
|
+
**/
|
4284
|
+
tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',
|
4285
|
+
/**
|
4286
|
+
@property inputclass
|
4287
|
+
@default 'input-small'
|
4288
|
+
**/
|
4289
|
+
inputclass: 'input-small',
|
4290
|
+
|
4291
|
+
/* datepicker config */
|
4292
|
+
datepicker: {
|
4293
|
+
weekStart: 0,
|
4294
|
+
startView: 0,
|
4295
|
+
autoclose: true
|
4296
|
+
}
|
4297
|
+
});
|
4298
|
+
|
4299
|
+
$.fn.editabletypes.datefield = DateField;
|
4300
|
+
|
4301
|
+
}(window.jQuery));
|
4302
|
+
|
4303
|
+
/**
|
4304
|
+
Typeahead input (bootstrap only). Based on Twitter Bootstrap [typeahead](http://twitter.github.com/bootstrap/javascript.html#typeahead).
|
4305
|
+
Depending on `source` format typeahead operates in two modes:
|
4306
|
+
|
4307
|
+
* **strings**:
|
4308
|
+
When `source` defined as array of strings, e.g. `['text1', 'text2', 'text3' ...]`.
|
4309
|
+
User can submit one of these strings or any text entered in input (even if it is not matching source).
|
4310
|
+
|
4311
|
+
* **objects**:
|
4312
|
+
When `source` defined as array of objects, e.g. `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`.
|
4313
|
+
User can submit only values that are in source (otherwise `null` is submitted). This is more like *dropdown* behavior.
|
4314
|
+
|
4315
|
+
@class typeahead
|
4316
|
+
@extends list
|
4317
|
+
@since 1.4.1
|
4318
|
+
@final
|
4319
|
+
@example
|
4320
|
+
<a href="#" id="country" data-type="typeahead" data-pk="1" data-url="/post" data-original-title="Input country"></a>
|
4321
|
+
<script>
|
4322
|
+
$(function(){
|
4323
|
+
$('#country').editable({
|
4324
|
+
value: 'ru',
|
4325
|
+
source: [
|
4326
|
+
{value: 'gb', text: 'Great Britain'},
|
4327
|
+
{value: 'us', text: 'United States'},
|
4328
|
+
{value: 'ru', text: 'Russia'}
|
4329
|
+
]
|
4330
|
+
}
|
4331
|
+
});
|
4332
|
+
});
|
4333
|
+
</script>
|
4334
|
+
**/
|
4335
|
+
(function ($) {
|
4336
|
+
|
4337
|
+
var Constructor = function (options) {
|
4338
|
+
this.init('typeahead', options, Constructor.defaults);
|
4339
|
+
|
4340
|
+
//overriding objects in config (as by default jQuery extend() is not recursive)
|
4341
|
+
this.options.typeahead = $.extend({}, Constructor.defaults.typeahead, {
|
4342
|
+
//set default methods for typeahead to work with objects
|
4343
|
+
matcher: this.matcher,
|
4344
|
+
sorter: this.sorter,
|
4345
|
+
highlighter: this.highlighter,
|
4346
|
+
updater: this.updater
|
4347
|
+
}, options.typeahead);
|
4348
|
+
};
|
4349
|
+
|
4350
|
+
$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.list);
|
4351
|
+
|
4352
|
+
$.extend(Constructor.prototype, {
|
4353
|
+
renderList: function() {
|
4354
|
+
this.$input = this.$tpl.is('input') ? this.$tpl : this.$tpl.find('input[type="text"]');
|
4355
|
+
|
4356
|
+
//set source of typeahead
|
4357
|
+
this.options.typeahead.source = this.sourceData;
|
4358
|
+
|
4359
|
+
//apply typeahead
|
4360
|
+
this.$input.typeahead(this.options.typeahead);
|
4361
|
+
|
4362
|
+
//attach own render method
|
4363
|
+
this.$input.data('typeahead').render = $.proxy(this.typeaheadRender, this.$input.data('typeahead'));
|
4364
|
+
|
4365
|
+
this.renderClear();
|
4366
|
+
this.setClass();
|
4367
|
+
this.setAttr('placeholder');
|
4368
|
+
},
|
4369
|
+
|
4370
|
+
value2htmlFinal: function(value, element) {
|
4371
|
+
if(this.getIsObjects()) {
|
4372
|
+
var items = $.fn.editableutils.itemsByValue(value, this.sourceData);
|
4373
|
+
$(element).text(items.length ? items[0].text : '');
|
4374
|
+
} else {
|
4375
|
+
$(element).text(value);
|
4376
|
+
}
|
4377
|
+
},
|
4378
|
+
|
4379
|
+
html2value: function (html) {
|
4380
|
+
return html ? html : null;
|
4381
|
+
},
|
4382
|
+
|
4383
|
+
value2input: function(value) {
|
4384
|
+
if(this.getIsObjects()) {
|
4385
|
+
var items = $.fn.editableutils.itemsByValue(value, this.sourceData);
|
4386
|
+
this.$input.data('value', value).val(items.length ? items[0].text : '');
|
4387
|
+
} else {
|
4388
|
+
this.$input.val(value);
|
4389
|
+
}
|
4390
|
+
},
|
4391
|
+
|
4392
|
+
input2value: function() {
|
4393
|
+
if(this.getIsObjects()) {
|
4394
|
+
var value = this.$input.data('value'),
|
4395
|
+
items = $.fn.editableutils.itemsByValue(value, this.sourceData);
|
4396
|
+
|
4397
|
+
if(items.length && items[0].text.toLowerCase() === this.$input.val().toLowerCase()) {
|
4398
|
+
return value;
|
4399
|
+
} else {
|
4400
|
+
return null; //entered string not found in source
|
4401
|
+
}
|
4402
|
+
} else {
|
4403
|
+
return this.$input.val();
|
4404
|
+
}
|
4405
|
+
},
|
4406
|
+
|
4407
|
+
/*
|
4408
|
+
if in sourceData values <> texts, typeahead in "objects" mode:
|
4409
|
+
user must pick some value from list, otherwise `null` returned.
|
4410
|
+
if all values == texts put typeahead in "strings" mode:
|
4411
|
+
anything what entered is submited.
|
4412
|
+
*/
|
4413
|
+
getIsObjects: function() {
|
4414
|
+
if(this.isObjects === undefined) {
|
4415
|
+
this.isObjects = false;
|
4416
|
+
for(var i=0; i<this.sourceData.length; i++) {
|
4417
|
+
if(this.sourceData[i].value !== this.sourceData[i].text) {
|
4418
|
+
this.isObjects = true;
|
4419
|
+
break;
|
4420
|
+
}
|
4421
|
+
}
|
4422
|
+
}
|
4423
|
+
return this.isObjects;
|
4424
|
+
},
|
4425
|
+
|
4426
|
+
/*
|
4427
|
+
Methods borrowed from text input
|
4428
|
+
*/
|
4429
|
+
activate: $.fn.editabletypes.text.prototype.activate,
|
4430
|
+
renderClear: $.fn.editabletypes.text.prototype.renderClear,
|
4431
|
+
postrender: $.fn.editabletypes.text.prototype.postrender,
|
4432
|
+
toggleClear: $.fn.editabletypes.text.prototype.toggleClear,
|
4433
|
+
clear: function() {
|
4434
|
+
$.fn.editabletypes.text.prototype.clear.call(this);
|
4435
|
+
this.$input.data('value', '');
|
4436
|
+
},
|
4437
|
+
|
4438
|
+
|
4439
|
+
/*
|
4440
|
+
Typeahead option methods used as defaults
|
4441
|
+
*/
|
4442
|
+
/*jshint eqeqeq:false, curly: false, laxcomma: true*/
|
4443
|
+
matcher: function (item) {
|
4444
|
+
return $.fn.typeahead.Constructor.prototype.matcher.call(this, item.text);
|
4445
|
+
},
|
4446
|
+
sorter: function (items) {
|
4447
|
+
var beginswith = []
|
4448
|
+
, caseSensitive = []
|
4449
|
+
, caseInsensitive = []
|
4450
|
+
, item
|
4451
|
+
, text;
|
4452
|
+
|
4453
|
+
while (item = items.shift()) {
|
4454
|
+
text = item.text;
|
4455
|
+
if (!text.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item);
|
4456
|
+
else if (~text.indexOf(this.query)) caseSensitive.push(item);
|
4457
|
+
else caseInsensitive.push(item);
|
4458
|
+
}
|
4459
|
+
|
4460
|
+
return beginswith.concat(caseSensitive, caseInsensitive);
|
4461
|
+
},
|
4462
|
+
highlighter: function (item) {
|
4463
|
+
return $.fn.typeahead.Constructor.prototype.highlighter.call(this, item.text);
|
4464
|
+
},
|
4465
|
+
updater: function (item) {
|
4466
|
+
item = this.$menu.find('.active').data('item');
|
4467
|
+
this.$element.data('value', item.value);
|
4468
|
+
return item.text;
|
4469
|
+
},
|
4470
|
+
|
4471
|
+
|
4472
|
+
/*
|
4473
|
+
Overwrite typeahead's render method to store objects.
|
4474
|
+
There are a lot of disscussion in bootstrap repo on this point and still no result.
|
4475
|
+
See https://github.com/twitter/bootstrap/issues/5967
|
4476
|
+
|
4477
|
+
This function just store item in via jQuery data() method instead of attr('data-value')
|
4478
|
+
*/
|
4479
|
+
typeaheadRender: function (items) {
|
4480
|
+
var that = this;
|
4481
|
+
|
4482
|
+
items = $(items).map(function (i, item) {
|
4483
|
+
// i = $(that.options.item).attr('data-value', item)
|
4484
|
+
i = $(that.options.item).data('item', item);
|
4485
|
+
i.find('a').html(that.highlighter(item));
|
4486
|
+
return i[0];
|
4487
|
+
});
|
4488
|
+
|
4489
|
+
items.first().addClass('active');
|
4490
|
+
this.$menu.html(items);
|
4491
|
+
return this;
|
4492
|
+
}
|
4493
|
+
/*jshint eqeqeq: true, curly: true, laxcomma: false*/
|
4494
|
+
|
4495
|
+
});
|
4496
|
+
|
4497
|
+
Constructor.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
|
4498
|
+
/**
|
4499
|
+
@property tpl
|
4500
|
+
@default <input type="text">
|
4501
|
+
**/
|
4502
|
+
tpl:'<input type="text">',
|
4503
|
+
/**
|
4504
|
+
Configuration of typeahead. [Full list of options](http://twitter.github.com/bootstrap/javascript.html#typeahead).
|
4505
|
+
|
4506
|
+
@property typeahead
|
4507
|
+
@type object
|
4508
|
+
@default null
|
4509
|
+
**/
|
4510
|
+
typeahead: null,
|
4511
|
+
/**
|
4512
|
+
Whether to show `clear` button
|
4513
|
+
|
4514
|
+
@property clear
|
4515
|
+
@type boolean
|
4516
|
+
@default true
|
4517
|
+
**/
|
4518
|
+
clear: true
|
4519
|
+
});
|
4520
|
+
|
4521
|
+
$.fn.editabletypes.typeahead = Constructor;
|
4522
|
+
|
4523
|
+
}(window.jQuery));
|