Serving dynamic .js files with node.js - javascript

I'm searching for some kind of 'template/macro engine' for javascript files.
What I need to do is serving javascript files to the browser. These javascript files are mostly static but vary in some ways.
Here's what I'm basically doing at the moment:
app.get('/api/api.js', function (req, res) {
fs.readFile(
path.join(__dirname, './libraries/api.js'),
{ encoding: 'utf8' },
function (err, data) {
if (err) throw err;
res.set('Content-Type', 'text/javascript');
res.send(data);
});
});
Now I need to add/replace some few lines of code within the read file before serving it to the client.
Update:
Here are some examples:
var host = 'http://mydomain.com/';
var domains = ['mydomain1.com', 'mydomain2.org'];
Yes I could use basic search and replace but I wondered if there's a more generic solution.

I solved it writing my own little render engine as you can see here: http://jsfiddle.net/g5PEp/
var ApiRenderer = function(fileContent) {
var fileContent = fileContent || '';
var data = {};
function sourroundString(str, char) {
char = char || '\'';
return char + str + char;
}
function render() {
var result = fileContent,
str;
for(var key in data) {
var substr = '/*{{' + key + '}}*/';
if(typeof data[key] === 'string') {
str = sourroundString(data[key]);
}
if(Object.prototype.toString.call( data[key] ) === '[object Array]') {
str = '[';
for(var i = 0; i < data[key].length; i++) {
str += sourroundString(data[key][i]);
if(i !== data[key].length - 1) {
str += ',';
}
}
str += ']';
}
result = result.replace(substr, str);
}
return result;
}
return {
setData: function(key, value) {
return data[key] = value;
},
render: function() {
return render();
}
}
};
This is working for me at the moment but I'm a bit sad that this doesn't allow me to write valid javascript code in my templates.

Related

Node.js writing data to file results in additional written characters

as the title says, when i try to save data to a file using the filesystem function fs.writeFile(), sometimes the file has extra data on it.
My code:
fs.writeFile('path', JSON.stringify(data), function (err) {});
May be its because of the JSON.stringify(), or its a problem of the fs.writeFile.
If you need additional information, im willing to give it!
More code:
function CheckLeaderBoards(player, tag, points) {
fs.readFile(datapath + '/data/topplayers.json', function(err, data) {
var lb = JSON.parse(data);
var isin = false;
for (let i = 0; i < lb.length; i++) {
if (lb[i].tag == tag) {
isin = true;
lb[i].points = points;
break;
}
}
if (!isin)
lb.push({"player": player.toString(), "tag": tag.toString(), "points": parseInt(points)});
for (let i = 0; i < lb.length; i++) {
var bestpoints = -100;
var bestindex = 0;
for (let j = i; j < lb.length; j++) {
if (lb[j].points > bestpoints) {
bestpoints = lb[j].points;
bestindex = j;
}
}
lb = ChangeArrayIndex(lb, bestindex, i);
}
fs.writeFile(datapath + '/data/topplayers.json', JSON.stringify(lb), function (err) {});
})
}
function ChangeArrayIndex(array, fromIndex, toIndex) {
var arr = [];
for (let i = 0; i < array.length; i++) {
if (i == toIndex) arr.push(array[fromIndex]);
if (i == fromIndex) continue;
arr.push(array[i]);
}
return arr;
}
Basicly i want to write a leaderboard, i have an array of JSON Objects, ex: {"player":"Bob","tag":"a10b","points": 10},...
To write in a file, you to open the file, in callback you will get file descriptor that descriptor will be used to write in the file. Please see example:
fs.open(datapath + '/data/topplayers.json', 'wx', function(error, fileDescriptor){
if(!error && fileDescriptor){
var stringData = JSON.stringify(data);
fs.writeFile(fileDescriptor, stringData, function(error){
if(!error){
fs.close(fileDescriptor, function(error){
if(!error){
callback(false);
}else{
callback('Error in close file');
}
});
}else{
callback('Error in writing file.');
}
});
}
}
Ok, If you want to update the file, please check this code:
const myUpdaterFcn = (dir,file,data,callback)=>{
//dir looks like this: '/your/existing/path/file.json'
// Open the file for writing (using the keyword r+)
fs.open(dir, 'r+', (err, fileDescriptor)=>{
if(!err && fileDescriptor){
// Convert data to string
const stringData = JSON.stringify(data)
// Truncate the file
fs.truncate(fileDescriptor,err=>{
if(!err){
// Write to file and close it
fs.writeFile(fileDescriptor, stringData,err=>{
if(!err){
fs.close(fileDescriptor,err=>{
if(!err){
callback(false)
} else {
callback('Error closing existing file')
}
})
} else {
callback('Error writing to existing file')
}
})
} else {
callback('Error truncating file')
}
})
} else {
callback('Could not open file for updating, it may not exist yet')
}
})
}
Good Luck.

indexOf() doesn't work

I got an array of strings and I'd like to check if any of these strings contain a certain substring.
The following code is an example of what I need to achieve: the function gets a message from an user and searches through a series of URLs the one that contains the user's message in the title:
var redditSubModule = "node";
var http = require('https');
var url = "https://www.reddit.com/r/soccerstreams/new/.json";
var i = 0;
var subject = (ctx.message.text).substring(8);
var request = http.get(url, function(response) {
var json = '';
response.on('data', function(chunk) {
json += chunk;
});
response.on('end', function() {
var redditResponse = JSON.parse(json);
redditResponse.data.children.forEach(function(child) {
if(child.data.domain !== 'self.node') {
url = child.data.url;
console.log('URL : ' + url);
if (url.indexOf(subject) > -1) {
console.log('URL : ' + url);
} else {
console.log("nothing...");
}
i++;
}
});
})
});
request.on('error', function(err) {
console.log(err);
});
If you try and fill subject with "USA" (because there's a thread with that string in the title) the indexOf doesn't seem to work and it prints a list of "nothing..."
Logging the typeof url gives string as type so I don't know what is going on here...
What am I doing wrong ?
You can use array.prototype.find and array.prototype.includes:
var strs = ["aaa", "xxx", "yyy_leon"];
var search = "leon";
var found = strs.find(s => s.includes(search));
if (found) {
console.log("found: ", found);
} else {
console.log("nothing");
}

How to load JSON feature in Javascript for IE8?

I am trying to load JSON feature in JavaScript for IE8 in a comparability mode.
I was advised to use douglascrockford/JSON-js to get JSON loaded in obsolete browsers.
Here is what I have done. I added a new file in my resources folder and called it json2.js Then from the JSON-js project I copied the json2.js file content and pasted it into my json2.js file and included the file resources/json2.js into my app.
Now, I am trying to use JSON.stringify to convert an object into json string which is giving me the following error
But when I use JSON.stringify(records) in IE8 under compatibility mode I get this error
Line: 314
Char: 21
Error: Invalid procedure call or argument
Code: 0
Here is what I have done
HTML Markup
<div id="d">Click Here</div>
<div id="s"></div>
Javascript code
var records = {};
$(function(e){
records['123'] = {};
records['456'] = {};
records['123']['rec_id'] = 4456;
records['123']['created_at'] = '';
records['123']['assigned_at'] = '';
records['123']['sys_id'] = 1745;
records['456']['rec_id'] = 4456;
records['456']['created_at'] = '';
records['456']['assigned_at'] = '';
records['456']['sys_id'] = 1745;
$.each(records, function(callID, record){
record['campaign_id'] = '1';
record['offset'] = 123;
record['attempt'] = '7';
record['phone'] = '800-123-4567';
record['identity'] = 123;
record['code'] = 'Some Code';
record['notes'] = 'Some notes';
record['completed_by'] = 'Mike A';
record['name'] = null;
record['completed_at'] = "";
});
$('#d').click(function(e){
$('#s').text( JSON.stringify(records) );
});
});
the above code can be found in the following jFiddle https://jsfiddle.net/4632wf5n/
What can I do to convert my object into json string in IE8 with comparability mode?
Although according to Mozilla Developer Network (MDN), JSON.stringify is supported in IE8, in case if it's not, then you can use the polyfill (i.e. if it's not supported by browser, then use custom implementation). If Douglas JSON.js is not working then, MDN also provides a code for the polyfill, and I have copied that here. Just insert it before any other scripts, and JSON.stringify would work in IE6+ browsers where it's not supported.
if (!window.JSON) {
window.JSON = {
parse: function(sJSON) { return eval('(' + sJSON + ')'); },
stringify: (function () {
var toString = Object.prototype.toString;
var isArray = Array.isArray || function (a) { return toString.call(a) === '[object Array]'; };
var escMap = {'"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t'};
var escFunc = function (m) { return escMap[m] || '\\u' + (m.charCodeAt(0) + 0x10000).toString(16).substr(1); };
var escRE = /[\\"\u0000-\u001F\u2028\u2029]/g;
return function stringify(value) {
if (value == null) {
return 'null';
} else if (typeof value === 'number') {
return isFinite(value) ? value.toString() : 'null';
} else if (typeof value === 'boolean') {
return value.toString();
} else if (typeof value === 'object') {
if (typeof value.toJSON === 'function') {
return stringify(value.toJSON());
} else if (isArray(value)) {
var res = '[';
for (var i = 0; i < value.length; i++)
res += (i ? ', ' : '') + stringify(value[i]);
return res + ']';
} else if (toString.call(value) === '[object Object]') {
var tmp = [];
for (var k in value) {
if (value.hasOwnProperty(k))
tmp.push(stringify(k) + ': ' + stringify(value[k]));
}
return '{' + tmp.join(', ') + '}';
}
}
return '"' + value.toString().replace(escRE, escFunc) + '"';
};
})()
};
}

How to correct structure an asynchronous program to ensure correct results?

I have a nodejs program that requests a series of XML files, parses them and then puts the output in an array which is written to disk as a CSV file.
The program mostly works, however occasionally the files end up in the wrong order in the array.
I want the order of the results to be in the same as the order as the URLs. The URLs are stored in an array, so when I get the XML file I check what the index of the URL was in the source array and insert the results at the same index in the destination URL.
can anyone see the flaw that is allowing the results to end up in the wrong order?
addResult = function (url, value, timestamp) {
data[config.sources.indexOf(url)] = {
value : value,
timestamp : timestamp,
url : url
};
numResults++;
if (numResults === config.sources.length) { //once all results are in build the output file
createOutputData();
}
}
fs.readFile("config.json", function (fileError, data) {
var eachSource, processResponse = function (responseError, response, body) {
if (responseError) {
console.log(responseError);
} else {
parseXML(body, {
explicitArray : false
}, function (xmlError, result) {
if (xmlError) {
console.log(xmlError);
}
addResult(response.request.uri.href, result.Hilltop.Measurement.Data.E.I1, moment(result.Hilltop.Measurement.Data.E.T));
});
}
};
if (fileError) {
console.log(fileError);
} else {
config = JSON.parse(data); //read in config file
for (eachSource = 0; eachSource < config.sources.length; eachSource++) {
config.sources[eachSource] = config.sources[eachSource].replace(/ /g, "%20"); //replace all %20 with " "
request(config.sources[eachSource], processResponse); //request each source
}
}
});
var writeOutputData, createOutputData, numResults = 0, data = [], eachDataPoint, multipliedFlow = 0;
writeOutputData = function (output, attempts) {
csv.writeToPath(config.outputFile, [ output ], {
headers : false
}).on("finish", function () {
console.log("successfully wrote data to: ", config.outputFile);
}).on("error", function (err) { //on write error
console.log(err);
if (attempts < 2) { //if there has been less than 3 attempts try writing again after 500ms
setTimeout(function () {
writeOutputData(output, attempts + 1);
}, 500);
}
});
};
createOutputData = function () {
var csvTimestamp, output = [];
if (config.hasOwnProperty("timestampFromSource")) {
csvTimestamp = data.filter(function (a) {
return a.url === config.sources[config.timestampFromSource];
})[0].timestamp.format("HHmm");
console.log("timestamp from source [" + config.timestampFromSource + "]:", csvTimestamp);
} else {
csvTimestamp = data.sort(function (a, b) { //sort results from oldest to newest
return a.timestamp.unix() - b.timestamp.unix();
});
csvTimestamp = csvTimestamp[0].timestamp.format("HHmm");//use the oldest date for the timestamp
console.log("timestamp from oldest source:", csvTimestamp);
}
//build array to represent data to be written
output.push(config.plDestVar); //pl var head address first
output.push(config.sources.length + 1); //number if vars to import
output.push(csvTimestamp); //the date of the data
for (eachDataPoint = 0; eachDataPoint < data.length; eachDataPoint++) { //add each data point
if (config.flowMultiplier) {
multipliedFlow = Math.round(data[eachDataPoint].value * config.flowMultiplier); //round to 1dp and remove decimal by *10
} else {
multipliedFlow = Math.round(data[eachDataPoint].value * 10); //round to 1dp and remove decimal by *10
}
if (multipliedFlow > 32766) {
multipliedFlow = 32766;
} else if (multipliedFlow < 0) {
multipliedFlow = 0;
}
output.push(multipliedFlow);
}
console.log(output);
writeOutputData(output, 0); //write the results, 0 is signalling first attempt
};
I think that the url to index code needs debugging.
Here is an example that uses an object that is pre-populated with keys in the for loop.
`
var http = require('http');
var fs = require("fs");
var allRequestsComplete = function(results){
console.log("All Requests Complete");
console.log(results);
};
fs.readFile("urls.json", function (fileError, data) {
var responseCount = 0;
if (fileError) {
console.log(fileError);
} else {
var allResponses = {};
config = JSON.parse(data); //read in config file
var requestComplete = function(url, fileData){
responseCount++;
allResponses[url] = fileData;
if(responseCount===config.sources.length){
allRequestsComplete(allResponses);
}
};
for (var eachSource = 0; eachSource < config.sources.length; eachSource++) {
(function(url){
allResponses[url] = "Waiting";
http.get({host: url,path: "/"}, function(response) {
response.on('error', function (chunk) {
requestComplete(url, "ERROR");
});
var str = ''
response.on('data', function (chunk) {
str += chunk;
});
response.on('end', function () {
requestComplete(url, str);
});
});
}(config.sources[eachSource].replace(/ /g, "%20").replace("http://", "")));
}
}
});
`
I agree with #Kevin B, you cannot assume that async callbacks will return in the same order of which you send them. However, you could ensure the order, by adding an index function on processResponse.
say you add the following to addResult
addResult = function (index, url, value, timestamp) {
data[index] = {
value : value,
timestamp : timestamp,
url : url
};
numResults++;
if (numResults === config.sources.length) { //once all results are in build the output file
createOutputData();
}
}
and use an extra function to call your request
function doRequest(index, url) {
request(url, function(responseError, response, body) {
if (responseError) {
console.log(responseError);
} else {
parseXML(body, {
explicitArray : false
}, function (xmlError, result) {
if (xmlError) {
console.log(xmlError);
}
addResult(index, response.request.uri.href, result.Hilltop.Measurement.Data.E.I1, moment(result.Hilltop.Measurement.Data.E.T));
});
}
});
}
then you can also change your loop to:
for (eachSource = 0; eachSource < config.sources.length; eachSource++) {
config.sources[eachSource] = config.sources[eachSource].replace(/ /g, "%20"); //replace all %20 with " "
doRequest(eachSource, config.sources[eachSource]); //request each source
}

Set URL parameters without causing page refresh

How can you set URL parameters using History.pushState() to avoid browser refreshes? If there is a not a simple JS solution, is there already a popular library or built in function for jQuery?
Here is a relevant SO question, where the accepted answer does not actually work according to comments & my test (it removes the query string instead of updating a value): history.pushState() change query values
Just to be clear, I am referring to the URL parameters in a query string:
http://google.com/page?name=don so we could change don to tim without causing a reload.
Here is one possible solution I found. However I'm nervous about using a JS library that only has 2 followers :P
You can just use queryString.push('my_param_key', 'some_new_value') from the small library below.
It will update your URL param using history.push, so the browser will not refresh.
It will only affect the param you wish to change, it will leave the path and other params unaffected.
/*!
query-string
Parse and stringify URL query strings
https://github.com/sindresorhus/query-string
by Sindre Sorhus
MIT License
*/
(function () {
'use strict';
var queryString = {};
queryString.parse = function (str) {
if (typeof str !== 'string') {
return {};
}
str = str.trim().replace(/^\?/, '');
if (!str) {
return {};
}
return str.trim().split('&').reduce(function (ret, param) {
var parts = param.replace(/\+/g, ' ').split('=');
var key = parts[0];
var val = parts[1];
key = decodeURIComponent(key);
// missing `=` should be `null`:
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
val = val === undefined ? null : decodeURIComponent(val);
if (!ret.hasOwnProperty(key)) {
ret[key] = val;
} else if (Array.isArray(ret[key])) {
ret[key].push(val);
} else {
ret[key] = [ret[key], val];
}
return ret;
}, {});
};
queryString.stringify = function (obj) {
return obj ? Object.keys(obj).map(function (key) {
var val = obj[key];
if (Array.isArray(val)) {
return val.map(function (val2) {
return encodeURIComponent(key) + '=' + encodeURIComponent(val2);
}).join('&');
}
return encodeURIComponent(key) + '=' + encodeURIComponent(val);
}).join('&') : '';
};
queryString.push = function (key, new_value) {
var params = queryString.parse(location.search);
params[key] = new_value;
var new_params_string = queryString.stringify(params)
history.pushState({}, "", window.location.pathname + '?' + new_params_string);
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = queryString;
} else {
window.queryString = queryString;
}
})();
Answering to the question in your comment, you'd be able to read those properties from history.state, a property that holds the value of the stat for the current URL. Whenever you go back and forward you'll receive a popstate event and you will be able tor read the state you pushed, which is far easier than dealing with urls.
Of course, when you go back or forward to a new entry in the history list pushed with pushState() or replaceState() the page does not reload.
You can read more about the History object in the MDN.
Here is a simple function I wrote it isn't as neat as the above answer but it does the trick...
function changeUrlParam (param, value) {
var currentURL = window.location.href;
var urlObject = currentURL.split('?');
var newQueryString = '?';
value = encodeURIComponent(value);
if(urlObject.length > 1){
var queries = urlObject[1].split('&');
var updatedExistingParam = false;
for (i = 0; i < queries.length; i++){
var queryItem = queries[i].split('=');
if(queryItem.length > 1){
if(queryItem[0] == param){
newQueryString += queryItem[0] + '=' + value + '&';
updatedExistingParam = true;
}else{
newQueryString += queryItem[0] + '=' + queryItem[1] + '&';
}
}
}
if(!updatedExistingParam){
newQueryString += param + '=' + value + '&';
}
}else{
newQueryString += param + '=' + value + '&';
}
window.history.replaceState('', '', urlObject[0] + newQueryString.slice(0, -1));
}

Categories

Resources