I am learning JavaScript so that I can implement Google Tag Manager. I have a list of paths that I would like GTM to rewrite to something friendlier like so:
function() {
return document.location.pathname.indexOf('/l/138281/2016-06-07/dy383') > -1 ? 'Test Success' : undefined;
}
function() {
return document.location.pathname.indexOf('/l/138281/2016-04-03/55z63') > -1 ? 'SPP Contact Success' : undefined;
I'm just not sure how to combine these returns into one function (I currently have about 30 URLs to rewrite). I imagine I can use if/else, but advice would be quite lovely.
--edit--
URL Path Rewrite To
/test-638-jsj /test-success
/spp-zxcv-765 /spp-contact-success
/foo-asdf-123 /foo
/foo-bar-987 /foo-bar
The return function mentioned above does this beautifully for an individual link. I just want to be able to rewrite a series of URLs in one function (or however it makes sense to do this most specifically). Hopefully that helps clarify.
Thanks!
It is always a great idea to structure your code: separate abstract functionality from the specific problem.
What you are actually doing is scannins strings for occurences of keywords and returning specific values if such a keyword has been found.
Therefore, you need a function performing the above computation and a JavaScript datastructure holding your keywords and their values (= Object):
// Return patterns[key] if any key is found in string, else return string:
function match(string, patterns) {
for (key of Object.keys(patterns)) {
if (string.indexOf(key) > -1) return patterns[key];
}
return string;
}
var patterns = {
'/l/138281/2016-06-07/dy383': 'Test Success',
'/l/138281/2016-04-03/55z63': 'SPP Contact Success'
}
console.log(match('/l/138281/2016-06-07/dy383', patterns)); // "Test Success"
console.log(match('/doesnotexist', patterns)); // "/doesnotexist"
console.log(match(document.location.pathname, patterns));
Related
Is there a way to prevent errors from being thrown while filtering?
The below function sometimes fails at conversationMember.Name.toLowerCase() when there is no conversationMember.
If it helps, this is also a computed property in a Vue application.
Should you need more information, please just ask!
filteredConversations() {
var self = this;
var filteredConvos = self.conversations;
filteredConvos = filteredConvos.filter(conversation => {
return conversation.MembershipData.some(conversationMember => {
return conversationMember.Name.toLowerCase().includes(
self.conversationSearchTerm.toLowerCase()
);
});
});
return filteredConvos;
},
This doesn't seem to have anything to do with arrays.
From your code I understand conversationMember.Name is supposed to be a string (because you're calling .toLowerCase() on it), which means incudes here is not Array.prototype.includes, but String.prototype.includes, especially since self.conversationSearchTerm seems to also be a string (you're also calling .toLowerCase() on it).
So, the problem is you're using includes on something that should be a string but is not. The simple fix is to default it to an empty string when it's falsy:
return (conversationMember.Name || '').toLowerCase().includes(
(self.conversationSearchTerm || '').toLowerCase()
);
As a side note, you don't need the var self = this;. this is available inside your filter since the filter is an arrow function. So your function (I'm guessing it's a computed but it can as well be a method) could look like this:
filteredConversations() {
return this.conversations.filter(c =>
c.MembershipData.some(md =>
(md.Name || '').toLowerCase().includes(
(this.conversationSearchTerm || '').toLowerCase()
)
)
);
}
One last note: this will still fail if any of your conversations does not have a MembershipData holding an array. To get around that, you could default it to an empty array on the fly:
...
(c.MembershipData || []).some(md =>
...
As expected, any conversation without an array in MembershipData will be filtered out by the function (not included in the result) - because .some(condition) will return false when called on an empty array.
Update: scroll to see my solution, can it be improved?
So I have this issue, I am building a word translator thats translates english to 'doggo', I have built this in vanilla JS but would like to do it React.
My object comes from firebase like this
dictionary = [
0: {
name: "paws",
paws: ["stumps", "toes beans"]
}
1: {
name: "fur",
fur: ["floof"]
}
2: {
name: "what"
what: ["wut"]
}
]
I then convert it to this format for easier access:
dictionary = {
what : ["wut"],
paws : ["stumps", "toe beans"],
fur : ["floof"]
}
Then, I have two text-area inputs one of which takes input and I would like the other one to output the corresponding translation. Currently I am just logging it to the console.
This works fine to output the array of the corresponding word, next I have another variable which I call 'levelOfDerp' which is basically a number between 0 - 2 (set to 0 by default) which I can throw on the end of the console.log() as follows to correspond to the word within the array that gets output.
dictionary.map(item => {
console.log(item[evt.target.value][levelOfDerp]);
});
When I do this I get a "TypeError: Cannot read property '0' of undefined". I am trying to figure out how to get past this error and perform the translation in real-time as the user types.
Here is the code from the vanilla js which performs the translation on a click event and everything at once. Not what I am trying to achieve here but I added it for clarity.
function convertText(event) {
event.preventDefault();
let text = inputForm.value.toLowerCase().trim();
let array = text.split(/,?\s+/);
array.forEach(word => {
if (dictionary[word] === undefined) {
outputForm.innerHTML += `${word} `;
noTranslationArr.push(word);
} else {
let output = dictionary[word][levelOfDerp];
if (output === undefined) {
output = dictionary[word][1];
if (output === undefined) {
output = dictionary[word][0];
}
}
outputForm.innerHTML += `${output} `;
hashtagArr.push(output);
}
});
addData(noTranslationArr);
}
Also here is a link to the translator in vanilla js to get a better idea of the project https://darrencarlin.github.io/DoggoSpk/
Solution, but could be better..
I found a solution but I just feel this code is going against the reason to use react in the first place.. My main concern is that I am declaring variables to store strings inside of an array within the function (on every keystroke) which I haven't really done in React, I feel this is going against best practice?
translate = evt => {
// Converting the firebase object
const dict = this.state.dictionary;
let dictCopy = Object.assign(
{},
...dict.map(item => ({ [item["name"]]: item }))
);
let text = evt.target.value.toLowerCase().trim();
let textArr = text.split(/,?\s+/);
let translation = "";
textArr.forEach(word => {
if (dictCopy[word] === undefined) {
translation += `${word} `;
} else {
translation += dictCopy[word][word][this.state.derpLvl];
}
});
this.setState({ translation });
};
levelOfDerp is not defined, try to use 'levelOfDerp' as string with quotes.
let output = dictionary[word]['levelOfDerp' ];
The problem happens because setState() is asynchronous, so by the time it's executed your evt.target.value reference might not be there anymore. The solution is, as you stated, to store that reference into a variable.
Maybe consider writing another function that handles the object conversion and store it in a variable, because as is, you're doing the conversion everytime the user inputs something.
I've been adopting ReactJS + Redux in my projects for a couple of years. I often end up in asynchronous situations where I need my component to wait for the state to be updated to render. Normally the simple logic !this.props.isFetching ? <Component /> : "Loading..." is enough.
However there are cases where I need to check for the state of an array that is embedded in the state object. In these cases, most of my components end up looking like this:
renderPostAuthor = () => {
if (!!this.props.postDetails.author) {
return this.props.postDetails.author[0].name;
} else {
return (
<div>
<StyledTitle variant="subheading" gutterBottom color="primary">
Loading...
</StyledTitle>
</div>
);
}
};
Is this use of the !! notation a good pattern / practice in ReactJS?
UPDATE: Thanks for the responses, and they are all valid. Perhaps, to clarify my question further, note that this.props.postDetails is a state itself that contains a number of objects and arrays. Therefore the problem is that if I omit the !! and this.props.postDetails isn't instantiated yet, and hence contains no arrays such as author[], I get the undefined error.
This has much more to do with just JavaScript in general than with React.
No, that use of !! isn't particularly useful. This:
if (!!this.props.postDetails.author) {
is the same as this:
if (this.props.postDetails.author) {
Neither of them means that author contains an array with at least one entry, which your next line of code is relying on. To do that, add .length or, with your particular example, probably [0] instead (in case author had an entry, but that entry was a falsy value):
if (this.props.postDetails.author[0]) {
If author may be null or undefined, we need to do two checks:
if (this.props.postDetails.author && this.props.postDetails.author[0]) {
Since we're going to use the result, it may be best to save the result to a variable or constant:
const firstAuthor = this.props.postDetails.author && this.props.postDetails.author[0];
if (firstAuthor) {
return firstAuthor.name;
}
Example of the current code throwing an error:
console.log("Running");
const author = [];
if (!!author) {
console.log(author[0].name);
} else {
console.log("No Author");
}
Example of checking [0] when we know author won't be null/falsy:
console.log("Running");
const author = [];
if (author[0]) {
console.log(author[0].name);
} else {
console.log("No Author");
}
Example of the double-check when author may be null/falsy:
console.log("Running");
const author = null;
if (author && author[0]) {
console.log(author[0].name);
} else {
console.log("No Author");
}
Example of saving and using the result:
function example(author) {
const firstAuthor = author && author[0];
if (firstAuthor) {
return firstAuthor.name;
} else {
return "Loading...";
}
}
console.log(example(null)); // Loading...
console.log(example([])); // Loading...
console.log(example([{name:"Harlan Ellison"}])); // "Harlan Ellison" (RIP)
There are times in react when using the !! is particularly helpful, but this is not the instance as stated above. The most common case I've found is when evaluating whether you're going to render array items or not. Often people will use the length of the array to decide whether to work with it or not since 0 length is a falsey boolean:
render () {
return this.props.array.length && <MyList items={this.props.array} />
}
Unfortunately this will return the 0 which will be rendered on the page. Since false will not render on the page a good alternative would be to use the double bang so that false is returned.
render () {
return !!this.props.array.length && <MyList items={this.props.array} />
}
So I guess the title is selfexplanatory. I have some code with nested forEach loops inside it. The loops are iterating over an array of chapter objects. Each object can have multiple child nodes and they again can have multiple child nodes, and so on.
I want to end up with one array which contains nested arrays with the child nodes.
So far my code looks like this:
exports.chapter = function(req, res) {
var chapters = [],
result = [];
chapters = exports.index(req, res);
chapters.forEach(function(chapter) {
if(chapter.orphan){
result.add({
'chapter': chapter,
'children': getChildren(chapter.children)
});
}
});
function getChildren(siblings) {
var children = [];
chapters.forEach(function(chapter) {
if($.inArray(chapter, siblings)){
children.add({
'chapter': chapter,
'children': getChildren(chapter.children)
});
}
});
return children;
};
};
I don't get any errors except for my page not loading. It doesn't write anything in my console. I think it's a problem in the setup but I'm unable to find out where at the moment. Really hope you guys can help.
Most likely problem is here:
if($.inArray(chapter, siblings)){
$.inArray is a horribly misnamed method: It returns an index, or -1 if not found, not a flag as the name implies. -1 is, of course, truthy; and 0 (a valid index), is falsey, so your if probably wants to be
if($.inArray(chapter, siblings) != -1){
// We found it...
}
or possibly
if($.inArray(chapter, siblings) == -1){
// We didn't find it
}
It's a bit strange.. I don't understand why you're using 'add' instead of 'push' method. If I try to "add" an object to an array I get an usual error. Don't you?
I have an XML document that defines a task, which is a list of actions to be performed on certain data. I need to convert this "task list" to a Javascript method which can be called at some later time, which in turn calls a series of pre-defined methods, with the appropriate data. How would you achieve this?
Important Clarification:
I'm not worried about the XML parsing. I'm more interested in how to actually build the Task Method, including binding the essential data to the pre-defined action methods. That's the part I'm struggling with.
Edit: I've revised my example to make it a bit more interesting, and hopefully a bit clearer.
XML:
<task id="enter-castle">
<if holding="castle-key">
<print message="You unlock the castle door and enter." />
<destroy item="castle-key" />
<goto location="castle" />
<else>
<print message="The castle door is locked." />
</else>
</if>
</task>
Javascript:
Game = {
print: function(message) {
// display message
},
destroy: function(item) {
// destroy the object
},
goto: function(location) {
// change player location
},
ifHolding: function(item) {
// return true if player has item
}
};
parseTask(taskNode) {
var taskId = taskNode.getAttribute('id');
// What goes here??
Game.tasks[taskId] = /* ??? */;
}
When I call parseTask() on the <task id="enter-castle"> node, this should create a function that, in effect, does the following when called:
Game.tasks.enterCastle = function() {
if (Game.ifHolding('castle-key')) {
Game.print("You unlock the castle door and enter.");
Game.destroy('castle-key');
Game.goto('castle');
} else {
Game.print("The castle door is locked.");
}
}
What you want are closures.
function createMethod(arguments) {
var task = doSomethingWithYour(arguments);
return function(xmlData) { // <- this is the fundamental part
// do the task with your data
// the "task" vars are still available
// even if the returned function is executed in a different context
}
}
This allows you to create an own method for each task. Don't use the Function constructor or eval.
This is a situation where JavaScript's eval() function will make your life much easier. You can easily build a JavaScript source string matching your desired one and evaluate it to assign the function to the desired property of your Game object.
Of course, there are drawbacks to using "eval", which I won't explore in this answer since you can find countless justifications for why not to use it on the web. However, building and evaluating a simple JS source string will be much easier in the short term than say, a closure based solution, despite any potential drawbacks of performance and security. Moreover, the "eval" based solution will be easy to test since you can simply inspect the source string before it is evaluated.
So try something like this:
function buildTaskFunction(taskXml) {
var source='', depth=0, node /*a visitor to each DOM node*/;
// foreach (node in traverseInOrder(taskXml)) {
switch (node.nodeName) {
case 'TASK':
source += 'Game.tasks.' + makeFunctionName(node.id) + '= function(){';
depth++;
break;
case 'IF':
source += 'if(' + getConditionalAttribute(node) + '){'
depth++;
break;
case 'ELSE':
source += '}else{';
break;
case 'DESTROY':
source += 'Game.destroy("' + node.getAttribute('item') + '");'
break;
case 'PRINT':
source += 'Game.print("' + node.getAttribute('message') + '");'
break;
// case etc...
default: throw new Error('unhandled node type "' + node.nodeName + '"');
}
// end "foreach node".
while (depth-- > 0) { // You'll need to account for nested "if"s somehow...
source += '}';
}
eval(source);
}
And again, there are many potential problems (not definitive ones) with using "eval", so please do read about and try to understand them in the context of your solution. Use your own judgement when deciding if the drawbacks are worth the benefits in your own program -- just because a tool can be dangerous doesn't mean you should not use it.
Example using dojo:
dojo.require("dojox.xml.parser");
dojo.ready(function(){
// Parse text and generate an XML DOM
var xml = "<tnode><node>Some Text</node><node>Some Other Text</node></tnode>";
var dom = dojox.xml.parser.parse(xml);
var docNode = dom.documentElement();
// ...
}
The remainder of the function is non-trivial, but would largely just consist of attribute lookup using ['<attribute-name>'] and child node iteration using dojo.forEach(<node>.childNodes, function(childNode) { /* do stuff */ });