<commit_before>/*
 * AjaxPage: 分页
 * */
import { $, Base } from './Base';
import Core from './Core';
import Cache from './Cache';
import I18n from './I18n';
class AjaxPage {
	/**
	 * 分页所需HTML
	 * @param $table
	 * @returns {string}
     */
	createHtml(settings) {
		const html = `

							${ I18n.i18nText(settings, 'goto-first-text') }
							<input type="text" class="gp-input"/>
							${ I18n.i18nText(settings, 'goto-last-text') }

<select name="pSizeArea"></select>

`;
		return html;
	}

	/**
	 * 初始化分页
	 * @param $table
	 * @param settings
     */
	initAjaxPage($table, settings) {
		const sizeData = settings.sizeData;
		// error
		if (!sizeData || sizeData.length === 0) {
			Base.outLog('渲染失败：参数[sizeData]配置错误', 'error');
			return;
		}

		// 根据本地缓存配置每页显示条数
		if (!settings.disableCache) {
			this.__configPageForCache($table, settings);
		}

		const tableWarp = $table.closest('.table-wrap');

		// 分页工具条
		const pageToolbar = $('.page-toolbar', tableWarp);

		// 分页区域
		const pSizeArea	= $('select[name="pSizeArea"]', pageToolbar);

		pageToolbar.hide();

		// 生成每页显示条数选择框
		pSizeArea.html(this.__getPageSizeHtml(sizeData));

		// 绑定页面跳转事件
		this.__bindPageJumpEvent($table);

		// 绑定设置显示条数切换事件
		this.__bindSetPageSizeEvent($table);
	}

	/**
	 * 重置分页数据
	 * @param $table
	 * @param settings
	 * @param totals 总条数
	 */
	resetPageData($table, settings, totals) {
		const _this = this;
		if (isNaN(parseInt(totals, 10))) {
			return;
		}
		const _pageData = this.__getPageData(settings, totals);

		// 生成页码DOM节点
		_this.__createPaginationDOM($table, settings, _pageData);

		// 重置当前页显示条数
		_this.__resetPSize($table, settings, _pageData);

		// 更新Cache
		Cache.setSettings($table, $.extend(true, settings, {pageData: _pageData}));

		const tableWarp = $table.closest('.table-wrap');

		// 分页工具条
		const pageToolbar = $('.page-toolbar', tableWarp);
		pageToolbar.show();
	}

	/**
	 * 跳转至指定页
	 * @param $table
	 * @param settings
	 * @param toPage 跳转页
	 */
	gotoPage($table, settings, toPage) {
		if (!toPage || toPage < 1) {
			toPage = 1;
		}

		// 跳转的指定页大于总页数
		if (toPage > settings.pageData.tPage) {
			toPage = settings.pageData.tPage;
		}

		// 替换被更改的值
		settings.pageData.cPage = toPage;
		settings.pageData.pSize = settings.pageData.pSize || settings.pageSize;

		// 更新缓存
		Cache.setSettings($table, settings);

		// 调用事件、渲染DOM
		const query = $.extend({}, settings.query, settings.sortData, settings.pageData);
		settings.pagingBefore(query);
		Core.refresh($table, () => {
			settings.pagingAfter(query);
		});
	}

	/**
	 * 消毁
	 * @param $table
	 */
	destroy($table) {
		const tableWarp = $table.closest('.table-wrap');
		const pageToolbar = $('.page-toolbar', tableWarp);
		const gp_input = $('.gp-input', pageToolbar);
		const refreshAction	= $('.refresh-action', pageToolbar);
		const sizeArea = $('select[name=pSizeArea]', pageToolbar);

		// 清理: 分页点击事件
		pageToolbar.off('click', 'li');

		// 清理: 快捷跳转事件
		gp_input.unbind('keyup');

		// 清理: 刷新界面事件
		refreshAction.unbind('click');

		// 清理: 设置当前页显示数事件
		sizeArea.unbind('change');
	}

	/**
	 * 生成页码DOM节点
	 * @param $table
	 * @param settings
	 * @param pageData 分页数据格式
	 * @private
     */
	__createPaginationDOM($table, settings, pageData) {
		const tableWarp = $table.closest('.table-wrap');

		// 分页工具条
		const pageToolbar = $('.page-toolbar', tableWarp);

		// 分页区域
		const pagination = $('.pagination', pageToolbar);

		pagination.html(this.__joinPagination(settings, pageData));
	}

	/**
	 * 拼接页码字符串
	 * @param settings
	 * @param pageData 分页数据格式
	 * @private
     */
	__joinPagination(settings, pageData) {
		// 当前页
		let cPage = Number(pageData.cPage || 0);

		// 总页数
		let tPage = Number(pageData.tPage || 0);

		// 临时存储分页HTML片段
		let	tHtml = '';

		// 临时存储末尾页码THML片段
		let	lHtml = '';

		// 配置首页
		let firstClassName = 'first-page';
		let	previousClassName = 'previous-page';

		if (cPage === 1) {
			firstClassName += ' disabled';
			previousClassName += ' disabled';
		}
		tHtml += `

					${I18n.i18nText(settings, 'first-page')}

					${I18n.i18nText(settings, 'previous-page')}

`;
		// 循环开始数
		let i = 1;

		// 循环结束数
		let	maxI = tPage;

		// 配置 first端省略符
		if (cPage > 4) {
			tHtml += `

						1

						...

`;
			i = cPage - 2;
		}
		// 配置 last端省略符
		if ((tPage - cPage) > 4) {
			maxI = cPage + 2;
			lHtml += `

						...

						${ tPage }

`;
		}
		// 配置页码
		for (i; i <= maxI; i++) {
			if (i === cPage) {
				tHtml += `<li class="active">${ cPage }</li>`;
				continue;
			}
			tHtml += `<li c-page="${ i }">${ i }</li>`;
		}
		tHtml += lHtml;

		// 配置下一页与尾页
		let nextClassName = 'next-page';
		let	lastClassName = 'last-page';
		if (cPage >= tPage) {
			nextClassName += ' disabled';
			lastClassName += ' disabled';
		}
		tHtml += `

					${ I18n.i18nText(settings, 'next-page') }

					${ I18n.i18nText(settings, 'last-page') }

`;
		return tHtml;
	}

	/**
	 * 生成每页显示条数选择框据
	 * @param sizeData 选择框自定义条数
	 * @private
     */
	__getPageSizeHtml(sizeData) {
		let pageSizeHtml = '';
		$.each(sizeData, (index, value) => {
			pageSizeHtml += `<option value="${value}">${value}</option>`;
		});
		return pageSizeHtml;
	}

	/**
	 * 绑定页面跳转事件
	 * @param $table
	 * @private
     */
	__bindPageJumpEvent($table) {
		const tableWarp	= $table.closest('.table-wrap');

		// 分页工具条
		const pageToolbar = $('.page-toolbar', tableWarp);

		this.__bindPageClick($table, pageToolbar);
		this.__bindInputEvent($table, pageToolbar);
		this.__bindRefreshEvent(pageToolbar);

	}

	/**
	 * 绑定分页点击事件
	 * @param $table
	 * @param pageToolbar
	 * @private
     */
	__bindPageClick($table, pageToolbar) {
		pageToolbar.off('click', 'li');
		pageToolbar.on('click', 'li', event => {
			const pageAction = $(event.target);

			// 分页页码
			let cPage = pageAction.attr('c-page');
			if (!cPage || !Number(cPage) || pageAction.hasClass('disabled')) {
				Base.outLog('指定页码无法跳转,已停止。原因:1、可能是当前页已处于选中状态; 2、所指向的页不存在', 'info');
				return false;
			}
			cPage = window.parseInt(cPage);
			this.gotoPage($table, Cache.getSettings($table), cPage);
		});
	}

	/**
	 * 绑定快捷跳转事件
	 * @param $table
	 * @param pageToolbar
	 * @private
     */
	__bindInputEvent($table, pageToolbar) {
		const gp_input = $('.gp-input', pageToolbar);

		gp_input.unbind('keyup');
		gp_input.bind('keyup', event => {
			if (event.which !== 13) {
				return;
			}
			let _cPage = parseInt(event.target.value, 10);
			this.gotoPage($table, Cache.getSettings($table), _cPage);
			event.target.value = '';
		});
	}

	/**
	 * 绑定刷新界面事件
	 * @param pageToolbar
	 * @private
     */
	__bindRefreshEvent(pageToolbar) {
		const refreshAction	= $('.refresh-action', pageToolbar);

		refreshAction.unbind('click');
		refreshAction.bind('click', event => {
			const _tableWarp = $(event.target).closest('.table-wrap');
			const _table = $('table[grid-manager]', _tableWarp);

			Core.refresh(_table);
		});
	}

	/**
	 * 绑定设置当前页显示数事件
	 * @param $table
	 * @private
     */
	__bindSetPageSizeEvent($table) {
		const tableWarp = $table.closest('.table-wrap');

		// 分页工具条
		const pageToolbar = $('.page-toolbar', tableWarp);

		// 切换条数区域
		const sizeArea = $('select[name=pSizeArea]', pageToolbar);

		if (!sizeArea || sizeArea.length === 0) {
			Base.outLog('未找到单页显示数切换区域，停止该事件绑定', 'info');
			return false;
		}
		sizeArea.unbind('change');
		sizeArea.bind('change', event => {
			const _size = $(event.target);
			const _tableWarp = _size.closest('.table-wrap');
			const _table = $('table[grid-manager]', _tableWarp);
			const settings = Cache.getSettings($table);
			settings.pageData = {
				cPage: 1,
				pSize: window.parseInt(_size.val())
			};

			Cache.saveUserMemory(_table);

			// 更新缓存
			Cache.setSettings($table, settings);

			// 调用事件、渲染tbody
			const query = $.extend({}, settings.query, settings.sortData, settings.pageData);
			settings.pagingBefore(query);
			Core.refresh(_table, () => {
				settings.pagingAfter(query);
			});
		});
	}

	/**
	 * 重置每页显示条数, 重置条数文字信息 [注: 这个方法只做显示更新, 不操作Cache 数据]
	 * @param $table
	 * @param settings
	 * @param _pageData_ 分页数据格式
	 * @returns {boolean}
	 * @private
     */
	__resetPSize($table, settings, _pageData_) {
		const tableWarp = $table.closest('.table-wrap');
		const toolBar = $('.page-toolbar', tableWarp);
		const pSizeArea = $('select[name="pSizeArea"]', toolBar);
		const pSizeInfo = $('.dataTables_info', toolBar);
		if (!pSizeArea || pSizeArea.length === 0) {
			Base.outLog('未找到条数切换区域，停止该事件绑定', 'info');
			return false;
		}

		// 从多少开始
		const fromNum = _pageData_.cPage === 1 ? 1 : (_pageData_.cPage - 1) * _pageData_.pSize + 1;

		// 到多少结束
		const toNum = _pageData_.cPage * _pageData_.pSize;

		// 总共条数
		const totalNum = _pageData_.tSize;

		const tmpHtml = I18n.i18nText(settings, 'dataTablesInfo', [fromNum, toNum, totalNum]);

		// 根据返回值修正单页条数显示值
		pSizeArea.val(_pageData_.pSize || 10);

		// 修改条数文字信息
		pSizeInfo.html(tmpHtml);
		pSizeArea.show();
		return true;
	}

	/**
	 * 计算并返回分页数据
	 * @param settings
	 * @param totals
	 * @returns {{tPage: number, cPage: *, pSize: *, tSize: *}}
	 * @private
     */
	__getPageData(settings, totals) {
		const _pSize = settings.pageData.pSize || settings.pageSize;
		const _cPage = settings.pageData.cPage || 1;
		return {
			tPage: Math.ceil(totals / _pSize),		// 总页数
			cPage: _cPage,							// 当前页
			pSize: _pSize,							// 每页显示条数
			tSize: totals							// 总条路
		};
	}

	/**
	 * 根据本地缓存配置分页数据
	 * @param $table
	 * @param settings
	 * @private
     */
	__configPageForCache($table, settings) {
		let _data = Cache.getUserMemory($table);

		// 缓存对应
		let	_cache = _data.cache;

		// 每页显示条数
		let	_pSize = null;

		// 验证是否存在每页显示条数缓存数据
		if (!_cache || !_cache.page || !_cache.page.pSize) {
			_pSize = settings.pageSize || 10;
		} else {
			_pSize = _cache.page.pSize;
		}
		const pageData = {
			pSize: _pSize,
			cPage: 1
		};
		$.extend(settings, {pageData: pageData});
		Cache.setSettings($table, settings);
	}
}
export default new AjaxPage();
<commit_msg>code review AjaxPage.js<commit_after>/*
 * AjaxPage: 分页
 * */
import { jTool, Base } from './Base';
import Core from './Core';
import Cache from './Cache';
import I18n from './I18n';
class AjaxPage {
	/**
	 * 分页所需HTML
	 * @param $table
	 * @returns {string}
     */
	createHtml(settings) {
		const html = `

							${ I18n.i18nText(settings, 'goto-first-text') }
							<input type="text" class="gp-input"/>
							${ I18n.i18nText(settings, 'goto-last-text') }

<select name="pSizeArea"></select>

`;
		return html;
	}

	/**
	 * 初始化分页
	 * @param $table
	 * @param settings
     */
	initAjaxPage($table, settings) {
		const sizeData = settings.sizeData;
		// error
		if (!sizeData || sizeData.length === 0) {
			Base.outLog('渲染失败：参数[sizeData]配置错误', 'error');
			return;
		}

		// 根据本地缓存配置每页显示条数
		if (!settings.disableCache) {
			this.__configPageForCache($table, settings);
		}

		const tableWarp = $table.closest('.table-wrap');

		// 分页工具条
		const pageToolbar = jTool('.page-toolbar', tableWarp);

		// 分页区域
		const pSizeArea	= jTool('select[name="pSizeArea"]', pageToolbar);

		pageToolbar.hide();

		// 生成每页显示条数选择框
		pSizeArea.html(this.__getPageSizeHtml(sizeData));

		// 绑定页面跳转事件
		this.__bindPageJumpEvent($table);

		// 绑定设置显示条数切换事件
		this.__bindSetPageSizeEvent($table);
	}

	/**
	 * 重置分页数据
	 * @param $table
	 * @param settings
	 * @param totals 总条数
	 */
	resetPageData($table, settings, totals) {
		const _this = this;
		if (isNaN(parseInt(totals, 10))) {
			return;
		}
		const _pageData = this.__getPageData(settings, totals);

		// 生成页码DOM节点
		_this.__createPaginationDOM($table, settings, _pageData);

		// 重置当前页显示条数
		_this.__resetPSize($table, settings, _pageData);

		// 更新Cache
		Cache.setSettings($table, jTool.extend(true, settings, {pageData: _pageData}));

		const tableWarp = $table.closest('.table-wrap');

		// 分页工具条
		const pageToolbar = jTool('.page-toolbar', tableWarp);
		pageToolbar.show();
	}

	/**
	 * 跳转至指定页
	 * @param $table
	 * @param settings
	 * @param toPage 跳转页
	 */
	gotoPage($table, settings, toPage) {
		if (!toPage || toPage < 1) {
			toPage = 1;
		}

		// 跳转的指定页大于总页数
		if (toPage > settings.pageData.tPage) {
			toPage = settings.pageData.tPage;
		}

		// 替换被更改的值
		settings.pageData.cPage = toPage;
		settings.pageData.pSize = settings.pageData.pSize || settings.pageSize;

		// 更新缓存
		Cache.setSettings($table, settings);

		// 调用事件、渲染DOM
		const query = jTool.extend({}, settings.query, settings.sortData, settings.pageData);
		settings.pagingBefore(query);
		Core.refresh($table, () => {
			settings.pagingAfter(query);
		});
	}

	/**
	 * 消毁
	 * @param $table
	 */
	destroy($table) {
		const tableWarp = $table.closest('.table-wrap');
		const pageToolbar = jTool('.page-toolbar', tableWarp);
		const gp_input = jTool('.gp-input', pageToolbar);
		const refreshAction	= jTool('.refresh-action', pageToolbar);
		const sizeArea = jTool('select[name=pSizeArea]', pageToolbar);

		// 清理: 分页点击事件
		pageToolbar.off('click', 'li');

		// 清理: 快捷跳转事件
		gp_input.unbind('keyup');

		// 清理: 刷新界面事件
		refreshAction.unbind('click');

		// 清理: 设置当前页显示数事件
		sizeArea.unbind('change');
	}

	/**
	 * 生成页码DOM节点
	 * @param $table
	 * @param settings
	 * @param pageData 分页数据格式
	 * @private
     */
	__createPaginationDOM($table, settings, pageData) {
		const tableWarp = $table.closest('.table-wrap');

		// 分页工具条
		const pageToolbar = jTool('.page-toolbar', tableWarp);

		// 分页区域
		const pagination = jTool('.pagination', pageToolbar);

		pagination.html(this.__joinPagination(settings, pageData));
	}

	/**
	 * 拼接页码字符串
	 * @param settings
	 * @param pageData 分页数据格式
	 * @private
     */
	__joinPagination(settings, pageData) {
		// 当前页
		let cPage = Number(pageData.cPage || 0);

		// 总页数
		let tPage = Number(pageData.tPage || 0);

		// 临时存储分页HTML片段
		let	tHtml = '';

		// 临时存储末尾页码THML片段
		let	lHtml = '';

		// 配置首页
		let firstClassName = 'first-page';
		let	previousClassName = 'previous-page';

		if (cPage === 1) {
			firstClassName += ' disabled';
			previousClassName += ' disabled';
		}
		tHtml += `

					${I18n.i18nText(settings, 'first-page')}

					${I18n.i18nText(settings, 'previous-page')}

`;
		// 循环开始数
		let i = 1;

		// 循环结束数
		let	maxI = tPage;

		// 配置 first端省略符
		if (cPage > 4) {
			tHtml += `

						1

						...

`;
			i = cPage - 2;
		}
		// 配置 last端省略符
		if ((tPage - cPage) > 4) {
			maxI = cPage + 2;
			lHtml += `

						...

						${ tPage }

`;
		}
		// 配置页码
		for (i; i <= maxI; i++) {
			if (i === cPage) {
				tHtml += `<li class="active">${ cPage }</li>`;
				continue;
			}
			tHtml += `<li c-page="${ i }">${ i }</li>`;
		}
		tHtml += lHtml;

		// 配置下一页与尾页
		let nextClassName = 'next-page';
		let	lastClassName = 'last-page';
		if (cPage >= tPage) {
			nextClassName += ' disabled';
			lastClassName += ' disabled';
		}
		tHtml += `

					${ I18n.i18nText(settings, 'next-page') }

					${ I18n.i18nText(settings, 'last-page') }

`;
		return tHtml;
	}

	/**
	 * 生成每页显示条数选择框据
	 * @param sizeData 选择框自定义条数
	 * @private
     */
	__getPageSizeHtml(sizeData) {
		let pageSizeHtml = '';
		jTool.each(sizeData, (index, value) => {
			pageSizeHtml += `<option value="${value}">${value}</option>`;
		});
		return pageSizeHtml;
	}

	/**
	 * 绑定页面跳转事件
	 * @param $table
	 * @private
     */
	__bindPageJumpEvent($table) {
		const tableWarp	= $table.closest('.table-wrap');

		// 分页工具条
		const pageToolbar = jTool('.page-toolbar', tableWarp);

		this.__bindPageClick($table, pageToolbar);
		this.__bindInputEvent($table, pageToolbar);
		this.__bindRefreshEvent(pageToolbar);

	}

	/**
	 * 绑定分页点击事件
	 * @param $table
	 * @param pageToolbar
	 * @private
     */
	__bindPageClick($table, pageToolbar) {
		pageToolbar.off('click', 'li');
		pageToolbar.on('click', 'li', event => {
			const pageAction = jTool(event.target);

			// 分页页码
			let cPage = pageAction.attr('c-page');
			if (!cPage || !Number(cPage) || pageAction.hasClass('disabled')) {
				Base.outLog('指定页码无法跳转,已停止。原因:1、可能是当前页已处于选中状态; 2、所指向的页不存在', 'info');
				return false;
			}
			cPage = window.parseInt(cPage);
			this.gotoPage($table, Cache.getSettings($table), cPage);
		});
	}

	/**
	 * 绑定快捷跳转事件
	 * @param $table
	 * @param pageToolbar
	 * @private
     */
	__bindInputEvent($table, pageToolbar) {
		const gp_input = jTool('.gp-input', pageToolbar);

		gp_input.unbind('keyup');
		gp_input.bind('keyup', event => {
			if (event.which !== 13) {
				return;
			}
			let _cPage = parseInt(event.target.value, 10);
			this.gotoPage($table, Cache.getSettings($table), _cPage);
			event.target.value = '';
		});
	}

	/**
	 * 绑定刷新界面事件
	 * @param pageToolbar
	 * @private
     */
	__bindRefreshEvent(pageToolbar) {
		const refreshAction	= jTool('.refresh-action', pageToolbar);

		refreshAction.unbind('click');
		refreshAction.bind('click', event => {
			const _tableWarp = jTool(event.target).closest('.table-wrap');
			const _table = jTool('table[grid-manager]', _tableWarp);

			Core.refresh(_table);
		});
	}

	/**
	 * 绑定设置当前页显示数事件
	 * @param $table
	 * @private
     */
	__bindSetPageSizeEvent($table) {
		const tableWarp = $table.closest('.table-wrap');

		// 分页工具条
		const pageToolbar = jTool('.page-toolbar', tableWarp);

		// 切换条数区域
		const sizeArea = jTool('select[name=pSizeArea]', pageToolbar);

		if (!sizeArea || sizeArea.length === 0) {
			Base.outLog('未找到单页显示数切换区域，停止该事件绑定', 'info');
			return false;
		}
		sizeArea.unbind('change');
		sizeArea.bind('change', event => {
			const _size = jTool(event.target);
			const _tableWarp = _size.closest('.table-wrap');
			const _table = jTool('table[grid-manager]', _tableWarp);
			const settings = Cache.getSettings($table);
			settings.pageData = {
				cPage: 1,
				pSize: window.parseInt(_size.val())
			};

			Cache.saveUserMemory(_table);

			// 更新缓存
			Cache.setSettings($table, settings);

			// 调用事件、渲染tbody
			const query = jTool.extend({}, settings.query, settings.sortData, settings.pageData);
			settings.pagingBefore(query);
			Core.refresh(_table, () => {
				settings.pagingAfter(query);
			});
		});
	}

	/**
	 * 重置每页显示条数, 重置条数文字信息 [注: 这个方法只做显示更新, 不操作Cache 数据]
	 * @param $table
	 * @param settings
	 * @param _pageData_ 分页数据格式
	 * @returns {boolean}
	 * @private
     */
	__resetPSize($table, settings, _pageData_) {
		const tableWarp = $table.closest('.table-wrap');
		const toolBar = jTool('.page-toolbar', tableWarp);
		const pSizeArea = jTool('select[name="pSizeArea"]', toolBar);
		const pSizeInfo = jTool('.dataTables_info', toolBar);
		if (!pSizeArea || pSizeArea.length === 0) {
			Base.outLog('未找到条数切换区域，停止该事件绑定', 'info');
			return false;
		}

		// 从多少开始
		const fromNum = _pageData_.cPage === 1 ? 1 : (_pageData_.cPage - 1) * _pageData_.pSize + 1;

		// 到多少结束
		const toNum = _pageData_.cPage * _pageData_.pSize;

		// 总共条数
		const totalNum = _pageData_.tSize;

		const tmpHtml = I18n.i18nText(settings, 'dataTablesInfo', [fromNum, toNum, totalNum]);

		// 根据返回值修正单页条数显示值
		pSizeArea.val(_pageData_.pSize || 10);

		// 修改条数文字信息
		pSizeInfo.html(tmpHtml);
		pSizeArea.show();
		return true;
	}

	/**
	 * 计算并返回分页数据
	 * @param settings
	 * @param totals
	 * @returns {{tPage: number, cPage: *, pSize: *, tSize: *}}
	 * @private
     */
	__getPageData(settings, totals) {
		const _pSize = settings.pageData.pSize || settings.pageSize;
		const _cPage = settings.pageData.cPage || 1;
		return {
			tPage: Math.ceil(totals / _pSize),		// 总页数
			cPage: _cPage,							// 当前页
			pSize: _pSize,							// 每页显示条数
			tSize: totals							// 总条路
		};
	}

	/**
	 * 根据本地缓存配置分页数据
	 * @param $table
	 * @param settings
	 * @private
     */
	__configPageForCache($table, settings) {
		let _data = Cache.getUserMemory($table);

		// 缓存对应
		let	_cache = _data.cache;

		// 每页显示条数
		let	_pSize = null;

		// 验证是否存在每页显示条数缓存数据
		if (!_cache || !_cache.page || !_cache.page.pSize) {
			_pSize = settings.pageSize || 10;
		} else {
			_pSize = _cache.page.pSize;
		}
		const pageData = {
			pSize: _pSize,
			cPage: 1
		};
		jTool.extend(settings, {pageData: pageData});
		Cache.setSettings($table, settings);
	}
}
export default new AjaxPage();
<|endoftext|>


<commit_before>import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';

import NativeCheckbox from './NativeCheckbox';
import nodeShape from './nodeShape';

class TreeNode extends React.Component {
    static propTypes = {
        checked: PropTypes.number.isRequired,
        disabled: PropTypes.bool.isRequired,
        expandDisabled: PropTypes.bool.isRequired,
        expanded: PropTypes.bool.isRequired,
        label: PropTypes.node.isRequired,
        optimisticToggle: PropTypes.bool.isRequired,
        showNodeIcon: PropTypes.bool.isRequired,
        treeId: PropTypes.string.isRequired,
        value: PropTypes.string.isRequired,
        onCheck: PropTypes.func.isRequired,
        onExpand: PropTypes.func.isRequired,

        children: PropTypes.node,
        className: PropTypes.string,
        expandOnClick: PropTypes.bool,
        icon: PropTypes.node,
        rawChildren: PropTypes.arrayOf(nodeShape),
        showCheckbox: PropTypes.bool,
        onClick: PropTypes.func,
    };

    static defaultProps = {
        children: null,
        className: null,
        expandOnClick: false,
        icon: null,
        rawChildren: null,
        showCheckbox: true,
        onClick: () => {},
    };

    constructor(props) {
        super(props);

        this.onCheck = this.onCheck.bind(this);
        this.onClick = this.onClick.bind(this);
        this.onExpand = this.onExpand.bind(this);
    }

    onCheck() {
        let isChecked = false;

        // Toggle off state to checked
        if (this.props.checked === 0) {
            isChecked = true;
        }

        // Toggle partial state based on cascade model
        if (this.props.checked === 2) {
            isChecked = this.props.optimisticToggle;
        }

        this.props.onCheck({
            value: this.props.value,
            checked: isChecked,
            children: this.props.rawChildren,
        });
    }

    onClick() {
        let isChecked = false;

        if (this.props.checked === 1) {
            isChecked = true;
        }

        // Get partial state based on cascade model
        if (this.props.checked === 2) {
            isChecked = this.props.optimisticToggle;
        }

        // Auto expand if enabled
        if (this.hasChildren() && this.props.expandOnClick) {
            this.onExpand();
        }

        this.props.onClick({
            value: this.props.value,
            checked: isChecked,
            children: this.props.rawChildren,
        });
    }

    onExpand() {
        this.props.onExpand({
            value: this.props.value,
            expanded: !this.props.expanded,
        });
    }

    hasChildren() {
        return this.props.rawChildren !== null;
    }

    renderCollapseButton() {
        const { expandDisabled } = this.props;

        if (!this.hasChildren()) {
            return (

            );
        }

        return (
            <button
                aria-label="Toggle"
                className="rct-collapse rct-collapse-btn"
                disabled={expandDisabled}
                title="Toggle"
                type="button"
                onClick={this.onExpand}
            >
                {this.renderCollapseIcon()}
            </button>
        );
    }

    renderCollapseIcon() {
        if (!this.props.expanded) {
            return ;
        }

        return ;
    }

    renderCheckboxIcon() {
        if (this.props.checked === 0) {
            return ;
        }

        if (this.props.checked === 1) {
            return ;
        }

        return ;
    }

    renderNodeIcon() {
        if (this.props.icon !== null) {
            return this.props.icon;
        }

        if (!this.hasChildren()) {
            return ;
        }

        if (!this.props.expanded) {
            return ;
        }

        return ;
    }

    renderBareLabel(children) {
        const { onClick } = this.props;

        const clickable = onClick.toString() !== TreeNode.defaultProps.onClick.toString();

        return (

                {clickable ? (

                        {children}

                ) : children}

        );
    }

    renderCheckboxLabel(children) {
        const {
            checked,
            disabled,
            label,
            treeId,
            value,
            onClick,
        } = this.props;

        const clickable = onClick.toString() !== TreeNode.defaultProps.onClick.toString();
        const inputId = `${treeId}-${String(value).split(' ').join('_')}`;

        const render = [(

                <NativeCheckbox
                    checked={checked === 1}
                    disabled={disabled}
                    id={inputId}
                    indeterminate={checked === 2}
                    onChange={this.onCheck}
                />

                    {this.renderCheckboxIcon()}

                {!clickable ? children : null}

        )];

        if (clickable) {
            render.push((

                    {children}

            ));
        }

        return render;
    }

    renderLabel() {
        const { label, showCheckbox, showNodeIcon } = this.props;

        const labelChildren = [
            showNodeIcon ? (

                    {this.renderNodeIcon()}

            ) : null,

                {label}
            ,
        ];

        if (!showCheckbox) {
            return this.renderBareLabel(labelChildren);
        }

        return this.renderCheckboxLabel(labelChildren);
    }

    renderChildren() {
        if (!this.props.expanded) {
            return null;
        }

        return this.props.children;
    }

    render() {
        const { className, disabled } = this.props;
        const nodeClass = classNames({
            'rct-node': true,
            'rct-node-parent': this.hasChildren(),
            'rct-node-leaf': !this.hasChildren(),
            'rct-disabled': disabled,
        }, className);

        return (

                    {this.renderCollapseButton()}
                    {this.renderLabel()}

                {this.renderChildren()}

        );
    }
}

export default TreeNode;
<commit_msg>eslint-disable-line comment - fixes #95<commit_after>import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';

import NativeCheckbox from './NativeCheckbox';
import nodeShape from './nodeShape';

class TreeNode extends React.Component {
    static propTypes = {
        checked: PropTypes.number.isRequired,
        disabled: PropTypes.bool.isRequired,
        expandDisabled: PropTypes.bool.isRequired,
        expanded: PropTypes.bool.isRequired,
        label: PropTypes.node.isRequired,
        optimisticToggle: PropTypes.bool.isRequired,
        showNodeIcon: PropTypes.bool.isRequired,
        treeId: PropTypes.string.isRequired,
        value: PropTypes.string.isRequired,
        onCheck: PropTypes.func.isRequired,
        onExpand: PropTypes.func.isRequired,

        children: PropTypes.node,
        className: PropTypes.string,
        expandOnClick: PropTypes.bool,
        icon: PropTypes.node,
        rawChildren: PropTypes.arrayOf(nodeShape),
        showCheckbox: PropTypes.bool,
        onClick: PropTypes.func,
    };

    static defaultProps = {
        children: null,
        className: null,
        expandOnClick: false,
        icon: null,
        rawChildren: null,
        showCheckbox: true,
        onClick: () => {},
    };

    constructor(props) {
        super(props);

        this.onCheck = this.onCheck.bind(this);
        this.onClick = this.onClick.bind(this);
        this.onExpand = this.onExpand.bind(this);
    }

    onCheck() {
        let isChecked = false;

        // Toggle off state to checked
        if (this.props.checked === 0) {
            isChecked = true;
        }

        // Toggle partial state based on cascade model
        if (this.props.checked === 2) {
            isChecked = this.props.optimisticToggle;
        }

        this.props.onCheck({
            value: this.props.value,
            checked: isChecked,
            children: this.props.rawChildren,
        });
    }

    onClick() {
        let isChecked = false;

        if (this.props.checked === 1) {
            isChecked = true;
        }

        // Get partial state based on cascade model
        if (this.props.checked === 2) {
            isChecked = this.props.optimisticToggle;
        }

        // Auto expand if enabled
        if (this.hasChildren() && this.props.expandOnClick) {
            this.onExpand();
        }

        this.props.onClick({
            value: this.props.value,
            checked: isChecked,
            children: this.props.rawChildren,
        });
    }

    onExpand() {
        this.props.onExpand({
            value: this.props.value,
            expanded: !this.props.expanded,
        });
    }

    hasChildren() {
        return this.props.rawChildren !== null;
    }

    renderCollapseButton() {
        const { expandDisabled } = this.props;

        if (!this.hasChildren()) {
            return (

            );
        }

        return (
            <button
                aria-label="Toggle"
                className="rct-collapse rct-collapse-btn"
                disabled={expandDisabled}
                title="Toggle"
                type="button"
                onClick={this.onExpand}
            >
                {this.renderCollapseIcon()}
            </button>
        );
    }

    renderCollapseIcon() {
        if (!this.props.expanded) {
            return ;
        }

        return ;
    }

    renderCheckboxIcon() {
        if (this.props.checked === 0) {
            return ;
        }

        if (this.props.checked === 1) {
            return ;
        }

        return ;
    }

    renderNodeIcon() {
        if (this.props.icon !== null) {
            return this.props.icon;
        }

        if (!this.hasChildren()) {
            return ;
        }

        if (!this.props.expanded) {
            return ;
        }

        return ;
    }

    renderBareLabel(children) {
        const { onClick } = this.props;

        const clickable = onClick.toString() !== TreeNode.defaultProps.onClick.toString();

        return (

                {clickable ? (

                        {children}

                ) : children}

        );
    }

    renderCheckboxLabel(children) {
        const {
            checked,
            disabled,
            label,
            treeId,
            value,
            onClick,
        } = this.props;

        const clickable = onClick.toString() !== TreeNode.defaultProps.onClick.toString();
        const inputId = `${treeId}-${String(value).split(' ').join('_')}`;

        const render = [(
             {/* eslint-disable-line jsx-a11y/label-has-for */}
                <NativeCheckbox
                    checked={checked === 1}
                    disabled={disabled}
                    id={inputId}
                    indeterminate={checked === 2}
                    onChange={this.onCheck}
                />

                    {this.renderCheckboxIcon()}

                {!clickable ? children : null}

        )];

        if (clickable) {
            render.push((

                    {children}

            ));
        }

        return render;
    }

    renderLabel() {
        const { label, showCheckbox, showNodeIcon } = this.props;

        const labelChildren = [
            showNodeIcon ? (

                    {this.renderNodeIcon()}

            ) : null,

                {label}
            ,
        ];

        if (!showCheckbox) {
            return this.renderBareLabel(labelChildren);
        }

        return this.renderCheckboxLabel(labelChildren);
    }

    renderChildren() {
        if (!this.props.expanded) {
            return null;
        }

        return this.props.children;
    }

    render() {
        const { className, disabled } = this.props;
        const nodeClass = classNames({
            'rct-node': true,
            'rct-node-parent': this.hasChildren(),
            'rct-node-leaf': !this.hasChildren(),
            'rct-disabled': disabled,
        }, className);

        return (

                    {this.renderCollapseButton()}
                    {this.renderLabel()}

                {this.renderChildren()}

        );
    }
}

export default TreeNode;
<|endoftext|>


<commit_before>// ------------------------------------------------------------------------
// gmaps: core/add.js
// ------------------------------------------------------------------------

var Core = ((Core) => {
  "use strict"

  // ----------------------------------------------------------------------
  // Constants
  // ----------------------------------------------------------------------

  const RequiredParms = {
    Label   : [ "position", "text" ],
    Marker  : [ "position" ],
    Polygon : [ ["path", "paths"] ]
  }

  // ----------------------------------------------------------------------
  // Public Methods
  // ----------------------------------------------------------------------

  Core.addComponent = function(parms) {
    const compOptions = parms.compOptions
    const map         = parms.map
    const type        = Util.getComponentType(parms.type)

    if (Util.validMapComponent(type)) {

      if ($.type(compOptions) === "array") {
        return _multiAdd(map, type, compOptions)
      }

      if ($.type(compOptions) === "object") {
        if (_validateParms(map, type, compOptions)) {
          const newCompArray = Util.getNewComponentArray(type, map)
          newCompArray.push(_add(map, type, compOptions))
          return newCompArray
        }
      }
    }
    else {
      return Util.throwError({
        method  : "add",
        message : `${type} is not a valid map component`,
        obj     : {type: type}
      })
    }
  }

  // ----------------------------------------------------------------------
  // Private Functions
  // ----------------------------------------------------------------------

  function _add(map, type, compOptions) {
    compOptions = Util.convertComponentOptions({
      compType    : type,
      compOptions : compOptions
    })

    if (compOptions.id === undefined) {
      compOptions.id = _getAutoId(map, type)
    }

    const comp = new Components[type]({
      id      : compOptions.id,
      options : _mergeDefaults(map, type, compOptions)
    })

    map.components[type].push(comp)
    return comp
  }

  function _multiAdd(map, type, compOptionsArray) {
    const newCompArray = Util.getNewComponentArray(type, map)

    for (var i = 0, i_end = compOptionsArray.length; i < i_end; i++) {
      const compOptions = compOptionsArray[i]

      if (_validateParms(map, type, compOptions)) {
        newCompArray.push(_add(map, type, compOptions))
      }
    }

    return newCompArray
  }

  function _getAutoId(map, type) {
    const id = map.components[type].seed++
    return `__${id}__`
  }

  function _mergeDefaults(map, type, compOptions) {
    const namespace = Util.getSetting(`${type}Options`)
    const defaults  = map.settings[namespace] || {}
    const options   = $.extend({}, defaults, compOptions)
    options.map     = map.obj

    // delete any options that are only for gmaps
    delete options.id

    return options
  }

  // ----------------------------------------------------------------------
  // Validation Functions
  // ----------------------------------------------------------------------

  function _requiredParmsAreEmpty(reqParms, parms) {
    return reqParms.map(function(key) {
      return parms[key] !== ""
          && parms[key] !== null
          && parms[key] !== undefined
          && parms[key] !== false
    }).indexOf(true) === -1
  }

  function _validateParms(map, type, parms) {

    // Check if Id already exists
    if (map.components[type].includes(parms.id) === true) {
      return Util.throwError({
        method  : "add",
        message : `A ${type} with an id of ${parms.id} already exists`,
        obj     : parms
      })
    }

    // Check if all required parms have values
    for (var i = 0, i_end = RequiredParms[type].length; i < i_end; i++) {
      const reqParm = Util.toArray(RequiredParms[type][i])

      if (_requiredParmsAreEmpty(reqParm, parms)) {
        return Util.throwError({
          method  : "add",
          message : `${reqParm.join(" or ")} must have a value`,
          obj     : parms
        })
      }
    }

    return true
  }

  return Core
})(Core || (Core = {}))
<commit_msg>force component ids to be a string or number<commit_after>// ------------------------------------------------------------------------
// gmaps: core/add.js
// ------------------------------------------------------------------------

var Core = ((Core) => {
  "use strict"

  // ----------------------------------------------------------------------
  // Constants
  // ----------------------------------------------------------------------

  const RequiredParms = {
    Label   : [ "position", "text" ],
    Marker  : [ "position" ],
    Polygon : [ ["path", "paths"] ]
  }

  // ----------------------------------------------------------------------
  // Public Methods
  // ----------------------------------------------------------------------

  Core.addComponent = function(parms) {
    const compOptions = parms.compOptions
    const map         = parms.map
    const type        = Util.getComponentType(parms.type)

    if (Util.validMapComponent(type)) {

      if ($.type(compOptions) === "array") {
        return _multiAdd(map, type, compOptions)
      }

      if ($.type(compOptions) === "object") {
        if (_validateParms(map, type, compOptions)) {
          const newCompArray = Util.getNewComponentArray(type, map)
          newCompArray.push(_add(map, type, compOptions))
          return newCompArray
        }
      }
    }
    else {
      return Util.throwError({
        method  : "add",
        message : `${type} is not a valid map component`,
        obj     : {type: type}
      })
    }
  }

  // ----------------------------------------------------------------------
  // Private Functions
  // ----------------------------------------------------------------------

  function _add(map, type, compOptions) {
    compOptions = Util.convertComponentOptions({
      compType    : type,
      compOptions : compOptions
    })

    if ($.type(compOptions.id) !== "string" && $.type(compOptions.id) !== "number") {
      compOptions.id = _getAutoId(map, type)
    }

    const comp = new Components[type]({
      id      : compOptions.id,
      options : _mergeDefaults(map, type, compOptions)
    })

    map.components[type].push(comp)
    return comp
  }

  function _multiAdd(map, type, compOptionsArray) {
    const newCompArray = Util.getNewComponentArray(type, map)

    for (var i = 0, i_end = compOptionsArray.length; i < i_end; i++) {
      const compOptions = compOptionsArray[i]

      if (_validateParms(map, type, compOptions)) {
        newCompArray.push(_add(map, type, compOptions))
      }
    }

    return newCompArray
  }

  function _getAutoId(map, type) {
    const id = map.components[type].seed++
    return `__${id}__`
  }

  function _mergeDefaults(map, type, compOptions) {
    const namespace = Util.getSetting(`${type}Options`)
    const defaults  = map.settings[namespace] || {}
    const options   = $.extend({}, defaults, compOptions)
    options.map     = map.obj

    // delete any options that are only for gmaps
    delete options.id

    return options
  }

  // ----------------------------------------------------------------------
  // Validation Functions
  // ----------------------------------------------------------------------

  function _requiredParmsAreEmpty(reqParms, parms) {
    return reqParms.map(function(key) {
      return parms[key] !== ""
          && parms[key] !== null
          && parms[key] !== undefined
          && parms[key] !== false
    }).indexOf(true) === -1
  }

  function _validateParms(map, type, parms) {

    // Check if Id already exists
    if (map.components[type].includes(parms.id) === true) {
      return Util.throwError({
        method  : "add",
        message : `A ${type} with an id of ${parms.id} already exists`,
        obj     : parms
      })
    }

    // Check if all required parms have values
    for (var i = 0, i_end = RequiredParms[type].length; i < i_end; i++) {
      const reqParm = Util.toArray(RequiredParms[type][i])

      if (_requiredParmsAreEmpty(reqParm, parms)) {
        return Util.throwError({
          method  : "add",
          message : `${reqParm.join(" or ")} must have a value`,
          obj     : parms
        })
      }
    }

    return true
  }

  return Core
})(Core || (Core = {}))
<|endoftext|>


<commit_before>/*jshint esversion: 6 */
const Router = require('express').Router;

const MongoClient = require('mongodb').MongoClient;
let db = null;
MongoClient.connect("mongodb://localhost:27017/db", function(err, mongo) {
  if(!err) {
    console.log("We are connected");
	mongo.collection('timesseries', function(err, collection) {
		if(!err) {
			console.log("Timeseries collection created");
			collection.createIndex( { "datasource_id": 1 }, { unique: false } );
			collection.createIndex( { "timestamp": 1 }, { unique: false } );
			db = collection;
		}
	});
  } else {
	  console.log("[ERROR] can't connect to mongo");
  }
});

module.exports.api = function (subscriptionManager) {
	let router = Router({mergeParams: true});

	const latest = function (req, res, next) {
		const datasource_id = req.params.datasourceid;
		db.find({ datasource_id: datasource_id }).limit(1).sort({ timestamp: -1 }).toArray(function (err, doc) {
			if (err) {
				console.log('[Error]::', req.originalUrl);
				// TODO: Status code + document
				res.status(400).send(err);
				return
			}
			res.send(doc);
		});
	};

	const since = function (req, res, next) {
		const datasource_id = req.params.datasourceid;
		const timestamp = req.body.startTimestamp;
		console.log("SINCE", datasource_id, timestamp);
		db.find({ 'datasource_id': datasource_id, 'timestamp':{$gte:timestamp} }).sort({ 'timestamp': 1 }).toArray(function (err, doc) {
			if (err) {
				console.log('[Error]::', req.originalUrl, timestamp);
				// TODO: Status code + document
				res.status(400).send(err);
				return;
			}
			res.send(doc);
		});
	};

	const range = function (req, res, next) {
		const datasource_id = req.params.datasourceid;
		const start = req.body.startTimestamp;
		const end = req.body.endTimestamp;

		db.find({ 'datasource_id': datasource_id, 'timestamp':{$gte:start, $lte:end}}).sort({ timestamp: 1 }).toArray(function (err, doc) {
			if (err) {
				console.log('[Error]::', req.originalUrl, timestamp);
				// TODO: Status code + document
				res.status(400).send(err);
				return;
			}
			res.send(doc);
		});
	};

	const index = function (req, res, next) {
		const indexName = req.body.index;
		let options = {};
		options[indexName] = 1;
		db.createIndex(options, function (err) {
			if (err) {
				console.log('[Error]::', err);
				res.status(400).send(err);
				return;
			}
			res.send();
		});
	};

	const query = function (req, res, next) {
		try{
			const query = JSON.parse(req.body.query);
			const limit = req.body.limit || 0;
			const sort = JSON.parse(req.body.sort || '{}');
			console.log(query);
			db.find(query).sort(sort).limit(limit).toArray(function (err,docs) {
				if (err) {
					console.log('[Error]::', err);
					res.status(400).send(err);
					return;
				}
				res.send(docs);
			});
		} catch (e) {
			console.log('[Error]:: bad query',e);
			res.status(400).send('[Error]:: bad query');
		}
	};

	router.get('/', function (req, res, next) {

		var cmd = req.params.cmd;
		if(cmd == 'latest') {
			latest(req, res, next);
		}
		if(cmd == 'since') {
			since(req, res, next);
		}
		if(cmd == 'range') {
			range(req, res, next);
		}
		if(cmd == 'index') {
			index(req, res, next);
		}
		if(cmd == 'query') {
			query(req, res, next);
		}

	});

	// TODO: .all, see #15
	router.post('/', function (req, res, next) {

		//trust the drivers timestamp
		let timestamp = null;
		if(req.body.data.timestamp && Number.isInteger(req.body.data.timestamp)) {
			timestamp = req.body.data.timestamp;
		} else {
			timestamp = Date.now();
		}

		var data = {
			datasource_id: req.params.datasourceid,
			'data': req.body.data,
			'timestamp': timestamp
		};

		db.insert(data, function (err, doc) {
			if (err) {
				console.log('[Error]::', req.originalUrl, data, err);
				// TODO: Status code + document
				res.status(400).send(err);
				return;
			}
			res.send(doc.ops[0]);
		});
		subscriptionManager.emit('/' + req.params.datasourceid + '/ts', data);
	});

	return router;
};
<commit_msg>Data can be in the body or body.data<commit_after>/*jshint esversion: 6 */
const Router = require('express').Router;

const MongoClient = require('mongodb').MongoClient;
let db = null;
MongoClient.connect("mongodb://localhost:27017/db", function(err, mongo) {
  if(!err) {
    console.log("We are connected");
	mongo.collection('timesseries', function(err, collection) {
		if(!err) {
			console.log("Timeseries collection created");
			collection.createIndex( { "datasource_id": 1 }, { unique: false } );
			collection.createIndex( { "timestamp": 1 }, { unique: false } );
			db = collection;
		}
	});
  } else {
	  console.log("[ERROR] can't connect to mongo");
  }
});

module.exports.api = function (subscriptionManager) {
	let router = Router({mergeParams: true});

	const latest = function (req, res, next) {
		const datasource_id = req.params.datasourceid;
		db.find({ datasource_id: datasource_id }).limit(1).sort({ timestamp: -1 }).toArray(function (err, doc) {
			if (err) {
				console.log('[Error]::', req.originalUrl);
				// TODO: Status code + document
				res.status(400).send(err);
				return
			}
			res.send(doc);
		});
	};

	const since = function (req, res, next) {
		const datasource_id = req.params.datasourceid;
		const timestamp = req.body.startTimestamp;
		console.log("SINCE", datasource_id, timestamp);
		db.find({ 'datasource_id': datasource_id, 'timestamp':{$gte:timestamp} }).sort({ 'timestamp': 1 }).toArray(function (err, doc) {
			if (err) {
				console.log('[Error]::', req.originalUrl, timestamp);
				// TODO: Status code + document
				res.status(400).send(err);
				return;
			}
			res.send(doc);
		});
	};

	const range = function (req, res, next) {
		const datasource_id = req.params.datasourceid;
		const start = req.body.startTimestamp;
		const end = req.body.endTimestamp;

		db.find({ 'datasource_id': datasource_id, 'timestamp':{$gte:start, $lte:end}}).sort({ timestamp: 1 }).toArray(function (err, doc) {
			if (err) {
				console.log('[Error]::', req.originalUrl, timestamp);
				// TODO: Status code + document
				res.status(400).send(err);
				return;
			}
			res.send(doc);
		});
	};

	const index = function (req, res, next) {
		const indexName = req.body.index;
		let options = {};
		options[indexName] = 1;
		db.createIndex(options, function (err) {
			if (err) {
				console.log('[Error]::', err);
				res.status(400).send(err);
				return;
			}
			res.send();
		});
	};

	const query = function (req, res, next) {
		try{
			const query = JSON.parse(req.body.query);
			const limit = req.body.limit || 0;
			const sort = JSON.parse(req.body.sort || '{}');
			console.log(query);
			db.find(query).sort(sort).limit(limit).toArray(function (err,docs) {
				if (err) {
					console.log('[Error]::', err);
					res.status(400).send(err);
					return;
				}
				res.send(docs);
			});
		} catch (e) {
			console.log('[Error]:: bad query',e);
			res.status(400).send('[Error]:: bad query');
		}
	};

	router.get('/', function (req, res, next) {

		var cmd = req.params.cmd;
		if(cmd == 'latest') {
			latest(req, res, next);
		}
		if(cmd == 'since') {
			since(req, res, next);
		}
		if(cmd == 'range') {
			range(req, res, next);
		}
		if(cmd == 'index') {
			index(req, res, next);
		}
		if(cmd == 'query') {
			query(req, res, next);
		}

	});

	// TODO: .all, see #15
	router.post('/', function (req, res, next) {

		//trust the drivers timestamp
		let timestamp = null;
		if(req.body && req.body.data && req.body.data.timestamp && Number.isInteger(req.body.data.timestamp)) {
			timestamp = req.body.data.timestamp;
		} else if (req.body && req.body.timestamp && Number.isInteger(req.body.timestamp)) {
			timestamp = req.body.timestamp;
		} else {
			timestamp = Date.now();
		}

		var data = {
			datasource_id: req.params.datasourceid,
			'data': req.body.data || req.body,
			'timestamp': timestamp
		};

		db.insert(data, function (err, doc) {
			if (err) {
				console.log('[Error]::', req.originalUrl, data, err);
				// TODO: Status code + document
				res.status(400).send(err);
				return;
			}
			res.send(doc.ops[0]);
		});
		subscriptionManager.emit('/' + req.params.datasourceid + '/ts', data);
	});

	return router;
};
<|endoftext|>


<commit_before>function fizzbuzz(number) {
  if (number % 15 === 0) {
    return "fizzbuzz";
  } else if (number % 5 === 0) {
    return "buzz";
  } else if (number % 3 === 0) {
    return "fizz";
  } else {
    return number;
  }
}
<commit_msg>Refactor fizzbuzz to split responsibility<commit_after>function fizzbuzz(number) {
  if (number % 15 === 0) {
    console.log("fizzbuzz");
  } else if (number % 5 === 0) {
    console.log("buzz");
  } else if (number % 3 === 0) {
    console.log("fizz");
  } else {
    console.log(number);;
  }
}

function rightPoint(maxNum, mult1, mult2) {
  for (var i=maxNum; i>0; i--) {
    rightPointLogic(i, mult1, mult2);
  };
}

function rightPointLogic(num, m1, m2) {
  var msg = "";

  if (num % m2 === 0) {
    msg += "Right";
  }
  if (num % m1 === 0) {
    msg += "Point";
  }
  if (msg.length === 0) {
    msg = num.toString();
  }

  print(msg);
}

function print(msg) {
  console.log(msg);
}

rightPoint(50, 4, 5);

// console.log(fizzbuzz());
<|endoftext|>


<commit_before>import R from 'ramda';
import Bacon from 'baconjs';
import Storage from './utils/storage-util';
import ActionTypes from './actions/action-types';
import TimeTravelAction from './actions/timetravel-action.js';

const isAction = R.pathEq(
  ['action', 'type']
);

const isRevert = isAction(
  ActionTypes.TIMETRAVEL_REVERT
);

const isClutch = isAction(
  ActionTypes.TIMETRAVEL_CLUTCH
);

const isDeclutch = isAction(
  ActionTypes.TIMETRAVEL_DECLUTCH
);

const findRecordByName = (name, records) => (
  R.find(R.propEq('name', name), records)
);

const findTimeRecord = R.converge(
  findRecordByName, [
    R.prop('name'),
    R.path(['action', 'timeslice', 'records'])
  ]
);

const getTimeRecord = R.converge(
  R.defaultTo, [
    R.identity,
    findTimeRecord
  ]
);

const mapTimeRevert = R.when(
  isRevert,
  getTimeRecord
);

const isNotTimeTravel = R.pipe(
  R.nthArg(0),
  R.path(['action', 'type']),
  R.flip(R.contains)([
    ActionTypes.TIMETRAVEL_HISTORY,
    ActionTypes.TIMETRAVEL_REVERT,
    ActionTypes.TIMETRAVEL_DECLUTCH,
    ActionTypes.TIMETRAVEL_CLUTCH
  ]),
  R.not
);

const rejectDeclutch = R.ifElse(
  // if was declutched and not time travelling.
  R.allPass([R.nthArg(1), isNotTimeTravel]),
  // abort the reduce.
  R.always(null),
  // otherwise continue reducing.
  R.nthArg(0)
);

const getDeclutchStream = (preStream) => (
  Bacon.mergeAll(
    preStream
      .filter(isClutch)
      .map(R.always(false)),

    preStream
      .filter(isDeclutch)
      .map(R.always(true))
  )
);

export const getPreReduce = () => {
  let preStream = new Bacon.Bus();
  let declutchStream = new Bacon.Bus();
  let declutchProperty = declutchStream.toProperty(
    // declutch by default when resuming from session storage.
    !!Storage.load('bduxHistory'));

  declutchStream.plug(
    // whether currently clutched to dispatcher.
    getDeclutchStream(preStream)
  );

  // start recording.
  TimeTravelAction.start();
  // resume from session storage.
  TimeTravelAction.resume();

  return {
    input: preStream,
    output: Bacon.when(
        // filter out actions while declutched.
        [preStream, declutchProperty], rejectDeclutch)
      .filter(R.identity)
      // record the action and store states.
      .doAction(TimeTravelAction.record)
      // handle revert action.
      .map(mapTimeRevert)
  };
};
<commit_msg>Handle undefined timeslice records<commit_after>import R from 'ramda';
import Bacon from 'baconjs';
import Storage from './utils/storage-util';
import ActionTypes from './actions/action-types';
import TimeTravelAction from './actions/timetravel-action.js';

const isAction = R.pathEq(
  ['action', 'type']
);

const isRevert = isAction(
  ActionTypes.TIMETRAVEL_REVERT
);

const isClutch = isAction(
  ActionTypes.TIMETRAVEL_CLUTCH
);

const isDeclutch = isAction(
  ActionTypes.TIMETRAVEL_DECLUTCH
);

const findRecordByName = (name, records) => (
  R.find(R.propEq('name', name), records || [])
);

const findTimeRecord = R.converge(
  findRecordByName, [
    R.prop('name'),
    R.path(['action', 'timeslice', 'records'])
  ]
);

const getTimeRecord = R.converge(
  R.defaultTo, [
    R.identity,
    findTimeRecord
  ]
);

const mapTimeRevert = R.when(
  isRevert,
  getTimeRecord
);

const isNotTimeTravel = R.pipe(
  R.nthArg(0),
  R.path(['action', 'type']),
  R.flip(R.contains)([
    ActionTypes.TIMETRAVEL_HISTORY,
    ActionTypes.TIMETRAVEL_REVERT,
    ActionTypes.TIMETRAVEL_DECLUTCH,
    ActionTypes.TIMETRAVEL_CLUTCH
  ]),
  R.not
);

const rejectDeclutch = R.ifElse(
  // if was declutched and not time travelling.
  R.allPass([R.nthArg(1), isNotTimeTravel]),
  // abort the reduce.
  R.always(null),
  // otherwise continue reducing.
  R.nthArg(0)
);

const getDeclutchStream = (preStream) => (
  Bacon.mergeAll(
    preStream
      .filter(isClutch)
      .map(R.always(false)),

    preStream
      .filter(isDeclutch)
      .map(R.always(true))
  )
);

export const getPreReduce = () => {
  let preStream = new Bacon.Bus();
  let declutchStream = new Bacon.Bus();
  let declutchProperty = declutchStream.toProperty(
    // declutch by default when resuming from session storage.
    !!Storage.load('bduxHistory'));

  declutchStream.plug(
    // whether currently clutched to dispatcher.
    getDeclutchStream(preStream)
  );

  // start recording.
  TimeTravelAction.start();
  // resume from session storage.
  TimeTravelAction.resume();

  return {
    input: preStream,
    output: Bacon.when(
        // filter out actions while declutched.
        [preStream, declutchProperty], rejectDeclutch)
      .filter(R.identity)
      // record the action and store states.
      .doAction(TimeTravelAction.record)
      // handle revert action.
      .map(mapTimeRevert)
  };
};
<|endoftext|>


<commit_before>module.exports = (() => {
  'use strict';

  const
    // Redux = require('redux'),

    todo_action = (state, action) => {
      if('ADD_TODO' === action.type) {
        return {
          'id': action.id,
          'text': action.text,
          'completed': false
        };
      }
      else if('TOGGLE_TODO' === action.type) {
        if(action.id !== state.id) {
          return state;
        }

        return Object.assign({}, state, { 'completed': !state.completed });
      }

      return state;
    },

    todos = (state, action) => {
      state = state || [];

      if('ADD_TODO' === action.type) {
        return [
          ...state,
          todo_action(null, action)
        ];
      }
      else if('TOGGLE_TODO' === action.type) {
        return state.map((todo) => todo_action(todo, action));
      }

      return state;
    },

    visibilityFilter = (state, action) => {
      state = state || 'SHOW_ALL';

      if('SET_VISIBILITY_FILTER' === action.type) {
        return action.filter;
      }

      return state;
    },

    combineReducers = (reducers) => {
      return (state, action) => {
        state = state || {};

        return Object.keys(reducers).reduce(
          (nextState, key) => {
            nextState[key] = reducers[key](state[key], action);

            return nextState;
          },
          {}
        );
      };
    };

  return combineReducers({
    todos,
    visibilityFilter
  });

})();
<commit_msg>esprima-fb requires spread item to be the last<commit_after>module.exports = (() => {
  'use strict';

  const
    // Redux = require('redux'),

    todo_action = (state, action) => {
      if('ADD_TODO' === action.type) {
        return {
          'id': action.id,
          'text': action.text,
          'completed': false
        };
      }
      else if('TOGGLE_TODO' === action.type) {
        if(action.id !== state.id) {
          return state;
        }

        return Object.assign({}, state, { 'completed': !state.completed });
      }

      return state;
    },

    todos = (state, action) => {
      state = state || [];

      if('ADD_TODO' === action.type) {
        return [
          todo_action(null, action),
          ...state
        ];
      }
      else if('TOGGLE_TODO' === action.type) {
        return state.map((todo) => todo_action(todo, action));
      }

      return state;
    },

    visibilityFilter = (state, action) => {
      state = state || 'SHOW_ALL';

      if('SET_VISIBILITY_FILTER' === action.type) {
        return action.filter;
      }

      return state;
    },

    combineReducers = (reducers) => {
      return (state, action) => {
        state = state || {};

        return Object.keys(reducers).reduce(
          (nextState, key) => {
            nextState[key] = reducers[key](state[key], action);

            return nextState;
          },
          {}
        );
      };
    };

  return combineReducers({
    todos,
    visibilityFilter
  });

})();
<|endoftext|>


<commit_before>var EmailSelector = function (template, emailList, maxInputWidth, styleInactive, styleEdit, styleApproved, styleUnApproved, autocompleteUrl, events) {
    this.template = template.clone();
    template.remove();
    this.$emailList = emailList;
    this.$newField = null;
    this.emailList = new Array();
    this.fieldRemoved = false;
    this.maxInputWidth = maxInputWidth;
    this.validate = this.validateEmail;
    this.autocompleteUrl = autocompleteUrl;
    this.events = events;
    if(autocompleteUrl) {
        this.validate = this.validateColleague;
    }

    // Styles
    this.styleInactive = "inactive";
    this.styleApproved = "approved";
    this.styleEdit = "edit";
    this.styleUnApproved = "unapproved";

    this.AdditionnalStyleInactive = styleInactive;
    this.AdditionnalStyleApproved = styleApproved;
    this.AdditionnalStyleEdit = styleEdit;
    this.AdditionnalStyleUnApproved = styleUnApproved;
    this.stylesAll = this.styleInactive + " " + this.styleApproved + " " + this.styleEdit + " " + this.styleUnApproved;

    this.addEmptyEmailField();
    this.$editedField = null;

    // Event handlers
    var curr = this;
    this.$emailList.click(function () {
        curr.$newField.focus();
    });

};

EmailSelector.prototype.AddAoutocompleteToField = function(input) {
    var curr = this;
    input.autocomplete({
        minLength: 2,

        source: function(request, response) {
            $.ajax({
                url: curr.autocompleteUrl,
                dataType: "json",
                data: {
                    phrase: request.term
                },
                success: function(data) {
                    response($.map(data, function(item) {
                        var fullName = item.TitleName + " " + item.FirstName + " " + item.SurName;
                        if (item.TrustLevelName == null) {
                            item.TrustLevelName = "";
                        }
                        return {
                            data: item,
                            label: fullName,
                            value: fullName,
                            desc: item.TrustLevelName
                        };
                    }));
                }
            });
        },

        select: function (event, ui) {
            input.val(ui.item.value);
            input.data("code", ui.item.data.Code);
            curr.$newField.focus();
        },
        autoFocus: true,
        search: function () {
            input.data("code", null);
        }
    }).data("autocomplete")._renderItem = function (ul, item) {
        return $("
")
            .data("item.autocomplete", item)
            .append("" + item.label + '
' + item.desc + "
")
            .appendTo(ul);
    };
};

EmailSelector.prototype.addEmptyEmailField = function () {
    var count = this.$emailList.children('.' + this.styleInactive).length;
    if (count === 0) {
        var field = this.template.clone().addClass(this.styleInactive);
        field.appendTo(this.$emailList);

        this.$newField = field.find('input');

        field.find('input:text').autoGrowInput({
            comfortZone: 15,
            minWidth: 30,
            maxWidth: this.maxInputWidth
        });
        if(this.autocompleteUrl) { //if autocomplete is set
          console.log("lol");
            this.AddAoutocompleteToField(field.find('input:text'));
        }

        //event handlers:
        var curr = this;
        field.find('input').click(function (e) {
            e.stopPropagation();
        });
        field.find('input.email-input').focus(function (e) {
            //set new field as edited
            if (!curr.$lastEdited || $(this).get(0) !== curr.$lastEdited.get(0)) {
                curr.$editedField = $(this).parents('.email-container');
                curr.$editedField.removeClass(curr.stylesAll).addClass(curr.styleEdit).find(".dots").hide();
                curr.addEmptyEmailField();
            }
        });

        field.find('input:text').focusout(function () {
            curr.$lastEdited = $(this);
            curr.addEmail($(this));
            curr.$lastEdited = $(this);
        });

        field.find('input').keypress(function (e) {
            var code = (e.keyCode ? e.keyCode : e.which),
                condition = false;
            if(curr.autocompleteUrl) {
                condition = code == 13 || code == 44 || code == 59 || code == 11; //11
            }else {
                condition = code == 13 || code == 44 || code == 59;
            }
            if (condition) {
                e.preventDefault();
                curr.$newField.focus();
            }
        });

        field.find('.email-remove').click(function () {
            curr.removeEmail(field);
        });
    }
    return $('.email-container .' + this.styleInactive);
};

EmailSelector.prototype.removeEmail = function (field) {
    if(field.hasClass(this.styleApproved)){
        if(typeof this.events.onRemove === "function"){
            this.events.onRemove(field.find('input').val());
        }
    }
    field.remove();
    this.fieldRemoved = true;
};

EmailSelector.prototype.addEmail = function (email) {
    var curr = this;
    email.val($.trim(email.val()));
    if (email) {
        if (email.val() === "") { //if email is empty
            setTimeout(function() {
                curr.removeEmail(email.parents('.email-container'));
            }, 200);
        } else {
            if (this.validate(email)) {
                var value = email.val();
                var lastProperVal = email.data("lastProperVal");

                email.parents('.email-container').removeClass(this.stylesAll).addClass(this.styleApproved);
                if(!lastProperVal){
                    if(typeof this.events.onAdd === "function"){
                        this.events.onAdd(value);
                    }
                }else if(value !== lastProperVal) {
                    if(typeof this.events.onAdd === "function"){
                        this.events.onChange(lastProperVal, value);
                    }
                }

                email.data("lastProperVal", value)
            } else {
                email.parents('.email-container').removeClass(this.stylesAll).addClass(this.styleUnApproved);
            }
            var actualWidth = email.width();
            email.parent('li').css("min-width", actualWidth + 25);
            if (email.autoGrowInput('isLonger')) {
                email.parents('.email-container').find(".dots").show();
            }else {
                email.parents('.email-container').find(".dots").hide();
            }
        }
    }
};

EmailSelector.prototype.getEmails = function () {
    this.emailList = [];
    var curr = this;
    this.$emailList.children('.email-container').each(function () {
        var input = $(this).find('input');
        if (curr.validate(input)) {
            curr.emailList.push(input.val().toString());
        }
    });
    return this.emailList;
};

EmailSelector.prototype.getColleagues = function () {
    var colleaguesList = [];
    var curr = this;
    this.$emailList.children('.email-container').each(function () {
        var input = $(this).find('input');
        if (curr.validate(input)) {
            colleaguesList.push(input.data("code"));
        }
    });
    return colleaguesList;
};

EmailSelector.prototype.validateEmail = function (input) {
    var email = input.val();
    var regex = /^((((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))|([^<>@]+\<(((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))\>))$/i;
    return regex.test(email);
};

EmailSelector.prototype.validateColleague = function (input) {
    var code = input.data("code");

    if (code) {
        // guid length
        if (code.length == 36) {
            return true;
        } else {
            return false;
        }
    }
    return false;
};

EmailSelector.prototype.validateLength = function (input) {
    var testSubject = $('<tester/>').css({
        position: 'absolute',
        top: -9999,
        left: -9999,
        width: 'auto',
        fontSize: input.css('fontSize'),
        fontFamily: input.css('fontFamily'),
        fontWeight: input.css('fontWeight'),
        letterSpacing: input.css('letterSpacing'),
        whiteSpace: 'nowrap'
    });

    var val = input.val();
    // Enter new content into testSubject
    var escaped = val.replace(/&/g, '&').replace(/\s/g, ' ').replace(/</g, '<').replace(/>/g, '>');
    testSubject.html(escaped);

    var testerWidth = testSubject.width(),
        currentWidth = input.width();

    if (testerWidth < currentWidth) {
        return true;
    }

    return false;
};

var methods = {
    settings: {
        emails: null
    },
    init: function(o) {
        var settings = $.extend({
            width: this.width(),
            minHeight: this.height(),
            styleInactive: '',
            styleEdit: '',
            styleApproved: '',
            styleUnApproved: '',
            autocompleteUrl: null,
            onAdd: function(email){return null;},
            onRemove: function(email){return null;},
            onChange: function(oldEmail, newEmail){return null;},
            onSomethingChange: function(emails){return null;}
        }, o);

        this.addClass('mail-area');
        var $template = $('
<input type="text" class="email-input"/>
...

');

        var $emailList = $('
').addClass('email-list');
        $emailList.appendTo(this);

        $emailList.css("min-height", settings.height);
        $emailList.width(settings.width);
        var events = {
            onAdd: settings.onAdd,
            onRemove: settings.onRemove,
            onChange: settings.onChange,
            onSomethingChange: settings.onSomethingChange
        }
        methods.settings.emails = new EmailSelector($template, $emailList, settings.width - 40, settings.styleInactive, settings.styleEdit, settings.styleApproved, settings.styleUnApproved, settings.autocompleteUrl, events);
        this.data("emails", methods.settings.emails);
    },
    get: function() {
        return this.data("emails").getEmails();
    },
    getCodes: function () {
        var emails = this.data("emails");
        return emails.getColleagues();
    }
};

module.exports = methods;<commit_msg>Fixed problem with inproper classes when mail is in edit mode<commit_after>var EmailSelector = function (template, emailList, maxInputWidth, styleInactive, styleEdit, styleApproved, styleUnApproved, autocompleteUrl, events) {
    this.template = template.clone();
    template.remove();
    this.$emailList = emailList;
    this.$newField = null;
    this.emailList = new Array();
    this.fieldRemoved = false;
    this.maxInputWidth = maxInputWidth;
    this.validate = this.validateEmail;
    this.autocompleteUrl = autocompleteUrl;
    this.events = events;
    if(autocompleteUrl) {
        this.validate = this.validateColleague;
    }

    // Styles
    this.styleInactive = "inactive";
    this.styleApproved = "approved";
    this.styleEdit = "edit";
    this.styleUnApproved = "unapproved";

    this.AdditionnalStyleInactive = styleInactive;
    this.AdditionnalStyleApproved = styleApproved;
    this.AdditionnalStyleEdit = styleEdit;
    this.AdditionnalStyleUnApproved = styleUnApproved;
    this.stylesAll = this.styleInactive + " " + this.styleApproved + " " + this.styleEdit + " " + this.styleUnApproved;

    this.addEmptyEmailField();
    this.$editedField = null;

    // Event handlers
    var curr = this;
    this.$emailList.click(function () {
        curr.$newField.focus();
    });

};

EmailSelector.prototype.AddAoutocompleteToField = function(input) {
    var curr = this;
    input.autocomplete({
        minLength: 2,

        source: function(request, response) {
            $.ajax({
                url: curr.autocompleteUrl,
                dataType: "json",
                data: {
                    phrase: request.term
                },
                success: function(data) {
                    response($.map(data, function(item) {
                        var fullName = item.TitleName + " " + item.FirstName + " " + item.SurName;
                        if (item.TrustLevelName == null) {
                            item.TrustLevelName = "";
                        }
                        return {
                            data: item,
                            label: fullName,
                            value: fullName,
                            desc: item.TrustLevelName
                        };
                    }));
                }
            });
        },

        select: function (event, ui) {
            input.val(ui.item.value);
            input.data("code", ui.item.data.Code);
            curr.$newField.focus();
        },
        autoFocus: true,
        search: function () {
            input.data("code", null);
        }
    }).data("autocomplete")._renderItem = function (ul, item) {
        return $("
")
            .data("item.autocomplete", item)
            .append("" + item.label + '
' + item.desc + "
")
            .appendTo(ul);
    };
};

EmailSelector.prototype.addEmptyEmailField = function () {
    var count = this.$emailList.children('.' + this.styleInactive).length;
    if (count === 0) {
        var field = this.template.clone().addClass(this.styleInactive);
        field.appendTo(this.$emailList);

        this.$newField = field.find('input');

        field.find('input:text').autoGrowInput({
            comfortZone: 15,
            minWidth: 30,
            maxWidth: this.maxInputWidth
        });
        if(this.autocompleteUrl) { //if autocomplete is set
          console.log("lol");
            this.AddAoutocompleteToField(field.find('input:text'));
        }

        //event handlers:
        var curr = this;
        field.find('input').click(function (e) {
            e.stopPropagation();
        });
        field.find('input.email-input').focus(function (e) {
            //set new field as edited
            if(!$(this).parents('.email-container').hasClass(curr.styleEdit)){
                curr.$editedField = $(this).parents('.email-container');
                curr.$editedField.removeClass(curr.stylesAll).addClass(curr.styleEdit).find(".dots").hide();
                curr.addEmptyEmailField();
            }
        });

        field.find('input:text').focusout(function () {
            curr.addEmail($(this));
        });

        field.find('input').keypress(function (e) {
            var code = (e.keyCode ? e.keyCode : e.which),
                condition = false;
            if(curr.autocompleteUrl) {
                condition = code == 13 || code == 44 || code == 59 || code == 11; //11
            }else {
                condition = code == 13 || code == 44 || code == 59;
            }
            if (condition) {
                e.preventDefault();
                curr.$newField.focus();
            }
        });

        field.find('.email-remove').click(function () {
            curr.removeEmail(field);
        });
    }
    return $('.email-container .' + this.styleInactive);
};

EmailSelector.prototype.removeEmail = function (field) {
    if(field.hasClass(this.styleApproved)){
        if(typeof this.events.onRemove === "function"){
            this.events.onRemove(field.find('input').val());
        }
    }
    field.remove();
    this.fieldRemoved = true;
};

EmailSelector.prototype.addEmail = function (email) {
    var curr = this;
    email.val($.trim(email.val()));
    if (email) {
        if (email.val() === "") { //if email is empty
            setTimeout(function() {
                curr.removeEmail(email.parents('.email-container'));
            }, 200);
        } else {
            if (this.validate(email)) {
                var value = email.val();
                var lastProperVal = email.data("lastProperVal");

                email.parents('.email-container').removeClass(this.stylesAll).addClass(this.styleApproved);
                if(!lastProperVal){
                    if(typeof this.events.onAdd === "function"){
                        this.events.onAdd(value);
                    }
                }else if(value !== lastProperVal) {
                    if(typeof this.events.onAdd === "function"){
                        this.events.onChange(lastProperVal, value);
                    }
                }

                email.data("lastProperVal", value)
            } else {
                email.parents('.email-container').removeClass(this.stylesAll).addClass(this.styleUnApproved);
            }
            var actualWidth = email.width();
            email.parent('li').css("min-width", actualWidth + 25);
            if (email.autoGrowInput('isLonger')) {
                email.parents('.email-container').find(".dots").show();
            }else {
                email.parents('.email-container').find(".dots").hide();
            }
        }
    }
};

EmailSelector.prototype.getEmails = function () {
    this.emailList = [];
    var curr = this;
    this.$emailList.children('.email-container').each(function () {
        var input = $(this).find('input');
        if (curr.validate(input)) {
            curr.emailList.push(input.val().toString());
        }
    });
    return this.emailList;
};

EmailSelector.prototype.getColleagues = function () {
    var colleaguesList = [];
    var curr = this;
    this.$emailList.children('.email-container').each(function () {
        var input = $(this).find('input');
        if (curr.validate(input)) {
            colleaguesList.push(input.data("code"));
        }
    });
    return colleaguesList;
};

EmailSelector.prototype.validateEmail = function (input) {
    var email = input.val();
    var regex = /^((((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))|([^<>@]+\<(((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))\>))$/i;
    return regex.test(email);
};

EmailSelector.prototype.validateColleague = function (input) {
    var code = input.data("code");

    if (code) {
        // guid length
        if (code.length == 36) {
            return true;
        } else {
            return false;
        }
    }
    return false;
};

EmailSelector.prototype.validateLength = function (input) {
    var testSubject = $('<tester/>').css({
        position: 'absolute',
        top: -9999,
        left: -9999,
        width: 'auto',
        fontSize: input.css('fontSize'),
        fontFamily: input.css('fontFamily'),
        fontWeight: input.css('fontWeight'),
        letterSpacing: input.css('letterSpacing'),
        whiteSpace: 'nowrap'
    });

    var val = input.val();
    // Enter new content into testSubject
    var escaped = val.replace(/&/g, '&').replace(/\s/g, ' ').replace(/</g, '<').replace(/>/g, '>');
    testSubject.html(escaped);

    var testerWidth = testSubject.width(),
        currentWidth = input.width();

    if (testerWidth < currentWidth) {
        return true;
    }

    return false;
};

var methods = {
    settings: {
        emails: null
    },
    init: function(o) {
        var settings = $.extend({
            width: this.width(),
            minHeight: this.height(),
            styleInactive: '',
            styleEdit: '',
            styleApproved: '',
            styleUnApproved: '',
            autocompleteUrl: null,
            onAdd: function(email){return null;},
            onRemove: function(email){return null;},
            onChange: function(oldEmail, newEmail){return null;},
            onSomethingChange: function(emails){return null;}
        }, o);

        this.addClass('mail-area');
        var $template = $('
<input type="text" class="email-input"/>
...

');

        var $emailList = $('
').addClass('email-list');
        $emailList.appendTo(this);

        $emailList.css("min-height", settings.height);
        $emailList.width(settings.width);
        var events = {
            onAdd: settings.onAdd,
            onRemove: settings.onRemove,
            onChange: settings.onChange,
            onSomethingChange: settings.onSomethingChange
        }
        methods.settings.emails = new EmailSelector($template, $emailList, settings.width - 40, settings.styleInactive, settings.styleEdit, settings.styleApproved, settings.styleUnApproved, settings.autocompleteUrl, events);
        this.data("emails", methods.settings.emails);
    },
    get: function() {
        return this.data("emails").getEmails();
    },
    getCodes: function () {
        var emails = this.data("emails");
        return emails.getColleagues();
    }
};

module.exports = methods;<|endoftext|>


<commit_before>import {Promise} from 'bluebird'
import {Registry} from './registry'
import {Set} from 'immutable'
import * as u from './useful'
import {read, set, remove} from './firebase_actions'
import log4js from 'log4js'
import {TODO_TRX_PATH, DONE_TRX_PATH} from './settings'

let logger = log4js.getLogger('transactor')

function configureLogging() {
  if (!log4js.configured) {
    log4js.configure({
      appenders: [{
        type: 'console',
        layout: {
          type: 'pattern',
          pattern: '[%[%5.5p%]] - %m'
        }
      }]
    })
    logger.setLevel('INFO')
  }
}

class AbortError {
  constructor(msg) {
    this.msg = msg
  }
}

class UserAbort {
  constructor(msg) {
    this.msg = msg
  }
}

export function transactor(firebase, handlers, options = {}) {

  const {
    todoTrxRef = firebase.child(TODO_TRX_PATH),
    closedTrxRef = firebase.child(DONE_TRX_PATH),
    trCountLimit = 50,
    rescheduleDelay = 100
  } = options

  if (!log4js.configured) configureLogging()

  const runs = {} // run id mapped to trData
  const waiting = []
  const trSummary = {aborted: 0, tried: 0, processed: 0}
  const registry = new Registry()
  let nextTrId = 0
  let nextRunId = 0
  let trCount = 0

  function _abort(id) {
    registry.cleanup(id)
    delete runs[id]
  }

  function refFromPath(path) {
    return firebase.child(path.join('/'))
  }

  function pushWaiting(val) {
    if (val == null) {
      throw new Error('val should be trx, not null/undefined')
    }
    waiting.push(val)
    tryConsumeWaiting()
  }

  function scheduleLater(trData) {
    setTimeout(() => {pushWaiting(trData)}, rescheduleDelay)
  }

  // --abortAndReschedule
  function abortAndReschedule(id) {
    if (runs[id] == null) {
      throw new Error('shouldnt abort transaction that is already aborted')
    }
    logger.debug(`CLEANUP & ABORT : tr no ${id}`)
    trSummary['aborted'] += 1
    scheduleLater(runs[id])
    _abort(id)
  }

  // --processTr
  function processTr(id) {
    logger.debug(`starting process ${id}`)
    const {type, data} = runs[id]
    const handler = handlers[type]
    if (handler == null) {
      throw new Error(`handler for type ${type} does not exist. Full trData: ${runs[id]}`)
    }

    if (runs[id] == null) {
      throw new Error('given run does not exist!')
    }

    return Promise.resolve()
      .then(() => {
        logger.debug(`starting handler ${id}`)
        trSummary['tried'] += 1
        return handler({
          abort: userAbort,
          change: userChange,
          getId: userGetId,
          push: userPush,
          read: userRead,
          set: userSet,
          update: userUpdate,
        }, data)
      })
      .catch((err) => {
        if (err instanceof UserAbort) {
          logger.debug(`user abort ${id}, msg: ${err.msg}`)
          return {error: err.msg}
        }
        throw err
      })
      .then((result) => {
        // even if no Error is thrown, the transaction might be aborted in the
        // very last moment. Better check for it (userabort is not relevant now, as
        // we want to process such transaction)
        if (runs[id] != null) {
          logger.debug(`FINISH: tr no ${id}`)
          let userAborted = runs[id].status === 'useraborted'
          let writes = userAborted ? [] : registry.writesByTrx.get(id, [])
          let writesRef = firebase.child('__internal/writes').child(id)
          trSummary['processed'] += 1
          runs[id].status = 'finishing'
          set(writesRef, writes)
          writes.forEach((write) => {
            // TODO immutable destructuring
            set(refFromPath(write.get('path')), write.get('value'))
          })
          let trData = runs[id]
          remove(writesRef)
          try {
            // TODO handle this better (some parts of result might get lost
            // here, so warn user about it)
            result = JSON.parse(JSON.stringify({result}))
          } catch (e) {
            let msg = `Error for transaction id=${id}, result cannot be saved\n to firebase (result=${result})`
            throw new Error(msg)
          }
          set(closedTrxRef.child(trData.frbId), {data: trData, ...result})
          remove(todoTrxRef.child(trData.frbId))
          registry.cleanup(id)
          delete runs[id]
        }
      })
      .catch((err) => {
        if (err instanceof AbortError) {
          logger.debug(`abort error ${id}`)
          return
        }
        throw err
      })

    function checkAbort() {
      if (runs[id] == null || runs[id].status === 'useraborted') {
        logger.debug('checkAbort: throwing abort')
        throw new AbortError('Transaction was aborted')
      }
      if (runs[id] === 'useraborted') {
        logger.debug('checkAbort: throwing useraborted')
        throw new AbortError('Transaction was aborted')
      }

    }

    function handlePossibleConflict(_conflict) {
      if (_conflict.constructor !== Set) {
        throw new Error('conflict must be of a type immutable.Set')
      }
      checkAbort()
      let conflict = _conflict.delete(id)
      let conflictTrIds = conflict.map((i) => runs[i].trId)
      let trId = runs[id].trId
      let finishingTrIds = conflict.filter((i) => runs[i].status === 'finishing').map((i) => runs[i].trId)
      if (!conflict.isEmpty()) {
        if (u.any(conflict, (i) => runs[i].status === 'finishing') ||
            conflictTrIds.min() < trId) {
          logger.debug(`aborting ${trId}, because of ${conflictTrIds}, finishing: ${finishingTrIds}`)
          abortAndReschedule(id)
        } else {
          logger.debug(`aborting ${conflictTrIds}, because of ${trId}, finishing: ${finishingTrIds}`)
          conflict.forEach(abortAndReschedule)
        }
      }
      checkAbort()
    }

    // TODO if possible, make DB operations accept also firebase ref
    function userRead(path) {
      if (u.any(path, (e) => (e == null))) throw new Error(`READ ERROR: undefined / null present in path: ${path}`)
      handlePossibleConflict(registry.conflictingWithRead(path))
      registry.addRead(id, path)
      return Promise.resolve()
        .then(() => read(refFromPath(path)))
        .then((val) => {
          checkAbort()
          return registry.readAsIfTrx(id, path, val)
        })
    }

    function userSet(path, value) {
      handlePossibleConflict(registry.conflictingWithWrite(path))
      registry.addWrite(id, path, value)
    }

    function userAbort(msg) {
      runs[id].abortMsg = msg
      runs[id].status = 'useraborted'
      throw new UserAbort(msg)
    }

    function userPush(path, value) {
      let ref = refFromPath(path).push()
      userSet([...path, ref.key()], value)
      return ref
    }

    function userGetId() {
      return firebase.push().key()
    }

    function userChange(path, updateFn) {
      return userRead(path)
        .then((snapshot) => userSet(path, updateFn(snapshot)))
    }

    function userUpdate(path, values) {
      if ((values == null) || (values.constructor !== Object)) {
        throw new Error(`The value argument in update must be a JS Object, found ${values}`)
      }
      Object.keys(values).forEach((key) => userSet([...path, key], values[key]))
    }

  }

  function tryConsumeWaiting() {
    while (waiting.length > 0 && trCount < trCountLimit) {
      trCount += 1
      let trData = waiting.shift()
      let id = nextRunId++
      runs[id] = {...trData, status: 'inprocess', id}
      processTr(id)
        .then(() => {
          trCount -= 1
          tryConsumeWaiting()
        })
    }
  }

  let onChildAdded = todoTrxRef.on('child_added', (childSnapshot, prevChildKey) => {
    let trData = childSnapshot.val()
    if (trData.type == null) {
      logger.error('malformed data: ', trData)
      throw new Error('malformed trData')
    }
    trData.trId = nextTrId++
    logger.debug(`SCHEDULED: tr no ${trData.trId} data: ${JSON.stringify(trData)}`)
    trData.frbId = childSnapshot.key()
    pushWaiting(trData)
  })

  return {
    'stop': () => todoTrxRef.off('child_added', onChildAdded),
    'registry': registry,
    'trSummary': trSummary,
  }

}

// Synchronous variant of finishing transaction code
//let toWait = []
//toWait.push(set(writesRef, writes))
//writes.forEach((write) => {
//  // TODO immutable destructuring
//  toWait.push(set(refFromPath(write.get('path')), write.get('value')))
//})
//let trData = runs[id]
//  Promise.all(toWait).then((_) => {
//  remove(writesRef)
//  set(firebase.child(DONE_TRX_PATH).child(trData.frbId), trData)
//  remove(firebase.child(TODO_TRX_PATH).child(trData.frbId))
//  registry.cleanup(id)
//  delete runs[id]
//})
<commit_msg>Transactor bugfix: when userAborting, check if trx not systemaborted first<commit_after>import {Promise} from 'bluebird'
import {Registry} from './registry'
import {Set} from 'immutable'
import * as u from './useful'
import {read, set, remove} from './firebase_actions'
import log4js from 'log4js'
import {TODO_TRX_PATH, DONE_TRX_PATH} from './settings'

let logger = log4js.getLogger('transactor')

function configureLogging() {
  if (!log4js.configured) {
    log4js.configure({
      appenders: [{
        type: 'console',
        layout: {
          type: 'pattern',
          pattern: '[%[%5.5p%]] - %m'
        }
      }]
    })
    logger.setLevel('INFO')
  }
}

class AbortError {
  constructor(msg) {
    this.msg = msg
  }
}

class UserAbort {
  constructor(msg) {
    this.msg = msg
  }
}

export function transactor(firebase, handlers, options = {}) {

  const {
    todoTrxRef = firebase.child(TODO_TRX_PATH),
    closedTrxRef = firebase.child(DONE_TRX_PATH),
    trCountLimit = 50,
    rescheduleDelay = 100
  } = options

  if (!log4js.configured) configureLogging()

  const runs = {} // run id mapped to trData
  const waiting = []
  const trSummary = {aborted: 0, tried: 0, processed: 0}
  const registry = new Registry()
  let nextTrId = 0
  let nextRunId = 0
  let trCount = 0

  function _abort(id) {
    registry.cleanup(id)
    delete runs[id]
  }

  function refFromPath(path) {
    return firebase.child(path.join('/'))
  }

  function pushWaiting(val) {
    if (val == null) {
      throw new Error('val should be trx, not null/undefined')
    }
    waiting.push(val)
    tryConsumeWaiting()
  }

  function scheduleLater(trData) {
    setTimeout(() => {pushWaiting(trData)}, rescheduleDelay)
  }

  // --abortAndReschedule
  function abortAndReschedule(id) {
    if (runs[id] == null) {
      throw new Error('shouldnt abort transaction that is already aborted')
    }
    logger.debug(`CLEANUP & ABORT : tr no ${id}`)
    trSummary['aborted'] += 1
    scheduleLater(runs[id])
    _abort(id)
  }

  // --processTr
  function processTr(id) {
    logger.debug(`starting process ${id}`)
    const {type, data} = runs[id]
    const handler = handlers[type]
    if (handler == null) {
      throw new Error(`handler for type ${type} does not exist. Full trData: ${runs[id]}`)
    }

    if (runs[id] == null) {
      throw new Error('given run does not exist!')
    }

    return Promise.resolve()
      .then(() => {
        logger.debug(`starting handler ${id}`)
        trSummary['tried'] += 1
        return handler({
          abort: userAbort,
          change: userChange,
          getId: userGetId,
          push: userPush,
          read: userRead,
          set: userSet,
          update: userUpdate,
        }, data)
      })
      .catch((err) => {
        if (err instanceof UserAbort) {
          logger.debug(`user abort ${id}, msg: ${err.msg}`)
          return {error: err.msg}
        }
        throw err
      })
      .then((result) => {
        // even if no Error is thrown, the transaction might be aborted in the
        // very last moment. Better check for it (userabort is not relevant now, as
        // we want to process such transaction)
        if (runs[id] != null) {
          logger.debug(`FINISH: tr no ${id}`)
          let userAborted = runs[id].status === 'useraborted'
          let writes = userAborted ? [] : registry.writesByTrx.get(id, [])
          let writesRef = firebase.child('__internal/writes').child(id)
          trSummary['processed'] += 1
          runs[id].status = 'finishing'
          set(writesRef, writes)
          writes.forEach((write) => {
            // TODO immutable destructuring
            set(refFromPath(write.get('path')), write.get('value'))
          })
          let trData = runs[id]
          remove(writesRef)
          try {
            // TODO handle this better (some parts of result might get lost
            // here, so warn user about it)
            result = JSON.parse(JSON.stringify({result}))
          } catch (e) {
            let msg = `Error for transaction id=${id}, result cannot be saved\n to firebase (result=${result})`
            throw new Error(msg)
          }
          set(closedTrxRef.child(trData.frbId), {data: trData, ...result})
          remove(todoTrxRef.child(trData.frbId))
          registry.cleanup(id)
          delete runs[id]
        }
      })
      .catch((err) => {
        if (err instanceof AbortError) {
          logger.debug(`abort error ${id}`)
          return
        }
        throw err
      })

    function checkAbort() {
      if (runs[id] == null || runs[id].status === 'useraborted') {
        logger.debug('checkAbort: throwing abort')
        throw new AbortError('Transaction was aborted')
      }
      if (runs[id] === 'useraborted') {
        logger.debug('checkAbort: throwing useraborted')
        throw new AbortError('Transaction was aborted')
      }

    }

    function handlePossibleConflict(_conflict) {
      if (_conflict.constructor !== Set) {
        throw new Error('conflict must be of a type immutable.Set')
      }
      checkAbort()
      let conflict = _conflict.delete(id)
      let conflictTrIds = conflict.map((i) => runs[i].trId)
      let trId = runs[id].trId
      let finishingTrIds = conflict.filter((i) => runs[i].status === 'finishing').map((i) => runs[i].trId)
      if (!conflict.isEmpty()) {
        if (u.any(conflict, (i) => runs[i].status === 'finishing') ||
            conflictTrIds.min() < trId) {
          logger.debug(`aborting ${trId}, because of ${conflictTrIds}, finishing: ${finishingTrIds}`)
          abortAndReschedule(id)
        } else {
          logger.debug(`aborting ${conflictTrIds}, because of ${trId}, finishing: ${finishingTrIds}`)
          conflict.forEach(abortAndReschedule)
        }
      }
      checkAbort()
    }

    // TODO if possible, make DB operations accept also firebase ref
    function userRead(path) {
      if (u.any(path, (e) => (e == null))) throw new Error(`READ ERROR: undefined / null present in path: ${path}`)
      handlePossibleConflict(registry.conflictingWithRead(path))
      registry.addRead(id, path)
      return Promise.resolve()
        .then(() => read(refFromPath(path)))
        .then((val) => {
          checkAbort()
          return registry.readAsIfTrx(id, path, val)
        })
    }

    function userSet(path, value) {
      handlePossibleConflict(registry.conflictingWithWrite(path))
      registry.addWrite(id, path, value)
    }

    function userAbort(msg) {
      checkAbort()
      runs[id].abortMsg = msg
      runs[id].status = 'useraborted'
      throw new UserAbort(msg)
    }

    function userPush(path, value) {
      let ref = refFromPath(path).push()
      userSet([...path, ref.key()], value)
      return ref
    }

    function userGetId() {
      return firebase.push().key()
    }

    function userChange(path, updateFn) {
      return userRead(path)
        .then((snapshot) => userSet(path, updateFn(snapshot)))
    }

    function userUpdate(path, values) {
      if ((values == null) || (values.constructor !== Object)) {
        throw new Error(`The value argument in update must be a JS Object, found ${values}`)
      }
      Object.keys(values).forEach((key) => userSet([...path, key], values[key]))
    }

  }

  function tryConsumeWaiting() {
    while (waiting.length > 0 && trCount < trCountLimit) {
      trCount += 1
      let trData = waiting.shift()
      let id = nextRunId++
      runs[id] = {...trData, status: 'inprocess', id}
      processTr(id)
        .then(() => {
          trCount -= 1
          tryConsumeWaiting()
        })
    }
  }

  let onChildAdded = todoTrxRef.on('child_added', (childSnapshot, prevChildKey) => {
    let trData = childSnapshot.val()
    if (trData.type == null) {
      logger.error('malformed data: ', trData)
      throw new Error('malformed trData')
    }
    trData.trId = nextTrId++
    logger.debug(`SCHEDULED: tr no ${trData.trId} data: ${JSON.stringify(trData)}`)
    trData.frbId = childSnapshot.key()
    pushWaiting(trData)
  })

  return {
    'stop': () => todoTrxRef.off('child_added', onChildAdded),
    'registry': registry,
    'trSummary': trSummary,
  }

}

// Synchronous variant of finishing transaction code
//let toWait = []
//toWait.push(set(writesRef, writes))
//writes.forEach((write) => {
//  // TODO immutable destructuring
//  toWait.push(set(refFromPath(write.get('path')), write.get('value')))
//})
//let trData = runs[id]
//  Promise.all(toWait).then((_) => {
//  remove(writesRef)
//  set(firebase.child(DONE_TRX_PATH).child(trData.frbId), trData)
//  remove(firebase.child(TODO_TRX_PATH).child(trData.frbId))
//  registry.cleanup(id)
//  delete runs[id]
//})
<|endoftext|>