<commit_before>import QIcon from '../icon/QIcon.js'
import QCheckbox from '../checkbox/QCheckbox.js'
import QSlideTransition from '../slide-transition/QSlideTransition.js'
import QSpinner from '../spinner/QSpinner.js'
import Ripple from '../../directives/ripple.js'

export default {
  name: 'QTree',
  directives: {
    Ripple
  },
  props: {
    nodes: Array,
    nodeKey: {
      type: String,
      required: true
    },

    color: {
      type: String,
      default: 'grey'
    },
    controlColor: String,
    textColor: String,
    dark: Boolean,

    icon: String,

    tickStrategy: {
      type: String,
      default: 'none',
      validator: v => ['none', 'strict', 'leaf', 'leaf-filtered'].includes(v)
    },
    ticked: Array, // sync
    expanded: Array, // sync
    selected: {}, // sync

    defaultExpandAll: Boolean,
    accordion: Boolean,

    filter: String,
    filterMethod: {
      type: Function,
      default (node, filter) {
        const filt = filter.toLowerCase()
        return node.label && node.label.toLowerCase().indexOf(filt) > -1
      }
    },

    noNodesLabel: String,
    noResultsLabel: String
  },
  computed: {
    hasRipple () {
      return process.env.THEME === 'mat' && !this.noRipple
    },
    classes () {
      return [
        `text-${this.color}`,
        { 'q-tree-dark': this.dark }
      ]
    },
    hasSelection () {
      return this.selected !== void 0
    },
    computedIcon () {
      return this.icon || this.$q.icon.tree.icon
    },
    computedControlColor () {
      return this.controlColor || this.color
    },
    contentClass () {
      return `text-${this.textColor || (this.dark ? 'white' : 'black')}`
    },
    meta () {
      const meta = {}

      const travel = (node, parent) => {
        const tickStrategy = node.tickStrategy || (parent ? parent.tickStrategy : this.tickStrategy)
        const
          key = node[this.nodeKey],
          isParent = node.children && node.children.length > 0,
          isLeaf = !isParent,
          selectable = !node.disabled && this.hasSelection && node.selectable !== false,
          expandable = !node.disabled && node.expandable !== false,
          hasTicking = tickStrategy !== 'none',
          strictTicking = tickStrategy === 'strict',
          leafFilteredTicking = tickStrategy === 'leaf-filtered',
          leafTicking = tickStrategy === 'leaf' || tickStrategy === 'leaf-filtered'

        let tickable = !node.disabled && node.tickable !== false
        if (leafTicking && tickable && parent && !parent.tickable) {
          tickable = false
        }

        let lazy = node.lazy
        if (lazy && this.lazy[key]) {
          lazy = this.lazy[key]
        }

        const m = {
          key,
          parent,
          isParent,
          isLeaf,
          lazy,
          disabled: node.disabled,
          link: selectable || (expandable && (isParent || lazy === true)),
          children: [],
          matchesFilter: this.filter ? this.filterMethod(node, this.filter) : true,

          selected: key === this.selected && selectable,
          selectable,
          expanded: isParent ? this.innerExpanded.includes(key) : false,
          expandable,
          noTick: node.noTick || (!strictTicking && lazy && lazy !== 'loaded'),
          tickable,
          tickStrategy,
          hasTicking,
          strictTicking,
          leafFilteredTicking,
          leafTicking,
          ticked: strictTicking
            ? this.innerTicked.includes(key)
            : (isLeaf ? this.innerTicked.includes(key) : false)
        }

        meta[key] = m

        if (isParent) {
          m.children = node.children.map(n => travel(n, m))

          if (this.filter) {
            if (!m.matchesFilter) {
              m.matchesFilter = m.children.some(n => n.matchesFilter)
            }
            if (
              m.matchesFilter &&
              !m.noTick &&
              !m.disabled &&
              m.tickable &&
              leafFilteredTicking &&
              m.children.every(n => !n.matchesFilter || n.noTick || !n.tickable)
            ) {
              m.tickable = false
            }
          }

          if (m.matchesFilter) {
            if (!m.noTick && !strictTicking && m.children.every(n => n.noTick)) {
              m.noTick = true
            }

            if (leafTicking) {
              m.ticked = false
              m.indeterminate = m.children.some(node => node.indeterminate)

              if (!m.indeterminate) {
                const sel = m.children
                  .reduce((acc, meta) => meta.ticked ? acc + 1 : acc, 0)

                if (sel === m.children.length) {
                  m.ticked = true
                }
                else if (sel > 0) {
                  m.indeterminate = true
                }
              }
            }
          }
        }

        return m
      }

      this.nodes.forEach(node => travel(node, null))
      return meta
    }
  },
  data () {
    return {
      lazy: {},
      innerTicked: this.ticked || [],
      innerExpanded: this.expanded || []
    }
  },
  watch: {
    ticked (val) {
      this.innerTicked = val
    },
    expanded (val) {
      this.innerExpanded = val
    }
  },
  methods: {
    getNodeByKey (key) {
      const reduce = [].reduce

      const find = (result, node) => {
        if (result || !node) {
          return result
        }
        if (Array.isArray(node)) {
          return reduce.call(Object(node), find, result)
        }
        if (node[this.nodeKey] === key) {
          return node
        }
        if (node.children) {
          return find(null, node.children)
        }
      }

      return find(null, this.nodes)
    },
    getTickedNodes () {
      return this.innerTicked.map(key => this.getNodeByKey(key))
    },
    getExpandedNodes () {
      return this.innerExpanded.map(key => this.getNodeByKey(key))
    },
    isExpanded (key) {
      return key && this.meta[key]
        ? this.meta[key].expanded
        : false
    },
    collapseAll () {
      if (this.expanded !== void 0) {
        this.$emit('update:expanded', [])
      }
      else {
        this.innerExpanded = []
      }
    },
    expandAll () {
      const
        expanded = this.innerExpanded,
        travel = node => {
          if (node.children && node.children.length > 0) {
            if (node.expandable !== false && node.disabled !== true) {
              expanded.push(node[this.nodeKey])
              node.children.forEach(travel)
            }
          }
        }

      this.nodes.forEach(travel)

      if (this.expanded !== void 0) {
        this.$emit('update:expanded', expanded)
      }
      else {
        this.innerExpanded = expanded
      }
    },
    setExpanded (key, state, node = this.getNodeByKey(key), meta = this.meta[key]) {
      if (meta.lazy && meta.lazy !== 'loaded') {
        if (meta.lazy === 'loading') {
          return
        }

        this.$set(this.lazy, key, 'loading')
        this.$emit('lazy-load', {
          node,
          key,
          done: children => {
            this.lazy[key] = 'loaded'
            if (children) {
              node.children = children
            }
            this.$nextTick(() => {
              const m = this.meta[key]
              if (m && m.isParent) {
                this.__setExpanded(key, true)
              }
            })
          },
          fail: () => {
            this.$delete(this.lazy, key)
          }
        })
      }
      else if (meta.isParent && meta.expandable) {
        this.__setExpanded(key, state)
      }
    },
    __setExpanded (key, state) {
      let target = this.innerExpanded
      const emit = this.expanded !== void 0

      if (emit) {
        target = target.slice()
      }

      if (state) {
        if (this.accordion) {
          if (this.meta[key]) {
            const collapse = []
            if (this.meta[key].parent) {
              this.meta[key].parent.children.forEach(m => {
                if (m.key !== key && m.expandable) {
                  collapse.push(m.key)
                }
              })
            }
            else {
              this.nodes.forEach(node => {
                const k = node[this.nodeKey]
                if (k !== key) {
                  collapse.push(k)
                }
              })
            }
            if (collapse.length > 0) {
              target = target.filter(k => !collapse.includes(k))
            }
          }
        }

        target = target.concat([ key ])
          .filter((key, index, self) => self.indexOf(key) === index)
      }
      else {
        target = target.filter(k => k !== key)
      }

      if (emit) {
        this.$emit(`update:expanded`, target)
      }
      else {
        this.innerExpanded = target
      }
    },
    isTicked (key) {
      return key && this.meta[key]
        ? this.meta[key].ticked
        : false
    },
    setTicked (keys, state) {
      let target = this.innerTicked
      const emit = this.ticked !== void 0

      if (emit) {
        target = target.slice()
      }

      if (state) {
        target = target.concat(keys)
          .filter((key, index, self) => self.indexOf(key) === index)
      }
      else {
        target = target.filter(k => !keys.includes(k))
      }

      if (emit) {
        this.$emit(`update:ticked`, target)
      }
    },
    __getSlotScope (node, meta, key) {
      const scope = { tree: this, node, key, color: this.color, dark: this.dark }

      Object.defineProperty(scope, 'expanded', {
        get: () => { return meta.expanded },
        set: val => { val !== meta.expanded && this.setExpanded(key, val) }
      })
      Object.defineProperty(scope, 'ticked', {
        get: () => { return meta.ticked },
        set: val => { val !== meta.ticked && this.setTicked([ key ], val) }
      })

      return scope
    },
    __getChildren (h, nodes) {
      return (
        this.filter
          ? nodes.filter(n => this.meta[n[this.nodeKey]].matchesFilter)
          : nodes
      ).map(child => this.__getNode(h, child))
    },
    __getNodeMedia (h, node) {
      if (node.icon) {
        return h(QIcon, {
          staticClass: `q-tree-icon q-mr-sm`,
          props: { name: node.icon, color: node.iconColor }
        })
      }
      if (node.img || node.avatar) {
        return h('img', {
          staticClass: `q-tree-img q-mr-sm`,
          'class': { avatar: node.avatar },
          attrs: { src: node.img || node.avatar }
        })
      }
    },
    __getNode (h, node) {
      const
        key = node[this.nodeKey],
        meta = this.meta[key],
        header = node.header
          ? this.$scopedSlots[`header-${node.header}`] || this.$scopedSlots['default-header']
          : this.$scopedSlots['default-header']

      const children = meta.isParent
        ? this.__getChildren(h, node.children)
        : []

      const isParent = children.length > 0 || (meta.lazy && meta.lazy !== 'loaded')

      let
        body = node.body
          ? this.$scopedSlots[`body-${node.body}`] || this.$scopedSlots['default-body']
          : this.$scopedSlots['default-body'],
        slotScope = header || body
          ? this.__getSlotScope(node, meta, key)
          : null

      if (body) {
        body = h('div', { staticClass: 'q-tree-node-body relative-position' }, [
          h('div', { 'class': this.contentClass }, [
            body(slotScope)
          ])
        ])
      }

      return h('div', {
        key,
        staticClass: 'q-tree-node',
        'class': { 'q-tree-node-parent': isParent, 'q-tree-node-child': !isParent }
      }, [
        h('div', {
          staticClass: 'q-tree-node-header relative-position row no-wrap items-center',
          'class': {
            'q-tree-node-link': meta.link,
            'q-tree-node-selected': meta.selected,
            disabled: meta.disabled
          },
          on: { click: () => { this.__onClick(node, meta) } },
          directives: process.env.THEME === 'mat' && meta.selectable
            ? [{ name: 'ripple' }]
            : null
        }, [
          meta.lazy === 'loading'
            ? h(QSpinner, {
              staticClass: 'q-tree-node-header-media q-mr-xs',
              props: { color: this.computedControlColor }
            })
            : (
              isParent
                ? h(QIcon, {
                  staticClass: 'q-tree-arrow q-mr-xs transition-generic',
                  'class': { 'q-tree-arrow-rotate': meta.expanded },
                  props: { name: this.computedIcon },
                  nativeOn: {
                    click: e => {
                      this.__onExpandClick(node, meta, e)
                    }
                  }
                })
                : null
            ),

          h('span', { 'staticClass': 'row no-wrap items-center', 'class': this.contentClass }, [
            meta.hasTicking && !meta.noTick
              ? h(QCheckbox, {
                staticClass: 'q-mr-xs',
                props: {
                  value: meta.indeterminate ? null : meta.ticked,
                  color: this.computedControlColor,
                  dark: this.dark,
                  keepColor: true,
                  disable: !meta.tickable
                },
                on: {
                  input: v => {
                    this.__onTickedClick(node, meta, v)
                  }
                }
              })
              : null,
            header
              ? header(slotScope)
              : [
                this.__getNodeMedia(h, node),
                h('span', node.label)
              ]
          ])
        ]),

        isParent
          ? h(QSlideTransition, [
            h('div', {
              directives: [{ name: 'show', value: meta.expanded }],
              staticClass: 'q-tree-node-collapsible',
              'class': `text-${this.color}`
            }, [
              body,
              h('div', {
                staticClass: 'q-tree-children',
                'class': { disabled: meta.disabled }
              }, children)
            ])
          ])
          : body
      ])
    },
    __onClick (node, meta) {
      if (this.hasSelection) {
        if (meta.selectable) {
          this.$emit('update:selected', meta.key !== this.selected ? meta.key : null)
        }
      }
      else {
        this.__onExpandClick(node, meta)
      }

      if (typeof node.handler === 'function') {
        node.handler(node)
      }
    },
    __onExpandClick (node, meta, e) {
      if (e !== void 0) {
        e.stopPropagation()
      }
      this.setExpanded(meta.key, !meta.expanded, node, meta)
    },
    __onTickedClick (node, meta, state) {
      if (meta.indeterminate && state) {
        state = false
      }
      if (meta.strictTicking) {
        this.setTicked([ meta.key ], state)
      }
      else if (meta.leafTicking) {
        const keys = []
        const travel = meta => {
          if (meta.isParent) {
            if (!state && !meta.noTick && meta.tickable) {
              keys.push(meta.key)
            }
            if (meta.leafTicking) {
              meta.children.forEach(travel)
            }
          }
          else if (!meta.noTick && meta.tickable && (!meta.leafFilteredTicking || meta.matchesFilter)) {
            keys.push(meta.key)
          }
        }
        travel(meta)
        this.setTicked(keys, state)
      }
    }
  },
  render (h) {
    const children = this.__getChildren(h, this.nodes)

    return h(
      'div', {
        staticClass: 'q-tree relative-position',
        'class': this.classes
      },
      children.length === 0
        ? (
          this.filter
            ? this.noResultsLabel || this.$q.i18n.tree.noResults
            : this.noNodesLabel || this.$q.i18n.tree.noNodes
        )
        : children
    )
  },
  created () {
    if (this.defaultExpandAll) {
      this.expandAll()
    }
  }
}
<commit_msg>feat: [Request] - Qtree - Ability to specify a different label field than 'label' #2442<commit_after>import QIcon from '../icon/QIcon.js'
import QCheckbox from '../checkbox/QCheckbox.js'
import QSlideTransition from '../slide-transition/QSlideTransition.js'
import QSpinner from '../spinner/QSpinner.js'
import Ripple from '../../directives/ripple.js'

export default {
  name: 'QTree',
  directives: {
    Ripple
  },
  props: {
    nodes: Array,
    nodeKey: {
      type: String,
      required: true
    },
    labelKey: {
      type: String,
      default: 'label'
    },

    color: {
      type: String,
      default: 'grey'
    },
    controlColor: String,
    textColor: String,
    dark: Boolean,

    icon: String,

    tickStrategy: {
      type: String,
      default: 'none',
      validator: v => ['none', 'strict', 'leaf', 'leaf-filtered'].includes(v)
    },
    ticked: Array, // sync
    expanded: Array, // sync
    selected: {}, // sync

    defaultExpandAll: Boolean,
    accordion: Boolean,

    filter: String,
    filterMethod: {
      type: Function,
      default (node, filter) {
        const filt = filter.toLowerCase()
        return node[this.labelKey] &&
          node[this.labelKey].toLowerCase().indexOf(filt) > -1
      }
    },

    noNodesLabel: String,
    noResultsLabel: String
  },
  computed: {
    hasRipple () {
      return process.env.THEME === 'mat' && !this.noRipple
    },
    classes () {
      return [
        `text-${this.color}`,
        { 'q-tree-dark': this.dark }
      ]
    },
    hasSelection () {
      return this.selected !== void 0
    },
    computedIcon () {
      return this.icon || this.$q.icon.tree.icon
    },
    computedControlColor () {
      return this.controlColor || this.color
    },
    contentClass () {
      return `text-${this.textColor || (this.dark ? 'white' : 'black')}`
    },
    meta () {
      const meta = {}

      const travel = (node, parent) => {
        const tickStrategy = node.tickStrategy || (parent ? parent.tickStrategy : this.tickStrategy)
        const
          key = node[this.nodeKey],
          isParent = node.children && node.children.length > 0,
          isLeaf = !isParent,
          selectable = !node.disabled && this.hasSelection && node.selectable !== false,
          expandable = !node.disabled && node.expandable !== false,
          hasTicking = tickStrategy !== 'none',
          strictTicking = tickStrategy === 'strict',
          leafFilteredTicking = tickStrategy === 'leaf-filtered',
          leafTicking = tickStrategy === 'leaf' || tickStrategy === 'leaf-filtered'

        let tickable = !node.disabled && node.tickable !== false
        if (leafTicking && tickable && parent && !parent.tickable) {
          tickable = false
        }

        let lazy = node.lazy
        if (lazy && this.lazy[key]) {
          lazy = this.lazy[key]
        }

        const m = {
          key,
          parent,
          isParent,
          isLeaf,
          lazy,
          disabled: node.disabled,
          link: selectable || (expandable && (isParent || lazy === true)),
          children: [],
          matchesFilter: this.filter ? this.filterMethod(node, this.filter) : true,

          selected: key === this.selected && selectable,
          selectable,
          expanded: isParent ? this.innerExpanded.includes(key) : false,
          expandable,
          noTick: node.noTick || (!strictTicking && lazy && lazy !== 'loaded'),
          tickable,
          tickStrategy,
          hasTicking,
          strictTicking,
          leafFilteredTicking,
          leafTicking,
          ticked: strictTicking
            ? this.innerTicked.includes(key)
            : (isLeaf ? this.innerTicked.includes(key) : false)
        }

        meta[key] = m

        if (isParent) {
          m.children = node.children.map(n => travel(n, m))

          if (this.filter) {
            if (!m.matchesFilter) {
              m.matchesFilter = m.children.some(n => n.matchesFilter)
            }
            if (
              m.matchesFilter &&
              !m.noTick &&
              !m.disabled &&
              m.tickable &&
              leafFilteredTicking &&
              m.children.every(n => !n.matchesFilter || n.noTick || !n.tickable)
            ) {
              m.tickable = false
            }
          }

          if (m.matchesFilter) {
            if (!m.noTick && !strictTicking && m.children.every(n => n.noTick)) {
              m.noTick = true
            }

            if (leafTicking) {
              m.ticked = false
              m.indeterminate = m.children.some(node => node.indeterminate)

              if (!m.indeterminate) {
                const sel = m.children
                  .reduce((acc, meta) => meta.ticked ? acc + 1 : acc, 0)

                if (sel === m.children.length) {
                  m.ticked = true
                }
                else if (sel > 0) {
                  m.indeterminate = true
                }
              }
            }
          }
        }

        return m
      }

      this.nodes.forEach(node => travel(node, null))
      return meta
    }
  },
  data () {
    return {
      lazy: {},
      innerTicked: this.ticked || [],
      innerExpanded: this.expanded || []
    }
  },
  watch: {
    ticked (val) {
      this.innerTicked = val
    },
    expanded (val) {
      this.innerExpanded = val
    }
  },
  methods: {
    getNodeByKey (key) {
      const reduce = [].reduce

      const find = (result, node) => {
        if (result || !node) {
          return result
        }
        if (Array.isArray(node)) {
          return reduce.call(Object(node), find, result)
        }
        if (node[this.nodeKey] === key) {
          return node
        }
        if (node.children) {
          return find(null, node.children)
        }
      }

      return find(null, this.nodes)
    },
    getTickedNodes () {
      return this.innerTicked.map(key => this.getNodeByKey(key))
    },
    getExpandedNodes () {
      return this.innerExpanded.map(key => this.getNodeByKey(key))
    },
    isExpanded (key) {
      return key && this.meta[key]
        ? this.meta[key].expanded
        : false
    },
    collapseAll () {
      if (this.expanded !== void 0) {
        this.$emit('update:expanded', [])
      }
      else {
        this.innerExpanded = []
      }
    },
    expandAll () {
      const
        expanded = this.innerExpanded,
        travel = node => {
          if (node.children && node.children.length > 0) {
            if (node.expandable !== false && node.disabled !== true) {
              expanded.push(node[this.nodeKey])
              node.children.forEach(travel)
            }
          }
        }

      this.nodes.forEach(travel)

      if (this.expanded !== void 0) {
        this.$emit('update:expanded', expanded)
      }
      else {
        this.innerExpanded = expanded
      }
    },
    setExpanded (key, state, node = this.getNodeByKey(key), meta = this.meta[key]) {
      if (meta.lazy && meta.lazy !== 'loaded') {
        if (meta.lazy === 'loading') {
          return
        }

        this.$set(this.lazy, key, 'loading')
        this.$emit('lazy-load', {
          node,
          key,
          done: children => {
            this.lazy[key] = 'loaded'
            if (children) {
              node.children = children
            }
            this.$nextTick(() => {
              const m = this.meta[key]
              if (m && m.isParent) {
                this.__setExpanded(key, true)
              }
            })
          },
          fail: () => {
            this.$delete(this.lazy, key)
          }
        })
      }
      else if (meta.isParent && meta.expandable) {
        this.__setExpanded(key, state)
      }
    },
    __setExpanded (key, state) {
      let target = this.innerExpanded
      const emit = this.expanded !== void 0

      if (emit) {
        target = target.slice()
      }

      if (state) {
        if (this.accordion) {
          if (this.meta[key]) {
            const collapse = []
            if (this.meta[key].parent) {
              this.meta[key].parent.children.forEach(m => {
                if (m.key !== key && m.expandable) {
                  collapse.push(m.key)
                }
              })
            }
            else {
              this.nodes.forEach(node => {
                const k = node[this.nodeKey]
                if (k !== key) {
                  collapse.push(k)
                }
              })
            }
            if (collapse.length > 0) {
              target = target.filter(k => !collapse.includes(k))
            }
          }
        }

        target = target.concat([ key ])
          .filter((key, index, self) => self.indexOf(key) === index)
      }
      else {
        target = target.filter(k => k !== key)
      }

      if (emit) {
        this.$emit(`update:expanded`, target)
      }
      else {
        this.innerExpanded = target
      }
    },
    isTicked (key) {
      return key && this.meta[key]
        ? this.meta[key].ticked
        : false
    },
    setTicked (keys, state) {
      let target = this.innerTicked
      const emit = this.ticked !== void 0

      if (emit) {
        target = target.slice()
      }

      if (state) {
        target = target.concat(keys)
          .filter((key, index, self) => self.indexOf(key) === index)
      }
      else {
        target = target.filter(k => !keys.includes(k))
      }

      if (emit) {
        this.$emit(`update:ticked`, target)
      }
    },
    __getSlotScope (node, meta, key) {
      const scope = { tree: this, node, key, color: this.color, dark: this.dark }

      Object.defineProperty(scope, 'expanded', {
        get: () => { return meta.expanded },
        set: val => { val !== meta.expanded && this.setExpanded(key, val) }
      })
      Object.defineProperty(scope, 'ticked', {
        get: () => { return meta.ticked },
        set: val => { val !== meta.ticked && this.setTicked([ key ], val) }
      })

      return scope
    },
    __getChildren (h, nodes) {
      return (
        this.filter
          ? nodes.filter(n => this.meta[n[this.nodeKey]].matchesFilter)
          : nodes
      ).map(child => this.__getNode(h, child))
    },
    __getNodeMedia (h, node) {
      if (node.icon) {
        return h(QIcon, {
          staticClass: `q-tree-icon q-mr-sm`,
          props: { name: node.icon, color: node.iconColor }
        })
      }
      if (node.img || node.avatar) {
        return h('img', {
          staticClass: `q-tree-img q-mr-sm`,
          'class': { avatar: node.avatar },
          attrs: { src: node.img || node.avatar }
        })
      }
    },
    __getNode (h, node) {
      const
        key = node[this.nodeKey],
        meta = this.meta[key],
        header = node.header
          ? this.$scopedSlots[`header-${node.header}`] || this.$scopedSlots['default-header']
          : this.$scopedSlots['default-header']

      const children = meta.isParent
        ? this.__getChildren(h, node.children)
        : []

      const isParent = children.length > 0 || (meta.lazy && meta.lazy !== 'loaded')

      let
        body = node.body
          ? this.$scopedSlots[`body-${node.body}`] || this.$scopedSlots['default-body']
          : this.$scopedSlots['default-body'],
        slotScope = header || body
          ? this.__getSlotScope(node, meta, key)
          : null

      if (body) {
        body = h('div', { staticClass: 'q-tree-node-body relative-position' }, [
          h('div', { 'class': this.contentClass }, [
            body(slotScope)
          ])
        ])
      }

      return h('div', {
        key,
        staticClass: 'q-tree-node',
        'class': { 'q-tree-node-parent': isParent, 'q-tree-node-child': !isParent }
      }, [
        h('div', {
          staticClass: 'q-tree-node-header relative-position row no-wrap items-center',
          'class': {
            'q-tree-node-link': meta.link,
            'q-tree-node-selected': meta.selected,
            disabled: meta.disabled
          },
          on: { click: () => { this.__onClick(node, meta) } },
          directives: process.env.THEME === 'mat' && meta.selectable
            ? [{ name: 'ripple' }]
            : null
        }, [
          meta.lazy === 'loading'
            ? h(QSpinner, {
              staticClass: 'q-tree-node-header-media q-mr-xs',
              props: { color: this.computedControlColor }
            })
            : (
              isParent
                ? h(QIcon, {
                  staticClass: 'q-tree-arrow q-mr-xs transition-generic',
                  'class': { 'q-tree-arrow-rotate': meta.expanded },
                  props: { name: this.computedIcon },
                  nativeOn: {
                    click: e => {
                      this.__onExpandClick(node, meta, e)
                    }
                  }
                })
                : null
            ),

          h('span', { 'staticClass': 'row no-wrap items-center', 'class': this.contentClass }, [
            meta.hasTicking && !meta.noTick
              ? h(QCheckbox, {
                staticClass: 'q-mr-xs',
                props: {
                  value: meta.indeterminate ? null : meta.ticked,
                  color: this.computedControlColor,
                  dark: this.dark,
                  keepColor: true,
                  disable: !meta.tickable
                },
                on: {
                  input: v => {
                    this.__onTickedClick(node, meta, v)
                  }
                }
              })
              : null,
            header
              ? header(slotScope)
              : [
                this.__getNodeMedia(h, node),
                h('span', node[this.labelKey])
              ]
          ])
        ]),

        isParent
          ? h(QSlideTransition, [
            h('div', {
              directives: [{ name: 'show', value: meta.expanded }],
              staticClass: 'q-tree-node-collapsible',
              'class': `text-${this.color}`
            }, [
              body,
              h('div', {
                staticClass: 'q-tree-children',
                'class': { disabled: meta.disabled }
              }, children)
            ])
          ])
          : body
      ])
    },
    __onClick (node, meta) {
      if (this.hasSelection) {
        if (meta.selectable) {
          this.$emit('update:selected', meta.key !== this.selected ? meta.key : null)
        }
      }
      else {
        this.__onExpandClick(node, meta)
      }

      if (typeof node.handler === 'function') {
        node.handler(node)
      }
    },
    __onExpandClick (node, meta, e) {
      if (e !== void 0) {
        e.stopPropagation()
      }
      this.setExpanded(meta.key, !meta.expanded, node, meta)
    },
    __onTickedClick (node, meta, state) {
      if (meta.indeterminate && state) {
        state = false
      }
      if (meta.strictTicking) {
        this.setTicked([ meta.key ], state)
      }
      else if (meta.leafTicking) {
        const keys = []
        const travel = meta => {
          if (meta.isParent) {
            if (!state && !meta.noTick && meta.tickable) {
              keys.push(meta.key)
            }
            if (meta.leafTicking) {
              meta.children.forEach(travel)
            }
          }
          else if (!meta.noTick && meta.tickable && (!meta.leafFilteredTicking || meta.matchesFilter)) {
            keys.push(meta.key)
          }
        }
        travel(meta)
        this.setTicked(keys, state)
      }
    }
  },
  render (h) {
    const children = this.__getChildren(h, this.nodes)

    return h(
      'div', {
        staticClass: 'q-tree relative-position',
        'class': this.classes
      },
      children.length === 0
        ? (
          this.filter
            ? this.noResultsLabel || this.$q.i18n.tree.noResults
            : this.noNodesLabel || this.$q.i18n.tree.noNodes
        )
        : children
    )
  },
  created () {
    if (this.defaultExpandAll) {
      this.expandAll()
    }
  }
}
<|endoftext|>


<commit_before>import React from 'react'
import {Table, Popconfirm} from 'antd'
import {TweenOneGroup} from 'rc-tween-one'
import styles from './list.less'

class list extends React.Component {
  constructor (props) {
    super(props)
    this.enterAnim = [
      {
        opacity: 0,
        x: 30,
        backgroundColor: '#fffeee',
        duration: 0
      }, {
        height: 0,
        duration: 200,
        type: 'from',
        delay: 250,
        ease: 'easeOutQuad',
        onComplete: this.onEnd
      }, {
        opacity: 1,
        x: 0,
        duration: 250,
        ease: 'easeOutQuad'
      }, {
        delay: 1000,
        backgroundColor: '#fff'
      }
    ]
    this.leaveAnim = [
      {
        duration: 250,
        opacity: 0
      }, {
        height: 0,
        duration: 200,
        ease: 'easeOutQuad'
      }
    ]
    const {current} = this.props.pagination
    this.currentPage = current
    this.newPage = current
  }

  getBodyWrapper = (body) => {
    // 切换分页去除动画;
    if (this.currentPage !== this.newPage) {
      this.currentPage = this.newPage
      return body
    }
    return (
      <TweenOneGroup component='tbody' className={body.props.className} enter={this.enterAnim} leave={this.leaveAnim} appear={false}>
        {body.props.children}
      </TweenOneGroup>
    )
  }

  onEnd = (e) => {
    e.target.style.height = 'auto'
  }

  async pageChange (pagination) {
    await this.props.onPageChange(pagination)
    this.newPage = pagination.current
  }

  render () {
    const {
      loading,
      dataSource,
      pagination,
      onDeleteItem,
      onEditItem
    } = this.props
    const columns = [
      {
        title: '头像',
        dataIndex: 'avatar',
        key: 'avatar',
        width: 64,
        className: styles.avatar,
        render: (text) =>
      }, {
        title: '姓名',
        dataIndex: 'name',
        key: 'name'
      }, {
        title: '昵称',
        dataIndex: 'nickName',
        key: 'nickName'
      }, {
        title: '年龄',
        dataIndex: 'age',
        key: 'age',
        render: (text) => {text}岁
      }, {
        title: '性别',
        dataIndex: 'isMale',
        key: 'isMale',
        render: (text) => {text
              ? '男'
              : '女'}
      }, {
        title: '电话',
        dataIndex: 'phone',
        key: 'phone'
      }, {
        title: '邮箱',
        dataIndex: 'email',
        key: 'email'
      }, {
        title: '住址',
        dataIndex: 'address',
        key: 'address'
      }, {
        title: '创建时间',
        dataIndex: 'createTime',
        key: 'createTime'
      }, {
        title: '操作',
        key: 'operation',
        width: 100,
        render: (text, record) => (

             onEditItem(record)} style={{
              marginRight: 4
            }}>编辑
            <Popconfirm title='确定要删除吗？' onConfirm={() => onDeleteItem(record.id)}>
              删除
            </Popconfirm>

        )
      }
    ]
    return

 record.id} getBodyWrapper={this.getBodyWrapper} />

  }
}

export default list
<commit_msg>option style<commit_after>import React from 'react'
import {Table, Dropdown, Button, Menu, Icon, Modal} from 'antd'
import {TweenOneGroup} from 'rc-tween-one'
import styles from './list.less'
const confirm = Modal.confirm

class list extends React.Component {
  constructor (props) {
    super(props)
    this.enterAnim = [
      {
        opacity: 0,
        x: 30,
        backgroundColor: '#fffeee',
        duration: 0
      }, {
        height: 0,
        duration: 200,
        type: 'from',
        delay: 250,
        ease: 'easeOutQuad',
        onComplete: this.onEnd
      }, {
        opacity: 1,
        x: 0,
        duration: 250,
        ease: 'easeOutQuad'
      }, {
        delay: 1000,
        backgroundColor: '#fff'
      }
    ]
    this.leaveAnim = [
      {
        duration: 250,
        opacity: 0
      }, {
        height: 0,
        duration: 200,
        ease: 'easeOutQuad'
      }
    ]
    const {current} = this.props.pagination
    this.currentPage = current
    this.newPage = current
  }

  getBodyWrapper = (body) => {
    // 切换分页去除动画;
    if (this.currentPage !== this.newPage) {
      this.currentPage = this.newPage
      return body
    }
    return (
      <TweenOneGroup component='tbody' className={body.props.className} enter={this.enterAnim} leave={this.leaveAnim} appear={false}>
        {body.props.children}
      </TweenOneGroup>
    )
  }

  handleMenuClick = (record, e) => {
  const {onDeleteItem, onEditItem} = this.props
  if (e.key === '1') {
    onEditItem(record)
  } else if (e.key === '2') {
    confirm({
      title: '您确定要删除这条记录吗?',
      onOk () {
        onDeleteItem(record.id)
      }
    })
  }
}

  onEnd = (e) => {
    e.target.style.height = 'auto'
  }

  async pageChange (pagination) {
    await this.props.onPageChange(pagination)
    this.newPage = pagination.current
  }

  render () {
    const {
      loading,
      dataSource,
      pagination,
      onDeleteItem,
      onEditItem
    } = this.props
    const columns = [
      {
        title: '头像',
        dataIndex: 'avatar',
        key: 'avatar',
        width: 64,
        className: styles.avatar,
        render: (text) =>
      }, {
        title: '姓名',
        dataIndex: 'name',
        key: 'name'
      }, {
        title: '昵称',
        dataIndex: 'nickName',
        key: 'nickName'
      }, {
        title: '年龄',
        dataIndex: 'age',
        key: 'age',
        render: (text) => {text}岁
      }, {
        title: '性别',
        dataIndex: 'isMale',
        key: 'isMale',
        render: (text) => {text
              ? '男'
              : '女'}
      }, {
        title: '电话',
        dataIndex: 'phone',
        key: 'phone'
      }, {
        title: '邮箱',
        dataIndex: 'email',
        key: 'email'
      }, {
        title: '住址',
        dataIndex: 'address',
        key: 'address'
      }, {
        title: '创建时间',
        dataIndex: 'createTime',
        key: 'createTime'
      }, {
        title: '操作',
        key: 'operation',
        width: 100,
        render: (text, record) => {
         return (<Dropdown overlay={<Menu onClick={this.handleMenuClick.bind(null, record)}>
           <Menu.Item key='1'>编辑</Menu.Item>
           <Menu.Item key='2'>删除</Menu.Item>
         </Menu>}>
           <Button style={{ border: 'none' }}>
             <Icon style={{ marginRight: 2 }} type='bars' />
             <Icon type='down' />
           </Button>
         </Dropdown>)
       }
      }
    ]
    return

 record.id} getBodyWrapper={this.getBodyWrapper} />

  }
}

export default list
<|endoftext|>


<commit_before>/**
 * @module lws
 */

const util = require('./util')
const t = require('typical')

/**
 * @alias module:lws
 */
class Lws {
  constructor (options) {
    this.options = options
    this.view = options.view

    /* validate stack */
    const Stack = require('./stack')
    if (!(options.stack instanceof Stack)) {
      options.stack = Stack.create(options.stack)
    }

    /**
     * Server instance.
     * @type {HTTPServer|HTTPSServer}
     */
    this.server = this.createServer(options)
    if (t.isDefined(options.maxConnections)) this.server.maxConnections = options.maxConnections
    if (t.isDefined(options.keepAliveTimeout)) this.server.keepAliveTimeout = options.keepAliveTimeout

    const Koa = require('koa')
    this.app = new Koa()
  }

  /**
   * Launch a server.
   * @param [options] {object} - Server options
   * @param [options.port] {number} - Port
   * @param [options.hostname] {string} -The hostname (or IP address) to listen on. Defaults to 0.0.0.0.
   * @param [options.configFile] {string} - Config file, defaults to 'lws.config.js'.
   * @param [options.stack] {string[]|Features[]} - Array of feature classes, or filenames of modules exporting a feature class.
   * @param [options.moduleDir] {string[]} - One or more directories to search for feature modules.
   * @param [options.https] {boolean} - Enable HTTPS using a built-in key and cert, registered to the domain 127.0.0.1.
   * @param [options.key] {string} - SSL key. Supply along with --cert to launch a https server.
   * @param [options.cert] {string} - SSL cert. Supply along with --key to launch a https server.
   */
  launch () {
    this.attachView()
    const options = this.options

    /* build Koa application, add it to server */
    const middlewares = options.stack.getMiddlewareFunctions(options)
    middlewares.forEach(middleware => this.app.use(middleware))
    this.server.on('request', this.app.callback())

    /* start server */
    this.server.listen(options.port, options.hostname)

    const interval = setInterval(() => {
      const byteSize = require('byte-size')
      const memUsage = process.memoryUsage()
      memUsage.rss = byteSize(memUsage.rss).toString()
      memUsage.heapTotal = byteSize(memUsage.heapTotal).toString()
      memUsage.heapUsed = byteSize(memUsage.heapUsed).toString()
      memUsage.external = byteSize(memUsage.external).toString()
      this.view.write('process.memoryUsage', memUsage)
    }, 30000)

    this.server.on('close', () => {
      clearInterval(interval)
    })
  }

  attachView () {
    const options = this.options

    /* attach view to middlewares */
    options.stack.attachView(this.view)

    /* attach view to Koa */
    this.app.on('error', err => {
      this.view.write('koa-error', err.stack)
      util.printError(err, 'Middleware error')
    })

    /* attach to view to server */
    attachServerToView(this.server, this.view, options)
  }

  /**
   * Returns a nodejs Server instance.
   * @returns {Server}
   * @ignore
   */
  createServer (options) {
    let ServerFactory = require('./server-factory')
    const loadModule = require('load-module')

    /* validation */
    if ((options.key && !options.cert) || (!options.key && options.cert)) {
      throw new Error('--key and --cert must always be supplied together.')
    } else if (options.https && options.pfx) {
      throw new Error('please use one of --https or --pfx, not both.')
    }

    /* create server */
    if (options.https || (options.key && options.cert) || options.pfx) {
      ServerFactory = require('./server-factory-https')(ServerFactory)
    } else if (options.server) {
      ServerFactory = loadModule(options.server)(ServerFactory)
    }
    const factory = new ServerFactory()
    factory.view = this.view
    return factory.create(options)
  }
}

function attachServerToView (server, view, options) {
  function write (name, value) {
    return function () {
      view.write(name, value)
    }
  }

  function socketProperties (socket) {
    return {
      id: socket.id,
      bytesRead: socket.bytesRead,
      bytesWritten: socket.bytesWritten
    }
  }

  let cId = 1
  server.on('connection', (socket) => {
    socket.id = cId++
    view.write('server.socket.new', socketProperties(socket))
    socket.on('connect', write('server.socket.connect', socketProperties(socket, cId)))
    socket.on('data', function () {
      view.write('server.socket.data', socketProperties(this))
    })
    socket.on('drain', function () {
      view.write('server.socket.drain', socketProperties(this))
    })
    socket.on('timeout', function () {
      view.write('server.socket.timeout', socketProperties(this))
    })
    socket.on('close', function () {
      view.write('server.socket.close', socketProperties(this))
    })
    socket.on('end', write('server.socket.end', socketProperties(socket, cId)))
    socket.on('lookup', write('server.socket.connect', socketProperties(socket, cId)))
  })
  server.on('close', write('server.close'))
  server.on('error', err => {
    view.write('server.error', err.stack)
    util.printError(err, 'Server error')
  })
  server.on('request', write('server.request'))
  server.on('checkContinue', write('server.checkContinue'))
  server.on('checkExpectation', write('server.checkExpectation'))
  server.on('clientError', write('server.clientError'))
  server.on('connect', write('server.connect'))
  server.on('upgrade', write('server.upgrade'))

  /* on server-up message */
  server.on('listening', () => {
    const isSecure = t.isDefined(server.addContext)
    let ipList
    if (options.hostname) {
      ipList = [ `${isSecure ? 'https' : 'http'}://${options.hostname}:${options.port}` ]
    } else {
      ipList = util.getIPList()
        .map(iface => `${isSecure ? 'https' : 'http'}://${iface.address}:${options.port}`)
    }
    view.write('server.listening', ipList)
  })
}

module.exports = Lws
<commit_msg>fix test<commit_after>/**
 * @module lws
 */

const util = require('./util')
const t = require('typical')

/**
 * @alias module:lws
 */
class Lws {
  constructor (options) {
    this.options = options
    const View = require('./view')
    this.view = options.view || new View()

    /* validate stack */
    const Stack = require('./stack')
    if (!(options.stack instanceof Stack)) {
      options.stack = Stack.create(options.stack)
    }

    /**
     * Server instance.
     * @type {HTTPServer|HTTPSServer}
     */
    this.server = this.createServer(options)
    if (t.isDefined(options.maxConnections)) this.server.maxConnections = options.maxConnections
    if (t.isDefined(options.keepAliveTimeout)) this.server.keepAliveTimeout = options.keepAliveTimeout

    const Koa = require('koa')
    this.app = new Koa()
  }

  /**
   * Launch a server.
   * @param [options] {object} - Server options
   * @param [options.port] {number} - Port
   * @param [options.hostname] {string} -The hostname (or IP address) to listen on. Defaults to 0.0.0.0.
   * @param [options.configFile] {string} - Config file, defaults to 'lws.config.js'.
   * @param [options.stack] {string[]|Features[]} - Array of feature classes, or filenames of modules exporting a feature class.
   * @param [options.moduleDir] {string[]} - One or more directories to search for feature modules.
   * @param [options.https] {boolean} - Enable HTTPS using a built-in key and cert, registered to the domain 127.0.0.1.
   * @param [options.key] {string} - SSL key. Supply along with --cert to launch a https server.
   * @param [options.cert] {string} - SSL cert. Supply along with --key to launch a https server.
   */
  launch () {
    this.attachView()
    const options = this.options

    /* build Koa application, add it to server */
    const middlewares = options.stack.getMiddlewareFunctions(options)
    middlewares.forEach(middleware => this.app.use(middleware))
    this.server.on('request', this.app.callback())

    /* start server */
    this.server.listen(options.port, options.hostname)

    const interval = setInterval(() => {
      const byteSize = require('byte-size')
      const memUsage = process.memoryUsage()
      memUsage.rss = byteSize(memUsage.rss).toString()
      memUsage.heapTotal = byteSize(memUsage.heapTotal).toString()
      memUsage.heapUsed = byteSize(memUsage.heapUsed).toString()
      memUsage.external = byteSize(memUsage.external).toString()
      this.view.write('process.memoryUsage', memUsage)
    }, 30000)

    this.server.on('close', () => {
      clearInterval(interval)
    })
  }

  attachView () {
    const options = this.options

    /* attach view to middlewares */
    options.stack.attachView(this.view)

    /* attach view to Koa */
    this.app.on('error', err => {
      this.view.write('koa-error', err.stack)
      util.printError(err, 'Middleware error')
    })

    /* attach to view to server */
    attachServerToView(this.server, this.view, options)
  }

  /**
   * Returns a nodejs Server instance.
   * @returns {Server}
   * @ignore
   */
  createServer (options) {
    let ServerFactory = require('./server-factory')
    const loadModule = require('load-module')

    /* validation */
    if ((options.key && !options.cert) || (!options.key && options.cert)) {
      throw new Error('--key and --cert must always be supplied together.')
    } else if (options.https && options.pfx) {
      throw new Error('please use one of --https or --pfx, not both.')
    }

    /* create server */
    if (options.https || (options.key && options.cert) || options.pfx) {
      ServerFactory = require('./server-factory-https')(ServerFactory)
    } else if (options.server) {
      ServerFactory = loadModule(options.server)(ServerFactory)
    }
    const factory = new ServerFactory()
    factory.view = this.view
    return factory.create(options)
  }
}

function attachServerToView (server, view, options) {
  function write (name, value) {
    return function () {
      view.write(name, value)
    }
  }

  function socketProperties (socket) {
    return {
      id: socket.id,
      bytesRead: socket.bytesRead,
      bytesWritten: socket.bytesWritten
    }
  }

  let cId = 1
  server.on('connection', (socket) => {
    socket.id = cId++
    view.write('server.socket.new', socketProperties(socket))
    socket.on('connect', write('server.socket.connect', socketProperties(socket, cId)))
    socket.on('data', function () {
      view.write('server.socket.data', socketProperties(this))
    })
    socket.on('drain', function () {
      view.write('server.socket.drain', socketProperties(this))
    })
    socket.on('timeout', function () {
      view.write('server.socket.timeout', socketProperties(this))
    })
    socket.on('close', function () {
      view.write('server.socket.close', socketProperties(this))
    })
    socket.on('end', write('server.socket.end', socketProperties(socket, cId)))
    socket.on('lookup', write('server.socket.connect', socketProperties(socket, cId)))
  })
  server.on('close', write('server.close'))
  server.on('error', err => {
    view.write('server.error', err.stack)
    util.printError(err, 'Server error')
  })
  server.on('request', write('server.request'))
  server.on('checkContinue', write('server.checkContinue'))
  server.on('checkExpectation', write('server.checkExpectation'))
  server.on('clientError', write('server.clientError'))
  server.on('connect', write('server.connect'))
  server.on('upgrade', write('server.upgrade'))

  /* on server-up message */
  server.on('listening', () => {
    const isSecure = t.isDefined(server.addContext)
    let ipList
    if (options.hostname) {
      ipList = [ `${isSecure ? 'https' : 'http'}://${options.hostname}:${options.port}` ]
    } else {
      ipList = util.getIPList()
        .map(iface => `${isSecure ? 'https' : 'http'}://${iface.address}:${options.port}`)
    }
    view.write('server.listening', ipList)
  })
}

module.exports = Lws
<|endoftext|>


<commit_before>(function(){
	"use strict";

	KC3StrategyTabs.expedpast = new KC3StrategyTab("expedpast");

	KC3StrategyTabs.expedpast.definition = {
		tabSelf: KC3StrategyTabs.expedpast,

		exped_filters: [],
		fleet_filters: [2,3,4],
		useItemMap: {
			1:"bucket",
			2:"ibuild",
			3:"devmat",
			4:"box1",
			5:"box2",
			6:"box3",
		},

		/* INIT
		Prepares all data needed
		---------------------------------*/
		init :function(){

		},

		/* EXECUTE
		Places data onto the interface
		---------------------------------*/
		execute :function(){
			const self = this;

			// Add all expedition numbers on the filter list
			const KE = PS["KanColle.Expedition"];
			$('.tab_expedpast .expedNumbers').empty();
			KE.allExpeditions.forEach( function(curVal, ind) {
				var row = $('.tab_expedpast .factory .expedNum').clone();
				$(".expedCheck input", row).attr("value", curVal.id);
				$(".expedCheck input", row).attr("world", curVal.world);
				$(".expedText", row).text( curVal.name );
				$(".expedTime", row).text( (curVal.cost.time*60).toString().toHHMMSS().substring(0,5) );

				self.exped_filters.push(curVal.id);

				$(".tab_expedpast .expedNumBox_"+curVal.world).append( row );
			});

			// Add world toggle
			$(".tab_expedpast .expedNumBox")
				.filter(function(i,x){return $(x).hasClass("expedNumBox_"+(i+1));})
				.each(function(i,x){
					var
						row = $('.tab_expedpast .factory .expedNum').clone().addClass("expedWhole").removeClass("expedNum"),
						val = true;
					$("input",".expedNumBox_"+(i+1)).each(function(id,elm){
						val&= $(elm).prop("checked");
					});
					$(row)
						.find(".expedCheck input")
							.attr("value", i+1)
							.prop("checked", val)
						.end()
						.find(".expedText")
							.text( "World " + (i+1) )
						.end()
						.find(".expedTime")
							.remove()
						.end();

					$(x).prepend(row);
				});

			// Expedition Number Filter
			$(".tab_expedpast .expedNumBox").on("click", '.expedNum input', function(){
				var
					filterExpeds = $('.tab_expedpast .expedNumBox .expedNum input:checked'),
					worldNum     = $(this).attr("world"),
					context      = ".tab_expedpast .expedNumBox_"+worldNum,
					parentCheck  = true;
				self.exped_filters = [];
				$(".expedNum   input",context).each(function(i,x){ parentCheck &= $(x).prop("checked"); });
				$(".expedWhole input",context).prop("checked",parentCheck);
				filterExpeds.each( function() {
					self.exped_filters.push( parseInt( $(this).attr("value"),10) );
				});
				self.tabSelf.definition.refreshList();
			}).on("click", ".expedWhole input", function() {
				var
					worldNum = $(this).val(),
					state    = $(this).prop("checked"),
					expeds   = $(".tab_expedpast .expedNumBox_"+worldNum+" .expedNum input");
				expeds.each(function(i,x){
					var
						elmState = $(x).prop("checked"),
						expedNum = parseInt($(x).val());
					if(elmState ^ state) { // check different state
						if(elmState) { // set -> unset
							self.exped_filters.splice(self.exped_filters.indexOf(expedNum),1);
						} else { // unset -> set
							self.exped_filters.push(expedNum);
						}
						$(x).prop("checked",state);
					}
				});
				self.exped_filters.sort(function(a,b){return a-b;});
				self.tabSelf.definition.refreshList();
			});

			// Fleet Number Filter
			$(".tab_expedpast .expedNumBox").on("click", '.fleetRadio input', function(){
				var filterFleets = $('.tab_expedpast .expedNumBox .fleetRadio input:checked');
				self.fleet_filters = [];
				filterFleets.each( function() {
					self.fleet_filters.push( parseInt( $(this).attr("value"),10) );
				});
				self.tabSelf.definition.refreshList();
			});

			// Show initial list
			self.tabSelf.definition.refreshList();
		},

		refreshList :function(){
			const self = this;

			$(".tab_expedpast .exped_list").empty();
			$(".tab_expedpast .exped_pages").html('

');
			// Get pagination
			KC3Database.count_expeds(this.exped_filters, this.fleet_filters, function(numRecords){
				const itemsPerPage = 20;
				const numPages = Math.ceil(numRecords / itemsPerPage);
				//console.debug("count_expeds", numRecords);

				if(numPages > 0){
					$('.tab_expedpast .pagination').twbsPagination({
						totalPages: numPages,
						visiblePages: 9,
						onPageClick: function (event, page) {
							self.tabSelf.definition.showPage( page );
						}
					});
					$('.tab_expedpast .pagination').show();
				}else{
					$('.tab_expedpast .pagination').hide();
				}
			});
		},

		showPage :function(pageNumber){
			const self = this;

			KC3Database.get_expeds(pageNumber, this.exped_filters, this.fleet_filters, function(response){
				//console.debug("get_expeds", response, self.exped_filters, self.fleet_filters);
				$(".tab_expedpast .exped_list").empty();

				for(let ctr in response){
					const ThisExped = response[ctr];
					//console.debug(ThisExped);

					const ExpedBox = $(".tab_expedpast .factory .exped_item").clone().appendTo(".tab_expedpast .exped_list");

					// Expedition date
					const expedDate = new Date(ThisExped.time * 1000);
					$(".exped_date", ExpedBox).text( expedDate.format("mmm dd") );
					$(".exped_time", ExpedBox).text( expedDate.format("hh:MM tt") );
					$(".exped_info", ExpedBox).attr("title", expedDate.format("yyyy-mm-dd HH:MM:ss"));

					// Number and HQ exp
					$(".exped_number", ExpedBox).text( KC3Master.missionDispNo(ThisExped.mission) );
					$(".exped_exp", ExpedBox).text( ThisExped.admiralXP );

					let drumCount = 0;
					// Ship List
					for(let sctr in ThisExped.fleet){
						const ThisShip = ThisExped.fleet[sctr];
						const ShipBox = $(".tab_expedpast .factory .exped_ship").clone();
						$(".exped_ship_img img", ShipBox).attr("src", KC3Meta.shipIcon(ThisShip.mst_id) );
						$(".exped_ship_exp", ShipBox).text( ThisExped.shipXP[sctr] );
						if(ThisShip.morale > 49){ $(".exped_ship_img", ShipBox).addClass("sparkled"); }
						$(".exped_ships", ExpedBox).append( ShipBox );
						for (var eId in ThisShip.equip) {
							if (ThisShip.equip[eId] === 75)
								++drumCount;
						}
					}

					// Drum count
					$(".exped_drums", ExpedBox).text( drumCount );

					// Resource gains
					for(let rctr in ThisExped.data.api_get_material){
						rctr = parseInt(rctr, 10);
						const rscAmt = ThisExped.data.api_get_material[rctr];
						if(rscAmt > 0){
							$(".exped_rsc"+(rctr+1), ExpedBox).text( rscAmt );
						}else{
							$(".exped_rsc"+(rctr+1), ExpedBox).text("");
							$(".exped_rsc"+(rctr+1), ExpedBox).addClass("empty");
						}
					}

					// Result image
					$(".exped_result img", ExpedBox).attr("src",
						"../../../../assets/img/client/exped_"+
						(["fail","fail","success","gs"][ThisExped.data.api_clear_result+1])
						+".png");

					// Reward item 1
					if(ThisExped.data.api_useitem_flag[0] > 0){
						$(".exped_item1 img", ExpedBox).attr("src",
							"../../assets/img/client/"+
							self.tabSelf.definition.useItemMap[
								ThisExped.data.api_useitem_flag[0]
							]+".png");
					}else{
						$("exped_item1 img", ExpedBox).hide();
					}

					// Reward item 2
					if(ThisExped.data.api_useitem_flag[1] > 0){
						$(".exped_item2 img", ExpedBox).attr("src",
							"../../assets/img/client/"+
							self.tabSelf.definition.useItemMap[
								ThisExped.data.api_useitem_flag[1]
							]+".png");
					}else{
						$("exped_item2 img", ExpedBox).hide();
					}
				}
			});
		}

	};

})();
<commit_msg>Load expeditions in expedpast from master data<commit_after>(function(){
	"use strict";

	KC3StrategyTabs.expedpast = new KC3StrategyTab("expedpast");

	KC3StrategyTabs.expedpast.definition = {
		tabSelf: KC3StrategyTabs.expedpast,

		exped_filters: [],
		fleet_filters: [2,3,4],
		useItemMap: {
			1:"bucket",
			2:"ibuild",
			3:"devmat",
			4:"box1",
			5:"box2",
			6:"box3",
		},

		/* INIT
		Prepares all data needed
		---------------------------------*/
		init :function(){

		},

		/* EXECUTE
		Places data onto the interface
		---------------------------------*/
		execute :function(){
			const self = this;

			// Add all expedition numbers on the filter list
			$('.tab_expedpast .expedNumbers').empty();
			if(localStorage.raw && JSON.parse(localStorage.raw).mission) {
				var missions = JSON.parse(localStorage.raw).mission;
				$.each(missions, function(ind, curVal) {
					var row = $('.tab_expedpast .factory .expedNum').clone();
					$(".expedCheck input", row).attr("value", curVal.api_id);
					$(".expedCheck input", row).attr("world", curVal.api_maparea_id);
					$(".expedText", row).text( curVal.api_disp_no );
					$(".expedTime", row).text( (curVal.api_time * 60).toString().toHHMMSS().substring(0,5) );

					self.exped_filters.push(curVal.api_id);

					$(".tab_expedpast .expedNumBox_"+curVal.api_maparea_id).append( row );
				});
			} else {
				// In case missing raw data
				const KE = PS["KanColle.Expedition"];
				$('.tab_expedpast .expedNumbers').empty();
				KE.allExpeditions.forEach( function(curVal, ind) {
					var row = $('.tab_expedpast .factory .expedNum').clone();
					$(".expedCheck input", row).attr("value", curVal.id);
					$(".expedCheck input", row).attr("world", curVal.world);
					$(".expedText", row).text( curVal.name );
					$(".expedTime", row).text( (curVal.cost.time*60).toString().toHHMMSS().substring(0,5) );

					self.exped_filters.push(curVal.id);

					$(".tab_expedpast .expedNumBox_"+curVal.world).append( row );
				});
			}

			// Add world toggle
			$(".tab_expedpast .expedNumBox")
				.filter(function(i,x){return $(x).hasClass("expedNumBox_"+(i+1));})
				.each(function(i,x){
					var
						row = $('.tab_expedpast .factory .expedNum').clone().addClass("expedWhole").removeClass("expedNum"),
						val = true;
					$("input",".expedNumBox_"+(i+1)).each(function(id,elm){
						val&= $(elm).prop("checked");
					});
					$(row)
						.find(".expedCheck input")
							.attr("value", i+1)
							.prop("checked", val)
						.end()
						.find(".expedText")
							.text( "World " + (i+1) )
						.end()
						.find(".expedTime")
							.remove()
						.end();

					$(x).prepend(row);
				});

			// Expedition Number Filter
			$(".tab_expedpast .expedNumBox").on("click", '.expedNum input', function(){
				var
					filterExpeds = $('.tab_expedpast .expedNumBox .expedNum input:checked'),
					worldNum     = $(this).attr("world"),
					context      = ".tab_expedpast .expedNumBox_"+worldNum,
					parentCheck  = true;
				self.exped_filters = [];
				$(".expedNum   input",context).each(function(i,x){ parentCheck &= $(x).prop("checked"); });
				$(".expedWhole input",context).prop("checked",parentCheck);
				filterExpeds.each( function() {
					self.exped_filters.push( parseInt( $(this).attr("value"),10) );
				});
				self.tabSelf.definition.refreshList();
			}).on("click", ".expedWhole input", function() {
				var
					worldNum = $(this).val(),
					state    = $(this).prop("checked"),
					expeds   = $(".tab_expedpast .expedNumBox_"+worldNum+" .expedNum input");
				expeds.each(function(i,x){
					var
						elmState = $(x).prop("checked"),
						expedNum = parseInt($(x).val());
					if(elmState ^ state) { // check different state
						if(elmState) { // set -> unset
							self.exped_filters.splice(self.exped_filters.indexOf(expedNum),1);
						} else { // unset -> set
							self.exped_filters.push(expedNum);
						}
						$(x).prop("checked",state);
					}
				});
				self.exped_filters.sort(function(a,b){return a-b;});
				self.tabSelf.definition.refreshList();
			});

			// Fleet Number Filter
			$(".tab_expedpast .expedNumBox").on("click", '.fleetRadio input', function(){
				var filterFleets = $('.tab_expedpast .expedNumBox .fleetRadio input:checked');
				self.fleet_filters = [];
				filterFleets.each( function() {
					self.fleet_filters.push( parseInt( $(this).attr("value"),10) );
				});
				self.tabSelf.definition.refreshList();
			});

			// Show initial list
			self.tabSelf.definition.refreshList();
		},

		refreshList :function(){
			const self = this;

			$(".tab_expedpast .exped_list").empty();
			$(".tab_expedpast .exped_pages").html('

');
			// Get pagination
			KC3Database.count_expeds(this.exped_filters, this.fleet_filters, function(numRecords){
				const itemsPerPage = 20;
				const numPages = Math.ceil(numRecords / itemsPerPage);
				//console.debug("count_expeds", numRecords);

				if(numPages > 0){
					$('.tab_expedpast .pagination').twbsPagination({
						totalPages: numPages,
						visiblePages: 9,
						onPageClick: function (event, page) {
							self.tabSelf.definition.showPage( page );
						}
					});
					$('.tab_expedpast .pagination').show();
				}else{
					$('.tab_expedpast .pagination').hide();
				}
			});
		},

		showPage :function(pageNumber){
			const self = this;

			KC3Database.get_expeds(pageNumber, this.exped_filters, this.fleet_filters, function(response){
				//console.debug("get_expeds", response, self.exped_filters, self.fleet_filters);
				$(".tab_expedpast .exped_list").empty();

				for(let ctr in response){
					const ThisExped = response[ctr];
					//console.debug(ThisExped);

					const ExpedBox = $(".tab_expedpast .factory .exped_item").clone().appendTo(".tab_expedpast .exped_list");

					// Expedition date
					const expedDate = new Date(ThisExped.time * 1000);
					$(".exped_date", ExpedBox).text( expedDate.format("mmm dd") );
					$(".exped_time", ExpedBox).text( expedDate.format("hh:MM tt") );
					$(".exped_info", ExpedBox).attr("title", expedDate.format("yyyy-mm-dd HH:MM:ss"));

					// Number and HQ exp
					$(".exped_number", ExpedBox).text( KC3Master.missionDispNo(ThisExped.mission) );
					$(".exped_exp", ExpedBox).text( ThisExped.admiralXP );

					let drumCount = 0;
					// Ship List
					for(let sctr in ThisExped.fleet){
						const ThisShip = ThisExped.fleet[sctr];
						const ShipBox = $(".tab_expedpast .factory .exped_ship").clone();
						$(".exped_ship_img img", ShipBox).attr("src", KC3Meta.shipIcon(ThisShip.mst_id) );
						$(".exped_ship_exp", ShipBox).text( ThisExped.shipXP[sctr] );
						if(ThisShip.morale > 49){ $(".exped_ship_img", ShipBox).addClass("sparkled"); }
						$(".exped_ships", ExpedBox).append( ShipBox );
						for (var eId in ThisShip.equip) {
							if (ThisShip.equip[eId] === 75)
								++drumCount;
						}
					}

					// Drum count
					$(".exped_drums", ExpedBox).text( drumCount );

					// Resource gains
					for(let rctr in ThisExped.data.api_get_material){
						rctr = parseInt(rctr, 10);
						const rscAmt = ThisExped.data.api_get_material[rctr];
						if(rscAmt > 0){
							$(".exped_rsc"+(rctr+1), ExpedBox).text( rscAmt );
						}else{
							$(".exped_rsc"+(rctr+1), ExpedBox).text("");
							$(".exped_rsc"+(rctr+1), ExpedBox).addClass("empty");
						}
					}

					// Result image
					$(".exped_result img", ExpedBox).attr("src",
						"../../../../assets/img/client/exped_"+
						(["fail","fail","success","gs"][ThisExped.data.api_clear_result+1])
						+".png");

					// Reward item 1
					if(ThisExped.data.api_useitem_flag[0] > 0){
						$(".exped_item1 img", ExpedBox).attr("src",
							"../../assets/img/client/"+
							self.tabSelf.definition.useItemMap[
								ThisExped.data.api_useitem_flag[0]
							]+".png");
					}else{
						$("exped_item1 img", ExpedBox).hide();
					}

					// Reward item 2
					if(ThisExped.data.api_useitem_flag[1] > 0){
						$(".exped_item2 img", ExpedBox).attr("src",
							"../../assets/img/client/"+
							self.tabSelf.definition.useItemMap[
								ThisExped.data.api_useitem_flag[1]
							]+".png");
					}else{
						$("exped_item2 img", ExpedBox).hide();
					}
				}
			});
		}

	};

})();
<|endoftext|>


<commit_before>'use strict';
var qs = require("querystring");
var crypto = require('crypto');
var xml2js = require('xml2js');
var request = require('request');
var _ = require('underscore');
var tls = require('tls');

/**
 * Constructor for the main MWS client interface used to make api calls and
 * various data structures to encapsulate MWS requests, definitions, etc.
 *
 * @param {String} accessKeyId     Id for your secret Access Key (required)
 * @param {String} secretAccessKey Secret Access Key provided by Amazon (required)
 * @param {String} merchantId      Aka SellerId, provided by Amazon (required)
 * @param {Object} options         Additional configuration options for this instance
 */
function AmazonMwsClient(accessKeyId, secretAccessKey, merchantId, options) {
	options = options || {};

	var createCredentials = tls.createSecureContext || crypto.createCredentials;
	this.host = options.host || 'mws.amazonservices.com';
	this.creds = createCredentials(options.creds || {});
	this.appName = options.appName || 'mws-js';
	this.appVersion = options.appVersion || '0.1.0';
	this.appLanguage = options.appLanguage || 'JavaScript';
	this.accessKeyId = accessKeyId || null;
	this.secretAccessKey = secretAccessKey || null;
	this.merchantId = merchantId || null;
	this.authToken = options.authToken;
}

/**
 * The method used to invoke calls against MWS Endpoints. Recommended usage is
 * through the invoke wrapper method when the api call you're invoking has a
 * request defined in one of the submodules. However, you can use call() manually
 * when a lower level of control is necessary (custom or new requests, for example).
 *
 * @param  {Object}   api      Settings object unique to each API submodule
 * @param  {String}   action   Api `Action`, such as GetServiceStatus or GetOrder
 * @param  {Object}   query    Any parameters belonging to the current action
 * @return Promise
 */
AmazonMwsClient.prototype.call = function(api, action, query) {
	if (this.secretAccessKey == null || this.accessKeyId == null || this.merchantId == null) {
		throw "accessKeyId, secretAccessKey, and merchantId must be set";
	}

	var requestOpts = {
		method: 'POST',
		uri: 'https://' + this.host + api.path
	};

	// Check if we're dealing with a file (such as a feed) upload
	if (api.upload) {
		requestOpts.body = query._BODY_;
		requestOpts.headers = {
			'Content-Type': query._FORMAT_,
			'Content-MD5': crypto.createHash('md5').update(query._BODY_).digest('base64')
		};
		delete query._BODY_;
		delete query._FORMAT_;
	}

	// Add required parameters and sign the query
	query['Action'] = action;
	query['Version'] = api.version;
	query["Timestamp"] = new Date().toISOString();
	query["AWSAccessKeyId"] = this.accessKeyId;

	if (this.authToken) {
		query["MWSAuthToken"] = this.authToken;
	}

	if (api.legacy) {
		query['Merchant'] = this.merchantId;
	} else {
		query['SellerId'] = this.merchantId;
	}

	query = this.sign(api.path, query);

	if (!api.upload) {
		requestOpts.form = query;
	} else {
		request.qs = query;
	}

	return new Promise(function(resolve, reject) {
		request(requestOpts, function(err, response, data) {
			if (err) {
				reject(err);
			} else {
				if (data.slice(0, 5) == '<?xml') {
					xml2js.parseString(data, function(err, result) {
						// Throw an error if there was a problem reported
						if (err != null) {
							reject(new Error(err.Code + ": " + err.Message));
						} else {
							resolve(result);
						}
					});
				} else {
					resolve(data);
				}
			}
		});
	});
};

/**
 * Calculates the HmacSHA256 signature and appends it with additional signature
 * parameters to the provided query object.
 *
 * @param  {String} path  Path of API call (used to build the string to sign)
 * @param  {Object} query Any non-signature parameters that will be sent
 * @return {Object}       Finalized object used to build query string of request
 */
AmazonMwsClient.prototype.sign = function(path, query) {
	// Configure the query signature method/version
	query["SignatureMethod"] = "HmacSHA256";
	query["SignatureVersion"] = "2";

	// Copy query keys, sort them, then copy over the values
	var sorted = _.reduce(_.keys(query).sort(), function(m, k) {
		m[k] = query[k];
		return m;
	}, {});

	var stringToSign = ["POST", this.host, path, qs.stringify(sorted)].join("\n");

	query['Signature'] = crypto.createHmac("sha256", this.secretAccessKey).update(stringToSign, 'utf8').digest("base64");

	return query;
};

/**
 * Suggested method for invoking a pre-defined mws request object.
 *
 * @param  {Object}   request  An instance of AmazonMwsRequest with params, etc.
 * @return Promise
 */
AmazonMwsClient.prototype.invoke = function(request) {
	var _this = this;
	return request.query().then(function(q) {
		return _this.call(request.api, request.action, q);
	});
};

/**
 * Constructor for general MWS request objects, wrapped by api submodules to keep
 * things DRY, yet familiar despite whichever api is being implemented.
 *
 * @param {Object} options Settings to apply to new request instance.
 */
function AmazonMwsRequest(options) {
	this.api = {
		path: options.path || '/',
		version: options.version || '2009-01-01',
		legacy: options.legacy || false,
		upload: options.upload
	};
	this.action = options.action || 'GetServiceStatus';
	this.params = options.params || {};
	this.paramsMap = {};

	if(Object.keys(this.params).length > 0){
		for( var name in this.params){
			var realName = this.params[name].name;
			if(name!== this.params[name].name){
				this.paramsMap[name] = realName;
				this.params[realName] = this.params[name];
				delete this.params[name];
			}
		}
	}
}

/**
 * Handles the casting, renaming, and setting of individual request params.
 *
 * @param {String} param Key of parameter (not ALWAYS the same as the param name!)
 * @param {Mixed} value Value to assign to parameter
 * @return {Object} Current instance to allow function chaining
 */
AmazonMwsRequest.prototype.set = function(param, value) {
	if (param instanceof Object && (value === null || value === undefined)) {
		return this.setMultiple(param);
	} else if (value !== null && value !== undefined) {
		var self = this;

		if(this.paramsMap.hasOwnProperty(param)){
				param = this.paramsMap[param];
		}

		var p = this.params[param],
			v = p.value = {};
		// Handles the actual setting based on type
		var setListValue = function setListValue(name, val) {
			if (p.type == 'Timestamp') {
				v[name] = val.toISOString();
			} else if (p.type == 'Boolean') {
				v[name] = val ? 'true' : 'false';
			} else {
				v[name] = val;
			}
		};

		// Handles the actual setting based on type
		var setValue = function setValue(name, val) {
			console.log(name, val)
			if (p.type == 'Timestamp') {
				self.params[name].value = val.toISOString();
			} else if (p.type == 'Boolean') {
				self.params[name].value = val ? 'true' : 'false';
			} else {
				self.params[name].value = val;
			}
		};

		// Lists need to be sequentially numbered and we take care of that here
		if (p.list) {
			var i = 0;
			if (typeof value == "string" || typeof value == "number") {
				setListValue(p.name + '.1', value);
			}
			if (typeof value == "object") {
				if (Array.isArray(value)) {
					for (i = value.length - 1; i >= 0; i--) {
						setListValue(p.name + '.' + (i + 1), value[i]);
					}
				} else {
					for (var key in value) {
						setListValue(p.name + '.' + ++i, value[key]);
					}
				}
			}
		} else {
			setValue(p.name, value);
		}
	}
	return this;
};

AmazonMwsRequest.prototype.setMultiple = function(conf) {
	_.each(conf, (function(value, key) {
		this.set(key, value);
	}).bind(this));

	return this;
};

/**
 * Builds a query object and checks for required parameters.
 *
 * @return {Object} KvP's of all provided parameters (used by invoke())
 */
AmazonMwsRequest.prototype.query = function() {
	var _this = this;
	return new Promise(function(resolve, reject) {
		var q = {};
		var missing = [];
		for (var param in _this.params) {
			var value = _this.params[param].value,
				name = _this.params[param].name,
				complex = _this.params[param].type === 'Complex',
				list = _this.params[param].list,
				required = _this.params[param].required;
			if (value !== undefined && value !== null) {
				if (complex) {
					value.appendTo(q);
				} else if (list) {
					for (var p in value) {
						q[p] = value[p];
					}
				} else {
					q[name] = value;
				}
			} else {
				if (param.required === true) {
					missing.push(name);
				}
			}
		}
		if (missing.length > 0) reject(new Error("ERROR: Missing required parameter(s): " + missing.join(',') + "!"));
		else resolve(q);
	});
};

/**
 * Contructor for objects used to represent enumeration states. Useful
 * when you need to make programmatic updates to an enumerated data type or
 * wish to encapsulate enum states in a handy, re-usable variable.
 *
 * @param {Array} choices An array of any possible values (choices)
 */
function EnumType(choices) {
	for (var choice in choices) {
		this[choices[choice]] = false;
	}
	this._choices = choices;
}

/**
 * Enable one or more choices (accepts a variable number of arguments)
 * @return {Object} Current instance of EnumType for chaining
 */
EnumType.prototype.enable = function() {
	for (var arg in arguments) {
		this[arguments[arg]] = true;
	}
	return this;
};

/**
 * Disable one or more choices (accepts a variable number of arguments)
 * @return {Object} Current instance of EnumType for chaining
 */
EnumType.prototype.disable = function() {
	for (var arg in arguments) {
		this[arguments[arg]] = false;
	}
	return this;
};

/**
 * Toggles one or more choices (accepts a variable number of arguments)
 * @return {Object} Current instance of EnumType for chaining
 */
EnumType.prototype.toggle = function() {
	for (var arg in arguments) {
		this[arguments[arg]] = !this[arguments[arg]];
	}
	return this;
};

/**
 * Return all possible values without regard to current state
 * @return {Array} Choices passed to EnumType constructor
 */
EnumType.prototype.all = function() {
	return this._choices;
};

/**
 * Return all enabled choices as an array (used to set list params, usually)
 * @return {Array} Choice values for each choice set to true
 */
EnumType.prototype.values = function() {
	var value = [];
	for (var choice in this._choices) {
		if (this[this._choices[choice]] === true) {
			value.push(this._choices[choice]);
		}
	}
	return value;
};

// /**
//  * Takes an object and adds an appendTo function that will add
//  * each kvp of object to a query. Used when dealing with complex
//  * parameters that need to be built in an abnormal or unique way.
//  *
//  * @param {String} name Name of parameter, prefixed to each key
//  * @param {Object} obj  Parameters belonging to the complex type
//  */
// function ComplexType(name) {
// 	this.pre = name;
// 	var _obj = obj;
// 	obj.appendTo = function(query) {
// 		for (var k in _obj) {
// 			query[name + '.' k] = _obj[k];
// 		}
// 		return query;
// 	}
// 	return obj;
// }

// ComplexType.prototype.appendTo = function(query) {
// 	for (var k in value)
// }

/**
 * Complex List helper object. Once initialized, you should set
 * an add(args) method which pushes a new complex object to members.
 *
 * @param {String} name Name of Complex Type (including .member or subtype)
 */
function ComplexListType(name) {
	this.pre = name;
	this.members = [];
}

/**
 * Appends each member object as a complex list item
 * @param  {Object} query Query object to append to
 * @return {Object}       query
 */
ComplexListType.prototype.appendTo = function(query) {
	var members = this.members;
	for (var i = 0; i < members.length; i++) {
		for (var j in members[i]) {
			query[this.pre + '.' + (i + 1) + '.' + j] = members[i][j];
		}
	}
	return query;
};

exports.Client = AmazonMwsClient;
exports.Request = AmazonMwsRequest;
exports.Enum = EnumType;
exports.ComplexList = ComplexListType;
exports.Fbs = require('./fba');
exports.Orders = require('./orders');
exports.Sellers = require('./sellers');
exports.Feeds = require('./feeds');
exports.Products = require('./products');
exports.Reports = require('./reports');
<commit_msg>Fix Issue #4 - query not passed to request. <commit_after>'use strict';
var qs = require("querystring");
var crypto = require('crypto');
var xml2js = require('xml2js');
var request = require('request');
var _ = require('underscore');
var tls = require('tls');

/**
 * Constructor for the main MWS client interface used to make api calls and
 * various data structures to encapsulate MWS requests, definitions, etc.
 *
 * @param {String} accessKeyId     Id for your secret Access Key (required)
 * @param {String} secretAccessKey Secret Access Key provided by Amazon (required)
 * @param {String} merchantId      Aka SellerId, provided by Amazon (required)
 * @param {Object} options         Additional configuration options for this instance
 */
function AmazonMwsClient(accessKeyId, secretAccessKey, merchantId, options) {
	options = options || {};

	var createCredentials = tls.createSecureContext || crypto.createCredentials;
	this.host = options.host || 'mws.amazonservices.com';
	this.creds = createCredentials(options.creds || {});
	this.appName = options.appName || 'mws-js';
	this.appVersion = options.appVersion || '0.1.0';
	this.appLanguage = options.appLanguage || 'JavaScript';
	this.accessKeyId = accessKeyId || null;
	this.secretAccessKey = secretAccessKey || null;
	this.merchantId = merchantId || null;
	this.authToken = options.authToken;
}

/**
 * The method used to invoke calls against MWS Endpoints. Recommended usage is
 * through the invoke wrapper method when the api call you're invoking has a
 * request defined in one of the submodules. However, you can use call() manually
 * when a lower level of control is necessary (custom or new requests, for example).
 *
 * @param  {Object}   api      Settings object unique to each API submodule
 * @param  {String}   action   Api `Action`, such as GetServiceStatus or GetOrder
 * @param  {Object}   query    Any parameters belonging to the current action
 * @return Promise
 */
AmazonMwsClient.prototype.call = function(api, action, query) {
	if (this.secretAccessKey == null || this.accessKeyId == null || this.merchantId == null) {
		throw "accessKeyId, secretAccessKey, and merchantId must be set";
	}

	var requestOpts = {
		method: 'POST',
		uri: 'https://' + this.host + api.path
	};

	// Check if we're dealing with a file (such as a feed) upload
	if (api.upload) {
		requestOpts.body = query._BODY_;
		requestOpts.headers = {
			'Content-Type': query._FORMAT_,
			'Content-MD5': crypto.createHash('md5').update(query._BODY_).digest('base64')
		};
		delete query._BODY_;
		delete query._FORMAT_;
	}

	// Add required parameters and sign the query
	query['Action'] = action;
	query['Version'] = api.version;
	query["Timestamp"] = new Date().toISOString();
	query["AWSAccessKeyId"] = this.accessKeyId;

	if (this.authToken) {
		query["MWSAuthToken"] = this.authToken;
	}

	if (api.legacy) {
		query['Merchant'] = this.merchantId;
	} else {
		query['SellerId'] = this.merchantId;
	}

	query = this.sign(api.path, query);

	if (!api.upload) {
		requestOpts.form = query;
	} else {
		requestOpts.qs = query;
	}

	return new Promise(function(resolve, reject) {
		request(requestOpts, function(err, response, data) {
			if (err) {
				reject(err);
			} else {
				if (data.slice(0, 5) == '<?xml') {
					xml2js.parseString(data, function(err, result) {
						// Throw an error if there was a problem reported
						if (err != null) {
							reject(new Error(err.Code + ": " + err.Message));
						} else {
							resolve(result);
						}
					});
				} else {
					resolve(data);
				}
			}
		});
	});
};

/**
 * Calculates the HmacSHA256 signature and appends it with additional signature
 * parameters to the provided query object.
 *
 * @param  {String} path  Path of API call (used to build the string to sign)
 * @param  {Object} query Any non-signature parameters that will be sent
 * @return {Object}       Finalized object used to build query string of request
 */
AmazonMwsClient.prototype.sign = function(path, query) {
	// Configure the query signature method/version
	query["SignatureMethod"] = "HmacSHA256";
	query["SignatureVersion"] = "2";

	// Copy query keys, sort them, then copy over the values
	var sorted = _.reduce(_.keys(query).sort(), function(m, k) {
		m[k] = query[k];
		return m;
	}, {});

	var stringToSign = ["POST", this.host, path, qs.stringify(sorted)].join("\n");

	query['Signature'] = crypto.createHmac("sha256", this.secretAccessKey).update(stringToSign, 'utf8').digest("base64");

	return query;
};

/**
 * Suggested method for invoking a pre-defined mws request object.
 *
 * @param  {Object}   request  An instance of AmazonMwsRequest with params, etc.
 * @return Promise
 */
AmazonMwsClient.prototype.invoke = function(request) {
	var _this = this;
	return request.query().then(function(q) {
		return _this.call(request.api, request.action, q);
	});
};

/**
 * Constructor for general MWS request objects, wrapped by api submodules to keep
 * things DRY, yet familiar despite whichever api is being implemented.
 *
 * @param {Object} options Settings to apply to new request instance.
 */
function AmazonMwsRequest(options) {
	this.api = {
		path: options.path || '/',
		version: options.version || '2009-01-01',
		legacy: options.legacy || false,
		upload: options.upload
	};
	this.action = options.action || 'GetServiceStatus';
	this.params = options.params || {};
	this.paramsMap = {};

	if(Object.keys(this.params).length > 0){
		for( var name in this.params){
			var realName = this.params[name].name;
			if(name!== this.params[name].name){
				this.paramsMap[name] = realName;
				this.params[realName] = this.params[name];
				delete this.params[name];
			}
		}
	}
}

/**
 * Handles the casting, renaming, and setting of individual request params.
 *
 * @param {String} param Key of parameter (not ALWAYS the same as the param name!)
 * @param {Mixed} value Value to assign to parameter
 * @return {Object} Current instance to allow function chaining
 */
AmazonMwsRequest.prototype.set = function(param, value) {
	if (param instanceof Object && (value === null || value === undefined)) {
		return this.setMultiple(param);
	} else if (value !== null && value !== undefined) {
		var self = this;

		if(this.paramsMap.hasOwnProperty(param)){
				param = this.paramsMap[param];
		}

		var p = this.params[param],
			v = p.value = {};
		// Handles the actual setting based on type
		var setListValue = function setListValue(name, val) {
			if (p.type == 'Timestamp') {
				v[name] = val.toISOString();
			} else if (p.type == 'Boolean') {
				v[name] = val ? 'true' : 'false';
			} else {
				v[name] = val;
			}
		};

		// Handles the actual setting based on type
		var setValue = function setValue(name, val) {
			console.log(name, val)
			if (p.type == 'Timestamp') {
				self.params[name].value = val.toISOString();
			} else if (p.type == 'Boolean') {
				self.params[name].value = val ? 'true' : 'false';
			} else {
				self.params[name].value = val;
			}
		};

		// Lists need to be sequentially numbered and we take care of that here
		if (p.list) {
			var i = 0;
			if (typeof value == "string" || typeof value == "number") {
				setListValue(p.name + '.1', value);
			}
			if (typeof value == "object") {
				if (Array.isArray(value)) {
					for (i = value.length - 1; i >= 0; i--) {
						setListValue(p.name + '.' + (i + 1), value[i]);
					}
				} else {
					for (var key in value) {
						setListValue(p.name + '.' + ++i, value[key]);
					}
				}
			}
		} else {
			setValue(p.name, value);
		}
	}
	return this;
};

AmazonMwsRequest.prototype.setMultiple = function(conf) {
	_.each(conf, (function(value, key) {
		this.set(key, value);
	}).bind(this));

	return this;
};

/**
 * Builds a query object and checks for required parameters.
 *
 * @return {Object} KvP's of all provided parameters (used by invoke())
 */
AmazonMwsRequest.prototype.query = function() {
	var _this = this;
	return new Promise(function(resolve, reject) {
		var q = {};
		var missing = [];
		for (var param in _this.params) {
			var value = _this.params[param].value,
				name = _this.params[param].name,
				complex = _this.params[param].type === 'Complex',
				list = _this.params[param].list,
				required = _this.params[param].required;
			if (value !== undefined && value !== null) {
				if (complex) {
					value.appendTo(q);
				} else if (list) {
					for (var p in value) {
						q[p] = value[p];
					}
				} else {
					q[name] = value;
				}
			} else {
				if (param.required === true) {
					missing.push(name);
				}
			}
		}
		if (missing.length > 0) reject(new Error("ERROR: Missing required parameter(s): " + missing.join(',') + "!"));
		else resolve(q);
	});
};

/**
 * Contructor for objects used to represent enumeration states. Useful
 * when you need to make programmatic updates to an enumerated data type or
 * wish to encapsulate enum states in a handy, re-usable variable.
 *
 * @param {Array} choices An array of any possible values (choices)
 */
function EnumType(choices) {
	for (var choice in choices) {
		this[choices[choice]] = false;
	}
	this._choices = choices;
}

/**
 * Enable one or more choices (accepts a variable number of arguments)
 * @return {Object} Current instance of EnumType for chaining
 */
EnumType.prototype.enable = function() {
	for (var arg in arguments) {
		this[arguments[arg]] = true;
	}
	return this;
};

/**
 * Disable one or more choices (accepts a variable number of arguments)
 * @return {Object} Current instance of EnumType for chaining
 */
EnumType.prototype.disable = function() {
	for (var arg in arguments) {
		this[arguments[arg]] = false;
	}
	return this;
};

/**
 * Toggles one or more choices (accepts a variable number of arguments)
 * @return {Object} Current instance of EnumType for chaining
 */
EnumType.prototype.toggle = function() {
	for (var arg in arguments) {
		this[arguments[arg]] = !this[arguments[arg]];
	}
	return this;
};

/**
 * Return all possible values without regard to current state
 * @return {Array} Choices passed to EnumType constructor
 */
EnumType.prototype.all = function() {
	return this._choices;
};

/**
 * Return all enabled choices as an array (used to set list params, usually)
 * @return {Array} Choice values for each choice set to true
 */
EnumType.prototype.values = function() {
	var value = [];
	for (var choice in this._choices) {
		if (this[this._choices[choice]] === true) {
			value.push(this._choices[choice]);
		}
	}
	return value;
};

// /**
//  * Takes an object and adds an appendTo function that will add
//  * each kvp of object to a query. Used when dealing with complex
//  * parameters that need to be built in an abnormal or unique way.
//  *
//  * @param {String} name Name of parameter, prefixed to each key
//  * @param {Object} obj  Parameters belonging to the complex type
//  */
// function ComplexType(name) {
// 	this.pre = name;
// 	var _obj = obj;
// 	obj.appendTo = function(query) {
// 		for (var k in _obj) {
// 			query[name + '.' k] = _obj[k];
// 		}
// 		return query;
// 	}
// 	return obj;
// }

// ComplexType.prototype.appendTo = function(query) {
// 	for (var k in value)
// }

/**
 * Complex List helper object. Once initialized, you should set
 * an add(args) method which pushes a new complex object to members.
 *
 * @param {String} name Name of Complex Type (including .member or subtype)
 */
function ComplexListType(name) {
	this.pre = name;
	this.members = [];
}

/**
 * Appends each member object as a complex list item
 * @param  {Object} query Query object to append to
 * @return {Object}       query
 */
ComplexListType.prototype.appendTo = function(query) {
	var members = this.members;
	for (var i = 0; i < members.length; i++) {
		for (var j in members[i]) {
			query[this.pre + '.' + (i + 1) + '.' + j] = members[i][j];
		}
	}
	return query;
};

exports.Client = AmazonMwsClient;
exports.Request = AmazonMwsRequest;
exports.Enum = EnumType;
exports.ComplexList = ComplexListType;
exports.Fbs = require('./fba');
exports.Orders = require('./orders');
exports.Sellers = require('./sellers');
exports.Feeds = require('./feeds');
exports.Products = require('./products');
exports.Reports = require('./reports');
<|endoftext|>