Problem
I have a repeat.for in my html markup bound to an array on my viewmodel. The array only has one element. However, the generation of the HTML always produces one extra undefined element.
This behavior only appears in one section of my code which makes it very hard to duplicate -- repeat.for is working everywhere else in my code.
Proof
So before you label me crazy and try to insist that my array must have more items than I think, have a look at my code and at the output from the Aurelia Inspector.
client.js
export class Client {
months = []
activate(id) {
...
this.loadData()
}
loadData(){
this.months.push("Item1")
}
client.html
<section class="scrollable">
${months.length}
<div repeat.for="month of months">
Index = ${$index}
</div>
</section>
My output looks like this:
1
Index = 0
Index = 1
If I use Aurelia Inspector, here is what I see:
Inspecting '1' = ${months.length}
Inspecting 'Index = 0' = ${$index}
Inspecting 'Index = 1' = ${$index}
Has anyone seen similar behavior with repeat.for? Any ideas as to what I might be doing wrong?
Related
I'm somewhat (or very) confused about the following:
from selenium.webdriver import Chrome
driver = Chrome()
html_content = """
<html>
<head></head>
<body>
<div class='first'>
Text 1
</div>
<div class="second">
Text 2
<span class='third'> Text 3
</span>
</div>
<div class='first'>
Text 4
</div>
<my_tag class="second">
Text 5
<span class='third'> Text 6
</span>
</my_tag>
</body>
</html>
"""
driver.get("data:text/html;charset=utf-8,{html_content}".format(html_content=html_content))
What I'm trying to do, is find each span element using xpath, print out its text and then print out the text of the parent of that element. The final output should be something like:
Text 3
Text 2
Text 6
Text 5
I can get the text of span like this:
el = driver.find_elements_by_xpath("*//span")
for i in el:
print(i.text)
With the output being:
Text 3
Text 6
But when I try to get the parent's (and only the parent's) text by using:
elp = driver.find_elements_by_xpath("*//span/..")
for i in elp:
print(i.text)
The output is:
Text 2 Text 3
Text 5 Text 6
The xpath expressions *//span/..and //span/../text() usually (but not always, depending on which xpath test site is being used) evaluate to:
Text 2
Text 5
which is what I need for my for loop.
Hence the confusion. So I guess what I'm looking for is a for loop which, in pseudo code, looks like:
el = driver.find_elements_by_xpath("*//span")
for i in el:
print(i.text)
print(i.parent.text) #trying this in real life raises an error....
There's probably a few ways to do this. Here's one way
elp = driver.find_elements_by_css_selector("span.third")
for i in elp:
print(i.text)
s = i.find_element_by_xpath("./..").get_attribute("innerHTML")
print(s.split('<')[0].strip())
I used a simple CSS selector to find the child elements ("text 3" and "text 6"). I loop through those elements and print their .text as well as navigate up one level to find the parent and print its text also. As OP noted, printing the parent text also prints the child. To get around this, we need to get the innerHTML, split it and strip out the spaces.
To explain the XPath in more detail
./..
^ start at an existing node, the 'i' in 'i.find_element_*'. If you skip/remove this '.', you will start at the top of the DOM instead of at the child element you've already located.
^ go up one level, to find the parent
I know I already accepted #JeffC's answer, but in the course of working on this question something occurred to me. It's very likely an overkill, but it's an interesting approach and, for the sake of future generations, I figured I might as well post it here as well.
The idea involves using BeautifulSoup. The reason is that BS has a couple of methods for erasing nodes from the tree. One of them which can be useful here (and for which, to my knowledge, Selenium doesn't have an equivalent method) is decompose() (see more here). We can use decompose() to suppress the printing of the second part of the text of the parent, which is contained inside a span tag by eliminating the tag and its content. So we import BS and start with #JeffC's answer:
from bs4 import BeautifulSoup
elp = driver.find_elements_by_css_selector("span.third")
for i in elp:
print(i.text)
s = i.find_element_by_xpath("./..").get_attribute("innerHTML")
and here switch to bs4
content = BeautifulSoup(s, 'html.parser')
content.find('span').decompose()
print(content.text)
And the output, without string manipulation, regex, or whatnot is...:
Text 3
Text 2
Text 6
Text 5
i.parent.text will not work, in java i used to write some thing like
ele.get(i).findElement("here path to parent may be parent::div ").getText();
Here is the python method that will retrieve the text from only parent node.
def get_text_exclude_children(element):
return driver.execute_script(
"""
var parent = arguments[0];
var child = parent.firstChild;
var textValue = "";
while(child) {
if (child.nodeType === Node.TEXT_NODE)
textValue += child.textContent;
child = child.nextSibling;
}
return textValue;""",
element).strip()
This is how to use the method in your case:
elements = driver.find_elements_by_css_selector("span.third")
for eleNum in range(len(elements)):
print(driver.find_element_by_xpath("(//span[#class='third'])[" + str(eleNum+1) +"]").text)
print(get_text_exclude_children(driver.find_element_by_xpath("(//span[#class='third'])[" + str(eleNum+1) +"]/parent::*")))
Here is the output:
I'm trying to make a blog using Vue as laid out in the excellent demo here. I'd like to include some mathematical formulas and equations in my blog, so I thought I'd try to use vue-katex. vue-katex formats my mathematical notation perfectly when I put all my KaTeX HTML directly into my Vue templates, but to create a useable blog I need to keep my content separate from my templates (as shown in the demo).
I can't get vue-katex to format HTML content in the static folder. That's what I'd like help with.
Setup
I cloned the github repo for the demo.
I added vue-katex to package.json:
"vue-katex": "^0.1.2",
I added the KaTeX CSS to index.html:
<!-- KaTeX styles -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-alpha2/katex.min.css"
integrity="sha384-exe4Ak6B0EoJI0ogGxjJ8rn+RN3ftPnEQrGwX59KTCl5ybGzvHGKjhPKk/KC3abb"
crossorigin="anonymous"
>
I added the import statement to src/App.vue:
import Vue from 'vue'
import VueKatex from 'vue-katex'
Vue.use(VueKatex)
and I added a simple line of HTML with KaTeX to the BlogPost template:
<p>Here's an equation in the actual Vue template: <div class="equation" v-katex="'X \\sim N(\\mu, \\sigma^2)'"></div></p>
As I said, this works - I see formatted mathematical notation in my blog post (URL http://localhost:8080/read/neque-libero-convallis-eget):
However, I need different equations for every blog post, of course.
So I tried adding KaTeX HTML to the "content" field in the JSON for the first blog post: static/api/post/neque-libero-convallis-eget.json. I changed the "content" line to:
"content": "Here's an equation in the static folder: <div class=\"equation\" v-katex=\"'X \\sim N(\\mu, \\sigma^2)'\"></div>",
This content appears on the page, but the equation doesn't render. I see this: (the text appears but no equation is shown)
When I use Developer Tools to inspect the HTML on the page, I see this:
You can see that vue-katex has been applied to the equation I put in the template directly: it has parsed the HTML I typed into lots of spans with all the mathematical symbols, which are showing perfectly.
However the KaTeX HTML I've added to the "content" in the static folder has simply been placed on the page exactly as I typed it, and is therefore not showing up as an equation on the page. I really need to keep my blog post content in this static folder - I don't want to have to create a different .vue file for each blog post, that defeats the point!
My question is: is there a way to manually "apply" vue-katex to the HTML I place in the static folder, when it loads? Perhaps there is something I can add to the plugins/resource/index.js file, since this contains the function that loads the data from the static folder?
Many thanks in advance for any help.
*Disclaimer: I'm definitely no expert / authority on what I'm about to explain!
One thing to remember is that Vue reads the templates you write, and then replaces them as reactive components. This means that although you often write Vue attributes like v-for, v-html or in this case v-katex these attributes are only useful up until the app or component is mounted.
With this in mind, if you have a Vue app that ajax loads some html, its not going to be able to rerender itself with those Vue bindings in place.
I have somewhat ignored your current set up and set about solving the issue in another way.
Step 1: Reformat your data from the server side
I've put the posts into an array, and each post contains the template (just a string of html) and the equations separately as an array. I've used [e1] in the post as a placeholder for where the katex will go.
var postsFromServer = [{
content : `<div>
<h2>Crazy equation</h2>
<p>Look here!</p>
[e1]
</div>`,
equations : [
{
key : 'e1',
value : "c = \\pm\\sqrt{a^2 + b^2}"
}
]
}];
Step 2: When the post is rendered, do some work on it
Rather than just use v-html="post.content", I've wrapped the html output in a method
<div id="app">
<div v-for="post in posts" v-html="parsePostContent(post)">
</div>
</div>
Step 3: Create a method that renders all the katex, and then replaces the placeholders in the post
methods : {
parsePostContent(post){
// Loop through every equation that we have in our post from the server
for(var e = 0; e < post.equations.length; e++){
// Get the raw katex text
var equation = post.equations[e].value;
// Get the placeholder i.e. e1
var position = post.equations[e].key;
// Replace [e1] in the post content with the rendered katex
post.content = post.content.replace("[" + position + "]", katex.renderToString(equation));
}
// Return
return post.content;
}
}
Here is the whole set up, which renders Katex:
https://codepen.io/EightArmsHQ/pen/qxzEQP?editors=1010
I have a simple dom-if in a template:
<div>
<template is="dom-if" if="{{checkListEmpty()}}" restamp>
<paper-button raised class="init" on-tap="initialize">Initialize</paper-button>
</template>
</div>
and a function to show or hide.
checkListEmpty() {
return this.todos.length == 0;
}
It works for the first time only. If the this.todos.length becomes 1 then the template does not goes away. How can i hide when the condition is false.
There is no binding working for your function because there is no property to bind.
To make it work you should add a property in parameter : checkListEmpty(foo).
Like that, everytime the property foo change the function will be executed.
However an array as property won't work if the content of this one changed (content pushed) except if this is the global array property that is replaced :
var bar = [], foo = ["ggg"];
bar = foo;
In that case the function will be called, but it's not great.
Anyway for your question you can use an hidden property for the paper-button or bind the DOM-IF with the table length.
<template is="dom-if" if="[[!bar.length]]" restamp>
<paper-button raised on-tap="addBar">Initialize</paper-button>
</template>
or
<paper-button raised on-tap="addBar" hidden="[[bar.length]]">Initialize</paper-button>
And then everytime a property is added into the array or removed until there is nothing in it your button will be displayed or not.
You can see a working jsfiddle (use chrome though and be patient for the initialization.. comment here if it's not working)
I have multiple div like below
<div class="one">send Message</div>
<div class="one">send Message</div>
<div class="one">send Message</div>
I have a web page where there is send Message buttons like above, in which only one button is visible at a time.Other two buttons are hidden via some javascript codes.So for example if 2nd button is visible , I should be able to click only that element.But in my selenium code , its trying to click first hidden div and its failing
driver.findElements(by.className(".one")).then((els) => {
var element = els[index];
element.click();
});
So basically I wanna convert below javascript code to Selenium nodejs code,If some one guide me that will be helpful
var all = document.getElementsByTagName("*");
for (var i = 0, max = all.length; i < max; i++) {
if (isHidden(all[i]))
// hidden
else
// visible
}
function isHidden(el) {
var style = window.getComputedStyle(el);
return ((style.display === 'none') || (style.visibility === 'hidden'))
}
Do you want to click the button ( basically a div as far as code is concerned ) which is visible ?
If that is your main agenda, then the code you've written will fail to find desired element. As you are selecting the element by it's classname not its visibility.
Your code will find all the matched class element. As it's a basic element selector and all your buttons have the same class, so they are basically rendered on the page.
Approach 1
driver.findElements(by.className(".one")).then((els) => {
for(var key in els){
var element = els[key];
if(element.isDisplayed()){ //if visible element
element.click();
}
}
});
The Key here is to check if the element you are trying to click is visible on the page.
Approach 2
By giving a unique class to the target button. Some class for eg 'clickable' or 'active'. So it will be a more optimized solution to select the target element using the Css selector. The Key here is to give uniqueness to your target element to be more identifiable.
Usually many Java Scripts are run in the node Js without the convert.
Have you try it in the node Js without converting ???
** Remember to import selenium
I have two Hyper Links on to a DOJO DIv
var create = dojo.create("div",{
id:"create_links",
className:"iconRow1",
innerHTML:"<a class='popupLink' href='javascript:openCreateDialog()'>Create </a> <span>|</span><a href='javascript:openUploadDialog()'>Batch </a>"
},dojo.query(".ui-jqgrid-titlebar")[0]);
On click of the Batch Hyperlink , i have a function
function openUploadDialog()
{
// Here i want to disable the Create Hyper Link tried this way
dojo.byId('create_links')[1].disabled=true; // Not working
}
See whether i can answer your question.
HTML Part:
<div id="create_links">
g
h
</div>
JS Part:
dojo.addOnLoad(function() {
var a = dojo.query("#create_links a")[1];
dojo.connect(a,'click',function(e){
console.log(e.preventDefault())
})
})
#Kiran, you are treating the return of dojo.byId('create_links') like an array when that statement will return to you a node on the dom.
Also, hyperlinks don't support a disabled attribute to prevent them from being actionable. You could probably create a click handler that returns false to accomplish this type of functionality, or like #rajkamal mentioned, calling e.preventDefault(). #rajkamal also provides a good solution to selection the link properly.