<commit_before>AUI().add(
	'editable',
	function(A) {
		var Lang = A.Lang,
			isFunction = Lang.isFunction,

			getClassName = A.ClassNameManager.getClassName,

			HOVER = 'hover',
			NAME = 'editable',

			CSS_EDITING = getClassName(NAME, 'editing'),
			CSS_HOVER = getClassName(NAME, HOVER),

			CONTENT_BOX = 'contentBox';

		var Editable = function() {
			Editable.superclass.constructor.apply(this, arguments);
		};

		Editable.NAME = 'editable';

		Editable.ATTRS = {
			cancelButton: {
				valueFn: function() {
					var instance = this;

					return {
						id: 'cancel',
						icon: 'circle-close',
						handler: {
							context: instance,
							fn: instance.cancel
						}
					};
				}
			},
			contentText: {
				value: '',
				setter: function(value) {
					var instance = this;

					value = Lang.trim(value);

					instance._toText(value);

					return value;
				}
			},

			formatInput: {
				value: null,
				validator: isFunction
			},

			formatOutput: {
				value: null,
				validator: isFunction
			},

			node: {
				setter: function(value) {
					var node = A.get(value);

					if (!node) {
						A.error('AUI.Editable: Invalid Node Given: ' + value);
					}
					else {
						node = node.item(0);
					}

					return node;
				}
			},

			eventType: {
				value: 'click'
			},

			renderTo: {
				value: document.body,
				setter: function(value) {
					var instance = this;

					var node;

					if (value == 'node') {
						node = instance.get(value);
					}
					else {
						node = A.get(value);
					}

					if (!node) {
						A.error('AUI.Editable: Invalid renderTo Given: ' + value);
					}
					else {
						node = node.item(0);
					}

					return node;
				}
			},

			saveButton: {
				valueFn: function() {
					var instance = this;

					return {
						id: 'save',
						icon: 'circle-check',
						handler: {
							context: instance,
							fn: instance.save
						}
					};
				}
			},

			tools: {
				value: []
			},

			inputType: {
				value: 'text',
				setter: function(value) {
					var instance = this;

					if (value != 'text' && value != 'textarea') {
						value = A.Attribute.INVALID_VALUE;
					}

					return value;
				}
			}
		};

		A.extend(
			Editable,
			A.Widget,
			{
				initializer: function() {
					var instance = this;

					instance._scopedSave = A.bind(instance.save, instance);

					var node = instance.get('node');
					var eventType = instance.get('eventType');

					node.on('mouseenter', instance._onMouseEnterEditable, instance);
					node.on('mouseleave', instance._onMouseLeaveEditable, instance);

					node.on(eventType, instance._startEditing, instance);

					instance._createEvents();
				},

				renderUI: function() {
					var instance = this;

					var contentBox = instance.get(CONTENT_BOX);
					var inputType = instance.get('inputType');

					var comboConfig = {};

					var tools = instance.get('tools');

					if (tools !== false) {
						var cancelButton = instance.get('cancelButton');
						var saveButton = instance.get('saveButton');

						if (cancelButton !== false) {
							tools.push(cancelButton);
						}

						if (saveButton !== false) {
							tools.push(saveButton);
						}

						comboConfig.tools = tools;
					}

					if (inputType != 'text') {
						A.mix(
							comboConfig,
							{
								field: {
									autoSize: true
								},
								fieldWidget: A.Textarea
							}
						);
					}

					var comboBox = new A.Combobox(comboConfig).render(contentBox);

					instance._comboBox = comboBox;

					instance.inputNode = comboBox.get('node');
				},

				bindUI: function() {
					var instance = this;

					var contentBox = instance.get(CONTENT_BOX);
					var node = instance.get('node');

					var inputNode = instance.inputNode;

					inputNode.on('keypress', instance._onKeypressEditable, instance);

					instance.after('contentTextChange', instance._syncContentText);

					instance.after('focusedChange', instance._afterFocusedChangeEditable);
				},

				syncUI: function() {
					var instance = this;

					var currentText = instance.get('node').get('innerHTML');

					currentText = currentText.replace(/\n|\r/gim, '');
					currentText = Lang.trim(currentText);

					currentText = instance._toText(currentText);

					instance._setInput(currentText);

					instance.set(
						'contentText',
						currentText,
						{
							initial: true
						}
					);
				},

				cancel: function() {
					var instance = this;

					instance.fire('cancel');
				},

				save: function(event) {
					var instance = this;

					instance.fire('save');
				},

				_afterFocusedChangeEditable: function(event) {
					var instance = this;

					if (event.newVal === false) {
						instance.fire('stopEditing', instance.get('visible'));
					}
				},

				_createEvents: function() {
					var instance = this;

					instance.publish(
						'startEditing',
						{
							bubbles: true,
							defaultFn: instance._defStartEditingFn,
							emitFacade: true,
							queable: false
						}
					);

					instance.publish(
						'stopEditing',
						{
							bubbles: true,
							defaultFn: instance._defStopEditingFn,
							emitFacade: true,
							queable: false
						}
					);

					instance.publish(
						'save',
						{
							bubbles: true,
							defaultFn: instance._defSaveFn,
							emitFacade: true,
							queable: false
						}
					);

					instance.publish(
						'cancel',
						{
							bubbles: true,
							defaultFn: instance._defCancelFn,
							emitFacade: true,
							queable: false
						}
					);
				},

				_defCancelFn: function(event) {
					var instance = this;

					instance.fire('stopEditing', false);
				},

				_defStartEditingFn: function(event) {
					var instance = this;

					var boundingBox = instance.get('boundingBox');
					var node = instance.get('node');

					var inputNode = instance.inputNode;

					var nodeHeight = node.get('offsetHeight');
					var nodeWidth = node.get('offsetWidth');

					instance.show();

					node.addClass(CSS_EDITING);

					var xy = node.getXY();

					boundingBox.setStyles(
						{
							height: nodeHeight + 'px',
							left: xy[0] + 'px',
							top: xy[1] + 'px',
							width: nodeWidth + 'px'
						}
					);

					var inputField = instance._comboBox._field

					inputField.set('width', nodeWidth);
					inputField.fire('adjustSize');

					inputNode.focus();
					inputNode.select();
				},

				_defStopEditingFn: function(event, save) {
					var instance = this;

					instance.hide();

					instance.get('node').removeClass(CSS_EDITING);

					if (save) {
						instance.set('contentText', instance.inputNode.get('value'));
					}
					else {
						instance._setInput(instance.get('contentText'));
					}
				},

				_defSaveFn: function(event) {
					var instance = this;

					instance.fire('stopEditing', true);
				},

				_onKeypressEditable: function(event) {
					var instance = this;

					var keyCode = event.keyCode;

					if (keyCode == 27) {
						event.preventDefault();

						instance.cancel();
					}
					else if (keyCode == 13 && (instance.get('inputType') == 'text')) {
						instance.save();
					}
				},

				_onMouseEnterEditable: function(event) {
					var instance = this;

					instance.get('node').addClass(CSS_HOVER);
				},

				_onMouseLeaveEditable: function(event) {
					var instance = this;

					instance.get('node').removeClass(CSS_HOVER);
				},

				_setInput: function(value) {
					var instance = this;

					var inputFormatter = instance.get('formatInput');

					if (inputFormatter) {
						value = inputFormatter.call(instance, value);
					}
					else {
						value = instance._toText(value);
					}

					instance.inputNode.set('value', value);
				},

				_setOutput: function(value) {
					var instance = this;

					var outputFormatter = instance.get('formatOutput');

					if (outputFormatter) {
						value = outputFormatter.call(instance, value);
					}
					else {
						value = instance._toHTML(value);
					}

					instance.get('node').set('innerHTML', value);
				},

				_startEditing: function(event) {
					var instance = this;

					if (!instance.get('rendered')) {
						instance.render(instance.get('renderTo'));
					}

					instance.fire('startEditing');

					event.halt();
				},

				_syncContentText: function(event) {
					var instance = this;

					if (!event.initial) {
						var contentText = event.newVal;

						instance._setInput(contentText);
						instance._setOutput(contentText);
					}
				},

				_toHTML: function(text) {
					var instance = this;

					return String(text).replace(/\n/gim, '
');
				},

				_toText: function(text) {
					var instance = this;

					var text = String(text);

					text = text.replace(/
/gim, '\n');

					text = text.replace(/(<\/?[^>]+>|\t)/gim, '');

					return text;
				}
			}
		);

		A.Editable = Editable;
	},
	'@VERSION',
	{
		requires: ['widget', 'tool'],
		use: []
	}
);<commit_msg>Editable improvement<commit_after>AUI().add(
	'editable',
	function(A) {
		var Lang = A.Lang,
			isFunction = Lang.isFunction,

			getClassName = A.ClassNameManager.getClassName,

			HOVER = 'hover',
			NAME = 'editable',

			CSS_EDITING = getClassName(NAME, 'editing'),
			CSS_HOVER = getClassName(NAME, HOVER),

			CONTENT_BOX = 'contentBox';

		var Editable = function() {
			Editable.superclass.constructor.apply(this, arguments);
		};

		Editable.NAME = 'editable';

		Editable.ATTRS = {
			cancelButton: {
				valueFn: function() {
					var instance = this;

					return {
						id: 'cancel',
						icon: 'circle-close',
						handler: {
							context: instance,
							fn: instance.cancel
						}
					};
				}
			},
			contentText: {
				value: '',
				setter: function(value) {
					var instance = this;

					value = Lang.trim(value);

					instance._toText(value);

					return value;
				}
			},

			formatInput: {
				value: null,
				validator: isFunction
			},

			formatOutput: {
				value: null,
				validator: isFunction
			},

			node: {
				setter: function(value) {
					var node = A.get(value);

					if (!node) {
						A.error('AUI.Editable: Invalid Node Given: ' + value);
					}
					else {
						node = node.item(0);
					}

					return node;
				}
			},

			eventType: {
				value: 'click'
			},

			renderTo: {
				value: document.body,
				setter: function(value) {
					var instance = this;

					var node;

					if (value == 'node') {
						node = instance.get(value);
					}
					else {
						node = A.get(value);
					}

					if (!node) {
						A.error('AUI.Editable: Invalid renderTo Given: ' + value);
					}
					else {
						node = node.item(0);
					}

					return node;
				}
			},

			saveButton: {
				valueFn: function() {
					var instance = this;

					return {
						id: 'save',
						icon: 'circle-check',
						handler: {
							context: instance,
							fn: instance.save
						}
					};
				}
			},

			tools: {
				value: []
			},

			inputType: {
				value: 'text',
				setter: function(value) {
					var instance = this;

					if (value != 'text' && value != 'textarea') {
						value = A.Attribute.INVALID_VALUE;
					}

					return value;
				}
			}
		};

		A.extend(
			Editable,
			A.Widget,
			{
				initializer: function() {
					var instance = this;

					instance._scopedSave = A.bind(instance.save, instance);

					var node = instance.get('node');
					var eventType = instance.get('eventType');

					node.on('mouseenter', instance._onMouseEnterEditable, instance);
					node.on('mouseleave', instance._onMouseLeaveEditable, instance);

					node.on(eventType, instance._startEditing, instance);

					instance._createEvents();
				},

				renderUI: function() {
					var instance = this;

					var contentBox = instance.get(CONTENT_BOX);
					var inputType = instance.get('inputType');

					var comboConfig = {};

					var tools = instance.get('tools');

					if (tools !== false) {
						var cancelButton = instance.get('cancelButton');
						var saveButton = instance.get('saveButton');

						if (cancelButton !== false) {
							tools.push(cancelButton);
						}

						if (saveButton !== false) {
							tools.push(saveButton);
						}

						comboConfig.tools = tools;
					}

					if (inputType != 'text') {
						A.mix(
							comboConfig,
							{
								field: {
									autoSize: true
								},
								fieldWidget: A.Textarea
							}
						);
					}

					var comboBox = new A.Combobox(comboConfig).render(contentBox);

					instance._comboBox = comboBox;

					instance.inputNode = comboBox.get('node');
				},

				bindUI: function() {
					var instance = this;

					var contentBox = instance.get(CONTENT_BOX);
					var node = instance.get('node');

					var inputNode = instance.inputNode;

					inputNode.on('keypress', instance._onKeypressEditable, instance);

					instance.after('contentTextChange', instance._syncContentText);

					contentBox.swallowEvent('click');

					A.getDoc().after('click', instance._afterFocusedChangeEditable, instance);
				},

				syncUI: function() {
					var instance = this;

					var currentText = instance.get('node').get('innerHTML');

					currentText = currentText.replace(/\n|\r/gim, '');
					currentText = Lang.trim(currentText);

					currentText = instance._toText(currentText);

					instance._setInput(currentText);

					instance.set(
						'contentText',
						currentText,
						{
							initial: true
						}
					);
				},

				cancel: function() {
					var instance = this;

					instance.fire('cancel');
				},

				save: function(event) {
					var instance = this;

					instance.fire('save');
				},

				_afterFocusedChangeEditable: function(event) {
					var instance = this;

					instance.fire('stopEditing', instance.get('visible'));
				},

				_createEvents: function() {
					var instance = this;

					instance.publish(
						'startEditing',
						{
							bubbles: true,
							defaultFn: instance._defStartEditingFn,
							emitFacade: true,
							queable: false
						}
					);

					instance.publish(
						'stopEditing',
						{
							bubbles: true,
							defaultFn: instance._defStopEditingFn,
							emitFacade: true,
							queable: false
						}
					);

					instance.publish(
						'save',
						{
							bubbles: true,
							defaultFn: instance._defSaveFn,
							emitFacade: true,
							queable: false
						}
					);

					instance.publish(
						'cancel',
						{
							bubbles: true,
							defaultFn: instance._defCancelFn,
							emitFacade: true,
							queable: false
						}
					);
				},

				_defCancelFn: function(event) {
					var instance = this;

					instance.fire('stopEditing', false);
				},

				_defStartEditingFn: function(event) {
					var instance = this;

					var boundingBox = instance.get('boundingBox');
					var node = instance.get('node');

					var inputNode = instance.inputNode;

					var nodeHeight = node.get('offsetHeight');
					var nodeWidth = node.get('offsetWidth');

					instance.show();

					node.addClass(CSS_EDITING);

					var xy = node.getXY();

					boundingBox.setStyles(
						{
							height: nodeHeight + 'px',
							left: xy[0] + 'px',
							top: xy[1] + 'px',
							width: nodeWidth + 'px'
						}
					);

					var inputField = instance._comboBox._field

					inputField.set('width', nodeWidth);
					inputField.fire('adjustSize');

					inputNode.focus();
					inputNode.select();
				},

				_defStopEditingFn: function(event, save) {
					var instance = this;

					instance.hide();

					instance.get('node').removeClass(CSS_EDITING);

					if (save) {
						instance.set('contentText', instance.inputNode.get('value'));
					}
					else {
						instance._setInput(instance.get('contentText'));
					}
				},

				_defSaveFn: function(event) {
					var instance = this;

					instance.fire('stopEditing', true);
				},

				_onKeypressEditable: function(event) {
					var instance = this;

					var keyCode = event.keyCode;

					if (keyCode == 27) {
						event.preventDefault();

						instance.cancel();
					}
					else if (keyCode == 13 && (instance.get('inputType') == 'text')) {
						instance.save();
					}
				},

				_onMouseEnterEditable: function(event) {
					var instance = this;

					instance.get('node').addClass(CSS_HOVER);
				},

				_onMouseLeaveEditable: function(event) {
					var instance = this;

					instance.get('node').removeClass(CSS_HOVER);
				},

				_setInput: function(value) {
					var instance = this;

					var inputFormatter = instance.get('formatInput');

					if (inputFormatter) {
						value = inputFormatter.call(instance, value);
					}
					else {
						value = instance._toText(value);
					}

					instance.inputNode.set('value', value);
				},

				_setOutput: function(value) {
					var instance = this;

					var outputFormatter = instance.get('formatOutput');

					if (outputFormatter) {
						value = outputFormatter.call(instance, value);
					}
					else {
						value = instance._toHTML(value);
					}

					instance.get('node').set('innerHTML', value);
				},

				_startEditing: function(event) {
					var instance = this;

					if (!instance.get('rendered')) {
						instance.render(instance.get('renderTo'));
					}

					instance.fire('startEditing');

					event.halt();
				},

				_syncContentText: function(event) {
					var instance = this;

					if (!event.initial) {
						var contentText = event.newVal;

						instance._setInput(contentText);
						instance._setOutput(contentText);
					}
				},

				_toHTML: function(text) {
					var instance = this;

					return String(text).replace(/\n/gim, '
');
				},

				_toText: function(text) {
					var instance = this;

					var text = String(text);

					text = text.replace(/
/gim, '\n');

					text = text.replace(/(<\/?[^>]+>|\t)/gim, '');

					return text;
				}
			}
		);

		A.Editable = Editable;
	},
	'@VERSION',
	{
		requires: ['widget', 'tool'],
		use: []
	}
);<|endoftext|>


<commit_before>/**
 * Created by sonja on 10/26/16.
 */

/**
 * Creates an object of type Captcha
 * @constructor
 */
function Captcha() {
    this.height = 100;
    this.width = 300;
    this.fingerprintLenght = 4;
}

/**
 *
 * @returns {string|*|string}
 */
Captcha.prototype.getPNG = function(){
    var fingerprint = [];
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789?#$%&";

    for( var i=0; i < 4; i++ ){
        fingerprint.push(possible.charAt(Math.floor(Math.random() * possible.length)));
    }
    console.log(fingerprint);

    // var svg = "<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>" +
    //     "<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xml:space='preserve'>" +
    //     "<text x='100 40 70 100' y='100' style='font-size:55; font-family:Times New Roman, Courier; font-weight: bold; font-style=oblique; stroke: black; stoke-width:1'transform='rotate(20, 100, 100)'>" +
    //     fingerprint[0] +
    //     "</text><text x='170 40 70 100' y='100' style='font-size:60; font-family:Arial black, Arial; font-weight: bold; font-style=oblique; stroke: black; stoke-width:1; fill:none'transform='rotate(-40, 170, 100)'>" +
    // fingerprint[1] +
    // "</text" +
    // "><text x='200 40 70 100' y='100' style='font-size:35; font-family:Comic Sans MS, Arial; font-weight: bold; " +
    // "font-style=oblique; stroke: black; stoke-width:1; fill:none'transform='rotate(30, 200, 100)'>" +
    //     fingerprint[2] +"</text>" +
    //     "<text x='250 40 70 100' y='100' style='font-size:37; font-family:Courier new, Courier; font-weight: bold; " +
    //     "font-style=oblique; stroke: black; stoke-width:1; fill:none'transform='rotate(-20, 250, 100)'>" +
    //     fingerprint[3] +"</text>" +
    //     "<filter id='filter1' filerUnits='objectBoundingBox' x='0%' width='100%' height='100%'><feTurbulence type='turbulence' baseFrequency='0.03' numOctaves='5' />" +
    //     "</filter><rect x='100' y='50' width='250' height='100' filter='url(#filter1)' />" +
    //     "<line x1='100' y1='50' x2='290' y2='50' /><line x1='100' y1='120' x2='290' y2='120' /><line x1='100' y1='50' x2='100' y2='120' /><line x1='290' y1='50' x2='290' y2='120' />g>";

    return svg;
};<commit_msg>Implement function and make one failing test pass<commit_after>/**
 * Created by sonja on 10/26/16.
 */

/**
 * Creates an object of type Captcha
 * @constructor
 */
function Captcha() {
    this.height = 100;
    this.width = 300;
    this.fingerprintLength = 4;
    this.fingerprintHash = "";
}

/**
 * Functions which generates a random fingerprint and return a svg
 * @returns {string|*|string}
 */
Captcha.prototype.getSVG = function(){
    var that = this;
    var fingerprint = "";
    var possible = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz023456789?#$%&";

    for( var i=0; i < that.fingerprintLength; i++ ){
        fingerprint += possible.charAt(Math.floor(Math.random() * possible.length))
    }
    that.fingerprintHash = CryptoJS.MD5(fingerprint);

    var svg = "<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>" +
        "<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xml:space='preserve'>" +
        "<text x='100 40 70 100' y='100' style='font-size:55; font-family:Times New Roman, Courier; font-weight: bold; font-style=oblique; stroke: black; stoke-width:1'transform='rotate(20, 100, 100)'>" +
        fingerprint[0] +
        "</text><text x='170 40 70 100' y='100' style='font-size:60; font-family:Arial black, Arial; font-weight: bold; font-style=oblique; stroke: black; stoke-width:1; fill:none'transform='rotate(-40, 170, 100)'>" +
    fingerprint[1] +
    "</text" +
    "><text x='200 40 70 100' y='100' style='font-size:35; font-family:Comic Sans MS, Arial; font-weight: bold; " +
    "font-style=oblique; stroke: black; stoke-width:1; fill:none'transform='rotate(30, 200, 100)'>" +
        fingerprint[2] +"</text>" +
        "<text x='250 40 70 100' y='100' style='font-size:37; font-family:Courier new, Courier; font-weight: bold; " +
        "font-style=oblique; stroke: black; stoke-width:1; fill:none'transform='rotate(-20, 250, 100)'>" +
        fingerprint[3] +"</text>" +
        "<filter id='filter1' filerUnits='objectBoundingBox' x='0%' width='100%' height='100%'><feTurbulence type='turbulence' baseFrequency='0.03' numOctaves='5' />" +
        "</filter><rect x='100' y='50' width='250' height='100' filter='url(#filter1)' />" +
        "<line x1='100' y1='50' x2='290' y2='50' /><line x1='100' y1='120' x2='290' y2='120' /><line x1='100' y1='50' x2='100' y2='120' /><line x1='290' y1='50' x2='290' y2='120' />g>";

    return svg;
};

/**
 * Function which check if the user input is equal to the fingerprint
 * @param input
 * @returns {boolean}
 */
Captcha.prototype.checkCaptcha = function(input){
    var that = this;
    var inputAsHash = CryptoJS.MD5(input);
    return inputAsHash.toString() === that.fingerprintHash.toString();
};
<|endoftext|>


<commit_before>import delegate from 'delegate-to'
import s from './multi-select.scss'
import Label from '../label'
import Loading from '../loading'

const MultiSelect = {
  props: {
    options: {
      type: Array,
      required: true
    },

    selectedOpts: {
      type: Array,
      required: true
    },

    displayKey: {
      type: String,
      required: true
    },

    isOpened: {
      type: Boolean,
      default: false,
    },

    isDisabled: {
      type: Boolean,
      default: false
    },

    caseInsensitive: {
      type: Boolean,
      default: false
    },

    // apiMode: {
    //   type: Boolean,
    //   default: false
    // },

    loading: {
      type: Boolean,
      default: false
    },

    loadingMsg: {
      type: String,
      default: 'loading...'
    }
  },

  data() {
    return {
      labels: [],
      opened: this.isOpened,
      disable: this.isDisabled,
      loadingPie: this.loading,
      displayIdx: [],
      optionsHovered: false,
      disablePointer: false,
      inputWidth: 1,
      focusedIdx: -1,
      focusedLabelIdx: -1,
      selectedIdx: this.selectedOpts
    }
  },

  mounted() {
    if (this.options.length) {
      this.displayIdx = [...Array(this.options.length).keys()]
      this.labels = this.selectedIdx.reduce((preVal, curVal, idx) => {
        preVal.push(this.options[curVal])
        return preVal
      }, [])
    }
  },

  watch: {
    selectedOpts(newVal) {
      this.selectedIdx = newVal
    },
    options(newVal) {
      this.displayIdx = [...Array(newVal.length).keys()]
      this.labels = this.selectedIdx.reduce((preVal, curVal, idx) => {
        preVal.push(newVal[curVal])
        return preVal
      }, [])
    },
    selectedIdx(newVal) {
      this.labels = newVal.reduce((preVal, curVal, idx) => {
        preVal.push(this.options[curVal])
        return preVal
      }, [])
    },
    isOpened(newVal) {
      this.opened = newVal
    },
    isDisabled(newVal) {
      this.disable = newVal
    },
    loading(newVal) {
      this.loadingPie = newVal
    }
  },

  computed: {
    renderOptions() {
      const { options, displayIdx, selectedIdx, displayKey, disablePointer } = this
      const h = this.$createElement

      const _options = options.reduce((preVal, newVal, idx) => {
        const toDisplay = displayIdx.indexOf(idx)
        const isSelected = selectedIdx.indexOf(idx)

        const classes = {
          [s.option]: true,
          [s.selected]: (isSelected >= 0),
          [s.focused]: (idx === this.focusedIdx),
          [s.disablePointer]: disablePointer
        }

        if (toDisplay >= 0) {
          preVal.push(

              {newVal[displayKey]}

          )
        }
        return preVal
      }, [])

      return _options
    },

    renderLoading() {
      const { style, loadingMsg } = this
      const h = this.$createElement
      const _loadingOption =
<Loading typ="pie" size={10} class={[s.loading]} />{loadingMsg}

      return _loadingOption
    },

    css() {
      const { opened, disable } = this
      const style = {
        [s.selectOpen]: opened,
        [s.disabled]: disable
      }
      return style
    },

  },

  methods: {
    outputResult() {
      const result = this.selectedIdx.reduce((preVal, curVal, idx) => {
        preVal.push(this.options[curVal])
        return preVal
      }, [])
      this.$emit('change', result)
    },

    close() {
      const { optionsHovered } = this
      this.opened = (this.optionsHovered) ? true : false
      this.focusedIdx = -1
      this.focusedLabelIdx = -1
    },

    handleMouseEnter(e) {
      if (e.type === 'mouseenter') {
        this.optionsHovered = true
      } else if (e.type === 'mouseleave') {
        this.optionsHovered = false
      }
    },

    toggleMenu(ev) {
      ev.stopPropagation()
      if (this.disable) {
        return
      }

      this.opened = !this.opened

      if (this.opened) {
        this.$refs.searchField.focus()
      }
    },

    handleInput(e) {
      const { displayKey, caseInsensitive } = this

      // if (this.apiMode) {
      //   this.$emit('inputChange', this.$refs.searchField.value)
      // }

      const searchResult = (caseInsensitive)
      ? this.options.reduce((preVal, newVal, idx) => {
        if (newVal[displayKey].toLowerCase().includes(this.$refs.searchField.value.trim().toLowerCase())) {
          preVal.push(idx)
        }
        return preVal
      }, [])
      : this.options.reduce((preVal, newVal, idx) => {
        if (newVal[displayKey].includes(this.$refs.searchField.value.trim())) {
          preVal.push(idx)
        }
        return preVal
      }, [])

      this.displayIdx = searchResult
      this.inputWidth = (this.$refs.searchField.value.length) ? (this.$refs.searchField.value.length) : 1
      this.opened = true
    },

    handleInputKeyDown(e) {
      this.optionsHovered = true
      this.disablePointer = true

      if (e.keyCode === 38) {//arrowUp
        this.focusAdjacentOption(this.focusedIdx, 'pre')
      } else if (e.keyCode === 40) { //arrowDown
        this.focusAdjacentOption(this.focusedIdx, 'next')
      } else if (e.keyCode === 13) { //ENTER
        const { selectedIdx } = this
        const idx = selectedIdx.indexOf(this.focusedIdx)
        if (idx >= 0) { //unselect
          this.selectedIdx.splice(idx, 1)
          this.outputResult()
        } else { //select
          this.selectedIdx.push(this.focusedIdx)
          this.outputResult()
        }
      } else if (e.keyCode === 8 && this.$refs.searchField.value.length <= 0) { //backspace delete
        const selectedArrLen = this.selectedIdx.length

        if (this.focusedLabelIdx === -1) { //empty input and focus on last label
          this.focusedLabelIdx = selectedArrLen - 1
        } else if (this.focusedLabelIdx > -1) {//delete the focused label
          this.selectedIdx.pop()
          this.focusedLabelIdx = -1
        }
      }
    },

    _handleOnChange: delegate('[data-role="select-option"]', function(ev) {
      const { delegateTarget } = ev
      delegateTarget.focus()
      const { selectedIdx } = this
      const changedId = delegateTarget.getAttribute('data-idx')
      const idx = selectedIdx.indexOf(+changedId)
      if (idx >= 0) { //unselect
        this.selectedIdx.splice(idx, 1)
        this.outputResult()
      } else { //select
        this.selectedIdx.push(+changedId)
        this.outputResult()
      }

      this.$refs.searchField.focus()
    }),

    _handleMouseOver: delegate('[data-role="select-option"]', function(ev) {
      const { delegateTarget } = ev
      const hoveredId = delegateTarget.getAttribute('data-idx')
      if (ev.type === 'mouseover') {
        this.optionsHovered = true
      }

      this.focusedIdx = +hoveredId
    }),

    handleMouseMove(ev) {
      this.disablePointer = false
    },

    handleLabelRemove(data) { //unselect
      const idxToRemove = this.options.indexOf(data)
      const idx = this.selectedIdx.indexOf(idxToRemove)
      this.selectedIdx.splice(idx, 1)
      this.outputResult()
      this.$refs.searchField.focus()
    },

    focusAdjacentOption(curDataIdx, dir) {
      let targetDataIdx
      const { displayIdx } = this
      const idxOfCurDataIdx = this.displayIdx.indexOf(curDataIdx)
      const displayArrLength = this.displayIdx.length

      if (curDataIdx === -1) { //current not focus on optionBox
        curDataIdx = displayIdx[0]
        targetDataIdx = displayIdx[0]
      } else {
        switch (dir) {
        case 'next':
          targetDataIdx = ((idxOfCurDataIdx + 1) >= displayArrLength) ? displayIdx[(idxOfCurDataIdx + 1) % displayArrLength] : displayIdx[idxOfCurDataIdx + 1]
          break
        case 'pre':
          targetDataIdx = ((idxOfCurDataIdx - 1) < 0) ? displayIdx[displayArrLength + idxOfCurDataIdx - 1] : displayIdx[idxOfCurDataIdx - 1]
          break
        }
      }

      const currentTarget = this.$el.querySelector(`[data-role="select-option"][data-idx="${curDataIdx}"]`)
      const focusTarget = this.$el.querySelector(`[data-role="select-option"][data-idx="${targetDataIdx}"]`)

      focusTarget.focus()
      this.$refs.searchField.focus()

      this.focusedIdx = targetDataIdx
      this.optionsHovered = false
    },

  },

  render(h) {
    const { css, close, toggleMenu, renderOptions, _handleMouseOver, handleMouseEnter, handleMouseMove,
            _handleOnChange, handleLabelRemove, handleInputKeyDown,
            labels, inputWidth, disable } = this
    return (

          <input class={[s.invisibleInput]}
                 size={inputWidth}
                 ref="searchField"
                 onInput={this.handleInput}
                 onBlur={close}
                 on-keydown={this.handleInputKeyDown}
                 disabled={disable}
          >
          </input>

          {
            (this.loadingPie)
            ? this.renderLoading
            : ''
          }
          {renderOptions}

    )
  }
}

module.exports = MultiSelect
<commit_msg>[MultiSelect] make `displayKey` for  customizable<commit_after>import delegate from 'delegate-to'
import s from './multi-select.scss'
import Label from '../label'
import Loading from '../loading'

const MultiSelect = {
  props: {
    options: {
      type: Array,
      required: true
    },

    selectedOpts: {
      type: Array,
      required: true
    },

    displayKey: {
      type: String,
      required: true
    },

    isOpened: {
      type: Boolean,
      default: false,
    },

    isDisabled: {
      type: Boolean,
      default: false
    },

    caseInsensitive: {
      type: Boolean,
      default: false
    },

    // apiMode: {
    //   type: Boolean,
    //   default: false
    // },

    loading: {
      type: Boolean,
      default: false
    },

    loadingMsg: {
      type: String,
      default: 'loading...'
    }
  },

  data() {
    return {
      labels: [],
      opened: this.isOpened,
      disable: this.isDisabled,
      loadingPie: this.loading,
      displayIdx: [],
      optionsHovered: false,
      disablePointer: false,
      inputWidth: 1,
      focusedIdx: -1,
      focusedLabelIdx: -1,
      selectedIdx: this.selectedOpts
    }
  },

  mounted() {
    if (this.options.length) {
      this.displayIdx = [...Array(this.options.length).keys()]
      this.labels = this.selectedIdx.reduce((preVal, curVal, idx) => {
        preVal.push(this.options[curVal])
        return preVal
      }, [])
    }
  },

  watch: {
    selectedOpts(newVal) {
      this.selectedIdx = newVal
    },
    options(newVal) {
      this.displayIdx = [...Array(newVal.length).keys()]
      this.labels = this.selectedIdx.reduce((preVal, curVal, idx) => {
        preVal.push(newVal[curVal])
        return preVal
      }, [])
    },
    selectedIdx(newVal) {
      this.labels = newVal.reduce((preVal, curVal, idx) => {
        preVal.push(this.options[curVal])
        return preVal
      }, [])
    },
    isOpened(newVal) {
      this.opened = newVal
    },
    isDisabled(newVal) {
      this.disable = newVal
    },
    loading(newVal) {
      this.loadingPie = newVal
    }
  },

  computed: {
    renderOptions() {
      const { options, displayIdx, selectedIdx, displayKey, disablePointer } = this
      const h = this.$createElement

      const _options = options.reduce((preVal, newVal, idx) => {
        const toDisplay = displayIdx.indexOf(idx)
        const isSelected = selectedIdx.indexOf(idx)

        const classes = {
          [s.option]: true,
          [s.selected]: (isSelected >= 0),
          [s.focused]: (idx === this.focusedIdx),
          [s.disablePointer]: disablePointer
        }

        if (toDisplay >= 0) {
          preVal.push(

              {newVal[displayKey]}

          )
        }
        return preVal
      }, [])

      return _options
    },

    renderLoading() {
      const { style, loadingMsg } = this
      const h = this.$createElement
      const _loadingOption =
<Loading typ="pie" size={10} class={[s.loading]} />{loadingMsg}

      return _loadingOption
    },

    css() {
      const { opened, disable } = this
      const style = {
        [s.selectOpen]: opened,
        [s.disabled]: disable
      }
      return style
    },

  },

  methods: {
    outputResult() {
      const result = this.selectedIdx.reduce((preVal, curVal, idx) => {
        preVal.push(this.options[curVal])
        return preVal
      }, [])
      this.$emit('change', result)
    },

    close() {
      const { optionsHovered } = this
      this.opened = (this.optionsHovered) ? true : false
      this.focusedIdx = -1
      this.focusedLabelIdx = -1
    },

    handleMouseEnter(e) {
      if (e.type === 'mouseenter') {
        this.optionsHovered = true
      } else if (e.type === 'mouseleave') {
        this.optionsHovered = false
      }
    },

    toggleMenu(ev) {
      ev.stopPropagation()
      if (this.disable) {
        return
      }

      this.opened = !this.opened

      if (this.opened) {
        this.$refs.searchField.focus()
      }
    },

    handleInput(e) {
      const { displayKey, caseInsensitive } = this

      // if (this.apiMode) {
      //   this.$emit('inputChange', this.$refs.searchField.value)
      // }

      const searchResult = (caseInsensitive)
      ? this.options.reduce((preVal, newVal, idx) => {
        if (newVal[displayKey].toLowerCase().includes(this.$refs.searchField.value.trim().toLowerCase())) {
          preVal.push(idx)
        }
        return preVal
      }, [])
      : this.options.reduce((preVal, newVal, idx) => {
        if (newVal[displayKey].includes(this.$refs.searchField.value.trim())) {
          preVal.push(idx)
        }
        return preVal
      }, [])

      this.displayIdx = searchResult
      this.inputWidth = (this.$refs.searchField.value.length) ? (this.$refs.searchField.value.length) : 1
      this.opened = true
    },

    handleInputKeyDown(e) {
      this.optionsHovered = true
      this.disablePointer = true

      if (e.keyCode === 38) {//arrowUp
        this.focusAdjacentOption(this.focusedIdx, 'pre')
      } else if (e.keyCode === 40) { //arrowDown
        this.focusAdjacentOption(this.focusedIdx, 'next')
      } else if (e.keyCode === 13) { //ENTER
        const { selectedIdx } = this
        const idx = selectedIdx.indexOf(this.focusedIdx)
        if (idx >= 0) { //unselect
          this.selectedIdx.splice(idx, 1)
          this.outputResult()
        } else { //select
          this.selectedIdx.push(this.focusedIdx)
          this.outputResult()
        }
      } else if (e.keyCode === 8 && this.$refs.searchField.value.length <= 0) { //backspace delete
        const selectedArrLen = this.selectedIdx.length

        if (this.focusedLabelIdx === -1) { //empty input and focus on last label
          this.focusedLabelIdx = selectedArrLen - 1
        } else if (this.focusedLabelIdx > -1) {//delete the focused label
          this.selectedIdx.pop()
          this.focusedLabelIdx = -1
        }
      }
    },

    _handleOnChange: delegate('[data-role="select-option"]', function(ev) {
      const { delegateTarget } = ev
      delegateTarget.focus()
      const { selectedIdx } = this
      const changedId = delegateTarget.getAttribute('data-idx')
      const idx = selectedIdx.indexOf(+changedId)
      if (idx >= 0) { //unselect
        this.selectedIdx.splice(idx, 1)
        this.outputResult()
      } else { //select
        this.selectedIdx.push(+changedId)
        this.outputResult()
      }

      this.$refs.searchField.focus()
    }),

    _handleMouseOver: delegate('[data-role="select-option"]', function(ev) {
      const { delegateTarget } = ev
      const hoveredId = delegateTarget.getAttribute('data-idx')
      if (ev.type === 'mouseover') {
        this.optionsHovered = true
      }

      this.focusedIdx = +hoveredId
    }),

    handleMouseMove(ev) {
      this.disablePointer = false
    },

    handleLabelRemove(data) { //unselect
      const idxToRemove = this.options.indexOf(data)
      const idx = this.selectedIdx.indexOf(idxToRemove)
      this.selectedIdx.splice(idx, 1)
      this.outputResult()
      this.$refs.searchField.focus()
    },

    focusAdjacentOption(curDataIdx, dir) {
      let targetDataIdx
      const { displayIdx } = this
      const idxOfCurDataIdx = this.displayIdx.indexOf(curDataIdx)
      const displayArrLength = this.displayIdx.length

      if (curDataIdx === -1) { //current not focus on optionBox
        curDataIdx = displayIdx[0]
        targetDataIdx = displayIdx[0]
      } else {
        switch (dir) {
        case 'next':
          targetDataIdx = ((idxOfCurDataIdx + 1) >= displayArrLength) ? displayIdx[(idxOfCurDataIdx + 1) % displayArrLength] : displayIdx[idxOfCurDataIdx + 1]
          break
        case 'pre':
          targetDataIdx = ((idxOfCurDataIdx - 1) < 0) ? displayIdx[displayArrLength + idxOfCurDataIdx - 1] : displayIdx[idxOfCurDataIdx - 1]
          break
        }
      }

      const currentTarget = this.$el.querySelector(`[data-role="select-option"][data-idx="${curDataIdx}"]`)
      const focusTarget = this.$el.querySelector(`[data-role="select-option"][data-idx="${targetDataIdx}"]`)

      focusTarget.focus()
      this.$refs.searchField.focus()

      this.focusedIdx = targetDataIdx
      this.optionsHovered = false
    },

  },

  render(h) {
    const { css, close, toggleMenu, renderOptions, _handleMouseOver, handleMouseEnter, handleMouseMove,
            _handleOnChange, handleLabelRemove, handleInputKeyDown,
            labels, inputWidth, disable, displayKey } = this
    return (

          <input class={[s.invisibleInput]}
                 size={inputWidth}
                 ref="searchField"
                 onInput={this.handleInput}
                 onBlur={close}
                 on-keydown={this.handleInputKeyDown}
                 disabled={disable}
          >
          </input>

          {
            (this.loadingPie)
            ? this.renderLoading
            : ''
          }
          {renderOptions}

    )
  }
}

module.exports = MultiSelect
<|endoftext|>


<commit_before>// GLOBAL VARIABLES
var GRID_SIZE = 5;
var SOLUTION_LENGTH = 1;
var DIR_PROB = 0.25;

var startingGrid;
var solution;
var grid;
var cookies = ["bestScore", "tutorialFinished"];

var GAME_MODE = (window.location.href.indexOf("tutorial") >= 0) ? "tutorial" : "game";

// Define some objects
function Tile(x_, y_) {
	var tile = {
		pos: {
			x: x_,
			y: y_
		},
		on: true,
		up: false,
		right: false,
		down: false,
		left: false,
	};

	return tile;
}

// Generate a random grid
function Grid(size, prob) {
	prob = (prob == undefined ? DIR_PROB : prob);

	// Initialize a random directed graph
	var tiles = [];

	for(var x = 0; x < size; x++) {
		tiles[x] = [];

		for(var y = 0; y < size; y++) {
			var tile = Tile(x, y);

			var tileUp = y > 0;
			var tileDown = y < size - 1;
			var tileLeft = x > 0;
			var tileRight = x < size - 1;

			// Randomize directions
			// Tiles can only control the lights on the left/right OR up/down
			// This is exclusively for design reasons. Too many different directions
			// looks cluttered.
			if(Math.random() < 0.5) {
				if(tileLeft && Math.random() < prob)
					tile.left = true;

				if(tileRight && Math.random() < prob)
					tile.right = true;
			} else {
				if(tileUp && Math.random() < prob)
					tile.up = true;

				if(tileDown && Math.random() < prob)
					tile.down = true;
			}

			// If we didn't assign any directions, then it's a normal lights and
			// controls all 4 adjacent tiles
			if(!tile.up && !tile.down && !tile.left && !tile.right) {
				tile.up = tileUp;
				tile.right = tileRight;
				tile.down = tileDown;
				tile.left = tileLeft;
			}

			tiles[x][y] = tile;
		}
	}

	var grid = {
		tiles: tiles,
		size: size
	};

	return grid;
}

// Utility functions
function isSolution(solution) {
	var gridCopy = copyGrid(grid);

	for(var s = 0; s < solution.length; s++) {
		var x = solution[s].x;
		var y = solution[s].y;
		toggle(gridCopy, x, y);
	}
	return isSolved(gridCopy);
}

function isSolved(grid) {
	for(var x = 0; x < grid.size; x++) {
		for(var y = 0; y < grid.size; y++) {
			if(!grid.tiles[x][y].on) return false;
		}
	}

	return true;
}

function isDirected(tile, size) {
	var tileUp = tile.pos.y > 0;
	var tileDown = tile.pos.y < size - 1;
	var tileLeft = tile.pos.x > 0;
	var tileRight = tile.pos.x < size - 1;

	var undirected = (tileUp) ? tile.up : true;
	undirected = undirected && ((tileRight) ? tile.right : true);
	undirected = undirected && ((tileDown) ? tile.down : true);
	undirected = undirected && ((tileLeft) ? tile.left : true);

	return !undirected;
}

function getBestScore() {
	var bestScore = document.getElementById("best-score");
	var score = parseInt(bestScore.innerHTML.substr(18));
	return score;

}

function setBestScore(score) {
	var bestScore = document.getElementById("best-score");
	bestScore.innerHTML = "Best:" + score;
}

function getNowScore() {
	var nowScore = document.getElementById("now-score");
	var score = parseInt(nowScore.innerHTML.substr(17));
	return score;
}

function setNowScore(score) {
	var nowScore = document.getElementById("now-score");
	nowScore.innerHTML = "Now:" + score;
}

function copyGrid(grid) {
	// This is dirty
	return JSON.parse(JSON.stringify(grid));
}

// COOOOOOOOOKIE crisp
function getCookie(cookieStr) {
	// Assuming we only store one cookie
	var ca = document.cookie.split(";");
	var name = cookieStr + "=";
	for(var i = 0; i < ca.length; i++) {
		var c = ca[i];
		while(c.charAt(0) == ' ') c = c.substring(1);
		if(c.indexOf(name) == 0)  {
			var value = c.substr(name.length);
			return value;
		}
	}

	return undefined;
}

function setCookie(name, value) {
	var d = new Date();
	d.setTime(d.getTime() + (100 * 24 * 60 * 60 * 1000));
	document.cookie = name + "=" + value + "; expires=" + d.toUTCString();
}

function refreshCookie(name) {
	var value = getCookie(name);
	if(value !== undefined)
		setCookie(name, value);
}

function listenForClicks(grid) {
	var tiles = document.getElementsByClassName("tile");
	for(var t = 0; t < tiles.length; t++) {
		var tileDiv = tiles[t];

		// We remove it to make sure we only have 1 event listener at a time
		tileDiv.removeEventListener("click", handleClick, false);
		tileDiv.addEventListener("click", handleClick, false);
	}
}

function handleClick(evt) {
	ga('send', 'event', GAME_MODE, 'tile_click');
	var tileDiv = evt.target;
	var id = tileDiv.id;
	var index = parseInt(id.substr(1));

	var x = index % grid.size;
	var y = (index - x) / grid.size;
	toggle(grid, x, y);
	displayGrid(grid);

	// Update score
	setNowScore(getNowScore() + 1);

	if(isSolved(grid)) {
		// Delayed to let animations finish
		setTimeout(function() {
			handleWin();
		}, 200);
	}
}

function toggle(grid, x, y) {
	if(x < 0 || x >= grid.size || y < 0 || y >= grid.size) {
		console.log("ERROR: Coordinates (" + x + ", " + y + ") out of bounds!");
		return;
	}

	var tile = grid.tiles[x][y];
	tile.on = !tile.on;

	if(tile.left) {
		var left = grid.tiles[x-1][y];
		left.on = !left.on;
	}

	if(tile.right) {
		var right = grid.tiles[x+1][y];
		right.on = !right.on;
	}

	if(tile.up) {
		var up = grid.tiles[x][y-1];
		up.on = !up.on;
	}

	if(tile.down) {
		var down = grid.tiles[x][y+1];
		down.on = !down.on;
	}
}

function unsolve(grid, numMoves) {
	var moves = [];
	var choices = [];

	for(var c = 0; c < GRID_SIZE * GRID_SIZE; c++)
		choices.push(c);

	for(var m = 0; m < numMoves; m++) {
		// Generate random place
		var index = Math.floor(Math.random() * (GRID_SIZE * GRID_SIZE - m));
		var tile = choices.splice(index, 1);
		var x = tile % GRID_SIZE;
		var y = (tile - x) / GRID_SIZE;
		toggle(grid, x, y);

		moves[numMoves - m - 1] = {x: x, y: y};
	}

	return moves;
}

function displayGrid() {
	var tiles = document.getElementsByClassName('tile');

	for(var t = 0; t < tiles.length; t++) {
		var x = t % grid.size;
		var y = (t - x) / grid.size;

		var tile = grid.tiles[x][y];
		var div = tiles[t];
		div.className = "tile";
		div.innerHTML = "";

		if(tile.on)
			div.className += " on";

		// Determine if this is a directed tile or not (edges make this tricky)
		if(!isDirected(tile, grid.size)) continue;

		if(tile.left)
			div.className += " left";
		if(tile.right)
			div.className += " right";
		if(tile.up)
			div.className += " up";
		if(tile.down)
			div.className += " down";
	}
}
function reset(oldGrid, newGrid) {
	grid = newGrid;

	if(grid == undefined) {
		grid = Grid(GRID_SIZE);
		solution = unsolve(grid, SOLUTION_LENGTH);

		// Sanity check
		if(!isSolution(solution)) console.log("Error! Bad solution!");
	}

	var tileDivs = document.getElementsByClassName('tile');

	for(var t = 0; t < tileDivs.length; t++) {
		var x = t % grid.size;
		var y = (t - x) / grid.size;

		if(oldGrid === undefined || !oldGrid.tiles[x][y].on) {
			tileDivs[t].className += " on";
		}
	}

	setNowScore(0);
	setTimeout(displayGrid.bind(this, grid), 200);
	startingGrid = copyGrid(grid);
	handleReset();
	return grid;
}

function offon(tile) {
	tile.className = "tile";

	setTimeout(function() {
		tile.className = "tile on";
	}, 200);
}

function fancyFlashEffect() {
	var tileDivs = document.getElementsByClassName("tile");

	for(var y = 0; y < 4*GRID_SIZE; y++) {
		(function(y) {
			setTimeout(function() {
				var boundedY = y % (2 * GRID_SIZE);
				for(var ty = boundedY; ty >= 0; ty--) {
					var tx = boundedY - ty;
					var index = ty * GRID_SIZE + tx;

					if(index >= 0 && index < GRID_SIZE * GRID_SIZE - 1)
						offon(tileDivs[index]);
				}
			}, y * 50);
		})(y);
	}
}

function offon(tile) {
	tile.className = "tile";

	setTimeout(function() {
		tile.className = "tile on";
	}, 200);
}

function fancyFlashEffect() {
	var tileDivs = document.getElementsByClassName("tile");

	for(var y = 0; y < 4*GRID_SIZE; y++) {
		(function(y) {
			setTimeout(function() {
				var boundedY = y % (2 * GRID_SIZE);
				for(var ty = boundedY; ty >= 0; ty--) {
					var tx = boundedY - ty;
					var index = ty * GRID_SIZE + tx;

					if(index >= 0 && index < GRID_SIZE * GRID_SIZE - 1)
						offon(tileDivs[index]);
				}
			}, y * 50);
		})(y);
	}
}

<commit_msg>Fixed solution length<commit_after>// GLOBAL VARIABLES
var GRID_SIZE = 5;
var SOLUTION_LENGTH = 17;
var DIR_PROB = 0.25;

var startingGrid;
var solution;
var grid;
var cookies = ["bestScore", "tutorialFinished"];

var GAME_MODE = (window.location.href.indexOf("tutorial") >= 0) ? "tutorial" : "game";

// Define some objects
function Tile(x_, y_) {
	var tile = {
		pos: {
			x: x_,
			y: y_
		},
		on: true,
		up: false,
		right: false,
		down: false,
		left: false,
	};

	return tile;
}

// Generate a random grid
function Grid(size, prob) {
	prob = (prob == undefined ? DIR_PROB : prob);

	// Initialize a random directed graph
	var tiles = [];

	for(var x = 0; x < size; x++) {
		tiles[x] = [];

		for(var y = 0; y < size; y++) {
			var tile = Tile(x, y);

			var tileUp = y > 0;
			var tileDown = y < size - 1;
			var tileLeft = x > 0;
			var tileRight = x < size - 1;

			// Randomize directions
			// Tiles can only control the lights on the left/right OR up/down
			// This is exclusively for design reasons. Too many different directions
			// looks cluttered.
			if(Math.random() < 0.5) {
				if(tileLeft && Math.random() < prob)
					tile.left = true;

				if(tileRight && Math.random() < prob)
					tile.right = true;
			} else {
				if(tileUp && Math.random() < prob)
					tile.up = true;

				if(tileDown && Math.random() < prob)
					tile.down = true;
			}

			// If we didn't assign any directions, then it's a normal lights and
			// controls all 4 adjacent tiles
			if(!tile.up && !tile.down && !tile.left && !tile.right) {
				tile.up = tileUp;
				tile.right = tileRight;
				tile.down = tileDown;
				tile.left = tileLeft;
			}

			tiles[x][y] = tile;
		}
	}

	var grid = {
		tiles: tiles,
		size: size
	};

	return grid;
}

// Utility functions
function isSolution(solution) {
	var gridCopy = copyGrid(grid);

	for(var s = 0; s < solution.length; s++) {
		var x = solution[s].x;
		var y = solution[s].y;
		toggle(gridCopy, x, y);
	}
	return isSolved(gridCopy);
}

function isSolved(grid) {
	for(var x = 0; x < grid.size; x++) {
		for(var y = 0; y < grid.size; y++) {
			if(!grid.tiles[x][y].on) return false;
		}
	}

	return true;
}

function isDirected(tile, size) {
	var tileUp = tile.pos.y > 0;
	var tileDown = tile.pos.y < size - 1;
	var tileLeft = tile.pos.x > 0;
	var tileRight = tile.pos.x < size - 1;

	var undirected = (tileUp) ? tile.up : true;
	undirected = undirected && ((tileRight) ? tile.right : true);
	undirected = undirected && ((tileDown) ? tile.down : true);
	undirected = undirected && ((tileLeft) ? tile.left : true);

	return !undirected;
}

function getBestScore() {
	var bestScore = document.getElementById("best-score");
	var score = parseInt(bestScore.innerHTML.substr(18));
	return score;

}

function setBestScore(score) {
	var bestScore = document.getElementById("best-score");
	bestScore.innerHTML = "Best:" + score;
}

function getNowScore() {
	var nowScore = document.getElementById("now-score");
	var score = parseInt(nowScore.innerHTML.substr(17));
	return score;
}

function setNowScore(score) {
	var nowScore = document.getElementById("now-score");
	nowScore.innerHTML = "Now:" + score;
}

function copyGrid(grid) {
	// This is dirty
	return JSON.parse(JSON.stringify(grid));
}

// COOOOOOOOOKIE crisp
function getCookie(cookieStr) {
	// Assuming we only store one cookie
	var ca = document.cookie.split(";");
	var name = cookieStr + "=";
	for(var i = 0; i < ca.length; i++) {
		var c = ca[i];
		while(c.charAt(0) == ' ') c = c.substring(1);
		if(c.indexOf(name) == 0)  {
			var value = c.substr(name.length);
			return value;
		}
	}

	return undefined;
}

function setCookie(name, value) {
	var d = new Date();
	d.setTime(d.getTime() + (100 * 24 * 60 * 60 * 1000));
	document.cookie = name + "=" + value + "; expires=" + d.toUTCString();
}

function refreshCookie(name) {
	var value = getCookie(name);
	if(value !== undefined)
		setCookie(name, value);
}

function listenForClicks(grid) {
	var tiles = document.getElementsByClassName("tile");
	for(var t = 0; t < tiles.length; t++) {
		var tileDiv = tiles[t];

		// We remove it to make sure we only have 1 event listener at a time
		tileDiv.removeEventListener("click", handleClick, false);
		tileDiv.addEventListener("click", handleClick, false);
	}
}

function handleClick(evt) {
	ga('send', 'event', GAME_MODE, 'tile_click');
	var tileDiv = evt.target;
	var id = tileDiv.id;
	var index = parseInt(id.substr(1));

	var x = index % grid.size;
	var y = (index - x) / grid.size;
	toggle(grid, x, y);
	displayGrid(grid);

	// Update score
	setNowScore(getNowScore() + 1);

	if(isSolved(grid)) {
		// Delayed to let animations finish
		setTimeout(function() {
			handleWin();
		}, 200);
	}
}

function toggle(grid, x, y) {
	if(x < 0 || x >= grid.size || y < 0 || y >= grid.size) {
		console.log("ERROR: Coordinates (" + x + ", " + y + ") out of bounds!");
		return;
	}

	var tile = grid.tiles[x][y];
	tile.on = !tile.on;

	if(tile.left) {
		var left = grid.tiles[x-1][y];
		left.on = !left.on;
	}

	if(tile.right) {
		var right = grid.tiles[x+1][y];
		right.on = !right.on;
	}

	if(tile.up) {
		var up = grid.tiles[x][y-1];
		up.on = !up.on;
	}

	if(tile.down) {
		var down = grid.tiles[x][y+1];
		down.on = !down.on;
	}
}

function unsolve(grid, numMoves) {
	var moves = [];
	var choices = [];

	for(var c = 0; c < GRID_SIZE * GRID_SIZE; c++)
		choices.push(c);

	for(var m = 0; m < numMoves; m++) {
		// Generate random place
		var index = Math.floor(Math.random() * (GRID_SIZE * GRID_SIZE - m));
		var tile = choices.splice(index, 1);
		var x = tile % GRID_SIZE;
		var y = (tile - x) / GRID_SIZE;
		toggle(grid, x, y);

		moves[numMoves - m - 1] = {x: x, y: y};
	}

	return moves;
}

function displayGrid() {
	var tiles = document.getElementsByClassName('tile');

	for(var t = 0; t < tiles.length; t++) {
		var x = t % grid.size;
		var y = (t - x) / grid.size;

		var tile = grid.tiles[x][y];
		var div = tiles[t];
		div.className = "tile";
		div.innerHTML = "";

		if(tile.on)
			div.className += " on";

		// Determine if this is a directed tile or not (edges make this tricky)
		if(!isDirected(tile, grid.size)) continue;

		if(tile.left)
			div.className += " left";
		if(tile.right)
			div.className += " right";
		if(tile.up)
			div.className += " up";
		if(tile.down)
			div.className += " down";
	}
}
function reset(oldGrid, newGrid) {
	grid = newGrid;

	if(grid == undefined) {
		grid = Grid(GRID_SIZE);
		solution = unsolve(grid, SOLUTION_LENGTH);

		// Sanity check
		if(!isSolution(solution)) console.log("Error! Bad solution!");
	}

	var tileDivs = document.getElementsByClassName('tile');

	for(var t = 0; t < tileDivs.length; t++) {
		var x = t % grid.size;
		var y = (t - x) / grid.size;

		if(oldGrid === undefined || !oldGrid.tiles[x][y].on) {
			tileDivs[t].className += " on";
		}
	}

	setNowScore(0);
	setTimeout(displayGrid.bind(this, grid), 200);
	startingGrid = copyGrid(grid);
	handleReset();
	return grid;
}

function offon(tile) {
	tile.className = "tile";

	setTimeout(function() {
		tile.className = "tile on";
	}, 200);
}

function fancyFlashEffect() {
	var tileDivs = document.getElementsByClassName("tile");

	for(var y = 0; y < 4*GRID_SIZE; y++) {
		(function(y) {
			setTimeout(function() {
				var boundedY = y % (2 * GRID_SIZE);
				for(var ty = boundedY; ty >= 0; ty--) {
					var tx = boundedY - ty;
					var index = ty * GRID_SIZE + tx;

					if(index >= 0 && index < GRID_SIZE * GRID_SIZE - 1)
						offon(tileDivs[index]);
				}
			}, y * 50);
		})(y);
	}
}

function offon(tile) {
	tile.className = "tile";

	setTimeout(function() {
		tile.className = "tile on";
	}, 200);
}

function fancyFlashEffect() {
	var tileDivs = document.getElementsByClassName("tile");

	for(var y = 0; y < 4*GRID_SIZE; y++) {
		(function(y) {
			setTimeout(function() {
				var boundedY = y % (2 * GRID_SIZE);
				for(var ty = boundedY; ty >= 0; ty--) {
					var tx = boundedY - ty;
					var index = ty * GRID_SIZE + tx;

					if(index >= 0 && index < GRID_SIZE * GRID_SIZE - 1)
						offon(tileDivs[index]);
				}
			}, y * 50);
		})(y);
	}
}

<|endoftext|>


<commit_before>var CORS_PROXY_URL = "https://cors-anywhere.herokuapp.com/";
var CHECK_DOWNLOADS_FINISHED_EVERY_MS = 250;

var checkFinishedInterval;
var downloadRequests = new Set();
var downloadedCount;
var toDownloadCount;
var subName;

var zip = new JSZip();

$("#downloadButton").click(function() {
    /* Reset states */
    downloadRequests.clear();
    downloadedCount = 0;
    toDownloadCount = 0;
    subName = $("#subNameInput").val();
    updateCancelButton();

    /* Hide the download button */
    $("#downloadButton").hide();
    $("#cancelButton").show();

    /* Find images to scrape and start downloading */
    startDownloading();
});

function startDownloading() {
    $.ajax({
        url: CORS_PROXY_URL + "https://www.reddit.com/r/" + subName + "/hot.json",
        type: "GET",
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        success: function(result) {
            var children = result.data.children;

            if (children.length > 0) {
                for (var i = 0; i < Math.min(children.length, $("#maxImageCountInput").val()); i++) {
                    var post = children[i].data;

                    if (post.preview !== undefined && post.preview.images.length > 0) {
                        var url = post.preview.images[0].source.url;

                        if (url.indexOf(".jpg?") !== -1) {
                            if (url.startsWith("http:")) {
                                url = url.replace("http:", "https:");
                            }

                            toDownloadCount++;

                            downloadImageAsBase64(url, function(url, data) {
                                zip.file(url.replace(/(.+\/)/, "").replace(/(\?.+)/, ""), data, { base64: true });
                                downloadedCount++;
                                updateCancelButton();
                            });
                        }
                    }
                }

                checkFinishedInterval = setInterval(function() {
                    if (downloadedCount == toDownloadCount) {
                        doneDownloading();
                    }
                }, CHECK_DOWNLOADS_FINISHED_EVERY_MS);
            }
        },
        error: function(error) {
            if (error.status === 404 || error.status === 403) {
                /* If HTTP status is 404 or 403, the subreddit probably doesn't exist */
                $("#subNameInput").addClass("incorrect-input");
            } else if (error.status != 200) {
                /* Notify user when a non-handled status code is received */
                alert("Unknown status code " + error.status + " received from lookup request.\nPlease contact the developer.");
            }
            doneDownloading();
        }
    });
}

function updateCancelButton() {
    $("#cancelButton").val("downloaded " + downloadedCount + " image" + (downloadedCount == 1 ? "" : "s") + ".. press to cancel");
}

function doneDownloading() {
    clearInterval(checkFinishedInterval);

    if (downloadedCount > 0) {
        zip.generateAsync({ type:"blob" })
            .then(function(content) {
                saveAs(content, "r-" + subName + "-images.zip");
            });
    }

    $("#cancelButton").hide();
    $("#downloadButton").show();
}

function downloadImageAsBase64(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
        downloadRequests.delete(this);

        var reader = new FileReader();
        reader.onloadend = function() {
            callback(url, reader.result.split(',').pop());
        }
        reader.readAsDataURL(xhr.response);
    };
    xhr.open('GET', CORS_PROXY_URL + url);
    xhr.responseType = 'blob';
    xhr.send();

    downloadRequests.add(xhr);
}

$("#subNameInput").keypress(function() {
    $("#subNameInput").removeClass("incorrect-input");
});
$("#subNameInput").mousedown(function() {
    $("#subNameInput").removeClass("incorrect-input");
});

$("#cancelButton").click(function() {
    for (var xhr in downloadRequests) {
        xhr.abort();
    }
    doneDownloading();
});
<commit_msg>Share callback for #subNameInput's keypress and mousedown<commit_after>var CORS_PROXY_URL = "https://cors-anywhere.herokuapp.com/";
var CHECK_DOWNLOADS_FINISHED_EVERY_MS = 250;

var checkFinishedInterval;
var downloadRequests = new Set();
var downloadedCount;
var toDownloadCount;
var subName;

var zip = new JSZip();

$("#downloadButton").click(function() {
    /* Reset states */
    downloadRequests.clear();
    downloadedCount = 0;
    toDownloadCount = 0;
    subName = $("#subNameInput").val();
    updateCancelButton();

    /* Hide the download button */
    $("#downloadButton").hide();
    $("#cancelButton").show();

    /* Find images to scrape and start downloading */
    startDownloading();
});

function startDownloading() {
    $.ajax({
        url: CORS_PROXY_URL + "https://www.reddit.com/r/" + subName + "/hot.json",
        type: "GET",
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        success: function(result) {
            var children = result.data.children;

            if (children.length > 0) {
                for (var i = 0; i < Math.min(children.length, $("#maxImageCountInput").val()); i++) {
                    var post = children[i].data;

                    if (post.preview !== undefined && post.preview.images.length > 0) {
                        var url = post.preview.images[0].source.url;

                        if (url.indexOf(".jpg?") !== -1) {
                            if (url.startsWith("http:")) {
                                url = url.replace("http:", "https:");
                            }

                            toDownloadCount++;

                            downloadImageAsBase64(url, function(url, data) {
                                zip.file(url.replace(/(.+\/)/, "").replace(/(\?.+)/, ""), data, { base64: true });
                                downloadedCount++;
                                updateCancelButton();
                            });
                        }
                    }
                }

                checkFinishedInterval = setInterval(function() {
                    if (downloadedCount == toDownloadCount) {
                        doneDownloading();
                    }
                }, CHECK_DOWNLOADS_FINISHED_EVERY_MS);
            }
        },
        error: function(error) {
            if (error.status === 404 || error.status === 403) {
                /* If HTTP status is 404 or 403, the subreddit probably doesn't exist */
                $("#subNameInput").addClass("incorrect-input");
            } else if (error.status != 200) {
                /* Notify user when a non-handled status code is received */
                alert("Unknown status code " + error.status + " received from lookup request.\nPlease contact the developer.");
            }
            doneDownloading();
        }
    });
}

function updateCancelButton() {
    $("#cancelButton").val("downloaded " + downloadedCount + " image" + (downloadedCount == 1 ? "" : "s") + ".. press to cancel");
}

function doneDownloading() {
    clearInterval(checkFinishedInterval);

    if (downloadedCount > 0) {
        zip.generateAsync({ type:"blob" })
            .then(function(content) {
                saveAs(content, "r-" + subName + "-images.zip");
            });
    }

    $("#cancelButton").hide();
    $("#downloadButton").show();
}

function downloadImageAsBase64(url, callback) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
        downloadRequests.delete(this);

        var reader = new FileReader();
        reader.onloadend = function() {
            callback(url, reader.result.split(',').pop());
        }
        reader.readAsDataURL(xhr.response);
    };
    xhr.open('GET', CORS_PROXY_URL + url);
    xhr.responseType = 'blob';
    xhr.send();

    downloadRequests.add(xhr);
}

$("#cancelButton").click(function() {
    for (var xhr in downloadRequests) {
        xhr.abort();
    }
    doneDownloading();
});

var removeIncorrectInput = function() {
    $("#subNameInput").removeClass("incorrect-input");
};
$("#subNameInput").keypress(removeIncorrectInput);
$("#subNameInput").mousedown(removeIncorrectInput);
<|endoftext|>


<commit_before>"use strict";

import React from 'react'
import ReactDOM from 'react-dom'
import './styles/main.scss'
import CalculatorSmall from './components/calculatorSmall/CalculatorSmall'
import CalculatorLarge from './components/calculatorLarge/CalculatorLarge'
import TablePrices from './components/tablePrices/TablePrices'
import createStore from './store/createStore'
import initialState from './store/initState';
import {Provider} from 'react-redux';
import {fetchInitTree, fetchStatistic, fetchCoupon, fetchUser} from './store/actions';

// Store Initialization
// ------------------------------------
const store = createStore(initialState);
store.dispatch(fetchUser());
store.dispatch(fetchInitTree());

// Render Setup
// ------------------------------------
const MOUNT_NODE_TP = document.getElementById('tp');
const MOUNT_NODE_CL_1 = document.getElementById('cl-1');
// const MOUNT_NODE_CL_2 = document.getElementById('cl-2');
const MOUNT_NODE_1 = document.getElementById('cs-1');
const MOUNT_NODE_2 = document.getElementById('cs-2');
// const MOUNT_NODE_3 = document.getElementById('cs-3');
const MOUNT_NODES = [MOUNT_NODE_TP, MOUNT_NODE_CL_1, MOUNT_NODE_1, MOUNT_NODE_2];
const MOUNT_CLASSES = ['tp','calc-lg', 'calc-sm', 'calc-sm'];

let render = () => {

    MOUNT_NODES.forEach((MOUNT_NODE, i) => {
        if (MOUNT_CLASSES[i].indexOf('calc-lg') !== -1) {
            ReactDOM.render(
                <Provider store={store}>

                        <CalculatorLarge calcId={i}
                                         calcTitle={MOUNT_NODE.dataset.title}
                                         calcTitleDiscount={MOUNT_NODE.dataset.titleDiscount}
                                         containerClass={MOUNT_CLASSES[i]}/>

                </Provider>,
                MOUNT_NODE
            );
        } else if (MOUNT_CLASSES[i] === 'tp') {
            ReactDOM.render(
                <Provider store={store}>

                        <TablePrices calcId={i} containerClass={MOUNT_CLASSES[i]}/>

                </Provider>,
                MOUNT_NODE
            );
        }
        else {
            ReactDOM.render(
                <Provider store={store}>

                        <CalculatorSmall calcId={i}
                                         calcTitle={MOUNT_NODE.dataset.title}
                                         calcTitleDiscount={MOUNT_NODE.dataset.titleDiscount}
                                         calcType={MOUNT_NODE.dataset.type}
                                         containerClass={MOUNT_CLASSES[i]}/>

                </Provider>,
                MOUNT_NODE
            );
        }
    });
};

// Development Tools
// ------------------------------------
if (__DEV__) {
    if (module.hot) {
        const renderApp = render
        const renderError = (error) => {
            const RedBox = require('redbox-react').default

            ReactDOM.render(<RedBox error={error}/>, MOUNT_NODE_1)
        }

        render = () => {
            try {
                renderApp()
            } catch (e) {
                console.error(e)
                renderError(e)
            }
        }

        // Setup hot module replacement
        module.hot.accept([
                './main',
            ], () =>
                setImmediate(() => {
                    ReactDOM.unmountComponentAtNode(MOUNT_NODE_1)
                    render()
                })
        )
    }
}

// Let's Go!
// ------------------------------------
if (!__TEST__) render()
<commit_msg>add loading style and fonts from CDN<commit_after>"use strict";

import React from 'react'
import ReactDOM from 'react-dom'
import './styles/main.scss'
import CalculatorSmall from './components/calculatorSmall/CalculatorSmall'
import CalculatorLarge from './components/calculatorLarge/CalculatorLarge'
import TablePrices from './components/tablePrices/TablePrices'
import createStore from './store/createStore'
import initialState from './store/initState';
import {Provider} from 'react-redux';
import {fetchInitTree, fetchStatistic, fetchCoupon, fetchUser} from './store/actions';

const css = 'https://s3.amazonaws.com/genericapps/resources/calculators/main.259b2e5f8db7f0ca0ae3a05093744601.css';
const font = 'https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet';
function loadCSS(filename) {

    const file = document.createElement("link");
    file.setAttribute("rel", "stylesheet");
    file.setAttribute("type", "text/css");
    file.setAttribute("href", filename);
    document.head.appendChild(file);

}
function loadFont(filename) {
    const file = document.createElement("link");
    file.setAttribute("rel", "stylesheet");
    file.setAttribute("href", filename);
    document.head.appendChild(file);
}

// loadCSS(css);
// loadFont(font);

// Store Initialization
// ------------------------------------
const store = createStore(initialState);
store.dispatch(fetchUser());
store.dispatch(fetchInitTree());

// Render Setup
// ------------------------------------
const MOUNT_NODE_TP = document.getElementById('tp');
const MOUNT_NODE_CL_1 = document.getElementById('cl-1');
// const MOUNT_NODE_CL_2 = document.getElementById('cl-2');
const MOUNT_NODE_1 = document.getElementById('cs-1');
const MOUNT_NODE_2 = document.getElementById('cs-2');
// const MOUNT_NODE_3 = document.getElementById('cs-3');
const MOUNT_NODES = [MOUNT_NODE_TP, MOUNT_NODE_CL_1, MOUNT_NODE_1, MOUNT_NODE_2];
const MOUNT_CLASSES = ['tp', 'calc-lg', 'calc-sm', 'calc-sm'];

let render = () => {

    MOUNT_NODES.forEach((MOUNT_NODE, i) => {
        if (MOUNT_CLASSES[i].indexOf('calc-lg') !== -1) {
            ReactDOM.render(
                <Provider store={store}>

                        <CalculatorLarge calcId={i}
                                         calcTitle={MOUNT_NODE.dataset.title}
                                         calcTitleDiscount={MOUNT_NODE.dataset.titleDiscount}
                                         containerClass={MOUNT_CLASSES[i]}/>

                </Provider>,
                MOUNT_NODE
            );
        } else if (MOUNT_CLASSES[i] === 'tp') {
            ReactDOM.render(
                <Provider store={store}>

                        <TablePrices calcId={i} containerClass={MOUNT_CLASSES[i]}/>

                </Provider>,
                MOUNT_NODE
            );
        }
        else {
            ReactDOM.render(
                <Provider store={store}>

                        <CalculatorSmall calcId={i}
                                         calcTitle={MOUNT_NODE.dataset.title}
                                         calcTitleDiscount={MOUNT_NODE.dataset.titleDiscount}
                                         calcType={MOUNT_NODE.dataset.type}
                                         containerClass={MOUNT_CLASSES[i]}/>

                </Provider>,
                MOUNT_NODE
            );
        }
    });
};

// Development Tools
// ------------------------------------
if (__DEV__) {
    if (module.hot) {
        const renderApp = render
        const renderError = (error) => {
            const RedBox = require('redbox-react').default

            ReactDOM.render(<RedBox error={error}/>, MOUNT_NODE_1)
        }

        render = () => {
            try {
                renderApp()
            } catch (e) {
                console.error(e)
                renderError(e)
            }
        }

        // Setup hot module replacement
        module.hot.accept([
                './main',
            ], () =>
                setImmediate(() => {
                    ReactDOM.unmountComponentAtNode(MOUNT_NODE_1)
                    render()
                })
        )
    }
}

// Let's Go!
// ------------------------------------
if (!__TEST__) render()
<|endoftext|>


<commit_before>//
// PagerMon - reader.js
// 2017-06-04
// Author: Dave McKenzie
//
// Description: Takes output of multimon-ng and pushes to PagerMon server
//
// Usage: Invoke via a shell script, ideally
// 		If not, just pipe multimon's output to it
//
// Example: reader.sh
//

// CONFIG
// create config file if it does not exist, and set defaults
var fs = require('fs');
var conf_defaults = require('./config/default.json');
var conf_file = './config/config.json';
if( ! fs.existsSync(conf_file) ) {
    fs.writeFileSync( conf_file, JSON.stringify(conf_defaults,null, 2) );
    console.log('created config file - set your api key in '+conf_file);
    return;
}
// load the config file
var nconf = require('nconf');
    nconf.file({file: conf_file});
    nconf.load();

var hostname = nconf.get('hostname');
var apikey = nconf.get('apikey');
var identifier = nconf.get('identifier');

var uri = hostname+"/api/messages";

var http = require('http');
var request = require('request');
require('request').debug = true;
var rp = require('request-promise-native');
var moment = require('moment');

var colors = require('colors/safe');
colors.setTheme({
  success: ['white', 'bold', 'bgBlue'],
  error: ['red', 'bold', 'bgwhite']
});

const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    terminal: true
});

var frag = {};

rl.on('line', (line) => {
  //console.log(`Received: ${line.trim()}`);
  var time = moment().format("YYYY-MM-DD HH:mm:ss");
  var datetime = moment().unix();
  var address;
  var message;
  var trimMessage;
  // TODO: pad address with zeros for better address matching
//  if (line.indexOf('POCSAG512: Address:') > -1) {
  if (/^POCSAG(\d+): Address: /.test(line) ) {
    address = line.match(/POCSAG(\d+): Address:(.*?)Function/)[2].trim();
    if (line.indexOf('Alpha:') > -1) {
      message = line.match(/Alpha:(.*?)$/)[1].trim();
      trimMessage = message.replace(/<[A-Za-z]{3}>/g,'').replace(/Ä/g,'[').replace(/Ü/g,']');
    } else if (line.indexOf('Numeric:') > -1) {
      message = line.match(/Numeric:(.*?)$/)[1].trim();
      trimMessage = message.replace(/<[A-Za-z]{3}>/g,'').replace(/Ä/g,'[').replace(/Ü/g,']');
    } else {
      message = false;
      trimMessage = '';
    }
  } else if (line.indexOf('FLEX: ') > -1) {
    address = line.match(/FLEX:.*?\[(\d*?)\] /)[1].trim();
    if (line.match( /( ALN | GPN | NUM)/ )) {
      if (line.match( / [0-9]{4}\/[0-9]\/F\/. / )) {
        // message is fragmented, hold onto it for next line
        frag[address] = line.match(/FLEX:.*?\[\d*\] ... (.*?)$/)[1].trim();
        message = false;
        trimMessage = '';
      } else if (line.match( / [0-9]{4}\/[0-9]\/C\/. / )) {
        // message is a completion of the last fragmented message
        message = line.match(/FLEX:.*?\[\d*\] ... (.*?)$/)[1].trim();
        trimMessage = frag[address]+message;
        delete frag[address];
      } else if (line.match( / [0-9]{4}\/[0-9]\/K\/. / )) {
        // message is a full message
        message = line.match(/FLEX:.*?\[\d*\] ... (.*?)$/)[1].trim();
        trimMessage = message;
      } else {
        // message doesn't have the KFC flags, treat as full message
        message = line.match(/FLEX:.*?\[\d*\] ... (.*?)$/)[1].trim();
        trimMessage = message;
      }
    }
  } else {
    address = '';
    message = false;
    trimMessage = '';
  }

  // filter out most false hits
  // if too much junk data, make sure '-p' option isn't enabled in multimon
  if (address.length > 2 && message) {
    var padAddress = padDigits(address,7);
    console.log(colors.red(time+': ')+colors.yellow(padAddress+': ')+colors.success(trimMessage));
    // now send the message
    var options = {
      method: 'POST',
      uri: uri,
      headers: {
        'X-Requested-With': 'XMLHttpRequest',
        apikey: apikey
      },
      form: {
        address: padAddress,
        message: trimMessage,
        datetime: datetime,
        source: identifier
      }
    };
    rp(options)
        .then(function (body) {
        //    console.log(colors.success('Message delivered. ID: '+body));
        })
        .catch(function (err) {
            console.log(colors.yellow('Message failed to deliver. '+err));
        });
  } else {
    console.log(colors.red(time+': ')+colors.grey(line));
  }

}).on('close', () => {
  console.log('Input died!');
});

var padDigits = function(number, digits) {
    return Array(Math.max(digits - String(number).length + 1, 0)).join(0) + number;
};
<commit_msg>exponential retries<commit_after>//
// PagerMon - reader.js
// 2017-06-04
// Author: Dave McKenzie
//
// Description: Takes output of multimon-ng and pushes to PagerMon server
//
// Usage: Invoke via a shell script, ideally
// 		If not, just pipe multimon's output to it
//
// Example: reader.sh
//

// CONFIG
// create config file if it does not exist, and set defaults
var fs = require('fs');
var conf_defaults = require('./config/default.json');
var conf_file = './config/config.json';
if( ! fs.existsSync(conf_file) ) {
    fs.writeFileSync( conf_file, JSON.stringify(conf_defaults,null, 2) );
    console.log('created config file - set your api key in '+conf_file);
    return;
}
// load the config file
var nconf = require('nconf');
    nconf.file({file: conf_file});
    nconf.load();

var hostname = nconf.get('hostname');
var apikey = nconf.get('apikey');
var identifier = nconf.get('identifier');

var uri = hostname+"/api/messages";

var http = require('http');
var request = require('request');
require('request').debug = true;
var rp = require('request-promise-native');
var moment = require('moment');

var colors = require('colors/safe');
colors.setTheme({
  success: ['white', 'bold', 'bgBlue'],
  error: ['red', 'bold', 'bgwhite']
});

const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    terminal: true
});

var frag = {};

rl.on('line', (line) => {
  //console.log(`Received: ${line.trim()}`);
  var time = moment().format("YYYY-MM-DD HH:mm:ss");
  var datetime = moment().unix();
  var address;
  var message;
  var trimMessage;
  // TODO: pad address with zeros for better address matching
//  if (line.indexOf('POCSAG512: Address:') > -1) {
  if (/^POCSAG(\d+): Address: /.test(line) ) {
    address = line.match(/POCSAG(\d+): Address:(.*?)Function/)[2].trim();
    if (line.indexOf('Alpha:') > -1) {
      message = line.match(/Alpha:(.*?)$/)[1].trim();
      trimMessage = message.replace(/<[A-Za-z]{3}>/g,'').replace(/Ä/g,'[').replace(/Ü/g,']');
    } else if (line.indexOf('Numeric:') > -1) {
      message = line.match(/Numeric:(.*?)$/)[1].trim();
      trimMessage = message.replace(/<[A-Za-z]{3}>/g,'').replace(/Ä/g,'[').replace(/Ü/g,']');
    } else {
      message = false;
      trimMessage = '';
    }
  } else if (line.indexOf('FLEX: ') > -1) {
    address = line.match(/FLEX:.*?\[(\d*?)\] /)[1].trim();
    if (line.match( /( ALN | GPN | NUM)/ )) {
      if (line.match( / [0-9]{4}\/[0-9]\/F\/. / )) {
        // message is fragmented, hold onto it for next line
        frag[address] = line.match(/FLEX:.*?\[\d*\] ... (.*?)$/)[1].trim();
        message = false;
        trimMessage = '';
      } else if (line.match( / [0-9]{4}\/[0-9]\/C\/. / )) {
        // message is a completion of the last fragmented message
        message = line.match(/FLEX:.*?\[\d*\] ... (.*?)$/)[1].trim();
        trimMessage = frag[address]+message;
        delete frag[address];
      } else if (line.match( / [0-9]{4}\/[0-9]\/K\/. / )) {
        // message is a full message
        message = line.match(/FLEX:.*?\[\d*\] ... (.*?)$/)[1].trim();
        trimMessage = message;
      } else {
        // message doesn't have the KFC flags, treat as full message
        message = line.match(/FLEX:.*?\[\d*\] ... (.*?)$/)[1].trim();
        trimMessage = message;
      }
    }
  } else {
    address = '';
    message = false;
    trimMessage = '';
  }

  // filter out most false hits
  // if too much junk data, make sure '-p' option isn't enabled in multimon
  if (address.length > 2 && message) {
    var padAddress = padDigits(address,7);
    console.log(colors.red(time+': ')+colors.yellow(padAddress+': ')+colors.success(trimMessage));
    // now send the message
    var form = {
      address: padAddress,
      message: trimMessage,
      datetime: datetime,
      source: identifier
    };
    sendPage(form, 0);
  } else {
    console.log(colors.red(time+': ')+colors.grey(line));
  }

}).on('close', () => {
  console.log('Input died!');
});

var sendPage = function(message,retries) {
  var options = {
    method: 'POST',
    uri: uri,
    headers: {
      'X-Requested-With': 'XMLHttpRequest',
      apikey: apikey
    },
    form: message
  };
  rp(options)
  .then(function (body) {
    // console.log(colors.success('Message delivered. ID: '+body));
  })
  .catch(function (err) {
    console.log(colors.yellow('Message failed to deliver. '+err));
    if (retries < 10) {
      var retryTime = Math.pow(2, retries) * 1000;
      retries++;
      console.log(colors.yellow(`Retrying in ${retryTime} ms`));
      setTimeout(sendPage, retryTime, message, retries);
    } else {
      console.log(colors.yellow('Message failed to deliver after 10 retries, giving up'));
    }
  });
};

var padDigits = function(number, digits) {
    return Array(Math.max(digits - String(number).length + 1, 0)).join(0) + number;
};
<|endoftext|>


<commit_before>/**
 * The MIT License (MIT)
 *
 * Copyright (c) <2014> <Ioannis Moutsatsos, Bruno P. Kinoshita>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

'use strict';

/**
 * @author Bruno P. Kinoshita <brunodepaulak@yahoo.com.br>
 */

/**
 * Create fixture DIV (hack for running with phantomJS and Maven)
 */
QUnit.testStart(function() {
 	var fixtures = $('#qunit-tests').find('#qunit-fixture');
 	fixtures.each(function() {
 		$(this).remove();
 	});
 	var hiddenDiv = document.createElement("div");
 	$(hiddenDiv).attr('id', 'qunit-fixture');
 	$('#qunit-tests').append(hiddenDiv);
 });

/**
 * Initial set up.
 */
QUnit.test("Tests UnoChoice module was loaded", function () {
	ok(UnoChoice !== null, "UnoChoice object exists");
});

/**
 * Tests for the endsWith function.
 */
//QUnit.test("Test endsWith", function(assert) {
//	assert.ok(UnoChoice.endsWith("Bruno", "uno"), "endsWith works as expected");
//});

/**
 * Tests for the fakeSelectRadioButton function.
 */
QUnit.test("Test fakeSelectRadioButton", function() {
	var $fixture = $("#qunit-fixture");
	$fixture.append("<input type='radio' name='group1' class='myClazz' id='option1' />");
	$fixture.append("<input type='radio' name='group1' class='myClazz' id='option2' />");
	$fixture.append("<input type='radio' name='group1' class='myClazz' id='option3' />");
	UnoChoice.fakeSelectRadioButton("myClazz", "option2");
	equal("", $("#option1").attr('name'), "option1 is empty");
	equal("value", $("#option2").attr('name'), "option2 is not empty");
	equal("", $("#option3").attr('name'), "option3 is empty");
});

/**
 * Tests for the getParameterValue / getSelectValues function.
 */
QUnit.test("Test getParameterValue", function() {
	var $fixture = $("#qunit-fixture");
	$fixture.append("<select name='value' multiple><option value='1' selected>1</option><option value='2'>2</option><option value='3' selected>3</option></select>");
	var select = $("select");
	var arr = UnoChoice.getParameterValue(select);
	equal(arr, '1,3', 'the content is correct');
});

/**
 * Tests for the getParameterValue / getElementValue function. Using select.
 */
QUnit.test("Test getElementValue using select", function() {
	var $fixture = $("#qunit-fixture");

	$fixture.append("<select name='value' multiple><option value='1' selected>1</option><option value='2'>2</option><option value='3' selected>3</option></select>");
	var select = $("select");
	var arr = UnoChoice.getParameterValue(select);
	equal(arr, '1,3', 'element value is correct for select');
});

/**
 * Tests for the getParameterValue / getElementValue function. Using checkbox.
 */
QUnit.test("Test getElementValue using checkbox", function() {
	var $fixture = $("#qunit-fixture");

	$fixture.append("<input id='checkbox1' type='checkbox' name='value' checked='checked' value='123' />");
	var e = $("#checkbox1");
	var val = UnoChoice.getParameterValue(e);
	equal(val, '123', 'element value is correct for checkbox');
});

/**
 * Tests for the getParameterValue / getElementValue function. Using text.
 */
QUnit.test("Test getElementValue using text", function() {
	var $fixture = $("#qunit-fixture");

	$fixture.append("<input id='input1' type='text' name='value' checked='checked' value='1234' />");
	var e = $("#input1");
	var val = UnoChoice.getParameterValue(e);
	equal(val, '1234', 'element value is correct for text');
});

/**
 * Tests for the getParameterValue / getElementValue function. Using empty select.
 */
QUnit.test("Test getElementValue using empty select", function() {
	var $fixture = $("#qunit-fixture");

	$fixture.append("<select name='value'></select>");
	var select = $("select");
	var val = UnoChoice.getParameterValue(select);
	equal(val, '', 'element value is correct for empty select');
});

/**
 * Tests for the getParameterValue / getElementValue function. Using empty div.
 */
QUnit.test("Test getElementValue using div", function() {
	var $fixture = $("#qunit-fixture");

	$fixture.append("
" +
			"E agora?" +
			"
" +
			"<input type='text' name='name' value='parameter1'/>" +
			"<input type='text' name='value' value='123432'/>" +
			"
" +
			"
");
	var e = $("#div1");
	var val = UnoChoice.getParameterValue(e);
	equal(val, '123432', 'element value is correct for div');
});

/**
 * Tests for the getParameterValue / getElementValue function. Using files.
 */
QUnit.test("Test getElementValue using files", function() {
	var $fixture = $("#qunit-fixture");

	$fixture.append("<input type='file' name='myfile' value='1.txt'/>");
	var e = $("input[type='file']");
	var val = UnoChoice.getParameterValue(e);
	equal(val, '', 'element value is correct for files'); // cannot set value due to security
});

/**
 * Tests for the CascadeParameter.
 */
QUnit.test("Test CascadeParameter class", function() {
	var $fixture = $("#qunit-fixture");
	var cascadeParameter = new UnoChoice.CascadeParameter('sample-param', $fixture, /*proxy*/ undefined);
	ok(cascadeParameter, 'cascade parameter object creation working');
	deepEqual('sample-param', cascadeParameter.getParameterName(), 'parameter name is retrieved correctly');
	var anotherCascadeParameter = new UnoChoice.CascadeParameter('another-sample-param', $fixture, /*proxy*/ undefined);
	deepEqual('sample-param', cascadeParameter.getParameterName(), 'parameter name is retrieved correctly');
	deepEqual('another-sample-param', anotherCascadeParameter.getParameterName(), 'parameter name is retrieved correctly');
	deepEqual('sample-param', cascadeParameter.getParameterName(), 'parameter name is retrieved correctly');
});

/**
 * Tests for FilteredElement.
 */
QUnit.test("Test FilteredElement with selects", function() {
	var $fixture = $("#qunit-fixture");
	$fixture.append('<select name="value" multiple="multiple" size="8"><option value="1">Bruno</option><option value="2">Nuno</option><option value="3">Joe</option><option selected="true" value="4">Jeea</option><option value="5">5</option><option value="6">6</option><option value="7">7</option><option value="8">8</option></select>');
	$fixture.append('<input class="uno_choice_filter" type="text" value="" name="test" placeholder="Filter">');
    var parameterHtmlElement = $fixture.find('*[name="value"]');
    if (parameterHtmlElement && parameterHtmlElement.get(0)) {
        var filterHtmlElement = $fixture.find('.uno_choice_filter');
        if (filterHtmlElement && filterHtmlElement.get(0)) {
            var filterElement = new UnoChoice.FilterElement(parameterHtmlElement.get(0), filterHtmlElement.get(0));
        } else {
            console.log('Filter error: Missing filter element!');
        }
    } else {
        log('Filter error: Missing parameter element!');
    }
    equal(8, jQuery(parameterHtmlElement).children().length, "Right select options count");
    filterElement.getFilterElement().value = 'uno';
    jQuery(filterElement.getFilterElement()).keyup();
    equal(2, jQuery(parameterHtmlElement).children().length, "Right select options count");
});<commit_msg>Fix tests JS<commit_after>/**
 * The MIT License (MIT)
 *
 * Copyright (c) <2014> <Ioannis Moutsatsos, Bruno P. Kinoshita>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

'use strict';

/**
 * @author Bruno P. Kinoshita <brunodepaulak@yahoo.com.br>
 */

/**
 * Create fixture DIV (hack for running with phantomJS and Maven)
 */
QUnit.testStart(function() {
 	var fixtures = jQuery('#qunit-tests').find('#qunit-fixture');
 	fixtures.each(function() {
 		jQuery(this).remove();
 	});
 	var hiddenDiv = document.createElement("div");
 	jQuery(hiddenDiv).attr('id', 'qunit-fixture');
 	jQuery('#qunit-tests').append(hiddenDiv);
 });

/**
 * Initial set up.
 */
QUnit.test("Tests UnoChoice module was loaded", function () {
	ok(UnoChoice !== null, "UnoChoice object exists");
});

/**
 * Tests for the endsWith function.
 */
//QUnit.test("Test endsWith", function(assert) {
//	assert.ok(UnoChoice.endsWith("Bruno", "uno"), "endsWith works as expected");
//});

/**
 * Tests for the fakeSelectRadioButton function.
 */
QUnit.test("Test fakeSelectRadioButton", function() {
	var $fixture = jQuery("#qunit-fixture");
	$fixture.append("<input type='radio' name='group1' class='myClazz' id='option1' />");
	$fixture.append("<input type='radio' name='group1' class='myClazz' id='option2' />");
	$fixture.append("<input type='radio' name='group1' class='myClazz' id='option3' />");
	UnoChoice.fakeSelectRadioButton("myClazz", "option2");
	equal("", jQuery("#option1").attr('name'), "option1 is empty");
	equal("value", jQuery("#option2").attr('name'), "option2 is not empty");
	equal("", jQuery("#option3").attr('name'), "option3 is empty");
});

/**
 * Tests for the getParameterValue / getSelectValues function.
 */
QUnit.test("Test getParameterValue", function() {
	var $fixture = jQuery("#qunit-fixture");
	$fixture.append("<select name='value' multiple><option value='1' selected>1</option><option value='2'>2</option><option value='3' selected>3</option></select>");
	var select = jQuery("select");
	var arr = UnoChoice.getParameterValue(select);
	equal(arr, '1,3', 'the content is correct');
});

/**
 * Tests for the getParameterValue / getElementValue function. Using select.
 */
QUnit.test("Test getElementValue using select", function() {
	var $fixture = jQuery("#qunit-fixture");

	$fixture.append("<select name='value' multiple><option value='1' selected>1</option><option value='2'>2</option><option value='3' selected>3</option></select>");
	var select = jQuery("select");
	var arr = UnoChoice.getParameterValue(select);
	equal(arr, '1,3', 'element value is correct for select');
});

/**
 * Tests for the getParameterValue / getElementValue function. Using checkbox.
 */
QUnit.test("Test getElementValue using checkbox", function() {
	var $fixture = jQuery("#qunit-fixture");

	$fixture.append("<input id='checkbox1' type='checkbox' name='value' checked='checked' value='123' />");
	var e = jQuery("#checkbox1");
	var val = UnoChoice.getParameterValue(e);
	equal(val, '123', 'element value is correct for checkbox');
});

/**
 * Tests for the getParameterValue / getElementValue function. Using text.
 */
QUnit.test("Test getElementValue using text", function() {
	var $fixture = jQuery("#qunit-fixture");

	$fixture.append("<input id='input1' type='text' name='value' checked='checked' value='1234' />");
	var e = jQuery("#input1");
	var val = UnoChoice.getParameterValue(e);
	equal(val, '1234', 'element value is correct for text');
});

/**
 * Tests for the getParameterValue / getElementValue function. Using empty select.
 */
QUnit.test("Test getElementValue using empty select", function() {
	var $fixture = jQuery("#qunit-fixture");

	$fixture.append("<select name='value'></select>");
	var select = jQuery("select");
	var val = UnoChoice.getParameterValue(select);
	equal(val, '', 'element value is correct for empty select');
});

/**
 * Tests for the getParameterValue / getElementValue function. Using empty div.
 */
QUnit.test("Test getElementValue using div", function() {
	var $fixture = jQuery("#qunit-fixture");

	$fixture.append("
" +
			"E agora?" +
			"
" +
			"<input type='text' name='name' value='parameter1'/>" +
			"<input type='text' name='value' value='123432'/>" +
			"
" +
			"
");
	var e = jQuery("#div1");
	var val = UnoChoice.getParameterValue(e);
	equal(val, '123432', 'element value is correct for div');
});

/**
 * Tests for the getParameterValue / getElementValue function. Using files.
 */
QUnit.test("Test getElementValue using files", function() {
	var $fixture = jQuery("#qunit-fixture");

	$fixture.append("<input type='file' name='myfile' value='1.txt'/>");
	var e = jQuery("input[type='file']");
	var val = UnoChoice.getParameterValue(e);
	equal(val, '', 'element value is correct for files'); // cannot set value due to security
});

/**
 * Tests for the CascadeParameter.
 */
QUnit.test("Test CascadeParameter class", function() {
	var $fixture = jQuery("#qunit-fixture");
	var cascadeParameter = new UnoChoice.CascadeParameter('sample-param', $fixture, /*proxy*/ undefined);
	ok(cascadeParameter, 'cascade parameter object creation working');
	deepEqual('sample-param', cascadeParameter.getParameterName(), 'parameter name is retrieved correctly');
	var anotherCascadeParameter = new UnoChoice.CascadeParameter('another-sample-param', $fixture, /*proxy*/ undefined);
	deepEqual('sample-param', cascadeParameter.getParameterName(), 'parameter name is retrieved correctly');
	deepEqual('another-sample-param', anotherCascadeParameter.getParameterName(), 'parameter name is retrieved correctly');
	deepEqual('sample-param', cascadeParameter.getParameterName(), 'parameter name is retrieved correctly');
});

/**
 * Tests for FilteredElement.
 */
QUnit.test("Test FilteredElement with selects", function() {
	var $fixture = jQuery("#qunit-fixture");
	$fixture.append('<select name="value" multiple="multiple" size="8"><option value="1">Bruno</option><option value="2">Nuno</option><option value="3">Joe</option><option selected="true" value="4">Jeea</option><option value="5">5</option><option value="6">6</option><option value="7">7</option><option value="8">8</option></select>');
	$fixture.append('<input class="uno_choice_filter" type="text" value="" name="test" placeholder="Filter">');
    var parameterHtmlElement = $fixture.find('*[name="value"]');
    if (parameterHtmlElement && parameterHtmlElement.get(0)) {
        var filterHtmlElement = $fixture.find('.uno_choice_filter');
        if (filterHtmlElement && filterHtmlElement.get(0)) {
            var filterElement = new UnoChoice.FilterElement(parameterHtmlElement.get(0), filterHtmlElement.get(0));
        } else {
            console.log('Filter error: Missing filter element!');
        }
    } else {
        log('Filter error: Missing parameter element!');
    }
    equal(8, jQuery(parameterHtmlElement).children().length, "Right select options count");
    filterElement.getFilterElement().value = 'uno';
    jQuery(filterElement.getFilterElement()).keyup();
    equal(2, jQuery(parameterHtmlElement).children().length, "Right select options count");
});<|endoftext|>