<commit_before>Wu.version = '1.6.1';
Wu.App = Wu.Class.extend({
	_ : 'app',

	// default options
	options : systemapicConfigOptions, // global var from config.js... perhaps refactor.
	language : language,
	_ready : false,

	initialize : function (options) {

		// print version
		console.log('Mapic v.' + Wu.version);

		// set global app
		window.app = Wu.app = this; // todo: remove Wu.app, use only window.app

		// merge options
		Wu.setOptions(app, options);

		// init api
		app.api = new Wu.Api({});

		// analytics
		app.analytics = new Wu.Analytics();

		// auth
		app.api.auth(app.authed);

	},

	raven : function () {

		// error handling
		Raven.config('https://594a4e7cc65f4e39bdd0337276e391b5@sentry.io/100809').install();

		Raven.setRelease(Wu.version);
	},

	authed : function (err, access_token) {

		// catch err
		if (err) return console.error('Something went wrong: ', err);

		// set access_token
		app.tokens = Wu.parse(access_token);

		// init socket
		app.Socket = new Wu.Socket();

		// error handling
		app._initErrorHandling();

		// set page title
		document.title = app.options.portalTitle;

		// get objects from server
		app.initServer();

	},

	_initSniffers : function () {

		// Detect mobile devices
		app.detectMobile();

		// get user agent
		app.sniffer = Sniffer(navigator.userAgent);
	},

	_initErrorHandling : function () {

		// log all errors
		window.onerror = app._onError;

		// forward console.error's to log also
		// console.error = function (message) {
		// 	try { throw Error(message); }
		// 	catch (e) {}
		// }
	},

	_onError : function (message, file, line, char, ref) {
		var stack = ref.stack;
		var project = app.activeProject ? app.activeProject.getTitle() : 'No active project';
		var username = app.Account ? app.Account.getName() : 'No username';
		var options = JSON.stringify({
			message : message,
			file : file,
			line : line,
			user : username,
			stack : stack,
			project : project
		});
		app.api.errorLog(options, function (err, response) {
			if (err) {
				return app.feedback.setError({
					title : 'Something went wrong.',
					description : err
				});
			}
		}); // todo: move req to api.js
	},

	_checkForInvite : function () {
		var pathname = window.location.pathname;
		if (pathname.indexOf('/invite/') == -1) return;
		var invite_token = pathname.split('/').reverse()[0];
		if (invite_token) app.options.invite_token = invite_token;
	},

	initServer : function () {
		console.log('Securely connected to server: \n', app.options.servers.portal);

		// check for invite link
		app._checkForInvite();

		// data for server
		var data = JSON.stringify(app.options);

		// get portal
		app.api.getPortal(function (err, response) {
			if (err) return console.error('Something went wrong.');

			// parse
			var store = Wu.parse(response);

			// build app
			app.build(store)
		});
	},

	build : function (portalStore) {

		// set vars
		app.options.json = portalStore;

		// load json model
		app._initObjects();

		// create app container
		app._initContainer();

		// init chrome
		app._initChrome();

		// create panes
		app._initPanes();

		// init pane view
		app._initView();

		// ready
		app._ready = true; // todo: fire app ready event

		// select project
		Wu.Mixin.Events.fire('appReady');

		// init sniffers
		app._initSniffers();

		// log entry
		app._logEntry();

		// force login
		// app._forceLogin();

	},

	_forceLogin : function () {
		if (!app.options.force_login) return;
		if (!app.Account.isPublic()) return;
		if (this._publicProjectActive) return;

		// login screen
		this._login('Welcome! Please log in.');
	},

	_logEntry : function () {
		var b = app.sniffer.browser;
		var o = app.sniffer.os;
		var browser = b.fullName + ' ' + b.majorVersion + '.' + b.minorVersion;
		var os = o.fullName + ' ' + o.majorVersion + '.' + o.minorVersion;

		app.log('login', {
		    	info : {
		    		browser : browser,
		    		os : os
		    	},
		    	category : 'Users'
		});
	},

	_initObjects : function () {

		// data controller
		app.Data = new Wu.Data();

		// controller .. todo: refactor what's in controller.. or expand..
		app.Controller = new Wu.Controller();

		// main user account
		app.Account = new Wu.User(app.options.json.account);

		// contact list
		app.Users = {};
		app.Account.getContactList().forEach(function(user) {
		       app.Users[user.uuid] = new Wu.User(user);
		});
		app.options.json.users.project_users.forEach(function(user) {
		       if (!app.Users[user.uuid]) app.Users[user.uuid] = new Wu.User(user);
		});

		// add self to users list
		app.Users[app.Account.getUuid()] = app.Account;

		// create project objects
		app.Projects = {};
		app.options.json.projects.forEach(function(store, i, arr) {
		       	app.Projects[store.uuid] = new Wu.Model.Project(store);
		});

		// phantomjs
		app.phantomjs = new Wu.PhantomJS();
	},

	_initChrome : function () {

		// chrome
		app.Chrome = {};

		// top chrome
		app.Chrome.Top = new Wu.Chrome.Top();

		// right chrome
		app.Chrome.Right = new Wu.Chrome.Right();

		// right chrome
		app.Chrome.Left = new Wu.Chrome.Left();
	},

	_initPanes : function () {

		// render tooltip
		app.Tooltip = new Wu.Tooltip();

		// render style handler
		app.Style = new Wu.Style();

		// render progress bar
		app.ProgressBar = new Wu.ProgressPane({
			color : 'white',
			addTo : app._appPane
		});

		// render map pane
		app.MapPane = new Wu.MapPane();

		// render eror pane
		app.FeedbackPane = new Wu.FeedbackPane();

		// settings pane
		app.MapSettingsPane = new Wu.MapSettingsPane();

		// share pane
		app.Share = new Wu.Share();

		// // big slider
		// app.Animator = new Wu.Animator({ // refactor to project controls
		// 	// data : 'allYears',
		// 	data : 'scf.average.2000.2015', // todo: refactor data fetching
		// 	hide : true
		// });

		// add account tab
		app.AccountPane = new Wu.Pane.Account();

		// load public stylesheet
		if (app.Account.isPublic()) {
			app.Controller.loadjscssfile('/css/public-stylesheet.css', 'css');
		}
	},

	// init default view on page-load
	_initView : function () {

		// runs hotlink
		if (app._initHotlink()) {
			return;
		}

		// open first project (ordered by lastUpdated)
		app.Controller.openDefaultProject();
	},

	_initInvite : function () {
		var project = app.options.json.invite;

		if (!project) return false;

		// select project
		Wu.Mixin.Events.fire('projectSelected', {detail : {
			projectUuid : project.id
		}});

		app.feedback.setMessage({
			title : 'Project access granted',
			description : 'You\'ve been given access to the project ' + project.name
		});
	},

	_initHotlink : function (hotlink) {

		// parse error prone content of hotlink..
		var hotlink = hotlink || window.hotlink;
		app.hotlink = Wu.parse(hotlink);

		// return if no hotlink
		if (_.isEmpty(app.hotlink)) return false;

		// check if user owns project
		var project = app._projectExists(app.hotlink);
		if (project) {
			app._setProject(project);
			return true;
		}

		// request project from server
		app.api.getProject({
			username : app.hotlink.username,
			project_slug : app.hotlink.project
		}, function (err, project_json) {
			console.log('err, projec', err, project_json);
			if (err) return app._login('Please log in to view this private project.');

			var project_store = Wu.parse(project_json);

			// import project
			app._importProject(project_store, function (err, project) {
				console.log('_importProject', err, project);
				app._setProject(project);

				if (project.isPublic()) {
					this._publicProjectActive = true;
				}
			}.bind(this));
		});

		return true;
	},

	_login : function (msg) {
		// open login
		var login = new Wu.Pane.Login();
		login.setDescription(msg);
		login.open();
	},

	_importProject : function (project_store, done) {

		// already exists
		if (app.Projects[project_store.uuid]) {
			return;
		}

		// create project model
		var project = new Wu.Model.Project(project_store);
		app.Projects[project.getUuid()] = project;
		app.Chrome.Projects._addProject(project);

		// return
		done(null, project);
	},

	// check if project exists (for hotlink)
	_projectExists : function (hotlink) {
		var project_slug = hotlink.project;
		var username = hotlink.username;

		// find project slug in Wu.app.Projects
		var project_slug = project_slug || window.hotlink.project;
		for (var p in Wu.app.Projects) {
			var project = Wu.app.Projects[p];
			if (project_slug == project.store.slug) {
				if (project.store.createdByUsername == username) {
					return project;
				}
			}
		}
		return false;
	},

	_initEvents : function () {
	},

	_getDimensions : function (e) {
		var w = window,
		    d = document,
		    e = d.documentElement,
		    g = d.getElementsByTagName('body')[0],
		    x = w.innerWidth || e.clientWidth || g.clientWidth,
		    y = w.innerHeight|| e.clientHeight|| g.clientHeight,
		    d = {
			height : y,
			width : x,
			e : e
		    }
		return d;
	},

	_initContainer : function () {

		// find or create container
		var id = app.options.id;
		app._appPane = Wu.DomUtil.get(id) || Wu.DomUtil.createId('div', id || 'app', document.body);

		// create map container
		app._mapContainer = Wu.DomUtil.createId('div', 'map-container', app._appPane);
	},

	_setProject : function (project) {

		// select project
		Wu.Mixin.Events.fire('projectSelected', {detail : {
			projectUuid : project.getUuid()
		}});
	},

	// get name provided for portal from options hash
	getPortalName : function () {
		return app.options.portalName;
	},

	// todo: move to own script
	detectMobile : function() {

		app.isMobile = Wu.Util.isMobile();

		if (app.isMobile) {

			// Set size
			this.setMobileSize();

			// Listen to the wind blow
			this.mobileListners();
		}

	},

	mobileListners : function () {

		Wu.DomEvent.on(window, 'resize', this.setMobileSize, this);

	},

	setMobileSize : function () {

		// Check landscape or portrait format
		var portrait = window.innerHeight > window.innerWidth;

		// Get width
		var width  = portrait ? app.isMobile.width : app.isMobile.height;
		var height = portrait ? app.isMobile.height : app.isMobile.width;

		// Check device type
		var device = app.isMobile.mobile ? 'mobile' : 'tablet';

		// load stylesheet
		app.Controller.loadjscssfile('/css/' + device + '.css', 'css');

		// set width of map
		app._map._container.style.width = width + 'px';
		// app._map._container.style.height = height + 'px';

	},

	debug : function () {

		// add red borders to tiles
		app.Style.setStyle('img.leaflet-tile', {
			'border-top': '1px solid rgba(255, 0, 0, 0.65)',
			'border-left': '1px solid rgba(255, 0, 0, 0.65)'
		});

		// click event to get tile address
		if (app._map) app._map.on('mousedown', function (e) {
			var lat = e.latlng.lat;
			var lng = e.latlng.lng;
			var zoom = app._map.getZoom();
			var tile = app._getTileURL(lat, lng, zoom);

			// log to console
			console.log('tile:', tile);
		});

		// extend Number with toRad
		if (typeof(Number.prototype.toRad) === "undefined") {
			Number.prototype.toRad = function() {
				return this * Math.PI / 180;
			}
		}
	},

	// for debug
	_getTileURL : function (lat, lon, zoom) {
		var xtile = parseInt(Math.floor( (lon + 180) / 360 * (1<<zoom) ));
		var ytile = parseInt(Math.floor( (1 - Math.log(Math.tan(lat.toRad()) + 1 / Math.cos(lat.toRad())) / Math.PI) / 2 * (1<<zoom) ));
		return "" + zoom + "/" + xtile + "/" + ytile;
	},

});<commit_msg>handling malformed errors<commit_after>Wu.version = '1.6.1';
Wu.App = Wu.Class.extend({
	_ : 'app',

	// default options
	options : systemapicConfigOptions, // global var from config.js... perhaps refactor.
	language : language,
	_ready : false,

	initialize : function (options) {

		// print version
		console.log('Mapic v.' + Wu.version);

		// set global app
		window.app = Wu.app = this; // todo: remove Wu.app, use only window.app

		// merge options
		Wu.setOptions(app, options);

		// init api
		app.api = new Wu.Api({});

		// analytics
		app.analytics = new Wu.Analytics();

		// auth
		app.api.auth(app.authed);

	},

	raven : function () {

		// error handling
		Raven.config('https://594a4e7cc65f4e39bdd0337276e391b5@sentry.io/100809').install();

		Raven.setRelease(Wu.version);
	},

	authed : function (err, access_token) {

		// catch err
		if (err) return console.error('Something went wrong: ', err);

		// set access_token
		app.tokens = Wu.parse(access_token);

		// init socket
		app.Socket = new Wu.Socket();

		// error handling
		app._initErrorHandling();

		// set page title
		document.title = app.options.portalTitle;

		// get objects from server
		app.initServer();

	},

	_initSniffers : function () {

		// Detect mobile devices
		app.detectMobile();

		// get user agent
		app.sniffer = Sniffer(navigator.userAgent);
	},

	_initErrorHandling : function () {

		// log all errors
		window.onerror = app._onError;

		// forward console.error's to log also
		// console.error = function (message) {
		// 	try { throw Error(message); }
		// 	catch (e) {}
		// }
	},

	_onError : function (message, file, line, char, ref) {
		if (!ref) return console.error('No ref', arguments);
		var stack = ref.stack;
		var project = app.activeProject ? app.activeProject.getTitle() : 'No active project';
		var username = app.Account ? app.Account.getName() : 'No username';
		var options = JSON.stringify({
			message : message,
			file : file,
			line : line,
			user : username,
			stack : stack,
			project : project
		});
		app.api.errorLog(options, function (err, response) {
			if (err) {
				return app.feedback.setError({
					title : 'Something went wrong.',
					description : err
				});
			}
		}); // todo: move req to api.js
	},

	_checkForInvite : function () {
		var pathname = window.location.pathname;
		if (pathname.indexOf('/invite/') == -1) return;
		var invite_token = pathname.split('/').reverse()[0];
		if (invite_token) app.options.invite_token = invite_token;
	},

	initServer : function () {
		console.log('Securely connected to server: \n', app.options.servers.portal);

		// check for invite link
		app._checkForInvite();

		// data for server
		var data = JSON.stringify(app.options);

		// get portal
		app.api.getPortal(function (err, response) {
			if (err) return console.error('Something went wrong.');

			// parse
			var store = Wu.parse(response);

			// build app
			app.build(store)
		});
	},

	build : function (portalStore) {

		// set vars
		app.options.json = portalStore;

		// load json model
		app._initObjects();

		// create app container
		app._initContainer();

		// init chrome
		app._initChrome();

		// create panes
		app._initPanes();

		// init pane view
		app._initView();

		// ready
		app._ready = true; // todo: fire app ready event

		// select project
		Wu.Mixin.Events.fire('appReady');

		// init sniffers
		app._initSniffers();

		// log entry
		app._logEntry();

		// force login
		// app._forceLogin();

	},

	_forceLogin : function () {
		if (!app.options.force_login) return;
		if (!app.Account.isPublic()) return;
		if (this._publicProjectActive) return;

		// login screen
		this._login('Welcome! Please log in.');
	},

	_logEntry : function () {
		var b = app.sniffer.browser;
		var o = app.sniffer.os;
		var browser = b.fullName + ' ' + b.majorVersion + '.' + b.minorVersion;
		var os = o.fullName + ' ' + o.majorVersion + '.' + o.minorVersion;

		app.log('login', {
		    	info : {
		    		browser : browser,
		    		os : os
		    	},
		    	category : 'Users'
		});
	},

	_initObjects : function () {

		// data controller
		app.Data = new Wu.Data();

		// controller .. todo: refactor what's in controller.. or expand..
		app.Controller = new Wu.Controller();

		// main user account
		app.Account = new Wu.User(app.options.json.account);

		// contact list
		app.Users = {};
		app.Account.getContactList().forEach(function(user) {
		       app.Users[user.uuid] = new Wu.User(user);
		});
		app.options.json.users.project_users.forEach(function(user) {
		       if (!app.Users[user.uuid]) app.Users[user.uuid] = new Wu.User(user);
		});

		// add self to users list
		app.Users[app.Account.getUuid()] = app.Account;

		// create project objects
		app.Projects = {};
		app.options.json.projects.forEach(function(store, i, arr) {
		       	app.Projects[store.uuid] = new Wu.Model.Project(store);
		});

		// phantomjs
		app.phantomjs = new Wu.PhantomJS();
	},

	_initChrome : function () {

		// chrome
		app.Chrome = {};

		// top chrome
		app.Chrome.Top = new Wu.Chrome.Top();

		// right chrome
		app.Chrome.Right = new Wu.Chrome.Right();

		// right chrome
		app.Chrome.Left = new Wu.Chrome.Left();
	},

	_initPanes : function () {

		// render tooltip
		app.Tooltip = new Wu.Tooltip();

		// render style handler
		app.Style = new Wu.Style();

		// render progress bar
		app.ProgressBar = new Wu.ProgressPane({
			color : 'white',
			addTo : app._appPane
		});

		// render map pane
		app.MapPane = new Wu.MapPane();

		// render eror pane
		app.FeedbackPane = new Wu.FeedbackPane();

		// settings pane
		app.MapSettingsPane = new Wu.MapSettingsPane();

		// share pane
		app.Share = new Wu.Share();

		// // big slider
		// app.Animator = new Wu.Animator({ // refactor to project controls
		// 	// data : 'allYears',
		// 	data : 'scf.average.2000.2015', // todo: refactor data fetching
		// 	hide : true
		// });

		// add account tab
		app.AccountPane = new Wu.Pane.Account();

		// load public stylesheet
		if (app.Account.isPublic()) {
			app.Controller.loadjscssfile('/css/public-stylesheet.css', 'css');
		}
	},

	// init default view on page-load
	_initView : function () {

		// runs hotlink
		if (app._initHotlink()) {
			return;
		}

		// open first project (ordered by lastUpdated)
		app.Controller.openDefaultProject();
	},

	_initInvite : function () {
		var project = app.options.json.invite;

		if (!project) return false;

		// select project
		Wu.Mixin.Events.fire('projectSelected', {detail : {
			projectUuid : project.id
		}});

		app.feedback.setMessage({
			title : 'Project access granted',
			description : 'You\'ve been given access to the project ' + project.name
		});
	},

	_initHotlink : function (hotlink) {

		// parse error prone content of hotlink..
		var hotlink = hotlink || window.hotlink;
		app.hotlink = Wu.parse(hotlink);

		// return if no hotlink
		if (_.isEmpty(app.hotlink)) return false;

		// check if user owns project
		var project = app._projectExists(app.hotlink);
		if (project) {
			app._setProject(project);
			return true;
		}

		// request project from server
		app.api.getProject({
			username : app.hotlink.username,
			project_slug : app.hotlink.project
		}, function (err, project_json) {
			console.log('err, projec', err, project_json);
			if (err) return app._login('Please log in to view this private project.');

			var project_store = Wu.parse(project_json);

			// import project
			app._importProject(project_store, function (err, project) {
				console.log('_importProject', err, project);
				app._setProject(project);

				if (project.isPublic()) {
					this._publicProjectActive = true;
				}
			}.bind(this));
		});

		return true;
	},

	_login : function (msg) {
		// open login
		var login = new Wu.Pane.Login();
		login.setDescription(msg);
		login.open();
	},

	_importProject : function (project_store, done) {

		// already exists
		if (app.Projects[project_store.uuid]) {
			return;
		}

		// create project model
		var project = new Wu.Model.Project(project_store);
		app.Projects[project.getUuid()] = project;
		app.Chrome.Projects._addProject(project);

		// return
		done(null, project);
	},

	// check if project exists (for hotlink)
	_projectExists : function (hotlink) {
		var project_slug = hotlink.project;
		var username = hotlink.username;

		// find project slug in Wu.app.Projects
		var project_slug = project_slug || window.hotlink.project;
		for (var p in Wu.app.Projects) {
			var project = Wu.app.Projects[p];
			if (project_slug == project.store.slug) {
				if (project.store.createdByUsername == username) {
					return project;
				}
			}
		}
		return false;
	},

	_initEvents : function () {
	},

	_getDimensions : function (e) {
		var w = window,
		    d = document,
		    e = d.documentElement,
		    g = d.getElementsByTagName('body')[0],
		    x = w.innerWidth || e.clientWidth || g.clientWidth,
		    y = w.innerHeight|| e.clientHeight|| g.clientHeight,
		    d = {
			height : y,
			width : x,
			e : e
		    }
		return d;
	},

	_initContainer : function () {

		// find or create container
		var id = app.options.id;
		app._appPane = Wu.DomUtil.get(id) || Wu.DomUtil.createId('div', id || 'app', document.body);

		// create map container
		app._mapContainer = Wu.DomUtil.createId('div', 'map-container', app._appPane);
	},

	_setProject : function (project) {

		// select project
		Wu.Mixin.Events.fire('projectSelected', {detail : {
			projectUuid : project.getUuid()
		}});
	},

	// get name provided for portal from options hash
	getPortalName : function () {
		return app.options.portalName;
	},

	// todo: move to own script
	detectMobile : function() {

		app.isMobile = Wu.Util.isMobile();

		if (app.isMobile) {

			// Set size
			this.setMobileSize();

			// Listen to the wind blow
			this.mobileListners();
		}

	},

	mobileListners : function () {

		Wu.DomEvent.on(window, 'resize', this.setMobileSize, this);

	},

	setMobileSize : function () {

		// Check landscape or portrait format
		var portrait = window.innerHeight > window.innerWidth;

		// Get width
		var width  = portrait ? app.isMobile.width : app.isMobile.height;
		var height = portrait ? app.isMobile.height : app.isMobile.width;

		// Check device type
		var device = app.isMobile.mobile ? 'mobile' : 'tablet';

		// load stylesheet
		app.Controller.loadjscssfile('/css/' + device + '.css', 'css');

		// set width of map
		app._map._container.style.width = width + 'px';
		// app._map._container.style.height = height + 'px';

	},

	debug : function () {

		// add red borders to tiles
		app.Style.setStyle('img.leaflet-tile', {
			'border-top': '1px solid rgba(255, 0, 0, 0.65)',
			'border-left': '1px solid rgba(255, 0, 0, 0.65)'
		});

		// click event to get tile address
		if (app._map) app._map.on('mousedown', function (e) {
			var lat = e.latlng.lat;
			var lng = e.latlng.lng;
			var zoom = app._map.getZoom();
			var tile = app._getTileURL(lat, lng, zoom);

			// log to console
			console.log('tile:', tile);
		});

		// extend Number with toRad
		if (typeof(Number.prototype.toRad) === "undefined") {
			Number.prototype.toRad = function() {
				return this * Math.PI / 180;
			}
		}
	},

	// for debug
	_getTileURL : function (lat, lon, zoom) {
		var xtile = parseInt(Math.floor( (lon + 180) / 360 * (1<<zoom) ));
		var ytile = parseInt(Math.floor( (1 - Math.log(Math.tan(lat.toRad()) + 1 / Math.cos(lat.toRad())) / Math.PI) / 2 * (1<<zoom) ));
		return "" + zoom + "/" + xtile + "/" + ytile;
	},

});<|endoftext|>


<commit_before>/*Test Creation Javascript Page */<commit_msg>Tried to make changes to the javascript for test creation<commit_after>/*Test Creation Javascript Page */

var Question =
{
   question : "question",
   points   : 0

};
function getFunction()
{
   document.getElementById("test").innerHTML = ;
}
var x = 0;
var y = Question.question;

/*
// This is the default constructor for the parent question class
function Default_question()
{
   this.question = "";
   this.points = 0;
}

// This is the generic question class
function Question(question, points)
{
   this.question = question;
   this.points = points;
}

// Gets question
Question.prototype.get_question = function()
{
   return this.question;
}

// Sets question
Question.prototype.set_question = function(question)
{
   this.question = question;
}

// Gets points for question
Question.prototype.get_points = function()
{
   return this.points;
}

// Sets points for question
Question.prototype.set_points = function(points)
{
   this.points = points;
}

// This includes short answer and essay questions (written answers)
function Short_answer(question)
{
   Question.call(this, question);
}

// This includes multiple choice, all that apply, and matching (multiple answers)
function Multiple_choice(question, answer, additional_answers)
{
   Question.call(this, question);
   this.answer = answer;
   this.additional_answers = additional_answers;
}

// Gets multiple choice answer
Multiple_choice.prototype.get_answer = function()
{
   return this.answer;
}

// Sets multiple choice answer
Multiple_choice.prototype.set_answer = function(answer)
{
   this.answer = answer;
}

// Gets multiple choice additional answers
Multiple_choice.prototype.get_additional_answers = function()
{
   return this.additional_answers;
}

// Sets multiple choice additional answers
Multiple_choice.prototype.set_additional_answers = function(answers)
{
   var count = answers.length;
   for (var i = 0; i < count; i++)
   {
      this.additional_answers[i] = answers[i];
   }
}

// This includes true/false (2 answers)
function True_false(question, answer)
{
   Question.call(this, question);
   this.answer = answer;
}

// Gets true/false answer
True_false.prototype.get_answer = function()
{
   return this.answer;
}

// Sets true/false answer
True_false.prototype.set_answer = function(answer)
{
   this.answer = answer;
}

Short_answer.prototype    = Question;
Multiple_choice.prototype = Question;
True_false.prototype      = Question;
*/<|endoftext|>


<commit_before>//
// HLF Media Grid Extensions
// =========================
// [Styles](../css/media-grid.html) | [Tests](../../tests/js/media-grid.html)
//
// The `mediaGrid` extension, inspired by the Cargo Voyager design template, can
// expand an item inline without affecting the position of its siblings. The
// extension tries to add the minimal amount of DOM elements and styles. So the
// layout rules are mostly defined in the styles, and initial html for items is
// required (see the tests for an example). The extension also handles additional
// effects like focusing on the expanded item and dimming its siblings.
//
(function(root, attach) {
  //
  // § __UMD__
  // - When AMD, register the attacher as an anonymous module.
  // - When Node or Browserify, set module exports to the attach result.
  // - When browser globals (root is window), Just run the attach function.
  //
  if (typeof define === 'function' && define.amd) {
    define(['hlf/core'], attach);
  } else if (typeof exports === 'object') {
    module.exports = attach(require('hlf/core'));
  } else {
    attach(hlf);
  }
})(this, function(hlf) {
  //
  // Namespace
  // ---------
  //
  // It takes some more boilerplate to write the extension. Any of this additional
  // support API is put into a extension specific namespace under `hlf`, which in
  // this case is __hlf.mediaGrid__.
  //
  // - __debug__ toggles debug logging for all instances of an extension.
  // - __toString__ helps to namespace when registering any DOM names.
  //
  // The extension's __defaults__ are available as reference. Also note that _the
  // extension instance gets extended with the options_.
  //
  // - __autoReady__ is `false` by default, as recommended. Turning it on means
  //   the `ready` event gets triggered immediately, synchronously, and is only
  //   recommended if your grid doesn't have images and such that require a wait
  //   before being fully loaded and sized.
  //
  // - __resizeDelay__ is the millis to wait for window resizing to stop before
  //   doing a re-layout. `100` is the default to balance responsiveness and
  //   performance.
  //
  // - Note: for these extensions, the majority of presentation state logic is
  //   in the extension stylesheet. We update the presentation state by using
  //   namespaced __classNames__ generated in a closure.
  //
  hlf.mediaGrid = {
    debug: false,
    toString(context) {
      switch (context) {
        case 'event': return 'hlfmg';
        case 'data': return 'hlf-mg';
        case 'class': return 'mg';
        default: return 'hlf-mg';
      }
    },
    defaults: {
      autoReady: false,
      resizeDelay: 100,
      undimDelay: 1000,
    },
  };
  //
  // MediaGrid
  // ---------
  //
  class MediaGrid {
    constructor(element, options) {
      [ '_onItemClick', '_onItemExpand', '_onItemMouseEnter',
        '_onItemMouseLeave', '_onMouseLeave', '_onWindowResize',
      ].forEach((name) => {
        this[name] = this[name].bind(this);
      });
      this.eventListeners = { mouseleave: this._onMouseLeave };
      this.eventListeners[this.eventName('expand')] = this._onItemExpand;
    }
    //
    // __init__ completes the setup:
    // 1. Select `itemElements` and `sampleItemElement`.
    // 2. Get `expandDuration` from styles, create event bindings for expansion.
    //    This also relies on the `expandedItemElement` state.
    // 3. Create event bindings for dimming on expansion, hovering expanded item.
    // 4. Set up initial layout for running when `ready`.
    // 5. Set up re-layout for running on window resize.
    //
    init() {
      if (!this.itemElements) {
        this.itemElements = this.selectAllByClass('item');
      }
      Array.from(this.itemElements).forEach((itemElement) => {
        this.addEventListeners({
          'click': this._onItemClick,
          'mouseenter': this._onItemMouseEnter,
          'mouseleave': this._onItemMouseLeave,
        }, itemElement);
      });
      window.addEventListener('resize', this._onWindowResize);
      this.sampleItemElement = this.itemElements[0];
      this.expandDuration = 1000 * parseFloat(
        getComputedStyle(this.sampleItemElement).transitionDuration
      );
      this.expandedItemElement = null;
      this.metrics = {};
      if (this.autoLoad) {
        this.performLoad();
      }
    }
    deinit() {
      this.removeEventListeners(this.eventListeners);
      Array.from(this.itemElements).forEach((itemElement) => {
        this.removeEventListeners({
          'click': this._onItemClick,
          'mouseenter': this._onItemMouseEnter,
          'mouseleave': this._onItemMouseLeave,
        }, itemElement);
      });
      window.removeEventListener('resize', this._onWindowResize);
    }
    performLoad() {
      this._updateMetrics();
      this._layoutItems();
      this.element.classList.add(this.className('ready'));
      this.dispatchCustomEvent('ready');
    }
    //
    // § __Public__
    //
    // You are welcome to call these methods from your own code, though currently
    // there is no intended use case for that.
    //
    // __toggleItemExpansion__ basically toggles the `-expanded` class on the
    // given `itemElement` to `expanded` and triggers the `expand` event. To allow
    // styling or scripting during the transition, it adds the `-transitioning`
    // class and removes it afterwards per `expandDuration`.
    //
    toggleItemExpansion(itemElement, expanded) {
      if (typeof expanded === 'undefined') {
        expanded = !itemElement.classList.contains(this.className('expanded'));
      }
      let index = Array.from(this.itemElements).indexOf(itemElement);
      if (expanded) {
        if (this.expandedItemElement) {
          this.toggleItemExpansion(this.expandedItemElement, false);
        }
        if (this._isRightEdgeItem(index)) {
          this._adjustItemToRightEdge(itemElement);
        }
        if (this._isBottomEdgeItem(index)) {
          this._adjustItemToBottomEdge(itemElement);
        }
      }
      this._toggleNeighborItemsRecessed(index, expanded);
      itemElement.classList.add(this.className('transitioning'));
      clearTimeout(itemElement.getAttribute(this.attrName('expand-timeout')));
      itemElement.setAttribute(this.attrName('expand-timeout'),
        setTimeout(() => {
          itemElement.classList.remove(this.className('transitioning'));
        }, this.expandDuration)
      );

      itemElement.classList.toggle(this.className('expanded'), expanded);
      this.expandedItemElement = expanded ? itemElement : null;

      itemElement.dispatchEvent(this.createCustomEvent('expand', { expanded }));
    }
    //
    // __toggleExpandedItemFocus__ wraps `toggleItemFocus` to factor in
    // `undimDelay` when toggling off `focus`. Focusing dims without delay.
    //
    toggleExpandedItemFocus(itemElement, focused) {
      if (!itemElement.classList.contains(this.className('expanded'))) { return; }
      let delay = focused ? 0 : this.undimDelay;
      this.toggleItemFocus(itemElement, focused, delay);
    }
    //
    // __toggleItemFocus__ basically toggles the `-focused` class on the given
    // `itemElement` to `focused` and the `-dimmed` class on the root element
    // after any given `delay`.
    //
    toggleItemFocus(itemElement, focused, delay) {
      if (focused) {
        Array.from(this.itemElements).forEach((itemElement) => {
          itemElement.classList.remove(this.className('focused'));
        });
      }
      itemElement.classList.toggle(this.className('focused'), focused);
      clearTimeout(this._dimTimeout);
      this._dimTimeout = setTimeout(() => {
        this.element.classList.toggle(this.className('dimmed'), focused);
      }, delay);
    }
    //
    // § __Internal__
    //
    _onItemClick(event) {
      const actionElementTags = ['A', 'AUDIO', 'BUTTON', 'INPUT', 'VIDEO'];
      if (actionElementTags.indexOf(event.target.tagName) !== -1) { return; }
      this.toggleItemExpansion(event.currentTarget);
    }
    _onItemExpand(event) {
      const { target } = event;
      if (!target.classList.contains(this.className('item'))) { return; }
      const { expanded } = event.detail;
      this.toggleItemFocus(target, expanded, this.expandDuration);
    }
    _onItemMouseEnter(event) {
      this.toggleExpandedItemFocus(event.currentTarget, true);
    }
    _onItemMouseLeave(event) {
      this.toggleExpandedItemFocus(event.currentTarget, false);
    }
    _onMouseLeave(_) {
      if (!this.expandedItemElement) { return; }
      this.toggleItemFocus(this.expandedItemElement, false, 0);
    }
    _onWindowResize(_) {
      const now = Date.now();
      if (this._ran && now < this._ran + this.resizeDelay) { return; }
      this._ran = now;
      this._updateMetrics(false);
      if (this.expandedItemElement) {
        this.toggleItemExpansion(this.expandedItemElement, false);
        this._reLayoutItems(this.expandDuration);
      } else {
        this._reLayoutItems();
      }
    }
    //
    // These are layout helpers for changing offset for an `itemElement`.
    //
    _adjustItemToBottomEdge(itemElement) {
      itemElement.style.top = 'auto';
      itemElement.style.bottom = '0px';
    }
    _adjustItemToRightEdge(itemElement) {
      itemElement.style.left = 'auto';
      itemElement.style.right = '0px';
    }
    //
    // ___getMetricSamples__ returns cloned `itemElement` and `expandedItemElement`
    // mainly for calculating initial metrics. For them to have the right sizes,
    // they're attached to an invisible container appended to the root element.
    //
    _getMetricSamples() {
      let containerElement = this.selectByClass('sample');
      if (containerElement) {
        containerElement.parentNode.removeChild(containerElement);
      }
      let itemElement = this.sampleItemElement.cloneNode(true);
      let expandedItemElement = this.sampleItemElement.cloneNode(true);
      expandedItemElement.classList.add(this.className('expanded'));
      containerElement = document.createElement('div');
      containerElement.classList.add(this.className('sample'));
      let { style } = containerElement;
      style.left = style.right = style.top = '0px';
      style.position = 'absolute';
      style.visibility = 'hidden';
      style.zIndex = 0;
      containerElement.appendChild(itemElement);
      containerElement.appendChild(expandedItemElement);
      this.element.appendChild(containerElement);
      return { itemElement, expandedItemElement };
    }
    //
    // These are layout helpers for checking item position based on index and the
    // current `rowSize` metric.
    //
    _isBottomEdgeItem(i) {
      return (i + 1) > (this.itemElements.length - this.metrics.rowSize);
    }
    _isRightEdgeItem(i) {
      return ((i + 1) % this.metrics.rowSize) === 0;
    }
    //
    // ___layoutItems__ occurs once `metrics` is updated. With the latest
    // `wrapWidth` and `wrapHeight` metrics, the root element is resized. Each
    // element in `itemElements` gets its position style set to `absolute` non-
    // destructively; this method assumes the original is `float`, and so
    // iterates in reverse.
    //
    _layoutItems() {
      Array.from(this.itemElements).reverse().forEach((itemElement) => {
        if (!itemElement.hasAttribute(this.attrName('original-position'))) {
          itemElement.setAttribute(this.attrName('original-position'),
            getComputedStyle(itemElement).position);
        }
        let { offsetLeft, offsetTop, style } = itemElement;
        style.position = 'absolute';
        style.left = `${offsetLeft}px`;
        style.top = `${offsetTop}px`;
      });
      let { style } = this.element;
      style.width = `${this.metrics.wrapWidth}px`;
      style.height = `${this.metrics.wrapHeight}px`;
    }
    //
    // ___reLayoutItems__ wraps `_layoutItems` to be its idempotent version by
    // first resetting each item's to its `original-position`. It can run after a
    // custom `delay`.
    //
    _reLayoutItems(delay = 0) {
      clearTimeout(this._layoutTimeout);
      this._layoutTimeout = setTimeout(() => {
        Array.from(this.itemElements).forEach((itemElement) => {
          let { style } = itemElement;
          style.bottom = style.left = style.right = style.top = 'auto';
          style.position = itemElement.getAttribute(this.attrName('original-position'));
        });
        this._layoutItems();
      }, delay);
    }
    //
    // TODO
    //
    _toggleNeighborItemsRecessed(index, recessed) {
      const { expandedScale, rowSize } = this.metrics;
      let dx = this._isRightEdgeItem(index) ? -1 : 1;
      let dy = this._isBottomEdgeItem(index) ? -1 : 1;
      let level = 1;
      let neighbors = [];
      while (level < expandedScale) {
        neighbors.push(
          this.itemElements[index + level * dx],
          this.itemElements[index + level * dy * rowSize],
          this.itemElements[index + level * (dy * rowSize + dx)]
        );
        level += 1;
      }
      neighbors.forEach((itemElement) => {
        itemElement.classList.toggle(this.className('recessed'));
      });
    }
    //
    // ___updateMetrics__ builds the `metrics` around item and wrap as well as
    // row and column sizes. It does so by measuring sample elements and their
    // margins, as well as sizing the wrap (root element) to fit its items. As
    // such, this method isn't idempotent and expects to be followed by a call
    // to `_layoutItems`.
    //
    _updateMetrics(hard = true) {
      if (hard) {
        const { itemElement, expandedItemElement } = this._getMetricSamples();
        this.metrics = {
          itemWidth: itemElement.offsetWidth,
          itemHeight: itemElement.offsetHeight,
          expandedWidth: expandedItemElement.offsetWidth,
          expandedHeight: expandedItemElement.offsetHeight,
          expandedScale: parseInt(this.cssVariable('item-expanded-scale')),
        };
      }
      let gutter = Math.round(parseFloat(
        getComputedStyle(this.sampleItemElement).marginRight
      ));
      let fullWidth = this.metrics.itemWidth + gutter;
      let fullHeight = this.metrics.itemHeight + gutter;

      let { style } = this.element;
      style.height = style.width = 'auto';

      let rowSize = parseInt(((this.element.offsetWidth + gutter) / fullWidth));
      let colSize = Math.ceil(this.itemElements.length / rowSize);
      Object.assign(this.metrics, { gutter, rowSize, colSize }, {
        wrapWidth: fullWidth * rowSize,
        wrapHeight: fullHeight * colSize,
      });
    }
  }
  //
  // § __Attaching__
  //
  return hlf.createExtension({
    name: 'mediaGrid',
    namespace: hlf.mediaGrid,
    apiClass: MediaGrid,
    autoListen: true,
    baseMethodGroups: ['css', 'event', 'selection'],
    compactOptions: true,
  });
});
<commit_msg>Minor media-grid re-layout refactor.<commit_after>//
// HLF Media Grid Extensions
// =========================
// [Styles](../css/media-grid.html) | [Tests](../../tests/js/media-grid.html)
//
// The `mediaGrid` extension, inspired by the Cargo Voyager design template, can
// expand an item inline without affecting the position of its siblings. The
// extension tries to add the minimal amount of DOM elements and styles. So the
// layout rules are mostly defined in the styles, and initial html for items is
// required (see the tests for an example). The extension also handles additional
// effects like focusing on the expanded item and dimming its siblings.
//
(function(root, attach) {
  //
  // § __UMD__
  // - When AMD, register the attacher as an anonymous module.
  // - When Node or Browserify, set module exports to the attach result.
  // - When browser globals (root is window), Just run the attach function.
  //
  if (typeof define === 'function' && define.amd) {
    define(['hlf/core'], attach);
  } else if (typeof exports === 'object') {
    module.exports = attach(require('hlf/core'));
  } else {
    attach(hlf);
  }
})(this, function(hlf) {
  //
  // Namespace
  // ---------
  //
  // It takes some more boilerplate to write the extension. Any of this additional
  // support API is put into a extension specific namespace under `hlf`, which in
  // this case is __hlf.mediaGrid__.
  //
  // - __debug__ toggles debug logging for all instances of an extension.
  // - __toString__ helps to namespace when registering any DOM names.
  //
  // The extension's __defaults__ are available as reference. Also note that _the
  // extension instance gets extended with the options_.
  //
  // - __autoReady__ is `false` by default, as recommended. Turning it on means
  //   the `ready` event gets triggered immediately, synchronously, and is only
  //   recommended if your grid doesn't have images and such that require a wait
  //   before being fully loaded and sized.
  //
  // - __resizeDelay__ is the millis to wait for window resizing to stop before
  //   doing a re-layout. `100` is the default to balance responsiveness and
  //   performance.
  //
  // - Note: for these extensions, the majority of presentation state logic is
  //   in the extension stylesheet. We update the presentation state by using
  //   namespaced __classNames__ generated in a closure.
  //
  hlf.mediaGrid = {
    debug: false,
    toString(context) {
      switch (context) {
        case 'event': return 'hlfmg';
        case 'data': return 'hlf-mg';
        case 'class': return 'mg';
        default: return 'hlf-mg';
      }
    },
    defaults: {
      autoReady: false,
      resizeDelay: 100,
      undimDelay: 1000,
    },
  };
  //
  // MediaGrid
  // ---------
  //
  class MediaGrid {
    constructor(element, options) {
      [ '_onItemClick', '_onItemExpand', '_onItemMouseEnter',
        '_onItemMouseLeave', '_onMouseLeave', '_onWindowResize',
      ].forEach((name) => {
        this[name] = this[name].bind(this);
      });
      this.eventListeners = { mouseleave: this._onMouseLeave };
      this.eventListeners[this.eventName('expand')] = this._onItemExpand;
    }
    //
    // __init__ completes the setup:
    // 1. Select `itemElements` and `sampleItemElement`.
    // 2. Get `expandDuration` from styles, create event bindings for expansion.
    //    This also relies on the `expandedItemElement` state.
    // 3. Create event bindings for dimming on expansion, hovering expanded item.
    // 4. Set up initial layout for running when `ready`.
    // 5. Set up re-layout for running on window resize.
    //
    init() {
      if (!this.itemElements) {
        this.itemElements = this.selectAllByClass('item');
      }
      Array.from(this.itemElements).forEach((itemElement) => {
        this.addEventListeners({
          'click': this._onItemClick,
          'mouseenter': this._onItemMouseEnter,
          'mouseleave': this._onItemMouseLeave,
        }, itemElement);
      });
      window.addEventListener('resize', this._onWindowResize);
      this.sampleItemElement = this.itemElements[0];
      this.expandDuration = 1000 * parseFloat(
        getComputedStyle(this.sampleItemElement).transitionDuration
      );
      this.expandedItemElement = null;
      this.metrics = {};
      if (this.autoLoad) {
        this.performLoad();
      }
    }
    deinit() {
      this.removeEventListeners(this.eventListeners);
      Array.from(this.itemElements).forEach((itemElement) => {
        this.removeEventListeners({
          'click': this._onItemClick,
          'mouseenter': this._onItemMouseEnter,
          'mouseleave': this._onItemMouseLeave,
        }, itemElement);
      });
      window.removeEventListener('resize', this._onWindowResize);
    }
    performLoad() {
      this._updateMetrics();
      this._layoutItems();
      this.element.classList.add(this.className('ready'));
      this.dispatchCustomEvent('ready');
    }
    //
    // § __Public__
    //
    // You are welcome to call these methods from your own code, though currently
    // there is no intended use case for that.
    //
    // __toggleItemExpansion__ basically toggles the `-expanded` class on the
    // given `itemElement` to `expanded` and triggers the `expand` event. To allow
    // styling or scripting during the transition, it adds the `-transitioning`
    // class and removes it afterwards per `expandDuration`.
    //
    toggleItemExpansion(itemElement, expanded, completion) {
      if (typeof expanded === 'undefined') {
        expanded = !itemElement.classList.contains(this.className('expanded'));
      }
      let index = Array.from(this.itemElements).indexOf(itemElement);
      if (expanded) {
        if (this.expandedItemElement) {
          this.toggleItemExpansion(this.expandedItemElement, false);
        }
        if (this._isRightEdgeItem(index)) {
          this._adjustItemToRightEdge(itemElement);
        }
        if (this._isBottomEdgeItem(index)) {
          this._adjustItemToBottomEdge(itemElement);
        }
      }
      this._toggleNeighborItemsRecessed(index, expanded);
      itemElement.classList.add(this.className('transitioning'));
      clearTimeout(itemElement.getAttribute(this.attrName('expand-timeout')));
      itemElement.setAttribute(this.attrName('expand-timeout'),
        setTimeout(() => {
          itemElement.classList.remove(this.className('transitioning'));
          if (completion) {
            completion();
          }
        }, this.expandDuration)
      );

      itemElement.classList.toggle(this.className('expanded'), expanded);
      this.expandedItemElement = expanded ? itemElement : null;

      itemElement.dispatchEvent(this.createCustomEvent('expand', { expanded }));
    }
    //
    // __toggleExpandedItemFocus__ wraps `toggleItemFocus` to factor in
    // `undimDelay` when toggling off `focus`. Focusing dims without delay.
    //
    toggleExpandedItemFocus(itemElement, focused) {
      if (!itemElement.classList.contains(this.className('expanded'))) { return; }
      let delay = focused ? 0 : this.undimDelay;
      this.toggleItemFocus(itemElement, focused, delay);
    }
    //
    // __toggleItemFocus__ basically toggles the `-focused` class on the given
    // `itemElement` to `focused` and the `-dimmed` class on the root element
    // after any given `delay`.
    //
    toggleItemFocus(itemElement, focused, delay) {
      if (focused) {
        Array.from(this.itemElements).forEach((itemElement) => {
          itemElement.classList.remove(this.className('focused'));
        });
      }
      itemElement.classList.toggle(this.className('focused'), focused);
      clearTimeout(this._dimTimeout);
      this._dimTimeout = setTimeout(() => {
        this.element.classList.toggle(this.className('dimmed'), focused);
      }, delay);
    }
    //
    // § __Internal__
    //
    _onItemClick(event) {
      const actionElementTags = ['A', 'AUDIO', 'BUTTON', 'INPUT', 'VIDEO'];
      if (actionElementTags.indexOf(event.target.tagName) !== -1) { return; }
      this.toggleItemExpansion(event.currentTarget);
    }
    _onItemExpand(event) {
      const { target } = event;
      if (!target.classList.contains(this.className('item'))) { return; }
      const { expanded } = event.detail;
      this.toggleItemFocus(target, expanded, this.expandDuration);
    }
    _onItemMouseEnter(event) {
      this.toggleExpandedItemFocus(event.currentTarget, true);
    }
    _onItemMouseLeave(event) {
      this.toggleExpandedItemFocus(event.currentTarget, false);
    }
    _onMouseLeave(_) {
      if (!this.expandedItemElement) { return; }
      this.toggleItemFocus(this.expandedItemElement, false, 0);
    }
    _onWindowResize(_) {
      const now = Date.now();
      if (this._ran && now < this._ran + this.resizeDelay) { return; }
      this._ran = now;
      this._updateMetrics(false);
      this._reLayoutItems();
    }
    //
    // These are layout helpers for changing offset for an `itemElement`.
    //
    _adjustItemToBottomEdge(itemElement) {
      itemElement.style.top = 'auto';
      itemElement.style.bottom = '0px';
    }
    _adjustItemToRightEdge(itemElement) {
      itemElement.style.left = 'auto';
      itemElement.style.right = '0px';
    }
    //
    // ___getMetricSamples__ returns cloned `itemElement` and `expandedItemElement`
    // mainly for calculating initial metrics. For them to have the right sizes,
    // they're attached to an invisible container appended to the root element.
    //
    _getMetricSamples() {
      let containerElement = this.selectByClass('sample');
      if (containerElement) {
        containerElement.parentNode.removeChild(containerElement);
      }
      let itemElement = this.sampleItemElement.cloneNode(true);
      let expandedItemElement = this.sampleItemElement.cloneNode(true);
      expandedItemElement.classList.add(this.className('expanded'));
      containerElement = document.createElement('div');
      containerElement.classList.add(this.className('sample'));
      let { style } = containerElement;
      style.left = style.right = style.top = '0px';
      style.position = 'absolute';
      style.visibility = 'hidden';
      style.zIndex = 0;
      containerElement.appendChild(itemElement);
      containerElement.appendChild(expandedItemElement);
      this.element.appendChild(containerElement);
      return { itemElement, expandedItemElement };
    }
    //
    // These are layout helpers for checking item position based on index and the
    // current `rowSize` metric.
    //
    _isBottomEdgeItem(i) {
      return (i + 1) > (this.itemElements.length - this.metrics.rowSize);
    }
    _isRightEdgeItem(i) {
      return ((i + 1) % this.metrics.rowSize) === 0;
    }
    //
    // ___layoutItems__ occurs once `metrics` is updated. With the latest
    // `wrapWidth` and `wrapHeight` metrics, the root element is resized. Each
    // element in `itemElements` gets its position style set to `absolute` non-
    // destructively; this method assumes the original is `float`, and so
    // iterates in reverse.
    //
    _layoutItems() {
      Array.from(this.itemElements).reverse().forEach((itemElement) => {
        if (!itemElement.hasAttribute(this.attrName('original-position'))) {
          itemElement.setAttribute(this.attrName('original-position'),
            getComputedStyle(itemElement).position);
        }
        let { offsetLeft, offsetTop, style } = itemElement;
        style.position = 'absolute';
        style.left = `${offsetLeft}px`;
        style.top = `${offsetTop}px`;
      });
      let { style } = this.element;
      style.width = `${this.metrics.wrapWidth}px`;
      style.height = `${this.metrics.wrapHeight}px`;
    }
    //
    // ___reLayoutItems__ wraps `_layoutItems` to be its idempotent version by
    // first resetting each item's to its `original-position`. It can run after a
    // custom `delay`.
    //
    _reLayoutItems() {
      if (this.expandedItemElement) {
        this.toggleItemExpansion(
          this.expandedItemElement, false, this._reLayoutItems.bind(this)
        );
        return;
      }
      Array.from(this.itemElements).forEach((itemElement) => {
        let { style } = itemElement;
        style.bottom = style.left = style.right = style.top = 'auto';
        style.position = itemElement.getAttribute(this.attrName('original-position'));
        itemElement.classList.remove(this.className('raw'));
      });
      this._layoutItems();
    }
    //
    // TODO
    //
    _toggleNeighborItemsRecessed(index, recessed) {
      const { expandedScale, rowSize } = this.metrics;
      let dx = this._isRightEdgeItem(index) ? -1 : 1;
      let dy = this._isBottomEdgeItem(index) ? -1 : 1;
      let level = 1;
      let neighbors = [];
      while (level < expandedScale) {
        neighbors.push(
          this.itemElements[index + level * dx],
          this.itemElements[index + level * dy * rowSize],
          this.itemElements[index + level * (dy * rowSize + dx)]
        );
        level += 1;
      }
      neighbors.forEach((itemElement) => {
        itemElement.classList.toggle(this.className('recessed'));
      });
    }
    //
    // ___updateMetrics__ builds the `metrics` around item and wrap as well as
    // row and column sizes. It does so by measuring sample elements and their
    // margins, as well as sizing the wrap (root element) to fit its items. As
    // such, this method isn't idempotent and expects to be followed by a call
    // to `_layoutItems`.
    //
    _updateMetrics(hard = true) {
      if (hard) {
        const { itemElement, expandedItemElement } = this._getMetricSamples();
        this.metrics = {
          itemWidth: itemElement.offsetWidth,
          itemHeight: itemElement.offsetHeight,
          expandedWidth: expandedItemElement.offsetWidth,
          expandedHeight: expandedItemElement.offsetHeight,
          expandedScale: parseInt(this.cssVariable('item-expanded-scale')),
        };
      }
      let gutter = Math.round(parseFloat(
        getComputedStyle(this.sampleItemElement).marginRight
      ));
      let fullWidth = this.metrics.itemWidth + gutter;
      let fullHeight = this.metrics.itemHeight + gutter;

      let { style } = this.element;
      style.height = style.width = 'auto';

      let rowSize = parseInt(((this.element.offsetWidth + gutter) / fullWidth));
      let colSize = Math.ceil(this.itemElements.length / rowSize);
      Object.assign(this.metrics, { gutter, rowSize, colSize }, {
        wrapWidth: fullWidth * rowSize,
        wrapHeight: fullHeight * colSize,
      });
    }
  }
  //
  // § __Attaching__
  //
  return hlf.createExtension({
    name: 'mediaGrid',
    namespace: hlf.mediaGrid,
    apiClass: MediaGrid,
    autoListen: true,
    baseMethodGroups: ['css', 'event', 'selection'],
    compactOptions: true,
  });
});
<|endoftext|>


<commit_before>define(
    ["js/ui/View", "js/core/Template", "js/core/List", "underscore"], function (View, Template, List, _) {
        return View.inherit({
            defaults: {
                tagName: "div",
                items: null,
                itemKey: 'item',
                indexKey: 'index'
            },
            $classAttributes: [
                'itemKey', 'indexKey'
            ],

            hasItems: function () {
                if (this.$.items) {
                    return this.$.items.length;
                }
                return 0;
            }.on('items'),
            render: function () {
                this.$renderedItems = [];
                return this.callBase();
            },
            clear: function () {

            },
            _renderItems: function (items, oldItems) {
                if (oldItems && oldItems instanceof List) {
                    oldItems.unbind('sort', this._onSort);
                    oldItems.unbind('reset', this._onReset);
                    oldItems.unbind('add', this._onItemAdd);
                    oldItems.unbind('remove', this._onItemRemove);
                }

                if (items instanceof List) {

                    items.bind('sort', this._onSort, this);
                    items.bind('reset', this._onReset, this);
                    items.bind('add', this._onItemAdd, this);
                    items.bind('remove', this._onItemRemove, this);
                    this._innerRenderItems(items.$items);
                } else if (_.isArray(items)) {
                    this._innerRenderItems(items);
                }
            },
            _onSort: function (e) {
                if (this.isRendered()) {
                    var item, c;
                    for (var i = 0; i < e.$.items.length; i++) {
                        item = e.$.items[i];
                        c = this.getComponentForItem(item);
                        this.$el.removeChild(c.$el);
                        this.$el.appendChild(c.$el);
                    }
                }
            },
            _onReset: function (e) {
                this._innerRenderItems(e.$.items);
            },
            _onItemAdd: function (e) {
                this._innerRenderItem(e.$.item, e.$.index);
            },
            _onItemRemove: function (e) {
                this._removeRenderedItem(e.$.item);
            },
            _innerRenderItems: function (items) {
                if (this.$renderedItems) {
                    var c;
                    for (var j = this.$renderedItems.length - 1; j >= 0; j--) {
                        c = this.$renderedItems[j];
                        this.$el.removeChild(c.component.$el);
                    }
                }
                this.$renderedItems = [];
                for (var i = 0; i < items.length; i++) {
                    this._innerRenderItem(items[i], i);
                }

            },
            _innerRenderItem: function (item, i) {
                var attr = {}; attr["$"+this.$.itemKey] = item; attr["$"+this.$.indexKey]  = i;
                var comp = this.$templates['item'].createComponents(attr)[0];
                // add to rendered item map
                this.$renderedItems.push({
                    item: item,
                    component: comp
                });
                this.addChild(comp);
            },
            _removeRenderedItem: function (item) {
                var ri;
                for (var i = 0; i < this.$renderedItems.length; i++) {
                    ri = this.$renderedItems[i];
                    if (ri.item === item) {
                        this.$el.removeChild(ri.component.$el);
                        this.$renderedItems.splice(i, 1);
                        return;
                    }
                }
            },
            getComponentForItem: function (item) {
                var ri;
                for (var i = 0; i < this.$renderedItems.length; i++) {
                    ri = this.$renderedItems[i];
                    if (ri.item === item) {
                        return ri.component;
                    }
                }
                return null;
            }
        });
    }
);<commit_msg>Fixed Bug in ItemsView<commit_after>define(
    ["js/ui/View", "js/core/Template", "js/core/List", "underscore"], function (View, Template, List, _) {
        return View.inherit({
            defaults: {
                tagName: "div",
                items: null,
                itemKey: 'item',
                indexKey: 'index'
            },
            $classAttributes: [
                'itemKey', 'indexKey'
            ],

            hasItems: function () {
                if (this.$.items) {
                    return this.$.items.length;
                }
                return 0;
            }.on('items'),
            render: function () {
                if(!this.isRendered()){
                    this.$renderedItems = [];
                }
                return this.callBase();
            },
            clear: function () {

            },
            _renderItems: function (items, oldItems) {
                if (oldItems && oldItems instanceof List) {
                    oldItems.unbind('sort', this._onSort);
                    oldItems.unbind('reset', this._onReset);
                    oldItems.unbind('add', this._onItemAdd);
                    oldItems.unbind('remove', this._onItemRemove);
                }

                if (items instanceof List) {

                    items.bind('sort', this._onSort, this);
                    items.bind('reset', this._onReset, this);
                    items.bind('add', this._onItemAdd, this);
                    items.bind('remove', this._onItemRemove, this);
                    this._innerRenderItems(items.$items);
                } else if (_.isArray(items)) {
                    this._innerRenderItems(items);
                }
            },
            _onSort: function (e) {
                if (this.isRendered()) {
                    var item, c;
                    for (var i = 0; i < e.$.items.length; i++) {
                        item = e.$.items[i];
                        c = this.getComponentForItem(item);
                        this.$el.removeChild(c.$el);
                        this.$el.appendChild(c.$el);
                    }
                }
            },
            _onReset: function (e) {
                this._innerRenderItems(e.$.items);
            },
            _onItemAdd: function (e) {
                this._innerRenderItem(e.$.item, e.$.index);
            },
            _onItemRemove: function (e) {
                this._removeRenderedItem(e.$.item);
            },
            _innerRenderItems: function (items) {
                if (this.$renderedItems) {
                    var c;
                    for (var j = this.$renderedItems.length - 1; j >= 0; j--) {
                        c = this.$renderedItems[j];
                        this.$el.removeChild(c.component.$el);
                    }
                }
                this.$renderedItems = [];
                for (var i = 0; i < items.length; i++) {
                    this._innerRenderItem(items[i], i);
                }

            },
            _innerRenderItem: function (item, i) {
                var attr = {}; attr["$"+this.$.itemKey] = item; attr["$"+this.$.indexKey]  = i;
                var comp = this.$templates['item'].createComponents(attr)[0];
                // add to rendered item map
                this.$renderedItems.push({
                    item: item,
                    component: comp
                });
                this.addChild(comp);
            },
            _removeRenderedItem: function (item) {
                var ri;
                for (var i = 0; i < this.$renderedItems.length; i++) {
                    ri = this.$renderedItems[i];
                    if (ri.item === item) {
                        this.$el.removeChild(ri.component.$el);
                        this.$renderedItems.splice(i, 1);
                        return;
                    }
                }
            },
            getComponentForItem: function (item) {
                var ri;
                for (var i = 0; i < this.$renderedItems.length; i++) {
                    ri = this.$renderedItems[i];
                    if (ri.item === item) {
                        return ri.component;
                    }
                }
                return null;
            }
        });
    }
);<|endoftext|>


<commit_before>/**
 * Created by Krysp on 4/21/16.
 */
var imgs = {1:"It's the final", 2:"count down", 3:"!!!!!!!",4:"tada"}
var clicked = '0';
var myBtn =  document.getElementById('navBtner').style;
var text = ''
function btnMove(){
     $("#wrapper").toggleClass("toggled");
    if (clicked == '0') {
        clicked = '1';
        myBtn.transform = 'rotate(180deg)';
        myBtn.left = '0px';
        myBtn.color = 'black';
    } else {
        clicked = '0';
        myBtn.transform = 'rotate(0deg)';
        myBtn.left = '200px';
        myBtn.color = 'ghostWhite';
    }
}

// $("#navBtner").click(function(e) {
//     $("#wrapper").toggleClass("toggled");
// });

function moveMe(){
   document.getElementById('sm-cont').style.position = 'absolute';
   document.getElementById('sm-cont').style.top = '0px';
   document.getElementById('sm-cont').style.right = '100px';
}

$(document).ready(function(){
    $("sss").click(function(){
        $(this).hide();
    });
});

<commit_msg>Added files via upload<commit_after>/**
 * Created by Krysp on 4/21/16.
 */

var imgs = {1:"It's the final", 2:"count down", 3:"!!!!!!!",4:"tada"}
var clicked = '0';
var myBtn =  document.getElementById('navBtner').style;
var text = ''

function btnMove(){
     $("#wrapper").toggleClass("toggled");
    if (clicked == '0') {
        clicked = '1';
        myBtn.transform = 'rotate(180deg)';
        myBtn.left = '0px';
        myBtn.color = 'black';
    } else {
        clicked = '0';
        myBtn.transform = 'rotate(0deg)';
        myBtn.left = '200px';
        myBtn.color = 'ghostWhite';
    }
}

 document.getElementById("noGo1").addEventListener("click", function(event){
    event.preventDefault()
});
 document.getElementById("noGo2").addEventListener("click", function(event){
    event.preventDefault()
});
 document.getElementById("noGo3").addEventListener("click", function(event){
    event.preventDefault()
});

//function clickedOn(){
  // $(“#thoughts”).click(function() {
   //$(this).addClass(“.your_class_with_new_color”);
//});
//}
// $("#navBtner").click(function(e) {
//     $("#wrapper").toggleClass("toggled");
// });

function moveMe(){
   document.getElementById('sm-cont').style.position = 'absolute';
   document.getElementById('sm-cont').style.top = '0px';
   document.getElementById('sm-cont').style.right = '100px';
}

$(document).ready(function(){
    $("sss").click(function(){
        $(this).hide();
    });
});

<|endoftext|>


<commit_before>/*
 * Developed by Radu Nicolae Puspana
 * Copyright Radu Nicolae Puspana
 * Developed in 2017
 * Version 1.0
 */

// UI module manipulates the UI of the app
var uiController = (function() {

    // private object containing the class names used in index.html and style.css
    var prvDOMstrings = {
        dateLabel: ".budget__title__month__year",
        inputType: ".add__type",
        inputDescription: ".add__description",
        inputValue: ".add__value",
        expenseTransInputFieldsColor: "input-fields-color-select-trans-type",
        inputButton: ".add__btn",
        expenseTransSubmitBtnColor: "button-color-select-trans-type",
        incomeContainer: ".income__list",
        expenseContainer: ".expenses__list",
        budgetLabel: ".budget__value",
        incomeLabel: ".budget__income__value",
        expenseLabel: ".budget__expenses__value",
        expPercentTotalInc: ".budget__expenses__percentage",
        transactionContainer: ".container",
        expensePercentageLabel: ".item__percentage",
        errorPopupOKbutton: "err_alert_box_OK_button",
        errorPopupXbutton: "alert_box_x_button",
        popupContainer: "alertBox_container",
        popupButtonDiv: "alertBox_button_div",
        popupTextAndButtonContainer: "alertBox_text",
        popupMainDiv: "alertBox"
    };

    return {
        // public method to retrieve and return all three field values as strings from the UI
        pblGetInput: function() {

            // return an object with all three fields values as strings from the UI
            return {
                // return "inc" or "exp" from the transaction type list
                type: document.querySelector(prvDOMstrings.inputType).value,

                // return the transaction description as string
                description: document.querySelector(prvDOMstrings.inputDescription).value,

                // return the transaction value as a string
                value: document.querySelector(prvDOMstrings.inputValue).value
            };
        },

        // public method for listing a transaction entered by the user as input, under the Expense or Income column
        // transcation  Expense/Income instance  trasaction entered by the user
        // type  String  type of transaction: income/expense
        pblAddListItem: function(transcation, type) {

            // html code of a transaction list item to be added with dummy code for the transaction's HTML id, description and value
            var transactionListItem;

            // html code of a transaction list item to be added with value from the transaction parameter for the item's HTML id, description and value
            var newTransactionListItem;

            // HTML class of the father element of the income or expense list
            var transListRootElemClass;

            // if the type of transaction is an income
            if (type === "inc") {
                // transaction list item HTML code with dummy values for:
                // - transaction's HTML id (income-%id%),
                // - the transaction description (%description%),
                // - transaction's value(%value%)
                transactionListItem = '

%description%

%value%

<button class="item__delete__btn"></button>

';

                transListRootElemClass = prvDOMstrings.incomeContainer;
            }
            // if the type of transaction is an expense
            else if (type === "exp") {

                transactionListItem = '

%description%

%value%

-

<button class="item__delete__btn"></button>

';

                transListRootElemClass = prvDOMstrings.expenseContainer;
            }

            // get the time and date
            var timeDateObj = rpJSframework.pblGetLocalTimeAndDate();

            // replace the dummy text in transactionListItem with actual data from the transaction object
            newTransactionListItem = transactionListItem.replace("%id%", transcation.id);
            newTransactionListItem = newTransactionListItem.replace("%description%",
                                                                    timeDateObj.timeDateCustomFormat + transcation.description);
            newTransactionListItem =
                newTransactionListItem.replace("%value%",                                       rpJSframework.pblFormatNumberUsingSystemLocale(transcation.value));

            // select the transacton column father HTML element and insert the new transaction as it's first child
            // the newest transaction will be placed at the end of the income or expense column
            document.querySelector(transListRootElemClass).insertAdjacentHTML("beforeend", newTransactionListItem);
        },

        // public method for returning the object containing the class names
        // in the index.html and style.css
        pblGetDOMstrings: function() {
            return prvDOMstrings;
        },

        // for each submited expense, display the percentage Of that expense out of the total income
        // expensePercentageTotalIncomeArray  Array instance  array with the expense percentages out of the total income for each expense
        pbldisplayPercentageOfExpenseOutOfTotalIncomeForAllExpenses: function(expensePercentageTotalIncomeArray) {

            var expensePercentageNodes;

            // select all the elements that have the item__percentage class
            expensePercentageNodes = document.querySelectorAll(prvDOMstrings.expensePercentageLabel);

            // display the expense percentage out of the total income in each NodeList with class prvDOMstrings.expensePercentageLabel
            // expensePercentageNode  NodeList instance  the DOM node that has the the class=prvDOMstrings.expensePercentageLabel
            // index                  Number             index of the current DOM node in the expensePercentageNodes
            var displayExpensePercentage = function(expensePercentageNode, index) {

                // if the expense percentage out of total income passed in as a parameter > 0
                if (expensePercentageTotalIncomeArray[index] > 0) {
                    expensePercentageNode.textContent =
                        rpJSframework.pblFormatNumberUsingSystemLocale(expensePercentageTotalIncomeArray[index]) + "%";
                }
                else {
                    expensePercentageNode.textContent = "-";
                }
            }

            // if we found at least one element with HTML class prvDOMstrings.expensePercentageLabel in the DOM
            if (expensePercentageNodes.length > 0) {

                for(var i = 0; i < expensePercentageNodes.length; i++) {
                    displayExpensePercentage(expensePercentageNodes[i], i);
                }
            }
            // if no element with this class was found in the DOM
            else {
                console.log("The user did not submit any expenses yet, or there are no nodes, in the DOM,  with the attribute class=%s yet.\n" +
                             "Can't display expense percentage out of total income label.", prvDOMstrings.expensePercentageLabel);
            }

        },

        // public method for deleting a submited transaction from the income or expense column based it's type
        // transId  string  id value of the 4-th ancestor of the event target DOM element
        pblDeleteListItem :function(transId) {
            var deleteNode, fatherNode;

            // select the node to delete
            deleteNode = document.getElementById(transId);

            // select the father of the node to delete
            fatherNode = deleteNode.parentNode;

            // remove the node from the DOM tree
            console.info(fatherNode.removeChild(deleteNode));
        },

        // clear the the transaction's details from the transaction form on the UI (after the user submited the transaction)
        pblClearTransFormFields: function() {
            // NodeList instance that includes the transaction's description and value UI fields
            var transFormDescAndValueFieldsNl;

            // Array instance of the transaction's description and value fields from the UI
            var transFormDescAndValueFieldsArray;

            // get the trasaction's description and value from the UI's input form
            transFormDescAndValueFieldsNl = document.querySelectorAll(prvDOMstrings.inputDescription + "," + prvDOMstrings.inputValue);

            // transform the descAndValueNodeList NodeList into an array
            transFormDescAndValueFieldsArray = Array.prototype.slice.call(transFormDescAndValueFieldsNl);

            // call the annonymous function on each element of the transFormDescAndValueFieldsArray array.
            // result : in the UI, clear the transaction's description and value in the transaction input form
            transFormDescAndValueFieldsArray.forEach(function(currentElem, currentElemIndex, array) {
                currentElem.value = "";
            });

            // put the focus back on the transaction description field on the transaction input form
            transFormDescAndValueFieldsArray[0].focus();
        },

        // when the user changes the transaction type, all the user input fields and the submit button
        // will be colored in a color associated to that type: green for an income transaction, orange for an expense
        pblColorCodeTransInputFields: function() {
            var fields = document.querySelectorAll(
                            prvDOMstrings.inputType + "," +
                            prvDOMstrings.inputDescription + "," +
                            prvDOMstrings.inputValue
            );

            // add a red border color to th last two input fields and ared color to the submit button
            // when the transaction type is changed to expense and remove them when the selection is changed to income
            rpJSframework.nodeListForEach(fields, function(currentElem, currentElemIndex) {
                currentElem.classList.toggle(prvDOMstrings.expenseTransInputFieldsColor);
            });

            document.querySelector(prvDOMstrings.inputButton).classList.toggle(prvDOMstrings.expenseTransSubmitBtnColor);

        },

        // display on the UI the total income, the total expenses, the budget and
        // how much of the total income the expenses represent in percentages
        pblDisplayTransactionOverview: function(budgetObj) {

            // select the budget label on the UI
            document.querySelector(prvDOMstrings.budgetLabel).textContent = rpJSframework.pblFormatNumberUsingSystemLocale(budgetObj.budget);

            // select the green background total income label on the UI and display it with two decimals
            document.querySelector(prvDOMstrings.incomeLabel).textContent =         rpJSframework.pblFormatNumberUsingSystemLocale(budgetObj.totalIncome);

            // select the red background total expenses label on the UI
            document.querySelector(prvDOMstrings.expenseLabel).textContent = rpJSframework.pblFormatNumberUsingSystemLocale(budgetObj.totalExpenses);

            // select the expenses percent label to the right of the total expenses label on the UI
            var expensePercentTotalIncElem =  document.querySelector(prvDOMstrings.expPercentTotalInc);

            // if at least one income transaction was submited
            if (budgetObj.expensesPercentageIncome > 0) {
                expensePercentTotalIncElem.textContent =
                    rpJSframework.pblFormatNumberUsingSystemLocale(budgetObj.expensesPercentageIncome) + "%";
            }
            // if there are no expsense transactions submited
            else {
                // add "-" in the ".budget__expenses__percentage"

                expensePercentTotalIncElem.textContent = "-";
            }

//            // get the with in px of expensePercentTotalIncElem
//            expensePercentTotalIncElemWidth = expensePercentTotalIncElem.scrollWidth;
//            console.info("expensePercentTotalIncElemWidth = %d", expensePercentTotalIncElem.scrollWidth);
//
//            // resize the budget__income__percentage to the width of expensePercentTotalIncElemWidth,
//            // so that the total income and total expense sums are right under each other
//            document.querySelector(".budget__income__percentage").style.width = expensePercentTotalIncElemWidth + "px";
        },

        // create and display a custom popup window
        // text    String  body text to be displayed in the popup
        // type    String  type of popup to be displayed
        // height  String  height of the modal window
        pblConstructPopupAndRegisterItsEvents: function(text, type, height) {

            var button, buttonId, popupColor, popupNode, popupContainerOpeningDivsStr, popupContainerClosingDivsStr;

            popupContainerOpeningDivsStr = '

myExpenses<input id="alert_box_x_button" type="button" value="X">

';

            popupContainerClosingDivsStr = '

';

            if (type === "err") {
                button = '
<input id="' +
                         prvDOMstrings.errorPopupOKbutton + '" class="button" type="button" value="OK" alt="OK">
';

                popupColor = "#D1363A";
                buttonId = prvDOMstrings.errorPopupOKbutton;
            }

            popupNode = popupContainerOpeningDivsStr + text + button + popupContainerClosingDivsStr;

            document.querySelector(prvDOMstrings.transactionContainer).insertAdjacentHTML("beforeend", popupNode);

            if (height !== undefined) {document.getElementById(prvDOMstrings.popupMainDiv).style.height = height;}
            document.getElementById(prvDOMstrings.popupContainer).style.backgroundColor = popupColor;

            // hide the custom popup when it's X button is clicked
            document.getElementById(prvDOMstrings.errorPopupXbutton).addEventListener("click", function() {
                    document.getElementById(prvDOMstrings.popupMainDiv).style.visibility = "hidden";
            });

            // hide the custom popup when it's OK button is clicked
            document.getElementById(buttonId).addEventListener("click", function() {
                    document.getElementById(prvDOMstrings.popupMainDiv).style.visibility = "hidden";
            });
        }
    }
})();

console.log("uiController.js has finished loading.");
<commit_msg>pblColorCodeTransInputFields()-change the call of rpJSframework.nodeListForEach(...) with rpJSframework.pblNodeListForEach due to the method's name change in library.js<commit_after>/*
 * Developed by Radu Nicolae Puspana
 * Copyright Radu Nicolae Puspana
 * Developed in 2017
 * Version 1.0
 */

// UI module manipulates the UI of the app
var uiController = (function() {

    // private object containing the class names used in index.html and style.css
    var prvDOMstrings = {
        dateLabel: ".budget__title__month__year",
        inputType: ".add__type",
        inputDescription: ".add__description",
        inputValue: ".add__value",
        expenseTransInputFieldsColor: "input-fields-color-select-trans-type",
        inputButton: ".add__btn",
        expenseTransSubmitBtnColor: "button-color-select-trans-type",
        incomeContainer: ".income__list",
        expenseContainer: ".expenses__list",
        budgetLabel: ".budget__value",
        incomeLabel: ".budget__income__value",
        expenseLabel: ".budget__expenses__value",
        expPercentTotalInc: ".budget__expenses__percentage",
        transactionContainer: ".container",
        expensePercentageLabel: ".item__percentage",
        errorPopupOKbutton: "err_alert_box_OK_button",
        errorPopupXbutton: "alert_box_x_button",
        popupContainer: "alertBox_container",
        popupButtonDiv: "alertBox_button_div",
        popupTextAndButtonContainer: "alertBox_text",
        popupMainDiv: "alertBox"
    };

    return {
        // public method to retrieve and return all three field values as strings from the UI
        pblGetInput: function() {

            // return an object with all three fields values as strings from the UI
            return {
                // return "inc" or "exp" from the transaction type list
                type: document.querySelector(prvDOMstrings.inputType).value,

                // return the transaction description as string
                description: document.querySelector(prvDOMstrings.inputDescription).value,

                // return the transaction value as a string
                value: document.querySelector(prvDOMstrings.inputValue).value
            };
        },

        // public method for listing a transaction entered by the user as input, under the Expense or Income column
        // transcation  Expense/Income instance  trasaction entered by the user
        // type  String  type of transaction: income/expense
        pblAddListItem: function(transcation, type) {

            // html code of a transaction list item to be added with dummy code for the transaction's HTML id, description and value
            var transactionListItem;

            // html code of a transaction list item to be added with value from the transaction parameter for the item's HTML id, description and value
            var newTransactionListItem;

            // HTML class of the father element of the income or expense list
            var transListRootElemClass;

            // if the type of transaction is an income
            if (type === "inc") {
                // transaction list item HTML code with dummy values for:
                // - transaction's HTML id (income-%id%),
                // - the transaction description (%description%),
                // - transaction's value(%value%)
                transactionListItem = '

%description%

%value%

<button class="item__delete__btn"></button>

';

                transListRootElemClass = prvDOMstrings.incomeContainer;
            }
            // if the type of transaction is an expense
            else if (type === "exp") {

                transactionListItem = '

%description%

%value%

-

<button class="item__delete__btn"></button>

';

                transListRootElemClass = prvDOMstrings.expenseContainer;
            }

            // get the time and date
            var timeDateObj = rpJSframework.pblGetLocalTimeAndDate();

            // replace the dummy text in transactionListItem with actual data from the transaction object
            newTransactionListItem = transactionListItem.replace("%id%", transcation.id);
            newTransactionListItem = newTransactionListItem.replace("%description%",
                                                                    timeDateObj.timeDateCustomFormat + transcation.description);
            newTransactionListItem =
                newTransactionListItem.replace("%value%",                                       rpJSframework.pblFormatNumberUsingSystemLocale(transcation.value));

            // select the transacton column father HTML element and insert the new transaction as it's first child
            // the newest transaction will be placed at the end of the income or expense column
            document.querySelector(transListRootElemClass).insertAdjacentHTML("beforeend", newTransactionListItem);
        },

        // public method for returning the object containing the class names
        // in the index.html and style.css
        pblGetDOMstrings: function() {
            return prvDOMstrings;
        },

        // for each submited expense, display the percentage Of that expense out of the total income
        // expensePercentageTotalIncomeArray  Array instance  array with the expense percentages out of the total income for each expense
        pbldisplayPercentageOfExpenseOutOfTotalIncomeForAllExpenses: function(expensePercentageTotalIncomeArray) {

            var expensePercentageNodes;

            // select all the elements that have the item__percentage class
            expensePercentageNodes = document.querySelectorAll(prvDOMstrings.expensePercentageLabel);

            // display the expense percentage out of the total income in each NodeList with class prvDOMstrings.expensePercentageLabel
            // expensePercentageNode  NodeList instance  the DOM node that has the the class=prvDOMstrings.expensePercentageLabel
            // index                  Number             index of the current DOM node in the expensePercentageNodes
            var displayExpensePercentage = function(expensePercentageNode, index) {

                // if the expense percentage out of total income passed in as a parameter > 0
                if (expensePercentageTotalIncomeArray[index] > 0) {
                    expensePercentageNode.textContent =
                        rpJSframework.pblFormatNumberUsingSystemLocale(expensePercentageTotalIncomeArray[index]) + "%";
                }
                else {
                    expensePercentageNode.textContent = "-";
                }
            }

            // if we found at least one element with HTML class prvDOMstrings.expensePercentageLabel in the DOM
            if (expensePercentageNodes.length > 0) {

                for(var i = 0; i < expensePercentageNodes.length; i++) {
                    displayExpensePercentage(expensePercentageNodes[i], i);
                }
            }
            // if no element with this class was found in the DOM
            else {
                console.log("The user did not submit any expenses yet, or there are no nodes, in the DOM,  with the attribute class=%s yet.\n" +
                             "Can't display expense percentage out of total income label.", prvDOMstrings.expensePercentageLabel);
            }

        },

        // public method for deleting a submited transaction from the income or expense column based it's type
        // transId  string  id value of the 4-th ancestor of the event target DOM element
        pblDeleteListItem :function(transId) {
            var deleteNode, fatherNode;

            // select the node to delete
            deleteNode = document.getElementById(transId);

            // select the father of the node to delete
            fatherNode = deleteNode.parentNode;

            // remove the node from the DOM tree
            console.info(fatherNode.removeChild(deleteNode));
        },

        // clear the the transaction's details from the transaction form on the UI (after the user submited the transaction)
        pblClearTransFormFields: function() {
            // NodeList instance that includes the transaction's description and value UI fields
            var transFormDescAndValueFieldsNl;

            // Array instance of the transaction's description and value fields from the UI
            var transFormDescAndValueFieldsArray;

            // get the trasaction's description and value from the UI's input form
            transFormDescAndValueFieldsNl = document.querySelectorAll(prvDOMstrings.inputDescription + "," + prvDOMstrings.inputValue);

            // transform the descAndValueNodeList NodeList into an array
            transFormDescAndValueFieldsArray = Array.prototype.slice.call(transFormDescAndValueFieldsNl);

            // call the annonymous function on each element of the transFormDescAndValueFieldsArray array.
            // result : in the UI, clear the transaction's description and value in the transaction input form
            transFormDescAndValueFieldsArray.forEach(function(currentElem, currentElemIndex, array) {
                currentElem.value = "";
            });

            // put the focus back on the transaction description field on the transaction input form
            transFormDescAndValueFieldsArray[0].focus();
        },

        // when the user changes the transaction type, all the user input fields and the submit button
        // will be colored in a color associated to that type: green for an income transaction, orange for an expense
        pblColorCodeTransInputFields: function() {
            var fields = document.querySelectorAll(
                            prvDOMstrings.inputType + "," +
                            prvDOMstrings.inputDescription + "," +
                            prvDOMstrings.inputValue
            );

            // add a red border color to th last two input fields and ared color to the submit button
            // when the transaction type is changed to expense and remove them when the selection is changed to income
            rpJSframework.pblNodeListForEach(fields, function(currentElem, currentElemIndex) {
                currentElem.classList.toggle(prvDOMstrings.expenseTransInputFieldsColor);
            });

            document.querySelector(prvDOMstrings.inputButton).classList.toggle(prvDOMstrings.expenseTransSubmitBtnColor);

        },

        // display on the UI the total income, the total expenses, the budget and
        // how much of the total income the expenses represent in percentages
        pblDisplayTransactionOverview: function(budgetObj) {

            // select the budget label on the UI
            document.querySelector(prvDOMstrings.budgetLabel).textContent = rpJSframework.pblFormatNumberUsingSystemLocale(budgetObj.budget);

            // select the green background total income label on the UI and display it with two decimals
            document.querySelector(prvDOMstrings.incomeLabel).textContent =         rpJSframework.pblFormatNumberUsingSystemLocale(budgetObj.totalIncome);

            // select the red background total expenses label on the UI
            document.querySelector(prvDOMstrings.expenseLabel).textContent = rpJSframework.pblFormatNumberUsingSystemLocale(budgetObj.totalExpenses);

            // select the expenses percent label to the right of the total expenses label on the UI
            var expensePercentTotalIncElem =  document.querySelector(prvDOMstrings.expPercentTotalInc);

            // if at least one income transaction was submited
            if (budgetObj.expensesPercentageIncome > 0) {
                expensePercentTotalIncElem.textContent =
                    rpJSframework.pblFormatNumberUsingSystemLocale(budgetObj.expensesPercentageIncome) + "%";
            }
            // if there are no expsense transactions submited
            else {
                // add "-" in the ".budget__expenses__percentage"

                expensePercentTotalIncElem.textContent = "-";
            }

//            // get the with in px of expensePercentTotalIncElem
//            expensePercentTotalIncElemWidth = expensePercentTotalIncElem.scrollWidth;
//            console.info("expensePercentTotalIncElemWidth = %d", expensePercentTotalIncElem.scrollWidth);
//
//            // resize the budget__income__percentage to the width of expensePercentTotalIncElemWidth,
//            // so that the total income and total expense sums are right under each other
//            document.querySelector(".budget__income__percentage").style.width = expensePercentTotalIncElemWidth + "px";
        },

        // create and display a custom popup window
        // text    String  body text to be displayed in the popup
        // type    String  type of popup to be displayed
        // height  String  height of the modal window
        pblConstructPopupAndRegisterItsEvents: function(text, type, height) {

            var button, buttonId, popupColor, popupNode, popupContainerOpeningDivsStr, popupContainerClosingDivsStr;

            popupContainerOpeningDivsStr = '

myExpenses<input id="alert_box_x_button" type="button" value="X">

';

            popupContainerClosingDivsStr = '

';

            if (type === "err") {
                button = '
<input id="' +
                         prvDOMstrings.errorPopupOKbutton + '" class="button" type="button" value="OK" alt="OK">
';

                popupColor = "#D1363A";
                buttonId = prvDOMstrings.errorPopupOKbutton;
            }

            popupNode = popupContainerOpeningDivsStr + text + button + popupContainerClosingDivsStr;

            document.querySelector(prvDOMstrings.transactionContainer).insertAdjacentHTML("beforeend", popupNode);

            if (height !== undefined) {document.getElementById(prvDOMstrings.popupMainDiv).style.height = height;}
            document.getElementById(prvDOMstrings.popupContainer).style.backgroundColor = popupColor;

            // hide the custom popup when it's X button is clicked
            document.getElementById(prvDOMstrings.errorPopupXbutton).addEventListener("click", function() {
                    document.getElementById(prvDOMstrings.popupMainDiv).style.visibility = "hidden";
            });

            // hide the custom popup when it's OK button is clicked
            document.getElementById(buttonId).addEventListener("click", function() {
                    document.getElementById(prvDOMstrings.popupMainDiv).style.visibility = "hidden";
            });
        }
    }
})();

console.log("uiController.js has finished loading.");
<|endoftext|>