From 7e7aae8d20f3faf45073e5b888989a0ee5fce146 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Sven=20Sch=C3=B6ling?= Date: Fri, 22 Sep 2017 16:49:56 +0200 Subject: [PATCH] CustomerVendor Picker: auf prototype Picker umgestellt analog zu Part --- SL/Controller/CustomerVendor.pm | 2 - SL/Presenter/CustomerVendor.pm | 9 +- js/autocomplete_customer.js | 208 ---------------- js/kivi.CustomerVendor.js | 234 +++++++++++++++++- js/kivi.js | 6 +- .../customer_vendor/tabs/contacts.html | 2 +- .../webpages/customer_vendor/tabs/shipto.html | 2 +- .../webpages/customer_vendor/test_page.html | 5 +- 8 files changed, 237 insertions(+), 231 deletions(-) delete mode 100644 js/autocomplete_customer.js diff --git a/SL/Controller/CustomerVendor.pm b/SL/Controller/CustomerVendor.pm index 86bba25f6..042ec25bf 100644 --- a/SL/Controller/CustomerVendor.pm +++ b/SL/Controller/CustomerVendor.pm @@ -640,7 +640,6 @@ sub action_ajaj_autocomplete { } sub action_test_page { - $::request->{layout}->add_javascripts('autocomplete_customer.js'); $_[0]->render('customer_vendor/test_page'); } @@ -939,7 +938,6 @@ sub _pre_render { $self->{template_args} ||= {}; - $::request->{layout}->add_javascripts('autocomplete_customer.js'); $::request->{layout}->add_javascripts('kivi.CustomerVendor.js'); $::request->{layout}->add_javascripts('kivi.File.js'); diff --git a/SL/Presenter/CustomerVendor.pm b/SL/Presenter/CustomerVendor.pm index fccfa1daf..d00c54d7e 100644 --- a/SL/Presenter/CustomerVendor.pm +++ b/SL/Presenter/CustomerVendor.pm @@ -53,18 +53,17 @@ sub customer_vendor_picker { } my $id = delete($params{id}) || $self->name_to_id($name); - my $fat_set_item = delete $params{fat_set_item}; my @classes = $params{class} ? ($params{class}) : (); push @classes, 'customer_vendor_autocomplete'; - push @classes, 'customer-vendor-picker-fat-set-item' if $fat_set_item; my $ret = - $self->input_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => "@classes", type => 'hidden', id => $id) . - join('', map { $params{$_} ? $self->input_tag("", delete $params{$_}, id => "${id}_${_}", type => 'hidden') : '' } qw(type)) . + $self->input_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => "@classes", type => 'hidden', id => $id, + 'data-customer-vendor-picker-data' => JSON::to_json(\%params), + ) . $self->input_tag("", ref $value ? $value->displayable_name : '', id => "${id}_name", %params); - $::request->layout->add_javascripts('autocomplete_customer.js'); + $::request->layout->add_javascripts('kivi.CustomerVendor.js'); $::request->presenter->need_reinit_widgets($id); $self->html_tag('span', $ret, class => 'customer_vendor_picker'); diff --git a/js/autocomplete_customer.js b/js/autocomplete_customer.js deleted file mode 100644 index 8f8771d86..000000000 --- a/js/autocomplete_customer.js +++ /dev/null @@ -1,208 +0,0 @@ -namespace('kivi', function(k){ - "use strict"; - - k.CustomerVendorPicker = function($real, options) { - // short circuit in case someone double inits us - if ($real.data("customer_vendor_picker")) - return $real.data("customer_vendor_picker"); - - var KEY = { - ESCAPE: 27, - ENTER: 13, - TAB: 9, - LEFT: 37, - RIGHT: 39, - PAGE_UP: 33, - PAGE_DOWN: 34, - SHIFT: 16, - CTRL: 17, - ALT: 18, - }; - var CLASSES = { - PICKED: 'customer-vendor-picker-picked', - UNDEFINED: 'customer-vendor-picker-undefined', - FAT_SET_ITEM: 'customer-vendor-picker-fat-set-item', - } - var o = $.extend({ - limit: 20, - delay: 50, - fat_set_item: $real.hasClass(CLASSES.FAT_SET_ITEM), - }, options); - var STATES = { - PICKED: CLASSES.PICKED, - UNDEFINED: CLASSES.UNDEFINED - } - var real_id = $real.attr('id'); - var $dummy = $('#' + real_id + '_name'); - var $type = $('#' + real_id + '_type'); - var $unit = $('#' + real_id + '_unit'); - var state = STATES.PICKED; - var last_real = $real.val(); - var last_dummy = $dummy.val(); - var timer; - - function ajax_data(term) { - var data = { - 'filter.all:substr:multi::ilike': term, - 'filter.obsolete': 0, - current: $real.val(), - type: $type.val(), - }; - - return data; - } - - function set_item (item) { - if (item.id) { - $real.val(item.id); - // autocomplete ui has name, ajax items have description - $dummy.val(item.name ? item.name : item.description); - } else { - $real.val(''); - $dummy.val(''); - } - state = STATES.PICKED; - last_real = $real.val(); - last_dummy = $dummy.val(); - $real.trigger('change'); - - if (o.fat_set_item && item.id) { - $.ajax({ - url: 'controller.pl?action=CustomerVendor/show.json', - data: { id: item.id, db: item.type }, - success: function(rsp) { - $real.trigger('set_item:CustomerVendorPicker', rsp); - }, - }); - } else { - $real.trigger('set_item:CustomerVendorPicker', item); - } - annotate_state(); - } - - function make_defined_state () { - if (state == STATES.PICKED) { - annotate_state(); - return true - } else if (state == STATES.UNDEFINED && $dummy.val() === '') - set_item({}) - else { - set_item({ id: last_real, name: last_dummy }) - } - annotate_state(); - } - - function annotate_state () { - if (state == STATES.PICKED) - $dummy.removeClass(STATES.UNDEFINED).addClass(STATES.PICKED); - else if (state == STATES.UNDEFINED && $dummy.val() === '') - $dummy.removeClass(STATES.UNDEFINED).addClass(STATES.PICKED); - else { - $dummy.addClass(STATES.UNDEFINED).removeClass(STATES.PICKED); - } - } - - function handle_changed_text(callbacks) { - $.ajax({ - url: 'controller.pl?action=CustomerVendor/ajaj_autocomplete', - dataType: "json", - data: $.extend( ajax_data($dummy.val()), { prefer_exact: 1 } ), - success: function (data) { - if (data.length == 1) { - set_item(data[0]); - if (callbacks && callbacks.match_one) callbacks.match_one(data[0]); - } else if (data.length > 1) { - state = STATES.UNDEFINED; - if (callbacks && callbacks.match_many) callbacks.match_many(data); - } else { - state = STATES.UNDEFINED; - if (callbacks &&callbacks.match_none) callbacks.match_none(); - } - annotate_state(); - } - }); - } - - $dummy.autocomplete({ - source: function(req, rsp) { - $.ajax($.extend(o, { - url: 'controller.pl?action=CustomerVendor/ajaj_autocomplete', - dataType: "json", - data: ajax_data(req.term), - success: function (data){ rsp(data) } - })); - }, - select: function(event, ui) { - set_item(ui.item); - }, - search: function(event, ui) { - if ((event.which == KEY.SHIFT) || (event.which == KEY.CTRL) || (event.which == KEY.ALT)) - event.preventDefault(); - } - }); - /* In case users are impatient and want to skip ahead: - * Capture key events and check if it's a unique hit. - * If it is, go ahead and assume it was selected. If it wasn't don't do - * anything so that autocompletion kicks in. For don't prevent - * propagation. It would be nice to catch it, but javascript is too stupid - * to fire a tab event later on, so we'd have to reimplement the "find - * next active element in tabindex order and focus it". - */ - /* note: - * event.which does not contain tab events in keypressed in firefox but will report 0 - * chrome does not fire keypressed at all on tab or escape - */ - $dummy.keydown(function(event){ - if (event.which == KEY.ENTER || event.which == KEY.TAB) { - // if string is empty assume they want to delete - if ($dummy.val() === '') { - set_item({}); - return true; - } else if (state == STATES.PICKED) { - return true; - } - if (event.which == KEY.TAB) { - event.preventDefault(); - handle_changed_text(); - } - if (event.which == KEY.ENTER) { - handle_changed_text({ - match_one: function(){$('#update_button').click();}, - }); - return false; - } - } else if ((event.which != KEY.SHIFT) && (event.which != KEY.CTRL) && (event.which != KEY.ALT)) { - state = STATES.UNDEFINED; - } - }); - - $dummy.on('paste', function(){ - setTimeout(function() { - handle_changed_text(); - }, 1); - }); - - $dummy.blur(function(){ - window.clearTimeout(timer); - timer = window.setTimeout(annotate_state, 100); - }); - - // now add a picker div after the original input - var pp = { - real: function() { return $real }, - dummy: function() { return $dummy }, - type: function() { return $type }, - set_item: set_item, - reset: make_defined_state, - is_defined_state: function() { return state == STATES.PICKED }, - } - $real.data('customer_vendor_picker', pp); - return pp; - } -}); - -$(function(){ - $('input.customer_vendor_autocomplete').each(function(i,real){ - kivi.CustomerVendorPicker($(real)); - }) -}); diff --git a/js/kivi.CustomerVendor.js b/js/kivi.CustomerVendor.js index 9efb990f1..14e02e0d1 100644 --- a/js/kivi.CustomerVendor.js +++ b/js/kivi.CustomerVendor.js @@ -45,7 +45,7 @@ namespace('kivi.CustomerVendor', function(ns) { $ctrl.prop('checked', cvar.value == 1 ? 'checked' : ''); else if ((cvar.type == 'customer') || (cvar.type == 'vendor')) - kivi.CustomerVendorPicker($ctrl).set_item({ id: cvar.id, name: cvar.value }); + kivi.CustomerVendor.Picker($ctrl).set_item({ id: cvar.id, name: cvar.value }); else if (cvar.type == 'part') kivi.Part.Picker($ctrl).set_item({ id: cvar.id, name: cvar.value }); @@ -222,10 +222,232 @@ namespace('kivi.CustomerVendor', function(ns) { event.preventDefault(); ns.inline_report(target, event.target + '', {}); }; -}); -function local_reinit_widgets() { - $('#cv_phone,#shipto_shiptophone,#contact_cp_phone1,#contact_cp_phone2,#contact_cp_mobile1,#contact_cp_mobile2').each(function(idx, elt) { - kivi.CustomerVendor.init_dial_action($(elt)); + var KEY = { + TAB: 9, + ENTER: 13, + SHIFT: 16, + CTRL: 17, + ALT: 18, + ESCAPE: 27, + PAGE_UP: 33, + PAGE_DOWN: 34, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + }; + + ns.Picker = function($real, options) { + var self = this; + this.o = $.extend(true, { + limit: 20, + delay: 50, + action: { + commit_none: function(){ }, + commit_one: function(){ $('#update_button').click(); }, + commit_many: function(){ } + } + }, $real.data('customer-vendor-picker-data'), options); + this.$real = $real; + this.real_id = $real.attr('id'); + this.last_real = $real.val(); + this.$dummy = $($real.siblings()[0]); + this.autocomplete_open = false; + this.state = this.STATES.PICKED; + this.last_dummy = this.$dummy.val(); + this.timer = undefined; + + this.init(); + }; + + ns.Picker.prototype = { + CLASSES: { + PICKED: 'customer-vendor-picker-picked', + UNDEFINED: 'customer-vendor-picker-undefined', + }, + ajax_data: function(term) { + return { + 'filter.all:substr:multi::ilike': term, + 'filter.obsolete': 0, + current: this.$real.val(), + type: this.o.type, + }; + }, + set_item: function(item) { + var self = this; + if (item.id) { + this.$real.val(item.id); + // autocomplete ui has name, use the value for ajax items, which contains displayable_name + this.$dummy.val(item.name ? item.name : item.value); + } else { + this.$real.val(''); + this.$dummy.val(''); + } + this.state = this.STATES.PICKED; + this.last_real = this.$real.val(); + this.last_dummy = this.$dummy.val(); + this.$real.trigger('change'); + + if (this.o.fat_set_item && item.id) { + $.ajax({ + url: 'controller.pl?action=CustomerVendor/show.json', + data: { 'id': item.id, 'db': item.type }, + success: function(rsp) { + self.$real.trigger('set_item:CustomerVendorPicker', rsp); + }, + }); + } else { + this.$real.trigger('set_item:CustomerVendorPicker', item); + } + this.annotate_state(); + }, + set_multi_items: function(data) { + this.run_action(this.o.action.set_multi_items, [ data ]); + }, + make_defined_state: function() { + if (this.state == this.STATES.PICKED) { + this.annotate_state(); + return true + } else if (this.state == this.STATES.UNDEFINED && this.$dummy.val() === '') + this.set_item({}) + else { + this.set_item({ id: this.last_real, name: this.last_dummy }) + } + this.annotate_state(); + }, + annotate_state: function() { + if (this.state == this.STATES.PICKED) + this.$dummy.removeClass(this.STATES.UNDEFINED).addClass(this.STATES.PICKED); + else if (this.state == this.STATES.UNDEFINED && this.$dummy.val() === '') + this.$dummy.removeClass(this.STATES.UNDEFINED).addClass(this.STATES.PICKED); + else { + this.$dummy.addClass(this.STATES.UNDEFINED).removeClass(this.STATES.PICKED); + } + }, + handle_changed_text: function(callbacks) { + var self = this; + $.ajax({ + url: 'controller.pl?action=CustomerVendor/ajaj_autocomplete', + dataType: "json", + data: $.extend( self.ajax_data(self.$dummy.val()), { prefer_exact: 1 } ), + success: function (data) { + if (data.length == 1) { + self.set_item(data[0]); + if (callbacks && callbacks.match_one) self.run_action(callbacks.match_one, [ data[0] ]); + } else if (data.length > 1) { + self.state = self.STATES.UNDEFINED; + if (callbacks && callbacks.match_many) self.run_action(callbacks.match_many, [ data ]); + } else { + self.state = self.STATES.UNDEFINED; + if (callbacks && callbacks.match_none) self.run_action(callbacks.match_none, [ self, self.$dummy.val() ]); + } + self.annotate_state(); + } + }); + }, + handle_keydown: function(event) { + var self = this; + if (event.which == KEY.ENTER || event.which == KEY.TAB) { + // if string is empty assume they want to delete + if (self.$dummy.val() === '') { + self.set_item({}); + return true; + } else if (self.state == self.STATES.PICKED) { + if (self.o.action.commit_one) { + self.run_action(self.o.action.commit_one); + } + return true; + } + if (event.which == KEY.TAB) { + event.preventDefault(); + self.handle_changed_text(); + } + if (event.which == KEY.ENTER) { + self.handle_changed_text({ + match_none: self.o.action.commit_none, + match_one: self.o.action.commit_one, + match_many: self.o.action.commit_many + }); + return false; + } + } else if (event.which == KEY.DOWN && !self.autocomplete_open) { + var old_options = self.$dummy.autocomplete('option'); + self.$dummy.autocomplete('option', 'minLength', 0); + self.$dummy.autocomplete('search', self.$dummy.val()); + self.$dummy.autocomplete('option', 'minLength', old_options.minLength); + } else if ((event.which != KEY.SHIFT) && (event.which != KEY.CTRL) && (event.which != KEY.ALT)) { + self.state = self.STATES.UNDEFINED; + } + }, + init: function() { + var self = this; + this.$dummy.autocomplete({ + source: function(req, rsp) { + $.ajax($.extend({}, self.o, { + url: 'controller.pl?action=CustomerVendor/ajaj_autocomplete', + dataType: "json", + type: 'get', + data: self.ajax_data(req.term), + success: function (data){ rsp(data) } + })); + }, + select: function(event, ui) { + self.set_item(ui.item); + }, + search: function(event, ui) { + if ((event.which == KEY.SHIFT) || (event.which == KEY.CTRL) || (event.which == KEY.ALT)) + event.preventDefault(); + }, + open: function() { + self.autocomplete_open = true; + }, + close: function() { + self.autocomplete_open = false; + } + }); + this.$dummy.keydown(function(event){ self.handle_keydown(event) }); + this.$dummy.on('paste', function(){ + setTimeout(function() { + self.handle_changed_text(); + }, 1); + }); + this.$dummy.blur(function(){ + window.clearTimeout(self.timer); + self.timer = window.setTimeout(function() { self.annotate_state() }, 100); + }); + }, + run_action: function(code, args) { + if (typeof code === 'function') + code.apply(this, args) + else + kivi.run(code, args); + }, + clear: function() { + this.set_item({}); + } + }; + ns.Picker.prototype.STATES = { + PICKED: ns.Picker.prototype.CLASSES.PICKED, + UNDEFINED: ns.Picker.prototype.CLASSES.UNDEFINED + }; + + ns.reinit_widgets = function() { + kivi.run_once_for('input.customer_vendor_autocomplete', 'customer_vendor_picker', function(elt) { + if (!$(elt).data('customer_vendor_picker')) + $(elt).data('customer_vendor_picker', new kivi.CustomerVendor.Picker($(elt))); + }); + + $('#cv_phone,#shipto_shiptophone,#contact_cp_phone1,#contact_cp_phone2,#contact_cp_mobile1,#contact_cp_mobile2').each(function(idx, elt) { + kivi.CustomerVendor.init_dial_action($(elt)); + }); + } + + ns.init = function() { + ns.reinit_widgets(); + } + + $(function(){ + ns.init(); }); -} +}); diff --git a/js/kivi.js b/js/kivi.js index 4e415d97f..210020a60 100644 --- a/js/kivi.js +++ b/js/kivi.js @@ -242,17 +242,13 @@ namespace("kivi", function(ns) { }); if (ns.Part) ns.Part.reinit_widgets(); + if (ns.CustomerVendor) ns.CustomerVendor.reinit_widgets(); if (ns.ProjectPicker) ns.run_once_for('input.project_autocomplete', 'project_picker', function(elt) { kivi.ProjectPicker($(elt)); }); - if (ns.CustomerVendorPicker) - ns.run_once_for('input.customer_vendor_autocomplete', 'customer_vendor_picker', function(elt) { - kivi.CustomerVendorPicker($(elt)); - }); - if (ns.ChartPicker) ns.run_once_for('input.chart_autocomplete', 'chart_picker', function(elt) { kivi.ChartPicker($(elt)); diff --git a/templates/webpages/customer_vendor/tabs/contacts.html b/templates/webpages/customer_vendor/tabs/contacts.html index 8361c6065..602352cea 100644 --- a/templates/webpages/customer_vendor/tabs/contacts.html +++ b/templates/webpages/customer_vendor/tabs/contacts.html @@ -18,7 +18,7 @@ empty_title = LxERP.t8('New contact'), value_key = 'cp_id', title_key = 'full_name', - onchange = "kivi.CustomerVendor.selectContact({onFormSet: function(){ contactsMapWidget.testInputs(); local_reinit_widgets(); }});", + onchange = "kivi.CustomerVendor.selectContact({onFormSet: function(){ contactsMapWidget.testInputs(); kivi.reinit_widgets(); }});", ) %] diff --git a/templates/webpages/customer_vendor/tabs/shipto.html b/templates/webpages/customer_vendor/tabs/shipto.html index e93531a30..5dd6d85e0 100644 --- a/templates/webpages/customer_vendor/tabs/shipto.html +++ b/templates/webpages/customer_vendor/tabs/shipto.html @@ -16,7 +16,7 @@ title_key = 'displayable_id', with_empty = 1, empty_title = LxERP.t8('New shipto'), - onchange = "kivi.CustomerVendor.selectShipto({onFormSet: function(){ shiptoMapWidget.testInputs(); local_reinit_widgets(); }});", + onchange = "kivi.CustomerVendor.selectShipto({onFormSet: function(){ shiptoMapWidget.testInputs(); kivi.reinit_widgets(); }});", ) %] diff --git a/templates/webpages/customer_vendor/test_page.html b/templates/webpages/customer_vendor/test_page.html index 4e2989719..c1d26f939 100644 --- a/templates/webpages/customer_vendor/test_page.html +++ b/templates/webpages/customer_vendor/test_page.html @@ -24,9 +24,8 @@ fat vendor with change


this one will be a reinit_widget after 4s:
- - - + + -- 2.20.1