Using a function to add to an existing xpath? - javascript

I have this example html, for which I have the following xpaths defined in a POM:
<button>
Foo
<i class="myClass" label="a label">Something</i>
</button>
<button>
Bar
<i class="myClass" label="another label">Something</i>
</button>
class myPage {
constructor() {
this.fooButton = () => $('//button[text()=\'Foo\']');
this.barButton = () => $('//button[text()=\'Bar\']');
}
}
I want to access the inner text from the myClass iframe, but don't want to define new constructor elements for it - feels like repeating code:
this.insideSomething = () => $('//button[text()=\'Foo\']/i[text()=\'Something\']');
Is there a way to perhaps only define the end of the xpath in its own function? Something like this:
somethingText(parentElement) {
return parentElement + '/i[text()=\'Something\']';
}
... so I can call it like this, for example:
expect(somethingText(MyPage.FooButton).isDisplayed()).to.be.true;

You could try using the "starts with" attribute selector operator ^=:
this.fooButton = () => $('//button[text()^=\'Foo\']');
More info on attribute selectors

Related

Dynamic onClick over void functions on iteration from HTML in Angular

I´ve been wondering if is possible to set in an object some items with values being void functions, thus once this object is iterated in HTML, on a click i could trigger those methods functionalities.
Lets say i have two methods
public method1():void{
...do something
},
public method2():void{
...do something
}
then i create an object containing this to methods of type void:
const objArray=[
{click:this.method1()},
{click:this.method2()}
]
then the idea would be to iterate over this array of objects in the HTML and trigger its functionality
HTML
<ul >
<span *ngFor="let item of objArray">
<a
(click)="(item.click)"
>
</a>
</span>
</ul>
on this approach as expected the value of each item is undefined thus I tried to modified the methods array to :
const objArray=[
{click:()=>this.method1()},
{click:()=>this.method2()}
]
Still not triggering anything.
How can i improve this ?
Thanks in advance!
TLDR you forgot brackets in your html
<ul>
<span *ngFor="let item of objArray">
<a (click)="item.click()">Click Me</a>
</span>
</ul>
You can write it this way to assign a reference to the methods:
With this way, any uses of this inside the method will refer to the new object and not your component
const objArray=[
{ click: this.method1 },
{ click: this.method2 }
]
or this way to create a new function that executes the methods:
With this way, this inside the method will refer to your component
const objArray=[
{ click: () => this.method1() },
{ click: () => this.method2() }
]
But in either case you forgot to actually call the function in your html (no brackets)
<ul>
<span *ngFor="let item of objArray">
<a (click)="item.click()">Click Me</a>
</span>
</ul>
Example: https://stackblitz.com/edit/angular-ivy-8awbxg?file=src/app/app.component.ts

How to get class of the element in stimulus.js

I want to toggle elements and I need a class names for that.
How can I get a class name of the nested element in stimulus.js and change it?
F.I, I need to toggle the "ul" element that is initially hidden.
div data-controller="my_controller"
a data-action="click->my_controller#toggle_my_elements"
| Click
ul.is-hidden id="my-id" data-target="my_controller.mytext"
li
| Text to be toggled.
and in stimulus controller I have:
import { Controller } from 'stimulus'
export default class extends Controller {
static targets = ["mytext"]
toggle_my_elements(){
console.log("debuggin") //Ok
const class_name = this.mytextTarget.className
}
}
I tried to call a js function className but it seems js functions don't work in the way they used to.
I just can't figure out how to get it.
As StimulusJS target is a HTML element, you can use its classList property
this.mytextTarget.classList.remove('is-hidden')
You could do the following to get the ul class:
static targets = [ "mytext" ]
connect() {
this.mytextClass = this.data.get("class") || "is-hidden"
}
Then use the following action descriptor to toggle your ul element
toggle(event) {
event.preventDefault();
this.mytextTargets.forEach(target => {
target.classList.toggle(this.mytextClass)
})
}
Have you tried element[:class]?
That's how I access the class of the html element from a Stimulus Reflex in ruby since element.class returns the class of the element (a StimulusReflex::Element) instead of the "btn btn-primary" String I was expecting.

javascript created template element not working [duplicate]

This question already has answers here:
Cannot get content from template
(2 answers)
Closed 3 years ago.
If I write a template node into the HTML by hand I can use it in my custom element just fine. If I create a template node and append it to the HTML using javascript when I try to use it, it's empty...
in the example below I make template-a the regular HTML way and make template-b to be the same shape using javascript. I define a very simple custom element that uses both templates. only template-a is visible.
const sandbox = document.getElementById('sandbox')
const slot = document.createElement('slot')
slot.setAttribute('name', 'b')
slot.append('slot content goes here')
const em = document.createElement('em')
em.append(slot, '?')
const createdTemplate = document.createElement('template')
createdTemplate.setAttribute('id', 'template-b')
createdTemplate.append(em)
sandbox.append(createdTemplate)
customElements.define('test-element', class extends HTMLElement {
constructor () {
super()
this.attachShadow({ mode: 'open' }).append(
...['template-a','template-b']
.map(id =>
document.getElementById(id).content.cloneNode(true)
)
)
}
})
<div id="sandbox">
<template id="template-a">
<strong><slot name="a">slot content goes here</slot>!</strong>
</template>
<test-element>
<span slot="a">some a slot content</span>
<span slot="b">some b slot content</span>
</test-element>
</div>
Some notes on your code:
this.shadowRoot
this.shadow = this.attachShadow({ mode: 'open' })
can become
this.attachShadow({ mode: 'open' })
this creates/sets this.shadowRoot for free
appendChild vs. append
Note that .appendChild(el) takes one element
and .append() takes an Array
Only difference is appendChild() returns a reference to the inserted element,
and append() returns nothing
So you can write:
em.appendChild(slot)
em.appendChild(document.createTextNode('?'))
as
em.append(slot, document.createTextNode('?'))
If you have Nodes in an Array:
let myElements = [slot, document.createTextNode('?')];
you can use the ES6 spread opperator:
em.append(...myElements)
This means you can write:
this.shadow.appendChild(document.getElementById('template-a').content.cloneNode(true))
this.shadow.appendChild(document.getElementById('template-b').content.cloneNode(true))
as:
this.shadowRoot
.append(
...['a','b']
.map(templ => document.getElementById(`template-${templ}`).content.cloneNode(true))
)
template nodes have a special content attribute which holds their children. (which I kind of knew but thought it was a little more magical than it is). If this line:
createdTemplate.append(em)
is changed to
createdTemplate.content.append(em)
then it all works
const sandbox = document.getElementById('sandbox')
const slot = document.createElement('slot')
slot.setAttribute('name', 'b')
slot.append('slot content goes here')
const em = document.createElement('em')
em.append(slot, '?')
const createdTemplate = document.createElement('template')
createdTemplate.setAttribute('id', 'template-b')
createdTemplate.content.append(em)
sandbox.append(createdTemplate)
customElements.define('test-element', class extends HTMLElement {
constructor () {
super()
this.attachShadow({ mode: 'open' }).append(
...['template-a','template-b']
.map(id =>
document.getElementById(id).content.cloneNode(true)
)
)
}
})
<div id="sandbox">
<template id="template-a">
<strong><slot name="a">slot content goes here</slot>!</strong>
</template>
<test-element>
<span slot="a">some a slot content</span>
<span slot="b">some b slot content</span>
</test-element>
</div>

Create a list of settings with Next and Previous buttons in the DOM procedurally

I wanted to create a list of settings that a user can change in the HTML procedurally through javascript.
Much like this: Quality: - 0 +
My approach to this was making an Option class with a value property and prev() and next() methods that change the value within its range. I'm extending this class so it can be a Range, Bool, etc. This is working fine.
My issue is that I can't seem to be able to incorporate this into the HTML. My current solution works only for the last option created, the others don't trigger the onclick event functions, and even if they did I don't feel like this is the right approach to it. How can I make this work in an elegant way?
I have tried the solution shown in this question but it prevents me from accessing the class instance with this.
class UIManager {
constructor (wrapperID, settings) {
this.wrapper = document.getElementById(wrapperID)
this.settings = settings
}
updateUI () {
this.wrapper.innerHTML = ``
for (let id = 0; id < this.settings.options.length; ++id) {
let option = this.settings.options[id]
this.wrapper.innerHTML += `
<li>
<div class="label">
${option.name}
</div>
<div class="option">
<input id="prev${id}" class="open" type="button" value="<">
${option.value}
<input id="next${id}" class="close" type="button" value=">">
</div>
</li>
`
let prevButton = document.getElementById(`prev${id}`)
let nextButton = document.getElementById(`next${id}`)
prevButton.onclick = _ => {
this.settings.options[id].prev()
this.updateUI()
}
nextButton.onclick = _ => {
this.settings.options[id].next()
this.updateUI()
}
}
}
}
The answer to the question you linked as a potential solution is usable so long as you bind the class instance member this to the anonymous function using Function.protoype.bind.
The code would look something like this (using the previously linked answer as the starting point):
for ( var i = 0; i < itemLists.length; i++ ) (function(i){
itemLists[i].onclick = function() {
// do something using `this`
}
}).bind(this)(i);
You mentioned in the comments that this didn't work, but that it was related to overwriting innerHTML and not due to the binding.
Hope this gives a small part of the larger picture.
Try closure to store the values required for functions created from inside the loop.
Something like this :
prevButton.onclick = (function(settings, updateUI) {
return function() {
settings.options[id].prev();
updateUI();
};
})(this.settings, this.updateUI);
nextButton.onclick = (function(settings, updateUI) {
return function() {
settings.options[id].next();
updateUI();
}
})(this.settings, this.updateUI);

Using an Array of buttons in React

In react I have a code like this:
var myButtons=[];
/*Products is an array of objects where each object identify a product*/
for (var p of Products) {
var button = <button
style={someStyle}
onClick={onClickFunction}>
p.name
</button>
myButtons.push(button)
}
I will use this react array of buttons on a render command. The problem I have is that I do not know how to make one of these buttons to show its label p.name through the onClickFunction.
A simpler more user friendly way is to iterate the data with a function. (note that this does not take into account scope, so this may be needed if it's inside a component)
function makeButton(data) {
return (
<button
style={someStyle}
onClick={() => onClickFunction(data.label)}> //pass parameter for callback here if binding isn't used
data.name
</button>
);
}
Now you can simply use a binding map inside your div!
<div>
{Products.map(makeButton, this)}
</div>
You can add your label as paremeter :
<button style={someStyle} onClick={p.name => onClickFunction(p.name)}>
p.name
</button>
And :
onClickFunction = (label) => () =>{
console.log(label)
}
The easiest way is to use ES6 syntax and array map.
The name property should be unique, and don't forget provide a key for each button:
const myButtons = Products.map(p => (
<button style={someStyle} onClick={e=>{ this.onClickFunction(e, p.name); }} key={p.name}/>
{p.name}
</button>
));
Using an arrow function, so it doesn't require .bind(this). Add e.preventDefault() to prevent default behavior, if the buttons are in a form.
onClickFunction = (e, name) => {
e.preventDefault();
// Your button behavior goes here.
}

Categories