<commit_before>// Hash
;var Hash = {
		extract: function(data, path) {
			if(!new RegExp('[{\[]').test(path))
				return Hash.get(data, path) || []
			var tokens = Hash._tokenize(path),
				got = [], out = [], context = {set: [data]}

			for (var i = 0; i < tokens.length; i++) {
				got = []
				for (var z = 0; z < context.set.length; z++) {
					for (var key in context.set[z]) if (context.set[z].hasOwnProperty(key)) {
						if (Hash._matchToken(key, tokens[i]))
							got.push(context.set[z][key])
					}
				}
				context.set = got
			}
			return context.set
		},
		_matchToken: function(key, token) {
			if (token === '{n}')
				return (Number(key) % 1 === 0)
			if (token === '{s}')
				return typeof key === 'string'
			if (Number(token) % 1 === 0)
				return (key == token)
			return (key === token)
		},
		_matches: function(val, condition) {

		},
		expand: function(data) {
			var path, tokens, parent, child, out = {}, cleanPath, val, curr

			if(!data.length)
				data = [data]

			for (var i = 0; i < data.length; i++) {
				curr = data[i]
				for (var path in curr) if (curr.hasOwnProperty(path)) {
					tokens = Hash._tokenize(path).reverse()
					val = typeof curr[path] === 'function' ? curr[path]() : curr[path]
					if (tokens[0] === '{n}' || !isNaN(Number(tokens[0])) ) {
						child = []
						child[tokens[0]] = val
					} else {
						child = {}
						child[tokens[0]] = val
					}
					tokens.shift()
					for (var z = 0; z < tokens.length; z++) {
						if (tokens[z] === '{n}' || !isNaN(Number(tokens[z])))
							parent = [], parent[tokens[z]] = child
						else
							parent = {}, parent[tokens[z]] = child
						child = parent
					}
					out = Hash.merge(false, out, child)
				}
			}
			return out
		},
		get: function(data, path) {
			var out = data,
				tokens = Hash._tokenize(path)
			for (var i = 0; i < tokens.length; i++) {
				if (typeof out === 'object' && typeof out[tokens[i]] !== 'undefined')
					out = out[tokens[i]]
				else
					return null
			}
			return out
		},
		merge: function() {
			var obs = Array.prototype.slice.call(arguments), out, dest = false

			if (typeof arguments[0] === 'boolean')
				dest = obs.shift()

			out = obs.shift()
			for (var i = 0; i < obs.length; i++) {
				for (var key in obs[i]) if (obs[i].hasOwnProperty(key)) {
					if (typeof obs[i][key] === 'object' && out[key] && !obs[i][key].nodeType)
						out[key] = Hash.merge(dest, out[key], obs[i][key])
					else
						out[key] = obs[i][key]
				}
			}
			return out
		},
		insert: function(data, path, values) {
			var tokens = Hash._tokenize(path), token, nextPath, expand = {}
			if (path.indexOf('{') === -1 && path.indexOf('[]') === -1) {
				return Hash._simpleOp('insert', data, tokens, values)
			}
			if (Hash.keys(data).length) {
				token = tokens.shift()
				nextPath = tokens.join('.')
				for (var key in data) if (data.hasOwnProperty(key)) {
					if (Hash._matchToken(key, token)) {
						if(!nextPath)
							data[key] = values
						else
							data[key] = Hash.insert(data[key], nextPath, values)
					}
				}
			} else {
				expand[path] = values
				return Hash.expand([expand])
			}
			return data
		},
		remove: function(data, path) {
			var tokens = Hash._tokenize(path), match, token, nextPath, removed
			if (path.indexOf('{') === -1) {
				return Hash._simpleOp('remove', data, tokens)
			}
			token = tokens.shift()
			nextPath = tokens.join('.')
			for (var key in data) if (data.hasOwnProperty(key)) {
				match = Hash._matchToken(key, token)
				if (match && typeof data[key] === 'object') {
					data[key] = Hash.remove(data[key], nextPath)
				} else if (match) {
					if (Array.isArray(data)) {
						data.splice(key,1)
					} else {
						delete data[key]
					}
				}
			}
			return data
		},
		_simpleOp: function(op, data, tokens, value) {
			var hold = data, removed
			for (var i = 0; i < tokens.length; i++) {
				if (op === 'insert') {
					if (i === tokens.length-1) {
						hold[tokens[i]] = value
						return data
					}
					if (typeof hold[tokens[i]] !== 'object') {
						if (!isNaN(Number(tokens[i+1]))) {
							hold[tokens[i]] = []
						} else {
							hold[tokens[i]] = {}
						}
					}
					hold = hold[tokens[i]]
				} else if (op === 'remove') {
					if (i === tokens.length-1) {
						removed = Hash.insert({}, 'item', hold[tokens[i]])
						if (Array.isArray(hold)) {
							hold.splice(tokens[i],1)
						} else {
							delete hold[tokens[i]]
						}
						data = removed.item
						return data
					}
					if (typeof hold[tokens[i]] === 'undefined') {
						return data
					}
					hold = hold[tokens[i]]
				}
			}
		},
		_tokenize: function(path) {
			if (path.indexOf('data[') === -1) {
				return path.split('.')
			} else {
				return path.replace(/^data/, '').replace(/^\[|\]$/g, '').split('][').map(function(v) {return v === '' ? '{n}' : v })
			}
		},
		flatten: function() {
			return Function.callWithCopy.apply(Hash._flatten, arguments)
		},
		_flatten: function(data, separator, limit) {
			var path = '', stack = [], out = {}, key, el, curr,
				separator = separator || '.', limit = limit || false, wrap = separator === ']['
			while (Hash.keys(data).length || (Array.isArray(data) && data.length) ) {
				if (Array.isArray(data)) {
					key = data.length-1
					el = data.pop()
				}
				else {
					key = Hash.keys(data)[0]
					el = data[key]
					delete data[key]
				}

				if (path.split(separator).length === limit || typeof el !== 'object' || el == null || el.nodeType) {
					if(wrap)
						out['data['+path+key+']'] = el || ''
					else
						out[path + key] = el || ''
				}
				else {
					if (Hash.keys(data).length > 0) {
						stack.push([data,path])
					}
					data = el
					path += key + separator
				}
				if (Hash.keys(data).length === 0 && stack.length) {
					curr = stack.pop()
					data = curr[0], path = curr[1]
				}
			}
			return out
		},
		keys: function(obj) {
			var keys = []
			if (Array.isArray(obj)) {
				obj.map(function(v, i) {keys.push(i)})
			} else {
				for (var key in obj) if (obj.hasOwnProperty(key))
					keys.push(key)
			}
			return keys
		}
	}
	return Hash
}()
<commit_msg>parseModel method (temporary) and removed function prototype callwithcopy in favor of using hash merge.<commit_after>// Hash.js
// https://github.com/jbielick/HashJS
var H = new function() {
	var Hash = {
		extract: function(data, path) {
			if(!new RegExp('[{\[]').test(path))
				return Hash.get(data, path) || []
			var tokens = Hash._tokenize(path),
				got = [], out = [], context = {set: [data]}

			for (var i = 0; i < tokens.length; i++) {
				got = []
				for (var z = 0; z < context.set.length; z++) {
					for (var key in context.set[z]) if (context.set[z].hasOwnProperty(key)) {
						if (Hash._matchToken(key, tokens[i]))
							got.push(context.set[z][key])
					}
				}
				context.set = got
			}
			return context.set
		},
		parseModel: function(el, returnInputs) {
			var model = {},
				$el = $(el),
				_flattenedDOM = {},
				_flattened = {},
				inputs = $el.is('form') ? $el.prop('elements') : $el.find('input, textarea, select'),
				obj, obj2, name, idxd

			for (var i = 0; i < inputs.length; i++) {
				name = inputs[i].getAttribute('name')
				if (name && inputs[i].type !== 'submit') {
					if (!inputs[name].tagName && inputs[name].length > 1)
						name = name.replace(/\[.{0}\]/g, function() {return '['+Array.prototype.slice.apply(inputs[name]).indexOf(inputs[i])+']'})
					if(inputs[i].type === 'file' && inputs[i].value !== '')
						r.files.push(inputs[i])
					_flattenedDOM[name] = inputs[i]
					_flattened[name] = $(inputs[i]).val()
				}
			}
			if (returnInputs) {
				model.get = function(path) {
					var input =  Hash.get(model.controls, path)
					return $(input).val()
				}
				model.set = function(path, value) {
					var input = Hash.get(model.controls, path)
					return $(input).val(value)
				}
				model.controls = Hash.expand(_flattenedDOM)
				model.data = Hash.expand(_flattened)
			} else {
				model = Hash.expand(_flattened)
			}
			return model
		},
		_matchToken: function(key, token) {
			if (token === '{n}')
				return (Number(key) % 1 === 0)
			if (token === '{s}')
				return typeof key === 'string'
			if (Number(token) % 1 === 0)
				return (key == token)
			return (key === token)
		},
		_matches: function(val, condition) {

		},
		expand: function(data) {
			var path, tokens, parent, child, out = {}, cleanPath, val, curr

			if(!data.length)
				data = [data]

			for (var i = 0; i < data.length; i++) {
				curr = data[i]
				for (var path in curr) if (curr.hasOwnProperty(path)) {
					tokens = Hash._tokenize(path).reverse()
					val = typeof curr[path] === 'function' ? curr[path]() : curr[path]
					if (tokens[0] === '{n}' || !isNaN(Number(tokens[0])) ) {
						child = []
						child[tokens[0]] = val
					} else {
						child = {}
						child[tokens[0]] = val
					}
					tokens.shift()
					for (var z = 0; z < tokens.length; z++) {
						if (tokens[z] === '{n}' || !isNaN(Number(tokens[z])))
							parent = [], parent[tokens[z]] = child
						else
							parent = {}, parent[tokens[z]] = child
						child = parent
					}
					out = Hash.merge(false, out, child)
				}
			}
			return out
		},
		get: function(data, path) {
			var out = data,
				tokens = Hash._tokenize(path)
			for (var i = 0; i < tokens.length; i++) {
				if (typeof out === 'object' && typeof out[tokens[i]] !== 'undefined')
					out = out[tokens[i]]
				else
					return null
			}
			return out
		},
		merge: function() {
			var obs = Array.prototype.slice.call(arguments), out, dest = false

			if (typeof arguments[0] === 'boolean')
				dest = obs.shift()

			out = obs.shift()
			for (var i = 0; i < obs.length; i++) {
				for (var key in obs[i]) if (obs[i].hasOwnProperty(key)) {
					if (typeof obs[i][key] === 'object' && out[key] && !obs[i][key].nodeType)
						out[key] = Hash.merge(dest, out[key], obs[i][key])
					else
						out[key] = obs[i][key]
				}
			}
			return out
		},
		insert: function(data, path, values) {
			var tokens = Hash._tokenize(path), token, nextPath, expand = {}
			if (path.indexOf('{') === -1 && path.indexOf('[]') === -1) {
				return Hash._simpleOp('insert', data, tokens, values)
			}
			if (Hash.keys(data).length) {
				token = tokens.shift()
				nextPath = tokens.join('.')
				for (var key in data) if (data.hasOwnProperty(key)) {
					if (Hash._matchToken(key, token)) {
						if(!nextPath)
							data[key] = values
						else
							data[key] = Hash.insert(data[key], nextPath, values)
					}
				}
			} else {
				expand[path] = values
				return Hash.expand([expand])
			}
			return data
		},
		remove: function(data, path) {
			var tokens = Hash._tokenize(path), match, token, nextPath, removed
			if (path.indexOf('{') === -1) {
				return Hash._simpleOp('remove', data, tokens)
			}
			token = tokens.shift()
			nextPath = tokens.join('.')
			for (var key in data) if (data.hasOwnProperty(key)) {
				match = Hash._matchToken(key, token)
				if (match && typeof data[key] === 'object') {
					data[key] = Hash.remove(data[key], nextPath)
				} else if (match) {
					if (Array.isArray(data)) {
						data.splice(key,1)
					} else {
						delete data[key]
					}
				}
			}
			return data
		},
		_simpleOp: function(op, data, tokens, value) {
			var hold = data, removed
			for (var i = 0; i < tokens.length; i++) {
				if (op === 'insert') {
					if (i === tokens.length-1) {
						hold[tokens[i]] = value
						return data
					}
					if (typeof hold[tokens[i]] !== 'object') {
						if (!isNaN(Number(tokens[i+1]))) {
							hold[tokens[i]] = []
						} else {
							hold[tokens[i]] = {}
						}
					}
					hold = hold[tokens[i]]
				} else if (op === 'remove') {
					if (i === tokens.length-1) {
						removed = Hash.insert({}, 'item', hold[tokens[i]])
						if (Array.isArray(hold)) {
							hold.splice(tokens[i],1)
						} else {
							delete hold[tokens[i]]
						}
						data = removed.item
						return data
					}
					if (typeof hold[tokens[i]] === 'undefined') {
						return data
					}
					hold = hold[tokens[i]]
				}
			}
		},
		_tokenize: function(path) {
			if (path.indexOf('data[') === -1) {
				return path.split('.')
			} else {
				return path.replace(/^data/, '').replace(/^\[|\]$/g, '').split('][').map(function(v) {return v === '' ? '{n}' : v })
			}
		},
		flatten: function() {
			var args = Array.prototype.slice.call(arguments)
			args[0] = Hash.merge(true, {}, args[0])
			return Hash._flatten(this, args)
		},
		_flatten: function(data, separator, limit) {
			var path = '', stack = [], out = {}, key, el, curr,
				separator = separator || '.', limit = limit || false, wrap = separator === ']['
			while (Hash.keys(data).length || (Array.isArray(data) && data.length) ) {
				if (Array.isArray(data)) {
					key = data.length-1
					el = data.pop()
				}
				else {
					key = Hash.keys(data)[0]
					el = data[key]
					delete data[key]
				}

				if (path.split(separator).length === limit || typeof el !== 'object' || el == null || el.nodeType) {
					if(wrap)
						out['data['+path+key+']'] = el || ''
					else
						out[path + key] = el || ''
				}
				else {
					if (Hash.keys(data).length > 0) {
						stack.push([data,path])
					}
					data = el
					path += key + separator
				}
				if (Hash.keys(data).length === 0 && stack.length) {
					curr = stack.pop()
					data = curr[0], path = curr[1]
				}
			}
			return out
		},
		keys: function(obj) {
			var keys = []
			if (Array.isArray(obj)) {
				obj.map(function(v, i) {keys.push(i)})
			} else {
				for (var key in obj) if (obj.hasOwnProperty(key))
					keys.push(key)
			}
			return keys
		}
	}
	return Hash
}()<|endoftext|>


<commit_before>
// global i18n object

var i18n = new(function() {
  var default_lng = "en";
  var supported_lngs = [
    default_lng, // default language
    "de", // translations found in locale/de.js
  ];
  this.translate = function() {
    var lng = settings.ui_language;
    if (lng == "auto") {
      // get user agent's language
      lng = navigator.language.replace(/-.*/,"").toLowerCase();
      if ($.inArray(lng,supported_lngs) == -1) {
        lng = default_lng;
        return false;
      }
    }
    if (lng == default_lng)
      return true; // nothing to do here :)

    // load language pack
    var lng_file = "locales/"+lng+".js";
    $.ajax(lng_file,{async:false,dataType:"json"}).success(function(data){
      td = $.extend(td,data);
      i18n.translate_ui();
      // todo: nicer implementation
    }).error(function(){
      alert("unexpected error: failed to load language file: "+lng_file);
    });
  }
  this.translate_ui = function() {
    // look for all object with the class "t"
    $(".t").each(function(nr,element) {
      // get translation term(s)
      var terms = $(element).attr("t");
      terms = terms.split(";");
      for (var i=0; i<terms.length; i++) {
        var term = terms[i];
        var tmp = term.match(/^(\[(.*)\])?(.*)$/);
        var what = tmp[2];
        var key  = tmp[3];
        var val = td[key];
        if (what === "html") {
          $(element).html(val);
        } else if (what !== undefined) {
          $(element).attr(what,val);
        } else {
          $(element).text(val);
        }
      }
    });
  }
  this.t = function(key) {
    return td[key];
  }

  // default texts
  var td = {
    "map_controlls.zoom_to_data": "zoom onto data",
    "map_controlls.localize_user": "pan to user location",
    "map_controlls.select_bbox": "manually select bbox",
    "map_controlls.toggle_wide_map": "toggle wide map",
    "map_controlls.clear_data": "clear data overlay",

    "dialog.dismiss": "dismiss",
    "dialog.cancel": "cancel",
    "dialog.save": "save",
    "dialog.delete": "delete",
    "dialog.close": "close",
    "dialog.done": "done",
    "dialog.repair_query": "repair query",
    "dialog.continue_anyway": "continue anyway",
    "dialog.show_data": "show data",

    "dialog.delete_query.title": "Delete Query?",
    "dialog.delete_query.expl": "Do you really want to delete the following query",

    "error.query.title": "Query Error",
    "error.query.expl": "An error occured during the execution of the overpass query! This is what overpass API returned:",
    "error.ajax.title": "Ajax Error",
    "error.ajax.expl": "An error occured during the execution of the overpass query!",
    "error.remote.title": "Remote Control Error",
    "error.remote.incompat": "Error: incompatible JOSM remote control version",
    "error.remote.not_found": "Remote control not found. :( Make sure JOSM is already running and properly configured.",

    "warning.browser.title": "Your browser is not supported :(",
    "warning.browser.expl.1": "
The browser you are currently using, is (most likely) not capable of running (significant parts of) this Application. It must support Web Storage API and cross origin resource sharing (CORS).
",
    "warning.browser.expl.2": "
Note that you may have to enable cookies and/or \"local Data\" for this site on some browsers (such as Firefox and Chrome).
",
    "warning.browser.expl.3": "
Please upgrade to a more up-to-date version of your browser or switch to a more capable one! Recent versions of Opera, Chrome and Firefox have been tested to work. Alternatively, you can still use the Overpass_API query form.
",

    "warning.incomplete.title": "Incomplete Data",
    "warning.incomplete.expl": "
This query returned no nodes. In OSM, only nodes contain coordinates. For example, a way cannot be displayed without its nodes.

If this is not what you meant to get, overpass tubo can help you to repair (auto-complete) the query by choosing \"repair query\" below. Otherwise you can continue to the data.
",
    "warning.incomplete.not_again": "do not show this message again.",
    "warning.incomplete.remote": "
It looks like if this query will not return OSM data in XML format with metadata. Editors like JOSM require the data to be in that format, though.

overpass turbo can help you to correct the query by choosing \"repair query\" below.
",

    "warning.share.long": "Warning: This share-link is quite long. It may not work under certain circumstances",
    "warning.share.very_long": "Warning: This share-link is very long. It is likely to fail under normal circumstances (browsers, webservers). Use with caution!",

    "waiter.processing_query": "processing query...",
    "waiter.export_as_image": "exporting as image...",

    "export.map_view.title": "Current Map View",
    "export.map_view.permalink_osm": "
Permalink
 to",
    "export.map_view.center": "Center",
    "export.map_view.center_expl": "lat/lon",
    "export.map_view.bounds": "Bounds",
    "export.map_view.bounds_expl": "south/west north/east",
    "export.map_view.zoom": "Zoom",

    "export.geoJSON.no_data": "No geoJSON data available! Please run a query first.",

    "export.image.title": "Export - Image",
    "export.image.alt": "the exported map",
    "export.image.download": "Download",
    "export.image.attribution_missing": "Make sure to include proper attributions when distributing this image!",

    "the end":""
  };
})(); // end create i18n object

<commit_msg>silent "failed to load language file" error<commit_after>
// global i18n object

var i18n = new(function() {
  var default_lng = "en";
  var supported_lngs = [
    default_lng, // default language
    "de", // translations found in locale/de.js
  ];
  this.translate = function() {
    var lng = settings.ui_language;
    if (lng == "auto") {
      // get user agent's language
      lng = navigator.language.replace(/-.*/,"").toLowerCase();
      if ($.inArray(lng,supported_lngs) == -1) {
        lng = default_lng;
        return false;
      }
    }
    if (lng == default_lng)
      return true; // nothing to do here :)

    // load language pack
    var lng_file = "locales/"+lng+".js";
    $.ajax(lng_file,{async:false,dataType:"json"}).success(function(data){
      td = $.extend(td,data);
      i18n.translate_ui();
      // todo: nicer implementation
    }).error(function(){
      console.log("failed to load language file: "+lng_file);
    });
  }
  this.translate_ui = function() {
    // look for all object with the class "t"
    $(".t").each(function(nr,element) {
      // get translation term(s)
      var terms = $(element).attr("t");
      terms = terms.split(";");
      for (var i=0; i<terms.length; i++) {
        var term = terms[i];
        var tmp = term.match(/^(\[(.*)\])?(.*)$/);
        var what = tmp[2];
        var key  = tmp[3];
        var val = td[key];
        if (what === "html") {
          $(element).html(val);
        } else if (what !== undefined) {
          $(element).attr(what,val);
        } else {
          $(element).text(val);
        }
      }
    });
  }
  this.t = function(key) {
    return td[key];
  }

  // default texts
  var td = {
    "map_controlls.zoom_to_data": "zoom onto data",
    "map_controlls.localize_user": "pan to user location",
    "map_controlls.select_bbox": "manually select bbox",
    "map_controlls.toggle_wide_map": "toggle wide map",
    "map_controlls.clear_data": "clear data overlay",

    "dialog.dismiss": "dismiss",
    "dialog.cancel": "cancel",
    "dialog.save": "save",
    "dialog.delete": "delete",
    "dialog.close": "close",
    "dialog.done": "done",
    "dialog.repair_query": "repair query",
    "dialog.continue_anyway": "continue anyway",
    "dialog.show_data": "show data",

    "dialog.delete_query.title": "Delete Query?",
    "dialog.delete_query.expl": "Do you really want to delete the following query",

    "error.query.title": "Query Error",
    "error.query.expl": "An error occured during the execution of the overpass query! This is what overpass API returned:",
    "error.ajax.title": "Ajax Error",
    "error.ajax.expl": "An error occured during the execution of the overpass query!",
    "error.remote.title": "Remote Control Error",
    "error.remote.incompat": "Error: incompatible JOSM remote control version",
    "error.remote.not_found": "Remote control not found. :( Make sure JOSM is already running and properly configured.",

    "warning.browser.title": "Your browser is not supported :(",
    "warning.browser.expl.1": "
The browser you are currently using, is (most likely) not capable of running (significant parts of) this Application. It must support Web Storage API and cross origin resource sharing (CORS).
",
    "warning.browser.expl.2": "
Note that you may have to enable cookies and/or \"local Data\" for this site on some browsers (such as Firefox and Chrome).
",
    "warning.browser.expl.3": "
Please upgrade to a more up-to-date version of your browser or switch to a more capable one! Recent versions of Opera, Chrome and Firefox have been tested to work. Alternatively, you can still use the Overpass_API query form.
",

    "warning.incomplete.title": "Incomplete Data",
    "warning.incomplete.expl": "
This query returned no nodes. In OSM, only nodes contain coordinates. For example, a way cannot be displayed without its nodes.

If this is not what you meant to get, overpass tubo can help you to repair (auto-complete) the query by choosing \"repair query\" below. Otherwise you can continue to the data.
",
    "warning.incomplete.not_again": "do not show this message again.",
    "warning.incomplete.remote": "
It looks like if this query will not return OSM data in XML format with metadata. Editors like JOSM require the data to be in that format, though.

overpass turbo can help you to correct the query by choosing \"repair query\" below.
",

    "warning.share.long": "Warning: This share-link is quite long. It may not work under certain circumstances",
    "warning.share.very_long": "Warning: This share-link is very long. It is likely to fail under normal circumstances (browsers, webservers). Use with caution!",

    "waiter.processing_query": "processing query...",
    "waiter.export_as_image": "exporting as image...",

    "export.map_view.title": "Current Map View",
    "export.map_view.permalink_osm": "
Permalink
 to",
    "export.map_view.center": "Center",
    "export.map_view.center_expl": "lat/lon",
    "export.map_view.bounds": "Bounds",
    "export.map_view.bounds_expl": "south/west north/east",
    "export.map_view.zoom": "Zoom",

    "export.geoJSON.no_data": "No geoJSON data available! Please run a query first.",

    "export.image.title": "Export - Image",
    "export.image.alt": "the exported map",
    "export.image.download": "Download",
    "export.image.attribution_missing": "Make sure to include proper attributions when distributing this image!",

    "the end":""
  };
})(); // end create i18n object

<|endoftext|>


<commit_before>// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

var exec = require('cordova/exec');

var STATE_ACTIVE = 'active';
var STATE_IDLE = 'idle';
var STATE_LOCKED = 'locked';

// This keeps track of the current state.
var currentState = STATE_ACTIVE;

// These listeners are called when the device's state changes.
var stateListeners = [ ];

// This interval represents the amount of idle time required for the above state listeners to fire with status "idle".
var detectionIntervalInSeconds = 60;

// This is a timeout used to trigger the idle state.
var idleTimer = null;

// This tracks the last time a touch event occurred.
var lastInputDate = new Date();

exports.queryState = function(_detectionIntervalInSeconds, callback) {
    // If the device is locked, this is easy.
    if (currentState === STATE_LOCKED) {
        callback(STATE_LOCKED);
        return;
    }

    // We can't simply return the current state.
    // This is because the specified detection interval may be different than the one used to determine the current state.
    var currentDate = new Date();
    msSinceLastInput = currentDate.getTime() - lastInputDate.getTime();
    if (msSinceLastInput >= _detectionIntervalInSeconds * 1000) {
        callback(STATE_IDLE);
    } else {
        callback(STATE_ACTIVE);
    }
};

exports.setDetectionInterval = function(_detectionIntervalInSeconds) {
    detectionIntervalInSeconds = _detectionIntervalInSeconds;
    resetIdleTimer();
};

exports.onStateChanged = { };
exports.onStateChanged.addListener = function(listener) {
    if (typeof(listener) === 'function') {
        stateListeners.push(listener);
    } else {
        console.log('Attempted to add a non-function listener.');
    }
}

// This function fires the state listeners with the given state.
var fireListeners = function(state) {
    for (var i = 0; i < stateListeners.length; i++) {
        stateListeners[i](state);
    }
};

// This function resets the idle timer.
var resetIdleTimer = function() {
    clearTimeout(idleTimer);
    idleTimer = setTimeout(changeState, detectionIntervalInSeconds * 1000, STATE_IDLE);
};

// This function handles a change in state.
var changeState = function(state) {
    // If we have a new state, set it appropriately and fire the state listeners.
    if (currentState !== state) {
        currentState = state;
        fireListeners(state);
    }
}

// This function handles a touch event by resetting the idle timer and changing the state.
var handleTouchEvent = function() {
    lastInputDate = new Date();
    resetIdleTimer();
    changeState(STATE_ACTIVE);
}

// Start the idle timer.
resetIdleTimer();

// Add a touch listener.
document.addEventListener('touchstart', handleTouchEvent);

// Add a listener for screen locking.
var lockCallback = function(newState) {
    // If the device has been unlocked, there must have been a touch event, so we handle that.
    // If the device has been locked, we merely want to change the state to reflect this.
    if (newState === STATE_ACTIVE) {
        handleTouchEvent();
    } else if (newState === STATE_LOCKED) {
        changeState(STATE_LOCKED);
    }
}
exec(lockCallback, undefined, 'ChromeIdle', 'initialize', []);

<commit_msg>Added a platform check.<commit_after>// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

var exec = require('cordova/exec');
var platform = require('cordova/platform');

var STATE_ACTIVE = 'active';
var STATE_IDLE = 'idle';
var STATE_LOCKED = 'locked';

// This keeps track of the current state.
var currentState = STATE_ACTIVE;

// These listeners are called when the device's state changes.
var stateListeners = [ ];

// This interval represents the amount of idle time required for the above state listeners to fire with status "idle".
var detectionIntervalInSeconds = 60;

// This is a timeout used to trigger the idle state.
var idleTimer = null;

// This tracks the last time a touch event occurred.
var lastInputDate = new Date();

exports.queryState = function(_detectionIntervalInSeconds, callback) {
    // If the device is locked, this is easy.
    if (currentState === STATE_LOCKED) {
        callback(STATE_LOCKED);
        return;
    }

    // We can't simply return the current state.
    // This is because the specified detection interval may be different than the one used to determine the current state.
    var currentDate = new Date();
    msSinceLastInput = currentDate.getTime() - lastInputDate.getTime();
    if (msSinceLastInput >= _detectionIntervalInSeconds * 1000) {
        callback(STATE_IDLE);
    } else {
        callback(STATE_ACTIVE);
    }
};

exports.setDetectionInterval = function(_detectionIntervalInSeconds) {
    detectionIntervalInSeconds = _detectionIntervalInSeconds;
    resetIdleTimer();
};

exports.onStateChanged = { };
exports.onStateChanged.addListener = function(listener) {
    if (typeof(listener) === 'function') {
        stateListeners.push(listener);
    } else {
        console.log('Attempted to add a non-function listener.');
    }
}

// This function fires the state listeners with the given state.
var fireListeners = function(state) {
    for (var i = 0; i < stateListeners.length; i++) {
        stateListeners[i](state);
    }
};

// This function resets the idle timer.
var resetIdleTimer = function() {
    clearTimeout(idleTimer);
    idleTimer = setTimeout(changeState, detectionIntervalInSeconds * 1000, STATE_IDLE);
};

// This function handles a change in state.
var changeState = function(state) {
    // If we have a new state, set it appropriately and fire the state listeners.
    if (currentState !== state) {
        currentState = state;
        fireListeners(state);
    }
}

// This function handles a touch event by resetting the idle timer and changing the state.
var handleTouchEvent = function() {
    lastInputDate = new Date();
    resetIdleTimer();
    changeState(STATE_ACTIVE);
}

// Start the idle timer.
resetIdleTimer();

// Add a touch listener.
document.addEventListener('touchstart', handleTouchEvent);

// If we're on Android, add a listener for screen locking.
if (platform.id === 'android') {
    var lockCallback = function(newState) {
        // If the device has been unlocked, there must have been a touch event, so we handle that.
        // If the device has been locked, we merely want to change the state to reflect this.
        if (newState === STATE_ACTIVE) {
            handleTouchEvent();
        } else if (newState === STATE_LOCKED) {
            changeState(STATE_LOCKED);
        }
    }
    exec(lockCallback, undefined, 'ChromeIdle', 'initialize', []);
}

<|endoftext|>


<commit_before>/*
 *	Initial setup
 */

"use strict" // Otherwise we're stuck with ES5

const LOCAL_HOST = 3000;

// Variables required for Markov Chain generation
var input = [];
var firstWords = [];
var currentWord = "";

// Variables required for bot
var currentTopicId;
var currentToken;

// Set up modules
var express = require("express");
var request = require("request");
var bodyParser = require("body-parser");
var cheerio = require('cheerio');
var client = require('redis').createClient(process.env.REDIS_URL);

// Set up app
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.cookieJar = request.jar(); // Global cookie store
app.cachedData = { "post": "" }; // Basic format for JSON response
app.isLoggedIn = false;

// Set up routing for API
var routes = require("./routes/routes.js")(app);

// Set up server
var server = app.listen(process.env.PORT || LOCAL_HOST, () => {
	console.log(process.env.USERNAME + "-bot is sentient and ready to shitpost");
});

/**
	*		Redis db handler
	*/

client.on("connect", () => {
		// Attempt to get "quotes" array from Redis
		client.lrange("quotes", 0, -1, (error, items) => {
			if (error) {
				// TODO: offer ability to use .txt files
			}
			else {
				input = items;
				if (input.length > 0) {
					app.generateMarkovChain();
				}
			}
		});
});

/**
  * Markov chain methods
	*/

app.generateMarkovChain = function(shouldReturn) {
	var output = [];

	app.createArrays();

	// Pick random first word to start with
	currentWord = firstWords[Math.floor((Math.random() * firstWords.length))];

	for (let i = 0, len = app.getPostLength(); i < len; i++) {
		if (currentWord !== "") {
			output.push(currentWord);
		}

		var nextWord = app.generateNextWord();

		if (nextWord) {
			currentWord = nextWord;
		}
		else {
			currentWord = "";
		}
	}

	app.cachedData.post = output.join(" ").trim();

	if (shouldReturn) {
		return app.cachedData.post;
	}
};

app.getPostLength = function() {
	return input[Math.floor((Math.random() * input.length))].length;
};

app.createArrays = function() {
	// Create array containing first word of each sentence
	for (let i = 0, len = input.length; i < len; i++) {
		let sentence = input[i];
		let firstWord = sentence.split(" ")[0];
		firstWords.push(firstWord);
	}
};

app.generateNextWord = function() {
	var tempArray = [];
	// Iterate over input array to find instances of current word
	for (let i = 0, len = input.length - 1; i < len; i++) {
		let sentenceToCheck = input[i].split(" ");

		for (let j = 0, len = sentenceToCheck.length; j < len; j++) {
			let wordToCheck = sentenceToCheck[j];
			// create array of words that are likely to follow current word
			if (wordToCheck === currentWord) {
				// Make sure that item at index i + 1 exists
				if (j < len - 1) {
					let nextWord = sentenceToCheck[j + 1];
					tempArray.push(nextWord);
				}
				else {
					// reached end of array - do nothing
				}
			}
		}
	}

	let rnd = Math.floor((Math.random() * tempArray.length));

	if (tempArray[rnd]) {
		return tempArray[rnd];
	}

	else {
		return;
	}
};

/**
  *		Method which allows us to add new quotes from pastebin raw links
  *		Calls onSuccess with truthy value after quotes have been pushed to Redis
  */

app.addNewQuotes = function(url, onSuccess) {
	request.get(url, ((error, response, body) => {
		if (!error && response.statusCode == 200) {
			// Ignore blank lines,
			var quotes = body.split("\r\n").filter((line) => line && line.indexOf("<") == -1 && line.charAt(0) !== "<");

			client.lrange("quotes", 0, -1, (error, items) => {
				if (error) {
					onSuccess(false);
					throw error;
				}
				else {
					// Update local & disk cache
					input = input.concat(quotes);
					client.rpush.apply(client, ["quotes"].concat(quotes).concat(() => { console.log("Quotes stored to redis") }));
					onSuccess(true);
				}
			});

		}

	}));
};

/**
  *		Log into ETI using environment vars as credentials & do bot stuff
  *		Calls back onSuccess method after successful POST to async-post.php
  */

app.initBot = function(onSuccess) {
	const LOGIN_URL = "https://endoftheinter.net/";
	const formData = { b: process.env.USERNAME, p: process.env.PASSWORD };

	if (!app.isLoggedIn) {

		request.post({

			headers: {"content-type": "application/x-www-form-urlencoded"},
			url: LOGIN_URL,
			form: formData,
			jar: app.cookieJar

		}, (error, response, body) => {

				// After successful login, ETI will attempt to redirect you to homepage
				if (!error && response.statusCode === 302) {
						console.log("Logged in successfully.");
						app.isLoggedIn = true;
						app.getTopicList(onSuccess);
				}

				else {
					console.log("Login failed.");
					throw error;
				}
		});
	}

	else {
		console.log("Already logged in - skipping");
		app.getTopicList(onSuccess);
	}
};

app.getTopicList = function(onSuccess) {
	var LUE_TOPICS = "https://boards.endoftheinter.net/topics/LUE";

	request({

		url: LUE_TOPICS,
		jar: app.cookieJar

	}, (error, response, body) => {

		if (!error && response.statusCode === 200) {
			// Find random topic to pester
			var $ = cheerio.load(body);
			var randomTopic = Math.floor(Math.random() * 50) + 1;
			var href;

			// I don't understand the cheerio library. This is ridiculous. I don't care.
			$('td.oh div.fl a').each((index, element) => {

				if (index === randomTopic) {
					href = "https:" + $(element).attr('href');
					var topicNumberRegex = href.match(/(topic=)([0-9]+)/);

					if (href !== "https:" && topicNumberRegex) {
						console.log("Found topic to pester:", topicNumberRegex[2]);
						currentTopicId = topicNumberRegex[2];
						return false;
					}

					else {
						console.log("Couldn't find href. Index:", index);
						console.log("Random topic:", randomTopic);
						// Avoid potential for infinite loop here
						return false;
					}
				}

			});

			app.getMessageList(onSuccess);
		}

		else {
			console.log("Topic list GET failed.");
			throw error;
		}

	});
};

app.getMessageList = function(onSuccess) {

	request({

		url: "https://boards.endoftheinter.net/showmessages.php?topic=" + currentTopicId,
		jar: app.cookieJar

	}, (error, response, body) => {

		if (!error && response.statusCode === 200) {

			var $ = cheerio.load(body);
			// Can't make POST requests without the value of this token, scraped from quickpost area
			currentToken = $('input[name="h"]').attr('value');
			app.contributeToDiscussion(onSuccess);
		}

		else {
			console.log("Topic list GET failed.");
			throw error;
		}

	});
};

app.contributeToDiscussion = function(onSuccess) {
	const QUICKPOST_URL = "https://boards.endoftheinter.net/async-post.php";

	var formData = {};
			formData.topic = currentTopicId;
			formData.h = currentToken;
			formData.message = app.generateMarkovChain(true); // Pass true to get return value immediately

	request.post({

		uri: QUICKPOST_URL,
		form: formData,
		jar: app.cookieJar

	}, (error, response, body) => {

			if (!error && response.statusCode === 200) {
				// Callback onSuccess with topic id
				console.log("Post successful @", currentTopicId);
				onSuccess(currentTopicId);
			}

			else {
				console.log("Post unsuccessful.");
				throw error;
			}

	});
};<commit_msg>This looks neater with an object literal<commit_after>/*
 *	Initial setup
 */

"use strict" // Otherwise we're stuck with ES5

const LOCAL_HOST = 3000;

// Variables required for Markov Chain generation
var input = [];
var firstWords = [];
var currentWord = "";

// Variables required for bot
var currentTopicId;
var currentToken;

// Set up modules
var express = require("express");
var request = require("request");
var bodyParser = require("body-parser");
var cheerio = require('cheerio');
var client = require('redis').createClient(process.env.REDIS_URL);

// Set up app
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.cookieJar = request.jar(); // Global cookie store
app.cachedData = { "post": "" }; // Basic format for JSON response
app.isLoggedIn = false;

// Set up routing for API
var routes = require("./routes/routes.js")(app);

// Set up server
var server = app.listen(process.env.PORT || LOCAL_HOST, () => {
	console.log(process.env.USERNAME + "-bot is sentient and ready to shitpost");
});

/**
	*		Redis db handler
	*/

client.on("connect", () => {
		// Attempt to get "quotes" array from Redis
		client.lrange("quotes", 0, -1, (error, items) => {
			if (error) {
				// TODO: offer ability to use .txt files
			}
			else {
				input = items;
				if (input.length > 0) {
					app.generateMarkovChain();
				}
			}
		});
});

/**
  * Markov chain methods
	*/

app.generateMarkovChain = function(shouldReturn) {
	var output = [];

	app.createArrays();

	// Pick random first word to start with
	currentWord = firstWords[Math.floor((Math.random() * firstWords.length))];

	for (let i = 0, len = app.getPostLength(); i < len; i++) {
		if (currentWord !== "") {
			output.push(currentWord);
		}

		var nextWord = app.generateNextWord();

		if (nextWord) {
			currentWord = nextWord;
		}
		else {
			currentWord = "";
		}
	}

	app.cachedData.post = output.join(" ").trim();

	if (shouldReturn) {
		return app.cachedData.post;
	}
};

app.getPostLength = function() {
	return input[Math.floor((Math.random() * input.length))].length;
};

app.createArrays = function() {
	// Create array containing first word of each sentence
	for (let i = 0, len = input.length; i < len; i++) {
		let sentence = input[i];
		let firstWord = sentence.split(" ")[0];
		firstWords.push(firstWord);
	}
};

app.generateNextWord = function() {
	var tempArray = [];
	// Iterate over input array to find instances of current word
	for (let i = 0, len = input.length - 1; i < len; i++) {
		let sentenceToCheck = input[i].split(" ");

		for (let j = 0, len = sentenceToCheck.length; j < len; j++) {
			let wordToCheck = sentenceToCheck[j];
			// create array of words that are likely to follow current word
			if (wordToCheck === currentWord) {
				// Make sure that item at index i + 1 exists
				if (j < len - 1) {
					let nextWord = sentenceToCheck[j + 1];
					tempArray.push(nextWord);
				}
				else {
					// reached end of array - do nothing
				}
			}
		}
	}

	let rnd = Math.floor((Math.random() * tempArray.length));

	if (tempArray[rnd]) {
		return tempArray[rnd];
	}

	else {
		return;
	}
};

/**
  *		Method which allows us to add new quotes from pastebin raw links
  *		Calls onSuccess with truthy value after quotes have been pushed to Redis
  */

app.addNewQuotes = function(url, onSuccess) {
	request.get(url, ((error, response, body) => {
		if (!error && response.statusCode == 200) {
			// Ignore blank lines,
			var quotes = body.split("\r\n").filter((line) => line && line.indexOf("<") == -1 && line.charAt(0) !== "<");

			client.lrange("quotes", 0, -1, (error, items) => {
				if (error) {
					onSuccess(false);
					throw error;
				}
				else {
					// Update local & disk cache
					input = input.concat(quotes);
					client.rpush.apply(client, ["quotes"].concat(quotes).concat(() => { console.log("Quotes stored to redis") }));
					onSuccess(true);
				}
			});

		}

	}));
};

/**
  *		Log into ETI using environment vars as credentials & do bot stuff
  *		Calls back onSuccess method after successful POST to async-post.php
  */

app.initBot = function(onSuccess) {
	const LOGIN_URL = "https://endoftheinter.net/";
	const formData = { b: process.env.USERNAME, p: process.env.PASSWORD };

	if (!app.isLoggedIn) {

		request.post({

			headers: {"content-type": "application/x-www-form-urlencoded"},
			url: LOGIN_URL,
			form: formData,
			jar: app.cookieJar

		}, (error, response, body) => {

				// After successful login, ETI will attempt to redirect you to homepage
				if (!error && response.statusCode === 302) {
						console.log("Logged in successfully.");
						app.isLoggedIn = true;
						app.getTopicList(onSuccess);
				}

				else {
					console.log("Login failed.");
					throw error;
				}
		});
	}

	else {
		console.log("Already logged in - skipping");
		app.getTopicList(onSuccess);
	}
};

app.getTopicList = function(onSuccess) {
	var LUE_TOPICS = "https://boards.endoftheinter.net/topics/LUE";

	request({

		url: LUE_TOPICS,
		jar: app.cookieJar

	}, (error, response, body) => {

		if (!error && response.statusCode === 200) {
			// Find random topic to pester
			var $ = cheerio.load(body);
			var randomTopic = Math.floor(Math.random() * 50) + 1;
			var href;

			// I don't understand the cheerio library. This is ridiculous. I don't care.
			$('td.oh div.fl a').each((index, element) => {

				if (index === randomTopic) {
					href = "https:" + $(element).attr('href');
					var topicNumberRegex = href.match(/(topic=)([0-9]+)/);

					if (href !== "https:" && topicNumberRegex) {
						console.log("Found topic to pester:", topicNumberRegex[2]);
						currentTopicId = topicNumberRegex[2];
						return false;
					}

					else {
						console.log("Couldn't find href. Index:", index);
						console.log("Random topic:", randomTopic);
						// Avoid potential for infinite loop here
						return false;
					}
				}

			});

			app.getMessageList(onSuccess);
		}

		else {
			console.log("Topic list GET failed.");
			throw error;
		}

	});
};

app.getMessageList = function(onSuccess) {

	request({

		url: "https://boards.endoftheinter.net/showmessages.php?topic=" + currentTopicId,
		jar: app.cookieJar

	}, (error, response, body) => {

		if (!error && response.statusCode === 200) {

			var $ = cheerio.load(body);
			// Can't make POST requests without the value of this token, scraped from quickpost area
			currentToken = $('input[name="h"]').attr('value');
			app.contributeToDiscussion(onSuccess);
		}

		else {
			console.log("Topic list GET failed.");
			throw error;
		}

	});
};

app.contributeToDiscussion = function(onSuccess) {
	const QUICKPOST_URL = "https://boards.endoftheinter.net/async-post.php";

	var formData = {
			topic: currentTopicId,
			h: currentToken,
			message: app.generateMarkovChain(true) // Pass true to get return value immediately
	};

	request.post({

		uri: QUICKPOST_URL,
		form: formData,
		jar: app.cookieJar

	}, (error, response, body) => {

			if (!error && response.statusCode === 200) {
				// Callback onSuccess with topic id
				console.log("Post successful @", currentTopicId);
				onSuccess(currentTopicId);
			}

			else {
				console.log("Post unsuccessful.");
				throw error;
			}

	});
};<|endoftext|>


<commit_before>/**
 * Copyright © 2009-2012 A. Matías Quezada
 */

var global;

(function(self) {

	global = self;

	function resolveNamespace(ns) {
		var path = ns.split('.');

		var current = use.root;
		for (var i = 0; i < path.length; i++)
			current = current[path[i]] = current[path[i]] || {};

		return current;
	}

	function use(var_args) {
		var namespaces = [];

		for (var i = 0; i < arguments.length; i++)
			namespaces[i] = resolveNamespace(arguments[i]);

		return {
			on: function(callback) {
				callback.apply(use.root, namespaces);
			}
		};
	}

	use.root = global;
	global.use = use;

})(this);

use().on(function() {

	function wrap(object, parent, method) {
		var original = object[method];
		var base = parent[method];
		var init = method === 'init';

		function wrapper() {
			var baseValue = this.base, protoValue = this.proto;
			(this.base = base), (this.proto = parent);
			var result = original.apply(this, arguments);
			(this.base = baseValue), (this.proto = protoValue);
			return result;
		}
		wrapper.toString = function() {
			return original.toString();
		};

		object[method] = wrapper;
	}

	var prototype = ({}).__proto__ ?
		function prototype(child, parent) {
			child.__proto__ = parent;
			return child;
		} :
		function prototype(config, parent) {
			function intermediate() { }
			intermediate.prototype = parent;
			var child = new intermediate();

			for (var i in config) if (config.hasOwnProperty(i))
				child[i] = config[i];

			return child;
		};

	var Class = global.Class = function() { };
	Class.__proto__ = Function.prototype;
	Class.prototype = { constructor: Class };

	Class.extend = function(config) {
		var base = this.prototype;
		config = config || {};

		if (!config.hasOwnProperty('constructor'))
			config.constructor = function() {
				if (!this.base)
					debugger;
				this.base();
			};

		for (var i in config) if (config.hasOwnProperty(i) && typeof config[i] === 'function')
				wrap(config, base, i);

		var clazz = prototype(config.constructor, this);
		clazz.prototype = prototype(config, this.prototype);
		return clazz;
	};
});
<commit_msg>Removed 'var global', declaration without var makes it global<commit_after>/**
 * Copyright © 2009-2012 A. Matías Quezada
 */

(function(self) {

	global = self;

	function resolveNamespace(ns) {
		var path = ns.split('.');

		var current = use.root;
		for (var i = 0; i < path.length; i++)
			current = current[path[i]] = current[path[i]] || {};

		return current;
	}

	function use(var_args) {
		var namespaces = [];

		for (var i = 0; i < arguments.length; i++)
			namespaces[i] = resolveNamespace(arguments[i]);

		return {
			on: function(callback) {
				callback.apply(use.root, namespaces);
			}
		};
	}

	use.root = global;
	global.use = use;

})(this);

use().on(function() {

	function wrap(object, parent, method) {
		var original = object[method];
		var base = parent[method];
		var init = method === 'init';

		function wrapper() {
			var baseValue = this.base, protoValue = this.proto;
			(this.base = base), (this.proto = parent);
			var result = original.apply(this, arguments);
			(this.base = baseValue), (this.proto = protoValue);
			return result;
		}
		wrapper.toString = function() {
			return original.toString();
		};

		object[method] = wrapper;
	}

	var prototype = ({}).__proto__ ?
		function prototype(child, parent) {
			child.__proto__ = parent;
			return child;
		} :
		function prototype(config, parent) {
			function intermediate() { }
			intermediate.prototype = parent;
			var child = new intermediate();

			for (var i in config) if (config.hasOwnProperty(i))
				child[i] = config[i];

			return child;
		};

	var Class = global.Class = function() { };
	Class.__proto__ = Function.prototype;
	Class.prototype = { constructor: Class };

	Class.extend = function(config) {
		var base = this.prototype;
		config = config || {};

		if (!config.hasOwnProperty('constructor'))
			config.constructor = function() {
				if (!this.base)
					debugger;
				this.base();
			};

		for (var i in config) if (config.hasOwnProperty(i) && typeof config[i] === 'function')
				wrap(config, base, i);

		var clazz = prototype(config.constructor, this);
		clazz.prototype = prototype(config, this.prototype);
		return clazz;
	};
});
<|endoftext|>


<commit_before>function isArray (arr) {
    return Object.prototype.toString.call(arr) === '[object Array]';
}

function isObject (obj) {
    return Object.prototype.toString.call(obj) === '[object Object]';
}

function isFunction (fn) {
    return Object.prototype.toString.call(fn) === '[object Function]';
}

function trim (string) {
    return string.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}

/*
 * Styles
 * - .css()
 * - .hasClass()
 * - .addClass(), .removeClass()
 */

// TODO: make sure setting with numbers works.
// currently faster than jQuery - no metrics yet.
$.fn.css = function (property, value) {

    if (!property) {
        return this;
    }

    var i = 0,
        len;

    if (!value) { // getting CSS or setting with object

        if (isObject(property)) { // set CSS with object

            for (len = this.length; i < len; i++) {
                for (var key in property) {
                    if (property.hasOwnProperty(key)) {
                        this[i].style[key] = property[key];
                    }
                }
            }

            return this;

        } else { // get CSS of first elem in collection with string or array of properties
            var result = {};

            if (typeof property === 'string') {
                return getStyle(this[0], property);
            } else if (isArray(property)) {
                for (len = property.length; i < len; i++) {
                    result[property[i]] = getStyle(this[0], property[i]);
                }
                return result;
            } else {
                return this; // is this fail safe necessary? should we error if improper params are passed?
            }
        }

    } else { // set string CSS property with string/num value or return from function

        if (isFunction(value)) {
            for (len = this.length; i < len; i++) {
                this[i].style[property] = value.call(this[0], i, getStyle(this[i], property)); // fn gets elem as this and params (index, current style)
            }
        } else {
            for (len = this.length; i < len; i++) {
                this[i].style[property] = value;
            }
        }

        return this;
    }

    // IE8 POLYFILL:
    function getStyle (elem, prop) {
        // while setting CSS can be done with either camel-cased or dash-separated properties
        // getting computed CSS properties is persnickety about formatting

        if (typeof window.getComputedStyle === 'undefined') { // IE8 POLYFILL
            prop = prop === 'float' ?
                'styleFloat' :
                prop.replace(/^-ms-/, 'ms-').replace(/-([a-z])/gi, function (all, letter) { // insure that property is camel cased
                    return letter.toUpperCase();
                });
            return elem.currentStyle[prop];
        } else {
            prop = prop.replace(/[A-Z]/g, function (match) { // insure the property is dash-separated
                return '-' + match.toLowerCase();
            });
            return window.getComputedStyle(elem, null).getPropertyValue(prop);
        }
    }
};

$.fn.hasClass = function (className) {
    // ripped nearly word for word from jQuery. Thanks, open source world.
    for (var i = 0, len = this.length; i < len; i++) {
        if (this[i].nodeType === 1 && (' ' + this[i].className + ' ').replace(/[\t\r\n\f]/g, ' ').indexOf(className) >= 0) {
            return true;
        }
    }

    return false;
};

$.fn.addClass = function (value) {

    if (!value) {
        return this;
    }

    var i = 0,
        len = this.length;

    if (typeof value === 'string') {

        // value.split(' ') is faster & more readable but ' klass', would return [' ', 'klass']
        var classes = (value || '').match(/\S+/g) || []; // gotta use jQuery's regexp.

        for (; i < len; i++) {
            var currentClasses = (' ' + this[i].className + ' ').replace(/[\t\r\n\f]/g, ' '),
                newClasses = '';

            for (var j = 0, classLen = classes.length; j < classLen; j++) {
                if (currentClasses.indexOf(classes[j]) < 0) {
                    newClasses += classes[j] + (j !== classLen-1 ? ' ' : '');
                }
            }

            if (trim(newClasses) !== currentClasses) {
                this[i].className = newClasses;
            }
        }
    } else if (isFunction(value)) {
        for (; i < len; i++) {
            // have to pass node recusively in an array so it registers in the add class loop
            $.fn.addClass.call([this[i]], value.call(this, i, this[i].className));
        }
    }

    return this;
};

<commit_msg>IW: fix return for recursing addClass<commit_after>function isArray (arr) {
    return Object.prototype.toString.call(arr) === '[object Array]';
}

function isObject (obj) {
    return Object.prototype.toString.call(obj) === '[object Object]';
}

function isFunction (fn) {
    return Object.prototype.toString.call(fn) === '[object Function]';
}

function trim (string) {
    return string.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}

/*
 * Styles
 * - .css()
 * - .hasClass()
 * - .addClass(), .removeClass()
 */

// TODO: make sure setting with numbers works.
// currently faster than jQuery - no metrics yet.
$.fn.css = function (property, value) {

    if (!property) {
        return this;
    }

    var i = 0,
        len;

    if (!value) { // getting CSS or setting with object

        if (isObject(property)) { // set CSS with object

            for (len = this.length; i < len; i++) {
                for (var key in property) {
                    if (property.hasOwnProperty(key)) {
                        this[i].style[key] = property[key];
                    }
                }
            }

            return this;

        } else { // get CSS of first elem in collection with string or array of properties
            var result = {};

            if (typeof property === 'string') {
                return getStyle(this[0], property);
            } else if (isArray(property)) {
                for (len = property.length; i < len; i++) {
                    result[property[i]] = getStyle(this[0], property[i]);
                }
                return result;
            } else {
                return this; // is this fail safe necessary? should we error if improper params are passed?
            }
        }

    } else { // set string CSS property with string/num value or return from function

        if (isFunction(value)) {
            for (len = this.length; i < len; i++) {
                this[i].style[property] = value.call(this[0], i, getStyle(this[i], property)); // fn gets elem as this and params (index, current style)
            }
        } else {
            for (len = this.length; i < len; i++) {
                this[i].style[property] = value;
            }
        }

        return this;
    }

    // IE8 POLYFILL:
    function getStyle (elem, prop) {
        // while setting CSS can be done with either camel-cased or dash-separated properties
        // getting computed CSS properties is persnickety about formatting

        if (typeof window.getComputedStyle === 'undefined') { // IE8 POLYFILL
            prop = prop === 'float' ?
                'styleFloat' :
                prop.replace(/^-ms-/, 'ms-').replace(/-([a-z])/gi, function (all, letter) { // insure that property is camel cased
                    return letter.toUpperCase();
                });
            return elem.currentStyle[prop];
        } else {
            prop = prop.replace(/[A-Z]/g, function (match) { // insure the property is dash-separated
                return '-' + match.toLowerCase();
            });
            return window.getComputedStyle(elem, null).getPropertyValue(prop);
        }
    }
};

$.fn.hasClass = function (className) {
    // ripped nearly word for word from jQuery. Thanks, open source world.
    for (var i = 0, len = this.length; i < len; i++) {
        if (this[i].nodeType === 1 && (' ' + this[i].className + ' ').replace(/[\t\r\n\f]/g, ' ').indexOf(className) >= 0) {
            return true;
        }
    }

    return false;
};

$.fn.addClass = function (value) {

    if (!value) {
        return $.merge($(), this);
    }

    var i = 0,
        len = this.length;

    if (typeof value === 'string') {

        // value.split(' ') is faster & more readable but ' klass', would return [' ', 'klass']
        var classes = (value || '').match(/\S+/g) || []; // gotta use jQuery's regexp.

        for (; i < len; i++) {
            var currentClasses = (' ' + this[i].className + ' ').replace(/[\t\r\n\f]/g, ' '),
                newClasses = '';

            for (var j = 0, classLen = classes.length; j < classLen; j++) {
                if (currentClasses.indexOf(classes[j]) < 0) {
                    newClasses += classes[j] + (j !== classLen-1 ? ' ' : '');
                }
            }

            if (trim(newClasses) !== currentClasses) {
                this[i].className = newClasses;
            }
        }

        return $.merge($(), this);

    } else if (isFunction(value)) {

        var result = [];

        for (; i < len; i++) {
            // have to pass node recusively in an array so it registers in the add class loop
            result.push($.fn.addClass.call([this[i]], value.call(this, i, this[i].className)));
        }

        return $.merge($(), result);
    }
};

<|endoftext|>


<commit_before>/*
 * Component.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
/** @namespace components **/
define([
  '../sys',
  './Container'
], function(
  sys,
  Container
) {
  /**
   * A component is a set of layout elements with a render and interaction logic baked in

   * automatically calls init, render, dispose and resize in child class
   *
   * acts as a view. when called from modules automatically gets controller property that points to module
   *
   * public functions that need to be overridden by child classes:
   *
   * * this.init: binds events
   * * this.show: shows component
   * * this.dispose: unbinds events and destroy possible elements
   * * this.render: renders data
   * * this.resize: called when window was resized
   *
   * @class Component
   * @extends dom.Container
   * @memberof dom
   * @param {Handlebars} template should be a compiled handlebars template. alternatively can be a plain string or jquery object
   * @param {object} data the data to pass through the template, optional
   * @param {object} options optional options to overwrite container properties before inheriting
   **/
  function Component(template, data, options){
    /**
     * stored component width
     * @memberof dom.Component
     * @instance
     * @var {object} _componentWidth
     */
    this._componentWidth = 0;

    /**
     * stored component height
     * @memberof dom.Component
     * @instance
     * @var {object} _componentHeight
     */
    this._componentHeight = 0;

    this.__component = {};

    // store timeouts
    this.__component._doRenderTimeout = null;
    this.__component._doResizeTimeout = null;

    // store first render and resize calls
    this.__component._didRender = false;
    this.__component._didResize = false;
    this.__component._didDispose = false;

    // stored, old component size
    this.__component._oldComponentWidth = 0;
    this.__component._oldComponentHeight = 0;

    // stored, old window size
    this.__component._oldWindowWidth = 0;
    this.__component._oldWindowHeight = 0;

    // call super constructor if not already done by some other mixin function
    if (! this._initializedContainer) Container.call(this, template, data, options);

    // initialize when added to stage
    this.addEventListener('addedToStage', this.__bind(_init));
  }

  var p = sys.extend(Component, Container);

  /**
   * binds events in child class
   * @method init
   * @memberof dom.Component
   * @public
   * @instance
   **/
  p.init = function(){};

  /**
   * shows component
   * @method show
   * @memberof dom.Component
   * @public
   * @instance
   **/
  p.show = function(){};

  /**
   * unbinds events in child class
   *
   * @method dispose
   * @memberof dom.Component
   * @public
   * @instance
   **/
  p.dispose = function(){};

  /**
   * renders child component
   * @method render
   * @memberof dom.Component
   * @public
   * @instance
   **/
  p.render = function(){};

  /**
   * resizes child component
   * @method resize
   * @memberof dom.Component
   * @public
   * @instance
   **/
  p.resize = function(){};

  /**
   * binds events in child class
   *
   * @method _init
   * @memberof dom.Component
   * @protected
   * @instance
   **/
  var _init = function(){
    this.__component._didDispose = false;
    this.removeEventListener('addedToStage', this.__bind(_init));

    this.addEventListener('removedFromStage', this.__bind(_dispose));

    // NOTE: $(window).trigger('resize') does not fire addEventListener
    //window.addEventListener('resize', this.__bind(_windowResized));
    $(window).bind('resize', this.__bind(_windowResized));

    this.dispatchEvent('init');

    this.init();
    _windowResized.call(this);

    this.dispatchEvent('show');

    this.show();
  };

  /**
   * unbinds events in child class
   *
   * @method _dispose
   * @memberof dom.Component
   * @protected
   * @instance
   **/
  var _dispose = function(){
    if (this.__component._didDispose) return;
    this.__component._didDispose = true;

    if (this.__component._doRenderTimeout) clearTimeout(this.__component._doRenderTimeout);
    if (this.__component._doResizeTimeout) clearTimeout(this.__component._doResizeTimeout);

    //window.removeEventListener('resize', this.__bind(_windowResized));
    $(window).unbind('resize', this.__bind(_windowResized));
    this.removeEventListener('removedFromStage', this.__bind(_dispose));

    this.dispatchEvent('dispose');

    this.dispose();
  };

  /**
   * renders child component
   *
   * @method _render
   * @memberof dom.Component
   * @protected
   * @instance
   **/
  var _render = function(){
    // catch possible multiple calls of render
    // allow first render to happen immediately
    if (this.__component._doRenderTimeout || this.__component._didRender) {
      clearTimeout(this.__component._doRenderTimeout);
      this.__component._doRenderTimeout = setTimeout(this.__bind(_doRender), 17);
    } else {
      _doRender.call(this);
      this.__component._didRender = true;
    }
  };

  /**
   * resizes child component
   *
   * @method _resize
   * @memberof dom.Component
   * @protected
   * @instance
   **/
  var _resize = function(){
    // catch possible multiple calls of _resize
    // allow first resizing to happen immediately
    if (this.__component._doResizeTimeout || this.__component._didResize) {
      clearTimeout(this.__component._doResizeTimeout);
      this.__component._doResizeTimeout = setTimeout(this.__bind(_doResize), 17);
    } else {
      _doResize.call(this);
      this.__component._didResize = true;
    }
  };

  /**
   * executes rendering
   *
   * @method _doRender
   * @memberof dom.Component
   * @private
   * @instance
   **/
  var _doRender = function(){
    // unset timeout
    this.__component._doRenderTimeout = null;
    this.__component._didRender = false;

    this.dispatchEvent('render');

    // resize component
    this.render();
  };

  /**
   * executes resizing
   *
   * @method _doResize
   * @memberof dom.Component
   * @private
   * @instance
   **/
  var _doResize = function(){
    // unset timeout
    this.__component._doResizeTimeout = null;
    this.__component._didResize = false;

    // resize component
    this.resize();

    // dispatch resize event
    this.dispatchEvent('resize');

    // render component
    _render.call(this);
  };

  /**
   * checks window size and if it changed when resizing window
   * splitting this function and the resize function up means that we can call them individually
   * and explicitly in child components to resize a component no matter what
   *
   * this allows to call .resize anytime (for example when children or anything else changed in component that needs to be resized),
   * after adding a small timeout delay so resize won't be called a million times in a row
   *
   * @method _windowResized
   * @memberof dom.Component
   * @private
   * @instance
   **/
  var _windowResized = function(){
    var windowWidth = $(window).width();
    var windowHeight = $(window).height();

    // store component sizes
    this._componentWidth = this.$el.width();
    this._componentHeight = this.$el.height();

    // if window or component size changed -> call child resize function
    if (! (
      this._componentWidth === this.__component._oldComponentWidth &&
      this._componentHeight === this.__component._oldComponentHeight &&
      windowWidth === this.__component._oldWindowWidth &&
      windowHeight === this.__component._oldWindowHeight
    )) {
      this.__component._oldComponentWidth = this._componentWidth;
      this.__component._oldComponentHeight = this._componentHeight;

      this.__component._oldWindowWidth = windowWidth;
      this.__component._oldWindowHeight = windowHeight;

      _resize.call(this);
    }
  };

  return Component;
});
<commit_msg>only resize if component is visible<commit_after>/*
 * Component.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
/** @namespace components **/
define([
  '../sys',
  './Container'
], function(
  sys,
  Container
) {
  /**
   * A component is a set of layout elements with a render and interaction logic baked in

   * automatically calls init, render, dispose and resize in child class
   *
   * acts as a view. when called from modules automatically gets controller property that points to module
   *
   * public functions that need to be overridden by child classes:
   *
   * * this.init: binds events
   * * this.show: shows component
   * * this.dispose: unbinds events and destroy possible elements
   * * this.render: renders data
   * * this.resize: called when window was resized
   *
   * @class Component
   * @extends dom.Container
   * @memberof dom
   * @param {Handlebars} template should be a compiled handlebars template. alternatively can be a plain string or jquery object
   * @param {object} data the data to pass through the template, optional
   * @param {object} options optional options to overwrite container properties before inheriting
   **/
  function Component(template, data, options){
    /**
     * stored component width
     * @memberof dom.Component
     * @instance
     * @var {object} _componentWidth
     */
    this._componentWidth = 0;

    /**
     * stored component height
     * @memberof dom.Component
     * @instance
     * @var {object} _componentHeight
     */
    this._componentHeight = 0;

    this.__component = {};

    // store timeouts
    this.__component._doRenderTimeout = null;
    this.__component._doResizeTimeout = null;

    // store first render and resize calls
    this.__component._didRender = false;
    this.__component._didResize = false;
    this.__component._didDispose = false;

    // stored, old component size
    this.__component._oldComponentWidth = null;
    this.__component._oldComponentHeight = null;

    // stored, old window size
    this.__component._oldWindowWidth = 0;
    this.__component._oldWindowHeight = 0;

    // call super constructor if not already done by some other mixin function
    if (! this._initializedContainer) Container.call(this, template, data, options);

    // initialize when added to stage
    this.addEventListener('addedToStage', this.__bind(_init));
  }

  var p = sys.extend(Component, Container);

  /**
   * binds events in child class
   * @method init
   * @memberof dom.Component
   * @public
   * @instance
   **/
  p.init = function(){};

  /**
   * shows component
   * @method show
   * @memberof dom.Component
   * @public
   * @instance
   **/
  p.show = function(){};

  /**
   * unbinds events in child class
   *
   * @method dispose
   * @memberof dom.Component
   * @public
   * @instance
   **/
  p.dispose = function(){};

  /**
   * renders child component
   * @method render
   * @memberof dom.Component
   * @public
   * @instance
   **/
  p.render = function(){};

  /**
   * resizes child component
   * @method resize
   * @memberof dom.Component
   * @public
   * @instance
   **/
  p.resize = function(){};

  /**
   * binds events in child class
   *
   * @method _init
   * @memberof dom.Component
   * @protected
   * @instance
   **/
  var _init = function(){
    this.__component._didDispose = false;
    this.removeEventListener('addedToStage', this.__bind(_init));

    this.addEventListener('removedFromStage', this.__bind(_dispose));

    // NOTE: $(window).trigger('resize') does not fire addEventListener
    //window.addEventListener('resize', this.__bind(_windowResized));
    $(window).bind('resize', this.__bind(_windowResized));

    this.dispatchEvent('init');

    this.init();
    _windowResized.call(this);

    this.dispatchEvent('show');

    this.show();
  };

  /**
   * unbinds events in child class
   *
   * @method _dispose
   * @memberof dom.Component
   * @protected
   * @instance
   **/
  var _dispose = function(){
    if (this.__component._didDispose) return;
    this.__component._didDispose = true;

    if (this.__component._doRenderTimeout) clearTimeout(this.__component._doRenderTimeout);
    if (this.__component._doResizeTimeout) clearTimeout(this.__component._doResizeTimeout);

    //window.removeEventListener('resize', this.__bind(_windowResized));
    $(window).unbind('resize', this.__bind(_windowResized));
    this.removeEventListener('removedFromStage', this.__bind(_dispose));

    this.dispatchEvent('dispose');

    this.dispose();
  };

  /**
   * renders child component
   *
   * @method _render
   * @memberof dom.Component
   * @protected
   * @instance
   **/
  var _render = function(){
    // catch possible multiple calls of render
    // allow first render to happen immediately
    if (this.__component._doRenderTimeout || this.__component._didRender) {
      clearTimeout(this.__component._doRenderTimeout);
      this.__component._doRenderTimeout = setTimeout(this.__bind(_doRender), 17);
    } else {
      _doRender.call(this);
      this.__component._didRender = true;
    }
  };

  /**
   * resizes child component
   *
   * @method _resize
   * @memberof dom.Component
   * @protected
   * @instance
   **/
  var _resize = function(){
    // catch possible multiple calls of _resize
    // allow first resizing to happen immediately
    if (this.__component._doResizeTimeout || this.__component._didResize) {
      clearTimeout(this.__component._doResizeTimeout);
      this.__component._doResizeTimeout = setTimeout(this.__bind(_doResize), 17);
    } else {
      _doResize.call(this);
      this.__component._didResize = true;
    }
  };

  /**
   * executes rendering
   *
   * @method _doRender
   * @memberof dom.Component
   * @private
   * @instance
   **/
  var _doRender = function(){
    // unset timeout
    this.__component._doRenderTimeout = null;
    this.__component._didRender = false;

    this.dispatchEvent('render');

    // resize component
    this.render();
  };

  /**
   * executes resizing
   *
   * @method _doResize
   * @memberof dom.Component
   * @private
   * @instance
   **/
  var _doResize = function(){
    // unset timeout
    this.__component._doResizeTimeout = null;
    this.__component._didResize = false;

    // resize component
    this.resize();

    // dispatch resize event
    this.dispatchEvent('resize');

    // render component
    _render.call(this);
  };

  /**
   * checks window size and if it changed when resizing window
   * splitting this function and the resize function up means that we can call them individually
   * and explicitly in child components to resize a component no matter what
   *
   * this allows to call .resize anytime (for example when children or anything else changed in component that needs to be resized),
   * after adding a small timeout delay so resize won't be called a million times in a row
   *
   * @method _windowResized
   * @memberof dom.Component
   * @private
   * @instance
   **/
  var _windowResized = function(){
    var windowWidth = $(window).width();
    var windowHeight = $(window).height();

    var elWidth = this.$el.width();
    var elHeight = this.$el.height();

    if ((! (elWidth === 0 && elHeight === 0) && this.$el.is(':visible')) || this.__component._oldComponentWidth === null) {
      // store component sizes
      this._componentWidth = elWidth;
      this._componentHeight = elHeight;

      // if window or component size changed -> call child resize function
      if (! (
        this._componentWidth === this.__component._oldComponentWidth &&
        this._componentHeight === this.__component._oldComponentHeight &&
        windowWidth === this.__component._oldWindowWidth &&
        windowHeight === this.__component._oldWindowHeight
      )) {
        this.__component._oldComponentWidth = this._componentWidth;
        this.__component._oldComponentHeight = this._componentHeight;

        this.__component._oldWindowWidth = windowWidth;
        this.__component._oldWindowHeight = windowHeight;

        _resize.call(this);
      }
    }
  };

  return Component;
});
<|endoftext|>


<commit_before>// @ts-check
import {DOM} from 'aurelia-pal';
import * as LogManager from 'aurelia-logging';

let emLogger = LogManager.getLogger('event-manager');
//Note: path and deepPath are designed to handle v0 and v1 shadow dom specs respectively
function findOriginalEventTarget(event) {
  return (event.composedPath && event.composedPath()[0]) || (event.deepPath && event.deepPath[0]) || (event.path && event.path[0]) || event.target;
}

function stopPropagation() {
  this.standardStopPropagation();
  this.propagationStopped = true;
}

function handleCapturedEvent(event) {
  event.propagationStopped = false;
  let target = findOriginalEventTarget(event);

  let orderedCallbacks = [];
  /**
   * During capturing phase, event 'bubbles' down from parent. Needs to reorder callback from root down to target
   */
  while (target) {
    if (target.capturedCallbacks) {
      let callback = target.capturedCallbacks[event.type];
      if (callback) {
        if (event.stopPropagation !== stopPropagation) {
          event.standardStopPropagation = event.stopPropagation;
          event.stopPropagation = stopPropagation;
        }
        orderedCallbacks.push(callback);
      }
    }
    target = target.parentNode;
  }
  for (let i = orderedCallbacks.length - 1; i >= 0 && !event.propagationStopped; i--) {
    let orderedCallback = orderedCallbacks[i];
    if ('handleEvent' in orderedCallback) {
      orderedCallback.handleEvent(event);
    } else {
      orderedCallback(event);
    }
  }
}

class CapturedHandlerEntry {
  constructor(eventName) {
    this.eventName = eventName;
    this.count = 0;
  }

  increment() {
    this.count++;

    if (this.count === 1) {
      DOM.addEventListener(this.eventName, handleCapturedEvent, true);
    }
  }

  decrement() {
    if (this.count === 0) {
      emLogger.warn('The same EventListener was disposed multiple times.');
    } else if (--this.count === 0) {
      DOM.removeEventListener(this.eventName, handleCapturedEvent, true);
    }
  }
}

class DelegateHandlerEntry {
  constructor(eventName, eventManager) {
    this.eventName = eventName;
    this.count = 0;
    this.eventManager = eventManager;
  }

  handleEvent(event) {
    event.propagationStopped = false;
    let target = findOriginalEventTarget(event);

    while (target && !event.propagationStopped) {
      if (target.delegatedCallbacks) {
        let callback = target.delegatedCallbacks[event.type];
        if (callback) {
          if (event.stopPropagation !== stopPropagation) {
            event.standardStopPropagation = event.stopPropagation;
            event.stopPropagation = stopPropagation;
          }
          if ('handleEvent' in callback) {
            callback.handleEvent(event);
          } else {
            callback(event);
          }
        }
      }

      const parent = target.parentNode;
      const shouldEscapeShadowRoot = this.eventManager.escapeShadowRoot && parent instanceof ShadowRoot;

      target = shouldEscapeShadowRoot ? parent.host : parent;
    }
  }

  increment() {
    this.count++;

    if (this.count === 1) {
      DOM.addEventListener(this.eventName, this, false);
    }
  }

  decrement() {
    if (this.count === 0) {
      emLogger.warn('The same EventListener was disposed multiple times.');
    } else if (--this.count === 0) {
      DOM.removeEventListener(this.eventName, this, false);
    }
  }
}

/**
 * Enable dispose() pattern for `delegate` & `capture` commands
 */
class DelegationEntryHandler {
  /**
   * @param {DelegateHandlerEntry | CapturedHandlerEntry} entry
   * @param {Record<string, Function>} lookup
   * @param {string} targetEvent
   */
  constructor(entry, lookup, targetEvent) {
    this.entry = entry;
    this.lookup = lookup;
    this.targetEvent = targetEvent;
  }

  dispose() {
    if (this.lookup[this.targetEvent]) {
      this.entry.decrement();
      this.lookup[this.targetEvent] = null;
    } else {
      emLogger.warn('Calling .dispose() on already disposed eventListener');
    }
  }
}

/**
 * Enable dispose() pattern for addEventListener for `trigger`
 */
class EventHandler {
  /**
   * @param {Element} target
   * @param {string} targetEvent
   * @param {EventListenerOrEventListenerObject} callback
   */
  constructor(target, targetEvent, callback) {
    this.target = target;
    this.targetEvent = targetEvent;
    this.callback = callback;
  }

  dispose() {
    this.target.removeEventListener(this.targetEvent, this.callback);
  }
}

class DefaultEventStrategy {
  delegatedHandlers = {};
  capturedHandlers = {};

  constructor(eventManager) {
    this.eventManager = eventManager;
  }

  /**
   * @param {Element} target
   * @param {string} targetEvent
   * @param {EventListenerOrEventListenerObject} callback
   * @param {delegationStrategy} strategy
   * @param {boolean} disposable
   */
  subscribe(target, targetEvent, callback, strategy, disposable) {
    let delegatedHandlers;
    let capturedHandlers;
    let handlerEntry;

    if (strategy === delegationStrategy.bubbling) {
      delegatedHandlers = this.delegatedHandlers;
      handlerEntry = delegatedHandlers[targetEvent] || (delegatedHandlers[targetEvent] = new DelegateHandlerEntry(targetEvent, this.eventManager));
      let delegatedCallbacks = target.delegatedCallbacks || (target.delegatedCallbacks = {});
      if (!delegatedCallbacks[targetEvent]) {
        handlerEntry.increment();
      } else {
        emLogger.warn('Overriding previous callback for event listener', {event: targetEvent, callback: callback, previousCallback: delegatedCallbacks[targetEvent]});
      }
      delegatedCallbacks[targetEvent] = callback;

      if (disposable === true) {
        return new DelegationEntryHandler(handlerEntry, delegatedCallbacks, targetEvent);
      }

      return function() {
        handlerEntry.decrement();
        delegatedCallbacks[targetEvent] = null;
      };
    }
    if (strategy === delegationStrategy.capturing) {
      capturedHandlers = this.capturedHandlers;
      handlerEntry = capturedHandlers[targetEvent] || (capturedHandlers[targetEvent] = new CapturedHandlerEntry(targetEvent));
      let capturedCallbacks = target.capturedCallbacks || (target.capturedCallbacks = {});
      if (!capturedCallbacks[targetEvent]) {
        handlerEntry.increment();
      } else {
        emLogger.error('already have a callback for event', {event: targetEvent, callback: callback});
      }
      capturedCallbacks[targetEvent] = callback;

      if (disposable === true) {
        return new DelegationEntryHandler(handlerEntry, capturedCallbacks, targetEvent);
      }

      return function() {
        handlerEntry.decrement();
        capturedCallbacks[targetEvent] = null;
      };
    }

    target.addEventListener(targetEvent, callback);

    if (disposable === true) {
      return new EventHandler(target, targetEvent, callback);
    }

    return function() {
      target.removeEventListener(targetEvent, callback);
    };
  }
}

export const delegationStrategy = {
  none: 0,
  capturing: 1,
  bubbling: 2
};

export class EventManager {
  constructor(escapeShadowRoot = false) {
    this.elementHandlerLookup = {};
    this.eventStrategyLookup = {};
    this.escapeShadowRoot = escapeShadowRoot;

    this.registerElementConfig({
      tagName: 'input',
      properties: {
        value: ['change', 'input'],
        checked: ['change', 'input'],
        files: ['change', 'input']
      }
    });

    this.registerElementConfig({
      tagName: 'textarea',
      properties: {
        value: ['change', 'input']
      }
    });

    this.registerElementConfig({
      tagName: 'select',
      properties: {
        value: ['change']
      }
    });

    this.registerElementConfig({
      tagName: 'content editable',
      properties: {
        value: ['change', 'input', 'blur', 'keyup', 'paste']
      }
    });

    this.registerElementConfig({
      tagName: 'scrollable element',
      properties: {
        scrollTop: ['scroll'],
        scrollLeft: ['scroll']
      }
    });

    this.defaultEventStrategy = new DefaultEventStrategy(this);
  }

  registerElementConfig(config) {
    let tagName = config.tagName.toLowerCase();
    let properties = config.properties;
    let propertyName;

    let lookup = this.elementHandlerLookup[tagName] = {};

    for (propertyName in properties) {
      if (properties.hasOwnProperty(propertyName)) {
        lookup[propertyName] = properties[propertyName];
      }
    }
  }

  registerEventStrategy(eventName, strategy) {
    this.eventStrategyLookup[eventName] = strategy;
  }

  /**
   * @param {Element | object} target
   * @param {string} propertyName
   */
  getElementHandler(target, propertyName) {
    let tagName;
    let lookup = this.elementHandlerLookup;

    if (target.tagName) {
      tagName = target.tagName.toLowerCase();

      if (lookup[tagName] && lookup[tagName][propertyName]) {
        return new EventSubscriber(lookup[tagName][propertyName]);
      }

      if (propertyName === 'textContent' || propertyName === 'innerHTML') {
        return new EventSubscriber(lookup['content editable'].value);
      }

      if (propertyName === 'scrollTop' || propertyName === 'scrollLeft') {
        return new EventSubscriber(lookup['scrollable element'][propertyName]);
      }
    }

    return null;
  }

  /**
   * @param {EventTarget} target
   * @param {string} targetEvent
   * @param {EventListenerOrEventListenerObject} callbackOrListener
   * @param {delegationStrategy} delegate
   * @param {boolean} disposable
   */
  addEventListener(target, targetEvent, callbackOrListener, delegate, disposable) {
    return (this.eventStrategyLookup[targetEvent] || this.defaultEventStrategy)
      .subscribe(target, targetEvent, callbackOrListener, delegate, disposable);
  }
}

export class EventSubscriber {
  /**
   * @param {string[]} events
   */
  constructor(events) {
    this.events = events;
    this.element = null;
    this.handler = null;
  }

  /**
   * @param {Element} element
   * @param {EventListenerOrEventListenerObject} callbackOrListener
   */
  subscribe(element, callbackOrListener) {
    this.element = element;
    this.handler = callbackOrListener;

    let events = this.events;
    for (let i = 0, ii = events.length; ii > i; ++i) {
      element.addEventListener(events[i], callbackOrListener);
    }
  }

  dispose() {
    if (this.element === null) {
      // already disposed
      return;
    }
    let element = this.element;
    let callbackOrListener = this.handler;
    let events = this.events;
    for (let i = 0, ii = events.length; ii > i; ++i) {
      element.removeEventListener(events[i], callbackOrListener);
    }
    this.element = this.handler = null;
  }
}
<commit_msg>deepPath is a method not an array<commit_after>// @ts-check
import {DOM} from 'aurelia-pal';
import * as LogManager from 'aurelia-logging';

let emLogger = LogManager.getLogger('event-manager');
//Note: path and deepPath are designed to handle v0 and v1 shadow dom specs respectively
function findOriginalEventTarget(event) {
  return (event.composedPath && event.composedPath()[0]) || (event.deepPath && event.deepPath()[0]) || (event.path && event.path[0]) || event.target;
}

function stopPropagation() {
  this.standardStopPropagation();
  this.propagationStopped = true;
}

function handleCapturedEvent(event) {
  event.propagationStopped = false;
  let target = findOriginalEventTarget(event);

  let orderedCallbacks = [];
  /**
   * During capturing phase, event 'bubbles' down from parent. Needs to reorder callback from root down to target
   */
  while (target) {
    if (target.capturedCallbacks) {
      let callback = target.capturedCallbacks[event.type];
      if (callback) {
        if (event.stopPropagation !== stopPropagation) {
          event.standardStopPropagation = event.stopPropagation;
          event.stopPropagation = stopPropagation;
        }
        orderedCallbacks.push(callback);
      }
    }
    target = target.parentNode;
  }
  for (let i = orderedCallbacks.length - 1; i >= 0 && !event.propagationStopped; i--) {
    let orderedCallback = orderedCallbacks[i];
    if ('handleEvent' in orderedCallback) {
      orderedCallback.handleEvent(event);
    } else {
      orderedCallback(event);
    }
  }
}

class CapturedHandlerEntry {
  constructor(eventName) {
    this.eventName = eventName;
    this.count = 0;
  }

  increment() {
    this.count++;

    if (this.count === 1) {
      DOM.addEventListener(this.eventName, handleCapturedEvent, true);
    }
  }

  decrement() {
    if (this.count === 0) {
      emLogger.warn('The same EventListener was disposed multiple times.');
    } else if (--this.count === 0) {
      DOM.removeEventListener(this.eventName, handleCapturedEvent, true);
    }
  }
}

class DelegateHandlerEntry {
  constructor(eventName, eventManager) {
    this.eventName = eventName;
    this.count = 0;
    this.eventManager = eventManager;
  }

  handleEvent(event) {
    event.propagationStopped = false;
    let target = findOriginalEventTarget(event);

    while (target && !event.propagationStopped) {
      if (target.delegatedCallbacks) {
        let callback = target.delegatedCallbacks[event.type];
        if (callback) {
          if (event.stopPropagation !== stopPropagation) {
            event.standardStopPropagation = event.stopPropagation;
            event.stopPropagation = stopPropagation;
          }
          if ('handleEvent' in callback) {
            callback.handleEvent(event);
          } else {
            callback(event);
          }
        }
      }

      const parent = target.parentNode;
      const shouldEscapeShadowRoot = this.eventManager.escapeShadowRoot && parent instanceof ShadowRoot;

      target = shouldEscapeShadowRoot ? parent.host : parent;
    }
  }

  increment() {
    this.count++;

    if (this.count === 1) {
      DOM.addEventListener(this.eventName, this, false);
    }
  }

  decrement() {
    if (this.count === 0) {
      emLogger.warn('The same EventListener was disposed multiple times.');
    } else if (--this.count === 0) {
      DOM.removeEventListener(this.eventName, this, false);
    }
  }
}

/**
 * Enable dispose() pattern for `delegate` & `capture` commands
 */
class DelegationEntryHandler {
  /**
   * @param {DelegateHandlerEntry | CapturedHandlerEntry} entry
   * @param {Record<string, Function>} lookup
   * @param {string} targetEvent
   */
  constructor(entry, lookup, targetEvent) {
    this.entry = entry;
    this.lookup = lookup;
    this.targetEvent = targetEvent;
  }

  dispose() {
    if (this.lookup[this.targetEvent]) {
      this.entry.decrement();
      this.lookup[this.targetEvent] = null;
    } else {
      emLogger.warn('Calling .dispose() on already disposed eventListener');
    }
  }
}

/**
 * Enable dispose() pattern for addEventListener for `trigger`
 */
class EventHandler {
  /**
   * @param {Element} target
   * @param {string} targetEvent
   * @param {EventListenerOrEventListenerObject} callback
   */
  constructor(target, targetEvent, callback) {
    this.target = target;
    this.targetEvent = targetEvent;
    this.callback = callback;
  }

  dispose() {
    this.target.removeEventListener(this.targetEvent, this.callback);
  }
}

class DefaultEventStrategy {
  delegatedHandlers = {};
  capturedHandlers = {};

  constructor(eventManager) {
    this.eventManager = eventManager;
  }

  /**
   * @param {Element} target
   * @param {string} targetEvent
   * @param {EventListenerOrEventListenerObject} callback
   * @param {delegationStrategy} strategy
   * @param {boolean} disposable
   */
  subscribe(target, targetEvent, callback, strategy, disposable) {
    let delegatedHandlers;
    let capturedHandlers;
    let handlerEntry;

    if (strategy === delegationStrategy.bubbling) {
      delegatedHandlers = this.delegatedHandlers;
      handlerEntry = delegatedHandlers[targetEvent] || (delegatedHandlers[targetEvent] = new DelegateHandlerEntry(targetEvent, this.eventManager));
      let delegatedCallbacks = target.delegatedCallbacks || (target.delegatedCallbacks = {});
      if (!delegatedCallbacks[targetEvent]) {
        handlerEntry.increment();
      } else {
        emLogger.warn('Overriding previous callback for event listener', {event: targetEvent, callback: callback, previousCallback: delegatedCallbacks[targetEvent]});
      }
      delegatedCallbacks[targetEvent] = callback;

      if (disposable === true) {
        return new DelegationEntryHandler(handlerEntry, delegatedCallbacks, targetEvent);
      }

      return function() {
        handlerEntry.decrement();
        delegatedCallbacks[targetEvent] = null;
      };
    }
    if (strategy === delegationStrategy.capturing) {
      capturedHandlers = this.capturedHandlers;
      handlerEntry = capturedHandlers[targetEvent] || (capturedHandlers[targetEvent] = new CapturedHandlerEntry(targetEvent));
      let capturedCallbacks = target.capturedCallbacks || (target.capturedCallbacks = {});
      if (!capturedCallbacks[targetEvent]) {
        handlerEntry.increment();
      } else {
        emLogger.error('already have a callback for event', {event: targetEvent, callback: callback});
      }
      capturedCallbacks[targetEvent] = callback;

      if (disposable === true) {
        return new DelegationEntryHandler(handlerEntry, capturedCallbacks, targetEvent);
      }

      return function() {
        handlerEntry.decrement();
        capturedCallbacks[targetEvent] = null;
      };
    }

    target.addEventListener(targetEvent, callback);

    if (disposable === true) {
      return new EventHandler(target, targetEvent, callback);
    }

    return function() {
      target.removeEventListener(targetEvent, callback);
    };
  }
}

export const delegationStrategy = {
  none: 0,
  capturing: 1,
  bubbling: 2
};

export class EventManager {
  constructor(escapeShadowRoot = false) {
    this.elementHandlerLookup = {};
    this.eventStrategyLookup = {};
    this.escapeShadowRoot = escapeShadowRoot;

    this.registerElementConfig({
      tagName: 'input',
      properties: {
        value: ['change', 'input'],
        checked: ['change', 'input'],
        files: ['change', 'input']
      }
    });

    this.registerElementConfig({
      tagName: 'textarea',
      properties: {
        value: ['change', 'input']
      }
    });

    this.registerElementConfig({
      tagName: 'select',
      properties: {
        value: ['change']
      }
    });

    this.registerElementConfig({
      tagName: 'content editable',
      properties: {
        value: ['change', 'input', 'blur', 'keyup', 'paste']
      }
    });

    this.registerElementConfig({
      tagName: 'scrollable element',
      properties: {
        scrollTop: ['scroll'],
        scrollLeft: ['scroll']
      }
    });

    this.defaultEventStrategy = new DefaultEventStrategy(this);
  }

  registerElementConfig(config) {
    let tagName = config.tagName.toLowerCase();
    let properties = config.properties;
    let propertyName;

    let lookup = this.elementHandlerLookup[tagName] = {};

    for (propertyName in properties) {
      if (properties.hasOwnProperty(propertyName)) {
        lookup[propertyName] = properties[propertyName];
      }
    }
  }

  registerEventStrategy(eventName, strategy) {
    this.eventStrategyLookup[eventName] = strategy;
  }

  /**
   * @param {Element | object} target
   * @param {string} propertyName
   */
  getElementHandler(target, propertyName) {
    let tagName;
    let lookup = this.elementHandlerLookup;

    if (target.tagName) {
      tagName = target.tagName.toLowerCase();

      if (lookup[tagName] && lookup[tagName][propertyName]) {
        return new EventSubscriber(lookup[tagName][propertyName]);
      }

      if (propertyName === 'textContent' || propertyName === 'innerHTML') {
        return new EventSubscriber(lookup['content editable'].value);
      }

      if (propertyName === 'scrollTop' || propertyName === 'scrollLeft') {
        return new EventSubscriber(lookup['scrollable element'][propertyName]);
      }
    }

    return null;
  }

  /**
   * @param {EventTarget} target
   * @param {string} targetEvent
   * @param {EventListenerOrEventListenerObject} callbackOrListener
   * @param {delegationStrategy} delegate
   * @param {boolean} disposable
   */
  addEventListener(target, targetEvent, callbackOrListener, delegate, disposable) {
    return (this.eventStrategyLookup[targetEvent] || this.defaultEventStrategy)
      .subscribe(target, targetEvent, callbackOrListener, delegate, disposable);
  }
}

export class EventSubscriber {
  /**
   * @param {string[]} events
   */
  constructor(events) {
    this.events = events;
    this.element = null;
    this.handler = null;
  }

  /**
   * @param {Element} element
   * @param {EventListenerOrEventListenerObject} callbackOrListener
   */
  subscribe(element, callbackOrListener) {
    this.element = element;
    this.handler = callbackOrListener;

    let events = this.events;
    for (let i = 0, ii = events.length; ii > i; ++i) {
      element.addEventListener(events[i], callbackOrListener);
    }
  }

  dispose() {
    if (this.element === null) {
      // already disposed
      return;
    }
    let element = this.element;
    let callbackOrListener = this.handler;
    let events = this.events;
    for (let i = 0, ii = events.length; ii > i; ++i) {
      element.removeEventListener(events[i], callbackOrListener);
    }
    this.element = this.handler = null;
  }
}
<|endoftext|>