Looping over multiple variables in Pug - javascript

I'm struggling with finding the correct way to declare these variables in a loop.. I need the variables to loop in two different places in the block.
First const breadcrumbsItems: Has to loop in the span tag.
Second const breadcrumbsAttributes: Has to loop in the (data-trigger="")
Can anyone please help me figuring it out?
Thanks in advance
- const breadcrumbsItems = [ `John`, `Jane`, `Jefferson`, `Ramirez` ]
- const breadcrumbsAttributes = ['tabOne','tabTwo', 'tabThree', 'tabFour']
.breadcrumbs
ul.breadcrumbs__list
li.breadcrumbs__elements
a.breadcrumbs__tabs.is-selected(data-trigger="#{const breadcrumbsAttributes}")
span.breadcrumbs__item #{const breadcrumbsItems}
svg.breadcrumbs__icon
use(xlink:href="assets/defs.svg#icon-close")

You can loop over one of the arrays, then use the index of the loop to access the corresponding value in the other array:
ul
each item, i in breadcrumbItems
li
button(data-trigger= breadcrumbsAttributes[i])
span #{item}
Also, try to always use buttons instead of links to perform actions that don't navigate the user to new content.

You'll need nested iterations. You can use each.
Here's an example that you can match to fit your markup.
Examples can also be found under the code section of the docs.
You can throw this code in a codepen.io and add pug as an html preprocessor if you ever want to quickly play with some pug markup.
- const breadcrumbsItems = [ `John`, `Jane`, `Jefferson`, `Ramirez` ]
- const breadcrumbsAttributes = ['tabOne','tabTwo', 'tabThree', 'tabFour']
each item in breadcrumbsItems
a.example(data-trigger=item) #{item}
each attr in breadcrumbsAttributes
div #{attr}
Another common confusion is when to use #{} and when not to.
If you are declaring attributes like this a(data-trigger=var), the inside of the parenthesis are treated like javascript. So no #{}.
If you are inside a text or markup area, you use #{}.

Related

Assign HTML elements ID to document.queryselector shorthand variables in a loop

I am new to Javascript development.
I am trying to assign HTML elements IDs stored in an array to shorthands to be used in my function later.
So that instead of writing :
let addprop = document.querySelector(`#addprop`);
let readprop = document.querySelector(`#readprop`);
let editprop = document.querySelector(`#editprop`);
let footer = document.querySelector(`#footer`);
let association = document.querySelector(`#association`);
I can attribute elements ids that i store in an array like this :
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"] ;
arrayElements.forEach(el => { return(new Function (`${el} = document.querySelector("#${el}");`)()); });
Now, this bit of code works but from what I read here :
Execute JavaScript code stored as a string
This is probably not a good way to do it and also declares global variables.
One problem I encountered is that if I try to directly execute the assignment like this :
el = document.querySelector(`#${el}`);
Then the el value takes the value of the named access ID element (https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object) and breaks the code.
So I resorted to generate a string first then execute it.
I could simply assign each shorthand manually but I spent way too much time trying to make this work and am now left curious as to what would be a good solution or approach for this.
And would the scope limitations for loops simply forbid me to do this without using global variables ?
edit : switched the working code in one line
Possible answer :
1 - does it matter to declare global variables like that ? As these variables already exist globally because of browsers named access for elements IDs.
2 - By kiranvj's answer, a solution can be to store in an object structured as keys being the shortcuts and the full strings being the values, and calling the shortcuts with the object[key] method ; or using destructuring to assign the values to variable directly with :
const {addprop, readprop, editprop, idfooter, assocpatients} = elements;
I feel like I am missing something on this last one but it also seems to work.
In the end I will stick with my first code as condensing the function in one line seems to negate the risks of cross site scripting (?), and global values for the variables assigned though this method anyway already exist because of named access.
You can create a dictionary with all the elements with ID and then destroy it into your variables, ignoring the unused ones.
function getAllElementsWithId() {
let elements = {}
for (let el of document.querySelectorAll('[id]')) {
if (!(el.id in elements)) {
elements[el.id] = el
}
}
return elements
}
let { addprop, readprop, editprop, footer, association } = getAllElementsWithId()
This uses document.querySelectorAll (link to MDN) to get all elements with an ID. Notice that for big pages this could be a performance issue.
Also, what you would usually do is to add them into a container, in this case it seems like a dictionary.
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"]
let elementsId = Object.fromEntries(arrayElements.map(id => [id, document.getElementById(id)]))
This uses Object.fromEntries (link to MDN) to generate the dictionary. Also I'm using document.getElementById (link to MDN) instead of document.querySelector so you don't need to add the hashtag before the id.
If you are concerned about global scope, you can try something like below. Use forEach instead of map . map also work but since you are not handling the return of map, forEach would be a better choice.
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"];
let elements = {};
arrayElements.forEach(el => elements[el] = document.querySelector(`#${el}`));
// access variables like elements.ID-NAME
console.log(elements);
<div id="addprop"></div>
<div id="readprop"></div>
Object destructing can be used if you know the object key name.
example : let {addprop} = element;
Another thing which you might be interested is Automatic global variables
This means a new variable (scoped to window) with the name of element id is created for all the elements in page. See the html5 spec. I would not recommend using it though.
So you don't have to call like document.querySelector('addprop')
addprop variable will have the DOM object.
See this example
// these works due to automatic global varaibles binding
alert(addprop);
console.log(addprop);
<div id="addprop">Some contents</div>

Set attribute for custom DOM nodes in text preview component

I want to add / setAttribute class for custom DOM nodes while writing some text in custom bbcode-like text editor. When the innerHTML of <item></item> is not empty I'm filtering through items array in order to find the value that matches. There can be unlimited amount of item nodes. (i.e 2, 5, 10)
So whenever i click on icon named item it shows in textarea as [item][/item] and in preview component as <item></item>. Once the item is written, lets say [item]id123[/item] I have in DOM <item>itemName123</item>.
Now, what I'm doing is manipulating the DOM outside React with:
const setAttributes = (el, item) =>{
el.dataset.img = item.img;
el.setAttribute('class', _.toLower(item.color))
};
const updateItems = () =>{
if(document.querySelectorAll('item')) {
document.querySelectorAll('item').forEach(el => items.find(item => {
if(_.toLower(el.innerHTML) === _.toLower(item.id))
setAttributes(el, item)
}));
}
}
The problem is, whenever I change the text, component re-renders so that removes attributes that have been set.
I tried to manipulate the text/string before it goes to dangerouslySetInnerHTML markup by splitting it, going through includes with map, filter all that godsent functions with some regex sauce from ES6 but it just smells bad.
It feels so hacky that i believe that there has to be something that I'm missing.
Edit: Forgot to add that I've also tried to put setAttributes() && updateItems() outside of class.
Edit#2: The way i'm changing from [item][/item] is via regexp text.replace(/\[item]/g, <item>).replace(/\[\/item]/g, </item>), so probably i could do something with regexp instead of setAtrribute on each re-render?And if so, i've been trying that via
new RegExp(/\[item]/+ _.toLower(item.name)+ /\[\/item]/, 'g');
and later on text.replace(<item class="${item.quality}">${_.toLower(item.name)}</item>)
but no success so far.
Thanks in advance for any tips and ideas!
Solved. Used RegExp + pattern.exec(text) and looping through text with while and replacing any matched occurances. Then used for(let..of) loop to compare the matched values and overwrite the value.

How to make a variable which consists of multiple 'divs'

I have a question regarding variables in Javascript.
When I assign a var to a ID I do it like this:
var x = document.getElementById("div_name");
But I would like to make a variable which consists of multiple 'divs'.
I thought this might work but I does not:
var x = document.getElementById("div_name"),document.getElementById("div_name2");
Can someone please help me find the right code syntax and explain why the syntax I tried is incorrect.
So, If you just want them as a list of div's you could do this:
var x = [document.getElementById("div_name"),document.getElementById("div_name2")];
Just wrap them up with [].
If your var should contain more than one object (div in your case), then you need to have more variable or, better, an array.
You can create yor array by using following code.
var x = [document.getElementById("div_name"), document.getElementById("div_name2")];
This is due to the fact that different DIVs in the DOM page are different objects...
There is no such variable that is defined as:
var x = somthing, somesthingElse
You need to chose a variable that can store a collection of "things". In your case the Array is an ideal choice:
var x = [document.getElementById("div_name"),document.getElementById("div_name2")];
The brackets at the beginning and end of the expression are the syntax to declare a variable.
In addition to using Array, you can also store your divs in an Object
var divs = {
div1: document.getElementById("div_name"),
div2: document.getElementById("div_name2")
};
Thus, you could give a convenient name to your divs, but still pass them around as you please:
divs.div1;
divs.div2;
Or loop through them like so:
for (div in divs) {
console.log(divs[div]);
};

How to create a string and use a function that's named after it?

Sorry for bad wording in the question but it's hard to explain for me. I'm using several bxsliders on a page and some are placed in hidden divs. Unfortunately images are not shown in the slider after making the parent div visible unless the slider is reloaded (See here: bxSlider within show/hide divs). So let's say I initiate the sliders at the beginning with:
var slider_0=$("#slider_0 .bxslider").bxSlider({
//bxslider options here
});
var slider_4=$("#slider_4 .bxslider").bxSlider({
//bxslider options here
});
var slider_7=$("#slider_7 .bxslider").bxSlider({
//bxslider options here
});
The sliders are not consecutively numbered but there is a navigation and if I click the 7th element it leads to slider_7. So I could get the index of the clicked item with:
$(this).index();
When I call slider_7.reloadSlider(); it would work but I don't know which slider the user clicks and which number it has. So would it be possible to call that with a created string like this:
slider_name='slider_'+$(this).index();
slider_name.reloadSlider();
works not of course. Is there a way to do it?
I would create a dictionary with strings as keys and functions as values. Then, you could have O(1) lookup of the functions you're targeting.
In general, you can do it like so:
// set up your dictionary
var dictionary = {};
// add your functions
dictionary['methodName'] = function() {};
// call the functions
dictionary['methodName']();
So, for your example, you could do:
dictionary['slider_7'] = slider_7.reloadSlider;
dictionary['slider_'+$(this).index()]();
You could trigger it with
window["slider_" + $(this).index()].reloadSlider()
Although, I'm not sure whether your approach is the best. I think I'd go with arrays or with object (as a key-value pairs)
Try this:
slider_name='slider_'+$(this).index();
$("#" + slider_name + " .bx_slider").reloadSlider();
Found a working solution:
eval("slider_" + $(this).index()).reloadSlider();
Its not entirely clear here what you want/are trying to do. What it seems like you want to do is get a programmatic handle on a specific slider when a user clicks a specific part of your page. You do not accomplish this by eval()ing a string...that's what event handlers are for. So create a click event handler and in that event handler
$('#idOfWhatTheUserClicksOn').click(function(event) {
var slider = document.getElementById('#idOfRelatedSlider');
$(slider).bxSlider();
//if you need the current value of the slider you can get that too
var value = slider.value;
});
You could achieve the same with fewer LOC by using a class instead of id's with different handlers, but the concept is the same.
var slider_cache = [
$("#slider_0 .bxslider").bxSlider(),
$("#slider_1 .bxslider").bxSlider(),
$("#slider_2 .bxslider").bxSlider()
];
...
slider_cache[$(this).index()].reloadSlider();

Edit html text value using directives

In my app, I'm using some variable which contain a code instead of the value itself. This code matches one field of an array which items contain the code with the matching value. What I could do to display the name is a loop to find the value based on the code. But as my app has a lot of these, I would need to do it for each value.
Here is the array:
[{code: 'PN', name: 'Panasonic'}, {code: 'SN', name: 'Sony'}]
Therefore, I thought using an attribute would be much better and cleaner. I would like to put the following jade: div(json-array={{televisions}}) {{ code }} and change the displayed code with televisions[X].name. The problem is that I'm not so familiar with directives.
I tried to use the link function to catch the value (code) and the binded variable array ({{televisions}}) but I encountered two problems:
How can I modify the div value without modifying the binded variable (code)?
How do I get the array (televisions) within my directive?
I still wouldn't use a directive for that. It is a simple presentation issue and can be easily (and declaratively) handled in the view (yet the question lacks all necessary info in order to provide the most appropriate solution).
<div>{{getTelevisionName(tv.code)}}</div>
$scope.getTelevisionName = function (code) {
for (var i = 0; i < $scope.televisions.length; i++) {
var tv = $scope.televisions[i];
if (tv.code === code) return tv.name;
}
return '';
};

Categories

Resources