Javascript String replacer for custom template - javascript

I would like to get some ideas how to achieve the following task.
I'm writing a lightweight template language. That takes any array or json object and replaces the string-values with values from my local data store.
Please let me illustrate how it works:
var obj = {
prop: "_p{propnameA}",
secondprop: "_p{propnameB}",
thirdprop: "Hello, this is \"_p{propnameC}\" and _p{propnameD},
arr: [
"textvalue", "propB value = _p{propB}"
]
}
I've wrote an algorithm that iterates over each property of each json or array. Now i need a fast way to replace all my template-tags to their actual values.
I would like to use different types of template-tags:
_p{...}
_c{...}
_v{...}
etc.
each template-tag means something different in my program. For Example: the template-tag _p{} calls a method in my application with the parameter of the tag-value. _p{propval} is a equivalent to myApp.getProperty("propval")
Other tags call other methods of my application.
I am thinking about using a string.replace with a regular expression for my tags. But i run into two problems:
How to write this regular expression?
How to handle non-string return values?
the evaluated value of a tag must not always be a string. it could also be a more complex data type like an array or json object. In my first example code at the top of this question the resulting value for "_p{propnameA}" could be an array like [1,2,3,4]. Or _p{propnameB} could be a number and so my example on top should evaluate like:
obj = {
prop: [1, 2, 3, 4],
secondprop: 827,
thirdprop: "Hello, this is \"valueC\" and valueD",
arr: ["textvalue", "propE value = 827"]
}
obviously obj.secondprop should not have the string value "827" but the number instead while obj.arr[1] should be a string.
Do you got some smart ideas how to do this?
Thank you very much for any help!

If I understood correctly, you're looking for something like this:
// evaluate a single placeholder like _p{foo}
function evalPlaceholder(prefix, content) {
switch(prefix) {
case "_p": do_this();
case "_c": do_that();
//etc
}
}
// eval a string with placeholders
function evalTemplate(str) {
var m = str.match(/^(_\w){([^{}]+)}$/);
if(m) {
// the whole string _is_ a placeholder
return evalPlaceholder(m[1], m[2]);
}
// otherwise, the string can _contain_ placeholders
return str.replace(/(_\w){(.+?)}/g, function(_, $1, $2) {
return evalPlaceholder($1, $2);
});
}
// walk an object recursively and eval all templates
function evalObject(obj) {
Object.keys(obj).forEach(function(k) {
var v = obj[k];
if(typeof v == "object")
evalObject(v)
else
obj[k] = evalTemplate(v);
})
}

first, you can cast numeric values to strings in two ways, which I am sure you have seen something like this before:
1- toString function call: var x = 825; console.log(x.toString())
2- adding the number to a string: var x = '' + 825
so if you don't want numeric values, but only strings, just make sure you convert the value to string (even if it's a string nothing will happen) before you use it.
second, I don't think I really got your problem, but from what I got, your problem is much simpler that a regex, you're only replacing well defined string, so while iterating over the values all you need is:
var p = prop.toString();
if(p.startsWith("_p") {
p.replace("_p", "_c)
}
I hope this is what you are looking for

Related

Pass an array in as function parameter or use array.split in extendscript

I'm using cep (csinterface) to communicate with extendscript and I need to pass an array in a fonction. The problem is that extendscript convert my array in a string. I wanted to send a string with delimiter at my function but when I use array.split() I got an evalscript error.
In the index.js
let stringNotes = "2287,3474|2268,3430|2255,3398|2255,3360|2255,3315|2255,3264|2255,3207|2261,3162|2331,3047|2389,3003|2433,2977|2484,2965|2541,2946|2580,2946|2618,2946|2650,2952|2688,2971|2720,2990|2745,3022|2764,3054|2777,3086|2790,3124|2803,3162|2803,3207|2803,3251|2803,3296|2783,3360|2752,3411|2726,3449|2707,3493|2688,3519|2637,3557|2611,3570|2592,3576|2573,3576|2554,3589|2548,3589|2541,3589|2535,3589|2529,3589|2516,3589|2510,3589|2503,3583|2497,3576|2497,3557|2497,3551|2497,3525|2497,3512|2497,3493|2490,3481|2490,3455|2478,3442|2478,3423|2478,3417|2471,3398|2465,3391|2459,3372|2452,3366|2446,3366*2401,3328|2389,3321|2382,3321|2376,3321|2369,3315|2350,3309|2344,3302";
stringNotes = stringNotes.split("*");
for(let i=0;i<stringNotes.length;i++) { stringNotes[i] = stringNotes[i].split("|"); for(let j=0;j<stringNotes[i].length;j++) {
stringNotes[i][j] = stringNotes[i][j].split(",");
stringNotes[i][j][0] = parseInt(stringNotes[i][j][0]);
stringNotes[i][j][1] = parseInt(stringNotes[i][j][1]); } }
console.log(stringNotes);
for(let i=0;i<stringNotes.length;i++) { //Call evalscript and send my list to evalscript csInterface.evalScript(`DrawShape('${JSON.stringify(stringNotes[i])}')`, function(res){console.log(res)}); }
NB : the console.log of my array stringNotes in index.js display the array correctly
In the index.jsx
function DrawShape(arr)
{
return arr[0];
}
Excepted output in console :
[2287,3474]
[2401,3328]
Result :
[
[
Thank you for helping !
Your template literal will indeed produce a single-quoted string literal that is passed to DrawShape.
Instead, make sure that it does not pass '123,132,322', but [123,132,322]. The easy part is that you should remove those single quotes. And then convert your array to JSON.
See below the difference of what gets evaluated. Here I don't call the evaluator, but just display what it gets to work with:
arrayNotes = [[2287,3474],[2268,3430],[2255,3398],[2255,3360]];
console.log("bad:");
console.log(`DrawShape('${arrayNotes}')`);
console.log("good:");
console.log(`DrawShape(${JSON.stringify(arrayNotes)})`);
it depend if you want an array of integer if its the case use JSON.parse:
var array = JSON.parse("[" + string + "]");
if you want an array of strings you can use the .split() method and get something like this:
["0", "1"]

Why is Array.indexOf() working correctly with one redux action but not with another? (same reducer)

var tags = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5',
'tag6', 'tag7', 'tag8', 'tag9', 'tag0'];
var selectedTags = [];
const tagsReducer = (state={}, action) => {
var tagIndex = '';
if(action.type==="ADD_TAG") { //this if statement works as expected
tagIndex = tags.indexOf(action.payload);
selectedTags.push(tags[tagIndex]);
tags.splice(tagIndex, 1);
return {tags, selectedTags};
}
if(action.type==="REMOVE_TAG"){ //this doesn't
tagIndex = selectedTags.indexOf(action.payload);
console.log(selectedTags); //prints the expected array
console.log(action.payload); //prints the expected string
console.log(tagIndex); //prints -1 (doesn't find the string)
console.log(typeof(selectedTags)); //prints object
console.log(typeof(action.payload)); // prints string
tags.push(selectedTags[tagIndex]);
selectedTags.splice(tagIndex, 1);
return {tags, selectedTags};
}
return {tags, selectedTags}
}
The string mathes one of the array items, but the indexOf() function still returns -1. It's confusing because the first action works fine, but the second doesn't. Any suggestions?
string mathes one of the array items, but the indexOf() function still returns -1
It doesn't match one of the array items as of when you're calling indexOf, because if it did, indexOf would find it. indexOf isn't broken.
So that leaves two general possibilities:
The string that's in the array looks like it should match, but is slightly different from the string in the payload. Ways it might be different that may not be obvious:
One of them (the one in the array or the one in payload) may have a space at the beginning or end, etc.
One of them may have a slight difference in capitalization.
They may have slightly different characters that are easily confused, like ç and ç.
The entry in the array may have a comma in it, for instance "thisTag,thatTag" which won't, of course, match "thatTag" if you go looking, although it looks like "thatTag" is in the array.
One of them may be a String object where the other is a string primitive; String objects and string primitives are not === to each other, and indexOf uses a === test.
The string isn't in the selectedTags array as of when you call indexOf, even though it looks like it in the console. That's because of this feature of consoles.
To figure it out, set a breakpoint on the indexOf line and, when it's hit, look at the string in payload and the string in selectedTags (if it's there — if not, then it was the console thing). You'll find some difference.
There are mutiple issue. You are trying to modify the external variable. Which is not pure. First rule of redux, Dont add side effect modifying external data. It has to be pure. The output should be processed data on input data. Below is the sample code. You can refine it later.
var tags = [
"tag1",
"tag2",
"tag3",
"tag4",
"tag5",
"tag6",
"tag7",
"tag8",
"tag9",
"tag0"
];
var selectedTags = [];
const tagsReducer = (state = { tags, selectedTags }, action) => {
var tagIndex = -1;
if (action.type === "ADD_TAG") {
//this if statement works as expected
tagIndex = state.tags.indexOf(action.payload);
const data = state.tags[tagIndex];
return {
tags: state.tags.filter(x => x !== data),
selectedTags: state.selectedTags.concat(data)
};
}
if (action.type === "REMOVE_TAG") {
//this doesn't
tagIndex = state.selectedTags.indexOf(action.payload);
const data = state.tags[tagIndex];
if (data)
return {
tags: state.tags,
selectedTags: selectedTags.filter(x => x !== data)
};
}
return state;
};
I tried to create stackblitz, draft also.
https://stackblitz.com/edit/redux-playground-twrxyy?file=index.js

Can you create object property names using template literals in javascript?

I'm attempting to write a function that takes an array of Strings as its input. The function should return an object with (1) key, value pair. The first element of the array should be the property name and the last element of the array should be its key.
function transformFirstAndLast(array){
return {`${array[0]}`: array[length-1];}
}
The above gives me an error. Can someone provide a detailed explanation why this isn't working? I'd like to avoid creating separate variables to store the values from the first and last array indices.
Your question really boils down to, "Can I use expressions as keys in object literals?"
The answer is yes (since es6):
function yell(template, ...parts) {
return parts[0] + '!';
}
function foo() {
return 'bar';
}
class Person {
constructor(first, last) {
this.first = first;
this.last = last;
}
toString() {
return `${this.first} ${this.last}`;
}
}
let names = ['Abe'];
let my_obj = {
[3+5]: 'some_value',
[yell `${foo()}`]: foo(),
[names[0]]: 64,
[new Person('Rafael', 'Cepeda')]: 25
};
console.log(my_obj);
As long as the expression evaluates to a string, all is fair.
You are missing the { and you can't use a template strings as a key. To use a "variable" as a key in an object, you should use the brakets around the variable. Here's the working code.
function transformFirstAndLast(array){
return {[array[0]]: array[array.length-1]};
}
I guess template literal is unnecessary here simply try like this way after fixing this line array[length-1] because its wrong, correct one is array[array.length-1]. I've added more verbose code but you can also do the shorthand version like return {[array[0]]: array[array.length-1]}; on your transformFirstAndLast(array) function.
function transformFirstAndLast(array){
const result = {};
result[array[0]] = array[array.length-1];
return result;
}
console.log(transformFirstAndLast(['a','b','c','d','e']))
yes you can use string literals as key. But the thing is that they are calculated in the run time. So you need to treat them as expressions. And to have variables/expressions as your keys you need to wrap them inside []
let a = {
`key`: value
} // is not allowed
let a = {
[`key`]: value
} // is allowed since you have wrapp
for your case
return {[`${array[0]}`]: array[array.length-1]};
Now since you have wrapped the array[0] item inside a string literal, you will get string values for your zero'th item. If your array[0] is to be a object this would not work as well. It needs to be either string or number. Or else you would get "[object Object]" as your key
var input = ["name", "fname", "lname", "stackOverflow"];
function transformFirstAndLast(array){
return {[array[0]]: array.pop()}
}
responseObject = transformFirstAndLast(input)
console.log(responseObject)

Matching all values, substitute to OR operator

Here is a first sample of code that work as intended: on the rest of the code this is used as a filter and will match 2 items from myids, those 2 where objectId match tWOsQhsP2Z and sStYrIU6lJ:
return myids.objectId === "tWOsQhsP2Z" || myids.objectId === "sStYrIU6lJ";
Because I need to pass arbitrary number of ids from an array, i'm trying to refactor code like so:
return myids.objectId === ("tWOsQhsP2Z" || "sStYrIU6lJ");
Problem with this new code is that filter that use return value will return only one item, the one with objectId that is tWOsQhsP2Z.
Do you know a way how I could refactor this second code so I keep single code "myids.objectId" but return match for ALL objectIds values ?
Sounds like you need something like underscore.js contains() method, would make things a lot simpler all round.
e.g.
return _.contains(arrayOfIds, myids.objectId);
You can use a switch:
switch (myids.objectId) {
case "tWOsQhsP2Z":
case "sStYrIU6lJ":
return true;
}
return false;
If you have an array of values to search, and the list is long and/or you're searching frequently, you can convert the list to an object and then do a property lookup. It's much more efficient that searching through an array.
For a simple constant case, your example would look like:
return myids.objectId in {"tWOsQhsP2Z": 1, "sStYrIU6lJ": 1};
If you start with an array that's server-generated or dynamic:
var knownIds = [ ... ];
then you can convert that to a map:
var idMap = knownIds.reduce(function(m, v) {
m[v] = 1;
return m;
}, {});
Now your lookup would be simply:
return myids.objectId in idMap;

What does ':' (colon) do in JavaScript?

I'm learning JavaScript and while browsing through the jQuery library I see : (colon) being used a lot. What is this used for in JavaScript?
// Return an array of filtered elements (r)
// and the modified expression string (t)
return { r: r, t: t };
var o = {
r: 'some value',
t: 'some other value'
};
is functionally equivalent to
var o = new Object();
o.r = 'some value';
o.t = 'some other value';
And also, a colon can be used to label a statement. for example
var i = 100, j = 100;
outerloop:
while(i>0) {
while(j>0) {
j++
if(j>50) {
break outerloop;
}
}
i++
}
You guys are forgetting that the colon is also used in the ternary operator (though I don't know if jquery uses it for this purpose).
the ternary operator is an expression form (expressions return a value) of an if/then statement. it's used like this:
var result = (condition) ? (value1) : (value2) ;
A ternary operator could also be used to produce side effects just like if/then, but this is profoundly bad practice.
The ':' is a delimiter for key value pairs basically. In your example it is a Javascript Object Literal notation.
In javascript, Objects are defined with the colon delimiting the identifier for the property, and its value so you can have the following:
return {
Property1 : 125,
Property2 : "something",
Method1 : function() { /* do nothing */ },
array: [5, 3, 6, 7]
};
and then use it like:
var o = {
property1 : 125,
property2 : "something",
method1 : function() { /* do nothing */ },
array: [5, 3, 6, 7]
};
alert(o.property1); // Will display "125"
A subset of this is also known as JSON (Javascript Object Notation) which is useful in AJAX calls because it is compact and quick to parse in server-side languages and Javascript can easily de-serialize a JSON string into an object.
// The parenthesis '(' & ')' around the object are important here
var o = eval('(' + "{key: \"value\"}" + ')');
You can also put the key inside quotes if it contains some sort of special character or spaces, but I wouldn't recommend that because it just makes things harder to work with.
Keep in mind that JavaScript Object Literal Notation in the JavaScript language is different from the JSON standard for message passing. The main difference between the 2 is that functions and constructors are not part of the JSON standard, but are allowed in JS object literals.
It is part of the object literal syntax. The basic format is:
var obj = { field_name: "field value", other_field: 42 };
Then you can access these values with:
obj.field_name; // -> "field value"
obj["field_name"]; // -> "field value"
You can even have functions as values, basically giving you the methods of the object:
obj['func'] = function(a) { return 5 + a;};
obj.func(4); // -> 9
It can be used to list objects in a variable. Also, it is used a little bit in the shorthand of an if sentence:
var something = {face: 'hello',man: 'hey',go: 'sup'};
And calling it like this
alert(something.man);
Also the if sentence:
function something() {
(some) ? doathing() : dostuff(); // if some = true doathing();, else dostuff();
}
These are generally the scenarios where colon ':' is used in JavaScript
1- Declaring and Initializing an Object
var Car = {model:"2015", color:"blue"}; //car object with model and color properties
2- Setting a Label (Not recommended since it results in complicated control structure and Spaghetti code)
List:
while(counter < 50)
{
userInput += userInput;
counter++;
if(userInput > 10000)
{
break List;
}
}
3- In Switch Statement
switch (new Date().getDay()) {
case 6:
text = "Today is Saturday";
break;
case 0:
text = "Today is Sunday";
break;
default:
text = "Looking forward to the Weekend";
}
4- In Ternary Operator
document.getElementById("demo").innerHTML = age>18? "True" : "False";
Let's not forget the switch statement, where colon is used after each "case".
That's JSON, or JavaScript Object Notation. It's a quick way of describing an object, or a hash map. The thing before the colon is the property name, and the thing after the colon is its value. So in this example, there's a property "r", whose value is whatever's in the variable r. Same for t.
Another usage of colon in JavaScript is to rename a variable, that is:
const person = {
nickNameThatIUseOnStackOverflow: "schlingel",
age: 30,
firstName: "John"
};
let { nickNameThatIUseOnStackOverflow: nick } = person; // I take nickNameThatIUseOnStackOverflow but want to refer it as "nick" from now on.
nick = "schling";
This is useful if you use a third party library that returns values having awkward / long variable names that you want to rename in your code.
One stupid mistake I did awhile ago that might help some people.
Keep in mind that if you are using ":" in an event like this, the value will not change
var ondrag = (function(event, ui) {
...
nub0x: event.target.offsetLeft + event.target.clientWidth/2;
nub0y = event.target.offsetTop + event.target.clientHeight/2;
...
});
So "nub0x" will initialize with the first event that happens and will never change its value. But "nub0y" will change during the next events.

Categories

Resources