<commit_before>/*
 * marks.js - Simple, fast & extensible markup parsing, traversing & rendering.
 *
 * Copyright (c) 2008 - 2013, Juerg Lehni
 * http://lehni.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

(function(exports) {
'use strict';
exports.version = '0.5.0';

var contexts = {};
var encoders = {
	'none': function(value) {
		return value;
	}
};

exports.register = function(tags, entry) {
	if (typeof tags === 'object') {
		for (var key in tags)
			this.register(key, tags[key]);
	} else {
		// Allow simple form of only defining a render function
		if (typeof entry === 'function')
			entry = { render: entry };
		// Parse attributes definition through the same tag parsing
		// mechanism that Node is using. Node.parse contains
		// special logic to produce an info object for attribute / argument
		// parsing further down in Node.create().
		var attributes = entry.attributes = entry.attributes
					? Node.parse(entry.attributes, null, true)
					: null;
		// Create lookup per tag name for entry to be used.
		var context = entry.context || 'default';
		context = contexts[context] = contexts[context] || {};
		tags = tags.split(',');
		for (var i = 0; i < tags.length; i++)
			context[tags[i]] = entry;
	}
};

exports.encode = function(type, encoder) {
	if (typeof type === 'object') {
		for (var key in type)
			this.encode(key, type[key]);
	} else {
		encoders[type] = encoder;
	}
};

// Parses the passed markup text to a DOM tree contained in a Node with
// name 'root', which can be rendered through its render function. This
// object can be used for caching. But that is not a necessity since parsing
// is very fast.
exports.parse = function(text, options) {
	if (!text)
		return null;
	// Create the root tag as a container for all the other bits
	var rootTag = Node.create('root');
	var start = 0, end = 0;
	// Current tag:
	var tag = rootTag;
	function addString(str) {
		// 'Unescape' escape characters, such as '\<', '\>'
		tag.nodes.push(str.replace(/\\(.)/g, function(all, chr) {
			return chr;
		}));
	}
	while (start !== -1) {
		start = text.indexOf('<', end);
		// Filter out escaped '\<'
		while (start > 0 && text.charAt(start - 1) === '\\')
			start = text.indexOf('<', start + 1);
		if (start > end)
			addString(text.substring(end, start));
		if (start >= 0) {
			end = text.indexOf('>', start);
			// Filter out escaped '\>'
			while (end > 0 && text.charAt(end - 1) === '\\')
				end = text.indexOf('>', end + 1);
			if (end === -1) {
				// Non-closing tag. Add rest of string and bail out
				addString(text.substring(start));
				break;
			}
			end++; // Increase by one since we're using it for substring()
			var closing = text.charAt(start + 1) === '/';
			// empty = contentless tag: <tag/>
			var empty = !closing && text.charAt(end - 2) === '/';
			var definition = text.substring(start + (closing ? 2 : 1),
					end - (empty ? 2 : 1));
			// There is a special convention in place for empty tags:
			// These are interpretated as empty tags:
			// <tag/>, <tag />, <tag param />
			// Thes are not an empty tags. The / is regarded as part of the
			// parameter instead:
			// <tag param/ >, <tag param/>
			// This is to make unnamed url parameters easy to handle, among
			// other things. Detect this here:
			// If the tag definition contains white space, we have
			// parameters. If such a tag ended with / and the last char is
			// not white, it's not actually an empty tag but the / is  part
			// of the parameter:
			if (empty && /\s.*[^\s]$/.test(definition)) {
				empty = false;
				definition += '/';
			}
			var closeTag = null;
			if (!closing || empty) {
				// Opening tag, pass current tag as parent
				tag = Node.create(definition, tag, options);
				// If this tag does not allow nesting, search for its end
				// immediately now, all what's inbetween to its nodes and
				// close it straight away.
				if (!empty && tag.nesting === false) {
					// Search for closing tag
					var close = '</' + tag.name + '>';
					start = text.indexOf(close, end);
					if (start >= 0) {
						// Found it, add the part
						addString(text.substring(end, start));
						end = start + close.length;
						// Close this tag now (see below):
						closeTag = tag;
					}
				}
			}
			if (closing || empty) {
				// Closing tag
				closeTag = tag;
				// Walk up hierarchy until we find opening tag:
				while(!empty && closeTag && closeTag.name !== definition)
					closeTag = closeTag.parent;
			}
			if (closeTag && closeTag !== rootTag) {
				// Activate parent tag
				tag = closeTag.parent;
				// Add the closed tag to its parent's nodes and set its
				// index inside the nodes array, as required by renderNode()
				closeTag.index = tag.nodes.push(closeTag) - 1;
			}
		}
	}
	if (end > start)
		rootTag.nodes.push(text.substring(end));
	return rootTag;
};

	// Parses the passed text into a DOM tree and renders it directly.
exports.render = function(text, options) {
	var root = this.parse(text, options);
	return root && root.render(options) || '';
};

function Node(name, entry) {
	this.name = name;
	this.entry = entry;
	// Copy over functions so they can be called directly on the object.
	this.initialize = entry.initialize;
	this.render = entry.render;
}

Node.prototype = {
	renderNode: function(node, options, encoder) {
		if (node.render && (!options || !options.allowedTags
				|| options.allowedTags[node.name])) {
			// Prevent nodes from rendering more than once by caching the
			// result, useful e.g. when a tag uses renderNode to render
			// selected nodes.
			if (node.rendered)
				return node.rendered;
			// This is a tag, render its children first into one content
			// string
			var content = node.renderChildren(options, encoder);
			// Now render the tag itself and place it in the resulting
			// buffer
			return node.rendered = node.render(content, options, encoder,
					this.nodes[node.index - 1], this.nodes[node.index + 1]);
		} else {
			// A simple string. Just encode it
			return encoder(node);
		}
	},

	renderChildren: function(options, encoder, start, end) {
		var buffer = new Array(this.nodes.length);
		for (var i = start || 0, l = end || this.nodes.length; i < l; i++) {
			buffer[i] = this.renderNode(this.nodes[i], options, encoder);
		}
		return buffer.join('');
	},

	toString: function() {
		// Since nodes contains both strings and tags, join calls toString
		// on each of them, resulting in automatic toString recursion for
		// child tags.
		var content = this.nodes.join('');
		return '<' + this.definition + (content
				? '>' + content + '</' + this.name + '>'
				: '/>');
	},

	mergeAttributes: function(definition) {
		// definition is the result of parsing attribuets definition in inject.
		// If attributes were defined, use the attributes object produced by
		// Node.parse() now to scan through defined named attributes and
		// unnamed values, and use default values if available.
		var attributes = this.attributes;
		var values = this.values;
		var index = 0;
		var attribs = definition.attributes;
		for (var i = 0, l = attribs.length; i < l; i++) {
			var attrib = attribs[i];
			// If the tag does not define this predefined attribute,
			// either take its value from the unnamed values,
			// and increase index, or use its default value.
			if (attributes[attrib.name] === undefined) {
				// Use default value if running out of unnamed args
				attributes[attrib.name] = values
						&& index < values.length ? values[index++]
								: attrib.defaultValue;
			}
			// If the attributes definition does not contain any more
			// defaults and we are running out of unnamed values, we
			// might as well drop out of the loop since there won't be
			// anything to be done.
			if (!attrib.defaultsFollow && (!values || index >= values.length))
				break;
		}
		// Cut away consumed unnamed values
		if (values && index > 0)
			values.splice(0, index);
	},

	renderAttributes: function(attributes) {
		attributes = attributes || this.attributes;
		var parts = [];
		for (var key in attributes)
			parts.push(key + '="' + attributes[key] + '"');
		return parts.join(' ');
	}
};

Node.create = function(definition, parent, options) {
	// Parse tag definition for attributes (named)
	// and values (unnamed).
	var tag = definition === 'root'
			? new Node('root',
					contexts[options && options.context || 'default']['root']
					|| contexts['default']['root'])
			: Node.parse(definition, options);
	tag.parent = parent;
	tag.definition = definition;
	tag.nodes = [];
	// Setup children list, and previous / next references
	tag.children = [];
	if (parent) {
		var siblings = parent.children;
		tag.previous = siblings[siblings.length - 1];
		if (tag.previous)
			tag.previous.next = tag;
		siblings.push(tag);
	}
	if (tag.initialize)
		tag.initialize();
	return tag;
};

// Parses tag definitions (everything wihtout the trailing '<' & '>'). This is
// used both when creating a tag object from a tag string and when parsing
// attributes definitions, in which case collectAttributes  is set to true and
// the method collects different information, see bellow.
Node.parse = function(definition, options, collectAttributes) {
	// When collectAttributes is true, list holds the array of attribute
	// definitions and lookup the lowercase attribute name lookup table.
	var name = null;
	var list = [];
	var attribute = null;
	var attributes = {};
	var lookup = {};
	var entry;
	// Match either name= parts, string parts (supporting both ' and ", and
	// escaped quotes inside), and pure value parts (in collectAttributes mode
	// these can also be attribute names without default values):
	var parser = /(\w+)=|(["'](?:[^"'\\]*(?:\\["']|\\|(?=["']))+)*["'])|(\S+)/gm;
	// TODO: See which version is faster, replace or repeated calls to exec?
	// definition.replace(parser, function() {
	// 	var match = arguments;
	for (var match; match = parser.exec(definition);) {
		if (match[1]) { // attribute name
			attribute = match[1];
		} else { // string or value
			// Do not eval match[2] as it might contain line breaks which will
			// throw errors. Instead, just take away the quotes and filter out
			// escape chars.
			var value = match[2];
			value = value && value.substring(1, value.length - 1).replace(
						/\\/g, '')
					|| match[3];
			if (collectAttributes) {
				// When collecting attributes, use list array to store them
				if (!attribute) {
					// attribute with no default value
					attribute = value;
					value = undefined;
				}
				list.push({ name: attribute, defaultValue: value });
				// See if there's a lowercase version, and if so, put it into
				// the list of attributes name translation lookup:
				var lower = attribute.toLowerCase();
				if (lower !== attribute)
					lookup[lower] = attribute;
			} else {
				// Normal tag parsing: Find tag name, and store named attributes
				// and unnamed values.
				if (!name) {
					// The first value is the tag name. Once we know it, we can
					// determine the entry to be used and from there the attribs
					// definition and name translation lookup.
					name = value;
					// Render any undefined tag through the UndefinedTag.
					var context = options && contexts[options.context]
							|| contexts['default'];
					entry = context[name] || contexts['default'][name]
							|| context['undefined']
							|| contexts['default']['undefined'];
					// Now get the attribute name translation lookup:
					lookup = entry.attributes && entry.attributes.lookup
							|| lookup;
				} else if (attribute) {
					// named attribute. Use definition.lookup to translate to
					// mixed case name.
					attribute = lookup[attribute] || attribute;
					attributes[attribute] = value;
				} else { // unnamed argument
					list.push(value);
				}
			}
			// Reset attribute name again
			attribute = null;
		}
	}
	if (collectAttributes) {
		// Scan backwards to see wether further attributes define defaults.
		// This is needed to stop looping through attributes early once no more
		// defaults are available and unnamed values are used up.
		// See Node#create
		var defaultsFollow = false;
		for (var i = list.length - 1; i >= 0; i--) {
			list[i].defaultsFollow = defaultsFollow;
			defaultsFollow = defaultsFollow
					|| list[i].defaultValue !== undefined;
		}
		// Return both the attributes definition and the name lookup
		return list.length > 0 ? {
			attributes: list,
			lookup: lookup
		} : null;
	} else {
		// Normal tag parsing. Create tag out of parsed definition and merge
		// attributes definition into it.
		var tag = new Node(name, entry);
		tag.values = list;
		tag.attributes = attributes;
		// Merge attribute definitions with the one from the entry
		if (entry.attributes)
			tag.mergeAttributes(entry.attributes);
		return tag;
	}
};

// The root tag is there to contain all other markup tags and content nodes and
// is produced internally in and returned by tags.parse(). Call render on it
// to render the parsed tags tree.
exports.register({
	'root': {
		// The root tag's render function is different as it is used to render
		// the whole tree and does not receive content or encoder as parameters.
		render: function(options) {
			if (options && typeof options.allowedTags === 'string') {
				var names = options.allowedTags.split(',');
				var allowed = options.allowedTags = {};
				for (var i = 0, l = names.length; i < l; i++)
					allowed[names[i]] = true;
			}
			// Determine encoder to be used, default is not encoding anything:
			var encoder = options && options.encoding
					&& encoders[options.encoding] || encoders['none'];
			return this.renderChildren(options, encoder);
		}
	},

	// Special tag to handle the rendering of all the undefined tags.
	// By default it renders the markup unmodified, but can be overridden.
	'undefined': {
		render: function(content, options, encoder) {
			return encoder('<' + this.definition) + (content
					? encoder('>') + content + encoder('</' + this.name + '>')
					: encoder('/>'));
		}
	}
});

})(typeof exports !== 'undefined' ? exports : this.marks = {});
<commit_msg>Pump version to 1.0.0<commit_after>/*
 * marks.js - Simple, fast & extensible markup parsing, traversing & rendering.
 *
 * Copyright (c) 2008 - 2013, Juerg Lehni
 * http://lehni.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

(function(exports) {
'use strict';
exports.version = '1.0.0';

var contexts = {};
var encoders = {
	'none': function(value) {
		return value;
	}
};

exports.register = function(tags, entry) {
	if (typeof tags === 'object') {
		for (var key in tags)
			this.register(key, tags[key]);
	} else {
		// Allow simple form of only defining a render function
		if (typeof entry === 'function')
			entry = { render: entry };
		// Parse attributes definition through the same tag parsing
		// mechanism that Node is using. Node.parse contains
		// special logic to produce an info object for attribute / argument
		// parsing further down in Node.create().
		var attributes = entry.attributes = entry.attributes
					? Node.parse(entry.attributes, null, true)
					: null;
		// Create lookup per tag name for entry to be used.
		var context = entry.context || 'default';
		context = contexts[context] = contexts[context] || {};
		tags = tags.split(',');
		for (var i = 0; i < tags.length; i++)
			context[tags[i]] = entry;
	}
};

exports.encode = function(type, encoder) {
	if (typeof type === 'object') {
		for (var key in type)
			this.encode(key, type[key]);
	} else {
		encoders[type] = encoder;
	}
};

// Parses the passed markup text to a DOM tree contained in a Node with
// name 'root', which can be rendered through its render function. This
// object can be used for caching. But that is not a necessity since parsing
// is very fast.
exports.parse = function(text, options) {
	if (!text)
		return null;
	// Create the root tag as a container for all the other bits
	var rootTag = Node.create('root');
	var start = 0, end = 0;
	// Current tag:
	var tag = rootTag;
	function addString(str) {
		// 'Unescape' escape characters, such as '\<', '\>'
		tag.nodes.push(str.replace(/\\(.)/g, function(all, chr) {
			return chr;
		}));
	}
	while (start !== -1) {
		start = text.indexOf('<', end);
		// Filter out escaped '\<'
		while (start > 0 && text.charAt(start - 1) === '\\')
			start = text.indexOf('<', start + 1);
		if (start > end)
			addString(text.substring(end, start));
		if (start >= 0) {
			end = text.indexOf('>', start);
			// Filter out escaped '\>'
			while (end > 0 && text.charAt(end - 1) === '\\')
				end = text.indexOf('>', end + 1);
			if (end === -1) {
				// Non-closing tag. Add rest of string and bail out
				addString(text.substring(start));
				break;
			}
			end++; // Increase by one since we're using it for substring()
			var closing = text.charAt(start + 1) === '/';
			// empty = contentless tag: <tag/>
			var empty = !closing && text.charAt(end - 2) === '/';
			var definition = text.substring(start + (closing ? 2 : 1),
					end - (empty ? 2 : 1));
			// There is a special convention in place for empty tags:
			// These are interpretated as empty tags:
			// <tag/>, <tag />, <tag param />
			// Thes are not an empty tags. The / is regarded as part of the
			// parameter instead:
			// <tag param/ >, <tag param/>
			// This is to make unnamed url parameters easy to handle, among
			// other things. Detect this here:
			// If the tag definition contains white space, we have
			// parameters. If such a tag ended with / and the last char is
			// not white, it's not actually an empty tag but the / is  part
			// of the parameter:
			if (empty && /\s.*[^\s]$/.test(definition)) {
				empty = false;
				definition += '/';
			}
			var closeTag = null;
			if (!closing || empty) {
				// Opening tag, pass current tag as parent
				tag = Node.create(definition, tag, options);
				// If this tag does not allow nesting, search for its end
				// immediately now, all what's inbetween to its nodes and
				// close it straight away.
				if (!empty && tag.nesting === false) {
					// Search for closing tag
					var close = '</' + tag.name + '>';
					start = text.indexOf(close, end);
					if (start >= 0) {
						// Found it, add the part
						addString(text.substring(end, start));
						end = start + close.length;
						// Close this tag now (see below):
						closeTag = tag;
					}
				}
			}
			if (closing || empty) {
				// Closing tag
				closeTag = tag;
				// Walk up hierarchy until we find opening tag:
				while(!empty && closeTag && closeTag.name !== definition)
					closeTag = closeTag.parent;
			}
			if (closeTag && closeTag !== rootTag) {
				// Activate parent tag
				tag = closeTag.parent;
				// Add the closed tag to its parent's nodes and set its
				// index inside the nodes array, as required by renderNode()
				closeTag.index = tag.nodes.push(closeTag) - 1;
			}
		}
	}
	if (end > start)
		rootTag.nodes.push(text.substring(end));
	return rootTag;
};

	// Parses the passed text into a DOM tree and renders it directly.
exports.render = function(text, options) {
	var root = this.parse(text, options);
	return root && root.render(options) || '';
};

function Node(name, entry) {
	this.name = name;
	this.entry = entry;
	// Copy over functions so they can be called directly on the object.
	this.initialize = entry.initialize;
	this.render = entry.render;
}

Node.prototype = {
	renderNode: function(node, options, encoder) {
		if (node.render && (!options || !options.allowedTags
				|| options.allowedTags[node.name])) {
			// Prevent nodes from rendering more than once by caching the
			// result, useful e.g. when a tag uses renderNode to render
			// selected nodes.
			if (node.rendered)
				return node.rendered;
			// This is a tag, render its children first into one content
			// string
			var content = node.renderChildren(options, encoder);
			// Now render the tag itself and place it in the resulting
			// buffer
			return node.rendered = node.render(content, options, encoder,
					this.nodes[node.index - 1], this.nodes[node.index + 1]);
		} else {
			// A simple string. Just encode it
			return encoder(node);
		}
	},

	renderChildren: function(options, encoder, start, end) {
		var buffer = new Array(this.nodes.length);
		for (var i = start || 0, l = end || this.nodes.length; i < l; i++) {
			buffer[i] = this.renderNode(this.nodes[i], options, encoder);
		}
		return buffer.join('');
	},

	toString: function() {
		// Since nodes contains both strings and tags, join calls toString
		// on each of them, resulting in automatic toString recursion for
		// child tags.
		var content = this.nodes.join('');
		return '<' + this.definition + (content
				? '>' + content + '</' + this.name + '>'
				: '/>');
	},

	mergeAttributes: function(definition) {
		// definition is the result of parsing attribuets definition in inject.
		// If attributes were defined, use the attributes object produced by
		// Node.parse() now to scan through defined named attributes and
		// unnamed values, and use default values if available.
		var attributes = this.attributes;
		var values = this.values;
		var index = 0;
		var attribs = definition.attributes;
		for (var i = 0, l = attribs.length; i < l; i++) {
			var attrib = attribs[i];
			// If the tag does not define this predefined attribute,
			// either take its value from the unnamed values,
			// and increase index, or use its default value.
			if (attributes[attrib.name] === undefined) {
				// Use default value if running out of unnamed args
				attributes[attrib.name] = values
						&& index < values.length ? values[index++]
								: attrib.defaultValue;
			}
			// If the attributes definition does not contain any more
			// defaults and we are running out of unnamed values, we
			// might as well drop out of the loop since there won't be
			// anything to be done.
			if (!attrib.defaultsFollow && (!values || index >= values.length))
				break;
		}
		// Cut away consumed unnamed values
		if (values && index > 0)
			values.splice(0, index);
	},

	renderAttributes: function(attributes) {
		attributes = attributes || this.attributes;
		var parts = [];
		for (var key in attributes)
			parts.push(key + '="' + attributes[key] + '"');
		return parts.join(' ');
	}
};

Node.create = function(definition, parent, options) {
	// Parse tag definition for attributes (named)
	// and values (unnamed).
	var tag = definition === 'root'
			? new Node('root',
					contexts[options && options.context || 'default']['root']
					|| contexts['default']['root'])
			: Node.parse(definition, options);
	tag.parent = parent;
	tag.definition = definition;
	tag.nodes = [];
	// Setup children list, and previous / next references
	tag.children = [];
	if (parent) {
		var siblings = parent.children;
		tag.previous = siblings[siblings.length - 1];
		if (tag.previous)
			tag.previous.next = tag;
		siblings.push(tag);
	}
	if (tag.initialize)
		tag.initialize();
	return tag;
};

// Parses tag definitions (everything wihtout the trailing '<' & '>'). This is
// used both when creating a tag object from a tag string and when parsing
// attributes definitions, in which case collectAttributes  is set to true and
// the method collects different information, see bellow.
Node.parse = function(definition, options, collectAttributes) {
	// When collectAttributes is true, list holds the array of attribute
	// definitions and lookup the lowercase attribute name lookup table.
	var name = null;
	var list = [];
	var attribute = null;
	var attributes = {};
	var lookup = {};
	var entry;
	// Match either name= parts, string parts (supporting both ' and ", and
	// escaped quotes inside), and pure value parts (in collectAttributes mode
	// these can also be attribute names without default values):
	var parser = /(\w+)=|(["'](?:[^"'\\]*(?:\\["']|\\|(?=["']))+)*["'])|(\S+)/gm;
	// TODO: See which version is faster, replace or repeated calls to exec?
	// definition.replace(parser, function() {
	// 	var match = arguments;
	for (var match; match = parser.exec(definition);) {
		if (match[1]) { // attribute name
			attribute = match[1];
		} else { // string or value
			// Do not eval match[2] as it might contain line breaks which will
			// throw errors. Instead, just take away the quotes and filter out
			// escape chars.
			var value = match[2];
			value = value && value.substring(1, value.length - 1).replace(
						/\\/g, '')
					|| match[3];
			if (collectAttributes) {
				// When collecting attributes, use list array to store them
				if (!attribute) {
					// attribute with no default value
					attribute = value;
					value = undefined;
				}
				list.push({ name: attribute, defaultValue: value });
				// See if there's a lowercase version, and if so, put it into
				// the list of attributes name translation lookup:
				var lower = attribute.toLowerCase();
				if (lower !== attribute)
					lookup[lower] = attribute;
			} else {
				// Normal tag parsing: Find tag name, and store named attributes
				// and unnamed values.
				if (!name) {
					// The first value is the tag name. Once we know it, we can
					// determine the entry to be used and from there the attribs
					// definition and name translation lookup.
					name = value;
					// Render any undefined tag through the UndefinedTag.
					var context = options && contexts[options.context]
							|| contexts['default'];
					entry = context[name] || contexts['default'][name]
							|| context['undefined']
							|| contexts['default']['undefined'];
					// Now get the attribute name translation lookup:
					lookup = entry.attributes && entry.attributes.lookup
							|| lookup;
				} else if (attribute) {
					// named attribute. Use definition.lookup to translate to
					// mixed case name.
					attribute = lookup[attribute] || attribute;
					attributes[attribute] = value;
				} else { // unnamed argument
					list.push(value);
				}
			}
			// Reset attribute name again
			attribute = null;
		}
	}
	if (collectAttributes) {
		// Scan backwards to see wether further attributes define defaults.
		// This is needed to stop looping through attributes early once no more
		// defaults are available and unnamed values are used up.
		// See Node#create
		var defaultsFollow = false;
		for (var i = list.length - 1; i >= 0; i--) {
			list[i].defaultsFollow = defaultsFollow;
			defaultsFollow = defaultsFollow
					|| list[i].defaultValue !== undefined;
		}
		// Return both the attributes definition and the name lookup
		return list.length > 0 ? {
			attributes: list,
			lookup: lookup
		} : null;
	} else {
		// Normal tag parsing. Create tag out of parsed definition and merge
		// attributes definition into it.
		var tag = new Node(name, entry);
		tag.values = list;
		tag.attributes = attributes;
		// Merge attribute definitions with the one from the entry
		if (entry.attributes)
			tag.mergeAttributes(entry.attributes);
		return tag;
	}
};

// The root tag is there to contain all other markup tags and content nodes and
// is produced internally in and returned by tags.parse(). Call render on it
// to render the parsed tags tree.
exports.register({
	'root': {
		// The root tag's render function is different as it is used to render
		// the whole tree and does not receive content or encoder as parameters.
		render: function(options) {
			if (options && typeof options.allowedTags === 'string') {
				var names = options.allowedTags.split(',');
				var allowed = options.allowedTags = {};
				for (var i = 0, l = names.length; i < l; i++)
					allowed[names[i]] = true;
			}
			// Determine encoder to be used, default is not encoding anything:
			var encoder = options && options.encoding
					&& encoders[options.encoding] || encoders['none'];
			return this.renderChildren(options, encoder);
		}
	},

	// Special tag to handle the rendering of all the undefined tags.
	// By default it renders the markup unmodified, but can be overridden.
	'undefined': {
		render: function(content, options, encoder) {
			return encoder('<' + this.definition) + (content
					? encoder('>') + content + encoder('</' + this.name + '>')
					: encoder('/>'));
		}
	}
});

})(typeof exports !== 'undefined' ? exports : this.marks = {});
<|endoftext|>


<commit_before>var app = angular.module('mergely', []);

app.directive('mergelyEditor', function() {
  return {
    restrict: 'E',
    replace: true,
    template: '

',
    scope: {
      files: '=',
      mergeFiles: '='
      complete: '='
    },
    link: function($scope, element) {
      $scope.lset = null;
      $scope.rset = null;

      // Make list of filenames
      var filenames = Object.keys($scope.files);
      for (var f in $scope.mergeFiles) {
        filenames.push(f);
      }

      // This function is called once per file
      var file_index = 0;
      var nextFile = function() {
        var lcontent = $scopes.files{
        file_index++;
      }

      // We need to wait until lset and rset have been set
      var set_sets = 0;
      var set_set = function() {
        set_sets++;
        if (set_sets == 2) {
          // lset and rset set
          nextFile();
        }
      };

      $('#mergely-editor').mergely({
        cmsettings: { readOnly: false, lineNumbers: true },
        lhs: function(setValue) {
          $scope.lset = setValue;
          set_set();
        },
        rhs: function(setValue) {
          $scope.rset = setValue;
          set_set();
        }
      });
    }
  }
});
<commit_msg>Silly mistakes<commit_after>var app = angular.module('mergely', []);

app.directive('mergelyEditor', function() {
  return {
    restrict: 'E',
    replace: true,
    template: '

',
    scope: {
      files: '=',
      mergeFiles: '=',
      complete: '='
    },
    link: function($scope, element) {
      $scope.lset = null;
      $scope.rset = null;

      // Make list of filenames
      var filenames = Object.keys($scope.files);
      for (var f in $scope.mergeFiles) {
        filenames.push(f);
      }

      // This function is called once per file
      var file_index = 0;
      var nextFile = function() {
        var lcontent = $scopes.files;
        file_index++;
      }

      // We need to wait until lset and rset have been set
      var set_sets = 0;
      var set_set = function() {
        set_sets++;
        if (set_sets == 2) {
          // lset and rset set
          nextFile();
        }
      };

      $('#mergely-editor').mergely({
        cmsettings: { readOnly: false, lineNumbers: true },
        lhs: function(setValue) {
          $scope.lset = setValue;
          set_set();
        },
        rhs: function(setValue) {
          $scope.rset = setValue;
          set_set();
        }
      });
    }
  }
});
<|endoftext|>


<commit_before>(function (window, document) {
  'use strict';

  // Grab options from dumped JS on html
  var opt = window._agmOpt;
  // Expose some vars to a global namespace
  var AGM = window.AGM = {};

  function loadGoogleMap() {
    var mapCenter = new google.maps.LatLng(parseFloat(opt.map.lat), parseFloat(opt.map.lng));

    var mapOptions = {
      zoomControl: !opt.controls.zoomControl,
      zoomControlOptions: {
        position: google.maps.ControlPosition.RIGHT_CENTER
      },
      mapTypeControl: !opt.controls.mapTypeControl,
      streetViewControl: !opt.controls.streetViewControl,
      scrollwheel: !opt.mobile.scrollwheel,
      center: mapCenter,
      zoom: parseInt(opt.map.zoom),
      mapTypeId: google.maps.MapTypeId[opt.map.type],
      mapTypeControlOptions: {
        style: google.maps.MapTypeControlStyle.DROPDOWN_MENU,
        position: google.maps.ControlPosition.TOP_LEFT
      },
      styles: opt.map.styles,
      fullscreenControl: !opt.controls.fullscreenControl,
      fullscreenControlOptions: {
        position: google.maps.ControlPosition.RIGHT_TOP
      },
      gestureHandling: opt.mobile.gestureHandling || 'auto',
    };
    var map = new google.maps.Map(mapCanvas, mapOptions);

    /**
     * If marker is enabled
     */
    if (opt.marker.enabled === 1) {
      var marker = new google.maps.Marker({
        position: mapCenter,
        map: map,
        optimized: false,
        title: opt.marker.title,
        icon: opt.marker.file || opt.marker.color || ''
      });

      if (opt.marker.animation !== 'NONE') {
        marker.setAnimation(google.maps.Animation[opt.marker.animation])
      }

      /**
       * Info window needs marker to be enabled first
       */
      if (opt.info_window.enabled === 1) {
        var infoWindow = new google.maps.InfoWindow({content: opt.info_window.text});
        /**
         * Clicking on map will close info-window
         */
        google.maps.event.addListener(map, 'click', function () {
          infoWindow.close();
        });
      }
    }

    if (opt.marker.enabled === 1 && opt.info_window.enabled === 1) {
      /**
       * Clicking on marker will show info-window
       */
      google.maps.event.addListener(marker, 'click', function () {
        infoWindow.open(map, marker);
        marker.setAnimation(null);
      });
      /**
       * If info window enabled by default
       */
      if (opt.info_window.state === 1) {
        window.setTimeout(function () {
          infoWindow.open(map, marker);
          marker.setAnimation(null);
        }, 2000);
      }

    }

    var timeout;
    /**
     * Resize event handling, make map more responsive
     * Center map after 300 ms
     */
    google.maps.event.addDomListener(window, 'resize', function () {
      if (timeout) {
        clearTimeout(timeout);
      }
      timeout = window.setTimeout(function () {
        map.setCenter(mapCenter);
      }, 300);
    });

    // Lets expose them
    AGM.map = map;
    AGM.marker = marker;
    AGM.infoWindow = infoWindow;
    window.dispatchEvent(new Event('agm.loaded'));
  }

  var mapCanvas = document.getElementById('agm-canvas');
  if (typeof mapCanvas !== 'undefined' && mapCanvas) {
    if (typeof google === 'object' && google.maps) {
      google.maps.event.addDomListener(window, 'load', loadGoogleMap)
    }
    else {
      mapCanvas.innerHTML = '
Failed to load Google Map.
Please try again.
';
      mapCanvas.style.height = 'auto';
    }
  }

})(window, document);
<commit_msg>Improve dispatchEvent<commit_after>(function (window, document) {
  'use strict';

  // Grab options from dumped JS on html
  var opt = window._agmOpt;

  function loadGoogleMap() {
    var mapCenter = new google.maps.LatLng(parseFloat(opt.map.lat), parseFloat(opt.map.lng));

    var mapOptions = {
      zoomControl: !opt.controls.zoomControl,
      zoomControlOptions: {
        position: google.maps.ControlPosition.RIGHT_CENTER
      },
      mapTypeControl: !opt.controls.mapTypeControl,
      streetViewControl: !opt.controls.streetViewControl,
      scrollwheel: !opt.mobile.scrollwheel,
      center: mapCenter,
      zoom: parseInt(opt.map.zoom),
      mapTypeId: google.maps.MapTypeId[opt.map.type],
      mapTypeControlOptions: {
        style: google.maps.MapTypeControlStyle.DROPDOWN_MENU,
        position: google.maps.ControlPosition.TOP_LEFT
      },
      styles: opt.map.styles,
      fullscreenControl: !opt.controls.fullscreenControl,
      fullscreenControlOptions: {
        position: google.maps.ControlPosition.RIGHT_TOP
      },
      gestureHandling: opt.mobile.gestureHandling || 'auto',
    };
    var map = new google.maps.Map(mapCanvas, mapOptions);

    /**
     * If marker is enabled
     */
    if (opt.marker.enabled === 1) {
      var marker = new google.maps.Marker({
        position: mapCenter,
        map: map,
        optimized: false,
        title: opt.marker.title,
        icon: opt.marker.file || opt.marker.color || ''
      });

      if (opt.marker.animation !== 'NONE') {
        marker.setAnimation(google.maps.Animation[opt.marker.animation])
      }

      /**
       * Info window needs marker to be enabled first
       */
      if (opt.info_window.enabled === 1) {
        var infoWindow = new google.maps.InfoWindow({content: opt.info_window.text});
        /**
         * Clicking on map will close info-window
         */
        google.maps.event.addListener(map, 'click', function () {
          infoWindow.close();
        });
      }
    }

    if (opt.marker.enabled === 1 && opt.info_window.enabled === 1) {
      /**
       * Clicking on marker will show info-window
       */
      google.maps.event.addListener(marker, 'click', function () {
        infoWindow.open(map, marker);
        marker.setAnimation(null);
      });
      /**
       * If info window enabled by default
       */
      if (opt.info_window.state === 1) {
        window.setTimeout(function () {
          infoWindow.open(map, marker);
          marker.setAnimation(null);
        }, 2000);
      }

    }

    var timeout;
    /**
     * Resize event handling, make map more responsive
     * Center map after 300 ms
     */
    google.maps.event.addDomListener(window, 'resize', function () {
      if (timeout) {
        clearTimeout(timeout);
      }
      timeout = window.setTimeout(function () {
        map.setCenter(mapCenter);
      }, 300);
    });

    // Expose some vars to window
    window.AGM = {
      map: map,
      marker: marker,
      infoWindow: infoWindow
    };

    window.dispatchEvent(new CustomEvent('agm.loaded', {detail: AGM}));
  }

  var mapCanvas = document.getElementById('agm-canvas');
  if (typeof mapCanvas !== 'undefined' && mapCanvas) {
    if (typeof google === 'object' && google.maps) {
      google.maps.event.addDomListener(window, 'load', loadGoogleMap)
    }
    else {
      mapCanvas.innerHTML = '
Failed to load Google Map.
Please try again.
';
      mapCanvas.style.height = 'auto';
    }
  }

})(window, document);
<|endoftext|>


<commit_before>/* ==========================================================================
   jQuery plugin settings and other scripts
   ========================================================================== */

$(document).ready(function(){

  // FitVids init
  $("#main").fitVids();

  // init sticky sidebar
  $(".sticky").Stickyfill();

  var stickySideBar = function(){
    var windowWidth = $(window).width();
    if (windowWidth > 1024) {
      // fix
      Stickyfill.rebuild();
      Stickyfill.init();
    } else {
      // unfix
      Stickyfill.stop();
    }
  };

  stickySideBar();

  $(window).resize(function(){
    stickySideBar();
  });

  // Follow menu drop down

  $(".author__urls-wrapper button").on("click", function() {
    $(".author__urls").fadeToggle("fast", function() {});
    $(".author__urls-wrapper button").toggleClass("open");
  });

  // init smooth scroll
  $("a").smoothScroll({offset: -20});

  // add lightbox class to all image links
  $("a[href$='.jpg'],a[href$='.jpeg'],a[href$='.JPG'],a[href$='.png'],a[href$='.gif']").addClass("image-popup");

  // Magnific-Popup options
  $(".image-popup").magnificPopup({
    disableOn: function() {
      if( $(window).width() < 500 ) {
        return false;
      }
      return true;
    },
    type: 'image',
    tLoading: 'Loading image #%curr%...',
    gallery: {
      enabled: true,
      navigateByImgClick: true,
      preload: [0,1] // Will preload 0 - before current, and 1 after the current image
    },
    image: {
      tError: 'Image #%curr% could not be loaded.',
    },
    removalDelay: 500, // Delay in milliseconds before popup is removed
    // Class that is added to body when popup is open.
    // make it unique to apply your CSS animations just to this exact popup
    mainClass: 'mfp-zoom-in',
    callbacks: {
      beforeOpen: function() {
        // just a hack that adds mfp-anim class to markup
        this.st.image.markup = this.st.image.markup.replace('mfp-figure', 'mfp-figure mfp-with-anim');
      }
    },
    closeOnContentClick: true,
    midClick: true // allow opening popup on middle mouse click. Always set it to true if you don't provide alternative source.
  });

});<commit_msg>Fixed disappearing menu.<commit_after>/* ==========================================================================
   jQuery plugin settings and other scripts
   ========================================================================== */

$(document).ready(function(){

  // FitVids init
  $("#main").fitVids();

  // init sticky sidebar
  $(".sticky").Stickyfill();

  var stickySideBar = function(){
    var windowWidth = $(window).width();
    if (windowWidth > 1024) {
      // fix
      Stickyfill.rebuild();
      Stickyfill.init();
      $(".author__urls").show()
    } else {
      // unfix
      Stickyfill.stop();
      $(".author__urls").hide()
    }
  };

  stickySideBar();

  $(window).resize(function(){
    stickySideBar();
  });

  // Follow menu drop down

  $(".author__urls-wrapper button").on("click", function() {
    $(".author__urls").fadeToggle("fast", function() {});
    $(".author__urls-wrapper button").toggleClass("open");
  });

  // init smooth scroll
  $("a").smoothScroll({offset: -20});

  // add lightbox class to all image links
  $("a[href$='.jpg'],a[href$='.jpeg'],a[href$='.JPG'],a[href$='.png'],a[href$='.gif']").addClass("image-popup");

  // Magnific-Popup options
  $(".image-popup").magnificPopup({
    disableOn: function() {
      if( $(window).width() < 500 ) {
        return false;
      }
      return true;
    },
    type: 'image',
    tLoading: 'Loading image #%curr%...',
    gallery: {
      enabled: true,
      navigateByImgClick: true,
      preload: [0,1] // Will preload 0 - before current, and 1 after the current image
    },
    image: {
      tError: 'Image #%curr% could not be loaded.',
    },
    removalDelay: 500, // Delay in milliseconds before popup is removed
    // Class that is added to body when popup is open.
    // make it unique to apply your CSS animations just to this exact popup
    mainClass: 'mfp-zoom-in',
    callbacks: {
      beforeOpen: function() {
        // just a hack that adds mfp-anim class to markup
        this.st.image.markup = this.st.image.markup.replace('mfp-figure', 'mfp-figure mfp-with-anim');
      }
    },
    closeOnContentClick: true,
    midClick: true // allow opening popup on middle mouse click. Always set it to true if you don't provide alternative source.
  });

});
<|endoftext|>


<commit_before>/*! Plugin options and other jQuery stuff */

// dl-menu options
$(function() {
  $( '#dl-menu' ).dlmenu({
    animationClasses : { classin : 'dl-animate-in', classout : 'dl-animate-out' }
  });
});

// FitVids options
$(function() {
  $("article").fitVids();
});

$(".close-menu").click(function () {
  $(".menu").toggleClass("disabled");
  $(".links").toggleClass("enabled");
});

$(".about").click(function () {
  $("#about").css('display','block');
});

$(".close-about").click(function () {
  $("#about").css('display','');
});

// Add lightbox class to all image links
$("a[href$='.jpg'],a[href$='.jpeg'],a[href$='.JPG'],a[href$='.png'],a[href$='.gif']").addClass("image-popup");

// Magnific-Popup options
$(document).ready(function() {
  $('.image-popup').magnificPopup({
    type: 'image',
    tLoading: 'Loading image #%curr%...',
    gallery: {
      enabled: true,
      navigateByImgClick: true,
      preload: [0,1] // Will preload 0 - before current, and 1 after the current image
    },
    image: {
      tError: 'Image #%curr% could not be loaded.',
    },
    removalDelay: 300, // Delay in milliseconds before popup is removed
    // Class that is added to body when popup is open.
    // make it unique to apply your CSS animations just to this exact popup
    mainClass: 'mfp-fade'
  });
});

// header
$(document).ready(function(e) {
  $(window).scroll(function(){
    var header = $('.header-menu');
    var scroll = $(window).scrollTop();
    if(scroll > 300){
      header.attr('class', 'header-menu header-menu-overflow');
    } else {
      header.attr('class', 'header-menu header-menu-top');
    }
  });
});

//mobile menu
$(document).ready(function(){
  $("#menu").attr('style', '');
  $("#menu").mmenu({
    "extensions": [
      "border-full",
      "effect-zoom-menu",
      "effect-zoom-panels",
      "pageshadow",
      "theme-dark"
    ],
    "counters": true,
    "navbars": [
      {
        "position": "bottom",
        "content": [
          "",
          "",
          "",
          ""
        ]
      }
    ]
  });
});<commit_msg>Blog update<commit_after>/*! Plugin options and other jQuery stuff */

// dl-menu options
$(function() {
  $( '#dl-menu' ).dlmenu({
    animationClasses : { classin : 'dl-animate-in', classout : 'dl-animate-out' }
  });
});

// FitVids options
$(function() {
  $("article").fitVids();
});

$(".close-menu").click(function () {
  $(".menu").toggleClass("disabled");
  $(".links").toggleClass("enabled");
});

$(".about").click(function () {
  $("#about").css('display','block');
});

$(".close-about").click(function () {
  $("#about").css('display','');
});

// Add lightbox class to all image links
$("a[href$='.jpg'],a[href$='.jpeg'],a[href$='.JPG'],a[href$='.png'],a[href$='.gif']").addClass("image-popup");

// Magnific-Popup options
$(document).ready(function() {
  $('.image-popup').magnificPopup({
    type: 'image',
    tLoading: 'Loading image #%curr%...',
    gallery: {
      enabled: true,
      navigateByImgClick: true,
      preload: [0,1] // Will preload 0 - before current, and 1 after the current image
    },
    image: {
      tError: 'Image #%curr% could not be loaded.',
    },
    removalDelay: 300, // Delay in milliseconds before popup is removed
    // Class that is added to body when popup is open.
    // make it unique to apply your CSS animations just to this exact popup
    mainClass: 'mfp-fade'
  });
});

// header
$(document).ready(function(e) {
  $(window).scroll(function(){
    var header = $('.header-menu');
    var scroll = $(window).scrollTop();
    if(scroll > 300){
      header.attr('class', 'header-menu header-menu-overflow');
    } else {
      header.attr('class', 'header-menu header-menu-top');
    }
  });
});

//mobile menu
$(document).ready(function(){
  $("#menu").attr('style', '');
  $("#menu").mmenu({
    "extensions": [
      "border-full",
      "effect-zoom-menu",
      "effect-zoom-panels",
      "pageshadow",
      "theme-dark"
    ],
    "counters": true
  });
});<|endoftext|>


<commit_before>require.config({
    packages: [
        {
            name: 'echarts',
            location: '//ecomfe.github.io/echarts/src',
            main: 'echarts'
        },
        {
            name: 'zrender',
            location: '//ecomfe.github.io/zrender/src',
            main: 'zrender'
        }
    ]
});

var mapOption = {
    title : {
        text: 'Kasus Korupsi di Indonesia Berdasarkan Provinsi (Semester I 2014)',
        subtext: 'Data from ICW',
        sublink: 'http://www.census.gov/popest/data/datasets.html',
        x:'right'
    },
    tooltip : {
        trigger: 'item',
        showDelay: 0,
        transitionDuration: 0.2,
        formatter : function (params) {
            var value = (params.value + '').split('.');
            value = value[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g, '$1,');
            return params.seriesName + '
' + params.name + ' : ' + value;
        }
    },
    dataRange: {
        x : 'right',
        min: 500000,
        max: 38000000,
        color: ['orangered','yellow','lightskyblue'],
        text:['High','Low'],
        calculable : true
    },
    toolbox: {
        show : true,
        //orient : 'vertical',
        x: 'left',
        y: 'top',
        feature : {
            mark : {show: true},
            dataView : {show: true, readOnly: false},
            restore : {show: true},
            saveAsImage : {show: true}
        }
    },
    series : [
        {
            name: 'USA PopEstimates',
            type: 'map',
            roam: true,
            mapType: 'USA',
            itemStyle:{
                emphasis:{label:{show:true}}
            },
            textFixed : {
                Alaska : [20, -20]
            },
            data:[
                {name : 'Aceh', value : 7},
				{name : 'Kalimantan Timur', value : 4},
				{name : 'Kalimantan Selatan', value : 3},
				{name : 'Sulawesi Barat', value : 3},
				{name : 'Sulawesi Tengah', value : 3},
				{name : 'Nusa Tenggara Barat', value : 4},
				{name : 'Sulawesi Tenggara', value : 4},
				{name : 'Sulawesi Utara', value : 4},
				{name : 'Bangka-Belitung', value : 5},
				{name : 'Lampung', value : 5},
				{name : 'Irian Jaya Barat', value : 5},
				{name : 'Bali', value : 6},
				{name : 'Bengkulu', value : 6},
				{name : 'Gorontalo', value : 6},
				{name : 'Kepulauan Riau', value : 6},
				{name : 'Maluku', value : 6},
				{name : 'Nusa Tenggara Timur', value : 6},
				{name : 'Sumatera Selatan', value : 6},
				{name : 'Yogyakarta', value : 7},
				{name : 'Jakarta Raya', value : 8},
				{name : 'Kalimantan Barat', value : 8},
				{name : 'Maluku Utara', value : 8},
				{name : 'Sumatera Utara', value : 9},
				{name : 'Banten', value : 10},
				{name : 'Sulawesi Selatan', value : 10},
				{name : 'Kalimantan Tengah', value : 12},
				{name : 'Papua', value : 12},
				{name : 'Sumatera Barat', value : 12},
				{name : 'Jambi', value : 14},
				{name : 'Riau', value : 16},
				{name : 'Jawa Timur', value : 21},
				{name : 'Jawa Barat', value : 25},
				{name : 'jawa Tengah', value : 29},
				{name : 'Aceh', value : 20000000},
            ]
        }
    ]
};

var modusOption = {
    title : {
        text: '世界人口总量',
        subtext: '数据来自网络'
    },
    tooltip : {
        trigger: 'axis'
    },
    legend: {
        data:['2011年', '2012年']
    },
    toolbox: {
        show : true,
        feature : {
            mark : {show: true},
            dataView : {show: true, readOnly: false},
            magicType: {show: true, type: ['line', 'bar']},
            restore : {show: true},
            saveAsImage : {show: true}
        }
    },
    calculable : true,
    xAxis : [
        {
            type : 'value',
            boundaryGap : [0, 0.01]
        }
    ],
    yAxis : [
        {
            type : 'category',
            data : ['Anggaran Ganda','Pungutan Liar','Suap','Kegiatan Fiktif','Pemotongan Anggaran','Penyalahgunaan Wewenang','Mark up','Laporan Fiktif','Penggelapan','Penyalahgunaan Anggaran']
        }
    ],
    series : [
        {
            //name:'2011年',
            type:'bar',
            data:[1, 8, 11, 12, 15, 20,33,66,71,71]
        }
    ]
};

require(
    [
        'echarts',
        'echarts/chart/map',
        'echarts/chart/bar'
    ],
    function (ec) {
        require('echarts/util/mapData/params').params.USA = {
		    getGeoJson: function (callback) {
                $.getJSON('assets/js/geojson.json', callback); // Offline
                // $.getJSON('//bitbucket.org/rifani/geojson-political-indonesia/raw/0e89dcb0b0454c5afffd414fd0cd0c25f1688d10/IDN_adm_1_province.json', callback); // Online
		    }
		}

        var mapChart = ec.init(document.getElementById('map'));

        mapChart.setOption(mapOption);

        var modusChart = ec.init(document.getElementById('modus-chart'));
        modusChart.setOption(modusOption);

        var sectorChart = ec.init(document.getElementById('sector-chart'));
        sectorChart.setOption(modusOption);

        var institutionChart = ec.init(document.getElementById('institution-chart'));
        institutionChart.setOption(modusOption);

    }
);<commit_msg>edit province bug (aceh, jawa tengah)<commit_after>require.config({
    packages: [
        {
            name: 'echarts',
            location: '//ecomfe.github.io/echarts/src',
            main: 'echarts'
        },
        {
            name: 'zrender',
            location: '//ecomfe.github.io/zrender/src',
            main: 'zrender'
        }
    ]
});

var mapOption = {
    title : {
        text: 'Kasus Korupsi di Indonesia Berdasarkan Provinsi (Semester I 2014)',
        subtext: 'Data from ICW',
        sublink: 'http://www.census.gov/popest/data/datasets.html',
        x:'right'
    },
    tooltip : {
        trigger: 'item',
        showDelay: 0,
        transitionDuration: 0.2,
        formatter : function (params) {
            var value = (params.value + '').split('.');
            value = value[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g, '$1,');
            return params.seriesName + '
' + params.name + ' : ' + value;
        }
    },
    dataRange: {
        x : 'right',
        min: 500000,
        max: 38000000,
        color: ['orangered','yellow','lightskyblue'],
        text:['High','Low'],
        calculable : true
    },
    toolbox: {
        show : true,
        //orient : 'vertical',
        x: 'left',
        y: 'top',
        feature : {
            mark : {show: true},
            dataView : {show: true, readOnly: false},
            restore : {show: true},
            saveAsImage : {show: true}
        }
    },
    series : [
        {
            name: 'USA PopEstimates',
            type: 'map',
            roam: true,
            mapType: 'USA',
            itemStyle:{
                emphasis:{label:{show:true}}
            },
            textFixed : {
                Alaska : [20, -20]
            },
            data:[
                {name : 'Aceh', value : 7},
				{name : 'Kalimantan Timur', value : 4},
				{name : 'Kalimantan Selatan', value : 3},
				{name : 'Sulawesi Barat', value : 3},
				{name : 'Sulawesi Tengah', value : 3},
				{name : 'Nusa Tenggara Barat', value : 4},
				{name : 'Sulawesi Tenggara', value : 4},
				{name : 'Sulawesi Utara', value : 4},
				{name : 'Bangka-Belitung', value : 5},
				{name : 'Lampung', value : 5},
				{name : 'Irian Jaya Barat', value : 5},
				{name : 'Bali', value : 6},
				{name : 'Bengkulu', value : 6},
				{name : 'Gorontalo', value : 6},
				{name : 'Kepulauan Riau', value : 6},
				{name : 'Maluku', value : 6},
				{name : 'Nusa Tenggara Timur', value : 6},
				{name : 'Sumatera Selatan', value : 6},
				{name : 'Yogyakarta', value : 7},
				{name : 'Jakarta Raya', value : 8},
				{name : 'Kalimantan Barat', value : 8},
				{name : 'Maluku Utara', value : 8},
				{name : 'Sumatera Utara', value : 9},
				{name : 'Banten', value : 10},
				{name : 'Sulawesi Selatan', value : 10},
				{name : 'Kalimantan Tengah', value : 12},
				{name : 'Papua', value : 12},
				{name : 'Sumatera Barat', value : 12},
				{name : 'Jambi', value : 14},
				{name : 'Riau', value : 16},
				{name : 'Jawa Timur', value : 21},
				{name : 'Jawa Barat', value : 25},
				{name : 'Jawa Tengah', value : 29},
            ]
        }
    ]
};

var modusOption = {
    title : {
        text: '世界人口总量',
        subtext: '数据来自网络'
    },
    tooltip : {
        trigger: 'axis'
    },
    legend: {
        data:['2011年', '2012年']
    },
    toolbox: {
        show : true,
        feature : {
            mark : {show: true},
            dataView : {show: true, readOnly: false},
            magicType: {show: true, type: ['line', 'bar']},
            restore : {show: true},
            saveAsImage : {show: true}
        }
    },
    calculable : true,
    xAxis : [
        {
            type : 'value',
            boundaryGap : [0, 0.01]
        }
    ],
    yAxis : [
        {
            type : 'category',
            data : ['Anggaran Ganda','Pungutan Liar','Suap','Kegiatan Fiktif','Pemotongan Anggaran','Penyalahgunaan Wewenang','Mark up','Laporan Fiktif','Penggelapan','Penyalahgunaan Anggaran']
        }
    ],
    series : [
        {
            //name:'2011年',
            type:'bar',
            data:[1, 8, 11, 12, 15, 20,33,66,71,71]
        }
    ]
};

require(
    [
        'echarts',
        'echarts/chart/map',
        'echarts/chart/bar'
    ],
    function (ec) {
        require('echarts/util/mapData/params').params.USA = {
		    getGeoJson: function (callback) {
                $.getJSON('assets/js/geojson.json', callback); // Offline
                // $.getJSON('//bitbucket.org/rifani/geojson-political-indonesia/raw/0e89dcb0b0454c5afffd414fd0cd0c25f1688d10/IDN_adm_1_province.json', callback); // Online
		    }
		}

        var mapChart = ec.init(document.getElementById('map'));

        mapChart.setOption(mapOption);

        var modusChart = ec.init(document.getElementById('modus-chart'));
        modusChart.setOption(modusOption);

        var sectorChart = ec.init(document.getElementById('sector-chart'));
        sectorChart.setOption(modusOption);

        var institutionChart = ec.init(document.getElementById('institution-chart'));
        institutionChart.setOption(modusOption);

    }
);<|endoftext|>


<commit_before>jQuery(function($) {
  'use strict';

  const $formInput = $('input');
  const $tourForm = $('#subscribe-tour-form');
  const $contactForm = $('#subscribe-contact-form');
  const route = window.location.pathname;

  // $('.required').each(function () {
  //   if ($(this).val === '') {
  //     $('#submit-form').addClass('disabled');
  //   } else {
  //     $('#submit-form').removeClass('disabled');
  //   }
  // });
  //  TO DO:
  //    disable the button if required fields are empty.
  //    validate email value in order to avoid untranslatable errors from MailChimp.

  $('#mce-PHONE').on('input', function() {
    let $input = $(this);
    let regExp = /^[\+]?[(]?[0-9]{3}[)]?[0-9]*$/;
    let isPhone = regExp.test($input.val());
    let $genErr = $('#phone-error');
    let errMsg = '';
    let formName = 'contact' || 'tour';

    if (isPhone) {
      $genErr.html(`<p>${errMsg}</p>`);
      $('#submit-form').removeClass('disabled');
    } else {
        if (route === `/${formName}`) {
          errMsg = 'Unacceptable value';
        } else if (route === `/ru/${formName}`) {
          errMsg = 'Недопустимое значение';
        } else {
          errMsg = 'Valoare inacceptabilă';
        }
          $genErr.html(`<p>${errMsg}</p>`);
          $('#submit-form').addClass('disabled');

    }
  });

  if ($contactForm.length) {
    /**
     * Contact form
     */
    $contactForm.MailChimpForm({
      url: '//404.us11.list-manage.com/subscribe/post?u=13a7a5fca813b378c24ec9fe3&id=092d77b13b',
      fields: '1:NAME,4:PHONE,0:EMAIL,2:MESSAGE',
      submitSelector: '#submit-form',
      onFail: function (errMsg) {
        let $genErr = $('#mc-general-error');

        if (route === '/ru/contact/'){
          translation(errMsg, 'ru');
        } else if (route === '/ro/contact/'){
          translation(errMsg, 'ro');
        } else {
          $genErr.html(`<div class="error-mc">${errMsg}</div>`);
        }
      },
      onOk: function(okMsg) {

        if (route === '/contact'){
          window.location.href = '/thank-you'
        } else if (route === '/ru/contact'){
          window.location.href = '/ru/thank-you'
        } else if (route === '/ro/contact'){
          window.location.href = '/ro/thank-you'
        }
      }
    });
  }

  if ($tourForm.length) {
    /**
     * Tour form
     */
    $tourForm.MailChimpForm({
      url: '//mitocgroup.us11.list-manage.com/subscribe/post?u=13a7a5fca813b378c24ec9fe3&id=de06a08172',
      fields: '0:EMAIL,1:FNAME,2:LNAME,3:PHONE,4:JOB,5:VDATE,6:MMERGE3,7:LANG',
      submitSelector: '#submit-form',
      onFail: function (errMsg) {
        let $genErr = $('#mc-general-error');
        let lnId = $( "input:checked" ).attr("id");

        if (lnId === 'romBtn') {
          translation(errMsg, 'ro');
        } else if (lnId === 'ruBtn') {
          translation(errMsg, 'ru');
        } else {
          $genErr.html(`<div class="error-mc">${errMsg}</div>`);
        }
      },
      onOk: function(okMsg) {
        let lnId = $( "input:checked" ).attr("id");
        console.log(route);
        if (lnId === 'engBtn' && (route === '/tour' || route === '/ru/tour' || '/ro/tour'))  {
          window.location.href = '/thank-you'
        }
        else if (lnId === 'romBtn' && (route === '/tour' || route === '/ru/tour' || '/ro/tour')) {
          window.location.href = '/ro/thank-you'
        }
        else if (lnId === 'ruBtn' && (route === '/tour' || route === '/ru/tour' || '/ro/tour')) {
          window.location.href = '/ru/thank-you'
        }
      }
    });
  }

  /**
   * mc:input:error event handler
   */
  $formInput.on('mc:input:error', function() {
    console.log('mc:input:error event fired');
  });

  /**
   * mc:input:ok event handler
   */
  $formInput.on('mc:input:ok', function() {
    console.log('mc:input:ok event fired');
  });
});

function translation(errMsg,lng) {
  let tr = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${lng}&dt=t&q=${encodeURI(errMsg)}`;

    fetch(`${tr}`)
    .then(function(response) {
      return response.json();
    })
    .then(function(myJson) {
      errMsg = myJson[0][0][0];
      $genErr.html(`<div class="error-mc">${errMsg}</div>`);
    });
}
<commit_msg>securing form's link<commit_after>jQuery(function($) {
  'use strict';

  const $formInput = $('input');
  const $tourForm = $('#subscribe-tour-form');
  const $contactForm = $('#subscribe-contact-form');
  const route = window.location.pathname;

  // $('.required').each(function () {
  //   if ($(this).val === '') {
  //     $('#submit-form').addClass('disabled');
  //   } else {
  //     $('#submit-form').removeClass('disabled');
  //   }
  // });
  //  TO DO:
  //    disable the button if required fields are empty.
  //    validate email value in order to avoid untranslatable errors from MailChimp.

  $('#mce-PHONE').on('input', function() {
    let $input = $(this);
    let regExp = /^[\+]?[(]?[0-9]{3}[)]?[0-9]*$/;
    let isPhone = regExp.test($input.val());
    let $genErr = $('#phone-error');
    let errMsg = '';
    let formName = 'contact' || 'tour';

    if (isPhone) {
      $genErr.html(`<p>${errMsg}</p>`);
      $('#submit-form').removeClass('disabled');
    } else {
        if (route === `/${formName}`) {
          errMsg = 'Unacceptable value';
        } else if (route === `/ru/${formName}`) {
          errMsg = 'Недопустимое значение';
        } else {
          errMsg = 'Valoare inacceptabilă';
        }
          $genErr.html(`<p>${errMsg}</p>`);
          $('#submit-form').addClass('disabled');

    }
  });

  if ($contactForm.length) {
    /**
     * Contact form
     */
    $contactForm.MailChimpForm({
      url: '//404.us11.list-manage.com/subscribe/post?u=13a7a5fca813b378c24ec9fe3&id=092d77b13b',
      fields: '1:NAME,4:PHONE,0:EMAIL,2:MESSAGE',
      submitSelector: '#submit-form',
      onFail: function (errMsg) {
        let $genErr = $('#mc-general-error');

        if (route === '/ru/contact/'){
          translation(errMsg, 'ru');
        } else if (route === '/ro/contact/'){
          translation(errMsg, 'ro');
        } else {
          $genErr.html(`<div class="error-mc">${errMsg}</div>`);
        }
      },
      onOk: function(okMsg) {

        if (route === '/contact'){
          window.location.href = '/thank-you'
        } else if (route === '/ru/contact'){
          window.location.href = '/ru/thank-you'
        } else if (route === '/ro/contact'){
          window.location.href = '/ro/thank-you'
        }
      }
    });
  }

  if ($tourForm.length) {
    /**
     * Tour form
     */
    $tourForm.MailChimpForm({
      url: '//mitocgroup.us11.list-manage.com/subscribe/post?u=13a7a5fca813b378c24ec9fe3&id=de06a08172',
      fields: '0:EMAIL,1:FNAME,2:LNAME,3:PHONE,4:JOB,5:VDATE,6:MMERGE3,7:LANG',
      submitSelector: '#submit-form',
      onFail: function (errMsg) {
        let $genErr = $('#mc-general-error');
        let lnId = $( "input:checked" ).attr("id");

        if (lnId === 'romBtn') {
          translation(errMsg, 'ro');
        } else if (lnId === 'ruBtn') {
          translation(errMsg, 'ru');
        } else {
          $genErr.html(`<div class="error-mc">${errMsg}</div>`);
        }
      },
      onOk: function(okMsg) {
        let lnId = $( "input:checked" ).attr("id");
        console.log(route);
        if (lnId === 'engBtn' && (route === '/tour' || route === '/ru/tour' || '/ro/tour'))  {
          window.location.href = '/thank-you'
        }
        else if (lnId === 'romBtn' && (route === '/tour' || route === '/ru/tour' || '/ro/tour')) {
          window.location.href = '/ro/thank-you'
        }
        else if (lnId === 'ruBtn' && (route === '/tour' || route === '/ru/tour' || '/ro/tour')) {
          window.location.href = '/ru/thank-you'
        }
      }
    });
  }

  /**
   * mc:input:error event handler
   */
  $formInput.on('mc:input:error', function() {
    console.log('mc:input:error event fired');
  });

  /**
   * mc:input:ok event handler
   */
  $formInput.on('mc:input:ok', function() {
    console.log('mc:input:ok event fired');
  });
});

function translation(errMsg,lng) {
  let tr = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${lng}&dt=t&q=${encodeURI(errMsg)}`;

    fetch(`${tr}`)
    .then(function(response) {
      return response.json();
    })
    .then(function(myJson) {
      errMsg = myJson[0][0][0];
      $genErr.html(`<div class="error-mc">${errMsg}</div>`);
    });
}
<|endoftext|>


<commit_before>// Third party libraries
import * as L from 'leaflet';
import $ from 'jquery';

// Local modules
import makeFilters from './filters';
import updateTable from './table-generator';
import updateRankings from './ranking-table';

// Constants
var MOBILE_WIDTH = 600;

// DOM refs
var autocomplete,
    $infoTip = $('#info'),
    $loading = $('#loading'),
    $map = $('#map'),
    $closeButton = $('#close-button'),
    $filterSelect = $('#demographic-select'),
    $countiesSelect = $('#counties-selector-holder'),
    $resultsSummary = $('#results-summary');

// State
var selectedBucket = 'all',
    selectedCounty = 'all counties',
    activePrecincts = [],
    features = [],
    geojson,
    interactiveLayer,
    aggStats,
    year = 2012; //TODO change this for 2016!!

// Helper functions
function highlight(el) {
    $('.filter-selected').attr('class', 'filter-bar')
    $(el).attr('class', 'filter-selected')
};

$map.hide(); // Map is hidden until it's done loading
toggleMobile(); // Check size of display and display precinct information accordingly

// Create Leaflet map and get tiles from Carto
var map = L.map('map', {minZoom: 9, scrollWheelZoom: false});
map.setView({ lat: 33.74, lng: -84.38}, 10);

// Use panes to "sandwich" GeoJSON features between map tiles and road labels
map.createPane('labels');
map.getPane('labels').style.zIndex = 650;
map.getPane('labels').style.pointerEvents = 'none';

// Attribution
var cartodbAttribution = '© OpenStreetMap contributors, © CartoDB';

// Labels
L.tileLayer('http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png', {pane: 'labels', attribution: cartodbAttribution}).addTo(map);

// Create base map
L.tileLayer('http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png', {attribution: cartodbAttribution}).addTo(map);

function updateFilter(filter) {
  // County filters and demographic filters are both set by this function,
  // so we need to check if the given filter is a county or not
  var counties = ['Clayton', 'DeKalb', 'Fulton', 'Gwinnett', 'Cobb', 'All counties', 'all counties']
  if (counties.indexOf(filter) === -1) {
    selectedBucket = filter; // Get the filter from the data-filter attribute
  }
  else {
    selectedCounty = filter;
  }

  // Empty the activePrecincts array so that we can fill in only the
  // precincts that meet the current precinct criteria
  activePrecincts = [];

  // Loop through features in the geoJSON layer (ie the precincts)
  geojson.eachLayer(function (layer) {
    var layerRace = layer.feature.properties['2012_pre_2'],
        layerCounty = layer.feature.properties.COUNTY_NAM,
        layerIncome;

    var income = layer.feature.properties['2012_pre_1']; // Assign income to high middle or low bucket

    // TODO bin this in the data so I don't have to do it here
    if (income < 50000) {
      layerIncome = 'low';
    }
    else if (income < 100000) {
      layerIncome = 'mid';
    }
    else if (income > 100000) {
      layerIncome = 'high'
    }

    // Check if each precinct meets the filter criteria, and change its fill color accordingly
    if (layerCounty === selectedCounty.toUpperCase() || selectedCounty.toUpperCase() === 'ALL COUNTIES') {
      if (layerRace === selectedBucket ||
      layerIncome === selectedBucket ||
      selectedBucket === 'all') {
        layer.setStyle(setColor(layer.feature));
        activePrecincts.push(layer);
      }
      else {
        layer.setStyle({fillOpacity: 0});
      };
    }
    else {
      layer.setStyle({fillOpacity: 0});
    };
  });

  // Update the summary table results for the given filter
  updateTitle(selectedBucket);
  updateTable($resultsSummary, aggStats[selectedCounty.toUpperCase()][selectedBucket], year);
  updateRankings(activePrecincts, selectedCounty, selectedBucket);
}
// Get shapefiles
function getPrecincts(cb) {
  $.ajax({
    dataType: 'json',
    //url: './2014_precincts_income_race_simple.min.json',
    url: './2014_precincts_income_raceUPDATE.json',
    success: function(data) {
      cb(data)
    },
    failure: function() {
      console.log('failed to get precincts.');
    }
  });
};

// push precincts from .json file to list of features so that Leaflet can render them.
function addPrecincts(layer) {
  features = layer;
  console.log(features);
  generateLayers();
  createMap();
  getAggregatedData();
}

/* Generate a map and a list of filter options */
function createMap() {
  // Render filters for demographic data like race and income

  // Default to display all precincts without any filtering
  $('.filter[data-filter="all"]').attr('class', 'filter-selected');

  // Add the geoJSON data to the map, hide the loading screen, and update the summary table
  geojson.addTo(map);

  // Add event listeners to filter precincts by the given criteria when clicked

  $('#county-select').change(function() {
    var value = $(this).val();
    updateFilter(value);
    map.setView({ lat: 33.74, lng: -84.38}, 10);
  })

  createInfo(); // Create the info box that displays precinct information
  $infoTip.hide(); // Infotip is hidden until one of the precincts is clicked
};

function addFilterListeners() {
  $('.filter-bar, .filter-selected, #demographic-select').each(function() {
    $(this).on('change click', function(e) {
      console.log('filter clicked')
      // Inelegant way of getting the minimaps instead of the select box.
      var value = this.dataset.filter || $(this).val();
      updateFilter(value);
      $filterSelect.val(value);
      map.setView({ lat: 33.74, lng: -84.38}, 10);
    });
  });

  $('.filter-bar, .filter-selected').each(function() {
    $(this).on('click', function(e) {
      highlight(this);
    });
  });
};

// Set the title of summary table
function updateTitle(feature) {
  var buckets = {
    'white': 'at least 50% white population',
    'black': 'at least 50% black population',
    'hispanic': 'at least 50% Hispanic population',
    'high': 'a mean household income above $100,000',
    'mid': 'a mean household income between $50,000 and $100,000',
    'low': 'a mean household income below $50,000'
  };

  var titleCounty = selectedCounty.toUpperCase() != 'ALL COUNTIES' ? selectedCounty : 'Atlanta';

  if (feature === 'all') {
    $('#results-summary-title').html(titleCounty + ' results')
  }
  else {
    $('#results-summary-title').html(`Results in precincts with ${buckets[feature] +
    (titleCounty != 'Atlanta'? ' (' + titleCounty + ' County)' : '')
    }`)
  }
};

// Return an object with appropriate styles given the party results of a given precinct
function setColor(feature) {
  var style = {color: '#2E64FE', fillOpacity: .3, fillColor: '#e2e2e2', opacity: .5, weight: 1},
      rep = feature.properties.rep_votes,
      dem = feature.properties.dem_votes;

  // Skip precincts with null values for both republican and democrat vote counts
  if (!rep && !dem) {
    return style;
  }

  var party = rep > dem ? 'Republican' : 'Democrat';

  switch (party) {
    case 'Republican': {
      style.fillColor = 'red';
      break;
    }
    case 'Democrat': {
      style.fillColor = '#2ECCFA';
      break;
    }
  };
  return style;
};

// generate a geoJson layer from the data, set initial styles, and add event listeners.
function generateLayers() {
  geojson = L.geoJson(features, {
      onEachFeature: onEachFeature,
      style: function(feature) {
        return setColor(feature)
      }
  });

  // Add event handlers to precinct features to change tooltips
  function onEachFeature(feature, layer) {
    if (!layer.feature.properties.rep_votes && !layer.feature.properties.dem_votes) {
      map.removeLayer(layer);
    };
    layer.on({
      click: zoomToFeature,
      mouseover: highlightFeature,
      mouseout: resetStyle
    })
  };

  /***********************
  * Begin helper functions
  ************************/
  function highlightFeature(e) {
    var layer = e.target;

    if ($(window).width() > MOBILE_WIDTH) {
      $('#info-title').html(`${layer.feature.properties.PRECINCT_N}

            ${layer.feature.properties.COUNTY_NAM} COUNTY
        `)
      updateTable('#info-data', layer.feature.properties, year);

      geojson.eachLayer(function (layer) {
        layer.setStyle({opacity: .5, weight: 1, color: '#2E64FE'})
      })

      layer.setStyle({
        weight: 3,
        opacity: 1,
        color: 'black'
      });

      $infoTip.show();
    };
  };

  function zoomToFeature(e) {
    var layer = e.target;

    $('#info-title').html(`${layer.feature.properties.PRECINCT_N}

          ${layer.feature.properties.COUNTY_NAM} COUNTY
      `)
    updateTable('#info-data', layer.feature.properties, year);

    geojson.eachLayer(function (layer) {
      layer.setStyle({opacity: .5, weight: 1, color: '#2E64FE'})
    })

    layer.setStyle({
      weight: 3,
      opacity: 1,
      color: 'black'
    });

    highlightFeature(e);
    if ($(window).width() > MOBILE_WIDTH) { // it's distracting to zoom on mobile
      map.fitBounds(e.target.getBounds());
      return
    }
    var coords = {x: e.originalEvent.clientX, y: e.originalEvent.clientY, click:true };
    placeInfo(coords)
    $infoTip.show();
  };

  function resetStyle(e) {
    if ($(window).width() > MOBILE_WIDTH) {
      $infoTip.hide();
    }

    e.target.setStyle({
      opacity: .5,
      weight: 1,
      color: '#2E64FE'
    })
  }
  /**********************
  * End helper functions
  ***********************/
};

// Add an info box to the main map
function createInfo() {
  // Event handler to change position of tooltip depending on mouse position (on desktop only)
  $('#map').bind('mousemove', function(e) {
    if ($(window).width() > MOBILE_WIDTH) {
      var dets = {x: e.pageX, y: e.pageY, click: false};
      placeInfo(dets);
    };
  });
};

function placeInfo(e) {
  // Move the info tip above the mouse if the user is at the bottom of the screen
  if ($(window).width() > MOBILE_WIDTH || e.click) { // it's distracting to zoom on mobile

    $infoTip.css({left: e.x, top: e.y + 20})

    if (e.y > ($(window).height() - 120)) {
      $infoTip.css({top: e.y - 100})
    };

    if (e.x > ($(window).width() - 200)) {
      $infoTip.css({left: $(window).width() - 200})
    };
  };

  // Event handler to change display of tooltip for mobile or desktop
  $(window).resize(function() {
    $infoTip.hide();
    toggleMobile();
  });

  // Hide info tip when close button is clicked
  $closeButton.on('click', function() {
    $infoTip.hide();
  })
};

/* Depending on the size of the display, have tooltip follow the mouse or stick to the
bottom of the screen. */
function toggleMobile() {
  if ($(window).width() < MOBILE_WIDTH) {
    $('#filters').hide(); // Instead of showing filter options as boxes, display as select box
    $closeButton.show();

    // Configure infotip
    $infoTip.css({left: '', top: ''}); // Remove css styles added by mousemove event handler
    $infoTip.toggleClass('fixed-bottom', true);
    $infoTip.toggleClass('follow', false);
  }
  else {
    $('#filters').show();
    $closeButton.hide();
    $infoTip.toggleClass('fixed-bottom', false);
    $infoTip.toggleClass('follow', true);
  }
}

/* Add event listeners to autocomplete input field and query Google
 * Places API */
function initInput() {
  var input = document.getElementById('address-input');
  var options = {types: ['address']}
  autocomplete = new google.maps.places.Autocomplete(input, options);

  autocomplete.addListener('place_changed', onPlaceChanged)
}

// Pan map to the address selected by the user
function onPlaceChanged() {
  var lat = autocomplete.getPlace().geometry.location.lat();
  var lng = autocomplete.getPlace().geometry.location.lng();
  map.setView(new L.LatLng(lat, lng), 15);
}

// Finally, run main function to generate the map
initInput();
getPrecincts(addPrecincts);

// Get aggregate data
function getAggregatedData() {
  $.ajax({
    dataType: 'json',
    url: './2014agg_stats.json',
    success: function(data) {
      // Update state
      aggStats = data;

      // Add filters
      makeFilters(aggStats);
      addFilterListeners();

      // Add default values
      updateTitle('all')
      updateTable($resultsSummary, aggStats['ALL COUNTIES']['all'], year);
      updateFilter('all');
    },
    failure: function() {
      console.log('failed to get precincts.');
    }
  });
}

$loading.hide();
$map.show();
// Fixes weird bug http://stackoverflow.com/questions/24547468/leaflet-map-on-hide-div
map._onResize();

<commit_msg>all set to add year toggle<commit_after>// Third party libraries
import * as L from 'leaflet';
import $ from 'jquery';

// Local modules
import makeFilters from './filters';
import updateTable from './table-generator';
import updateRankings from './ranking-table';

// Constants
var MOBILE_WIDTH = 600;

// DOM refs
var autocomplete,
    $infoTip = $('#info'),
    $loading = $('#loading'),
    $map = $('#map'),
    $closeButton = $('#close-button'),
    $filterSelect = $('#demographic-select'),
    $countiesSelect = $('#counties-selector-holder'),
    $resultsSummary = $('#results-summary');

// State
var selectedBucket = 'all',
    selectedCounty = 'all counties',
    activePrecincts = [],
    features = [],
    geojson,
    interactiveLayer,
    aggStats,
    year = 2016; //TODO change this for 2016!!

// Helper functions
function highlight(el) {
    $('.filter-selected').attr('class', 'filter-bar')
    $(el).attr('class', 'filter-selected')
};

$map.hide(); // Map is hidden until it's done loading
toggleMobile(); // Check size of display and display precinct information accordingly

// Create Leaflet map and get tiles from Carto
var map = L.map('map', {minZoom: 9, scrollWheelZoom: false});
map.setView({ lat: 33.74, lng: -84.38}, 10);

// Use panes to "sandwich" GeoJSON features between map tiles and road labels
map.createPane('labels');
map.getPane('labels').style.zIndex = 650;
map.getPane('labels').style.pointerEvents = 'none';

// Attribution
var cartodbAttribution = '© OpenStreetMap contributors, © CartoDB';

// Labels
L.tileLayer('http://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png', {pane: 'labels', attribution: cartodbAttribution}).addTo(map);

// Create base map
L.tileLayer('http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png', {attribution: cartodbAttribution}).addTo(map);

function updateFilter(filter) {
  // County filters and demographic filters are both set by this function,
  // so we need to check if the given filter is a county or not
  var counties = ['Clayton', 'DeKalb', 'Fulton', 'Gwinnett', 'Cobb', 'All counties', 'all counties']
  if (counties.indexOf(filter) === -1) {
    selectedBucket = filter; // Get the filter from the data-filter attribute
  }
  else {
    selectedCounty = filter;
  }

  // Empty the activePrecincts array so that we can fill in only the
  // precincts that meet the current precinct criteria
  activePrecincts = [];

  // Loop through features in the geoJSON layer (ie the precincts)
  geojson.eachLayer(function (layer) {
    var layerRace = layer.feature.properties['2012_pre_2'],
        layerCounty = layer.feature.properties.COUNTY_NAM,
        layerIncome;

    var income = layer.feature.properties['2012_pre_1']; // Assign income to high middle or low bucket

    if (!layer.feature.properties['dem_votes'] && !layer.feature.properties['rep_votes']) {
      map.removeLayer(layer);
    };

    // TODO bin this in the data so I don't have to do it here
    if (income < 50000) {
      layerIncome = 'low';
    }
    else if (income < 100000) {
      layerIncome = 'mid';
    }
    else if (income > 100000) {
      layerIncome = 'high'
    }

    // Check if each precinct meets the filter criteria, and change its fill color accordingly
    if (layerCounty === selectedCounty.toUpperCase() || selectedCounty.toUpperCase() === 'ALL COUNTIES') {
      if (layerRace === selectedBucket ||
      layerIncome === selectedBucket ||
      selectedBucket === 'all') {
        layer.setStyle(setColor(layer.feature));
        activePrecincts.push(layer);
      }
      else {
        layer.setStyle({fillOpacity: 0});
      };
    }
    else {
      layer.setStyle({fillOpacity: 0});
    };
  });

  // Update the summary table results for the given filter
  updateTitle(selectedBucket);
  updateTable($resultsSummary, aggStats[selectedCounty.toUpperCase()][selectedBucket], year);
  updateRankings(activePrecincts, selectedCounty, selectedBucket);
}
// Get shapefiles
function getPrecincts(cb) {
  $.ajax({
    dataType: 'json',
    //url: './2014_precincts_income_race_simple.min.json',
    url: './2014_precincts_income_raceUPDATE.json',
    success: function(data) {
      cb(data)
    },
    failure: function() {
      console.log('failed to get precincts.');
    }
  });
};

// push precincts from .json file to list of features so that Leaflet can render them.
function addPrecincts(layer) {
  features = layer;
  console.log(features);
  generateLayers();
  createMap();
  getAggregatedData();
}

/* Generate a map and a list of filter options */
function createMap() {
  // Render filters for demographic data like race and income

  // Default to display all precincts without any filtering
  $('.filter[data-filter="all"]').attr('class', 'filter-selected');

  // Add the geoJSON data to the map, hide the loading screen, and update the summary table
  geojson.addTo(map);

  // Add event listeners to filter precincts by the given criteria when clicked

  $('#county-select').change(function() {
    var value = $(this).val();
    updateFilter(value);
    map.setView({ lat: 33.74, lng: -84.38}, 10);
  })

  createInfo(); // Create the info box that displays precinct information
  $infoTip.hide(); // Infotip is hidden until one of the precincts is clicked
};

function addFilterListeners() {
  $('.filter-bar, .filter-selected, #demographic-select').each(function() {
    $(this).on('change click', function(e) {
      console.log('filter clicked')
      // Inelegant way of getting the minimaps instead of the select box.
      var value = this.dataset.filter || $(this).val();
      updateFilter(value);
      $filterSelect.val(value);
      map.setView({ lat: 33.74, lng: -84.38}, 10);
    });
  });

  $('.filter-bar, .filter-selected').each(function() {
    $(this).on('click', function(e) {
      highlight(this);
    });
  });
};

// Set the title of summary table
function updateTitle(feature) {
  var buckets = {
    'white': 'at least 50% white population',
    'black': 'at least 50% black population',
    'hispanic': 'at least 50% Hispanic population',
    'high': 'a mean household income above $100,000',
    'mid': 'a mean household income between $50,000 and $100,000',
    'low': 'a mean household income below $50,000'
  };

  var titleCounty = selectedCounty.toUpperCase() != 'ALL COUNTIES' ? selectedCounty : 'Atlanta';

  if (feature === 'all') {
    $('#results-summary-title').html(titleCounty + ' results')
  }
  else {
    $('#results-summary-title').html(`Results in precincts with ${buckets[feature] +
    (titleCounty != 'Atlanta'? ' (' + titleCounty + ' County)' : '')
    }`)
  }
};

// Return an object with appropriate styles given the party results of a given precinct
function setColor(feature) {
  var style = {color: '#2E64FE', fillOpacity: .3, fillColor: '#e2e2e2', opacity: .5, weight: 1},
      rep = feature.properties.rep_votes,
      dem = feature.properties.dem_votes;

  // Skip precincts with null values for both republican and democrat vote counts
  if (!rep && !dem) {
    return style;
  }

  var party = rep > dem ? 'Republican' : 'Democrat';

  switch (party) {
    case 'Republican': {
      style.fillColor = 'red';
      break;
    }
    case 'Democrat': {
      style.fillColor = '#2ECCFA';
      break;
    }
  };
  return style;
};

// generate a geoJson layer from the data, set initial styles, and add event listeners.
function generateLayers() {
  geojson = L.geoJson(features, {
      onEachFeature: onEachFeature,
      style: function(feature) {
        return setColor(feature)
      }
  });

  // Add event handlers to precinct features to change tooltips
  function onEachFeature(feature, layer) {
    if (!layer.feature.properties.rep_votes && !layer.feature.properties.dem_votes) {
      map.removeLayer(layer);
    };
    layer.on({
      click: zoomToFeature,
      mouseover: highlightFeature,
      mouseout: resetStyle
    })
  };

  /***********************
  * Begin helper functions
  ************************/
  function highlightFeature(e) {
    var layer = e.target;

    if ($(window).width() > MOBILE_WIDTH) {
      $('#info-title').html(`${layer.feature.properties.PRECINCT_N}

            ${layer.feature.properties.COUNTY_NAM} COUNTY
        `)
      updateTable('#info-data', layer.feature.properties, year);

      geojson.eachLayer(function (layer) {
        layer.setStyle({opacity: .5, weight: 1, color: '#2E64FE'})
      })

      layer.setStyle({
        weight: 3,
        opacity: 1,
        color: 'black'
      });

      $infoTip.show();
    };
  };

  function zoomToFeature(e) {
    var layer = e.target;

    $('#info-title').html(`${layer.feature.properties.PRECINCT_N}

          ${layer.feature.properties.COUNTY_NAM} COUNTY
      `)
    updateTable('#info-data', layer.feature.properties, year);

    geojson.eachLayer(function (layer) {
      layer.setStyle({opacity: .5, weight: 1, color: '#2E64FE'})
    })

    layer.setStyle({
      weight: 3,
      opacity: 1,
      color: 'black'
    });

    highlightFeature(e);
    if ($(window).width() > MOBILE_WIDTH) { // it's distracting to zoom on mobile
      map.fitBounds(e.target.getBounds());
      return
    }
    var coords = {x: e.originalEvent.clientX, y: e.originalEvent.clientY, click:true };
    placeInfo(coords)
    $infoTip.show();
  };

  function resetStyle(e) {
    if ($(window).width() > MOBILE_WIDTH) {
      $infoTip.hide();
    }

    e.target.setStyle({
      opacity: .5,
      weight: 1,
      color: '#2E64FE'
    })
  }
  /**********************
  * End helper functions
  ***********************/
};

// Add an info box to the main map
function createInfo() {
  // Event handler to change position of tooltip depending on mouse position (on desktop only)
  $('#map').bind('mousemove', function(e) {
    if ($(window).width() > MOBILE_WIDTH) {
      var dets = {x: e.pageX, y: e.pageY, click: false};
      placeInfo(dets);
    };
  });
};

function placeInfo(e) {
  // Move the info tip above the mouse if the user is at the bottom of the screen
  if ($(window).width() > MOBILE_WIDTH || e.click) { // it's distracting to zoom on mobile

    $infoTip.css({left: e.x, top: e.y + 20})

    if (e.y > ($(window).height() - 120)) {
      $infoTip.css({top: e.y - 100})
    };

    if (e.x > ($(window).width() - 200)) {
      $infoTip.css({left: $(window).width() - 200})
    };
  };

  // Event handler to change display of tooltip for mobile or desktop
  $(window).resize(function() {
    $infoTip.hide();
    toggleMobile();
  });

  // Hide info tip when close button is clicked
  $closeButton.on('click', function() {
    $infoTip.hide();
  })
};

/* Depending on the size of the display, have tooltip follow the mouse or stick to the
bottom of the screen. */
function toggleMobile() {
  if ($(window).width() < MOBILE_WIDTH) {
    $('#filters').hide(); // Instead of showing filter options as boxes, display as select box
    $closeButton.show();

    // Configure infotip
    $infoTip.css({left: '', top: ''}); // Remove css styles added by mousemove event handler
    $infoTip.toggleClass('fixed-bottom', true);
    $infoTip.toggleClass('follow', false);
  }
  else {
    $('#filters').show();
    $closeButton.hide();
    $infoTip.toggleClass('fixed-bottom', false);
    $infoTip.toggleClass('follow', true);
  }
}

/* Add event listeners to autocomplete input field and query Google
 * Places API */
function initInput() {
  var input = document.getElementById('address-input');
  var options = {types: ['address']}
  autocomplete = new google.maps.places.Autocomplete(input, options);

  autocomplete.addListener('place_changed', onPlaceChanged)
}

// Pan map to the address selected by the user
function onPlaceChanged() {
  var lat = autocomplete.getPlace().geometry.location.lat();
  var lng = autocomplete.getPlace().geometry.location.lng();
  map.setView(new L.LatLng(lat, lng), 15);
}

// Finally, run main function to generate the map
initInput();
getPrecincts(addPrecincts);

// Get aggregate data
function getAggregatedData() {
  $.ajax({
    dataType: 'json',
    url: './2014agg_stats.json',
    success: function(data) {
      // Update state
      aggStats = data;

      // Add filters
      makeFilters(aggStats);
      addFilterListeners();

      // Add default values
      updateTitle('all')
      updateTable($resultsSummary, aggStats['ALL COUNTIES']['all'], year);
      updateFilter('all');
    },
    failure: function() {
      console.log('failed to get precincts.');
    }
  });
}

$loading.hide();
$map.show();
// Fixes weird bug http://stackoverflow.com/questions/24547468/leaflet-map-on-hide-div
map._onResize();

<|endoftext|>


<commit_before>/**
 * Main JS file for Landscape behaviours
 */

/*globals jQuery, document */
(function ($) {
    "use strict";

    $(document).ready(function(){

        $(".post-content").fitVids();

        function landscapeFullImg() {
            $("img").each( function() {
                var contentWidth = $(".post-content").outerWidth(); // Width of the content
                var imageWidth = $(this)[0].naturalWidth; // Original image resolution

                if (imageWidth >= contentWidth) {
                    $(this).addClass('full-img');
                } else {
                    $(this).removeClass('full-img');
                }
            });
        };

        landscapeFullImg();
        $(window).smartresize(landscapeFullImg);

    });

}(jQuery));

(function($,sr){

  // debouncing function from John Hann
  // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
  var debounce = function (func, threshold, execAsap) {
      var timeout;

      return function debounced () {
          var obj = this, args = arguments;
          function delayed () {
              if (!execAsap)
                  func.apply(obj, args);
              timeout = null;
          };

          if (timeout)
              clearTimeout(timeout);
          else if (execAsap)
              func.apply(obj, args);

          timeout = setTimeout(delayed, threshold || 100);
      };
  }
  // smartresize
  jQuery.fn[sr] = function(fn){  return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); };

})(jQuery,'smartresize');

    // CSS Tricks - Chris Coyier's smooth scrolling for anchored links
    $(function() {
        $('a[href*="#"]:not([href="#"])').click(function() {
            if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
                var target = $(this.hash);
                target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
                if (target.length) {
                    $('html, body').animate({
                        scrollTop: target.offset().top
                    }, 1000);
                    return false;
                }
            }
        });
    });
<commit_msg>adjusted scroll position to consider fixed menu height<commit_after>/**
 * Main JS file for Landscape behaviours
 */

/*globals jQuery, document */
(function ($) {
    "use strict";

    $(document).ready(function(){

        $(".post-content").fitVids();

        function landscapeFullImg() {
            $("img").each( function() {
                var contentWidth = $(".post-content").outerWidth(); // Width of the content
                var imageWidth = $(this)[0].naturalWidth; // Original image resolution

                if (imageWidth >= contentWidth) {
                    $(this).addClass('full-img');
                } else {
                    $(this).removeClass('full-img');
                }
            });
        };

        landscapeFullImg();
        $(window).smartresize(landscapeFullImg);

    });

}(jQuery));

(function($,sr){

  // debouncing function from John Hann
  // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
  var debounce = function (func, threshold, execAsap) {
      var timeout;

      return function debounced () {
          var obj = this, args = arguments;
          function delayed () {
              if (!execAsap)
                  func.apply(obj, args);
              timeout = null;
          };

          if (timeout)
              clearTimeout(timeout);
          else if (execAsap)hidden
              func.apply(obj, args);

          timeout = setTimeout(delayed, threshold || 100);
      };
  }
  // smartresize
  jQuery.fn[sr] = function(fn){  return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); };

})(jQuery,'smartresize');

    // CSS Tricks - Chris Coyier's smooth scrolling for anchored links
    $(function() {
        $('a[href*="#"]:not([href="#"])').click(function() {
            if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
                var target = $(this.hash);
                target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
                if (target.length) {
                    $('html, body').animate({
                        scrollTop: target.offset().top - $('.main-navigation').height()
                    }, 1000);
                    return false;
                }
            }
        });
    });
<|endoftext|>