<commit_before>import React from 'react';
import _ from 'lodash';
import { push } from 'connected-react-router';
import sha256 from 'js-sha256';
// api
import { LocalizationService, AuthenticateService } from '../../services';

/*
 * action types
 */

export const ADD_MESSAGE = 'ADD_MESSAGE';
export const HIDE_MESSAGE = 'HIDE_MESSAGE';
export const HIDE_ALL_MESSAGES = 'HIDE_ALL_MESSAGES';
export const REMOVE_MESSAGE = 'REMOVE_MESSAGE';
export const REMOVE_ALL_MESSAGES = 'REMOVE_ALL_MESSAGES';
//
export const DEFAULT_SERVER_UNAVAILABLE_TIMEOUT = 3000;

const _DEFAULT_MESSAGE = {
  id: null, // internal id
  key: null, // key for unique checking
  title: null,
  message: null,
  level: 'success', // React.PropTypes.oneOf(['success', 'info', 'warning', 'error']),
  position: 'tr', // React.PropTypes.oneOf(['tr', 'tc']),
  autoDismiss: 5,
  dismissible: true,
  action: null,
  hidden: false,
  date: new Date()
};

/**
 * Flash messages
 *
 * @author Radek Tomiška
 */
export default class FlashMessagesManager {

  constructor() {
    this.serverUnavailableTimeout = DEFAULT_SERVER_UNAVAILABLE_TIMEOUT;
  }

  getServerUnavailableTimeout() {
    return this.serverUnavailableTimeout;
  }

  setServerUnavailableTimeout(serverUnavailableTimeout) {
    this.serverUnavailableTimeout = serverUnavailableTimeout;
  }

  addMessage(message) {
    return dispatch => {
      if (message.key) {
        dispatch(this.hideMessage(message.key));
      }
      dispatch({
        type: ADD_MESSAGE,
        message: _.merge({}, message, { date: new Date() })
      });
    };
  }

  addError(error, context = null) {
    return this.addErrorMessage({}, error, context);
  }

  /**
  * Transforms options to message and adds default props
  */
  createMessage(options) {
    if (!options) {
      return null;
    }
    let message = options;
    if (message == null) {
      message = LocalizationService.i18n('message.success.common', { defaultValue: 'The operation was successfully completed' });
    }
    if (typeof message === 'string') {
      message = _.merge({}, _DEFAULT_MESSAGE, { message });
    }
    // errors are shown centered by default
    if (message.level && (message.level === 'error' /* || message.level === 'warning' */) && !message.position) {
      message.position = 'tc';
    }

    // add default
    message = _.merge({}, _DEFAULT_MESSAGE, message);
    if (!message.title && !message.message) {
      message.message = LocalizationService.i18n('message.success.common', { defaultValue: 'The operation was successfully completed' });
    }
    if (message.title && typeof message.title === 'object') {
      message.title = JSON.stringify(message.title);
    }
    if (message.message && typeof message.message === 'object') {
      message.message = JSON.stringify(message.message);
    }
    return message;
  }

  /**
   * Converts result model to flash message
   *
   * @param  {ResultModel} resultModel
   * @return {FlashMessage}
   */
  convertFromResultModel(resultModel) {
    if (!resultModel) {
      return null;
    }
    // automatic localization
    const messageTitle = LocalizationService.i18n(
      `${ resultModel.module }:error.${ resultModel.statusEnum }.title`,
      this._prepareParams(resultModel.parameters, `${ resultModel.statusEnum } (${ resultModel.statusCode }:${ resultModel.id })`)
    );
    let defaultMessage = resultModel.message;
    if (!_.isEmpty(resultModel.parameters)) {
      defaultMessage += ' (';
      let first = true;
      for (const parameterKey in resultModel.parameters) {
        if (!first) {
          defaultMessage += ', ';
        } else {
          first = false;
        }
        defaultMessage += `${parameterKey}:${resultModel.parameters[parameterKey]}`;
      }
      defaultMessage += ')';
    }
    const messageText = LocalizationService.i18n(
      `${ resultModel.module }:error.${ resultModel.statusEnum }.message`,
      this._prepareParams(resultModel.parameters, defaultMessage)
    );
    //
    const levelStatusCode = parseInt(resultModel.statusCode, 10);
    let level; // 4xx - warning message, 5xx - error message
    if (levelStatusCode >= 500) {
      level = 'error';
    } else if (levelStatusCode >= 200 && levelStatusCode < 300) {
      if (levelStatusCode === 202) {
        // accepted - info
        level = 'info';
      } else {
        level = 'success';
      }
    } else {
      level = 'warning';
      // TODO: info level?
    }
    //
    return this.createMessage({
      key: resultModel.statusEnum,
      level,
      title: messageTitle,
      message: messageText,
    });
  }

  convertFromError(error) {
    if (this.isServerUnavailableError(error)) {
      return {
        key: 'error-app-load',
        level: 'error',
        title: LocalizationService.i18n('content.error.503.description'),
        message: LocalizationService.i18n('content.error.503.note'),
        dismissible: true/* ,
        action: {
          label: 'Odeslat na podporu',
          callback: () => {
            alert('Comming soon.')
          }
        }*/
      };
    }
    if (this.isSyntaxError(error)) {
      return {
        key: 'syntax-error',
        level: 'warning',
        message: LocalizationService.i18n('content.error.syntax-error.message')
      };
    }

    //
    let errorMessage;
    if (error.statusEnum) { // our error message
      errorMessage = this.convertFromResultModel(error);
    } else { // system error - only contain message
      errorMessage = {
        level: error.level || 'error',
        title: error.title,
        message: error.message
      };
    }
    return errorMessage;
  }

  /**
   * Converts message from BE message
   *
   * @param  {IdmMessage} notificationMessage
   * @return {FlashMessage}
   */
  convertFromWebsocketMessage(notificationMessage) {
    if (!notificationMessage.model) {
      return notificationMessage;
    }
    //
    const message = this.convertFromResultModel(notificationMessage.model);
    // TODO: registrable message converters
    if (notificationMessage.key === 'core:event'
        && notificationMessage.model
        && notificationMessage.model.parameters
        && notificationMessage.model.parameters.processors) {
      message.children = (

            {
              [...notificationMessage.model.parameters.processors.map(processor => {
                return (

{ LocalizationService.i18n(`${processor.module}:processor.${processor.name}.title`, { defaultValue: processor.id }) }

                );
              }).values()]
            }

      );
    }
    if (notificationMessage.position) {
      message.position = notificationMessage.position;
    }
    if (notificationMessage.level) {
      message.level = notificationMessage.level;
    }
    return message;
  }

  addErrorMessage(message, error) {
    return (dispatch, getState) => {
      if (DEBUG) {
        getState().logger.error(message, error);
      }
      const errorMessage = _.merge({}, this.convertFromError(error), message);
      // error redirect handler
      if (this.isServerUnavailableError(error)) {
        // timeout to prevent showing message on ajax call interuption
        setTimeout(
          () => {
            dispatch(this.addMessage(errorMessage));
            dispatch(this._clearConfiguations());
          },
          this.getServerUnavailableTimeout()
        );
      // TODO: module defined exception handlers
      } else if (this._isLoginError(error)) {

        // If authorities changed exception occurred, but token in the exception is
        // different then tokne in userContext, then we donť want make a logout (user
        // could be different (logout/login)). From same reason we will not show exception (maybe is current user different).
        if (error.statusEnum === 'AUTHORITIES_CHANGED') {
          if (error.parameters && error.parameters.token) {
            const currentToken = AuthenticateService.getTokenCIDMST();
            // Make SHA1 hash from token
            const currentTokenHash = sha256(currentToken);
            const tokenFromErrorHash = error.parameters.token;
            if (tokenFromErrorHash !== currentTokenHash) {
              alert(currentTokenHash + '=tady=' + tokenFromErrorHash);
              return;
            }
          }
        }
        this._logoutImmediatelly(); // we dont want to propagate LOGOUT dispatch event ... we want propagate new event:
        dispatch({
          type: 'RECEIVE_LOGIN_EXPIRED'
        });
      } else {
        dispatch(this.addMessage(errorMessage));
      }
    };
  }

  // TODO: cyclic dependency in security manager
  _logoutImmediatelly() {
    const authenticateService = new AuthenticateService();
    authenticateService.clearStorage();
    return {
      type: 'LOGOUT'
    };
  }

  // TODO: cyclic dependency in configuration manager - refactor action constants to separate file
  _clearConfiguations() {
    return {
      type: 'APP_UNAVAILABLE'
    };
  }

  /**
   * Returns true, if new login is needed
   */
  _isLoginError(error) {
    if (!error.statusEnum) {
      return false;
    }

    if (error.statusEnum === 'XSRF'
        || error.statusEnum === 'LOG_IN'
        || error.statusEnum === 'AUTH_EXPIRED'
        || error.statusEnum === 'AUTHORITIES_CHANGED') {
      return true;
    }
    return false;
  }

  /**
   * Returns true, if BE is not available
   */
  isServerUnavailableError(error) {
    if (error
        && error.message
        && (error.message.indexOf('NetworkError') > -1
          || error.message.indexOf('Failed to fetch') > -1
          || (error.statusCode && (error.statusCode === '400' || error.statusCode === '503')))) {
      return true;
    }
    return false;
  }

  /**
   * Returns true, if SyntaxError
   */
  isSyntaxError(error) {
    if (error
        && error.message
        && (error.name === 'SyntaxError' && error.message.indexOf('JSON.parse') === 0)) {
      return true;
    }
    if (error && error.stack && error.stack.indexOf('SyntaxError') === 0) {
      return true;
    }
    return false;
  }

  _prepareParams(params, defaultMessage) {
    let results = {};
    if (params) {
      // iterate over all params do necessary converts
      // TODO converter for date
      // for (const key in params) {
      //   if (params.hasOwnProperty(key)) {
      //     console.log(params[key], 111);
      //     if (!isNaN(Number(params[key]))) {
      //       // nothing, just number
      //     } else if (params[key] instanceof Date && !isNaN(new Date(params[key])) && params) {
      //       // convert to date
      //       const date = moment(new Date(params[key]));
      //       params[key] = date.format(LocalizationService.i18n('format.date'));
      //     }
      //   }
      // }
      results = _.merge({}, params);
    }
    if (defaultMessage) {
      results.defaultValue = defaultMessage;
    }
    return results;
  }

  hideMessage(id) {
    return {
      type: HIDE_MESSAGE,
      id
    };
  }

  removeMessage(id) {
    return {
      type: REMOVE_MESSAGE,
      id
    };
  }

  hideAllMessages() {
    return {
      type: HIDE_ALL_MESSAGES
    };
  }

  removeAllMessages() {
    return {
      type: REMOVE_ALL_MESSAGES
    };
  }

  redirect() {
    return dispatch => {
      dispatch(push(`/error/403`));
    };
  }

}
<commit_msg>FE: remove forgotten alert<commit_after>import React from 'react';
import _ from 'lodash';
import { push } from 'connected-react-router';
import sha256 from 'js-sha256';
// api
import { LocalizationService, AuthenticateService } from '../../services';

/*
 * action types
 */

export const ADD_MESSAGE = 'ADD_MESSAGE';
export const HIDE_MESSAGE = 'HIDE_MESSAGE';
export const HIDE_ALL_MESSAGES = 'HIDE_ALL_MESSAGES';
export const REMOVE_MESSAGE = 'REMOVE_MESSAGE';
export const REMOVE_ALL_MESSAGES = 'REMOVE_ALL_MESSAGES';
//
export const DEFAULT_SERVER_UNAVAILABLE_TIMEOUT = 3000;

const _DEFAULT_MESSAGE = {
  id: null, // internal id
  key: null, // key for unique checking
  title: null,
  message: null,
  level: 'success', // React.PropTypes.oneOf(['success', 'info', 'warning', 'error']),
  position: 'tr', // React.PropTypes.oneOf(['tr', 'tc']),
  autoDismiss: 5,
  dismissible: true,
  action: null,
  hidden: false,
  date: new Date()
};

/**
 * Flash messages
 *
 * @author Radek Tomiška
 */
export default class FlashMessagesManager {

  constructor() {
    this.serverUnavailableTimeout = DEFAULT_SERVER_UNAVAILABLE_TIMEOUT;
  }

  getServerUnavailableTimeout() {
    return this.serverUnavailableTimeout;
  }

  setServerUnavailableTimeout(serverUnavailableTimeout) {
    this.serverUnavailableTimeout = serverUnavailableTimeout;
  }

  addMessage(message) {
    return dispatch => {
      if (message.key) {
        dispatch(this.hideMessage(message.key));
      }
      dispatch({
        type: ADD_MESSAGE,
        message: _.merge({}, message, { date: new Date() })
      });
    };
  }

  addError(error, context = null) {
    return this.addErrorMessage({}, error, context);
  }

  /**
  * Transforms options to message and adds default props
  */
  createMessage(options) {
    if (!options) {
      return null;
    }
    let message = options;
    if (message == null) {
      message = LocalizationService.i18n('message.success.common', { defaultValue: 'The operation was successfully completed' });
    }
    if (typeof message === 'string') {
      message = _.merge({}, _DEFAULT_MESSAGE, { message });
    }
    // errors are shown centered by default
    if (message.level && (message.level === 'error' /* || message.level === 'warning' */) && !message.position) {
      message.position = 'tc';
    }

    // add default
    message = _.merge({}, _DEFAULT_MESSAGE, message);
    if (!message.title && !message.message) {
      message.message = LocalizationService.i18n('message.success.common', { defaultValue: 'The operation was successfully completed' });
    }
    if (message.title && typeof message.title === 'object') {
      message.title = JSON.stringify(message.title);
    }
    if (message.message && typeof message.message === 'object') {
      message.message = JSON.stringify(message.message);
    }
    return message;
  }

  /**
   * Converts result model to flash message
   *
   * @param  {ResultModel} resultModel
   * @return {FlashMessage}
   */
  convertFromResultModel(resultModel) {
    if (!resultModel) {
      return null;
    }
    // automatic localization
    const messageTitle = LocalizationService.i18n(
      `${ resultModel.module }:error.${ resultModel.statusEnum }.title`,
      this._prepareParams(resultModel.parameters, `${ resultModel.statusEnum } (${ resultModel.statusCode }:${ resultModel.id })`)
    );
    let defaultMessage = resultModel.message;
    if (!_.isEmpty(resultModel.parameters)) {
      defaultMessage += ' (';
      let first = true;
      for (const parameterKey in resultModel.parameters) {
        if (!first) {
          defaultMessage += ', ';
        } else {
          first = false;
        }
        defaultMessage += `${parameterKey}:${resultModel.parameters[parameterKey]}`;
      }
      defaultMessage += ')';
    }
    const messageText = LocalizationService.i18n(
      `${ resultModel.module }:error.${ resultModel.statusEnum }.message`,
      this._prepareParams(resultModel.parameters, defaultMessage)
    );
    //
    const levelStatusCode = parseInt(resultModel.statusCode, 10);
    let level; // 4xx - warning message, 5xx - error message
    if (levelStatusCode >= 500) {
      level = 'error';
    } else if (levelStatusCode >= 200 && levelStatusCode < 300) {
      if (levelStatusCode === 202) {
        // accepted - info
        level = 'info';
      } else {
        level = 'success';
      }
    } else {
      level = 'warning';
      // TODO: info level?
    }
    //
    return this.createMessage({
      key: resultModel.statusEnum,
      level,
      title: messageTitle,
      message: messageText,
    });
  }

  convertFromError(error) {
    if (this.isServerUnavailableError(error)) {
      return {
        key: 'error-app-load',
        level: 'error',
        title: LocalizationService.i18n('content.error.503.description'),
        message: LocalizationService.i18n('content.error.503.note'),
        dismissible: true/* ,
        action: {
          label: 'Odeslat na podporu',
          callback: () => {
            alert('Comming soon.')
          }
        }*/
      };
    }
    if (this.isSyntaxError(error)) {
      return {
        key: 'syntax-error',
        level: 'warning',
        message: LocalizationService.i18n('content.error.syntax-error.message')
      };
    }

    //
    let errorMessage;
    if (error.statusEnum) { // our error message
      errorMessage = this.convertFromResultModel(error);
    } else { // system error - only contain message
      errorMessage = {
        level: error.level || 'error',
        title: error.title,
        message: error.message
      };
    }
    return errorMessage;
  }

  /**
   * Converts message from BE message
   *
   * @param  {IdmMessage} notificationMessage
   * @return {FlashMessage}
   */
  convertFromWebsocketMessage(notificationMessage) {
    if (!notificationMessage.model) {
      return notificationMessage;
    }
    //
    const message = this.convertFromResultModel(notificationMessage.model);
    // TODO: registrable message converters
    if (notificationMessage.key === 'core:event'
        && notificationMessage.model
        && notificationMessage.model.parameters
        && notificationMessage.model.parameters.processors) {
      message.children = (

            {
              [...notificationMessage.model.parameters.processors.map(processor => {
                return (

{ LocalizationService.i18n(`${processor.module}:processor.${processor.name}.title`, { defaultValue: processor.id }) }

                );
              }).values()]
            }

      );
    }
    if (notificationMessage.position) {
      message.position = notificationMessage.position;
    }
    if (notificationMessage.level) {
      message.level = notificationMessage.level;
    }
    return message;
  }

  addErrorMessage(message, error) {
    return (dispatch, getState) => {
      if (DEBUG) {
        getState().logger.error(message, error);
      }
      const errorMessage = _.merge({}, this.convertFromError(error), message);
      // error redirect handler
      if (this.isServerUnavailableError(error)) {
        // timeout to prevent showing message on ajax call interuption
        setTimeout(
          () => {
            dispatch(this.addMessage(errorMessage));
            dispatch(this._clearConfiguations());
          },
          this.getServerUnavailableTimeout()
        );
      // TODO: module defined exception handlers
      } else if (this._isLoginError(error)) {

        // If authorities changed exception occurred, but token in the exception is
        // different then tokne in userContext, then we donť want make a logout (user
        // could be different (logout/login)). From same reason we will not show exception (maybe is current user different).
        if (error.statusEnum === 'AUTHORITIES_CHANGED') {
          if (error.parameters && error.parameters.token) {
            const currentToken = AuthenticateService.getTokenCIDMST();
            // Make SHA1 hash from token
            const currentTokenHash = sha256(currentToken);
            const tokenFromErrorHash = error.parameters.token;
            if (tokenFromErrorHash !== currentTokenHash) {
              return;
            }
          }
        }
        this._logoutImmediatelly(); // we dont want to propagate LOGOUT dispatch event ... we want propagate new event:
        dispatch({
          type: 'RECEIVE_LOGIN_EXPIRED'
        });
      } else {
        dispatch(this.addMessage(errorMessage));
      }
    };
  }

  // TODO: cyclic dependency in security manager
  _logoutImmediatelly() {
    const authenticateService = new AuthenticateService();
    authenticateService.clearStorage();
    return {
      type: 'LOGOUT'
    };
  }

  // TODO: cyclic dependency in configuration manager - refactor action constants to separate file
  _clearConfiguations() {
    return {
      type: 'APP_UNAVAILABLE'
    };
  }

  /**
   * Returns true, if new login is needed
   */
  _isLoginError(error) {
    if (!error.statusEnum) {
      return false;
    }

    if (error.statusEnum === 'XSRF'
        || error.statusEnum === 'LOG_IN'
        || error.statusEnum === 'AUTH_EXPIRED'
        || error.statusEnum === 'AUTHORITIES_CHANGED') {
      return true;
    }
    return false;
  }

  /**
   * Returns true, if BE is not available
   */
  isServerUnavailableError(error) {
    if (error
        && error.message
        && (error.message.indexOf('NetworkError') > -1
          || error.message.indexOf('Failed to fetch') > -1
          || (error.statusCode && (error.statusCode === '400' || error.statusCode === '503')))) {
      return true;
    }
    return false;
  }

  /**
   * Returns true, if SyntaxError
   */
  isSyntaxError(error) {
    if (error
        && error.message
        && (error.name === 'SyntaxError' && error.message.indexOf('JSON.parse') === 0)) {
      return true;
    }
    if (error && error.stack && error.stack.indexOf('SyntaxError') === 0) {
      return true;
    }
    return false;
  }

  _prepareParams(params, defaultMessage) {
    let results = {};
    if (params) {
      // iterate over all params do necessary converts
      // TODO converter for date
      // for (const key in params) {
      //   if (params.hasOwnProperty(key)) {
      //     console.log(params[key], 111);
      //     if (!isNaN(Number(params[key]))) {
      //       // nothing, just number
      //     } else if (params[key] instanceof Date && !isNaN(new Date(params[key])) && params) {
      //       // convert to date
      //       const date = moment(new Date(params[key]));
      //       params[key] = date.format(LocalizationService.i18n('format.date'));
      //     }
      //   }
      // }
      results = _.merge({}, params);
    }
    if (defaultMessage) {
      results.defaultValue = defaultMessage;
    }
    return results;
  }

  hideMessage(id) {
    return {
      type: HIDE_MESSAGE,
      id
    };
  }

  removeMessage(id) {
    return {
      type: REMOVE_MESSAGE,
      id
    };
  }

  hideAllMessages() {
    return {
      type: HIDE_ALL_MESSAGES
    };
  }

  removeAllMessages() {
    return {
      type: REMOVE_ALL_MESSAGES
    };
  }

  redirect() {
    return dispatch => {
      dispatch(push(`/error/403`));
    };
  }

}
<|endoftext|>


<commit_before>'use strict';

var builtIns = require('./builtins')
	, helpers = require('./helpers');

/**
 * Exception that is thrown from a chain
 *
 * @constructor
 * @this {ValidationException}
 * @param {string} msg Constructed message of all error messages
 */
var ValidationException = function (msg) {
	this.name = "ValidationException";
	this.message = msg;
};
/**
 * Exception that is thrown from is
 *
 * @constructor
 * @this {ValidationsException}
 * @param {array} msgs List of constructed error messages
 */
var ValidationsException = function (msgs) {
	this.name = "ValidationsException";
	this.messages = msgs;
};

/**
 * Create a new instance of Is with private error queues
 *
 * @constructor
 * @this {Is}
 * @return {Is} Constructed instance
 */
var Is = function () {
	this.clear();
	return this;
};

/**
 * Creates a chain instance
 *
 * @constructor
 * @this {Chain}
 * @param {!Object} val The value the chain is focused on
 * @param {string} name A human-readable name of the value
 */
var Chain = function (val, name) {

	this._val = helpers.cloneObj(val); // uses cloneObj to prevent affecting original objects through reference
	this._name = name;

	this.clear();

	return this;

};

/**
 * Default error format for top-level chains
 * {0}: val name
 * {1}: constructed error list
 */
Chain.prototype._errorFormat = "{0} must {1}";

/**
 * Default error format for non-top-level chains
 * {0}: val name
 * {1}: constructed error list
 */
Chain.prototype._propFormat = "have a {0} which must {1}";

/**
 * Changes the format of the error message returned
 * {0} - The variables name
 * {1} - The list of errors
 *
 * @return {Chain} this
 */
Chain.prototype.errorFormat = function (format) {
	this._errorFormat = format;
	return this;
};

/**
 * Change the format of property error segments of the error message
 * {0} - The property name
 * {1} - The list of errors
 *
 * @return {Chain} this
 */
Chain.prototype.propFormat = function (format) {
	this._propFormat = format;
	return this;
};

/**
 * Creates a new chain focused on the property name of current chain
 *
 * @param {string} prop Name of property to change focus to
 * @param {string} name A human readable name of the property
 * @return {Chain} A new chain focused on property value
 */
Chain.prototype.prop = function (prop, name) {
	var p = new Chain(this._val[prop], name || prop);
	p._up = this;
	p._propName = prop;
	this._registered.push(p);
	if (this._bypass || typeof(this._val[prop]) === "undefined") {
		p._bypass = true;
	}
	return p;
};

/**
 * Manipulates the current chain value using a supplied function
 *
 * @param {function} fn Accepts the val and returns the manipulated val
 * @param {string} name Manipulated value name
 * @return {Chain} A new chain focused on the manipulated value
 */
Chain.prototype.manip = function (fn, name) {
	var val = fn.call(this, this._val)
	, c = new Chain(val, name || 'manipulated value');
	c._up = this;
	this._registered.push(c);
	if (this._bypass) {
		c._bypass = true;
	}
	return c;
};

/**
 * Replaces the current value if it is equal to the comparator value
 *
 * @param {!Object} val The value to set as current
 * @param {Object} comparator The value to compare to
 * @return {Chain} this
 */
Chain.prototype.replace = function (val, comparator) {
	if (this._val === comparator) {
		this._val = val;
	}
	return this;
};

/**
 * Bypasses all future tests and manipulations
 *
 * @return {Chain} this
 */
Chain.prototype.stop = function () {
	this._bypass = true;
	return this;
};

/**
 * Stop bypassing future tests and manipulations
 *
 * @return {Chain} this
 */
Chain.prototype.resume = function () {
	this._bypass = false;
	return this;
};

Chain.prototype.not = function () {
	this._negate = true;
	return this;
};

/**
 * Starts Bypassing future tests and manipulations if any errors occured
 *
 * @return {Chain} this
 */
Chain.prototype.stopIfErrs = function () {
	if (!this.valid()) {
		this.stop();
	}
	return this;
};

/**
 * Provides access to previous chains of .prop and .manip
 *
 * @return {Chain} this
 */
Chain.prototype.up = function () {
	return this._up;
};

/**
 * Rebuilds current value by replacing the properties with manipulated values
 *
 * @return {Chain} this
 */
Chain.prototype.constructProps = function () {
	var i, l = this._registered.length;
	for (i = 0; i < l; i += 1) {
		this._val[this._registered[i]._propName] = this._registered[i].val();
	}
	return this;
};

/**
 * Get the current manipulated value
 *
 * @return {Object} The manipulated value
 */
Chain.prototype.val = function () {
	return this.constructProps()._val;
};

/**
 * Clears chain of all errors and children chains
 *
 * @return {Chain} this
 */
Chain.prototype.clear = function () {
	this._registered = [];
	this._errors = [];
	this._testCount = 0;
	this._errCount = 0;
	return this;
};

/**
 * Used internally if an error occurs in the chain
 *
 * @param {string} msg A formatted message describing the error
 * @return {Chain} this
 */
Chain.prototype.addError = function (msg) {
	this._errors.push(msg);
	this._errCount += 1;
	return this;
};

/**
 * Used internally to construct the list of errors excluding the subject
 *
 * @return {string} Human-readable list of errors
 */
Chain.prototype.errorList = function () {
	var i, errs = this._errors.slice(0);
	for (i = 0; i < this._registered.length; i += 1) {
		if (this._registered[i].errCount() > 0) {
			errs.push(helpers.formatStr(this._propFormat, this._registered[i]._name, this._registered[i].errorList()));
		}
	}
	return helpers.stringListJoin(errs);
};

/**
 * Constructs a human-readable message describing all the errors in the chain
 *
 * @return {string} Human-readable list of errors
 */
Chain.prototype.errorMessage = function () {
	if (this.valid()) {
		return;
	}
	var list = this.errorList();
	return helpers.formatStr(this._errorFormat, this._name, list);
};

/**
 * The number of tests that have been run in the chain
 *
 * @return {number} The number of tests ran
 */
Chain.prototype.testCount = function () {
	var i, count = this._testCount;
	for (i = 0; i < this._registered.length; i += 1) {
		count += this._registered[i].testCount();
	}
	return count;
};

/**
 * The number of errors that have occured in the chain
 *
 * @return {number} The number of errors
 */
Chain.prototype.errCount = function () {
	var i, count = this._errCount;
	for (i = 0; i < this._registered.length; i += 1) {
		count += this._registered[i].errCount();
	}
	return count;
};

/**
 * @return {boolean} If the chain has no errors
 */
Chain.prototype.valid = function () {
	return this.errCount() === 0;
};

/**
 * Throws an exception if there are any errors in the chain
 *
 * @return {Chain} this
 */
Chain.prototype.throwErr = function () {
	if (this.errCount() > 0) {
		var msg = this.errorMessage();
		this.clear();
		throw new ValidationException(msg);
	}
	return this;
};

/**
 * Creates a new chain and registers it to this instance of Is
 *
 * @param {Object} val The value used in the chain
 * @param {string} name The name of the value used in the chain
 * @return {Chain} A constructed chain object
 */
Is.prototype.that = function (val, name) {

	if (!(this instanceof Is)) {
		throw new Error('Not an instance');
	}

	var c = new Chain(val, name || 'value');
	this._registered.push(c);

	return c;

};

/**
 * Extends Is and Chain with a new test function
 *
 * @param {string} name The name the test will be available by
 * @param {function} fn The test function. Returns true or false
 * @return {Is} this
 */
Is.prototype.addTest = function (name, fn) {
	Is.prototype[name] = fn;
	Chain.prototype[name] = function () {
		if (this._bypass) {
			return this;
		}
		var args = Array.prototype.slice.call(arguments)
			, msg = (this._negate ? 'not ' : '') + fn.failMessage || 'pass ' + name + ' test'
			, result
		;

		args = [this._val].concat(args);

		if (args.length > fn.length) {
			msg = args.pop();
		}

		result = fn.apply(null, args);

		if ((this._negate && result) || (!this._negate && !result)) {
			this.addError(helpers.formatStr.apply(null, [msg].concat(args)));
		}

		delete this._negate;

		this._testCount += 1;
		return this;
	};
	return this;
};

/**
 * Extends Is and Chain with a new manipulation function
 *
 * @param {string} name The name the manipulation will be available by
 * @param {function} fn The manipulation function. Returns manipulated value
 * @return {Is} this
 */
Is.prototype.addManip = function (name, fn) {
	Is.prototype[name] = fn;
	Chain.prototype[name] = function () {
		if (this._bypass) {
			return this;
		}
		var args = Array.prototype.slice.call(arguments)
		, val = fn.apply(null, [this._val].concat(args));
		if (typeof(fn.failVal) !== "undefined"
				&& ((typeof(fn.failVal) === "function" && fn.failVal.call(this, val))
						|| val === fn.failVal)) {
			this._bypass = true;
			this.addError(fn.failMessage || "Generic error message");
		}
		this._val = val;
		return this;
	};
	return this;
};

/**
 * Creates a list of error messages
 *
 * @return {array} List of human-readable error messages
 */
Is.prototype.errorMessages = function () {
	if (this.valid()) {
		return;
	}
	var i, messages = [];
	for (i = 0; i < this._registered.length; i += 1) {
		messages.push(this._registered[i].errorMessage());
	}
	return messages;
};

/**
 * Adds up all tests ran in this instance
 *
 * @return {number} The number of tests ran
 */
Is.prototype.testCount = function () {
	var i, count = 0;
	for (i = 0; i < this._registered.length; i += 1) {
		count += this._registered[i].testCount();
	}
	return count;
};

/**
 * Adds up all errors occured in this instance
 *
 * @return {number} The number of errors
 */
Is.prototype.errCount = function () {
	var i, count = 0;
	for (i = 0; i < this._registered.length; i += 1) {
		count += this._registered[i].errCount();
	}
	return count;
};

/**
 * Reset all errors and tests recoreded
 *
 * @return {Is} this
 */
Is.prototype.clear = function () {
	this._registered = [];
	return this;
};

/**
 * True if the instance has no errors
 *
 * @return {boolean} Has no errors
 */
Is.prototype.valid = function () {
	return this.errCount() === 0;
};

/**
 * Throws an exception if there are any errors
 *
 * @return {Is} this
 */
Is.prototype.throwErrs = function () {
	if (this.errCount() > 0) {
		var msgs = this.errorMessages();
		this.clear();
		throw new ValidationsException(msgs);
	}
	return this;
};

/**
 * Creates and returns a new instance of Is
 *
 * @return {Is} A new instance
 */
Is.prototype.create = function () {
	return new Is();
};

var name, is = module.exports = new Is();

for (name in builtIns.validators) {
	if (builtIns.validators.hasOwnProperty(name)) {
		is.addTest(name, builtIns.validators[name]);
	}
}

for (name in builtIns.manipulators) {
	if (builtIns.manipulators.hasOwnProperty(name)) {
		is.addManip(name, builtIns.manipulators[name]);
	}
}
<commit_msg>Fix to is.errorMessages<commit_after>'use strict';

var builtIns = require('./builtins')
	, helpers = require('./helpers');

/**
 * Exception that is thrown from a chain
 *
 * @constructor
 * @this {ValidationException}
 * @param {string} msg Constructed message of all error messages
 */
var ValidationException = function (msg) {
	this.name = "ValidationException";
	this.message = msg;
};
/**
 * Exception that is thrown from is
 *
 * @constructor
 * @this {ValidationsException}
 * @param {array} msgs List of constructed error messages
 */
var ValidationsException = function (msgs) {
	this.name = "ValidationsException";
	this.messages = msgs;
};

/**
 * Create a new instance of Is with private error queues
 *
 * @constructor
 * @this {Is}
 * @return {Is} Constructed instance
 */
var Is = function () {
	this.clear();
	return this;
};

/**
 * Creates a chain instance
 *
 * @constructor
 * @this {Chain}
 * @param {!Object} val The value the chain is focused on
 * @param {string} name A human-readable name of the value
 */
var Chain = function (val, name) {

	this._val = helpers.cloneObj(val); // uses cloneObj to prevent affecting original objects through reference
	this._name = name;

	this.clear();

	return this;

};

/**
 * Default error format for top-level chains
 * {0}: val name
 * {1}: constructed error list
 */
Chain.prototype._errorFormat = "{0} must {1}";

/**
 * Default error format for non-top-level chains
 * {0}: val name
 * {1}: constructed error list
 */
Chain.prototype._propFormat = "have a {0} which must {1}";

/**
 * Changes the format of the error message returned
 * {0} - The variables name
 * {1} - The list of errors
 *
 * @return {Chain} this
 */
Chain.prototype.errorFormat = function (format) {
	this._errorFormat = format;
	return this;
};

/**
 * Change the format of property error segments of the error message
 * {0} - The property name
 * {1} - The list of errors
 *
 * @return {Chain} this
 */
Chain.prototype.propFormat = function (format) {
	this._propFormat = format;
	return this;
};

/**
 * Creates a new chain focused on the property name of current chain
 *
 * @param {string} prop Name of property to change focus to
 * @param {string} name A human readable name of the property
 * @return {Chain} A new chain focused on property value
 */
Chain.prototype.prop = function (prop, name) {
	var p = new Chain(this._val[prop], name || prop);
	p._up = this;
	p._propName = prop;
	this._registered.push(p);
	if (this._bypass || typeof(this._val[prop]) === "undefined") {
		p._bypass = true;
	}
	return p;
};

/**
 * Manipulates the current chain value using a supplied function
 *
 * @param {function} fn Accepts the val and returns the manipulated val
 * @param {string} name Manipulated value name
 * @return {Chain} A new chain focused on the manipulated value
 */
Chain.prototype.manip = function (fn, name) {
	var val = fn.call(this, this._val)
	, c = new Chain(val, name || 'manipulated value');
	c._up = this;
	this._registered.push(c);
	if (this._bypass) {
		c._bypass = true;
	}
	return c;
};

/**
 * Replaces the current value if it is equal to the comparator value
 *
 * @param {!Object} val The value to set as current
 * @param {Object} comparator The value to compare to
 * @return {Chain} this
 */
Chain.prototype.replace = function (val, comparator) {
	if (this._val === comparator) {
		this._val = val;
	}
	return this;
};

/**
 * Bypasses all future tests and manipulations
 *
 * @return {Chain} this
 */
Chain.prototype.stop = function () {
	this._bypass = true;
	return this;
};

/**
 * Stop bypassing future tests and manipulations
 *
 * @return {Chain} this
 */
Chain.prototype.resume = function () {
	this._bypass = false;
	return this;
};

Chain.prototype.not = function () {
	this._negate = true;
	return this;
};

/**
 * Starts Bypassing future tests and manipulations if any errors occured
 *
 * @return {Chain} this
 */
Chain.prototype.stopIfErrs = function () {
	if (!this.valid()) {
		this.stop();
	}
	return this;
};

/**
 * Provides access to previous chains of .prop and .manip
 *
 * @return {Chain} this
 */
Chain.prototype.up = function () {
	return this._up;
};

/**
 * Rebuilds current value by replacing the properties with manipulated values
 *
 * @return {Chain} this
 */
Chain.prototype.constructProps = function () {
	var i, l = this._registered.length;
	for (i = 0; i < l; i += 1) {
		this._val[this._registered[i]._propName] = this._registered[i].val();
	}
	return this;
};

/**
 * Get the current manipulated value
 *
 * @return {Object} The manipulated value
 */
Chain.prototype.val = function () {
	return this.constructProps()._val;
};

/**
 * Clears chain of all errors and children chains
 *
 * @return {Chain} this
 */
Chain.prototype.clear = function () {
	this._registered = [];
	this._errors = [];
	this._testCount = 0;
	this._errCount = 0;
	return this;
};

/**
 * Used internally if an error occurs in the chain
 *
 * @param {string} msg A formatted message describing the error
 * @return {Chain} this
 */
Chain.prototype.addError = function (msg) {
	this._errors.push(msg);
	this._errCount += 1;
	return this;
};

/**
 * Used internally to construct the list of errors excluding the subject
 *
 * @return {string} Human-readable list of errors
 */
Chain.prototype.errorList = function () {
	var i, errs = this._errors.slice(0);
	for (i = 0; i < this._registered.length; i += 1) {
		if (this._registered[i].errCount() > 0) {
			errs.push(helpers.formatStr(this._propFormat, this._registered[i]._name, this._registered[i].errorList()));
		}
	}
	return helpers.stringListJoin(errs);
};

/**
 * Constructs a human-readable message describing all the errors in the chain
 *
 * @return {string} Human-readable list of errors
 */
Chain.prototype.errorMessage = function () {
	if (this.valid()) {
		return;
	}
	var list = this.errorList();
	return helpers.formatStr(this._errorFormat, this._name, list);
};

/**
 * The number of tests that have been run in the chain
 *
 * @return {number} The number of tests ran
 */
Chain.prototype.testCount = function () {
	var i, count = this._testCount;
	for (i = 0; i < this._registered.length; i += 1) {
		count += this._registered[i].testCount();
	}
	return count;
};

/**
 * The number of errors that have occured in the chain
 *
 * @return {number} The number of errors
 */
Chain.prototype.errCount = function () {
	var i, count = this._errCount;
	for (i = 0; i < this._registered.length; i += 1) {
		count += this._registered[i].errCount();
	}
	return count;
};

/**
 * @return {boolean} If the chain has no errors
 */
Chain.prototype.valid = function () {
	return this.errCount() === 0;
};

/**
 * Throws an exception if there are any errors in the chain
 *
 * @return {Chain} this
 */
Chain.prototype.throwErr = function () {
	if (this.errCount() > 0) {
		var msg = this.errorMessage();
		this.clear();
		throw new ValidationException(msg);
	}
	return this;
};

/**
 * Creates a new chain and registers it to this instance of Is
 *
 * @param {Object} val The value used in the chain
 * @param {string} name The name of the value used in the chain
 * @return {Chain} A constructed chain object
 */
Is.prototype.that = function (val, name) {

	if (!(this instanceof Is)) {
		throw new Error('Not an instance');
	}

	var c = new Chain(val, name || 'value');
	this._registered.push(c);

	return c;

};

/**
 * Extends Is and Chain with a new test function
 *
 * @param {string} name The name the test will be available by
 * @param {function} fn The test function. Returns true or false
 * @return {Is} this
 */
Is.prototype.addTest = function (name, fn) {
	Is.prototype[name] = fn;
	Chain.prototype[name] = function () {
		if (this._bypass) {
			return this;
		}
		var args = Array.prototype.slice.call(arguments)
			, msg = (this._negate ? 'not ' : '') + fn.failMessage || 'pass ' + name + ' test'
			, result
		;

		args = [this._val].concat(args);

		if (args.length > fn.length) {
			msg = args.pop();
		}

		result = fn.apply(null, args);

		if ((this._negate && result) || (!this._negate && !result)) {
			this.addError(helpers.formatStr.apply(null, [msg].concat(args)));
		}

		delete this._negate;

		this._testCount += 1;
		return this;
	};
	return this;
};

/**
 * Extends Is and Chain with a new manipulation function
 *
 * @param {string} name The name the manipulation will be available by
 * @param {function} fn The manipulation function. Returns manipulated value
 * @return {Is} this
 */
Is.prototype.addManip = function (name, fn) {
	Is.prototype[name] = fn;
	Chain.prototype[name] = function () {
		if (this._bypass) {
			return this;
		}
		var args = Array.prototype.slice.call(arguments)
		, val = fn.apply(null, [this._val].concat(args));
		if (typeof(fn.failVal) !== "undefined"
				&& ((typeof(fn.failVal) === "function" && fn.failVal.call(this, val))
						|| val === fn.failVal)) {
			this._bypass = true;
			this.addError(fn.failMessage || "Generic error message");
		}
		this._val = val;
		return this;
	};
	return this;
};

/**
 * Creates a list of error messages
 *
 * @return {array} List of human-readable error messages
 */
Is.prototype.errorMessages = function () {
	if (this.valid()) {
		return;
	}
	var i, messages = [];
	for (i = 0; i < this._registered.length; i += 1) {
		if (!this._registered[i].valid()) {
			messages.push(this._registered[i].errorMessage());
		}
	}
	return messages;
};

/**
 * Adds up all tests ran in this instance
 *
 * @return {number} The number of tests ran
 */
Is.prototype.testCount = function () {
	var i, count = 0;
	for (i = 0; i < this._registered.length; i += 1) {
		count += this._registered[i].testCount();
	}
	return count;
};

/**
 * Adds up all errors occured in this instance
 *
 * @return {number} The number of errors
 */
Is.prototype.errCount = function () {
	var i, count = 0;
	for (i = 0; i < this._registered.length; i += 1) {
		count += this._registered[i].errCount();
	}
	return count;
};

/**
 * Reset all errors and tests recoreded
 *
 * @return {Is} this
 */
Is.prototype.clear = function () {
	this._registered = [];
	return this;
};

/**
 * True if the instance has no errors
 *
 * @return {boolean} Has no errors
 */
Is.prototype.valid = function () {
	return this.errCount() === 0;
};

/**
 * Throws an exception if there are any errors
 *
 * @return {Is} this
 */
Is.prototype.throwErrs = function () {
	if (this.errCount() > 0) {
		var msgs = this.errorMessages();
		this.clear();
		throw new ValidationsException(msgs);
	}
	return this;
};

/**
 * Creates and returns a new instance of Is
 *
 * @return {Is} A new instance
 */
Is.prototype.create = function () {
	return new Is();
};

var name, is = module.exports = new Is();

for (name in builtIns.validators) {
	if (builtIns.validators.hasOwnProperty(name)) {
		is.addTest(name, builtIns.validators[name]);
	}
}

for (name in builtIns.manipulators) {
	if (builtIns.manipulators.hasOwnProperty(name)) {
		is.addManip(name, builtIns.manipulators[name]);
	}
}
<|endoftext|>


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

    var indexedDB,
        IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange,
        transactionModes = {
            readonly: 'readonly',
            readwrite: 'readwrite'
        };

    var hasOwn = Object.prototype.hasOwnProperty;

    var getIndexedDB = function() {
      if ( !indexedDB ) {
        indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.oIndexedDB || window.msIndexedDB || ((window.indexedDB === null && window.shimIndexedDB) ? window.shimIndexedDB : undefined);

        if ( !indexedDB ) {
          throw 'IndexedDB required';
        }
      }
      return indexedDB;
    };

    var defaultMapper = function (value) {
        return value;
    };

    var CallbackList = function () {
        var state,
            list = [];

        var exec = function ( context , args ) {
            if ( list ) {
                args = args || [];
                state = state || [ context , args ];

                for ( var i = 0 , il = list.length ; i < il ; i++ ) {
                    list[ i ].apply( state[ 0 ] , state[ 1 ] );
                }

                list = [];
            }
        };

        this.add = function () {
            for ( var i = 0 , il = arguments.length ; i < il ; i ++ ) {
                list.push( arguments[ i ] );
            }

            if ( state ) {
                exec();
            }

            return this;
        };

        this.execute = function () {
            exec( this , arguments );
            return this;
        };
    };

    var Server = function ( db , name ) {
        var that = this,
            closed = false;

		this.getIndexedDB = function () {
			return db;
		};

        this.add = function( table ) {
            if ( closed ) {
                throw 'Database has been closed';
            }

            var records = [];
            var counter = 0;

            for (var i = 0; i < arguments.length - 1; i++) {
                if (Array.isArray(arguments[i + 1])) {
                    for (var j = 0; j < (arguments[i + 1]).length; j++) {
                        records[counter] = (arguments[i + 1])[j];
                        counter++;
                    }
                } else {
                    records[counter] = arguments[i + 1];
                    counter++;
                }
            }

            var transaction = db.transaction( table , transactionModes.readwrite ),
                store = transaction.objectStore( table );

            return new Promise(function(resolve, reject){
              records.forEach( function ( record ) {
                  var req;
                  if ( record.item && record.key ) {
                      var key = record.key;
                      record = record.item;
                      req = store.add( record , key );
                  } else {
                      req = store.add( record );
                  }

                  req.onsuccess = function ( e ) {
                      var target = e.target;
                      var keyPath = target.source.keyPath;
                      if ( keyPath === null ) {
                          keyPath = '__id__';
                      }
                      Object.defineProperty( record , keyPath , {
                          value: target.result,
                          enumerable: true
                      });
                  };
              } );

              transaction.oncomplete = function () {
                  resolve( records , that );
              };
              transaction.onerror = function ( e ) {
                  reject( e );
              };
              transaction.onabort = function ( e ) {
                  reject( e );
              };

            });
        };

        this.update = function( table ) {
            if ( closed ) {
                throw 'Database has been closed';
            }

            var records = [];
            for ( var i = 0 ; i < arguments.length - 1 ; i++ ) {
                records[ i ] = arguments[ i + 1 ];
            }

            var transaction = db.transaction( table , transactionModes.readwrite ),
                store = transaction.objectStore( table ),
                keyPath = store.keyPath;

            return new Promise(function(resolve, reject){
              records.forEach( function ( record ) {
                  var req;
                  var count;
                  if ( record.item && record.key ) {
                      var key = record.key;
                      record = record.item;
                      req = store.put( record , key );
                  } else {
                      req = store.put( record );
                  }

                  req.onsuccess = function ( e ) {
                      // deferred.notify(); es6 promise can't notify
                  };
              } );

              transaction.oncomplete = function () {
                  resolve( records , that );
              };
              transaction.onerror = function ( e ) {
                  reject( e );
              };
              transaction.onabort = function ( e ) {
                  reject( e );
              };
            });

        };

        this.remove = function ( table , key ) {
            if ( closed ) {
                throw 'Database has been closed';
            }
            var transaction = db.transaction( table , transactionModes.readwrite ),
                store = transaction.objectStore( table );

            return new Promise(function(resolve, reject){
              var req = store['delete']( key );
              transaction.oncomplete = function ( ) {
                  resolve( key );
              };
              transaction.onerror = function ( e ) {
                  reject( e );
              };
            });
        };

        this.clear = function ( table ) {
            if ( closed ) {
                throw 'Database has been closed';
            }
            var transaction = db.transaction( table , transactionModes.readwrite ),
                store = transaction.objectStore( table );

            var req = store.clear();
            return new Promise(function(resolve, reject){
              transaction.oncomplete = function ( ) {
                  resolve( );
              };
              transaction.onerror = function ( e ) {
                  reject( e );
              };
            });
        };

        this.close = function ( ) {
            if ( closed ) {
                throw 'Database has been closed';
            }
            db.close();
            closed = true;
            delete dbCache[ name ];
        };

        this.get = function ( table , id ) {
            if ( closed ) {
                throw 'Database has been closed';
            }
            var transaction = db.transaction( table ),
                store = transaction.objectStore( table );

            var req = store.get( id );
            return new Promise(function(resolve, reject){
              req.onsuccess = function ( e ) {
                  resolve( e.target.result );
              };
              transaction.onerror = function ( e ) {
                  reject( e );
              };
            });
        };

        this.query = function ( table , index ) {
            if ( closed ) {
                throw 'Database has been closed';
            }
            return new IndexQuery( table , db , index );
        };

        this.count = function (table , key) {
            if ( closed ) {
                throw 'Database has been closed';
            }
            var transaction = db.transaction( table ),
                store = transaction.objectStore( table );
        }

        for ( var i = 0 , il = db.objectStoreNames.length ; i < il ; i++ ) {
            (function ( storeName ) {
                that[ storeName ] = { };
                for ( var i in that ) {
                    if ( !hasOwn.call( that , i ) || i === 'close' ) {
                        continue;
                    }
                    that[ storeName ][ i ] = (function ( i ) {
                        return function () {
                            var args = [ storeName ].concat( [].slice.call( arguments , 0 ) );
                            return that[ i ].apply( that , args );
                        };
                    })( i );
                }
            })( db.objectStoreNames[ i ] );
        }
    };

    var IndexQuery = function ( table , db , indexName ) {
        var that = this;
        var modifyObj = false;

        var runQuery = function ( type, args , cursorType , direction, limitRange, filters , mapper ) {
            var transaction = db.transaction( table, modifyObj ? transactionModes.readwrite : transactionModes.readonly ),
                store = transaction.objectStore( table ),
                index = indexName ? store.index( indexName ) : store,
                keyRange = type ? IDBKeyRange[ type ].apply( null, args ) : null,
                results = [],
                indexArgs = [ keyRange ],
                limitRange = limitRange ? limitRange : null,
                filters = filters ? filters : [],
                counter = 0;

            if ( cursorType !== 'count' ) {
                indexArgs.push( direction || 'next' );
            };

            // create a function that will set in the modifyObj properties into
            // the passed record.
            var modifyKeys = modifyObj ? Object.keys(modifyObj) : false;
            var modifyRecord = function(record) {
                for(var i = 0; i < modifyKeys.length; i++) {
                    var key = modifyKeys[i];
                    var val = modifyObj[key];
                    if(val instanceof Function) val = val(record);
                    record[key] = val;
                }
                return record;
            };

            index[cursorType].apply( index , indexArgs ).onsuccess = function ( e ) {
                var cursor = e.target.result;
                if ( typeof cursor === typeof 0 ) {
                    results = cursor;
                } else if ( cursor ) {
                	if ( limitRange !== null && limitRange[0] > counter) {
                    	counter = limitRange[0];
                    	cursor.advance(limitRange[0]);
                    } else if ( limitRange !== null && counter >= (limitRange[0] + limitRange[1]) ) {
                        //out of limit range... skip
                    } else {
                        var matchFilter = true;
                        var result = 'value' in cursor ? cursor.value : cursor.key;

                        filters.forEach( function ( filter ) {
                            if ( !filter || !filter.length ) {
                                //Invalid filter do nothing
                            } else if ( filter.length === 2 ) {
                                matchFilter = matchFilter && (result[filter[0]] === filter[1])
                            } else {
                                matchFilter = matchFilter && filter[0].apply(undefined,[result]);
                            }
                        });

                        if (matchFilter) {
                            counter++;
                            results.push( mapper(result) );
                            // if we're doing a modify, run it now
                            if(modifyObj) {
                                result = modifyRecord(result);
                                cursor.update(result);
                            }
                        }
                        cursor['continue']();
                    }
                }
            };

            return new Promise(function(resolve, reject){
              transaction.oncomplete = function () {
                  resolve( results );
              };
              transaction.onerror = function ( e ) {
                  reject( e );
              };
              transaction.onabort = function ( e ) {
                  reject( e );
              };
            });
        };

        var Query = function ( type , args ) {
            var direction = 'next',
                cursorType = 'openCursor',
                filters = [],
                limitRange = null,
                mapper = defaultMapper,
                unique = false;

            var execute = function () {
                return runQuery( type , args , cursorType , unique ? direction + 'unique' : direction, limitRange, filters , mapper );
            };

            var limit = function () {
                limitRange = Array.prototype.slice.call( arguments , 0 , 2 )
                if (limitRange.length == 1) {
                    limitRange.unshift(0)
                }

                return {
                    execute: execute
                };
            };
            var count = function () {
                direction = null;
                cursorType = 'count';

                return {
                    execute: execute
                };
            };
            var keys = function () {
                cursorType = 'openKeyCursor';

                return {
                    desc: desc,
                    execute: execute,
                    filter: filter,
                    distinct: distinct,
                    map: map
                };
            };
            var filter = function ( ) {
                filters.push( Array.prototype.slice.call( arguments , 0 , 2 ) );

                return {
                    keys: keys,
                    execute: execute,
                    filter: filter,
                    desc: desc,
                    distinct: distinct,
                    modify: modify,
                    limit: limit,
                    map: map
                };
            };
            var desc = function () {
                direction = 'prev';

                return {
                    keys: keys,
                    execute: execute,
                    filter: filter,
                    distinct: distinct,
                    modify: modify,
                    map: map
                };
            };
            var distinct = function () {
                unique = true;
                return {
                    keys: keys,
                    count: count,
                    execute: execute,
                    filter: filter,
                    desc: desc,
                    modify: modify,
                    map: map
                };
            };
            var modify = function(update) {
                modifyObj = update;
                return {
                    execute: execute
                };
            };
            var map = function (fn) {
                mapper = fn;

                return {
                    execute: execute,
                    count: count,
                    keys: keys,
                    filter: filter,
                    desc: desc,
                    distinct: distinct,
                    modify: modify,
                    limit: limit,
                    map: map
                };
            };

            return {
                execute: execute,
                count: count,
                keys: keys,
                filter: filter,
                desc: desc,
                distinct: distinct,
                modify: modify,
                limit: limit,
                map: map
            };
        };

        'only bound upperBound lowerBound'.split(' ').forEach(function (name) {
            that[name] = function () {
                return new Query( name , arguments );
            };
        });

        this.filter = function () {
            var query = new Query( null , null );
            return query.filter.apply( query , arguments );
        };

        this.all = function () {
            return this.filter();
        };
    };

    var createSchema = function ( e , schema , db ) {
        if ( typeof schema === 'function' ) {
            schema = schema();
        }

        for ( var tableName in schema ) {
            var table = schema[ tableName ];
            var store;
            if (!hasOwn.call(schema, tableName) || db.objectStoreNames.contains(tableName)) {
                store = e.currentTarget.transaction.objectStore(tableName);
            } else {
                store = db.createObjectStore(tableName, table.key);
            }

            for ( var indexKey in table.indexes ) {
                var index = table.indexes[ indexKey ];
                try {
                    store.index(indexKey)
                } catch (e) {
                    store.createIndex( indexKey , index.key || indexKey , Object.keys(index).length ? index : { unique: false } );
                }
            }
        }
    };

    var open = function ( e , server , version , schema ) {
        var db = e.target.result;
        var s = new Server( db , server );
        var upgrade;

        dbCache[ server ] = db;

        return Promise.resolve(s)
    };

    var dbCache = {};

    var db = {
        version: '0.9.2',
        open: function ( options ) {
            var request;

            return new Promise(function(resolve, reject){
              if ( dbCache[ options.server ] ) {
                  open( {
                      target: {
                          result: dbCache[ options.server ]
                      }
                  } , options.server , options.version , options.schema )
                  .then(resolve, reject)
              } else {
                  request = getIndexedDB().open( options.server , options.version );

                  request.onsuccess = function ( e ) {
                      open( e , options.server , options.version , options.schema )
                          .then(resolve, reject)
                  };

                  request.onupgradeneeded = function ( e ) {
                      createSchema( e , options.schema , e.target.result );
                  };
                  request.onerror = function ( e ) {
                      reject( e );
                  };
              }
            });
        }
    };

    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        module.exports = db;
    } else if ( typeof define === 'function' && define.amd ) {
        define( function() { return db; } );
    } else {
        window.db = db;
    }
})( window );
<commit_msg>Work around Firefox issue when ContraintError occurs<commit_after>(function ( window , undefined ) {
    'use strict';

    var indexedDB,
        IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange,
        transactionModes = {
            readonly: 'readonly',
            readwrite: 'readwrite'
        };

    var hasOwn = Object.prototype.hasOwnProperty;

    var getIndexedDB = function() {
      if ( !indexedDB ) {
        indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.oIndexedDB || window.msIndexedDB || ((window.indexedDB === null && window.shimIndexedDB) ? window.shimIndexedDB : undefined);

        if ( !indexedDB ) {
          throw 'IndexedDB required';
        }
      }
      return indexedDB;
    };

    var defaultMapper = function (value) {
        return value;
    };

    var CallbackList = function () {
        var state,
            list = [];

        var exec = function ( context , args ) {
            if ( list ) {
                args = args || [];
                state = state || [ context , args ];

                for ( var i = 0 , il = list.length ; i < il ; i++ ) {
                    list[ i ].apply( state[ 0 ] , state[ 1 ] );
                }

                list = [];
            }
        };

        this.add = function () {
            for ( var i = 0 , il = arguments.length ; i < il ; i ++ ) {
                list.push( arguments[ i ] );
            }

            if ( state ) {
                exec();
            }

            return this;
        };

        this.execute = function () {
            exec( this , arguments );
            return this;
        };
    };

    var Server = function ( db , name ) {
        var that = this,
            closed = false;

		this.getIndexedDB = function () {
			return db;
		};

        this.add = function( table ) {
            if ( closed ) {
                throw 'Database has been closed';
            }

            var records = [];
            var counter = 0;

            for (var i = 0; i < arguments.length - 1; i++) {
                if (Array.isArray(arguments[i + 1])) {
                    for (var j = 0; j < (arguments[i + 1]).length; j++) {
                        records[counter] = (arguments[i + 1])[j];
                        counter++;
                    }
                } else {
                    records[counter] = arguments[i + 1];
                    counter++;
                }
            }

            var transaction = db.transaction( table , transactionModes.readwrite ),
                store = transaction.objectStore( table );

            return new Promise(function(resolve, reject){
              records.forEach( function ( record ) {
                  var req;
                  if ( record.item && record.key ) {
                      var key = record.key;
                      record = record.item;
                      req = store.add( record , key );
                  } else {
                      req = store.add( record );
                  }

                  req.onsuccess = function ( e ) {
                      var target = e.target;
                      var keyPath = target.source.keyPath;
                      if ( keyPath === null ) {
                          keyPath = '__id__';
                      }
                      Object.defineProperty( record , keyPath , {
                          value: target.result,
                          enumerable: true
                      });
                  };
              } );

              transaction.oncomplete = function () {
                  resolve( records , that );
              };
              transaction.onerror = function ( e ) {
                  // prevent Firefox from throwing a ConstraintError and aborting (hard)
                  // https://bugzilla.mozilla.org/show_bug.cgi?id=872873
                  e.preventDefault();
                  reject( e );
              };
              transaction.onabort = function ( e ) {
                  reject( e );
              };

            });
        };

        this.update = function( table ) {
            if ( closed ) {
                throw 'Database has been closed';
            }

            var records = [];
            for ( var i = 0 ; i < arguments.length - 1 ; i++ ) {
                records[ i ] = arguments[ i + 1 ];
            }

            var transaction = db.transaction( table , transactionModes.readwrite ),
                store = transaction.objectStore( table ),
                keyPath = store.keyPath;

            return new Promise(function(resolve, reject){
              records.forEach( function ( record ) {
                  var req;
                  var count;
                  if ( record.item && record.key ) {
                      var key = record.key;
                      record = record.item;
                      req = store.put( record , key );
                  } else {
                      req = store.put( record );
                  }

                  req.onsuccess = function ( e ) {
                      // deferred.notify(); es6 promise can't notify
                  };
              } );

              transaction.oncomplete = function () {
                  resolve( records , that );
              };
              transaction.onerror = function ( e ) {
                  reject( e );
              };
              transaction.onabort = function ( e ) {
                  reject( e );
              };
            });

        };

        this.remove = function ( table , key ) {
            if ( closed ) {
                throw 'Database has been closed';
            }
            var transaction = db.transaction( table , transactionModes.readwrite ),
                store = transaction.objectStore( table );

            return new Promise(function(resolve, reject){
              var req = store['delete']( key );
              transaction.oncomplete = function ( ) {
                  resolve( key );
              };
              transaction.onerror = function ( e ) {
                  reject( e );
              };
            });
        };

        this.clear = function ( table ) {
            if ( closed ) {
                throw 'Database has been closed';
            }
            var transaction = db.transaction( table , transactionModes.readwrite ),
                store = transaction.objectStore( table );

            var req = store.clear();
            return new Promise(function(resolve, reject){
              transaction.oncomplete = function ( ) {
                  resolve( );
              };
              transaction.onerror = function ( e ) {
                  reject( e );
              };
            });
        };

        this.close = function ( ) {
            if ( closed ) {
                throw 'Database has been closed';
            }
            db.close();
            closed = true;
            delete dbCache[ name ];
        };

        this.get = function ( table , id ) {
            if ( closed ) {
                throw 'Database has been closed';
            }
            var transaction = db.transaction( table ),
                store = transaction.objectStore( table );

            var req = store.get( id );
            return new Promise(function(resolve, reject){
              req.onsuccess = function ( e ) {
                  resolve( e.target.result );
              };
              transaction.onerror = function ( e ) {
                  reject( e );
              };
            });
        };

        this.query = function ( table , index ) {
            if ( closed ) {
                throw 'Database has been closed';
            }
            return new IndexQuery( table , db , index );
        };

        this.count = function (table , key) {
            if ( closed ) {
                throw 'Database has been closed';
            }
            var transaction = db.transaction( table ),
                store = transaction.objectStore( table );
        }

        for ( var i = 0 , il = db.objectStoreNames.length ; i < il ; i++ ) {
            (function ( storeName ) {
                that[ storeName ] = { };
                for ( var i in that ) {
                    if ( !hasOwn.call( that , i ) || i === 'close' ) {
                        continue;
                    }
                    that[ storeName ][ i ] = (function ( i ) {
                        return function () {
                            var args = [ storeName ].concat( [].slice.call( arguments , 0 ) );
                            return that[ i ].apply( that , args );
                        };
                    })( i );
                }
            })( db.objectStoreNames[ i ] );
        }
    };

    var IndexQuery = function ( table , db , indexName ) {
        var that = this;
        var modifyObj = false;

        var runQuery = function ( type, args , cursorType , direction, limitRange, filters , mapper ) {
            var transaction = db.transaction( table, modifyObj ? transactionModes.readwrite : transactionModes.readonly ),
                store = transaction.objectStore( table ),
                index = indexName ? store.index( indexName ) : store,
                keyRange = type ? IDBKeyRange[ type ].apply( null, args ) : null,
                results = [],
                indexArgs = [ keyRange ],
                limitRange = limitRange ? limitRange : null,
                filters = filters ? filters : [],
                counter = 0;

            if ( cursorType !== 'count' ) {
                indexArgs.push( direction || 'next' );
            };

            // create a function that will set in the modifyObj properties into
            // the passed record.
            var modifyKeys = modifyObj ? Object.keys(modifyObj) : false;
            var modifyRecord = function(record) {
                for(var i = 0; i < modifyKeys.length; i++) {
                    var key = modifyKeys[i];
                    var val = modifyObj[key];
                    if(val instanceof Function) val = val(record);
                    record[key] = val;
                }
                return record;
            };

            index[cursorType].apply( index , indexArgs ).onsuccess = function ( e ) {
                var cursor = e.target.result;
                if ( typeof cursor === typeof 0 ) {
                    results = cursor;
                } else if ( cursor ) {
                	if ( limitRange !== null && limitRange[0] > counter) {
                    	counter = limitRange[0];
                    	cursor.advance(limitRange[0]);
                    } else if ( limitRange !== null && counter >= (limitRange[0] + limitRange[1]) ) {
                        //out of limit range... skip
                    } else {
                        var matchFilter = true;
                        var result = 'value' in cursor ? cursor.value : cursor.key;

                        filters.forEach( function ( filter ) {
                            if ( !filter || !filter.length ) {
                                //Invalid filter do nothing
                            } else if ( filter.length === 2 ) {
                                matchFilter = matchFilter && (result[filter[0]] === filter[1])
                            } else {
                                matchFilter = matchFilter && filter[0].apply(undefined,[result]);
                            }
                        });

                        if (matchFilter) {
                            counter++;
                            results.push( mapper(result) );
                            // if we're doing a modify, run it now
                            if(modifyObj) {
                                result = modifyRecord(result);
                                cursor.update(result);
                            }
                        }
                        cursor['continue']();
                    }
                }
            };

            return new Promise(function(resolve, reject){
              transaction.oncomplete = function () {
                  resolve( results );
              };
              transaction.onerror = function ( e ) {
                  reject( e );
              };
              transaction.onabort = function ( e ) {
                  reject( e );
              };
            });
        };

        var Query = function ( type , args ) {
            var direction = 'next',
                cursorType = 'openCursor',
                filters = [],
                limitRange = null,
                mapper = defaultMapper,
                unique = false;

            var execute = function () {
                return runQuery( type , args , cursorType , unique ? direction + 'unique' : direction, limitRange, filters , mapper );
            };

            var limit = function () {
                limitRange = Array.prototype.slice.call( arguments , 0 , 2 )
                if (limitRange.length == 1) {
                    limitRange.unshift(0)
                }

                return {
                    execute: execute
                };
            };
            var count = function () {
                direction = null;
                cursorType = 'count';

                return {
                    execute: execute
                };
            };
            var keys = function () {
                cursorType = 'openKeyCursor';

                return {
                    desc: desc,
                    execute: execute,
                    filter: filter,
                    distinct: distinct,
                    map: map
                };
            };
            var filter = function ( ) {
                filters.push( Array.prototype.slice.call( arguments , 0 , 2 ) );

                return {
                    keys: keys,
                    execute: execute,
                    filter: filter,
                    desc: desc,
                    distinct: distinct,
                    modify: modify,
                    limit: limit,
                    map: map
                };
            };
            var desc = function () {
                direction = 'prev';

                return {
                    keys: keys,
                    execute: execute,
                    filter: filter,
                    distinct: distinct,
                    modify: modify,
                    map: map
                };
            };
            var distinct = function () {
                unique = true;
                return {
                    keys: keys,
                    count: count,
                    execute: execute,
                    filter: filter,
                    desc: desc,
                    modify: modify,
                    map: map
                };
            };
            var modify = function(update) {
                modifyObj = update;
                return {
                    execute: execute
                };
            };
            var map = function (fn) {
                mapper = fn;

                return {
                    execute: execute,
                    count: count,
                    keys: keys,
                    filter: filter,
                    desc: desc,
                    distinct: distinct,
                    modify: modify,
                    limit: limit,
                    map: map
                };
            };

            return {
                execute: execute,
                count: count,
                keys: keys,
                filter: filter,
                desc: desc,
                distinct: distinct,
                modify: modify,
                limit: limit,
                map: map
            };
        };

        'only bound upperBound lowerBound'.split(' ').forEach(function (name) {
            that[name] = function () {
                return new Query( name , arguments );
            };
        });

        this.filter = function () {
            var query = new Query( null , null );
            return query.filter.apply( query , arguments );
        };

        this.all = function () {
            return this.filter();
        };
    };

    var createSchema = function ( e , schema , db ) {
        if ( typeof schema === 'function' ) {
            schema = schema();
        }

        for ( var tableName in schema ) {
            var table = schema[ tableName ];
            var store;
            if (!hasOwn.call(schema, tableName) || db.objectStoreNames.contains(tableName)) {
                store = e.currentTarget.transaction.objectStore(tableName);
            } else {
                store = db.createObjectStore(tableName, table.key);
            }

            for ( var indexKey in table.indexes ) {
                var index = table.indexes[ indexKey ];
                try {
                    store.index(indexKey)
                } catch (e) {
                    store.createIndex( indexKey , index.key || indexKey , Object.keys(index).length ? index : { unique: false } );
                }
            }
        }
    };

    var open = function ( e , server , version , schema ) {
        var db = e.target.result;
        var s = new Server( db , server );
        var upgrade;

        dbCache[ server ] = db;

        return Promise.resolve(s)
    };

    var dbCache = {};

    var db = {
        version: '0.9.2',
        open: function ( options ) {
            var request;

            return new Promise(function(resolve, reject){
              if ( dbCache[ options.server ] ) {
                  open( {
                      target: {
                          result: dbCache[ options.server ]
                      }
                  } , options.server , options.version , options.schema )
                  .then(resolve, reject)
              } else {
                  request = getIndexedDB().open( options.server , options.version );

                  request.onsuccess = function ( e ) {
                      open( e , options.server , options.version , options.schema )
                          .then(resolve, reject)
                  };

                  request.onupgradeneeded = function ( e ) {
                      createSchema( e , options.schema , e.target.result );
                  };
                  request.onerror = function ( e ) {
                      reject( e );
                  };
              }
            });
        }
    };

    if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
        module.exports = db;
    } else if ( typeof define === 'function' && define.amd ) {
        define( function() { return db; } );
    } else {
        window.db = db;
    }
})( window );
<|endoftext|>


<commit_before>/*
TODO: keyboard navigation, onsumbit, look like real element with dot until confirmed sumbitted, way to end inline submit?, talk to real db
			idea: insertboxes always exist, just hidden normally, same css trickery, onclick out disappear! expand to fit for textboxes
			BUG THAT DISSAPEARS WHEN INSPECTED (or resized)! INSERT BELOW!
			idea - make insert above below list items (like before?)
			REMOVE ALL FIDDLEY NUMBERS FROM CSS - SIMPLIFY CSS WHERE POSSIBLE
SHOULD COMMENTS BE CSS TRICKS OR JS?
WHAT ABOUT COMMENT INSERTS
WAY TO UPDATE PAGE ON NEW INFO (should be good ideomatic way for this)

TODO SOON:
onsubmit -- show as part of list with Submitting in orange
remove submitting when confirmed uploaded
some way to close inserts?
hide ability to make 10 insert lis under same comment

figure out how best to store/upload/backend it up
	possibility - firebase free for hosting and db?

notify via email on comments to own submission?

*/

document.addEventListener("DOMContentLoaded", function(e) {
  createCollapsableList(); //serverside in the future
  createNavSidebar(); //serverside in the future
  createInsertableLinks(); //serverside in the future
  createHeaderPicker();
});

function createCollapsableList() {
  collapsableItems = document.getElementById("top").getElementsByTagName("LI");
  var incrementingId = 0;
  for (var i=0; i < collapsableItems.length; i++) {
    var item = collapsableItems[i];
    insertCollapser(item, incrementingId);
    incrementingId++;
  }
}

function insertCollapser(item, incrementingId) {
  var id = "id" + incrementingId;
  var checkbox = document.createElement("input");
  checkbox.type = "checkbox";
  checkbox.id = id;
  var minimize = document.createElement("label");
  minimize.htmlFor = id;
  minimize.className = "minimize";
  minimize.innerHTML = "[-]";
  var maximize = document.createElement("label");
  maximize.htmlFor = id;
  maximize.className = "maximize";
  maximize.innerHTML = "[+]";
  item.insertBefore(maximize, item.firstChild); //lowest
  item.insertBefore(minimize, item.firstChild);
  item.insertBefore(checkbox, item.firstChild); //highest
}

function createInsertableLinks() {
  var ideas = document.getElementsByClassName("idea");
  for (var j=0; j < ideas.length; j++) {
    var insertableItems = ideas[j].getElementsByTagName("LI");
    for (var i=0; i < insertableItems.length; i++) {
      var element = insertableItems[i];
      insertInsertAbove(element);
      if (!hasULChild(element)) {
        var insertBeneath = insertInsertBeneath(element);
        var insertUnder = createInsertUnder(element);
        insertBeneath.appendChild(insertUnder);
        if (!hasElementSibling(element)) {
          var insertBelow = createInsertBelow(element);
          insertBeneath.appendChild(insertBelow);
        }
      }
    }
  }
}

function hasULChild(element) {
  return element.getElementsByTagName("UL").length > 0
}

function hasElementSibling(element) {
  var next = element.nextSibling;
  var hasSibling = false;
  while(next !== null) {
    if(next.nodeType === 1) {
      hasSibling = true;
    }
    next = next.nextSibling;
  }
  return hasSibling;
}

function getItemName(element) {
  var itemName = "Comment";
  if(element.parentNode.className === "idea") {
    itemName = "Idea";
  }
  return itemName;
}

function insertInsertAbove(element) {
  var contentItem = element.getElementsByClassName("content")[0];
  var insertAbove = document.createElement("div");
  insertAbove.className = "insertAbove";
  insertAbove.addEventListener('click', function() { newInsertAbove(insertAbove) });
  var newElement = element.insertBefore(insertAbove, element.firstChild);
  return newElement;
}

function insertInsertBeneath(element) {
  var insertBeneath = document.createElement("div");
  insertBeneath.className = "insertBeneath";
  var newElement = element.appendChild(insertBeneath);
  return newElement;
}

function createInsertBelow(element) {
  var insertBelow = document.createElement("div");
  insertBelow.className = "insertBelow";
  insertBelow.addEventListener('click', function() { newInsertBelow(insertBelow) });
  return insertBelow;
}

function createInsertUnder() {
  var insertUnder = document.createElement("div");
  insertUnder.className = "insertUnder";
  insertUnder.addEventListener('click', function() { newInsertUnder(insertUnder) });
  return insertUnder;
}

function createInsertLi() {
  var newLi = document.createElement("li");
  newLi.className = "insert";
  var textBox = document.createElement("input");
  textBox.type = "text";
  textBox.className = "inlineInput";
  var submit = document.createElement("input");
  submit.type = "submit";
  submit.value = "submit";
  newLi.appendChild(textBox);
  newLi.appendChild(submit);
  return newLi;
}

//Replace ALL this with hidden elements & css trickery?

function newInsertAbove(element) {
  var contaningLi = element.parentNode;
  var newLi = createInsertLi();
  var insertedLi = contaningLi.parentNode.insertBefore(newLi, contaningLi);
  insertedLi.getElementsByTagName("input")[0].focus();
}

function newInsertBelow(element) {
  var contaningLi = element.parentNode.parentNode;
  var newLi = createInsertLi();
  contaningLi.parentNode.insertBefore(newLi, contaningLi.nextSibling);
}

function newInsertUnder(element) {
  //only on ideas/comments? TAKE ARG OF TYPE!
  var contaningLi = element.parentNode.parentNode;
  var child = document.createElement("ul");
  var ulName = contaningLi.parentNode.className;
  var className = "idea";
  if (ulName === "top") {
    className = "category";
  }
  else if (ulName === "category") {
    className = "subcategory";
  }
  else if (ulName === "subcategory") {
    className = "idea";
  }
  else if (ulName === "idea") {
    className = "comment";
  }
  else if (ulName === "comment") {
    className = "comment";
  }
  child.className = className;
  var newLi = createInsertLi();
  child.appendChild(newLi);
  contaningLi.appendChild(child);
}

function createHeaderPicker() {
  var button = document.getElementById("header-submit");
  button.onclick = function() { toggle('category-picker', 'flex') };
  button.value = "+";
  button.type = "button";
  button.id = "header-button";
  var submit = document.getElementById("unused-button");
  submit.id = "header-submit"
  var inputs = document.getElementById("many-inputs");
  inputs = setupManyInputs(inputs);
  //appendChild inputs
}

function setupManyInputs(element) {
  addSelectItem(document.getElementById("top"), 0);
}

function updateManyInputs(changedNumber) {
	var noBlanks = true;
	var selectedIndexes = [];
	var manyInputs = document.getElementById("many-inputs");
	var selects = manyInputs.getElementsByTagName("select");
	for (var i = 0; i <= changedNumber; i++) {
		select = selects[i];
		index = select.selectedIndex;
		if (index === 0) {
			noBlanks = false;
		}
		else if (noBlanks) {
			selectedIndexes.push(index);
		}
	}
	while(manyInputs.firstChild) {
		manyInputs.removeChild(manyInputs.firstChild);
	}
	var lastUl = document.getElementById("top");
	for (var i=0; i <= changedNumber; i++) {
		index = selectedIndexes[i];
		addSelectItem(lastUl, i, index);
		if (lastUl.children.item(index-1).getElementsByTagName("UL").length > 0) {
			lastUl = lastUl.children.item(index-1).getElementsByTagName("UL")[0];
			if(i === selectedIndexes.length - 1) {
				addSelectItem(lastUl, i+1);
			}
		}
	}
}

function addSelectItem(ul, index, selectedIndex = -1) {
  var select = document.createElement("select");
  var blank = document.createElement("option");
  blank.selected = true;
  select.appendChild(blank);
  var lis = ul.children;
  for(var i=0; i < lis.length; i++) {
    var liText = lis[i].getElementsByClassName("content")[0].childNodes[0].nodeValue;
    var option = document.createElement("option");
    option.innerHTML = liText;
    select.appendChild(option);
    //option.value = UNIQUEID <- must have!
	//<option disabled selected value> -- select an option -- </option>
  }
  if (selectedIndex >= 0) {
	select.selectedIndex = selectedIndex;
  }
  console.log(index);
  select.onchange = function(j) { return function() { updateManyInputs(j); }; }(index); //MAGIC! look into how clojures work
  document.getElementById("many-inputs").appendChild(select);
}

function toggle(id, display) {
  var element = document.getElementById(id);
  var main = document.getElementById("main");
  var button = document.getElementById("header-button");
  if(element.style.display === "none" || element.style.display === "") {
    element.style.display = display;
    main.style.marginTop = "7.1rem";
    button.value = "-";
  }
  else {
    element.style.display = "none";
    main.style.marginTop = "5rem";
    button.value = "+";
  }
}

function insertIntoList() {

}

function createNavSidebar() {

}
<commit_msg>Revert "works now and about to try something weird"<commit_after>/*
TODO: keyboard navigation, onsumbit, look like real element with dot until confirmed sumbitted, way to end inline submit?, talk to real db
			idea: insertboxes always exist, just hidden normally, same css trickery, onclick out disappear! expand to fit for textboxes
			BUG THAT DISSAPEARS WHEN INSPECTED (or resized)! INSERT BELOW!
			idea - make insert above below list items (like before?)
			REMOVE ALL FIDDLEY NUMBERS FROM CSS - SIMPLIFY CSS WHERE POSSIBLE
SHOULD COMMENTS BE CSS TRICKS OR JS?
WHAT ABOUT COMMENT INSERTS
WAY TO UPDATE PAGE ON NEW INFO (should be good ideomatic way for this)

TODO SOON:
onsubmit -- show uplading icon (orange)
then once its sucessful show as part of list
get top menu figured out, howevers best idk yet
figure out how best to store/upload/backend it up
	possibility - firebase free for hosting and db?

notify via email on comments to own submission?

*/

document.addEventListener("DOMContentLoaded", function(e) {
  createCollapsableList(); //serverside in the future
  createNavSidebar(); //serverside in the future
  createInsertableLinks(); //serverside in the future
  createHeaderPicker();
});

function createCollapsableList() {
  collapsableItems = document.getElementById("top").getElementsByTagName("LI");
  var incrementingId = 0;
  for (var i=0; i < collapsableItems.length; i++) {
    var item = collapsableItems[i];
    insertCollapser(item, incrementingId);
    incrementingId++;
  }
}

function insertCollapser(item, incrementingId) {
  var id = "id" + incrementingId;
  var checkbox = document.createElement("input");
  checkbox.type = "checkbox";
  checkbox.id = id;
  var minimize = document.createElement("label");
  minimize.htmlFor = id;
  minimize.className = "minimize";
  minimize.innerHTML = "[-]";
  var maximize = document.createElement("label");
  maximize.htmlFor = id;
  maximize.className = "maximize";
  maximize.innerHTML = "[+]";
  item.insertBefore(maximize, item.firstChild); //lowest
  item.insertBefore(minimize, item.firstChild);
  item.insertBefore(checkbox, item.firstChild); //highest
}

function createInsertableLinks() {
  var ideas = document.getElementsByClassName("idea");
  for (var j=0; j < ideas.length; j++) {
    var insertableItems = ideas[j].getElementsByTagName("LI");
    for (var i=0; i < insertableItems.length; i++) {
      var element = insertableItems[i];
      insertInsertAbove(element);
      if (!hasULChild(element)) {
        var insertBeneath = insertInsertBeneath(element);
        var insertUnder = createInsertUnder(element);
        insertBeneath.appendChild(insertUnder);
        if (!hasElementSibling(element)) {
          var insertBelow = createInsertBelow(element);
          insertBeneath.appendChild(insertBelow);
        }
      }
    }
  }
}

function hasULChild(element) {
  return element.getElementsByTagName("UL").length > 0
}

function hasElementSibling(element) {
  var next = element.nextSibling;
  var hasSibling = false;
  while(next !== null) {
    if(next.nodeType === 1) {
      hasSibling = true;
    }
    next = next.nextSibling;
  }
  return hasSibling;
}

function getItemName(element) {
  var itemName = "Comment";
  if(element.parentNode.className === "idea") {
    itemName = "Idea";
  }
  return itemName;
}

function insertInsertAbove(element) {
  var contentItem = element.getElementsByClassName("content")[0];
  var insertAbove = document.createElement("div");
  insertAbove.className = "insertAbove";
  insertAbove.addEventListener('click', function() { newInsertAbove(insertAbove) });
  var newElement = element.insertBefore(insertAbove, element.firstChild);
  return newElement;
}

function insertInsertBeneath(element) {
  var insertBeneath = document.createElement("div");
  insertBeneath.className = "insertBeneath";
  var newElement = element.appendChild(insertBeneath);
  return newElement;
}

function createInsertBelow(element) {
  var insertBelow = document.createElement("div");
  insertBelow.className = "insertBelow";
  insertBelow.addEventListener('click', function() { newInsertBelow(insertBelow) });
  return insertBelow;
}

function createInsertUnder() {
  var insertUnder = document.createElement("div");
  insertUnder.className = "insertUnder";
  insertUnder.addEventListener('click', function() { newInsertUnder(insertUnder) });
  return insertUnder;
}

function createInsertLi() {
  var newLi = document.createElement("li");
  newLi.className = "insert";
  var textBox = document.createElement("input");
  textBox.type = "text";
  textBox.className = "inlineInput";
  var submit = document.createElement("input");
  submit.type = "submit";
  submit.value = "submit";
  newLi.appendChild(textBox);
  newLi.appendChild(submit);
  return newLi;
}

//Replace ALL this with hidden elements & css trickery?

function newInsertAbove(element) {
  var contaningLi = element.parentNode;
  var newLi = createInsertLi();
  var insertedLi = contaningLi.parentNode.insertBefore(newLi, contaningLi);
  insertedLi.getElementsByTagName("input")[0].focus();
}

function newInsertBelow(element) {
  var contaningLi = element.parentNode.parentNode;
  var newLi = createInsertLi();
  contaningLi.parentNode.insertBefore(newLi, contaningLi.nextSibling);
}

function newInsertUnder(element) {
  //only on ideas/comments? TAKE ARG OF TYPE!
  var contaningLi = element.parentNode.parentNode;
  var child = document.createElement("ul");
  var ulName = contaningLi.parentNode.className;
  var className = "idea";
  if (ulName === "top") {
    className = "category";
  }
  else if (ulName === "category") {
    className = "subcategory";
  }
  else if (ulName === "subcategory") {
    className = "idea";
  }
  else if (ulName === "idea") {
    className = "comment";
  }
  else if (ulName === "comment") {
    className = "comment";
  }
  child.className = className;
  var newLi = createInsertLi();
  child.appendChild(newLi);
  contaningLi.appendChild(child);
}

function createHeaderPicker() {
  var button = document.getElementById("header-submit");
  button.onclick = function() { toggle('category-picker', 'flex') };
  button.value = "+";
  button.type = "button";
  button.id = "header-button";
  var submit = document.getElementById("unused-button");
  submit.id = "header-submit"
  var inputs = document.getElementById("many-inputs");
  inputs = setupManyInputs(inputs);
  //appendChild inputs
}

function setupManyInputs(element) {
  addSelectItem(document.getElementById("top"));
}

function updateManyInputs() {
	//if first one changes - all rest should be blanked!!!
	var noBlanks = true;
	var selectedIndexes = [];
	var manyInputs = document.getElementById("many-inputs");
	var selects = manyInputs.getElementsByTagName("select");
	for (var i = 0; i < selects.length; i++) {
		select = selects[i];
		index = select.selectedIndex;
		if (index === 0) {
			noBlanks = false;
		}
		else if (noBlanks) {
			selectedIndexes.push(index);
		}
	}
	while(manyInputs.firstChild) {
		manyInputs.removeChild(manyInputs.firstChild);
	}
	var lastUl = document.getElementById("top");
	for (var i=0; i < selectedIndexes.length; i++) {
		index = selectedIndexes[i];
		addSelectItem(lastUl, index);
		if (lastUl.children.item(index-1).getElementsByTagName("UL").length > 0) {
			lastUl = lastUl.children.item(index-1).getElementsByTagName("UL")[0];
			if(i === selectedIndexes.length - 1) {
				addSelectItem(lastUl);
			}
		}
	}
}

function addSelectItem(ul, selectedIndex = -1) {
  var select = document.createElement("select");
  var blank = document.createElement("option");
  blank.selected = true;
  select.appendChild(blank);
  var lis = ul.children;
  for(var i=0; i < lis.length; i++) {
    var liText = lis[i].getElementsByClassName("content")[0].childNodes[0].nodeValue;
    var option = document.createElement("option");
    option.innerHTML = liText;
    select.appendChild(option);
    //option.value = UNIQUEID <- must have!
	//<option disabled selected value> -- select an option -- </option>
  }
  if (selectedIndex >= 0) {
	select.selectedIndex = selectedIndex;
  }
  select.addEventListener('change', function() { updateManyInputs() });
  document.getElementById("many-inputs").appendChild(select);
}

function toggle(id, display) {
  var element = document.getElementById(id);
  var main = document.getElementById("main");
  var button = document.getElementById("header-button");
  if(element.style.display === "none" || element.style.display === "") {
    element.style.display = display;
    main.style.marginTop = "7.1rem";
    button.value = "-";
  }
  else {
    element.style.display = "none";
    main.style.marginTop = "5rem";
    button.value = "+";
  }
}

function insertIntoList() {

}

function createNavSidebar() {

}
<|endoftext|>