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