<commit_before>import keyBy from 'lodash/keyBy';

export default keyBy([
  {
    id: 'Hairstyles',
    required: false,
    clickable: true,
    sortOrder: 10,
    styles: {
      '10': {
        left: 194,
        top: 57
      },
      '11': {
        left: 197,
        top: 65
      },
      '12': {
        left: 195,
        top: 71
      },
      '13': {
        left: 195,
        top: 71
      },
      '14': {
        left: 194,
        top: 76
      },
      '15': {
        left: 200,
        top: 56
      },
      '16': {
        left: 194,
        top: 54
      },
      '17': {
        left: 195,
        top: 65
      },
      '18': {
        left: 195,
        top: 57
      },
      '19': {
        left: 194,
        top: 46
      },
      '20': {
        left: 195,
        top: 56
      },
      '21': {
        left: 194,
        top: 56
      },
      '22': {
        left: 174,
        top: 46
      },
      '23': {
        left: 193,
        top: 39
      },
      '01': {
        left: 194,
        top: 36
      },
      '02': {
        left: 194,
        top: 48
      },
      '03': {
        left: 194,
        top: 28
      },
      '04': {
        left: 194,
        top: 42
      },
      '05': {
        left: 193,
        top: 38
      },
      '06': {
        left: 194,
        top: 48
      },
      '07': {
        left: 194,
        top: 47
      },
      '08': {
        left: 195,
        top: 42
      },
      '09': {
        left: 196,
        top: 39
      }
    }
  },
  {
    id: 'Beards',
    required: false,
    clickable: true,
    sortOrder: 43,
    styles: {
      '10': {
        left: 212,
        top: 273
      },
      '01': {
        left: 207,
        top: 309
      },
      '02': {
        left: 209,
        top: 312
      },
      '03': {
        left: 208,
        top: 311
      },
      '04': {
        left: 209,
        top: 313
      },
      '05': {
        left: 209,
        top: 310
      },
      '06': {
        left: 209,
        top: 312
      },
      '07': {
        left: 209,
        top: 313
      },
      '08': {
        left: 209,
        top: 312
      },
      '09': {
        left: 211,
        top: 270
      }
    }
  },
  {
    id: 'Body',
    required: true,
    clickable: false,
    sortOrder: 0,
    styles: {
      '01': {
        left: 148,
        top: 165
      }
    }
  },
  {
    id: 'Coats',
    required: false,
    clickable: true,
    sortOrder: 42,
    conflicts: {
      replace: {
        assets: ['Jackets']
      }
    },
    styles: {
      '01': {
        left: 60,
        top: 411
      },
      '02': {
        left: 60,
        top: 400
      },
      '03': {
        left: 59,
        top: 400
      },
      '04': {
        left: 58,
        top: 397
      },
      '05': {
        left: 60,
        top: 403
      },
      '06': {
        left: 60,
        top: 399
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Glasses',
    required: false,
    clickable: true,
    sortOrder: 80,
    styles: {
      '10': {
        left: 226,
        top: 296
      },
      '01': {
        left: 226,
        top: 299
      },
      '02': {
        left: 225,
        top: 298
      },
      '03': {
        left: 228,
        top: 295
      },
      '04': {
        left: 228,
        top: 295
      },
      '05': {
        left: 225,
        top: 295
      },
      '06': {
        left: 227,
        top: 296
      },
      '07': {
        left: 226,
        top: 296
      },
      '08': {
        left: 224,
        top: 296
      },
      '09': {
        left: 227,
        top: 297
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Scarves',
    required: false,
    clickable: true,
    sortOrder: 42,
    styles: {
      '01': {
        left: 155,
        top: 434
      },
      '02': {
        left: 153,
        top: 426
      },
      '03': {
        left: 154,
        top: 426
      },
      '04': {
        left: 155,
        top: 429
      },
      '05': {
        left: 155,
        top: 426
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Shirts',
    required: false,
    clickable: true,
    sortOrder: 10,
    conflicts: {
      replace: {
        assets: ['T-shirts', 'Sweatshirts']
      }
    },
    styles: {
      '01': {
        left: 110,
        top: 465
      },
      '02': {
        left: 111,
        top: 465
      },
      '03': {
        left: 111,
        top: 465
      },
      '04': {
        left: 110,
        top: 465
      },
      '05': {
        left: 109,
        top: 465
      },
      '06': {
        left: 110,
        top: 465
      },
      '07': {
        left: 110,
        top: 465
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Sweatshirts',
    required: false,
    clickable: true,
    sortOrder: 30,
    conflicts: {
      replace: {
        assets: ['T-shirts', 'Shirts']
      }
    },
    styles: {
      '01': {
        left: 114,
        top: 453
      },
      '02': {
        left: 115,
        top: 451
      },
      '03': {
        left: 116,
        top: 447
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'T-shirts',
    required: false,
    clickable: true,
    sortOrder: 10,
    conflicts: {
      replace: {
        assets: ['Shirts', 'Sweatshirts']
      }
    },
    styles: {
      '01': {
        left: 110,
        top: 473
      },
      '02': {
        left: 113,
        top: 473
      },
      '03': {
        left: 110,
        top: 472
      },
      '04': {
        left: 110,
        top: 470
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Ties',
    required: false,
    clickable: true,
    sortOrder: 20,
    styles: {
      '01': {
        left: 308,
        top: 536
      },
      '02': {
        left: 309,
        top: 539
      },
      '03': {
        left: 310,
        top: 536
      },
      '04': {
        left: 310,
        top: 539
      },
      '05': {
        left: 310,
        top: 539
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Eyes',
    required: true,
    clickable: true,
    sortOrder: 70,
    styles: {
      '01': {
        left: 286,
        top: 288
      },
      '02': {
        left: 286,
        top: 285
      },
      '03': {
        left: 284,
        top: 294
      }
    }
  },
  {
    id: 'Jackets',
    required: false,
    clickable: true,
    sortOrder: 40,
    conflicts: {
      replace: {
        assets: ['Coats']
      }
    },
    styles: {
      '01': {
        left: 62,
        top: 406
      },
      '02': {
        left: 62,
        top: 411
      },
      '03': {
        left: 62,
        top: 406
      },
      '04': {
        left: 62,
        top: 406
      },
      '05': {
        left: 64,
        top: 406
      },
      '06': {
        left: 62,
        top: 406
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Mouths',
    required: true,
    clickable: true,
    sortOrder: 20,
    styles: {
      '01': {
        left: 311,
        top: 405
      },
      '02': {
        left: 311,
        top: 407
      },
      '03': {
        left: 311,
        top: 407
      }
    }
  },
  {
    id: 'Vests',
    required: false,
    clickable: true,
    sortOrder: 40,
    styles: {
      '01': {
        left: 61,
        top: 411
      },
      '02': {
        left: 62,
        top: 408
      },
      '03': {
        left: 63,
        top: 404
      },
      '04': {
        left: 63,
        top: 404
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Hats',
    required: false,
    clickable: true,
    sortOrder: 80,
    conflicts: {
      changeType: {
        assets: {
          'Hairstyles': '14'
        }
      }
    },
    styles: {
      '01': {
        left: 113,
        top: -78
      },
      '02': {
        left: 116,
        top: -78
      },
      '03': {
        left: 121,
        top: -78
      },
      '04': {
        left: 116,
        top: -67
      },
      '05': {
        left: 119,
        top: -78
      },
      '06': {
        left: 119,
        top: -78
      },
      '07': {
        left: 121,
        top: -78
      },
      '08': {
        left: 122,
        top: -78
      },
      '09': {
        left: 116,
        top: -68
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Suits',
    required: false,
    clickable: true,
    sortOrder: 41,
    styles: {
      '01': {
        left: 63,
        top: 411
      },
      '02': {
        left: 64,
        top: 406
      }
    },
    menuItem: 'Clothes'
  }
], 'id');
<commit_msg>Add new positions<commit_after>import keyBy from 'lodash/keyBy';

export default keyBy([
  {
    id: 'Hairstyles',
    required: false,
    clickable: true,
    sortOrder: 10,
    styles: {
      '10': {
        left: 194,
        top: 57
      },
      '11': {
        left: 197,
        top: 65
      },
      '12': {
        left: 195,
        top: 71
      },
      '13': {
        left: 195,
        top: 71
      },
      '14': {
        left: 194,
        top: 76
      },
      '15': {
        left: 200,
        top: 56
      },
      '16': {
        left: 194,
        top: 54
      },
      '17': {
        left: 195,
        top: 65
      },
      '18': {
        left: 195,
        top: 57
      },
      '19': {
        left: 194,
        top: 46
      },
      '20': {
        left: 195,
        top: 56
      },
      '21': {
        left: 194,
        top: 56
      },
      '22': {
        left: 174,
        top: 46
      },
      '23': {
        left: 193,
        top: 39
      },
      '01': {
        left: 194,
        top: 36
      },
      '02': {
        left: 194,
        top: 48
      },
      '03': {
        left: 194,
        top: 28
      },
      '04': {
        left: 194,
        top: 42
      },
      '05': {
        left: 193,
        top: 38
      },
      '06': {
        left: 194,
        top: 48
      },
      '07': {
        left: 194,
        top: 47
      },
      '08': {
        left: 195,
        top: 42
      },
      '09': {
        left: 196,
        top: 39
      }
    }
  },
  {
    id: 'Beards',
    required: false,
    clickable: true,
    sortOrder: 43,
    styles: {
      '10': {
        left: 212,
        top: 273
      },
      '01': {
        left: 207,
        top: 309
      },
      '02': {
        left: 209,
        top: 312
      },
      '03': {
        left: 208,
        top: 311
      },
      '04': {
        left: 209,
        top: 313
      },
      '05': {
        left: 209,
        top: 310
      },
      '06': {
        left: 209,
        top: 312
      },
      '07': {
        left: 209,
        top: 313
      },
      '08': {
        left: 209,
        top: 312
      },
      '09': {
        left: 211,
        top: 270
      }
    }
  },
  {
    id: 'Body',
    required: true,
    clickable: false,
    sortOrder: 0,
    styles: {
      '01': {
        left: 148,
        top: 165
      }
    }
  },
  {
    id: 'Coats',
    required: false,
    clickable: true,
    sortOrder: 42,
    conflicts: {
      replace: {
        assets: [
          'Jackets'
        ]
      }
    },
    styles: {
      '01': {
        left: 60,
        top: 411
      },
      '02': {
        left: 60,
        top: 400
      },
      '03': {
        left: 59,
        top: 400
      },
      '04': {
        left: 58,
        top: 397
      },
      '05': {
        left: 60,
        top: 403
      },
      '06': {
        left: 60,
        top: 399
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Glasses',
    required: false,
    clickable: true,
    sortOrder: 80,
    styles: {
      '10': {
        left: 226,
        top: 296
      },
      '01': {
        left: 226,
        top: 299
      },
      '02': {
        left: 225,
        top: 298
      },
      '03': {
        left: 228,
        top: 295
      },
      '04': {
        left: 228,
        top: 295
      },
      '05': {
        left: 225,
        top: 295
      },
      '06': {
        left: 227,
        top: 296
      },
      '07': {
        left: 226,
        top: 296
      },
      '08': {
        left: 224,
        top: 296
      },
      '09': {
        left: 227,
        top: 297
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Scarves',
    required: false,
    clickable: true,
    sortOrder: 42,
    styles: {
      '01': {
        left: 155,
        top: 434
      },
      '02': {
        left: 153,
        top: 421
      },
      '03': {
        left: 154,
        top: 417
      },
      '04': {
        left: 155,
        top: 429
      },
      '05': {
        left: 155,
        top: 426
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Shirts',
    required: false,
    clickable: true,
    sortOrder: 10,
    conflicts: {
      replace: {
        assets: [
          'T-shirts',
          'Sweatshirts'
        ]
      }
    },
    styles: {
      '01': {
        left: 110,
        top: 465
      },
      '02': {
        left: 111,
        top: 465
      },
      '03': {
        left: 111,
        top: 465
      },
      '04': {
        left: 110,
        top: 465
      },
      '05': {
        left: 109,
        top: 465
      },
      '06': {
        left: 110,
        top: 465
      },
      '07': {
        left: 110,
        top: 465
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Sweatshirts',
    required: false,
    clickable: true,
    sortOrder: 30,
    conflicts: {
      replace: {
        assets: [
          'T-shirts',
          'Shirts'
        ]
      }
    },
    styles: {
      '01': {
        left: 114,
        top: 453
      },
      '02': {
        left: 115,
        top: 451
      },
      '03': {
        left: 116,
        top: 447
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'T-shirts',
    required: false,
    clickable: true,
    sortOrder: 10,
    conflicts: {
      replace: {
        assets: [
          'Shirts',
          'Sweatshirts'
        ]
      }
    },
    styles: {
      '01': {
        left: 109,
        top: 473
      },
      '02': {
        left: 113,
        top: 473
      },
      '03': {
        left: 110,
        top: 472
      },
      '04': {
        left: 110,
        top: 470
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Ties',
    required: false,
    clickable: true,
    sortOrder: 20,
    styles: {
      '01': {
        left: 308,
        top: 536
      },
      '02': {
        left: 309,
        top: 539
      },
      '03': {
        left: 310,
        top: 536
      },
      '04': {
        left: 310,
        top: 539
      },
      '05': {
        left: 310,
        top: 539
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Eyes',
    required: true,
    clickable: true,
    sortOrder: 70,
    styles: {
      '01': {
        left: 286,
        top: 288
      },
      '02': {
        left: 286,
        top: 285
      },
      '03': {
        left: 284,
        top: 294
      }
    }
  },
  {
    id: 'Jackets',
    required: false,
    clickable: true,
    sortOrder: 42,
    conflicts: {
      replace: {
        assets: [
          'Coats'
        ]
      }
    },
    styles: {
      '01': {
        left: 62,
        top: 406
      },
      '02': {
        left: 62,
        top: 411
      },
      '03': {
        left: 62,
        top: 406
      },
      '04': {
        left: 62,
        top: 406
      },
      '05': {
        left: 64,
        top: 406
      },
      '06': {
        left: 62,
        top: 406
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Mouths',
    required: true,
    clickable: true,
    sortOrder: 20,
    styles: {
      '01': {
        left: 311,
        top: 405
      },
      '02': {
        left: 311,
        top: 407
      },
      '03': {
        left: 311,
        top: 407
      }
    }
  },
  {
    id: 'Vests',
    required: false,
    clickable: true,
    sortOrder: 40,
    styles: {
      '01': {
        left: 61,
        top: 411
      },
      '02': {
        left: 62,
        top: 408
      },
      '03': {
        left: 63,
        top: 404
      },
      '04': {
        left: 63,
        top: 404
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Hats',
    required: false,
    clickable: true,
    sortOrder: 80,
    conflicts: {
      changeType: {
        assets: {
          'Hairstyles': '14'
        }
      }
    },
    styles: {
      '01': {
        left: 113,
        top: -78
      },
      '02': {
        left: 116,
        top: -78
      },
      '03': {
        left: 121,
        top: -78
      },
      '04': {
        left: 116,
        top: -67
      },
      '05': {
        left: 119,
        top: -78
      },
      '06': {
        left: 119,
        top: -78
      },
      '07': {
        left: 121,
        top: -78
      },
      '08': {
        left: 122,
        top: -78
      },
      '09': {
        left: 116,
        top: -68
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Suits',
    required: false,
    clickable: true,
    sortOrder: 41,
    styles: {
      '01': {
        left: 63,
        top: 411
      },
      '02': {
        left: 64,
        top: 406
      }
    },
    menuItem: 'Clothes'
  }
], 'id');
<|endoftext|>


<commit_before>(function($, UI) {

    "use strict";

    var renderers = {},

        Search = function(element, options) {

        var $this = this;

        this.options = $.extend({}, this.options, options);

        this.element = $(element);

        this.timer = null;
        this.value = null;
        this.input = this.element.find(".uk-search-field");
        this.form  = this.input.length ? $(this.input.get(0).form) : $();
        this.input.attr('autocomplete', 'off');

        this.input.on({
            keydown: function(event) {
                $this.form[($this.input.val()) ? 'addClass' : 'removeClass']($this.options.filledClass);

                if (event && event.which && !event.shiftKey) {

                    switch (event.which) {
                        case 13: // enter
                            $this.done($this.selected);
                            event.preventDefault();
                            break;
                        case 38: // up
                            $this.pick('prev');
                            event.preventDefault();
                            break;
                        case 40: // down
                            $this.pick('next');
                            event.preventDefault();
                            break;
                        case 27:
                        case 9: // esc, tab
                            $this.hide();
                            break;
                        default:
                            break;
                    }
                }

            },
            keyup: function(event) {
                $this.trigger();
            },
            blur: function(event) {
                setTimeout(function() { $this.hide(event); }, 200);
            }
        });

        this.form.find('button[type=reset]').bind('click', function() {
            $this.form.removeClass("uk-open").removeClass("uk-loading").removeClass("uk-active");
            $this.value = null;
            $this.input.focus();
        });

        this.dropdown = $('

').appendTo(this.form).find('.uk-nav-search');

        if (this.options.flipDropdown) {
            this.dropdown.parent().addClass('uk-dropdown-flip');
        }

        this.dropdown.on("mouseover", ">li", function(){
            $this.pick($(this));
        });

        this.renderer = new renderers[this.options.renderer](this);
    };

    $.extend(Search.prototype, {

        options: {
            source: false,
            param: 'search',
            method: 'post',
            minLength: 3,
            delay: 300,
            flipDropdown: false,
            match: ':not(.uk-skip)',
            skipClass: 'uk-skip',
            loadingClass: 'uk-loading',
            filledClass: 'uk-active',
            listClass: 'results',
            hoverClass: 'uk-active',
            onSelect: function(selected) {  },
            onLoadedResults: function(results) { return results; },
            renderer: "default"
        },

        request: function(options) {
            var $this = this;

            this.form.addClass(this.options.loadingClass);

            if (this.options.source) {

                $.ajax($.extend({
                    url: this.options.source,
                    type: this.options.method,
                    dataType: 'json',
                    success: function(data) {
                        data = $this.options.onLoadedResults.apply(this, [data]);
                        $this.form.removeClass($this.options.loadingClass);
                        $this.suggest(data);
                    }
                }, options));

            } else {
                this.form.removeClass($this.options.loadingClass);
            }
        },

        pick: function(item) {
            var selected = false;

            if (typeof item !== "string" && !item.hasClass(this.options.skipClass)) {
                selected = item;
            }

            if (item == 'next' || item == 'prev') {

                var items = this.dropdown.children().filter(this.options.match);

                if (this.selected) {
                    var index = items.index(this.selected);

                    if (item == 'next') {
                        selected = items.eq(index + 1 < items.length ? index + 1 : 0);
                    } else {
                        selected = items.eq(index - 1 < 0 ? items.length - 1 : index - 1);
                    }

                } else {
                    selected = items[(item == 'next') ? 'first' : 'last']();
                }

            }

            if (selected && selected.length) {
                this.selected = selected;
                this.dropdown.children().removeClass(this.options.hoverClass);
                this.selected.addClass(this.options.hoverClass);
            }
        },

        trigger: function() {

            var $this = this, old = this.value, data = {};

            this.value = this.input.val();

            if (this.value.length < this.options.minLength) {
                return this.hide();
            }

            if (this.value != old) {

                if (this.timer) window.clearTimeout(this.timer);

                this.timer = window.setTimeout(function() {
                    data[$this.options.param] = $this.value;
                    $this.request({'data': data});
                }, this.options.delay, this);
            }

            return this;
        },

        done: function(selected) {

            this.renderer.done(selected);
        },

        suggest: function(data) {

            if (!data) return;

            if (data === false) {
                this.hide();
            } else {

                this.selected = null;

                this.dropdown.empty();

                this.renderer.suggest(data);

                this.show();
            }
        },

        show: function() {
            if (this.visible) return;
            this.visible = true;
            this.form.addClass("uk-open");
        },

        hide: function() {
            if (!this.visible)
                return;
            this.visible = false;
            this.form.removeClass(this.options.loadingClass).removeClass("uk-open");
        }
    });

    Search.addRenderer = function(name, klass) {
        renderers[name] = klass;
    };

    var DefaultRenderer = function(search) {
        this.search = search;
        this.options = $.extend({}, DefaultRenderer.defaults, search.options);
    };

    $.extend(DefaultRenderer.prototype, {

        done: function(selected) {

            if (!selected) {
                this.search.form.submit();
                return;
            }

            if (selected.hasClass(this.options.moreResultsClass)) {
                this.search.form.submit();
            } else if (selected.data('choice')) {
                window.location = selected.data('choice').url;
            }

            this.search.hide();
        },

        suggest: function(data) {

           var $this  = this,
               events = {
                   'click': function(e) {
                       e.preventDefault();
                       $this.done($(this).parent());
                   }
               };

            if (this.options.msgResultsHeader) {
                $('
').addClass(this.options.resultsHeaderClass + ' ' + this.options.skipClass).html(this.options.msgResultsHeader).appendTo(this.search.dropdown);
            }

            if (data.results && data.results.length > 0) {

                $(data.results).each(function(i) {

                    var item = $('
' + this.title + '
').data('choice', this);

                    if (this["text"]) {
                        item.find("a").append('
' + this.text + '
');
                    }

                    $this.search.dropdown.append(item);
                });

                if (this.options.msgMoreResults) {
                    $('
').addClass('uk-nav-divider ' + $this.options.skipClass).appendTo($this.dropdown);
                    $('
').addClass($this.options.moreResultsClass).html('' + $this.options.msgMoreResults + '').appendTo($this.search.dropdown).on(events);
                }

                $this.search.dropdown.find("li>a").on(events);

            } else if (this.options.msgNoResults) {
                $('
').addClass(this.options.noResultsClass + ' ' + this.options.skipClass).html('' + this.options.msgNoResults + '').appendTo($this.search.dropdown);
            }
        }
    });

    DefaultRenderer.defaults = {
        resultsHeaderClass: 'uk-nav-header',
        moreResultsClass: '',
        noResultsClass: '',
        msgResultsHeader: 'Search Results',
        msgMoreResults: 'More Results',
        msgNoResults: 'No results found'
    };

    Search.addRenderer("default", DefaultRenderer);

    UI["search"] = Search;

    // init code
    $(document).on("focus.search.uikit", "[data-uk-search]", function(e) {
        var ele = $(this);

        if (!ele.data("search")) {
            ele.data("search", new Search(ele, UI.Utils.options(ele.data("uk-search"))));
        }
    });

})(jQuery, jQuery.UIkit);<commit_msg>search component cleanup<commit_after>(function($, UI) {

    "use strict";

    var renderers = {},

        Search = function(element, options) {

        var $this = this;

        this.options = $.extend({}, this.options, options);

        this.element = $(element);

        this.timer = null;
        this.value = null;
        this.input = this.element.find(".uk-search-field");
        this.form  = this.input.length ? $(this.input.get(0).form) : $();
        this.input.attr('autocomplete', 'off');

        this.input.on({
            keydown: function(event) {
                $this.form[($this.input.val()) ? 'addClass' : 'removeClass']($this.options.filledClass);

                if (event && event.which && !event.shiftKey) {

                    switch (event.which) {
                        case 13: // enter
                            $this.done($this.selected);
                            event.preventDefault();
                            break;
                        case 38: // up
                            $this.pick('prev');
                            event.preventDefault();
                            break;
                        case 40: // down
                            $this.pick('next');
                            event.preventDefault();
                            break;
                        case 27:
                        case 9: // esc, tab
                            $this.hide();
                            break;
                        default:
                            break;
                    }
                }

            },
            keyup: function(event) {
                $this.trigger();
            },
            blur: function(event) {
                setTimeout(function() { $this.hide(event); }, 200);
            }
        });

        this.form.find('button[type=reset]').bind('click', function() {
            $this.form.removeClass("uk-open").removeClass("uk-loading").removeClass("uk-active");
            $this.value = null;
            $this.input.focus();
        });

        this.dropdown = $('

').appendTo(this.form).find('.uk-nav-search');

        if (this.options.flipDropdown) {
            this.dropdown.parent().addClass('uk-dropdown-flip');
        }

        this.dropdown.on("mouseover", ">li", function(){
            $this.pick($(this));
        });

        this.renderer = new renderers[this.options.renderer](this);
    };

    $.extend(Search.prototype, {

        options: {
            source: false,
            param: 'search',
            method: 'post',
            minLength: 3,
            delay: 300,
            flipDropdown: false,
            match: ':not(.uk-skip)',
            skipClass: 'uk-skip',
            loadingClass: 'uk-loading',
            filledClass: 'uk-active',
            listClass: 'results',
            hoverClass: 'uk-active',
            onLoadedResults: function(results) { return results; },
            renderer: "default"
        },

        request: function(options) {
            var $this = this;

            this.form.addClass(this.options.loadingClass);

            if (this.options.source) {

                $.ajax($.extend({
                    url: this.options.source,
                    type: this.options.method,
                    dataType: 'json',
                    success: function(data) {
                        data = $this.options.onLoadedResults.apply(this, [data]);
                        $this.form.removeClass($this.options.loadingClass);
                        $this.suggest(data);
                    }
                }, options));

            } else {
                this.form.removeClass($this.options.loadingClass);
            }
        },

        pick: function(item) {
            var selected = false;

            if (typeof item !== "string" && !item.hasClass(this.options.skipClass)) {
                selected = item;
            }

            if (item == 'next' || item == 'prev') {

                var items = this.dropdown.children().filter(this.options.match);

                if (this.selected) {
                    var index = items.index(this.selected);

                    if (item == 'next') {
                        selected = items.eq(index + 1 < items.length ? index + 1 : 0);
                    } else {
                        selected = items.eq(index - 1 < 0 ? items.length - 1 : index - 1);
                    }

                } else {
                    selected = items[(item == 'next') ? 'first' : 'last']();
                }

            }

            if (selected && selected.length) {
                this.selected = selected;
                this.dropdown.children().removeClass(this.options.hoverClass);
                this.selected.addClass(this.options.hoverClass);
            }
        },

        trigger: function() {

            var $this = this, old = this.value, data = {};

            this.value = this.input.val();

            if (this.value.length < this.options.minLength) {
                return this.hide();
            }

            if (this.value != old) {

                if (this.timer) window.clearTimeout(this.timer);

                this.timer = window.setTimeout(function() {
                    data[$this.options.param] = $this.value;
                    $this.request({'data': data});
                }, this.options.delay, this);
            }

            return this;
        },

        done: function(selected) {

            this.renderer.done(selected);
        },

        suggest: function(data) {

            if (!data) return;

            if (data === false) {
                this.hide();
            } else {

                this.selected = null;

                this.dropdown.empty();

                this.renderer.suggest(data);

                this.show();
            }
        },

        show: function() {
            if (this.visible) return;
            this.visible = true;
            this.form.addClass("uk-open");
        },

        hide: function() {
            if (!this.visible)
                return;
            this.visible = false;
            this.form.removeClass(this.options.loadingClass).removeClass("uk-open");
        }
    });

    Search.addRenderer = function(name, klass) {
        renderers[name] = klass;
    };

    var DefaultRenderer = function(search) {
        this.search = search;
        this.options = $.extend({}, DefaultRenderer.defaults, search.options);
    };

    $.extend(DefaultRenderer.prototype, {

        done: function(selected) {

            if (!selected) {
                this.search.form.submit();
                return;
            }

            if (selected.hasClass(this.options.moreResultsClass)) {
                this.search.form.submit();
            } else if (selected.data('choice')) {
                window.location = selected.data('choice').url;
            }

            this.search.hide();
        },

        suggest: function(data) {

           var $this  = this,
               events = {
                   'click': function(e) {
                       e.preventDefault();
                       $this.done($(this).parent());
                   }
               };

            if (this.options.msgResultsHeader) {
                $('
').addClass(this.options.resultsHeaderClass + ' ' + this.options.skipClass).html(this.options.msgResultsHeader).appendTo(this.search.dropdown);
            }

            if (data.results && data.results.length > 0) {

                $(data.results).each(function(i) {

                    var item = $('
' + this.title + '
').data('choice', this);

                    if (this["text"]) {
                        item.find("a").append('
' + this.text + '
');
                    }

                    $this.search.dropdown.append(item);
                });

                if (this.options.msgMoreResults) {
                    $('
').addClass('uk-nav-divider ' + $this.options.skipClass).appendTo($this.dropdown);
                    $('
').addClass($this.options.moreResultsClass).html('' + $this.options.msgMoreResults + '').appendTo($this.search.dropdown).on(events);
                }

                $this.search.dropdown.find("li>a").on(events);

            } else if (this.options.msgNoResults) {
                $('
').addClass(this.options.noResultsClass + ' ' + this.options.skipClass).html('' + this.options.msgNoResults + '').appendTo($this.search.dropdown);
            }
        }
    });

    DefaultRenderer.defaults = {
        resultsHeaderClass: 'uk-nav-header',
        moreResultsClass: '',
        noResultsClass: '',
        msgResultsHeader: 'Search Results',
        msgMoreResults: 'More Results',
        msgNoResults: 'No results found'
    };

    Search.addRenderer("default", DefaultRenderer);

    UI["search"] = Search;

    // init code
    $(document).on("focus.search.uikit", "[data-uk-search]", function(e) {
        var ele = $(this);

        if (!ele.data("search")) {
            ele.data("search", new Search(ele, UI.Utils.options(ele.data("uk-search"))));
        }
    });

})(jQuery, jQuery.UIkit);<|endoftext|>


<commit_before>import keyBy from 'lodash/keyBy';

export default keyBy([
  {
    id: 'Hairstyles',
    required: false,
    clickable: true,
    sortOrder: 10,
    styles: {
      '10': {
        left: 194,
        top: 36
      },
      '11': {
        left: 194,
        top: 36
      },
      '12': {
        left: 194,
        top: 36
      },
      '13': {
        left: 194,
        top: 36
      },
      '14': {
        left: 194,
        top: 36
      },
      '15': {
        left: 194,
        top: 36
      },
      '16': {
        left: 194,
        top: 36
      },
      '17': {
        left: 194,
        top: 36
      },
      '18': {
        left: 194,
        top: 36
      },
      '19': {
        left: 194,
        top: 36
      },
      '20': {
        left: 194,
        top: 36
      },
      '21': {
        left: 194,
        top: 36
      },
      '22': {
        left: 194,
        top: 36
      },
      '23': {
        left: 194,
        top: 36
      },
      '01': {
        left: 194,
        top: 36
      },
      '02': {
        left: 194,
        top: 48
      },
      '03': {
        left: 194,
        top: 28
      },
      '04': {
        left: 194,
        top: 42
      },
      '05': {
        left: 193,
        top: 38
      },
      '06': {
        left: 194,
        top: 48
      },
      '07': {
        left: 194,
        top: 48
      },
      '08': {
        left: 194,
        top: 48
      },
      '09': {
        left: 194,
        top: 48
      }
    }
  },
  {
    id: 'Beards',
    required: false,
    clickable: true,
    sortOrder: 50,
    styles: {
      '10': {
        left: 208,
        top: 310
      },
      '01': {
        left: 208,
        top: 310
      },
      '02': {
        left: 209,
        top: 312
      },
      '03': {
        left: 208,
        top: 311
      },
      '04': {
        left: 209,
        top: 313
      },
      '05': {
        left: 209,
        top: 310
      },
      '06': {
        left: 209,
        top: 312
      },
      '07': {
        left: 209,
        top: 313
      },
      '08': {
        left: 209,
        top: 312
      },
      '09': {
        left: 208,
        top: 310
      }
    }
  },
  {
    id: 'Body',
    required: true,
    clickable: false,
    sortOrder: 0,
    styles: {
      '01': {
        left: 148,
        top: 165
      }
    }
  },
  {
    id: 'Coats',
    required: false,
    clickable: true,
    sortOrder: 42,
    styles: {
      '01': {
        left: 60,
        top: 411
      },
      '02': {
        left: 60,
        top: 400
      },
      '03': {
        left: 59,
        top: 400
      },
      '04': {
        left: 58,
        top: 397
      },
      '05': {
        left: 60,
        top: 403
      },
      '06': {
        left: 60,
        top: 399
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Glasses',
    required: false,
    clickable: true,
    sortOrder: 80,
    styles: {
      '10': {
        left: 228,
        top: 296
      },
      '01': {
        left: 228,
        top: 296
      },
      '02': {
        left: 227,
        top: 296
      },
      '03': {
        left: 228,
        top: 295
      },
      '04': {
        left: 228,
        top: 295
      },
      '05': {
        left: 227,
        top: 293
      },
      '06': {
        left: 228,
        top: 293
      },
      '07': {
        left: 228,
        top: 295
      },
      '08': {
        left: 227,
        top: 293
      },
      '09': {
        left: 228,
        top: 295
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Scarves',
    required: false,
    clickable: true,
    sortOrder: 42,
    styles: {
      '01': {
        left: 155,
        top: 434
      },
      '02': {
        left: 153,
        top: 426
      },
      '03': {
        left: 154,
        top: 426
      },
      '04': {
        left: 155,
        top: 429
      },
      '05': {
        left: 155,
        top: 426
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Shirts',
    required: false,
    clickable: true,
    sortOrder: 10,
    styles: {
      '01': {
        left: 110,
        top: 465
      },
      '02': {
        left: 111,
        top: 465
      },
      '03': {
        left: 111,
        top: 465
      },
      '04': {
        left: 110,
        top: 465
      },
      '05': {
        left: 109,
        top: 465
      },
      '06': {
        left: 110,
        top: 465
      },
      '07': {
        left: 110,
        top: 465
      }
    },
    menuItem: 'Clothes',
    conflicts: {
      types: [
        '01',
        '02',
        '03'
      ],
      assets: [
        'Ties'
      ],
      message: 'Sorry but you can\'t use ties with this shirt'
    }
  },
  {
    id: 'Sweatshirts',
    required: false,
    clickable: true,
    sortOrder: 30,
    styles: {
      '01': {
        left: 114,
        top: 453
      },
      '02': {
        left: 115,
        top: 451
      },
      '03': {
        left: 117,
        top: 447
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'T-shirts',
    required: false,
    clickable: true,
    sortOrder: 10,
    styles: {
      '01': {
        left: 111,
        top: 473
      },
      '02': {
        left: 113,
        top: 473
      },
      '03': {
        left: 111,
        top: 473
      },
      '04': {
        left: 111,
        top: 471
      },
      '05': {
        left: 111,
        top: 473
      },
      '06': {
        left: 111,
        top: 473
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Ties',
    required: false,
    clickable: true,
    sortOrder: 20,
    styles: {
      '01': {
        left: 308,
        top: 536
      },
      '02': {
        left: 309,
        top: 539
      },
      '03': {
        left: 310,
        top: 536
      },
      '04': {
        left: 310,
        top: 539
      },
      '05': {
        left: 310,
        top: 539
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Eyes',
    required: true,
    clickable: true,
    sortOrder: 70,
    styles: {
      blue: {
        left: 286,
        top: 284
      },
      brown: {
        left: 286,
        top: 284
      },
      green: {
        left: 286,
        top: 284
      }
    }
  },
  {
    id: 'Jackets',
    required: false,
    clickable: true,
    sortOrder: 40,
    styles: {
      '01': {
        left: 62,
        top: 406
      },
      '02': {
        left: 62,
        top: 411
      },
      '03': {
        left: 62,
        top: 406
      },
      '04': {
        left: 62,
        top: 406
      },
      '05': {
        left: 64,
        top: 406
      },
      '06': {
        left: 62,
        top: 406
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Mouths',
    required: true,
    clickable: true,
    sortOrder: 20,
    styles: {
      '01': {
        left: 311,
        top: 405
      },
      '02': {
        left: 311,
        top: 407
      },
      '03': {
        left: 311,
        top: 407
      }
    }
  },
  {
    id: 'Vests',
    required: false,
    clickable: true,
    sortOrder: 40,
    styles: {
      '01': {
        left: 61,
        top: 411
      },
      '02': {
        left: 62,
        top: 408
      },
      '03': {
        left: 63,
        top: 404
      },
      '04': {
        left: 63,
        top: 404
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Hats',
    required: false,
    clickable: true,
    sortOrder: 80,
    styles: {
      '01': {
        left: 113,
        top: -78
      },
      '02': {
        left: 116,
        top: -78
      },
      '03': {
        left: 121,
        top: -78
      },
      '04': {
        left: 116,
        top: -67
      },
      '05': {
        left: 119,
        top: -78
      },
      '06': {
        left: 119,
        top: -78
      },
      '07': {
        left: 121,
        top: -78
      },
      '08': {
        left: 122,
        top: -78
      },
      '09': {
        left: 116,
        top: -68
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Suits',
    required: false,
    clickable: true,
    sortOrder: 41,
    styles: {
      '01': {
        left: 63,
        top: 411
      },
      '02': {
        left: 64,
        top: 406
      }
    },
    menuItem: 'Clothes'
  }
], 'id');
<commit_msg>Add new positions<commit_after>import keyBy from 'lodash/keyBy';

export default keyBy([
  {
    id: 'Hairstyles',
    required: false,
    clickable: true,
    sortOrder: 10,
    styles: {
      '10': {
        left: 194,
        top: 57
      },
      '11': {
        left: 197,
        top: 65
      },
      '12': {
        left: 195,
        top: 71
      },
      '13': {
        left: 195,
        top: 71
      },
      '14': {
        left: 194,
        top: 76
      },
      '15': {
        left: 200,
        top: 56
      },
      '16': {
        left: 194,
        top: 54
      },
      '17': {
        left: 195,
        top: 65
      },
      '18': {
        left: 195,
        top: 57
      },
      '19': {
        left: 194,
        top: 46
      },
      '20': {
        left: 195,
        top: 56
      },
      '21': {
        left: 194,
        top: 56
      },
      '22': {
        left: 174,
        top: 46
      },
      '23': {
        left: 193,
        top: 39
      },
      '01': {
        left: 194,
        top: 36
      },
      '02': {
        left: 194,
        top: 48
      },
      '03': {
        left: 194,
        top: 28
      },
      '04': {
        left: 194,
        top: 42
      },
      '05': {
        left: 193,
        top: 38
      },
      '06': {
        left: 194,
        top: 48
      },
      '07': {
        left: 194,
        top: 47
      },
      '08': {
        left: 195,
        top: 42
      },
      '09': {
        left: 196,
        top: 39
      }
    }
  },
  {
    id: 'Beards',
    required: false,
    clickable: true,
    sortOrder: 43,
    styles: {
      '10': {
        left: 212,
        top: 273
      },
      '01': {
        left: 207,
        top: 309
      },
      '02': {
        left: 209,
        top: 312
      },
      '03': {
        left: 208,
        top: 311
      },
      '04': {
        left: 209,
        top: 313
      },
      '05': {
        left: 209,
        top: 310
      },
      '06': {
        left: 209,
        top: 312
      },
      '07': {
        left: 209,
        top: 313
      },
      '08': {
        left: 209,
        top: 312
      },
      '09': {
        left: 211,
        top: 270
      }
    }
  },
  {
    id: 'Body',
    required: true,
    clickable: false,
    sortOrder: 0,
    styles: {
      '01': {
        left: 148,
        top: 165
      }
    }
  },
  {
    id: 'Coats',
    required: false,
    clickable: true,
    sortOrder: 42,
    styles: {
      '01': {
        left: 60,
        top: 411
      },
      '02': {
        left: 60,
        top: 400
      },
      '03': {
        left: 59,
        top: 400
      },
      '04': {
        left: 58,
        top: 397
      },
      '05': {
        left: 60,
        top: 403
      },
      '06': {
        left: 60,
        top: 399
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Glasses',
    required: false,
    clickable: true,
    sortOrder: 80,
    styles: {
      '10': {
        left: 226,
        top: 296
      },
      '01': {
        left: 226,
        top: 299
      },
      '02': {
        left: 225,
        top: 298
      },
      '03': {
        left: 228,
        top: 295
      },
      '04': {
        left: 228,
        top: 295
      },
      '05': {
        left: 225,
        top: 295
      },
      '06': {
        left: 227,
        top: 296
      },
      '07': {
        left: 226,
        top: 296
      },
      '08': {
        left: 224,
        top: 296
      },
      '09': {
        left: 227,
        top: 297
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Scarves',
    required: false,
    clickable: true,
    sortOrder: 42,
    styles: {
      '01': {
        left: 155,
        top: 434
      },
      '02': {
        left: 153,
        top: 426
      },
      '03': {
        left: 154,
        top: 426
      },
      '04': {
        left: 155,
        top: 429
      },
      '05': {
        left: 155,
        top: 426
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Shirts',
    required: false,
    clickable: true,
    sortOrder: 10,
    styles: {
      '01': {
        left: 110,
        top: 465
      },
      '02': {
        left: 111,
        top: 465
      },
      '03': {
        left: 111,
        top: 465
      },
      '04': {
        left: 110,
        top: 465
      },
      '05': {
        left: 109,
        top: 465
      },
      '06': {
        left: 110,
        top: 465
      },
      '07': {
        left: 110,
        top: 465
      }
    },
    menuItem: 'Clothes',
    conflicts: {
      types: [
        '01',
        '02',
        '03'
      ],
      assets: [
        'Ties'
      ],
      message: 'Sorry but you can\'t use ties with this shirt'
    }
  },
  {
    id: 'Sweatshirts',
    required: false,
    clickable: true,
    sortOrder: 30,
    styles: {
      '01': {
        left: 114,
        top: 453
      },
      '02': {
        left: 115,
        top: 451
      },
      '03': {
        left: 116,
        top: 447
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'T-shirts',
    required: false,
    clickable: true,
    sortOrder: 10,
    styles: {
      '01': {
        left: 110,
        top: 473
      },
      '02': {
        left: 113,
        top: 473
      },
      '03': {
        left: 110,
        top: 472
      },
      '04': {
        left: 110,
        top: 470
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Ties',
    required: false,
    clickable: true,
    sortOrder: 20,
    styles: {
      '01': {
        left: 308,
        top: 536
      },
      '02': {
        left: 309,
        top: 539
      },
      '03': {
        left: 310,
        top: 536
      },
      '04': {
        left: 310,
        top: 539
      },
      '05': {
        left: 310,
        top: 539
      }
    },
    menuItem: 'Accessories'
  },
  {
    id: 'Eyes',
    required: true,
    clickable: true,
    sortOrder: 70,
    styles: {
      blue: {
        left: 285,
        top: 291
      },
      brown: {
        left: 285,
        top: 292
      },
      green: {
        left: 285,
        top: 292
      }
    }
  },
  {
    id: 'Jackets',
    required: false,
    clickable: true,
    sortOrder: 40,
    styles: {
      '01': {
        left: 62,
        top: 406
      },
      '02': {
        left: 62,
        top: 411
      },
      '03': {
        left: 62,
        top: 406
      },
      '04': {
        left: 62,
        top: 406
      },
      '05': {
        left: 64,
        top: 406
      },
      '06': {
        left: 62,
        top: 406
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Mouths',
    required: true,
    clickable: true,
    sortOrder: 20,
    styles: {
      '01': {
        left: 311,
        top: 405
      },
      '02': {
        left: 311,
        top: 407
      },
      '03': {
        left: 311,
        top: 407
      }
    }
  },
  {
    id: 'Vests',
    required: false,
    clickable: true,
    sortOrder: 40,
    styles: {
      '01': {
        left: 61,
        top: 411
      },
      '02': {
        left: 62,
        top: 408
      },
      '03': {
        left: 63,
        top: 404
      },
      '04': {
        left: 63,
        top: 404
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Hats',
    required: false,
    clickable: true,
    sortOrder: 80,
    styles: {
      '01': {
        left: 113,
        top: -78
      },
      '02': {
        left: 116,
        top: -78
      },
      '03': {
        left: 121,
        top: -78
      },
      '04': {
        left: 116,
        top: -67
      },
      '05': {
        left: 119,
        top: -78
      },
      '06': {
        left: 119,
        top: -78
      },
      '07': {
        left: 121,
        top: -78
      },
      '08': {
        left: 122,
        top: -78
      },
      '09': {
        left: 116,
        top: -68
      }
    },
    menuItem: 'Clothes'
  },
  {
    id: 'Suits',
    required: false,
    clickable: true,
    sortOrder: 41,
    styles: {
      '01': {
        left: 63,
        top: 411
      },
      '02': {
        left: 64,
        top: 406
      }
    },
    menuItem: 'Clothes'
  }
], 'id');
<|endoftext|>


<commit_before>/**
* String validates a given text as string.
* @name String
* @class String
* @interface
* @augments ch.Controls
* @augments ch.Validation
* @memberOf ch
* @param {Object} [conf] Object with configuration properties.
* @param {String} [conf.message] Validation message.
* @param {String} [conf.points] Sets the points where validation-bubble will be positioned.
* @param {String} [conf.offset] Sets the offset in pixels that validation-bubble will be displaced from original position determined by points. It's specified by configuration or zero by default: "0 0".
* @param {String} [conf.context] It's a reference to position the validation-bubble
* @returns itself
* @factorized
* @see ch.Validation
* @see ch.Required
* @see ch.Custom
* @see ch.Number
* @see ch.Validator
* @see ch.Condition
* @exampleDescription Create a string validation
* @example
* $("input").string("This field must be a string.");
*/
ch.extend("validation").as("string", function (conf) {

	conf.condition = {
		name: "string",
		// the following regular expression has the utf code for the lating characters
		// the ranges are A,EI,O,U,a,ei,o,u,ç,Ç please for reference see http://www.fileformat.info/info/charset/UTF-8/list.htm
		patt: /^([a-zA-Z\u00C0-\u00C4\u00C8-\u00CF\u00D2-\u00D6\u00D9-\u00DC\u00E0-\u00E4\u00E8-\u00EF\u00F2-\u00F6\u00E9-\u00FC\u00C7\u00E7\s]*)$/,
		message: conf.msg || conf.message
	};

	return conf;

});

/**
* Email validates a correct email syntax.
* @name Email
* @class Email
* @interface
* @augments ch.Controls
* @augments ch.Validation
* @requires ch.Validation
* @memberOf ch
* @param {Object} [conf] Object with configuration properties.
* @param {String} [conf.message] Validation message.
* @param {String} [conf.points] Sets the points where validation-bubble will be positioned.
* @param {String} [conf.offset] Sets the offset in pixels that validation-bubble will be displaced from original position determined by points. It's specified by configuration or zero by default: "0 0".
* @param {String} [conf.context] It's a reference to position the validation-bubble
* @returns itself
* @factorized
* @see ch.Validation
* @see ch.Required
* @see ch.Custom
* @see ch.Number
* @see ch.Validator
* @see ch.Condition
* @exampleDescription Create a email validation
* @example
* $("input").email("This field must be a valid email.");
*/
ch.extend("validation").as("email", function (conf) {

	conf.condition = {
		name: "email",
		patt: /^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/,
		message: conf.msg || conf.message
	};

	return conf;

});

/**
* Url validates URL syntax.
* @name Url
* @class Url
* @interface
* @augments ch.Controls
* @augments ch.Validation
* @requires ch.Validation
* @memberOf ch
* @param {Object} [conf] Object with configuration properties.
* @param {String} [conf.message] Validation message.
* @param {String} [conf.points] Sets the points where validation-bubble will be positioned.
* @param {String} [conf.offset] Sets the offset in pixels that validation-bubble will be displaced from original position determined by points. It's specified by configuration or zero by default: "0 0".
* @param {String} [conf.context] It's a reference to position the validation-bubble
* @returns itself
* @factorized
* @see ch.Validation
* @see ch.Required
* @see ch.Custom
* @see ch.Number
* @see ch.Validator
* @see ch.Condition
* @exampleDescription Create a URL validation
* @example
* $("input").url("This field must be a valid URL.");
*/
ch.extend("validation").as("url", function (conf) {

	conf.condition = {
		name: "url",
		patt: /^((https?|ftp|file):\/\/|((www|ftp)\.)|(\/|.*\/)*)[a-z0-9-]+((\.|\/)[a-z0-9-]+)+([/?].*)?$/,
		message: conf.msg || conf.message
	};

	return conf;

});

/**
* MinLength validates a minimun amount of characters.
* @name MinLength
* @class MinLength
* @interface
* @augments ch.Controls
* @augments ch.Validation
* @requires ch.Validation
* @memberOf ch
* @param {Object} [conf] Object with configuration properties.
* @param {String} [conf.message] Validation message.
* @param {String} [conf.points] Sets the points where validation-bubble will be positioned.
* @param {String} [conf.offset] Sets the offset in pixels that validation-bubble will be displaced from original position determined by points. It's specified by configuration or zero by default: "0 0".
* @param {String} [conf.context] It's a reference to position the validation-bubble
* @param {Number} value Minimun number value.
* @returns itself
* @factorized
* @see ch.Validation
* @see ch.Required
* @see ch.Custom
* @see ch.Number
* @see ch.Validator
* @see ch.Condition
* @exampleDescription Create a minLength validation
* @example
* $("input").minLength(10, "At least 10 characters..");
*/
ch.extend("validation").as("minLength", function (conf) {

	conf.condition = {
		name: "minLength",
		expr: function(a,b) { return a.length >= b },
		message: conf.msg || conf.message,
		value: conf.value
	};

	return conf;

});

/**
* MaxLength validates a maximun amount of characters.
* @name MaxLength
* @class MaxLength
* @interface
* @augments ch.Controls
* @augments ch.Validation
* @requires ch.Validation
* @memberOf ch
* @param {Object} [conf] Object with configuration properties.
* @param {String} [conf.message] Validation message.
* @param {String} [conf.points] Sets the points where validation-bubble will be positioned.
* @param {String} [conf.offset] Sets the offset in pixels that validation-bubble will be displaced from original position determined by points. It's specified by configuration or zero by default: "0 0".
* @param {String} [conf.context] It's a reference to position the validation-bubble
* @param {Number} value Maximun number value.
* @returns itself
* @factorized
* @see ch.Validation
* @see ch.Required
* @see ch.Custom
* @see ch.Number
* @see ch.Validator
* @see ch.Condition
* @exampleDescription Create a maxLength validation
* @example
* $("input").maxLength(10, "No more than 10 characters..");
*/
ch.extend("validation").as("maxLength", function (conf) {

	conf.condition = {
		name: "maxLength",
		expr: function(a,b) { return a.length <= b },
		message: conf.msg || conf.message,
		value: conf.value
	};

	return conf;

});<commit_msg>GH-669 The email validation is using W3C RegExp for email validation<commit_after>/**
* String validates a given text as string.
* @name String
* @class String
* @interface
* @augments ch.Controls
* @augments ch.Validation
* @memberOf ch
* @param {Object} [conf] Object with configuration properties.
* @param {String} [conf.message] Validation message.
* @param {String} [conf.points] Sets the points where validation-bubble will be positioned.
* @param {String} [conf.offset] Sets the offset in pixels that validation-bubble will be displaced from original position determined by points. It's specified by configuration or zero by default: "0 0".
* @param {String} [conf.context] It's a reference to position the validation-bubble
* @returns itself
* @factorized
* @see ch.Validation
* @see ch.Required
* @see ch.Custom
* @see ch.Number
* @see ch.Validator
* @see ch.Condition
* @exampleDescription Create a string validation
* @example
* $("input").string("This field must be a string.");
*/
ch.extend("validation").as("string", function (conf) {

	conf.condition = {
		name: "string",
		// the following regular expression has the utf code for the lating characters
		// the ranges are A,EI,O,U,a,ei,o,u,ç,Ç please for reference see http://www.fileformat.info/info/charset/UTF-8/list.htm
		patt: /^([a-zA-Z\u00C0-\u00C4\u00C8-\u00CF\u00D2-\u00D6\u00D9-\u00DC\u00E0-\u00E4\u00E8-\u00EF\u00F2-\u00F6\u00E9-\u00FC\u00C7\u00E7\s]*)$/,
		message: conf.msg || conf.message
	};

	return conf;

});

/**
* Email validates a correct email syntax.
* @name Email
* @class Email
* @interface
* @augments ch.Controls
* @augments ch.Validation
* @requires ch.Validation
* @memberOf ch
* @param {Object} [conf] Object with configuration properties.
* @param {String} [conf.message] Validation message.
* @param {String} [conf.points] Sets the points where validation-bubble will be positioned.
* @param {String} [conf.offset] Sets the offset in pixels that validation-bubble will be displaced from original position determined by points. It's specified by configuration or zero by default: "0 0".
* @param {String} [conf.context] It's a reference to position the validation-bubble
* @returns itself
* @factorized
* @see ch.Validation
* @see ch.Required
* @see ch.Custom
* @see ch.Number
* @see ch.Validator
* @see ch.Condition
* @exampleDescription Create a email validation
* @example
* $("input").email("This field must be a valid email.");
*/
ch.extend("validation").as("email", function (conf) {
	// OLD RegExp
	// /^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/,
	conf.condition = {
		name: "email",
		patt: /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
		message: conf.msg || conf.message
	};

	return conf;

});

/**
* Url validates URL syntax.
* @name Url
* @class Url
* @interface
* @augments ch.Controls
* @augments ch.Validation
* @requires ch.Validation
* @memberOf ch
* @param {Object} [conf] Object with configuration properties.
* @param {String} [conf.message] Validation message.
* @param {String} [conf.points] Sets the points where validation-bubble will be positioned.
* @param {String} [conf.offset] Sets the offset in pixels that validation-bubble will be displaced from original position determined by points. It's specified by configuration or zero by default: "0 0".
* @param {String} [conf.context] It's a reference to position the validation-bubble
* @returns itself
* @factorized
* @see ch.Validation
* @see ch.Required
* @see ch.Custom
* @see ch.Number
* @see ch.Validator
* @see ch.Condition
* @exampleDescription Create a URL validation
* @example
* $("input").url("This field must be a valid URL.");
*/
ch.extend("validation").as("url", function (conf) {

	conf.condition = {
		name: "url",
		patt: /^((https?|ftp|file):\/\/|((www|ftp)\.)|(\/|.*\/)*)[a-z0-9-]+((\.|\/)[a-z0-9-]+)+([/?].*)?$/,
		message: conf.msg || conf.message
	};

	return conf;

});

/**
* MinLength validates a minimun amount of characters.
* @name MinLength
* @class MinLength
* @interface
* @augments ch.Controls
* @augments ch.Validation
* @requires ch.Validation
* @memberOf ch
* @param {Object} [conf] Object with configuration properties.
* @param {String} [conf.message] Validation message.
* @param {String} [conf.points] Sets the points where validation-bubble will be positioned.
* @param {String} [conf.offset] Sets the offset in pixels that validation-bubble will be displaced from original position determined by points. It's specified by configuration or zero by default: "0 0".
* @param {String} [conf.context] It's a reference to position the validation-bubble
* @param {Number} value Minimun number value.
* @returns itself
* @factorized
* @see ch.Validation
* @see ch.Required
* @see ch.Custom
* @see ch.Number
* @see ch.Validator
* @see ch.Condition
* @exampleDescription Create a minLength validation
* @example
* $("input").minLength(10, "At least 10 characters..");
*/
ch.extend("validation").as("minLength", function (conf) {

	conf.condition = {
		name: "minLength",
		expr: function(a,b) { return a.length >= b },
		message: conf.msg || conf.message,
		value: conf.value
	};

	return conf;

});

/**
* MaxLength validates a maximun amount of characters.
* @name MaxLength
* @class MaxLength
* @interface
* @augments ch.Controls
* @augments ch.Validation
* @requires ch.Validation
* @memberOf ch
* @param {Object} [conf] Object with configuration properties.
* @param {String} [conf.message] Validation message.
* @param {String} [conf.points] Sets the points where validation-bubble will be positioned.
* @param {String} [conf.offset] Sets the offset in pixels that validation-bubble will be displaced from original position determined by points. It's specified by configuration or zero by default: "0 0".
* @param {String} [conf.context] It's a reference to position the validation-bubble
* @param {Number} value Maximun number value.
* @returns itself
* @factorized
* @see ch.Validation
* @see ch.Required
* @see ch.Custom
* @see ch.Number
* @see ch.Validator
* @see ch.Condition
* @exampleDescription Create a maxLength validation
* @example
* $("input").maxLength(10, "No more than 10 characters..");
*/
ch.extend("validation").as("maxLength", function (conf) {

	conf.condition = {
		name: "maxLength",
		expr: function(a,b) { return a.length <= b },
		message: conf.msg || conf.message,
		value: conf.value
	};

	return conf;

});<|endoftext|>


<commit_before>/*global Tabris: true, ClientBridge: false */
(function() {

  Tabris = {

    create : function( type, properties ) {
      var id = generateId();
      ClientBridge._processCreate( id, fixType( type ), fixProperties( properties ) );
      return new WidgetProxy( id );
    },

    createPage : function( title, toplevel ) {
      if( !Tabris._isInitialized ) {
        Tabris._initialize();
      }
      var composite = Tabris.create( "rwt.widgets.Composite", {
        parent: Tabris._shell.id,
        layoutData: { left: 0, right: 0, top: 0, bottom: 0 }
      });
      var page = Tabris.create( "tabris.Page", {
        parent: Tabris._UI.id,
        control: composite.id,
        title: title,
        topLevel: toplevel
      });
      composite.open = function() {
        Tabris._UI.set( "activePage", page.id );
      };
      composite.close = function() {
        // TODO
      };
      return composite;
    },

    _initialize : function() {
      Tabris._isInitialized = true;
      ClientBridge._processHead( "tabris.UI", true );
      Tabris.create( "rwt.widgets.Display" );
      Tabris._shell = Tabris.create( "rwt.widgets.Shell", {
        style: ["NO_TRIM"],
        mode: "maximized",
        active: true,
        visibility: true
      });
      Tabris._UI = Tabris.create( "tabris.UI", {
        shell: Tabris._shell.id
      });
      Tabris._UI.on( "ShowPage", function( properties ) {
        var page = proxies[ properties.pageId ];
        Tabris._UI.set( "activePage", page.id );
      });
    }

  };

  var proxies = {};

  var WidgetProxy = function( id ) {
    this.id = id;
    proxies[id] = this;
  };

  WidgetProxy.prototype = {

    get: function( method ) {
      return ClientBridge._processGet( this.id, method );
    },

    set: function( arg1, arg2 ) {
      var properties;
      if( typeof arg1 === "string" ) {
        properties = {};
        properties[arg1] = arg2;
      } else {
        properties = arg1;
      }
      ClientBridge._processSet( this.id, fixProperties( properties ) );
      return this;
    },

    call: function( method, parameters ) {
      ClientBridge._processCall( this.id, method, parameters );
      return this;
    },

    on: function( event, listener /*, options*/ ) {
      ClientBridge._processListen( this.id, event, true, listener );
      return this;
    },

    destroy: function() {
      ClientBridge._processDestroy( this.id );
      delete proxies[this.id];
    },

    append: function( type, properties ) {
      return Tabris.create( type, merge( properties, { parent: this.id } ) );
    }

  };

  var translateWidgetToId = function( value ) {
    if( typeof value === 'object' && value.id ) {
      return value.id;
    }
    return value;
  };

  var fixLayoutData = function( data ) {
    for( var key in data ) {
      if( Array.isArray( data[key] ) ) {
        for( var i = 0; i < data[key].length; i++ ) {
          data[key][i] = translateWidgetToId( data[key][i] );
        }
      } else {
        data[key] = translateWidgetToId( data[key] );
      }
    }
    return data;
  };

  var fixProperties = function( properties ) {
    for( var key in properties ) {
      if( key === 'layoutData' ) {
        properties[key] = fixLayoutData( properties[key] );
      }
    }
    return properties;
  };

  var fixType = function( type ) {
    if( type.indexOf( '.' ) === -1 ) {
      return "rwt.widgets." + type;
    }
    return type;
  };

  var idSequence = 1;

  var generateId = function() {
    return "o" + ( idSequence++ );
  };

  var merge = function( o1, o2 ) {
    var result = {};
    var name;
    for( name in o1 ) {
      result[name] = o1[name];
    }
    for( name in o2 ) {
      result[name] = o2[name];
    }
    return result;
  };

})();
<commit_msg>Move proxies map to Tabris object to make it accessible<commit_after>/*global Tabris: true, ClientBridge: false */
(function() {

  Tabris = {

    create : function( type, properties ) {
      var id = generateId();
      ClientBridge._processCreate( id, fixType( type ), fixProperties( properties ) );
      return new WidgetProxy( id );
    },

    createPage : function( title, toplevel ) {
      if( !Tabris._isInitialized ) {
        Tabris._initialize();
      }
      var composite = Tabris.create( "rwt.widgets.Composite", {
        parent: Tabris._shell.id,
        layoutData: { left: 0, right: 0, top: 0, bottom: 0 }
      });
      var page = Tabris.create( "tabris.Page", {
        parent: Tabris._UI.id,
        control: composite.id,
        title: title,
        topLevel: toplevel
      });
      composite.open = function() {
        Tabris._UI.set( "activePage", page.id );
      };
      composite.close = function() {
        // TODO
        // destroy composite
        // set active page
        // destroy page
      };
      return composite;
    },

    _initialize : function() {
      Tabris._isInitialized = true;
      ClientBridge._processHead( "tabris.UI", true );
      Tabris.create( "rwt.widgets.Display" );
      Tabris._shell = Tabris.create( "rwt.widgets.Shell", {
        style: ["NO_TRIM"],
        mode: "maximized",
        active: true,
        visibility: true
      });
      Tabris._UI = Tabris.create( "tabris.UI", {
        shell: Tabris._shell.id
      });
      Tabris._UI.on( "ShowPage", function( properties ) {
        var page = Tabris._proxies[ properties.pageId ];
        Tabris._UI.set( "activePage", page.id );
      });
    },

    _proxies: {}

  };

  var WidgetProxy = function( id ) {
    this.id = id;
    Tabris._proxies[id] = this;
  };

  WidgetProxy.prototype = {

    get: function( method ) {
      return ClientBridge._processGet( this.id, method );
    },

    set: function( arg1, arg2 ) {
      var properties;
      if( typeof arg1 === "string" ) {
        properties = {};
        properties[arg1] = arg2;
      } else {
        properties = arg1;
      }
      ClientBridge._processSet( this.id, fixProperties( properties ) );
      return this;
    },

    call: function( method, parameters ) {
      ClientBridge._processCall( this.id, method, parameters );
      return this;
    },

    on: function( event, listener /*, options*/ ) {
      ClientBridge._processListen( this.id, event, true, listener );
      return this;
    },

    destroy: function() {
      ClientBridge._processDestroy( this.id );
      delete Tabris._proxies[this.id];
    },

    append: function( type, properties ) {
      return Tabris.create( type, merge( properties, { parent: this.id } ) );
    }

  };

  var translateWidgetToId = function( value ) {
    if( typeof value === 'object' && value.id ) {
      return value.id;
    }
    return value;
  };

  var fixLayoutData = function( data ) {
    for( var key in data ) {
      if( Array.isArray( data[key] ) ) {
        for( var i = 0; i < data[key].length; i++ ) {
          data[key][i] = translateWidgetToId( data[key][i] );
        }
      } else {
        data[key] = translateWidgetToId( data[key] );
      }
    }
    return data;
  };

  var fixProperties = function( properties ) {
    for( var key in properties ) {
      if( key === 'layoutData' ) {
        properties[key] = fixLayoutData( properties[key] );
      }
    }
    return properties;
  };

  var fixType = function( type ) {
    if( type.indexOf( '.' ) === -1 ) {
      return "rwt.widgets." + type;
    }
    return type;
  };

  var idSequence = 1;

  var generateId = function() {
    return "o" + ( idSequence++ );
  };

  var merge = function( o1, o2 ) {
    var result = {};
    var name;
    for( name in o1 ) {
      result[name] = o1[name];
    }
    for( name in o2 ) {
      result[name] = o2[name];
    }
    return result;
  };

})();
<|endoftext|>


<commit_before>'use strict';
require("style/keyboard.css");
//var kana = require('./kana.js');

var debug = true;
function setAttribute(parentElement, name, key, value){
    if(! parentElement) {
        return;
    }
    var elements = parentElement.getElementsByTagName(name);
    if(elements){
        for(var i=0; i<elements.length;i++){elements[i].setAttribute(key, value);}
    }else{
        parentElement.setAttribute(key, value);
    }
}

function removeAllChildrenNode(element){
    while(element.hasChildNodes()){
        element.removeChild(element.lastChild);
    }
}

function TextSource(){
    this.index = -1;
    this.src = ["hogehoge","abcdefg", "AAABBBCCC", "Japanese Touch-typing Practice"];

    this.getNextText = function(){
        this.index++;
        if(this.index == this.src.length) {
            this.index = 0;
        }
        return this.src[this.index];
    };
}

function MainPanel(document, element, callback){
    this.document = document;
    this.element = element;
    this._isEnabled = false;

    this.setEnabled = function(isEnabled){
        if(isEnabled){
            this.element.setAttribute('style', 'background-color: #fff');
        }else{
            this.element.setAttribute('style', 'background-color: #bbb;');
        }
        this._isEnabled = isEnabled;
    };

    this.isEnabled = function(){
        return this._isEnabled;
    };

    this.setSVGDocument = function(svgDocument){
        this.svgDocument = svgDocument;
    };

    this.setKeyFocused = function(code, fillColor){
        if(this.svgDocument){
            setAttribute(this.svgDocument.getElementById("code-"+code), 'path', 'fill', fillColor);
        }
    };

    this.initialize = function(callback){
        this.setEnabled(false);
        this.objectElement = this.document.createElement('object');
        this.objectElement.setAttribute('tabindex','0');
        this.objectElement.setAttribute('type','image/svg+xml');
        this.objectElement.setAttribute('width','1190');
        this.objectElement.setAttribute('height','348');
        this.objectElement.setAttribute('data', 'img/keyboard_ja.svg');

        this.document.getElementById('keyboard').appendChild(this.objectElement);

        var _this = this;
        this.objectElement.addEventListener("load", function(){
            _this.setSVGDocument(_this.objectElement.getSVGDocument());
            callback();
        });
    };

    this.initialize(callback);
}

function PromptField(document, element){
    this.element = element;
    this._isEnabled = false;

    this.clearText = function(){
        removeAllChildrenNode(this.element);
    };

    this.setText = function(text){
        this.clearText();
        text.split('').forEach(function(c){
            var span1 = document.createElement('span');
            var span2 = document.createElement('span');
            span1.appendChild(span2);
            span2.appendChild(document.createTextNode(c));
            element.appendChild(span1);
        });
    };

    this.setEnabled = function(isEnabled){
        this._isEnabled = isEnabled;
        setAttribute(this.element, 'span', 'style', 'border-left-color:'+(isEnabled?'#fff':'#bbb'));
    };

    this.isEnabled = function(){
        return this._isEnabled;
    };

    this.setEnabled(false);
}

function InputField(document, element){
    this.element = element;

    this.clearCursor = function(){
        for(var i = 0; i < this.element.childElementCount; i++){
            var item = this.element.childNodes.item(i);
            if(item.hasAttribute('class')){
                item.removeAttribute('class');
            }
        }
    };

    this.setCursor = function(cursor) {
        if(cursor < this.element.childNodes.length){
            this.element.childNodes.item(cursor).setAttribute('class', 'cursor');
        }
    };

    this.setCursorBlink = function(cursorBlink){
        for(var i = 0; i < this.element.childElementCount; i++){
            var item = this.element.childNodes.item(i);
            var clazz = item.getAttribute('class');
            if(clazz){
                if(cursorBlink){
                    item.setAttribute('class', 'cursor');
                }else{
                    item.setAttribute('class', '_cursor');
                }
            }
        }
    };

    this.clearText = function(){
        while(1 < this.element.childElementCount){
            this.element.removeChild(this.element.firstElementChild);
        }
    };

    this.removeTextAfter = function(cursor){
        var count = this.element.childElementCount - 1;
        for(var i = cursor ; i < count; i++){
            this.element.removeChild(this.element.childNodes.item(cursor));
        }
    };

    this.removeCharAt = function(cursor){
        var target = this.element.childNodes.item(cursor);
        this.element.removeChild(target);
    };

    this.length = function(){
        return this.element.childElementCount - 1;
    };

    this.appendChar = function(cursor, ch){
        var span1 = document.createElement('span');
        var span2 = document.createElement('span');
        span1.appendChild(span2);
        span2.appendChild(document.createTextNode(ch));
        var target = this.element.childNodes.item(cursor);
        this.element.insertBefore(span1, target);
    };

    this.setEnabled = function(isEnabled){
        setAttribute(this.element, 'span', 'style', 'border-left-color:'+(isEnabled?'#fff':'#bbb'));
    };

    this.setEnabled(false);
    this.setCursorBlink(false);

}

var src = new TextSource();

function setupTypingPracticeElement(document){

    var mainPanel = new MainPanel(document, document.getElementById("main"), function(){
        var promptField = new PromptField(document, document.getElementById('prompt'));
        var inputField = new InputField(document, document.getElementById('typed'));
        promptField.setText(src.getNextText());

        var cursor = 0;
        mainPanel.setEnabled(false);
        promptField.setEnabled(false);
        inputField.setEnabled(false);
        inputField.setCursorBlink(false);

        document.addEventListener("mousedown", function (){
            mainPanel.setEnabled(true);
            promptField.setEnabled(true);
            inputField.setEnabled(true);
            inputField.setCursorBlink(true);
        }, false);

        document.addEventListener("mouseleave", function (){
            mainPanel.setEnabled(false);
            promptField.setEnabled(false);
            inputField.setEnabled(false);
            inputField.setCursorBlink(false);
        }, false);

        document.addEventListener("keydown", function (e) {
            if(! mainPanel.isEnabled()){
                return;
            }

            var code = e.keyCode ? e.keyCode : e.which;
            if(debug){
                console.log(code);
            }

            if(code == 9){//tab
                return;
            }

            mainPanel.setKeyFocused(code, (e.key.length == 1)?'red':'yellow');

            inputField.clearCursor();

            if(code == 13){//Enter
                inputField.clearText();
                promptField.clearText();
                promptField.setText(src.getNextText());
                cursor = 0;
            } if(code == 75/*'k'*/ && e.ctrlKey){
                inputField.removeTextAfter(cursor);
            }else if(code == 8 || (code == 72/*'h'*/ && e.ctrlKey)){ // backspace
                if(0 < cursor){
                    cursor -= 1;
                    inputField.removeCharAt(cursor);
                }
            }else if(code == 37 || (code == 66 /*'b'*/ && e.ctrlKey)){ // backward
                if(0 < cursor){
                    cursor -= 1;
                }
            }else if(code == 39 || (code == 70 /*'f'*/ && e.ctrlKey)){ // forward
                if(cursor < inputField.length()) {
                    cursor += 1;
                }
            }else if(code == 65 /*'a'*/ && e.ctrlKey){ // ahead
                cursor = 0;
            }else if(code == 69 /*'e'*/ && e.ctrlKey){ // end
                cursor = inputField.length();
            }else if(e.key.length == 1){
                inputField.appendChar(cursor, e.key);
                cursor += 1;
            }
            inputField.setCursor(cursor);
        }, false);

        document.addEventListener("keyup", function (e) {
            if(! mainPanel.isEnabled()){
                return;
            }
            var code = e.keyCode ? e.keyCode : e.which;
            mainPanel.setKeyFocused(code, 'none');
        }, false);

    });

}

setupTypingPracticeElement(document);

module.exports = {
    setupTypingPracticeElement:setupTypingPracticeElement
};
<commit_msg>reduce using a global variable<commit_after>'use strict';
require("style/keyboard.css");
//var kana = require('./kana.js');

var debug = true;
function setAttribute(parentElement, name, key, value){
    if(! parentElement) {
        return;
    }
    var elements = parentElement.getElementsByTagName(name);
    if(elements){
        for(var i=0; i<elements.length;i++){elements[i].setAttribute(key, value);}
    }else{
        parentElement.setAttribute(key, value);
    }
}

function removeAllChildrenNode(element){
    while(element.hasChildNodes()){
        element.removeChild(element.lastChild);
    }
}

function TextSource(){
    this.index = -1;
    this.src = ["hogehoge","abcdefg", "AAABBBCCC", "Japanese Touch-typing Practice"];

    this.getNextText = function(){
        this.index++;
        if(this.index == this.src.length) {
            this.index = 0;
        }
        return this.src[this.index];
    };
}

function MainPanel(document, element, callback){
    this.document = document;
    this.element = element;
    this._isEnabled = false;

    this.setEnabled = function(isEnabled){
        if(isEnabled){
            this.element.setAttribute('style', 'background-color: #fff');
        }else{
            this.element.setAttribute('style', 'background-color: #bbb;');
        }
        this._isEnabled = isEnabled;
    };

    this.isEnabled = function(){
        return this._isEnabled;
    };

    this.setSVGDocument = function(svgDocument){
        this.svgDocument = svgDocument;
    };

    this.setKeyFocused = function(code, fillColor){
        if(this.svgDocument){
            setAttribute(this.svgDocument.getElementById("code-"+code), 'path', 'fill', fillColor);
        }
    };

    this.initialize = function(callback){
        this.setEnabled(false);
        this.objectElement = this.document.createElement('object');
        this.objectElement.setAttribute('tabindex','0');
        this.objectElement.setAttribute('type','image/svg+xml');
        this.objectElement.setAttribute('width','1190');
        this.objectElement.setAttribute('height','348');
        this.objectElement.setAttribute('data', 'img/keyboard_ja.svg');

        this.document.getElementById('keyboard').appendChild(this.objectElement);

        var _this = this;
        this.objectElement.addEventListener("load", function(){
            _this.setSVGDocument(_this.objectElement.getSVGDocument());
            callback();
        });
    };

    this.initialize(callback);
}

function PromptField(document, element){
    this.element = element;
    this._isEnabled = false;

    this.clearText = function(){
        removeAllChildrenNode(this.element);
    };

    this.setText = function(text){
        this.clearText();
        text.split('').forEach(function(c){
            var span1 = document.createElement('span');
            var span2 = document.createElement('span');
            span1.appendChild(span2);
            span2.appendChild(document.createTextNode(c));
            element.appendChild(span1);
        });
    };

    this.setEnabled = function(isEnabled){
        this._isEnabled = isEnabled;
        setAttribute(this.element, 'span', 'style', 'border-left-color:'+(isEnabled?'#fff':'#bbb'));
    };

    this.isEnabled = function(){
        return this._isEnabled;
    };

    this.setEnabled(false);
}

function InputField(document, element){
    this.element = element;

    this.clearCursor = function(){
        for(var i = 0; i < this.element.childElementCount; i++){
            var item = this.element.childNodes.item(i);
            if(item.hasAttribute('class')){
                item.removeAttribute('class');
            }
        }
    };

    this.setCursor = function(cursor) {
        if(cursor < this.element.childNodes.length){
            this.element.childNodes.item(cursor).setAttribute('class', 'cursor');
        }
    };

    this.setCursorBlink = function(cursorBlink){
        for(var i = 0; i < this.element.childElementCount; i++){
            var item = this.element.childNodes.item(i);
            var clazz = item.getAttribute('class');
            if(clazz){
                if(cursorBlink){
                    item.setAttribute('class', 'cursor');
                }else{
                    item.setAttribute('class', '_cursor');
                }
            }
        }
    };

    this.clearText = function(){
        while(1 < this.element.childElementCount){
            this.element.removeChild(this.element.firstElementChild);
        }
    };

    this.removeTextAfter = function(cursor){
        var count = this.element.childElementCount - 1;
        for(var i = cursor ; i < count; i++){
            this.element.removeChild(this.element.childNodes.item(cursor));
        }
    };

    this.removeCharAt = function(cursor){
        var target = this.element.childNodes.item(cursor);
        this.element.removeChild(target);
    };

    this.length = function(){
        return this.element.childElementCount - 1;
    };

    this.appendChar = function(cursor, ch){
        var span1 = document.createElement('span');
        var span2 = document.createElement('span');
        span1.appendChild(span2);
        span2.appendChild(document.createTextNode(ch));
        var target = this.element.childNodes.item(cursor);
        this.element.insertBefore(span1, target);
    };

    this.setEnabled = function(isEnabled){
        setAttribute(this.element, 'span', 'style', 'border-left-color:'+(isEnabled?'#fff':'#bbb'));
    };

    this.setEnabled(false);
    this.setCursorBlink(false);

}

function setupTypingPracticeElement(document, src){

    var mainPanel = new MainPanel(document, document.getElementById("main"), function(){
        var promptField = new PromptField(document, document.getElementById('prompt'));
        var inputField = new InputField(document, document.getElementById('typed'));
        promptField.setText(src.getNextText());

        var cursor = 0;
        mainPanel.setEnabled(false);
        promptField.setEnabled(false);
        inputField.setEnabled(false);
        inputField.setCursorBlink(false);

        document.addEventListener("mousedown", function (){
            mainPanel.setEnabled(true);
            promptField.setEnabled(true);
            inputField.setEnabled(true);
            inputField.setCursorBlink(true);
        }, false);

        document.addEventListener("mouseleave", function (){
            mainPanel.setEnabled(false);
            promptField.setEnabled(false);
            inputField.setEnabled(false);
            inputField.setCursorBlink(false);
        }, false);

        document.addEventListener("keydown", function (e) {
            if(! mainPanel.isEnabled()){
                return;
            }

            var code = e.keyCode ? e.keyCode : e.which;
            if(debug){
                console.log(code);
            }

            if(code == 9){//tab
                return;
            }

            mainPanel.setKeyFocused(code, (e.key.length == 1)?'red':'yellow');

            inputField.clearCursor();

            if(code == 13){//Enter
                inputField.clearText();
                promptField.clearText();
                promptField.setText(src.getNextText());
                cursor = 0;
            } if(code == 75/*'k'*/ && e.ctrlKey){
                inputField.removeTextAfter(cursor);
            }else if(code == 8 || (code == 72/*'h'*/ && e.ctrlKey)){ // backspace
                if(0 < cursor){
                    cursor -= 1;
                    inputField.removeCharAt(cursor);
                }
            }else if(code == 37 || (code == 66 /*'b'*/ && e.ctrlKey)){ // backward
                if(0 < cursor){
                    cursor -= 1;
                }
            }else if(code == 39 || (code == 70 /*'f'*/ && e.ctrlKey)){ // forward
                if(cursor < inputField.length()) {
                    cursor += 1;
                }
            }else if(code == 65 /*'a'*/ && e.ctrlKey){ // ahead
                cursor = 0;
            }else if(code == 69 /*'e'*/ && e.ctrlKey){ // end
                cursor = inputField.length();
            }else if(e.key.length == 1){
                inputField.appendChar(cursor, e.key);
                cursor += 1;
            }
            inputField.setCursor(cursor);
        }, false);

        document.addEventListener("keyup", function (e) {
            if(! mainPanel.isEnabled()){
                return;
            }
            var code = e.keyCode ? e.keyCode : e.which;
            mainPanel.setKeyFocused(code, 'none');
        }, false);

    });

}

setupTypingPracticeElement(document, new TextSource());

module.exports = {
    setupTypingPracticeElement:setupTypingPracticeElement
};
<|endoftext|>