Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 8ae5ec0d

Von Sven Schöling vor fast 8 Jahren hinzugefügt

  • ID 8ae5ec0d2919e7f2496c80c36a21af357892db13
  • Vorgänger 04b01603
  • Nachfolger a45a5332

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.

Unterschiede anzeigen:

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