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

	angular.module('accessibleAutocomplete', [])
		.provider('accessibleAutocomplete', accessibleAutocompleteProvider)
		.factory('accessibleSerialTimeout', accessibleSerialTimeout)
		.factory('accessibleSerialPromise', accessibleSerialPromise)
		.service('accessibleAutocompleteKeyProcessor', accessibleAutocompleteKeyProcessor)
		.controller('accessibleAutocompleteController', accessibleAutocompleteController)
		.directive('accessibleAutocomplete', accessibleAutocompleteDirective);

	function accessibleAutocompleteProvider() {
		var defaultOptions = {
			template: '
 ' +
				'

' +
				'
',
			templateUrl: null,
			minimumLength: 3,
			delay: 100
		};

		this.setDefaultOptions = function(options) {
			angular.extend(defaultOptions, options);
		};

		var provider = this;

		this.$get = function() {
			return defaultOptions;
		};
	}

	function accessibleAutocompleteDirective($http, $compile, $templateCache, $q, accessibleAutocomplete, accessibleAutocompleteKeyProcessor) {

		var stripHttpData = function(resp) {
			return resp.data;
		};

		return {
			restrict: 'A',
			scope: {
				selectedHandler: '&accessibleAutocompleteSelected',
				suggestionsSelector: '&accessibleAutocomplete',
				templateUrl: '@accessibleAutocompleteTemplateUrl',
				minimumLength: '&accessibleAutocompleteMinimumLength'
			},
			link: function(scope, element, attrs) {
				// TODO: Apply aria attributes if missing

				var ariaAutocomplete = element.attr('aria-autocomplete'),
					ariaRole = element.attr('role');

				if (!ariaAutocomplete) {
					ariaAutocomplete = (ariaRole == 'textbox') ? 'inline' : 'list';
					element.attr('aria-autocomplete', ariaAutocomplete);
				}

				if (!attrs.role) {
					ariaRole = (ariaAutocomplete == 'list' || ariaAutocomplete == 'both')
						? 'combobox' : 'textbox';

					element.attr('role', ariaRole);
				}

				// TODO: Accessible 'activation'
				element.on('keydown', function(event) {
					scope.$apply(function() {
						if (accessibleAutocompleteKeyProcessor.isEscape(event)) {
							scope.hideSuggestions();
							scope.setSelectionSuggestionIndex(-1);
							event.preventDefault();
						} else if (accessibleAutocompleteKeyProcessor.isDown(event)) {
							scope.moveSelectionSuggestionIndex(1);
							event.preventDefault();
						} else if (accessibleAutocompleteKeyProcessor.isUp(event)) {
							scope.moveSelectionSuggestionIndex(-1);
							event.preventDefault();
						} else if (accessibleAutocompleteKeyProcessor.isTab(event)) {
							if (scope.showSuggestions && scope.selectedSuggestionIndex != -1) {
								scope.selectSuggestion(scope.selectedSuggestion);

								event.preventDefault();
							}
						} else if (accessibleAutocompleteKeyProcessor.isEnter(event)) {
							if (scope.showSuggestions) {
								scope.selectSuggestion(scope.selectedSuggestion);

								event.preventDefault();
							}
						}
					});
				});

				element.on('keyup input', function() {
					scope.$apply(function() {
						if (element.val() != scope.textInput + scope.textInputSuffix)
							scope.textChanged(element.val());
					});
				});

				if (ariaAutocomplete == 'list' || ariaAutocomplete == 'both') {

					var templatePromise = scope.options.templateUrl ?
						$http.get(scope.options.templateUrl, {cache: $templateCache}).then(stripHttpData) :
						$q.when(scope.options.template);

					templatePromise.then(function(template) {
						scope.suggestionsListId = 'accessible-autocomplete-' + (new Date()).getTime();
						scope.suggestionsList = $compile(template)(scope);

						element.after(scope.suggestionsList);

						element.attr('aria-owns', scope.suggestionsListId);
					}, function(err) {
						// TODO: err
						throw err;
					});
				}

				scope.$watch('selectedSuggestionIndex', function(newValue) {
					if (newValue == -1) {
						element.attr('aria-activedescendant', null);
					} else {
						element.attr('aria-activedescendant', scope.getSuggestionId(newValue));
					}
				});

				scope.$watch('isLoadingSuggestions', function(newValue) {
					element.attr('aria-busy', newValue === true);
				});

				scope.$watch(function() {
					return '['+scope.textInput+']['+scope.textInputSuffix+']';
				}, function(newValue, oldValue) {
					if (typeof newValue == 'undefined')
						return;

					if (ariaAutocomplete == 'inline' || ariaAutocomplete == 'both') {
						if (scope.textInput + scope.textInputSuffix != element.val()) {
							element.val(scope.textInput + scope.textInputSuffix);
						}
						element[0].setSelectionRange(scope.textInput.length, scope.textInput.length + scope.textInputSuffix.length);
					} else {
						if (scope.textInput != element.val())
							element.val(scope.textInput);
					}
				});
			},
			unlink: function(scope, element) {
				// TODO: Less conflict-prone
				element.off('keydown');
				element.off('keyup');

				scope.suggestionsList.remove();
				scope.suggestionsList = null;
			},
			controller: accessibleAutocompleteController
		};
	}

	function accessibleAutocompleteKeyProcessor() {
		return {
			isEscape: function(event) {
				return event.keyCode === 27;
			},
			isDown: function(event) {
				return event.keyCode === 40;
			},
			isUp: function(event) {
				return event.keyCode == 38;
			},
			isEnter: function(event) {
				return event.keyCode == 13;
			},
			isTab: function(event) {
				return event.keyCode == 9;
			}
		};
	}

	function accessibleAutocompleteController($scope, accessibleSerialTimeout, accessibleSerialPromise, accessibleAutocomplete) {
		$scope.options = angular.extend({}, accessibleAutocomplete);

		$scope.options.templateUrl = $scope.templateUrl || $scope.options.templateUrl;

		$scope.options.minimumLength = $scope.minimumLength ? $scope.minimumLength() : $scope.options.minimumLength;

		$scope.textInput = '';
		$scope.textInputSuffix = '';

		$scope.suggestions = [];
		$scope.showSuggestions = false;
		$scope.selectedSuggestion = null;
		$scope.selectedSuggestionIndex = -1;

		// TODO: defaultOptions via provider

		var serialTimeout = accessibleSerialTimeout();
		var serialPromise = accessibleSerialPromise();

		if (typeof $scope.suggestionsSelector != 'function')
			$scope.suggestionsSelector = function() { return []; };
		else
			$scope.suggestionsSelector = $scope.suggestionsSelector(); // TODO: ?

		$scope.hideSuggestions = function() {
			$scope.showSuggestions = false;
		};

		$scope.setSelectionSuggestionIndex = function(index, setSuffix) {
			$scope.selectedSuggestionIndex = index;
			$scope.selectedSuggestion = $scope.suggestions[index];

			if (setSuffix) {
				calculateTextInputSuffix();
			}
		}

		$scope.moveSelectionSuggestionIndex = function(delta) {
			if (!$scope.showSuggestions) {
				if ($scope.suggestions.length > 0) {
					$scope.showSuggestions = true;
				}

				return;
			}

			var newIndex = loopIndex($scope.selectedSuggestionIndex + delta, $scope.suggestions.length);

			$scope.setSelectionSuggestionIndex(newIndex);
		};

		function loopIndex(newIndex, length) {
			if (newIndex < 0)
				return length - 1;
			else if (newIndex >= length)
				return 0;
			else
				return newIndex;
		}

		function replaceSuggestions(suggestions) {
			$scope.suggestions = suggestions;
			$scope.selectedSuggestion = null;
			$scope.selectedSuggestionIndex = -1;
			$scope.showSuggestions = (suggestions.length > 0);
		}

		// TODO: Need to differentiate between 'committing' a selection and simply moving up/down
		$scope.selectSuggestion = function(suggestion) {
			$scope.selectedSuggestion = suggestion;
			$scope.selectedSuggestionIndex = $scope.suggestions.indexOf(suggestion);
			$scope.showSuggestions = false;
			$scope.textInputSuffix = '';

			var handler = $scope.selectedHandler();

			if ($scope.selectedSuggestionIndex != -1) {
				$scope.textInput = $scope.formatSuggestion(suggestion);
				if (typeof handler == 'function') {
					handler($scope.selectedSuggestion, $scope.selectedSuggestionIndex);
				}
			} else {
				handler($scope.textInput, -1);
			}
		}

		$scope.isInlineCompletionEnabled = function() {
			return ariaAutocomplete == 'inline' || ariaAutocomplete == 'both';
		}

		$scope.textChanged = function(newText) {
			if ($scope.selectedSuggestion && newText == $scope.formatSuggestion($scope.selectedSuggestion))

			if ($scope.selectedSuggestion && $scope.isInlineCompletionEnabled() && newText == $scope.textInput + $scope.textInputSuffix)
				return;

			if (newText == $scope.textInput)
				return;

			var isContinuation = ($scope.textInput.length && newText.indexOf($scope.textInput) == 0);

			$scope.textInput = newText;

			if (isContinuation) {
				calculateTextInputSuffix();
			} else {
				$scope.textInputSuffix = '';
			}

			var normalizedInput = newText||'';

			if ($scope.options.minimumLength && normalizedInput.length < $scope.options.minimumLength)
				return;

			serialTimeout(null, $scope.options.delay)
				.then(function() {
					$scope.isLoadingSuggestions = true;

					if (typeof $scope.suggestionsSelector == 'function')
						return $scope.suggestionsSelector(normalizedInput);

					return $scope.suggestionsSelector;
				})
				.then(function(data) {
					$scope.isLoadingSuggestions = false;

					// TODO: Data formats
					$scope.suggestions = data;
					$scope.isLoadingSuggestions = false;
					$scope.showSuggestions = true;

					if ($scope.suggestions.length == 0) {
						$scope.setSelectionSuggestionIndex(-1);
					} else {
						var newIndex = $scope.suggestions.indexOf($scope.selectedSuggestion);

						if (newIndex == -1)
							newIndex = 0;

						$scope.setSelectionSuggestionIndex(newIndex, isContinuation);
					}

				}, function(err) {
					$scope.isLoadingSuggestions = false;
					if (err === 'cancelled')
						return;

					// TODO: Handle errors
				});
		};

		$scope.formatSuggestion = function(suggestion) {
			return (suggestion||{}).toString();
		}

		$scope.$watch('selectedSuggestion', function(newValue) {
			calculateTextInputSuffix(newValue);
		});

		function calculateTextInputSuffix() {
			if ($scope.selectedSuggestionIndex == -1) {
				$scope.textInputSuffix = '';
				return;
			}

			var formattedSuggestion = $scope.formatSuggestion($scope.selectedSuggestion);

			if (formattedSuggestion.indexOf($scope.textInput) == 0) {
				$scope.textInputSuffix = formattedSuggestion.substr($scope.textInput.length);
			}
		}

		$scope.getSuggestionId = function(index) {
			return $scope.suggestionsListId + '-' + index;
		};
	}

	function accessibleSerialTimeout($timeout) {
		return function() {
			var currentTimeout = null;

			return function(callback, delay) {
				if (currentTimeout != null) {
					$timeout.cancel(currentTimeout);
				}

				return (currentTimeout = $timeout(function(){}, delay));
			}
		}
	}

	function accessibleSerialPromise($q) {
		return function() {
			var latestDefer = null;

			return function(promise) {
				latestDefer = $q.defer();

				return (function(thisDefer) {
					return promise.then(function(data) {
						if (thisDefer === latestDefer) {
							return data;
						}

						return $q.reject('cancelled');
					});
				})(latestDefer);
			}
		};
	}
})();<commit_msg>Added a feature test around setSelectionRange as it was causing errors on IE8<commit_after>(function() {
	'use strict';

	angular.module('accessibleAutocomplete', [])
		.provider('accessibleAutocomplete', accessibleAutocompleteProvider)
		.factory('accessibleSerialTimeout', accessibleSerialTimeout)
		.factory('accessibleSerialPromise', accessibleSerialPromise)
		.service('accessibleAutocompleteKeyProcessor', accessibleAutocompleteKeyProcessor)
		.controller('accessibleAutocompleteController', accessibleAutocompleteController)
		.directive('accessibleAutocomplete', accessibleAutocompleteDirective);

	function accessibleAutocompleteProvider() {
		var defaultOptions = {
			template: '
 ' +
				'

' +
				'
',
			templateUrl: null,
			minimumLength: 3,
			delay: 100
		};

		this.setDefaultOptions = function(options) {
			angular.extend(defaultOptions, options);
		};

		var provider = this;

		this.$get = function() {
			return defaultOptions;
		};
	}

	function accessibleAutocompleteDirective($http, $compile, $templateCache, $q, accessibleAutocomplete, accessibleAutocompleteKeyProcessor) {

		var stripHttpData = function(resp) {
			return resp.data;
		};

		return {
			restrict: 'A',
			scope: {
				selectedHandler: '&accessibleAutocompleteSelected',
				suggestionsSelector: '&accessibleAutocomplete',
				templateUrl: '@accessibleAutocompleteTemplateUrl',
				minimumLength: '&accessibleAutocompleteMinimumLength'
			},
			link: function(scope, element, attrs) {
				// TODO: Apply aria attributes if missing

				var ariaAutocomplete = element.attr('aria-autocomplete'),
					ariaRole = element.attr('role');

				if (!ariaAutocomplete) {
					ariaAutocomplete = (ariaRole == 'textbox') ? 'inline' : 'list';
					element.attr('aria-autocomplete', ariaAutocomplete);
				}

				if (!attrs.role) {
					ariaRole = (ariaAutocomplete == 'list' || ariaAutocomplete == 'both')
						? 'combobox' : 'textbox';

					element.attr('role', ariaRole);
				}

				// TODO: Accessible 'activation'
				element.on('keydown', function(event) {
					scope.$apply(function() {
						if (accessibleAutocompleteKeyProcessor.isEscape(event)) {
							scope.hideSuggestions();
							scope.setSelectionSuggestionIndex(-1);
							event.preventDefault();
						} else if (accessibleAutocompleteKeyProcessor.isDown(event)) {
							scope.moveSelectionSuggestionIndex(1);
							event.preventDefault();
						} else if (accessibleAutocompleteKeyProcessor.isUp(event)) {
							scope.moveSelectionSuggestionIndex(-1);
							event.preventDefault();
						} else if (accessibleAutocompleteKeyProcessor.isTab(event)) {
							if (scope.showSuggestions && scope.selectedSuggestionIndex != -1) {
								scope.selectSuggestion(scope.selectedSuggestion);

								event.preventDefault();
							}
						} else if (accessibleAutocompleteKeyProcessor.isEnter(event)) {
							if (scope.showSuggestions) {
								scope.selectSuggestion(scope.selectedSuggestion);

								event.preventDefault();
							}
						}
					});
				});

				element.on('keyup input', function() {
					scope.$apply(function() {
						if (element.val() != scope.textInput + scope.textInputSuffix)
							scope.textChanged(element.val());
					});
				});

				if (ariaAutocomplete == 'list' || ariaAutocomplete == 'both') {

					var templatePromise = scope.options.templateUrl ?
						$http.get(scope.options.templateUrl, {cache: $templateCache}).then(stripHttpData) :
						$q.when(scope.options.template);

					templatePromise.then(function(template) {
						scope.suggestionsListId = 'accessible-autocomplete-' + (new Date()).getTime();
						scope.suggestionsList = $compile(template)(scope);

						element.after(scope.suggestionsList);

						element.attr('aria-owns', scope.suggestionsListId);
					}, function(err) {
						// TODO: err
						throw err;
					});
				}

				scope.$watch('selectedSuggestionIndex', function(newValue) {
					if (newValue == -1) {
						element.attr('aria-activedescendant', null);
					} else {
						element.attr('aria-activedescendant', scope.getSuggestionId(newValue));
					}
				});

				scope.$watch('isLoadingSuggestions', function(newValue) {
					element.attr('aria-busy', newValue === true);
				});

				scope.$watch(function() {
					return '['+scope.textInput+']['+scope.textInputSuffix+']';
				}, function(newValue, oldValue) {
					if (typeof newValue == 'undefined')
						return;

					if (ariaAutocomplete == 'inline' || ariaAutocomplete == 'both') {
						if (scope.textInput + scope.textInputSuffix != element.val()) {
							element.val(scope.textInput + scope.textInputSuffix);
						}

						if (element[0].setSelectionRange) {
							element[0].setSelectionRange(scope.textInput.length, scope.textInput.length + scope.textInputSuffix.length);
						}
					} else {
						if (scope.textInput != element.val())
							element.val(scope.textInput);
					}
				});
			},
			unlink: function(scope, element) {
				// TODO: Less conflict-prone
				element.off('keydown');
				element.off('keyup');

				scope.suggestionsList.remove();
				scope.suggestionsList = null;
			},
			controller: accessibleAutocompleteController
		};
	}

	function accessibleAutocompleteKeyProcessor() {
		return {
			isEscape: function(event) {
				return event.keyCode === 27;
			},
			isDown: function(event) {
				return event.keyCode === 40;
			},
			isUp: function(event) {
				return event.keyCode == 38;
			},
			isEnter: function(event) {
				return event.keyCode == 13;
			},
			isTab: function(event) {
				return event.keyCode == 9;
			}
		};
	}

	function accessibleAutocompleteController($scope, accessibleSerialTimeout, accessibleSerialPromise, accessibleAutocomplete) {
		$scope.options = angular.extend({}, accessibleAutocomplete);

		$scope.options.templateUrl = $scope.templateUrl || $scope.options.templateUrl;

		$scope.options.minimumLength = $scope.minimumLength ? $scope.minimumLength() : $scope.options.minimumLength;

		$scope.textInput = '';
		$scope.textInputSuffix = '';

		$scope.suggestions = [];
		$scope.showSuggestions = false;
		$scope.selectedSuggestion = null;
		$scope.selectedSuggestionIndex = -1;

		// TODO: defaultOptions via provider

		var serialTimeout = accessibleSerialTimeout();
		var serialPromise = accessibleSerialPromise();

		if (typeof $scope.suggestionsSelector != 'function')
			$scope.suggestionsSelector = function() { return []; };
		else
			$scope.suggestionsSelector = $scope.suggestionsSelector(); // TODO: ?

		$scope.hideSuggestions = function() {
			$scope.showSuggestions = false;
		};

		$scope.setSelectionSuggestionIndex = function(index, setSuffix) {
			$scope.selectedSuggestionIndex = index;
			$scope.selectedSuggestion = $scope.suggestions[index];

			if (setSuffix) {
				calculateTextInputSuffix();
			}
		}

		$scope.moveSelectionSuggestionIndex = function(delta) {
			if (!$scope.showSuggestions) {
				if ($scope.suggestions.length > 0) {
					$scope.showSuggestions = true;
				}

				return;
			}

			var newIndex = loopIndex($scope.selectedSuggestionIndex + delta, $scope.suggestions.length);

			$scope.setSelectionSuggestionIndex(newIndex);
		};

		function loopIndex(newIndex, length) {
			if (newIndex < 0)
				return length - 1;
			else if (newIndex >= length)
				return 0;
			else
				return newIndex;
		}

		function replaceSuggestions(suggestions) {
			$scope.suggestions = suggestions;
			$scope.selectedSuggestion = null;
			$scope.selectedSuggestionIndex = -1;
			$scope.showSuggestions = (suggestions.length > 0);
		}

		// TODO: Need to differentiate between 'committing' a selection and simply moving up/down
		$scope.selectSuggestion = function(suggestion) {
			$scope.selectedSuggestion = suggestion;
			$scope.selectedSuggestionIndex = $scope.suggestions.indexOf(suggestion);
			$scope.showSuggestions = false;
			$scope.textInputSuffix = '';

			var handler = $scope.selectedHandler();

			if ($scope.selectedSuggestionIndex != -1) {
				$scope.textInput = $scope.formatSuggestion(suggestion);
				if (typeof handler == 'function') {
					handler($scope.selectedSuggestion, $scope.selectedSuggestionIndex);
				}
			} else {
				handler($scope.textInput, -1);
			}
		}

		$scope.isInlineCompletionEnabled = function() {
			return ariaAutocomplete == 'inline' || ariaAutocomplete == 'both';
		}

		$scope.textChanged = function(newText) {
			if ($scope.selectedSuggestion && newText == $scope.formatSuggestion($scope.selectedSuggestion))

			if ($scope.selectedSuggestion && $scope.isInlineCompletionEnabled() && newText == $scope.textInput + $scope.textInputSuffix)
				return;

			if (newText == $scope.textInput)
				return;

			var isContinuation = ($scope.textInput.length && newText.indexOf($scope.textInput) == 0);

			$scope.textInput = newText;

			if (isContinuation) {
				calculateTextInputSuffix();
			} else {
				$scope.textInputSuffix = '';
			}

			var normalizedInput = newText||'';

			if ($scope.options.minimumLength && normalizedInput.length < $scope.options.minimumLength)
				return;

			serialTimeout(null, $scope.options.delay)
				.then(function() {
					$scope.isLoadingSuggestions = true;

					if (typeof $scope.suggestionsSelector == 'function')
						return $scope.suggestionsSelector(normalizedInput);

					return $scope.suggestionsSelector;
				})
				.then(function(data) {
					$scope.isLoadingSuggestions = false;

					// TODO: Data formats
					$scope.suggestions = data;
					$scope.isLoadingSuggestions = false;
					$scope.showSuggestions = true;

					if ($scope.suggestions.length == 0) {
						$scope.setSelectionSuggestionIndex(-1);
					} else {
						var newIndex = $scope.suggestions.indexOf($scope.selectedSuggestion);

						if (newIndex == -1)
							newIndex = 0;

						$scope.setSelectionSuggestionIndex(newIndex, isContinuation);
					}

				}, function(err) {
					$scope.isLoadingSuggestions = false;
					if (err === 'cancelled')
						return;

					// TODO: Handle errors
				});
		};

		$scope.formatSuggestion = function(suggestion) {
			return (suggestion||{}).toString();
		}

		$scope.$watch('selectedSuggestion', function(newValue) {
			calculateTextInputSuffix(newValue);
		});

		function calculateTextInputSuffix() {
			if ($scope.selectedSuggestionIndex == -1) {
				$scope.textInputSuffix = '';
				return;
			}

			var formattedSuggestion = $scope.formatSuggestion($scope.selectedSuggestion);

			if (formattedSuggestion.indexOf($scope.textInput) == 0) {
				$scope.textInputSuffix = formattedSuggestion.substr($scope.textInput.length);
			}
		}

		$scope.getSuggestionId = function(index) {
			return $scope.suggestionsListId + '-' + index;
		};
	}

	function accessibleSerialTimeout($timeout) {
		return function() {
			var currentTimeout = null;

			return function(callback, delay) {
				if (currentTimeout != null) {
					$timeout.cancel(currentTimeout);
				}

				return (currentTimeout = $timeout(function(){}, delay));
			}
		}
	}

	function accessibleSerialPromise($q) {
		return function() {
			var latestDefer = null;

			return function(promise) {
				latestDefer = $q.defer();

				return (function(thisDefer) {
					return promise.then(function(data) {
						if (thisDefer === latestDefer) {
							return data;
						}

						return $q.reject('cancelled');
					});
				})(latestDefer);
			}
		};
	}
})();<|endoftext|>


<commit_before>'use strict';

var consts = require('../../constants');

function configure (app, wares, ctx) {
  var express = require('express'),
    api = express.Router( );

  // invoke common middleware
  api.use(wares.sendJSONStatus);
  // text body types get handled as raw buffer stream
  api.use(wares.bodyParser.raw( ));
  // json body types get handled as parsed json
  api.use(wares.bodyParser.json( ));
  // also support url-encoded content-type
  api.use(wares.bodyParser.urlencoded({ extended: true }));

  api.use(ctx.authorization.isPermitted('api:devicestatus:read'));

  // List settings available
  api.get('/devicestatus/', function(req, res) {
    var q = req.query;
    if (!q.count) {
      q.count = 10;
    }
    ctx.devicestatus.list(q, function (err, results) {
      return res.json(results);
    });
  });

  /**
   * @function delete_records
   * Delete entries.  The query logic works the same way as find/list.  This
   * endpoint uses same search logic to remove records from the database.
  */
  function delete_records(req, res, next) {
    // bias towards model, but allow expressing a preference
    if (!req.model) {
      req.model = ctx.devicestatus;
    }
    var query = req.query;
    if (!query.count) {
      query.count = 10
    }

    console.log('Delete records with query: ', query);

    // remove using the query
    req.model.remove(query, function(err, stat) {
      if (err) {
        return next(err);
      }
      // yield some information about success of operation
      res.json(stat);
      return next();
    });
  }

  function config_authed (app, api, wares, ctx) {

    function doPost (req, res) {
      var obj = req.body;
      ctx.devicestatus.create(obj, function (err, created) {
        if (err) {
          res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
        } else {
          res.json(created);
        }
      });
    }

    api.post('/devicestatus/', ctx.authorization.isPermitted('api:devicestatus:create'), doPost);

    api.delete('/devicestatus/:_id', ctx.authorization.isPermitted('api:devicestatus:delete'), function(req, res) {
      console.log('Deleting id: ' + req.params._id);

      ctx.devicestatus.remove_id(req.params._id, function (err, removed) {
        if (err) {
          res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
        } else {
          res.json(removed);
        }
      });
    });

    // delete record that match query
    api.delete('/devicestatus/', ctx.authorization.isPermitted('api:devicestatus:delete'), function(req, res, next) {
      next();
    }, delete_records);

  }

  if (app.enabled('api') || true /*TODO: auth disabled for quick UI testing...*/) {
    config_authed(app, api, wares, ctx);
  }

  return api;
}

module.exports = configure;

<commit_msg>Cleaned up code in api/devicestatus/index.js<commit_after>'use strict';

var consts = require('../../constants');

function configure (app, wares, ctx) {
  var express = require('express'),
    api = express.Router( );

  // invoke common middleware
  api.use(wares.sendJSONStatus);
  // text body types get handled as raw buffer stream
  api.use(wares.bodyParser.raw( ));
  // json body types get handled as parsed json
  api.use(wares.bodyParser.json( ));
  // also support url-encoded content-type
  api.use(wares.bodyParser.urlencoded({ extended: true }));

  api.use(ctx.authorization.isPermitted('api:devicestatus:read'));

  // List settings available
  api.get('/devicestatus/', function(req, res) {
    var q = req.query;
    if (!q.count) {
      q.count = 10;
    }
    ctx.devicestatus.list(q, function (err, results) {
      return res.json(results);
    });
  });

  function config_authed (app, api, wares, ctx) {

    function doPost (req, res) {
      var obj = req.body;
      ctx.devicestatus.create(obj, function (err, created) {
        if (err) {
          res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
        } else {
          res.json(created);
        }
      });
    }

    api.post('/devicestatus/', ctx.authorization.isPermitted('api:devicestatus:create'), doPost);

    api.delete('/devicestatus/:_id', ctx.authorization.isPermitted('api:devicestatus:delete'), function(req, res) {
      console.log('Deleting id: ' + req.params._id);

      ctx.devicestatus.remove_id(req.params._id, function (err, removed) {
        if (err) {
          res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
        } else {
          res.json(removed);
        }
      });
    });

    // delete record that match query
    api.delete('/devicestatus/', ctx.authorization.isPermitted('api:devicestatus:delete'), function(req, res) {
      var query = req.query;
      if (!query.count) {
        query.count = 10
      }

      console.log('Delete records with query: ', query);

      // remove using the query
      ctx.devicestatus.remove(query, function(err, stat) {
        if (err) {
          res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
          console.log('Error saving treatment');
          console.log(err);
        } else {
          res.json(stat);
          console.log('devicestatus records deleted', data);
        }
      });
    });

  }

  if (app.enabled('api') || true /*TODO: auth disabled for quick UI testing...*/) {
    config_authed(app, api, wares, ctx);
  }

  return api;
}

module.exports = configure;

<|endoftext|>


<commit_before>/**
 * @author zz85 / http://www.lab4games.net/zz85/blog
 *
 * Bezier Curves formulas obtained from
 * http://en.wikipedia.org/wiki/Bézier_curve
 */

function QuadraticBezierP0( t, p ) {

	var k = 1 - t;
	return k * k * p;

}

function QuadraticBezierP1( t, p ) {

	return 2 * ( 1 - t ) * t * p;

}

function QuadraticBezierP2( t, p ) {

	return t * t * p;

}

function QuadraticBezier( t, p0, p1, p2 ) {

	return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) +
		QuadraticBezierP2( t, p2 );

}

//

function CubicBezierP0( t, p ) {

	var k = 1 - t;
	return k * k * k * p;

}

function CubicBezierP1( t, p ) {

	var k = 1 - t;
	return 3 * k * k * t * p;

}

function CubicBezierP2( t, p ) {

	return 3 * ( 1 - t ) * t * t * p;

}

function CubicBezierP3( t, p ) {

	return t * t * t * p;

}

function CubicBezier( t, p0, p1, p2, p3 ) {

	return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) +
		CubicBezierP3( t, p3 );

}

//

function TangentQuadraticBezier( t, p0, p1, p2 ) {

	return 2 * ( 1 - t ) * ( p1 - p0 ) + 2 * t * ( p2 - p1 );

}

function TangentCubicBezier( t, p0, p1, p2, p3 ) {

	return - 3 * p0 * ( 1 - t ) * ( 1 - t ) +
		3 * p1 * ( 1 - t ) * ( 1 - t ) - 6 * t * p1 * ( 1 - t ) +
		6 * t * p2 * ( 1 - t ) - 3 * t * t * p2 +
		3 * t * t * p3;

}

export { QuadraticBezier, CubicBezier, TangentQuadraticBezier, TangentCubicBezier };
<commit_msg>Bezier: Optimised TangentCubicBezier.<commit_after>/**
 * @author zz85 / http://www.lab4games.net/zz85/blog
 *
 * Bezier Curves formulas obtained from
 * http://en.wikipedia.org/wiki/Bézier_curve
 */

function QuadraticBezierP0( t, p ) {

	var k = 1 - t;
	return k * k * p;

}

function QuadraticBezierP1( t, p ) {

	return 2 * ( 1 - t ) * t * p;

}

function QuadraticBezierP2( t, p ) {

	return t * t * p;

}

function QuadraticBezier( t, p0, p1, p2 ) {

	return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) +
		QuadraticBezierP2( t, p2 );

}

//

function CubicBezierP0( t, p ) {

	var k = 1 - t;
	return k * k * k * p;

}

function CubicBezierP1( t, p ) {

	var k = 1 - t;
	return 3 * k * k * t * p;

}

function CubicBezierP2( t, p ) {

	return 3 * ( 1 - t ) * t * t * p;

}

function CubicBezierP3( t, p ) {

	return t * t * t * p;

}

function CubicBezier( t, p0, p1, p2, p3 ) {

	return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) +
		CubicBezierP3( t, p3 );

}

//

function TangentQuadraticBezier( t, p0, p1, p2 ) {

	return 2 * ( 1 - t ) * ( p1 - p0 ) + 2 * t * ( p2 - p1 );

}

function TangentCubicBezier( t, p0, p1, p2, p3 ) {

	var k = 1 - t;
	return - 3 * p0 * k * k + 3 * p1 * k * k - 6 * t * p1 * k +
		6 * t * p2 * k - 3 * t * t * p2 + 3 * t * t * p3;

}

export { QuadraticBezier, CubicBezier, TangentQuadraticBezier, TangentCubicBezier };
<|endoftext|>


<commit_before>function savePersonFields (node) {
    var base = node.id.split('-')[0];
    var personIdNode = document.getElementById(base + '-name-id');
    var personID = personIdNode.value;

    var fields = {};
    var data = {};
    forField(node,function(field){
        // Save individual fields here
        var fieldName = field.id.split('-')[1];
        fields[fieldName] = field.value;
        cache[field.id] = field.value;
        showSave(field);
        var colname = field.id.split('-')[1];
        data[colname] = field.value;
    });

    // API call
    var adminID = getParameterByName('admin');
    var pageName = getParameterByName('page');
    if (!pageName) {
        pageName = 'top';
    }
    var row = apiRequest(
        '/?admin='
            + adminID
            + '&page=' + pageName
            + '&cmd=savetopersons'
        , {
            data:data,
            personid:personID,
            touchdate:pageDate
        }
    );
    if (false === row) return;
    // Refresh personID
    personIdNode.value = row.personID;
};

function startingUpload (ev) {
    status.uploadID = ev.id;
    var uploadButton = document.getElementById('uploader-attachment-button');
    var form = document.getElementById('uploader');
    // XXX How to acquire the adminID?
    form.action = '?admin=iw9acthc&cmd=upload';
    var uploadPagedate = document.getElementById('uploader-attachment-pagedate');
    uploadPagedate.value = pageDate;
    var uploadExtension = document.getElementById('uploader-attachment-extension');
    uploadExtension.value = '';
    var uploadMimeType = document.getElementById('uploader-attachment-mimetype');
    uploadMimeType.value = 'application/octet-stream';
    var uploadFilename = document.getElementById('uploader-attachment-filename');
    var fileName = uploadFilename.files[0].name;
    var m = fileName.match(/.*\.([a-zA-Z]+$)/);
    if (m) {
        // XXX Get and set the extension, if any
        uploadExtension.value = m[1].toLowerCase();
        // XXX Set the mimeType
        uploadMimeType.value = mimeTypes[m[1].toLowerCase()] ? mimeTypes[m[1].toLowerCase()] : 'application/octet-stream';
    }
};

function completedUpload (ev) {
    // Rewrite uploader node, wake up delete button
    var iframe = ev.target;
    var innerDocument = iframe.contentDocument || iframe.contentWindow.document;
    var body = innerDocument.getElementsByTagName('BODY')[0];
    var ret = JSON.parse(body.textContent)
    addAttachment(ret.documentID,ret.documentTitle);
    status.uploadID = null;
    var uploadButton = document.getElementById('uploader-attachment-button');
    uploadButton.disabled = true;
    var searchableToggle = document.getElementById('uploader-attachment-searchable');
    searchableToggle.checked = false;
};

<commit_msg>Taking a stab at handling adminID for uploads to named server<commit_after>function savePersonFields (node) {
    var base = node.id.split('-')[0];
    var personIdNode = document.getElementById(base + '-name-id');
    var personID = personIdNode.value;

    var fields = {};
    var data = {};
    forField(node,function(field){
        // Save individual fields here
        var fieldName = field.id.split('-')[1];
        fields[fieldName] = field.value;
        cache[field.id] = field.value;
        showSave(field);
        var colname = field.id.split('-')[1];
        data[colname] = field.value;
    });

    // API call
    var adminID = getParameterByName('admin');
    var pageName = getParameterByName('page');
    if (!pageName) {
        pageName = 'top';
    }
    var row = apiRequest(
        '/?admin='
            + adminID
            + '&page=' + pageName
            + '&cmd=savetopersons'
        , {
            data:data,
            personid:personID,
            touchdate:pageDate
        }
    );
    if (false === row) return;
    // Refresh personID
    personIdNode.value = row.personID;
};

function startingUpload (ev) {
    status.uploadID = ev.id;
    var uploadButton = document.getElementById('uploader-attachment-button');
    var form = document.getElementById('uploader');
    // XXX How to acquire the adminID?
    var admin = '';
    if (getParameterByName('admin')) {
        admin = 'admin=' + getParameterByName('admin') + '&';
    }
    form.action = '?' + admin + 'cmd=upload';
    var uploadPagedate = document.getElementById('uploader-attachment-pagedate');
    uploadPagedate.value = pageDate;
    var uploadExtension = document.getElementById('uploader-attachment-extension');
    uploadExtension.value = '';
    var uploadMimeType = document.getElementById('uploader-attachment-mimetype');
    uploadMimeType.value = 'application/octet-stream';
    var uploadFilename = document.getElementById('uploader-attachment-filename');
    var fileName = uploadFilename.files[0].name;
    var m = fileName.match(/.*\.([a-zA-Z]+$)/);
    if (m) {
        // XXX Get and set the extension, if any
        uploadExtension.value = m[1].toLowerCase();
        // XXX Set the mimeType
        uploadMimeType.value = mimeTypes[m[1].toLowerCase()] ? mimeTypes[m[1].toLowerCase()] : 'application/octet-stream';
    }
};

function completedUpload (ev) {
    // Rewrite uploader node, wake up delete button
    var iframe = ev.target;
    var innerDocument = iframe.contentDocument || iframe.contentWindow.document;
    var body = innerDocument.getElementsByTagName('BODY')[0];
    var ret = JSON.parse(body.textContent)
    addAttachment(ret.documentID,ret.documentTitle);
    status.uploadID = null;
    var uploadButton = document.getElementById('uploader-attachment-button');
    uploadButton.disabled = true;
    var searchableToggle = document.getElementById('uploader-attachment-searchable');
    searchableToggle.checked = false;
};

<|endoftext|>


<commit_before>var UserSecurity = require('./UserSecurity'),
    NodeSecurity = require('./NodeSecurity'),
    userSecturity = new UserSecurity(),
    nodeSecurity = new NodeSecurity();

module.exports = function(){
    "use strict";

    var express = require('express'),
        bridgetown = require('bridgetown-api'),
        setHeaders = require('./setHeaders'),
        middleware = bridgetown.middleware;

    this.middleware = [];
    this.middleware.push(setHeaders);
    this.middleware.push(express.logger('default'));
    this.middleware.push(express.urlencoded());
    this.middleware.push(express.json());
    this.middleware.push(express.methodOverride());

    this.add = function(middleware){
        this.middleware.push(middleware);
        return this;
    };

    this.secure = function(security){
        this.middleware.push(middleware.authorization);
        this.middleware.push(middleware.authToken);
        if(security){
            this.middleware.push(security);
        }
        return this;
    };

    return this;
};

module.exports.user = {};
module.exports.node = {};

module.exports.user.required = {
    admin: userSecturity.requireAdmin,
    adminOrSelf: userSecturity.requireAdminOrSelf,
    editor: userSecturity.requireEditor,
    author: userSecturity.requireAuthor,
    reader: userSecturity.requireReader
};

module.exports.node.required = {
    admin: nodeSecurity.requireAdmin,
    editor: nodeSecurity.requireEditor,
    author: nodeSecurity.requireAuthor,
    reader: nodeSecurity.requireReader
};<commit_msg>Removed unneeded headers since they were not getting added anyways.<commit_after>var UserSecurity = require('./UserSecurity'),
    NodeSecurity = require('./NodeSecurity'),
    userSecturity = new UserSecurity(),
    nodeSecurity = new NodeSecurity();

module.exports = function(){
    "use strict";

    var bridgetown = require('bridgetown-api'),
        setHeaders = require('./setHeaders'),
        middleware = bridgetown.middleware;

    this.middleware = [];

    this.add = function(middleware){
        this.middleware.push(middleware);
        return this;
    };

    this.secure = function(security){
        this.middleware.push(middleware.authorization);
        this.middleware.push(middleware.authToken);
        if(security){
            this.middleware.push(security);
        }
        return this;
    };

    return this;
};

module.exports.user = {};
module.exports.node = {};

module.exports.user.required = {
    admin: userSecturity.requireAdmin,
    adminOrSelf: userSecturity.requireAdminOrSelf,
    editor: userSecturity.requireEditor,
    author: userSecturity.requireAuthor,
    reader: userSecturity.requireReader
};

module.exports.node.required = {
    admin: nodeSecurity.requireAdmin,
    editor: nodeSecurity.requireEditor,
    author: nodeSecurity.requireAuthor,
    reader: nodeSecurity.requireReader
};<|endoftext|>


<commit_before>/**
 * Copyright (c) 2013-2016 Memba Sarl. All rights reserved.
 * Sources at https://github.com/Memba
 */

/* jshint browser: true */
/* globals require: false, __NODE_ENV__: false */

if (typeof(require) === 'function') {
    // require('./window.assert');
    // require('./window.logger');
    // require('./app.logger.js');
    require('./app.config.jsx?env=' + __NODE_ENV__);
    require('./app.support.js');
}

/**
 * The application has <meta name="apple-mobile-web-app-capable" content="yes">
 * so to run the app full screen when pinned to IOS home screen
 * The following prevents links from opening in a new safari window
 * Source: https://gist.github.com/irae/1042167
 */
(function () {

    'use strict';

    var document = window.document;
    var navigator = window.navigator;
    var STANDALONE = 'standalone';

    // prevents links from apps from opening in mobile safari
    // this javascript must be the first script in your <head>
    if ((STANDALONE in navigator) && navigator[STANDALONE]) {
        var curnode;
        var chref;
        var location = document.location;
        var stop = /^(a|html)$/i;
        document.addEventListener('click', function (e) {
            curnode = e.target;
            while (!(stop).test(curnode.nodeName)) {
                curnode = curnode.parentNode;
            }
            // Conditions to do this only on links to your own app
            // if you want all links, use if('href' in curnode) instead.
            if (
                'href' in curnode && // is a link
                (chref = curnode.href).replace(location.href, '').indexOf('#') && // is not an anchor
                (!(/^[a-z\+\.\-]+:/i).test(chref) ||                       // either does not have a proper scheme (relative links)
                chref.indexOf(location.protocol + '//' + location.host) === 0) // or is in the same protocol and domain
            ) {
                e.preventDefault();
                location.href = curnode.href;
            }
        }, false);
    }

} ());

/**
 * Check browser features and redirect to error page if any essential feature is missing
 */
(function () {

    'use strict';

    var location = window.location;
    // Note: jQuery, kendo and app.i18n are not yet loaded
    var lang = window.document.getElementsByTagName('html')[0].getAttribute('lang');
    var errorUrl = app.uris.webapp.error.replace('{0}', lang);

    // Make sure we are not yet on the error page
    if (location.href.substr(0, errorUrl.length) !== errorUrl) {

        // Check browser features
        // TODO consider testing javascript enabled
        var support = app.support;
        var supported = support.atobbtoa &&
            support.audio && (support.audio.mp3 || support.audio.ogg) &&
            support.blobconstructor &&
            support.bloburls &&
            support.canvas && support.canvastext &&
            support.csstransforms &&
            // support.datauri &&
            support.filereader &&
            support.flexbox &&
            support.hashchange &&
            support.history &&
            support.localstorage && support.sessionstorage &&
            support.svg && support.inlinesvg && support.svgasimg &&
            // support.touchevents &&
            support.video && (support.video.h264 || support.video.ogg || support.video.webm) &&
            support.webworkers;

        // If any feature is missing, redirect to error page with error code 1000
        if (!supported) {
            location.assign(errorUrl + '?code=1000');
        }
    }

} ());
<commit_msg>Fixed jscs and jshint errors<commit_after>/**
 * Copyright (c) 2013-2016 Memba Sarl. All rights reserved.
 * Sources at https://github.com/Memba
 */

/* jshint browser: true */
/* globals require: false, __NODE_ENV__: false */

if (typeof(require) === 'function') {
    // require('./window.assert');
    // require('./window.logger');
    // require('./app.logger.js');
    require('./app.config.jsx?env=' + __NODE_ENV__);
    require('./app.support.js');
}

/**
 * The application has <meta name="apple-mobile-web-app-capable" content="yes">
 * so to run the app full screen when pinned to IOS home screen
 * The following prevents links from opening in a new safari window
 * Source: https://gist.github.com/irae/1042167
 */
(function () {

    'use strict';

    var document = window.document;
    var navigator = window.navigator;
    var STANDALONE = 'standalone';

    // prevents links from apps from opening in mobile safari
    // this javascript must be the first script in your <head>
    if ((STANDALONE in navigator) && navigator[STANDALONE]) {
        var curnode;
        var chref;
        var location = document.location;
        var stop = /^(a|html)$/i;
        document.addEventListener('click', function (e) {
            curnode = e.target;
            while (!(stop).test(curnode.nodeName)) {
                curnode = curnode.parentNode;
            }
            // Conditions to do this only on links to your own app
            // if you want all links, use if('href' in curnode) instead.
            if (
                'href' in curnode && // is a link
                (chref = curnode.href).replace(location.href, '').indexOf('#') && // is not an anchor
                (!(/^[a-z\+\.\-]+:/i).test(chref) ||                       // either does not have a proper scheme (relative links)
                chref.indexOf(location.protocol + '//' + location.host) === 0) // or is in the same protocol and domain
            ) {
                e.preventDefault();
                location.href = curnode.href;
            }
        }, false);
    }

} ());

/**
 * Check browser features and redirect to error page if any essential feature is missing
 */
(function () {

    'use strict';

    var app = window.app;
    var location = window.location;
    // Note: jQuery, kendo and app.i18n are not yet loaded
    var lang = window.document.getElementsByTagName('html')[0].getAttribute('lang');
    var errorUrl = app.uris.webapp.error.replace('{0}', lang);

    // Make sure we are not yet on the error page
    if (location.href.substr(0, errorUrl.length) !== errorUrl) {

        // Check browser features
        // TODO consider testing javascript enabled
        var support = app.support;
        var supported = support.atobbtoa &&
            support.audio && (support.audio.mp3 || support.audio.ogg) &&
            support.blobconstructor &&
            support.bloburls &&
            support.canvas && support.canvastext &&
            support.csstransforms &&
            // support.datauri &&
            support.filereader &&
            support.flexbox &&
            support.hashchange &&
            support.history &&
            support.localstorage && support.sessionstorage &&
            support.svg && support.inlinesvg && support.svgasimg &&
            // support.touchevents &&
            support.video && (support.video.h264 || support.video.ogg || support.video.webm) &&
            support.webworkers;

        // If any feature is missing, redirect to error page with error code 1000
        if (!supported) {
            location.assign(errorUrl + '?code=1000');
        }
    }

} ());
<|endoftext|>


<commit_before>/** Note: Remove the commented out lines of assertion code to see fails.

/* Require modules */
var $         = require('jquery'),
    Backbone  = require('backbone'),
    Router    = require('./common/router'),
    events    = require('./common/eventChannel'),
    utils     = require('./common/utilities'),
    qunit     = require('./_vendor/qunit');

/**
 * Set the module for the following tests
 */
QUnit.module('Search');

// Test the pattern matching for the reg no and stock ref no text input value
test("Test pattern matching for input value", 2, function () {

	// Create variables for pass case
	var sPassInput = 'SO06DNV ARNFH-U-5728',
	    rPattern = /^[^\s]+\s[^\s]+$/;

	// Set as boolean for result of pattern match
	var bPassed = rPattern.test(sPassInput);

	ok(bPassed, 'A correct string input should be detected as true');

	// Create variables for fail case
	var sFailInput = 'SO06DNV ARNFH-U-5728 232432sdgd-dsfgdfsg';

	// Set as boolean for result of pattern match
	var bFailed = rPattern.test(sFailInput);

	strictEqual(bFailed, false, 'An incorrect string input should be detected as false');

});

/**************************************************************/

/**
 * Set the module for the following tests
 */
QUnit.module('Events Channel');

// Test the events channel to check events are triggered/listened to
asyncTest("Test event listen/trigger", 1, function () {

	// Set the event name
	var sEventName = 'testEvent';

	// Listen for event name
	events.on(sEventName, function () {
		// If we are in here then the event has been caught, so pass the test
		ok(true, 'Event is caught');
		// Resume testing
		start();
	});

	// Trigger the event, if the event is not caught then the test will fail as we expect two tests to be run
	events.trigger(sEventName);
});

// Test the events channel to check events are triggered/listened to and parameters are received
asyncTest("Test events listen/trigger with param", 1, function () {

	// Set the parameter and new event name
	var iParam = 2,
		sEventParamName = 'testParamEvent';

	// Listen for event name, capture param in function
	events.on(sEventParamName, function (iParam) {
		// If we are in here then the event has been caught, so pass the test
		strictEqual(iParam, 2, 'Event is caught and the param === 2');
		// Resume testing
		start();
	});

	// Trigger the event, passing the param
	events.trigger(sEventParamName, iParam);
});

/**************************************************************/
<commit_msg>Note about testing<commit_after>/** Note: We could add the logic from app.js here to start the app and then use jQuery to perform
          DOM manipulation to test the view/template logic to ensure that the form submits, images load, etc */

/* Require modules */
var $         = require('jquery'),
    Backbone  = require('backbone'),
    Router    = require('./common/router'),
    events    = require('./common/eventChannel'),
    utils     = require('./common/utilities'),
    qunit     = require('./_vendor/qunit');

/**
 * Set the module for the following tests
 */
QUnit.module('Search');

// Test the pattern matching for the reg no and stock ref no text input value
test("Test pattern matching for input value", 2, function () {

	// Create variables for pass case
	var sPassInput = 'SO06DNV ARNFH-U-5728',
	    rPattern = /^[^\s]+\s[^\s]+$/;

	// Set as boolean for result of pattern match
	var bPassed = rPattern.test(sPassInput);

	ok(bPassed, 'A correct string input should be detected as true');

	// Create variables for fail case
	var sFailInput = 'SO06DNV ARNFH-U-5728 232432sdgd-dsfgdfsg';

	// Set as boolean for result of pattern match
	var bFailed = rPattern.test(sFailInput);

	strictEqual(bFailed, false, 'An incorrect string input should be detected as false');

});

/**************************************************************/

/**
 * Set the module for the following tests
 */
QUnit.module('Events Channel');

// Test the events channel to check events are triggered/listened to
asyncTest("Test event listen/trigger", 1, function () {

	// Set the event name
	var sEventName = 'testEvent';

	// Listen for event name
	events.on(sEventName, function () {
		// If we are in here then the event has been caught, so pass the test
		ok(true, 'Event is caught');
		// Resume testing
		start();
	});

	// Trigger the event, if the event is not caught then the test will fail as we expect two tests to be run
	events.trigger(sEventName);
});

// Test the events channel to check events are triggered/listened to and parameters are received
asyncTest("Test events listen/trigger with param", 1, function () {

	// Set the parameter and new event name
	var iParam = 2,
		sEventParamName = 'testParamEvent';

	// Listen for event name, capture param in function
	events.on(sEventParamName, function (iParam) {
		// If we are in here then the event has been caught, so pass the test
		strictEqual(iParam, 2, 'Event is caught and the param === 2');
		// Resume testing
		start();
	});

	// Trigger the event, passing the param
	events.trigger(sEventParamName, iParam);
});

/**************************************************************/
<|endoftext|>


<commit_before>/*global define,$ */
/*jslint browser:true,sloppy:true,nomen:true,unparam:true,plusplus:true */
/** @license
 | Copyright 2015 Esri
 |
 | Licensed under the Apache License, Version 2.0 (the "License");
 | you may not use this file except in compliance with the License.
 | You may obtain a copy of the License at
 |
 |    http://www.apache.org/licenses/LICENSE-2.0
 |
 | Unless required by applicable law or agreed to in writing, software
 | distributed under the License is distributed on an "AS IS" BASIS,
 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | See the License for the specific language governing permissions and
 | limitations under the License.
 */
//============================================================================================================================//
define(['lib/barcode.min'], function (barcode) {
    var showDiag = false, showTest = false;

    return {

        //--------------------------------------------------------------------------------------------------------------------//

        /**
         * Initializes the module by creating the diagnostic modal display and its trigger button.
         */
        init: function (appParams) {
            showDiag = appParams.diag !== undefined;
            showTest = appParams.test !== undefined;

            if (showDiag) {
                // Create the display modal box and the button to trigger it
                $("body").append("<button id='diagnosticButton' style='z-index:2000;position:absolute;left:0;top:0;width:32px;"
                    + "height:32px;background-color:transparent' data-toggle='modal' data-target='#diagnosticPanel' class='iconButton'></button>"
                    + "
"
                    + "
"
                    + "

"
                    + "
"
                    + "
");
                $("#diagnosticPanel").modal({show: false});
            }

            if (showTest) {
                // Create the barcode display box
                $("head").append("<link href='js/lib/barcode.min.css' rel='stylesheet'>");
                $("body").append("");
            }
        },

        /**
         * Appends HTML text to the diagnostic modal display.
         * @param {string} note Text to append; text can contain HTML
         */
        append: function (note) {
            if (showDiag) {
                $("#diagnosticLog").append(note);
            }
        },

        /**
         * Appends HTML text to the diagnostic modal display followed by an HTML <br>.
         * @param {string} note Text to append; text can contain HTML
         */
        appendWithLF: function (note) {
            this.append(note + "
");
        },

        /**
         * Appends an HTML <hr> to the diagnostic modal display.
         */
        appendLine: function () {
            this.append("
");
        },

        /**
         * Displays the supplied text as a code-128 barcode.
         * @param {string} text Text to convert and display
         */
        showAsCode: function (text) {
            if (showTest) {
                var codeContainer = $("#barcode");
                codeContainer[0].innerHTML = barcode.code128(text);
                codeContainer.css("display", "block");
            }
        },

        /**
         * Clears and hides the code-128 barcode.
         */
        clearCode: function () {
            if (showTest) {
                var codeContainer = $("#barcode");
                codeContainer[0].innerHTML = "";
                codeContainer.css("display", "none");
            }
        }

    };
});
<commit_msg>Added direction URL option for visual feedback<commit_after>/*global define,$ */
/*jslint browser:true,sloppy:true,nomen:true,unparam:true,plusplus:true */
/** @license
 | Copyright 2015 Esri
 |
 | Licensed under the Apache License, Version 2.0 (the "License");
 | you may not use this file except in compliance with the License.
 | You may obtain a copy of the License at
 |
 |    http://www.apache.org/licenses/LICENSE-2.0
 |
 | Unless required by applicable law or agreed to in writing, software
 | distributed under the License is distributed on an "AS IS" BASIS,
 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | See the License for the specific language governing permissions and
 | limitations under the License.
 */
//============================================================================================================================//
define(['lib/barcode.min'], function (barcode) {
    var showDiag = false, showTest = false;

    return {

        //--------------------------------------------------------------------------------------------------------------------//

        /**
         * Initializes the module by creating the diagnostic modal display and its trigger button.
         */
        init: function (appParams) {
            var barcodeDir = "h";

            showDiag = appParams.diag !== undefined;
            showTest = appParams.test !== undefined;

            if (showDiag) {
                // Create the display modal box and the button to trigger it
                $("body").append("<button id='diagnosticButton' style='z-index:2000;position:absolute;left:0;top:0;width:32px;"
                    + "height:32px;background-color:transparent' data-toggle='modal' data-target='#diagnosticPanel' class='iconButton'></button>"
                    + "
"
                    + "
"
                    + "

"
                    + "
"
                    + "
");
                $("#diagnosticPanel").modal({show: false});
            }

            if (showTest) {
                if (appParams.test === "v") {
                    barcodeDir = "v";
                }
                // Create the barcode display box
                $("head").append("<link href='js/lib/barcode.min.css' rel='stylesheet'>");
                $("body").append("");
            }
        },

        /**
         * Appends HTML text to the diagnostic modal display.
         * @param {string} note Text to append; text can contain HTML
         */
        append: function (note) {
            if (showDiag) {
                $("#diagnosticLog").append(note);
            }
        },

        /**
         * Appends HTML text to the diagnostic modal display followed by an HTML <br>.
         * @param {string} note Text to append; text can contain HTML
         */
        appendWithLF: function (note) {
            this.append(note + "
");
        },

        /**
         * Appends an HTML <hr> to the diagnostic modal display.
         */
        appendLine: function () {
            this.append("
");
        },

        /**
         * Displays the supplied text as a code-128 barcode.
         * @param {string} text Text to convert and display
         */
        showAsCode: function (text) {
            if (showTest) {
                var codeContainer = $("#barcode");
                codeContainer[0].innerHTML = barcode.code128(text);
                codeContainer.css("display", "block");
            }
        },

        /**
         * Clears and hides the code-128 barcode.
         */
        clearCode: function () {
            if (showTest) {
                var codeContainer = $("#barcode");
                codeContainer[0].innerHTML = "";
                codeContainer.css("display", "none");
            }
        }

    };
});
<|endoftext|>


<commit_before>/**
 * Keys used for various directions.
 *
 * The property names of this object indicate actions, and the values are lists
 * of keyboard keys or key combinations that will invoke these actions. Valid
 * keys include anything that {@link jQuery.hotkeys} accepts. The up, down,
 * left, and right properties are required if the `keys` variable exists; if
 * you don't want to use them, just set them to an empty array. {@link Actor}s
 * can have their own {@link Actor#keys keys} which will override the global
 * set.
 */
var keys = {
  up: ['up', 'w'],
  down: ['down', 's'],
  left: ['left', 'a'],
  right: ['right', 'd'],
  go: ['space', 'enter']
};

/**
 * An array of image file paths to pre-load.
 */
var preloadables = [];

/**
 * A magic-named function where all updates should occur.
 */
function update() {
}

/**
 * A magic-named function where all drawing should occur.
 */
function draw() {
  // Draw a background. This is just for illustration so we can see scrolling.
  //context.drawCheckered(80, 0, 0, world.width, world.height);
  var img = new Image();
  //console.log("help");
  img.onload = function () {
    context.drawImage(img, 0, 0);
  };
  img.src = "img/cloudacus.jpg";

  context.drawImage(img, 0, 0, img.width,    img.height,    // source rectangle
      0, 0, canvas.width, canvas.height);
  for (var i = 0; i < pits.length; i++) {
    pits[i].draw();
  }

	treasuries[PLAYER_1].draw();
	treasuries[PLAYER_2].draw();

	turnTitle.draw();

  if (gameIsOver) {
    gameOverTitle.draw();
  }
}
function resetTime(){
  var els = document.getElementsByClassName("fireworks");

  Array.prototype.forEach.call(els, function(el) {
    el.style.visibility = "visible";
  });
  setup();
}
/**
 * A magic-named function for one-time setup.
 *
 * @param {Boolean} first
 *   true if the app is being set up for the first time; false if the app has
 *   been reset and is starting over.
 */
function setup(first) {
  // Change the size of the playable area. Do this before placing items!
  world.resize(canvas.width, canvas.height);

  // Switch from side view to top-down.
  Actor.prototype.GRAVITY = false;

  // Initialize the player.
  // player = new Player();
  pits = [];
  treasuries = [];
  turn = PLAYER_1;
  gameIsOver = false;

  for (var i = 0, x=0; i < 6; i++) {
    pits[i] = new Pit(i, PLAYER_1, PIT_OFFSET_X+50*x, PIT_OFFSET_Y*2);
    x++;
  }

  for (var i = 6, x=5; i < 12; i++) {
    pits[i] = new Pit(i, PLAYER_2, PIT_OFFSET_X+50*x, PIT_OFFSET_Y);
    x--;
  }

  treasuries[PLAYER_1] = new Treasury(PLAYER_1, TREASURY_ONE_OFFSET_X, TREASURY_ONE_OFFSET_Y);
  treasuries[PLAYER_2] = new Treasury(PLAYER_2, TREASURY_TWO_OFFSET_X, TREASURY_TWO_OFFSET_Y);

	turnTitle = new TurnTitle(TITLE_OFFSET_X, TITLE_OFFSET_Y);
  gameOverTitle = new GameOverTitle(GAMEOVER_OFFSET_X, GAMEOVER_OFFSET_Y);
}

<commit_msg>Firework one time<commit_after>/**
 * Keys used for various directions.
 *
 * The property names of this object indicate actions, and the values are lists
 * of keyboard keys or key combinations that will invoke these actions. Valid
 * keys include anything that {@link jQuery.hotkeys} accepts. The up, down,
 * left, and right properties are required if the `keys` variable exists; if
 * you don't want to use them, just set them to an empty array. {@link Actor}s
 * can have their own {@link Actor#keys keys} which will override the global
 * set.
 */
var keys = {
  up: ['up', 'w'],
  down: ['down', 's'],
  left: ['left', 'a'],
  right: ['right', 'd'],
  go: ['space', 'enter']
};

/**
 * An array of image file paths to pre-load.
 */
var preloadables = [];

/**
 * A magic-named function where all updates should occur.
 */
function update() {
}

/**
 * A magic-named function where all drawing should occur.
 */
function draw() {
  // Draw a background. This is just for illustration so we can see scrolling.
  //context.drawCheckered(80, 0, 0, world.width, world.height);
  var img = new Image();
  //console.log("help");
  img.onload = function () {
    context.drawImage(img, 0, 0);
  };
  img.src = "img/cloudacus.jpg";

  context.drawImage(img, 0, 0, img.width,    img.height,    // source rectangle
      0, 0, canvas.width, canvas.height);
  for (var i = 0; i < pits.length; i++) {
    pits[i].draw();
  }

	treasuries[PLAYER_1].draw();
	treasuries[PLAYER_2].draw();

	turnTitle.draw();

  if (gameIsOver) {
    gameOverTitle.draw();
  }
}
function resetTime(){
  var els = document.getElementsByClassName("fireworks");

  Array.prototype.forEach.call(els, function(el) {

    el.src = el.src.replace(/\?.*$/,"")+"?x="+Math.random();
    el.style.visibility = "visible";
  });
  setTimeout(function(){
    Array.prototype.forEach.call(els, function(el) {
    el.style.visibility = "hidden";
  }); }, 2000);
  setup();
}
/**
 * A magic-named function for one-time setup.
 *
 * @param {Boolean} first
 *   true if the app is being set up for the first time; false if the app has
 *   been reset and is starting over.
 */
function setup(first) {
  // Change the size of the playable area. Do this before placing items!
  world.resize(canvas.width, canvas.height);

  // Switch from side view to top-down.
  Actor.prototype.GRAVITY = false;

  // Initialize the player.
  // player = new Player();
  pits = [];
  treasuries = [];
  turn = PLAYER_1;
  gameIsOver = false;

  for (var i = 0, x=0; i < 6; i++) {
    pits[i] = new Pit(i, PLAYER_1, PIT_OFFSET_X+50*x, PIT_OFFSET_Y*2);
    x++;
  }

  for (var i = 6, x=5; i < 12; i++) {
    pits[i] = new Pit(i, PLAYER_2, PIT_OFFSET_X+50*x, PIT_OFFSET_Y);
    x--;
  }

  treasuries[PLAYER_1] = new Treasury(PLAYER_1, TREASURY_ONE_OFFSET_X, TREASURY_ONE_OFFSET_Y);
  treasuries[PLAYER_2] = new Treasury(PLAYER_2, TREASURY_TWO_OFFSET_X, TREASURY_TWO_OFFSET_Y);

	turnTitle = new TurnTitle(TITLE_OFFSET_X, TITLE_OFFSET_Y);
  gameOverTitle = new GameOverTitle(GAMEOVER_OFFSET_X, GAMEOVER_OFFSET_Y);
}

<|endoftext|>


<commit_before>var debug = false;
var trace = false;

var text = [];
var delay = 50;
var currentWord = 0;
var currentChar = 0;
var dest = null;

console.log("testing - 1");

// Title
document.title = "Spencer's Portfolio";

// Description
$('meta[name=description]').remove();
$('head').append('<meta name="description" content="A portolio about Spencer Shadley regarding projects, resume, etc.">');

// Favicon
var link = document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = '../img/favicon.png';
document.getElementsByTagName('head')[0].appendChild(link);

// Background image (header)
var classCycle = ['androids1', 'androids2', 'androids3'];
var randomNumber = Math.floor(Math.random() * classCycle.length);
var classToAdd = classCycle[randomNumber];
$('header').addClass(classToAdd);

function type() {
    if(debug && trace) console.log("type()");
    dest.innerHTML = text[currentWord].substr(0, ++currentChar);
    if (currentChar > text[currentWord].length) setTimeout("unType()", 1500);
    else                                        setTimeout("type()", delay);
}

function unType() {
    if (debug && trace) console.log("unType()");

    dest.innerHTML = text[currentWord].substr(0, currentChar);
    if (--currentChar === 0) {
        dest.innerHTML = "
"; // otherwise page will jump due to line deletion
        if (currentWord + 1 === text.length) currentWord = 0;
        else ++currentWord;
        setTimeout("type()", 750);
    } else {
        setTimeout("unType()", delay/2);
    }
}

function startTyping(textParam, delayParam, destinationParam) {
    if (debug && trace) console.log("startTyping()");

    text = textParam;
    delay = delayParam;
    dest = document.getElementById(destinationParam);
    setTimeout("type()", 500);
}

window.onload = startTyping(["Software Developer", "University of Texas Student", "Entrepreneur"], 50, "messages");<commit_msg>added 5s delay to change background<commit_after>var debug = false;
var trace = false;

var text = [];
var delay = 50;
var currentWord = 0;
var currentChar = 0;
var dest = null;

console.log("testing - 1");

// Title
document.title = "Spencer's Portfolio";

// Description
$('meta[name=description]').remove();
$('head').append('<meta name="description" content="A portolio about Spencer Shadley regarding projects, resume, etc.">');

// Favicon
var link = document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = '../img/favicon.png';
document.getElementsByTagName('head')[0].appendChild(link);

// Background image (header)
var classCycle = ['androids1', 'androids2', 'androids3'];
var randomNumber = Math.floor(Math.random() * classCycle.length);
var classToAdd = classCycle[randomNumber];
$('header').addClass(classToAdd);

setTimeout("fadeImage()", 5000);

function fadeImage() {

    // remove old class
    randomNumber = Math.floor(Math.random() * classCycle.length);
    $('header').removeClass(classToAdd);

    // add new class
    classToAdd = classCycle[randomNumber];
    $('header').addClass(classToAdd);
}

/*
// Background image (header)
//var ogClass = $('header').className;
//console.log(ogClass);
var classCycle = ['androids1', 'androids2', 'androids3'];
var randomNumber = Math.floor(Math.random() * classCycle.length);
var classToAdd = classCycle[randomNumber];
$('header').addClass = classToAdd;

$('header').fadeOut(500, function () {
    $(this).css('background', 'url(../img/androids' + 1 + '.jpg)').fadeIn(1000);
});

setInterval(
    function () {
        console.log("running setInterval");
        console.log("old className: " + $('header').className);

        $('header').removeClass(classToAdd);

        randomNumber = Math.floor(Math.random() * classCycle.length);
        classToAdd = classCycle[randomNumber];

        $('header').addClass = classToAdd;

        console.log("new className: " + $('header').className);
    }, 5000
);
*/

function type() {
    if(debug && trace) console.log("type()");
    dest.innerHTML = text[currentWord].substr(0, ++currentChar);
    if (currentChar > text[currentWord].length) setTimeout("unType()", 1500);
    else                                        setTimeout("type()", delay);
}

function unType() {
    if (debug && trace) console.log("unType()");

    dest.innerHTML = text[currentWord].substr(0, currentChar);
    if (--currentChar === 0) {
        dest.innerHTML = "
"; // otherwise page will jump due to line deletion
        if (currentWord + 1 === text.length) currentWord = 0;
        else ++currentWord;
        setTimeout("type()", 750);
    } else {
        setTimeout("unType()", delay/2);
    }
}

function startTyping(textParam, delayParam, destinationParam) {
    if (debug && trace) console.log("startTyping()");

    text = textParam;
    delay = delayParam;
    dest = document.getElementById(destinationParam);
    setTimeout("type()", 500);
}

window.onload = startTyping(["Software Developer", "University of Texas Student", "Entrepreneur"], 50, "messages");<|endoftext|>


<commit_before>'use strict'

const
  REF_NUMBER_MULTIPLIERS = [7, 3, 1],
  FINNISH_REF_NUMBER_REGEX = /^(\d{4,20}|RF\d{6,22})$/i,
  FINNISH_IBAN_REGEX = /^FI\d{16}$/,
  FINNISH_VIRTUAL_BAR_CODE_REGEX = /^[45]\d{53}$/,
  FINNISH_DATE_REGEX = /^(\d\d?)\.(\d\d?)\.(\d{4})$/,
  IBAN_OFFSET_FROM_ASCIICODE = -55

function removeAllWhiteSpaces(str) {
  return str.replace(/\s+/g, '')
}

function removeLeadingZeros(str) {
  return str.replace(/^0+/, '')
}

function lettersToNumbers(str) {
  return [...str].map(char => {
    if (/\D/.test(char)) {
      return String(char.charCodeAt(0) + IBAN_OFFSET_FROM_ASCIICODE)
    }
    return char
  }).join('')
}

function reverseString(str) {
  return [...str].reverse().join('')
}

function removeStringFromEnd(str, strToRemove) {
  if (str.substr(-strToRemove.length) === strToRemove) {
    return str.substr(0, str.length - strToRemove.length)
  }
  return str
}

function leftPadString(str, char, pad) {
  return (char.repeat(pad) + str).substr(-Math.max(str.length, pad))
}

function randomNumberWithLength(length) {
  let randomNumber = ''
  for (let i = 0; i < length; i++) {
    randomNumber += Math.floor(Math.random() * 9) + 1 // 1...9, because a real number can't begin with zero
  }
  return parseInt(randomNumber, 10)
}

/** JS number type can't handle the long account numbers... */
function modForLargeNumber(base, divisor) {
  let dividend = ''
  for (let i = 0; i < base.length; i++) {
    dividend = parseInt(dividend + base[i], 10)
    if (dividend >= divisor) {
      const remainder = dividend % divisor
      if (i == base.length - 1) {
        return remainder
      } else {
        dividend = remainder
      }
    }
  }
  return parseInt(dividend, 10)
}

/** Luhn mod 10 checksum algorithm https://en.wikipedia.org/wiki/Luhn_algorithm */
function luhnMod10(value) {
  let sum = 0
  for (let i = 0; i < value.length; i++) {
    const multiplier = (i % 2 === 0) ? 2 : 1
    let add = multiplier * parseInt(value[i], 10)
    if (add >= 10) {
      add -= 9
    }
    sum += add
  }
  const mod10 = sum % 10
  return mod10 === 0 ? mod10 : 10 - mod10
}

function isValidFinnishBBAN(accountNumber) {
  accountNumber = removeAllWhiteSpaces(accountNumber)
  const
    localAccountNumberWithoutCheckSum = accountNumber.substr(4, 13),
    luhnChecksumChar = parseInt(accountNumber.substr(17, 1), 10)

  return luhnMod10(localAccountNumberWithoutCheckSum) === luhnChecksumChar
}

function isValidIBAN(iban) {
  iban = removeAllWhiteSpaces(iban.toUpperCase())
  const
    prefixAndChecksum = iban.substr(0, 4),
    number = iban.substr(4)

  return modForLargeNumber(lettersToNumbers(number + prefixAndChecksum), 97) === 1
}

function isValidFinnishDate(string) {
  if (
    !string ||
    typeof string != 'string' ||
    !FINNISH_DATE_REGEX.test(string)
  ) {
    return false
  }

  const
    [day, month, year] = string.match(FINNISH_DATE_REGEX).slice(1, 4).map(Number),
    date = new Date(year, month - 1, day)

  return year == date.getFullYear() && month - 1 == date.getMonth() && day == date.getDate()
}

function sliceVirtualBarCode(barCode) {
  const version = Number(barCode.substr(0, 1))
  let slices
  if (version === 4) {
    slices = [1, 16, 6, 2, 3, 20, 2, 2, 2]
  } else if (version === 5) {
    slices = [1, 16, 6, 2, 0, 23, 2, 2, 2]
  }
  let index = 0
  return slices.map(length => {
    const slice = barCode.substr(index, length)
    index += length
    return slice
  })
}

const FinnishBankUtils = {

  /**
   * Validate parameter given Finnish banking reference number.
   * Allows grouping of numbers with whitespace.
   * @param refNumber - {String} Reference number to parse, for example: 1234 56789 123123
   * @returns {boolean}
   */
  isValidFinnishRefNumber(refNumber) {
    //  Sanity and format check, which allows to make safe assumptions on the format.
    if (
      !refNumber ||
      typeof refNumber !== 'string' ||
      !FINNISH_REF_NUMBER_REGEX.test(removeAllWhiteSpaces(refNumber.toUpperCase()))
    ) {
      return false
    }

    refNumber = removeAllWhiteSpaces(refNumber.toUpperCase())

    if (/^RF/.test(refNumber)) {
      if (!isValidIBAN(refNumber)) {
        return false
      }
      refNumber = refNumber.substr(4)
    } else {
      refNumber = removeLeadingZeros(refNumber)
    }

    const
      reversedRefNumber = reverseString(refNumber),
      providedChecksumNumber = parseInt(reversedRefNumber.charAt(0))

    refNumber = reversedRefNumber.substr(1)

    let
      checksum = 0,
      checksumNumber

    for (let i = 0; i < refNumber.length; i++) {
      checksum += REF_NUMBER_MULTIPLIERS[i % REF_NUMBER_MULTIPLIERS.length] * parseInt(refNumber.charAt(i))
    }

    checksumNumber = 10 - checksum % 10

    if (checksumNumber === 10) {
      checksumNumber = 0
    }

    return checksumNumber === providedChecksumNumber
  },

  /**
   * Validate Finnish IBAN. Allows grouping of numbers with whitespace.
   *
   * @param accountNumber - {String} Account number to validate: FI 90 800026 2776 1348
   * @returns {boolean}
   */
  isValidFinnishIBAN(accountNumber) {
    if (
      !accountNumber ||
      typeof accountNumber !== 'string' ||
      !FINNISH_IBAN_REGEX.test(removeAllWhiteSpaces(accountNumber.toUpperCase()))
    ) {
      return false
    }

    return isValidFinnishBBAN(accountNumber) && isValidIBAN(accountNumber)
  },

  /**
   * Format Finnish reference number. Adds whitespace every 5 or 4 characters
   *
   * @param refNumber - {String} Reference number to format: RF341234561
   * @param separator - {String} Whitespace or other string to be used
   * @returns {string|undefined}
   */
  formatFinnishRefNumber(refNumber, separator = ' ') {
    if (this.isValidFinnishRefNumber(refNumber)) {
      refNumber = removeAllWhiteSpaces(refNumber.toUpperCase())
      if (/^RF/.test(refNumber)) {
        refNumber = refNumber.substr(0, 4) + removeLeadingZeros(refNumber.substr(4))
        return removeStringFromEnd(refNumber.replace(/.{4}/g, '$&' + separator), separator)
      } else {
        refNumber = removeLeadingZeros(refNumber)
        return reverseString(removeStringFromEnd(reverseString(refNumber).replace(/.{5}/g, '$&' + separator), separator))
      }
    }
  },

  /**
   * Format Finnish IBAN. Adds whitespace every 4 characters
   *
   * @param accountNumber - {String} Account number to format: FI9080002627761348
   * @param separator - {String} Whitespace or other string to be used
   * @returns {string|undefined}
   */
  formatFinnishIBAN(accountNumber, separator = ' ') {
    if (this.isValidFinnishIBAN(accountNumber)) {
      accountNumber = removeAllWhiteSpaces(accountNumber.toUpperCase())
      return removeStringFromEnd(accountNumber.replace(/.{4}/g, '$&' + separator), separator)
    }
  },

  /**
   * Returns a reference number including checksum char. If no initial string
   * is given, will use a random 9 char string instead.
   * Will not preserve whitespace in the initial string.
   * @param {String} initial - Initial string to calculate checksum for
   * @returns {String} - For example '1776750586'
   */
  generateFinnishRefNumber(initial) {
    const
      refNumber = typeof initial === 'string' ? removeAllWhiteSpaces(initial) : randomNumberWithLength(9).toString(),
      reversedRefNumber = reverseString(refNumber)

    let
      checksum = 0,
      checksumNumber

    for (let i = 0; i < reversedRefNumber.length; i++) {
      checksum += REF_NUMBER_MULTIPLIERS[i % REF_NUMBER_MULTIPLIERS.length] * parseInt(reversedRefNumber.charAt(i))
    }

    checksumNumber = 10 - checksum % 10

    if (checksumNumber === 10) {
      checksumNumber = 0
    }

    return refNumber + checksumNumber
  },

  /**
   * Returns a semi-random valid Finnish Iban bank account number
   * https://en.wikipedia.org/wiki/International_Bank_Account_Number#Generating_IBAN_check_digits
   * @returns {string} IBAN string, for example FI9080002627761348
   */
  generateFinnishIBAN() {
    const
      defaultCheckDigit = '00',
      danskeBankOffice = '800026',  //  Use a real bank and office for simplicity
      countryCodeInDigits = lettersToNumbers('FI'),
      bankAccount = randomNumberWithLength(7),
      localAccountNumber = danskeBankOffice + bankAccount + luhnMod10(danskeBankOffice + bankAccount),

      accountNumberCandidate = localAccountNumber + countryCodeInDigits + defaultCheckDigit,

      checkDigit = 98 - modForLargeNumber(accountNumberCandidate, 97),
      checkChars = checkDigit >= 10 ? checkDigit.toString() : '0' + checkDigit

    return 'FI' + checkChars + localAccountNumber
  },

  /**
   * Parse Finnish virtual bar code (aka virtuaaliviivakoodi, pankkiviidakoodi).
   * Supports versions 4 and 5
   * Based on: http://www.finanssiala.fi/maksujenvalitys/dokumentit/Pankkiviivakoodi-opas.pdf
   *
   * @param barCode - {String} Bar code to parse: 458101710000001220004829900000000559582243294671120131
   * @returns {object|false}
   */
  parseFinnishVirtualBarCode(barCode) {
    if (
      !barCode ||
      typeof barCode != 'string' ||
      !FINNISH_VIRTUAL_BAR_CODE_REGEX.test(barCode)
    ) {
      return false
    }

    let [version, iban, euros, cents, reserve, reference, year, month, day] = sliceVirtualBarCode(barCode)

    version = Number(version)

    iban = this.formatFinnishIBAN('FI' + iban)
    const sum = Number(euros) + Number(cents) / 100

    if (version === 5) {
      reference = 'RF' + reference.substr(0, 2) + removeLeadingZeros(reference.substr(2))
    }
    reference = this.formatFinnishRefNumber(reference)

    let date
    day = Number(day)
    month = Number(month)
    if (day > 0 && month > 0) {
      date = `${day}.${month}.20${year}`
    }

    return {iban, sum, reference, date}
  },

  /**
   * Formats Finnish virtual bar code
   * Supports versions 4 and 5
   * Based on: http://www.finanssiala.fi/maksujenvalitys/dokumentit/Pankkiviivakoodi-opas.pdf
   *
   * @param object - {Object} Parameters
   * @returns {string|false}
   */
  formatFinnishVirtualBarCode(object) {
    if (
      !object ||
      typeof object != 'object' ||
      !this.isValidFinnishIBAN(object.iban) ||
      typeof object.sum != 'number' ||
      object.sum < 0 ||
      object.sum > 999999.99 ||
      object.sum != Number(object.sum.toFixed(2)) ||
      !this.isValidFinnishRefNumber(object.reference) ||
      (object.date != undefined && !isValidFinnishDate(object.date))
    ) {
      return false
    }

    let
      iban = removeAllWhiteSpaces(object.iban),
      euros = Math.floor(object.sum),
      cents = object.sum * 100 - euros * 100,
      reference = removeAllWhiteSpaces(object.reference),
      day = 0,
      month = 0,
      year = 0,
      version = /^RF/.test(reference) ? 5 : 4

    if (object.date) {
      [day, month, year] = object.date.match(FINNISH_DATE_REGEX).slice(1, 4).map(Number)
    }

    if (version == 5) {
      reference = reference.replace(/^RF/, '')
      reference = reference.substr(0, 2) + leftPadString(reference.substr(2), '0', 21)
    }

    return String(version)
      + iban.replace(/^FI/, '')
      + leftPadString(String(euros), '0', 6)
      + leftPadString(String(cents), '0', 2)
      + leftPadString(reference, '0', 23)
      + leftPadString(String(year).substr(-2), '0', 2)
      + leftPadString(String(month), '0', 2)
      + leftPadString(String(day), '0', 2)
  }

}

module.exports = Object.freeze(FinnishBankUtils)
<commit_msg>Fix #11<commit_after>'use strict'

const
  REF_NUMBER_MULTIPLIERS = [7, 3, 1],
  FINNISH_REF_NUMBER_REGEX = /^(\d{4,20}|RF\d{6,22})$/i,
  FINNISH_IBAN_REGEX = /^FI\d{16}$/,
  FINNISH_VIRTUAL_BAR_CODE_REGEX = /^[45]\d{53}$/,
  FINNISH_DATE_REGEX = /^(\d\d?)\.(\d\d?)\.(\d{4})$/,
  IBAN_OFFSET_FROM_ASCIICODE = -55

function removeAllWhiteSpaces(str) {
  return str.replace(/\s+/g, '')
}

function removeLeadingZeros(str) {
  return str.replace(/^0+/, '')
}

function lettersToNumbers(str) {
  return [...str].map(char => {
    if (/\D/.test(char)) {
      return String(char.charCodeAt(0) + IBAN_OFFSET_FROM_ASCIICODE)
    }
    return char
  }).join('')
}

function reverseString(str) {
  return [...str].reverse().join('')
}

function removeStringFromEnd(str, strToRemove) {
  if (str.substr(-strToRemove.length) === strToRemove) {
    return str.substr(0, str.length - strToRemove.length)
  }
  return str
}

function leftPadString(str, char, pad) {
  return (char.repeat(pad) + str).substr(-Math.max(str.length, pad))
}

function randomNumberWithLength(length) {
  let randomNumber = ''
  for (let i = 0; i < length; i++) {
    randomNumber += Math.floor(Math.random() * 9) + 1 // 1...9, because a real number can't begin with zero
  }
  return parseInt(randomNumber, 10)
}

/** JS number type can't handle the long account numbers... */
function modForLargeNumber(base, divisor) {
  let dividend = ''
  for (let i = 0; i < base.length; i++) {
    dividend = parseInt(dividend + base[i], 10)
    if (dividend >= divisor) {
      const remainder = dividend % divisor
      if (i == base.length - 1) {
        return remainder
      } else {
        dividend = remainder
      }
    }
  }
  return parseInt(dividend, 10)
}

/** Luhn mod 10 checksum algorithm https://en.wikipedia.org/wiki/Luhn_algorithm */
function luhnMod10(value) {
  let sum = 0
  for (let i = 0; i < value.length; i++) {
    const multiplier = (i % 2 === 0) ? 2 : 1
    let add = multiplier * parseInt(value[i], 10)
    if (add >= 10) {
      add -= 9
    }
    sum += add
  }
  const mod10 = sum % 10
  return mod10 === 0 ? mod10 : 10 - mod10
}

function isValidFinnishBBAN(accountNumber) {
  accountNumber = removeAllWhiteSpaces(accountNumber)
  const
    localAccountNumberWithoutCheckSum = accountNumber.substr(4, 13),
    luhnChecksumChar = parseInt(accountNumber.substr(17, 1), 10)

  return luhnMod10(localAccountNumberWithoutCheckSum) === luhnChecksumChar
}

function isValidIBAN(iban) {
  iban = removeAllWhiteSpaces(iban.toUpperCase())
  const
    prefixAndChecksum = iban.substr(0, 4),
    number = iban.substr(4)

  return modForLargeNumber(lettersToNumbers(number + prefixAndChecksum), 97) === 1
}

function isValidFinnishDate(string) {
  if (
    !string ||
    typeof string != 'string' ||
    !FINNISH_DATE_REGEX.test(string)
  ) {
    return false
  }

  const
    [day, month, year] = string.match(FINNISH_DATE_REGEX).slice(1, 4).map(Number),
    date = new Date(year, month - 1, day)

  return year == date.getFullYear() && month - 1 == date.getMonth() && day == date.getDate()
}

function sliceVirtualBarCode(barCode) {
  const version = Number(barCode.substr(0, 1))
  let slices
  if (version === 4) {
    slices = [1, 16, 6, 2, 3, 20, 2, 2, 2]
  } else if (version === 5) {
    slices = [1, 16, 6, 2, 0, 23, 2, 2, 2]
  }
  let index = 0
  return slices.map(length => {
    const slice = barCode.substr(index, length)
    index += length
    return slice
  })
}

const FinnishBankUtils = {

  /**
   * Validate parameter given Finnish banking reference number.
   * Allows grouping of numbers with whitespace.
   * @param refNumber - {String} Reference number to parse, for example: 1234 56789 123123
   * @returns {boolean}
   */
  isValidFinnishRefNumber(refNumber) {
    //  Sanity and format check, which allows to make safe assumptions on the format.
    if (
      !refNumber ||
      typeof refNumber !== 'string' ||
      !FINNISH_REF_NUMBER_REGEX.test(removeAllWhiteSpaces(refNumber.toUpperCase()))
    ) {
      return false
    }

    refNumber = removeAllWhiteSpaces(refNumber.toUpperCase())

    if (/^RF/.test(refNumber)) {
      if (!isValidIBAN(refNumber)) {
        return false
      }
      refNumber = refNumber.substr(4)
    } else {
      refNumber = removeLeadingZeros(refNumber)
    }

    const
      reversedRefNumber = reverseString(refNumber),
      providedChecksumNumber = parseInt(reversedRefNumber.charAt(0))

    refNumber = reversedRefNumber.substr(1)

    let
      checksum = 0,
      checksumNumber

    for (let i = 0; i < refNumber.length; i++) {
      checksum += REF_NUMBER_MULTIPLIERS[i % REF_NUMBER_MULTIPLIERS.length] * parseInt(refNumber.charAt(i))
    }

    checksumNumber = 10 - checksum % 10

    if (checksumNumber === 10) {
      checksumNumber = 0
    }

    return checksumNumber === providedChecksumNumber
  },

  /**
   * Validate Finnish IBAN. Allows grouping of numbers with whitespace.
   *
   * @param accountNumber - {String} Account number to validate: FI 90 800026 2776 1348
   * @returns {boolean}
   */
  isValidFinnishIBAN(accountNumber) {
    if (
      !accountNumber ||
      typeof accountNumber !== 'string' ||
      !FINNISH_IBAN_REGEX.test(removeAllWhiteSpaces(accountNumber.toUpperCase()))
    ) {
      return false
    }

    return isValidFinnishBBAN(accountNumber) && isValidIBAN(accountNumber)
  },

  /**
   * Format Finnish reference number. Adds whitespace every 5 or 4 characters
   *
   * @param refNumber - {String} Reference number to format: RF341234561
   * @param separator - {String} Whitespace or other string to be used
   * @returns {string|undefined}
   */
  formatFinnishRefNumber(refNumber, separator = ' ') {
    if (this.isValidFinnishRefNumber(refNumber)) {
      refNumber = removeAllWhiteSpaces(refNumber.toUpperCase())
      if (/^RF/.test(refNumber)) {
        refNumber = refNumber.substr(0, 4) + removeLeadingZeros(refNumber.substr(4))
        return removeStringFromEnd(refNumber.replace(/.{4}/g, '$&' + separator), separator)
      } else {
        refNumber = removeLeadingZeros(refNumber)
        return reverseString(removeStringFromEnd(reverseString(refNumber).replace(/.{5}/g, '$&' + separator), separator))
      }
    }
  },

  /**
   * Format Finnish IBAN. Adds whitespace every 4 characters
   *
   * @param accountNumber - {String} Account number to format: FI9080002627761348
   * @param separator - {String} Whitespace or other string to be used
   * @returns {string|undefined}
   */
  formatFinnishIBAN(accountNumber, separator = ' ') {
    if (this.isValidFinnishIBAN(accountNumber)) {
      accountNumber = removeAllWhiteSpaces(accountNumber.toUpperCase())
      return removeStringFromEnd(accountNumber.replace(/.{4}/g, '$&' + separator), separator)
    }
  },

  /**
   * Returns a reference number including checksum char. If no initial string
   * is given, will use a random 9 char string instead.
   * Will not preserve whitespace in the initial string.
   * @param {String} initial - Initial string to calculate checksum for
   * @returns {String} - For example '1776750586'
   */
  generateFinnishRefNumber(initial) {
    const
      refNumber = typeof initial === 'string' ? removeAllWhiteSpaces(initial) : randomNumberWithLength(9).toString(),
      reversedRefNumber = reverseString(refNumber)

    let
      checksum = 0,
      checksumNumber

    for (let i = 0; i < reversedRefNumber.length; i++) {
      checksum += REF_NUMBER_MULTIPLIERS[i % REF_NUMBER_MULTIPLIERS.length] * parseInt(reversedRefNumber.charAt(i))
    }

    checksumNumber = 10 - checksum % 10

    if (checksumNumber === 10) {
      checksumNumber = 0
    }

    return refNumber + checksumNumber
  },

  /**
   * Returns a semi-random valid Finnish Iban bank account number
   * https://en.wikipedia.org/wiki/International_Bank_Account_Number#Generating_IBAN_check_digits
   * @returns {string} IBAN string, for example FI9080002627761348
   */
  generateFinnishIBAN() {
    const
      defaultCheckDigit = '00',
      danskeBankOffice = '800026',  //  Use a real bank and office for simplicity
      countryCodeInDigits = lettersToNumbers('FI'),
      bankAccount = randomNumberWithLength(7),
      localAccountNumber = danskeBankOffice + bankAccount + luhnMod10(danskeBankOffice + bankAccount),

      accountNumberCandidate = localAccountNumber + countryCodeInDigits + defaultCheckDigit,

      checkDigit = 98 - modForLargeNumber(accountNumberCandidate, 97),
      checkChars = checkDigit >= 10 ? checkDigit.toString() : '0' + checkDigit

    return 'FI' + checkChars + localAccountNumber
  },

  /**
   * Parse Finnish virtual bar code (aka virtuaaliviivakoodi, pankkiviidakoodi).
   * Supports versions 4 and 5
   * Based on: http://www.finanssiala.fi/maksujenvalitys/dokumentit/Pankkiviivakoodi-opas.pdf
   *
   * @param barCode - {String} Bar code to parse: 458101710000001220004829900000000559582243294671120131
   * @returns {object|false}
   */
  parseFinnishVirtualBarCode(barCode) {
    if (
      !barCode ||
      typeof barCode != 'string' ||
      !FINNISH_VIRTUAL_BAR_CODE_REGEX.test(barCode)
    ) {
      return false
    }

    let [version, iban, euros, cents, reserve, reference, year, month, day] = sliceVirtualBarCode(barCode)

    version = Number(version)

    iban = this.formatFinnishIBAN('FI' + iban)
    const sum = Number(euros) + Number(cents) / 100

    if (version === 5) {
      reference = 'RF' + reference.substr(0, 2) + removeLeadingZeros(reference.substr(2))
    }
    reference = this.formatFinnishRefNumber(reference)

    let date
    day = Number(day)
    month = Number(month)
    if (day > 0 && month > 0) {
      date = `${day}.${month}.20${year}`
    }

    return {iban, sum, reference, date}
  },

  /**
   * Formats Finnish virtual bar code
   * Supports versions 4 and 5
   * Based on: http://www.finanssiala.fi/maksujenvalitys/dokumentit/Pankkiviivakoodi-opas.pdf
   *
   * @param object - {Object} Parameters
   * @returns {string|false}
   */
  formatFinnishVirtualBarCode(object) {
    if (
      !object ||
      typeof object != 'object' ||
      !this.isValidFinnishIBAN(object.iban) ||
      typeof object.sum != 'number' ||
      object.sum < 0 ||
      object.sum > 999999.99 ||
      object.sum != Number(object.sum.toFixed(2)) ||
      !this.isValidFinnishRefNumber(object.reference) ||
      (object.date != undefined && !isValidFinnishDate(object.date))
    ) {
      return false
    }

    let
      iban = removeAllWhiteSpaces(object.iban),
      euros = Math.floor(object.sum),
      cents = Math.floor(object.sum * 100 - euros * 100),
      reference = removeAllWhiteSpaces(object.reference),
      day = 0,
      month = 0,
      year = 0,
      version = /^RF/.test(reference) ? 5 : 4

    if (object.date) {
      [day, month, year] = object.date.match(FINNISH_DATE_REGEX).slice(1, 4).map(Number)
    }

    if (version == 5) {
      reference = reference.replace(/^RF/, '')
      reference = reference.substr(0, 2) + leftPadString(reference.substr(2), '0', 21)
    }

    return String(version)
      + iban.replace(/^FI/, '')
      + leftPadString(String(euros), '0', 6)
      + leftPadString(String(cents), '0', 2)
      + leftPadString(reference, '0', 23)
      + leftPadString(String(year).substr(-2), '0', 2)
      + leftPadString(String(month), '0', 2)
      + leftPadString(String(day), '0', 2)
  }

}

module.exports = Object.freeze(FinnishBankUtils)
<|endoftext|>