<commit_before>/**
 * parser 指令解析模块（部分正则借鉴于 vue）
 */
define([
	'../util'
], function(util) {

	// 表达式中允许的关键字
	var allowKeywords = 'Math.parseInt.parseFloat.Date.this.true.false.null.undefined.Infinity.NaN.isNaN.isFinite.decodeURI.decodeURIComponent.encodeURI.encodeURIComponent';
	var regAllowKeyword = new RegExp('^(' + allowKeywords.replace(/\./g, '\\b|') + '\\b)');

	// 表达式中禁止的关键字
	var avoidKeywords = 'var.const.let.if.else.for.in.continue.switch.case.break.default.function.return.do.while.delete.try.catch.throw.finally.with.import.export.instanceof.yield.await';
	var regAviodKeyword = new RegExp('^(' + avoidKeywords.replace(/\./g, '\\b|') + '\\b)');

	// 匹配纯数字
	var regNumber = /^[0-9]*$/;
	// 匹配常量缓存序号 "1"
	var regSaveConst = /"(\d+)"/g;
	// 只含有 true 或 false
	var regBool = /^(true|false)$/;
	// 匹配循环下标别名
	var regIndex = /^\$index|\W\$\bindex\b/;
	// 匹配表达式中的常量
	var regReplaceConst = /[\{,]\s*[\w\$_]+\s*:|('[^']*'|"[^"]*")|typeof /g;
	// 匹配表达式中的取值域
	var regReplaceScope = /[^\w$\.]([A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\['.*?'\]|\[".*?"\])*)/g;
	// 匹配常规取值: item or item['x'] or item["y"] or item[0]
	var regNormal = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\['.*?'\]|\[".*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*$/;

	/**
	 * 是否是常规指令表达式
	 * @param   {String}   expression
	 * @return  {Boolean}
	 */
	function isNormal(expression) {
		return regNormal.test(expression) && !regBool.test(expression) && expression.indexOf('Math.') !== 0;
	}

	// 保存常量，返回序号 "i"
	var consts = [];
	function saveConst(string) {
		var i = consts.length;
		consts[i] = string;
		return '"' + i + '"';
	}

	/**
	 * 返回替换之前的常量
	 * @param   {Strinf}  string
	 * @param   {Number}  i
	 * @return  {String}
	 */
	function returnConst(string, i) {
		return consts[i];
	}

	/**
	 * 返回表达式的 scope 替换
	 * @param   {String}  string
	 * @return  {String}
	 */
	function replaceScope(string) {
		var pad = string.charAt(0);
		var path = string.slice(1);

		if (regAllowKeyword.test(path)) {
			return string;
		}
		else {
			path = path.indexOf('"') !== -1 ? path.replace(regSaveConst, returnConst) : path;
			return pad + 'scope.' + path;
		}
	}

	/**
	 * 获取取值表达式的 vfor 取值域别名
	 * @param   {Object}  fors         <必选>
	 * @param   {String}  expression   <必选>
	 * @return  {String}
	 */
	function getAlias(fors, expression) {
		var alias, exp = expression.replace(/(\(.*\))/g, '');

		// $index or item in items {{item}}
		if (exp === fors.alias || regIndex.test(exp)) {
			return fors.alias;
		}

		// 在表达式中匹配 alias.xxx
		util.each(fors.aliases, function(al) {
			var reg = new RegExp('\\b' + al + '\\b|\\b'+ al +'\\.');
			if (reg.test(exp)) {
				alias = al;
				return false;
			}
		});

		return alias;
	}

	/**
	 * 生成取值路径
	 * @param   {String}  access
	 * @return  {Array}
	 */
	function makePaths(access) {
		var length, paths = access && access.split('*');

		if (!paths || paths.length < 2) {
			return [];
		}

		for (var i = paths.length - 1; i > -1; i--) {
			if (regNumber.test(paths[i])) {
				length = i + 1;
				break;
			}
		}

		return paths.slice(0, length);
	}

	/**
	 * 生成取值路径数组
	 * [items, 0, ps, 0] => [[items, 0], [items, 0, ps, 0]]
	 * @param   {Array}  paths
	 * @return  {Array}
	 */
	function makeScopePaths(paths) {
		var index = 0, scopePaths = [];

		if (paths.length % 2 === 0) {
			while (index < paths.length) {
				index += 2;
				scopePaths.push(paths.slice(0, index));
			}
		}

		return scopePaths;
	}

	/**
	 * 通过访问层级取值
	 * @param   {Object}  target
	 * @param   {Array}   paths
	 * @return  {Mix}
	 */
	function getDeepValue(target, paths) {
		var _paths = paths.slice(0);

		while (_paths.length) {
			target = target[_paths.shift()];
		}

		return target;
	}

	/**
	 * Parser 基础解析器模块，指令解析模块都继承于 Parser
	 */
	function Parser() {}
	var p = Parser.prototype;

	/**
	 * 绑定监测 & 初始化视图
	 * @param   {Object}      fors
	 * @param   {DOMElement}  node
	 * @param   {String}      expression
	 */
	p.bind = function(fors, node, expression) {
		var vm = this.vm;
		// 提取依赖
		var deps = this.getDeps(fors, expression);
		// 获取取值域
		var scope = this.getScope(fors, expression);
		// 生成取值函数
		var getter = this.getEval(fors, expression);
		// 取值别名映射
		var maps = fors && util.copy(fors.maps);

		// 初始视图更新
		this.update(node, getter.call(scope, scope));

		// 监测依赖变化，更新取值 & 视图
		vm.watcher.watch(deps, function() {
			scope = this.updateScope(scope, maps, deps, arguments);
			this.update(node, getter.call(scope, scope));
		}, this);
	}

	/**
	 * 设置数据模型的值（用于双向数据绑定）
	 * @param  {String}  path
	 * @param  {String}  field
	 * @param  {Mix}     value
	 */
	p.setModel = function(path, field, value) {
		var paths, target;
		var model = this.vm.$data;

		if (path) {
			paths = makePaths(path);
			target = getDeepValue(model, paths);
			target[field] = value;
		}
		else {
			model[field] = value;
		}
	}

	/**
	 * 生成表达式取值函数
	 * @param   {String}    expression
	 * @return  {Function}
	 */
	p.createGetter = function(expression) {
		try {
			return new Function('scope', 'return ' + expression + ';');
		}
		catch (e) {
			util.error('Invalid generated expression: ' + expression);
		}
	}

	/**
	 * 获取表达式的取值函数
	 */
	p.getEval = function(fors, expression) {
		var exp = this.toScope(expression);

		if (regAviodKeyword.test(exp)) {
			util.warn('Avoid using unallow keyword in expression: ' + exp);
			return;
		}

		// 替换取值域别名
		if (fors) {
			util.each(fors.aliases, function(alias) {
				var reg = new RegExp('scope.' + alias, 'g');
				exp = exp.replace(reg, function(scope) {
					return 'scope.$' + scope;
				});
			});
		}

		return this.createGetter(exp);
	}

	/**
	 * 转换表达式的 scope 取值域
	 * @return  {String}
	 */
	p.toScope = function(expression) {
		var exp = expression;

		if (isNormal(exp)) {
			return 'scope.' + exp;
		}

		exp = (' ' + exp).replace(regReplaceConst, saveConst);
		exp = exp.replace(regReplaceScope, replaceScope);
		exp = exp.replace(regSaveConst, returnConst);

		return exp;
	}

	/**
	 * 获取表达式的取值域
	 * @param   {Object}  fors
	 * @param   {String}  expression
	 * @return  {Object}
	 */
	p.getScope = function(fors, expression) {
		var model = this.vm.$data;

		if (!fors) {
			return model;
		}

		model.$index = fors.index;
		model.$scope = fors.scopes;

		return model;
	}

	/**
	 * 更新取值域
	 * @param   {Object}  oldScope   [旧取值域]
	 * @param   {Object}  maps       [别名映射]
	 * @param   {Object}  deps       [取值依赖]
	 * @param   {Array}   args       [变更参数]
	 * @return  {Mix}
	 */
	p.updateScope = function(oldScope, maps, deps, args) {
		var targetPaths;
		var accesses = deps.acc;
		var leng = 0, $scope = {};
		var model = this.vm.$data;

		// 获取最深层的依赖
		accesses.unshift(args[0]);
		util.each(accesses, function(access) {
			var paths = makePaths(access);
			if (paths.length > leng) {
				targetPaths = paths;
				leng = paths.length;
			}
		});

		// 更新 vfor 取值
		if (targetPaths) {
			util.each(makeScopePaths(targetPaths), function(paths) {
				var leng = paths.length;

				// 更新下标的情况通过变更参数来确定
				if ((args && args[0] === '$index')) {
					paths[leng - 1] = args[1];
				}

				var field = paths[leng - 2];
				var index = +paths[leng - 1];
				var scope = getDeepValue(model, paths) || {};

				// 支持两种 $index 取值方式
				model.$index = index;
				if (util.isObject(scope)) {
					scope.$index = index;
				}

				$scope[maps[field]] = scope;
			});

			util.extend(model, {
				'$scope': util.extend(oldScope.$scope, $scope)
			});
		}

		return model;
	}

	/**
	 * 获取表达式的所有依赖（取值模型+访问路径）
	 * @param   {Object}  fors        [vfor 数据]
	 * @param   {String}  expression
	 * @return  {Object}
	 */
	p.getDeps = function(fors, expression) {
		var deps = [], paths = [];
		var exp = ' ' + expression.replace(regReplaceConst, saveConst);

		exp.replace(regReplaceScope, function(dep) {
			var model = dep.substr(1);
			var alias, hasIndex, access, valAccess;

			// 取值域别名或 items.length -> items
			if (fors) {
				alias = getAlias(fors, dep);
				hasIndex = model.indexOf('$index') !== -1;
				// 取值域路径
				if (model.indexOf(alias) !== -1 || hasIndex) {
					access = fors.accesses[fors.aliases.indexOf(alias)];
				}
			}
			else {
				alias = util.getExpAlias(model);
			}

			// 取值字段访问路径，输出别名和下标
			if (hasIndex || model === alias) {
				valAccess = access || fors && fors.access;
			}
			else {
				if (access && model !== '$event') {
					valAccess = access + '*' + util.getExpKey(model);
				}
			}

			deps.push(model);
			paths.push(valAccess);
		});

		return {
			'dep': deps,
			'acc': paths
		}
	}

	return Parser;
});<commit_msg>The same dependence get only once when it uses many times<commit_after>/**
 * parser 指令解析模块（部分正则借鉴于 vue）
 */
define([
	'../util'
], function(util) {

	// 表达式中允许的关键字
	var allowKeywords = 'Math.parseInt.parseFloat.Date.this.true.false.null.undefined.Infinity.NaN.isNaN.isFinite.decodeURI.decodeURIComponent.encodeURI.encodeURIComponent';
	var regAllowKeyword = new RegExp('^(' + allowKeywords.replace(/\./g, '\\b|') + '\\b)');

	// 表达式中禁止的关键字
	var avoidKeywords = 'var.const.let.if.else.for.in.continue.switch.case.break.default.function.return.do.while.delete.try.catch.throw.finally.with.import.export.instanceof.yield.await';
	var regAviodKeyword = new RegExp('^(' + avoidKeywords.replace(/\./g, '\\b|') + '\\b)');

	// 匹配纯数字
	var regNumber = /^[0-9]*$/;
	// 匹配常量缓存序号 "1"
	var regSaveConst = /"(\d+)"/g;
	// 只含有 true 或 false
	var regBool = /^(true|false)$/;
	// 匹配循环下标别名
	var regIndex = /^\$index|\W\$\bindex\b/;
	// 匹配表达式中的常量
	var regReplaceConst = /[\{,]\s*[\w\$_]+\s*:|('[^']*'|"[^"]*")|typeof /g;
	// 匹配表达式中的取值域
	var regReplaceScope = /[^\w$\.]([A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\['.*?'\]|\[".*?"\])*)/g;
	// 匹配常规取值: item or item['x'] or item["y"] or item[0]
	var regNormal = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\['.*?'\]|\[".*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*$/;

	/**
	 * 是否是常规指令表达式
	 * @param   {String}   expression
	 * @return  {Boolean}
	 */
	function isNormal(expression) {
		return regNormal.test(expression) && !regBool.test(expression) && expression.indexOf('Math.') !== 0;
	}

	// 保存常量，返回序号 "i"
	var consts = [];
	function saveConst(string) {
		var i = consts.length;
		consts[i] = string;
		return '"' + i + '"';
	}

	/**
	 * 返回替换之前的常量
	 * @param   {Strinf}  string
	 * @param   {Number}  i
	 * @return  {String}
	 */
	function returnConst(string, i) {
		return consts[i];
	}

	/**
	 * 返回表达式的 scope 替换
	 * @param   {String}  string
	 * @return  {String}
	 */
	function replaceScope(string) {
		var pad = string.charAt(0);
		var path = string.slice(1);

		if (regAllowKeyword.test(path)) {
			return string;
		}
		else {
			path = path.indexOf('"') !== -1 ? path.replace(regSaveConst, returnConst) : path;
			return pad + 'scope.' + path;
		}
	}

	/**
	 * 获取取值表达式的 vfor 取值域别名
	 * @param   {Object}  fors         <必选>
	 * @param   {String}  expression   <必选>
	 * @return  {String}
	 */
	function getAlias(fors, expression) {
		var alias, exp = expression.replace(/(\(.*\))/g, '');

		// $index or item in items {{item}}
		if (exp === fors.alias || regIndex.test(exp)) {
			return fors.alias;
		}

		// 在表达式中匹配 alias.xxx
		util.each(fors.aliases, function(al) {
			var reg = new RegExp('\\b' + al + '\\b|\\b'+ al +'\\.');
			if (reg.test(exp)) {
				alias = al;
				return false;
			}
		});

		return alias;
	}

	/**
	 * 生成取值路径
	 * @param   {String}  access
	 * @return  {Array}
	 */
	function makePaths(access) {
		var length, paths = access && access.split('*');

		if (!paths || paths.length < 2) {
			return [];
		}

		for (var i = paths.length - 1; i > -1; i--) {
			if (regNumber.test(paths[i])) {
				length = i + 1;
				break;
			}
		}

		return paths.slice(0, length);
	}

	/**
	 * 生成取值路径数组
	 * [items, 0, ps, 0] => [[items, 0], [items, 0, ps, 0]]
	 * @param   {Array}  paths
	 * @return  {Array}
	 */
	function makeScopePaths(paths) {
		var index = 0, scopePaths = [];

		if (paths.length % 2 === 0) {
			while (index < paths.length) {
				index += 2;
				scopePaths.push(paths.slice(0, index));
			}
		}

		return scopePaths;
	}

	/**
	 * 通过访问层级取值
	 * @param   {Object}  target
	 * @param   {Array}   paths
	 * @return  {Mix}
	 */
	function getDeepValue(target, paths) {
		var _paths = paths.slice(0);

		while (_paths.length) {
			target = target[_paths.shift()];
		}

		return target;
	}

	/**
	 * Parser 基础解析器模块，指令解析模块都继承于 Parser
	 */
	function Parser() {}
	var p = Parser.prototype;

	/**
	 * 绑定监测 & 初始化视图
	 * @param   {Object}      fors
	 * @param   {DOMElement}  node
	 * @param   {String}      expression
	 */
	p.bind = function(fors, node, expression) {
		// 提取依赖
		var deps = this.getDeps(fors, expression);
		// 取值域
		var scope = this.getScope(fors, expression);
		// 取值函数
		var getter = this.getEval(fors, expression);
		// 别名映射
		var maps = fors && util.copy(fors.maps);

		// 初始视图更新
		this.update(node, getter.call(scope, scope));

		// 监测依赖变化，更新取值 & 视图
		this.vm.watcher.watch(deps, function() {
			scope = this.updateScope(scope, maps, deps, arguments);
			this.update(node, getter.call(scope, scope));
		}, this);
	}

	/**
	 * 设置数据模型的值（用于双向数据绑定）
	 * @param  {String}  path
	 * @param  {String}  field
	 * @param  {Mix}     value
	 */
	p.setModel = function(path, field, value) {
		var paths, target;
		var model = this.vm.$data;

		if (path) {
			paths = makePaths(path);
			target = getDeepValue(model, paths);
			target[field] = value;
		}
		else {
			model[field] = value;
		}
	}

	/**
	 * 生成表达式取值函数
	 * @param   {String}    expression
	 * @return  {Function}
	 */
	p.createGetter = function(expression) {
		try {
			return new Function('scope', 'return ' + expression + ';');
		}
		catch (e) {
			util.error('Invalid generated expression: ' + expression);
		}
	}

	/**
	 * 获取表达式的取值函数
	 */
	p.getEval = function(fors, expression) {
		var exp = this.toScope(expression);

		if (regAviodKeyword.test(exp)) {
			util.warn('Avoid using unallow keyword in expression: ' + exp);
			return;
		}

		// 替换取值域别名
		if (fors) {
			util.each(fors.aliases, function(alias) {
				var reg = new RegExp('scope.' + alias, 'g');
				exp = exp.replace(reg, function(scope) {
					return 'scope.$' + scope;
				});
			});
		}

		return this.createGetter(exp);
	}

	/**
	 * 转换表达式的 scope 取值域
	 * @return  {String}
	 */
	p.toScope = function(expression) {
		var exp = expression;

		if (isNormal(exp)) {
			return 'scope.' + exp;
		}

		exp = (' ' + exp).replace(regReplaceConst, saveConst);
		exp = exp.replace(regReplaceScope, replaceScope);
		exp = exp.replace(regSaveConst, returnConst);

		return exp;
	}

	/**
	 * 获取表达式的取值域
	 * @param   {Object}  fors
	 * @param   {String}  expression
	 * @return  {Object}
	 */
	p.getScope = function(fors, expression) {
		var model = this.vm.$data;

		if (!fors) {
			return model;
		}

		model.$index = fors.index;
		model.$scope = fors.scopes;

		return model;
	}

	/**
	 * 更新取值域
	 * @param   {Object}  oldScope   [旧取值域]
	 * @param   {Object}  maps       [别名映射]
	 * @param   {Object}  deps       [取值依赖]
	 * @param   {Array}   args       [变更参数]
	 * @return  {Mix}
	 */
	p.updateScope = function(oldScope, maps, deps, args) {
		var targetPaths;
		var accesses = deps.acc;
		var leng = 0, $scope = {};
		var model = this.vm.$data;

		// 获取最深层的依赖
		accesses.unshift(args[0]);
		util.each(accesses, function(access) {
			var paths = makePaths(access);
			if (paths.length > leng) {
				targetPaths = paths;
				leng = paths.length;
			}
		});

		// 更新 vfor 取值
		if (targetPaths) {
			util.each(makeScopePaths(targetPaths), function(paths) {
				var leng = paths.length;

				// 更新下标的情况通过变更参数来确定
				if ((args[0] === '$index')) {
					paths[leng - 1] = args[1];
				}

				var field = paths[leng - 2];
				var index = +paths[leng - 1];
				var scope = getDeepValue(model, paths) || {};

				// 支持两种 $index 取值方式
				model.$index = index;
				if (util.isObject(scope)) {
					scope.$index = index;
				}

				$scope[maps[field]] = scope;
			});

			util.extend(model, {
				'$scope': util.extend(oldScope.$scope, $scope)
			});
		}

		return model;
	}

	/**
	 * 获取表达式的所有依赖（取值模型+访问路径）
	 * @param   {Object}  fors        [vfor 数据]
	 * @param   {String}  expression
	 * @return  {Object}
	 */
	p.getDeps = function(fors, expression) {
		var deps = [], paths = [];
		var exp = ' ' + expression.replace(regReplaceConst, saveConst);

		exp.replace(regReplaceScope, function(dep) {
			var model = dep.substr(1);
			var alias, hasIndex, access, valAccess;

			// 取值域别名或 items.length -> items
			if (fors) {
				alias = getAlias(fors, dep);
				hasIndex = model.indexOf('$index') !== -1;
				// 取值域路径
				if (model.indexOf(alias) !== -1 || hasIndex) {
					access = fors.accesses[fors.aliases.indexOf(alias)];
				}
			}
			else {
				alias = util.getExpAlias(model);
			}

			// 取值字段访问路径，输出别名和下标
			if (hasIndex || model === alias) {
				valAccess = access || fors && fors.access;
			}
			else {
				if (access && model !== '$event') {
					valAccess = access + '*' + util.getExpKey(model);
				}
			}

			// 相同的依赖出现多次只需记录一次
			if (deps.indexOf(model) === -1) {
				deps.push(model);
				paths.push(valAccess);
			}
		});

		return {
			'dep': deps,
			'acc': paths
		}
	}

	return Parser;
});<|endoftext|>


<commit_before>/*============================================================================*\

	A PNG Opacity Work-Around for Internet Explorer
	-----------------------------------------------------------------------
	© 2015 by Carroket, Inc.
	http://www.carroket.com/
	-----------------------------------------------------------------------
	Made by Brian Sexton.
	http://www.briansexton.com/
	-----------------------------------------------------------------------
	MIT License

\*============================================================================*/

function stylePNGs() {

	if (window.navigator.appName && window.navigator.appName == "Microsoft Internet Explorer" && document.body.filters) {

		var containerNode;

		var imageToReplace;

		var newElement;

		var PNGs = [];

		// Find all PNG IMG elements in the document.

		for (var i = 0; i < document.images.length; i++) {

			if (document.images[i].src.indexOf('.png') == document.images[i].src.length - 4) {

				PNGs[PNGs.length] = document.images[i];
			}
		}

		// Replace all PNG IMG elements with DIV elements to which Microsoft's
		// suggested work-around are applied.

		for (var j = 0; j < PNGs.length; j++) {

			containerNode = PNGs[j].parentNode;

			imageToReplace = PNGs[j];

			newElement = document.createElement('div');

			newElement.style.width = imageToReplace.width;
			newElement.style.height = imageToReplace.height;
			newElement.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + imageToReplace.src + "', sizingMethod='scale');";

			newElement.className = imageToReplace.className;

			containerNode.replaceChild(newElement, imageToReplace);
		}
	}
}<commit_msg>Update the permissions for png-work-around.js.<commit_after>/*============================================================================*\

	A PNG Opacity Work-Around for Internet Explorer
	-----------------------------------------------------------------------
	© 2015 by Carroket, Inc.
	http://www.carroket.com/
	-----------------------------------------------------------------------
	Made by Brian Sexton.
	http://www.briansexton.com/
	-----------------------------------------------------------------------
	MIT License

\*============================================================================*/

function stylePNGs() {

	if (window.navigator.appName && window.navigator.appName == "Microsoft Internet Explorer" && document.body.filters) {

		var containerNode;

		var imageToReplace;

		var newElement;

		var PNGs = [];

		// Find all PNG IMG elements in the document.

		for (var i = 0; i < document.images.length; i++) {

			if (document.images[i].src.indexOf('.png') == document.images[i].src.length - 4) {

				PNGs[PNGs.length] = document.images[i];
			}
		}

		// Replace all PNG IMG elements with DIV elements to which Microsoft's
		// suggested work-around are applied.

		for (var j = 0; j < PNGs.length; j++) {

			containerNode = PNGs[j].parentNode;

			imageToReplace = PNGs[j];

			newElement = document.createElement('div');

			newElement.style.width = imageToReplace.width;
			newElement.style.height = imageToReplace.height;
			newElement.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + imageToReplace.src + "', sizingMethod='scale');";

			newElement.className = imageToReplace.className;

			containerNode.replaceChild(newElement, imageToReplace);
		}
	}
}<|endoftext|>


<commit_before>// pages/_app.js
import React from "react";
import {createStore} from "redux";
import {Provider} from "react-redux";
import App, {Container} from "next/app";
import withRedux from "next-redux-wrapper";
import configureStore from '../configureStore'

const reducer = (state = {foo: ''}, action) => {
    switch (action.type) {
        case 'FOO':
            return {...state, foo: action.payload};
        default:
            return state
    }
};

class MyApp extends App {
    static async getInitialProps ({ Component, ctx }) {
        return {
          pageProps: {
            ...(Component.getInitialProps ? await Component.getInitialProps(ctx) : {}),
          }
        };
      }

    render() {
        const {Component, pageProps, store} = this.props;
        return (
            <Container>
                <Provider store={store}>
                    <Component {...pageProps} />
                </Provider>
            </Container>
        );
    }

}

export default withRedux(configureStore)(MyApp);<commit_msg>feat(public): create _app.js default file<commit_after>// pages/_app.js
import React from "react";
import { createStore } from "redux";
import { Provider } from "react-redux";
import App, { Container } from "next/app";
import withRedux from "next-redux-wrapper";
import configureStore from '../src/configureStore'

class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
    return {
      pageProps: {
        ...(Component.getInitialProps ? await Component.getInitialProps(ctx) : {}),
      }
    };
  }

  render() {
    const { Component, pageProps, store } = this.props;
    return (
      <Container>
        <Provider store={store}>
          <Component {...pageProps} />
        </Provider>
      </Container>
    );
  }

}

export default withRedux(configureStore)(MyApp);
<|endoftext|>


<commit_before>///import core
///commands 段落格式,居左,居右,居中,两端对齐
///commandsName  JustifyLeft,JustifyCenter,JustifyRight,JustifyJustify
///commandsTitle  居左对齐,居中对齐,居右对齐,两端对齐
/**
 * @description 居左右中
 * @name baidu.editor.execCommand
 * @param   {String}   cmdName     justify执行对齐方式的命令
 * @param   {String}   align               对齐方式：left居左，right居右，center居中，justify两端对齐
 * @author zhanyi
 */
UE.plugins['justify']=function(){
    var me=this,
        block = domUtils.isBlockElm,
        defaultValue = {
            left:1,
            right:1,
            center:1,
            justify:1
        },
        doJustify = function (range, style) {
            var bookmark = range.createBookmark(),
                filterFn = function (node) {
                    return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' && !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace(node);
                };

            range.enlarge(true);
            var bookmark2 = range.createBookmark(),
                current = domUtils.getNextDomNode(bookmark2.start, false, filterFn),
                tmpRange = range.cloneRange(),
                tmpNode;
            while (current && !(domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING)) {
                if (current.nodeType == 3 || !block(current)) {
                    tmpRange.setStartBefore(current);
                    while (current && current !== bookmark2.end && !block(current)) {
                        tmpNode = current;
                        current = domUtils.getNextDomNode(current, false, null, function (node) {
                            return !block(node);
                        });
                    }
                    tmpRange.setEndAfter(tmpNode);
                    var common = tmpRange.getCommonAncestor();
                    if (!domUtils.isBody(common) && block(common)) {
                        domUtils.setStyles(common, utils.isString(style) ? {'text-align':style} : style);
                        current = common;
                    } else {
                        var p = range.document.createElement('p');
                        domUtils.setStyles(p, utils.isString(style) ? {'text-align':style} : style);
                        var frag = tmpRange.extractContents();
                        p.appendChild(frag);
                        tmpRange.insertNode(p);
                        current = p;
                    }
                    current = domUtils.getNextDomNode(current, false, filterFn);
                } else {
                    current = domUtils.getNextDomNode(current, true, filterFn);
                }
            }
            return range.moveToBookmark(bookmark2).moveToBookmark(bookmark);
        };

    UE.commands['justify'] = {
        execCommand:function (cmdName, align) {

            var range = this.selection.getRange(),
                txt;

            //闭合时单独处理
            if (range.collapsed) {
                txt = this.document.createTextNode('p');
                range.insertNode(txt);
            }
            doJustify(range, align);
            if (txt) {
                range.setStartBefore(txt).collapse(true);
                domUtils.remove(txt);
            }

            range.select();

            return true;
        },
        queryCommandValue:function () {
            var startNode = this.selection.getStart(),
                value = domUtils.getComputedStyle(startNode, 'text-align');
            return defaultValue[value] ? value : 'left';
        },
        queryCommandState:function () {
            var start = this.selection.getStart(),
                cell = start && domUtils.findParentByTagName(start, ["td", "th","caption"], true);

            return cell? -1:0;
        }

    };
};
<commit_msg>[fixed]by xuheng<commit_after>///import core
///commands 段落格式,居左,居右,居中,两端对齐
///commandsName  JustifyLeft,JustifyCenter,JustifyRight,JustifyJustify
///commandsTitle  居左对齐,居中对齐,居右对齐,两端对齐
/**
 * @description 居左右中
 * @name baidu.editor.execCommand
 * @param   {String}   cmdName     justify执行对齐方式的命令
 * @param   {String}   align               对齐方式：left居左，right居右，center居中，justify两端对齐
 * @author zhanyi
 */
UE.plugins['justify']=function(){
    var me=this,
        block = domUtils.isBlockElm,
        defaultValue = {
            left:1,
            right:1,
            center:1,
            justify:1
        },
        doJustify = function (range, style) {
            var bookmark = range.createBookmark(),
                filterFn = function (node) {
                    return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' && !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace(node);
                };

            range.enlarge(true);
            var bookmark2 = range.createBookmark(),
                current = domUtils.getNextDomNode(bookmark2.start, false, filterFn),
                tmpRange = range.cloneRange(),
                tmpNode;
            while (current && !(domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING)) {
                if (current.nodeType == 3 || !block(current)) {
                    tmpRange.setStartBefore(current);
                    while (current && current !== bookmark2.end && !block(current)) {
                        tmpNode = current;
                        current = domUtils.getNextDomNode(current, false, null, function (node) {
                            return !block(node);
                        });
                    }
                    tmpRange.setEndAfter(tmpNode);
                    var common = tmpRange.getCommonAncestor();
                    if (!domUtils.isBody(common) && block(common)) {
                        domUtils.setStyles(common, utils.isString(style) ? {'text-align':style} : style);
                        current = common;
                    } else {
                        var p = range.document.createElement('p');
                        domUtils.setStyles(p, utils.isString(style) ? {'text-align':style} : style);
                        var frag = tmpRange.extractContents();
                        p.appendChild(frag);
                        tmpRange.insertNode(p);
                        current = p;
                    }
                    current = domUtils.getNextDomNode(current, false, filterFn);
                } else {
                    current = domUtils.getNextDomNode(current, true, filterFn);
                }
            }
            return range.moveToBookmark(bookmark2).moveToBookmark(bookmark);
        };
    //添加快捷键
    me.addshortcutkey({
        "justify" : "CTRL+76:left,CTRL+82:right"
    });
    UE.commands['justify'] = {
        execCommand:function (cmdName, align) {
            var range = this.selection.getRange(),
                txt;

            //闭合时单独处理
            if (range.collapsed) {
                txt = this.document.createTextNode('p');
                range.insertNode(txt);
            }
            doJustify(range, align);
            if (txt) {
                range.setStartBefore(txt).collapse(true);
                domUtils.remove(txt);
            }

            range.select();

            return true;
        },
        queryCommandValue:function () {
            var startNode = this.selection.getStart(),
                value = domUtils.getComputedStyle(startNode, 'text-align');
            return defaultValue[value] ? value : 'left';
        },
        queryCommandState:function () {
            var start = this.selection.getStart(),
                cell = start && domUtils.findParentByTagName(start, ["td", "th","caption"], true);

            return cell? -1:0;
        }

    };
};
<|endoftext|>


<commit_before>var _ = require('underscore');
var Model = require('../core/model');
var BackboneAbortSync = require('../util/backbone-abort-sync');
var WindshaftFiltersBoundingBoxFilter = require('../windshaft/filters/bounding-box');
var BOUNDING_BOX_FILTER_WAIT = 500;

var UNFETCHED_STATUS = 'unfeteched';
var FETCHING_STATUS = 'fetching';
var FETCHED_STATUS = 'fetched';
var FETCH_ERROR_STATUS = 'error';

/**
 * Default dataview model
 */
module.exports = Model.extend({
  defaults: {
    url: '',
    data: [],
    sync_on_data_change: true,
    sync_on_bbox_change: true,
    enabled: true,
    status: UNFETCHED_STATUS
  },

  url: function () {
    var params = _.union(
      [ this._getBoundingBoxFilterParam() ],
      this._getDataviewSpecificURLParams()
    );

    if (this.get('apiKey')) {
      params.push('api_key=' + this.get('apiKey'));
    } else if (this.get('authToken')) {
      var authToken = this.get('authToken');
      if (authToken instanceof Array) {
        _.each(authToken, function (token) {
          params.push('auth_token[]=' + token);
        });
      } else {
        params.push('auth_token=' + authToken);
      }
    }
    return this.get('url') + '?' + params.join('&');
  },

  _getBoundingBoxFilterParam: function () {
    var result = '';
    var boundingBoxFilter;

    if (this.syncsOnBoundingBoxChanges()) {
      boundingBoxFilter = new WindshaftFiltersBoundingBoxFilter(this._getMapViewBounds());
      result = 'bbox=' + boundingBoxFilter.toString();
    }

    return result;
  },

  _getMapViewBounds: function () {
    return this._map.getViewBounds();
  },

  /**
   * Subclasses might override this method to define extra params that will be appended
   * to the dataview's URL.
   * @return {[Array]} An array of strings in the form of "key=value".
   */
  _getDataviewSpecificURLParams: function () {
    return [];
  },

  initialize: function (attrs, opts) {
    attrs = attrs || {};
    opts = opts || {};

    if (!opts.map) throw new Error('map is required');
    if (!opts.vis) throw new Error('vis is required');
    if (!opts.analysisCollection) throw new Error('analysisCollection is required');
    if (!attrs.source) throw new Error('source is a required attr');

    if (!attrs.id) {
      this.set('id', this.defaults.type + '-' + this.cid);
    }

    this.layer = opts.layer;
    this._map = opts.map;
    this._vis = opts.vis;
    this._analysisCollection = opts.analysisCollection;

    this.sync = BackboneAbortSync.bind(this);

    // filter is optional, so have to guard before using it
    this.filter = opts.filter;
    if (this.filter) {
      this.filter.set('dataviewId', this.id);
    }

    this._initBinds();
    this._setupAnalysisStatusEvents();
  },

  _getLayerDataProvider: function () {
    return this.layer.getDataProvider();
  },

  _initBinds: function () {
    this.listenTo(this.layer, 'change:visible', this._onLayerVisibilityChanged);
    this.listenTo(this.layer, 'change:source', this._setupAnalysisStatusEvents);
    this.on('change:source', this._setupAnalysisStatusEvents, this);

    var layerDataProvider = this._getLayerDataProvider();
    if (layerDataProvider) {
      this.listenToOnce(layerDataProvider, 'dataChanged', this._onChangeBinds, this);
      this.listenTo(layerDataProvider, 'dataChanged', this.fetch);
    } else {
      this.listenToOnce(this, 'change:url', function () {
        if (this.syncsOnBoundingBoxChanges() && !this._getMapViewBounds()) {
          // wait until map gets bounds from view
          this._map.on('change:view_bounds_ne', function () {
            this._initialFetch();
          }, this);
        } else {
          this._initialFetch();
        }
      });
    }

    if (this.filter) {
      this.listenTo(this.filter, 'change', this._onFilterChanged);
    }
  },

  _initialFetch: function () {
    this.fetch({
      success: this._onChangeBinds.bind(this)
    });
  },

  _setupAnalysisStatusEvents: function () {
    this._removeExistingAnalysisBindings();
    this._analysis = this.getSource();
    if (this._analysis) {
      this._analysis.on('change:status', this._onAnalysisStatusChange, this);
    }
  },

  _removeExistingAnalysisBindings: function () {
    if (!this._analysis) return;
    this._analysis.off('change:status', this._onAnalysisStatusChange, this);
  },

  _onAnalysisStatusChange: function (analysis, status) {
    if (analysis.isLoading()) {
      this._triggerLoading();
    } else if (analysis.isFailed()) {
      this._triggerError(analysis.get('error'));
    }
    // loaded will be triggered through the default behavior, so not necessary to react on that status here
  },

  _triggerLoading: function () {
    this.trigger('loading', this);
  },

  _triggerError: function (error) {
    this.trigger('error', this, error);
  },

  /**
   * @private
   */
  _onFilterChanged: function (filter) {
    var layerDataProvider = this._getLayerDataProvider();
    if (layerDataProvider && layerDataProvider.canApplyFilterTo(this)) {
      layerDataProvider.applyFilter(this, filter);
    } else {
      this._reloadVis();
    }
  },

  /**
   * @protected
   */
  _reloadVis: function (opts) {
    opts = opts || {};
    this._vis.reload(
      _.extend(
        opts, {
          sourceId: this.getSourceId()
        }
      )
    );
  },

  _reloadVisAndForceFetch: function () {
    this._reloadVis({
      forceFetch: true
    });
  },

  /**
   * Enable/disable the dataview depending on the layer visibility.
   * @private
   * @param  {LayerModel} model the layer model which visible property has changed.
   * @param  {Boolean} value New value for visible.
   * @returns {void}
   */
  _onLayerVisibilityChanged: function (model, value) {
    this.set({enabled: value});
  },

  _onChangeBinds: function () {
    this.on('change:sync_on_bbox_change', function () {
      this.refresh();
    }, this);

    this.listenTo(this._map, 'change:center change:zoom', _.debounce(this._onMapBoundsChanged.bind(this), BOUNDING_BOX_FILTER_WAIT));

    var onChangedUrl = function (model, value, opts) {
      if (this.syncsOnDataChanges()) {
        this._newDataAvailable = true;
      }
      if (this._shouldFetchOnURLChange(opts && _.pick(opts, ['forceFetch', 'sourceId']))) {
        this.fetch();
      }
    }.bind(this);

    // We need to unbind the event because we end up with one more
    // event binding everytime this function is called
    this.off('change:url', onChangedUrl, this);
    this.on('change:url', onChangedUrl, this);

    this.on('change:enabled', function (mdl, isEnabled) {
      if (isEnabled && this._newDataAvailable) {
        this.fetch();
        this._newDataAvailable = false;
      }
    }, this);
  },

  _onMapBoundsChanged: function () {
    if (this._shouldFetchOnBoundingBoxChange()) {
      this.fetch();
    }

    if (this.syncsOnBoundingBoxChanges()) {
      this._newDataAvailable = true;
    }
  },

  _shouldFetchOnURLChange: function (options) {
    options = options || {};
    var sourceId = options.sourceId;
    var forceFetch = options.forceFetch;

    if (forceFetch) {
      return true;
    }

    return this.isEnabled() &&
      this.syncsOnDataChanges() &&
        this._sourceAffectsMyOwnSource(sourceId);
  },

  _sourceAffectsMyOwnSource: function (sourceId) {
    if (!sourceId) {
      return true;
    }
    var sourceAnalysis = this.getSource();
    return sourceAnalysis && sourceAnalysis.findAnalysisById(sourceId);
  },

  _shouldFetchOnBoundingBoxChange: function () {
    return this.isEnabled() &&
      this.syncsOnBoundingBoxChanges();
  },

  refresh: function () {
    this.fetch();
  },

  update: function (attrs) {
    attrs = _.pick(attrs, this.constructor.ATTRS_NAMES);
    this.set(attrs);
  },

  getData: function () {
    return this.get('data');
  },

  getPreviousData: function () {
    return this.previous('data');
  },

  fetch: function (opts) {
    opts = opts || {};
    this.set('status', FETCHING_STATUS);
    var layerDataProvider = this._getLayerDataProvider();
    if (layerDataProvider && layerDataProvider.canProvideDataFor(this)) {
      this.set(this.parse(layerDataProvider.getDataFor(this)));
    } else {
      this._triggerLoading();

      if (opts.success) {
        var successCallback = opts && opts.success;
      }

      return Model.prototype.fetch.call(this, _.extend(opts, {
        success: function () {
          this.set('status', FETCHED_STATUS);
          successCallback && successCallback(arguments);
          this.trigger('loaded', this);
        }.bind(this),
        error: function (mdl, err) {
          this.set('status', FETCH_ERROR_STATUS);
          if (!err || (err && err.statusText !== 'abort')) {
            this._triggerError(err);
          }
        }.bind(this)
      }));
    }
  },

  toJSON: function () {
    throw new Error('toJSON should be defined for each dataview');
  },

  getSource: function () {
    var sourceId = this.getSourceId();
    return sourceId && this._analysisCollection.get(sourceId);
  },

  getSourceId: function () {
    // Dataview is pointing to a layer that has a source, so its
    // source is actually the the layers's source
    if (this.hasLayerAsSource() && this.layer.has('source')) {
      return this.layer.get('source');
    }

    // Dataview is pointing to a layer with `sql` or an analysis
    // node directly, so just return the id that has been set by
    // dataviews-factory.js
    return this._ownSourceId();
  },

  _ownSourceId: function () {
    return this.has('source') && this.get('source').id;
  },

  hasLayerAsSource: function () {
    return this._ownSourceId() === this.layer.id;
  },

  isFiltered: function () {
    var isFiltered = false;
    if (this.filter) {
      isFiltered = !this.filter.isEmpty();
    }
    return isFiltered;
  },

  remove: function () {
    this._removeExistingAnalysisBindings();
    this.trigger('destroy', this);
    this.stopListening();
  },

  isFetched: function () {
    return this.get('status') === FETCHED_STATUS;
  },

  isUnavailable: function () {
    return this.get('status') === FETCH_ERROR_STATUS;
  },

  isEnabled: function () {
    return this.get('enabled');
  },

  syncsOnDataChanges: function () {
    return this.get('sync_on_data_change');
  },

  syncsOnBoundingBoxChanges: function () {
    return this.get('sync_on_bbox_change');
  }
},

  // Class props
  {
    ATTRS_NAMES: [
      'id',
      'sync_on_data_change',
      'sync_on_bbox_change',
      'enabled',
      'source'
    ]
  }
);
<commit_msg>Change event bindings<commit_after>var _ = require('underscore');
var Model = require('../core/model');
var BackboneAbortSync = require('../util/backbone-abort-sync');
var WindshaftFiltersBoundingBoxFilter = require('../windshaft/filters/bounding-box');
var BOUNDING_BOX_FILTER_WAIT = 500;

var UNFETCHED_STATUS = 'unfeteched';
var FETCHING_STATUS = 'fetching';
var FETCHED_STATUS = 'fetched';
var FETCH_ERROR_STATUS = 'error';

/**
 * Default dataview model
 */
module.exports = Model.extend({
  defaults: {
    url: '',
    data: [],
    sync_on_data_change: true,
    sync_on_bbox_change: true,
    enabled: true,
    status: UNFETCHED_STATUS
  },

  url: function () {
    var params = _.union(
      [ this._getBoundingBoxFilterParam() ],
      this._getDataviewSpecificURLParams()
    );

    if (this.get('apiKey')) {
      params.push('api_key=' + this.get('apiKey'));
    } else if (this.get('authToken')) {
      var authToken = this.get('authToken');
      if (authToken instanceof Array) {
        _.each(authToken, function (token) {
          params.push('auth_token[]=' + token);
        });
      } else {
        params.push('auth_token=' + authToken);
      }
    }
    return this.get('url') + '?' + params.join('&');
  },

  _getBoundingBoxFilterParam: function () {
    var result = '';
    var boundingBoxFilter;

    if (this.syncsOnBoundingBoxChanges()) {
      boundingBoxFilter = new WindshaftFiltersBoundingBoxFilter(this._getMapViewBounds());
      result = 'bbox=' + boundingBoxFilter.toString();
    }

    return result;
  },

  _getMapViewBounds: function () {
    return this._map.getViewBounds();
  },

  /**
   * Subclasses might override this method to define extra params that will be appended
   * to the dataview's URL.
   * @return {[Array]} An array of strings in the form of "key=value".
   */
  _getDataviewSpecificURLParams: function () {
    return [];
  },

  initialize: function (attrs, opts) {
    attrs = attrs || {};
    opts = opts || {};

    if (!opts.map) throw new Error('map is required');
    if (!opts.vis) throw new Error('vis is required');
    if (!opts.analysisCollection) throw new Error('analysisCollection is required');
    if (!attrs.source) throw new Error('source is a required attr');

    if (!attrs.id) {
      this.set('id', this.defaults.type + '-' + this.cid);
    }

    this.layer = opts.layer;
    this._map = opts.map;
    this._vis = opts.vis;
    this._analysisCollection = opts.analysisCollection;

    this.sync = BackboneAbortSync.bind(this);

    // filter is optional, so have to guard before using it
    this.filter = opts.filter;
    if (this.filter) {
      this.filter.set('dataviewId', this.id);
    }

    this._initBinds();
    this._setupAnalysisStatusEvents();
  },

  _getLayerDataProvider: function () {
    return this.layer.getDataProvider();
  },

  _initBinds: function () {
    this.listenTo(this.layer, 'change:visible', this._onLayerVisibilityChanged);
    this.listenTo(this.layer, 'change:source', this._setupAnalysisStatusEvents);
    this.on('change:source', this._setupAnalysisStatusEvents, this);

    var layerDataProvider = this._getLayerDataProvider();
    if (layerDataProvider) {
      this.listenToOnce(layerDataProvider, 'dataChanged', this._onChangeBinds, this);
      this.listenTo(layerDataProvider, 'dataChanged', this.fetch);
    } else {
      this.listenToOnce(this, 'change:url', function () {
        if (this.syncsOnBoundingBoxChanges() && !this._getMapViewBounds()) {
          // wait until map gets bounds from view
          this._map.on('change:view_bounds_ne', function () {
            this._initialFetch();
          }, this);
        } else {
          this._initialFetch();
        }
      });
    }

    if (this.filter) {
      this.listenTo(this.filter, 'change', this._onFilterChanged);
    }
  },

  _onChangeBinds: function () {
    this.on('change:sync_on_bbox_change', function () {
      this.refresh();
    }, this);

    this.listenTo(this._map, 'change:center change:zoom', _.debounce(this._onMapBoundsChanged.bind(this), BOUNDING_BOX_FILTER_WAIT));

    this.on('change:url', function (model, value, opts) {
      if (this.syncsOnDataChanges()) {
        this._newDataAvailable = true;
      }
      if (this._shouldFetchOnURLChange(opts && _.pick(opts, ['forceFetch', 'sourceId']))) {
        this.fetch();
      }
    }, this);

    this.on('change:enabled', function (mdl, isEnabled) {
      if (isEnabled && this._newDataAvailable) {
        this.fetch();
        this._newDataAvailable = false;
      }
    }, this);
  },

  _onMapBoundsChanged: function () {
    if (this._shouldFetchOnBoundingBoxChange()) {
      this.fetch();
    }

    if (this.syncsOnBoundingBoxChanges()) {
      this._newDataAvailable = true;
    }
  },

  _initialFetch: function () {
    this.fetch({
      success: this._onChangeBinds.bind(this)
    });
  },

  _setupAnalysisStatusEvents: function () {
    this._removeExistingAnalysisBindings();
    this._analysis = this.getSource();
    if (this._analysis) {
      this._analysis.on('change:status', this._onAnalysisStatusChange, this);
    }
  },

  _removeExistingAnalysisBindings: function () {
    if (!this._analysis) return;
    this._analysis.off('change:status', this._onAnalysisStatusChange, this);
  },

  _onAnalysisStatusChange: function (analysis, status) {
    if (analysis.isLoading()) {
      this._triggerLoading();
    } else if (analysis.isFailed()) {
      this._triggerError(analysis.get('error'));
    }
    // loaded will be triggered through the default behavior, so not necessary to react on that status here
  },

  _triggerLoading: function () {
    this.trigger('loading', this);
  },

  _triggerError: function (error) {
    this.trigger('error', this, error);
  },

  /**
   * @private
   */
  _onFilterChanged: function (filter) {
    var layerDataProvider = this._getLayerDataProvider();
    if (layerDataProvider && layerDataProvider.canApplyFilterTo(this)) {
      layerDataProvider.applyFilter(this, filter);
    } else {
      this._reloadVis();
    }
  },

  /**
   * @protected
   */
  _reloadVis: function (opts) {
    opts = opts || {};
    this._vis.reload(
      _.extend(
        opts, {
          sourceId: this.getSourceId()
        }
      )
    );
  },

  _reloadVisAndForceFetch: function () {
    this._reloadVis({
      forceFetch: true
    });
  },

  /**
   * Enable/disable the dataview depending on the layer visibility.
   * @private
   * @param  {LayerModel} model the layer model which visible property has changed.
   * @param  {Boolean} value New value for visible.
   * @returns {void}
   */
  _onLayerVisibilityChanged: function (model, value) {
    this.set({enabled: value});
  },

  _shouldFetchOnURLChange: function (options) {
    options = options || {};
    var sourceId = options.sourceId;
    var forceFetch = options.forceFetch;

    if (forceFetch) {
      return true;
    }

    return this.isEnabled() &&
      this.syncsOnDataChanges() &&
        this._sourceAffectsMyOwnSource(sourceId);
  },

  _sourceAffectsMyOwnSource: function (sourceId) {
    if (!sourceId) {
      return true;
    }
    var sourceAnalysis = this.getSource();
    return sourceAnalysis && sourceAnalysis.findAnalysisById(sourceId);
  },

  _shouldFetchOnBoundingBoxChange: function () {
    return this.isEnabled() &&
      this.syncsOnBoundingBoxChanges();
  },

  refresh: function () {
    this.fetch();
  },

  update: function (attrs) {
    attrs = _.pick(attrs, this.constructor.ATTRS_NAMES);
    this.set(attrs);
  },

  getData: function () {
    return this.get('data');
  },

  getPreviousData: function () {
    return this.previous('data');
  },

  fetch: function (opts) {
    opts = opts || {};
    this.set('status', FETCHING_STATUS);
    var layerDataProvider = this._getLayerDataProvider();
    if (layerDataProvider && layerDataProvider.canProvideDataFor(this)) {
      this.set(this.parse(layerDataProvider.getDataFor(this)));
    } else {
      this._triggerLoading();

      if (opts.success) {
        var successCallback = opts && opts.success;
      }

      return Model.prototype.fetch.call(this, _.extend(opts, {
        success: function () {
          this.set('status', FETCHED_STATUS);
          successCallback && successCallback(arguments);
          this.trigger('loaded', this);
        }.bind(this),
        error: function (mdl, err) {
          this.set('status', FETCH_ERROR_STATUS);
          if (!err || (err && err.statusText !== 'abort')) {
            this._triggerError(err);
          }
        }.bind(this)
      }));
    }
  },

  toJSON: function () {
    throw new Error('toJSON should be defined for each dataview');
  },

  getSource: function () {
    var sourceId = this.getSourceId();
    return sourceId && this._analysisCollection.get(sourceId);
  },

  getSourceId: function () {
    // Dataview is pointing to a layer that has a source, so its
    // source is actually the the layers's source
    if (this.hasLayerAsSource() && this.layer.has('source')) {
      return this.layer.get('source');
    }

    // Dataview is pointing to a layer with `sql` or an analysis
    // node directly, so just return the id that has been set by
    // dataviews-factory.js
    return this._ownSourceId();
  },

  _ownSourceId: function () {
    return this.has('source') && this.get('source').id;
  },

  hasLayerAsSource: function () {
    return this._ownSourceId() === this.layer.id;
  },

  isFiltered: function () {
    var isFiltered = false;
    if (this.filter) {
      isFiltered = !this.filter.isEmpty();
    }
    return isFiltered;
  },

  remove: function () {
    this._removeExistingAnalysisBindings();
    this.trigger('destroy', this);
    this.stopListening();
  },

  isFetched: function () {
    return this.get('status') === FETCHED_STATUS;
  },

  isUnavailable: function () {
    return this.get('status') === FETCH_ERROR_STATUS;
  },

  isEnabled: function () {
    return this.get('enabled');
  },

  syncsOnDataChanges: function () {
    return this.get('sync_on_data_change');
  },

  syncsOnBoundingBoxChanges: function () {
    return this.get('sync_on_bbox_change');
  }
},

  // Class props
  {
    ATTRS_NAMES: [
      'id',
      'sync_on_data_change',
      'sync_on_bbox_change',
      'enabled',
      'source'
    ]
  }
);
<|endoftext|>


<commit_before>import { Renderer } from 'soundworks/client';
import * as konva from 'konva';

/**
 * A simple canvas renderer.
 * The class renders a dot moving over the screen and rebouncing on the edges.
 */
export default class PlayerRenderer extends Renderer {
  constructor(tapCallback, numImages) {
    super(0); // update rate = 0: synchronize updates to frame rate

    this.tapCallback = tapCallback;

    this.stage = new Konva.Stage({
        container: 'experience',
        width: window.innerWidth,
        height: window.innerHeight
    });

    this.stickers = new Array(numImages);
  }

  /**
   * Initialize renderer state.
   */
    init() {

        this.layer = new Konva.Layer();

        let background = new Konva.Rect({
            x: 0,
            y: 0,
            width: this.stage.attrs.width,
            height: this.stage.attrs.height,
            fill: 'black', //background color
        });
        this.layer.add(background);

        this.stickerSize = 150;
        for (var i = 0; i < this.stickers.length; i++) {
            this.stickers[i] = new Image();

            this.stickers[i].onload = (e) => {
                // console.log(e);
                const imgElement = e.path ? e.path[0] : e.target;
                // imgElement.src.match
                const antimg = new Konva.Image({
                    x: this.stickerSize / 2 + Math.random() * this.stage.attrs.width - this.stickerSize,
                    y: this.stickerSize / 2 + Math.random() * this.stage.attrs.height - this.stickerSize,
                    image: imgElement,
                    width: this.stickerSize,
                    height: this.stickerSize,
                    rotation: Math.random() * 180 - 90,
                    draggable: true,
                    idAnti: imgElement.src.match("s/stick(.*)\\.")[1]
                });

                // TAP event

                antimg.on('dragstart', (e) => {

                    antimg.setZIndex(100);

                    antimg.tween.play();
                });

                antimg.on('dragend', (e) => {

                    antimg.tween.reverse();

                });

                antimg.on('click tap', (e) => {
                    background.tween.play();
                    setTimeout(() => {
                        background.tween.reverse();
                    }, 300);
                    this.tapCallback(antimg.attrs.idAnti);
                });

                antimg.cache();
                antimg.drawHitFromCache();

                // add the shape to the layer
                this.layer.add(antimg);

                antimg.tween = new Konva.Tween({
                    node: antimg,
                    scaleX: 1.2,
                    scaleY: 1.2,
                    easing: Konva.Easings.BounceEaseInOut,
                    duration: 0.1
                });

                background.tween = new Konva.Tween({
                    node: background,
                    fill: 'red',
                    easing: Konva.Easings.StrongEaseInOut,
                    duration: 0.2
                });

                // add the layer to the stage
                this.stage.add(this.layer);
            };
            this.stickers[i].src = `stickers/stick${i + 1}.png`;
        }
        //console.log(this.layer);
    }

  /**
   * Update rederer state.
   * @param {Number} dt - time since last update in seconds.
   */
  update(dt) {

  }

  /**
   * Draw into canvas.
   * Method is called by animation frame loop in current frame rate.
   * @param {CanvasRenderingContext2D} ctx - canvas 2D rendering context
   */
  render(ctx) {
    // canvas operations
    // ctx.save();
    //
    // ctx.restore();
  }
}
<commit_msg>Arreglado arrastre en móviles<commit_after>import { Renderer } from 'soundworks/client';
import * as konva from 'konva';

/**
 * A simple canvas renderer.
 * The class renders a dot moving over the screen and rebouncing on the edges.
 */
export default class PlayerRenderer extends Renderer {
  constructor(tapCallback, numImages) {
    super(0); // update rate = 0: synchronize updates to frame rate

    this.tapCallback = tapCallback;

    this.stage = new Konva.Stage({
        container: 'experience',
        width: window.innerWidth,
        height: window.innerHeight
    });

    this.stickers = new Array(numImages);
  }

  /**
   * Initialize renderer state.
   */
    init() {

        this.layer = new Konva.Layer();

        let background = new Konva.Rect({
            x: 0,
            y: 0,
            width: this.stage.attrs.width,
            height: this.stage.attrs.height,
            fill: 'black', //background color
        });
        this.layer.add(background);

        this.stickerSize = 150;
        for (var i = 0; i < this.stickers.length; i++) {
            this.stickers[i] = new Image();

            this.stickers[i].onload = (e) => {
                // console.log(e);
                const imgElement = e.path ? e.path[0] : e.target;
                // imgElement.src.match
                const antimg = new Konva.Image({
                    x: this.stickerSize / 2 + Math.random() * this.stage.attrs.width - this.stickerSize,
                    y: this.stickerSize / 2 + Math.random() * this.stage.attrs.height - this.stickerSize,
                    image: imgElement,
                    width: this.stickerSize,
                    height: this.stickerSize,
                    rotation: Math.random() * 180 - 90,
                    draggable: true,
                    dragDistance: 5,
                    idAnti: imgElement.src.match("s/stick(.*)\\.")[1]
                });

                // TAP event

                antimg.on('dragstart', (e) => {

                    antimg.setZIndex(100);

                    antimg.tween.play();
                });

                antimg.on('dragend', (e) => {

                    antimg.tween.reverse();

                });

                antimg.on('click tap', (e) => {
                    antimg.setZIndex(100);
                    background.tween.play();
                    setTimeout(() => {
                        background.tween.reverse();
                    }, 300);
                    this.tapCallback(antimg.attrs.idAnti);
                });

                antimg.cache();
                antimg.drawHitFromCache();

                // add the shape to the layer
                this.layer.add(antimg);

                antimg.tween = new Konva.Tween({
                    node: antimg,
                    scaleX: 1.2,
                    scaleY: 1.2,
                    easing: Konva.Easings.BounceEaseInOut,
                    duration: 0.1
                });

                background.tween = new Konva.Tween({
                    node: background,
                    fill: 'red',
                    easing: Konva.Easings.StrongEaseInOut,
                    duration: 0.2
                });

                // add the layer to the stage
                this.stage.add(this.layer);
            };
            this.stickers[i].src = `stickers/stick${i + 1}.png`;
        }
        //console.log(this.layer);
    }

  /**
   * Update rederer state.
   * @param {Number} dt - time since last update in seconds.
   */
  update(dt) {

  }

  /**
   * Draw into canvas.
   * Method is called by animation frame loop in current frame rate.
   * @param {CanvasRenderingContext2D} ctx - canvas 2D rendering context
   */
  render(ctx) {
    // canvas operations
    // ctx.save();
    //
    // ctx.restore();
  }
}
<|endoftext|>


<commit_before>/**
  [Glimmer](https://github.com/tildeio/glimmer) is a [Handlebars](http://handlebarsjs.com/)
  compatible templating engine used by Ember.js.
  Any valid Handlebars syntax is valid in an Ember template.

  ### Showing a property

  Templates manage the flow of an application's UI, and display state (through
  the DOM) to a user. For example, given a component with the property "name",
  that component's template can use the name in several ways:

  ```javascript
    // app/components/person.js
    export default Ember.Component.extend({
      name: 'Jill'
    });
  ```

  ```handlebars
  {{! app/components/person.hbs }}
  {{name}}
  <div>{{name}}</div>
  <span data-name={{name}}></span>
  ```

  Any time the "name" property on the component changes, the DOM will be
  updated.

  Properties can be chained as well:

  ```handlebars
  {{aUserModel.name}}
  <div>{{listOfUsers.firstObject.name}}</div>
  ```

  ### Using Ember helpers

  When content is passed in mustaches `{{}}`, Ember will first try to find a helper
  or component with that name. For example, the `if` helper:

  ```handlebars
  {{if name "I have a name" "I have no name"}}
  <span data-has-name={{if name true}}></span>
  ```

  The returned value is placed where the `{{}}` is called. The above style is
  called "inline". A second style of helper usage is called "block". For example:

  ```handlebars
  {{#if name}}
  I have a name
  {{else}}
  I have no name
  {{/if}}
  ```

  The block form of helpers allows you to control how the UI is created based
  on the values of properties.
  A third form of helper is called "nested". For example here the concat
  helper will add " Doe" to a displayed name if the person has no last name:

  ```handlebars
  <span data-name={{concat firstName (
  if lastName (concat " " lastName) "Doe"
  )}}></span>
  ```

  Ember's built-in helpers are described under the [Ember.Templates.helpers](/api/classes/Ember.Templates.helpers.html)
  namespace. Documentation on creating custom helpers can be found under
  [Ember.Helper](/api/classes/Ember.Helper.html).

  ### Invoking a Component

  Ember components represent state to the UI of an application. Further
  reading on components can be found under [Ember.Component](/api/classes/Ember.Component.html).

  @module ember
  @submodule ember-glimmer
  @main ember-glimmer
  @public
 */

/**
  Use the `{{with}}` helper when you want to alias a property to a new name. This is helpful
  for semantic clarity as it allows you to retain default scope or to reference a property from another
  `{{with}}` block.

  If the aliased property is "falsey", for example: `false`, `undefined` `null`, `""`, `0`, NaN or
  an empty array, the block will not be rendered.

  ```handlebars
  {{! Will only render if user.posts contains items}}
  {{#with user.posts as |blogPosts|}}
    <div class="notice">
      There are {{blogPosts.length}} blog posts written by {{user.name}}.
    </div>
    {{#each blogPosts as |post|}}
      <li>{{post.title}}</li>
    {{/each}}
  {{/with}}
  ```

  Without the `as` operator, it would be impossible to reference `user.name` in the example above.

  NOTE: The alias should not reuse a name from the bound property path.

  For example: `{{#with foo.bar as |foo|}}` is not supported because it attempts to alias using
  the first part of the property path, `foo`. Instead, use `{{#with foo.bar as |baz|}}`.

  @method with
  @for Ember.Templates.helpers
  @param {Object} options
  @return {String} HTML string
  @public
 */

/**
  Execute the `debugger` statement in the current template's context.

  ```handlebars
  {{debugger}}
  ```

  When using the debugger helper you will have access to a `get` function. This
  function retrieves values available in the context of the template.
  For example, if you're wondering why a value `{{foo}}` isn't rendering as
  expected within a template, you could place a `{{debugger}}` statement and,
  when the `debugger;` breakpoint is hit, you can attempt to retrieve this value:

  ```
  > get('foo')
  ```

  `get` is also aware of keywords. So in this situation

  ```handlebars
  {{#each items as |item|}}
    {{debugger}}
  {{/each}}
  ```

  You'll be able to get values from the current item:

  ```
  > get('item.name')
  ```

  You can also access the context of the view to make sure it is the object that
  you expect:

  ```
  > context
  ```

  @method debugger
  @for Ember.Templates.helpers
  @public
 */

/**
  The `partial` helper renders another template without
  changing the template context:

  ```handlebars
  {{foo}}
  {{partial "nav"}}
  ```

  The above example template will render a template named
  "-nav", which has the same context as the parent template
  it's rendered into, so if the "-nav" template also referenced
  `{{foo}}`, it would print the same thing as the `{{foo}}`
  in the above example.

  If a "-nav" template isn't found, the `partial` helper will
  fall back to a template named "nav".

  ### Bound template names

  The parameter supplied to `partial` can also be a path
  to a property containing a template name, e.g.:

  ```handlebars
  {{partial someTemplateName}}
  ```

  The above example will look up the value of `someTemplateName`
  on the template context (e.g. a controller) and use that
  value as the name of the template to render. If the resolved
  value is falsy, nothing will be rendered. If `someTemplateName`
  changes, the partial will be re-rendered using the new template
  name.

  @method partial
  @for Ember.Templates.helpers
  @param {String} partialName The name of the template to render minus the leading underscore.
  @public
*/

export { INVOKE } from './helpers/action';
export { default as RootTemplate } from './templates/root';
export { default as template } from './template';
export { default as Checkbox } from './components/checkbox';
export { default as TextField } from './components/text_field';
export { default as TextArea } from './components/text_area';
export { default as LinkComponent } from './components/link-to';
export { default as Component } from './component';
export { default as Helper, helper } from './helper';
export { default as Environment } from './environment';
export { default as makeBoundHelper } from './make-bound-helper';
export {
  SafeString,
  escapeExpression,
  htmlSafe,
  isHTMLSafe,
  getSafeString as _getSafeString
} from './utils/string';
export {
  Renderer,
  InertRenderer,
  InteractiveRenderer
} from './renderer';
export {
  getTemplate,
  setTemplate,
  hasTemplate,
  getTemplates,
  setTemplates
} from './template_registry';
export { setupEngineRegistry, setupApplicationRegistry } from './setup-registry';
export { DOMChanges, NodeDOMTreeConstruction, DOMTreeConstruction } from './dom';
<commit_msg>[DOC release] Update ember-glimmer top-level documentation.<commit_after>/**
  [Glimmer](https://github.com/tildeio/glimmer) is a templating engine used by Ember.js that is compatible with a subset of the [Handlebars](http://handlebarsjs.com/) syntax.

  ### Showing a property

  Templates manage the flow of an application's UI, and display state (through
  the DOM) to a user. For example, given a component with the property "name",
  that component's template can use the name in several ways:

  ```app/components/person.js
    export default Ember.Component.extend({
      name: 'Jill'
    });
  ```

  ```app/components/person.hbs
  {{name}}
  <div>{{name}}</div>
  <span data-name={{name}}></span>
  ```

  Any time the "name" property on the component changes, the DOM will be
  updated.

  Properties can be chained as well:

  ```handlebars
  {{aUserModel.name}}
  <div>{{listOfUsers.firstObject.name}}</div>
  ```

  ### Using Ember helpers

  When content is passed in mustaches `{{}}`, Ember will first try to find a helper
  or component with that name. For example, the `if` helper:

  ```handlebars
  {{if name "I have a name" "I have no name"}}
  <span data-has-name={{if name true}}></span>
  ```

  The returned value is placed where the `{{}}` is called. The above style is
  called "inline". A second style of helper usage is called "block". For example:

  ```handlebars
  {{#if name}}
  I have a name
  {{else}}
  I have no name
  {{/if}}
  ```

  The block form of helpers allows you to control how the UI is created based
  on the values of properties.
  A third form of helper is called "nested". For example here the concat
  helper will add " Doe" to a displayed name if the person has no last name:

  ```handlebars
  <span data-name={{concat firstName (
  if lastName (concat " " lastName) "Doe"
  )}}></span>
  ```

  Ember's built-in helpers are described under the [Ember.Templates.helpers](/api/classes/Ember.Templates.helpers.html)
  namespace. Documentation on creating custom helpers can be found under
  [Ember.Helper](/api/classes/Ember.Helper.html).

  ### Invoking a Component

  Ember components represent state to the UI of an application. Further
  reading on components can be found under [Ember.Component](/api/classes/Ember.Component.html).

  @module ember
  @submodule ember-glimmer
  @main ember-glimmer
  @public
 */

/**
  Use the `{{with}}` helper when you want to alias a property to a new name. This is helpful
  for semantic clarity as it allows you to retain default scope or to reference a property from another
  `{{with}}` block.

  If the aliased property is "falsey", for example: `false`, `undefined` `null`, `""`, `0`, NaN or
  an empty array, the block will not be rendered.

  ```handlebars
  {{! Will only render if user.posts contains items}}
  {{#with user.posts as |blogPosts|}}
    <div class="notice">
      There are {{blogPosts.length}} blog posts written by {{user.name}}.
    </div>
    {{#each blogPosts as |post|}}
      <li>{{post.title}}</li>
    {{/each}}
  {{/with}}
  ```

  Without the `as` operator, it would be impossible to reference `user.name` in the example above.

  NOTE: The alias should not reuse a name from the bound property path.

  For example: `{{#with foo.bar as |foo|}}` is not supported because it attempts to alias using
  the first part of the property path, `foo`. Instead, use `{{#with foo.bar as |baz|}}`.

  @method with
  @for Ember.Templates.helpers
  @param {Object} options
  @return {String} HTML string
  @public
 */

/**
  Execute the `debugger` statement in the current template's context.

  ```handlebars
  {{debugger}}
  ```

  When using the debugger helper you will have access to a `get` function. This
  function retrieves values available in the context of the template.
  For example, if you're wondering why a value `{{foo}}` isn't rendering as
  expected within a template, you could place a `{{debugger}}` statement and,
  when the `debugger;` breakpoint is hit, you can attempt to retrieve this value:

  ```
  > get('foo')
  ```

  `get` is also aware of keywords. So in this situation

  ```handlebars
  {{#each items as |item|}}
    {{debugger}}
  {{/each}}
  ```

  You'll be able to get values from the current item:

  ```
  > get('item.name')
  ```

  You can also access the context of the view to make sure it is the object that
  you expect:

  ```
  > context
  ```

  @method debugger
  @for Ember.Templates.helpers
  @public
 */

/**
  The `partial` helper renders another template without
  changing the template context:

  ```handlebars
  {{foo}}
  {{partial "nav"}}
  ```

  The above example template will render a template named
  "-nav", which has the same context as the parent template
  it's rendered into, so if the "-nav" template also referenced
  `{{foo}}`, it would print the same thing as the `{{foo}}`
  in the above example.

  If a "-nav" template isn't found, the `partial` helper will
  fall back to a template named "nav".

  ### Bound template names

  The parameter supplied to `partial` can also be a path
  to a property containing a template name, e.g.:

  ```handlebars
  {{partial someTemplateName}}
  ```

  The above example will look up the value of `someTemplateName`
  on the template context (e.g. a controller) and use that
  value as the name of the template to render. If the resolved
  value is falsy, nothing will be rendered. If `someTemplateName`
  changes, the partial will be re-rendered using the new template
  name.

  @method partial
  @for Ember.Templates.helpers
  @param {String} partialName The name of the template to render minus the leading underscore.
  @public
*/

export { INVOKE } from './helpers/action';
export { default as RootTemplate } from './templates/root';
export { default as template } from './template';
export { default as Checkbox } from './components/checkbox';
export { default as TextField } from './components/text_field';
export { default as TextArea } from './components/text_area';
export { default as LinkComponent } from './components/link-to';
export { default as Component } from './component';
export { default as Helper, helper } from './helper';
export { default as Environment } from './environment';
export { default as makeBoundHelper } from './make-bound-helper';
export {
  SafeString,
  escapeExpression,
  htmlSafe,
  isHTMLSafe,
  getSafeString as _getSafeString
} from './utils/string';
export {
  Renderer,
  InertRenderer,
  InteractiveRenderer
} from './renderer';
export {
  getTemplate,
  setTemplate,
  hasTemplate,
  getTemplates,
  setTemplates
} from './template_registry';
export { setupEngineRegistry, setupApplicationRegistry } from './setup-registry';
export { DOMChanges, NodeDOMTreeConstruction, DOMTreeConstruction } from './dom';
<|endoftext|>


<commit_before>(function () {
  angular.module('tpTagger')
      .directive('tpTagger', tpTagger);

  function tpTagger($timeout, $filter, $log){
    return {
      restrict: 'AE',
      templateUrl: 'tp_tagger.tpl.html',
      scope: {
        options: '='
      },
      controller: directiveController,
      link: link,
    };

    function directiveController($scope) {
      initCtrl();
      //$scope config
      $scope.hasFocus = false;
      $scope.isSuggestionsVisible = false;
      $scope.suggestions = [];
      $scope.searchTag = '';
      $scope.searching = false;
      $scope.selectedSuggestionIndex = -1;
      $scope.uniqueError = false;
      $scope.maxTagLengthError = false;
      $scope.hasError = false;

      //$scope methods
      $scope.addTag = addTag;
      $scope.deleteTag = deleteTag;
      $scope.search = search;
      $scope.isActive = isActive;
      $scope.selectActive = selectActive;
      $scope.changeInput = changeInput;
      $scope.changeSuggestionVisible = changeSuggestionVisible;
      $scope.resetErrors = resetErrors;
      /////////////////////////////////////////////

      var setError = function(errorName, state) {
        //it is only possible to show one error
        $scope[errorName] = state;
        $scope.hasError = state;
      };

      function addTag(tag) {
        if(tag === '' || tag == undefined) {
          $log.debug('tag empty');
        } else if(tag.length > $scope.options.maxTagLength) {
          $log.debug('tag too long');
          setError('maxTagLengthError', true);
          $scope.searchTag = '';
        } else if ($scope.options.uniqueTags && $scope.selectedLowerTags.indexOf(tag.toLowerCase()) !== -1) {
          $log.debug('tag not unique');
          setError('uniqueError', true);
          $scope.searchTag = '';
        } else {
          //add tag
          $scope.selectedTags.push(tag);
          $scope.selectedLowerTags.push(tag.toLowerCase());
          $scope.searchTag = '';
          if($scope.options.addFunction) {
            $scope.options.addFunction(tag);
          }
        }
      }

      function deleteTag(index) {
        if(!$scope.selectedTags[index] && !$scope.selectedLowerTags[index])
          return false;
        var tag = angular.copy($scope.selectedTags[index]);

        $scope.selectedTags.splice(index, 1);
        $scope.selectedLowerTags.splice(index, 1);

        if($scope.options.deleteFunction) {
          $scope.options.deleteFunction(tag);
        }
      }

      //the supplied search method must return a promise to indicate finish of loading
      function search(callback) {
        $scope.searching = true;
        return callback($scope.selectedTags).then(function() {
          $log.debug('finished searching');
          $scope.searching = false;
          return true;
        }, function(reason) {
          $log.error(reason);
          $scope.searching = false;
          return false;
        });
      }

      function isActive(index) {
        return index === $scope.selectedSuggestionIndex;
      }

      function selectActive(index) {
        if(!$scope.selectedTags[index])
          return false;
        $scope.selectedSuggestionIndex = index;
      }

      function changeInput() {
        //$log.info($scope.searchTag);
        if ($scope.searchTag.length > $scope.options.minChar) {
          $scope.suggestions = $filter('filter')($scope.dictionary, $scope.searchTag) || [];
          $scope.suggestions = $scope.suggestions.slice(0, $scope.options.maxResults);
          //check if suggestions view should be open
          if ($scope.suggestions.length >= 1) {
            $scope.changeSuggestionVisible(true);
          } else {
            $scope.changeSuggestionVisible(false);
          }

          //$scope.$digest();
        } else {
          if ($scope.isSuggestionsVisible) {
            $scope.changeSuggestionVisible(false);
            //scope.$digest();
          }
        }

        $scope.resetErrors();
      }

      function changeSuggestionVisible(visible) {
        if (visible) {
          $scope.isSuggestionsVisible = true;
        } else {
          $scope.selectedSuggestionIndex = -1;
          $scope.selectedSuggestion = undefined;
          $scope.isSuggestionsVisible = false;
        }
      }

      function resetErrors() {
        if($scope.hasError) {
          $log.debug('reset errors');
          $scope.uniqueError = false;
          $scope.hasError = false;
        } else
          return false;
      }

      function initCtrl() {
        //SUPPORTED OPTIONS (OPTIONS)
        $scope.options = $scope.options || {};
        //minimal no of characters that needs to be entered before typeahead kicks-in
        $scope.options.minChar = $scope.options.minChar || 1;
        $scope.options.maxResults = $scope.options.maxResults || 10;
        $scope.options.maxTagLength = $scope.options.maxTagLength || 50;
        //array of preselected Tags
        $scope.selectedTags = $scope.options.selectedTags || [];
        $scope.selectedLowerTags = [];
        //copy lower case version to seperate array
        for(var i=0; i<$scope.selectedTags.length; i++) {
          $scope.selectedLowerTags.push($scope.selectedTags[i].toLowerCase());
        }
        //array of tag to search for suggestions
        $scope.dictionary = $scope.options.dictionary || [];
        //should the user only be able to add a tag once
        $scope.options.uniqueTags = $scope.options.uniqueTags || true;
        //custom error message can be provided
        $scope.options.errors = $scope.options.errors || {
            notUniqueTag: 'The tag which you tried to add is not unique. You may only add a Tag once.',
            maxTagLength: 'The tag which you tried to add is too long. Only ' + this.maxTagLength + ' characters are allowed.'
          };
      }
    }

    function link(scope) {
      // //SUPPORTED OPTIONS (OPTIONS)
      // scope.options = scope.options || {};
      // //minimal no of characters that needs to be entered before typeahead kicks-in
      // scope.options.minChar = scope.options.minChar || 1;
      // scope.options.maxResults = scope.options.maxResults || 10;
      // scope.options.maxTagLength = scope.options.maxTagLength || 50;
      // //array of preselected Tags
      // scope.selectedTags = scope.options.selectedTags || [];
      // scope.selectedLowerTags = [];
      // //copy lower case version to seperate array
      // for(var i=0; i<scope.selectedTags.length; i++) {
      //   scope.selectedLowerTags.push(scope.selectedTags[i].toLowerCase());
      // }
      // //array of tag to search for suggestions
      // scope.dictionary = scope.options.dictionary || [];
      // //should the user only be able to add a tag once
      // scope.options.uniqueTags = scope.options.uniqueTags || true;
      // //custom error message can be provided
      // scope.options.errors = scope.options.errors || {
      //       notUniqueTag: 'The tag which you tried to add is not unique. You may only add a Tag once.',
      //       maxTagLength: 'The tag which you tried to add is too long. Only ' + scope.options.maxTagLength + ' characters are allowed.'
      //     };
    }
  }
})();
<commit_msg>Adde JSDoc for the main directive functions<commit_after>(function () {
  angular.module('tpTagger')
      .directive('tpTagger', tpTagger);
  /**
   * @name tpTagger
   * @desc tpTagger directive function
   * @param $filter
   * @param $log
   * @returns {{restrict: string, templateUrl: string, scope: {options: string}, controller: directiveController, link: link}}
   */
  function tpTagger($filter, $log){
    return {
      restrict: 'AE',
      templateUrl: 'tp_tagger.tpl.html',
      scope: {
        options: '='
      },
      controller: directiveController,
      link: link,
    };

    /**
     * @name directiveController
     * @desc controller function for the tpTagger directive
     * @param $scope
     */
    function directiveController($scope) {
      initCtrl();
      $scope.hasFocus = false;
      $scope.isSuggestionsVisible = false;
      $scope.suggestions = [];
      $scope.searchTag = '';
      $scope.searching = false;
      $scope.selectedSuggestionIndex = -1;
      $scope.uniqueError = false;
      $scope.maxTagLengthError = false;
      $scope.hasError = false;

      //$scope methods
      $scope.addTag = addTag;
      $scope.deleteTag = deleteTag;
      $scope.search = search;
      $scope.isActive = isActive;
      $scope.selectActive = selectActive;
      $scope.changeInput = changeInput;
      $scope.changeSuggestionVisible = changeSuggestionVisible;
      $scope.resetErrors = resetErrors;
      /////////////////////////////////////////////

      /**
       * @name initCtrl
       * @desc Initialize the controller's isolated scope with options passed into the directive or default values
       */
      function initCtrl() {
        //SUPPORTED OPTIONS (OPTIONS)
        $scope.options = $scope.options || {};
        //minimal no of characters that needs to be entered before typeahead kicks-in
        $scope.options.minChar = $scope.options.minChar || 1;
        $scope.options.maxResults = $scope.options.maxResults || 10;
        $scope.options.maxTagLength = $scope.options.maxTagLength || 50;
        //array of preselected Tags
        $scope.selectedTags = $scope.options.selectedTags || [];
        $scope.selectedLowerTags = [];
        //copy lower case version to seperate array
        for(var i=0; i<$scope.selectedTags.length; i++) {
          $scope.selectedLowerTags.push($scope.selectedTags[i].toLowerCase());
        }
        //array of tag to search for suggestions
        $scope.dictionary = $scope.options.dictionary || [];
        //should the user only be able to add a tag once
        $scope.options.uniqueTags = $scope.options.uniqueTags || true;
        //custom error message can be provided
        $scope.options.errors = $scope.options.errors || {
            notUniqueTag: 'The tag which you tried to add is not unique. You may only add a Tag once.',
            maxTagLength: 'The tag which you tried to add is too long. Only ' + this.maxTagLength + ' characters are allowed.'
          };
      }

      /**
       * @desc Sets an error on the scope to use with error showing functions
       * @param errorName
       * @param state
       */
      var setError = function(errorName, state) {
        //it is only possible to show one error
        $scope[errorName] = state;
        $scope.hasError = state;
      };

      /**
       * @desc Adds a tag to the selectedTags array and a lower-cased version to the selectedLowerTags array
       *       & also invokes a customized add function if passed
       * @param {String} tag
       */
      function addTag(tag) {
        if(tag === '' || tag == undefined) {
          $log.debug('tag empty');
        } else if(tag.length > $scope.options.maxTagLength) {
          $log.debug('tag too long');
          setError('maxTagLengthError', true);
          $scope.searchTag = '';
        } else if ($scope.options.uniqueTags && $scope.selectedLowerTags.indexOf(tag.toLowerCase()) !== -1) {
          $log.debug('tag not unique');
          setError('uniqueError', true);
          $scope.searchTag = '';
        } else {
          //add tag
          $scope.selectedTags.push(tag);
          $scope.selectedLowerTags.push(tag.toLowerCase());
          $scope.searchTag = '';
          if($scope.options.addFunction) {
            $scope.options.addFunction(tag);
          }
        }
      }

      /**
       * @desc Removes a tag from the selectedTags & selectedLowerTags arrays be index
       * @param {Number} index
       * @returns {boolean}
       */
      function deleteTag(index) {
        if(!$scope.selectedTags[index] && !$scope.selectedLowerTags[index])
          return false;
        var tag = angular.copy($scope.selectedTags[index]);

        $scope.selectedTags.splice(index, 1);
        $scope.selectedLowerTags.splice(index, 1);

        if($scope.options.deleteFunction) {
          $scope.options.deleteFunction(tag);
        }
      }

      //the supplied search method must return a promise to indicate finish of loading
      /**
       * @desc Invoked a searching funcion passed in options
       * @param callback
       * @returns {Promise}
       */
      function search(callback) {
        $scope.searching = true;
        return callback($scope.selectedTags).then(function() {
          $log.debug('finished searching');
          $scope.searching = false;
          return true;
        }, function(reason) {
          $log.error(reason);
          $scope.searching = false;
          return false;
        });
      }

      /**
       * @desc Returnes whether a specific selectedSuggestion by index is active or not
       * @param {Number} index
       * @returns {boolean}
       */
      function isActive(index) {
        return index === $scope.selectedSuggestionIndex;
      }

      /**
       * @desc Sets a tag's suggestion as active by index
       * @param index
       * @returns {boolean}
       */
      function selectActive(index) {
        if(!$scope.selectedTags[index])
          return false;
        $scope.selectedSuggestionIndex = index;
      }

      /**
       * @desc A set of operations creating a list of suggestion when an input is changed
       */
      function changeInput() {
        //$log.info($scope.searchTag);
        if ($scope.searchTag.length > $scope.options.minChar) {
          $scope.suggestions = $filter('filter')($scope.dictionary, $scope.searchTag) || [];
          $scope.suggestions = $scope.suggestions.slice(0, $scope.options.maxResults);
          //check if suggestions view should be open
          if ($scope.suggestions.length >= 1) {
            $scope.changeSuggestionVisible(true);
          } else {
            $scope.changeSuggestionVisible(false);
          }

          //$scope.$digest();
        } else {
          if ($scope.isSuggestionsVisible) {
            $scope.changeSuggestionVisible(false);
            //scope.$digest();
          }
        }

        $scope.resetErrors();
      }

      /**
       * Changes isSuggestionsVisible's to true or false to indicate suggestion visibility
       * @param visible
       */
      function changeSuggestionVisible(visible) {
        if (visible) {
          $scope.isSuggestionsVisible = true;
        } else {
          $scope.selectedSuggestionIndex = -1;
          $scope.selectedSuggestion = undefined;
          $scope.isSuggestionsVisible = false;
        }
      }

      /**
       * @desc reset error properties to their initial values
       * @returns {boolean}
       */
      function resetErrors() {
        if($scope.hasError) {
          $log.debug('reset errors');
          $scope.uniqueError = false;
          $scope.hasError = false;
        } else
          return false;
      }
    }

    /**
     * @desc link function used to perform dom manipulations & more of the like.
     *        old code left commented for the author to decide on delete or keep
     */
    function link(scope) {
      // //SUPPORTED OPTIONS (OPTIONS)
      // scope.options = scope.options || {};
      // //minimal no of characters that needs to be entered before typeahead kicks-in
      // scope.options.minChar = scope.options.minChar || 1;
      // scope.options.maxResults = scope.options.maxResults || 10;
      // scope.options.maxTagLength = scope.options.maxTagLength || 50;
      // //array of preselected Tags
      // scope.selectedTags = scope.options.selectedTags || [];
      // scope.selectedLowerTags = [];
      // //copy lower case version to seperate array
      // for(var i=0; i<scope.selectedTags.length; i++) {
      //   scope.selectedLowerTags.push(scope.selectedTags[i].toLowerCase());
      // }
      // //array of tag to search for suggestions
      // scope.dictionary = scope.options.dictionary || [];
      // //should the user only be able to add a tag once
      // scope.options.uniqueTags = scope.options.uniqueTags || true;
      // //custom error message can be provided
      // scope.options.errors = scope.options.errors || {
      //       notUniqueTag: 'The tag which you tried to add is not unique. You may only add a Tag once.',
      //       maxTagLength: 'The tag which you tried to add is too long. Only ' + scope.options.maxTagLength + ' characters are allowed.'
      //     };
    }
  }
})();
<|endoftext|>