Parse JSON to create Object in Javascript - javascript

Maybe this was already discussed somewhere, I myself could not find an exact answer how to approach my problem:
I have a mutliniear «story» which executes code in each segment. I wrote a state machine, which initiates a new segment, whenever another segment calls that one. Each segment has an onEnter-, an onCheck and an onLeave function. As the name already says, the onEnter executes when the segment is called, the onCheck checks some input until some conditions are fullfilled (if yes they will lead to another segment) and onLeave executes just before the next segment is called.
Currently I just wrote a javascript object kinda like this:
var flow = {
seg1: {
onEnter: function() {
this.say('Seg1');
},
onCheck: function(data) {
if (data.condition) {
machine.callNextSeg('seg2');
} else if (data.condition2) {
machine.callNextSeg('seg3');
}
},
onLeave: function() {
}
},
seg2: {
onEnter: function() {
this.say('Seg2');
},
onGestureCheck: function(data) {
},
onLeave: function() {
}
},
seg3: {
onEnter: function() {
this.say('Seg3');
},
onGestureCheck: function(data) {
},
onLeave: function() {
}
}
};
The example is a bit simplified for understanding, but the code inside would be a little more complex.
I would rather like to have a JSON file, which is loaded, parsed and creates such an object. The JSON File should use a more simple and more abstract syntax to write. Especially to make it quicker.
I imagine something like this:
{
"seg1": {
"onEnter": {
"say": 'Seg1'
},
"onCheck": {
"condition1": "seg2",
"condition2": "seg3"
},
"onLeave": {
}
},
"seg2": {
"onEnter": {
"say": 'Seg2'
},
"onCheck": {
},
"onLeave": {
}
}
}
The conditions are booleans and if true the described segment should be called.
Would that be easy to parse the json, create an object, and create functions inside it for each onEnter, onCheck and onLeave? And also write the necessary if clauses?
Thanks for hints into the right direction.
cheers
J.

var flow = {...} is an object literal and is actually best suited for what you need to do, but I do understand the desire to have all of this "defined" in a JSON file. The issue there becomes the fact that the application processing your JSON file MUST understand/be Javascript (which defies the idea of having JSON as language agnostic - its name notwithstanding).
This might be what you need:
JavaScript Function Serialization
Also check the voted answer here:
What is the correct way to "serialize" functions in javascript for later use

Related

Trying to build nest functions within nested functions

working on a script and I thought dot notation would be a good way of building methods to use later on in the grander scheme of the script.
the original system would declare functions written as
memRead();
memReadGlobal();
memWrite();
memEtc();.......
but I wanted to change this to
mem.Read();
mem.Read.Global();
Here is an example
var mem = {
Read: {
function() {
console.log('Hello World')
},
Global:
function(key) {
console.log('Goodbye World')
},
},
}
I can call mem.Global just fine, but I can't call mem.Read
I can declare mem.Read if I add another object like Local(mem.Read.Local), but I feel like writing local is redundant and would like to avoid that.
Is there a way to create a nested function like I describe above?
You can do that, but not with an object initializer expression.
var mem = {
Read() {
console.log("Hello from Read");
}
};
mem.Read.Global = function() {
console.log("Hello from Global");
};
mem.Read();
mem.Read.Global();

Pass a new argument in Javascript code to target HTML5 attr

I currently have created a piece of Javascript code which checks a http response and if its a successful 200 code the traffic light shows green and if its not a 200 code the traffic light flashes red.
My current problem at the moment is I want to be able to pass Two arguments to the initializing function like so
requestResponses.init('/status', "ATTR");
The first attribute being the URL I want to test against and the second attribute being the value of the HTML5 data-request attribute I have created.
My reason for this as I want to have multiple traffic lights on the same page targeting different URL's simultaneously without effecting each other.
So for Example I create a data request attribute and give it a value of Four I then want to be able to hook it up like so
requestResponses.init('/status', "Four");
and start targeting elements like below within the code. This makes it that bit more modular and reusable.
.cp_trafficLight[data-request="Four"]
Please find my code attached and a working JSFiddle.
var requestResponses = {
greenLight: $('.cp_trafficLight_Light--greenDimmed'),
redLight: $('.cp_trafficLight_Light--redDimmed'),
greenBright: 'cp_trafficLight_Light--greenBright',
redBright: 'cp_trafficLight_Light--redBright',
settings: {
flashError: 400,
requestTime: 10000
},
init: function (url, targAttr) {
requestResponses.url = url;
requestResponses.getResponse(requestResponses.url, targAttr);
setInterval(function () {
if (requestResponses.errorCode === true) {
requestResponses.redLight.toggleClass(requestResponses.redBright);
}
}, requestResponses.settings.flashError);
},
successResponse: function (targAttr) {
requestResponses.errorCode = false;
requestResponses.redLight.removeClass(requestResponses.redBright);
requestResponses.greenLight.addClass(requestResponses.greenBright);
console.log(targAttr);
},
errorResponse: function (targAttr) {
requestResponses.greenLight.removeClass(requestResponses.greenBright);
},
getResponse: function (serverURL, targAttr) {
$.ajax(serverURL, {
success: function () {
requestResponses.errorCode = false;
requestResponses.successResponse(targAttr);
},
error: function () {
requestResponses.errorCode = true;
requestResponses.errorResponse(targAttr);
},
complete: function () {
setTimeout(function () {
requestResponses.getResponse(requestResponses.url);
}, requestResponses.settings.requestTime);
}
});
},
errorCode: false
}
requestResponses.init('/status', "TEST");
https://jsfiddle.net/8700h582/
Appreciate any help!
Then in your function you include a second parameter separated with a comma.
function foo(param1, param2) {
//Do something.
};
foo("1st", "2nd");
EDIT
i see now in your init include the second param for the light and in the init function pass this second param to your request and then from there pass it down to your error and respons function. And then return the function. That can be held in a var.

Loop in js for specific value without if

I use the following code which is working great but I wonder if in JS there is a way to avoid the if and to do it inside the loop, I want to use also lodash if it helps
for (provider in config.providers[0]) {
if (provider === "save") {
....
You can chain calls together using _.chain, filter by a value, and then use each to call a function for each filtered result. However, you have to add a final .value() call at the end for it to evaluate the expression you just built.
I'd argue that for short, simple conditional blocks, an if statement is easier and more readable. I'd use lodash- and more specifically chaining- if you are combining multiple operations or performing sophisticated filtering, sorting, etc. over an object or collection.
var providers = ['hello', 'world', 'save'];
_.chain(providers)
.filter(function(provider) {
return provider === 'save';
}).each(function(p) {
document.write(p); // your code here
}).value();
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.8.0/lodash.js"></script>
Edit: My mistake; filter does not have an overload where you can just supply a literal value. If you want to do literal value checking you have to supply a function as in my amended answer above.
I'd argue that what you have there is pretty good, clean and readable, but since you mentioned lodash, I will give it a try.
_.each(_.filter(config.providers[0], p => p === 'save'), p => {
// Do something with p
...
});
Note that the arrow function/lambda of ECMAScript 6 doesn't come to Chrome until version 45.
Basically, you are testing to see if config.providers[0], which is an object, contains a property called save (or some other dynamic value, I'm using a variable called provider to store that value in my example code below).
You can use this instead of using a for .. in .. loop:
var provider = 'save';
if (config.providers[0][provider] !== undefined) {
...
}
Or using #initialxy's (better!) suggestion:
if (provider in config.providers[0]) {
...
}
How about:
for (provider in config.providers[0].filter(function(a) {return a === "save"}) {
...
}
Strategy, you are looking for some kind of strategy pattern as,
Currenlty the save is hardcoded but what will you do if its coming from other varible – Al Bundy
var actions = {
save: function() {
alert('saved with args: ' + JSON.stringify(arguments))
},
delete: function() {
alert('deleted')
},
default: function() {
alert('action not supported')
}
}
var config = {
providers: [{
'save': function() {
return {
action: 'save',
args: 'some arguments'
}
},
notSupported: function() {}
}]
}
for (provider in config.providers[0]) {
(actions[provider] || actions['default'])(config.providers[0][provider]())
}
Push „Run code snippet” button will shows two pop-ups - be carefull
It is not clearly stated by the original poster whether the desired output
should be a single save - or an array containing all occurrences of
save.
This answer shows a solution to the latter case.
const providers = ['save', 'hello', 'world', 'save'];
const saves = [];
_.forEach(_.filter(providers, elem => { return elem==='save' }),
provider => { saves.push(provider); });
console.log(saves);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.19/lodash.js"></script>

Properly loading Template.templateName.rendered data context

I have a template, chartEditPreview, that runs a D3.js chart-drawing function once the rendered callback fires. It looks like this:
Template.chartEditPreview.rendered = function() {
drawChart(".chart-container", this.data);
}
Where .chart-container is the target div and this.data is the data for the object in the DB currently being accessed. Problem is, this.data often returns null, seemingly at random. It looks like this has something to do with how the publish/subscribe pattern works — Iron Router (which I'm using) lets the templates render and then hot-pushes the data into those templates.
My question is (hopefully) pretty simple: how can I make sure this.data is actually full of DB data before drawChart is run? Should I be doing this in some other way, instead of calling it on the rendered callback?
I'm thinking of storing the DB data in a Session variable during the routing and calling that from rendered, but it seems like an extra step, and I'm not certain it'll fix this problem. The chart's also not rendered only once on the page — it's interactive, so it needs to be redrawn every time the database object is updated via one of the inputs on screen.
Any help would be much appreciated. Thanks!
For reference, here's what my routes.js looks like:
Router.route('/chart/edit/:_id', {
name: 'chart.edit',
layoutTemplate: 'applicationLayout',
yieldRegions: {
'chartEditType': { to: 'type' },
'chartEditPreview': { to: 'preview' },
'chartEditOutput': { to: 'output' },
'chartEditAside': { to: 'aside' },
'chartEditEmbed': { to: 'embed' }
},
data: function () {
return Charts.findOne({_id: this.params._id});
},
waitOn: function () {
return Meteor.subscribe('chart', this.params._id);
}
});
And my publications.js:
Meteor.publish("chart", function (id) {
return Charts.find({ _id: id });
});
This is a common problem with Meteor. While the subscription might be ready (you should check for it like Ethaan shows) , that does not mean the find() function actually had time to return something.
Usually I solve it with some defensive code, i.e:
Template.chartEditPreview.rendered = function () {
if(this.data)
drawChart(".chart-container", this.data);
// else do nothing until the next deps change
}
Of course this is not as clean as it should be, but as far as I know the only way to solve problems like this properly.
Updated answer
In this case we need a dependency to trigger rerun on data change. Iron router solves this for us:
Template.chartEditPreview.rendered = function () {
var data = Router.current() && Router.current().data();
if(data)
drawChart(".chart-container", data);
// else do nothing until the next deps change
}
add this.ready() into the data:function
data: function () {
if(this.ready()){
return Charts.findOne({_id: this.params._id});
}else{
this.render('loading')
}
},
Something using data and waitOn could be a little bit tricky
Template.chartEditPreview.rendered = function() {
Meteor.setTimeout(function(){
drawChart(".chart-container", this.data);
},1000)
}

jQuery + Javascript - accessing object fields in anonymous function

I'm not into JavaScript OOP, so I've made an object with some fields which contains some functions to invoke.
var test = {
questions: [],
addQuestion: function(questionTitle, possibleAnwsers)
{
// not really important
},
appendQuestionToHTML: function(question)
{
// not really important
},
makeQuestionFieldsEditable: function($questionNode)
{
$questionNode.find(".questionTitle").first(function(){this.changeTextOnClick($(this));});
$questionNode.find(".questionChoice").each(function(){this.changeTextOnClick($(this));});
},
changeTextOnClick: function($spanElement)
{
// not really important
}
};
Following object in makeQuestionFieldsEditable() function looks for ".questionTitle"-class node and all of ".questionChoice"-class nodes invoke another function for them.
The problem is that using this in anonymous function references to itself, not function saved on field changeTextOnClick.
Javascript/JQuery wants to invoke this function on HTMLDivElement, which doesn't exists.
Is there any solution?
You can do the trick using a reference to your this variable :
makeQuestionFieldsEditable: function($questionNode)
{
var that = this;
$questionNode.find(".questionTitle").first(function(){that.changeTextOnClick($(this));});
$questionNode.find(".questionChoice").each(function(){that.changeTextOnClick($(this));});
},
I think all you need to do is change 'this' to 'test' (the variable you have assigned this object to).

Categories