<commit_before>/**
 * Autocomplete for jQuery, version 1.10.X
 * (c) 2013 Lloyd Watkin
 * Licensed under the MIT License
 *
 * Heavily modified from http://www.devbridge.com/projects/autocomplete/jquery/
 */
(function($) {

  function Autocomplete(el, options) {
    this.regEx = new RegExp(
      '(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\']
          .join('|\\') + ')',
      'g'
  )
    this.el = $(el)
    this.el.attr('autocomplete', 'off')
    this.suggestions = []
    this.data = []
    this.badQueries = []
    this.selectedIndex = -1
    this.currentValue = this.isFormField() ? this.el.val() : this.el.html()
    this.intervalId = 0
    this.cachedResponse = []
    this.onChangeInterval = null
    this.ignoreValueChange = false
    this.serviceUrl = options.serviceUrl
    this.isLocal = false
    this.options = {
      autoSubmit: false,
      minChars: 1,
      maxHeight: 300,
      deferRequestBy: 0,
      width: 300,
      highlight: true,
      params: {},
      dataKey: null,
      formatResult: this.formatResult,
      delimiter: ' ',
      zIndex: 9999,
      searchPrefix: '',
      searchEverywhere: false,
      appendChars: ' ',
      insertDomContent: this.insertDomContent,
      disableMouseOver: false
    }
    this.initialize()
    this.setOptions(options)
    return this
  }

  $.fn.autocomplete = function(options) {
    return new Autocomplete(this.get(0) || $('<input />'), options)
  }

  Autocomplete.prototype = {

    killerFn: null,

    initialize: function() {

      var self = this
      var uid = Math.floor(Math.random() * 0x100000).toString(16)
      var autocompleteElementId = 'Autocomplete_' + uid

      this.killerFn = function(e) {
        if (0 === $(e.target).parents('.autocomplete').size()) {
          self.killSuggestions()
          self.disableKillerFn()
        }
      }

      if (!this.options.width) this.options.width = this.el.width()
      this.mainContainerId = 'AutocompleteContainer_' + uid
      var identifier = ''
      if (this.options.identifier) {
          identifier = 'data-identifier="' + this.options.identifier + '"'
      }
      $('
' +
        '

')
        .appendTo('body')

      $('#'+this.mainContainerId).css({'position': 'absolute', 'z-index': '9999'})
      $('#'+autocompleteElementId).css({'display':'none', 'width':'300px'})

      this.container = $('#' + autocompleteElementId)
      this.fixPosition()
      if (window.opera) {
        this.el.keypress(function(e) { self.onKeyPress(e) })
      } else {
        this.el.keydown(function(e) { self.onKeyPress(e) })
      }

      this.el.keyup(function(e) { self.onKeyUp(e) })
      this.el.blur(function() { self.enableKillerFn() })
      this.el.focus(function() { self.fixPosition() })
    },

    template: function(suggestion, data) {
      return this.options.formatResult(suggestion, data, this)
    },

    setOptions: function(options) {
      var o = this.options
      $.extend(o, options)

      if (o.lookup) this.setLookup(o.lookup)
      $('#' + this.mainContainerId).css({ zIndex: o.zIndex })
      if (o.identifier) {
         $('#' + this.mainContainerId).attr('data-identifier', o.identifier)
      }
      this.container.css({ maxHeight: o.maxHeight + 'px', width: o.width })
    },

    setLookup: function(lookup) {
        this.isLocal = true
        if ($.isArray(lookup) || $.isPlainObject(lookup))
            this.options.lookup = { suggestions: lookup, data: [] }
    },

    clearCache: function() {
      this.cachedResponse = []
      this.badQueries = []
    },

    disable: function() {
      this.disabled = true
    },

    enable: function() {
      this.disabled = false
    },

    fixPosition: function() {
      var offset = this.el.offset()
      $('#' + this.mainContainerId).css({
          top: (offset.top + this.el.innerHeight()) + 'px',
          left: offset.left + 'px'
      })
    },

    enableKillerFn: function() {
      var self = this
      $(document).bind('click', self.killerFn)
    },

    disableKillerFn: function() {
      var self = this
      $(document).unbind('click', self.killerFn)
    },

    killSuggestions: function() {
      var self = this
      this.stopKillSuggestions()
      this.intervalId = window.setInterval(function() {
          self.hide()
          self.stopKillSuggestions()
      }, 300)
    },

    stopKillSuggestions: function() {
      window.clearInterval(this.intervalId)
    },

    onKeyPress: function(e) {
      if (this.disabled || !this.enabled) return

      switch (e.keyCode) {
        case 27: /* ESC */
          this.el.val(this.currentValue)
          this.hide()
          break
        case 9:  /* TAB */
        case 13: /* RETURN */
          if (1 === this.suggestions.length) {
              this.select(0)
          } else if (-1 === this.selectedIndex) {
              return this.hide()
          } else {
              this.select(this.selectedIndex)
          }
          if (9 === e.keyCode) return
          break
        case 38: /* UP */
          this.moveUp()
          break
        case 40: /* DOWN */
          this.moveDown()
          break
        case 32: /* SPACE */
          if (-1 === this.selectedIndex) break
          this.select(this.selectedIndex)
          break
        default:
          return
      }
      e.stopImmediatePropagation()
      e.preventDefault()
    },

    onKeyUp: function(e) {
      if (this.disabled) return
      switch (e.keyCode) {
        case 38: // UP:
        case 40: // DOWN:
          return
      }
      clearInterval(this.onChangeInterval)
      var self = this
      var value = this.isFormField() ? this.el.val() : this.el.html()
      if (this.currentValue !== value) {
        if (this.options.deferRequestBy > 0) {
          this.onChangeInterval = setInterval(
              function() { self.onValueChange() },
              this.options.deferRequestBy
          )
        } else {
          this.onValueChange()
        }
      }
    },

    onValueChange: function() {
      clearInterval(this.onChangeInterval)
      this.currentValue = this.isFormField() ? this.el.val() : this.el.html()
      var q = this.getQuery(this.currentValue)
      this.selectedIndex = -1
      if (this.ignoreValueChange) {
        this.ignoreValueChange = false
        return
      }
      if (('' === q) || (q.length < this.options.minChars)) {
        this.hide()
      } else {
        this.getSuggestions(q)
      }
    },

    getQuery: function(val) {
      var prefixLength = this.options.searchPrefix.length
      var d = this.options.delimiter
      if (!d) { return $.trim(val) }
      var arr = val.split(d)
      var query = $.trim(arr[arr.length - 1])
      if (query.substring(0, prefixLength) === this.options.searchPrefix)
         return query.substring(this.options.searchPrefix.length)
      return ''
    },

    getSuggestionsLocal: function(q) {
      var arr = this.options.lookup.suggestions
      var ret = { suggestions: [], data: [] }
      q       = q.toLowerCase()

      for (var index in arr) {
        var val = arr[index]
        if ('object' === typeof(val)) {
          val = val[this.options.searchKey]
        }
        var indexPosition = (val  + ' ' + arr[index][this.options.dataKey])
            .toLowerCase().indexOf(q)
        var addData = false
        if ((true === this.options.searchEverywhere) && (indexPosition > -1)) {
          addData = true
        } else if (0 === indexPosition) {
          addData = true
        }
        if (true === addData) {
          ret.suggestions.push(val)
          ret.data.push(arr[index])
        }
      }
      return ret
    },

    getSuggestions: function(q) {
      var self = this
      var cached = this.isLocal ? this.getSuggestionsLocal(q) : this.cachedResponse[q]
      if (cached && ($.isArray(cached.suggestions) || $.isObject(cached.suggestions))) {
        this.suggestions = cached.suggestions
        this.data = cached.data
        this.suggest()
      } else if (!this.isBadQuery(q)) {
        this.options.params.query = q
        $.get(this.serviceUrl, this.options.params, function(txt) {
            self.processResponse(txt)
        }, 'text')
      }
    },

    isBadQuery: function(q) {
      var i = this.badQueries.length
      while (i--) {
        if (0 === q.indexOf(this.badQueries[i])) return true
      }
      return false
    },

    hide: function() {
      this.enabled = false
      this.selectedIndex = -1
      this.container.hide()
    },

    suggest: function() {
      if (0 === this.suggestions.length)
        return this.hide()
      this.fixPosition()
      var div, s
      var self = this
      var len = this.suggestions.length
      var v = this.getQuery(this.currentValue)
      var mOver = function(xi) { return function() { self.activate(xi) } }
      var mClick = function(xi) { return function() { self.select(xi) } }
      this.container.hide().empty()
      for (var i = 0; i < len; i++) {
        s = this.suggestions[i]
        if ((typeof(entry) === 'object') &&
            (typeof(this.options.dataKey) !== 'undefined'))
            s = this.data[i][this.options.dataKey]
        div = $((self.selectedIndex === i ?
            '<div class="selected"' : '
' +
            this.template(v, this.data[i]) + '
'
        )
        if (true !== this.options.disableMouseOver) div.mouseover(mOver(i))
        div.click(mClick(i))
        this.container.append(div)
      }
      this.enabled = true
      this.container.show()
    },

    processResponse: function(text) {
      var response
      try {
        response = JSON.parse(text)
      } catch (err) {
        return
      }
      if (!$.isArray(response.data)) response.data = []
      if (!this.options.noCache) {
        this.cachedResponse[response.query] = response
        if (0 === response.suggestions.length)
            this.badQueries.push(response.query)
      }
      if (response.query === this.getQuery(this.currentValue)) {
        this.suggestions = response.suggestions
        this.data = response.data
        this.suggest()
      }
    },

    activate: function(index) {
      var divs = this.container.children()
      // Clear previous selection:
      if ((-1 !== this.selectedIndex) && (divs.length > this.selectedIndex))
        $(divs.get(this.selectedIndex)).removeClass()

      this.selectedIndex = index
      var activeItem
      if ((-1 !== this.selectedIndex) && (divs.length > this.selectedIndex)) {
        activeItem = divs.get(this.selectedIndex)
        $(activeItem).addClass('selected')
      }
      return activeItem
    },

    deactivate: function(div, index) {
      div.className = ''
      if (this.selectedIndex === index) { this.selectedIndex = -1 }
    },

    select: function(i) {
      var selectedValue = this.suggestions[i]
      if (!selectedValue) return
      this.el.val(selectedValue)
      if (this.options.autoSubmit) {
        var f = this.el.parents('form')
        if (f.length > 0) { f.get(0).submit() }
      }
      this.ignoreValueChange = true
      this.hide()
      this.onSelect(i)
    },

    moveUp: function() {
      if (-1 === this.selectedIndex) return
      if (0 === this.selectedIndex) {
        this.container.children().get(0).className = ''
        this.selectedIndex = -1
        this.el.val(this.currentValue)
        return
      }
      this.adjustScroll(this.selectedIndex - 1)
    },

    moveDown: function() {
      if (this.selectedIndex === (this.suggestions.length - 1)) return
      this.adjustScroll(this.selectedIndex + 1)
    },

    adjustScroll: function(i) {
      var activeItem = this.activate(i)
      var offsetTop = activeItem.offsetTop
      var upperBound = this.container.scrollTop()
      var lowerBound = upperBound + this.options.maxHeight - 25
      if (offsetTop < upperBound) {
        this.container.scrollTop(offsetTop)
      } else if (offsetTop > lowerBound) {
        this.container.scrollTop(offsetTop - this.options.maxHeight + 25)
      }
      this.el.val(this.getValue(this.options.searchPrefix + this.suggestions[i]))
    },

    onSelect: function(i) {
      var self = this
      var fn   = this.options.onSelect
      var s    = this.suggestions[i]
      var d    = this.data[i]
      var val  = this.options.insertDomContent(d, s, this)
      if (this.isFormField()) {
          this.el.val(val)
          this.selectRange(val.length)
      } else {
          this.el.html(val)
      }
      this.el.focus()
      this.el.trigger('autosize.resize')
      if ($.isFunction(fn)) { fn(s, d, self.el) }
    },

    selectRange: function(start, end) {
      if (!end) end = start
      if (this.el[0].setSelectionRange) {
          this.el.focus()
          this.el[0].setSelectionRange(start, end)
      } else if (this.el.createTextRange) {
          var range = this.el.createTextRange()
          range.collapse(true)
          range.moveEnd('character', selectionEnd)
          range.moveStart('character', selectionStart)
          range.select()
      } else {
          console.debug('Unable to change caret position')
      }
    },

    insertDomContent: function(data, suggestion, context) {
      var val = context.getValue(suggestion)
      if ('object' === typeof(data))
        val = context.getValue(data[context.options.dataKey])
      if (context.options.appendChars) val = val + context.options.appendChars
      return val
    },

    isFormField: function() {
        return this.el.is('input') || this.el.is('textarea')
    },

    getValue: function(value) {
        var del = this.options.delimiter
        if (!del) return value
        var currVal = this.currentValue
        var arr = currVal.split(del)
        if (1 === arr.length) return value
        var response = currVal.substr(0, currVal.length - arr[arr.length - 1].length)
        return response + value
    },

    /**
     * value: Data entered to autocomplete
     * data: Matching data
     */
    formatResult: function(value, data) {
      var search = (this.searchKey) ? data[this.searchKey] : ''
      if (this.dataKey) data = data[this.dataKey]
      var pattern = '(' + value.replace(this.regEx, '\\$1') + ')'
      if (this.searchKey) {
          return ((search + ' (' + data).replace(new RegExp(pattern, 'gi'), '$1<\/strong>') + ')')
      }
      return data.replace(new RegExp(pattern, 'gi'), '$1<\/strong>')
    },

    remove: function() {
        $('#' + this.mainContainerId).remove()
    },
  }

})(jQuery)
<commit_msg>Altered setOptions so the setLookup function only runs on newly set options.<commit_after>/**
 * Autocomplete for jQuery, version 1.10.X
 * (c) 2013 Lloyd Watkin
 * Licensed under the MIT License
 *
 * Heavily modified from http://www.devbridge.com/projects/autocomplete/jquery/
 */
(function($) {

  function Autocomplete(el, options) {
    this.regEx = new RegExp(
      '(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\']
          .join('|\\') + ')',
      'g'
  )
    this.el = $(el)
    this.el.attr('autocomplete', 'off')
    this.suggestions = []
    this.data = []
    this.badQueries = []
    this.selectedIndex = -1
    this.currentValue = this.isFormField() ? this.el.val() : this.el.html()
    this.intervalId = 0
    this.cachedResponse = []
    this.onChangeInterval = null
    this.ignoreValueChange = false
    this.serviceUrl = options.serviceUrl
    this.isLocal = false
    this.options = {
      autoSubmit: false,
      minChars: 1,
      maxHeight: 300,
      deferRequestBy: 0,
      width: 300,
      highlight: true,
      params: {},
      dataKey: null,
      formatResult: this.formatResult,
      delimiter: ' ',
      zIndex: 9999,
      searchPrefix: '',
      searchEverywhere: false,
      appendChars: ' ',
      insertDomContent: this.insertDomContent,
      disableMouseOver: false
    }
    this.initialize()
    this.setOptions(options)
    return this
  }

  $.fn.autocomplete = function(options) {
    return new Autocomplete(this.get(0) || $('<input />'), options)
  }

  Autocomplete.prototype = {

    killerFn: null,

    initialize: function() {

      var self = this
      var uid = Math.floor(Math.random() * 0x100000).toString(16)
      var autocompleteElementId = 'Autocomplete_' + uid

      this.killerFn = function(e) {
        if (0 === $(e.target).parents('.autocomplete').size()) {
          self.killSuggestions()
          self.disableKillerFn()
        }
      }

      if (!this.options.width) this.options.width = this.el.width()
      this.mainContainerId = 'AutocompleteContainer_' + uid
      var identifier = ''
      if (this.options.identifier) {
          identifier = 'data-identifier="' + this.options.identifier + '"'
      }
      $('
' +
        '

')
        .appendTo('body')

      $('#'+this.mainContainerId).css({'position': 'absolute', 'z-index': '9999'})
      $('#'+autocompleteElementId).css({'display':'none', 'width':'300px'})

      this.container = $('#' + autocompleteElementId)
      this.fixPosition()
      if (window.opera) {
        this.el.keypress(function(e) { self.onKeyPress(e) })
      } else {
        this.el.keydown(function(e) { self.onKeyPress(e) })
      }

      this.el.keyup(function(e) { self.onKeyUp(e) })
      this.el.blur(function() { self.enableKillerFn() })
      this.el.focus(function() { self.fixPosition() })
    },

    template: function(suggestion, data) {
      return this.options.formatResult(suggestion, data, this)
    },

    setOptions: function(options) {
      var o = this.options
      $.extend(o, options)

      if (options.lookup) this.setLookup(options.lookup)
      $('#' + this.mainContainerId).css({ zIndex: o.zIndex })
      if (o.identifier) {
         $('#' + this.mainContainerId).attr('data-identifier', o.identifier)
      }
      this.container.css({ maxHeight: o.maxHeight + 'px', width: o.width })
    },

    setLookup: function(lookup) {
        this.isLocal = true
        if ($.isArray(lookup) || $.isPlainObject(lookup))
            this.options.lookup = { suggestions: lookup, data: [] }
    },

    clearCache: function() {
      this.cachedResponse = []
      this.badQueries = []
    },

    disable: function() {
      this.disabled = true
    },

    enable: function() {
      this.disabled = false
    },

    fixPosition: function() {
      var offset = this.el.offset()
      $('#' + this.mainContainerId).css({
          top: (offset.top + this.el.innerHeight()) + 'px',
          left: offset.left + 'px'
      })
    },

    enableKillerFn: function() {
      var self = this
      $(document).bind('click', self.killerFn)
    },

    disableKillerFn: function() {
      var self = this
      $(document).unbind('click', self.killerFn)
    },

    killSuggestions: function() {
      var self = this
      this.stopKillSuggestions()
      this.intervalId = window.setInterval(function() {
          self.hide()
          self.stopKillSuggestions()
      }, 300)
    },

    stopKillSuggestions: function() {
      window.clearInterval(this.intervalId)
    },

    onKeyPress: function(e) {
      if (this.disabled || !this.enabled) return

      switch (e.keyCode) {
        case 27: /* ESC */
          this.el.val(this.currentValue)
          this.hide()
          break
        case 9:  /* TAB */
        case 13: /* RETURN */
          if (1 === this.suggestions.length) {
              this.select(0)
          } else if (-1 === this.selectedIndex) {
              return this.hide()
          } else {
              this.select(this.selectedIndex)
          }
          if (9 === e.keyCode) return
          break
        case 38: /* UP */
          this.moveUp()
          break
        case 40: /* DOWN */
          this.moveDown()
          break
        case 32: /* SPACE */
          if (-1 === this.selectedIndex) break
          this.select(this.selectedIndex)
          break
        default:
          return
      }
      e.stopImmediatePropagation()
      e.preventDefault()
    },

    onKeyUp: function(e) {
      if (this.disabled) return
      switch (e.keyCode) {
        case 38: // UP:
        case 40: // DOWN:
          return
      }
      clearInterval(this.onChangeInterval)
      var self = this
      var value = this.isFormField() ? this.el.val() : this.el.html()
      if (this.currentValue !== value) {
        if (this.options.deferRequestBy > 0) {
          this.onChangeInterval = setInterval(
              function() { self.onValueChange() },
              this.options.deferRequestBy
          )
        } else {
          this.onValueChange()
        }
      }
    },

    onValueChange: function() {
      clearInterval(this.onChangeInterval)
      this.currentValue = this.isFormField() ? this.el.val() : this.el.html()
      var q = this.getQuery(this.currentValue)
      this.selectedIndex = -1
      if (this.ignoreValueChange) {
        this.ignoreValueChange = false
        return
      }
      if (('' === q) || (q.length < this.options.minChars)) {
        this.hide()
      } else {
        this.getSuggestions(q)
      }
    },

    getQuery: function(val) {
      var prefixLength = this.options.searchPrefix.length
      var d = this.options.delimiter
      if (!d) { return $.trim(val) }
      var arr = val.split(d)
      var query = $.trim(arr[arr.length - 1])
      if (query.substring(0, prefixLength) === this.options.searchPrefix)
         return query.substring(this.options.searchPrefix.length)
      return ''
    },

    getSuggestionsLocal: function(q) {
      var arr = this.options.lookup.suggestions
      var ret = { suggestions: [], data: [] }
      q       = q.toLowerCase()

      for (var index in arr) {
        var val = arr[index]
        if ('object' === typeof(val)) {
          val = val[this.options.searchKey]
        }
        var indexPosition = (val  + ' ' + arr[index][this.options.dataKey])
            .toLowerCase().indexOf(q)
        var addData = false
        if ((true === this.options.searchEverywhere) && (indexPosition > -1)) {
          addData = true
        } else if (0 === indexPosition) {
          addData = true
        }
        if (true === addData) {
          ret.suggestions.push(val)
          ret.data.push(arr[index])
        }
      }
      return ret
    },

    getSuggestions: function(q) {
      var self = this
      var cached = this.isLocal ? this.getSuggestionsLocal(q) : this.cachedResponse[q]
      if (cached && ($.isArray(cached.suggestions) || $.isObject(cached.suggestions))) {
        this.suggestions = cached.suggestions
        this.data = cached.data
        this.suggest()
      } else if (!this.isBadQuery(q)) {
        this.options.params.query = q
        $.get(this.serviceUrl, this.options.params, function(txt) {
            self.processResponse(txt)
        }, 'text')
      }
    },

    isBadQuery: function(q) {
      var i = this.badQueries.length
      while (i--) {
        if (0 === q.indexOf(this.badQueries[i])) return true
      }
      return false
    },

    hide: function() {
      this.enabled = false
      this.selectedIndex = -1
      this.container.hide()
    },

    suggest: function() {
      if (0 === this.suggestions.length)
        return this.hide()
      this.fixPosition()
      var div, s
      var self = this
      var len = this.suggestions.length
      var v = this.getQuery(this.currentValue)
      var mOver = function(xi) { return function() { self.activate(xi) } }
      var mClick = function(xi) { return function() { self.select(xi) } }
      this.container.hide().empty()
      for (var i = 0; i < len; i++) {
        s = this.suggestions[i]
        if ((typeof(entry) === 'object') &&
            (typeof(this.options.dataKey) !== 'undefined'))
            s = this.data[i][this.options.dataKey]
        div = $((self.selectedIndex === i ?
            '<div class="selected"' : '
' +
            this.template(v, this.data[i]) + '
'
        )
        if (true !== this.options.disableMouseOver) div.mouseover(mOver(i))
        div.click(mClick(i))
        this.container.append(div)
      }
      this.enabled = true
      this.container.show()
    },

    processResponse: function(text) {
      var response
      try {
        response = JSON.parse(text)
      } catch (err) {
        return
      }
      if (!$.isArray(response.data)) response.data = []
      if (!this.options.noCache) {
        this.cachedResponse[response.query] = response
        if (0 === response.suggestions.length)
            this.badQueries.push(response.query)
      }
      if (response.query === this.getQuery(this.currentValue)) {
        this.suggestions = response.suggestions
        this.data = response.data
        this.suggest()
      }
    },

    activate: function(index) {
      var divs = this.container.children()
      // Clear previous selection:
      if ((-1 !== this.selectedIndex) && (divs.length > this.selectedIndex))
        $(divs.get(this.selectedIndex)).removeClass()

      this.selectedIndex = index
      var activeItem
      if ((-1 !== this.selectedIndex) && (divs.length > this.selectedIndex)) {
        activeItem = divs.get(this.selectedIndex)
        $(activeItem).addClass('selected')
      }
      return activeItem
    },

    deactivate: function(div, index) {
      div.className = ''
      if (this.selectedIndex === index) { this.selectedIndex = -1 }
    },

    select: function(i) {
      var selectedValue = this.suggestions[i]
      if (!selectedValue) return
      this.el.val(selectedValue)
      if (this.options.autoSubmit) {
        var f = this.el.parents('form')
        if (f.length > 0) { f.get(0).submit() }
      }
      this.ignoreValueChange = true
      this.hide()
      this.onSelect(i)
    },

    moveUp: function() {
      if (-1 === this.selectedIndex) return
      if (0 === this.selectedIndex) {
        this.container.children().get(0).className = ''
        this.selectedIndex = -1
        this.el.val(this.currentValue)
        return
      }
      this.adjustScroll(this.selectedIndex - 1)
    },

    moveDown: function() {
      if (this.selectedIndex === (this.suggestions.length - 1)) return
      this.adjustScroll(this.selectedIndex + 1)
    },

    adjustScroll: function(i) {
      var activeItem = this.activate(i)
      var offsetTop = activeItem.offsetTop
      var upperBound = this.container.scrollTop()
      var lowerBound = upperBound + this.options.maxHeight - 25
      if (offsetTop < upperBound) {
        this.container.scrollTop(offsetTop)
      } else if (offsetTop > lowerBound) {
        this.container.scrollTop(offsetTop - this.options.maxHeight + 25)
      }
      this.el.val(this.getValue(this.options.searchPrefix + this.suggestions[i]))
    },

    onSelect: function(i) {
      var self = this
      var fn   = this.options.onSelect
      var s    = this.suggestions[i]
      var d    = this.data[i]
      var val  = this.options.insertDomContent(d, s, this)
      if (this.isFormField()) {
          this.el.val(val)
          this.selectRange(val.length)
      } else {
          this.el.html(val)
      }
      this.el.focus()
      this.el.trigger('autosize.resize')
      if ($.isFunction(fn)) { fn(s, d, self.el) }
    },

    selectRange: function(start, end) {
      if (!end) end = start
      if (this.el[0].setSelectionRange) {
          this.el.focus()
          this.el[0].setSelectionRange(start, end)
      } else if (this.el.createTextRange) {
          var range = this.el.createTextRange()
          range.collapse(true)
          range.moveEnd('character', selectionEnd)
          range.moveStart('character', selectionStart)
          range.select()
      } else {
          console.debug('Unable to change caret position')
      }
    },

    insertDomContent: function(data, suggestion, context) {
      var val = context.getValue(suggestion)
      if ('object' === typeof(data))
        val = context.getValue(data[context.options.dataKey])
      if (context.options.appendChars) val = val + context.options.appendChars
      return val
    },

    isFormField: function() {
        return this.el.is('input') || this.el.is('textarea')
    },

    getValue: function(value) {
        var del = this.options.delimiter
        if (!del) return value
        var currVal = this.currentValue
        var arr = currVal.split(del)
        if (1 === arr.length) return value
        var response = currVal.substr(0, currVal.length - arr[arr.length - 1].length)
        return response + value
    },

    /**
     * value: Data entered to autocomplete
     * data: Matching data
     */
    formatResult: function(value, data) {
      var search = (this.searchKey) ? data[this.searchKey] : ''
      if (this.dataKey) data = data[this.dataKey]
      var pattern = '(' + value.replace(this.regEx, '\\$1') + ')'
      if (this.searchKey) {
          return ((search + ' (' + data).replace(new RegExp(pattern, 'gi'), '$1<\/strong>') + ')')
      }
      return data.replace(new RegExp(pattern, 'gi'), '$1<\/strong>')
    },

    remove: function() {
        $('#' + this.mainContainerId).remove()
    },
  }

})(jQuery)
<|endoftext|>


<commit_before>document.app = {};
document.app.onswitch = function (id) {};
document.app.peerjs = {};
document.app.peerjs.serverId = "avallach_ovh_contracts_server";
document.app.peerjs.apiKey = "lwjd5qra8257b9";

function isScrollingBack(event) {
    var backScrollingKeys = ["ArrowUp", "ArrowLeft", "PageUp"];
    return (event instanceof WheelEvent && event.deltaY < 0) ||
        (event instanceof KeyboardEvent && backScrollingKeys.indexOf(event.key) >= 0);
}

function setSlide(id) {
    //    console.log(`setSlide(${id}})`);
    document.app.activeSlideId = id;
    document.app.slides[id].scrollIntoView(true);
    document.app.onswitch(id);
    //    console.log(`document.app.activeSlideId = ${document.app.activeSlideId}`);
}

function changeSlide(event) {
    var newSlide = document.app.activeSlideId + (isScrollingBack(event) ? -1 : 1);
    //    console.log(`newSlide = ${newSlide}`);
    if (newSlide >= document.app.slides.length)
        newSlide = 0;
    else if (newSlide < 0)
        newSlide = document.app.slides.length - 1;
    setSlide(newSlide);
}

function setupPeerJs() {
    if (document.location.href.indexOf("server") >= 0) {
        var server = new Peer(
            document.app.peerjs.serverId, {
                key: document.app.peerjs.apiKey
            }
        );
        var connections = [];
        server.on('connection', function (connection) {
            connections.push(connection);
            connection.on('open', function () {
                connection.send(document.app.activeSlideId);
            });
        });

        document.app.onswitch = function () {
            connections.forEach(function (connection) {
                connection.send(document.app.activeSlideId);
            });
        };
    } else {
        peer = new Peer({
            key: document.app.peerjs.apiKey
        });
        peer.on('open', function (id) {
            peer
                .connect(document.app.peerjs.serverId)
                .on('data', function (data) {
                    setSlide(data);
                });
        });
    }
}

window.addEventListener("load", function () {
    document.app.slides = document.getElementsByTagName("section");
    document.app.activeSlideId = 0;
    document.body.addEventListener("click", changeSlide);
    document.body.addEventListener("wheel", changeSlide);
    document.body.addEventListener("keydown", changeSlide);
    setupPeerJs();
});
<commit_msg>Added support for touch scrolling<commit_after>document.app = {};
document.app.onswitch = function (id) {};
document.app.peerjs = {};
document.app.peerjs.serverId = "avallach_ovh_contracts_server";
document.app.peerjs.apiKey = "lwjd5qra8257b9";

function isScrollingBack(event) {
    var backScrollingKeys = ["ArrowUp", "ArrowLeft", "PageUp"];
    return (event instanceof WheelEvent && event.deltaY < 0) ||
        (event instanceof KeyboardEvent && backScrollingKeys.indexOf(event.key) >= 0) ||
        (event instanceof TouchEvent &&
            (document.app.touchStart.y - event.changedTouches[0].clientY > 0 ||
                document.app.touchStart.x - event.changedTouches[0].clientX < 0));
}

function setSlide(id) {
    //    console.log(`setSlide(${id}})`);
    document.app.activeSlideId = id;
    document.app.slides[id].scrollIntoView(true);
    document.app.onswitch(id);
    //    console.log(`document.app.activeSlideId = ${document.app.activeSlideId}`);
}

function changeSlide(event) {
    var newSlide = document.app.activeSlideId + (isScrollingBack(event) ? -1 : 1);
    //    console.log(`newSlide = ${newSlide}`);
    if (newSlide >= document.app.slides.length)
        newSlide = 0;
    else if (newSlide < 0)
        newSlide = document.app.slides.length - 1;
    setSlide(newSlide);
}

function setupPeerJs() {
    if (document.location.href.indexOf("server") >= 0) {
        var server = new Peer(
            document.app.peerjs.serverId, {
                key: document.app.peerjs.apiKey
            }
        );
        var connections = [];
        server.on('connection', function (connection) {
            connections.push(connection);
            connection.on('open', function () {
                connection.send(document.app.activeSlideId);
            });
        });

        document.app.onswitch = function () {
            connections.forEach(function (connection) {
                connection.send(document.app.activeSlideId);
            });
        };
    } else {
        peer = new Peer({
            key: document.app.peerjs.apiKey
        });
        peer.on('open', function (id) {
            peer
                .connect(document.app.peerjs.serverId)
                .on('data', function (data) {
                    setSlide(data);
                });
        });
    }
}

window.addEventListener("load", function () {
    document.app.slides = document.getElementsByTagName("section");
    document.app.activeSlideId = 0;
    document.body.addEventListener("click", changeSlide);
    document.body.addEventListener("wheel", changeSlide);
    document.body.addEventListener("keydown", changeSlide);
    document.body.addEventListener("touchstart", function (event) {
        document.app.touchStart = {
            x: event.touches[0].clientX,
            y: event.touches[0].clientY
        }
    });
    document.body.addEventListener("touchend", changeSlide);
    setupPeerJs();
});
<|endoftext|>


<commit_before>/*jslint browser: true */
(function ($, window, document) {
    'use strict';

    var IcbiaControl, AbstractCheckedControl, FileUploadControl, controlMap;

    IcbiaControl = function () {};

    IcbiaControl.prototype = {
        optionAttributePrefix: 'data-icbiacontrol-',
        initialize: function ($el, options) {
            this.$el = $el;
            this.options = $.extend(this.defaultOptions, this.getHtmlOptions(), options);
            this.$el
                .change($.proxy(this.changeHandler, this))
                .focus($.proxy(this.focusHandler, this))
                .blur($.proxy(this.blurHandler, this));
            this.wrap();
            this.updateWidget();
        },

        blurHandler: function (event) {
            this.wrapper.removeClass('focus');
        },

        focusHandler: function (event) {
            this.wrapper.addClass('focus');
        },

        changeHandler: function (event) {
            this.updateWidget();
        },

        updateWidget: $.noop,

        getHtmlOptions: function () {
            var self = this,
                opts = {};
            $.each(this.$el[0].attributes, function (i, attr) {
                if (attr.name.indexOf(self.optionAttributePrefix) !== -1) {
                    opts[attr.name.substr(self.optionAttributePrefix.length)] = attr.value;
                }
            });
            return opts;
        },

        defaultOptions: {
        },

        createWidget: function () {
            var wt = this.options.widgetTemplate,
                widget = typeof wt === 'function' ? wt() : $($(wt).html());

            return widget
                .removeAttr(this.optionAttributePrefix + 'widgetTemplate')
                .addClass('icbiacontrol-widget')
                .addClass('icbia' + this.controlName + '-widget');
        },

        wrap: function () {
            var wrapperClass = this.options.wrapperClass;
            if (!this.wrapper) {
                this.widget = this.createWidget();
                this.wrapper = $('')
                    .addClass('icbia' + this.controlName)
                    .addClass($.isFunction(wrapperClass) ? wrapperClass(this.$el[0]) : wrapperClass || '')
                    .css({
                        position: 'relative',
                        display: 'inline-block'
                    })
                    .append(this.widget)
                    .insertBefore(this.$el)
                    .append(
                        this.$el.css({
                            position: 'absolute',
                            width: '100%',
                            height: '100%',
                            border: '1px solid #fff',  // To force the dimensions to take effect.
                            top: 0,
                            left: 0,
                            opacity: 0
                        })
                    );
            }
        }
    };

    IcbiaControl.extend = function (attrs) {
        var SuperClass = this,
            ctor = function () {};
        ctor.prototype = new SuperClass();
        ctor.prototype.constructor = ctor;
        $.extend(ctor.prototype, attrs);
        ctor.extend = SuperClass.extend;
        return ctor;
    };

    // A base control for checkboxes and radio buttons.
    AbstractCheckedControl = IcbiaControl.extend({
        defaultOptions: $.extend({}, IcbiaControl.prototype.defaultOptions, {
            widgetTemplate: function () {
                return $('');
            }
        }),
        updateWidget: function () {
            var isChecked = this.$el.is(':checked');
            this.wrapper
                .addClass(isChecked ? 'checked' : 'unchecked')
                .removeClass(isChecked ? 'unchecked' : 'checked');
        }
    });

    // A base control for file upload inputs
    FileUploadControl = IcbiaControl.extend({
        defaultOptions: $.extend({}, IcbiaControl.prototype.defaultOptions, {
            widgetTemplate: function () {
                return $('');
            }
        }),
        updateWidget: function () {
            // HTML5 is protecting everyone involved in the file transaction
            // let's prune the fakepath for readibilities sake
            var value = this.$el.val().replace("C:\\fakepath\\", ""),
                label = 'No File Chosen',
                isSelected = this.$el.val();
            this.wrapper
                .addClass(isSelected ? 'selected' : 'empty')
                .removeClass(isSelected ? 'empty' : 'selected');
            if (this.$el.attr('placeholder')) {
                label = this.$el.attr('placeholder');
            }
            this.widget.find('.icbiafile-display').html(value || label);
        }
    });

    controlMap = {
        'select': IcbiaControl.extend({
            controlName: 'select',
            defaultOptions: $.extend({}, IcbiaControl.prototype.defaultOptions, {
                widgetTemplate: function () {
                    var template =
                            '                                                        ' +
                            '                    ' +
                            '                     ' +
                            '                                                       ' +
                            '                ' +
                            '                                                       ';
                    return $(template);
                }
            }),
            updateWidget: function () {
                var value = this.$el.val(),
                    label = this.$el.find('option:selected').html();
                this.widget.find('.icbiaselect-display').html(label || ' ');
                this.wrapper.attr('data-value', value);
            }
        }),
        'input[type=checkbox]': AbstractCheckedControl.extend({
            controlName: 'checkbox'
        }),
        'input[type=radio]': AbstractCheckedControl.extend({
            controlName: 'radio',
            initialize: function ($el, options) {
                IcbiaControl.prototype.initialize.call(this, $el, options);
                $('input[name="' + this.$el.attr('name') + '"]').not(this.$el)
                    .change($.proxy(this.changeHandler, this));
            }
        }),
        'input[type=file]': FileUploadControl.extend({
            controlName: 'file',
            defaultOptions: $.extend({}, IcbiaControl.prototype.defaultOptions, {
                widgetTemplate: function () {
                    var template =
                            '                                                        ' +
                            '                      ' +
                            '                       ' +
                            '                                                       ' +
                            '                 ' +
                            '                                                       ';
                    return $(template);
                }
            })
        })
    };

    function createIcbiaControl($el, options) {
        var plugin;
        $.each(controlMap, function (selector, Cls) {
            if ($el.is(selector)) {
                plugin = new Cls();
                plugin.initialize($el, options);
                return false;
            }
        });

        if (!plugin) {
            $.error('There\'s no control for the ' + $el.prop('tagName') + ' element ' + $el);
        }

        return plugin;
    }

    $.extend($.fn, {
        icbiaControl: function (optionsOrMethod) {
            var returnValue = this,
                args = arguments;
            this.each(function (i, el) {
                var $el = $(el),
                    plugin = $el.data('icbiaControl'),
                    method = typeof optionsOrMethod === 'string' ? optionsOrMethod : null,
                    options = method === null ? optionsOrMethod || {} : {};

                if (!plugin) {
                    if (method) {
                        $.error('You can\'t call the icbiaControl method "' + method
                                + '" without first initializing the plugin by calling '
                                + 'icbiaControl() on the jQuery object.');
                    } else {
                        plugin = createIcbiaControl($el, options);
                        $el.data('icbiaControl', plugin);
                    }
                } else if (method) {
                    if (typeof plugin[method] !== 'function') {
                        $.error('Method "' + method + '" does not exist on jQuery.icbiaControl');
                    } else {
                        // NOTE: If you call a method that returns a value, you will only get the result from the last item in the collection.
                        returnValue = plugin[method].apply(plugin, Array.prototype.slice.call(args, 1));
                    }
                }
            });
            return returnValue;
        }
    });
}(this.jQuery, window, document));
<commit_msg>Consolidate file control definition<commit_after>/*jslint browser: true */
(function ($, window, document) {
    'use strict';

    var IcbiaControl, AbstractCheckedControl, controlMap;

    IcbiaControl = function () {};

    IcbiaControl.prototype = {
        optionAttributePrefix: 'data-icbiacontrol-',
        initialize: function ($el, options) {
            this.$el = $el;
            this.options = $.extend(this.defaultOptions, this.getHtmlOptions(), options);
            this.$el
                .change($.proxy(this.changeHandler, this))
                .focus($.proxy(this.focusHandler, this))
                .blur($.proxy(this.blurHandler, this));
            this.wrap();
            this.updateWidget();
        },

        blurHandler: function (event) {
            this.wrapper.removeClass('focus');
        },

        focusHandler: function (event) {
            this.wrapper.addClass('focus');
        },

        changeHandler: function (event) {
            this.updateWidget();
        },

        updateWidget: $.noop,

        getHtmlOptions: function () {
            var self = this,
                opts = {};
            $.each(this.$el[0].attributes, function (i, attr) {
                if (attr.name.indexOf(self.optionAttributePrefix) !== -1) {
                    opts[attr.name.substr(self.optionAttributePrefix.length)] = attr.value;
                }
            });
            return opts;
        },

        defaultOptions: {
        },

        createWidget: function () {
            var wt = this.options.widgetTemplate,
                widget = typeof wt === 'function' ? wt() : $($(wt).html());

            return widget
                .removeAttr(this.optionAttributePrefix + 'widgetTemplate')
                .addClass('icbiacontrol-widget')
                .addClass('icbia' + this.controlName + '-widget');
        },

        wrap: function () {
            var wrapperClass = this.options.wrapperClass;
            if (!this.wrapper) {
                this.widget = this.createWidget();
                this.wrapper = $('')
                    .addClass('icbia' + this.controlName)
                    .addClass($.isFunction(wrapperClass) ? wrapperClass(this.$el[0]) : wrapperClass || '')
                    .css({
                        position: 'relative',
                        display: 'inline-block'
                    })
                    .append(this.widget)
                    .insertBefore(this.$el)
                    .append(
                        this.$el.css({
                            position: 'absolute',
                            width: '100%',
                            height: '100%',
                            border: '1px solid #fff',  // To force the dimensions to take effect.
                            top: 0,
                            left: 0,
                            opacity: 0
                        })
                    );
            }
        }
    };

    IcbiaControl.extend = function (attrs) {
        var SuperClass = this,
            ctor = function () {};
        ctor.prototype = new SuperClass();
        ctor.prototype.constructor = ctor;
        $.extend(ctor.prototype, attrs);
        ctor.extend = SuperClass.extend;
        return ctor;
    };

    // A base control for checkboxes and radio buttons.
    AbstractCheckedControl = IcbiaControl.extend({
        defaultOptions: $.extend({}, IcbiaControl.prototype.defaultOptions, {
            widgetTemplate: function () {
                return $('');
            }
        }),
        updateWidget: function () {
            var isChecked = this.$el.is(':checked');
            this.wrapper
                .addClass(isChecked ? 'checked' : 'unchecked')
                .removeClass(isChecked ? 'unchecked' : 'checked');
        }
    });

    controlMap = {
        'select': IcbiaControl.extend({
            controlName: 'select',
            defaultOptions: $.extend({}, IcbiaControl.prototype.defaultOptions, {
                widgetTemplate: function () {
                    var template =
                            '                                                        ' +
                            '                    ' +
                            '                     ' +
                            '                                                       ' +
                            '                ' +
                            '                                                       ';
                    return $(template);
                }
            }),
            updateWidget: function () {
                var value = this.$el.val(),
                    label = this.$el.find('option:selected').html();
                this.widget.find('.icbiaselect-display').html(label || ' ');
                this.wrapper.attr('data-value', value);
            }
        }),
        'input[type=checkbox]': AbstractCheckedControl.extend({
            controlName: 'checkbox'
        }),
        'input[type=radio]': AbstractCheckedControl.extend({
            controlName: 'radio',
            initialize: function ($el, options) {
                IcbiaControl.prototype.initialize.call(this, $el, options);
                $('input[name="' + this.$el.attr('name') + '"]').not(this.$el)
                    .change($.proxy(this.changeHandler, this));
            }
        }),
        'input[type=file]': IcbiaControl.extend({
            controlName: 'file',
            defaultOptions: $.extend({}, IcbiaControl.prototype.defaultOptions, {
                widgetTemplate: function () {
                    var template =
                            '                                                        ' +
                            '                      ' +
                            '                       ' +
                            '                                                       ' +
                            '                 ' +
                            '                                                       ';
                    return $(template);
                }
            }),
            updateWidget: function () {
                // HTML5 is protecting everyone involved in the file transaction
                // let's prune the fakepath for readibilities sake
                var value = this.$el.val().replace("C:\\fakepath\\", ""),
                    label = 'No File Chosen',
                    isSelected = this.$el.val();
                this.wrapper
                    .addClass(isSelected ? 'selected' : 'empty')
                    .removeClass(isSelected ? 'empty' : 'selected');
                if (this.$el.attr('placeholder')) {
                    label = this.$el.attr('placeholder');
                }
                this.widget.find('.icbiafile-display').html(value || label);
            }
        })
    };

    function createIcbiaControl($el, options) {
        var plugin;
        $.each(controlMap, function (selector, Cls) {
            if ($el.is(selector)) {
                plugin = new Cls();
                plugin.initialize($el, options);
                return false;
            }
        });

        if (!plugin) {
            $.error('There\'s no control for the ' + $el.prop('tagName') + ' element ' + $el);
        }

        return plugin;
    }

    $.extend($.fn, {
        icbiaControl: function (optionsOrMethod) {
            var returnValue = this,
                args = arguments;
            this.each(function (i, el) {
                var $el = $(el),
                    plugin = $el.data('icbiaControl'),
                    method = typeof optionsOrMethod === 'string' ? optionsOrMethod : null,
                    options = method === null ? optionsOrMethod || {} : {};

                if (!plugin) {
                    if (method) {
                        $.error('You can\'t call the icbiaControl method "' + method
                                + '" without first initializing the plugin by calling '
                                + 'icbiaControl() on the jQuery object.');
                    } else {
                        plugin = createIcbiaControl($el, options);
                        $el.data('icbiaControl', plugin);
                    }
                } else if (method) {
                    if (typeof plugin[method] !== 'function') {
                        $.error('Method "' + method + '" does not exist on jQuery.icbiaControl');
                    } else {
                        // NOTE: If you call a method that returns a value, you will only get the result from the last item in the collection.
                        returnValue = plugin[method].apply(plugin, Array.prototype.slice.call(args, 1));
                    }
                }
            });
            return returnValue;
        }
    });
}(this.jQuery, window, document));
<|endoftext|>


<commit_before>(function ($) {

    $.fn.dottify = function (options) {
        var dots = [];
        var dotBox = this;
        var transitionTime = 500;
        var running = true;
        var timer;

        var settings = $.extend({
            numDots: 5, //Number of dots
            swoopPause: 200, //Pause time between dots
            dotColor: "#0A78C7",
            dotSize: "12px",
            radius: "100%",
            randomColors: false, //random colors on switch
            progress: false,
            percent: 0
        }, options);

        var $dotsSelelector;

        function createDots() {
            dotBox.css({
                position: "relative",
                overflow: "hidden"
            });

            if ( settings.progress && !options.numDots )
                settings.numDots = 30;

            for (var i = 0; i < settings.numDots; i++) {
                var percent = ( i + .5 ) * ( 100 / settings.numDots );
                var dot = $('

').data( "pos", percent );
                dots.push( dot.appendTo( dotBox ) );
            }

            $dotsSelelector = dotBox.find( ".swoopDot" ).css({
                'background-color'  : settings.dotColor,
                width               : settings.dotSize,
                height              : settings.dotSize,
                'border-radius'     : settings.radius
            });

            if ( settings.progress )
            {
                var dotWidth = dotBox.width() / settings.numDots;

                $dotsSelelector.css({ height: "90%", width: dotWidth * .6 });
            }

        }

        function swoop(left) {

            var addClass = left?"swoopReverse":"swoopActive";

            $dotsSelelector.removeClass( "swoopReverse swoopActive" );

            if ( left && settings.randomColors ) {
                $dotsSelelector.css( { "background-color": randomColor() } );
            }

            var movingDots = Math.floor( ( settings.percent / 100 ) * settings.numDots );

            if ( !movingDots )
            {
                movingDots = 1;
            }

            for (var i = 0; i < settings.numDots; i++) {
                moveDot( i );
            }

            function moveDot ( i ) {
                var dot = dots[i];
                var percent = dot.data("pos");

                //setting initial state

                if ( ( left && settings.progress ) || !settings.progress )
                {
                    dot.css({
                        left: (left ? "-100" : percent) + "%"
                    });
                }

                var timerPause;
                if ( settings.progress && left )
                {
                    timerPause = i * settings.swoopPause;
                }
                else
                {
                    timerPause = ( (settings.progress?movingDots:settings.numDots) - i) * settings.swoopPause;
                }

                if ( i >= movingDots && settings.progress )
                    return false;

                timer = setTimeout(function (dot, $sel) {
                    if ( !running )
                        return false;

                    $sel.addClass(addClass);

                    //final state
                    dot.css({
                        left: (left ? dot.data("pos") : "200") + "%"
                    });

                }, timerPause, dots[i], $dotsSelelector );
            }

            var transitionEndSelector;

            if ( settings.progress && left )
            {
                $transitionEndSelector = dots[movingDots-1];
            }
            else
            {
                $transitionEndSelector = $dotsSelelector.first();
            }

            $transitionEndSelector.bind( "transitionend", function () {

                $(this).unbind();
                swoop(!left);

            });

        }

        function randomColor() {
            return '#'+Math.floor(Math.random()*16777215).toString(16);
        }

        createDots();
        swoop( settings.progress );

        return {
            stop: function () {
                running = false;
                clearInterval(timer);
            },
            start: function () {
                running = true;
                $dotsSelelector.removeClass("swoopReverse swoopActive");
                swoop();
            },
            setProgress: function (percent) {
                settings.percent = percent;
            }
        }
    }

}(jQuery));<commit_msg>var fix<commit_after>(function ($) {

    $.fn.dottify = function (options) {
        var dots = [];
        var dotBox = this;
        var transitionTime = 500;
        var running = true;
        var timer;

        var settings = $.extend({
            numDots: 5, //Number of dots
            swoopPause: 200, //Pause time between dots
            dotColor: "#0A78C7",
            dotSize: "12px",
            radius: "100%",
            randomColors: false, //random colors on switch
            progress: false,
            percent: 0
        }, options);

        var $dotsSelelector;

        function createDots() {
            dotBox.css({
                position: "relative",
                overflow: "hidden"
            });

            if ( settings.progress && !options.numDots )
                settings.numDots = 30;

            for (var i = 0; i < settings.numDots; i++) {
                var percent = ( i + .5 ) * ( 100 / settings.numDots );
                var dot = $('

').data( "pos", percent );
                dots.push( dot.appendTo( dotBox ) );
            }

            $dotsSelelector = dotBox.find( ".swoopDot" ).css({
                'background-color'  : settings.dotColor,
                width               : settings.dotSize,
                height              : settings.dotSize,
                'border-radius'     : settings.radius
            });

            if ( settings.progress )
            {
                var dotWidth = dotBox.width() / settings.numDots;

                $dotsSelelector.css({ height: "90%", width: dotWidth * .6 });
            }

        }

        function swoop(left) {

            var addClass = left?"swoopReverse":"swoopActive";

            $dotsSelelector.removeClass( "swoopReverse swoopActive" );

            if ( left && settings.randomColors ) {
                $dotsSelelector.css( { "background-color": randomColor() } );
            }

            var movingDots = Math.floor( ( settings.percent / 100 ) * settings.numDots );

            if ( !movingDots )
            {
                movingDots = 1;
            }

            for (var i = 0; i < settings.numDots; i++) {
                moveDot( i );
            }

            function moveDot ( i ) {
                var dot = dots[i];
                var percent = dot.data("pos");

                //setting initial state

                if ( ( left && settings.progress ) || !settings.progress )
                {
                    dot.css({
                        left: (left ? "-100" : percent) + "%"
                    });
                }

                var timerPause;
                if ( settings.progress && left )
                {
                    timerPause = i * settings.swoopPause;
                }
                else
                {
                    timerPause = ( (settings.progress?movingDots:settings.numDots) - i) * settings.swoopPause;
                }

                if ( i >= movingDots && settings.progress )
                    return false;

                timer = setTimeout(function (dot, $sel) {
                    if ( !running )
                        return false;

                    $sel.addClass(addClass);

                    //final state
                    dot.css({
                        left: (left ? dot.data("pos") : "200") + "%"
                    });

                }, timerPause, dots[i], $dotsSelelector );
            }

            var $transitionEndSelector;

            if ( settings.progress && left )
            {
                $transitionEndSelector = dots[movingDots-1];
            }
            else
            {
                $transitionEndSelector = $dotsSelelector.first();
            }

            $transitionEndSelector.bind( "transitionend", function () {

                $(this).unbind();
                swoop(!left);

            });

        }

        function randomColor() {
            return '#'+Math.floor(Math.random()*16777215).toString(16);
        }

        createDots();
        swoop( settings.progress );

        return {
            stop: function () {
                running = false;
                clearInterval(timer);
            },
            start: function () {
                running = true;
                $dotsSelelector.removeClass("swoopReverse swoopActive");
                swoop();
            },
            setProgress: function (percent) {
                settings.percent = percent;
            }
        }
    }

}(jQuery));<|endoftext|>


<commit_before>var parser = new DOMParser();

class Form {
  constructor(template, configName) {
    this.document = parser.parseFromString(template, "text/xml");
    this.document.getElementsByTagName('button').item(0).addEventListener("select", function() {
      localStorage.setItem(configName, this.document.getElementsByTagName('textField').item(0).getFeature('Keyboard').text);
      this.cb();
    }.bind(this));
  }

  show(cb) {
    this.cb = cb;
    navigationDocument.pushDocument(this.document);
  }
}

class UsernameForm extends Form {
  constructor() {
    var template = `<document>
      <formTemplate>
        <banner>
          <description>Enter username / email</description>
        </banner>
        <textField></textField>
        <footer>
          <button>
            <text>Next</text>
          </button>
        </footer>
      </formTemplate>
      </document>`;

    super(template, "username");
  }
}

class PasswordForm extends Form {
  constructor() {
    var template = `<document>
      <formTemplate>
        <banner>
          <description>Enter password</description>
        </banner>
        <textField secure="true"></textField>
        <footer>
          <button>
            <text>Next</text>
          </button>
        </footer>
      </formTemplate>
      </document>`;

    super(template, "password");
  }
}

class SiteForm {
  constructor() {
    var template = `<document>
      <alertTemplate>
         <title>Select your site</title>
         <button data-service-name="viewms" data-port="3655">
            <text>MyStreams & uSport</text>
         </button>
         <button data-service-name="view247" data-port="3625">
            <text>Live 247</text>
         </button>
         <button data-service-name="viewss" data-port="3665">
            <text>StarStreams</text>
         </button>
         <button data-service-name="viewmma" data-port="3645">
            <text>MMA-TV / MyShout</text>
         </button>
         <button data-service-name="viewstvn" data-port="3615">
            <text>StreamTVnow</text>
         </button>
      </alertTemplate>
    </document>`;

    this.document = parser.parseFromString(template, "text/xml");
    this.document.addEventListener("select", function(evt) {
      localStorage.setItem("site", evt.target.getAttribute("data-service-name"));
      localStorage.setItem("port", evt.target.getAttribute("data-port"));
      this.cb();
    }.bind(this));
  }

  show(cb) {
    this.cb = cb;
    navigationDocument.pushDocument(this.document);
  }
}

class ServerForm {
  constructor() {
    var template = `<document>
      <alertTemplate>
         <title>Select your site</title>
         <button data-host="deu.nl2.smoothstreams.tv">
            <text>EU NL Evo</text>
         </button>
         <button data-host="deu.uk1.smoothstreams.tv">
            <text>EU UK Lon</text>
         </button>
         <button data-host="deu.nl1.smoothstreams.tv">
            <text>EU NL i3d</text>
         </button>
         <button data-host="dnae2.smoothstreams.tv">
            <text>US East 2 VA</text>
         </button>
         <button data-host="dnae1.smoothstreams.tv">
            <text>US East 1 NJ</text>
         </button>
         <button data-host="dnaw1.smoothstreams.tv">
            <text>US West LA</text>
         </button>
         <button data-host="dnae3.smoothstreams.tv">
            <text>CA East</text>
         </button>
         <button data-host="dsg.smoothstreams.tv">
            <text>Asia SG</text>
         </button>
      </alertTemplate>
    </document>`;

    this.document = parser.parseFromString(template, "text/xml");
    this.document.addEventListener("select", function(evt) {
      localStorage.setItem("host", evt.target.getAttribute("data-host"));
      this.cb();
    }.bind(this));
  }

  show(cb) {
    this.cb = cb;
    navigationDocument.pushDocument(this.document);
  }
}

class ConfigController {
  constructor() {
    this.usernameForm = new UsernameForm();
    this.passwordForm = new PasswordForm();
    this.siteForm = new SiteForm();
    this.serverForm = new ServerForm();
  }

  show(cb) {
    this.usernameForm.show(() => {
      this.passwordForm.show(() => {
        this.siteForm.show(() => {
          this.serverForm.show(cb);
        });
      });
    });
  }
}

module.exports = ConfigController;
<commit_msg>Add new servers for DE and CA<commit_after>var parser = new DOMParser();

class Form {
  constructor(template, configName) {
    this.document = parser.parseFromString(template, "text/xml");
    this.document.getElementsByTagName('button').item(0).addEventListener("select", function() {
      localStorage.setItem(configName, this.document.getElementsByTagName('textField').item(0).getFeature('Keyboard').text);
      this.cb();
    }.bind(this));
  }

  show(cb) {
    this.cb = cb;
    navigationDocument.pushDocument(this.document);
  }
}

class UsernameForm extends Form {
  constructor() {
    var template = `<document>
      <formTemplate>
        <banner>
          <description>Enter username / email</description>
        </banner>
        <textField></textField>
        <footer>
          <button>
            <text>Next</text>
          </button>
        </footer>
      </formTemplate>
      </document>`;

    super(template, "username");
  }
}

class PasswordForm extends Form {
  constructor() {
    var template = `<document>
      <formTemplate>
        <banner>
          <description>Enter password</description>
        </banner>
        <textField secure="true"></textField>
        <footer>
          <button>
            <text>Next</text>
          </button>
        </footer>
      </formTemplate>
      </document>`;

    super(template, "password");
  }
}

class SiteForm {
  constructor() {
    var template = `<document>
      <alertTemplate>
         <title>Select your site</title>
         <button data-service-name="viewms" data-port="3655">
            <text>MyStreams & uSport</text>
         </button>
         <button data-service-name="view247" data-port="3625">
            <text>Live 247</text>
         </button>
         <button data-service-name="viewss" data-port="3665">
            <text>StarStreams</text>
         </button>
         <button data-service-name="viewmma" data-port="3645">
            <text>MMA-TV / MyShout</text>
         </button>
         <button data-service-name="viewstvn" data-port="3615">
            <text>StreamTVnow</text>
         </button>
      </alertTemplate>
    </document>`;

    this.document = parser.parseFromString(template, "text/xml");
    this.document.addEventListener("select", function(evt) {
      localStorage.setItem("site", evt.target.getAttribute("data-service-name"));
      localStorage.setItem("port", evt.target.getAttribute("data-port"));
      this.cb();
    }.bind(this));
  }

  show(cb) {
    this.cb = cb;
    navigationDocument.pushDocument(this.document);
  }
}

class ServerForm {
  constructor() {
    var template = `<document>
      <alertTemplate>
        <title>Select your site</title>
        <button data-host="deu.nl2.smoothstreams.tv">
          <text>EU NL Evo</text>
        </button>
        <button data-host="deu.uk1.smoothstreams.tv">
          <text>EU UK Lon</text>
        </button>
        <button data-host="deu.nl1.smoothstreams.tv">
          <text>EU NL i3d</text>
        </button>
        <button data-host="deu.de1.smoothstreams.tv">
           <text>EU Deutschland</text>
        </button>
        <button data-host="dnae2.smoothstreams.tv">
          <text>US East 2 VA</text>
        </button>
        <button data-host="dnae1.smoothstreams.tv">
          <text>US East 1 NJ</text>
        </button>
        <button data-host="dnaw1.smoothstreams.tv">
          <text>US West LA</text>
        </button>
        <button data-host="dnae3.smoothstreams.tv">
          <text>CA East Toronto</text>
        </button>
        <button data-host="dnae4.smoothstreams.tv">
          <text>CA East Montréal</text>
        </button>
        <button data-host="dsg.smoothstreams.tv">
          <text>Asia SG</text>
        </button>
      </alertTemplate>
    </document>`;

    this.document = parser.parseFromString(template, "text/xml");
    this.document.addEventListener("select", function(evt) {
      localStorage.setItem("host", evt.target.getAttribute("data-host"));
      this.cb();
    }.bind(this));
  }

  show(cb) {
    this.cb = cb;
    navigationDocument.pushDocument(this.document);
  }
}

class ConfigController {
  constructor() {
    this.usernameForm = new UsernameForm();
    this.passwordForm = new PasswordForm();
    this.siteForm = new SiteForm();
    this.serverForm = new ServerForm();
  }

  show(cb) {
    this.usernameForm.show(() => {
      this.passwordForm.show(() => {
        this.siteForm.show(() => {
          this.serverForm.show(cb);
        });
      });
    });
  }
}

module.exports = ConfigController;
<|endoftext|>


<commit_before>/**
 * Provides observer pattern for basic eventing
 *
 * @class
 */
F.EventEmitter = new Class(/** @lends F.EventEmitter# */{
	construct: function() {
		this._events = {};
	},

	destruct: function() {
		delete this._events;
	},

	/**
	 * Attach an event listener
	 *
	 * @param {String} evt		Name of event to listen to
	 * @param {Function} func	Function to execute
	 *
	 * @returns {F.EventEmitter}	this, chainable
	 */
	on: function(evt, func) {
		var listeners = this._events[evt] = this._events[evt] || [];
		listeners.push(func);

		return this;
	},

	/**
	 * Remove an event listener
	 *
	 * @param {String} evt		Name of event that function is bound to
	 * @param {Function} func	Bound function
	 *
	 * @returns {F.EventEmitter}	this, chainable
	 */
	off: function(evt, func) {
		var listeners = this._events[evt];
		if (listeners !== undefined);
			listeners.splice(listeners.indexOf(func), 1);

		return this;
	},

	/**
	 * Trigger an event
	 *
	 * @param {String} evt		Name of event to trigger
	 * @param {Arguments} args	Additional arguments are passed to the listener functions
	 *
	 * @returns {F.EventEmitter}	this, chainable
	 */
	trigger: function(evt) {
		var listeners = this._events[evt];
		if (listeners !== undefined) {
			for (var i = 0, n = listeners.length; i < n; i++) {
				listeners[i].apply(this, Array.prototype.slice.call(arguments, 1));
			}
		}

		return this;
	}
});
<commit_msg>Documentation updates, added reference to BaseClass so docs include BaseClass' methods<commit_after>/**
 * Provides observer pattern for basic eventing
 *
 * @class
 * @extends BaseClass
 */
F.EventEmitter = new Class(/** @lends F.EventEmitter# */{
	/** @constructs */
	construct: function() {
		this._events = {};
	},

	/**
	 * Destroy references to events and listeners.
	 */
	destruct: function() {
		delete this._events;
	},

	/**
	 * Attach an event listener
	 *
	 * @param {String} evt		Name of event to listen to
	 * @param {Function} func	Function to execute
	 *
	 * @returns {F.EventEmitter}	this, chainable
	 */
	on: function(evt, func) {
		var listeners = this._events[evt] = this._events[evt] || [];
		listeners.push(func);

		return this;
	},

	/**
	 * Remove an event listener
	 *
	 * @param {String} evt		Name of event that function is bound to
	 * @param {Function} func	Bound function
	 *
	 * @returns {F.EventEmitter}	this, chainable
	 */
	off: function(evt, func) {
		var listeners = this._events[evt];
		if (listeners !== undefined);
			listeners.splice(listeners.indexOf(func), 1);

		return this;
	},

	/**
	 * Trigger an event
	 *
	 * @param {String} evt		Name of event to trigger
	 * @param {Arguments} args	Additional arguments are passed to the listener functions
	 *
	 * @returns {F.EventEmitter}	this, chainable
	 */
	trigger: function(evt) {
		var listeners = this._events[evt];
		if (listeners !== undefined) {
			for (var i = 0, n = listeners.length; i < n; i++) {
				listeners[i].apply(this, Array.prototype.slice.call(arguments, 1));
			}
		}

		return this;
	}
});
<|endoftext|>


<commit_before>'use strict';

var $ = require('jquery');

function NavMainComponent (html, window) {
    this.$html = html;
    this.$window = $(window);
};

NavMainComponent.prototype.init = function () {
    var component = this;

    component.adjustNavItems();

    component.$navMain = this.$html.find('.nav-main');
    component.$contentMain = this.$html.find('.content-main');
    component.$navPrimary = this.$html.find('.nav-primary');
    component.$navSecondary = this.$html.find('.nav-secondary');
    component.$navTertiary = component.$navMain.find('.nav-tertiary');
    component.$navQuaternary = component.$navMain.find('.nav-quaternary');
    component.$primaryNavLinks = component.$navPrimary.find('.nav-link');
    component.$secondaryNavLinks = component.$navSecondary.find('.nav-link');
    component.$tertiaryNavLinks = component.$navTertiary.find('.nav-link');
    component.$quaternaryNavLinks = component.$navQuaternary.find('.nav-link');
    component.$moreIcon = component.$navMain.find('.more-icon');
    component.$closeLink = component.$navMain.find('[data-nav-action=close]');

    component.$window.resize(function () {
        component.adjustNavItems();
    });

    component.closeHandler = function () {
        component.closeNavs();
    };

    component.$primaryNavLinks.on('click', function (e) {
        var $self = $(this),
            href = $self.attr('href');

        // If href is a fragment, don't add it to the URL because it breaks the
        // back button
        if (href.indexOf('#') !== -1) {
            e.preventDefault();
        }

        component.switchPrimaryNav(href);
        component.switchSecondaryNav(href);
        component.$contentMain.on('click', component.closeHandler);
    });

    component.$secondaryNavLinks.on('click', function () {
        component.changeActiveSecondaryNavLink($(this).attr('href'));
    });

    component.$tertiaryNavLinks.on('click', function (e) {
        var $self = $(this),
            href = $self.attr('href');

        if (href.indexOf('#') !== -1) {
            e.preventDefault();
        }

        component.switchTertiartyNav(href);
        component.switchQuaternaryNav(href);
        component.$contentMain.on('click', component.closeHandler);
    });

    component.$quaternaryNavLinks.on('click', function () {
        component.changeActiveQuaternaryNavLink($(this).attr('href'));
    });

    component.$moreIcon.find('.nav-link').on('click', function () {
        component.$navSecondary.removeClass('is-open');
        component.$navTertiary.toggleClass('is-open');
        component.$navTertiary.find('.nav-list').addClass('is-active');
    });

    component.$closeLink.on('click', function (e) {
        e.preventDefault();
        component.closeNavs();
        component.closeSubNavs();
        component.$primaryNavLinks.removeClass('is-active');
    });
};

NavMainComponent.prototype.switchPrimaryNav = function (target) {
    var component = this;

    component.$primaryNavLinks.removeClass('is-active');
    component.$navQuaternary.removeClass('is-open');
    component.$navTertiary.removeClass('is-open');

    if (component.$html.find('[data-nav="' + target + '"]').length >= 1) {
        component.$navMain.addClass('is-open');
    } else {
        component.closeNavs();
    }

    component.$navPrimary.find('.is-active').removeClass('is-active');
    component.$navPrimary.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.switchSecondaryNav = function (target) {
    var component = this;

    component.closeSubNavs();
    if (target.indexOf('#') !== -1) {
        component.$navSecondary.toggleClass('is-open');
        component.$navSecondary.find('.nav-list.is-active').removeClass('is-active');
        component.$navSecondary.find('[data-nav="' + target + '"]').addClass('is-active');
    }
};

NavMainComponent.prototype.switchTertiartyNav = function (target) {
    var component = this;

    if (!component.$html.find('[data-nav="' + target + '"]').length) {
        component.closeNavs();
    }

    component.$html.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.switchQuaternaryNav = function (target) {
    var component = this;

    // If category item has encapsulated options and if it is same-page link then open the proper menu
    if (target.indexOf('#') !== -1) {
        component.$navQuaternary.addClass('is-open');
    }

    component.$html.find('[data-nav="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.changeActiveSecondaryNavLink = function (target) {
    var component = this;

    component.$navSecondary.find('.nav-item.is-active').removeClass('is-active');
    component.$html.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.changeActiveQuaternaryNavLink = function (target) {
    var component = this;

    component.$html.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.closeNavs = function () {
    var component = this;

    if (component.$navSecondary.hasClass('is-open')) {
        component.$navSecondary.removeClass('is-open');
    }

    component.$contentMain.unbind('click', component.closeHandler);
};

NavMainComponent.prototype.closeSubNavs = function () {
    var component = this;

    component.$navSecondary.find('.nav-list').removeClass('is-active');

    if (component.$navQuaternary.hasClass('is-open')) {
        component.$navQuaternary.removeClass('is-open');
        component.$navQuaternary.find('.nav-list.is-active').removeClass('is-active');
    } else {
        component.$navTertiary.removeClass('is-open');
    }
};

// Detect window height, adjust the number of items in the primary nav and check when to add "More" option
NavMainComponent.prototype.adjustNavItems = function () {
    var component = this,
        availableHeight = component.$window.height(),
        navItemsHeight = (component.$html.find('.nav-primary .nav-items').outerHeight(true) + component.$html.find('.jadu-branding').outerHeight(true)),
        moreIconHeight = 72, // Pre calculated height of the "More" nav item
        navItemsCountTotal = component.$html.find('.nav-primary .nav-items li').length,
        numberOfHiddenNavItems = 0;

    if (navItemsHeight + moreIconHeight > availableHeight) {
        // If there is not enough space hide the last primary nav items
        component.hidePrimaryNavItems(navItemsHeight, moreIconHeight, availableHeight);
        // Get the number of hidden items to make only them visible in the tertiary menu
        numberOfHiddenNavItems = component.$html.find('.nav-primary .nav-items li:hidden').length;
        // Add "More" nav item and check its visibility if already exists
        component.addMoreNavItem(numberOfHiddenNavItems);
        // Hide the primary nav items duplicate in tertiary menu
        component.hideMoreCategoriesTopItems(navItemsCountTotal, numberOfHiddenNavItems);
    } else {
        // Unhide items if they were hidden and there is space in the primary nav
        component.$navPrimary.find('.nav-items li').show();
        component.$navPrimary.find('.nav-items [label="More"]').hide();
    }
};

NavMainComponent.prototype.hidePrimaryNavItems = function (navItemsHeight, moreIconHeight, availableHeight) {
    var component = this,
        navItems = component.$html.find('.nav-primary .nav-items'),
        lastPrimaryNavItem =  navItems.find('li:last-child'),
        nthChild = 2; // This number represents the item before the last in the nth-last-child;

    while (navItemsHeight + moreIconHeight > availableHeight) {
        // If last nav item is visible hide it
        if (lastPrimaryNavItem.is(':visible')) {
            lastPrimaryNavItem.hide();
        } else {
            // If last nav item is hidden hide the next one up
            navItems.find('li:nth-last-child('+ nthChild +')').hide();
            nthChild++;
        }

        // Recalculate nav items height based on items just hidden
        navItemsHeight = (navItems.outerHeight(true) + component.$html.find('.jadu-branding').outerHeight(true));
    }
};

NavMainComponent.prototype.addMoreNavItem = function (numberOfHiddenNavItems) {
    var component = this,
        navItems = component.$html.find('.nav-primary .nav-items'),
        navItemMore = navItems.find('[label="More"]');

    // Add the "More" nav item
    if ((numberOfHiddenNavItems > 0) && (!component.$html.find('.more-icon').length)){
        navItems.append('
More
');
    }

    // Check if "More" nav item is visible
    if (navItemMore.is(':visible') === false) {
        navItemMore.show();
    }
};

NavMainComponent.prototype.hideMoreCategoriesTopItems = function (navItemsCountTotal, numberOfHiddenNavItems) {
    var component = this,
        nthChild = 1, // Number used to iterate nth-child
        itemsToHideCount;

    // Reset hidden nav items
    component.$html.find('.nav-tertiary .nav-items li').show();

    // Calculate the number of visible nav items in primary nav
    itemsToHideCount = navItemsCountTotal - numberOfHiddenNavItems - 1; // 1 is for the "More" nav item

    // Hide top items in "More Categories" equal to the number of visible items of primary nav
    while (itemsToHideCount >= 0) {
        component.$html.find('.nav-tertiary .nav-items li:nth-child('+ nthChild +')').hide();
        nthChild++;
        itemsToHideCount--;
    }
};

module.exports = NavMainComponent;
<commit_msg>Restore Correct Selectors<commit_after>'use strict';

var $ = require('jquery');

function NavMainComponent (html, window) {
    this.$html = html;
    this.$window = $(window);
};

NavMainComponent.prototype.init = function () {
    var component = this;

    component.adjustNavItems();

    component.$navMain = this.$html.find('.nav-main');
    component.$contentMain = this.$html.find('.content-main');
    component.$navPrimary = this.$html.find('.nav-primary');
    component.$navSecondary = this.$html.find('.nav-secondary');
    component.$navTertiary = component.$navMain.find('.nav-tertiary');
    component.$navQuaternary = component.$navMain.find('.nav-quaternary');
    component.$primaryNavLinks = component.$navPrimary.find('.nav-link');
    component.$secondaryNavLinks = component.$navSecondary.find('.nav-link');
    component.$tertiaryNavLinks = component.$navTertiary.find('.nav-link');
    component.$quaternaryNavLinks = component.$navQuaternary.find('.nav-link');
    component.$moreIcon = component.$navMain.find('.more-icon');
    component.$closeLink = component.$navMain.find('[data-nav-action=close]');

    component.$window.resize(function () {
        component.adjustNavItems();
    });

    component.closeHandler = function () {
        component.closeNavs();
    };

    component.$primaryNavLinks.on('click', function (e) {
        var $self = $(this),
            href = $self.attr('href');

        // If href is a fragment, don't add it to the URL because it breaks the
        // back button
        if (href.indexOf('#') !== -1) {
            e.preventDefault();
        }

        component.switchPrimaryNav(href);
        component.switchSecondaryNav(href);
        component.$contentMain.on('click', component.closeHandler);
    });

    component.$secondaryNavLinks.on('click', function () {
        component.changeActiveSecondaryNavLink($(this).attr('href'));
    });

    component.$tertiaryNavLinks.on('click', function (e) {
        var $self = $(this),
            href = $self.attr('href');

        if (href.indexOf('#') !== -1) {
            e.preventDefault();
        }

        component.switchTertiartyNav(href);
        component.switchQuaternaryNav(href);
        component.$contentMain.on('click', component.closeHandler);
    });

    component.$quaternaryNavLinks.on('click', function () {
        component.changeActiveQuaternaryNavLink($(this).attr('href'));
    });

    component.$moreIcon.find('.nav-link').on('click', function () {
        component.$navSecondary.removeClass('is-open');
        component.$navTertiary.toggleClass('is-open');
        component.$navTertiary.find('.nav-list').addClass('is-active');
    });

    component.$closeLink.on('click', function (e) {
        e.preventDefault();
        component.closeNavs();
        component.closeSubNavs();
        component.$primaryNavLinks.removeClass('is-active');
    });
};

NavMainComponent.prototype.switchPrimaryNav = function (target) {
    var component = this;

    component.$primaryNavLinks.removeClass('is-active');
    component.$navQuaternary.removeClass('is-open');
    component.$navTertiary.removeClass('is-open');

    if (component.$html.find('[data-nav="' + target + '"]').length >= 1) {
        component.$navMain.addClass('is-open');
    } else {
        component.closeNavs();
    }

    component.$navPrimary.find('.is-active').removeClass('is-active');
    component.$navPrimary.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.switchSecondaryNav = function (target) {
    var component = this;

    component.closeSubNavs();
    if (target.indexOf('#') !== -1) {
        component.$navSecondary.toggleClass('is-open');
        component.$navSecondary.find('.nav-list.is-active').removeClass('is-active');
        component.$navSecondary.find('[data-nav="' + target + '"]').addClass('is-active');
    }
};

NavMainComponent.prototype.switchTertiartyNav = function (target) {
    var component = this;

    if (!component.$html.find('[data-nav="' + target + '"]').length) {
        component.closeNavs();
    }

    component.$html.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.switchQuaternaryNav = function (target) {
    var component = this;

    // If category item has encapsulated options and if it is same-page link then open the proper menu
    if (target.indexOf('#') !== -1) {
        component.$navQuaternary.addClass('is-open');
    }

    component.$html.find('[data-nav="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.changeActiveSecondaryNavLink = function (target) {
    var component = this;

    component.$navSecondary.find('.nav-item.is-active').removeClass('is-active');
    component.$html.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.changeActiveQuaternaryNavLink = function (target) {
    var component = this;

    component.$html.find('[href="' + target + '"]').addClass('is-active');
};

NavMainComponent.prototype.closeNavs = function () {
    var component = this;

    if (component.$navSecondary.hasClass('is-open')) {
        component.$navSecondary.removeClass('is-open');
    }

    component.$contentMain.unbind('click', component.closeHandler);
};

NavMainComponent.prototype.closeSubNavs = function () {
    var component = this;

    component.$navSecondary.find('.nav-list').removeClass('is-active');

    if (component.$navQuaternary.hasClass('is-open')) {
        component.$navQuaternary.removeClass('is-open');
        component.$navQuaternary.find('.nav-list.is-active').removeClass('is-active');
    } else {
        component.$navTertiary.removeClass('is-open');
    }
};

// Detect window height, adjust the number of items in the primary nav and check when to add "More" option
NavMainComponent.prototype.adjustNavItems = function () {
    var component = this,
        availableHeight = component.$window.height(),
        navItemsHeight = (component.$html.find('.nav-primary .nav-items').outerHeight(true) + component.$html.find('.jadu-branding').outerHeight(true)),
        moreIconHeight = 72, // Pre calculated height of the "More" nav item
        navItemsCountTotal = component.$html.find('.nav-primary .nav-items li').length,
        numberOfHiddenNavItems = 0;

    if (navItemsHeight + moreIconHeight > availableHeight) {
        // If there is not enough space hide the last primary nav items
        component.hidePrimaryNavItems(navItemsHeight, moreIconHeight, availableHeight);
        // Get the number of hidden items to make only them visible in the tertiary menu
        numberOfHiddenNavItems = component.$html.find('.nav-primary .nav-items li:hidden').length;
        // Add "More" nav item and check its visibility if already exists
        component.addMoreNavItem(numberOfHiddenNavItems);
        // Hide the primary nav items duplicate in tertiary menu
        component.hideMoreCategoriesTopItems(navItemsCountTotal, numberOfHiddenNavItems);
    } else {
        // Unhide items if they were hidden and there is space in the primary nav
        component.$navPrimary.find('.nav-items li').show();
        component.$navPrimary.find('.nav-items [label="More"]').hide();
        component.$html.find('.nav-primary .nav-items li').show();
        component.$html.find('.nav-primary .nav-items [label="More"]').hide();
    }
};

NavMainComponent.prototype.hidePrimaryNavItems = function (navItemsHeight, moreIconHeight, availableHeight) {
    var component = this,
        navItems = component.$html.find('.nav-primary .nav-items'),
        lastPrimaryNavItem =  navItems.find('li:last-child'),
        nthChild = 2; // This number represents the item before the last in the nth-last-child;

    while (navItemsHeight + moreIconHeight > availableHeight) {
        // If last nav item is visible hide it
        if (lastPrimaryNavItem.is(':visible')) {
            lastPrimaryNavItem.hide();
        } else {
            // If last nav item is hidden hide the next one up
            navItems.find('li:nth-last-child('+ nthChild +')').hide();
            nthChild++;
        }

        // Recalculate nav items height based on items just hidden
        navItemsHeight = (navItems.outerHeight(true) + component.$html.find('.jadu-branding').outerHeight(true));
    }
};

NavMainComponent.prototype.addMoreNavItem = function (numberOfHiddenNavItems) {
    var component = this,
        navItems = component.$html.find('.nav-primary .nav-items'),
        navItemMore = navItems.find('[label="More"]');

    // Add the "More" nav item
    if ((numberOfHiddenNavItems > 0) && (!component.$html.find('.more-icon').length)){
        navItems.append('
More
');
    }

    // Check if "More" nav item is visible
    if (navItemMore.is(':visible') === false) {
        navItemMore.show();
    }
};

NavMainComponent.prototype.hideMoreCategoriesTopItems = function (navItemsCountTotal, numberOfHiddenNavItems) {
    var component = this,
        nthChild = 1, // Number used to iterate nth-child
        itemsToHideCount;

    // Reset hidden nav items
    component.$html.find('.nav-tertiary .nav-items li').show();

    // Calculate the number of visible nav items in primary nav
    itemsToHideCount = navItemsCountTotal - numberOfHiddenNavItems - 1; // 1 is for the "More" nav item

    // Hide top items in "More Categories" equal to the number of visible items of primary nav
    while (itemsToHideCount >= 0) {
        component.$html.find('.nav-tertiary .nav-items li:nth-child('+ nthChild +')').hide();
        nthChild++;
        itemsToHideCount--;
    }
};

module.exports = NavMainComponent;
<|endoftext|>