mebil
[kivitendo-erp.git] / js / autocomplete_project.js
1 namespace('kivi', function(k){
2   k.ProjectPicker = function($real, options) {
3     // short circuit in case someone double inits us
4     if ($real.data("project_picker"))
5       return $real.data("project_picker");
6
7     var KEY = {
8       ESCAPE: 27,
9       ENTER:  13,
10       TAB:    9,
11       LEFT:   37,
12       RIGHT:  39,
13       PAGE_UP: 33,
14       PAGE_DOWN: 34,
15     };
16     var CLASSES = {
17       PICKED:       'projectpicker-picked',
18       UNDEFINED:    'projectpicker-undefined',
19     }
20     var o = $.extend({
21       limit: 20,
22       delay: 50,
23     }, options);
24     var STATES = {
25       PICKED:    CLASSES.PICKED,
26       UNDEFINED: CLASSES.UNDEFINED
27     }
28     var real_id      = $real.attr('id');
29     var $dummy       = $('#' + real_id + '_name');
30     var $customer_id = $('#' + real_id + '_customer_id');
31     var state        = STATES.PICKED;
32     var last_real    = $real.val();
33     var last_dummy   = $dummy.val();
34     var timer;
35
36     function ajax_data(term) {
37       var data = {
38         'filter.all:substr:multi::ilike': term,
39         'filter.valid': 'valid',
40         no_paginate:  $('#no_paginate').prop('checked') ? 1 : 0,
41         current:  $real.val(),
42       };
43
44       if ($customer_id && $customer_id.val())
45         data['filter.customer_id'] = $customer_id.val().split(',');
46
47       return data;
48     }
49
50     function set_item (item) {
51       if (item.id) {
52         $real.val(item.id);
53         // autocomplete ui has name, use the value for ajax items, which contains displayable_name
54         $dummy.val(item.name ? item.name : item.value);
55       } else {
56         $real.val('');
57         $dummy.val('');
58       }
59       state                 = STATES.PICKED;
60       last_real             = $real.val();
61       last_dummy            = $dummy.val();
62       last_unverified_dummy = $dummy.val();
63
64       $real.trigger('change');
65       $real.trigger('set_item:ProjectPicker', item);
66
67       annotate_state();
68     }
69
70     function make_defined_state () {
71       if (state == STATES.PICKED) {
72         annotate_state();
73         return true
74       } else if (state == STATES.UNDEFINED && $dummy.val() == '')
75         set_item({})
76       else {
77         last_unverified_dummy = $dummy.val();
78         set_item({ id: last_real, name: last_dummy })
79       }
80       annotate_state();
81     }
82
83     function annotate_state () {
84       if (state == STATES.PICKED)
85         $dummy.removeClass(STATES.UNDEFINED).addClass(STATES.PICKED);
86       else if (state == STATES.UNDEFINED && $dummy.val() == '')
87         $dummy.removeClass(STATES.UNDEFINED).addClass(STATES.PICKED);
88       else {
89         last_unverified_dummy = $dummy.val();
90         $dummy.addClass(STATES.UNDEFINED).removeClass(STATES.PICKED);
91       }
92     }
93
94     function update_results () {
95       $.ajax({
96         url: 'controller.pl?action=Project/project_picker_result',
97         data: $.extend({
98             'real_id': $real.val(),
99         }, ajax_data(function(){ var val = $('#project_picker_filter').val(); return val === undefined ? '' : val })),
100         success: function(data){ $('#project_picker_result').html(data) }
101       });
102     };
103
104     function result_timer (event) {
105       if (!$('no_paginate').prop('checked')) {
106         if (event.keyCode == KEY.PAGE_UP) {
107           $('#project_picker_result a.paginate-prev').click();
108           return;
109         }
110         if (event.keyCode == KEY.PAGE_DOWN) {
111           $('#project_picker_result a.paginate-next').click();
112           return;
113         }
114       }
115       window.clearTimeout(timer);
116       timer = window.setTimeout(update_results, 100);
117     }
118
119     function handle_changed_text(callbacks) {
120       $.ajax({
121         url: 'controller.pl?action=Project/ajax_autocomplete',
122         dataType: "json",
123         data: $.extend( ajax_data($dummy.val()), { prefer_exact: 1 } ),
124         success: function (data) {
125           if (data.length == 1) {
126             set_item(data[0]);
127             if (callbacks && callbacks.match_one) callbacks.match_one(data[0]);
128           } else if (data.length > 1) {
129             state = STATES.UNDEFINED;
130             if (callbacks && callbacks.match_many) callbacks.match_many(data);
131           } else {
132             state = STATES.UNDEFINED;
133             if (callbacks &&callbacks.match_none) callbacks.match_none();
134           }
135           annotate_state();
136         }
137       });
138     };
139
140     $dummy.autocomplete({
141       source: function(req, rsp) {
142         $.ajax($.extend(o, {
143           url:      'controller.pl?action=Project/ajax_autocomplete',
144           dataType: "json",
145           data:     ajax_data(req.term),
146           success:  function (data){ rsp(data) }
147         }));
148       },
149       select: function(event, ui) {
150         set_item(ui.item);
151       },
152     });
153     /*  In case users are impatient and want to skip ahead:
154      *  Capture <enter> key events and check if it's a unique hit.
155      *  If it is, go ahead and assume it was selected. If it wasn't don't do
156      *  anything so that autocompletion kicks in.  For <tab> don't prevent
157      *  propagation. It would be nice to catch it, but javascript is too stupid
158      *  to fire a tab event later on, so we'd have to reimplement the "find
159      *  next active element in tabindex order and focus it".
160      */
161     /* note:
162      *  event.which does not contain tab events in keypressed in firefox but will report 0
163      *  chrome does not fire keypressed at all on tab or escape
164      */
165     $dummy.keydown(function(event){
166       if (event.which == KEY.ENTER || event.which == KEY.TAB) {
167         // if string is empty assume they want to delete
168         if ($dummy.val() == '') {
169           set_item({});
170           return true;
171         } else if (state == STATES.PICKED) {
172           return true;
173         }
174         if (event.which == KEY.TAB) {
175           event.preventDefault();
176           handle_changed_text();
177         }
178         if (event.which == KEY.ENTER) {
179           handle_changed_text({
180             match_one:  function(){$('#update_button').click();},
181           });
182           return false;
183         }
184       } else {
185         state = STATES.UNDEFINED;
186       }
187     });
188
189     $dummy.on('paste', function(){
190       setTimeout(function() {
191         handle_changed_text();
192       }, 1);
193     });
194
195     $dummy.blur(function(){
196       window.clearTimeout(timer);
197       timer = window.setTimeout(annotate_state, 100);
198     });
199
200     // now add a picker div after the original input
201     var pcont  = $('<span>').addClass('position-absolute');
202     var picker = $('<div>');
203     $dummy.after(pcont);
204     pcont.append(picker);
205
206     var pp = {
207       real:           function() { return $real },
208       dummy:          function() { return $dummy },
209       type:           function() { return $type },
210       customer_id:    function() { return $customer_id },
211       update_results: update_results,
212       result_timer:   result_timer,
213       set_item:       set_item,
214       reset:          make_defined_state,
215       is_defined_state: function() { return state == STATES.PICKED },
216       init_results:    function () {
217         $('div.project_picker_project').each(function(){
218           $(this).click(function(){
219             set_item({
220               id:   $(this).children('input.project_picker_id').val(),
221               name: $(this).children('input.project_picker_description').val(),
222             });
223             $dummy.focus();
224             return true;
225           });
226         });
227         $('#project_selection').keydown(function(e){
228            if (e.which == KEY.ESCAPE) {
229              $dummy.focus();
230            }
231         });
232       }
233     }
234     $real.data('project_picker', pp);
235     return pp;
236   }
237 });
238
239 $(function(){
240   $('input.project_autocomplete').each(function(i,real){
241     kivi.ProjectPicker($(real));
242   })
243 });