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.
Related
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 #{}.
I am trying to scrape the following Javascript frontend website to practise my Javascript scraping skills:
https://www.oplaadpalen.nl/laadpaal/112618
I am trying to find two different elements by their xPath. The first one is the title, which it does find. The second one is the actual text itself, which it somehow fails to find. It's strange since I just copied the xPath's from Chrome browser.
from selenium import webdriver
link = 'https://www.oplaadpalen.nl/laadpaal/112618'
driver = webdriver.PhantomJS()
driver.get(link)
#It could find the right element
xpath_attribute_title = '//*[#id="main-sidebar-container"]/div/div[1]/div[2]/div/div[' + str(3) + ']/label'
next_page_elem_title = driver.find_element_by_xpath(xpath_attribute_title)
print(next_page_elem_title.text)
#It fails to find the right element
xpath_attribute_value = '//*[#id="main-sidebar-container"]/div/div[1]/div[2]/div/div[' + str(3) + ']/text()'
next_page_elem_value = driver.find_element_by_xpath(xpath_attribute_value)
print(next_page_elem_value.text)
I have tried a couple of things: change "text()" into "text", "(text)", but none of them seem to work.
I have two questions:
Why doesn't it find the correct element?
What can we do to make it find the correct element?
Selenium's find_element_by_xpath() method returns the first element node matching the given XPath query, if any. However, XPath's text() function returns a text nodeānot the element node that contains it.
To extract the text using Selenium's finder methods, you'll need to find the containing element, then extract the text from the returned object.
Keeping your own logic intact you can extract the labels and the associate value as follows :
for x in range(3, 8):
label = driver.find_element_by_xpath("//div[#class='labels']//following::div[%s]/label" %x).get_attribute("innerHTML")
value = driver.find_element_by_xpath("//div[#class='labels']//following::div[%s]" %x).get_attribute("innerHTML").split(">")[2]
print("Label is %s and value is %s" % (label, value))
Console Output :
Label is Paalcode: and value is NewMotion 04001157
Label is Adres: and value is Deventerstraat 130
Label is pc/plaats: and value is 7321cd Apeldoorn
I would suggest a slightly different approach. I would grab the entire text and then split one time on :. That will get you the title and the value. The code below will get Paalcode through openingstijden labels.
for x in range(2, 8):
s = driver.find_element_by_css_selector("div.leftblock > div.labels > div")[x].text
t = s.split(":", 1)
print(t[0]) # title
print(t[1]) # value
You don't want to split more than once because Status contains more semicolons.
Going with #JeffC's approach, if you want to first select all those elements using xpath instead of css selector, you may use this code:
xpath_title_value = "//div[#class='labels']//div[label[contains(text(),':')] and not(div) and not(contains(#class,'toolbox'))]"
title_and_value_elements = driver.find_elements_by_xpath(xpath_title_value)
Notice the plural elements in the find_elements_by_xpath method. The xpath above selects div elements that are descendants of a div element that had a class attribute of "labels". The nested label of each selected div must contain a colon. Furthermore, the div itself may not have a class of "toolbox" (Something that certain other divs on the page have), nor must it contain any additional nested divs.
Following which, you can extract the text within the individual div elements (which also contain the text from the nested label elements) and then split them using ":\n" which separates the title and value in the raw text string.
for element in title_and_value_elements:
element = element.text
title,value = element.split(":\n")
print(title)
print(value,"\n")
Since you want to practice JS skills you can do this also in JS, actually all the divs contain more data, you can see if you do paste this in the browser console:
labels = document.querySelectorAll(".labels");
divs = labels[0].querySelectorAll("div");
for (div of divs) console.log(div.firstChild, div.textContent);
you can push to an array and check only divs and that have label and return the resulted array in a python variable:
labels_value_pair.driver.execute_script('''
scrap = [];
labels = document.querySelectorAll(".labels");
divs = labels[0].querySelectorAll("div");
for (div of divs) if (div.firstChild.tagName==="LABEL") scrap.push(div.firstChild.textContent, div.textContent);
return scrap;
''')
I've created an array and want to access the created elements outside of the loop. I know that they are not in the scope and writing this. infront of it wont make them accessible either.
colIdx = colIdx + this.columns.findIndex(c => c.editable);
this.focusInput(rowIdx, colIdx);
this.autocompletes.toArray().forEach(el => {
console.log(el);
})
I have a table with lots of input fields with an autocomplete function and a suggestion panel. I also have a custom method which lets the user tab with the enter key. However at first, tabbing with enter didnt close the suggestion panel of the autocomplete hence after a while all the suggestion panels where open.
Thats why I've created the method above. You could actually ignore the first two lines because they are needed for the tabbing with enter.
this.autocompletes is a Querylist with all my input elements. I've turned them into an array and called each element el.
This way I'm able to call a closePanel() method on el in order to close the suggestion panels of the autocomplete method. However doing it this way shuts down the suggestion panels of ALL el elements. Thats why I need the index of el the user has set focus on and close this one.
In order to do so I have to access el outside the for-loop it has been created in.
You can initialize an empty array outside the loop like var arr: type = emptyArr[]; and then push the data (el) in it from inside the loop.
To push do something like: arr.push(el) from inside the loop and then access it outside the loop.
The easiest way would be to just assign it them to an array outside the scope of the loop:
elements: any[] = [];
colIdx = colIdx + this.columns.findIndex(c => c.editable);
this.focusInput(rowIdx, colIdx);
this.autocompletes.toArray().forEach(el => {
console.log(el);
this.elements.push(el);
});
// You can now loop over `this.elements` and do what you need with them.
this.autocompletes is a Querylist with all my input elements
This means autocompletes won't change and you can save it in your Init call into a local variable.
ex: if your form have 4 input it will still contain 4 unless you are removing it from the dom. Save it in a local variable during your create/init. depending how you have written your code.
Here is what you can do.
//do not do .toArray again and again its just not usefull as your data is constant
elements: Array<any> = this.autocompletes.toArray();
...
elements.forEach(el => {
//work on elements here
//to check focus on el you could do something like this el.is(":focus")
});
i am new to js.
can you tell me why I am getting empty values for sports-title and third.
since we have one div with content in it.
sports-title---->{"0":{}}
third---->{}
providing my code below.
findStringInsideDiv() {
/*
var str = document.getElementsByClassName("sports-title").innerHTML;
*/
var sportsTitle = document.getElementsByClassName("sports-title");
var third = sportsTitle[0];
var thirdHTML = third.innerHTML
//str = str.split(" ")[4];
console.log("sports-title---->" + JSON.stringify(sportsTitle));
console.log("third---->" + JSON.stringify(third));
console.log("thirdHTML---->" + JSON.stringify(thirdHTML));
if ( thirdHTML === " basketball football swimming " ) {
console.log("matching basketball---->");
var menu = document.querySelector('.sports');
menu.classList.add('sports-with-basketball');
// how to add this class name directly to the first div after body.
// but we are not rendering that div in accordion
//is it possible
}
else{
console.log("not matching");
}
}
When you call an object in the Document Object Model (DOM) using any of the GetElement selectors, it returns an object that can be considered that HTML element. This object includes much more than just the text included in the HTML element. In order to access the text of that element, you want to use the .textContent property.
In addition, an HTML class can potentially be assigned to several elements and therefore GetElementsByClassName returns an array so you would have to do the following, for example:
console.log("sports-title---->" + JSON.stringify(sportsTitle[0].textContent));
You can find a brief introduction to the DOM on the W3Schools Website. https://www.w3schools.com/js/js_htmldom.asp If you follow along it gives an overview of different aspects of the DOM including elements.
Maybe this would be helpful
As you see sportsTitle[0].textContent returns full heading and 0 is the index thus you get "0" when you stringify (serialize) sportsTitle. Why 0? Because you have one <h1> element . See this fiddle http://jsfiddle.net/cqj6g7f0/3/
I added second h1 and see the console.log and you get two indexes 0 and 1
if you want to get a word from element so get substring use substr() method https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr
One way is to change <h1> class attr to id and do sportsTitle.textContent;
and use substr() on this string
or
2nd way is to remain class attr and do sportsTitle[0].textContent;
and substr() on this string
The 2nd is the better way
I'm trying to implement a History with Adobe Edge Animate and somewhere in a trigger I have to update it. What I'm trying to do is deleting all previous symbols and rewrite them with the updated history content.
This is how I'm creating child symbols:
// Creating child symbols
$.each( h.reverse(), function(index, item){
var itemRenderer = sym.createChildSymbol("ItemRenderer", "HistoryContainer");
itemRenderer.$("ItemText").text(item);
});
Then I try to get all of the child symbols in order to delete them:
// Delete all child symbols
var cs = sym.$("HistoryContainer").getChildSymbols();
And I get:
Javascript error in event handler! Event Type = timeline
How am I able to get the list of childSymbols and update the HistoryContainer?
Replace this:
sym.$("HistoryContainer").getChildSymbols();
with:
sym.getSymbol("HistoryContainer").getChildSymbols();
With your code you are getting the div element (as a jQuery element). In order to have the getChildSymbol method you need to get the AdobeEdge symbol.