<commit_before>define([ "knockout", "jquery", "moment", "highstock", "filesaver"],
function(ko, $, moment) {
	var instance;
    function Singleton (data) {
    	var self = {};
    	var timeoutId = null;

    	self.moveToTopic = function(topicId, event){
    		if (!timeoutId) {
    		    timeoutId = window.setTimeout(function() {
    	            timeoutId = null;
    	            $(event.currentTarget).css('cursor', 'pointer').children('*').css('cursor', 'pointer');
    	            ko.postbox.publish('moveToTopic',topicId);
    		    }, 1500);
    		}
    	};

    	self.stopMoveToTopic = function(topicId, event){
    		if (timeoutId) {
    			$(event.currentTarget).css('cursor', 'progress').children('*').css('cursor', 'progress');
    		    window.clearTimeout(timeoutId);
    		    timeoutId = null;
    		}
    	};

    	self.windowHeight = ko.observable(Math.max(400, $(window).height(), /* For opera: */ document.documentElement.clientHeight)).subscribeTo("windowHeight");
    	self.windowHeight.subscribe(function(newValue) {
    		self.chart.setSize($('#desktop').width(),((newValue - 184) * 0.7) - ($('#topicCheckboxes').height() + 32.0 * $('#getcsv').size()), false);
    	});

		self.timeDataLoaded = false;
		self.loadTimeData = function() {
			$.getJSON("JsonServlet?Command=getDates").success(function(receivedParsedJson) {
				var avg = new Object();

				for (key in globalData.Topic) {
					$.extend(globalData.Topic[key], receivedParsedJson[key]);
					if($.inArray(parseInt(key), self.timeData.allTopics()) > -1) {
					    $.each(receivedParsedJson[key].TIME$WORDS_PER_WEEK, function(index, value) {
				    		if(avg[index] == null) avg[index] = 0;
				    		avg[index] += value.WORD_COUNT;
					    });
					}
				}
				var topicCount = self.timeData.allTopics().length;

				globalData.Topic.average = new Object();
				globalData.Topic.average.COLOR_TOPIC$COLOR = "#000000";
				globalData.Topic.average.TIME$WORDS_PER_WEEK = new Object();
				$.each(avg , function(weekStamp, value) {
					globalData.Topic.average.TIME$WORDS_PER_WEEK[weekStamp] = {'WORD_COUNT':Math.round(value / topicCount), 'LABEL': 'Average'};
				});
				self.makeChart();
				self.timeDataLoaded = true;
			});
		};

		self.timeData = {};

		self.downloadCSV = function() {
    		try {
    		    var isFileSaverSupported = !!new Blob;
    		    var out = "id;color";
        		for(tstamp in topicexplorerModel.data.topic[0].TIME$WORDS_PER_WEEK)
        			out += ";" + tstamp;
        		out += "\n";
        		for(topic in self.renderedTopics()) {
        			out += self.renderedTopics()[topic] + ";" + topicexplorerModel.data.topic[self.renderedTopics()[topic]].COLOR_TOPIC$COLOR;
        			if(self.renderedTopics()[topic] != "average") {
        				for(tstamp in topicexplorerModel.data.topic[self.renderedTopics()[topic]].TIME$WORDS_PER_WEEK) {
        					out += ";" + topicexplorerModel.data.topic[self.renderedTopics()[topic]].TIME$WORDS_PER_WEEK[tstamp].WORD_COUNT;
        				}
        				out += "\n";
        			}
        		}
        		var blob = new Blob([out], {type: "application/csv;charset=utf-8"});
        		saveAs(blob, "data.csv");
    		} catch (e) {
    			alert("Download is not supported by your browser - please update!");
    		}
    	};

    	self.loadDocumentsForTopicsAndWeek = function (week) {
    		ko.postbox.publish('openNewTab',{moduleName:"document-browse-tab", tabHeading: moment(week).format('L').substr(0, 6) + ' - ' + moment(week).add('d', 6).format('L') + " (" + self.timeData[self.active()].topicId() + ")", data: {topicId: self.timeData[self.active()].topicId(), week:  week.toString().substr(0,10), getParam: "bestDocs&TopicId="+ self.timeData[self.active()].topicId() + "&week=" + week.toString().substr(0,10)}});
    	};

    	self.makeChart = function() {
    		self.chart = new Highcharts.StockChart({
		   	    chart: {
		    	   renderTo: 'chart',
		    	   height: $('#desktop').height() + 10 - ($('#topicCheckboxes').height() + 32.0 * $('#getcsv').size())
		    	},
		    	tooltip: {
		            formatter: function() {
		            	var html ="";
		            	for(var i = 0; i < self.timeData[self.active()].renderedTopics().length; i++ ) {
				            html += '⬤ ';
				            if(self.timeData[self.active()].renderedTopics()[i] != 'average')
				            	html += self.timeData[self.active()].renderedTopics()[i] + ": ";
				            html +=  globalData.Topic[self.timeData[self.active()].renderedTopics()[i]].TIME$WORDS_PER_WEEK[this.x].LABEL
				            	+ ': ' + globalData.Topic[self.timeData[self.active()].renderedTopics()[i]].TIME$WORDS_PER_WEEK[this.x].WORD_COUNT
				            	+ '
';
		            	}
		            	html += 'click to open best documents for topic ' + self.timeData[self.active()].topicId() + ' from ' + moment(this.x).format('LL') + ' to ' + moment(this.x).add('d', 6).format('LL');
		            	return html;
		            }
		    	}
		    });
    		for(var i = 0; i < self.timeData[self.active()].renderedTopics().length; i++ ) {
	    		self.addChart(self.timeData[self.active()].renderedTopics()[i]);
	    	}
    	};

    	self.addChart = function(topicId) {
    		self.chart.addSeries({
    			id: 'topicChart' + topicId,
    			name: 'topicChart' + topicId,
    			data: (function() {
    				var data = new Array();
    				for(key in globalData.Topic[topicId].TIME$WORDS_PER_WEEK) {
    					data.push(new Array(parseInt(key), globalData.Topic[topicId].TIME$WORDS_PER_WEEK[key].WORD_COUNT));
    				}
    				return data;
    			})(),
		    	color: globalData.Topic[topicId].COLOR_TOPIC$COLOR,
		    	cursor: 'pointer',
		    	events: {
		    		click: function(event) {
		    			self.loadDocumentsForTopicsAndWeek(event.point.x);
		    		}
		    	}
    		});
    	};

    	self.changeCharts = function(newValue) {
   			var diff = $(self.timeData[self.active()].lastRenderedTopics).not(newValue).get();
   			for(var i = diff.length - 1; i >= 0; i--) {
   				self.chart.get('topicChart' + diff[i]).remove();
   			}
			var diff = $(newValue).not(self.timeData[self.active()].lastRenderedTopics).get();
			for(var i = diff.length - 1; i >= 0; i--) {
				self.addChart(diff[i]);
			}
       		self.timeData[self.active()].lastRenderedTopics = newValue.slice();;
    	};

    	self.changeTopics = function(newValue) {
    		renderedTopics = self.timeData[self.active()].renderedTopics.slice();
    		for(var i = renderedTopics.length - 1; i >= 0; i--) {
    			topicId = renderedTopics[i];
    			if(newValue.indexOf(topicId) < 0 && topicId != 'average') {
    				renderedTopics.splice(i, 1);
    			}
    		}
    		if(newValue.indexOf(self.timeData[self.active()].topicId()) > -1 && renderedTopics.indexOf(self.timeData[self.active()].topicId()) == -1) {
    			renderedTopics.push(self.timeData[self.active()].topicId());
    		}
    		self.timeData[self.active()].renderedTopics(renderedTopics);
    		self.oldTopics=self.newTopics.slice();
    		self.newTopics=newValue.slice();

    		var avg = new Object();

			for (key in newValue) {
				$.each(globalData.Topic[newValue[key]].TIME$WORDS_PER_WEEK, function(index, value) {
		    		if(avg[index] == null) avg[index] = 0;
		    		avg[index] += value.WORD_COUNT;

			    });
			}

			var topicCount = self.timeData.allTopics().length;
			var averageData = new Array();
			$.each(avg , function(weekStamp, value) {
				var averageAtWeek = Math.round(value / topicCount);
				globalData.Topic.average.TIME$WORDS_PER_WEEK[weekStamp] = {'WORD_COUNT': averageAtWeek, 'LABEL': 'Average'};
				averageData.push(averageAtWeek);

			});
			if($.inArray('average', self.timeData[self.active()].renderedTopics()) > -1) {
				self.chart.get('topicChartaverage').setData(averageData);
				console.log(averageData);
			}
    	};

    	self.oldTopics = globalData.TOPIC_SORTING;
    	self.newTopics = globalData.TOPIC_SORTING;

    	self.timeData.allTopics = ko.observableArray(globalData.TOPIC_SORTING).subscribeTo("selectedTopics");
		self.timeData.allTopics.subscribe(self.changeTopics);
		self.addTopic = function(elem, addedIndezes) {
			if($(self.oldTopics).not(self.newTopics).get().length < $(self.newTopics).not(self.oldTopics).get().length) {
				$(elem).filter('span').css('width', '5px').animate({width: '33px'}, 500);
			}
		};
		self.removeTopic = function(elem, deletedIndezes) {
			if($(self.oldTopics).not(self.newTopics).get().length > $(self.newTopics).not(self.oldTopics).get().length) {
				$(elem).filter('span').css('opacity', '0').animate({width: '5px'}, 500, function() {$(this).remove();});
			} else {
				$(elem).remove();
			}
		};

		self.setData = function (data) {
			self.active = ko.observable(data.topicId);
			if (!self.timeData[self.active()]) {
				self.timeData[self.active()] = {};

				self.timeData[self.active()].renderedTopics = ko.observableArray([data.topicId]);
				self.timeData[self.active()].lastRenderedTopics = [data.topicId];
				self.timeData[self.active()].renderedTopics.subscribe(self.changeCharts);
				self.timeData[self.active()].topicId = ko.observable(data.topicId);
			}
			if(!self.timeDataLoaded) {
				self.loadTimeData();
			} else {
				setTimeout(function() {
					self.makeChart();
				},0);
			}
    	};
    	self.getData = function () { return self.active(); };
		self.setData(data);
    	return self;
    }

    return function getSingleton(data) {
		if (instance) { // already exists
			instance.setData(data); // pass the data from view
			return instance; // that already exists

		} else { // instance is not existing
			return(instance = new Singleton(data)); // create a new one with data from view
		};
	};
});
<commit_msg>#190 fast fix for not hiding removed topics<commit_after>define([ "knockout", "jquery", "moment", "highstock", "filesaver"],
function(ko, $, moment) {
	var instance;
    function Singleton (data) {
    	var self = {};
    	var timeoutId = null;

    	self.moveToTopic = function(topicId, event){
    		if (!timeoutId) {
    		    timeoutId = window.setTimeout(function() {
    	            timeoutId = null;
    	            $(event.currentTarget).css('cursor', 'pointer').children('*').css('cursor', 'pointer');
    	            ko.postbox.publish('moveToTopic',topicId);
    		    }, 1500);
    		}
    	};

    	self.stopMoveToTopic = function(topicId, event){
    		if (timeoutId) {
    			$(event.currentTarget).css('cursor', 'progress').children('*').css('cursor', 'progress');
    		    window.clearTimeout(timeoutId);
    		    timeoutId = null;
    		}
    	};

    	self.windowHeight = ko.observable(Math.max(400, $(window).height(), /* For opera: */ document.documentElement.clientHeight)).subscribeTo("windowHeight");
    	self.windowHeight.subscribe(function(newValue) {
    		self.chart.setSize($('#desktop').width(),((newValue - 184) * 0.7) - ($('#topicCheckboxes').height() + 32.0 * $('#getcsv').size()), false);
    	});

		self.timeDataLoaded = false;
		self.loadTimeData = function() {
			$.getJSON("JsonServlet?Command=getDates").success(function(receivedParsedJson) {
				var avg = new Object();

				for (key in globalData.Topic) {
					$.extend(globalData.Topic[key], receivedParsedJson[key]);
					if($.inArray(parseInt(key), self.timeData.allTopics()) > -1) {
					    $.each(receivedParsedJson[key].TIME$WORDS_PER_WEEK, function(index, value) {
				    		if(avg[index] == null) avg[index] = 0;
				    		avg[index] += value.WORD_COUNT;
					    });
					}
				}
				var topicCount = self.timeData.allTopics().length;

				globalData.Topic.average = new Object();
				globalData.Topic.average.COLOR_TOPIC$COLOR = "#000000";
				globalData.Topic.average.TIME$WORDS_PER_WEEK = new Object();
				$.each(avg , function(weekStamp, value) {
					globalData.Topic.average.TIME$WORDS_PER_WEEK[weekStamp] = {'WORD_COUNT':Math.round(value / topicCount), 'LABEL': 'Average'};
				});
				self.makeChart();
				self.timeDataLoaded = true;
			});
		};

		self.timeData = {};

		self.downloadCSV = function() {
    		try {
    		    var isFileSaverSupported = !!new Blob;
    		    var out = "id;color";
        		for(tstamp in topicexplorerModel.data.topic[0].TIME$WORDS_PER_WEEK)
        			out += ";" + tstamp;
        		out += "\n";
        		for(topic in self.renderedTopics()) {
        			out += self.renderedTopics()[topic] + ";" + topicexplorerModel.data.topic[self.renderedTopics()[topic]].COLOR_TOPIC$COLOR;
        			if(self.renderedTopics()[topic] != "average") {
        				for(tstamp in topicexplorerModel.data.topic[self.renderedTopics()[topic]].TIME$WORDS_PER_WEEK) {
        					out += ";" + topicexplorerModel.data.topic[self.renderedTopics()[topic]].TIME$WORDS_PER_WEEK[tstamp].WORD_COUNT;
        				}
        				out += "\n";
        			}
        		}
        		var blob = new Blob([out], {type: "application/csv;charset=utf-8"});
        		saveAs(blob, "data.csv");
    		} catch (e) {
    			alert("Download is not supported by your browser - please update!");
    		}
    	};

    	self.loadDocumentsForTopicsAndWeek = function (week) {
    		ko.postbox.publish('openNewTab',{moduleName:"document-browse-tab", tabHeading: moment(week).format('L').substr(0, 6) + ' - ' + moment(week).add('d', 6).format('L') + " (" + self.timeData[self.active()].topicId() + ")", data: {topicId: self.timeData[self.active()].topicId(), week:  week.toString().substr(0,10), getParam: "bestDocs&TopicId="+ self.timeData[self.active()].topicId() + "&week=" + week.toString().substr(0,10)}});
    	};

    	self.makeChart = function() {
    		self.chart = new Highcharts.StockChart({
		   	    chart: {
		    	   renderTo: 'chart',
		    	   height: $('#desktop').height() + 10 - ($('#topicCheckboxes').height() + 32.0 * $('#getcsv').size())
		    	},
		    	tooltip: {
		            formatter: function() {
		            	var html ="";
		            	for(var i = 0; i < self.timeData[self.active()].renderedTopics().length; i++ ) {
				            html += '⬤ ';
				            if(self.timeData[self.active()].renderedTopics()[i] != 'average')
				            	html += self.timeData[self.active()].renderedTopics()[i] + ": ";
				            html +=  globalData.Topic[self.timeData[self.active()].renderedTopics()[i]].TIME$WORDS_PER_WEEK[this.x].LABEL
				            	+ ': ' + globalData.Topic[self.timeData[self.active()].renderedTopics()[i]].TIME$WORDS_PER_WEEK[this.x].WORD_COUNT
				            	+ '
';
		            	}
		            	html += 'click to open best documents for topic ' + self.timeData[self.active()].topicId() + ' from ' + moment(this.x).format('LL') + ' to ' + moment(this.x).add('d', 6).format('LL');
		            	return html;
		            }
		    	}
		    });
    		for(var i = 0; i < self.timeData[self.active()].renderedTopics().length; i++ ) {
	    		self.addChart(self.timeData[self.active()].renderedTopics()[i]);
	    	}
    	};

    	self.addChart = function(topicId) {
    		self.chart.addSeries({
    			id: 'topicChart' + topicId,
    			name: 'topicChart' + topicId,
    			data: (function() {
    				var data = new Array();
    				for(key in globalData.Topic[topicId].TIME$WORDS_PER_WEEK) {
    					data.push(new Array(parseInt(key), globalData.Topic[topicId].TIME$WORDS_PER_WEEK[key].WORD_COUNT));
    				}
    				return data;
    			})(),
		    	color: globalData.Topic[topicId].COLOR_TOPIC$COLOR,
		    	cursor: 'pointer',
		    	events: {
		    		click: function(event) {
		    			self.loadDocumentsForTopicsAndWeek(event.point.x);
		    		}
		    	}
    		});
    	};

    	self.changeCharts = function(newValue) {
   			var diff = $(self.timeData[self.active()].lastRenderedTopics).not(newValue).get();
   			for(var i = diff.length - 1; i >= 0; i--) {
   				self.chart.get('topicChart' + diff[i]).remove();
   			}
			var diff = $(newValue).not(self.timeData[self.active()].lastRenderedTopics).get();
			for(var i = diff.length - 1; i >= 0; i--) {
				self.addChart(diff[i]);
			}
       		self.timeData[self.active()].lastRenderedTopics = newValue.slice();;
    	};

    	self.changeTopics = function(newValue) {
    		renderedTopics = self.timeData[self.active()].renderedTopics.slice();
    		for(var i = renderedTopics.length - 1; i >= 0; i--) {
    			topicId = renderedTopics[i];
    			if(newValue.indexOf(topicId) < 0 && topicId != 'average') {
    				renderedTopics.splice(i, 1);
    			}
    		}
    		if(newValue.indexOf(self.timeData[self.active()].topicId()) > -1 && renderedTopics.indexOf(self.timeData[self.active()].topicId()) == -1) {
    			renderedTopics.push(self.timeData[self.active()].topicId());
    		}
    		self.timeData[self.active()].renderedTopics(renderedTopics);
    		self.oldTopics=self.newTopics.slice();
    		self.newTopics=newValue.slice();

    		var avg = new Object();

			for (key in newValue) {
				$.each(globalData.Topic[newValue[key]].TIME$WORDS_PER_WEEK, function(index, value) {
		    		if(avg[index] == null) avg[index] = 0;
		    		avg[index] += value.WORD_COUNT;

			    });
			}

			var topicCount = self.timeData.allTopics().length;
			var averageData = new Array();
			$.each(avg , function(weekStamp, value) {
				var averageAtWeek = Math.round(value / topicCount);
				globalData.Topic.average.TIME$WORDS_PER_WEEK[weekStamp] = {'WORD_COUNT': averageAtWeek, 'LABEL': 'Average'};
				averageData.push(averageAtWeek);

			});
			if($.inArray('average', self.timeData[self.active()].renderedTopics()) > -1) {
				self.chart.get('topicChartaverage').setData(averageData);
				console.log(averageData);
			}
    	};

    	self.oldTopics = globalData.TOPIC_SORTING;
    	self.newTopics = globalData.TOPIC_SORTING;

    	self.timeData.allTopics = ko.observableArray(globalData.TOPIC_SORTING).subscribeTo("selectedTopics");
		self.timeData.allTopics.subscribe(self.changeTopics);
		self.addTopic = function(elem, addedIndezes) {
			if($(self.oldTopics).not(self.newTopics).get().length < $(self.newTopics).not(self.oldTopics).get().length) {
				$(elem).filter('div').css('width', '5px').animate({width: '40px'}, 500);
			}
		};
		self.removeTopic = function(elem, deletedIndezes) {
			if($(self.oldTopics).not(self.newTopics).get().length > $(self.newTopics).not(self.oldTopics).get().length) {
				$(elem).filter('div').css('opacity', '0').animate({width: '5px'}, 500, function() {$(this).remove();});
			} else {
				$(elem).remove();
			}
		};

		self.setData = function (data) {
			self.active = ko.observable(data.topicId);
			if (!self.timeData[self.active()]) {
				self.timeData[self.active()] = {};

				self.timeData[self.active()].renderedTopics = ko.observableArray([data.topicId]);
				self.timeData[self.active()].lastRenderedTopics = [data.topicId];
				self.timeData[self.active()].renderedTopics.subscribe(self.changeCharts);
				self.timeData[self.active()].topicId = ko.observable(data.topicId);
			}
			if(!self.timeDataLoaded) {
				self.loadTimeData();
			} else {
				setTimeout(function() {
					self.makeChart();
				},0);
			}
    	};
    	self.getData = function () { return self.active(); };
		self.setData(data);
    	return self;
    }

    return function getSingleton(data) {
		if (instance) { // already exists
			instance.setData(data); // pass the data from view
			return instance; // that already exists

		} else { // instance is not existing
			return(instance = new Singleton(data)); // create a new one with data from view
		};
	};
});
<|endoftext|>


<commit_before>// SAMPLE DATA (Can be used for testing!)
// A sample pad
var pad = {
	name: 'Sample Pad',
	color: 'red',
	contents: [
	{
		program: "Firefox",
		files: ["http://www.illinois.edu", "http://www.cnn.com", "http://www.piazza.com"]
	},{
		program: "Adobe Reader",
		files: ["home/jmpierc2/cs233/lab7.pdf", "home/jmpierc2/cs233/lab8.pdf"]
	},{
		program: "Calculator",
		files: []
	},{
		program: "Sublime Text",
		files: ["home/jmpierc2/lilypad/frontend/frontend.js", "home/jmpierc2/lilypad/frontend/lilylibrary.js", "home/jmpierc2/lilypad/backend/backend.py", "home/jmpierc2/lilypad/backend/config.json"]
	}
	]
};
// A pad delta. Eessentially a "contents" array liek the one from above.
var paddelta = [
	{
		program: "Firefox",
		files: ["http://www.cnn.com", "http://www.twitter.com", "http://www.facebook.com"]
	},
	{
		program: "Kile",
		files: ["home/jmpierc2/cs473/hw4.tex", "home/jmpierc2/cs473/hw4.tex"]
	},
	{
		program: "Terminal",
		files: []
	},
	{
		program: "Adobe Reader",
		files: ["home/jmpierc2/cs233/lab9.pdf", "home/jmpierc2/cs233/lab7.pdf"]
	}
];
// END SAMPLE DATA

// generateShellScript
// input (Object): pad - A pad in the format of the sample one above
// output (String): A shell script in the format defined by the google doc
var generateShellScript = function(pad) {

};

// insertIntoPad
// Inserts every element of the delta into the pad, keeping track of duplicates
// input (Object): pad - What we are inserting to. A pad like the one above.
// input (Array): delta - Delta array, like the one above
// output (Array): Array of duplicates found in the same format as the delta
var insertIntoPad = function(pad, delta) {

};

// getDefaultPrograms
// Given an array of paths, create a delta object like the example one above.
// Match each path to the appropriate default program from config.json.
var getDefaultPrograms = function(paths) {

};

// getAlternativePrograms
// Depends on config.json!
// This function should have a way to detect URLs
// input (Array): A array of file/url paths
// output: A list of programs (array of display-name strings) that supports every filetype in the list
var getAlternativePrograms = function(paths) {

};

// switchToAlternative
// Switches the program associated with certain paths.
// Given a pad as input and a delta, removes members of the delta from the current pad,
// and then adds them again using insertIntoPad().
var switchToAlternative = function(pad, delta) {

};

// getConsiseProgramList
// Returns an array matching program display names to associated files counts
// input (Object): pad - A pad like the example one above
// output (Object array): Array of {name: "program display name", count: int}
var getConsiseProgramList = function(pad) {

};

// getStandalonePrograms
// Searches the config object, returns an array of all entries that are standalone programs
var getStandalonePrograms = function() {

};

// Given a program name string, returns the corresponding entry from the config.
var getProgramInfo(name, config){

};

//returns true if the String is a valid URL.
var validateURL(input){

};
<commit_msg>Modified some documentation in the hope of making it clearer in lilylibrary.js, added author names to the functions in lilylibrary.js.<commit_after>// SAMPLE DATA (Can be used for testing!)
// A sample pad
var pad = {
	name: 'Sample Pad',
	color: 'red',
	contents: [
	{
		program: "Firefox",
		files: ["http://www.illinois.edu", "http://www.cnn.com", "http://www.piazza.com"]
	},{
		program: "Adobe Reader",
		files: ["home/jmpierc2/cs233/lab7.pdf", "home/jmpierc2/cs233/lab8.pdf"]
	},{
		program: "Calculator",
		files: []
	},{
		program: "Sublime Text",
		files: ["home/jmpierc2/lilypad/frontend/frontend.js", "home/jmpierc2/lilypad/frontend/lilylibrary.js", "home/jmpierc2/lilypad/backend/backend.py", "home/jmpierc2/lilypad/backend/config.json"]
	}
	]
};
// A pad delta. Essentially a "contents" array like the one from above.
var paddelta = [
	{
		program: "Firefox",
		files: ["http://www.cnn.com", "http://www.twitter.com", "http://www.facebook.com"]
	},
	{
		program: "Kile",
		files: ["home/jmpierc2/cs473/hw4.tex", "home/jmpierc2/cs473/hw4.tex"]
	},
	{
		program: "Terminal",
		files: []
	},
	{
		program: "Adobe Reader",
		files: ["home/jmpierc2/cs233/lab9.pdf", "home/jmpierc2/cs233/lab7.pdf"]
	}
];
// END SAMPLE DATA

// generateShellScript
// input (Object): pad - A pad in the format of the sample one above
// output (String): A shell script in the format defined by the google doc
// author: Andrew
var generateShellScript = function(pad) {

};

// insertIntoPad
// Inserts every element of the delta into the pad, keeping track of paths
// that appear in both pad and delta, and are associated with the same program.
// input (Object): pad - What we are inserting to. A pad like the one above.
// input (Array): delta - Delta array (stuff we're adding), like the one above
// output (Array): Array of paths found in both pad and delta that are
// associated with the same program.
// author: Lorraine
var insertIntoPad = function(pad, delta) {

};

// getDefaultPrograms
// Given an array of paths, create a delta object like the example one above.
// Match each path to the appropriate default program from config.json.
// author: John
var getDefaultPrograms = function(paths, config) {

};

// getAlternativePrograms
// Depends on config.json!
// This function should have a way to detect URLs
// input (Array): A array of file/url paths
// output: A list of programs (array of display-name strings) that supports
// every filetype in the list.
// author: John
var getAlternativePrograms = function(paths, config) {

};

// switchToAlternative
// Switches the program associated with certain paths.
// Given a pad as input and a delta , adds the program/file pairings specified
// in delta to pad. Like insertIntoPad, this should return the list of
// paths that appear in both pad and delta and are associated with the same
// program.
// // author: Lorraine
var switchToAlternative = function(pad, delta) {

};

// getConciseProgramList
// Returns an array mapping program display names to the number of files
// associated with said program.
// input (Object): pad - A pad like the example one above
// output (Object array): Array of {name: "program display name", count: int}
// author: Andrew
var getConciseProgramList = function(pad) {

};

// getStandalonePrograms
// Searches the config object, returns an array of all entries that are standalone programs
// author: Lorraine
var getStandalonePrograms = function() {

};

// Given a program name string, returns the corresponding entry from the config.
// author: John
var getProgramInfo(name, config){

};

//returns true if the String is a valid URL.
// author: Andrew
var validateURL(input){

};
<|endoftext|>


<commit_before>require('../helper');
require('../support/assert');

var assert = require('assert')
    , App = require(global.settings.app_root + '/app/controllers/app')
    , querystring = require('querystring')
    , _ = require('underscore')
    , Step = require('step')
    , net = require('net')
    ;

var sql_server_port = 5556;
var sql_server = net.createServer(function(c) {
  console.log('server connected');
  c.destroy();
  console.log('server socket destroyed.');
  sql_server.close(function() {
  console.log('server closed');
  });
});

suite('backend crash', function() {

suiteSetup(function(done){
  sql_server.listen(sql_server_port, done);
});

// See https://github.com/CartoDB/CartoDB-SQL-API/issues/135
test('does not hang server', function(done){
//console.log("settings:"); console.dir(global.settings);
  var db_host_backup = global.settings.db_host;
  var db_port_backup = global.settings.db_port;
  global.settings.db_host = 'localhost';
  global.settings.db_port = sql_server_port;
  var app = App();
  Step(
    function sendQuery() {
      var next = this;
      assert.response(app, {
          url: '/api/v1/sql?q=SELECT+1',
          method: 'GET',
          headers: {host: 'vizzuality.localhost' }
      },{}, function(res, err) {
          next(err, res);
      });
    },
    function checkResponse(err, res) {
      if ( err ) throw err;
      assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body);
      var parsed = JSON.parse(res.body);
      assert.ok(parsed.error);
      var msg = parsed.error[0];
      assert.ok(msg.match(/unexpected.*end/), msg);
      return null;
    },
    function sendAnotherQuery() {
      var next = this;
      assert.response(app, {
          url: '/api/v1/sql?q=SELECT+2',
          method: 'GET',
          headers: {host: 'vizzuality.localhost' }
      },{}, function(res, err) {
          next(err, res);
      });
    },
    function checkResponse(err, res) {
      if ( err ) throw err;
      assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body);
      var parsed = JSON.parse(res.body);
      assert.ok(parsed.error);
      var msg = parsed.error[0];
      assert.ok(msg.match(/connect/), msg);
      return null;
    },
    function finish(err) {
      global.settings.db_host = db_host_backup;
      global.settings.db_port = db_port_backup;
      done(err);
    }
  );
});

suiteTeardown(function(done) {
  try {
    sql_server.close(done);
  } catch (er) {
    console.log(er);
    done(); // error expected as server is probably closed already
  }
});

});
<commit_msg>Use another port for fake sql server assert.response helper use port 5555 as default but it keeps incrementing the port number if 5555 is busy so this suite was failing randomly because sometimes port 5556 was already in use.<commit_after>require('../helper');
require('../support/assert');

var assert = require('assert')
    , App = require(global.settings.app_root + '/app/controllers/app')
    , querystring = require('querystring')
    , _ = require('underscore')
    , Step = require('step')
    , net = require('net')
    ;

var sql_server_port = 5540;
var sql_server = net.createServer(function(c) {
  console.log('server connected');
  c.destroy();
  console.log('server socket destroyed.');
  sql_server.close(function() {
  console.log('server closed');
  });
});

suite('backend crash', function() {

suiteSetup(function(done){
  sql_server.listen(sql_server_port, done);
});

// See https://github.com/CartoDB/CartoDB-SQL-API/issues/135
test('does not hang server', function(done){
//console.log("settings:"); console.dir(global.settings);
  var db_host_backup = global.settings.db_host;
  var db_port_backup = global.settings.db_port;
  global.settings.db_host = 'localhost';
  global.settings.db_port = sql_server_port;
  var app = App();
  Step(
    function sendQuery() {
      var next = this;
      assert.response(app, {
          url: '/api/v1/sql?q=SELECT+1',
          method: 'GET',
          headers: {host: 'vizzuality.localhost' }
      },{}, function(res, err) {
          next(err, res);
      });
    },
    function checkResponse(err, res) {
      if ( err ) throw err;
      assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body);
      var parsed = JSON.parse(res.body);
      assert.ok(parsed.error);
      var msg = parsed.error[0];
      assert.ok(msg.match(/unexpected.*end/), msg);
      return null;
    },
    function sendAnotherQuery() {
      var next = this;
      assert.response(app, {
          url: '/api/v1/sql?q=SELECT+2',
          method: 'GET',
          headers: {host: 'vizzuality.localhost' }
      },{}, function(res, err) {
          next(err, res);
      });
    },
    function checkResponse(err, res) {
      if ( err ) throw err;
      assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body);
      var parsed = JSON.parse(res.body);
      assert.ok(parsed.error);
      var msg = parsed.error[0];
      assert.ok(msg.match(/connect/), msg);
      return null;
    },
    function finish(err) {
      global.settings.db_host = db_host_backup;
      global.settings.db_port = db_port_backup;
      done(err);
    }
  );
});

suiteTeardown(function(done) {
  try {
    sql_server.close(done);
  } catch (er) {
    console.log(er);
    done(); // error expected as server is probably closed already
  }
});

});
<|endoftext|>


<commit_before>const os = require('os');
const path = require('path');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const BundleTracker = require('webpack-bundle-tracker');

var entryPointsPathPrefix = './src/pages';

const publicPath = '/static/';

module.exports = {
  mode: 'development',
  optimization: {
    minimizer: []  // This list is built below as per platform requirements
  },
  plugins: [
    new BundleTracker({
      filename: '../backend/uclapi/static/webpack-stats.json'
    })
  ],
  module: {
    rules: [
      {
        test: /\.jsx$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      {
        test: /\.scss$/,
        use: [
          { loader: "style-loader" },
          { loader: "css-loader" },
          { loader: "sass-loader" }
        ]
      },
      {
        test: /\.(jpg|png|svg|jpeg)$/,
        loader: 'url-loader'
      },
    ]
  },
  resolve: {
    alias: {
      'Images': path.resolve(__dirname, './src/images'),
      'Layout': path.resolve(__dirname, './src/components/layout'),
      'Styles': path.resolve(__dirname, './src/sass'),
    }
  },
  entry: {
    HomePage: entryPointsPathPrefix + '/HomePage.jsx',
    Documentation: entryPointsPathPrefix + '/Documentation.jsx',
    AboutPage: entryPointsPathPrefix + '/AboutPage.jsx',
    Dashboard: entryPointsPathPrefix + '/Dashboard.jsx',
    Marketplace: entryPointsPathPrefix + '/Marketplace.jsx',
    Authorise: entryPointsPathPrefix + '/Authorise.jsx',
    AppSettings: entryPointsPathPrefix + '/AppSettings.jsx',
    vendors: ['react'],
  },
  output: {
    path: path.resolve(__dirname, '../backend/uclapi/static/'),
    publicPath,
    filename: '[name].js'
  }
};
if (os.platform == "linux" && os.release().indexOf("Microsoft") != -1) {
  module.exports.optimization.minimizer.push(
    new UglifyJsPlugin({
      cache: true,
      sourceMap: true
    })
  );
} else {
  module.exports.optimization.minimizer.push(
    new UglifyJsPlugin({
      cache: true,
      sourceMap: true,
      parallel: true
    })
  );
}<commit_msg>Update webpack.dev.js<commit_after>const os = require('os');
const path = require('path');
const BundleTracker = require('webpack-bundle-tracker');

var entryPointsPathPrefix = './src/pages';

const publicPath = '/static/';

module.exports = {
  mode: 'development',
  optimization: {
    minimizer: []  // This list is built below as per platform requirements
  },
  plugins: [
    new BundleTracker({
      filename: '../backend/uclapi/static/webpack-stats.json'
    })
  ],
  module: {
    rules: [
      {
        test: /\.jsx$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      {
        test: /\.scss$/,
        use: [
          { loader: "style-loader" },
          { loader: "css-loader" },
          { loader: "sass-loader" }
        ]
      },
      {
        test: /\.(jpg|png|svg|jpeg)$/,
        loader: 'url-loader'
      },
    ]
  },
  resolve: {
    alias: {
      'Images': path.resolve(__dirname, './src/images'),
      'Layout': path.resolve(__dirname, './src/components/layout'),
      'Styles': path.resolve(__dirname, './src/sass'),
    }
  },
  entry: {
    HomePage: entryPointsPathPrefix + '/HomePage.jsx',
    Documentation: entryPointsPathPrefix + '/Documentation.jsx',
    AboutPage: entryPointsPathPrefix + '/AboutPage.jsx',
    Dashboard: entryPointsPathPrefix + '/Dashboard.jsx',
    Marketplace: entryPointsPathPrefix + '/Marketplace.jsx',
    Authorise: entryPointsPathPrefix + '/Authorise.jsx',
    AppSettings: entryPointsPathPrefix + '/AppSettings.jsx',
    vendors: ['react'],
  },
  output: {
    path: path.resolve(__dirname, '../backend/uclapi/static/'),
    publicPath,
    filename: '[name].js'
  }
};
<|endoftext|>


<commit_before>import countCredits from '../../src/lib/count-credits'

describe('countCredits', () => {
    it('counts the number of credits in an array of courses', () => {
        const courses = [
            {credits: 1.0},
            {credits: 1.5},
            {credits: 1.5},
        ]
        expect(countCredits(courses)).to.equal(4.0)
    })
})
<commit_msg>add test for countCredits' Immutable.List support<commit_after>import countCredits from '../../src/lib/count-credits'
import {List} from 'immutable'

describe('countCredits', () => {
    it('counts the number of credits in an array of courses', () => {
        const courses = [
            {credits: 1.0},
            {credits: 1.5},
            {credits: 1.5},
        ]
        expect(countCredits(courses)).to.equal(4.0)
    })

    it('counts the number of credits in an immutable.list of courses', () => {
        const courses = List([
            {credits: 1.0},
            {credits: 1.5},
            {credits: 1.5},
        ])
        expect(countCredits(courses)).to.equal(4.0)
    })
})
<|endoftext|>


<commit_before>//META{"name":"EvenBetterDiscord"}*//

let request = require('request');
let fs = require('fs');

let EvenBetterDiscord = function ()
{
    this.targetAPI = "https://api.github.com/repos/DeathStrikeV/BetterDiscordApp/commits/master";
    this.targetURL = "https://raw.githubusercontent.com/DeathStrikeV/BetterDiscordApp/master/data/emotedata_ffz.json";

    this.bdPath = (process.platform == "win32" ?
        process.env.APPDATA : process.platform == "darwin" ?
        process.env.HOME + "/Library/Preferences/" : "/var/local/") + "/BetterDiscord/";

    this.preferencesFile = this.bdPath + "/EBD_Preferences.json";
    this.emotesFile = this.bdPath + "/EBD_Emotes.json";
    this.defaultEmotesFile = this.bdPath + "/emotes_ffz.json";
};

EvenBetterDiscord.prototype.start = function ()
{
    this.loadEBDFiles();
};

EvenBetterDiscord.prototype.loadEBDFiles = function ()
{
    let readPreferencesFile = function (err, data)
    {
        if (data)
            this.currentHash = JSON.parse(data).currentHash;
        else
            this.currentHash = "";

        if (this.currentHash === "ignore")
        {
            this.loadEmotes(this.emotesFile);
            return;
        }

        request({ url: this.targetAPI, headers: { "User-Agent": "DeathStrikeV" } }, requestRepoHash.bind(this));
    };

    let requestRepoHash = function (error, response, body)
    {
        if (response.statusCode !== 200)
        {
            this.loadEmotes(this.emotesFile);
            return;
        }

        this.latestHash = JSON.parse(body).sha;

        if (this.currentHash === this.latestHash)
        {
            this.loadEmotes(this.emotesFile);
            return;
        }

        request(this.targetURL, downloadEmotesFile.bind(this));
    };

    let downloadEmotesFile = function (error, response, body)
    {
        if (response.statusCode !== 200)
        {
            this.loadEmotes(this.emotesFile);
            return;
        }

        fs.writeFile(this.emotesFile, body, saveEmotesFile.bind(this));
    };

    let saveEmotesFile = function (err)
    {
        if (!err)
        {
            //console.log("Retreived new emotes file!");
            fs.writeFile(this.preferencesFile, JSON.stringify({ currentHash: this.latestHash }), null);
        }

        this.loadEmotes(this.emotesFile);
    }

    fs.readFile(this.preferencesFile, "utf8", readPreferencesFile.bind(this));
};

EvenBetterDiscord.prototype.loadEmotes = function (targetFile)
{
    fs.readFile(targetFile, "utf8", (err, data) =>
    {
        if (data)
        {
            let emoteData = JSON.parse(data);

            if (!emoteData)
            {
                //console.log("Failed to parse emotes file!");
                return;
            }

            window.emotesFfz = emoteData;
            //console.log("Loaded emotes file!");

            // TODO: Replace localstorage
            if (typeof localStorage !== "undefined")
                this.fixBrokenFavorites();
        }
    });
};

EvenBetterDiscord.prototype.fixBrokenFavorites = function ()
{
    let result;
    let regex = new RegExp("\"([\\w!]+(?:~\\d+)?)\":\"https:\\/\\/cdn\\.frankerfacez\\.com\\/emoticon\\/\\d*\\/", "g");
    let brokenFavorites = atob(localStorage.bdfavemotes);
    let fixedFavorites = brokenFavorites;

    while (result = regex.exec(brokenFavorites))
    {
        if (!emotesFfz[result[1]])
            continue;

        let replacementRegex = new RegExp("\"" + result[1] + "\":\"https:\\/\\/cdn\\.frankerfacez\\.com\\/emoticon\\/\\d*\\/", "g");
        let replacementString = "\"" + result[1] + "\":\"https://cdn.frankerfacez.com/emoticon/" + emotesFfz[result[1]] + "/";
        fixedFavorites = fixedFavorites.replace(replacementRegex, replacementString);
    }

    localStorage.bdfavemotes = btoa(fixedFavorites);
    quickEmoteMenu.favoriteEmotes = JSON.parse(fixedFavorites);
    quickEmoteMenu.updateFavorites();
};

EvenBetterDiscord.prototype.stop = function ()
{
    this.removeEBDFiles();
    this.loadEmotes(this.defaultEmotesFile);
};

EvenBetterDiscord.prototype.removeEBDFiles = function ()
{
    fs.stat(this.preferencesFile, (err, stats) =>
    {
        if (err)
            return;

        fs.unlink(this.preferencesFile, (err) => { });
    });

    fs.stat(this.emotesFile, (err, stats) =>
    {
        if (err)
            return;

        fs.unlink(this.emotesFile, (err) => { });
    });
};

EvenBetterDiscord.prototype.load = function () { };
EvenBetterDiscord.prototype.unload = function () { };
EvenBetterDiscord.prototype.onMessage = function () { };
EvenBetterDiscord.prototype.onSwitch = function () { };

EvenBetterDiscord.prototype.getName = function ()
{
    return "EvenBetterDiscord";
};

EvenBetterDiscord.prototype.getDescription = function ()
{
    return "Loads FFZ emotes from alternate repository";
};

EvenBetterDiscord.prototype.getVersion = function ()
{
    return "1.1.0";
};

EvenBetterDiscord.prototype.getAuthor = function ()
{
    return "DeathStrikeV";
};<commit_msg>Updated plugin<commit_after>//META{"name":"EvenBetterDiscord"}*//

let request = require('request');
let fs = require('fs');

let EvenBetterDiscord = function ()
{
    this.targetAPI = "https://api.github.com/repos/DeathStrikeV/BetterDiscordApp/commits/master";
    this.targetURL = "https://raw.githubusercontent.com/DeathStrikeV/BetterDiscordApp/master/data/emotedata_ffz.json";

    this.bdPath = (process.platform == "win32" ?
        process.env.APPDATA : process.platform == "darwin" ?
        process.env.HOME + "/Library/Preferences/" : "/var/local/") + "/BetterDiscord/";

    this.preferencesFile = this.bdPath + "/EBD_Preferences.json";
    this.emotesFile = this.bdPath + "/EBD_Emotes.json";
    this.defaultEmotesFile = this.bdPath + "/emotes_ffz.json";
};

EvenBetterDiscord.prototype.start = function ()
{
    this.loadEBDFiles();
};

EvenBetterDiscord.prototype.loadEBDFiles = function ()
{
    let readPreferencesFile = function (err, data)
    {
        if (data)
            this.currentHash = JSON.parse(data).currentHash;
        else
            this.currentHash = "";

        if (this.currentHash === "ignore")
        {
            this.loadEmotes(this.emotesFile);
            return;
        }

        request({ url: this.targetAPI, headers: { "User-Agent": "DeathStrikeV" } }, requestRepoHash.bind(this));
    };

    let requestRepoHash = function (error, response, body)
    {
        if (response.statusCode !== 200)
        {
            this.loadEmotes(this.emotesFile);
            return;
        }

        this.latestHash = JSON.parse(body).sha;

        if (this.currentHash === this.latestHash)
        {
            this.loadEmotes(this.emotesFile);
            return;
        }

        request(this.targetURL, downloadEmotesFile.bind(this));
    };

    let downloadEmotesFile = function (error, response, body)
    {
        if (response.statusCode !== 200)
        {
            this.loadEmotes(this.emotesFile);
            return;
        }

        fs.writeFile(this.emotesFile, body, saveEmotesFile.bind(this));
    };

    let saveEmotesFile = function (err)
    {
        if (!err)
        {
            //console.log("Retreived new emotes file!");
            fs.writeFile(this.preferencesFile, JSON.stringify({ currentHash: this.latestHash }), null);
        }

        this.loadEmotes(this.emotesFile);
    }

    fs.readFile(this.preferencesFile, "utf8", readPreferencesFile.bind(this));
};

EvenBetterDiscord.prototype.loadEmotes = function (targetFile)
{
    fs.readFile(targetFile, "utf8", (err, data) =>
    {
        if (data)
        {
            let emoteData = JSON.parse(data);

            if (!emoteData)
            {
                //console.log("Failed to parse emotes file!");
                return;
            }

            window.emotesFfz = emoteData;
            //console.log("Loaded emotes file!");

            // TODO: Replace localstorage
            if (typeof localStorage !== "undefined")
                this.fixBrokenFavorites();
        }
    });
};

EvenBetterDiscord.prototype.fixBrokenFavorites = function ()
{
    let result;
    let regex = new RegExp("\"([\\w!]+(?:~\\d+)?)\":\"https:\\/\\/cdn\\.frankerfacez\\.com\\/emoticon\\/\\d*\\/", "g");
    let brokenFavorites = atob(localStorage.bdfavemotes);
    let fixedFavorites = brokenFavorites;

    while (result = regex.exec(brokenFavorites))
    {
        if (!emotesFfz[result[1]])
            continue;

        let replacementRegex = new RegExp("\"" + result[1] + "\":\"https:\\/\\/cdn\\.frankerfacez\\.com\\/emoticon\\/\\d*\\/", "g");
        let replacementString = "\"" + result[1] + "\":\"https://cdn.frankerfacez.com/emoticon/" + emotesFfz[result[1]] + "/";
        fixedFavorites = fixedFavorites.replace(replacementRegex, replacementString);
    }

    localStorage.bdfavemotes = btoa(fixedFavorites);
    quickEmoteMenu.favoriteEmotes = JSON.parse(fixedFavorites);
    quickEmoteMenu.updateFavorites();
};

EvenBetterDiscord.prototype.stop = function ()
{
    this.removeEBDFiles();
    this.loadEmotes(this.defaultEmotesFile);
};

EvenBetterDiscord.prototype.removeEBDFiles = function ()
{
    fs.stat(this.preferencesFile, (err, stats) =>
    {
        if (err)
            return;

        fs.unlink(this.preferencesFile, (err) => { });
    });

    fs.stat(this.emotesFile, (err, stats) =>
    {
        if (err)
            return;

        fs.unlink(this.emotesFile, (err) => { });
    });
};

EvenBetterDiscord.prototype.load = function () { };
EvenBetterDiscord.prototype.unload = function () { };
EvenBetterDiscord.prototype.onMessage = function () { };
EvenBetterDiscord.prototype.onSwitch = function () { };

EvenBetterDiscord.prototype.getName = function ()
{
    return "EvenBetterDiscord";
};

EvenBetterDiscord.prototype.getDescription = function ()
{
    return "Loads FFZ emotes from alternate repository";
};

EvenBetterDiscord.prototype.getVersion = function ()
{
    return "1.1.1";
};

EvenBetterDiscord.prototype.getAuthor = function ()
{
    return "DeathStrikeV";
};<|endoftext|>


<commit_before>/* See license.txt for terms of usage */

define([
    "firebug/lib/object",
    "firebug/firebug",
    "firebug/lib/domplate",
    "firebug/lib/events",
    "firebug/lib/dom",
    "firebug/lib/css",
    "firebug/lib/array",
],
function(Obj, Firebug, Domplate, Events, Dom, Css, Arr) {
with (Domplate) {

// ********************************************************************************************* //
// DOM Tree Implementation

function DomTree(provider)
{
    this.provider = provider;
}

/**
 * @domplate This object represents UI DomTree widget based on Domplate. You can use
 * data provider to populate the tree with custom data. Or just pass a JS object as
 * an input.
 */
DomTree.prototype = domplate(
/** @lends DomTree */
{
    sizerRowTag:
        TR({role: "presentation"},
            TD({width: "30%"}),
            TD({width: "70%"})
        ),

    tag:
        TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0, onclick: "$onClick"},
            TBODY(
                FOR("member", "$object|memberIterator",
                    TAG("$member|getRowTag", {member: "$member"}))
            )
        ),

    rowTag:
        TR({"class": "memberRow $member.open $member.type\\Row",
            $hasChildren: "$member|hasChildren",
            _repObject: "$member", level: "$member.level"},
            TD({"class": "memberLabelCell", style: "padding-left: $member|getIndent\\px"},
                SPAN({"class": "memberLabel $member.type\\Label"}, "$member|getLabel")
            ),
            TD({"class": "memberValueCell"},
                TAG("$member|getValueTag", {object: "$member|getValue"})
            )
        ),

    loop:
        FOR("member", "$members",
            TAG("$member|getRowTag", {member: "$member"})),

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
    // Domplate Accessors

    hasChildren: function(member)
    {
        return member.hasChildren ? "hasChildren" : "";
    },

    getIndent: function(member)
    {
        return member.level * 16;
    },

    getLabel: function(member)
    {
        if (member.provider)
            return member.provider.getLabel(member.value);

        return member.name;
    },

    getValue: function(member)
    {
        if (member.provider)
        {
            var value = member.provider.getValue(member.value);
            if (isPromise(value))
                return this.resolvePromise(value, member.value);

            return value;
        }

        return member.value;
    },

    getValueTag: function(member)
    {
        // Get proper UI template for the value.
        // xxxHonza: The value can be fetched asynchronously so, the value tag
        // should be also provided (by a decorator or provider).
        var value = this.getValue(member);
        var valueTag = Firebug.getRep(value);
        return valueTag.tag;
    },

    getRowTag: function(member)
    {
        return this.rowTag;
    },

    getSizerRowTag: function()
    {
        return this.sizerRowTag;
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
    // Domplate Event Handlers

    onClick: function(event)
    {
        if (!Events.isLeftClick(event))
            return;

        var row = Dom.getAncestorByClass(event.target, "memberRow");
        var label = Dom.getAncestorByClass(event.target, "memberLabel");
        if (label && Css.hasClass(row, "hasChildren"))
            this.toggleRow(row);
    },

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

    toggleRow: function(row, forceOpen)
    {
        if (!row)
            return;

        var level = parseInt(row.getAttribute("level"));
        if (forceOpen && Css.hasClass(row, "opened"))
            return;

        if (Css.hasClass(row, "opened"))
        {
            Css.removeClass(row, "opened");

            var tbody = row.parentNode;
            for (var firstRow = row.nextSibling; firstRow; firstRow = row.nextSibling)
            {
                if (parseInt(firstRow.getAttribute("level")) <= level)
                    break;

                tbody.removeChild(firstRow);
            }
        }
        else
        {
            var member = row.repObject;
            if (member)
            {
                // Do not expand if the member says there are no children.
                if (!member.hasChildren)
                    return;

                Css.setClass(row, "opened");

                // Get children object for the next level.
                var members = this.getMembers(member.value, level + 1);

                // Insert rows if they are immediatelly available. Otherwise set a spinner
                // and wait for the update.
                if (members && members.length)
                    this.loop.insertRows({members: members}, row, this);
                else if (isPromise(members))
                    Css.setClass(row, "spinning");
            }
        }
    },

    memberIterator: function(object)
    {
        return this.getMembers(object);
    },

    getMembers: function(object, level)
    {
        if (!level)
            level = 0;

        var members = [];

        if (this.provider)
        {
            // Use data provider if it's available.
            var children = this.fetchChildren(object);
            if (isPromise(children))
                return children;

            for (var i=0; i<children.length; i++)
            {
                var child = children[i];
                var hasChildren = this.provider.hasChildren(child);
                var type = this.getType(child);

                var member = this.createMember(type, null, child, level, hasChildren);
                member.provider = this.provider;
                members.push(member);
            }
        }
        else
        {
            // If there is no provider, iterate the object properties.
            // xxxHonza: Introduce an interator that is customizable (e.g. from derived objects)
            for (var p in object)
            {
                var value = object[p];
                var valueType = typeof(value);
                var hasChildren = this.hasProperties(value) && (valueType == "object");
                var type = this.getType(value);

                members.push(this.createMember(type, p, value, level, hasChildren));
            }
        }

        return members;
    },

    fetchChildren: function(object)
    {
        var children = [];

        try
        {
            children = this.provider.getChildren(object);
        }
        catch (e)
        {
            if (FBTrace.DBG_ERRORS)
                FBTrace.sysout("domTree.fetchChildren; EXCEPTION " + e, e);

            return children;
        }

        if (isPromise(children))
            return this.resolvePromise(children, object);

        return children;
    },

    getType: function(object)
    {
        // Type is used for UI decoration of a tree row.
        // A 'decorator' should be introduced for these things. This object should be
        // used to change style of a row like for example: append icons, badges, prefixes, etc.
        // xxxHonza: Use Decorator pattern and introduce a Decorator interface.
        // var Decorator =
        // {
        //     decorateLabel: function(object),
        //     decorateStyle: function(object),
        // }
        //
        // return this.decorator.decorateStyle(object);

        return "dom";
    },

    createMember: function(type, name, value, level, hasChildren)
    {
        var member = {
            name: name,
            type: type,
            rowClass: "memberRow-" + type,
            open: "",
            level: level,
            hasChildren: hasChildren,
            value: value,
        };

        return member;
    },

    hasProperties: function(ob)
    {
        if (typeof(ob) == "string")
            return false;

        try {
            for (var name in ob)
                return true;
        } catch (exc) {}
        return false;
    },

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

    getRow: function(object)
    {
        // If not rendered yet, bail out.
        if (!this.element)
            return;

        // Iterate all existing rows and expand the one associated with specified object.
        // The repObject is a "member" object created in createMember method.
        var rows = Dom.getElementsByClass(this.element, "memberRow");
        for (var i=0; i<rows.length; i++)
        {
            var row = rows[i];
            var member = row.repObject;
            if (member.value == object)
                return row;
        }

        return null;
    },

    resolvePromise: function(promise, object)
    {
        var result;

        // This flag is used to differentiate sync and async scenario.
        var sync = true;

        // The callback can be executed immediatelly if children are provided
        // synchronously. In such case, 'arr' is immediatelly used as the result value.
        // The object (i.e. the associated row) is updated later in asynchronous senario.
        var self = this;
        var promise = promise.then(function onThen(value)
        {
            if (FBTrace.DBG_DOMTREE)
                FBTrace.sysout("domTree.onThen; sync: " + sync, value);

            if (sync)
                result = value;
            else
                self.updateObject(object); // xxxHonza: value should be passed too

        },
        function onError(err)
        {
            FBTrace.sysout("domTree.onResolvePromise; ERROR " + err, err);
        });

        sync = false;

        return result || promise;
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
    // Public

    replace: function(parentNode, input)
    {
        Dom.clearNode(parentNode);
        this.append(parentNode, input);
    },

    append: function(parentNode, input)
    {
        this.parentNode = parentNode;

        this.element = this.tag.append(input, parentNode, this);
        this.element.repObject = this;

        this.input = input;

        // Expand the first node (root) by default
        // Do not expand if the root is an array with more than one element.
        var value = Arr.isArray(input) && input.length > 2;
        var firstRow = this.element.firstChild.firstChild;
        if (firstRow && !value)
            this.toggleRow(firstRow);
    },

    expandObject: function(object)
    {
        var row = this.getRow(object);
        if (!row)
        {
            if (FBTrace.DBG_ERRORS)
                FBTrace.sysout("domTree.expandObject; ERROR no such object", object);
            return;
        }

        this.toggleRow(row, true);
        return row;
    },

    collapseObject: function(object)
    {
        var row = this.getRow(object);
        if (Css.hasClass(row, "opened"))
            this.toggleRow(row);
        return row;
    },

    updateObject: function(object)
    {
        if (FBTrace.DBG_DOMTREE)
            FBTrace.sysout("domTree.updateObject;", object);

        var row = this.getRow(object);

        // The input.object itself (the root) doesn't have a row.
        if (this.input.object == object)
        {
            var members = this.getMembers(object); // xxxHonza: what about level?
            if (members)
                this.loop.insertRows({members: members}, this.element.firstChild);
            return;
        }

        // Root will always bail out.
        if (!row)
        {
            FBTrace.sysout("domTree.updateObject; This object can't be updated", object);
            return;
        }

        var member = row.repObject;
        member.hasChildren = this.provider.hasChildren(object);

        // Generate new row with new value.
        var rowTag = this.getRowTag();
        var rows = rowTag.insertRows({member: member}, row, this);

        // If the old row was expanded remember it.
        var expanded = Css.hasClass(row, "opened");

        // Remove the old row before expanding the new row,otherwise the old one
        // would be expanded and consequently removed.
        row.parentNode.removeChild(row);

        if (expanded)
        {
            // Expand if it was expanded and the flag still says there are
            // some children. Otherwise close the row.
            if (member.hasChildren)
                this.expandObject(object);
            else
                this.collapseObject(object);
        }
    }
});

// ********************************************************************************************* //
// Helpers

function isPromise(object)
{
    return object && typeof(object.then) == "function";
}

// ********************************************************************************************* //
// Registration

return DomTree;

// ********************************************************************************************* //
}});

<commit_msg>Make sure domTree is properly updated when resolving async promises<commit_after>/* See license.txt for terms of usage */

define([
    "firebug/lib/object",
    "firebug/firebug",
    "firebug/lib/domplate",
    "firebug/lib/events",
    "firebug/lib/dom",
    "firebug/lib/css",
    "firebug/lib/array",
],
function(Obj, Firebug, Domplate, Events, Dom, Css, Arr) {
with (Domplate) {

// ********************************************************************************************* //
// DOM Tree Implementation

function DomTree(provider)
{
    this.provider = provider;
}

/**
 * @domplate This object represents UI DomTree widget based on Domplate. You can use
 * data provider to populate the tree with custom data. Or just pass a JS object as
 * an input.
 */
DomTree.prototype = domplate(
/** @lends DomTree */
{
    sizerRowTag:
        TR({role: "presentation"},
            TD({width: "30%"}),
            TD({width: "70%"})
        ),

    tag:
        TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0, onclick: "$onClick"},
            TBODY(
                FOR("member", "$object|memberIterator",
                    TAG("$member|getRowTag", {member: "$member"}))
            )
        ),

    rowTag:
        TR({"class": "memberRow $member.open $member.type\\Row",
            $hasChildren: "$member|hasChildren",
            _repObject: "$member", level: "$member.level"},
            TD({"class": "memberLabelCell", style: "padding-left: $member|getIndent\\px"},
                SPAN({"class": "memberLabel $member.type\\Label"}, "$member|getLabel")
            ),
            TD({"class": "memberValueCell"},
                TAG("$member|getValueTag", {object: "$member|getValue"})
            )
        ),

    loop:
        FOR("member", "$members",
            TAG("$member|getRowTag", {member: "$member"})),

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
    // Domplate Accessors

    hasChildren: function(member)
    {
        return member.hasChildren ? "hasChildren" : "";
    },

    getIndent: function(member)
    {
        return member.level * 16;
    },

    getLabel: function(member)
    {
        if (member.provider)
            return member.provider.getLabel(member.value);

        return member.name;
    },

    getValue: function(member)
    {
        if (member.provider)
        {
            var value = member.provider.getValue(member.value);
            if (isPromise(value))
                return member.tree.resolvePromise(value, member.value);

            return value;
        }

        return member.value;
    },

    getValueTag: function(member)
    {
        // xxxHonza: |this| is wrong at this moment (callback from Domplate uses wrong context).

        // Get proper UI template for the value.
        // xxxHonza: The value can be fetched asynchronously so, the value tag
        // should be also provided (by a decorator or provider).
        var value = this.getValue(member);
        var valueTag = Firebug.getRep(value);
        return valueTag.tag;
    },

    getRowTag: function(member)
    {
        return this.rowTag;
    },

    getSizerRowTag: function()
    {
        return this.sizerRowTag;
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
    // Domplate Event Handlers

    onClick: function(event)
    {
        if (!Events.isLeftClick(event))
            return;

        var row = Dom.getAncestorByClass(event.target, "memberRow");
        var label = Dom.getAncestorByClass(event.target, "memberLabel");
        if (label && Css.hasClass(row, "hasChildren"))
            this.toggleRow(row);
    },

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

    toggleRow: function(row, forceOpen)
    {
        if (!row)
            return;

        var level = parseInt(row.getAttribute("level"));
        if (forceOpen && Css.hasClass(row, "opened"))
            return;

        if (Css.hasClass(row, "opened"))
        {
            Css.removeClass(row, "opened");

            var tbody = row.parentNode;
            for (var firstRow = row.nextSibling; firstRow; firstRow = row.nextSibling)
            {
                if (parseInt(firstRow.getAttribute("level")) <= level)
                    break;

                tbody.removeChild(firstRow);
            }
        }
        else
        {
            var member = row.repObject;
            if (member)
            {
                // Do not expand if the member says there are no children.
                if (!member.hasChildren)
                    return;

                Css.setClass(row, "opened");

                // Get children object for the next level.
                var members = this.getMembers(member.value, level + 1);

                // Insert rows if they are immediatelly available. Otherwise set a spinner
                // and wait for the update.
                if (members && members.length)
                    this.loop.insertRows({members: members}, row, this);
                else if (isPromise(members))
                    Css.setClass(row, "spinning");
            }
        }
    },

    memberIterator: function(object)
    {
        return this.getMembers(object);
    },

    getMembers: function(object, level)
    {
        if (!level)
            level = 0;

        var members = [];

        if (this.provider)
        {
            // Use data provider if it's available.
            var children = this.fetchChildren(object);
            if (isPromise(children))
                return children;

            for (var i=0; i<children.length; i++)
            {
                var child = children[i];
                var hasChildren = this.provider.hasChildren(child);
                var type = this.getType(child);

                var member = this.createMember(type, null, child, level, hasChildren);
                member.provider = this.provider;
                member.tree = this; // Domplate derivation doesn't work properly
                members.push(member);
            }
        }
        else
        {
            // If there is no provider, iterate the object properties.
            // xxxHonza: Introduce an interator that is customizable (e.g. from derived objects)
            for (var p in object)
            {
                var value = object[p];
                var valueType = typeof(value);
                var hasChildren = this.hasProperties(value) && (valueType == "object");
                var type = this.getType(value);

                members.push(this.createMember(type, p, value, level, hasChildren));
            }
        }

        return members;
    },

    fetchChildren: function(object)
    {
        var children = [];

        try
        {
            children = this.provider.getChildren(object);
        }
        catch (e)
        {
            if (FBTrace.DBG_ERRORS)
                FBTrace.sysout("domTree.fetchChildren; EXCEPTION " + e, e);

            return children;
        }

        if (isPromise(children))
            return this.resolvePromise(children, object);

        return children;
    },

    getType: function(object)
    {
        // Type is used for UI decoration of a tree row.
        // A 'decorator' should be introduced for these things. This object should be
        // used to change style of a row like for example: append icons, badges, prefixes, etc.
        // xxxHonza: Use Decorator pattern and introduce a Decorator interface.
        // var Decorator =
        // {
        //     decorateLabel: function(object),
        //     decorateStyle: function(object),
        // }
        //
        // return this.decorator.decorateStyle(object);

        return "dom";
    },

    createMember: function(type, name, value, level, hasChildren)
    {
        var member = {
            name: name,
            type: type,
            rowClass: "memberRow-" + type,
            open: "",
            level: level,
            hasChildren: hasChildren,
            value: value,
        };

        return member;
    },

    hasProperties: function(ob)
    {
        if (typeof(ob) == "string")
            return false;

        try {
            for (var name in ob)
                return true;
        } catch (exc) {}
        return false;
    },

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

    getRow: function(object)
    {
        // If not rendered yet, bail out.
        if (!this.element)
            return;

        // Iterate all existing rows and expand the one associated with specified object.
        // The repObject is a "member" object created in createMember method.
        var rows = Dom.getElementsByClass(this.element, "memberRow");
        for (var i=0; i<rows.length; i++)
        {
            var row = rows[i];
            var member = row.repObject;
            if (member.value == object)
                return row;
        }

        return null;
    },

    resolvePromise: function(promise, object)
    {
        var result;

        // This flag is used to differentiate sync and async scenario.
        var sync = true;

        // The callback can be executed immediatelly if children are provided
        // synchronously. In such case, 'arr' is immediatelly used as the result value.
        // The object (i.e. the associated row) is updated later in asynchronous senario.
        var self = this;
        var promise = promise.then(function onThen(value)
        {
            if (FBTrace.DBG_DOMTREE)
            {
                FBTrace.sysout("domTree.onThen; sync: " + sync,
                    {value: value, object: object});
            }

            if (sync)
                result = value;
            else
                self.updateObject(object); // xxxHonza: value should be passed too

        },
        function onError(err)
        {
            FBTrace.sysout("domTree.onResolvePromise; ERROR " + err, err);
        });

        sync = false;

        return result || promise;
    },

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
    // Public

    replace: function(parentNode, input)
    {
        Dom.clearNode(parentNode);
        this.append(parentNode, input);
    },

    append: function(parentNode, input)
    {
        this.parentNode = parentNode;

        this.element = this.tag.append(input, parentNode, this);
        this.element.repObject = this;

        this.input = input;

        // Expand the first node (root) by default
        // Do not expand if the root is an array with more than one element.
        var value = Arr.isArray(input) && input.length > 2;
        var firstRow = this.element.firstChild.firstChild;
        if (firstRow && !value)
            this.toggleRow(firstRow);
    },

    expandObject: function(object)
    {
        var row = this.getRow(object);
        if (!row)
        {
            if (FBTrace.DBG_ERRORS)
                FBTrace.sysout("domTree.expandObject; ERROR no such object", object);
            return;
        }

        this.toggleRow(row, true);
        return row;
    },

    collapseObject: function(object)
    {
        var row = this.getRow(object);
        if (Css.hasClass(row, "opened"))
            this.toggleRow(row);
        return row;
    },

    updateObject: function(object)
    {
        try
        {
            this.doUpdateObject(object);
        }
        catch (e)
        {
            FBTrace.sysout("domTree.updateObject; EXCEPTION " + e, e);
        }
    },

    doUpdateObject: function(object)
    {
        if (FBTrace.DBG_DOMTREE)
            FBTrace.sysout("domTree.updateObject;", object);

        var row = this.getRow(object);

        // The input.object itself (the root) doesn't have a row.
        if (this.input.object == object)
        {
            var members = this.getMembers(object); // xxxHonza: what about level?
            if (members)
                this.loop.insertRows({members: members}, this.element.firstChild, this);
            return;
        }

        // Root will always bail out.
        if (!row)
        {
            FBTrace.sysout("domTree.updateObject; This object can't be updated", object);
            return;
        }

        var member = row.repObject;
        member.hasChildren = this.provider.hasChildren(object);

        // Generate new row with new value.
        var rowTag = this.getRowTag();
        var rows = rowTag.insertRows({member: member}, row, this);

        // If the old row was expanded remember it.
        var expanded = Css.hasClass(row, "opened");

        // Remove the old row before expanding the new row,otherwise the old one
        // would be expanded and consequently removed.
        row.parentNode.removeChild(row);

        if (expanded)
        {
            // Expand if it was expanded and the flag still says there are
            // some children. Otherwise close the row.
            if (member.hasChildren)
                this.expandObject(object);
            else
                this.collapseObject(object);
        }
    }
});

// ********************************************************************************************* //
// Helpers

function isPromise(object)
{
    return object && typeof(object.then) == "function";
}

// ********************************************************************************************* //
// Registration

return DomTree;

// ********************************************************************************************* //
}});

<|endoftext|>


<commit_before>describe('b7 - shared data component', function () {

  this.timeout(120000);

  //require('benchmarket').start();
  //after(//require('benchmarket').store());

  var should = require('chai').should();
  var Mesh = require('../');
  var meshInstance;
  var dataEvents;
  var config;
  var expect = require('expect.js');
  var TestModule1 = {
    setSharedData: function ($happn, path, data, callback) {
      $happn.exchange.data.set(path, data, callback);
    }
  }

  var TestModule2 = {
    getSharedData: function ($happn, path, callback) {
      $happn.exchange.data.get(path, callback);
    }
  }

  before(function (done) {
    var _this = this;
    Mesh.create(config = {
      port: 9898,
      modules: {
        'module1': {
          instance: TestModule1
        },
        'module2': {
          instance: TestModule2
        }
      },

      components: {
        'data': {},
        'module1': {},
        'module2': {},
      }

    }).then(function (mesh) {
      meshInstance = mesh;
      dataComponent = mesh.exchange.data;
      dataEvents = mesh.event.data;
      done();
    }).catch(done);
  });

  after(function (done) {
    meshInstance.stop({reconnect: false}, done);
  });

  context('direct use', function () {

    it('can set and get with opts', function (done) {
      dataComponent.set('some/path/one', {key: 'value'}, {}, function (e, result) {
        if (e) return done(e);
        dataComponent.get('some/path/one', {}, function (e, result) {
          if (e) return done(e);
          result.key.should.equal('value');
          done();
        });
      });
    });

    it('can set and get without opts', function (done) {

      dataComponent.set('some/path/two', {key: 'value'}, function (e, result) {
        if (e) return done(e);
        dataComponent.get('some/path/two', function (e, result) {
          if (e) return done(e);
          result.key.should.equal('value');
          done();
        });
      });
    });

    it('can subscribe with opts', function (done) {

      dataComponent.on('/some/path/three', {}, function (data, meta) {
        data.should.eql({key: 'VAL'});
        done();
      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/three', {key: 'VAL'}, {}, function (e) {
          if (e) return done(e);
        })
      });
    });

    it('can subscribe without opts', function (done) {
      dataComponent.on('/some/path/four', function (data, meta) {
        data.should.eql({key: 'VALUE'});
        done();
      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/four', {key: 'VALUE'}, function (e) {
          if (e) return done(e);
        })
      });
    });

    it('respects noPublish set option', function (done) {
      meshInstance.event.data.on('/some/path/testNoPublish', function (data, meta) {
        delete data._meta;
        data.should.eql({val: 'must be emitted'});
        done();
      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/testNoPublish', {val: 'must not be emitted'}, {noPublish: true}, function (e) {
          if (e) return done(e);
          dataComponent.set('/some/path/testNoPublish', {val: 'must be emitted'}, {noPublish: false}, function (e) {
            if (e) return done(e);
          })
        })
      });
    });

    it('should subscribe and get an initial value on the callback', function (callback) {

      dataComponent.set('/b7/testsubscribe/data/value_on_callback_test', {"test": "data"}, function (e) {
        if (e) return callback(e);

        dataComponent.on('/b7/testsubscribe/data/value_on_callback_test', {
          "event_type": "set",
          "initialCallback": true
        }, function (message) {

          expect(message.updated).to.be(true);
          callback();

        }, function (e, reference, response) {
          if (e) return callback(e);
          try {

            expect(response.length).to.be(1);
            expect(response[0].test).to.be('data');

            dataComponent.set('/b7/testsubscribe/data/value_on_callback_test', {
              "test": "data",
              "updated": true
            }, function (e) {
              if (e) return callback(e);
            });

          } catch (e) {
            return callback(e);
          }
        });

      });

    });

    it('should subscribe and get initial values on the callback', function (callback) {

      dataComponent.set('/b7/testsubscribe/data/values_on_callback_test/1', {"test": "data"}, function (e) {
        if (e) return callback(e);

        dataComponent.set('/b7/testsubscribe/data/values_on_callback_test/2', {"test": "data1"}, function (e) {
          if (e) return callback(e);

          dataComponent.on('/b7/testsubscribe/data/values_on_callback_test/*', {
            "event_type": "set",
            "initialCallback": true
          }, function (message) {

            expect(message.updated).to.be(true);
            callback();

          }, function (e, reference, response) {
            if (e) return callback(e);
            try {

              expect(response.length).to.be(2);
              expect(response[0].test).to.be('data');
              expect(response[1].test).to.be('data1');

              dataComponent.set('/b7/testsubscribe/data/values_on_callback_test/1', {
                "test": "data",
                "updated": true
              }, function (e) {
                if (e) return callback(e);
              });

            } catch (e) {
              return callback(e);
            }
          });
        });
      });
    });

    it('should subscribe and get initial values emitted immediately', function (callback) {

      var caughtEmitted = 0;

      dataComponent.set('/b7/testsubscribe/data/values_emitted_test/1', {"test": "data"}, function (e) {
        if (e) return callback(e);

        dataComponent.set('/b7/testsubscribe/data/values_emitted_test/2', {"test": "data1"}, function (e) {
          if (e) return callback(e);

          dataComponent.on('/b7/testsubscribe/data/values_emitted_test/*', {
            "event_type": "set",
            "initialEmit": true
          }, function (message, meta) {

            caughtEmitted++;

            if (caughtEmitted == 2) {
              expect(message.test).to.be("data1");
              callback();
            }

          }, function (e) {
            if (e) return callback(e);
          });
        });
      });
    });

    it('can unsubscribe', function (done) {
      var received = [];
      dataComponent.on('/some/path/five', function (data, meta) {
        received.push(data);
      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/five', {key: 1}) // <--------------- 1
          .then(function () {
            return dataComponent.set('/some/path/five', {key: 1}) // <------ 2
          })
          .then(function () {
            return dataComponent.off('/some/path/five') // <------------- unsub
          })
          .then(function () {
            return dataComponent.set('/some/path/five', {key: 1}) // <------- 3
          })
          .then(function () {
            received.length.should.equal(2);
            done();
          })
          .catch(done)
      });
    })

    it('can unsubscribe from all', function (done) {
      var received = [];
      dataComponent.on('/some/path/six', function (data, meta) {
        received.push(data);
      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/six', {key: 1}) // <--------------- 1
          .then(function () {
            return dataComponent.set('/some/path/six', {key: 1}) // <------ 2
          })
          .then(function(){
           return dataComponent.offAll()
          })
          .then(function () {
            return dataComponent.set('/some/path/six', {key: 1}) // <------- 3
          })
          .then(function () {
            received.length.should.equal(2);
            done();
          })
          .catch(done)
      });
    });

    it('can unsubscribe from a path', function (done) {
      var received = [];
      dataComponent.on('/some/path/seven', function (data, meta) {
        received.push(data);
      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/seven', {key: 1}) // <--------------- 1
          .then(function () {
            return dataComponent.set('/some/path/seven', {key: 1}) // <------ 2
          })
          .then(function(){
            return dataComponent.offPath('/some/path/seven');
          })
          .then(function () {
            return dataComponent.set('/some/path/seven', {key: 1}) // <------- 3
          })
          .then(function () {
            received.length.should.equal(2);
            done();
          })
          .catch(done)
      });
    })

    it('can delete', function (done) {
      dataComponent.set('some/path/eight', 6)
        .then(function () {
          return dataComponent.get('some/path/eight');
        })
        .then(function (six) {
          six.value.should.equal(6);
          return dataComponent.remove('some/path/eight')
        })
        .then(function (res) {
          return dataComponent.get('some/path/eight');
        })
        .then(function (res) {
          should.not.exist(res);
          done();
        })
        .catch(done)
    });

    it('can get paths', function (done) {
      require('bluebird').all([
          dataComponent.set('this/one', 1),
          dataComponent.set('this/two', 2),
          dataComponent.set('this/three', 3),
        ])
        .then(function () {
          return dataComponent.getPaths('this/*')
        })
        .then(function (paths) {
          paths.length.should.equal(3);
          done();
        })
        .catch(done);
    })

    it('can subscribe to data change with events', function (done) {

      dataEvents.on('/some/path/five', function (data) {

        data.should.property('key', 'VALUE');
        dataEvents.off('/some/path/five', function (data, meta) {
          done();
        });

      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/five', {key: 'VALUE'}, function (e) {
          if (e) return done(e);
        })
      });
    })

  });

  context('shared use', function () {

    it('can set from one component and getted from another', function (done) {
      meshInstance.exchange.module1.setSharedData('/my/thing', {'y': 'x'})
        .then(function () {
          return meshInstance.exchange.module2.getSharedData('/my/thing')
        })
        .then(function (d) {
          d.y.should.equal('x');
          done();
        })
        .catch(done);
    });

  });

  //require('benchmarket').stop();

});

<commit_msg>issue-303 - fix date in RELEASES.md<commit_after>describe('b7 - shared data component', function () {

  this.timeout(120000);

  //require('benchmarket').start();
  //after(//require('benchmarket').store());

  var should = require('chai').should();
  var Mesh = require('../');
  var meshInstance;
  var dataEvents;
  var config;
  var expect = require('expect.js');
  var TestModule1 = {
    setSharedData: function ($happn, path, data, callback) {
      $happn.exchange.data.set(path, data, callback);
    }
  }

  var TestModule2 = {
    getSharedData: function ($happn, path, callback) {
      $happn.exchange.data.get(path, callback);
    }
  }

  before(function (done) {
    var _this = this;
    Mesh.create(config = {
      port: 9898,
      modules: {
        'module1': {
          instance: TestModule1
        },
        'module2': {
          instance: TestModule2
        }
      },

      components: {
        'data': {},
        'module1': {},
        'module2': {},
      }

    }).then(function (mesh) {
      meshInstance = mesh;
      dataComponent = mesh.exchange.data;
      dataEvents = mesh.event.data;
      done();
    }).catch(done);
  });

  after(function (done) {
    meshInstance.stop({reconnect: false}, done);
  });

  context('direct use', function () {

    it('can set and get with opts', function (done) {
      dataComponent.set('some/path/one', {key: 'value'}, {}, function (e, result) {
        if (e) return done(e);
        dataComponent.get('some/path/one', {}, function (e, result) {
          if (e) return done(e);
          result.key.should.equal('value');
          done();
        });
      });
    });

    it('can set and get without opts', function (done) {

      dataComponent.set('some/path/two', {key: 'value'}, function (e, result) {
        if (e) return done(e);
        dataComponent.get('some/path/two', function (e, result) {
          if (e) return done(e);
          result.key.should.equal('value');
          done();
        });
      });
    });

    it('can subscribe with opts', function (done) {

      dataComponent.on('/some/path/three', {}, function (data, meta) {
        data.should.eql({key: 'VAL'});
        done();
      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/three', {key: 'VAL'}, {}, function (e) {
          if (e) return done(e);
        })
      });
    });

    it('can subscribe without opts', function (done) {
      dataComponent.on('/some/path/four', function (data, meta) {
        data.should.eql({key: 'VALUE'});
        done();
      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/four', {key: 'VALUE'}, function (e) {
          if (e) return done(e);
        })
      });
    });

    it('respects noPublish set option', function (done) {
      meshInstance.event.data.on('/some/path/testNoPublish', function (data, meta) {
        delete data._meta;
        data.should.eql({val: 'must be emitted'});
        // allow time for possible wrong event
        setTimeout(done, 1000);
      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/testNoPublish', {val: 'must not be emitted'}, {noPublish: true}, function (e) {
          if (e) return done(e);
          dataComponent.set('/some/path/testNoPublish', {val: 'must be emitted'}, {noPublish: false}, function (e) {
            if (e) return done(e);
          })
        })
      });
    });

    it('should subscribe and get an initial value on the callback', function (callback) {

      dataComponent.set('/b7/testsubscribe/data/value_on_callback_test', {"test": "data"}, function (e) {
        if (e) return callback(e);

        dataComponent.on('/b7/testsubscribe/data/value_on_callback_test', {
          "event_type": "set",
          "initialCallback": true
        }, function (message) {

          expect(message.updated).to.be(true);
          callback();

        }, function (e, reference, response) {
          if (e) return callback(e);
          try {

            expect(response.length).to.be(1);
            expect(response[0].test).to.be('data');

            dataComponent.set('/b7/testsubscribe/data/value_on_callback_test', {
              "test": "data",
              "updated": true
            }, function (e) {
              if (e) return callback(e);
            });

          } catch (e) {
            return callback(e);
          }
        });

      });

    });

    it('should subscribe and get initial values on the callback', function (callback) {

      dataComponent.set('/b7/testsubscribe/data/values_on_callback_test/1', {"test": "data"}, function (e) {
        if (e) return callback(e);

        dataComponent.set('/b7/testsubscribe/data/values_on_callback_test/2', {"test": "data1"}, function (e) {
          if (e) return callback(e);

          dataComponent.on('/b7/testsubscribe/data/values_on_callback_test/*', {
            "event_type": "set",
            "initialCallback": true
          }, function (message) {

            expect(message.updated).to.be(true);
            callback();

          }, function (e, reference, response) {
            if (e) return callback(e);
            try {

              expect(response.length).to.be(2);
              expect(response[0].test).to.be('data');
              expect(response[1].test).to.be('data1');

              dataComponent.set('/b7/testsubscribe/data/values_on_callback_test/1', {
                "test": "data",
                "updated": true
              }, function (e) {
                if (e) return callback(e);
              });

            } catch (e) {
              return callback(e);
            }
          });
        });
      });
    });

    it('should subscribe and get initial values emitted immediately', function (callback) {

      var caughtEmitted = 0;

      dataComponent.set('/b7/testsubscribe/data/values_emitted_test/1', {"test": "data"}, function (e) {
        if (e) return callback(e);

        dataComponent.set('/b7/testsubscribe/data/values_emitted_test/2', {"test": "data1"}, function (e) {
          if (e) return callback(e);

          dataComponent.on('/b7/testsubscribe/data/values_emitted_test/*', {
            "event_type": "set",
            "initialEmit": true
          }, function (message, meta) {

            caughtEmitted++;

            if (caughtEmitted == 2) {
              expect(message.test).to.be("data1");
              callback();
            }

          }, function (e) {
            if (e) return callback(e);
          });
        });
      });
    });

    it('can unsubscribe', function (done) {
      var received = [];
      dataComponent.on('/some/path/five', function (data, meta) {
        received.push(data);
      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/five', {key: 1}) // <--------------- 1
          .then(function () {
            return dataComponent.set('/some/path/five', {key: 1}) // <------ 2
          })
          .then(function () {
            return dataComponent.off('/some/path/five') // <------------- unsub
          })
          .then(function () {
            return dataComponent.set('/some/path/five', {key: 1}) // <------- 3
          })
          .then(function () {
            received.length.should.equal(2);
            done();
          })
          .catch(done)
      });
    })

    it('can unsubscribe from all', function (done) {
      var received = [];
      dataComponent.on('/some/path/six', function (data, meta) {
        received.push(data);
      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/six', {key: 1}) // <--------------- 1
          .then(function () {
            return dataComponent.set('/some/path/six', {key: 1}) // <------ 2
          })
          .then(function () {
            return dataComponent.offAll()
          })
          .then(function () {
            return dataComponent.set('/some/path/six', {key: 1}) // <------- 3
          })
          .then(function () {
            received.length.should.equal(2);
            done();
          })
          .catch(done)
      });
    });

    it('can unsubscribe from a path', function (done) {
      var received = [];
      dataComponent.on('/some/path/seven', function (data, meta) {
        received.push(data);
      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/seven', {key: 1}) // <--------------- 1
          .then(function () {
            return dataComponent.set('/some/path/seven', {key: 1}) // <------ 2
          })
          .then(function () {
            return dataComponent.offPath('/some/path/seven');
          })
          .then(function () {
            return dataComponent.set('/some/path/seven', {key: 1}) // <------- 3
          })
          .then(function () {
            received.length.should.equal(2);
            done();
          })
          .catch(done)
      });
    })

    it('can delete', function (done) {
      dataComponent.set('some/path/eight', 6)
        .then(function () {
          return dataComponent.get('some/path/eight');
        })
        .then(function (six) {
          six.value.should.equal(6);
          return dataComponent.remove('some/path/eight')
        })
        .then(function (res) {
          return dataComponent.get('some/path/eight');
        })
        .then(function (res) {
          should.not.exist(res);
          done();
        })
        .catch(done)
    });

    it('can get paths', function (done) {
      require('bluebird').all([
        dataComponent.set('this/one', 1),
        dataComponent.set('this/two', 2),
        dataComponent.set('this/three', 3),
      ])
        .then(function () {
          return dataComponent.getPaths('this/*')
        })
        .then(function (paths) {
          paths.length.should.equal(3);
          done();
        })
        .catch(done);
    })

    it('can subscribe to data change with events', function (done) {

      dataEvents.on('/some/path/five', function (data) {

        data.should.property('key', 'VALUE');
        dataEvents.off('/some/path/five', function (data, meta) {
          done();
        });

      }, function (e) {
        if (e) return done(e);
        dataComponent.set('/some/path/five', {key: 'VALUE'}, function (e) {
          if (e) return done(e);
        })
      });
    })

  });

  context('shared use', function () {

    it('can set from one component and getted from another', function (done) {
      meshInstance.exchange.module1.setSharedData('/my/thing', {'y': 'x'})
        .then(function () {
          return meshInstance.exchange.module2.getSharedData('/my/thing')
        })
        .then(function (d) {
          d.y.should.equal('x');
          done();
        })
        .catch(done);
    });

  });

  //require('benchmarket').stop();

});

<|endoftext|>