<commit_before>/**
 * Copyright (c) 2009
 * Jan-Felix Schwarz
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

// Declare the MOVI.widget namespace
MOVI.namespace("widget");

(function() {

	var _SCROLLBOX_CLASS_NAME = "movi-scrollbox",
		_MODELIMG_CLASS_NAME = "movi-modelimg";

	/**
     * Array to keep track of all instances
	 * @property _instances
     * @private
     */
	var _instances = new Array();

    /**
     * A widget to display an Oryx model
     * @namespace MOVI.widget
     * @class MOVI.widget.ModelViewer
     * @extends YAHOO.util.Element
     * @constructor
     * @param {HTMLElement | String} el The id of the container DIV element that will
 	 * wrap the ModelViewer, or a reference to a DIV element. The DIV element must
	 * exist in the document.
     */
    MOVI.widget.ModelViewer = function(el) {

		// register the new instance
		this._index = _instances.length;
		_instances[this._index] = this;

    	MOVI.widget.ModelViewer.superclass.constructor.call(this, el);

		this.set("innerHTML", "");

	  	// create scrollbox element
		this._scrollbox = new YAHOO.util.Element(document.createElement("div"));
		this._scrollbox.set("className", _SCROLLBOX_CLASS_NAME);
		this._scrollbox.set("id", _SCROLLBOX_CLASS_NAME + this._index);
		this._scrollbox.set("innerHTML", "");
		this.appendChild(this._scrollbox);

		this._image = new YAHOO.util.Element(_MODELIMG_CLASS_NAME + this._index);
    };

	/**
     * Returns the ModelViewer instance with the specified id.
     * @method getInstance
     * @param {Integer | String} id The id of the ModelViewer instance,
	 * or the id of the instance's host DIV element.
     * @return {MOVI.widget.ModelViewer} A ModelViewer instance
     */
	MOVI.widget.ModelViewer.getInstance = function(id) {
		if(YAHOO.lang.isNumber(id)) {
			return _instances[id];
		} else {
			for(key in _instances) {
				if(_instances[key].get("id")==id)
					return _instances[key];
			}
		}
	};

 	MOVI.extend(MOVI.widget.ModelViewer, YAHOO.util.Element, {

		/**
	     * URI of the model to display
	     * @property _modelUri
		 * @type String
		 * @private
	     */
		_modelUri: "",

		/**
	     * Options passed to loadModel method
	     * @property _loadOptions
		 * @type Object
		 * @private
	     */
		_loadOptions: {},

		/**
	     * The canvas container Element realizing scrolling
	     * @property _scrollbox
		 * @type Element
		 * @private
	     */
		_scrollbox: null,

		/**
	     * The image element
	     * @property _image
		 * @type Element
		 * @private
	     */
		_image: null,

		/**
		 * The model's canvas
		 * @property canvas
		 * @type Canvas
		 */
		canvas: null,

		/**
	     * Callback that is executed when the model is finished
		 * loading.
	     * @method _onSuccess
		 * @private
	     */
		_onSuccess: function() {
			MOVI.log("Model loaded successfully.", "info", "modelviewer.js");
			var scope = this._loadOptions.scope || window;
			if(this._loadOptions.onSuccess)
				this._loadOptions.onSuccess.call(scope, this);
		},

		/**
	     * Callback for handling model load failures.
	     * @method _onLoadFailure
		 * @private
	     */
		_onLoadFailure: function() {
			MOVI.log("Could not load model.", "error", "modelviewer.js");
			var scope = this._loadOptions.scope || window;
			if(this._loadOptions.onFailure)
				this._loadOptions.onFailure.call(scope, this);
		},

		/**
	     * Callback for handling model load timeouts.
	     * @method _onLoadTimeout
		 * @private
	     */
		_onLoadTimeout: function() {
			MOVI.log("A timeout occured while trying to load model.", "error", "modelviewer.js");
			var scope = this._loadOptions.scope || window;
			if(this._loadOptions.onTimeout)
				this._loadOptions.onTimeout.call(scope, this);
			else if(this._loadOptions.onFailure)
				this._loadOptions.onFailure.call(scope, this);
		},

		/**
	     * Callback for handling stencil set load failures.
	     * @method _onStencilSetLoadFailure
		 * @private
	     */
		_onStencilSetLoadFailure: function() {
			MOVI.log("Could not load stencil set for model.", "error", "modelviewer.js");
			var scope = this._loadOptions.scope || window;
			if(this._loadOptions.onFailure)
				this._loadOptions.onFailure.call(scope, this);
		},

		/**
	     * Callback for handling stencil set load timeouts.
	     * @method _onStencilSetLoadTimeout
		 * @private
	     */
		_onStencilSetLoadTimeout: function() {
			MOVI.log("A timeout occured while trying to load stencil set for model.", "error", "modelviewer.js");
			var scope = this._loadOptions.scope || window;
			if(this._loadOptions.onTimeout)
				this._loadOptions.onTimeout.call(scope, this);
			else if(this._loadOptions.onFailure)
				this._loadOptions.onFailure.call(scope, this);
		},

		/**
	     * Loads the specified Oryx model in the viewer.
	     * @method loadModel
	     * @param {String} uri The URI of the model (without /self).
		 * For example: 'http://oryx-editor.org/backend/poem/model/1234'
		 * @param {Object} opt Options:
         *

         *
onSuccess

         *

         * Callback to execute when the model is finished loading
         * The callback receives the ModelViewer instance back.
         *

         *
onFailure

         *

         * Callback to execute when the model load operation fails
         * The callback receives the ModelViewer instance back.
         *

		 *
onTimeout

         *

         * Callback to execute when a timeout occurs. If not set
		 * the onFailure callback will be executed on timeout.
         * The callback receives the ModelViewer instance back.
         *

 		 *
scope

		 *
The execution scope for the callbacks.

 	 	 *
timeout

         *

         * Number of milliseconds to wait before aborting the loading
		 * of the model and executing the onFailure callback.
		 * The default value is 15000 (15 seconds).
         *

		 *

		 * @throws Exception, if passed URI is not valid
	     */
		loadModel: function(uri, opt) {

			if(!YAHOO.lang.isString(uri)) {
				throw new URIError("No valid URI passed to loadModel." , "modelviewer.js");
				this._onLoadFailure();
			}

			this._modelUri = uri;

			this._loadOptions = opt || {};
			if(!this._loadOptions.timeout)
				this._loadOptions.timeout = 15000; // default timeout

			var jsonp = encodeURIComponent(
				"MOVI.widget.ModelViewer.getInstance(" +
				this._index + ").loadModelCallback");
			var url = uri + "/json?jsonp=" + jsonp;

			var transactionObj = YAHOO.util.Get.script(url, {
				onFailure: this._onLoadFailure,
				onTimeout: this._onLoadTimeout,
				timeout  : this._loadOptions.timeout,
				scope    : this
			});

			this._loadImage(uri);
		},

		/**
	     * Load the PNG image representation of the model.
	     * @method _loadImage
		 * @param {String} uri The URI of the model (without /self).
		 * @private
	     */
		_loadImage: function(uri) {
			// append timestamp to allow reloads of the image
			this._image.set("src", uri + "/png?" + (new Date()).getTime());
		},

		/**
	     * JSONP callback to load model data from the JSON web service
	     * @method loadModelCallback
	     * @param jsonObj The delivered JSON Object
	     */
		loadModelCallback: function(jsonObj) {

			if(this.canvas!=null)
				this._scrollbox.removeChild(this.canvas);

			this.canvas = jsonObj;

			if(!this.canvas.stencilset) {
				MOVI.log("Could not find stencilset definition for model.", "error", "canvas.js");
				this._onFailure();
			}

			var jsonp = encodeURIComponent(
				"MOVI.widget.ModelViewer.getInstance(" +
				this._index + ").loadStencilSetCallback");
			var i = this.canvas.stencilset.url.indexOf("/stencilsets/");
			var url = this.canvas.stencilset.url.substring(0, i) +
				"/jsonp?resource=" + encodeURIComponent(this.canvas.stencilset.url.substring(i+13)) +
				"&jsonp=" + jsonp;

			var transactionObj = YAHOO.util.Get.script(url, {
				onFailure: this._onStencilSetLoadFailure,
				onTimeout: this._onStencilSetLoadTimeout,
				data	 : this._loadOptions,
				timeout  : this._loadOptions.timeout,
				scope    : this
			});

		},

		/**
	     * JSONP callback to load stencilset data from the JSON web service
	     * @method loadStencilSetCallback
	     * @param jsonObj The delivered JSON Object
	     */
		loadStencilSetCallback: function(jsonObj) {
			var prefix = "movi_" + this._index + "-";
			try {
				this.canvas.stencilset = new MOVI.stencilset.Stencilset(jsonObj);
				this.canvas = new MOVI.model.Canvas(this, this.canvas, prefix);
			} catch(e) {
				MOVI.log("A " + e.name + " occured while trying to load model: " +
							e.message, "error", "modelviewer.js");
				this._onLoadFailure();
				return;
			}
			this._scrollbox.appendChild(this.canvas);
			this._onSuccess();
		},

		/**
		 * Returns the width of the model image element in pixels
		 * @method getImgWidth
		 * @return {Integer} The image width
		 */
		getImgWidth: function() {
			return this._image.get("width");
		},

		/**
		 * Returns the height of the model image element in pixels
		 * @method getImgHeight
		 * @return {Integer} The image height
		 */
		getImgHeight: function() {
			return this._image.get("height");
		},

		/**
		 * Returns the absolute URI of the displayed model
		 * @method getImgHeight
		 * @return {String} The models absolute URI
		 */
		getModelUri: function() {
			return this._modelUri;
		},

		/**
		 * Returns the canvas container Element realizing scrolling
		 * @method getScrollboxEx
		 * @return {Element} The scrollbox Element
		 */
		getScrollboxEl: function() {
			return this._scrollbox;
		},

		/**
		 * Scrolls to the shape, i.e. centers the (canvas around the) shape
		 * within the scrollbox.
		 * @method scrollToShape
		 * @param {Shape} shape the shape or its resource id
		 * @return {Element} The shape element / null if it does not exist
		 * @throws Error if scrollbox dimensions cannot be calculated
		 */
		scrollToShape: function(shape) {
			if ("string" == typeof(shape)) {
				shape = this.canvas.getShape(shape);
				if (null == shape) {
					return null;
				}
			}

			if (null != (h = this.getScrollboxEl().getStyle("height").match(/(\d+)px/)) &&
			    null != (w = this.getScrollboxEl().getStyle("width").match(/(\d+)px/)))
			{
				var bounds = shape.getAbsBounds();

				var origin = {
					x: bounds.upperLeft.x + (bounds.lowerRight.x - bounds.upperLeft.x)/2,
					y: bounds.upperLeft.y + (bounds.lowerRight.y - bounds.upperLeft.y)/2
				}

				var target = {
					x: parseInt(origin.x - parseInt(w[1])/2 ),
					y: parseInt(origin.y - parseInt(h[1])/2 )
				}

				this.getScrollboxEl().set("scrollTop", target.y);
				this.getScrollboxEl().set("scrollLeft", target.x);
			}
			else {
				throw new Error("Unable to calculate scrollbox dimensions", "modelviewer.js");
			}

			return shape;
		},

		/**
		 * Scrolls to the specified position, centers the (canvas around the) position
		 * within the scrollbox.
		 * @method centerScrollTo
		 * @param {Integer} x The center's x position
		 * @param {Integer} y The center's y position
		 * @throws Error if scrollbox dimensions cannot be calculated
		 */
		centerScrollTo: function(x, y) {
			if(!YAHOO.lang.isNumber(x) || !YAHOO.lang.isNumber(y)) {
				throw new TypeError("The coordinates passed to centerScrollTo(x, y) have to be of type Integer.",
									"modelviewer.js");
			}

			var left = Math.round(x - parseInt(this.getScrollboxEl().getStyle("width"), 10)/2),
				top  = Math.round(y - parseInt(this.getScrollboxEl().getStyle("height"), 10)/2);
			this.getScrollboxEl().set("scrollLeft", left);
			this.getScrollboxEl().set("scrollTop", top);
		}

	});

})();<commit_msg>MOVI API: fix for relative stencil set urls in model json<commit_after>/**
 * Copyright (c) 2009
 * Jan-Felix Schwarz
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

// Declare the MOVI.widget namespace
MOVI.namespace("widget");

(function() {

	var _SCROLLBOX_CLASS_NAME = "movi-scrollbox",
		_MODELIMG_CLASS_NAME = "movi-modelimg";

	/**
     * Array to keep track of all instances
	 * @property _instances
     * @private
     */
	var _instances = new Array();

    /**
     * A widget to display an Oryx model
     * @namespace MOVI.widget
     * @class MOVI.widget.ModelViewer
     * @extends YAHOO.util.Element
     * @constructor
     * @param {HTMLElement | String} el The id of the container DIV element that will
 	 * wrap the ModelViewer, or a reference to a DIV element. The DIV element must
	 * exist in the document.
     */
    MOVI.widget.ModelViewer = function(el) {

		// register the new instance
		this._index = _instances.length;
		_instances[this._index] = this;

    	MOVI.widget.ModelViewer.superclass.constructor.call(this, el);

		this.set("innerHTML", "");

	  	// create scrollbox element
		this._scrollbox = new YAHOO.util.Element(document.createElement("div"));
		this._scrollbox.set("className", _SCROLLBOX_CLASS_NAME);
		this._scrollbox.set("id", _SCROLLBOX_CLASS_NAME + this._index);
		this._scrollbox.set("innerHTML", "");
		this.appendChild(this._scrollbox);

		this._image = new YAHOO.util.Element(_MODELIMG_CLASS_NAME + this._index);
    };

	/**
     * Returns the ModelViewer instance with the specified id.
     * @method getInstance
     * @param {Integer | String} id The id of the ModelViewer instance,
	 * or the id of the instance's host DIV element.
     * @return {MOVI.widget.ModelViewer} A ModelViewer instance
     */
	MOVI.widget.ModelViewer.getInstance = function(id) {
		if(YAHOO.lang.isNumber(id)) {
			return _instances[id];
		} else {
			for(key in _instances) {
				if(_instances[key].get("id")==id)
					return _instances[key];
			}
		}
	};

 	MOVI.extend(MOVI.widget.ModelViewer, YAHOO.util.Element, {

		/**
	     * URI of the model to display
	     * @property _modelUri
		 * @type String
		 * @private
	     */
		_modelUri: "",

		/**
	     * Options passed to loadModel method
	     * @property _loadOptions
		 * @type Object
		 * @private
	     */
		_loadOptions: {},

		/**
	     * The canvas container Element realizing scrolling
	     * @property _scrollbox
		 * @type Element
		 * @private
	     */
		_scrollbox: null,

		/**
	     * The image element
	     * @property _image
		 * @type Element
		 * @private
	     */
		_image: null,

		/**
		 * The model's canvas
		 * @property canvas
		 * @type Canvas
		 */
		canvas: null,

		/**
	     * Callback that is executed when the model is finished
		 * loading.
	     * @method _onSuccess
		 * @private
	     */
		_onSuccess: function() {
			MOVI.log("Model loaded successfully.", "info", "modelviewer.js");
			var scope = this._loadOptions.scope || window;
			if(this._loadOptions.onSuccess)
				this._loadOptions.onSuccess.call(scope, this);
		},

		/**
	     * Callback for handling model load failures.
	     * @method _onLoadFailure
		 * @private
	     */
		_onLoadFailure: function() {
			MOVI.log("Could not load model.", "error", "modelviewer.js");
			var scope = this._loadOptions.scope || window;
			if(this._loadOptions.onFailure)
				this._loadOptions.onFailure.call(scope, this);
		},

		/**
	     * Callback for handling model load timeouts.
	     * @method _onLoadTimeout
		 * @private
	     */
		_onLoadTimeout: function() {
			MOVI.log("A timeout occured while trying to load model.", "error", "modelviewer.js");
			var scope = this._loadOptions.scope || window;
			if(this._loadOptions.onTimeout)
				this._loadOptions.onTimeout.call(scope, this);
			else if(this._loadOptions.onFailure)
				this._loadOptions.onFailure.call(scope, this);
		},

		/**
	     * Callback for handling stencil set load failures.
	     * @method _onStencilSetLoadFailure
		 * @private
	     */
		_onStencilSetLoadFailure: function() {
			MOVI.log("Could not load stencil set for model.", "error", "modelviewer.js");
			var scope = this._loadOptions.scope || window;
			if(this._loadOptions.onFailure)
				this._loadOptions.onFailure.call(scope, this);
		},

		/**
	     * Callback for handling stencil set load timeouts.
	     * @method _onStencilSetLoadTimeout
		 * @private
	     */
		_onStencilSetLoadTimeout: function() {
			MOVI.log("A timeout occured while trying to load stencil set for model.", "error", "modelviewer.js");
			var scope = this._loadOptions.scope || window;
			if(this._loadOptions.onTimeout)
				this._loadOptions.onTimeout.call(scope, this);
			else if(this._loadOptions.onFailure)
				this._loadOptions.onFailure.call(scope, this);
		},

		/**
	     * Loads the specified Oryx model in the viewer.
	     * @method loadModel
	     * @param {String} uri The URI of the model (without /self).
		 * For example: 'http://oryx-editor.org/backend/poem/model/1234'
		 * @param {Object} opt Options:
         *

         *
onSuccess

         *

         * Callback to execute when the model is finished loading
         * The callback receives the ModelViewer instance back.
         *

         *
onFailure

         *

         * Callback to execute when the model load operation fails
         * The callback receives the ModelViewer instance back.
         *

		 *
onTimeout

         *

         * Callback to execute when a timeout occurs. If not set
		 * the onFailure callback will be executed on timeout.
         * The callback receives the ModelViewer instance back.
         *

 		 *
scope

		 *
The execution scope for the callbacks.

 	 	 *
timeout

         *

         * Number of milliseconds to wait before aborting the loading
		 * of the model and executing the onFailure callback.
		 * The default value is 15000 (15 seconds).
         *

		 *

		 * @throws Exception, if passed URI is not valid
	     */
		loadModel: function(uri, opt) {

			if(!YAHOO.lang.isString(uri)) {
				throw new URIError("No valid URI passed to loadModel." , "modelviewer.js");
				this._onLoadFailure();
			}

			this._modelUri = uri;

			this._loadOptions = opt || {};
			if(!this._loadOptions.timeout)
				this._loadOptions.timeout = 15000; // default timeout

			var jsonp = encodeURIComponent(
				"MOVI.widget.ModelViewer.getInstance(" +
				this._index + ").loadModelCallback");
			var url = uri + "/json?jsonp=" + jsonp;

			var transactionObj = YAHOO.util.Get.script(url, {
				onFailure: this._onLoadFailure,
				onTimeout: this._onLoadTimeout,
				timeout  : this._loadOptions.timeout,
				scope    : this
			});

			this._loadImage(uri);
		},

		/**
	     * Load the PNG image representation of the model.
	     * @method _loadImage
		 * @param {String} uri The URI of the model (without /self).
		 * @private
	     */
		_loadImage: function(uri) {
			// append timestamp to allow reloads of the image
			this._image.set("src", uri + "/png?" + (new Date()).getTime());
		},

		/**
	     * JSONP callback to load model data from the JSON web service
	     * @method loadModelCallback
	     * @param jsonObj The delivered JSON Object
	     */
		loadModelCallback: function(jsonObj) {

			if(this.canvas!=null)
				this._scrollbox.removeChild(this.canvas);

			this.canvas = jsonObj;

			if(!this.canvas.stencilset) {
				MOVI.log("Could not find stencilset definition for model.", "error", "canvas.js");
				this._onFailure();
			}

			if(this.canvas.stencilset.url.substring(0, 7)!="http://") {
				// relative stencilset url, make absolute
				// guess server base url (model url substring until first slash)
				var index = this._modelUri.substring(7).indexOf("/");
				this.canvas.stencilset.url = this._modelUri.substring(0, 7+index) + this.canvas.stencilset.url;
			}

			var jsonp = encodeURIComponent(
				"MOVI.widget.ModelViewer.getInstance(" +
				this._index + ").loadStencilSetCallback");
			var i = this.canvas.stencilset.url.indexOf("/stencilsets/");

			var url = this.canvas.stencilset.url.substring(0, i) +
				"/jsonp?resource=" + encodeURIComponent(this.canvas.stencilset.url.substring(i+13)) +
				"&jsonp=" + jsonp;

			var transactionObj = YAHOO.util.Get.script(url, {
				onFailure: this._onStencilSetLoadFailure,
				onTimeout: this._onStencilSetLoadTimeout,
				data	 : this._loadOptions,
				timeout  : this._loadOptions.timeout,
				scope    : this
			});

		},

		/**
	     * JSONP callback to load stencilset data from the JSON web service
	     * @method loadStencilSetCallback
	     * @param jsonObj The delivered JSON Object
	     */
		loadStencilSetCallback: function(jsonObj) {
			var prefix = "movi_" + this._index + "-";
			try {
				this.canvas.stencilset = new MOVI.stencilset.Stencilset(jsonObj);
				this.canvas = new MOVI.model.Canvas(this, this.canvas, prefix);
			} catch(e) {
				MOVI.log("A " + e.name + " occured while trying to load model: " +
							e.message, "error", "modelviewer.js");
				this._onLoadFailure();
				return;
			}
			this._scrollbox.appendChild(this.canvas);
			this._onSuccess();
		},

		/**
		 * Returns the width of the model image element in pixels
		 * @method getImgWidth
		 * @return {Integer} The image width
		 */
		getImgWidth: function() {
			return this._image.get("width");
		},

		/**
		 * Returns the height of the model image element in pixels
		 * @method getImgHeight
		 * @return {Integer} The image height
		 */
		getImgHeight: function() {
			return this._image.get("height");
		},

		/**
		 * Returns the absolute URI of the displayed model
		 * @method getImgHeight
		 * @return {String} The models absolute URI
		 */
		getModelUri: function() {
			return this._modelUri;
		},

		/**
		 * Returns the canvas container Element realizing scrolling
		 * @method getScrollboxEx
		 * @return {Element} The scrollbox Element
		 */
		getScrollboxEl: function() {
			return this._scrollbox;
		},

		/**
		 * Scrolls to the shape, i.e. centers the (canvas around the) shape
		 * within the scrollbox.
		 * @method scrollToShape
		 * @param {Shape} shape the shape or its resource id
		 * @return {Element} The shape element / null if it does not exist
		 * @throws Error if scrollbox dimensions cannot be calculated
		 */
		scrollToShape: function(shape) {
			if ("string" == typeof(shape)) {
				shape = this.canvas.getShape(shape);
				if (null == shape) {
					return null;
				}
			}

			if (null != (h = this.getScrollboxEl().getStyle("height").match(/(\d+)px/)) &&
			    null != (w = this.getScrollboxEl().getStyle("width").match(/(\d+)px/)))
			{
				var bounds = shape.getAbsBounds();

				var origin = {
					x: bounds.upperLeft.x + (bounds.lowerRight.x - bounds.upperLeft.x)/2,
					y: bounds.upperLeft.y + (bounds.lowerRight.y - bounds.upperLeft.y)/2
				}

				var target = {
					x: parseInt(origin.x - parseInt(w[1])/2 ),
					y: parseInt(origin.y - parseInt(h[1])/2 )
				}

				this.getScrollboxEl().set("scrollTop", target.y);
				this.getScrollboxEl().set("scrollLeft", target.x);
			}
			else {
				throw new Error("Unable to calculate scrollbox dimensions", "modelviewer.js");
			}

			return shape;
		},

		/**
		 * Scrolls to the specified position, centers the (canvas around the) position
		 * within the scrollbox.
		 * @method centerScrollTo
		 * @param {Integer} x The center's x position
		 * @param {Integer} y The center's y position
		 * @throws Error if scrollbox dimensions cannot be calculated
		 */
		centerScrollTo: function(x, y) {
			if(!YAHOO.lang.isNumber(x) || !YAHOO.lang.isNumber(y)) {
				throw new TypeError("The coordinates passed to centerScrollTo(x, y) have to be of type Integer.",
									"modelviewer.js");
			}

			var left = Math.round(x - parseInt(this.getScrollboxEl().getStyle("width"), 10)/2),
				top  = Math.round(y - parseInt(this.getScrollboxEl().getStyle("height"), 10)/2);
			this.getScrollboxEl().set("scrollLeft", left);
			this.getScrollboxEl().set("scrollTop", top);
		}

	});

})();<|endoftext|>


<commit_before>var http = require('http');

var content = '';

function getUsers(next) {
    console.log("----- NAMES -----");
    http.get('http://country.io/names.json', function (res) {
        console.log(res.statusCode);
        console.log(res.headers);
        console.log("----- end header -----");
        res.setEncoding('utf8');
        res.on('data', function(chunk) {
            console.log(chunk);
            console.log("----- end body -----");
            content += chunk;
        }).on('end', function() {
            console.log("----- end data -----");
            next();
        });
    }).on('error', function(err) {
        done(err,null);
    });
}

function getAccounts(next) {
    console.log("----- ISO3 -----");
    http.get('http://country.io/iso3.json', function (res) {
        console.log(res.statusCode);
        console.log(res.headers);
        console.log("----- end header -----");
        res.setEncoding('utf8');
        res.on('data', function(chunk) {
            console.log(chunk);
            console.log("----- end body -----");
            content += chunk;
        }).on('end', function() {
            console.log("----- end data -----");
            next();
        });
    }).on('error', function(err) {
        done(err,null);
    });
}

function getEntitlements(next) {
    console.log("----- CURRENCY -----");
    http.get('http://country.io/currency.json', function (res) {
        console.log(res.statusCode);
        console.log(res.headers);
        console.log("----- end header -----");
        res.setEncoding('utf8');
        res.on('data', function(chunk) {
            console.log(chunk);
            console.log("----- end body -----");
            content += chunk;
        }).on('end', function() {
            console.log("----- end data -----");
            next();
        });
    }).on('error', function(err) {
        done(err,null);
    });
}

function done (err, res) {
    console.log("----- done "+err +" -----");
    if (err) { throw err; }
    console.log("-----");
    console.log(res);
    console.log("-----");
}

function flow(steps,done) {
    function factory() {
        var used;
        return function next() {
            console.log("NEXT "+steps.length);
            if (used) {
                return;
            }
            used = true;
            var step = steps.shift();
            if (step) {
                var args = Array.prototype.slice.call(arguments);
                var err = args.shift();
                if (err) {
                    done(err, null);
                    return;
                }
                args.push(factory());
                step.apply(null, args);
            } else {
                done.apply(null, arguments);
            }
        };
    }
    var start = factory();
    start();
}

flow([getUsers,getAccounts,getEntitlements],done);
console.log("EOF");
<commit_msg>comments<commit_after>var http = require('http');

var content = '';

function getUsers(next) {
    console.log("----- NAMES -----");
    http.get('http://country.io/names.json', function (res) {
        console.log(res.statusCode);
        console.log(res.headers);
        console.log("----- end header -----");
        res.setEncoding('utf8');
        res.on('data', function(chunk) {
            console.log(chunk);
            console.log("----- end body -----");
            content += chunk;
        }).on('end', function() {
            console.log("----- end data -----");
            next();
        });
    }).on('error', function(err) {
        done(err,null);
    });
}

function getAccounts(next) {
    console.log("----- ISO3 -----");
    http.get('http://country.io/iso3.json', function (res) {
        console.log(res.statusCode);
        console.log(res.headers);
        console.log("----- end header -----");
        res.setEncoding('utf8');
        res.on('data', function(chunk) {
            console.log(chunk);
            console.log("----- end body -----");
            content += chunk;
        }).on('end', function() {
            console.log("----- end data -----");
            next();
        });
    }).on('error', function(err) {
        done(err,null);
    });
}

function getEntitlements(next) {
    console.log("----- CURRENCY -----");
    http.get('http://country.io/currency.json', function (res) {
        console.log(res.statusCode);
        console.log(res.headers);
        console.log("----- end header -----");
        res.setEncoding('utf8');
        res.on('data', function(chunk) {
            console.log(chunk);
            console.log("----- end body -----");
            content += chunk;
        }).on('end', function() {
            console.log("----- end data -----");
            next();
        });
    }).on('error', function(err) {
        done(err,null);
    });
}

function done (err, res) {
    console.log("----- done "+err +" -----");
    if (err) { throw err; }
    console.log("-----");
    console.log(res);
    console.log("-----");
}

function flow(steps,done) {
    function next() {
        // stores whether a callback has already been used
        var used;
        // uses a factory so that 'used' is local to each step
        return function factory() {
            console.log("NEXT "+steps.length);
            // after one use, next becomes a no-op
            if (used) {
                return;
            }
            used = true;
            // gets the next step and removes it from the list
            var step = steps.shift();
            // are there more steps?
            if (step) {
                // casts arguments to an array
                var args = Array.prototype.slice.call(arguments);
                // gets the error argument, remove it from the arguments
                var err = args.shift();
                // short circuits if error was provided
                if (err) {
                    done(err, null);
                    return;
                }
                // adds a completion callback to the arguments
                args.push(next());
                // invokes the step passing the needed arguments
                step.apply(null, args);
            } else {
                // call done, no need to manipulate arguments
                done.apply(null, arguments);
            }
        };
    }
    // creates the first step function
    var start = next();
    // executes the step, doesn't provide additional arguments
    start();
}

flow([getUsers,getAccounts,getEntitlements],done);
console.log("EOF");
<|endoftext|>


<commit_before>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod
 * http://www.melonjs.org
 *
 * A QuadTree implementation in JavaScript, a 2d spatial subdivision algorithm.
 * Based on the QuadTree Library by Timo Hausmann and released under the MIT license
 * https://github.com/timohausmann/quadtree-js/
**/

(function () {

    /**
     * a pool of `QuadTree` objects
     */
    var QT_ARRAY = [];

    /**
     * will pop a quadtree object from the array
     * or create a new one if the array is empty
     */
    var QT_ARRAY_POP = function (bounds, max_objects, max_levels, level) {
        if (QT_ARRAY.length > 0) {
            var _qt =  QT_ARRAY.pop();
            _qt.bounds = bounds;
            _qt.max_objects = max_objects || 4;
            _qt.max_levels  = max_levels || 4;
            _qt.level = level || 0;
            return _qt;
        } else {
            return new me.QuadTree(bounds, max_objects, max_levels, level);
        }
    };

    /**
     * Push back a quadtree back into the array
     */
    var QT_ARRAY_PUSH = function (qt) {
        QT_ARRAY.push(qt);
    };

    /**
     * Quadtree Constructor

     * note: the global quadtree instance is available through `me.collision.quadTree`
     * @class
     * @name QuadTree
     * @extends Object
     * @memberOf me
     * @constructor
     * @see me.collision.quadTree
     * @param {me.Rect} bounds bounds of the node
     * @param {Number} [max_objects=4] max objects a node can hold before splitting into 4 subnodes
     * @param {Number} [max_levels=4] total max levels inside root Quadtree
     * @param {Number} [level] deepth level, required for subnodes
     */
    function Quadtree(bounds, max_objects, max_levels, level) {
        this.max_objects = max_objects || 4;
        this.max_levels  = max_levels || 4;

        this.level = level || 0;
        this.bounds = bounds;

        this.objects = [];
        this.nodes = [];
    }

    /*
     * Split the node into 4 subnodes
     */
    Quadtree.prototype.split = function () {

        var nextLevel = this.level + 1,
            subWidth  = ~~(0.5 + this.bounds.width / 2),
            subHeight = ~~(0.5 + this.bounds.height / 2),
            x = ~~(0.5 + this.bounds.pos.x),
            y = ~~(0.5 + this.bounds.pos.y);

         //top right node
        this.nodes[0] = QT_ARRAY_POP({
            pos : {
                x : x + subWidth,
                y : y
            },
            width : subWidth,
            height : subHeight
        }, this.max_objects, this.max_levels, nextLevel);

        //top left node
        this.nodes[1] = QT_ARRAY_POP({
            pos : {
                x : x,
                y : y
            },
            width : subWidth,
            height : subHeight
        }, this.max_objects, this.max_levels, nextLevel);

        //bottom left node
        this.nodes[2] = QT_ARRAY_POP({
            pos : {
                x : x,
                y : y + subHeight
            },
            width : subWidth,
            height : subHeight
        }, this.max_objects, this.max_levels, nextLevel);

        //bottom right node
        this.nodes[3] = QT_ARRAY_POP({
            pos : {
                x : x + subWidth,
                y : y + subHeight
            },
            width : subWidth,
            height : subHeight
        }, this.max_objects, this.max_levels, nextLevel);
    };

    /*
     * Determine which node the object belongs to
     * @param {me.Rect} rect bounds of the area to be checked
     * @return Integer index of the subnode (0-3), or -1 if rect cannot completely fit within a subnode and is part of the parent node
     */
    Quadtree.prototype.getIndex = function (rect) {

        var index = -1,
            rx = rect.pos.x,
            ry = rect.pos.y,
            rw = rect.width,
            rh = rect.height,
            verticalMidpoint = this.bounds.pos.x + (this.bounds.width / 2),
            horizontalMidpoint = this.bounds.pos.y + (this.bounds.height / 2),
            //rect can completely fit within the top quadrants
            topQuadrant = (ry < horizontalMidpoint && ry + rh < horizontalMidpoint),
            //rect can completely fit within the bottom quadrants
            bottomQuadrant = (ry > horizontalMidpoint);

        //rect can completely fit within the left quadrants
        if (rx < verticalMidpoint && rx + rw < verticalMidpoint) {
            if (topQuadrant) {
                index = 1;
            } else if (bottomQuadrant) {
                index = 2;
            }
        } else if (rx > verticalMidpoint) {
            //rect can completely fit within the right quadrants
            if (topQuadrant) {
                index = 0;
            } else if (bottomQuadrant) {
                index = 3;
            }
        }

        return index;
    };

    /**
     * Insert the given object container into the node.
     * @name insertContainer
     * @memberOf me.QuadTree
     * @function
     * @param {me.Container} container group of objects to be added
     */
    Quadtree.prototype.insertContainer = function (container) {

        for (var i = container.children.length, child; i--, (child = container.children[i]);) {
            if (child instanceof me.Container) {
                // recursivly insert childs
                this.insertContainer(child);
            } else {
                // only insert object with a bounding box
                if (typeof (child.getBounds) === "function") {
                    this.insert(child);
                }
            }
        }
    };

    /**
     * Insert the given object into the node. If the node
     * exceeds the capacity, it will split and add all
     * objects to their corresponding subnodes.
     * @name insert
     * @memberOf me.QuadTree
     * @function
     * @param {Object} item object to be added
     */
    Quadtree.prototype.insert = function (item) {

        var index = -1;

        //if we have subnodes ...
        if (this.nodes.length > 0) {
            index = this.getIndex(item.getBounds());

            if (index !== -1) {
                this.nodes[index].insert(item);
                return;
            }
        }

        this.objects.push(item);

        if (this.objects.length > this.max_objects && this.level < this.max_levels) {

            //split if we don't already have subnodes
            if (this.nodes.length === 0) {
                this.split();
            }

            var i = 0;

            //add all objects to there corresponding subnodes
            while (i < this.objects.length) {

                index = this.getIndex(this.objects[i].getBounds());

                if (index !== -1) {
                    this.nodes[index].insert(this.objects.splice(i, 1)[0]);
                } else {
                    i = i + 1;
                }
            }
        }
    };

    /**
     * Return all objects that could collide with the given object
     * @name retrieve
     * @memberOf me.QuadTree
     * @function
     * @param {Object} object object to be checked against
     * @param {Object} [function] a sorting function for the returned array
     * @return {Object[]} array with all detected objects
     */
    Quadtree.prototype.retrieve = function (item, fn) {

        var returnObjects = this.objects;

        //if we have subnodes ...
        if (this.nodes.length > 0) {

            var index = this.getIndex(item.getBounds());

            //if rect fits into a subnode ..
            if (index !== -1) {
                returnObjects = returnObjects.concat(this.nodes[index].retrieve(item));
            } else {
                 //if rect does not fit into a subnode, check it against all subnodes
                for (var i = 0; i < this.nodes.length; i = i + 1) {
                    returnObjects = returnObjects.concat(this.nodes[i].retrieve(item));
                }
            }
        }

        if (typeof(fn) === "function") {
            returnObjects.sort(fn);
        }

        return returnObjects;
    };

    /**
     * clear the quadtree
     * @name clear
     * @memberOf me.QuadTree
     * @function
     */
    Quadtree.prototype.clear = function (bounds) {

        this.objects.length = 0;

        for (var i = 0; i < this.nodes.length; i = i + 1) {
            this.nodes[i].clear();
            // recycle the quadTree object
            QT_ARRAY_PUSH(this.nodes[i]);
        }
        // empty the array
        this.nodes = [];

        // resize the root bounds if required
        if (typeof bounds !== "undefined") {
            this.bounds.setShape(bounds.pos.x, bounds.pos.y, bounds.width, bounds.height);
        }
    };

    //make Quadtree available in the me namespace
    me.QuadTree = Quadtree;

})();
<commit_msg>[#611] also add child `me.Container` to the quadtree<commit_after>/*
 * MelonJS Game Engine
 * Copyright (C) 2011 - 2016, Olivier Biot, Jason Oster, Aaron McLeod
 * http://www.melonjs.org
 *
 * A QuadTree implementation in JavaScript, a 2d spatial subdivision algorithm.
 * Based on the QuadTree Library by Timo Hausmann and released under the MIT license
 * https://github.com/timohausmann/quadtree-js/
**/

(function () {

    /**
     * a pool of `QuadTree` objects
     */
    var QT_ARRAY = [];

    /**
     * will pop a quadtree object from the array
     * or create a new one if the array is empty
     */
    var QT_ARRAY_POP = function (bounds, max_objects, max_levels, level) {
        if (QT_ARRAY.length > 0) {
            var _qt =  QT_ARRAY.pop();
            _qt.bounds = bounds;
            _qt.max_objects = max_objects || 4;
            _qt.max_levels  = max_levels || 4;
            _qt.level = level || 0;
            return _qt;
        } else {
            return new me.QuadTree(bounds, max_objects, max_levels, level);
        }
    };

    /**
     * Push back a quadtree back into the array
     */
    var QT_ARRAY_PUSH = function (qt) {
        QT_ARRAY.push(qt);
    };

    /**
     * Quadtree Constructor

     * note: the global quadtree instance is available through `me.collision.quadTree`
     * @class
     * @name QuadTree
     * @extends Object
     * @memberOf me
     * @constructor
     * @see me.collision.quadTree
     * @param {me.Rect} bounds bounds of the node
     * @param {Number} [max_objects=4] max objects a node can hold before splitting into 4 subnodes
     * @param {Number} [max_levels=4] total max levels inside root Quadtree
     * @param {Number} [level] deepth level, required for subnodes
     */
    function Quadtree(bounds, max_objects, max_levels, level) {
        this.max_objects = max_objects || 4;
        this.max_levels  = max_levels || 4;

        this.level = level || 0;
        this.bounds = bounds;

        this.objects = [];
        this.nodes = [];
    }

    /*
     * Split the node into 4 subnodes
     */
    Quadtree.prototype.split = function () {

        var nextLevel = this.level + 1,
            subWidth  = ~~(0.5 + this.bounds.width / 2),
            subHeight = ~~(0.5 + this.bounds.height / 2),
            x = ~~(0.5 + this.bounds.pos.x),
            y = ~~(0.5 + this.bounds.pos.y);

         //top right node
        this.nodes[0] = QT_ARRAY_POP({
            pos : {
                x : x + subWidth,
                y : y
            },
            width : subWidth,
            height : subHeight
        }, this.max_objects, this.max_levels, nextLevel);

        //top left node
        this.nodes[1] = QT_ARRAY_POP({
            pos : {
                x : x,
                y : y
            },
            width : subWidth,
            height : subHeight
        }, this.max_objects, this.max_levels, nextLevel);

        //bottom left node
        this.nodes[2] = QT_ARRAY_POP({
            pos : {
                x : x,
                y : y + subHeight
            },
            width : subWidth,
            height : subHeight
        }, this.max_objects, this.max_levels, nextLevel);

        //bottom right node
        this.nodes[3] = QT_ARRAY_POP({
            pos : {
                x : x + subWidth,
                y : y + subHeight
            },
            width : subWidth,
            height : subHeight
        }, this.max_objects, this.max_levels, nextLevel);
    };

    /*
     * Determine which node the object belongs to
     * @param {me.Rect} rect bounds of the area to be checked
     * @return Integer index of the subnode (0-3), or -1 if rect cannot completely fit within a subnode and is part of the parent node
     */
    Quadtree.prototype.getIndex = function (rect) {

        var index = -1,
            rx = rect.pos.x,
            ry = rect.pos.y,
            rw = rect.width,
            rh = rect.height,
            verticalMidpoint = this.bounds.pos.x + (this.bounds.width / 2),
            horizontalMidpoint = this.bounds.pos.y + (this.bounds.height / 2),
            //rect can completely fit within the top quadrants
            topQuadrant = (ry < horizontalMidpoint && ry + rh < horizontalMidpoint),
            //rect can completely fit within the bottom quadrants
            bottomQuadrant = (ry > horizontalMidpoint);

        //rect can completely fit within the left quadrants
        if (rx < verticalMidpoint && rx + rw < verticalMidpoint) {
            if (topQuadrant) {
                index = 1;
            } else if (bottomQuadrant) {
                index = 2;
            }
        } else if (rx > verticalMidpoint) {
            //rect can completely fit within the right quadrants
            if (topQuadrant) {
                index = 0;
            } else if (bottomQuadrant) {
                index = 3;
            }
        }

        return index;
    };

    /**
     * Insert the given object container into the node.
     * @name insertContainer
     * @memberOf me.QuadTree
     * @function
     * @param {me.Container} container group of objects to be added
     */
    Quadtree.prototype.insertContainer = function (container) {

        for (var i = container.children.length, child; i--, (child = container.children[i]);) {
            if (child instanceof me.Container) {
                if (child.name !== "rootContainer") {
                    this.insert(child);
                }
                // recursivly insert all childs
                this.insertContainer(child);
            } else {
                // only insert object with a bounding box
                if (typeof (child.getBounds) === "function") {
                    this.insert(child);
                }
            }
        }
    };

    /**
     * Insert the given object into the node. If the node
     * exceeds the capacity, it will split and add all
     * objects to their corresponding subnodes.
     * @name insert
     * @memberOf me.QuadTree
     * @function
     * @param {Object} item object to be added
     */
    Quadtree.prototype.insert = function (item) {

        var index = -1;

        //if we have subnodes ...
        if (this.nodes.length > 0) {
            index = this.getIndex(item.getBounds());

            if (index !== -1) {
                this.nodes[index].insert(item);
                return;
            }
        }

        this.objects.push(item);

        if (this.objects.length > this.max_objects && this.level < this.max_levels) {

            //split if we don't already have subnodes
            if (this.nodes.length === 0) {
                this.split();
            }

            var i = 0;

            //add all objects to there corresponding subnodes
            while (i < this.objects.length) {

                index = this.getIndex(this.objects[i].getBounds());

                if (index !== -1) {
                    this.nodes[index].insert(this.objects.splice(i, 1)[0]);
                } else {
                    i = i + 1;
                }
            }
        }
    };

    /**
     * Return all objects that could collide with the given object
     * @name retrieve
     * @memberOf me.QuadTree
     * @function
     * @param {Object} object object to be checked against
     * @param {Object} [function] a sorting function for the returned array
     * @return {Object[]} array with all detected objects
     */
    Quadtree.prototype.retrieve = function (item, fn) {

        var returnObjects = this.objects;

        //if we have subnodes ...
        if (this.nodes.length > 0) {

            var index = this.getIndex(item.getBounds());

            //if rect fits into a subnode ..
            if (index !== -1) {
                returnObjects = returnObjects.concat(this.nodes[index].retrieve(item));
            } else {
                 //if rect does not fit into a subnode, check it against all subnodes
                for (var i = 0; i < this.nodes.length; i = i + 1) {
                    returnObjects = returnObjects.concat(this.nodes[i].retrieve(item));
                }
            }
        }

        if (typeof(fn) === "function") {
            returnObjects.sort(fn);
        }

        return returnObjects;
    };

    /**
     * clear the quadtree
     * @name clear
     * @memberOf me.QuadTree
     * @function
     */
    Quadtree.prototype.clear = function (bounds) {

        this.objects.length = 0;

        for (var i = 0; i < this.nodes.length; i = i + 1) {
            this.nodes[i].clear();
            // recycle the quadTree object
            QT_ARRAY_PUSH(this.nodes[i]);
        }
        // empty the array
        this.nodes = [];

        // resize the root bounds if required
        if (typeof bounds !== "undefined") {
            this.bounds.setShape(bounds.pos.x, bounds.pos.y, bounds.width, bounds.height);
        }
    };

    //make Quadtree available in the me namespace
    me.QuadTree = Quadtree;

})();
<|endoftext|>


<commit_before>
  basis.require('basis.cssom');

 /**
  * @namespace basis.utils.highlight
  */

  var lead = basis.number.lead;
  var repeat = basis.string.repeat;

  var LANG_PARSER = {};
  var PARSER = {
    add: function(lang, fn){
      LANG_PARSER[lang] = fn;
    }
  };

  //
  // default parser
  //

  PARSER.add('text', basis.array);

  //
  // javascript parser
  //

  PARSER.add('js', (function(){
    var keywords =
      'break case catch continue ' +
      'default delete do else false ' +
      'for function if in instanceof ' +
      'new null return super switch ' +
      'this throw true try typeof var while with';

    var keywordRegExp = new RegExp('\\b(' + keywords.split(' ').join('|') + ')\\b', 'g');

    return function(text){
      function addMatch(kind, start, end, rn){
        if (lastMatchPos != start)
          result.push(text.substring(lastMatchPos, start).replace(keywordRegExp, '$1'));

        lastMatchPos = end + 1;

        if (kind)
          result.push('' + text.substring(start, end + 1) + '' + (rn || ''));
      }

      var result = [];
      var sym = text.split('');
      var start;
      var lastMatchPos = 0;
      var strSym;

      for (var i = 0; i < sym.length; i++)
      {
        if (sym[i] == '\'' || sym[i] == '\"')
        {
          strSym = sym[i];
          start = i;
          while (++i < sym.length)
          {
            if (sym[i] == '\\')
            {
              if (sym[i + 1] == '\n')
              {
                addMatch('string', start, i);
                start = ++i + 1;
              }
              else
                i += 2;
            }

            if (sym[i] == strSym)
            {
              addMatch('string', start, i);
              break;
            }

            if (sym[i] == '\n')
              break;
          }
        }
        else if (sym[i] == '/')
        {
          start = i;
          i++;

          if (sym[i] == '/')
          {
            while (++i < sym.length)
            {
              if (sym[i] == '\n')
                break;
            }

            addMatch('comment', start, i - 1);
          }
          else if (sym[i] == '*')
          {
            while (++i < sym.length)
            {
              if (sym[i] == '*' && sym[i + 1] == '/')
              {
                addMatch('comment', start, ++i);
                break;
              }
              else if (sym[i] == '\n')
              {
                addMatch('comment', start, i - 1, '\n');
                lastMatchPos = start = i + 1;
              }
            }
          }
        }
      }

      addMatch(null, text.length);

      return result;
    };
  })());

  //
  // css parser
  //

  PARSER.add('css', (function(){
    var prefixes =
      '-webkit- -o- -ms- -moz- -khtml-';
    var properties =
      'azimuth background-attachment background-color background-image background-position ' +
      'background-repeat background border-collapse border-color border-spacing border-style ' +
      'border-top border-right border-bottom border-left border-top-color border-right-color ' +
      'border-bottom-color border-left-color border-top-style border-right-style border-bottom-style ' +
      'border-left-style border-top-width border-right-width border-bottom-width border-left-width ' +
      'border-width border bottom caption-side clear clip color content counter-increment ' +
      'counter-reset cue-after cue-before cue cursor direction display elevation empty-cells ' +
      'float font-family font-size font-style font-variant font-weight font height left ' +
      'letter-spacing line-height list-style-image list-style-position list-style-type ' +
      'list-style margin-right margin-left margin-top margin-bottom margin max-height ' +
      'max-width min-height min-width orphans outline-color outline-style outline-width ' +
      'outline overflow padding-top padding-right padding-bottom padding-left padding ' +
      'page-break-after page-break-before page-break-inside pause-after pause-before ' +
      'pause pitch-range pitch play-during position quotes richness right speak-header ' +
      'speak-numeral speak-punctuation speak speech-rate stress table-layout text-align ' +
      'text-decoration text-indent text-transform top unicode-bidi vertical-align visibility ' +
      'voice-family volume white-space widows width word-spacing z-index';
    var css3properties =
      // CSS Transitions Module Level 3 (http://www.w3.org/TR/css3-transitions/)
      'transition transition-delay transition-duration transition-property transition-timing-function ' +
      // CSS 2D Transforms (http://www.w3.org/TR/css3-2d-transforms/)
      'transform transform-origin ' +
      // CSS Backgrounds and Borders Module Level 3 (http://www.w3.org/TR/css3-background/)
      'background-origin background-clip background-size ' +
      'border-image border-image-outset border-image-repeat border-image-slice border-image-source border-image-width ' +
      'border-radius border-top-right-radius border-bottom-right-radius border-bottom-left-radius border-top-left-radius ' +
      'box-decoration-break box-shadow ' +
      // CSS Multi-column Layout Module (http://www.w3.org/TR/css3-multicol/)
      'column-count column-fill column-gap column-rule column-rule-color column-rule-style column-rule-width columns column-span column-width';

    var values =
      'left-side far-left left center-left center-right center far-right right-side ' +
      'right behind leftwards rightwards inherit scroll fixed transparent none repeat-x ' +
      'repeat-y repeat no-repeat collapse separate auto top bottom both open-quote ' +
      'close-quote no-open-quote no-close-quote crosshair default pointer move e-resize ' +
      'ne-resize nw-resize n-resize se-resize sw-resize s-resize text wait help ltr rtl ' +
      'inline block list-item run-in compact marker table inline-table table-row-group ' +
      'table-header-group table-footer-group table-row table-column-group table-column ' +
      'table-cell table-caption below level above higher lower show hide caption icon ' +
      'menu message-box small-caption status-bar normal wider narrower ultra-condensed ' +
      'extra-condensed condensed semi-condensed semi-expanded expanded extra-expanded ' +
      'ultra-expanded italic oblique small-caps bold bolder lighter inside outside ' +
      'disc circle square decimal decimal-leading-zero lower-roman upper-roman lower-greek ' +
      'lower-alpha lower-latin upper-alpha upper-latin hebrew armenian georgian ' +
      'cjk-ideographic hiragana katakana hiragana-iroha katakana-iroha crop cross invert ' +
      'visible hidden always avoid x-low low medium high x-high static relative absolute ' +
      'portrait landscape spell-out once digits continuous code x-slow slow fast x-fast ' +
      'faster slower justify underline overline line-through blink capitalize uppercase ' +
      'lowercase embed bidi-override baseline sub super text-top middle text-bottom silent ' +
      'x-soft soft loud x-loud pre nowrap serif sans-serif cursive fantasy monospace empty ' +
      'string strict loose char true false dotted dashed solid double groove ridge inset ' +
      'outset larger smaller xx-small x-small small large x-large xx-large all newspaper ' +
      'distribute distribute-all-lines distribute-center-last inter-word inter-ideograph ' +
      'inter-cluster kashida ideograph-alpha ideograph-numeric ideograph-parenthesis ' +
      'ideograph-space keep-all break-all break-word lr-tb tb-rl thin thick inline-block ' +
      'w-resize hand distribute-letter distribute-space whitespace ignore';

    var propertiesRegExp = new RegExp('(^|[^a-z0-9\-])((?:' + prefixes.split(' ').join('|') + ')?(?:' + css3properties.split(' ').join('|') + ')|(?:' + properties.split(' ').join('|') + '))(\s|:|$)', 'gi');
    var valuesRegExp = new RegExp('\\b(' + values.split(' ').join('|') + ')\\b', 'g');

    return function(text){
      function addMatch(kind, start, end, rn){
        if (lastMatchPos != start)
        {
          var fragment = text.substring(lastMatchPos, start);

          if (blockScope)
          {
            if (valueScope)
              fragment = fragment.replace(valuesRegExp, '$1');
            else
              fragment = fragment.replace(propertiesRegExp, '$1$2$3');
          }

          result.push(fragment);
        }

        lastMatchPos = end + 1;

        if (kind)
          result.push('' + text.substring(start, end + 1) + '' + (rn || ''));
      }

      var result = [];
      var sym = text.split('');
      var start = 0;
      var lastMatchPos = 0;
      var strSym;
      var blockScope = false;
      var valueScope = false;
      var braceScope = false;

      for (var i = 0; i < sym.length; i++)
      {
        if (sym[i] == '\'' || sym[i] == '\"')
        {
          strSym = sym[i];
          start = i;
          while (++i < sym.length)
          {
            if (sym[i] == strSym)
            {
              addMatch('string', start, i);
              break;
            }

            if (sym[i] == '\n')
              break;

            if (sym[i] == '\\' && sym[i + 1] == '\n')
            {
              addMatch('string', start, i);
              start = ++i + 1;
            }
          }
        }
        else if (sym[i] == '/')
        {
          start = i;
          i++;

          if (sym[i] == '*')
          {
            while (++i < sym.length)
            {
              if (sym[i] == '*' && sym[i + 1] == '/')
              {
                addMatch('comment', start, ++i);
                break;
              }
              else if (sym[i] == '\n')
              {
                addMatch('comment', start, i - 1, '\n');
                lastMatchPos = start = i + 1;
              }
            }
          }
        }
        else if (sym[i] == '{')
        {
          blockScope = true;
          valueScope = false;
          braceScope = false;
          start = i + 1;
          addMatch('', start, i);
        }
        else if (sym[i] == '(')
        {
          if (valueScope)
            braceScope = true;
        }
        else if (sym[i] == ')')
        {
          if (valueScope)
            braceScope = false;
        }
        else if (sym[i] == ':')
        {
          if (blockScope && !braceScope)
          {
            start = i + 1;
            addMatch('', start, i);
            valueScope = true;
          }
        }
        else if (sym[i] == ';')
        {
          if (blockScope && !braceScope)
          {
            start = i + 1;
            addMatch('', start, i);
            valueScope = false;
          }
        }
        else if (sym[i] == '}')
        {
          blockScope = false;
          valueScope = false;
        }
      }

      addMatch(null, text.length);

      return result;
    };
  })());

 /**
  * Function that produce html code from text.
  * @param {string} text
  * @param {string=} lang
  * @param {object=} options
  * @return {string}
  */
  function highlight(text, lang, options){

    function normalize(text){
      text = text
        .trimRight()
        .replace(/\r\n|\n\r|\r/g, '\n');

      if (!options.keepFormat)
        text = text.replace(/^(?:\s*[\n]+)+?([ \t]*)/, '$1');

      // fix empty strings
      text = text
               .replace(/\n[ \t]+/g, function(m){
                  return m.replace(/\t/g, '  ');
                })
               .replace(/\n[ \t]+\n/g, '\n\n');

      if (!options.keepFormat)
      {
        // normalize text offset
        var minOffset = 1000;
        var lines = text.split(/\n+/);
        var startLine = Number(text.match(/^function/) != null); // fix for function.toString()
        for (var i = startLine; i < lines.length; i++)
        {
          var m = lines[i].match(/^\s*/);
          if (m[0].length < minOffset)
            minOffset = m[0].length;
          if (minOffset == 0)
            break;
        }

        if (minOffset > 0)
          text = text.replace(new RegExp('(^|\\n) {' + minOffset + '}', 'g'), '$1');
      }

      text = text.replace(new RegExp('(^|\\n)( +)', 'g'), function(m, a, b){
        return a + repeat('\xA0', b.length);
      });

      return text;
    }

    //  MAIN PART

    if (!options)
      options = {};

    var parser = LANG_PARSER[lang] || LANG_PARSER['text'];
    var html = parser(normalize(text || '').replace(/</g, '<'));

    var lines = html.join('').split('\n');
    var numberWidth = String(lines.length).length;
    var res = [];
    var lineClass = (options.noLineNumber ? '' : ' hasLineNumber');
    for (var i = 0; i < lines.length; i++)
    {
      res.push(
        '
' +
          '' +
            (!options.noLineNumber
              ? '<input class="lineNumber" value="' + lead(i + 1, numberWidth) + '" type="none" unselectable="on" readonly="readonly" tabindex="-1" />' +
                ''
              : ''
            ) +
            (lines[i] + '\r\n') +
          '' +
        '
'
      );
    }

    return res.join('');
  }

  //
  // export names
  //

  module.exports = {
    highlight: highlight,
    useStyle: basis.fn.runOnce(function(){
      resource('../ui/templates/highlight/SourceCode.css')().startUse();
    })
  };
<commit_msg>basis.utils.highlight remove unused require<commit_after>
 /**
  * @namespace basis.utils.highlight
  */

  var lead = basis.number.lead;
  var repeat = basis.string.repeat;

  var LANG_PARSER = {};
  var PARSER = {
    add: function(lang, fn){
      LANG_PARSER[lang] = fn;
    }
  };

  //
  // default parser
  //

  PARSER.add('text', basis.array);

  //
  // javascript parser
  //

  PARSER.add('js', (function(){
    var keywords =
      'break case catch continue ' +
      'default delete do else false ' +
      'for function if in instanceof ' +
      'new null return super switch ' +
      'this throw true try typeof var while with';

    var keywordRegExp = new RegExp('\\b(' + keywords.split(' ').join('|') + ')\\b', 'g');

    return function(text){
      function addMatch(kind, start, end, rn){
        if (lastMatchPos != start)
          result.push(text.substring(lastMatchPos, start).replace(keywordRegExp, '$1'));

        lastMatchPos = end + 1;

        if (kind)
          result.push('' + text.substring(start, end + 1) + '' + (rn || ''));
      }

      var result = [];
      var sym = text.split('');
      var start;
      var lastMatchPos = 0;
      var strSym;

      for (var i = 0; i < sym.length; i++)
      {
        if (sym[i] == '\'' || sym[i] == '\"')
        {
          strSym = sym[i];
          start = i;
          while (++i < sym.length)
          {
            if (sym[i] == '\\')
            {
              if (sym[i + 1] == '\n')
              {
                addMatch('string', start, i);
                start = ++i + 1;
              }
              else
                i += 2;
            }

            if (sym[i] == strSym)
            {
              addMatch('string', start, i);
              break;
            }

            if (sym[i] == '\n')
              break;
          }
        }
        else if (sym[i] == '/')
        {
          start = i;
          i++;

          if (sym[i] == '/')
          {
            while (++i < sym.length)
            {
              if (sym[i] == '\n')
                break;
            }

            addMatch('comment', start, i - 1);
          }
          else if (sym[i] == '*')
          {
            while (++i < sym.length)
            {
              if (sym[i] == '*' && sym[i + 1] == '/')
              {
                addMatch('comment', start, ++i);
                break;
              }
              else if (sym[i] == '\n')
              {
                addMatch('comment', start, i - 1, '\n');
                lastMatchPos = start = i + 1;
              }
            }
          }
        }
      }

      addMatch(null, text.length);

      return result;
    };
  })());

  //
  // css parser
  //

  PARSER.add('css', (function(){
    var prefixes =
      '-webkit- -o- -ms- -moz- -khtml-';
    var properties =
      'azimuth background-attachment background-color background-image background-position ' +
      'background-repeat background border-collapse border-color border-spacing border-style ' +
      'border-top border-right border-bottom border-left border-top-color border-right-color ' +
      'border-bottom-color border-left-color border-top-style border-right-style border-bottom-style ' +
      'border-left-style border-top-width border-right-width border-bottom-width border-left-width ' +
      'border-width border bottom caption-side clear clip color content counter-increment ' +
      'counter-reset cue-after cue-before cue cursor direction display elevation empty-cells ' +
      'float font-family font-size font-style font-variant font-weight font height left ' +
      'letter-spacing line-height list-style-image list-style-position list-style-type ' +
      'list-style margin-right margin-left margin-top margin-bottom margin max-height ' +
      'max-width min-height min-width orphans outline-color outline-style outline-width ' +
      'outline overflow padding-top padding-right padding-bottom padding-left padding ' +
      'page-break-after page-break-before page-break-inside pause-after pause-before ' +
      'pause pitch-range pitch play-during position quotes richness right speak-header ' +
      'speak-numeral speak-punctuation speak speech-rate stress table-layout text-align ' +
      'text-decoration text-indent text-transform top unicode-bidi vertical-align visibility ' +
      'voice-family volume white-space widows width word-spacing z-index';
    var css3properties =
      // CSS Transitions Module Level 3 (http://www.w3.org/TR/css3-transitions/)
      'transition transition-delay transition-duration transition-property transition-timing-function ' +
      // CSS 2D Transforms (http://www.w3.org/TR/css3-2d-transforms/)
      'transform transform-origin ' +
      // CSS Backgrounds and Borders Module Level 3 (http://www.w3.org/TR/css3-background/)
      'background-origin background-clip background-size ' +
      'border-image border-image-outset border-image-repeat border-image-slice border-image-source border-image-width ' +
      'border-radius border-top-right-radius border-bottom-right-radius border-bottom-left-radius border-top-left-radius ' +
      'box-decoration-break box-shadow ' +
      // CSS Multi-column Layout Module (http://www.w3.org/TR/css3-multicol/)
      'column-count column-fill column-gap column-rule column-rule-color column-rule-style column-rule-width columns column-span column-width';

    var values =
      'left-side far-left left center-left center-right center far-right right-side ' +
      'right behind leftwards rightwards inherit scroll fixed transparent none repeat-x ' +
      'repeat-y repeat no-repeat collapse separate auto top bottom both open-quote ' +
      'close-quote no-open-quote no-close-quote crosshair default pointer move e-resize ' +
      'ne-resize nw-resize n-resize se-resize sw-resize s-resize text wait help ltr rtl ' +
      'inline block list-item run-in compact marker table inline-table table-row-group ' +
      'table-header-group table-footer-group table-row table-column-group table-column ' +
      'table-cell table-caption below level above higher lower show hide caption icon ' +
      'menu message-box small-caption status-bar normal wider narrower ultra-condensed ' +
      'extra-condensed condensed semi-condensed semi-expanded expanded extra-expanded ' +
      'ultra-expanded italic oblique small-caps bold bolder lighter inside outside ' +
      'disc circle square decimal decimal-leading-zero lower-roman upper-roman lower-greek ' +
      'lower-alpha lower-latin upper-alpha upper-latin hebrew armenian georgian ' +
      'cjk-ideographic hiragana katakana hiragana-iroha katakana-iroha crop cross invert ' +
      'visible hidden always avoid x-low low medium high x-high static relative absolute ' +
      'portrait landscape spell-out once digits continuous code x-slow slow fast x-fast ' +
      'faster slower justify underline overline line-through blink capitalize uppercase ' +
      'lowercase embed bidi-override baseline sub super text-top middle text-bottom silent ' +
      'x-soft soft loud x-loud pre nowrap serif sans-serif cursive fantasy monospace empty ' +
      'string strict loose char true false dotted dashed solid double groove ridge inset ' +
      'outset larger smaller xx-small x-small small large x-large xx-large all newspaper ' +
      'distribute distribute-all-lines distribute-center-last inter-word inter-ideograph ' +
      'inter-cluster kashida ideograph-alpha ideograph-numeric ideograph-parenthesis ' +
      'ideograph-space keep-all break-all break-word lr-tb tb-rl thin thick inline-block ' +
      'w-resize hand distribute-letter distribute-space whitespace ignore';

    var propertiesRegExp = new RegExp('(^|[^a-z0-9\-])((?:' + prefixes.split(' ').join('|') + ')?(?:' + css3properties.split(' ').join('|') + ')|(?:' + properties.split(' ').join('|') + '))(\s|:|$)', 'gi');
    var valuesRegExp = new RegExp('\\b(' + values.split(' ').join('|') + ')\\b', 'g');

    return function(text){
      function addMatch(kind, start, end, rn){
        if (lastMatchPos != start)
        {
          var fragment = text.substring(lastMatchPos, start);

          if (blockScope)
          {
            if (valueScope)
              fragment = fragment.replace(valuesRegExp, '$1');
            else
              fragment = fragment.replace(propertiesRegExp, '$1$2$3');
          }

          result.push(fragment);
        }

        lastMatchPos = end + 1;

        if (kind)
          result.push('' + text.substring(start, end + 1) + '' + (rn || ''));
      }

      var result = [];
      var sym = text.split('');
      var start = 0;
      var lastMatchPos = 0;
      var strSym;
      var blockScope = false;
      var valueScope = false;
      var braceScope = false;

      for (var i = 0; i < sym.length; i++)
      {
        if (sym[i] == '\'' || sym[i] == '\"')
        {
          strSym = sym[i];
          start = i;
          while (++i < sym.length)
          {
            if (sym[i] == strSym)
            {
              addMatch('string', start, i);
              break;
            }

            if (sym[i] == '\n')
              break;

            if (sym[i] == '\\' && sym[i + 1] == '\n')
            {
              addMatch('string', start, i);
              start = ++i + 1;
            }
          }
        }
        else if (sym[i] == '/')
        {
          start = i;
          i++;

          if (sym[i] == '*')
          {
            while (++i < sym.length)
            {
              if (sym[i] == '*' && sym[i + 1] == '/')
              {
                addMatch('comment', start, ++i);
                break;
              }
              else if (sym[i] == '\n')
              {
                addMatch('comment', start, i - 1, '\n');
                lastMatchPos = start = i + 1;
              }
            }
          }
        }
        else if (sym[i] == '{')
        {
          blockScope = true;
          valueScope = false;
          braceScope = false;
          start = i + 1;
          addMatch('', start, i);
        }
        else if (sym[i] == '(')
        {
          if (valueScope)
            braceScope = true;
        }
        else if (sym[i] == ')')
        {
          if (valueScope)
            braceScope = false;
        }
        else if (sym[i] == ':')
        {
          if (blockScope && !braceScope)
          {
            start = i + 1;
            addMatch('', start, i);
            valueScope = true;
          }
        }
        else if (sym[i] == ';')
        {
          if (blockScope && !braceScope)
          {
            start = i + 1;
            addMatch('', start, i);
            valueScope = false;
          }
        }
        else if (sym[i] == '}')
        {
          blockScope = false;
          valueScope = false;
        }
      }

      addMatch(null, text.length);

      return result;
    };
  })());

 /**
  * Function that produce html code from text.
  * @param {string} text
  * @param {string=} lang
  * @param {object=} options
  * @return {string}
  */
  function highlight(text, lang, options){

    function normalize(text){
      text = text
        .trimRight()
        .replace(/\r\n|\n\r|\r/g, '\n');

      if (!options.keepFormat)
        text = text.replace(/^(?:\s*[\n]+)+?([ \t]*)/, '$1');

      // fix empty strings
      text = text
               .replace(/\n[ \t]+/g, function(m){
                  return m.replace(/\t/g, '  ');
                })
               .replace(/\n[ \t]+\n/g, '\n\n');

      if (!options.keepFormat)
      {
        // normalize text offset
        var minOffset = 1000;
        var lines = text.split(/\n+/);
        var startLine = Number(text.match(/^function/) != null); // fix for function.toString()
        for (var i = startLine; i < lines.length; i++)
        {
          var m = lines[i].match(/^\s*/);
          if (m[0].length < minOffset)
            minOffset = m[0].length;
          if (minOffset == 0)
            break;
        }

        if (minOffset > 0)
          text = text.replace(new RegExp('(^|\\n) {' + minOffset + '}', 'g'), '$1');
      }

      text = text.replace(new RegExp('(^|\\n)( +)', 'g'), function(m, a, b){
        return a + repeat('\xA0', b.length);
      });

      return text;
    }

    //  MAIN PART

    if (!options)
      options = {};

    var parser = LANG_PARSER[lang] || LANG_PARSER['text'];
    var html = parser(normalize(text || '').replace(/</g, '<'));

    var lines = html.join('').split('\n');
    var numberWidth = String(lines.length).length;
    var res = [];
    var lineClass = (options.noLineNumber ? '' : ' hasLineNumber');
    for (var i = 0; i < lines.length; i++)
    {
      res.push(
        '
' +
          '' +
            (!options.noLineNumber
              ? '<input class="lineNumber" value="' + lead(i + 1, numberWidth) + '" type="none" unselectable="on" readonly="readonly" tabindex="-1" />' +
                ''
              : ''
            ) +
            (lines[i] + '\r\n') +
          '' +
        '
'
      );
    }

    return res.join('');
  }

  //
  // export names
  //

  module.exports = {
    highlight: highlight,
    useStyle: basis.fn.runOnce(function(){
      resource('../ui/templates/highlight/SourceCode.css')().startUse();
    })
  };
<|endoftext|>


<commit_before>/*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
Copyright (c) 2017 Mozilla Corporation
*/

//configuration settings
if (Meteor.isServer) {
    mozdef = {
    rootURL: process.env.OPTIONS_METEOR_ROOTURL || "http://localhost",
    port: process.env.OPTIONS_METEOR_PORT || "80",
    rootAPI: process.env.OPTIONS_METEOR_ROOTAPI || "http://rest:8081",
    kibanaURL: process.env.OPTIONS_METEOR_KIBANAURL || "http://localhost:9090/app/kibana#",
    enableBlockIP: process.env.OPTIONS_METEOR_ENABLEBLOCKIP || true,
    enableClientAccountCreation: process.env.OPTIONS_METEOR_ENABLECLIENTACCOUNTCREATION || true,
    authenticationType: process.env.OPTIONS_METEOR_AUTHENTICATIONTYPE || "meteor-password"
    }

    Meteor.settings.public=mozdef;
}

if (Meteor.isClient) {
    // client should use getSetting instead to get settings from the server
    mozdef={}
}<commit_msg>indents<commit_after>/*
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
Copyright (c) 2017 Mozilla Corporation
*/

//configuration settings
if (Meteor.isServer) {
    mozdef = {
        rootURL: process.env.OPTIONS_METEOR_ROOTURL || "http://localhost",
        port: process.env.OPTIONS_METEOR_PORT || "80",
        rootAPI: process.env.OPTIONS_METEOR_ROOTAPI || "http://rest:8081",
        kibanaURL: process.env.OPTIONS_METEOR_KIBANAURL || "http://localhost:9090/app/kibana#",
        enableBlockIP: process.env.OPTIONS_METEOR_ENABLEBLOCKIP || true,
        enableClientAccountCreation: process.env.OPTIONS_METEOR_ENABLECLIENTACCOUNTCREATION || true,
        authenticationType: process.env.OPTIONS_METEOR_AUTHENTICATIONTYPE || "meteor-password"
    }

    Meteor.settings.public=mozdef;

}

if (Meteor.isClient) {
    // client should use getSetting instead to get settings from the server
    mozdef={}
}<|endoftext|>


<commit_before>EasySearch = (function () {
  'use strict';

  var ESCounts,
    Searchers,
    indexes = {/** @see defaultOptions */},
    defaultOptions = {
      'format' : 'mongo',
      'skip' : 0,
      'limit' : 10,
      'use' : 'minimongo',
      'reactive' : true,
      'props' : {},
      'sort' : function (searchString, options) {
        if (Searchers[this.use]) {
          return Searchers[this.use].defaultSort(this);
        }

        return {};
      },
      'count' : function () {
        var doc = ESCounts.findOne({ _id : this.name });

        if (doc) {
          return doc.count;
        }

        return 0;
      },
      'changeResults' : function (results) {
        return results;
      },
      /**
       * When using elastic-search it's the query object,
       * while using with mongo-db it's the selector object.
       *
       * @param {String} searchString
       * @param {Object} options
       * @return {Object}
       */
      'query' : function (searchString, options) {
        return Searchers[this.use].defaultQuery(this, searchString);
      }
    };

  ESCounts = new Mongo.Collection('esCounts');

  /** Helper Functions */
  function setUpPublication(name, opts) {
    Meteor.publish(name + '/easySearch', function (conf) {
      var resultSet,
        resultArray = [],
        publishScope = this,
        publishHandle;

      check(conf, Object);

      // TODO: sanity check each property
      indexes[name].skip = conf.skip;
      indexes[name].limit = conf.limit || indexes[name].limit;
      indexes[name].props = _.extend(indexes[name].props, conf.props);
      indexes[name].publishScope = this;

      resultSet = Searchers[opts.use].search(name, conf.value, indexes[name]);

      ESCounts.update({ _id: name }, { $set: { count: resultSet.total } }, { upsert: true });

      if (!resultSet.results.length) return this.ready();

      if (_.isObject(resultSet.results[0])) {
        resultArray = _.pluck(resultSet.results, '_id');
      } else if (_.isString(resultSet.results[0])) {
        resultArray = resultSet.results;
      }

      // properly observe the collection!
      publishHandle = opts.collection
        .find({ _id: { $in: resultArray } }, { sort: indexes[name].sort() })
        .observe({
          added: function (doc) {
            doc._index = name;
            publishScope.added('esSearchResults', doc._id, doc);
          },
          changed: function (doc) {
            publishScope.changed('esSearchResults', doc._id, doc);
          },
          removed: function (doc) {
            publishScope.removed('esSearchResults', doc._id);
          }
        }
      );

      publishScope.onStop(function () {
        publishHandle.stop();
      });

      publishScope.ready();
    });

    Meteor.publish(name + '/easySearchCount', function () {
      return ESCounts.find({ '_id' : name });
    });
  }

  function extendTransformFunction(collection, originalTransform) {
    return function (doc) {
      var transformedDoc = collection._transform(doc);
      return _.isFunction(originalTransform) ? originalTransform(transformedDoc) : transformedDoc;
    };
  }

  if (Meteor.isClient) {
    /**
     * find method to let users interact with search results.
     * @param {Object} selector
     * @param {Object} options
     * @returns {MongoCursor}
     */
    defaultOptions.find = function (selector, options) {
      selector = selector || {};
      selector._index = this.name;

      if (this.collection._hasCollectionHelpers) {
        options.transform = extendTransformFunction(this.collection, options.transform);
      }

      return ESSearchResults.find(selector, options);
    };

    /**
     * findOne method to let users interact with search results.
     * @param {Object} selector
     * @param {Object} options
     * @returns {Document}
     */
    defaultOptions.findOne = function (selector, options) {
      if (_.isObject(selector) || !selector) {
        selector = selector || {};
        selector._index = this.name;
      }

      if (this.collection._hasCollectionHelpers) {
        options.transform = extendTransformFunction(this.collection, options.transform);
      }

      return ESSearchResults.findOne(selector, options);
    };
  }

  /**
   * Searchers contains all engines that can be used to search content, until now:
   *
   * minimongo (client): Client side collection for reactive search
   * elastic-search (server): Elastic search server to search with (fast)
   * mongo-db (server): MongoDB on the server to search (more convenient)
   *
   */
  Searchers = {};

  return {
    /**
     * Placeholder config method.
     *
     * @param {Object} newConfig
     */
    'config' : function (newConfig) {
      return {};
    },
    /**
     * Simple logging method.
     *
     * @param {String} message
     * @param {String} type
     */
    'log' : function (message, type) {
      type = type || 'log';

      if (console && _.isFunction(console[type])) {
        console[type](message);
      } else if (console && _.isFunction(console.log)) {
        console.log(message);
      }
    },
    /**
     * Create a search index.
     *
     * @param {String} name
     * @param {Object} options
     */
    'createSearchIndex' : function (name, options) {
      check(name, Match.OneOf(String, null));
      check(options, Object);

      options.name = name;
      options.field = _.isArray(options.field) ? options.field : [options.field];
      indexes[name] = _.extend(_.clone(defaultOptions), options);

      options = indexes[name];

      if (options.permission) {
        EasySearch.log(
            'permission property is now deprecated! Return false inside a custom query method instead',
            'warn'
        );
      }

      if (Meteor.isServer && EasySearch._usesSubscriptions(name)) {
        setUpPublication(name, indexes[name]);
      }

      Searchers[options.use] && Searchers[options.use].createSearchIndex(name, options);
    },
    /**
     * Perform a search.
     *
     * @param {String} name             the search index
     * @param {String} searchString     the string to be searched
     * @param {Object} options          defined with createSearchIndex
     * @param {Function} callback       optional callback to be used
     */
    'search' : function (name, searchString, options, callback) {
      var results,
        index = indexes[name],
        searcherType = index.use;

      check(name, String);
      check(searchString, String);
      check(options, Object);
      check(callback, Match.Optional(Function));

      if ("undefined" === typeof Searchers[searcherType]) {
        throw new Meteor.Error(500, "Couldnt search with type: '" + searcherType + "'");
      }

      results = Searchers[searcherType].search(name, searchString, _.extend(indexes[name], options), callback);

      return index.changeResults(results);
    },
    /**
     * Retrieve a specific index configuration.
     *
     * @param {String} name
     * @return {Object}
     * @api public
     */
    'getIndex' : function (name) {
      return indexes[name];
    },
    /**
     * Retrieve all index configurations
     */
    'getIndexes' : function () {
      return indexes;
    },
    /**
     * Retrieve a specific Seacher.
     *
     * @param {String} name
     * @return {Object}
     * @api public
     */
    'getSearcher' : function (name) {
      return Searchers[name];
    },
    /**
     * Retrieve all Searchers.
     */
    'getSearchers' : function () {
      return Searchers;
    },
    /**
     * Loop through the indexes and provide the configuration.
     *
     * @param {Array|String} indexes
     * @param callback
     */
    'eachIndex' : function (indexes, callback) {
      indexes = !_.isArray(indexes) ? [indexes] : indexes;

      _.each(indexes, function (index) {
        callback(index, EasySearch.getIndex(index));
      });
    },
    /**
     * Makes it possible to override or extend the different
     * types of search to use with EasySearch (the "use" property)
     * when using EasySearch.createSearchIndex()
     *
     * @param {String} key      Type, e.g. mongo-db, elastic-search
     * @param {Object} methods  Methods to be used, only 2 are required:
     *                          - createSearchIndex (name, options)
     *                          - search (name, searchString, [options, callback])
     *                          - defaultQuery (options, searchString)
     *                          - defaultSort (options)
     */
    'createSearcher' : function (key, methods) {
      check(key, String);
      check(methods.search, Function);
      check(methods.createSearchIndex, Function);

      Searchers[key] = methods;
    },
    /**
     * Simple helper to check if searcher uses server side subscriptions for searching.
     *
     * @param {String} index Index name to check configuration for
     */
    '_usesSubscriptions' : function (index) {
      var conf = EasySearch.getIndex(index);
      return conf && conf.reactive && conf.use !== 'minimongo';
    }
  };
})();
<commit_msg>Check on _transform function, to generally apply this transform functionality<commit_after>EasySearch = (function () {
  'use strict';

  var ESCounts,
    Searchers,
    indexes = {/** @see defaultOptions */},
    defaultOptions = {
      'format' : 'mongo',
      'skip' : 0,
      'limit' : 10,
      'use' : 'minimongo',
      'reactive' : true,
      'props' : {},
      'sort' : function (searchString, options) {
        if (Searchers[this.use]) {
          return Searchers[this.use].defaultSort(this);
        }

        return {};
      },
      'count' : function () {
        var doc = ESCounts.findOne({ _id : this.name });

        if (doc) {
          return doc.count;
        }

        return 0;
      },
      'changeResults' : function (results) {
        return results;
      },
      /**
       * When using elastic-search it's the query object,
       * while using with mongo-db it's the selector object.
       *
       * @param {String} searchString
       * @param {Object} options
       * @return {Object}
       */
      'query' : function (searchString, options) {
        return Searchers[this.use].defaultQuery(this, searchString);
      }
    };

  ESCounts = new Mongo.Collection('esCounts');

  /** Helper Functions */
  function setUpPublication(name, opts) {
    Meteor.publish(name + '/easySearch', function (conf) {
      var resultSet,
        resultArray = [],
        publishScope = this,
        publishHandle;

      check(conf, Object);

      // TODO: sanity check each property
      indexes[name].skip = conf.skip;
      indexes[name].limit = conf.limit || indexes[name].limit;
      indexes[name].props = _.extend(indexes[name].props, conf.props);
      indexes[name].publishScope = this;

      resultSet = Searchers[opts.use].search(name, conf.value, indexes[name]);

      ESCounts.update({ _id: name }, { $set: { count: resultSet.total } }, { upsert: true });

      if (!resultSet.results.length) return this.ready();

      if (_.isObject(resultSet.results[0])) {
        resultArray = _.pluck(resultSet.results, '_id');
      } else if (_.isString(resultSet.results[0])) {
        resultArray = resultSet.results;
      }

      // properly observe the collection!
      publishHandle = opts.collection
        .find({ _id: { $in: resultArray } }, { sort: indexes[name].sort() })
        .observe({
          added: function (doc) {
            doc._index = name;
            publishScope.added('esSearchResults', doc._id, doc);
          },
          changed: function (doc) {
            publishScope.changed('esSearchResults', doc._id, doc);
          },
          removed: function (doc) {
            publishScope.removed('esSearchResults', doc._id);
          }
        }
      );

      publishScope.onStop(function () {
        publishHandle.stop();
      });

      publishScope.ready();
    });

    Meteor.publish(name + '/easySearchCount', function () {
      return ESCounts.find({ '_id' : name });
    });
  }

  function extendTransformFunction(collection, originalTransform) {
    return function (doc) {
      var transformedDoc = collection._transform(doc);
      return _.isFunction(originalTransform) ? originalTransform(transformedDoc) : transformedDoc;
    };
  }

  if (Meteor.isClient) {
    /**
     * find method to let users interact with search results.
     * @param {Object} selector
     * @param {Object} options
     * @returns {MongoCursor}
     */
    defaultOptions.find = function (selector, options) {
      selector = selector || {};
      selector._index = this.name;

      if (this.collection._transform) {
        options.transform = extendTransformFunction(this.collection, options.transform);
      }

      return ESSearchResults.find(selector, options);
    };

    /**
     * findOne method to let users interact with search results.
     * @param {Object} selector
     * @param {Object} options
     * @returns {Document}
     */
    defaultOptions.findOne = function (selector, options) {
      if (_.isObject(selector) || !selector) {
        selector = selector || {};
        selector._index = this.name;
      }

      if (this.collection._transform) {
        options.transform = extendTransformFunction(this.collection, options.transform);
      }

      return ESSearchResults.findOne(selector, options);
    };
  }

  /**
   * Searchers contains all engines that can be used to search content, until now:
   *
   * minimongo (client): Client side collection for reactive search
   * elastic-search (server): Elastic search server to search with (fast)
   * mongo-db (server): MongoDB on the server to search (more convenient)
   *
   */
  Searchers = {};

  return {
    /**
     * Placeholder config method.
     *
     * @param {Object} newConfig
     */
    'config' : function (newConfig) {
      return {};
    },
    /**
     * Simple logging method.
     *
     * @param {String} message
     * @param {String} type
     */
    'log' : function (message, type) {
      type = type || 'log';

      if (console && _.isFunction(console[type])) {
        console[type](message);
      } else if (console && _.isFunction(console.log)) {
        console.log(message);
      }
    },
    /**
     * Create a search index.
     *
     * @param {String} name
     * @param {Object} options
     */
    'createSearchIndex' : function (name, options) {
      check(name, Match.OneOf(String, null));
      check(options, Object);

      options.name = name;
      options.field = _.isArray(options.field) ? options.field : [options.field];
      indexes[name] = _.extend(_.clone(defaultOptions), options);

      options = indexes[name];

      if (options.permission) {
        EasySearch.log(
            'permission property is now deprecated! Return false inside a custom query method instead',
            'warn'
        );
      }

      if (Meteor.isServer && EasySearch._usesSubscriptions(name)) {
        setUpPublication(name, indexes[name]);
      }

      Searchers[options.use] && Searchers[options.use].createSearchIndex(name, options);
    },
    /**
     * Perform a search.
     *
     * @param {String} name             the search index
     * @param {String} searchString     the string to be searched
     * @param {Object} options          defined with createSearchIndex
     * @param {Function} callback       optional callback to be used
     */
    'search' : function (name, searchString, options, callback) {
      var results,
        index = indexes[name],
        searcherType = index.use;

      check(name, String);
      check(searchString, String);
      check(options, Object);
      check(callback, Match.Optional(Function));

      if ("undefined" === typeof Searchers[searcherType]) {
        throw new Meteor.Error(500, "Couldnt search with type: '" + searcherType + "'");
      }

      results = Searchers[searcherType].search(name, searchString, _.extend(indexes[name], options), callback);

      return index.changeResults(results);
    },
    /**
     * Retrieve a specific index configuration.
     *
     * @param {String} name
     * @return {Object}
     * @api public
     */
    'getIndex' : function (name) {
      return indexes[name];
    },
    /**
     * Retrieve all index configurations
     */
    'getIndexes' : function () {
      return indexes;
    },
    /**
     * Retrieve a specific Seacher.
     *
     * @param {String} name
     * @return {Object}
     * @api public
     */
    'getSearcher' : function (name) {
      return Searchers[name];
    },
    /**
     * Retrieve all Searchers.
     */
    'getSearchers' : function () {
      return Searchers;
    },
    /**
     * Loop through the indexes and provide the configuration.
     *
     * @param {Array|String} indexes
     * @param callback
     */
    'eachIndex' : function (indexes, callback) {
      indexes = !_.isArray(indexes) ? [indexes] : indexes;

      _.each(indexes, function (index) {
        callback(index, EasySearch.getIndex(index));
      });
    },
    /**
     * Makes it possible to override or extend the different
     * types of search to use with EasySearch (the "use" property)
     * when using EasySearch.createSearchIndex()
     *
     * @param {String} key      Type, e.g. mongo-db, elastic-search
     * @param {Object} methods  Methods to be used, only 2 are required:
     *                          - createSearchIndex (name, options)
     *                          - search (name, searchString, [options, callback])
     *                          - defaultQuery (options, searchString)
     *                          - defaultSort (options)
     */
    'createSearcher' : function (key, methods) {
      check(key, String);
      check(methods.search, Function);
      check(methods.createSearchIndex, Function);

      Searchers[key] = methods;
    },
    /**
     * Simple helper to check if searcher uses server side subscriptions for searching.
     *
     * @param {String} index Index name to check configuration for
     */
    '_usesSubscriptions' : function (index) {
      var conf = EasySearch.getIndex(index);
      return conf && conf.reactive && conf.use !== 'minimongo';
    }
  };
})();
<|endoftext|>


<commit_before>var _ = require('lodash');
var bluebird = require('bluebird');
var util = require('./util');

module.exports = function resolveQueryObjects(config, knexInst) {
  var pendingQueries = [];

  _.forIn(config, function(entries, table) {
    entries = config[table] = util.asArray(entries);

    entries.forEach(function(entry, index) {
      _.forIn(entry, function(colValue, colName, record) {
        if (isQueryObject(colValue)) {
          var constraints = colValue;
          pendingQueries.push(
            knexInst(constraints.from)
              .where(constraints.where)
              .then(function(result) {
                if (result.length > 1) {
                  var where = JSON.stringify(constraints);
                  throw new Error(where + ' matches >1 possible FK!');
                } else {
                  record[colName] = result[0].id;
                }
              })
          );
        }
      });
    });
  });
  return pendingQueries;
};

function isQueryObject(value) {
  return _(value).has('from', 'where')
}
<commit_msg>Refactor executeFkQueries<commit_after>var _ = require('lodash');
var bluebird = require('bluebird');
var util = require('./util');
var knex;

module.exports = function resolveQueryObjects(config, knexInst) {
  var pendingQueries = [];
  knex = knexInst;

  _.forEach(config, function handleTable(entries) {
    util.asArray(entries).forEach(function (entry) {
      var promises = _.map(entry, resolveSingleValue);
      pendingQueries = pendingQueries.concat(promises);
    });
  });
  return pendingQueries;
};

function resolveSingleValue(colValue, colName, entry) {
  if (isQueryObject(colValue)) {
    return knex(colValue.from)
      .where(colValue.where)
      .then(function(result) {
        if (result.length > 1) {
          var where = JSON.stringify(colValue);
          throw new Error(where + ' matches >1 possible FK!');
        } else {
          entry[colName] = result[0].id;
        }
      });
  }
}

function isQueryObject(value) {
  return _(value).has('from', 'where')
}
<|endoftext|>