<commit_before>/*global abaplint*/
/*global CodeMirror*/

let config = new abaplint.Config.getDefault();

function stripNewline(input) {
  let result = input;
  while (result.substr(result.length - 1, 1) === "\n") {
    result = result.substr(0, result.length - 1);
  }
  return result;
}

function buildStatements(file) {
  let output = "";

  for (let statement of file.getStatements()) {
    let row = statement.getStart().getRow();
    let col = statement.getStart().getCol();
    let erow = statement.getEnd().getRow();
    let ecol = statement.getEnd().getCol();
// getting the class name only works if uglify does not mangle names
    output = output +
      "
" +
      row + ": " +
      (statement.constructor + "").match(/\w+/g)[1] +
      "
\n";
  }

  return output;
}

function buildAst(file) {
  return file.getRoot().viz();
}

function process() {
  let input = stripNewline(editor.getValue());
  let file = new abaplint.File("foobar.clas.abap", input);
  return new abaplint.Runner([file], config).findIssues();
}

function parse() {
  let input = stripNewline(editor.getValue());
  let file = new abaplint.File("foobar.clas.abap", input);
  return new abaplint.Runner([file], config).parse()[0];
}

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

function issues() {
  let issues = process();
  let json = JSON.parse(abaplint.Formatter.format(issues, "json"));
  let output = "";
  for (let issue of json) {
    output = output +
      "
" +
      "[" + issue.start.row + ", "+ issue.start.col + "] " +
      issue.description +
      "
\n";
  }
  document.getElementById("info").innerHTML = output;
}

function tokens() {
  let file = parse();

  let inner = "
";
  for (let token of file.getTokens()) {
    inner = inner + "
\"" +
      token.getStr() + "\"	" +
      token.constructor.name + "
";
  }
  inner = inner + "
";

  document.getElementById("info").innerHTML = inner;
}

function statements() {
  let file = parse();
  document.getElementById("info").innerHTML = buildStatements(file);
}

function ast() {
  let file = parse();
  document.getElementById("info").innerHTML = buildAst(file);
}

function escape(str) {
  return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
}

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

var editor = null;
var _mark = null;

function markLine(line, col, eline, ecol) {
  if (_mark) _mark.clear();
  if (!col) col = 0;
  if (!eline) eline = line;
  if (!ecol) ecol = 100;
  _mark = editor.markText({line: line - 1, ch: col - 1},
                          {line: eline - 1, ch: ecol - 1},
                          {className: "styled-background"});
  editor.scrollIntoView({line: line - 1, ch: 0}, 200);
}

function changeVersion(evt) {
  config.setVersion(abaplint.textToVersion(evt.target.value));
}

function popuplateVersionDropdown() {
  let options = "";
  for(let version in abaplint.Version) {
    if (!isNaN(Number(version))) {
      let selected = "";
      if (abaplint.Config.defaultVersion + "" === version + "") { // huh?
        selected = " selected";
      }
      options = options + "<option value=\"" + abaplint.Version[version] + "\"" + selected + ">" +
        abaplint.Version[version] + "</option>\n";
    }
  }
  document.getElementById("version_dropdown").innerHTML = options;
  document.getElementById("version_dropdown").onchange = changeVersion;
}

function run() {
  editor = CodeMirror.fromTextArea(document.getElementById("input"), {
    lineNumbers: true,
    theme: "mbo",
    tabSize: 2,
    styleSelectedText: true,
    mode: "abap"
  });
  popuplateVersionDropdown();
  editor.setSize(null, "100%");
  document.getElementById("abaplintver").innerHTML = abaplint.Runner.version();
}

run();<commit_msg>fix web<commit_after>/*global abaplint*/
/*global CodeMirror*/

let config = new abaplint.Config.getDefault();

function stripNewline(input) {
  let result = input;
  while (result.substr(result.length - 1, 1) === "\n") {
    result = result.substr(0, result.length - 1);
  }
  return result;
}

function buildStatements(file) {
  let output = "";

  for (let statement of file.getStatements()) {
    let row = statement.getStart().getRow();
    let col = statement.getStart().getCol();
    let erow = statement.getEnd().getRow();
    let ecol = statement.getEnd().getCol();
// getting the class name only works if uglify does not mangle names
    output = output +
      "
" +
      row + ": " +
      (statement.constructor + "").match(/\w+/g)[1] +
      "
\n";
  }

  return output;
}

function buildAst(file) {
  return file.getRoot().viz();
}

function process() {
  let input = stripNewline(editor.getValue());
  let file = new abaplint.File("foobar.prog.abap", input);
  return new abaplint.Runner([file], config).findIssues();
}

function parse() {
  let input = stripNewline(editor.getValue());
  let file = new abaplint.File("foobar.prog.abap", input);
  return new abaplint.Runner([file], config).parse()[0];
}

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

function issues() {
  let issues = process();
  let json = JSON.parse(abaplint.Formatter.format(issues, "json"));
  let output = "";
  for (let issue of json) {
    output = output +
      "
" +
      "[" + issue.start.row + ", "+ issue.start.col + "] " +
      issue.description +
      "
\n";
  }
  document.getElementById("info").innerHTML = output;
}

function tokens() {
  let file = parse();

  let inner = "
";
  for (let token of file.getTokens()) {
    inner = inner + "
\"" +
      token.getStr() + "\"	" +
      token.constructor.name + "
";
  }
  inner = inner + "
";

  document.getElementById("info").innerHTML = inner;
}

function statements() {
  let file = parse();
  document.getElementById("info").innerHTML = buildStatements(file);
}

function ast() {
  let file = parse();
  document.getElementById("info").innerHTML = buildAst(file);
}

function escape(str) {
  return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
}

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

var editor = null;
var _mark = null;

function markLine(line, col, eline, ecol) {
  if (_mark) _mark.clear();
  if (!col) col = 0;
  if (!eline) eline = line;
  if (!ecol) ecol = 100;
  _mark = editor.markText({line: line - 1, ch: col - 1},
                          {line: eline - 1, ch: ecol - 1},
                          {className: "styled-background"});
  editor.scrollIntoView({line: line - 1, ch: 0}, 200);
}

function changeVersion(evt) {
  config.setVersion(abaplint.textToVersion(evt.target.value));
}

function popuplateVersionDropdown() {
  let options = "";
  for(let version in abaplint.Version) {
    if (!isNaN(Number(version))) {
      let selected = "";
      if (abaplint.Config.defaultVersion + "" === version + "") { // huh?
        selected = " selected";
      }
      options = options + "<option value=\"" + abaplint.Version[version] + "\"" + selected + ">" +
        abaplint.Version[version] + "</option>\n";
    }
  }
  document.getElementById("version_dropdown").innerHTML = options;
  document.getElementById("version_dropdown").onchange = changeVersion;
}

function run() {
  editor = CodeMirror.fromTextArea(document.getElementById("input"), {
    lineNumbers: true,
    theme: "mbo",
    tabSize: 2,
    styleSelectedText: true,
    mode: "abap"
  });
  popuplateVersionDropdown();
  editor.setSize(null, "100%");
  document.getElementById("abaplintver").innerHTML = abaplint.Runner.version();
}

run();<|endoftext|>


<commit_before>var demand = require('must');
var NameType = require('../NameType');

exports.initList = function (List) {
	List.add({
		name: NameType,
	});
};

exports.testFieldType = function (List) {
	describe('updateItem', function () {

	});

	describe('validateInput', function () {
		it('should validate string input', function (done) {
			List.fields.name.validateInput({
				name: 'Max',
			}, function (result) {
				demand(result).be(true);
				done();
			});
		});

		it('should validate emtpy string input', function (done) {
			List.fields.name.validateInput({
				name: '',
			}, function (result) {
				demand(result).be(true);
				done();
			});
		});

		it('should validate undefined input', function (done) {
			List.fields.name.validateInput({}, function (result) {
				demand(result).be(true);
				done();
			});
		});

		it('should validate null input', function (done) {
			List.fields.name.validateInput({
				name: null,
			}, function (result) {
				demand(result).be(true);
				done();
			});
		});

		it('should invalidate numeric input', function (done) {
			List.fields.name.validateInput({
				name: 1,
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate object input', function (done) {
			List.fields.name.validateInput({
				name: { things: 'stuff' },
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate array input', function (done) {
			List.fields.name.validateInput({
				name: [1, 2, 3],
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate Boolean input', function (done) {
			List.fields.name.validateInput({
				name: true,
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate function input', function (done) {
			List.fields.name.validateInput({
				name: function () {},
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate regexp input', function (done) {
			List.fields.name.validateInput({
				name: /foo/,
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate date input', function (done) {
			List.fields.name.validateInput({
				name: Date.now(),
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		describe('first name', function () {
			it('should validate string input', function (done) {
				List.fields.name.validateInput({
					name_first: 'Max',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate dot notation', function (done) {
				List.fields.name.validateInput({
					'name.first': 'Max',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate emtpy string input', function (done) {
				List.fields.name.validateInput({
					name_first: '',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate undefined input', function (done) {
				List.fields.name.validateInput({}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate null input', function (done) {
				List.fields.name.validateInput({
					name_first: null,
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should invalidate numeric input', function (done) {
				List.fields.name.validateInput({
					name_first: 1,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate object input', function (done) {
				List.fields.name.validateInput({
					name_first: { things: 'stuff' },
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate array input', function (done) {
				List.fields.name.validateInput({
					name_first: [1, 2, 3],
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate Boolean input', function (done) {
				List.fields.name.validateInput({
					name_first: true,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate function input', function (done) {
				List.fields.name.validateInput({
					name_first: function () {},
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate regexp input', function (done) {
				List.fields.name.validateInput({
					name_first: /foo/,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate date input', function (done) {
				List.fields.name.validateInput({
					name_first: Date.now(),
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});
		});

		describe('last name', function () {
			it('should validate string input', function (done) {
				List.fields.name.validateInput({
					name_last: 'Max',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate dot notation', function (done) {
				List.fields.name.validateInput({
					'name.last': 'Max',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate emtpy string input', function (done) {
				List.fields.name.validateInput({
					name_last: '',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate undefined input', function (done) {
				List.fields.name.validateInput({}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate null input', function (done) {
				List.fields.name.validateInput({
					name_last: null,
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should invalidate numeric input', function (done) {
				List.fields.name.validateInput({
					name_last: 1,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate object input', function (done) {
				List.fields.name.validateInput({
					name_last: { things: 'stuff' },
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate array input', function (done) {
				List.fields.name.validateInput({
					name_last: [1, 2, 3],
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate Boolean input', function (done) {
				List.fields.name.validateInput({
					name_last: true,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate function input', function (done) {
				List.fields.name.validateInput({
					name_last: function () {},
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate regexp input', function (done) {
				List.fields.name.validateInput({
					name_last: /foo/,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate date input', function (done) {
				List.fields.name.validateInput({
					name_last: Date.now(),
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});
		});
	});

	describe('validateRequiredInput', function () {
		it('should validate input present', function (done) {
			var testItem = new List.model();
			List.fields.name.validateRequiredInput(testItem, {
				name: 'Max',
			}, function (result) {
				demand(result).be(true);
				done();
			});
		});

		it('should invalidate undefined', function (done) {
			var testItem = new List.model();
			List.fields.name.validateRequiredInput(testItem, {
				name: undefined,
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should validate undefined if a previous value exists', function (done) {
			var testItem = new List.model({
				'name.first': 'Max',
			});
			List.fields.name.validateRequiredInput(testItem, {
				name: undefined,
			}, function (result) {
				demand(result).be(true);
				done();
			});
		});

		it('should invalidate empty string', function (done) {
			var testItem = new List.model();
			List.fields.name.validateRequiredInput(testItem, {
				name: '',
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate null', function (done) {
			var testItem = new List.model();
			List.fields.name.validateRequiredInput(testItem, {
				name: null,
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		describe('first name', function () {
			it('should validate input present', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_first: 'Max',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should invalidate undefined', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_first: undefined,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should validate undefined if a previous value exists', function (done) {
				var testItem = new List.model({
					'name.first': 'Max',
				});
				List.fields.name.validateRequiredInput(testItem, {
					name_first: undefined,
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should invalidate empty string', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_first: '',
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate null', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_first: null,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});
		});

		describe('last name', function () {
			it('should validate input present', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_last: 'Max',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should invalidate undefined', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_last: undefined,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should validate undefined if a previous value exists', function (done) {
				var testItem = new List.model({
					'name.last': 'Max',
				});
				List.fields.name.validateRequiredInput(testItem, {
					name_last: undefined,
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should invalidate empty string', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_last: '',
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate null', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_last: null,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});
		});
	});

	describe('getInputFromData', function () {

	});

	describe('getSortString', function () {

	});
};
<commit_msg>Add updateItem tests, cleanup<commit_after>var demand = require('must');
var NameType = require('../NameType');

exports.initList = function (List) {
	List.add({
		name: NameType,
	});
};

exports.testFieldType = function (List) {
	describe('updateItem', function () {
		it('should update the full name', function (done) {
			var testItem = new List.model();
			List.fields.name.updateItem(testItem, {
				name: 'Max Mustermann',
			}, function () {
				demand(testItem.name.full).be('Max Mustermann');
				done();
			});
		});

		it('should update the first name', function (done) {
			var testItem = new List.model();
			List.fields.name.updateItem(testItem, {
				name: {
					first: 'Max',
				},
			}, function () {
				demand(testItem.name.first).be('Max');
				done();
			});
		});

		it('should update the last name', function (done) {
			var testItem = new List.model();
			List.fields.name.updateItem(testItem, {
				name: {
					last: 'Max',
				},
			}, function () {
				demand(testItem.name.last).be('Max');
				done();
			});
		});
	});

	describe('validateInput', function () {
		it('should validate string input', function (done) {
			List.fields.name.validateInput({
				name: 'Max',
			}, function (result) {
				demand(result).be(true);
				done();
			});
		});

		it('should validate empty string input', function (done) {
			List.fields.name.validateInput({
				name: '',
			}, function (result) {
				demand(result).be(true);
				done();
			});
		});

		it('should validate undefined input', function (done) {
			List.fields.name.validateInput({}, function (result) {
				demand(result).be(true);
				done();
			});
		});

		it('should validate null input', function (done) {
			List.fields.name.validateInput({
				name: null,
			}, function (result) {
				demand(result).be(true);
				done();
			});
		});

		it('should invalidate numeric input', function (done) {
			List.fields.name.validateInput({
				name: 1,
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate object input', function (done) {
			List.fields.name.validateInput({
				name: { things: 'stuff' },
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate array input', function (done) {
			List.fields.name.validateInput({
				name: [1, 2, 3],
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate Boolean input', function (done) {
			List.fields.name.validateInput({
				name: true,
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate function input', function (done) {
			List.fields.name.validateInput({
				name: function () {},
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate regexp input', function (done) {
			List.fields.name.validateInput({
				name: /foo/,
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate date input', function (done) {
			List.fields.name.validateInput({
				name: new Date(),
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		describe('first name', function () {
			it('should validate string input', function (done) {
				List.fields.name.validateInput({
					name_first: 'Max',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate dot notation', function (done) {
				List.fields.name.validateInput({
					'name.first': 'Max',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate nested object', function (done) {
				List.fields.name.validateInput({
					name: {
						first: 'Max',
					},
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate empty string input', function (done) {
				List.fields.name.validateInput({
					name_first: '',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate undefined input', function (done) {
				List.fields.name.validateInput({}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate null input', function (done) {
				List.fields.name.validateInput({
					name_first: null,
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should invalidate numeric input', function (done) {
				List.fields.name.validateInput({
					name_first: 1,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate object input', function (done) {
				List.fields.name.validateInput({
					name_first: { things: 'stuff' },
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate array input', function (done) {
				List.fields.name.validateInput({
					name_first: [1, 2, 3],
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate Boolean input', function (done) {
				List.fields.name.validateInput({
					name_first: true,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate function input', function (done) {
				List.fields.name.validateInput({
					name_first: function () {},
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate regexp input', function (done) {
				List.fields.name.validateInput({
					name_first: /foo/,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate date input', function (done) {
				List.fields.name.validateInput({
					name_first: Date.now(),
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});
		});

		describe('last name', function () {
			it('should validate string input', function (done) {
				List.fields.name.validateInput({
					name_last: 'Max',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate dot notation', function (done) {
				List.fields.name.validateInput({
					'name.last': 'Max',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate nested object', function (done) {
				List.fields.name.validateInput({
					name: {
						last: 'Max',
					},
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate empty string input', function (done) {
				List.fields.name.validateInput({
					name_last: '',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate undefined input', function (done) {
				List.fields.name.validateInput({}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should validate null input', function (done) {
				List.fields.name.validateInput({
					name_last: null,
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should invalidate numeric input', function (done) {
				List.fields.name.validateInput({
					name_last: 1,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate object input', function (done) {
				List.fields.name.validateInput({
					name_last: { things: 'stuff' },
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate array input', function (done) {
				List.fields.name.validateInput({
					name_last: [1, 2, 3],
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate Boolean input', function (done) {
				List.fields.name.validateInput({
					name_last: true,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate function input', function (done) {
				List.fields.name.validateInput({
					name_last: function () {},
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate regexp input', function (done) {
				List.fields.name.validateInput({
					name_last: /foo/,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate date input', function (done) {
				List.fields.name.validateInput({
					name_last: new Date(),
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});
		});
	});

	describe('validateRequiredInput', function () {
		it('should validate input present', function (done) {
			var testItem = new List.model();
			List.fields.name.validateRequiredInput(testItem, {
				name: 'Max',
			}, function (result) {
				demand(result).be(true);
				done();
			});
		});

		it('should invalidate undefined', function (done) {
			var testItem = new List.model();
			List.fields.name.validateRequiredInput(testItem, {
				name: undefined,
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should validate undefined if a previous value exists', function (done) {
			var testItem = new List.model({
				'name.first': 'Max',
			});
			List.fields.name.validateRequiredInput(testItem, {
				name: undefined,
			}, function (result) {
				demand(result).be(true);
				done();
			});
		});

		it('should invalidate empty string', function (done) {
			var testItem = new List.model();
			List.fields.name.validateRequiredInput(testItem, {
				name: '',
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		it('should invalidate null', function (done) {
			var testItem = new List.model();
			List.fields.name.validateRequiredInput(testItem, {
				name: null,
			}, function (result) {
				demand(result).be(false);
				done();
			});
		});

		describe('first name', function () {
			it('should validate input present', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_first: 'Max',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should invalidate undefined', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_first: undefined,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should validate undefined if a previous value exists', function (done) {
				var testItem = new List.model({
					'name.first': 'Max',
				});
				List.fields.name.validateRequiredInput(testItem, {
					name_first: undefined,
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should invalidate empty string', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_first: '',
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate null', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_first: null,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});
		});

		describe('last name', function () {
			it('should validate input present', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_last: 'Max',
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should invalidate undefined', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_last: undefined,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should validate undefined if a previous value exists', function (done) {
				var testItem = new List.model({
					'name.last': 'Max',
				});
				List.fields.name.validateRequiredInput(testItem, {
					name_last: undefined,
				}, function (result) {
					demand(result).be(true);
					done();
				});
			});

			it('should invalidate empty string', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_last: '',
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});

			it('should invalidate null', function (done) {
				var testItem = new List.model();
				List.fields.name.validateRequiredInput(testItem, {
					name_last: null,
				}, function (result) {
					demand(result).be(false);
					done();
				});
			});
		});
	});

	describe('getInputFromData', function () {

	});

	describe('getSortString', function () {
		// TODO Figure out how to test this
	});

	describe('format', function () {

	});
};
<|endoftext|>


<commit_before>$(function () {
    'strict mode';

    var CONFIG_ITEMS = [
        new GroupItem("General Config", legendFactory),
        new ConfigItem("deviceName", deviceNameInputFactory, inputApply, inputGet, "The general name of the device"),
        new ConfigItem("configPassword", devicePasswordInputFactory, inputApply, inputGet, "The admin password for the web UI (min. 8 characters)"),

        new GroupItem("MQTT Config", legendFactory),
        new ConfigItem("mqttBroker", hostNameInputFactory, inputApply, inputGet, "MQTT Broker host"),
        new ConfigItem("mqttBrokerPort", portNumberInputFactory, inputApply, inputGetInt, "MQTT Broker port"),
        new ConfigItem("mqttUser", inputFieldFactory, inputApply, inputGet, "MQTT username (optional)"),
        new ConfigItem("mqttPassword", passwordFieldFactory, inputApply, inputGet, "MQTT password (optional)"),
        new ConfigItem("mqttRetain", checkboxFactory, checkboxApply, checkboxGet, "Retain MQTT messages"),

        new GroupItem("MQTT Topic Config", legendFactory),
        new ConfigItem("mqttReceiveTopic", mqttTopicInputFactory, inputApply, inputGet, "Topic to publish received signal"),
        new ConfigItem("mqttSendTopic", mqttTopicInputFactory, inputApply, inputGet, "Topic to get signals to send from"),

        new GroupItem("433MHz RF Config", legendFactory),
        new ConfigItem("rfEchoMessages", checkboxFactory, checkboxApply, checkboxGet, "Echo sent rf messages back"),
        new ConfigItem("rfReceiverPin", pinNumberInputFactory, inputApply, inputGetInt, "The GPIO pin used for the rf receiver"),
        new ConfigItem("rfReceiverPinPullUp", checkboxFactory, checkboxApply, checkboxGet, "Activate pullup on rf receiver pin (required for 5V protection with reverse diode)"),
        new ConfigItem("rfTransmitterPin", pinNumberInputFactory, inputApply, inputGetInt, "The GPIO pin used for the RF transmitter"),

        new GroupItem("Enabled RF protocols", legendFactory),
        new ConfigItem("rfProtocols", protocolInputField, protocolApply, protocolGet, ""),

        new GroupItem("Log Config", legendFactory),
        new ConfigItem("serialLogLevel", logLevelInputFactory, inputApply, inputGet, "Level for serial logging"),
        new ConfigItem("webLogLevel", logLevelInputFactory, inputApply, inputGet, "Level for logging to the web UI"),
        new ConfigItem("syslogLevel", logLevelInputFactory, inputApply, inputGet, "Level for syslog logging"),
        new ConfigItem("syslogHost", hostNameInputFactory, inputApply, inputGet, "Syslog server (optional)"),
        new ConfigItem("syslogPort", portNumberInputFactory, inputApply, inputGetInt, "Syslog port (optional)"),

        new GroupItem("Status LED", legendFactory),
        new ConfigItem("ledPin", pinNumberInputFactory, inputApply, inputGetInt, "The GPIO pin used for the status LED"),
        new ConfigItem("ledActiveHigh", checkboxFactory, checkboxApply, checkboxGet, "The way how the LED is connected to the pin (false for built-in led)")
    ];

    var DEBUG_FLAGS = {
        protocolRaw: "Enable Raw RF message logging",
        systemLoad: "Show the processed loop() iterations for each second",
        freeHeap: "Show the free heap memory every second"
    };

    var SystemCommandActions = {
        restart: function () {
            var body = $("body");
            body.empty();
            body.append("
Device will reboot!

Try to reconnect in 15 seconds.
");
            setTimeout(function () {
                window.location.reload(true);
            }, 15000);
        },
        reset_wifi: function () {
            var body = $("body");
            body.empty();
            body.append("
Devices WIFI settings where cleared!

Please reconfigure it.
");
        },
        reset_config: function () {
            var body = $("body");
            body.empty();
            body.append("
Devices Config was reset - reboot device!
" +
                "
You might have to reconfigure the wifi!
" +
                "
Reload page in 10 seconds...
");
            setTimeout(function () {
                window.location.reload(true);
            }, 10000);
        }
    };

    function ConfigItem(name, factory, apply, fetch, help) {
        this.name = name;
        this.factory = factory;
        this.apply = apply;
        this.fetch = fetch;
        this.help = help;
    }

    function GroupItem(name, factory) {
        this.name = name;
        this.factory = factory;
    }

    function inputLabelFactory(item) {
        return $('', {
            text: item.name,
            for: 'cfg-' + item.name,
        });
    }

    function inputHelpFactory(item) {
        return $('', {
            class: 'pure-form-message',
            text: item.help,
        });
    }

    function logLevelInputFactory(item) {
        var element = $('<select>', {
            class: 'config-item',
            id: 'cfg-' + item.name,
            name: item.name,
        }).append([
            $('<option>', {value: '', text: "None"}),
            $('<option>', {value: 'error', text: 'Error'}),
            $('<option>', {value: 'warning', text: 'Warning'}),
            $('<option>', {value: 'info', text: 'Info'}),
            $('<option>', {value: 'debug', text: 'Debug'}),
        ]);
        registerConfigUi(element, item);
        return [
            inputLabelFactory(item),
            element,
            inputHelpFactory(item),
        ];
    }

    function inputFieldFactory(item, pattern, required) {
        var element = $('<input>', {
            type: 'text',
            class: 'pure-input-1 config-item',
            id: 'cfg-' + item.name,
            pattern: pattern,
            required: required,
            name: item.name,
        });
        registerConfigUi(element, item);
        return [
            inputLabelFactory(item),
            element,
            inputHelpFactory(item),
        ];
    }

    function deviceNameInputFactory(item) {
        return inputFieldFactory(item, '[.-_A-Za-z0-9]+', true);
    }

    function hostNameInputFactory(item) {
        return inputFieldFactory(item, '[.-_A-Za-z0-9]*');
    }

    function mqttTopicInputFactory(item) {
        return inputFieldFactory(item, undefined, true);
    }

    function inputFieldNumberFactory(item, min, max) {
        var element = $('<input>', {
            type: 'number',
            class: 'pure-input-1 config-item',
            id: 'cfg-' + item.name,
            name: item.name,
            min: min,
            max: max,
        });
        registerConfigUi(element, item);
        return [
            inputLabelFactory(item),
            element,
            inputHelpFactory(item),
        ];
    }

    function portNumberInputFactory(item) {
        return inputFieldNumberFactory(item, 1, 65535);
    }

    function pinNumberInputFactory(item) {
        return inputFieldNumberFactory(item, 0, 16);
    }

    function passwordFieldFactory(item, minlength) {
        var element = $('<input>', {
            type: 'password',
            class: 'pure-input-1 config-item',
            id: 'cfg-' + item.name,
            name: item.name,
            minlength: minlength,
        });
        registerConfigUi(element, item);
        return [
            inputLabelFactory(item),
            element,
            inputHelpFactory(item),
        ];
    }

    function devicePasswordInputFactory(item) {
        return passwordFieldFactory(item, 8);
    }

    function checkboxFactory(item) {
        var element = $('<input>', {
            type: 'checkbox',
            class: 'config-item',
            id: 'cfg-' + item.name,
            name: item.name,
        });
        registerConfigUi(element, item);
        return $('', {
            class: 'pure-checkbox',
        }).append([
            element,
            ' ' + item.name,
            inputHelpFactory(item),
        ]);
    }

    function legendFactory(item) {
        return $('<legend>', { text: item.name });
    }

    var protocols;
    function protocolInputField(item) {
        var container = $('
', {
            id: 'cfg-' + item.name,
            class: 'pure-g',
        });
        registerConfigUi(container, item);
        function protocolListFactory(protos) {
            protos.forEach(function (value) {
                var element = $('<input>', {
                    type: 'checkbox',
                    class: 'config-item protocols-item',
                    id: 'cfg-' + item.name + '-' + value,
                    name: item.name,
                    value: value,
                });
                container.append($('
', {
                    class: 'pure-u-1 pure-u-md-1-2 pure-u-lg-1-3 pure-u-xl-1-4',
                }).append($('', {
                    class: 'pure-checkbox',
                }).append([
                    element,
                    ' Protocol ' + value,
                ])));
            });
            protocols = protos;
        }
        $.ajax({
           url: "/protocols",
           type: "GET",
           contentType: 'application/json',
           success: protocolListFactory
        });
        return container;
    }

    function inputApply(itemName, data) {
        $('#cfg-' + itemName).val(data);
    }

    function checkboxApply(itemName, data) {
        $('#cfg-' + itemName).prop("checked", data);
    }

    function protocolApply(itemName, data) {
        if (protocols === undefined) {
            setTimeout(protocolApply(itemName, data), 100);
            return;
        }
        if (data.length == 0) {
            data = protocols;
        }
        data.forEach(function (value) {
            $('#cfg-' + itemName + '-' + value).prop('checked', true);
        });
    }

    function inputGet(element) {
        if (! element.get(0).checkValidity()) {
            return undefined;
        }
        return element.val();
    }

    function inputGetInt(element) {
        return parseInt(inputGet(element));
    }

    function checkboxGet(element) {
        return element.prop("checked");
    }

    function protocolGet(element) {
        var checked = $('.protocols-item:checked');
        if ($('.protocols-item').length === checked.length) {
            return [];
        }
        return $.map(checked, function (x) {
            return $(x).val();
        });
    }

    var lastConfig = {};
    var changes = {};
    function registerConfigUi(element, item) {
        element.change(function (event) {
            var newData = item.fetch(element);
            if (newData !== undefined) {
                if (JSON.stringify(lastConfig[item.name]) !== JSON.stringify(newData)) {
                    changes[item.name] = newData;
                } else {
                    delete changes[item.name];
                }
            }
        });
    }

    function throttle(callback, limit) {
        var wait = false;
        return function () {
            if (!wait) {
                callback.apply(this, arguments);
                wait = true;
                setTimeout(function () {
                    wait = false;
                }, limit);
            }
        };
    }

    function initConfigUi() {
        function applyConfig(data) {
            CONFIG_ITEMS.forEach(function (item) {
                if (item.apply) {
                    item.apply(item.name, data[item.name]);
                }
            });
            changes = {};
            lastConfig = data;
        }

        function loadConfig() {
            $.ajax({
                url: '/config',
                type: 'GET',
                contentType: 'application/json',
                success: applyConfig
            });
        }

        var settings = $("#settings");
        CONFIG_ITEMS.forEach(function (item) {
            settings.append(item.factory(item));
        });
        loadConfig();
        $('#settings-form').submit(function (event) {
            event.preventDefault();
            $.ajax({
                url: "/config",
                type: 'PUT',
                contentType: 'application/json',
                data: JSON.stringify(changes),
                success: applyConfig
            });
            return false;
        });
        $('#cfg-form-reset').click(function (event) {
            event.preventDefault();
            loadConfig();
            return false;
        });
    }

    function initDebugUi(debugFlags, container) {
        function create(debugFlag, helpText) {
            var checkbox = $('<input>', {
                type: 'checkbox',
                class: 'debug-item',
                id: 'debug-' + debugFlag,
                name: debugFlag,
            });
            checkbox.change(function (event) {
                submit(this);
            });
            return $('
', {
                class: 'pure-u-1 pure-u-md-1-3'
            }).append($('', {class: 'pure-checkbox'}).append([
                checkbox,
                ' ' + debugFlag,
                $('', {
                    class: 'pure-form-message',
                    text: helpText,
                }),
            ]));
        }

        function apply(data) {
            $.each(data, function (key, value) {
                $('#debug-' + key).prop("checked", value);
            });
        }

        function submit(item) {
            var data = {};
            data[item.name] = item.checked;
            $.ajax({
                url: '/debug',
                type: "PUT",
                data: JSON.stringify(data),
                contentType: 'application/json',
                success: apply
            });
        }

        $.each(debugFlags, function(debugFlag, helpText) {
            container.append(create(debugFlag, helpText));
        });
        $.ajax({
            url: "/debug",
            type: "GET",
            contentType: 'application/json',
            success: apply
        });
    }

    var sendCommand = throttle(
        function (params) {
            $.ajax({
                url: '/system',
                type: 'POST',
                data: JSON.stringify(params),
                contentType: 'application/json',
                success: function () {
                    SystemCommandActions[params.command]();
                }
            });
        },
        1000
    );

    $('.system-btn').click(function (event) {
        sendCommand({command: $(this).data('command')});
    });

    function loadFwVersion() {
        $.ajax({
            url: "/firmware",
            type: "GET",
            contentType: 'application/json',
            success: function (data) {
                $('#current-fw-version').append('Current version: ' + data.version);
            }
        });
    }

    function openWebSocket() {
        var container = $('#log-container');
        var pre = container.find('pre');

        var webSocket = new WebSocket("ws://" + location.hostname + ":81");
        var tm;

        function ping() {
            clearTimeout(tm);
            tm = setTimeout(function () {
                webSocket.send("__PING__");
                tm = setTimeout(function () {
                    webSocket.close();
                    openWebSocket();
                }, 2000);
            }, 5000);
        }

        webSocket.onmessage = function (event) {
            var message = event.data;

            if (message === "__PONG__") {
                ping();
                return;
            }

            pre.append(message);
            if (message === '\n' || (typeof message === "string" && message.indexOf('\n') !== -1)) {
                container.scrollTop(pre.get(0).scrollHeight);
            }
        };

        webSocket.onerror = function (event) {
            webSocket.close();
            if (tm === undefined) {
                openWebSocket();
            }
        };

        webSocket.onopen = function (event) {
            ping();
        };
    }
    // Clear log
    $('#btn-clear-log').click(function (event) {
        $('#log-container').find('pre').empty();
    });

    initConfigUi();
    initDebugUi(DEBUG_FLAGS, $("#debugflags"));
    loadFwVersion();
    openWebSocket();
});
<commit_msg>Frontend: put GroupItems in <fieldset><commit_after>$(function () {
    'strict mode';

    var CONFIG_ITEMS = [
        new GroupItem("General Config", legendFactory),
        new ConfigItem("deviceName", deviceNameInputFactory, inputApply, inputGet, "The general name of the device"),
        new ConfigItem("configPassword", devicePasswordInputFactory, inputApply, inputGet, "The admin password for the web UI (min. 8 characters)"),

        new GroupItem("MQTT Config", legendFactory),
        new ConfigItem("mqttBroker", hostNameInputFactory, inputApply, inputGet, "MQTT Broker host"),
        new ConfigItem("mqttBrokerPort", portNumberInputFactory, inputApply, inputGetInt, "MQTT Broker port"),
        new ConfigItem("mqttUser", inputFieldFactory, inputApply, inputGet, "MQTT username (optional)"),
        new ConfigItem("mqttPassword", passwordFieldFactory, inputApply, inputGet, "MQTT password (optional)"),
        new ConfigItem("mqttRetain", checkboxFactory, checkboxApply, checkboxGet, "Retain MQTT messages"),

        new GroupItem("MQTT Topic Config", legendFactory),
        new ConfigItem("mqttReceiveTopic", mqttTopicInputFactory, inputApply, inputGet, "Topic to publish received signal"),
        new ConfigItem("mqttSendTopic", mqttTopicInputFactory, inputApply, inputGet, "Topic to get signals to send from"),

        new GroupItem("433MHz RF Config", legendFactory),
        new ConfigItem("rfEchoMessages", checkboxFactory, checkboxApply, checkboxGet, "Echo sent rf messages back"),
        new ConfigItem("rfReceiverPin", pinNumberInputFactory, inputApply, inputGetInt, "The GPIO pin used for the rf receiver"),
        new ConfigItem("rfReceiverPinPullUp", checkboxFactory, checkboxApply, checkboxGet, "Activate pullup on rf receiver pin (required for 5V protection with reverse diode)"),
        new ConfigItem("rfTransmitterPin", pinNumberInputFactory, inputApply, inputGetInt, "The GPIO pin used for the RF transmitter"),

        new GroupItem("Enabled RF protocols", legendFactory),
        new ConfigItem("rfProtocols", protocolInputField, protocolApply, protocolGet, ""),

        new GroupItem("Log Config", legendFactory),
        new ConfigItem("serialLogLevel", logLevelInputFactory, inputApply, inputGet, "Level for serial logging"),
        new ConfigItem("webLogLevel", logLevelInputFactory, inputApply, inputGet, "Level for logging to the web UI"),
        new ConfigItem("syslogLevel", logLevelInputFactory, inputApply, inputGet, "Level for syslog logging"),
        new ConfigItem("syslogHost", hostNameInputFactory, inputApply, inputGet, "Syslog server (optional)"),
        new ConfigItem("syslogPort", portNumberInputFactory, inputApply, inputGetInt, "Syslog port (optional)"),

        new GroupItem("Status LED", legendFactory),
        new ConfigItem("ledPin", pinNumberInputFactory, inputApply, inputGetInt, "The GPIO pin used for the status LED"),
        new ConfigItem("ledActiveHigh", checkboxFactory, checkboxApply, checkboxGet, "The way how the LED is connected to the pin (false for built-in led)")
    ];

    var DEBUG_FLAGS = {
        protocolRaw: "Enable Raw RF message logging",
        systemLoad: "Show the processed loop() iterations for each second",
        freeHeap: "Show the free heap memory every second"
    };

    var SystemCommandActions = {
        restart: function () {
            var body = $("body");
            body.empty();
            body.append("
Device will reboot!

Try to reconnect in 15 seconds.
");
            setTimeout(function () {
                window.location.reload(true);
            }, 15000);
        },
        reset_wifi: function () {
            var body = $("body");
            body.empty();
            body.append("
Devices WIFI settings where cleared!

Please reconfigure it.
");
        },
        reset_config: function () {
            var body = $("body");
            body.empty();
            body.append("
Devices Config was reset - reboot device!
" +
                "
You might have to reconfigure the wifi!
" +
                "
Reload page in 10 seconds...
");
            setTimeout(function () {
                window.location.reload(true);
            }, 10000);
        }
    };

    function ConfigItem(name, factory, apply, fetch, help) {
        this.name = name;
        this.factory = factory;
        this.apply = apply;
        this.fetch = fetch;
        this.help = help;
    }

    function GroupItem(name, factory) {
        this.name = name;
        this.container = true;
        this.factory = factory;
    }

    function inputLabelFactory(item) {
        return $('', {
            text: item.name,
            for: 'cfg-' + item.name,
        });
    }

    function inputHelpFactory(item) {
        return $('', {
            class: 'pure-form-message',
            text: item.help,
        });
    }

    function logLevelInputFactory(item) {
        var element = $('<select>', {
            class: 'config-item',
            id: 'cfg-' + item.name,
            name: item.name,
        }).append([
            $('<option>', {value: '', text: "None"}),
            $('<option>', {value: 'error', text: 'Error'}),
            $('<option>', {value: 'warning', text: 'Warning'}),
            $('<option>', {value: 'info', text: 'Info'}),
            $('<option>', {value: 'debug', text: 'Debug'}),
        ]);
        registerConfigUi(element, item);
        return [
            inputLabelFactory(item),
            element,
            inputHelpFactory(item),
        ];
    }

    function inputFieldFactory(item, pattern, required) {
        var element = $('<input>', {
            type: 'text',
            class: 'pure-input-1 config-item',
            id: 'cfg-' + item.name,
            pattern: pattern,
            required: required,
            name: item.name,
        });
        registerConfigUi(element, item);
        return [
            inputLabelFactory(item),
            element,
            inputHelpFactory(item),
        ];
    }

    function deviceNameInputFactory(item) {
        return inputFieldFactory(item, '[.-_A-Za-z0-9]+', true);
    }

    function hostNameInputFactory(item) {
        return inputFieldFactory(item, '[.-_A-Za-z0-9]*');
    }

    function mqttTopicInputFactory(item) {
        return inputFieldFactory(item, undefined, true);
    }

    function inputFieldNumberFactory(item, min, max) {
        var element = $('<input>', {
            type: 'number',
            class: 'pure-input-1 config-item',
            id: 'cfg-' + item.name,
            name: item.name,
            min: min,
            max: max,
        });
        registerConfigUi(element, item);
        return [
            inputLabelFactory(item),
            element,
            inputHelpFactory(item),
        ];
    }

    function portNumberInputFactory(item) {
        return inputFieldNumberFactory(item, 1, 65535);
    }

    function pinNumberInputFactory(item) {
        return inputFieldNumberFactory(item, 0, 16);
    }

    function passwordFieldFactory(item, minlength) {
        var element = $('<input>', {
            type: 'password',
            class: 'pure-input-1 config-item',
            id: 'cfg-' + item.name,
            name: item.name,
            minlength: minlength,
        });
        registerConfigUi(element, item);
        return [
            inputLabelFactory(item),
            element,
            inputHelpFactory(item),
        ];
    }

    function devicePasswordInputFactory(item) {
        return passwordFieldFactory(item, 8);
    }

    function checkboxFactory(item) {
        var element = $('<input>', {
            type: 'checkbox',
            class: 'config-item',
            id: 'cfg-' + item.name,
            name: item.name,
        });
        registerConfigUi(element, item);
        return $('', {
            class: 'pure-checkbox',
        }).append([
            element,
            ' ' + item.name,
            inputHelpFactory(item),
        ]);
    }

    function legendFactory(item) {
        return $('<fieldset>', {class: 'config-group'}).append(
            $('<legend>', { text: item.name })
        );
    }

    var protocols;
    function protocolInputField(item) {
        var container = $('
', {
            id: 'cfg-' + item.name,
            class: 'pure-g',
        });
        registerConfigUi(container, item);
        function protocolListFactory(protos) {
            protos.forEach(function (value) {
                var element = $('<input>', {
                    type: 'checkbox',
                    class: 'config-item protocols-item',
                    id: 'cfg-' + item.name + '-' + value,
                    name: item.name,
                    value: value,
                });
                container.append($('
', {
                    class: 'pure-u-1 pure-u-md-1-2 pure-u-lg-1-3 pure-u-xl-1-4',
                }).append($('', {
                    class: 'pure-checkbox',
                }).append([
                    element,
                    ' Protocol ' + value,
                ])));
            });
            protocols = protos;
        }
        $.ajax({
           url: "/protocols",
           type: "GET",
           contentType: 'application/json',
           success: protocolListFactory
        });
        return container;
    }

    function inputApply(itemName, data) {
        $('#cfg-' + itemName).val(data);
    }

    function checkboxApply(itemName, data) {
        $('#cfg-' + itemName).prop("checked", data);
    }

    function protocolApply(itemName, data) {
        if (protocols === undefined) {
            setTimeout(protocolApply(itemName, data), 100);
            return;
        }
        if (data.length == 0) {
            data = protocols;
        }
        data.forEach(function (value) {
            $('#cfg-' + itemName + '-' + value).prop('checked', true);
        });
    }

    function inputGet(element) {
        if (! element.get(0).checkValidity()) {
            return undefined;
        }
        return element.val();
    }

    function inputGetInt(element) {
        return parseInt(inputGet(element));
    }

    function checkboxGet(element) {
        return element.prop("checked");
    }

    function protocolGet(element) {
        var checked = $('.protocols-item:checked');
        if ($('.protocols-item').length === checked.length) {
            return [];
        }
        return $.map(checked, function (x) {
            return $(x).val();
        });
    }

    var lastConfig = {};
    var changes = {};
    function registerConfigUi(element, item) {
        element.change(function (event) {
            var newData = item.fetch(element);
            if (newData !== undefined) {
                if (JSON.stringify(lastConfig[item.name]) !== JSON.stringify(newData)) {
                    changes[item.name] = newData;
                } else {
                    delete changes[item.name];
                }
            }
        });
    }

    function throttle(callback, limit) {
        var wait = false;
        return function () {
            if (!wait) {
                callback.apply(this, arguments);
                wait = true;
                setTimeout(function () {
                    wait = false;
                }, limit);
            }
        };
    }

    function initConfigUi() {
        function applyConfig(data) {
            CONFIG_ITEMS.forEach(function (item) {
                if (item.apply) {
                    item.apply(item.name, data[item.name]);
                }
            });
            changes = {};
            lastConfig = data;
        }

        function loadConfig() {
            $.ajax({
                url: '/config',
                type: 'GET',
                contentType: 'application/json',
                success: applyConfig
            });
        }

        var settings = $("#settings");
        CONFIG_ITEMS.forEach(function (item) {
            var result = item.factory(item);
            if (item.container) {
                result.appendTo(settings);
                container = result;
            } else {
                container.append(result);
            }
        });
        loadConfig();
        $('#settings-form').submit(function (event) {
            event.preventDefault();
            $.ajax({
                url: "/config",
                type: 'PUT',
                contentType: 'application/json',
                data: JSON.stringify(changes),
                success: applyConfig
            });
            return false;
        });
        $('#cfg-form-reset').click(function (event) {
            event.preventDefault();
            loadConfig();
            return false;
        });
    }

    function initDebugUi(debugFlags, container) {
        function create(debugFlag, helpText) {
            var checkbox = $('<input>', {
                type: 'checkbox',
                class: 'debug-item',
                id: 'debug-' + debugFlag,
                name: debugFlag,
            });
            checkbox.change(function (event) {
                submit(this);
            });
            return $('
', {
                class: 'pure-u-1 pure-u-md-1-3'
            }).append($('', {class: 'pure-checkbox'}).append([
                checkbox,
                ' ' + debugFlag,
                $('', {
                    class: 'pure-form-message',
                    text: helpText,
                }),
            ]));
        }

        function apply(data) {
            $.each(data, function (key, value) {
                $('#debug-' + key).prop("checked", value);
            });
        }

        function submit(item) {
            var data = {};
            data[item.name] = item.checked;
            $.ajax({
                url: '/debug',
                type: "PUT",
                data: JSON.stringify(data),
                contentType: 'application/json',
                success: apply
            });
        }

        $.each(debugFlags, function(debugFlag, helpText) {
            container.append(create(debugFlag, helpText));
        });
        $.ajax({
            url: "/debug",
            type: "GET",
            contentType: 'application/json',
            success: apply
        });
    }

    var sendCommand = throttle(
        function (params) {
            $.ajax({
                url: '/system',
                type: 'POST',
                data: JSON.stringify(params),
                contentType: 'application/json',
                success: function () {
                    SystemCommandActions[params.command]();
                }
            });
        },
        1000
    );

    $('.system-btn').click(function (event) {
        sendCommand({command: $(this).data('command')});
    });

    function loadFwVersion() {
        $.ajax({
            url: "/firmware",
            type: "GET",
            contentType: 'application/json',
            success: function (data) {
                $('#current-fw-version').append('Current version: ' + data.version);
            }
        });
    }

    function openWebSocket() {
        var container = $('#log-container');
        var pre = container.find('pre');

        var webSocket = new WebSocket("ws://" + location.hostname + ":81");
        var tm;

        function ping() {
            clearTimeout(tm);
            tm = setTimeout(function () {
                webSocket.send("__PING__");
                tm = setTimeout(function () {
                    webSocket.close();
                    openWebSocket();
                }, 2000);
            }, 5000);
        }

        webSocket.onmessage = function (event) {
            var message = event.data;

            if (message === "__PONG__") {
                ping();
                return;
            }

            pre.append(message);
            if (message === '\n' || (typeof message === "string" && message.indexOf('\n') !== -1)) {
                container.scrollTop(pre.get(0).scrollHeight);
            }
        };

        webSocket.onerror = function (event) {
            webSocket.close();
            if (tm === undefined) {
                openWebSocket();
            }
        };

        webSocket.onopen = function (event) {
            ping();
        };
    }
    // Clear log
    $('#btn-clear-log').click(function (event) {
        $('#log-container').find('pre').empty();
    });

    initConfigUi();
    initDebugUi(DEBUG_FLAGS, $("#debugflags"));
    loadFwVersion();
    openWebSocket();
});
<|endoftext|>


<commit_before>// Brunch automatically concatenates all files in your
// watched paths. Those paths can be configured at
// config.paths.watched in "brunch-config.js".
//
// However, those files will only be executed if
// explicitly imported. The only exception are files
// in vendor, which are never wrapped in imports and
// therefore are always executed.

// Import dependencies
//
// If you no longer want to use a dependency, remember
// to also remove its path from "config.paths.watched".
import "phoenix_html"

import $ from "jquery"
import "jquery"

// Ugh...
global.jQuery = require("jquery")  // Needed for tether or bootstrap...
global.bootstrap = require("bootstrap")
// Import local files
//
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".

// import socket from "./socket"

// Set up our Elm App
import Elm from './vender/';

const elmDiv = document.querySelector('#elm-container');
if(elmDiv) {
  Elm.App.embed(elmDiv);
}
<commit_msg>removed directory path since phoenix handles js file combining<commit_after>// Brunch automatically concatenates all files in your
// watched paths. Those paths can be configured at
// config.paths.watched in "brunch-config.js".
//
// However, those files will only be executed if
// explicitly imported. The only exception are files
// in vendor, which are never wrapped in imports and
// therefore are always executed.

// Import dependencies
//
// If you no longer want to use a dependency, remember
// to also remove its path from "config.paths.watched".
import "phoenix_html"

import $ from "jquery"
import "jquery"

// Ugh...
global.jQuery = require("jquery")  // Needed for tether or bootstrap...
global.bootstrap = require("bootstrap")
// Import local files
//
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".

// import socket from "./socket"

// Set up our Elm App
const elmDiv = document.querySelector('#elm-container');
if(elmDiv) {
  Elm.App.embed(elmDiv);
}
<|endoftext|>


<commit_before>// Brunch automatically concatenates all files in your
// watched paths. Those paths can be configured at
// config.paths.watched in "brunch-config.js".
//
// However, those files will only be executed if
// explicitly imported. The only exception are files
// in vendor, which are never wrapped in imports and
// therefore are always executed.

// Import dependencies
//
// If you no longer want to use a dependency, remember
// to also remove its path from "config.paths.watched".
import "deps/phoenix_html/web/static/js/phoenix_html"

// Import local files
//
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".

import socket from "./socket"
// import L from 'leaflet'
// TODO: find a way to load with npm and remove scripts from html template

let channel = socket.channel('issues:home', {})
channel.join()
  .receive('ok', resp => { console.log('Joined successfully', resp) })
  .receive('error', resp => { console.log('Unable to join', resp) })

let opts = {
  contextmenu: true,
  contextmenuWidth: 140,
  contextmenuItems: [
    {
      text: 'Make report',
      callback: e => {
        form.find('textarea[name="message"]').val('')
        form.find('input[name="latitude"]').val(e.latlng.lat)
        form.find('input[name="longitude"]').val(e.latlng.lng)
        modal.modal('show')
      }
    }
  ],
  fullscreenControl: true
}

let map = L.map('map', opts).setView([-12.043333, -77.028333], 12)
let markers = []

map.on('locationfound', e => {
  let radius = e.accuracy / 2
  L.circle(e.latlng, radius).addTo(map)
})

L.tileLayer('//{s}.tile.osm.org/{z}/{x}/{y}.png', {
  attribution: '© OpenStreetMap contributors',
}).addTo(map);

$('#btn-enable-geolocation').on('click', function(event) {
  event.preventDefault()
  map.locate({setView: true, zoom: 16})
})

$('#btn-load-all-issues').on('click', function(event) {
  event.preventDefault()
  loadIssues()
})

$('#btn-load-fixed').on('click', function(event) {
  event.preventDefault()
  loadIssues({fixed: true})
})

$('#btn-load-unfixed').on('click', function(event) {
  event.preventDefault()
  loadIssues({fixed: false})
})

let form = $('#issue-form')
let modal = $('#issue-modal')

const ICONS = {
  'broken semaphore': '/images/semaphore.png',
  'broken lamp post': '/images/lamp.png'
}

window.fixIssue = function (id) {
  $.ajax({
    method: 'put',
    url: `/api/issues/${id}/`,
    data: {
      issue: {
        fixed: true
      }
    },
    success: function () {}
  })
}

function addMarker(issue) {
  var marker = L.marker([issue.latitude, issue.longitude], {
    icon: L.icon({
      iconUrl: ICONS[issue.type]
    })
  })
  marker.addTo(map)
  markers.push(marker)
  marker.on('click', e => {
    L.popup()
      .setLatLng(e.latlng)
      .setContent(`

${issue.message} ${issue.fixed ? '(fixed)' : ''}

        <button class="btn btn-primary" onClick="fixIssue(${issue.id})">
          Fix
        </button>
      `)
      .openOn(map)
  })
}

function removeMarkers() {
  markers.forEach(marker => {
    map.removeLayer(marker)
  })
  markers = []
}

function loadIssues(params) {
  removeMarkers()
  params = params || {}
  $.ajax({
    method: 'get',
    url: `/api/issues/?${$.param(params)}`,
    success: function (issues) {
      issues.data.map(issue => {
        addMarker(issue)
      })
    }
  })
}

loadIssues()

channel.on('new_issue', payload => {
  addMarker(payload.issue)
})

$('#btn-add-issue').on('click', function () {
  let issue = {
    latitude: parseFloat(form.find('input[name="latitude"]').val()),
    longitude: parseFloat(form.find('input[name="longitude"]').val()),
    type: form.find('select[name="type"]').val(),
    message: form.find('textarea[name="message"]').val(),
    fixed: false
  }
  channel.push('new_issue', {issue: issue})
  modal.modal('toggle')
})
<commit_msg>change openlayers domain<commit_after>// Brunch automatically concatenates all files in your
// watched paths. Those paths can be configured at
// config.paths.watched in "brunch-config.js".
//
// However, those files will only be executed if
// explicitly imported. The only exception are files
// in vendor, which are never wrapped in imports and
// therefore are always executed.

// Import dependencies
//
// If you no longer want to use a dependency, remember
// to also remove its path from "config.paths.watched".
import "deps/phoenix_html/web/static/js/phoenix_html"

// Import local files
//
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".

import socket from "./socket"
// import L from 'leaflet'
// TODO: find a way to load with npm and remove scripts from html template

let channel = socket.channel('issues:home', {})
channel.join()
  .receive('ok', resp => { console.log('Joined successfully', resp) })
  .receive('error', resp => { console.log('Unable to join', resp) })

let opts = {
  contextmenu: true,
  contextmenuWidth: 140,
  contextmenuItems: [
    {
      text: 'Make report',
      callback: e => {
        form.find('textarea[name="message"]').val('')
        form.find('input[name="latitude"]').val(e.latlng.lat)
        form.find('input[name="longitude"]').val(e.latlng.lng)
        modal.modal('show')
      }
    }
  ],
  fullscreenControl: true
}

let map = L.map('map', opts).setView([-12.043333, -77.028333], 12)
let markers = []

map.on('locationfound', e => {
  let radius = e.accuracy / 2
  L.circle(e.latlng, radius).addTo(map)
})

L.tileLayer('//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© OpenStreetMap contributors',
}).addTo(map);

$('#btn-enable-geolocation').on('click', function(event) {
  event.preventDefault()
  map.locate({setView: true, zoom: 16})
})

$('#btn-load-all-issues').on('click', function(event) {
  event.preventDefault()
  loadIssues()
})

$('#btn-load-fixed').on('click', function(event) {
  event.preventDefault()
  loadIssues({fixed: true})
})

$('#btn-load-unfixed').on('click', function(event) {
  event.preventDefault()
  loadIssues({fixed: false})
})

let form = $('#issue-form')
let modal = $('#issue-modal')

const ICONS = {
  'broken semaphore': '/images/semaphore.png',
  'broken lamp post': '/images/lamp.png'
}

window.fixIssue = function (id) {
  $.ajax({
    method: 'put',
    url: `/api/issues/${id}/`,
    data: {
      issue: {
        fixed: true
      }
    },
    success: function () {}
  })
}

function addMarker(issue) {
  var marker = L.marker([issue.latitude, issue.longitude], {
    icon: L.icon({
      iconUrl: ICONS[issue.type]
    })
  })
  marker.addTo(map)
  markers.push(marker)
  marker.on('click', e => {
    L.popup()
      .setLatLng(e.latlng)
      .setContent(`

${issue.message} ${issue.fixed ? '(fixed)' : ''}

        <button class="btn btn-primary" onClick="fixIssue(${issue.id})">
          Fix
        </button>
      `)
      .openOn(map)
  })
}

function removeMarkers() {
  markers.forEach(marker => {
    map.removeLayer(marker)
  })
  markers = []
}

function loadIssues(params) {
  removeMarkers()
  params = params || {}
  $.ajax({
    method: 'get',
    url: `/api/issues/?${$.param(params)}`,
    success: function (issues) {
      issues.data.map(issue => {
        addMarker(issue)
      })
    }
  })
}

loadIssues()

channel.on('new_issue', payload => {
  addMarker(payload.issue)
})

$('#btn-add-issue').on('click', function () {
  let issue = {
    latitude: parseFloat(form.find('input[name="latitude"]').val()),
    longitude: parseFloat(form.find('input[name="longitude"]').val()),
    type: form.find('select[name="type"]').val(),
    message: form.find('textarea[name="message"]').val(),
    fixed: false
  }
  channel.push('new_issue', {issue: issue})
  modal.modal('toggle')
})
<|endoftext|>


<commit_before>(function() {
  var storage = new Storage();
  storage.load();

  var graph = new Graph();
  var editor = new Editor();
  editor.setRawText(storage.getEditorText());
  editor.setListener(draw);

  var config = new Config();
  config.apply(storage.getConfigObj());
  config.setListener(draw);

  draw();

  function draw() {
    var commands = editor.getCommands();
    graph.draw(commands);

    storage.save(editor, config);
  }

  function Graph() {
    var _target = document.getElementById('target');

    this.draw = function(commands) {
      _refreshCanvas();

      var git = new GitGraphWrapperExtention({
        template: _createTemplate(),
        reverseArrow: config.reverseArrow,
        orientation: config.orientation,
        mode: config.mode,
        author: config.author === '' ? null : config.author
      });

      var branchDefaultOptions = {};
      for (var key in config) {
        if (/^branch_/.test(key)) {
          var value = config[key];
          var matchResult = /_([^_]+)_(.*)/.exec(key);
          var branchName = matchResult[1];
          var optionName = matchResult[2];
          if (!(branchName in branchDefaultOptions)) {
            branchDefaultOptions[branchName] = {};
          }
          branchDefaultOptions[branchName][optionName] = value;
        }
      }
      git.defaultOptions({
        branch: branchDefaultOptions
      });

      for (var i=0; i<commands.length; i++) {
        var command = commands[i];
        command.invoke(git);
      }
    };

    function _createTemplate() {
      var template = new GitGraph.Template().get(config.template);

      if (config.colors !== '') {
        var colors = config.colors.split(',');
        template.colors = colors;
      }

      // arrow
      if (config.arrowColor) {
        template.arrow.color = config.arrowColor;
      }
      if (config.arrowSize) {
        template.arrow.size = config.arrowSize;
      }
      if (config.arrowOffset) {
        template.arrow.offset = config.arrowOffset;
      }

      // branch
      if (config.branchColor) {
        template.branch.color = config.branchColor;
      }
      if (config.branchLineWidth) {
        template.branch.lineWidth = config.branchLineWidth;
      }
      if (config.branchMergeStyle) {
        template.branch.mergeStyle = config.branchMergeStyle;
      }
      if (config.branchSpacingX) {
        template.branch.spacingX = config.branchSpacingX;
      }
      if (config.branchSpacingY) {
        template.branch.spacingY = config.branchSpacingY;
      }
      if (config.branchShowLabel) {
        template.branch.showLabel = config.branchShowLabel === 'true';
      }
      if (config.branchLabelColor) {
        template.branch.labelColor = config.branchLabelColor;
      }
      if (config.branchLabelFont) {
        template.branch.labelFont = config.branchLabelFont;
      }

      // commit
      if (config.commitSpacingX) {
        template.commit.spacingX = config.commitSpacingX;
      }
      if (config.commitSpacingY) {
        template.commit.spacingY = config.commitSpacingY;
      }
      if (config.commitWidthExtension) {
        template.commit.widthExtension = config.commitWidthExtension;
      }
      if (config.commitColor) {
        template.commit.color = config.commitColor;
      }
      if (config.commitShouldDisplayTooltipsInCompactMode) {
        template.commit.shouldDisplayTooltipsInCompactMode = config.commitShouldDisplayTooltipsInCompactMode === 'true';
      }

      // commit.dot
      if (config.commitDotColor) {
        template.commit.dot.color = config.commitDotColor;
      }
      if (config.commitDotSize) {
        template.commit.dot.size = config.commitDotSize;
      }
      if (config.commitDotStrokeWidth) {
        template.commit.dot.strokeWidth = config.commitDotStrokeWidth;
      }
      if (config.commitDotStrokeColor) {
        template.commit.dot.strokeColor = config.commitDotStrokeColor;
      }

      // commit.message
      if (config.commitMessageColor) {
        template.commit.message.color = config.commitMessageColor;
      }
      if (config.commitMessageDisplay) {
        template.commit.message.display = config.commitMessageDisplay === 'true';
      }
      if (config.commitMessageDisplayAuthor) {
        template.commit.message.displayAuthor = config.commitMessageDisplayAuthor === 'true';
      }
      if (config.commitMessageDisplayBranch) {
        template.commit.message.displayBranch = config.commitMessageDisplayBranch === 'true';
      }
      if (config.commitMessageDisplayHash) {
        template.commit.message.displayHash = config.commitMessageDisplayHash === 'true';
      }
      if (config.commitMessageFont) {
        template.commit.message.font = config.commitMessageFont;
      }

      return template;
    }

    function _refreshCanvas() {
      _removeCurrentCanvas();
      var canvas = _createCanvas();
      _target.appendChild(canvas);
    }

    function _removeCurrentCanvas() {
      if (_target.childNodes.length !== 0) {
        _target.removeChild(_target.firstChild);
      }
    }

    function _createCanvas() {
      var canvas = document.createElement('canvas');
      canvas.id = 'gitGraph';
      return canvas;
    }
  }

  function Command(line) {
    var _elements = _normalize(line);
    var _method = _elements[0];
    var _arguments = _elements.slice(1);

    this.invoke = function(git) {
      if (!(typeof git[_method] === 'function')) {
        return;
      }

      try {
        git[_method].apply(git, _arguments);
      } catch (e) {
        console.warn(e);
      }
    };

    function _normalize(line) {
      var elements = line.split(' ');
      var result = [];

      for (var i=0; i<elements.length; i++) {
        var element = elements[i]

        // remove empty strings
        if (element !== '') {
          result.push(element);
        }
      }

      return result;
    }
  }

  function Editor() {
    var _editor = document.getElementById('editor');
    var _timeoutKey;

    this.setListener = function(listener) {
      _editor.addEventListener('keyup', function() {
        if (_timeoutKey) {
          clearTimeout(_timeoutKey);
        }

        _timeoutKey = setTimeout(function() {
          try {
            listener();
          } finally {
            _timeoutKey = null;
          }
        }, 500);
      });
    };

    this.getCommands = function() {
      var commands = [];

      var lines = _editor.value.split('\n');
      for (var i=0; i<lines.length; i++) {
        var line = lines[i].trim();

        if (line.length !== 0) {
          commands.push(new Command(line));
        }
      }

      return commands;
    };

    this.getRawText = function() {
      return _editor.value;
    };

    this.setRawText = function(value) {
      _editor.value = value;
    };
  }

  function Storage() {
    var KEY = "gitgraph.storage";
    var _editor;
    var _config;

    this.save = function(editor, config) {
      var obj = {
        editor: editor.getRawText(),
        config: config
      };

      localStorage.setItem(KEY, JSON.stringify(obj));
    };

    this.load = function() {
      var json = localStorage.getItem(KEY);
      if (typeof json === 'string') {
        var obj = JSON.parse(json);
        _editor = obj.editor;
        _config = obj.config;
      } else {
        _editor = '';
        _config = {};
      }
    };

    this.getEditorText = function() {
      return _editor;
    };

    this.getConfigObj = function() {
      return _config;
    };

    this.remove = function() {
      localStorage.removeItem(KEY);
    };
  }

  function Config() {
    var _defaults = {
      template: 'metro',
      orientation: 'vertical',
      mode: '',
      author: '',
      reverseArrow: false,
      colors: '',
      arrowColor: '',
      arrowSize: '',
      arrowOffset: '',
      branchColor: '',
      branchLineWidth: '',
      branchMergeStyle: '',
      branchSpacingX: '',
      branchSpacingY: '',
      branchShowLabel: '',
      branchLabelColor: '',
      branchLabelFont: '',
      commitSpacingX: '',
      commitSpacingY: '',
      commitWidthExtension: '',
      commitColor: '',
      commitDotColor: '',
      commitDotSize: '',
      commitDotStrokeWidth: '',
      commitMessageColor: '',
      commitMessageDisplay: '',
      commitMessageDisplayAuthor: '',
      commitMessageDisplayBranch: '',
      commitMessageDisplayHash: '',
      commitMessageFont: '',
      commitShouldDisplayTooltipsInCompactMode: ''
    };

    var _elements = {};
    var _listener = function() {};
    var _self = this;

    for (var key in _defaults) {
      _initElement(key);
    }

    this.apply = function(obj) {
      for (var key in _defaults) {
        _setValue(key, obj[key] || _defaults[key]);
      }

      for (var key in obj) {
        var matchResult = /^branch_([^_]+)_.*$/.exec(key);
        if (matchResult) {
          _addBranchOptionArea(matchResult[1]);
          _initElement(key);
          _setValue(key, obj[key]);
        }
      }
    };

    function _setValue(key, value) {
      _self[key] = value;
      _elements[key].setValue(_self[key]);
    }

    this.reset = function() {
      this.apply(_defaults);
    };

    this.setListener = function(listener) {
      _listener = listener;
    };

    this.addBranchOption = function(name) {
      _initElement('branch_' + name + '_color');
    };

    this.removeBranchOption = function(name) {
      for (var key in _elements) {
        if (new RegExp('^branch_' + name).test(key)) {
          delete this[key];
          delete _elements[key];
        }
      }
    };

    function _initElement(elementId) {
      _elements[elementId] = new Element(document.getElementById(elementId));
      _elements[elementId].addEventListener(_onChange);
      _self[key] = _elements[elementId].getValue();
    }

    function _onChange() {
      for (var key in _elements) {
        _self[key] = _elements[key].getValue();
      }

      _listener();
    }

    this.reset();
  }

  function Element(e) {
    this.getValue = function() {
      if (_isCheckbox()) {
        return e.checked;
      } else if (e.type === 'number') {
        return Number(e.value);
      } else {
        return e.value;
      }
    };

    this.setValue = function(value) {
      if (_isCheckbox()) {
        e.checked = value;
      } else {
        e.value = value;
      }
    };

    this.addEventListener = function(listener) {
      e.addEventListener('change', listener);
    };

    function _isCheckbox() {
      return e.type === 'checkbox';
    }
  }

  var bottomArea = document.getElementById('bottom-area');
  var expand = document.getElementById('expand');
  expand.addEventListener('click', function() {
    if (bottomArea.style.height === '') {
      bottomArea.style.height = '80%';
      expand.innerText = 'expand↓';
    } else {
      bottomArea.style.height = '';
      expand.innerText = 'expand↑';
    }
  });

  var newBranchName = document.getElementById('newBranchName');

  document.getElementById('addNewBranchOptionButton').addEventListener('click', function() {
    var name = newBranchName.value;

    _addBranchOptionArea(name);

    config.addBranchOption(name);
  });

  function _addBranchOptionArea(name) {
    // skip if aleady exists
    if (document.getElementById(name)) {
      return;
    }

    var newBranchesTarget = document.getElementById('newBranchesTarget');

    var newBranchOptionTemplate = document.getElementById('newBranchOptionTemplate');
    var html = newBranchOptionTemplate.innerText;
    html = html.replace(/\${name}/g, name);

    var div = document.createElement('div');
    div.innerHTML = html;

    newBranchesTarget.appendChild(div);

    var removeButton = document.getElementById(name + '_remove');
    removeButton.addEventListener('click', function() {
      newBranchesTarget.removeChild(div);
      config.removeBranchOption(name);
      draw();
    });
  }
})();
<commit_msg>refactoring branch options<commit_after>(function() {
  var storage = new Storage();
  storage.load();

  var graph = new Graph();
  var editor = new Editor();
  editor.setRawText(storage.getEditorText());
  editor.setListener(draw);

  var config = new Config();
  _rebuildBranchOption(storage);
  config.apply(storage.getConfigObj());
  config.setListener(draw);

  draw();

  function draw() {
    var commands = editor.getCommands();
    graph.draw(commands);

    storage.save(editor, config);
  }

  function Graph() {
    var _target = document.getElementById('target');

    this.draw = function(commands) {
      _refreshCanvas();

      var git = new GitGraphWrapperExtention({
        template: _createTemplate(),
        reverseArrow: config.reverseArrow,
        orientation: config.orientation,
        mode: config.mode,
        author: config.author === '' ? null : config.author
      });

      git.defaultOptions({
        branch: _createBranchOption()
      });

      for (var i=0; i<commands.length; i++) {
        var command = commands[i];
        command.invoke(git);
      }
    };

    function _createBranchOption() {
      var branchDefaultOptions = {};

      for (var key in config) {
        var branchOptionKey = new BranchOptionKey(key);
        if (branchOptionKey.isBranchOption()) {
          var value = config[key];
          var branchName = branchOptionKey.getBranchName();
          var optionName = branchOptionKey.getOptionName();
          if (!(branchName in branchDefaultOptions)) {
            branchDefaultOptions[branchName] = {};
          }
          branchDefaultOptions[branchName][optionName] = value;
        }
      }

      return branchDefaultOptions;
    }

    function _createTemplate() {
      var template = new GitGraph.Template().get(config.template);

      if (config.colors !== '') {
        var colors = config.colors.split(',');
        template.colors = colors;
      }

      // arrow
      if (config.arrowColor) {
        template.arrow.color = config.arrowColor;
      }
      if (config.arrowSize) {
        template.arrow.size = config.arrowSize;
      }
      if (config.arrowOffset) {
        template.arrow.offset = config.arrowOffset;
      }

      // branch
      if (config.branchColor) {
        template.branch.color = config.branchColor;
      }
      if (config.branchLineWidth) {
        template.branch.lineWidth = config.branchLineWidth;
      }
      if (config.branchMergeStyle) {
        template.branch.mergeStyle = config.branchMergeStyle;
      }
      if (config.branchSpacingX) {
        template.branch.spacingX = config.branchSpacingX;
      }
      if (config.branchSpacingY) {
        template.branch.spacingY = config.branchSpacingY;
      }
      if (config.branchShowLabel) {
        template.branch.showLabel = config.branchShowLabel === 'true';
      }
      if (config.branchLabelColor) {
        template.branch.labelColor = config.branchLabelColor;
      }
      if (config.branchLabelFont) {
        template.branch.labelFont = config.branchLabelFont;
      }

      // commit
      if (config.commitSpacingX) {
        template.commit.spacingX = config.commitSpacingX;
      }
      if (config.commitSpacingY) {
        template.commit.spacingY = config.commitSpacingY;
      }
      if (config.commitWidthExtension) {
        template.commit.widthExtension = config.commitWidthExtension;
      }
      if (config.commitColor) {
        template.commit.color = config.commitColor;
      }
      if (config.commitShouldDisplayTooltipsInCompactMode) {
        template.commit.shouldDisplayTooltipsInCompactMode = config.commitShouldDisplayTooltipsInCompactMode === 'true';
      }

      // commit.dot
      if (config.commitDotColor) {
        template.commit.dot.color = config.commitDotColor;
      }
      if (config.commitDotSize) {
        template.commit.dot.size = config.commitDotSize;
      }
      if (config.commitDotStrokeWidth) {
        template.commit.dot.strokeWidth = config.commitDotStrokeWidth;
      }
      if (config.commitDotStrokeColor) {
        template.commit.dot.strokeColor = config.commitDotStrokeColor;
      }

      // commit.message
      if (config.commitMessageColor) {
        template.commit.message.color = config.commitMessageColor;
      }
      if (config.commitMessageDisplay) {
        template.commit.message.display = config.commitMessageDisplay === 'true';
      }
      if (config.commitMessageDisplayAuthor) {
        template.commit.message.displayAuthor = config.commitMessageDisplayAuthor === 'true';
      }
      if (config.commitMessageDisplayBranch) {
        template.commit.message.displayBranch = config.commitMessageDisplayBranch === 'true';
      }
      if (config.commitMessageDisplayHash) {
        template.commit.message.displayHash = config.commitMessageDisplayHash === 'true';
      }
      if (config.commitMessageFont) {
        template.commit.message.font = config.commitMessageFont;
      }

      return template;
    }

    function _refreshCanvas() {
      _removeCurrentCanvas();
      var canvas = _createCanvas();
      _target.appendChild(canvas);
    }

    function _removeCurrentCanvas() {
      if (_target.childNodes.length !== 0) {
        _target.removeChild(_target.firstChild);
      }
    }

    function _createCanvas() {
      var canvas = document.createElement('canvas');
      canvas.id = 'gitGraph';
      return canvas;
    }
  }

  function Command(line) {
    var _elements = _normalize(line);
    var _method = _elements[0];
    var _arguments = _elements.slice(1);

    this.invoke = function(git) {
      if (!(typeof git[_method] === 'function')) {
        return;
      }

      try {
        git[_method].apply(git, _arguments);
      } catch (e) {
        console.warn(e);
      }
    };

    function _normalize(line) {
      var elements = line.split(' ');
      var result = [];

      for (var i=0; i<elements.length; i++) {
        var element = elements[i]

        // remove empty strings
        if (element !== '') {
          result.push(element);
        }
      }

      return result;
    }
  }

  function Editor() {
    var _editor = document.getElementById('editor');
    var _timeoutKey;

    this.setListener = function(listener) {
      _editor.addEventListener('keyup', function() {
        if (_timeoutKey) {
          clearTimeout(_timeoutKey);
        }

        _timeoutKey = setTimeout(function() {
          try {
            listener();
          } finally {
            _timeoutKey = null;
          }
        }, 500);
      });
    };

    this.getCommands = function() {
      var commands = [];

      var lines = _editor.value.split('\n');
      for (var i=0; i<lines.length; i++) {
        var line = lines[i].trim();

        if (line.length !== 0) {
          commands.push(new Command(line));
        }
      }

      return commands;
    };

    this.getRawText = function() {
      return _editor.value;
    };

    this.setRawText = function(value) {
      _editor.value = value;
    };
  }

  function Storage() {
    var KEY = "gitgraph.storage";
    var _editor;
    var _config;

    this.save = function(editor, config) {
      var obj = {
        editor: editor.getRawText(),
        config: config
      };

      localStorage.setItem(KEY, JSON.stringify(obj));
    };

    this.load = function() {
      var json = localStorage.getItem(KEY);
      if (typeof json === 'string') {
        var obj = JSON.parse(json);
        _editor = obj.editor;
        _config = obj.config;
      } else {
        _editor = '';
        _config = {};
      }
    };

    this.getEditorText = function() {
      return _editor;
    };

    this.getConfigObj = function() {
      return _config;
    };

    this.remove = function() {
      localStorage.removeItem(KEY);
    };
  }

  function Config() {
    var _defaults = {
      template: 'metro',
      orientation: 'vertical',
      mode: '',
      author: '',
      reverseArrow: false,
      colors: '',
      arrowColor: '',
      arrowSize: '',
      arrowOffset: '',
      branchColor: '',
      branchLineWidth: '',
      branchMergeStyle: '',
      branchSpacingX: '',
      branchSpacingY: '',
      branchShowLabel: '',
      branchLabelColor: '',
      branchLabelFont: '',
      commitSpacingX: '',
      commitSpacingY: '',
      commitWidthExtension: '',
      commitColor: '',
      commitDotColor: '',
      commitDotSize: '',
      commitDotStrokeWidth: '',
      commitMessageColor: '',
      commitMessageDisplay: '',
      commitMessageDisplayAuthor: '',
      commitMessageDisplayBranch: '',
      commitMessageDisplayHash: '',
      commitMessageFont: '',
      commitShouldDisplayTooltipsInCompactMode: ''
    };

    var _elements = {};
    var _listener = function() {};
    var _self = this;

    for (var key in _defaults) {
      _initElement(key);
    }

    this.apply = function(obj) {
      for (var key in _defaults) {
        _setValue(key, obj[key] || _defaults[key]);
      }

      for (var key in obj) {
        var branchOptionKey = new BranchOptionKey(key);

        if (branchOptionKey.isBranchOption()) {
          _initElement(key);
          _setValue(key, obj[key]);
        }
      }
    };

    function _setValue(key, value) {
      _self[key] = value;
      _elements[key].setValue(_self[key]);
    }

    this.reset = function() {
      this.apply(_defaults);
    };

    this.setListener = function(listener) {
      _listener = listener;
    };

    this.addBranchOption = function(name) {
      _initElement('branch_' + name + '_color');
    };

    this.removeBranchOption = function(name) {
      for (var key in _elements) {
        var branchOptionKey = new BranchOptionKey(key);
        var branchName = branchOptionKey.getBranchName();

        if (branchOptionKey.isBranchOption() && branchName === name) {
          delete this[key];
          delete _elements[key];
        }
      }
    };

    function _initElement(elementId) {
      _elements[elementId] = new Element(document.getElementById(elementId));
      _elements[elementId].addEventListener(_onChange);
      _self[key] = _elements[elementId].getValue();
    }

    function _onChange() {
      for (var key in _elements) {
        _self[key] = _elements[key].getValue();
      }

      _listener();
    }

    this.reset();
  }

  function BranchOptionKey(key) {
    var _regexp = new RegExp("^branch_([^_]*)_(.*)$").exec(key);

    this.getKey = function() {
      return key;
    };

    this.getBranchName = function() {
      return this.isBranchOption() ? _regexp[1] : null;
    };

    this.getOptionName = function() {
      return this.isBranchOption() ? _regexp[2] : null;
    };

    this.isBranchOption = function() {
      return !!_regexp;
    };
  }

  function Element(e) {
    this.getValue = function() {
      if (_isCheckbox()) {
        return e.checked;
      } else if (e.type === 'number') {
        return Number(e.value);
      } else {
        return e.value;
      }
    };

    this.setValue = function(value) {
      if (_isCheckbox()) {
        e.checked = value;
      } else {
        e.value = value;
      }
    };

    this.addEventListener = function(listener) {
      e.addEventListener('change', listener);
    };

    function _isCheckbox() {
      return e.type === 'checkbox';
    }
  }

  var bottomArea = document.getElementById('bottom-area');
  var expand = document.getElementById('expand');
  expand.addEventListener('click', function() {
    if (bottomArea.style.height === '') {
      bottomArea.style.height = '80%';
      expand.innerText = 'expand↓';
    } else {
      bottomArea.style.height = '';
      expand.innerText = 'expand↑';
    }
  });

  var newBranchName = document.getElementById('newBranchName');

  document.getElementById('addNewBranchOptionButton').addEventListener('click', function() {
    var name = newBranchName.value;

    _addBranchOptionArea(name);

    config.addBranchOption(name);
  });

  function _rebuildBranchOption(storage) {
    for (var key in storage.getConfigObj()) {
      var branchOptionKey = new BranchOptionKey(key);

      if (branchOptionKey.isBranchOption()) {
        _addBranchOptionArea(branchOptionKey.getBranchName());
      }
    }
  }

  function _addBranchOptionArea(name) {
    // skip if aleady exists
    if (document.getElementById(name)) {
      return;
    }

    var newBranchesTarget = document.getElementById('newBranchesTarget');

    var newBranchOptionTemplate = document.getElementById('newBranchOptionTemplate');
    var html = newBranchOptionTemplate.innerText;
    html = html.replace(/\${name}/g, name);

    var div = document.createElement('div');
    div.innerHTML = html;

    newBranchesTarget.appendChild(div);

    var removeButton = document.getElementById(name + '_remove');
    removeButton.addEventListener('click', function() {
      newBranchesTarget.removeChild(div);
      config.removeBranchOption(name);
      draw();
    });
  }
})();
<|endoftext|>