UNPKG

17.4 kBJavaScriptView Raw
1/***
2 * Contains basic SlickGrid editors.
3 * @module Editors
4 * @namespace Slick
5 */
6
7(function ($) {
8 // register namespace
9 $.extend(true, window, {
10 "Slick": {
11 "Editors": {
12 "Text": TextEditor,
13 "Integer": IntegerEditor,
14 "Float": FloatEditor,
15 "Date": DateEditor,
16 "YesNoSelect": YesNoSelectEditor,
17 "Checkbox": CheckboxEditor,
18 "PercentComplete": PercentCompleteEditor,
19 "LongText": LongTextEditor
20 }
21 }
22 });
23
24 function TextEditor(args) {
25 var $input;
26 var defaultValue;
27 var scope = this;
28
29 this.init = function () {
30 var navOnLR = args.grid.getOptions().editorCellNavOnLRKeys;
31 $input = $("<INPUT type=text class='editor-text' />")
32 .appendTo(args.container)
33 .on("keydown.nav", navOnLR ? handleKeydownLRNav : handleKeydownLRNoNav)
34 .focus()
35 .select();
36 };
37
38 this.destroy = function () {
39 $input.remove();
40 };
41
42 this.focus = function () {
43 $input.focus();
44 };
45
46 this.getValue = function () {
47 return $input.val();
48 };
49
50 this.setValue = function (val) {
51 $input.val(val);
52 };
53
54 this.loadValue = function (item) {
55 defaultValue = item[args.column.field] || "";
56 $input.val(defaultValue);
57 $input[0].defaultValue = defaultValue;
58 $input.select();
59 };
60
61 this.serializeValue = function () {
62 return $input.val();
63 };
64
65 this.applyValue = function (item, state) {
66 item[args.column.field] = state;
67 };
68
69 this.isValueChanged = function () {
70 return (!($input.val() === "" && defaultValue == null)) && ($input.val() != defaultValue);
71 };
72
73 this.validate = function () {
74 if (args.column.validator) {
75 var validationResults = args.column.validator($input.val());
76 if (!validationResults.valid) {
77 return validationResults;
78 }
79 }
80
81 return {
82 valid: true,
83 msg: null
84 };
85 };
86
87 this.init();
88 }
89
90 function IntegerEditor(args) {
91 var $input;
92 var defaultValue;
93 var scope = this;
94
95 this.init = function () {
96 var navOnLR = args.grid.getOptions().editorCellNavOnLRKeys;
97 $input = $("<INPUT type=text class='editor-text' />")
98 .appendTo(args.container)
99 .on("keydown.nav", navOnLR ? handleKeydownLRNav : handleKeydownLRNoNav)
100 .focus()
101 .select();
102 };
103
104 this.destroy = function () {
105 $input.remove();
106 };
107
108 this.focus = function () {
109 $input.focus();
110 };
111
112 this.loadValue = function (item) {
113 defaultValue = item[args.column.field];
114 $input.val(defaultValue);
115 $input[0].defaultValue = defaultValue;
116 $input.select();
117 };
118
119 this.serializeValue = function () {
120 return parseInt($input.val(), 10) || 0;
121 };
122
123 this.applyValue = function (item, state) {
124 item[args.column.field] = state;
125 };
126
127 this.isValueChanged = function () {
128 return (!($input.val() === "" && defaultValue == null)) && ($input.val() != defaultValue);
129 };
130
131 this.validate = function () {
132 if (isNaN($input.val())) {
133 return {
134 valid: false,
135 msg: "Please enter a valid integer"
136 };
137 }
138
139 if (args.column.validator) {
140 var validationResults = args.column.validator($input.val());
141 if (!validationResults.valid) {
142 return validationResults;
143 }
144 }
145
146 return {
147 valid: true,
148 msg: null
149 };
150 };
151
152 this.init();
153 }
154
155 function FloatEditor(args) {
156 var $input;
157 var defaultValue;
158 var scope = this;
159
160 this.init = function () {
161 var navOnLR = args.grid.getOptions().editorCellNavOnLRKeys;
162 $input = $("<INPUT type=text class='editor-text' />")
163 .appendTo(args.container)
164 .on("keydown.nav", navOnLR ? handleKeydownLRNav : handleKeydownLRNoNav)
165 .focus()
166 .select();
167 };
168
169 this.destroy = function () {
170 $input.remove();
171 };
172
173 this.focus = function () {
174 $input.focus();
175 };
176
177 function getDecimalPlaces() {
178 // returns the number of fixed decimal places or null
179 var rtn = args.column.editorFixedDecimalPlaces;
180 if (typeof rtn == 'undefined') {
181 rtn = FloatEditor.DefaultDecimalPlaces;
182 }
183 return (!rtn && rtn!==0 ? null : rtn);
184 }
185
186 this.loadValue = function (item) {
187 defaultValue = item[args.column.field];
188
189 var decPlaces = getDecimalPlaces();
190 if (decPlaces !== null
191 && (defaultValue || defaultValue===0)
192 && defaultValue.toFixed) {
193 defaultValue = defaultValue.toFixed(decPlaces);
194 }
195
196 $input.val(defaultValue);
197 $input[0].defaultValue = defaultValue;
198 $input.select();
199 };
200
201 this.serializeValue = function () {
202 var rtn = parseFloat($input.val());
203 if (FloatEditor.AllowEmptyValue) {
204 if (!rtn && rtn !==0) { rtn = ''; }
205 } else {
206 rtn = rtn || 0;
207 }
208
209 var decPlaces = getDecimalPlaces();
210 if (decPlaces !== null
211 && (rtn || rtn===0)
212 && rtn.toFixed) {
213 rtn = parseFloat(rtn.toFixed(decPlaces));
214 }
215
216 return rtn;
217 };
218
219 this.applyValue = function (item, state) {
220 item[args.column.field] = state;
221 };
222
223 this.isValueChanged = function () {
224 return (!($input.val() === "" && defaultValue == null)) && ($input.val() != defaultValue);
225 };
226
227 this.validate = function () {
228 if (isNaN($input.val())) {
229 return {
230 valid: false,
231 msg: "Please enter a valid number"
232 };
233 }
234
235 if (args.column.validator) {
236 var validationResults = args.column.validator($input.val());
237 if (!validationResults.valid) {
238 return validationResults;
239 }
240 }
241
242 return {
243 valid: true,
244 msg: null
245 };
246 };
247
248 this.init();
249 }
250
251 FloatEditor.DefaultDecimalPlaces = null;
252 FloatEditor.AllowEmptyValue = false;
253
254 function DateEditor(args) {
255 var $input;
256 var defaultValue;
257 var scope = this;
258 var calendarOpen = false;
259
260 this.init = function () {
261 $input = $("<INPUT type=text class='editor-text' />");
262 $input.appendTo(args.container);
263 $input.focus().select();
264 $input.datepicker({
265 showOn: "button",
266 buttonImageOnly: true,
267 beforeShow: function () {
268 calendarOpen = true;
269 },
270 onClose: function () {
271 calendarOpen = false;
272 }
273 });
274 $input.width($input.width() - 18);
275 };
276
277 this.destroy = function () {
278 $.datepicker.dpDiv.stop(true, true);
279 $input.datepicker("hide");
280 $input.datepicker("destroy");
281 $input.remove();
282 };
283
284 this.show = function () {
285 if (calendarOpen) {
286 $.datepicker.dpDiv.stop(true, true).show();
287 }
288 };
289
290 this.hide = function () {
291 if (calendarOpen) {
292 $.datepicker.dpDiv.stop(true, true).hide();
293 }
294 };
295
296 this.position = function (position) {
297 if (!calendarOpen) {
298 return;
299 }
300 $.datepicker.dpDiv
301 .css("top", position.top + 30)
302 .css("left", position.left);
303 };
304
305 this.focus = function () {
306 $input.focus();
307 };
308
309 this.loadValue = function (item) {
310 defaultValue = item[args.column.field];
311 $input.val(defaultValue);
312 $input[0].defaultValue = defaultValue;
313 $input.select();
314 };
315
316 this.serializeValue = function () {
317 return $input.val();
318 };
319
320 this.applyValue = function (item, state) {
321 item[args.column.field] = state;
322 };
323
324 this.isValueChanged = function () {
325 return (!($input.val() === "" && defaultValue == null)) && ($input.val() != defaultValue);
326 };
327
328 this.validate = function () {
329 if (args.column.validator) {
330 var validationResults = args.column.validator($input.val());
331 if (!validationResults.valid) {
332 return validationResults;
333 }
334 }
335
336 return {
337 valid: true,
338 msg: null
339 };
340 };
341
342 this.init();
343 }
344
345 function YesNoSelectEditor(args) {
346 var $select;
347 var defaultValue;
348 var scope = this;
349
350 this.init = function () {
351 $select = $("<SELECT tabIndex='0' class='editor-yesno'><OPTION value='yes'>Yes</OPTION><OPTION value='no'>No</OPTION></SELECT>");
352 $select.appendTo(args.container);
353 $select.focus();
354 };
355
356 this.destroy = function () {
357 $select.remove();
358 };
359
360 this.focus = function () {
361 $select.focus();
362 };
363
364 this.loadValue = function (item) {
365 $select.val((defaultValue = item[args.column.field]) ? "yes" : "no");
366 $select.select();
367 };
368
369 this.serializeValue = function () {
370 return ($select.val() == "yes");
371 };
372
373 this.applyValue = function (item, state) {
374 item[args.column.field] = state;
375 };
376
377 this.isValueChanged = function () {
378 return ($select.val() != defaultValue);
379 };
380
381 this.validate = function () {
382 return {
383 valid: true,
384 msg: null
385 };
386 };
387
388 this.init();
389 }
390
391 function CheckboxEditor(args) {
392 var $select;
393 var defaultValue;
394 var scope = this;
395
396 this.init = function () {
397 $select = $("<INPUT type=checkbox value='true' class='editor-checkbox' hideFocus>");
398 $select.appendTo(args.container);
399 $select.focus();
400 };
401
402 this.destroy = function () {
403 $select.remove();
404 };
405
406 this.focus = function () {
407 $select.focus();
408 };
409
410 this.loadValue = function (item) {
411 defaultValue = !!item[args.column.field];
412 if (defaultValue) {
413 $select.prop('checked', true);
414 } else {
415 $select.prop('checked', false);
416 }
417 };
418
419 this.preClick = function () {
420 $select.prop('checked', !$select.prop('checked'));
421 };
422
423 this.serializeValue = function () {
424 return $select.prop('checked');
425 };
426
427 this.applyValue = function (item, state) {
428 item[args.column.field] = state;
429 };
430
431 this.isValueChanged = function () {
432 return (this.serializeValue() !== defaultValue);
433 };
434
435 this.validate = function () {
436 return {
437 valid: true,
438 msg: null
439 };
440 };
441
442 this.init();
443 }
444
445 function PercentCompleteEditor(args) {
446 var $input, $picker;
447 var defaultValue;
448 var scope = this;
449
450 this.init = function () {
451 $input = $("<INPUT type=text class='editor-percentcomplete' />");
452 $input.width($(args.container).innerWidth() - 25);
453 $input.appendTo(args.container);
454
455 $picker = $("<div class='editor-percentcomplete-picker' />").appendTo(args.container);
456 $picker.append("<div class='editor-percentcomplete-helper'><div class='editor-percentcomplete-wrapper'><div class='editor-percentcomplete-slider' /><div class='editor-percentcomplete-buttons' /></div></div>");
457
458 $picker.find(".editor-percentcomplete-buttons").append("<button val=0>Not started</button><br/><button val=50>In Progress</button><br/><button val=100>Complete</button>");
459
460 $input.focus().select();
461
462 $picker.find(".editor-percentcomplete-slider").slider({
463 orientation: "vertical",
464 range: "min",
465 value: defaultValue,
466 slide: function (event, ui) {
467 $input.val(ui.value);
468 }
469 });
470
471 $picker.find(".editor-percentcomplete-buttons button").on("click", function (e) {
472 $input.val($(this).attr("val"));
473 $picker.find(".editor-percentcomplete-slider").slider("value", $(this).attr("val"));
474 });
475 };
476
477 this.destroy = function () {
478 $input.remove();
479 $picker.remove();
480 };
481
482 this.focus = function () {
483 $input.focus();
484 };
485
486 this.loadValue = function (item) {
487 $input.val(defaultValue = item[args.column.field]);
488 $input.select();
489 };
490
491 this.serializeValue = function () {
492 return parseInt($input.val(), 10) || 0;
493 };
494
495 this.applyValue = function (item, state) {
496 item[args.column.field] = state;
497 };
498
499 this.isValueChanged = function () {
500 return (!($input.val() === "" && defaultValue == null)) && ((parseInt($input.val(), 10) || 0) != defaultValue);
501 };
502
503 this.validate = function () {
504 if (isNaN(parseInt($input.val(), 10))) {
505 return {
506 valid: false,
507 msg: "Please enter a valid positive number"
508 };
509 }
510
511 return {
512 valid: true,
513 msg: null
514 };
515 };
516
517 this.init();
518 }
519
520 /*
521 * An example of a "detached" editor.
522 * The UI is added onto document BODY and .position(), .show() and .hide() are implemented.
523 * KeyDown events are also handled to provide handling for Tab, Shift-Tab, Esc and Ctrl-Enter.
524 */
525 function LongTextEditor(args) {
526 var $input, $wrapper;
527 var defaultValue;
528 var scope = this;
529
530 this.init = function () {
531 var $container = $("body");
532 var navOnLR = args.grid.getOptions().editorCellNavOnLRKeys;
533
534 $wrapper = $("<DIV style='z-index:10000;position:absolute;background:white;padding:5px;border:3px solid gray; -moz-border-radius:10px; border-radius:10px;'/>")
535 .appendTo($container);
536
537 $input = $("<TEXTAREA hidefocus rows=5 style='background:white;width:250px;height:80px;border:0;outline:0'>")
538 .appendTo($wrapper);
539
540 $("<DIV style='text-align:right'><BUTTON>Save</BUTTON><BUTTON>Cancel</BUTTON></DIV>")
541 .appendTo($wrapper);
542
543 $wrapper.find("button:first").on("click", this.save);
544 $wrapper.find("button:last").on("click", this.cancel);
545 $input.on("keydown", this.handleKeyDown);
546
547 scope.position(args.position);
548 $input.focus().select();
549 };
550
551 this.handleKeyDown = function (e) {
552 if (e.which == $.ui.keyCode.ENTER && e.ctrlKey) {
553 scope.save();
554 } else if (e.which == $.ui.keyCode.ESCAPE) {
555 e.preventDefault();
556 scope.cancel();
557 } else if (e.which == $.ui.keyCode.TAB && e.shiftKey) {
558 e.preventDefault();
559 args.grid.navigatePrev();
560 } else if (e.which == $.ui.keyCode.TAB) {
561 e.preventDefault();
562 args.grid.navigateNext();
563 } else if (e.which == $.ui.keyCode.LEFT || e.which == $.ui.keyCode.RIGHT) {
564 if (args.grid.getOptions().editorCellNavOnLRKeys) {
565 var cursorPosition = this.selectionStart;
566 var textLength = this.value.length;
567 if (e.keyCode === $.ui.keyCode.LEFT && cursorPosition === 0) {
568 args.grid.navigatePrev();
569 }
570 if (e.keyCode === $.ui.keyCode.RIGHT && cursorPosition >= textLength-1) {
571 args.grid.navigateNext();
572 }
573 }
574 }
575 };
576
577 this.save = function () {
578 args.commitChanges();
579 };
580
581 this.cancel = function () {
582 $input.val(defaultValue);
583 args.cancelChanges();
584 };
585
586 this.hide = function () {
587 $wrapper.hide();
588 };
589
590 this.show = function () {
591 $wrapper.show();
592 };
593
594 this.position = function (position) {
595 $wrapper
596 .css("top", position.top - 5)
597 .css("left", position.left - 5);
598 };
599
600 this.destroy = function () {
601 $wrapper.remove();
602 };
603
604 this.focus = function () {
605 $input.focus();
606 };
607
608 this.loadValue = function (item) {
609 $input.val(defaultValue = item[args.column.field]);
610 $input.select();
611 };
612
613 this.serializeValue = function () {
614 return $input.val();
615 };
616
617 this.applyValue = function (item, state) {
618 item[args.column.field] = state;
619 };
620
621 this.isValueChanged = function () {
622 return (!($input.val() === "" && defaultValue == null)) && ($input.val() != defaultValue);
623 };
624
625 this.validate = function () {
626 if (args.column.validator) {
627 var validationResults = args.column.validator($input.val());
628 if (!validationResults.valid) {
629 return validationResults;
630 }
631 }
632
633 return {
634 valid: true,
635 msg: null
636 };
637 };
638
639 this.init();
640 }
641
642 /*
643 * Depending on the value of Grid option 'editorCellNavOnLRKeys', us
644 * Navigate to the cell on the left if the cursor is at the beginning of the input string
645 * and to the right cell if it's at the end. Otherwise, move the cursor within the text
646 */
647 function handleKeydownLRNav(e) {
648 var cursorPosition = this.selectionStart;
649 var textLength = this.value.length;
650 if ((e.keyCode === $.ui.keyCode.LEFT && cursorPosition > 0) ||
651 e.keyCode === $.ui.keyCode.RIGHT && cursorPosition < textLength-1) {
652 e.stopImmediatePropagation();
653 }
654 }
655
656 function handleKeydownLRNoNav(e) {
657 if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {
658 e.stopImmediatePropagation();
659 }
660 }
661
662})(jQuery);