<commit_before>/**
 * Copyright (c) 2010 unscriptable.com
 */

/*jslint browser:true, on:true, sub:true */

(function (doc) {
"use strict";

/*
 * RequireJS css! plugin
 * This plugin will load and wait for css files.  This could be handy when
 * loading css files as part of a layer or as a way to apply a run-time theme.
 * Most browsers do not support the load event handler of the link element.
 * Therefore, we have to use other means to detect when a css file loads.
 * (The HTML5 spec states that the LINK element should have a load event, but
 * not even Chrome 8 or FF4b7 have it, yet.
 * http://www.w3.org/TR/html5/semantics.html#the-link-element)
 *
 * This plugin tries to use the load event and a universal work-around when
 * it is invoked the first time.  If the load event works, it is used on
 * every successive load.  Therefore, browsers that support the load event will
 * just work (i.e. no need for hacks!).  FYI, Feature-detecting the load
 * event is tricky since most browsers have a non-functional onload property.
 *
 * The universal work-around watches a stylesheet until its rules are
 * available (not null or undefined).  There are nuances, of course, between
 * the various browsers.  The isLinkReady function accounts for these.
 *
 * Note: it appears that all browsers load @import'ed stylesheets before
 * fully processing the rest of the importing stylesheet. Therefore, we
 * don't need to find and wait for any @import rules explicitly.
 *
 * Note #2: for Opera compatibility, stylesheets must have at least one rule.
 * AFAIK, there's no way to tell the difference between an empty sheet and
 * one that isn't finished loading in Opera (XD or same-domain).
 *
 * Options:
 *      !nowait - does not wait for the stylesheet to be parsed, just loads it
 *
 * Global configuration options:
 *
 * cssDeferLoad: Boolean. You can also instruct this plugin to not wait
 * for css resources. They'll get loaded asap, but other code won't wait
 * for them. This is just like using the !nowait option on every css file.
 *
 * cssWatchPeriod: if direct load-detection techniques fail, this option
 * determines the msec to wait between brute-force checks for rules. The
 * default is 50 msec.
 *
 * You may specify an alternate file extension:
 *      require('css!myproj/component.less') // --> myproj/component.less
 *      require('css!myproj/component.scss') // --> myproj/component.scss
 *
 * When using alternative file extensions, be sure to serve the files from
 * the server with the correct mime type (text/css) or some browsers won't
 * parse them, causing an error in the plugin.
 *
 * usage:
 *      require(['css!myproj/comp']); // load and wait for myproj/comp.css
 *      define(['css!some/folder/file'], {}); // wait for some/folder/file.css
 *      require(['css!myWidget!nowait']);
 *
 * Tested in:
 *      Firefox 1.5, 2.0, 3.0, 3.5, 3.6, and 4.0b6
 *      Safari 3.0.4, 3.2.1, 5.0
 *      Chrome 7 (8+ is partly b0rked)
 *      Opera 9.52, 10.63, and Opera 11.00
 *      IE 6, 7, and 8
 *      Netscape 7.2 (WTF? SRSLY!)
 * Does not work in Safari 2.x :(
 * In Chrome 8+, there's no way to wait for cross-domain (XD) stylesheets.
 * See comments in the code below.
 * TODO: figure out how to be forward-compatible when browsers support HTML5's
 *  load handler without breaking IE and Opera
*/

var
	// compressibility shortcuts
		onreadystatechange = 'onreadystatechange',
		onload = 'onload',
		createElement = 'createElement',
	// failed is true if RequireJS threw an exception
	failed = false,
		undef,
		insertedSheets = {},
		features = {
// true if the onload event handler works
			// "event-link-onload" : false
		},
		// find the head element and set it to it's standard property if nec.
		head = doc.head || (doc.head = doc.getElementsByTagName('head')[0]);

	function has (feature) {
		return features[feature];
	}

	// failure detection
	// we need to watch for onError when using RequireJS so we can shut off
	// our setTimeouts when it encounters an error.
	if (require['onError']) {
		require['onError'] = (function (orig) {
		return function () {
			failed = true;
			orig.apply(this, arguments);
		}
		})(require['onError']);
}

/***** load-detection functions *****/

function loadHandler (params, cb) {
	// We're using 'readystatechange' because IE and Opera happily support both
	var link = params.link;
		link[onreadystatechange] = link[onload] = function () {
		if (!link.readyState || link.readyState == 'complete') {
				features["event-link-onload"] = true;
			cleanup(params);
			cb();
		}
	};
}

function nameWithExt (name, defaultExt) {
	return name.lastIndexOf('.') <= name.lastIndexOf('/') ?
		name + '.' + defaultExt : name;
}

function parseSuffixes (name) {
	// creates a dual-structure: both an array and a hashmap
	// suffixes[0] is the actual name
	var parts = name.split('!'),
		suf, i = 1, pair;
	while ((suf = parts[i++])) { // double-parens to avoid jslint griping
		pair = suf.split('=', 2);
		parts[pair[0]] = pair.length == 2 ? pair[1] : true;
	}
	return parts;
}

function createLink (doc, optHref) {
		var link = doc[createElement]('link');
	link.rel = "stylesheet";
	link.type = "text/css";
	if (optHref) {
		link.href = optHref;
	}
	return link;
}

// Chrome 8 hax0rs!
// This is an ugly hack needed by Chrome 8+ which no longer waits for rules
// to be applied to the document before exposing them to javascript.
// Unfortunately, this routine will never fire for XD stylsheets since
// Chrome will also throw an exception if attempting to access the rules
// of an XD stylesheet.  Therefore, there's no way to detect the load
// event of XD stylesheets until Google fixes this, preferably with a
// functional load event!  As a work-around, use ready() before rendering
// widgets / components that need the css to be ready.
var testEl;
function styleIsApplied () {
	if (!testEl) {
			testEl = doc[createElement]('div');
		testEl.id = '_cssx_load_test';
		testEl.style.cssText = 'position:absolute;top:-999px;left:-999px;';
		doc.body.appendChild(testEl);
	}
	return doc.defaultView.getComputedStyle(testEl, null).marginTop == '-5px';
}

function isLinkReady (link) {
    // This routine is a bit fragile: browser vendors seem oblivious to
	// the need to know precisely when stylesheets load.  Therefore, we need
	// to continually test beta browsers until they all support the LINK load
	// event like IE and Opera.
    var sheet, rules, ready = false;
    try {
        // webkit's and IE's sheet is null until the sheet is loaded
        sheet = link.sheet || link.styleSheet;
        // mozilla's sheet throws an exception if trying to access xd rules
        rules = sheet.cssRules || sheet.rules;
        // webkit's xd sheet returns rules == null
        // opera's sheet always returns rules, but length is zero until loaded
        // friggin IE doesn't count @import rules as rules, but IE should
	    // never hit this routine anyways.
        ready = rules ?
            rules.length > 0 : // || (sheet.imports && sheet.imports.length > 0) :
            rules !== undef;
	    // thanks, Chrome 8, for this lovely hack
	    if (ready && navigator.userAgent.indexOf('Chrome') >= 0) {
		    sheet.insertRule('#_cssx_load_test{margin-top:-5px;}', 0);
		    ready = styleIsApplied();
		    sheet.deleteRule(0);
	    }
    }
    catch (ex) {
        // 1000 means FF loaded an xd stylesheet
        // other browsers just throw a security error here (IE uses the phrase 'Access is denied')
        ready = (ex.code == 1000) || (ex.message.match(/security|denied/i));
    }
    return ready;
}

function ssWatcher (params, cb) {
    // watches a stylesheet for loading signs.
    if (isLinkReady(params.link)) {
		cleanup(params);
        cb();
    }
    else if (!failed) {
        setTimeout(function () { ssWatcher(params, cb); }, params.wait);
    }
}

function loadDetector (params, cb) {
	// It would be nice to use onload everywhere, but the onload handler
	// only works in IE and Opera.
	// Detecting it cross-browser is completely impossible, too, since
	// THE BROWSERS ARE LIARS! DON'T TELL ME YOU HAVE AN ONLOAD PROPERTY
	// IF IT DOESN'T DO ANYTHING!
	var loaded;
	function cbOnce () {
		if (!loaded) {
			loaded = true;
			cb();
		}
	}
	loadHandler(params, cbOnce);
		if (!has("event-link-onload")) ssWatcher(params, cbOnce);
}

function cleanup (params) {
	var link = params.link;
		link[onreadystatechange] = link[onload] = null;
}

/***** finally! the actual plugin *****/

var plugin = {

		//prefix: 'css',

			'load': function (resourceDef, require, callback, config) {
				var resources = resourceDef.split(","),
					loadingCount = resources.length;

			// all detector functions must ensure that this function only gets
			// called once per stylesheet!
			function loaded () {
				// load/error handler may have executed before stylesheet is
				// fully parsed / processed in Opera, so use setTimeout.
				// Opera will process before the it next enters the event loop
				// (so 0 msec is enough time).
					if(--loadingCount == 0){
						// TODO: move this setTimeout to loadHandler
				setTimeout(function () { callback(link); }, 0);
			}
				}

				// after will become truthy once the loop executes a second time
				for(var i = 0, after; i < resources.length; i++, after = url){
					resourceDef = resources[i];
					var
						// TODO: this is a bit weird: find a better way to extract name?
						opts = parseSuffixes(resourceDef),
						name = opts.shift(),
						url = require['toUrl'](nameWithExt(name, 'css')),
						link = createLink(doc),
						nowait = 'nowait' in opts ? opts['nowait'] != 'false' : !!config['cssDeferLoad'],
						params = {
							link: link,
							url: url,
							wait: config['cssWatchPeriod'] || 50
						};

			if (nowait) {
				callback(link);
			}
			else {
				// hook up load detector(s)
				loadDetector(params, loaded);
			}

			// go!
			link.href = url;

					head.insertBefore(link, after ? insertedSheets[after].nextSibling : head.firstChild);
					insertedSheets[url] = link;
				}
		},

		/* the following methods are public in case they're useful to other plugins */

			'nameWithExt': nameWithExt,

			'parseSuffixes': parseSuffixes,

			'createLink': createLink

	};

define(plugin);

})(document);
<commit_msg>just some added spacing<commit_after>/**
 * Copyright (c) 2010 unscriptable.com
 */

/*jslint browser:true, on:true, sub:true */

(function (doc) {
"use strict";

/*
 * RequireJS css! plugin
 * This plugin will load and wait for css files.  This could be handy when
 * loading css files as part of a layer or as a way to apply a run-time theme.
 * Most browsers do not support the load event handler of the link element.
 * Therefore, we have to use other means to detect when a css file loads.
 * (The HTML5 spec states that the LINK element should have a load event, but
 * not even Chrome 8 or FF4b7 have it, yet.
 * http://www.w3.org/TR/html5/semantics.html#the-link-element)
 *
 * This plugin tries to use the load event and a universal work-around when
 * it is invoked the first time.  If the load event works, it is used on
 * every successive load.  Therefore, browsers that support the load event will
 * just work (i.e. no need for hacks!).  FYI, Feature-detecting the load
 * event is tricky since most browsers have a non-functional onload property.
 *
 * The universal work-around watches a stylesheet until its rules are
 * available (not null or undefined).  There are nuances, of course, between
 * the various browsers.  The isLinkReady function accounts for these.
 *
 * Note: it appears that all browsers load @import'ed stylesheets before
 * fully processing the rest of the importing stylesheet. Therefore, we
 * don't need to find and wait for any @import rules explicitly.
 *
 * Note #2: for Opera compatibility, stylesheets must have at least one rule.
 * AFAIK, there's no way to tell the difference between an empty sheet and
 * one that isn't finished loading in Opera (XD or same-domain).
 *
 * Options:
 *      !nowait - does not wait for the stylesheet to be parsed, just loads it
 *
 * Global configuration options:
 *
 * cssDeferLoad: Boolean. You can also instruct this plugin to not wait
 * for css resources. They'll get loaded asap, but other code won't wait
 * for them. This is just like using the !nowait option on every css file.
 *
 * cssWatchPeriod: if direct load-detection techniques fail, this option
 * determines the msec to wait between brute-force checks for rules. The
 * default is 50 msec.
 *
 * You may specify an alternate file extension:
 *      require('css!myproj/component.less') // --> myproj/component.less
 *      require('css!myproj/component.scss') // --> myproj/component.scss
 *
 * When using alternative file extensions, be sure to serve the files from
 * the server with the correct mime type (text/css) or some browsers won't
 * parse them, causing an error in the plugin.
 *
 * usage:
 *      require(['css!myproj/comp']); // load and wait for myproj/comp.css
 *      define(['css!some/folder/file'], {}); // wait for some/folder/file.css
 *      require(['css!myWidget!nowait']);
 *
 * Tested in:
 *      Firefox 1.5, 2.0, 3.0, 3.5, 3.6, and 4.0b6
 *      Safari 3.0.4, 3.2.1, 5.0
 *      Chrome 7 (8+ is partly b0rked)
 *      Opera 9.52, 10.63, and Opera 11.00
 *      IE 6, 7, and 8
 *      Netscape 7.2 (WTF? SRSLY!)
 * Does not work in Safari 2.x :(
 * In Chrome 8+, there's no way to wait for cross-domain (XD) stylesheets.
 * See comments in the code below.
 * TODO: figure out how to be forward-compatible when browsers support HTML5's
 *  load handler without breaking IE and Opera
*/

var
	// compressibility shortcuts
		onreadystatechange = 'onreadystatechange',
		onload = 'onload',
		createElement = 'createElement',
	// failed is true if RequireJS threw an exception
	failed = false,
		undef,
		insertedSheets = {},
		features = {
// true if the onload event handler works
			// "event-link-onload" : false
		},
		// find the head element and set it to it's standard property if nec.
		head = doc.head || (doc.head = doc.getElementsByTagName('head')[0]);

	function has (feature) {
		return features[feature];
	}

	// failure detection
	// we need to watch for onError when using RequireJS so we can shut off
	// our setTimeouts when it encounters an error.
	if (require['onError']) {
		require['onError'] = (function (orig) {
		return function () {
			failed = true;
			orig.apply(this, arguments);
		}
		})(require['onError']);
}

/***** load-detection functions *****/

function loadHandler (params, cb) {
	// We're using 'readystatechange' because IE and Opera happily support both
	var link = params.link;
		link[onreadystatechange] = link[onload] = function () {
		if (!link.readyState || link.readyState == 'complete') {
				features["event-link-onload"] = true;
			cleanup(params);
			cb();
		}
	};
}

function nameWithExt (name, defaultExt) {
	return name.lastIndexOf('.') <= name.lastIndexOf('/') ?
		name + '.' + defaultExt : name;
}

function parseSuffixes (name) {
	// creates a dual-structure: both an array and a hashmap
	// suffixes[0] is the actual name
	var parts = name.split('!'),
		suf, i = 1, pair;
	while ((suf = parts[i++])) { // double-parens to avoid jslint griping
		pair = suf.split('=', 2);
		parts[pair[0]] = pair.length == 2 ? pair[1] : true;
	}
	return parts;
}

function createLink (doc, optHref) {
		var link = doc[createElement]('link');
	link.rel = "stylesheet";
	link.type = "text/css";
	if (optHref) {
		link.href = optHref;
	}
	return link;
}

// Chrome 8 hax0rs!
// This is an ugly hack needed by Chrome 8+ which no longer waits for rules
// to be applied to the document before exposing them to javascript.
// Unfortunately, this routine will never fire for XD stylsheets since
// Chrome will also throw an exception if attempting to access the rules
// of an XD stylesheet.  Therefore, there's no way to detect the load
// event of XD stylesheets until Google fixes this, preferably with a
// functional load event!  As a work-around, use ready() before rendering
// widgets / components that need the css to be ready.
var testEl;
function styleIsApplied () {
	if (!testEl) {
			testEl = doc[createElement]('div');
		testEl.id = '_cssx_load_test';
		testEl.style.cssText = 'position:absolute;top:-999px;left:-999px;';
		doc.body.appendChild(testEl);
	}
	return doc.defaultView.getComputedStyle(testEl, null).marginTop == '-5px';
}

function isLinkReady (link) {
    // This routine is a bit fragile: browser vendors seem oblivious to
	// the need to know precisely when stylesheets load.  Therefore, we need
	// to continually test beta browsers until they all support the LINK load
	// event like IE and Opera.
    var sheet, rules, ready = false;
    try {
        // webkit's and IE's sheet is null until the sheet is loaded
        sheet = link.sheet || link.styleSheet;
        // mozilla's sheet throws an exception if trying to access xd rules
        rules = sheet.cssRules || sheet.rules;
        // webkit's xd sheet returns rules == null
        // opera's sheet always returns rules, but length is zero until loaded
        // friggin IE doesn't count @import rules as rules, but IE should
	    // never hit this routine anyways.
        ready = rules ?
            rules.length > 0 : // || (sheet.imports && sheet.imports.length > 0) :
            rules !== undef;
	    // thanks, Chrome 8, for this lovely hack
	    if (ready && navigator.userAgent.indexOf('Chrome') >= 0) {
		    sheet.insertRule('#_cssx_load_test{margin-top:-5px;}', 0);
		    ready = styleIsApplied();
		    sheet.deleteRule(0);
	    }
    }
    catch (ex) {
        // 1000 means FF loaded an xd stylesheet
        // other browsers just throw a security error here (IE uses the phrase 'Access is denied')
        ready = (ex.code == 1000) || (ex.message.match(/security|denied/i));
    }
    return ready;
}

function ssWatcher (params, cb) {
    // watches a stylesheet for loading signs.
    if (isLinkReady(params.link)) {
		cleanup(params);
        cb();
    }
    else if (!failed) {
        setTimeout(function () { ssWatcher(params, cb); }, params.wait);
    }
}

function loadDetector (params, cb) {
	// It would be nice to use onload everywhere, but the onload handler
	// only works in IE and Opera.
	// Detecting it cross-browser is completely impossible, too, since
	// THE BROWSERS ARE LIARS! DON'T TELL ME YOU HAVE AN ONLOAD PROPERTY
	// IF IT DOESN'T DO ANYTHING!
	var loaded;
	function cbOnce () {
		if (!loaded) {
			loaded = true;
			cb();
		}
	}
	loadHandler(params, cbOnce);
		if (!has("event-link-onload")) ssWatcher(params, cbOnce);
}

function cleanup (params) {
	var link = params.link;
		link[onreadystatechange] = link[onload] = null;
}

/***** finally! the actual plugin *****/

var plugin = {

		//prefix: 'css',

		'load': function (resourceDef, require, callback, config) {
				var resources = resourceDef.split(","),
					loadingCount = resources.length;

			// all detector functions must ensure that this function only gets
			// called once per stylesheet!
			function loaded () {
				// load/error handler may have executed before stylesheet is
				// fully parsed / processed in Opera, so use setTimeout.
				// Opera will process before the it next enters the event loop
				// (so 0 msec is enough time).
				if(--loadingCount == 0){
					// TODO: move this setTimeout to loadHandler
					setTimeout(function () { callback(link); }, 0);
				}
			}

			// after will become truthy once the loop executes a second time
			for (var i = 0, after; i < resources.length; i++, after = url) {

				resourceDef = resources[i];

				var
					// TODO: this is a bit weird: find a better way to extract name?
					opts = parseSuffixes(resourceDef),
					name = opts.shift(),
					url = require['toUrl'](nameWithExt(name, 'css')),
					link = createLink(doc),
					nowait = 'nowait' in opts ? opts['nowait'] != 'false' : !!config['cssDeferLoad'],
					params = {
						link: link,
						url: url,
						wait: config['cssWatchPeriod'] || 50
					};

				if (nowait) {
					callback(link);
				}
				else {
					// hook up load detector(s)
					loadDetector(params, loaded);
				}

				// go!
				link.href = url;

				head.insertBefore(link, after ? insertedSheets[after].nextSibling : head.firstChild);
				insertedSheets[url] = link;
			}

		},

		/* the following methods are public in case they're useful to other plugins */

			'nameWithExt': nameWithExt,

			'parseSuffixes': parseSuffixes,

			'createLink': createLink

	};

define(plugin);

})(document);
<|endoftext|>


<commit_before>(function(window, undefined) {

	"use strict";

	var d3 = window.d3;

	var Dummy = function() {};
	var variadicNew = function(Ctor, args) {
		var inst;
		Dummy.prototype = Ctor.prototype;
		inst = new Dummy();
		Ctor.apply(inst, args);
		return inst;
	};

	// extend
	// Borrowed from Underscore.js
	function extend(object) {
		var argsIndex, argsLength, iteratee, key;
		if (!object) {
			return object;
		}
		argsLength = arguments.length;
		for (argsIndex = 1; argsIndex < argsLength; argsIndex++) {
			iteratee = arguments[argsIndex];
			if (iteratee) {
				for (key in iteratee) {
					object[key] = iteratee[key];
				}
			}
		}
		return object;
	}

	var Chart = function(opts) {

		var mixin, mixinNs;

		if (opts == null) {
			opts = {};
		}

		if (!this.base) {
			if (!opts.base) {
				opts.base = d3.select("body").append("svg").append("g");
			}
			this.base = opts.base;
		}

		this.layers = {};
		this._mixins = {};

		this.initialize.apply(this, arguments);
	};

	Chart.prototype.initialize = function() {};

	Chart.prototype.transform = function(data) {
		return data;
	};

	Chart.prototype.mixin = function(nameSpace, chartName) {
		var args = Array.prototype.slice.call(arguments, 2);
		var ctor = Chart[chartName];

		this._mixins[nameSpace] = variadicNew(ctor, args);
		// Extend root-level of instance for easier access (i.e.
		// `this[namepsace]` instead of `this._mixins[namepsace]`)
		this[nameSpace] = this._mixins[nameSpace];
	};

	Chart.prototype.draw = function(data) {

		var layerName, mixinName;

		data = this.transform(data);

		for (layerName in this.layers) {
			this.layers[layerName].draw(data);
		}

		for (mixinName in this._mixins) {
			this._mixins[mixinName].draw(data);
		}
	};

	d3.chart = function(name, protoProps, staticProps) {
		var parent = Chart;
		var child;

		// The constructor function for the new subclass is either defined by you
		// (the "constructor" property in your `extend` definition), or defaulted
		// by us to simply call the parent's constructor.
		if (protoProps && Object.hasOwnProperty.call(protoProps, 'constructor')) {
			child = protoProps.constructor;
		} else {
			child = function(){ return parent.apply(this, arguments); };
		}

		// Add static properties to the constructor function, if supplied.
		extend(child, parent, staticProps);

		// Set the prototype chain to inherit from `parent`, without calling
		// `parent`'s constructor function.
		var Surrogate = function(){ this.constructor = child; };
		Surrogate.prototype = parent.prototype;
		child.prototype = new Surrogate;

		// Add prototype properties (instance properties) to the subclass, if
		// supplied.
		if (protoProps) extend(child.prototype, protoProps);

		// Set a convenience property in case the parent's prototype is needed
		// later.
		child.__super__ = parent.prototype;

		Chart[name] = child;
		return child;
	};

	d3.selection.prototype.chart = function(chartName) {
		var chartArgs = Array.prototype.slice.call(arguments, 1);
		var ChartCtor = Chart[chartName];
		return variadicNew(ChartCtor, chartArgs);
	};

}(this));
<commit_msg>Further simplify mixin declaration<commit_after>(function(window, undefined) {

	"use strict";

	var d3 = window.d3;

	var Dummy = function() {};
	var variadicNew = function(Ctor, args) {
		var inst;
		Dummy.prototype = Ctor.prototype;
		inst = new Dummy();
		Ctor.apply(inst, args);
		return inst;
	};

	// extend
	// Borrowed from Underscore.js
	function extend(object) {
		var argsIndex, argsLength, iteratee, key;
		if (!object) {
			return object;
		}
		argsLength = arguments.length;
		for (argsIndex = 1; argsIndex < argsLength; argsIndex++) {
			iteratee = arguments[argsIndex];
			if (iteratee) {
				for (key in iteratee) {
					object[key] = iteratee[key];
				}
			}
		}
		return object;
	}

	var Chart = function(opts) {

		var mixin, mixinNs;

		if (opts == null) {
			opts = {};
		}

		if (!this.base) {
			if (!opts.base) {
				opts.base = d3.select("body").append("svg").append("g");
			}
			this.base = opts.base;
		}

		this.layers = {};
		this._mixins = [];

		this.initialize.apply(this, arguments);
	};

	Chart.prototype.initialize = function() {};

	Chart.prototype.transform = function(data) {
		return data;
	};

	Chart.prototype.mixin = function(chartName) {
		var args = Array.prototype.slice.call(arguments, 1);
		var ctor = Chart[chartName];
		var chart = variadicNew(ctor, args);

		this._mixins.push(chart);
		return chart;
	};

	Chart.prototype.draw = function(data) {

		var layerName, mixinName;

		data = this.transform(data);

		for (layerName in this.layers) {
			this.layers[layerName].draw(data);
		}

		for (mixinName in this._mixins) {
			this._mixins[mixinName].draw(data);
		}
	};

	d3.chart = function(name, protoProps, staticProps) {
		var parent = Chart;
		var child;

		// The constructor function for the new subclass is either defined by you
		// (the "constructor" property in your `extend` definition), or defaulted
		// by us to simply call the parent's constructor.
		if (protoProps && Object.hasOwnProperty.call(protoProps, 'constructor')) {
			child = protoProps.constructor;
		} else {
			child = function(){ return parent.apply(this, arguments); };
		}

		// Add static properties to the constructor function, if supplied.
		extend(child, parent, staticProps);

		// Set the prototype chain to inherit from `parent`, without calling
		// `parent`'s constructor function.
		var Surrogate = function(){ this.constructor = child; };
		Surrogate.prototype = parent.prototype;
		child.prototype = new Surrogate;

		// Add prototype properties (instance properties) to the subclass, if
		// supplied.
		if (protoProps) extend(child.prototype, protoProps);

		// Set a convenience property in case the parent's prototype is needed
		// later.
		child.__super__ = parent.prototype;

		Chart[name] = child;
		return child;
	};

	d3.selection.prototype.chart = function(chartName) {
		var chartArgs = Array.prototype.slice.call(arguments, 1);
		var ChartCtor = Chart[chartName];
		return variadicNew(ChartCtor, chartArgs);
	};

}(this));
<|endoftext|>


<commit_before>(function(window, undefined) {

	"use strict";

	var d3 = window.d3;

	var Dummy = function() {};
	var variadicNew = function(Ctor, args) {
		var inst;
		Dummy.prototype = Ctor.prototype;
		inst = new Dummy();
		Ctor.apply(inst, args);
		return inst;
	};

	// extend
	// Borrowed from Underscore.js
	function extend(object) {
		var argsIndex, argsLength, iteratee, key;
		if (!object) {
			return object;
		}
		argsLength = arguments.length;
		for (argsIndex = 1; argsIndex < argsLength; argsIndex++) {
			iteratee = arguments[argsIndex];
			if (iteratee) {
				for (key in iteratee) {
					object[key] = iteratee[key];
				}
			}
		}
		return object;
	}

	var Chart = function(opts) {

		var mixin, mixinNs;

		if (opts == null) {
			opts = {};
		}

		if (!this.base) {
			if (!opts.base) {
				opts.base = d3.select("body").append("svg").append("g");
			}
			this.base = opts.base;
		}

		this.layers = {};
		this._mixins = {};

		this.initialize.apply(this, arguments);
	};

	Chart.prototype.initialize = function() {};

	Chart.prototype.transform = function(data) {
		return data;
	};

	Chart.prototype.mixin = function() {
		var mixins, mixinName;

		// Support signature #mixin( <string> name [, arguments...] )
		if (typeof arguments[0] === "string") {
			mixins = {};
			mixins[arguments[0]] = Array.prototype.slice.call(arguments, 1);
		// Support signature #mixin( <object> mixins )
		} else {
			mixins = arguments[0];
		}

		for (mixinName in mixins) {
			if (Object.hasOwnProperty.call(mixins, mixinName)) {
				this._mixins[mixinName] = variadicNew(Chart[mixinName], mixins[mixinName]);
				// Extend root-level of instance for easier access (i.e.
				// `this[mixinName]` instead of `this._mixins[mixinName]`)
				this[mixinName] = this._mixins[mixinName];
			}
		}
	};

	Chart.prototype.draw = function(data) {

		var layerName, mixinName;

		data = this.transform(data);

		for (layerName in this.layers) {
			this.layers[layerName].draw(data);
		}

		for (mixinName in this._mixins) {
			this._mixins[mixinName].draw(data);
		}
	};

	d3.chart = function(name, protoProps, staticProps) {
		var parent = Chart;
		var child;

		// The constructor function for the new subclass is either defined by you
		// (the "constructor" property in your `extend` definition), or defaulted
		// by us to simply call the parent's constructor.
		if (protoProps && Object.hasOwnProperty.call(protoProps, 'constructor')) {
			child = protoProps.constructor;
		} else {
			child = function(){ return parent.apply(this, arguments); };
		}

		// Add static properties to the constructor function, if supplied.
		extend(child, parent, staticProps);

		// Set the prototype chain to inherit from `parent`, without calling
		// `parent`'s constructor function.
		var Surrogate = function(){ this.constructor = child; };
		Surrogate.prototype = parent.prototype;
		child.prototype = new Surrogate;

		// Add prototype properties (instance properties) to the subclass, if
		// supplied.
		if (protoProps) extend(child.prototype, protoProps);

		// Set a convenience property in case the parent's prototype is needed
		// later.
		child.__super__ = parent.prototype;

		Chart[name] = child;
		return child;
	};

	d3.selection.prototype.chart = function(chartName) {
		var chartArgs = Array.prototype.slice.call(arguments, 1);
		var ChartCtor = Chart[chartName];
		console.log(chartName, ChartCtor);
		return variadicNew(ChartCtor, chartArgs);
	};

}(this));
<commit_msg>Implement sane API for mixins<commit_after>(function(window, undefined) {

	"use strict";

	var d3 = window.d3;

	var Dummy = function() {};
	var variadicNew = function(Ctor, args) {
		var inst;
		Dummy.prototype = Ctor.prototype;
		inst = new Dummy();
		Ctor.apply(inst, args);
		return inst;
	};

	// extend
	// Borrowed from Underscore.js
	function extend(object) {
		var argsIndex, argsLength, iteratee, key;
		if (!object) {
			return object;
		}
		argsLength = arguments.length;
		for (argsIndex = 1; argsIndex < argsLength; argsIndex++) {
			iteratee = arguments[argsIndex];
			if (iteratee) {
				for (key in iteratee) {
					object[key] = iteratee[key];
				}
			}
		}
		return object;
	}

	var Chart = function(opts) {

		var mixin, mixinNs;

		if (opts == null) {
			opts = {};
		}

		if (!this.base) {
			if (!opts.base) {
				opts.base = d3.select("body").append("svg").append("g");
			}
			this.base = opts.base;
		}

		this.layers = {};
		this._mixins = {};

		this.initialize.apply(this, arguments);
	};

	Chart.prototype.initialize = function() {};

	Chart.prototype.transform = function(data) {
		return data;
	};

	Chart.prototype.mixin = function(nameSpace, chartName) {
		var args = Array.prototype.slice.call(arguments, 2);
		var ctor = Chart[chartName];

		this._mixins[nameSpace] = variadicNew(ctor, args);
		// Extend root-level of instance for easier access (i.e.
		// `this[namepsace]` instead of `this._mixins[namepsace]`)
		this[nameSpace] = this._mixins[nameSpace];
	};

	Chart.prototype.draw = function(data) {

		var layerName, mixinName;

		data = this.transform(data);

		for (layerName in this.layers) {
			this.layers[layerName].draw(data);
		}

		for (mixinName in this._mixins) {
			this._mixins[mixinName].draw(data);
		}
	};

	d3.chart = function(name, protoProps, staticProps) {
		var parent = Chart;
		var child;

		// The constructor function for the new subclass is either defined by you
		// (the "constructor" property in your `extend` definition), or defaulted
		// by us to simply call the parent's constructor.
		if (protoProps && Object.hasOwnProperty.call(protoProps, 'constructor')) {
			child = protoProps.constructor;
		} else {
			child = function(){ return parent.apply(this, arguments); };
		}

		// Add static properties to the constructor function, if supplied.
		extend(child, parent, staticProps);

		// Set the prototype chain to inherit from `parent`, without calling
		// `parent`'s constructor function.
		var Surrogate = function(){ this.constructor = child; };
		Surrogate.prototype = parent.prototype;
		child.prototype = new Surrogate;

		// Add prototype properties (instance properties) to the subclass, if
		// supplied.
		if (protoProps) extend(child.prototype, protoProps);

		// Set a convenience property in case the parent's prototype is needed
		// later.
		child.__super__ = parent.prototype;

		Chart[name] = child;
		return child;
	};

	d3.selection.prototype.chart = function(chartName) {
		var chartArgs = Array.prototype.slice.call(arguments, 1);
		var ChartCtor = Chart[chartName];
		return variadicNew(ChartCtor, chartArgs);
	};

}(this));
<|endoftext|>


<commit_before>(function() {

    window.lang = {

        default_language: 'en',
        language: 'en',
        language_set: false,

        strings: {
            en: {
                'score': 'Score',
                'grader_msg': 'Your score is ',
                'wrong_answer_msg': 'You have at least one mistake.',
                'wrong_answer_msg_partial_feedback': 'You have at least one mistake. Here is a hint:',
                'wrong_answer_msg_not_answered': 'You didn\'t answer this question',
                'wrong_fill_gaps_msg': 'You have %% incorrect answers for this question, highlighted in red.',
                'validate': 'Submit',
                'solution': 'Show answer',
                'restart': 'Restart',
                'restart_scratch': 'Restart from scratch',
                'restart_current': 'Restart from current answer',
                'return_to_top': 'Return to the list of questions',
                'placeholder_text': 'Enter text',
                'placeholder_number': 'Enter number',
                'error_number': 'Must be a number',
                'placeholder_string': 'Enter string',
                'error_string': 'Must be a string',
                'placeholder_regexp': 'Enter text',
                'error_regexp': 'Invalid format',
                'error_grading': 'There was an error while submitting this answer, please try again in a few minutes.',
                'feedback_score_binary_correct': 'Congratulations, everything is correct',
                'feedback_score_binary_mistake': 'There is at least one mistake'
            },
            fr: {
                'score': 'Score',
                'grader_msg': 'Votre score est ',
                'wrong_answer_msg': 'Vous avez au moins une erreur.',
                'wrong_answer_msg_partial_feedback': 'Vous avez au moins une erreur. Voici un indice :',
                'wrong_answer_msg_not_answered': 'Vous n\'avez pas répondu à cette question',
                'wrong_fill_gaps_msg': 'You have %% incorrect answers for this question, highlighted in red.',
                'validate': 'Valider',
                'solution': 'Voir la réponse',
                'restart': 'Recommencer',
                'restart_scratch': 'Recommencer au début',
                'restart_current': 'Modifier ma réponse',
                'return_to_top': 'Retour à la liste des questions',
                'cancel' : 'Annuler',
                'placeholder_text': 'Entrez du texte',
                'placeholder_number': 'Entrez un nombre',
                'error_number': 'Vous devez entrer un nombre.',
                'placeholder_string': 'Entrez une chaîne de caractères',
                'error_string': 'Vous devez entrer une chaïne de caractères',
                'placeholder_regexp': 'Entrez du texte.',
                'error_regexp': 'Format invalide',
                'error_grading': 'Erreur lors de la soumission, veuillez réessayer dans quelques minutes.',
                'feedback_score_binary_correct': 'Congratulations, everything is correct',
                'feedback_score_binary_mistake': 'There is at least one mistake'
            },
        },

        set: function(lng) {
            if(!lng) {
                lng = window.stringsLanguage;
            }
            this.language = lng;
            this.language_set = true;
        },

        translate: function() {
            if(!this.language_set) {
                this.set();
            }
            var str = '', key = arguments[0];
            if(this.strings[this.language] && this.strings[this.language][key]) {
                str = this.strings[this.language][key];
            } else {
                str = this.strings[this.default_language][key] || key;
            }
            return str.replace('%%', arguments[1]);
        }
    }

    var task_toolbar = {

        buttons: {},
        holder: false,
        popup: false,

        addButton: function(parent, name, callback) {
            var btn = $('<button class="btn btn-success">' + lang.translate(name) + '</button>');
            btn.on('click', callback);
            parent.append(btn);
            this.buttons[name] = btn;
        },

        restartTask: function(from_scratch) {
            this.setValidated(false);
            this.popup.hide();
            this.unfreezeTask();
            window.quiz_ui.toggleFeedback(false);
            task.showViews({"task": true, "solution": false}, function(){});
            window.quiz_ui.reset(from_scratch);
        },

        showPopup: function() {
            if(!this.popup) {
                this.popup = $(
                    '
\

\

\

'
                );
                $(document.body).append(this.popup);
                var el = this.popup.find('.content');
                var self = this;
                this.addButton(el, 'restart_scratch', function() {
                    self.restartTask(true);
                });
                this.addButton(el, 'restart_current', function() {
                    self.restartTask();
                });
                this.addButton(el, 'cancel', function() {
                    self.popup.hide();
                });
            }
            this.popup.show();
        },

        freezeTask: function() {
            if(!this.freezer) {
                this.freezer = $('

')
                $('.taskContent').append(this.freezer);
            }
            this.freezer.show();
        },

        unfreezeTask: function() {
            this.freezer && this.freezer.hide();
        },

        setValidated: function(validated) {
            if(validated) {
                this.buttons.validate.hide();
                this.buttons.solution && this.buttons.solution.show();
            } else {
                this.buttons.validate.show();
                this.buttons.solution && this.buttons.solution.hide();
            }
        },

        displayError: function(error) {
            if(!this.errorHolder) {
                this.errorHolder = $('

');
                this.holder.append('
');
                this.holder.append(this.errorHolder);
            }
            this.errorHolder.html(' ' + error);
            this.errorHolder.toggle(!!error);
        },

        init: function() {
            if(this.holder) return;
            $('#showSolutionButton').remove();
            this.holder = $('

');
            var self = this;
            this.addButton(this.holder, 'validate', function() {
                platform.validate('done');
                self.freezeTask();
                self.setValidated(true);
            });
            var hasSolution = false;
            $('solution, .solution, #solution').each(function() {
               if($(this).text().trim() != '') { hasSolution = true; }
            });
            if(hasSolution) {
                this.addButton(this.holder, 'solution', function() {
                    miniPlatformShowSolution();
                });
                this.buttons.solution.hide();
            }
            if(!quiz_settings.hide_restart) {
                this.addButton(this.holder, 'restart', function() {
                    self.showPopup();
                });
            }
            if(quiz_settings.display_return_to_top) {
                this.holder.append('

');
                this.addButton(this.holder, 'return_to_top', function() {
                    platform.validate('top');
                });
            }
            this.holder.insertAfter($('.taskContent'));
        }

    }

    var task_token = {

        token: null,

        init: function() {
            var query = document.location.search.replace(/(^\?)/,'').split("&").map(function(n){return n = n.split("="),this[n[0]] = n[1],this}.bind({}))[0];
            this.token = query.sToken || '';
        },

        get: function() {
            return this.token
        },

        update: function(token) {
            this.token = token
        },

        getAnswerToken: function(answer) {
            return null;
        }
    }

    window.task = {}

    task.getViews = function(success, error) {
        var views = {
            task: {}
        };
        success(views);
    };

    task.updateToken = function(token, success, error) {
        task_token.update(token)
        success();
    };

    task.getHeight = function(success, error) {
        var d = document;
        var h = Math.max(d.body.offsetHeight, d.documentElement.offsetHeight);
        success(h);
    };

    task.getMetaData = function(success, error) {
        if (typeof json !== 'undefined') {
            success(json);
        } else {
            success({nbHints: 0});
        }
    };

    task.reloadState = function(state, success, error) { success() }
    task.getState = function(success, error) { success("{}")  }
    task.reloadStateObject = function(obj) { }
    task.getStateObject = function() { return {} }
    task.getDefaultStateObject = function() { return {} }

    $('.grader').hide();

    // grade

    function useGraderData(answer, versions, score_settings, callback) {
        if(window.Quiz.grader.handler && window.Quiz.grader.data) {
            var res = window.Quiz.grader.handler(window.Quiz.grader.data, answer, versions, score_settings);
            return callback(res);
        }
        console.error('Local Quiz grader not found');
        if(errorcb) { errorcb(); }
    }

    function useGraderUrl(url, task_token, answer, versions, score_settings, callback, errorcb) {
        var data = {
            action: 'grade2', //
            task: task_token,
            answer: answer,
            versions: versions,
            score_settings: score_settings
        }
        $.ajax({
            type: 'POST',
            url: url,
            data: JSON.stringify(data),
            crossDomain: true,
            contentType: 'application/json'
        }).done(function(res) {
            if(res.success) {
                return callback(res.data);
            }
            console.error('Grader response error: ', res);
            if(errorcb) { errorcb(); }
        }).fail(function(jqxhr, settings, exception ) {
            console.error('Grader url not responding: ' + url);
            if(errorcb) { errorcb(); }
        });
    }

    task.load = function(views, success) {
        task_token.init()

        platform.getTaskParams(null, null, function(taskParams) {
            var params = Object.assign(quiz_settings, {
                random: parseInt(taskParams.randomSeed, 10) || Math.floor(Math.random() * 100), //0
                parent: $('#task')
            })
            var q = Quiz.UI(params);
            window.quiz_ui = q;

            task.showViews = function(views, callback) {
                q.toggleSolutions(!!views.solution);
                if(views.solution) {
                    task.getAnswer(function(answer) {
                        task.gradeAnswer(answer, null, callback);
                        });
                } else {
                    callback();
                }
            }

            task.getDefaultAnswerObject = function() {
                return {
                    data: [],
                    versions: {}
                }
            }

            task.getAnswer = function(callback) {
                var answer = this.getAnswerObject();
                answer = JSON.stringify(answer);
                //console.log('task.getAnswer', answer)
                callback(answer);
            };

            task.getAnswerObject = function() {
                var answerObj = {
                    data: q.getAnswer(),
                    versions: Quiz.versions.get()
                }
                //console.log('task.getAnswerObject', answerObj)
                return answerObj;
            };

            task.reloadAnswer = function(answer, callback) {
                try {
                    //console.log('task.reloadAnswer', answer)
                    answer = JSON.parse(answer);
                    this.reloadAnswerObject(answer);
                } catch(e) {
                    console.error('Quiz: answer parsing error.')
                }
                callback();
            };

            task.reloadAnswerObject = function(answerObj) {
                var new_format = answerObj !== null && typeof answerObj === 'object' && 'data' in answerObj;
                q.setAnswer(new_format ? answerObj.data : answerObj);
            }

            function displayScore(score, max_score) {
                if(Quiz.params.feedback_score == 'binary') {
                    var msg = '';
                    if(score == max_score) {
                        msg += lang.translate('feedback_score_binary_correct');
                    } else {
                        msg += lang.translate('feedback_score_binary_mistake');
                    }
                    msg += '';
                } else if(Quiz.params.feedback_score == 'exact') {
                    var msg =
                        '' + lang.translate('score') + '' +
                        '' + score + '' +
                        '/' + max_score + '';
                } else {
                    return;
                }
                if($('#score').length == 0) {
                    var div = '

';
                    $('.taskContent').first().append(div);
                }
                $('#score').html(msg);
            }

            function displayMessages(messages) {
                if($('#grader-messages').length == 0) {
                    var div = '

';
                    $('.taskContent').first().append(div);
                }
                $('#grader-messages').html(messages.join('
'));
            }

            task.gradeAnswer = function(answer, answer_token, callback) {
                answer = JSON.parse(answer);
                var new_format = answer !== null && typeof answer === 'object' && 'data' in answer;
                function onGrade(result) {
                    q.displayFeedback(result.feedback);
                    displayScore(result.score, taskParams.maxScore);
                    //displayMessages(result.messages);
                    callback(result.score, lang.translate('grader_msg') + result.score, null);
                }
                function onError(result) {
                    task_toolbar.displayError(lang.translate('error_grading'));
                }
                var scoreSettings = {
                    maxScore: taskParams.maxScore,
                    minScore: taskParams.minScore,
                    noScore: taskParams.noScore
                };
                var token = task_token.get()
                if(token) {
                    useGraderUrl(
                        quiz_settings.graderUrl,
                        token,
                        new_format ? answer.data : answer,
                        new_format ? answer.versions : Quiz.versions.get(),
                        scoreSettings,
                        onGrade,
                        onError
                    );
                } else {
                    useGraderData(
                        new_format ? answer.data : answer,
                        new_format ? answer.versions : Quiz.versions.get(),
                        scoreSettings,
                        onGrade,
                        onError
                    );
                }
            };

            success();
        });
    };

    var grader = {
        gradeTask: task.gradeAnswer
    };

    $(function() {
        if(!window.quiz_settings) { window.quiz_settings = {}; }
        if(window.platform) {
            platform.initWithTask(task);
            task_toolbar.init();
        }
    })

    window.taskGetResourcesPost = function(res, callback) {
        // Add grader_data, if available, to the javascript
        try {
            $.get('grader_data.js').success(function(data) {
                res.task.push({type: 'javascript', id: 'grader_data', content: data});
                callback(res);
            }).error(function() {
                callback(res);
            });
        } catch(e) {
            callback(res);
        }
    };

// dev code for quiz2 testing, remove it later
/*
    $(document).ready(function() {

        var answer = '[[1],[1,2],["test"],["ipsum","amet"]]';
        task.reloadAnswer(answer, function() {
            task.gradeAnswer(answer, '', function(res) {
                //alert(res)
            })
        })

        var btn = $('<button class="btn btn-success">test</button>')
        btn.click(function() {
            task.getAnswer(function(res) {
                alert(res)
            })
        })
        $(document.body).prepend(btn);
    })
*/

})();
<commit_msg>quiz2: Display grading for reloaded answer<commit_after>(function() {

    window.lang = {

        default_language: 'en',
        language: 'en',
        language_set: false,

        strings: {
            en: {
                'score': 'Score',
                'grader_msg': 'Your score is ',
                'wrong_answer_msg': 'You have at least one mistake.',
                'wrong_answer_msg_partial_feedback': 'You have at least one mistake. Here is a hint:',
                'wrong_answer_msg_not_answered': 'You didn\'t answer this question',
                'wrong_fill_gaps_msg': 'You have %% incorrect answers for this question, highlighted in red.',
                'validate': 'Submit',
                'solution': 'Show answer',
                'restart': 'Restart',
                'restart_scratch': 'Restart from scratch',
                'restart_current': 'Restart from current answer',
                'return_to_top': 'Return to the list of questions',
                'placeholder_text': 'Enter text',
                'placeholder_number': 'Enter number',
                'error_number': 'Must be a number',
                'placeholder_string': 'Enter string',
                'error_string': 'Must be a string',
                'placeholder_regexp': 'Enter text',
                'error_regexp': 'Invalid format',
                'error_grading': 'There was an error while submitting this answer, please try again in a few minutes.',
                'feedback_score_binary_correct': 'Congratulations, everything is correct',
                'feedback_score_binary_mistake': 'There is at least one mistake'
            },
            fr: {
                'score': 'Score',
                'grader_msg': 'Votre score est ',
                'wrong_answer_msg': 'Vous avez au moins une erreur.',
                'wrong_answer_msg_partial_feedback': 'Vous avez au moins une erreur. Voici un indice :',
                'wrong_answer_msg_not_answered': 'Vous n\'avez pas répondu à cette question',
                'wrong_fill_gaps_msg': 'You have %% incorrect answers for this question, highlighted in red.',
                'validate': 'Valider',
                'solution': 'Voir la réponse',
                'restart': 'Recommencer',
                'restart_scratch': 'Recommencer au début',
                'restart_current': 'Modifier ma réponse',
                'return_to_top': 'Retour à la liste des questions',
                'cancel' : 'Annuler',
                'placeholder_text': 'Entrez du texte',
                'placeholder_number': 'Entrez un nombre',
                'error_number': 'Vous devez entrer un nombre.',
                'placeholder_string': 'Entrez une chaîne de caractères',
                'error_string': 'Vous devez entrer une chaïne de caractères',
                'placeholder_regexp': 'Entrez du texte.',
                'error_regexp': 'Format invalide',
                'error_grading': 'Erreur lors de la soumission, veuillez réessayer dans quelques minutes.',
                'feedback_score_binary_correct': 'Congratulations, everything is correct',
                'feedback_score_binary_mistake': 'There is at least one mistake'
            },
        },

        set: function(lng) {
            if(!lng) {
                lng = window.stringsLanguage;
            }
            this.language = lng;
            this.language_set = true;
        },

        translate: function() {
            if(!this.language_set) {
                this.set();
            }
            var str = '', key = arguments[0];
            if(this.strings[this.language] && this.strings[this.language][key]) {
                str = this.strings[this.language][key];
            } else {
                str = this.strings[this.default_language][key] || key;
            }
            return str.replace('%%', arguments[1]);
        }
    }

    var task_toolbar = {

        buttons: {},
        holder: false,
        popup: false,

        addButton: function(parent, name, callback) {
            var btn = $('<button class="btn btn-success">' + lang.translate(name) + '</button>');
            btn.on('click', callback);
            parent.append(btn);
            this.buttons[name] = btn;
        },

        restartTask: function(from_scratch) {
            this.setValidated(false);
            this.popup.hide();
            this.unfreezeTask();
            window.quiz_ui.toggleFeedback(false);
            task.showViews({"task": true, "solution": false}, function(){});
            window.quiz_ui.reset(from_scratch);
        },

        showPopup: function() {
            if(!this.popup) {
                this.popup = $(
                    '
\

\

\

'
                );
                $(document.body).append(this.popup);
                var el = this.popup.find('.content');
                var self = this;
                this.addButton(el, 'restart_scratch', function() {
                    self.restartTask(true);
                });
                this.addButton(el, 'restart_current', function() {
                    self.restartTask();
                });
                this.addButton(el, 'cancel', function() {
                    self.popup.hide();
                });
            }
            this.popup.show();
        },

        freezeTask: function() {
            if(!this.freezer) {
                this.freezer = $('

')
                $('.taskContent').append(this.freezer);
            }
            this.freezer.show();
        },

        unfreezeTask: function() {
            this.freezer && this.freezer.hide();
        },

        setValidated: function(validated) {
            if(validated) {
                this.buttons.validate.hide();
                this.buttons.solution && this.buttons.solution.show();
            } else {
                this.buttons.validate.show();
                this.buttons.solution && this.buttons.solution.hide();
            }
        },

        displayError: function(error) {
            if(!this.errorHolder) {
                this.errorHolder = $('

');
                this.holder.append('
');
                this.holder.append(this.errorHolder);
            }
            this.errorHolder.html(' ' + error);
            this.errorHolder.toggle(!!error);
        },

        init: function() {
            if(this.holder) return;
            $('#showSolutionButton').remove();
            this.holder = $('

');
            var self = this;
            this.addButton(this.holder, 'validate', function() {
                platform.validate('done');
                self.freezeTask();
                self.setValidated(true);
            });
            var hasSolution = false;
            $('solution, .solution, #solution').each(function() {
               if($(this).text().trim() != '') { hasSolution = true; }
            });
            if(hasSolution) {
                this.addButton(this.holder, 'solution', function() {
                    miniPlatformShowSolution();
                });
                this.buttons.solution.hide();
            }
            if(!quiz_settings.hide_restart) {
                this.addButton(this.holder, 'restart', function() {
                    self.showPopup();
                });
            }
            if(quiz_settings.display_return_to_top) {
                this.holder.append('

');
                this.addButton(this.holder, 'return_to_top', function() {
                    platform.validate('top');
                });
            }
            this.holder.insertAfter($('.taskContent'));
        }

    }

    var task_token = {

        token: null,

        init: function() {
            var query = document.location.search.replace(/(^\?)/,'').split("&").map(function(n){return n = n.split("="),this[n[0]] = n[1],this}.bind({}))[0];
            this.token = query.sToken || '';
        },

        get: function() {
            return this.token
        },

        update: function(token) {
            this.token = token
        },

        getAnswerToken: function(answer) {
            return null;
        }
    }

    window.task = {}

    task.getViews = function(success, error) {
        var views = {
            task: {}
        };
        success(views);
    };

    task.updateToken = function(token, success, error) {
        task_token.update(token)
        success();
    };

    task.getHeight = function(success, error) {
        var d = document;
        var h = Math.max(d.body.offsetHeight, d.documentElement.offsetHeight);
        success(h);
    };

    task.getMetaData = function(success, error) {
        if (typeof json !== 'undefined') {
            success(json);
        } else {
            success({nbHints: 0});
        }
    };

    task.reloadState = function(state, success, error) { success() }
    task.getState = function(success, error) { success("{}")  }
    task.reloadStateObject = function(obj) { }
    task.getStateObject = function() { return {} }
    task.getDefaultStateObject = function() { return {} }

    $('.grader').hide();

    // grade

    function useGraderData(answer, versions, score_settings, callback) {
        if(window.Quiz.grader.handler && window.Quiz.grader.data) {
            var res = window.Quiz.grader.handler(window.Quiz.grader.data, answer, versions, score_settings);
            return callback(res);
        }
        console.error('Local Quiz grader not found');
        if(errorcb) { errorcb(); }
    }

    function useGraderUrl(url, task_token, answer, versions, score_settings, callback, errorcb) {
        var data = {
            action: 'grade2', //
            task: task_token,
            answer: answer,
            versions: versions,
            score_settings: score_settings
        }
        $.ajax({
            type: 'POST',
            url: url,
            data: JSON.stringify(data),
            crossDomain: true,
            contentType: 'application/json'
        }).done(function(res) {
            if(res.success) {
                return callback(res.data);
            }
            console.error('Grader response error: ', res);
            if(errorcb) { errorcb(); }
        }).fail(function(jqxhr, settings, exception ) {
            console.error('Grader url not responding: ' + url);
            if(errorcb) { errorcb(); }
        });
    }

    task.load = function(views, success) {
        var lastViews = views;
        task_token.init()

        platform.getTaskParams(null, null, function(taskParams) {
            var params = Object.assign(quiz_settings, {
                random: parseInt(taskParams.randomSeed, 10) || Math.floor(Math.random() * 100), //0
                parent: $('#task')
            })
            var q = Quiz.UI(params);
            window.quiz_ui = q;

            task.showViews = function(views, callback) {
                lastViews = views;
                q.toggleSolutions(!!views.solution);
                callback();
            }

            task.getDefaultAnswerObject = function() {
                return {
                    data: [],
                    versions: {}
                }
            }

            task.getAnswer = function(callback) {
                var answer = this.getAnswerObject();
                answer = JSON.stringify(answer);
                //console.log('task.getAnswer', answer)
                callback(answer);
            };

            task.getAnswerObject = function() {
                var answerObj = {
                    data: q.getAnswer(),
                    versions: Quiz.versions.get()
                }
                //console.log('task.getAnswerObject', answerObj)
                return answerObj;
            };

            task.reloadAnswer = function(answer, callback) {
                try {
                    //console.log('task.reloadAnswer', answer)
                    var answerObject = JSON.parse(answer);
                    this.reloadAnswerObject(answerObject);
                    if(lastViews.solution) {
                        task.gradeAnswer(answer, null, function () {});
                    }
                } catch(e) {
                    console.error('Quiz: answer parsing error.')
                }
                callback();
            };

            task.reloadAnswerObject = function(answerObj) {
                var new_format = answerObj !== null && typeof answerObj === 'object' && 'data' in answerObj;
                q.setAnswer(new_format ? answerObj.data : answerObj);
            }

            function displayScore(score, max_score) {
                if(Quiz.params.feedback_score == 'binary') {
                    var msg = '';
                    if(score == max_score) {
                        msg += lang.translate('feedback_score_binary_correct');
                    } else {
                        msg += lang.translate('feedback_score_binary_mistake');
                    }
                    msg += '';
                } else if(Quiz.params.feedback_score == 'exact') {
                    var msg =
                        '' + lang.translate('score') + '' +
                        '' + score + '' +
                        '/' + max_score + '';
                } else {
                    return;
                }
                if($('#score').length == 0) {
                    var div = '

';
                    $('.taskContent').first().append(div);
                }
                $('#score').html(msg);
            }

            function displayMessages(messages) {
                if($('#grader-messages').length == 0) {
                    var div = '

';
                    $('.taskContent').first().append(div);
                }
                $('#grader-messages').html(messages.join('
'));
            }

            task.gradeAnswer = function(answer, answer_token, callback) {
                answer = JSON.parse(answer);
                var new_format = answer !== null && typeof answer === 'object' && 'data' in answer;
                function onGrade(result) {
                    q.displayFeedback(result.feedback);
                    displayScore(result.score, taskParams.maxScore);
                    //displayMessages(result.messages);
                    callback(result.score, lang.translate('grader_msg') + result.score, null);
                }
                function onError(result) {
                    task_toolbar.displayError(lang.translate('error_grading'));
                }
                var scoreSettings = {
                    maxScore: taskParams.maxScore,
                    minScore: taskParams.minScore,
                    noScore: taskParams.noScore
                };
                var token = task_token.get()
                if(token) {
                    useGraderUrl(
                        quiz_settings.graderUrl,
                        token,
                        new_format ? answer.data : answer,
                        new_format ? answer.versions : Quiz.versions.get(),
                        scoreSettings,
                        onGrade,
                        onError
                    );
                } else {
                    useGraderData(
                        new_format ? answer.data : answer,
                        new_format ? answer.versions : Quiz.versions.get(),
                        scoreSettings,
                        onGrade,
                        onError
                    );
                }
            };

            success();
        });
    };

    var grader = {
        gradeTask: task.gradeAnswer
    };

    $(function() {
        if(!window.quiz_settings) { window.quiz_settings = {}; }
        if(window.platform) {
            platform.initWithTask(task);
            task_toolbar.init();
        }
    })

    window.taskGetResourcesPost = function(res, callback) {
        // Add grader_data, if available, to the javascript
        try {
            $.get('grader_data.js').success(function(data) {
                res.task.push({type: 'javascript', id: 'grader_data', content: data});
                callback(res);
            }).error(function() {
                callback(res);
            });
        } catch(e) {
            callback(res);
        }
    };

// dev code for quiz2 testing, remove it later
/*
    $(document).ready(function() {

        var answer = '[[1],[1,2],["test"],["ipsum","amet"]]';
        task.reloadAnswer(answer, function() {
            task.gradeAnswer(answer, '', function(res) {
                //alert(res)
            })
        })

        var btn = $('<button class="btn btn-success">test</button>')
        btn.click(function() {
            task.getAnswer(function(res) {
                alert(res)
            })
        })
        $(document.body).prepend(btn);
    })
*/

})();
<|endoftext|>


<commit_before>(function() {

    window.lang = {

        default_language: 'en',
        language: 'en',

        strings: {
            en: {
                'score': 'Score',
                'grader_msg': 'Your score is ',
                'wrong_answer_msg': 'You have at least one mistake.',
                'wrong_answer_msg_partial_feedback': 'You have at least one mistake. Here is a hint:',
                'wrong_answer_msg_not_answered': 'You didn\'t answer this question',
                'wrong_fill_gaps_msg': 'You have %% incorrect answers for this question, highlighted in red.',
                'validate': 'Submit',
                'solution': 'Show answer',
                'restart': 'Restart',
                'restart_scratch': 'Restart from scratch',
                'restart_current': 'Restart from current answer',
                'placeholder_text': 'Enter text',
                'placeholder_number': 'Enter number',
                'error_number': 'Must be a number',
                'placeholder_string': 'Enter string',
                'error_string': 'Must be a string',
                'placeholder_regexp': 'Enter text',
                'error_regexp': 'Invalid format',
                'error_grading': 'There was an error while submitting this answer, please try again in a few minutes.',
                'feedback_score_binary_correct': 'Congratulations, everything is correct',
                'feedback_score_binary_mistake': 'There is at least one mistake'
            },
            fr: {
                'score': 'Score',
                'grader_msg': 'Votre score est ',
                'wrong_answer_msg': 'Vous avez au moins une erreur.',
                'wrong_answer_msg_partial_feedback': 'Vous avez au moins une erreur. Voici un indice :',
                'wrong_answer_msg_not_answered': 'Vous n\'avez pas répondu à cette question',
                'wrong_fill_gaps_msg': 'You have %% incorrect answers for this question, highlighted in red.',
                'validate': 'Valider',
                'solution': 'Voir la réponse',
                'restart': 'Recommencer',
                'restart_scratch': 'Recommencer au début',
                'restart_current': 'Modifier ma réponse',
                'cancel' : 'Annuler',
                'placeholder_text': 'Entrez du texte',
                'placeholder_number': 'Entrez un nombre',
                'error_number': 'Vous devez entrer un nombre.',
                'placeholder_string': 'Entrez une chaîne de caractères',
                'error_string': 'Vous devez entrer une chaïne de caractères',
                'placeholder_regexp': 'Entrez du texte.',
                'error_regexp': 'Format invalide',
                'error_grading': 'Erreur lors de la soumission, veuillez réessayer dans quelques minutes.',
                'feedback_score_binary_correct': 'Congratulations, everything is correct',
                'feedback_score_binary_mistake': 'There is at least one mistake'
            },
        },

        set: function(lng) {
            this.language = lng;
        },

        translate: function() {
            var str = '', key = arguments[0];
            if(this.strings[this.language] && this.strings[this.language][key]) {
                str = this.strings[this.language][key];
            } else {
                str = this.strings[this.default_language][key] || key;
            }
            return str.replace('%%', arguments[1]);
        }
    }

    var task_toolbar = {

        buttons: {},
        holder: false,
        popup: false,

        addButton: function(parent, name, callback) {
            var btn = $('<button class="btn btn-success">' + lang.translate(name) + '</button>');
            btn.on('click', callback);
            parent.append(btn);
            this.buttons[name] = btn;
        },

        restartTask: function(from_scratch) {
            this.setValidated(false);
            this.popup.hide();
            this.unfreezeTask();
            window.quiz_ui.toggleFeedback(false);
            task.showViews({"task": true, "solution": false}, function(){});
            window.quiz_ui.reset(from_scratch);
        },

        showPopup: function() {
            if(!this.popup) {
                this.popup = $(
                    '
\

\

\

'
                );
                $(document.body).append(this.popup);
                var el = this.popup.find('.content');
                var self = this;
                this.addButton(el, 'restart_scratch', function() {
                    self.restartTask(true);
                });
                this.addButton(el, 'restart_current', function() {
                    self.restartTask();
                });
                this.addButton(el, 'cancel', function() {
                    self.popup.hide();
                });
            }
            this.popup.show();
        },

        freezeTask: function() {
            if(!this.freezer) {
                this.freezer = $('

')
                $('.taskContent').append(this.freezer);
            }
            this.freezer.show();
        },

        unfreezeTask: function() {
            this.freezer && this.freezer.hide();
        },

        setValidated: function(validated) {
            if(validated) {
                this.buttons.validate.hide();
                this.buttons.solution && this.buttons.solution.show();
            } else {
                this.buttons.validate.show();
                this.buttons.solution && this.buttons.solution.hide();
            }
        },

        displayError: function(error) {
            if(!this.errorHolder) {
                this.errorHolder = $('

');
                this.holder.append('
');
                this.holder.append(this.errorHolder);
            }
            this.errorHolder.html(' ' + error);
            this.errorHolder.toggle(!!error);
        },

        init: function() {
            if(this.holder) return;
            $('#showSolutionButton').remove();
            this.holder = $('

');
            var self = this;
            this.addButton(this.holder, 'validate', function() {
                platform.validate('done');
                self.freezeTask();
                self.setValidated(true);
            });
            var hasSolution = false;
            $('solution, .solution, #solution').each(function() {
               if($(this).text().trim() != '') { hasSolution = true; }
            });
            if(hasSolution) {
                this.addButton(this.holder, 'solution', function() {
                    miniPlatformShowSolution();
                });
                this.buttons.solution.hide();
            }
            this.addButton(this.holder, 'restart', function() {
                self.showPopup();
            });
            this.holder.insertAfter($('.taskContent'));
        }

    }

    var task_token = {

        token: null,

        init: function() {
            var query = document.location.search.replace(/(^\?)/,'').split("&").map(function(n){return n = n.split("="),this[n[0]] = n[1],this}.bind({}))[0];
            this.token = query.sToken || '';
        },

        get: function() {
            return this.token
        },

        update: function(token) {
            this.token = token
        },

        getAnswerToken: function(answer) {
            return null;
        }
    }

    window.task = {}

    task.getViews = function(success, error) {
        var views = {
            task: {}
        };
        success(views);
    };

    task.updateToken = function(token, success, error) {
        task_token.update(token)
        success();
    };

    task.getHeight = function(success, error) {
        var d = document;
        var h = Math.max(d.body.offsetHeight, d.documentElement.offsetHeight);
        success(h);
    };

    task.getMetaData = function(success, error) {
        if (typeof json !== 'undefined') {
            success(json);
        } else {
            success({nbHints: 0});
        }
    };

    task.reloadState = function(state, success, error) { success() }
    task.getState = function(success, error) { success("{}")  }
    task.reloadStateObject = function(obj) { }
    task.getStateObject = function() { return {} }
    task.getDefaultStateObject = function() { return {} }

    $('.grader').hide();

    // grade

    function useGraderData(answer, versions, score_settings, callback) {
        if(window.Quiz.grader.handler && window.Quiz.grader.data) {
            var res = window.Quiz.grader.handler(window.Quiz.grader.data, answer, versions, score_settings);
            return callback(res);
        }
        console.error('Local Quiz grader not found');
        if(errorcb) { errorcb(); }
    }

    function useGraderUrl(url, task_token, answer, versions, score_settings, callback, errorcb) {
        var data = {
            action: 'grade2', //
            task: task_token,
            answer: answer,
            versions: versions,
            score_settings: score_settings
        }
        $.ajax({
            type: 'POST',
            url: url,
            data: JSON.stringify(data),
            crossDomain: true,
            contentType: 'application/json'
        }).done(function(res) {
            if(res.success) {
                return callback(res.data);
            }
            console.error('Grader response error: ', res);
            if(errorcb) { errorcb(); }
        }).fail(function(jqxhr, settings, exception ) {
            console.error('Grader url not responding: ' + url);
            if(errorcb) { errorcb(); }
        });
    }

    task.load = function(views, success) {
        task_token.init()

        platform.getTaskParams(null, null, function(taskParams) {
            var params = Object.assign(quiz_settings, {
                random: parseInt(taskParams.randomSeed, 10) || Math.floor(Math.random() * 100), //0
                parent: $('#task')
            })
            var q = Quiz.UI(params);
            window.quiz_ui = q;

            task.showViews = function(views, callback) {
                q.toggleSolutions(!!views.solution);
                callback()
            }

            task.getAnswer = function(callback) {
                callback(JSON.stringify(q.getAnswer()));
            };

            task.reloadAnswer = function(answer, callback) {
                try {
                    answer = JSON.parse(answer);
                    q.setAnswer(answer);
                } catch(e) {

                }
                callback();
            };

            function displayScore(score, max_score) {
                if(Quiz.params.feedback_score == 'binary') {
                    var msg = '';
                    if(score == max_score) {
                        msg += lang.translate('feedback_score_binary_correct');
                    } else {
                        msg += lang.translate('feedback_score_binary_mistake');
                    }
                    msg += '';
                } else if(Quiz.params.feedback_score == 'exact') {
                    var msg =
                        '' + lang.translate('score') + '' +
                        '' + score + '' +
                        '/' + max_score + '';
                } else {
                    return;
                }
                if($('#score').length == 0) {
                    var div = '

';
                    $('.taskContent').first().append(div);
                }
                $('#score').html(msg);
            }

            function displayMessages(messages) {
                if($('#grader-messages').length == 0) {
                    var div = '

';
                    $('.taskContent').first().append(div);
                }
                $('#grader-messages').html(messages.join('
'));
            }

            task.gradeAnswer = function(answer, answer_token, callback) {
                answer = JSON.parse(answer);
                function onGrade(result) {
                    q.displayFeedback(result.feedback);
                    displayScore(result.score, taskParams.maxScore);
                    //displayMessages(result.messages);
                    callback(result.score, lang.translate('grader_msg') + result.score, null);
                }
                function onError(result) {
                    task_toolbar.displayError(lang.translate('error_grading'));
                }
                var scoreSettings = {
                    maxScore: taskParams.maxScore,
                    minScore: taskParams.minScore,
                    noScore: taskParams.noScore
                };
                var token = task_token.get()
                if(token) {
                    useGraderUrl(
                        quiz_settings.graderUrl,
                        token,
                        answer,
                        Quiz.versions.get(),
                        scoreSettings,
                        onGrade,
                        onError
                    );
                } else {
                    useGraderData(
                        answer,
                        Quiz.versions.get(),
                        scoreSettings,
                        onGrade,
                        onError
                    );
                }
            };

            task.reloadAnswerObject = function(answerObj) {
                return q.setAnswer(answerObj);
            }

            task.getAnswerObject = function() {
                return q.getAnswer();
            }

            task.getDefaultAnswerObject = function() {
                return [];
            }

            success();
        });
    };

    var grader = {
        gradeTask: task.gradeAnswer
    };

    lang.set(window.stringsLanguage)

    $(function() {
        if(window.platform) {
            platform.initWithTask(task);
            task_toolbar.init();
        }
    })

// dev code for quiz2 testing, remove it later
/*
    $(document).ready(function() {

        var answer = '[[1],[1,2],["test"],["ipsum","amet"]]';
        task.reloadAnswer(answer, function() {
            task.gradeAnswer(answer, '', function(res) {
                //alert(res)
            })
        })

        var btn = $('<button class="btn btn-success">test</button>')
        btn.click(function() {
            task.getAnswer(function(res) {
                alert(res)
            })
        })
        $(document.body).prepend(btn);
    })
*/

})();
<commit_msg>quiz2: Read stringsLanguage after load<commit_after>(function() {

    window.lang = {

        default_language: 'en',
        language: 'en',
        language_set: false,

        strings: {
            en: {
                'score': 'Score',
                'grader_msg': 'Your score is ',
                'wrong_answer_msg': 'You have at least one mistake.',
                'wrong_answer_msg_partial_feedback': 'You have at least one mistake. Here is a hint:',
                'wrong_answer_msg_not_answered': 'You didn\'t answer this question',
                'wrong_fill_gaps_msg': 'You have %% incorrect answers for this question, highlighted in red.',
                'validate': 'Submit',
                'solution': 'Show answer',
                'restart': 'Restart',
                'restart_scratch': 'Restart from scratch',
                'restart_current': 'Restart from current answer',
                'placeholder_text': 'Enter text',
                'placeholder_number': 'Enter number',
                'error_number': 'Must be a number',
                'placeholder_string': 'Enter string',
                'error_string': 'Must be a string',
                'placeholder_regexp': 'Enter text',
                'error_regexp': 'Invalid format',
                'error_grading': 'There was an error while submitting this answer, please try again in a few minutes.',
                'feedback_score_binary_correct': 'Congratulations, everything is correct',
                'feedback_score_binary_mistake': 'There is at least one mistake'
            },
            fr: {
                'score': 'Score',
                'grader_msg': 'Votre score est ',
                'wrong_answer_msg': 'Vous avez au moins une erreur.',
                'wrong_answer_msg_partial_feedback': 'Vous avez au moins une erreur. Voici un indice :',
                'wrong_answer_msg_not_answered': 'Vous n\'avez pas répondu à cette question',
                'wrong_fill_gaps_msg': 'You have %% incorrect answers for this question, highlighted in red.',
                'validate': 'Valider',
                'solution': 'Voir la réponse',
                'restart': 'Recommencer',
                'restart_scratch': 'Recommencer au début',
                'restart_current': 'Modifier ma réponse',
                'cancel' : 'Annuler',
                'placeholder_text': 'Entrez du texte',
                'placeholder_number': 'Entrez un nombre',
                'error_number': 'Vous devez entrer un nombre.',
                'placeholder_string': 'Entrez une chaîne de caractères',
                'error_string': 'Vous devez entrer une chaïne de caractères',
                'placeholder_regexp': 'Entrez du texte.',
                'error_regexp': 'Format invalide',
                'error_grading': 'Erreur lors de la soumission, veuillez réessayer dans quelques minutes.',
                'feedback_score_binary_correct': 'Congratulations, everything is correct',
                'feedback_score_binary_mistake': 'There is at least one mistake'
            },
        },

        set: function(lng) {
            if(!lng) {
                lng = window.stringsLanguage;
            }
            this.language = lng;
            this.language_set = true;
        },

        translate: function() {
            if(!this.language_set) {
                this.set();
            }
            var str = '', key = arguments[0];
            if(this.strings[this.language] && this.strings[this.language][key]) {
                str = this.strings[this.language][key];
            } else {
                str = this.strings[this.default_language][key] || key;
            }
            return str.replace('%%', arguments[1]);
        }
    }

    var task_toolbar = {

        buttons: {},
        holder: false,
        popup: false,

        addButton: function(parent, name, callback) {
            var btn = $('<button class="btn btn-success">' + lang.translate(name) + '</button>');
            btn.on('click', callback);
            parent.append(btn);
            this.buttons[name] = btn;
        },

        restartTask: function(from_scratch) {
            this.setValidated(false);
            this.popup.hide();
            this.unfreezeTask();
            window.quiz_ui.toggleFeedback(false);
            task.showViews({"task": true, "solution": false}, function(){});
            window.quiz_ui.reset(from_scratch);
        },

        showPopup: function() {
            if(!this.popup) {
                this.popup = $(
                    '
\

\

\

'
                );
                $(document.body).append(this.popup);
                var el = this.popup.find('.content');
                var self = this;
                this.addButton(el, 'restart_scratch', function() {
                    self.restartTask(true);
                });
                this.addButton(el, 'restart_current', function() {
                    self.restartTask();
                });
                this.addButton(el, 'cancel', function() {
                    self.popup.hide();
                });
            }
            this.popup.show();
        },

        freezeTask: function() {
            if(!this.freezer) {
                this.freezer = $('

')
                $('.taskContent').append(this.freezer);
            }
            this.freezer.show();
        },

        unfreezeTask: function() {
            this.freezer && this.freezer.hide();
        },

        setValidated: function(validated) {
            if(validated) {
                this.buttons.validate.hide();
                this.buttons.solution && this.buttons.solution.show();
            } else {
                this.buttons.validate.show();
                this.buttons.solution && this.buttons.solution.hide();
            }
        },

        displayError: function(error) {
            if(!this.errorHolder) {
                this.errorHolder = $('

');
                this.holder.append('
');
                this.holder.append(this.errorHolder);
            }
            this.errorHolder.html(' ' + error);
            this.errorHolder.toggle(!!error);
        },

        init: function() {
            if(this.holder) return;
            $('#showSolutionButton').remove();
            this.holder = $('

');
            var self = this;
            this.addButton(this.holder, 'validate', function() {
                platform.validate('done');
                self.freezeTask();
                self.setValidated(true);
            });
            var hasSolution = false;
            $('solution, .solution, #solution').each(function() {
               if($(this).text().trim() != '') { hasSolution = true; }
            });
            if(hasSolution) {
                this.addButton(this.holder, 'solution', function() {
                    miniPlatformShowSolution();
                });
                this.buttons.solution.hide();
            }
            this.addButton(this.holder, 'restart', function() {
                self.showPopup();
            });
            this.holder.insertAfter($('.taskContent'));
        }

    }

    var task_token = {

        token: null,

        init: function() {
            var query = document.location.search.replace(/(^\?)/,'').split("&").map(function(n){return n = n.split("="),this[n[0]] = n[1],this}.bind({}))[0];
            this.token = query.sToken || '';
        },

        get: function() {
            return this.token
        },

        update: function(token) {
            this.token = token
        },

        getAnswerToken: function(answer) {
            return null;
        }
    }

    window.task = {}

    task.getViews = function(success, error) {
        var views = {
            task: {}
        };
        success(views);
    };

    task.updateToken = function(token, success, error) {
        task_token.update(token)
        success();
    };

    task.getHeight = function(success, error) {
        var d = document;
        var h = Math.max(d.body.offsetHeight, d.documentElement.offsetHeight);
        success(h);
    };

    task.getMetaData = function(success, error) {
        if (typeof json !== 'undefined') {
            success(json);
        } else {
            success({nbHints: 0});
        }
    };

    task.reloadState = function(state, success, error) { success() }
    task.getState = function(success, error) { success("{}")  }
    task.reloadStateObject = function(obj) { }
    task.getStateObject = function() { return {} }
    task.getDefaultStateObject = function() { return {} }

    $('.grader').hide();

    // grade

    function useGraderData(answer, versions, score_settings, callback) {
        if(window.Quiz.grader.handler && window.Quiz.grader.data) {
            var res = window.Quiz.grader.handler(window.Quiz.grader.data, answer, versions, score_settings);
            return callback(res);
        }
        console.error('Local Quiz grader not found');
        if(errorcb) { errorcb(); }
    }

    function useGraderUrl(url, task_token, answer, versions, score_settings, callback, errorcb) {
        var data = {
            action: 'grade2', //
            task: task_token,
            answer: answer,
            versions: versions,
            score_settings: score_settings
        }
        $.ajax({
            type: 'POST',
            url: url,
            data: JSON.stringify(data),
            crossDomain: true,
            contentType: 'application/json'
        }).done(function(res) {
            if(res.success) {
                return callback(res.data);
            }
            console.error('Grader response error: ', res);
            if(errorcb) { errorcb(); }
        }).fail(function(jqxhr, settings, exception ) {
            console.error('Grader url not responding: ' + url);
            if(errorcb) { errorcb(); }
        });
    }

    task.load = function(views, success) {
        task_token.init()

        platform.getTaskParams(null, null, function(taskParams) {
            var params = Object.assign(quiz_settings, {
                random: parseInt(taskParams.randomSeed, 10) || Math.floor(Math.random() * 100), //0
                parent: $('#task')
            })
            var q = Quiz.UI(params);
            window.quiz_ui = q;

            task.showViews = function(views, callback) {
                q.toggleSolutions(!!views.solution);
                callback()
            }

            task.getAnswer = function(callback) {
                callback(JSON.stringify(q.getAnswer()));
            };

            task.reloadAnswer = function(answer, callback) {
                try {
                    answer = JSON.parse(answer);
                    q.setAnswer(answer);
                } catch(e) {

                }
                callback();
            };

            function displayScore(score, max_score) {
                if(Quiz.params.feedback_score == 'binary') {
                    var msg = '';
                    if(score == max_score) {
                        msg += lang.translate('feedback_score_binary_correct');
                    } else {
                        msg += lang.translate('feedback_score_binary_mistake');
                    }
                    msg += '';
                } else if(Quiz.params.feedback_score == 'exact') {
                    var msg =
                        '' + lang.translate('score') + '' +
                        '' + score + '' +
                        '/' + max_score + '';
                } else {
                    return;
                }
                if($('#score').length == 0) {
                    var div = '

';
                    $('.taskContent').first().append(div);
                }
                $('#score').html(msg);
            }

            function displayMessages(messages) {
                if($('#grader-messages').length == 0) {
                    var div = '

';
                    $('.taskContent').first().append(div);
                }
                $('#grader-messages').html(messages.join('
'));
            }

            task.gradeAnswer = function(answer, answer_token, callback) {
                answer = JSON.parse(answer);
                function onGrade(result) {
                    q.displayFeedback(result.feedback);
                    displayScore(result.score, taskParams.maxScore);
                    //displayMessages(result.messages);
                    callback(result.score, lang.translate('grader_msg') + result.score, null);
                }
                function onError(result) {
                    task_toolbar.displayError(lang.translate('error_grading'));
                }
                var scoreSettings = {
                    maxScore: taskParams.maxScore,
                    minScore: taskParams.minScore,
                    noScore: taskParams.noScore
                };
                var token = task_token.get()
                if(token) {
                    useGraderUrl(
                        quiz_settings.graderUrl,
                        token,
                        answer,
                        Quiz.versions.get(),
                        scoreSettings,
                        onGrade,
                        onError
                    );
                } else {
                    useGraderData(
                        answer,
                        Quiz.versions.get(),
                        scoreSettings,
                        onGrade,
                        onError
                    );
                }
            };

            task.reloadAnswerObject = function(answerObj) {
                return q.setAnswer(answerObj);
            }

            task.getAnswerObject = function() {
                return q.getAnswer();
            }

            task.getDefaultAnswerObject = function() {
                return [];
            }

            success();
        });
    };

    var grader = {
        gradeTask: task.gradeAnswer
    };

    $(function() {
        if(window.platform) {
            platform.initWithTask(task);
            task_toolbar.init();
        }
    })

// dev code for quiz2 testing, remove it later
/*
    $(document).ready(function() {

        var answer = '[[1],[1,2],["test"],["ipsum","amet"]]';
        task.reloadAnswer(answer, function() {
            task.gradeAnswer(answer, '', function(res) {
                //alert(res)
            })
        })

        var btn = $('<button class="btn btn-success">test</button>')
        btn.click(function() {
            task.getAnswer(function(res) {
                alert(res)
            })
        })
        $(document.body).prepend(btn);
    })
*/

})();
<|endoftext|>