Revision 8ae5ec0d
Von Sven Schöling vor fast 8 Jahren hinzugefügt
js/kivi.Part.js | ||
---|---|---|
279 | 279 |
}; |
280 | 280 |
|
281 | 281 |
ns.Picker = function($real, options) { |
282 |
// short circuit in case someone double inits us |
|
283 |
if ($real.data("part_picker")) |
|
284 |
return $real.data("part_picker"); |
|
285 |
|
|
286 |
var CLASSES = { |
|
287 |
PICKED: 'partpicker-picked', |
|
288 |
UNDEFINED: 'partpicker-undefined', |
|
289 |
FAT_SET_ITEM: 'partpicker_fat_set_item', |
|
290 |
}; |
|
291 |
var o = $.extend({ |
|
282 |
var self = this; |
|
283 |
this.o = $.extend({ |
|
292 | 284 |
limit: 20, |
293 | 285 |
delay: 50, |
294 |
fat_set_item: $real.hasClass(CLASSES.FAT_SET_ITEM), |
|
286 |
fat_set_item: $real.hasClass(this.CLASSES.FAT_SET_ITEM),
|
|
295 | 287 |
action: { |
296 | 288 |
on_enter_match_none: function(){ }, |
297 | 289 |
on_enter_match_one: function(){ $('#update_button').click(); }, |
298 |
on_enter_match_many: function(){ open_dialog(); } |
|
290 |
on_enter_match_many: function(){ self.open_dialog(); }
|
|
299 | 291 |
} |
300 | 292 |
}, options); |
301 |
var STATES = { |
|
302 |
PICKED: CLASSES.PICKED, |
|
303 |
UNDEFINED: CLASSES.UNDEFINED |
|
304 |
} |
|
305 |
var real_id = $real.attr('id'); |
|
306 |
var $dummy = $('#' + real_id + '_name'); |
|
307 |
var $part_type = $('#' + real_id + '_part_type'); |
|
308 |
var $classification_id = $('#' + real_id + '_classification_id'); |
|
309 |
var $unit = $('#' + real_id + '_unit'); |
|
310 |
var $convertible_unit = $('#' + real_id + '_convertible_unit'); |
|
311 |
var autocomplete_open = false; |
|
312 |
var state = STATES.PICKED; |
|
313 |
var last_real = $real.val(); |
|
314 |
var last_dummy = $dummy.val(); |
|
315 |
var timer; |
|
316 |
|
|
317 |
function ajax_data(term) { |
|
293 |
this.$real = $real; |
|
294 |
this.real_id = $real.attr('id'); |
|
295 |
this.last_real = $real.val(); |
|
296 |
this.$dummy = $('#' + this.real_id + '_name'); |
|
297 |
this.$part_type = $('#' + this.real_id + '_part_type'); |
|
298 |
this.$classification_id = $('#' + this.real_id + '_classification_id'); |
|
299 |
this.$unit = $('#' + this.real_id + '_unit'); |
|
300 |
this.$convertible_unit = $('#' + this.real_id + '_convertible_unit'); |
|
301 |
this.autocomplete_open = false; |
|
302 |
this.state = this.STATES.PICKED; |
|
303 |
this.last_dummy = this.$dummy.val(); |
|
304 |
this.timer = undefined; |
|
305 |
|
|
306 |
this.init(); |
|
307 |
}; |
|
308 |
|
|
309 |
ns.Picker.prototype = { |
|
310 |
CLASSES: { |
|
311 |
PICKED: 'partpicker-picked', |
|
312 |
UNDEFINED: 'partpicker-undefined', |
|
313 |
FAT_SET_ITEM: 'partpicker_fat_set_item', |
|
314 |
}, |
|
315 |
ajax_data: function(term) { |
|
318 | 316 |
var data = { |
319 | 317 |
'filter.all:substr:multi::ilike': term, |
320 | 318 |
'filter.obsolete': 0, |
321 |
'filter.unit_obj.convertible_to': $convertible_unit && $convertible_unit.val() ? $convertible_unit.val() : '',
|
|
322 |
current: $real.val(), |
|
319 |
'filter.unit_obj.convertible_to': this.$convertible_unit && this.$convertible_unit.val() ? this.$convertible_unit.val() : '',
|
|
320 |
current: this.$real.val(),
|
|
323 | 321 |
}; |
324 | 322 |
|
325 |
if ($part_type && $part_type.val())
|
|
326 |
data['filter.part_type'] = $part_type.val().split(','); |
|
323 |
if (this.$part_type && this.$part_type.val())
|
|
324 |
data['filter.part_type'] = this.$part_type.val().split(',');
|
|
327 | 325 |
|
328 |
if ($classification_id && $classification_id.val())
|
|
329 |
data['filter.classification_id'] = $classification_id.val().split(','); |
|
326 |
if (this.$classification_id && this.$classification_id.val())
|
|
327 |
data['filter.classification_id'] = this.$classification_id.val().split(',');
|
|
330 | 328 |
|
331 |
if ($unit && $unit.val())
|
|
332 |
data['filter.unit'] = $unit.val().split(','); |
|
329 |
if (this.$unit && this.$unit.val())
|
|
330 |
data['filter.unit'] = this.$unit.val().split(',');
|
|
333 | 331 |
|
334 | 332 |
return data; |
335 |
} |
|
336 |
|
|
337 |
function set_item (item) {
|
|
333 |
},
|
|
334 |
set_item: function(item) { |
|
335 |
var self = this;
|
|
338 | 336 |
if (item.id) { |
339 |
$real.val(item.id); |
|
337 |
this.$real.val(item.id);
|
|
340 | 338 |
// autocomplete ui has name, use the value for ajax items, which contains displayable_name |
341 |
$dummy.val(item.name ? item.name : item.value); |
|
339 |
this.$dummy.val(item.name ? item.name : item.value);
|
|
342 | 340 |
} else { |
343 |
$real.val(''); |
|
344 |
$dummy.val(''); |
|
341 |
this.$real.val('');
|
|
342 |
this.$dummy.val('');
|
|
345 | 343 |
} |
346 |
state = STATES.PICKED;
|
|
347 |
last_real = $real.val();
|
|
348 |
last_dummy = $dummy.val();
|
|
349 |
$real.trigger('change'); |
|
344 |
this.state = this.STATES.PICKED;
|
|
345 |
this.last_real = this.$real.val();
|
|
346 |
this.last_dummy = this.$dummy.val();
|
|
347 |
this.$real.trigger('change');
|
|
350 | 348 |
|
351 |
if (o.fat_set_item && item.id) { |
|
349 |
if (this.o.fat_set_item && item.id) {
|
|
352 | 350 |
$.ajax({ |
353 | 351 |
url: 'controller.pl?action=Part/show.json', |
354 | 352 |
data: { 'part.id': item.id }, |
355 | 353 |
success: function(rsp) { |
356 |
$real.trigger('set_item:PartPicker', rsp); |
|
354 |
self.$real.trigger('set_item:PartPicker', rsp);
|
|
357 | 355 |
}, |
358 | 356 |
}); |
359 | 357 |
} else { |
360 |
$real.trigger('set_item:PartPicker', item); |
|
358 |
this.$real.trigger('set_item:PartPicker', item);
|
|
361 | 359 |
} |
362 |
annotate_state(); |
|
363 |
} |
|
364 |
|
|
365 |
function make_defined_state () { |
|
366 |
if (state == STATES.PICKED) { |
|
367 |
annotate_state(); |
|
360 |
this.annotate_state(); |
|
361 |
}, |
|
362 |
make_defined_state: function() { |
|
363 |
if (this.state == this.STATES.PICKED) { |
|
364 |
this.annotate_state(); |
|
368 | 365 |
return true |
369 |
} else if (state == STATES.UNDEFINED && $dummy.val() === '')
|
|
370 |
set_item({}) |
|
366 |
} else if (this.state == this.STATES.UNDEFINED && this.$dummy.val() === '')
|
|
367 |
this.set_item({})
|
|
371 | 368 |
else { |
372 |
set_item({ id: last_real, name: last_dummy })
|
|
369 |
this.set_item({ id: this.last_real, name: this.last_dummy })
|
|
373 | 370 |
} |
374 |
annotate_state(); |
|
375 |
} |
|
376 |
|
|
377 |
function annotate_state () { |
|
378 |
if (state == STATES.PICKED) |
|
379 |
$dummy.removeClass(STATES.UNDEFINED).addClass(STATES.PICKED); |
|
380 |
else if (state == STATES.UNDEFINED && $dummy.val() === '') |
|
381 |
$dummy.removeClass(STATES.UNDEFINED).addClass(STATES.PICKED); |
|
371 |
this.annotate_state(); |
|
372 |
}, |
|
373 |
annotate_state: function() { |
|
374 |
if (this.state == this.STATES.PICKED) |
|
375 |
this.$dummy.removeClass(this.STATES.UNDEFINED).addClass(this.STATES.PICKED); |
|
376 |
else if (this.state == this.STATES.UNDEFINED && this.$dummy.val() === '') |
|
377 |
this.$dummy.removeClass(this.STATES.UNDEFINED).addClass(this.STATES.PICKED); |
|
382 | 378 |
else { |
383 |
$dummy.addClass(STATES.UNDEFINED).removeClass(STATES.PICKED);
|
|
379 |
this.$dummy.addClass(this.STATES.UNDEFINED).removeClass(this.STATES.PICKED);
|
|
384 | 380 |
} |
385 |
} |
|
386 |
|
|
387 |
function handle_changed_text(callbacks) {
|
|
381 |
},
|
|
382 |
handle_changed_text: function(callbacks) { |
|
383 |
var self = this;
|
|
388 | 384 |
$.ajax({ |
389 | 385 |
url: 'controller.pl?action=Part/ajax_autocomplete', |
390 | 386 |
dataType: "json", |
391 |
data: $.extend( ajax_data($dummy.val()), { prefer_exact: 1 } ),
|
|
387 |
data: $.extend( self.ajax_data(self.$dummy.val()), { prefer_exact: 1 } ),
|
|
392 | 388 |
success: function (data) { |
393 | 389 |
if (data.length == 1) { |
394 |
set_item(data[0]); |
|
390 |
self.set_item(data[0]);
|
|
395 | 391 |
if (callbacks && callbacks.match_one) callbacks.match_one(data[0]); |
396 | 392 |
} else if (data.length > 1) { |
397 |
state = STATES.UNDEFINED;
|
|
393 |
self.state = self.STATES.UNDEFINED;
|
|
398 | 394 |
if (callbacks && callbacks.match_many) callbacks.match_many(data); |
399 | 395 |
} else { |
400 |
state = STATES.UNDEFINED;
|
|
396 |
self.state = self.STATES.UNDEFINED;
|
|
401 | 397 |
if (callbacks && callbacks.match_none) callbacks.match_none(); |
402 | 398 |
} |
403 |
annotate_state(); |
|
399 |
self.annotate_state();
|
|
404 | 400 |
} |
405 | 401 |
}); |
406 |
} |
|
407 |
|
|
408 |
function open_dialog() { |
|
402 |
}, |
|
403 |
open_dialog: function() { |
|
409 | 404 |
// TODO: take the actual object here |
410 |
var dialog = new ns.PickerPopup({ |
|
411 |
ajax_data: ajax_data, |
|
412 |
real_id: real_id, |
|
413 |
dummy: $dummy, |
|
414 |
real: $real, |
|
415 |
set_item: set_item |
|
416 |
}); |
|
417 |
} |
|
418 |
|
|
419 |
$dummy.autocomplete({ |
|
420 |
source: function(req, rsp) { |
|
421 |
$.ajax($.extend(o, { |
|
422 |
url: 'controller.pl?action=Part/ajax_autocomplete', |
|
423 |
dataType: "json", |
|
424 |
data: ajax_data(req.term), |
|
425 |
success: function (data){ rsp(data) } |
|
426 |
})); |
|
427 |
}, |
|
428 |
select: function(event, ui) { |
|
429 |
set_item(ui.item); |
|
430 |
}, |
|
431 |
search: function(event, ui) { |
|
432 |
if ((event.which == KEY.SHIFT) || (event.which == KEY.CTRL) || (event.which == KEY.ALT)) |
|
433 |
event.preventDefault(); |
|
434 |
}, |
|
435 |
open: function() { |
|
436 |
autocomplete_open = true; |
|
437 |
}, |
|
438 |
close: function() { |
|
439 |
autocomplete_open = false; |
|
440 |
} |
|
441 |
}); |
|
442 |
/* In case users are impatient and want to skip ahead: |
|
443 |
* Capture <enter> key events and check if it's a unique hit. |
|
444 |
* If it is, go ahead and assume it was selected. If it wasn't don't do |
|
445 |
* anything so that autocompletion kicks in. For <tab> don't prevent |
|
446 |
* propagation. It would be nice to catch it, but javascript is too stupid |
|
447 |
* to fire a tab event later on, so we'd have to reimplement the "find |
|
448 |
* next active element in tabindex order and focus it". |
|
449 |
*/ |
|
450 |
/* note: |
|
451 |
* event.which does not contain tab events in keypressed in firefox but will report 0 |
|
452 |
* chrome does not fire keypressed at all on tab or escape |
|
453 |
*/ |
|
454 |
$dummy.keydown(function(event){ |
|
455 |
if (event.which == KEY.ENTER || event.which == KEY.TAB) { |
|
456 |
// if string is empty assume they want to delete |
|
457 |
if ($dummy.val() === '') { |
|
458 |
set_item({}); |
|
459 |
return true; |
|
460 |
} else if (state == STATES.PICKED) { |
|
461 |
return true; |
|
462 |
} |
|
463 |
if (event.which == KEY.TAB) { |
|
464 |
event.preventDefault(); |
|
465 |
handle_changed_text(); |
|
405 |
var dialog = new ns.PickerPopup(this); |
|
406 |
}, |
|
407 |
init: function() { |
|
408 |
var self = this; |
|
409 |
this.$dummy.autocomplete({ |
|
410 |
source: function(req, rsp) { |
|
411 |
$.ajax($.extend(self.o, { |
|
412 |
url: 'controller.pl?action=Part/ajax_autocomplete', |
|
413 |
dataType: "json", |
|
414 |
data: self.ajax_data(req.term), |
|
415 |
success: function (data){ rsp(data) } |
|
416 |
})); |
|
417 |
}, |
|
418 |
select: function(event, ui) { |
|
419 |
self.set_item(ui.item); |
|
420 |
}, |
|
421 |
search: function(event, ui) { |
|
422 |
if ((event.which == KEY.SHIFT) || (event.which == KEY.CTRL) || (event.which == KEY.ALT)) |
|
423 |
event.preventDefault(); |
|
424 |
}, |
|
425 |
open: function() { |
|
426 |
self.autocomplete_open = true; |
|
427 |
}, |
|
428 |
close: function() { |
|
429 |
self.autocomplete_open = false; |
|
466 | 430 |
} |
467 |
if (event.which == KEY.ENTER) { |
|
468 |
handle_changed_text({ |
|
469 |
match_one: o.action.on_enter_match_one, |
|
470 |
match_many: o.action.on_enter_match_many |
|
471 |
}); |
|
472 |
return false; |
|
431 |
}); |
|
432 |
/* In case users are impatient and want to skip ahead: |
|
433 |
* Capture <enter> key events and check if it's a unique hit. |
|
434 |
* If it is, go ahead and assume it was selected. If it wasn't don't do |
|
435 |
* anything so that autocompletion kicks in. For <tab> don't prevent |
|
436 |
* propagation. It would be nice to catch it, but javascript is too stupid |
|
437 |
* to fire a tab event later on, so we'd have to reimplement the "find |
|
438 |
* next active element in tabindex order and focus it". |
|
439 |
*/ |
|
440 |
/* note: |
|
441 |
* event.which does not contain tab events in keypressed in firefox but will report 0 |
|
442 |
* chrome does not fire keypressed at all on tab or escape |
|
443 |
*/ |
|
444 |
this.$dummy.keydown(function(event){ |
|
445 |
if (event.which == KEY.ENTER || event.which == KEY.TAB) { |
|
446 |
// if string is empty assume they want to delete |
|
447 |
if (self.$dummy.val() === '') { |
|
448 |
self.set_item({}); |
|
449 |
return true; |
|
450 |
} else if (self.state == self.STATES.PICKED) { |
|
451 |
return true; |
|
452 |
} |
|
453 |
if (event.which == KEY.TAB) { |
|
454 |
event.preventDefault(); |
|
455 |
self.handle_changed_text(); |
|
456 |
} |
|
457 |
if (event.which == KEY.ENTER) { |
|
458 |
self.handle_changed_text({ |
|
459 |
match_one: self.o.action.on_enter_match_one, |
|
460 |
match_many: self.o.action.on_enter_match_many |
|
461 |
}); |
|
462 |
return false; |
|
463 |
} |
|
464 |
} else if (event.which == KEY.DOWN && !self.autocomplete_open) { |
|
465 |
var old_options = self.$dummy.autocomplete('option'); |
|
466 |
self.$dummy.autocomplete('option', 'minLength', 0); |
|
467 |
self.$dummy.autocomplete('search', self.$dummy.val()); |
|
468 |
self.$dummy.autocomplete('option', 'minLength', old_options.minLength); |
|
469 |
} else if ((event.which != KEY.SHIFT) && (event.which != KEY.CTRL) && (event.which != KEY.ALT)) { |
|
470 |
self.state = self.STATES.UNDEFINED; |
|
473 | 471 |
} |
474 |
} else if (event.which == KEY.DOWN && !autocomplete_open) { |
|
475 |
var old_options = $dummy.autocomplete('option'); |
|
476 |
$dummy.autocomplete('option', 'minLength', 0); |
|
477 |
$dummy.autocomplete('search', $dummy.val()); |
|
478 |
$dummy.autocomplete('option', 'minLength', old_options.minLength); |
|
479 |
} else if ((event.which != KEY.SHIFT) && (event.which != KEY.CTRL) && (event.which != KEY.ALT)) { |
|
480 |
state = STATES.UNDEFINED; |
|
481 |
} |
|
482 |
}); |
|
472 |
}); |
|
483 | 473 |
|
484 |
$dummy.on('paste', function(){ |
|
485 |
setTimeout(function() { |
|
486 |
handle_changed_text(); |
|
487 |
}, 1); |
|
488 |
}); |
|
474 |
this.$dummy.on('paste', function(){
|
|
475 |
setTimeout(function() {
|
|
476 |
self.handle_changed_text();
|
|
477 |
}, 1);
|
|
478 |
});
|
|
489 | 479 |
|
490 |
$dummy.blur(function(){ |
|
491 |
window.clearTimeout(timer);
|
|
492 |
timer = window.setTimeout(annotate_state, 100);
|
|
493 |
}); |
|
480 |
this.$dummy.blur(function(){
|
|
481 |
window.clearTimeout(self.timer);
|
|
482 |
self.timer = window.setTimeout(function() { self.annotate_state() }, 100);
|
|
483 |
});
|
|
494 | 484 |
|
495 |
// now add a picker div after the original input |
|
496 |
var popup_button = $('<span>').addClass('ppp_popup_button'); |
|
497 |
$dummy.after(popup_button); |
|
498 |
popup_button.click(open_dialog); |
|
499 |
|
|
500 |
var pp = { |
|
501 |
real: function() { return $real }, |
|
502 |
dummy: function() { return $dummy }, |
|
503 |
part_type: function() { return $part_type }, |
|
504 |
classification_id: function() { return $classification_id }, |
|
505 |
unit: function() { return $unit }, |
|
506 |
convertible_unit: function() { return $convertible_unit }, |
|
507 |
set_item: set_item, |
|
508 |
reset: make_defined_state, |
|
509 |
is_defined_state: function() { return state == STATES.PICKED }, |
|
485 |
var popup_button = $('<span>').addClass('ppp_popup_button'); |
|
486 |
this.$dummy.after(popup_button); |
|
487 |
popup_button.click(function() { self.open_dialog() }); |
|
510 | 488 |
} |
511 |
$real.data('part_picker', pp); |
|
512 |
return pp; |
|
489 |
}; |
|
490 |
ns.Picker.prototype.STATES = { |
|
491 |
PICKED: ns.Picker.prototype.CLASSES.PICKED, |
|
492 |
UNDEFINED: ns.Picker.prototype.CLASSES.UNDEFINED |
|
513 | 493 |
}; |
514 | 494 |
|
515 | 495 |
ns.PickerPopup = function(pp) { |
... | ... | |
525 | 505 |
url: 'controller.pl?action=Part/part_picker_search', |
526 | 506 |
data: $.extend({ |
527 | 507 |
real_id: self.pp.real_id, |
528 |
}, self.pp.ajax_data(this.pp.dummy.val())), |
|
508 |
}, self.pp.ajax_data(this.pp.$dummy.val())),
|
|
529 | 509 |
id: 'part_selection', |
530 | 510 |
dialog: { |
531 | 511 |
title: kivi.t8('Part picker'), |
... | ... | |
548 | 528 |
$.ajax({ |
549 | 529 |
url: 'controller.pl?action=Part/part_picker_result', |
550 | 530 |
data: $.extend({ |
551 |
'real_id': self.pp.real.val(), |
|
531 |
'real_id': self.pp.$real.val(),
|
|
552 | 532 |
no_paginate: $('#no_paginate').prop('checked') ? 1 : 0, |
553 | 533 |
}, self.pp.ajax_data(function(){ |
554 | 534 |
var val = $('#part_picker_filter').val(); |
... | ... | |
573 | 553 |
description: $(this).children('input.part_picker_description').val(), |
574 | 554 |
}); |
575 | 555 |
self.close_popup(); |
576 |
self.pp.dummy.focus(); |
|
556 |
self.pp.$dummy.focus();
|
|
577 | 557 |
return true; |
578 | 558 |
}); |
579 | 559 |
}); |
580 | 560 |
$('#part_selection').keydown(function(e){ |
581 | 561 |
if (e.which == KEY.ESCAPE) { |
582 | 562 |
self.close_popup(); |
583 |
self.pp.dummy.focus(); |
|
563 |
self.pp.$dummy.focus();
|
|
584 | 564 |
} |
585 | 565 |
}); |
586 | 566 |
}, |
... | ... | |
610 | 590 |
|
611 | 591 |
ns.reinit_widgets = function() { |
612 | 592 |
kivi.run_once_for('input.part_autocomplete', 'part_picker', function(elt) { |
613 |
kivi.Part.Picker($(elt)); |
|
593 |
if (!$(elt).data('part_picker')) |
|
594 |
$(elt).data('part_picker', new kivi.Part.Picker($(elt))); |
|
614 | 595 |
}); |
615 | 596 |
} |
616 | 597 |
|
Auch abrufbar als: Unified diff
kivi.Part.js: PartPicker von closure auf prototype style umgeschrieben
War notwendig, damit das Objekt sich selbst an andere Objekte weitrgeben
kann. Vorher musste es dafür eine anonyme Closure über die eigenen
Funktionen machen, die extrem schwer verständlich war.
Nachteil dafür jetzt, man kann keine Methoden mehr ohne closure als
callback verwenden und alles ist voll mit this.