<commit_before>'use strict';

var CustomError    = require('es5-ext/lib/Error/custom')
  , isFunction     = require('es5-ext/lib/Function/is-function')
  , invoke         = require('es5-ext/lib/Function/invoke')
  , noop           = require('es5-ext/lib/Function/noop')
  , d              = require('es5-ext/lib/Object/descriptor')
  , extend         = require('es5-ext/lib/Object/extend')
  , callable       = require('es5-ext/lib/Object/valid-callable')
  , memoize        = require('memoizee/lib/regular')
  , ee             = require('event-emitter/lib/core')
  , isNode         = require('dom-ext/lib/Node/is-node')
  , validDocument  = require('dom-ext/lib/Document/valid-document')
  , makeElement    = require('dom-ext/lib/Document/prototype/make-element')
  , replaceContent = require('dom-ext/lib/Element/prototype/replace-content')
  , validSet       = require('set-collection/lib/valid-set')
  , stringify      = require('querystring').stringify
  , Db             = require('dbjs')

  , isArray = Array.isArray, map = Array.prototype.map
  , forEach = Array.prototype.forEach
  , Base = Db.Base
  , cellName = { td: true, th: true }
  , Table;

require('memoizee/lib/ext/method');

module.exports = Table = function (document, set/*, options*/) {
	var options = Object(arguments[2]), getList;
	this.document = validDocument(document);
	this.el = makeElement.bind(this.document);
	this.set = this.obj = validSet(set);
	this.sortMethods = {};
	this.current = {};

	this.render(options);

	// Columns
	if (options.columns != null) {

		// Sort methods
		forEach.call(options.columns, function (options, index) {
			var method, name;
			if (!options.sort) return;
			name = options.default ? '' : String(options.name || index);
			method = this.setSortMethod(name, options.sort, options.reverse);
			options.sortName = name;
		}, this);

		// Head
		if (this.head) {
			map.call(options.columns, function (options, index) {
				if (options.head) return this.headCellRender(options.head, options);
				return this.head.appendChild(document.createElement('th'));
			}, this).forEach(this.head.appendChild, this.head);
		}

		// Cells
		this.cellRenderers = map.call(options.columns, function (options, index) {
			var name;
			if ((set._type_ === 'namespace') && (typeof options === 'string')) {
				name = options;
				if (this.head) {
					this.head.appendChild(
						this.headCellRender(set.prototype.get(name)._label)
					);
				}
				return function (item) { return item.get(name); };
			}
			if (options.render != null) {
				return callable(options.render);
			} else if (options.name != null) {
				name = String(options.name);
				return function (item) { return item.get(name); };
			} else {
				return noop;
			}
		}, this);
	} else if (set._type_ === 'namespace') {
		this.cellRenderers = set.prototype.getPropertyNames(options.tag)
			.listByOrder().map(function (name) {
				if (this.head) {
					this.head.appendChild(
						this.headCellRenderer(this.get(name)._label.toDOM(this.document))
					);
				}
				return function (item) { return item.get(name); };
			});
	} else {
		throw new CustomError("Columns layout not provided", 'MISSING_COLUMNS');
	}

	// Rows
	if (!this.sortMethods['']) {
		if (options.sort) {
			this.setSortMethod('', options.sort, options.reverse);
		} else {
			if (set._type_ === 'namespace') getList = invoke('listByCreatedAt');
			else if (set.listByOrder) getList = invoke('listByOrder');
			else getList = invoke('list');
			this.sortMethods[''] = {
				getList: getList,
				reverse: false
			};
		}
	}

	this.reset();
};

ee(Object.defineProperties(Table.prototype, extend({
	reverse: d(false),
	render: d(function (options) {
		var el = makeElement.bind(this.document);
		this.dom = el('table',
			options.head ? el('thead', this.head = el('tr')) : null,
			this.body = el('tbody'));
	}),
	headCellRender: d(function (dom, options) {
		var attr, sortName;
		if (isNode(dom) && (dom.nodeName.toLowerCase() === 'th')) return dom;
		if (options.sortName != null) {
			sortName = options.sortName;
			attr = this.document.createAttribute('href');
			this.on('change', function () {
				var data = {};
				if (sortName) data.sort = sortName;
				if ((this.current.sort === sortName) && !this.current.reverse) {
					data.reverse = true;
				}
				data = stringify(data);
				attr.value = data ? '?' + data : '.';
			});
			dom = this.el('a', attr, dom);
		}
		return this.el('th', dom);
	}),
	cellRender: d(function (render, item) {
		var dom = render(item);
		if (isNode(dom) && cellName.hasOwnProperty(dom.nodeName.toLowerCase())) {
			return dom;
		}
		return makeElement.call(this.document, 'td', dom);
	}),
	setSortMethod: d(function (name, data, reverse) {
		var getList;
		if (typeof data === 'string') {
			if (!this.obj.listByProperty) {
				throw new CustomError("Property sort not supported",
					'NO_SET_NAME_SORT_SUPPORT');
			}
			getList = function (set) { return set.listByProperty(data); };
		} else if (isFunction(data)) {
			if (data.length === 1) {
				getList = memoize(data);
			} else {
				getList = memoize(function (set) { return set.list(data); });
			}
		}
		return (this.sortMethods[name] = {
			getList: getList,
			reverse: Boolean(reverse)
		});
	}),
	reset: d(function (data) {
		data = Object(data);
		this.set = this.obj;
		this.sortBy(data.sort || '', data.reverse);
	}),
	sortBy: d(function (name, reverse) {
		var method;
		method = this.sortMethods[name];
		reverse = Boolean(reverse);
		this.current.sort = name;
		this.current.reverse = reverse;
		if (method.reverse) reverse = !reverse;
		this.sort(method.getList(this.set), reverse);
	}),
	sort: d(function (list, reverse) {
		if (!isArray(list)) {
			throw new CustomError("List must be an array", "ARRAY_EXPECTED");
		}
		if (list.obj !== this.set) {
			throw new CustomError("List doesn't match set", 'LIST_MISMATCH');
		}
		reverse = Boolean(reverse);
		if ((this.list === list) && (this.reverse === reverse)) return;
		this.emit('change');
		if (this.list !== list) {
			if (this.list) this.list.off('change', this.reload);
			list.on('change', this.reload);
		}
		this.list = list;
		this.reverse = reverse;
		this.reload();
	}),
	toDOM: d(function () { return this.dom; })
}, memoize(function (item) {
	return makeElement.call(this.document, 'tr',
		this.cellRenderers.map(function (render) {
			return this.cellRender(render, item);
		}, this));
}, { method: 'rowRender' }), d.binder({
	reload: d(function () {
		var list = this.list.map(this.rowRender);
		if (this.reverse) list.reverse();
		replaceContent.call(this.body, list);
	})
}))));

Object.defineProperty(Base, 'DOMTable', d(Table));

Object.defineProperty(Db, 'toDOMTable', d(function (document/*, options*/) {
	return new this.DOMTable(document, this, arguments[1]);
}));
<commit_msg>Add 'dbjs' class<commit_after>'use strict';

var CustomError    = require('es5-ext/lib/Error/custom')
  , isFunction     = require('es5-ext/lib/Function/is-function')
  , invoke         = require('es5-ext/lib/Function/invoke')
  , noop           = require('es5-ext/lib/Function/noop')
  , d              = require('es5-ext/lib/Object/descriptor')
  , extend         = require('es5-ext/lib/Object/extend')
  , callable       = require('es5-ext/lib/Object/valid-callable')
  , memoize        = require('memoizee/lib/regular')
  , ee             = require('event-emitter/lib/core')
  , isNode         = require('dom-ext/lib/Node/is-node')
  , validDocument  = require('dom-ext/lib/Document/valid-document')
  , makeElement    = require('dom-ext/lib/Document/prototype/make-element')
  , replaceContent = require('dom-ext/lib/Element/prototype/replace-content')
  , validSet       = require('set-collection/lib/valid-set')
  , stringify      = require('querystring').stringify
  , Db             = require('dbjs')

  , isArray = Array.isArray, map = Array.prototype.map
  , forEach = Array.prototype.forEach
  , Base = Db.Base
  , cellName = { td: true, th: true }
  , Table;

require('memoizee/lib/ext/method');

module.exports = Table = function (document, set/*, options*/) {
	var options = Object(arguments[2]), getList;
	this.document = validDocument(document);
	this.el = makeElement.bind(this.document);
	this.set = this.obj = validSet(set);
	this.sortMethods = {};
	this.current = {};

	this.render(options);

	// Columns
	if (options.columns != null) {

		// Sort methods
		forEach.call(options.columns, function (options, index) {
			var method, name;
			if (!options.sort) return;
			name = options.default ? '' : String(options.name || index);
			method = this.setSortMethod(name, options.sort, options.reverse);
			options.sortName = name;
		}, this);

		// Head
		if (this.head) {
			map.call(options.columns, function (options, index) {
				if (options.head) return this.headCellRender(options.head, options);
				return this.head.appendChild(document.createElement('th'));
			}, this).forEach(this.head.appendChild, this.head);
		}

		// Cells
		this.cellRenderers = map.call(options.columns, function (options, index) {
			var name;
			if ((set._type_ === 'namespace') && (typeof options === 'string')) {
				name = options;
				if (this.head) {
					this.head.appendChild(
						this.headCellRender(set.prototype.get(name)._label)
					);
				}
				return function (item) { return item.get(name); };
			}
			if (options.render != null) {
				return callable(options.render);
			} else if (options.name != null) {
				name = String(options.name);
				return function (item) { return item.get(name); };
			} else {
				return noop;
			}
		}, this);
	} else if (set._type_ === 'namespace') {
		this.cellRenderers = set.prototype.getPropertyNames(options.tag)
			.listByOrder().map(function (name) {
				if (this.head) {
					this.head.appendChild(
						this.headCellRenderer(this.get(name)._label.toDOM(this.document))
					);
				}
				return function (item) { return item.get(name); };
			});
	} else {
		throw new CustomError("Columns layout not provided", 'MISSING_COLUMNS');
	}

	// Rows
	if (!this.sortMethods['']) {
		if (options.sort) {
			this.setSortMethod('', options.sort, options.reverse);
		} else {
			if (set._type_ === 'namespace') getList = invoke('listByCreatedAt');
			else if (set.listByOrder) getList = invoke('listByOrder');
			else getList = invoke('list');
			this.sortMethods[''] = {
				getList: getList,
				reverse: false
			};
		}
	}

	this.reset();
};

ee(Object.defineProperties(Table.prototype, extend({
	reverse: d(false),
	render: d(function (options) {
		var el = makeElement.bind(this.document);
		this.dom = el('table', { class: 'dbjs' },
			options.head ? el('thead', this.head = el('tr')) : null,
			this.body = el('tbody'));
	}),
	headCellRender: d(function (dom, options) {
		var attr, sortName;
		if (isNode(dom) && (dom.nodeName.toLowerCase() === 'th')) return dom;
		if (options.sortName != null) {
			sortName = options.sortName;
			attr = this.document.createAttribute('href');
			this.on('change', function () {
				var data = {};
				if (sortName) data.sort = sortName;
				if ((this.current.sort === sortName) && !this.current.reverse) {
					data.reverse = true;
				}
				data = stringify(data);
				attr.value = data ? '?' + data : '.';
			});
			dom = this.el('a', attr, dom);
		}
		return this.el('th', dom);
	}),
	cellRender: d(function (render, item) {
		var dom = render(item);
		if (isNode(dom) && cellName.hasOwnProperty(dom.nodeName.toLowerCase())) {
			return dom;
		}
		return makeElement.call(this.document, 'td', dom);
	}),
	setSortMethod: d(function (name, data, reverse) {
		var getList;
		if (typeof data === 'string') {
			if (!this.obj.listByProperty) {
				throw new CustomError("Property sort not supported",
					'NO_SET_NAME_SORT_SUPPORT');
			}
			getList = function (set) { return set.listByProperty(data); };
		} else if (isFunction(data)) {
			if (data.length === 1) {
				getList = memoize(data);
			} else {
				getList = memoize(function (set) { return set.list(data); });
			}
		}
		return (this.sortMethods[name] = {
			getList: getList,
			reverse: Boolean(reverse)
		});
	}),
	reset: d(function (data) {
		data = Object(data);
		this.set = this.obj;
		this.sortBy(data.sort || '', data.reverse);
	}),
	sortBy: d(function (name, reverse) {
		var method;
		method = this.sortMethods[name];
		reverse = Boolean(reverse);
		this.current.sort = name;
		this.current.reverse = reverse;
		if (method.reverse) reverse = !reverse;
		this.sort(method.getList(this.set), reverse);
	}),
	sort: d(function (list, reverse) {
		if (!isArray(list)) {
			throw new CustomError("List must be an array", "ARRAY_EXPECTED");
		}
		if (list.obj !== this.set) {
			throw new CustomError("List doesn't match set", 'LIST_MISMATCH');
		}
		reverse = Boolean(reverse);
		if ((this.list === list) && (this.reverse === reverse)) return;
		this.emit('change');
		if (this.list !== list) {
			if (this.list) this.list.off('change', this.reload);
			list.on('change', this.reload);
		}
		this.list = list;
		this.reverse = reverse;
		this.reload();
	}),
	toDOM: d(function () { return this.dom; })
}, memoize(function (item) {
	return makeElement.call(this.document, 'tr',
		this.cellRenderers.map(function (render) {
			return this.cellRender(render, item);
		}, this));
}, { method: 'rowRender' }), d.binder({
	reload: d(function () {
		var list = this.list.map(this.rowRender);
		if (this.reverse) list.reverse();
		replaceContent.call(this.body, list);
	})
}))));

Object.defineProperty(Base, 'DOMTable', d(Table));

Object.defineProperty(Db, 'toDOMTable', d(function (document/*, options*/) {
	return new this.DOMTable(document, this, arguments[1]);
}));
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

define(["mobileui/utils/animation", "require",
        "mobileui/utils/timing-functions"], function(Animation, require, TimingFunctions) {

    var BasicAnimationState = function(time) {
        this.startTime = time;
        this.playDuration = 0;
        this.percent = 0;
        this.started = true;
    };

    var BasicAnimation = function(name) {
        Animation.call(this, name);
        this._duration = -1;
        this._next = null;
        this._timingFunction = TimingFunctions.linear;
    };

    _.extend(BasicAnimation.prototype, Backbone.Events, {
        compute: function(state, view) {
            var animationState = state.get(this._id);
            if (!animationState) {
                animationState = new BasicAnimationState(state.time);
                state.set(this._id, animationState);
                this._internalStart(state, view, animationState);
                this.trigger("start");
            }
            animationState.playDuration = state.time - animationState.startTime;
            if (this._duration >= 0) {
                animationState.percent = this._duration ? animationState.playDuration / this._duration : 1;
                if (animationState.percent >= 1) {
                    animationState.timingFunctionPercent = 1;
                    animationState.percent = 1;
                    state.remove(this._id);
                    animationState.started = false;
                } else {
                    animationState.timingFunctionPercent = this._timingFunction(animationState.percent, this._duration);
                }
            }
            this._internalCompute(state, view, animationState);
            if (!animationState.started)
                this.trigger("stop");
        },

        setTimingFunction: function(value) {
            if (_.isString(value))
                value = TimingFunctions[value];
            this._timingFunction = value;
            return this;
        },

        _internalStart: function(state, view, animationState) {
        },

        _internalCompute: function(state, view, animationState) {
            if (animationState.started)
                state.requestTimer(this._duration - animationState.playDuration);
        },

        setDuration: function(duration) {
            this._duration = duration;
            return this;
        },

        duration: function() {
            return this._duration;
        },

        setNext: function(nextAnimation) {
            this._next = nextAnimation;
            return this;
        },

        next: function() {
            return this._next;
        },

        wait: function(duration) {
            return this.setNext(new BasicAnimation("wait " + duration).setDuration(duration)).next();
        },

        log: function(message) {
            return this.setNext(new BasicAnimation("log " + message).setDuration(0).on("stop", function() {
                console.log("animation: ", message);
            })).next();
        },

        callback: function(callback) {
            return this.setNext(new BasicAnimation("callback").setDuration(0).on("stop", callback)).next();
        },

        transform: function(duration, startTransform, endTransform) {
            if (!endTransform) {
                endTransform = startTransform;
                startTransform = null;
            }
            var TransformAnimation = require("mobileui/utils/transform-animation");
            var transformAnimation = new TransformAnimation("transform");
            transformAnimation.getTransform().take(endTransform);
            if (startTransform)
                transformAnimation.startTransform().take(startTransform);
            return this.setNext(transformAnimation.setDuration(duration)).next();
        },

        filter: function(duration, startFilter, endFilter) {
            if (!endFilter) {
                endFilter = startFilter;
                startFilter = null;
            }
            var FilterAnimation = require("mobileui/utils/filter-animation");
            var filterAnimation = new FilterAnimation("filter");
            filterAnimation.getFilter().take(h);
            if (startFilter)
                filterAnimation.startFilter().take(startFilter);
            return this.setNext(filterAnimation.setDuration(duration)).next();
        },

        opacity: function(duration, startOpacity, endOpacity) {
            if (endOpacity === undefined) {
                endOpacity = startOpacity;
                startOpacity = null;
            }
            var OpacityAnimation = require("mobileui/utils/opacity-animation");
            var opacityAnimation = new OpacityAnimation("opacity");
            opacityAnimation.setOpacity(endOpacity);
            if (startOpacity !== null)
                opacityAnimation.setStartOpacity(startOpacity);
            return this.setNext(opacityAnimation.setDuration(duration)).next();
        }
    });

    return BasicAnimation;
});<commit_msg>fixed typo in prev commit<commit_after>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

define(["mobileui/utils/animation", "require",
        "mobileui/utils/timing-functions"], function(Animation, require, TimingFunctions) {

    var BasicAnimationState = function(time) {
        this.startTime = time;
        this.playDuration = 0;
        this.percent = 0;
        this.started = true;
    };

    var BasicAnimation = function(name) {
        Animation.call(this, name);
        this._duration = -1;
        this._next = null;
        this._timingFunction = TimingFunctions.linear;
    };

    _.extend(BasicAnimation.prototype, Backbone.Events, {
        compute: function(state, view) {
            var animationState = state.get(this._id);
            if (!animationState) {
                animationState = new BasicAnimationState(state.time);
                state.set(this._id, animationState);
                this._internalStart(state, view, animationState);
                this.trigger("start");
            }
            animationState.playDuration = state.time - animationState.startTime;
            if (this._duration >= 0) {
                animationState.percent = this._duration ? animationState.playDuration / this._duration : 1;
                if (animationState.percent >= 1) {
                    animationState.timingFunctionPercent = 1;
                    animationState.percent = 1;
                    state.remove(this._id);
                    animationState.started = false;
                } else {
                    animationState.timingFunctionPercent = this._timingFunction(animationState.percent, this._duration);
                }
            }
            this._internalCompute(state, view, animationState);
            if (!animationState.started)
                this.trigger("stop");
        },

        setTimingFunction: function(value) {
            if (_.isString(value))
                value = TimingFunctions[value];
            this._timingFunction = value;
            return this;
        },

        _internalStart: function(state, view, animationState) {
        },

        _internalCompute: function(state, view, animationState) {
            if (animationState.started)
                state.requestTimer(this._duration - animationState.playDuration);
        },

        setDuration: function(duration) {
            this._duration = duration;
            return this;
        },

        duration: function() {
            return this._duration;
        },

        setNext: function(nextAnimation) {
            this._next = nextAnimation;
            return this;
        },

        next: function() {
            return this._next;
        },

        wait: function(duration) {
            return this.setNext(new BasicAnimation("wait " + duration).setDuration(duration)).next();
        },

        log: function(message) {
            return this.setNext(new BasicAnimation("log " + message).setDuration(0).on("stop", function() {
                console.log("animation: ", message);
            })).next();
        },

        callback: function(callback) {
            return this.setNext(new BasicAnimation("callback").setDuration(0).on("stop", callback)).next();
        },

        transform: function(duration, startTransform, endTransform) {
            if (!endTransform) {
                endTransform = startTransform;
                startTransform = null;
            }
            var TransformAnimation = require("mobileui/utils/transform-animation");
            var transformAnimation = new TransformAnimation("transform");
            transformAnimation.getTransform().take(endTransform);
            if (startTransform)
                transformAnimation.startTransform().take(startTransform);
            return this.setNext(transformAnimation.setDuration(duration)).next();
        },

        filter: function(duration, startFilter, endFilter) {
            if (!endFilter) {
                endFilter = startFilter;
                startFilter = null;
            }
            var FilterAnimation = require("mobileui/utils/filter-animation");
            var filterAnimation = new FilterAnimation("filter");
            filterAnimation.getFilter().take(endFilter);
            if (startFilter)
                filterAnimation.startFilter().take(startFilter);
            return this.setNext(filterAnimation.setDuration(duration)).next();
        },

        opacity: function(duration, startOpacity, endOpacity) {
            if (endOpacity === undefined) {
                endOpacity = startOpacity;
                startOpacity = null;
            }
            var OpacityAnimation = require("mobileui/utils/opacity-animation");
            var opacityAnimation = new OpacityAnimation("opacity");
            opacityAnimation.setOpacity(endOpacity);
            if (startOpacity !== null)
                opacityAnimation.setStartOpacity(startOpacity);
            return this.setNext(opacityAnimation.setDuration(duration)).next();
        }
    });

    return BasicAnimation;
});<|endoftext|>


<commit_before>import axios from 'axios';
import { assign } from 'lodash';

/**
 * Global configuration for all service classes.
 */
let config = {
  csrfToken: null, // defines the CSRF token if required by your web application
  onSuccess: null, // defines a callback to trigger on every successful response
  onError: null    // defines a callback to trigger on every erroneous response
};

class Service {
  /**
   * Allows the global config data to be set for your application.
   * eg. Service.configure({ csrfToken: global.CSRF_TOKEN });
   *
   * @method configure
   * @param {Object} opts - see config object above
   * @return {Void}
   */
  static configure(opts) {
    assign(config, config, opts);
  }

  /**
   * @constructor
   */
  constructor(opts = {}) {
    // sets up the axios client with default options
    this.client = axios.create({
      headers: {
        'X-CSRF-Token': config.csrfToken,
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      transformResponse: [ this.responseTransform ]
    });

    // applies the default axios interceptors used for manipulating the data
    this.client.interceptors.response.use(this.handleSuccess, this.handleError);

    // turns any configured global callbacks on by default
    if (opts.globalCallbacks !== false) {
      this.enableGlobalCallbacks();
    }
  }

  /**
   * Is called after a successful response from the server, and determines how
   * to handle the response.
   *
   * @method handleSuccess
   * @param {Object} response - the reponse from the server
   * @return {Object} the response data
   */
  handleSuccess = (response) => {
    if (!response.data.message) {
      return response.data;
    }

    if (response.data.status === "error") {
      // respond with an error if the server responds with an error status
      if (this.shouldTriggerCallback(config.onError)) {
        config.onError(response.data.message);
      }

      return Promise.reject(response);
    } else {
      // otherwise respond with a success
      if (this.shouldTriggerCallback(config.onSuccess)) {
        config.onSuccess(response.data.message);
      }

      return response.data;
    }
  }

  /**
   * Is called after an erroneous response from the server.
   *
   * @method handleError
   * @param {Object} error - the response from the server
   * @return {Object}
   */
  handleError = (error) => {
    if (this.shouldTriggerCallback(config.onError)) {
      config.onError(error.response.data);
    }

    return Promise.reject(error.response);
  }

  /**
   * Sets the base URL for axios to use for all requests using this class.
   *
   * @method setURL
   * @param {String} url
   * @return {Void}
   */
  setURL = (url) => {
    this.client.defaults.baseURL = url;
  }

  /**
   * Sets a function to run before a request is made, allowing manipulation of the data.
   *
   * @method setTransformRequest
   * @param {Function} func
   * @return {Void}
   */
  setTransformRequest = (func) => {
    this.client.defaults.transformRequest[1] = func;
  }

  /**
   * Sets a function to run before a response is received, allowing manipulation of the data.
   *
   * @method setTransformResponse
   * @param {Function} func
   * @return {Void}
   */
  setTransformResponse = (func) => {
    this.client.defaults.transformResponse[1] = func;
  }

  /**
   * Enables global callbacks (eg onSuccess and onError).
   *
   * @method enableGlobalCallbacks
   * @return {Void}
   */
  enableGlobalCallbacks = () => {
    this.globalCallbacks = true;
  }

  /**
   * Disables global callbacks (eg onSuccess and onError).
   *
   * @method disableGlobalCallbacks
   * @return {Void}
   */
  disableGlobalCallbacks = () => {
    this.globalCallbacks = false;
  }

  /**
   * Performs a GET request to the server.
   *
   * @method get
   * @param {Number} id - the ID of the resource.
   * @param {Function} onSuccess - a callback to trigger on success
   * @param {Function} onError - a callback to trigger on error
   * @return {Void}
   */
  get = (id, onSuccess, onError) => {
    this.client.get(String(id)).then(
      this.handleResponse.bind(this, onSuccess),
      this.handleResponse.bind(this, onError)
    );
  }

  /**
   * Performs a POST request to the server.
   *
   * @method post
   * @param {Object} data - the data to post to the server.
   * @param {Function} onSuccess - a callback to trigger on success
   * @param {Function} onError - a callback to trigger on error
   * @return {Void}
   */
  post = (data, onSuccess, onError) => {
    this.client.post(data).then(
      this.handleResponse.bind(this, onSuccess),
      this.handleResponse.bind(this, onError)
    );
  }

  /**
   * Performs a PUT request to the server.
   *
   * @method put
   * @param {Number} id - the ID of the resource.
   * @param {Object} data - the data to post to the server.
   * @param {Function} onSuccess - a callback to trigger on success
   * @param {Function} onError - a callback to trigger on error
   * @return {Void}
   */
  put = (id, data, onSuccess, onError) => {
    this.client.put(String(id), data).then(
      this.handleResponse.bind(this, onSuccess),
      this.handleResponse.bind(this, onError)
    );
  }

  /**
   * Performs a DEL request to the server.
   *
   * @method delete
   * @param {Number} id - the ID of the resource.
   * @param {Function} onSuccess - a callback to trigger on success
   * @param {Function} onError - a callback to trigger on error
   * @return {Void}
   */
  delete = (id, onSuccess, onError) => {
    this.client.delete(String(id)).then(
      this.handleResponse.bind(this, onSuccess),
      this.handleResponse.bind(this, onError)
    );
  }

  /**
   * Handles all responses from the axios promise.
   *
   * @method handleResponse
   * @param {Function} callback
   * @param {Object} response
   * @return {Void}
   */
  handleResponse = (callback, response) => {
    if (callback) { callback(response); }
  }

  /**
   * A default response transformer, to convert the data into something useful for devs.
   *
   * @method responseTransform
   * @param {Object}
   * @return {Void}
   */
  responseTransform = (response) => {
    return JSON.parse(response);
  }

  /**
   * Determines if a callback should be triggered.
   *
   * @method shouldTriggerCallback
   * @param {Function}
   * @return {Boolean}
   */
  shouldTriggerCallback = (method) => {
    return this.globalCallbacks && method;
  }
}

export default Service;
<commit_msg>created single line to multi line<commit_after>import axios from 'axios';
import { assign } from 'lodash';

/**
 * Global configuration for all service classes.
 */
let config = {
  csrfToken: null, // defines the CSRF token if required by your web application
  onSuccess: null, // defines a callback to trigger on every successful response
  onError: null    // defines a callback to trigger on every erroneous response
};

class Service {
  /**
   * Allows the global config data to be set for your application.
   * eg. Service.configure({ csrfToken: global.CSRF_TOKEN });
   *
   * @method configure
   * @param {Object} opts - see config object above
   * @return {Void}
   */
  static configure(opts) {
    assign(config, config, opts);
  }

  /**
   * @constructor
   */
  constructor(opts = {}) {
    // sets up the axios client with default options
    this.client = axios.create({
      headers: {
        'X-CSRF-Token': config.csrfToken,
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      },
      transformResponse: [ this.responseTransform ]
    });

    // applies the default axios interceptors used for manipulating the data
    this.client.interceptors.response.use(this.handleSuccess, this.handleError);

    // turns any configured global callbacks on by default
    if (opts.globalCallbacks !== false) {
      this.enableGlobalCallbacks();
    }
  }

  /**
   * Is called after a successful response from the server, and determines how
   * to handle the response.
   *
   * @method handleSuccess
   * @param {Object} response - the reponse from the server
   * @return {Object} the response data
   */
  handleSuccess = (response) => {
    if (!response.data.message) {
      return response.data;
    }

    if (response.data.status === "error") {
      // respond with an error if the server responds with an error status
      if (this.shouldTriggerCallback(config.onError)) {
        config.onError(response.data.message);
      }

      return Promise.reject(response);
    } else {
      // otherwise respond with a success
      if (this.shouldTriggerCallback(config.onSuccess)) {
        config.onSuccess(response.data.message);
      }

      return response.data;
    }
  }

  /**
   * Is called after an erroneous response from the server.
   *
   * @method handleError
   * @param {Object} error - the response from the server
   * @return {Object}
   */
  handleError = (error) => {
    if (this.shouldTriggerCallback(config.onError)) {
      config.onError(error.response.data);
    }

    return Promise.reject(error.response);
  }

  /**
   * Sets the base URL for axios to use for all requests using this class.
   *
   * @method setURL
   * @param {String} url
   * @return {Void}
   */
  setURL = (url) => {
    this.client.defaults.baseURL = url;
  }

  /**
   * Sets a function to run before a request is made, allowing manipulation of the data.
   *
   * @method setTransformRequest
   * @param {Function} func
   * @return {Void}
   */
  setTransformRequest = (func) => {
    this.client.defaults.transformRequest[1] = func;
  }

  /**
   * Sets a function to run before a response is received, allowing manipulation of the data.
   *
   * @method setTransformResponse
   * @param {Function} func
   * @return {Void}
   */
  setTransformResponse = (func) => {
    this.client.defaults.transformResponse[1] = func;
  }

  /**
   * Enables global callbacks (eg onSuccess and onError).
   *
   * @method enableGlobalCallbacks
   * @return {Void}
   */
  enableGlobalCallbacks = () => {
    this.globalCallbacks = true;
  }

  /**
   * Disables global callbacks (eg onSuccess and onError).
   *
   * @method disableGlobalCallbacks
   * @return {Void}
   */
  disableGlobalCallbacks = () => {
    this.globalCallbacks = false;
  }

  /**
   * Performs a GET request to the server.
   *
   * @method get
   * @param {Number} id - the ID of the resource.
   * @param {Function} onSuccess - a callback to trigger on success
   * @param {Function} onError - a callback to trigger on error
   * @return {Void}
   */
  get = (id, onSuccess, onError) => {
    this.client.get(String(id)).then(
      this.handleResponse.bind(this, onSuccess),
      this.handleResponse.bind(this, onError)
    );
  }

  /**
   * Performs a POST request to the server.
   *
   * @method post
   * @param {Object} data - the data to post to the server.
   * @param {Function} onSuccess - a callback to trigger on success
   * @param {Function} onError - a callback to trigger on error
   * @return {Void}
   */
  post = (data, onSuccess, onError) => {
    this.client.post(data).then(
      this.handleResponse.bind(this, onSuccess),
      this.handleResponse.bind(this, onError)
    );
  }

  /**
   * Performs a PUT request to the server.
   *
   * @method put
   * @param {Number} id - the ID of the resource.
   * @param {Object} data - the data to post to the server.
   * @param {Function} onSuccess - a callback to trigger on success
   * @param {Function} onError - a callback to trigger on error
   * @return {Void}
   */
  put = (id, data, onSuccess, onError) => {
    this.client.put(String(id), data).then(
      this.handleResponse.bind(this, onSuccess),
      this.handleResponse.bind(this, onError)
    );
  }

  /**
   * Performs a DEL request to the server.
   *
   * @method delete
   * @param {Number} id - the ID of the resource.
   * @param {Function} onSuccess - a callback to trigger on success
   * @param {Function} onError - a callback to trigger on error
   * @return {Void}
   */
  delete = (id, onSuccess, onError) => {
    this.client.delete(String(id)).then(
      this.handleResponse.bind(this, onSuccess),
      this.handleResponse.bind(this, onError)
    );
  }

  /**
   * Handles all responses from the axios promise.
   *
   * @method handleResponse
   * @param {Function} callback
   * @param {Object} response
   * @return {Void}
   */
  handleResponse = (callback, response) => {
    if (callback) {
      callback(response);
    }
  }

  /**
   * A default response transformer, to convert the data into something useful for devs.
   *
   * @method responseTransform
   * @param {Object}
   * @return {Void}
   */
  responseTransform = (response) => {
    return JSON.parse(response);
  }

  /**
   * Determines if a callback should be triggered.
   *
   * @method shouldTriggerCallback
   * @param {Function}
   * @return {Boolean}
   */
  shouldTriggerCallback = (method) => {
    return this.globalCallbacks && method;
  }
}

export default Service;
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { connect } from "react-redux"
import { Row, Col, Card, CardBlock } from "reactstrap"

import { requestInfo } from "../../actions"

class Dashboard extends Component {

	constructor(props) {
		super(props);

		this.props.requestInfo();
	}

	componentDidMount() {
		this.interval = setInterval(this.props.requestInfo, 5000);
	}

	componentWillUnmount() {
		clearInterval(this.interval);
	}

	render() {
		let playerState = "primary";
		if (this.props.data) {
			const ratio = this.props.data.players / this.props.data.maxPlayers;
			if (ratio > 0.95)
				playerState = "danger";
			else if (ratio > 0.8)
				playerState = "warning";
			else
				playerState = "success";
		}

		let tpsState = "primary";
		if (this.props.data) {
			if (this.props.data.tps >= 19.5)
				tpsState = "success";
			else if (this.props.data.tps >= 15)
				tpsState = "warning";
			else
				tpsState = "danger";
		}

		return (

				{ this.props.data ?
					<Row>

						<Col xs={6} lg={3}>
							<Card inverse={true} color={playerState}>
								<CardBlock className="pb-0">

{this.props.data.players}/{this.props.data.maxPlayers}

Online players

								</CardBlock>
							</Card>
						</Col>

						<Col xs={6} lg={3}>
							<Card inverse={true} color={tpsState}>
								<CardBlock className="pb-0">

{this.props.data.tps}

Current TPS

								</CardBlock>
							</Card>
						</Col>

						<Col xs={6} lg={3}>
							<Card inverse={true} color="info">
								<CardBlock className="pb-0">

{this.props.data.address}

Address

								</CardBlock>
							</Card>
						</Col>

						<Col xs={6} lg={3}>
							<Card inverse={true} color="info">
								<CardBlock className="pb-0">

{this.props.data.onlineMode ? "Yes" : "No"}

Online mode

								</CardBlock>
							</Card>
						</Col>

						<Col xs={6} lg={3}>
							<Card inverse={true} color="primary">
								<CardBlock className="pb-0">

{this.props.data.uptimeTicks}

Uptime ticks

								</CardBlock>
							</Card>
						</Col>

						<Col xs={6} lg={3}>
							<Card inverse={true} color="primary">
								<CardBlock className="pb-0">

{this.props.data.game.version}

Minecraft Version

								</CardBlock>
							</Card>
						</Col>

						<Col xs={6} lg={3}>
							<Card inverse={true} color="primary">
								<CardBlock className="pb-0">

{this.props.data.api.version}

API Version

								</CardBlock>
							</Card>
						</Col>

						<Col xs={6} lg={3}>
							<Card inverse={true} color="primary">
								<CardBlock className="pb-0">

{this.props.data.implementation.version}

Implementation Version

								</CardBlock>
							</Card>
						</Col>

					</Row>
				: null}

		)
	}
}

const mapStateToProps = (_state) => {
	const state = _state.dashboard

	return {
		data: state.data
	}
}

const mapDispatchToProps = (dispatch) => {
	return {
		requestInfo: () => {
			dispatch(requestInfo())
		}
	}
}

export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
<commit_msg>feat(dashboard): Add TPS and player graphs<commit_after>import React, { Component } from 'react'
import { connect } from "react-redux"
import { Row, Col, Card, CardHeader, CardBlock } from "reactstrap"
import { Line } from "react-chartjs-2";
import _ from "lodash"

import { requestInfo } from "../../actions"

const line = {
	datasets: [
		{
			label: 'Average TPS',
			fill: false,
			backgroundColor: 'rgb(255, 99, 132)',
			borderColor: 'rgb(255, 99, 132)',
			pointRadius: 0,
			pointHitRadius: 10,
			xAxisID: "time",
			yAxisID: "tps",
		},
		{
			label: 'Online players',
			fill: false,
			backgroundColor: 'rgb(54, 162, 235)',
			borderColor: 'rgb(54, 162, 235)',
			pointRadius: 0,
			pointHitRadius: 10,
			xAxisID: "time",
			yAxisID: "players",
		}
	]
};

const options = {
	maintainAspectRatio: false,
	legend: {
		display: false,
	},
	scales: {
		xAxes: [{
			id: "time",
			type: "time",
			time: {
				displayFormats: {
						second: "HH:mm:ss",
						minute: "HH:mm",
						hour: "HH:mm",
						day: "DD.MM.YYYY",
				},
				tooltipFormat: "DD.MM.YYYY HH:mm:ss"
			}
		}],
		yAxes: [{
			type: "linear",
			id: "tps",
			ticks: {
				beginAtZero: true,
				max: 20,
				min: 0,
			},
			scaleLabel: {
				display: true,
				labelString: "TPS",
			},
		},{
			type: "linear",
			id: "players",
			gridLines: {
				drawOnChartArea: false,
			},
			ticks: {
				beginAtZero: true,
				min: 0,
			},
			scaleLabel: {
				display: true,
				labelString: "Players",
			},
			position: "right",
		}]
	},
};

class Dashboard extends Component {

	constructor(props) {
		super(props);

		this.props.requestInfo();

		this.test = this.test.bind(this);
	}

	componentDidMount() {
		this.interval = setInterval(this.props.requestInfo, 5000);
	}

	componentWillUnmount() {
		clearInterval(this.interval);
	}

	test() {
		this.props.test();
	}

	render() {
		if (!this.props.data)
			return null;

		line.labels = _.map(this.props.data.averageTps, tps => new Date(tps.first * 1000))
		line.datasets[0].data = _.map(this.props.data.averageTps, tps => tps.second)
		line.datasets[1].data = _.map(this.props.data.onlinePlayers, pls => pls.second)

		let playerState = "primary";
		if (this.props.data) {
			const ratio = this.props.data.players / this.props.data.maxPlayers;
			if (ratio > 0.95)
				playerState = "danger";
			else if (ratio > 0.8)
				playerState = "warning";
			else
				playerState = "success";
		}

		let tpsState = "primary";
		if (this.props.data) {
			if (this.props.data.tps >= 19.5)
				tpsState = "success";
			else if (this.props.data.tps >= 15)
				tpsState = "warning";
			else
				tpsState = "danger";
		}

		return (

				<Row>

					<Col xs={6} lg={3}>
						<Card inverse={true} color={playerState}>
							<CardBlock className="pb-0">

{this.props.data.players}/{this.props.data.maxPlayers}

Online players

							</CardBlock>
						</Card>
					</Col>

					<Col xs={6} lg={3}>
						<Card inverse={true} color={tpsState}>
							<CardBlock className="pb-0">

{this.props.data.tps}

Current TPS

							</CardBlock>
						</Card>
					</Col>

					<Col xs={6} lg={3}>
						<Card inverse={true} color="info">
							<CardBlock className="pb-0">

{this.props.data.address}

Address

							</CardBlock>
						</Card>
					</Col>

					<Col xs={6} lg={3}>
						<Card inverse={true} color="info">
							<CardBlock className="pb-0">

{this.props.data.onlineMode ? "Yes" : "No"}

Online mode

							</CardBlock>
						</Card>
					</Col>

					<Col xs={6} lg={3}>
						<Card inverse={true} color="primary">
							<CardBlock className="pb-0">

{this.props.data.uptimeTicks}

Uptime ticks

							</CardBlock>
						</Card>
					</Col>

					<Col xs={6} lg={3}>
						<Card inverse={true} color="primary">
							<CardBlock className="pb-0">

{this.props.data.game.version}

Minecraft Version

							</CardBlock>
						</Card>
					</Col>

					<Col xs={6} lg={3}>
						<Card inverse={true} color="primary">
							<CardBlock className="pb-0">

{this.props.data.api.version}

API Version

							</CardBlock>
						</Card>
					</Col>

					<Col xs={6} lg={3}>
						<Card inverse={true} color="primary">
							<CardBlock className="pb-0">

{this.props.data.implementation.version}

Implementation Version

							</CardBlock>
						</Card>
					</Col>

				</Row>
				<Row>

					<Col xs={12}>
						<Card>
							<CardHeader>
								Average TPS
							</CardHeader>
							<CardBlock>

									<Line data={line} options={options} />

							</CardBlock>
						</Card>
					</Col>

				</Row>

		)
	}
}

const mapStateToProps = (_state) => {
	const state = _state.dashboard

	return {
		data: state.data
	}
}

const mapDispatchToProps = (dispatch) => {
	return {
		requestInfo: () => {
			dispatch(requestInfo())
		},
	}
}

export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
<|endoftext|>


<commit_before>import Electron from 'electron';
import path from 'path';
import L10nManager from '../../modules/L10nManager';

const Shell = Electron.shell;
const Remote = Electron.remote;
const Dialog = Remote.dialog;
const Menu = Remote.Menu;
const Tray = Remote.Tray;
const App = Remote.app;

const _ = L10nManager.get.bind(L10nManager);
const iconsFolder = path.join('src', 'public', 'images', 'icons');
const trayDefaultIcon = path.join(iconsFolder, 'tray', 'default.png');
const trayWindowsIcon = path.join(iconsFolder, 'tray', 'windows.ico');

class AppTray {
  constructor() {
    this._initialized = false;
    this.tray = null;
  }

  build() {
    if (this._initialized) {
      return;
    }

    this._initialized = true;
    let mainWindow = Remote.getCurrentWindow();
    let icon = trayDefaultIcon;

    if (process.platform === 'win32') {
      icon = trayWindowsIcon;
    }

    this.tray = new Tray(icon);

    const trayMenu = [
      {
        label: _('app_tray_show_or_minimize'),
        click() {
          !mainWindow.isMinimized() ? mainWindow.minimize() : mainWindow.show();
        }
      },
      {
        type: 'separator'
      },
      {
        label: _('app_tray_play_previous_track'),
        click() {
          mainWindow.webContents.send('tray-MediaPreviousTrack');
        }
      },
      {
        label: _('app_tray_play_next_track'),
        click() {
          mainWindow.webContents.send('tray-MediaNextTrack');
        }
      },
      {
        label: _('app_tray_play_or_pause_track'),
        click() {
          mainWindow.webContents.send('tray-MediaPlayPause');
        }
      },
      {
        type: 'separator'
      },
      {
        label: _('app_tray_help'),
        submenu: [{
            label: _('app_tray_docs'),
            click() {
              Shell.openExternal('http://kaku.rocks/docs/index.html');
            }
          }, {
            label: _('app_tray_issue'),
            click() {
              Shell.openExternal('https://github.com/EragonJ/Kaku/issues');
            }
          }
        ]
      },
      {
        label: _('app_tray_quit'),
        click() {
          App.quit();
        }
      }
    ];

    this.tray.setToolTip('Kaku');
    this.tray.setContextMenu(Menu.buildFromTemplate(trayMenu));
  }

  destroy() {
    if (this.tray) {
      this.tray.destroy();
    }
  }
}

module.exports = new AppTray();
<commit_msg>fixed the problem that menuItems are not translated<commit_after>import Electron from 'electron';
import path from 'path';
import L10nManager from '../../modules/L10nManager';

const Shell = Electron.shell;
const Remote = Electron.remote;
const Dialog = Remote.dialog;
const Menu = Remote.Menu;
const Tray = Remote.Tray;
const App = Remote.app;

const _ = L10nManager.get.bind(L10nManager);
const iconsFolder = path.join('src', 'public', 'images', 'icons');
const trayDefaultIcon = path.join(iconsFolder, 'tray', 'default.png');
const trayWindowsIcon = path.join(iconsFolder, 'tray', 'windows.ico');

class AppTray {
  constructor() {
    this.tray = null;

    L10nManager.on('language-changed', () => {
      this.destroy();
      this.build();
    });
  }

  build() {
    let mainWindow = Remote.getCurrentWindow();
    let icon = trayDefaultIcon;

    if (process.platform === 'win32') {
      icon = trayWindowsIcon;
    }

    this.tray = new Tray(icon);

    const trayMenu = [
      {
        label: _('app_tray_show_or_minimize'),
        click() {
          !mainWindow.isMinimized() ? mainWindow.minimize() : mainWindow.show();
        }
      },
      {
        type: 'separator'
      },
      {
        label: _('app_tray_play_previous_track'),
        click() {
          mainWindow.webContents.send('tray-MediaPreviousTrack');
        }
      },
      {
        label: _('app_tray_play_next_track'),
        click() {
          mainWindow.webContents.send('tray-MediaNextTrack');
        }
      },
      {
        label: _('app_tray_play_or_pause_track'),
        click() {
          mainWindow.webContents.send('tray-MediaPlayPause');
        }
      },
      {
        type: 'separator'
      },
      {
        label: _('app_tray_help'),
        submenu: [{
            label: _('app_tray_docs'),
            click() {
              Shell.openExternal('http://kaku.rocks/docs/index.html');
            }
          }, {
            label: _('app_tray_issue'),
            click() {
              Shell.openExternal('https://github.com/EragonJ/Kaku/issues');
            }
          }
        ]
      },
      {
        label: _('app_tray_quit'),
        click() {
          App.quit();
        }
      }
    ];

    this.tray.setToolTip('Kaku');
    this.tray.setContextMenu(Menu.buildFromTemplate(trayMenu));
  }

  destroy() {
    if (this.tray) {
      this.tray.destroy();
    }
  }
}

module.exports = new AppTray();
<|endoftext|>


<commit_before>/*jshint indent: 2 */
/*global angular: false */

(function () {
  'use strict';

  var mainModule = angular.module('as.sortable');

  /**
   * Helper factory for sortable.
   */
  mainModule.factory('$helper', ['$document', '$window',
    function ($document, $window) {
      return {

        /**
         * Get the height of an element.
         *
         * @param {Object} element Angular element.
         * @returns {String} Height
         */
        height: function (element) {
          return element[0].getBoundingClientRect().height;
        },

        /**
         * Get the width of an element.
         *
         * @param {Object} element Angular element.
         * @returns {String} Width
         */
        width: function (element) {
          return element[0].getBoundingClientRect().width;
        },

        /**
         * Get the offset values of an element.
         *
         * @param {Object} element Angular element.
         * @param {Object} [scrollableContainer] Scrollable container object for calculating relative top & left (optional, defaults to Document)
         * @returns {Object} Object with properties width, height, top and left
         */
        offset: function (element, scrollableContainer) {
          var boundingClientRect = element[0].getBoundingClientRect();
          if (!scrollableContainer) {
            scrollableContainer = $document[0].documentElement;
          }

          return {
            width: boundingClientRect.width || element.prop('offsetWidth'),
            height: boundingClientRect.height || element.prop('offsetHeight'),
            top: boundingClientRect.top + ($window.pageYOffset || scrollableContainer.scrollTop - scrollableContainer.offsetTop),
            left: boundingClientRect.left + ($window.pageXOffset || scrollableContainer.scrollLeft - scrollableContainer.offsetLeft)
          };
        },

        /**
         * get the event object for touch.
         *
         * @param  {Object} event the touch event
         * @return {Object} the touch event object.
         */
        eventObj: function (event) {
          var obj = event;
          if (event.targetTouches !== undefined) {
            obj = event.targetTouches.item(0);
          } else if (event.originalEvent !== undefined && event.originalEvent.targetTouches !== undefined) {
            obj = event.originalEvent.targetTouches.item(0);
          }
          return obj;
        },

        /**
         * Checks whether the touch is valid and multiple.
         *
         * @param event the event object.
         * @returns {boolean} true if touch is multiple.
         */
        isTouchInvalid: function (event) {

          var touchInvalid = false;
          if (event.touches !== undefined && event.touches.length > 1) {
            touchInvalid = true;
          } else if (event.originalEvent !== undefined &&
            event.originalEvent.touches !== undefined && event.originalEvent.touches.length > 1) {
            touchInvalid = true;
          }
          return touchInvalid;
        },

        /**
         * Get the start position of the target element according to the provided event properties.
         *
         * @param {Object} event Event
         * @param {Object} target Target element
         * @param {Object} [scrollableContainer] (optional) Scrollable container object
         * @returns {Object} Object with properties offsetX, offsetY.
         */
        positionStarted: function (event, target, scrollableContainer) {
          var pos = {};
          pos.offsetX = event.pageX - this.offset(target, scrollableContainer).left;
          pos.offsetY = event.pageY - this.offset(target, scrollableContainer).top;
          pos.startX = pos.lastX = event.pageX;
          pos.startY = pos.lastY = event.pageY;
          pos.nowX = pos.nowY = pos.distX = pos.distY = pos.dirAx = 0;
          pos.dirX = pos.dirY = pos.lastDirX = pos.lastDirY = pos.distAxX = pos.distAxY = 0;
          return pos;
        },

        /**
         * Calculates the event position and sets the direction
         * properties.
         *
         * @param pos the current position of the element.
         * @param event the move event.
         */
        calculatePosition: function (pos, event) {
          // mouse position last events
          pos.lastX = pos.nowX;
          pos.lastY = pos.nowY;

          // mouse position this events
          pos.nowX = event.pageX;
          pos.nowY = event.pageY;

          // distance mouse moved between events
          pos.distX = pos.nowX - pos.lastX;
          pos.distY = pos.nowY - pos.lastY;

          // direction mouse was moving
          pos.lastDirX = pos.dirX;
          pos.lastDirY = pos.dirY;

          // direction mouse is now moving (on both axis)
          pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1;
          pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1;

          // axis mouse is now moving on
          var newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0;

          // calc distance moved on this axis (and direction)
          if (pos.dirAx !== newAx) {
            pos.distAxX = 0;
            pos.distAxY = 0;
          } else {
            pos.distAxX += Math.abs(pos.distX);
            if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) {
              pos.distAxX = 0;
            }

            pos.distAxY += Math.abs(pos.distY);
            if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) {
              pos.distAxY = 0;
            }
          }
          pos.dirAx = newAx;
        },

        /**
         * Move the position by applying style.
         *
         * @param event the event object
         * @param element - the dom element
         * @param pos - current position
         * @param container - the bounding container.
         * @param containerPositioning - absolute or relative positioning.
         * @param {Object} [scrollableContainer] (optional) Scrollable container object
         */
        movePosition: function (event, element, pos, container, containerPositioning, scrollableContainer) {
          var bounds;
          var useRelative = (containerPositioning === 'relative');

          element.x = event.pageX - pos.offsetX;
          element.y = event.pageY - pos.offsetY;

          if (container) {
            bounds = this.offset(container, scrollableContainer);

            if (useRelative) {
              // reduce positioning by bounds
              element.x -= bounds.left;
              element.y -= bounds.top;

              // reset bounds
              bounds.left = 0;
              bounds.top = 0;
            }

            if (element.x < bounds.left) {
              element.x = bounds.left;
            } else if (element.x >= bounds.width + bounds.left - this.offset(element).width) {
              element.x = bounds.width + bounds.left - this.offset(element).width;
            }
            if (element.y < bounds.top) {
              element.y = bounds.top;
            } else if (element.y >= bounds.height + bounds.top - this.offset(element).height) {
              element.y = bounds.height + bounds.top - this.offset(element).height;
            }
          }

          element.css({
            'left': element.x + 'px',
            'top': element.y + 'px'
          });

          this.calculatePosition(pos, event);
        },

        /**
         * The drag item info and functions.
         * retains the item info before and after move.
         * holds source item and target scope.
         *
         * @param item - the drag item
         * @returns {{index: *, parent: *, source: *,
                 *          sourceInfo: {index: *, itemScope: (*|.dragItem.sourceInfo.itemScope|$scope.itemScope|itemScope), sortableScope: *},
                 *         moveTo: moveTo, isSameParent: isSameParent, isOrderChanged: isOrderChanged, eventArgs: eventArgs, apply: apply}}
         */
        dragItem: function (item) {

          return {
            index: item.index(),
            parent: item.sortableScope,
            source: item,
            sourceInfo: {
              index: item.index(),
              itemScope: item.itemScope,
              sortableScope: item.sortableScope
            },
            moveTo: function (parent, index) { // Move the item to a new position
              this.parent = parent;
              //If source Item is in the same Parent.
              if (this.isSameParent() && this.source.index() < index) { // and target after
                index = index - 1;
              }
              this.index = index;
            },
            isSameParent: function () {
              return this.parent.element === this.sourceInfo.sortableScope.element;
            },
            isOrderChanged: function () {
              return this.index !== this.sourceInfo.index;
            },
            eventArgs: function () {
              return {
                source: this.sourceInfo,
                dest: {
                  index: this.index,
                  sortableScope: this.parent
                }
              };
            },
            apply: function () {

              // If clone is not set, set it to false
              if (typeof (this.sourceInfo.sortableScope.options.clone) === 'undefined') {
                this.sourceInfo.sortableScope.options.clone = false;
              }

              // If clone is not set to true, remove the item from the source model.
              if (this.sourceInfo.sortableScope.options.clone === false) {
                this.sourceInfo.sortableScope.removeItem(this.sourceInfo.index);
              }

              // If the dragged item is not already there, insert the item. This avoids ng-repeat dupes error
              if(this.parent.modelValue.indexOf(this.source.modelValue) < 0) {
                this.parent.insertItem(this.index, this.source.modelValue);
              }

            }
          };
        },

        /**
         * Check the drag is not allowed for the element.
         *
         * @param element - the element to check
         * @returns {boolean} - true if drag is not allowed.
         */
        noDrag: function (element) {
          return element.attr('no-drag') !== undefined || element.attr('data-no-drag') !== undefined;
        },

        /**
         * Helper function to find the first ancestor with a given selector
         * @param el - angular element to start looking at
         * @param selector - selector to find the parent
         * @returns {Object} - Angular element of the ancestor or body if not found
         * @private
         */
        findAncestor: function (el, selector) {
          el = el[0];
          var matches = Element.matches || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector;
          while ((el = el.parentElement) && !matches.call(el, selector)) {
          }
          return el ? angular.element(el) : angular.element(document.body);
        }
      };
    }
  ]);

}());<commit_msg>Fix: use the most up-to-date model value when dropping an item<commit_after>/*jshint indent: 2 */
/*global angular: false */

(function () {
  'use strict';

  var mainModule = angular.module('as.sortable');

  /**
   * Helper factory for sortable.
   */
  mainModule.factory('$helper', ['$document', '$window',
    function ($document, $window) {
      return {

        /**
         * Get the height of an element.
         *
         * @param {Object} element Angular element.
         * @returns {String} Height
         */
        height: function (element) {
          return element[0].getBoundingClientRect().height;
        },

        /**
         * Get the width of an element.
         *
         * @param {Object} element Angular element.
         * @returns {String} Width
         */
        width: function (element) {
          return element[0].getBoundingClientRect().width;
        },

        /**
         * Get the offset values of an element.
         *
         * @param {Object} element Angular element.
         * @param {Object} [scrollableContainer] Scrollable container object for calculating relative top & left (optional, defaults to Document)
         * @returns {Object} Object with properties width, height, top and left
         */
        offset: function (element, scrollableContainer) {
          var boundingClientRect = element[0].getBoundingClientRect();
          if (!scrollableContainer) {
            scrollableContainer = $document[0].documentElement;
          }

          return {
            width: boundingClientRect.width || element.prop('offsetWidth'),
            height: boundingClientRect.height || element.prop('offsetHeight'),
            top: boundingClientRect.top + ($window.pageYOffset || scrollableContainer.scrollTop - scrollableContainer.offsetTop),
            left: boundingClientRect.left + ($window.pageXOffset || scrollableContainer.scrollLeft - scrollableContainer.offsetLeft)
          };
        },

        /**
         * get the event object for touch.
         *
         * @param  {Object} event the touch event
         * @return {Object} the touch event object.
         */
        eventObj: function (event) {
          var obj = event;
          if (event.targetTouches !== undefined) {
            obj = event.targetTouches.item(0);
          } else if (event.originalEvent !== undefined && event.originalEvent.targetTouches !== undefined) {
            obj = event.originalEvent.targetTouches.item(0);
          }
          return obj;
        },

        /**
         * Checks whether the touch is valid and multiple.
         *
         * @param event the event object.
         * @returns {boolean} true if touch is multiple.
         */
        isTouchInvalid: function (event) {

          var touchInvalid = false;
          if (event.touches !== undefined && event.touches.length > 1) {
            touchInvalid = true;
          } else if (event.originalEvent !== undefined &&
            event.originalEvent.touches !== undefined && event.originalEvent.touches.length > 1) {
            touchInvalid = true;
          }
          return touchInvalid;
        },

        /**
         * Get the start position of the target element according to the provided event properties.
         *
         * @param {Object} event Event
         * @param {Object} target Target element
         * @param {Object} [scrollableContainer] (optional) Scrollable container object
         * @returns {Object} Object with properties offsetX, offsetY.
         */
        positionStarted: function (event, target, scrollableContainer) {
          var pos = {};
          pos.offsetX = event.pageX - this.offset(target, scrollableContainer).left;
          pos.offsetY = event.pageY - this.offset(target, scrollableContainer).top;
          pos.startX = pos.lastX = event.pageX;
          pos.startY = pos.lastY = event.pageY;
          pos.nowX = pos.nowY = pos.distX = pos.distY = pos.dirAx = 0;
          pos.dirX = pos.dirY = pos.lastDirX = pos.lastDirY = pos.distAxX = pos.distAxY = 0;
          return pos;
        },

        /**
         * Calculates the event position and sets the direction
         * properties.
         *
         * @param pos the current position of the element.
         * @param event the move event.
         */
        calculatePosition: function (pos, event) {
          // mouse position last events
          pos.lastX = pos.nowX;
          pos.lastY = pos.nowY;

          // mouse position this events
          pos.nowX = event.pageX;
          pos.nowY = event.pageY;

          // distance mouse moved between events
          pos.distX = pos.nowX - pos.lastX;
          pos.distY = pos.nowY - pos.lastY;

          // direction mouse was moving
          pos.lastDirX = pos.dirX;
          pos.lastDirY = pos.dirY;

          // direction mouse is now moving (on both axis)
          pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1;
          pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1;

          // axis mouse is now moving on
          var newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0;

          // calc distance moved on this axis (and direction)
          if (pos.dirAx !== newAx) {
            pos.distAxX = 0;
            pos.distAxY = 0;
          } else {
            pos.distAxX += Math.abs(pos.distX);
            if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) {
              pos.distAxX = 0;
            }

            pos.distAxY += Math.abs(pos.distY);
            if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) {
              pos.distAxY = 0;
            }
          }
          pos.dirAx = newAx;
        },

        /**
         * Move the position by applying style.
         *
         * @param event the event object
         * @param element - the dom element
         * @param pos - current position
         * @param container - the bounding container.
         * @param containerPositioning - absolute or relative positioning.
         * @param {Object} [scrollableContainer] (optional) Scrollable container object
         */
        movePosition: function (event, element, pos, container, containerPositioning, scrollableContainer) {
          var bounds;
          var useRelative = (containerPositioning === 'relative');

          element.x = event.pageX - pos.offsetX;
          element.y = event.pageY - pos.offsetY;

          if (container) {
            bounds = this.offset(container, scrollableContainer);

            if (useRelative) {
              // reduce positioning by bounds
              element.x -= bounds.left;
              element.y -= bounds.top;

              // reset bounds
              bounds.left = 0;
              bounds.top = 0;
            }

            if (element.x < bounds.left) {
              element.x = bounds.left;
            } else if (element.x >= bounds.width + bounds.left - this.offset(element).width) {
              element.x = bounds.width + bounds.left - this.offset(element).width;
            }
            if (element.y < bounds.top) {
              element.y = bounds.top;
            } else if (element.y >= bounds.height + bounds.top - this.offset(element).height) {
              element.y = bounds.height + bounds.top - this.offset(element).height;
            }
          }

          element.css({
            'left': element.x + 'px',
            'top': element.y + 'px'
          });

          this.calculatePosition(pos, event);
        },

        /**
         * The drag item info and functions.
         * retains the item info before and after move.
         * holds source item and target scope.
         *
         * @param item - the drag item
         * @returns {{index: *, parent: *, source: *,
                 *          sourceInfo: {index: *, itemScope: (*|.dragItem.sourceInfo.itemScope|$scope.itemScope|itemScope), sortableScope: *},
                 *         moveTo: moveTo, isSameParent: isSameParent, isOrderChanged: isOrderChanged, eventArgs: eventArgs, apply: apply}}
         */
        dragItem: function (item) {

          return {
            index: item.index(),
            parent: item.sortableScope,
            source: item,
            sourceInfo: {
              index: item.index(),
              itemScope: item.itemScope,
              sortableScope: item.sortableScope
            },
            moveTo: function (parent, index) { // Move the item to a new position
              this.parent = parent;
              //If source Item is in the same Parent.
              if (this.isSameParent() && this.source.index() < index) { // and target after
                index = index - 1;
              }
              this.index = index;
            },
            isSameParent: function () {
              return this.parent.element === this.sourceInfo.sortableScope.element;
            },
            isOrderChanged: function () {
              return this.index !== this.sourceInfo.index;
            },
            eventArgs: function () {
              return {
                source: this.sourceInfo,
                dest: {
                  index: this.index,
                  sortableScope: this.parent
                }
              };
            },
            apply: function () {

              // If clone is not set, set it to false
              if (typeof (this.sourceInfo.sortableScope.options.clone) === 'undefined') {
                this.sourceInfo.sortableScope.options.clone = false;
              }

              var sourceItem;
              // If clone is not set to true, remove the item from the source model.
              if (this.sourceInfo.sortableScope.options.clone === false) {
                sourceItem = this.sourceInfo.sortableScope.removeItem(this.sourceInfo.index);
              } else {
                sourceItem = this.source.itemData();
              }

              // If the dragged item is not already there, insert the item. This avoids ng-repeat dupes error
              if(this.parent.modelValue.indexOf(sourceItem) < 0) {
                this.parent.insertItem(this.index, sourceItem);
              }

            }
          };
        },

        /**
         * Check the drag is not allowed for the element.
         *
         * @param element - the element to check
         * @returns {boolean} - true if drag is not allowed.
         */
        noDrag: function (element) {
          return element.attr('no-drag') !== undefined || element.attr('data-no-drag') !== undefined;
        },

        /**
         * Helper function to find the first ancestor with a given selector
         * @param el - angular element to start looking at
         * @param selector - selector to find the parent
         * @returns {Object} - Angular element of the ancestor or body if not found
         * @private
         */
        findAncestor: function (el, selector) {
          el = el[0];
          var matches = Element.matches || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector;
          while ((el = el.parentElement) && !matches.call(el, selector)) {
          }
          return el ? angular.element(el) : angular.element(document.body);
        }
      };
    }
  ]);

}());<|endoftext|>


<commit_before>/*jshint bitwise:true, indent:2, curly:true eqeqeq:true, immed:true,
latedef:true, newcap:true, noarg:true, regexp:true, undef:true,
trailing:true white:true*/
/*global XT:true, XV:true, XM:true, _:true, enyo:true*/

(function () {
  var FETCH_TRIGGER = 100;
  var MODULE_MENU = 0;
  var PANEL_MENU = 1;

  enyo.kind({
    name: "XV.Module",
    kind: "Panels",
    classes: "app enyo-unselectable",
    published: {
      label: "",
      modules: []
    },
    events: {
      onListAdded: "",
      onTogglePullout: ""
    },
    handlers: {
      onParameterChange: "requery",
      onScroll: "scrolled",
      onListItemTapped: "listItemTapped"
    },
    showPullout: true,
    arrangerKind: "CollapsingArranger",
    components: [
      {kind: "FittableRows", classes: "left", components: [
        {kind: "onyx.Toolbar", classes: "onyx-menu-toolbar", components: [
          {kind: "onyx.Button", name: "backButton", content: "_logout".loc(),
            ontap: "backTapped"},
          {kind: "Group", name: "iconButtonGroup",
            defaultKind: "onyx.IconButton", tag: null, components: [
            {name: "historyIconButton", src: "assets/menu-icon-bookmark.png",
              ontap: "showHistory"},
            {name: "searchIconButton", src: "assets/menu-icon-search.png",
              ontap: "showParameters"}
          ]},
          {name: "leftLabel"},
          {kind: "onyx.Popup", name: "logoutPopup", centered: true,
            modal: true, floating: true, components: [
            {content: "_logoutConfirmation".loc() },
            {tag: "br"},
            {kind: "onyx.Button", content: "_ok".loc(), ontap: "logout"},
            {kind: "onyx.Button", content: "_cancel".loc(),
              ontap: "closeLogoutPopup", classes: "onyx-blue"}
          ]}
        ]},
        {name: "menuPanels", kind: "Panels", draggable: false, fit: true,
          components: [
          {name: "moduleMenu", kind: "List", touch: true,
              onSetupItem: "setupModuleMenuItem",
              components: [
            {name: "moduleItem", classes: "item enyo-border-box",
              ontap: "moduleItemTap"}
          ]},
          {name: "panelMenu", kind: "List", touch: true,
             onSetupItem: "setupPanelMenuItem", components: [
            {name: "listItem", classes: "item enyo-border-box",
              ontap: "menuItemTap"}
          ]},
          {} // Why do panels only work when there are 3+ objects?
        ]}
      ]},
      {kind: "FittableRows", components: [
        {kind: "onyx.MoreToolbar", name: "contentToolbar", components: [
          {kind: "onyx.Grabber"},
          {name: "rightLabel", style: "text-align: center"},
          {kind: "onyx.InputDecorator", style: "float: right;", components: [
            {name: 'searchInput', kind: "onyx.Input", style: "width: 200px;",
              placeholder: "Search", onchange: "inputChanged"},
            {kind: "Image", src: "assets/search-input-search.png"}
          ]},
          {kind: "onyx.Button", content: "_new".loc(), ontap: "newRecord",
            style: "float: right;" }
        ]},
        {name: "contentPanels", kind: "Panels", arrangerKind: "LeftRightArranger",
           margin: 0, fit: true, panelCount: 0, onTransitionFinish: "finishedTransition"}
      ]},
      {kind: "Signals", onModelSave: "refreshInfoObject"}
    ],
    fetched: {},
    activate: function () {
      this.setMenuPanel(MODULE_MENU);
    },
    backTapped: function () {
      var index = this.$.menuPanels.getIndex();
      if (index === MODULE_MENU) {
        this.warnLogout();
      } else {
        this.setMenuPanel(MODULE_MENU);
      }
    },
    create: function () {
      this.inherited(arguments);
      var modules = this.getModules() || [],
        label = this.getLabel(),
        panels,
        panel,
        i,
        n;
      this.$.leftLabel.setContent(label);

      // Build panels
      for (i = 0; i < modules.length; i++) {
        panels = modules[i].panels || [];
        for (n = 0; n < panels.length; n++) {
          // Keep track of where this panel is being placed for later reference
          panels[n].index = this.$.contentPanels.panelCount++;
          panel = this.$.contentPanels.createComponent(panels[n]);
          if (panel instanceof XV.List) {
            // Bubble parameter widget up to pullout
            this.doListAdded(panel);
          }
        }
      }
      this.$.moduleMenu.setCount(modules.length);
    },
    finishedTransition: function (inSender, inEvent) {
      this.setContentPanel(inSender.index);
    },
    getSelectedModule: function (index) {
      return this._selectedModule;
    },
    inputChanged: function (inSender, inEvent) {
      var index = this.$.contentPanels.getIndex(),
        list = this.panels[index].name;
      this.fetched = {};
      this.fetch(list);
    },
    listItemTapped: function (inSender, inEvent) {
      var list = this.$.contentPanels.getActive(),
        workspace = list.getWorkspace(),
        id = list.getModel(inEvent.index).id;

      // Transition to workspace view, including the model id payload
      this.bubble("workspace", {
        eventName: "workspace",
        workspace: workspace,
        id: id
      });
      return true;
    },
    fetch: function (index, options) {
      index = index || this.$.contentPanels.getIndex();
      var panel = this.$.contentPanels.getPanels()[index],
        name = panel ? panel.name : "",
        query,
        input,
        parameterWidget,
        parameters;
      if (!panel instanceof XV.List) { return; }
      query = panel.getQuery() || {};
      input = this.$.searchInput.getValue();
      parameterWidget = XT.app ? XT.app.getPullout().getItem(name) : null;
      parameters = parameterWidget ? parameterWidget.getParameters() : [];
      options = options ? _.clone(options) : {};
      options.showMore = _.isBoolean(options.showMore) ?
        options.showMore : false;

      // Build parameters
      if (input || parameters.length) {
        query.parameters = [];

        // Input search parameters
        if (input) {
          query.parameters = [{
            attribute: panel.getSearchableAttributes(),
            operator: 'MATCHES',
            value: this.$.searchInput.getValue()
          }];
        }

        // Advanced parameters
        if (parameters) {
          query.parameters = query.parameters.concat(parameters);
        }
      } else {
        delete query.parameters;
      }

      panel.setQuery(query);
      panel.fetch(options);
      this.fetched[index] = true;
    },
    newRecord: function (inSender, inEvent) {
      var list = this.$.contentPanels.getActive(),
        workspace = list.getWorkspace();
      this.bubble("workspace", {
        eventName: "workspace",
        workspace: workspace
      });
      return true;
    },
    menuItemTap: function (inSender, inEvent) {
      this.setContentPanel(inEvent.index);
    },
    moduleItemTap: function (inSender, inEvent) {
      //this.setModule(inEvent.index);
    },
    requery: function (inSender, inEvent) {
      this.fetch();
    },
    scrolled: function (inSender, inEvent) {
      if (inEvent.originator instanceof XV.List === false) { return; }
      var list = inEvent.originator,
        max = list.getScrollBounds().maxTop - list.rowHeight * FETCH_TRIGGER,
        options = {};
      if (list.getIsMore() && list.getScrollPosition() > max && !list.getIsFetching()) {
        list.setIsFetching(true);
        options.showMore = true;
        this.fetch(list.name, options);
      }
    },
    setContentPanel: function (index) {
      var module = this.getSelectedModule(),
        panelIndex = module && module.panels ? module.panels[index].index : -1,
        panel = panelIndex > -1 ? this.$.contentPanels.getPanels()[panelIndex] : null,
        label = panel ? panel.getLabel() : "";
      if (!panel) { return; }

      // Select panelMenu
      if (!this.$.panelMenu.isSelected(index)) {
        this.$.panelMenu.select(index);
      }

      // Select list
      if (this.$.contentPanels.getIndex() !== panelIndex) {
        this.$.contentPanels.setIndex(panelIndex);
      }
      this.$.rightLabel.setContent(label);
      if (panel.fetch && !this.fetched[panelIndex]) {
        this.fetch(panelIndex);
      }
    },
    setMenuPanel: function (index) {
      var label = index ? "_back".loc() : "_logout".loc();
      this.$.menuPanels.setIndex(index);
      this.$.menuPanels.getActive().select(0);
      this.setContentPanel(0);
      this.$.backButton.setContent(label);
    },
    setModule: function (index) {
      var module = this.getModules()[index],
        panels = module.panels || [];
      if (module !== this._selectedModule) {
        this._selectedModule = module;
        if (panels.length) {
          this.$.panelMenu.setCount(panels.length);
          this.setMenuPanel(PANEL_MENU);
        }
      }
    },
    setupModuleMenuItem: function (inSender, inEvent) {
      var index = inEvent.index,
        label = this.modules[index].label,
        isSelected = inSender.isSelected(index);
      this.$.moduleItem.setContent(label);
      this.$.moduleItem.addRemoveClass("onyx-selected", isSelected);
      if (isSelected) { this.setModule(index); }
    },
    setupPanelMenuItem: function (inSender, inEvent) {
      var module = this.getSelectedModule(),
        panel;
      panel =  module.panels[inEvent.index].name;
      this.$.listItem.setContent(this.$.contentPanels.$[panel].getLabel());
      this.$.listItem.addRemoveClass("onyx-selected", inSender.isSelected(inEvent.index));
    },
    showHistory: function (inSender, inEvent) {
      var panel = {name: 'history'};
      this.doTogglePullout(panel);
    },
    showParameters: function (inSender, inEvent) {
      var panel = this.$.contentPanels.getActive();
      this.doTogglePullout(panel);
    },
    /**
     * If a model has changed, check the panels of this module to see if we can
     * update the info object in the list.
     * XXX if there are multiple modules alive then all of them will catch
     * XXX the signal, which isn't ideal for performance
     */
    refreshInfoObject: function (inSender, inPayload) {
      // obnoxious massaging. Can't think of an elegant way to do this.
      // salt in wounds: in setup we massage by adding List on the end, but with
      // crm we massage by adding List on the end. This is horrible.
      // XXX not sustainable
      var listBase = XV.util.stripModelNamePrefix(inPayload.recordType).camelize(),
        listName = this.name === "setup" ? listBase + "List" : listBase + "List",
        list = this.$.contentPanels.$[listName];

      /**
       * If the update model is part of a cached collection, refresh the cache
       * XXX This string massaging should be refactored into intelligence within
       * the collection itself.
       * XXX this part will happen for each living module. But it only needs to
       * be refreshed once. Moving away from signals would solve this. Or we
       * could create a dedicated signal event.
       */
      var cacheName;
      if (listBase.charAt(listBase.length - 1) === 'y') {
        cacheName = listBase.substring(0, listBase.length - 1) + 'ies';
      } else {
        cacheName = listBase + 's';
      }
      if (XM[cacheName]) {
        XM[cacheName].fetch();
        // no need to fetch the model itself, but we do want to tell the
        // module that contains the list to refresh that list by setting
        // the fetched property to false

        if (this.fetched[listName]) {
          this.fetched[listName] = false;
        }

        return;
      }

      /**
       * Update the model itself
       */
      if (!list) {
        // we don't have this model on our list. No need to update
        return;
      }
      var model = _.find(list.collection.models, function (model) {
        return model.id === inPayload.id;
      });
      if (!model) {
        // this model isn't in the list. No need to update
        return;
      }
      model.fetch();
    },

    /**
      Logout management. We show the user a warning popup before we log them out.
    */
    warnLogout: function () {
      this.$.logoutPopup.show();
    },
    closeLogoutPopup: function () {
      this.$.logoutPopup.hide();
    },
    logout: function () {
      this.$.logoutPopup.hide();
      XT.session.logout();
    },

    /**
      Manually set one of the icon buttons to be active. Note passing null
      as the parameter will disactivate all icons.
    */
    setActiveIconButton: function (buttonName) {
      var activeIconButton = null;
      if (buttonName === 'search') {
        activeIconButton = this.$.searchIconButton;
      } else if (buttonName === 'history') {
        activeIconButton = this.$.historyIconButton;
      }
      this.$.iconButtonGroup.setActive(activeIconButton);
    }

  });

}());
<commit_msg>getting there...<commit_after>/*jshint bitwise:true, indent:2, curly:true eqeqeq:true, immed:true,
latedef:true, newcap:true, noarg:true, regexp:true, undef:true,
trailing:true white:true*/
/*global XT:true, XV:true, XM:true, _:true, enyo:true*/

(function () {
  var FETCH_TRIGGER = 100;
  var MODULE_MENU = 0;
  var PANEL_MENU = 1;

  enyo.kind({
    name: "XV.Module",
    kind: "Panels",
    classes: "app enyo-unselectable",
    published: {
      label: "",
      modules: []
    },
    events: {
      onListAdded: "",
      onTogglePullout: ""
    },
    handlers: {
      onParameterChange: "requery",
      onScroll: "scrolled",
      onListItemTapped: "listItemTapped"
    },
    showPullout: true,
    arrangerKind: "CollapsingArranger",
    components: [
      {kind: "FittableRows", classes: "left", components: [
        {kind: "onyx.Toolbar", classes: "onyx-menu-toolbar", components: [
          {kind: "onyx.Button", name: "backButton", content: "_logout".loc(),
            ontap: "backTapped"},
          {kind: "Group", name: "iconButtonGroup",
            defaultKind: "onyx.IconButton", tag: null, components: [
            {name: "historyIconButton", src: "assets/menu-icon-bookmark.png",
              ontap: "showHistory"},
            {name: "searchIconButton", src: "assets/menu-icon-search.png",
              ontap: "showParameters"}
          ]},
          {name: "leftLabel"},
          {kind: "onyx.Popup", name: "logoutPopup", centered: true,
            modal: true, floating: true, components: [
            {content: "_logoutConfirmation".loc() },
            {tag: "br"},
            {kind: "onyx.Button", content: "_ok".loc(), ontap: "logout"},
            {kind: "onyx.Button", content: "_cancel".loc(),
              ontap: "closeLogoutPopup", classes: "onyx-blue"}
          ]}
        ]},
        {name: "menuPanels", kind: "Panels", draggable: false, fit: true,
          components: [
          {name: "moduleMenu", kind: "List", touch: true,
              onSetupItem: "setupModuleMenuItem",
              components: [
            {name: "moduleItem", classes: "item enyo-border-box",
              ontap: "moduleItemTap"}
          ]},
          {name: "panelMenu", kind: "List", touch: true,
             onSetupItem: "setupPanelMenuItem", components: [
            {name: "listItem", classes: "item enyo-border-box",
              ontap: "menuItemTap"}
          ]},
          {} // Why do panels only work when there are 3+ objects?
        ]}
      ]},
      {kind: "FittableRows", components: [
        {kind: "onyx.MoreToolbar", name: "contentToolbar", components: [
          {kind: "onyx.Grabber"},
          {name: "rightLabel", style: "text-align: center"},
          {kind: "onyx.InputDecorator", style: "float: right;", components: [
            {name: 'searchInput', kind: "onyx.Input", style: "width: 200px;",
              placeholder: "Search", onchange: "inputChanged"},
            {kind: "Image", src: "assets/search-input-search.png"}
          ]},
          {kind: "onyx.Button", content: "_new".loc(), ontap: "newRecord",
            style: "float: right;" }
        ]},
        {name: "contentPanels", kind: "Panels", margin: 0, fit: true,
          draggable: false, panelCount: 0}
      ]},
      {kind: "Signals", onModelSave: "refreshInfoObject"}
    ],
    fetched: {},
    activate: function () {
      this.setMenuPanel(MODULE_MENU);
    },
    backTapped: function () {
      var index = this.$.menuPanels.getIndex();
      if (index === MODULE_MENU) {
        this.warnLogout();
      } else {
        this.setMenuPanel(MODULE_MENU);
      }
    },
    create: function () {
      this.inherited(arguments);
      var modules = this.getModules() || [],
        label = this.getLabel(),
        panels,
        panel,
        i,
        n;
      this.$.leftLabel.setContent(label);

      // Build panels
      for (i = 0; i < modules.length; i++) {
        panels = modules[i].panels || [];
        for (n = 0; n < panels.length; n++) {
          // Keep track of where this panel is being placed for later reference
          panels[n].index = this.$.contentPanels.panelCount++;
          panel = this.$.contentPanels.createComponent(panels[n]);
          if (panel instanceof XV.List) {
            // Bubble parameter widget up to pullout
            this.doListAdded(panel);
          }
        }
      }
      this.$.moduleMenu.setCount(modules.length);
    },
    getSelectedModule: function (index) {
      return this._selectedModule;
    },
    inputChanged: function (inSender, inEvent) {
      var index = this.$.contentPanels.getIndex(),
        list = this.panels[index].name;
      this.fetched = {};
      this.fetch(list);
    },
    listItemTapped: function (inSender, inEvent) {
      var list = this.$.contentPanels.getActive(),
        workspace = list.getWorkspace(),
        id = list.getModel(inEvent.index).id;

      // Transition to workspace view, including the model id payload
      this.bubble("workspace", {
        eventName: "workspace",
        workspace: workspace,
        id: id
      });
      return true;
    },
    fetch: function (index, options) {
      index = index || this.$.contentPanels.getIndex();
      var panel = this.$.contentPanels.getPanels()[index],
        name = panel ? panel.name : "",
        query,
        input,
        parameterWidget,
        parameters;
      if (!panel instanceof XV.List) { return; }
      query = panel.getQuery() || {};
      input = this.$.searchInput.getValue();
      parameterWidget = XT.app ? XT.app.getPullout().getItem(name) : null;
      parameters = parameterWidget ? parameterWidget.getParameters() : [];
      options = options ? _.clone(options) : {};
      options.showMore = _.isBoolean(options.showMore) ?
        options.showMore : false;

      // Build parameters
      if (input || parameters.length) {
        query.parameters = [];

        // Input search parameters
        if (input) {
          query.parameters = [{
            attribute: panel.getSearchableAttributes(),
            operator: 'MATCHES',
            value: this.$.searchInput.getValue()
          }];
        }

        // Advanced parameters
        if (parameters) {
          query.parameters = query.parameters.concat(parameters);
        }
      } else {
        delete query.parameters;
      }

      panel.setQuery(query);
      panel.fetch(options);
      this.fetched[index] = true;
    },
    newRecord: function (inSender, inEvent) {
      var list = this.$.contentPanels.getActive(),
        workspace = list.getWorkspace();
      this.bubble("workspace", {
        eventName: "workspace",
        workspace: workspace
      });
      return true;
    },
    menuItemTap: function (inSender, inEvent) {
      this.setContentPanel(inEvent.index);
    },
    moduleItemTap: function (inSender, inEvent) {
      //this.setModule(inEvent.index);
    },
    requery: function (inSender, inEvent) {
      this.fetch();
    },
    scrolled: function (inSender, inEvent) {
      if (inEvent.originator instanceof XV.List === false) { return; }
      var list = inEvent.originator,
        max = list.getScrollBounds().maxTop - list.rowHeight * FETCH_TRIGGER,
        options = {};
      if (list.getIsMore() && list.getScrollPosition() > max && !list.getIsFetching()) {
        list.setIsFetching(true);
        options.showMore = true;
        this.fetch(list.name, options);
      }
    },
    setContentPanel: function (index) {
      var module = this.getSelectedModule(),
        panelIndex = module && module.panels ? module.panels[index].index : -1,
        panel = panelIndex > -1 ? this.$.contentPanels.getPanels()[panelIndex] : null,
        label = panel ? panel.getLabel() : "";
      if (!panel) { return; }

      // Select panelMenu
      if (!this.$.panelMenu.isSelected(index)) {
        this.$.panelMenu.select(index);
      }

      // Select list
      if (this.$.contentPanels.getIndex() !== panelIndex) {
        this.$.contentPanels.setIndex(panelIndex);
      }
      this.$.rightLabel.setContent(label);
      if (panel.fetch && !this.fetched[panelIndex]) {
        this.fetch(panelIndex);
      }
    },
    setMenuPanel: function (index) {
      var label = index ? "_back".loc() : "_logout".loc();
      this.$.menuPanels.setIndex(index);
      this.$.menuPanels.getActive().select(0);
      this.setContentPanel(0);
      this.$.backButton.setContent(label);
    },
    setModule: function (index) {
      var module = this.getModules()[index],
        panels = module.panels || [];
      if (module !== this._selectedModule) {
        this._selectedModule = module;
        if (panels.length) {
          this.$.panelMenu.setCount(panels.length);
          this.setMenuPanel(PANEL_MENU);
        }
      }
    },
    setupModuleMenuItem: function (inSender, inEvent) {
      var index = inEvent.index,
        label = this.modules[index].label,
        isSelected = inSender.isSelected(index);
      this.$.moduleItem.setContent(label);
      this.$.moduleItem.addRemoveClass("onyx-selected", isSelected);
      if (isSelected) { this.setModule(index); }
    },
    setupPanelMenuItem: function (inSender, inEvent) {
      var module = this.getSelectedModule(),
        panel;
      panel =  module.panels[inEvent.index].name;
      this.$.listItem.setContent(this.$.contentPanels.$[panel].getLabel());
      this.$.listItem.addRemoveClass("onyx-selected", inSender.isSelected(inEvent.index));
    },
    showHistory: function (inSender, inEvent) {
      var panel = {name: 'history'};
      this.doTogglePullout(panel);
    },
    showParameters: function (inSender, inEvent) {
      var panel = this.$.contentPanels.getActive();
      this.doTogglePullout(panel);
    },
    /**
     * If a model has changed, check the panels of this module to see if we can
     * update the info object in the list.
     * XXX if there are multiple modules alive then all of them will catch
     * XXX the signal, which isn't ideal for performance
     */
    refreshInfoObject: function (inSender, inPayload) {
      // obnoxious massaging. Can't think of an elegant way to do this.
      // salt in wounds: in setup we massage by adding List on the end, but with
      // crm we massage by adding List on the end. This is horrible.
      // XXX not sustainable
      var listBase = XV.util.stripModelNamePrefix(inPayload.recordType).camelize(),
        listName = this.name === "setup" ? listBase + "List" : listBase + "List",
        list = this.$.contentPanels.$[listName];

      /**
       * If the update model is part of a cached collection, refresh the cache
       * XXX This string massaging should be refactored into intelligence within
       * the collection itself.
       * XXX this part will happen for each living module. But it only needs to
       * be refreshed once. Moving away from signals would solve this. Or we
       * could create a dedicated signal event.
       */
      var cacheName;
      if (listBase.charAt(listBase.length - 1) === 'y') {
        cacheName = listBase.substring(0, listBase.length - 1) + 'ies';
      } else {
        cacheName = listBase + 's';
      }
      if (XM[cacheName]) {
        XM[cacheName].fetch();
        // no need to fetch the model itself, but we do want to tell the
        // module that contains the list to refresh that list by setting
        // the fetched property to false

        if (this.fetched[listName]) {
          this.fetched[listName] = false;
        }

        return;
      }

      /**
       * Update the model itself
       */
      if (!list) {
        // we don't have this model on our list. No need to update
        return;
      }
      var model = _.find(list.collection.models, function (model) {
        return model.id === inPayload.id;
      });
      if (!model) {
        // this model isn't in the list. No need to update
        return;
      }
      model.fetch();
    },

    /**
      Logout management. We show the user a warning popup before we log them out.
    */
    warnLogout: function () {
      this.$.logoutPopup.show();
    },
    closeLogoutPopup: function () {
      this.$.logoutPopup.hide();
    },
    logout: function () {
      this.$.logoutPopup.hide();
      XT.session.logout();
    },

    /**
      Manually set one of the icon buttons to be active. Note passing null
      as the parameter will disactivate all icons.
    */
    setActiveIconButton: function (buttonName) {
      var activeIconButton = null;
      if (buttonName === 'search') {
        activeIconButton = this.$.searchIconButton;
      } else if (buttonName === 'history') {
        activeIconButton = this.$.historyIconButton;
      }
      this.$.iconButtonGroup.setActive(activeIconButton);
    }

  });

}());
<|endoftext|>