The space between the two horizontal rules is clickable, although there is no a tag to target a selector onto. I have tried probably 10 selectors at this point and none have been successful. I would like any suggestions you may have for targeting difficult selectors for a click.
Patterns I have tried:
const clickTarget = await page.waitForSelector(selector, {timeout: 0});
await clickTarget.click();
// alternatively
await page.waitForNavigation("load");
const clickTarget = await page.$(selector);
await clickTarget.click();
Some selectors used (all have failed)
"#StoreListItemContainer"
"p[id=StoresListItem-LabelOnHover]"
"div.Polaris-ResourceItem_Content"
"div#SeanDezoysa"
The error message
TypeError: Cannot read property 'click' of null
42 |
43 | const devStoreLink = await page.$(".Polaris-ResourceItem__Content");
> 44 | await devStoreLink.click();
| ^
Related
I'm trying to figure out a way to build a progress bar with React. I have a forEach loop that iterates through an array of about 7,000 indexes. Each time I validate a row, I want to update a state variable with percentage completion (and render this on the page live). I've tried iterating through these indexes, and updating my state variable (hoping to update the page) in the loop but I'm realizing that will not work. I obviously can't do this with a normal variable as it will reset when the component re-renders. Can anyone give me some insight on this topic?
Thanks.
Here is a code snippet from what I'm looking at:
parsedAssets.forEach(asset => {
newAssetValidated = validateBulkUpload(asset, parsedAssets, assetList, accountLogged, jobSites);
!newAssetValidated.reject_err ? validatedAssetList.accepted.push(newAssetValidated) : validatedAssetList.rejected.push(newAssetValidated);
setStateAssets({ ...stateAssets, validatedAssetList });
});
}
So essentially, as each asset is either accepted or rejected we add it to "stateAssets", and I'm hoping to build the progress bar from the length of the combined arrays that are getting set in stateAssets. However, when the forEach loop is completed, only the last validated asset is getting set due to it not updating until the forEach loop is completed.
Personally I can't imagine such a heavy validation, that you need progress-bar, but anyway.
First solution is to separate validation itself from state update for progress-bar into separate "threads". But since JS is single threaded, you may use some tricks with setTimeout or setInterval functionality. It may be very tricky, and in general not recommended practice with React.
Another way is - to set the work into queue & process 1 item at a time.
As an example I would do it something like this:
function ComponentWithProgress({parsedAssets, setParsedAssets}) {
const [validatedAssetList, setValidatedAssetList] = useState([])
const [progress, setProgress] = useState(0)
const [toDo, setToDo] = useState([])
if(parsedAssets && parsedAssets.length>0) {
setToDo(parsedAssets)
// clear parsedAssets in parent component to: [], false, null ...
// so you put it into toDo only once
setParsedAssets([])
}
if(toDo.length > 0) {
const asset = toDo[0]
const newToDo = toDo.slice(1) // All but 0th element
const newAssetValidated = validateBulkUpload(asset);
setValidatedAssetList([ ...validatedAssetList, newAssetValidated ]);
setToDo(newToDo)
setProgress( newToDo.length / ( validatedAssetList.length + newToDo.length ) * 100 )
}
// ... Render here
// If you need only accepted
const accepted = validatedAssetList.filter(v => !v.reject_err)
}
This example maybe not work for you as is, because you didn't showed us the context, but the main idea is here.
I am new to Google Apps Script and trying to generate a document using Excel Data.
I am able to successfully create the document and add tables and paragraphs.
I see some odd behavior with appendParagraph.
It adds a space (blank like) when adding the first paragraph. The the paragraphs that follow are fine.
I tried replacing the new line (\n) with '', but did not work.
Any suggestion how to get rid of the line or add a paragraph without the blank line for the first paragraph (Sample Code below).
titleStyle[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.CENTER
titleStyle[DocumentApp.Attribute.FONT_SIZE] = 19
titleStyle[DocumentApp.Attribute.FONT_FAMILY] = 'Arial'
titleStyle[DocumentApp.Attribute.BOLD] = true
const compTitle = cell.appendParagraph('TITLE PARAGRAPH')
compTitle.setAttributes(titleStyle)
const contentStyle = {}
contentStyle[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.CENTER
contentStyle[DocumentApp.Attribute.FONT_SIZE] = 11
contentStyle[DocumentApp.Attribute.FONT_FAMILY] = 'Arial'
contentStyle[DocumentApp.Attribute.BOLD] = false
const content = cell.appendParagraph('CONTENT PARAGRAPH')
content.setAttributes(contentStyle)
As Cooper mentioned, your documents start off with a blank paragraph. If you want to remove it programmatically, run the following function:
function removeEmptyPara() {
const paras = DocumentApp.getActiveDocument().getBody().getParagraphs();
const firstPara = paras[0];
if (paras.length > 1 && !firstPara.getText()) firstPara.removeFromParent()
}
Let me know if this helps.
I need to add a space after every 4th digit I enter, I am getting this in the console, how can I can achieve this to change in the input in Angular 5.
Code I used given below .ts
mychange(val) {
const self = this;
let chIbn = val.split(' ').join('');
if (chIbn.length > 0) {
chIbn = chIbn.match(new RegExp('.{1,4}', 'g')).join(' ');
}
console.log(chIbn);
this.id = chIbn;
}
HTML
<input class="customerno" (ngModelChange)="mychange($event)" [formControl]="inputFormControl" (keydown.backspace)="onKeydown($event)" maxlength="{{digit}}" (keyup)="RestrictNumber($event)" type="tel" matInput [(ngModel)]="id" placeholder="Customer No. ">
Console:
1
11
111
1111
1111 1
1111 11
1111 111
1111 1111
1111 1111 1
1111 1111 11
1111 1111 111
1111 1111 1111
I adapted it from Angular 2 : add hyphen after every 4 digit in input , card number input. but I changed the hypen to a space.
I would recommend to check out this Directive
import { Directive, HostListener, ElementRef } from '#angular/core';
#Directive({
selector: '[credit-card]'
})
export class CreditCardDirective {
#HostListener('input', ['$event'])
onKeyDown(event: KeyboardEvent) {
const input = event.target as HTMLInputElement;
let trimmed = input.value.replace(/\s+/g, '');
if (trimmed.length > 16) {
trimmed = trimmed.substr(0, 16);
}
let numbers = [];
for (let i = 0; i < trimmed.length; i += 4) {
numbers.push(trimmed.substr(i, 4));
}
input.value = numbers.join(' ');
}
}
and use in your html template as
<input type="text" credit-card>
Here is the
source code
UPDATE: (10/10/2019)
Input type should be only text (default type)
Don't forget to handle Backspace, Cursor Position, and American Express
I had to handle some extra complexity, but it's likely what many people will confront when developing this. We need to consider use of the Backspace key and arrow keys when rewriting the input value. There is also American Express numbers to consider, which are not simply 4-4-4-4 numbers.
Here's how I did it in a component using a template reference variable and cursor position detection. (No need for a custom directive if you only have one component that is taking credit card numbers, which is often the case.)
To handle Backspace and cursor arrow keys, we have to store the original cursor position and restore it after editing from a spot anywhere other than the end of the string.
To enable handling of American Express, I use a variable called partitions to store the 4-6-5 spacing format for Amex and the 4-4-4-4 spacing format for all other cards. We loop partitions as we add spaces.
/* Insert spaces to make CC number more legible */
cardNumberSpacing() {
const input = this.ccNumberField.nativeElement;
const { selectionStart } = input;
const { cardNumber } = this.paymentForm.controls;
let trimmedCardNum = cardNumber.value.replace(/\s+/g, '');
if (trimmedCardNum.length > 16) {
trimmedCardNum = trimmedCardNum.substr(0, 16);
}
/* Handle American Express 4-6-5 spacing format */
const partitions = trimmedCardNum.startsWith('34') || trimmedCardNum.startsWith('37')
? [4,6,5]
: [4,4,4,4];
const numbers = [];
let position = 0;
partitions.forEach(partition => {
const part = trimmedCardNum.substr(position, partition);
if (part) numbers.push(part);
position += partition;
})
cardNumber.setValue(numbers.join(' '));
/* Handle caret position if user edits the number later */
if (selectionStart < cardNumber.value.length - 1) {
input.setSelectionRange(selectionStart, selectionStart, 'none');
}
}
If you have a routine of your own to detect American Express numbers, use it. What I'm using here simply examines the first two digits and compares them to PAN/IIN standards.
Higher in your component, ensure you have the right imports:
import { ViewChild, ElementRef } from '#angular/core';
And:
#ViewChild('ccNumber') ccNumberField: ElementRef;
And when you set up your form controls, do something like this so that spaces can be included in your regex pattern:
this.paymentForm = this.fb.group({
cardNumber: ['', [Validators.required, Validators.pattern('^[ 0-9]*$';), Validators.minLength(17)]]
})
And finally, in your template, configure your element like this:
<input maxlength="20"
formControlName="cardNumber"
type="tel"
#ccNumber
(keyup)="cardNumberSpacing()">
You should be good to go!
In google chrome, especially now with custom elements, it became very cumbersome to select and element by hand nowadays, even though the browser knows the whole path to it already. Or is there a way that leads to a query selected for an element that I'm inspecting?
Situation:
What chrome can tell me:
What chrome is unable to create for me AFAIK:
While building an chrome extension I have found a need to uniquely locate an element when returning to a page. To do this I needed to create a query string for a selected element (custom context menu click)
While searching for a solution I found this unanswered question.
As I could not find an off the shelf solution or API to do the task I wrote the following function. It is untested in the wild, is very rough and ready (using poor node traversing techniques). I posted it in this state lest I forget and this question remains unanswered.
Create Query String For Element
A function to build a query string that will uniquely locate an element from a reference of the element.
const querytStr = createQueryStringForElement(myElement); // return string or undefined
if (querytStr) {
const element = document.querySelector(queryStr);
console.log(element === myElement); // expected result true
}
If the function fails to create a query that uniquely locates an element it returns undefined. else it returns the query string.
Example results
"#editor > div.ace_scroller > div.ace_content > div.ace_layer.ace_text-layer > div.ace_line:nth-child(45) > span.ace_punctuation.ace_operator"
"#buttons" // A UI container
"#buttons > div.buttons" // A sub UI container
"#buttons > div.buttons:nth-child(2)" // A button element by position
"#buttons > div.buttons:nth-child(3)" // A button element by position
How it works
The code assumes that the page is well formed (ids must be unique).
The query string will try to start with an id eg "#elementId" but if an element has no id the query will use the tag and class names. eg "div.my-class".
The tag and class name may not uniquely identify the element. To check if the query is unique, the query string is used to query the DOM from the elements parent.
If needed the query string will use the elements position to refine the query "div.my-class:nth-child(2)". Unfortunately this makes the resultant query string insensitive to changes in element order.
The query string is built up along each parent until it finds an element with an id or there are no more parents.
The final step uses the query to see if the query finds the correct element returning the query if successful.
The code
function createQueryStringForElement(element) {
const getElementSel = element => {
const tName = element.tagName.toLowerCase();
var i = 0, str = element.id ? "#" + element.id : sel = tName;
if (str.includes("#")) { return str}
str += element.classList.length ? "." + [...element.classList.values()].join(".") : "";
if (element.parentElement) {
const res = element.parentElement.querySelector(str);
if (res !== element) {
while (i < element.parentElement.children.length) {
if (element.parentElement.children[i] === element) {
i > 0 && (str += ":nth-child(" + (i + 1) + ")" );
break;
}
i++;
}
}
}
return str;
}
const queryPath = [];
const original = element;
do {
const subQuery = getElementSel(element);
queryPath.push(subQuery);
if (subQuery[0] === "#") { break }
element = element.parentElement;
} while (element);
const query = queryPath.reverse().join(" > ");
try {
const els = document.querySelector(query);
if (els === original) { return query }
} catch(e) { }
}
Playing with ImmutableJS, documentation needs work and actual working examples.
const a = [["a"],["b"],["c"]]
const b = Immutable.List(a)
const c = Immutable.OrderedSet(a)
b.first() // => "a"
b.get(1) // => "b"
c.first() // => ["a"]
c.get(1) // => undefined !uh oh!
c.toList().get(1) // => "b" !indirect!
Question: How do I print out the 2nd element of .OrderedSet c without converting it to a .List or loop over the entire list?
You can do it such as:
// using some ES6 features
let ordSet = immutable.OrderedSet([1, 2, 3])
let iterator = ordSet[Symbol.iterator] // get iterator to the collection
iterator.next() // 1
iterator.next() // 2
iterator.next() // 3
This said, let me note that even if this is not the nicest syntax, from the point of performance, it is the best as it gets: OrderedSet does not provide random access to its elements, each element simply remembers its successor and predecessor. Therefore, getting n-th element requires n hops, whether immutable.js provides some fancy helper for it or not. AFAIK, such linked-list-like implementation of OrderedSet is inevitable, if add / delete should remain fast.