Blazor Virtualize then run JavaScript Method - javascript

I have a Virtualize table that does exactly what it needs to do and does it great. I now have the need to add a tooltip/popover to certain rows. This is not difficult, but I am noticing that it will not get the JavaScript portion of the setup for these tooltips to run. It seems like when I click in the table somewhere it realizes things have changed and calls the JS setup function. At that point it works great.
<Virtualize #ref="RowsVirtualizerLeft" ItemsProvider="RowsLoader" Context="row" ItemSize="RowHeight">
<ItemContent>
...
And the loader:
public async ValueTask<ItemsProviderResult<VirtualizedRow>> RowsLoader(ItemsProviderRequest request)
{
var result = BodyRows
.Where(x => x.IsVisible);
return new ItemsProviderResult<VirtualizedRow>(
result
.Skip(request.StartIndex)
.Take(request.Count),
result.Count()
);
}
The overridden OnAfterRender event:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JSRuntime.InvokeVoidAsync("setupPopovers");
}
And lasty the JavaScript method:
function setupPopovers() {
const triggers = document.querySelectorAll("[id^='swift-trigger-']");
triggers.forEach((trig) => {
...
That is until I scroll down the dataset to a new area of data. These new rows also need to be "setup" and are not. It seems like any interaction on the table (like the click) will alert Blazor to call the OnAfterRenderAsyn which in turn calls me JS setup function and those tooltips now work.
So, my question is, is there a way to know when the UI is done rendering from the Virtualize component? I am not seeing an event I can catch from it? I suppose at worst, I could make a call that is paused for a second and then does the setup, but that does not seem to be a guarantee to always be enough time. Not like I would want to slow the app down either.
Is there a JavaScript page event I can catch that knows when things are done rendering? Perhaps whatever JavaScript Microsoft is doing has a way to catch an event from there?
More Info
I am not able to add any third party projects to the solution. So the suggestion by #Vikram-reddy will not work. We have the Bootstrap 5 (css only, not JS) and Popper.js. Plus whatever we write ourselves.

const triggers = document.querySelectorAll("[id^='swift-trigger-']");
The tooltip will not work in a virtualization scenario.
Please try the below component from BlazorBootstrap.
Demo: https://demos.getblazorbootstrap.com/tooltips
Github: https://github.com/vikramlearning/blazorbootstrap
Sample Code:
<Tooltip Title="Tooltip Left" Placement="TooltipPlacement.Left">Tooltip Left</Tooltip>
OR
Use the below approach on each row when it renders
var exampleTriggerEl = document.getElementById('example')
var tooltip = bootstrap.Tooltip.getOrCreateInstance(exampleTriggerEl)
tooltip.show();

Related

"No element found using locator..." when performing e2e testing with cucumber

I get the folowing failure on several features of Cucumber "NoSuchElementError: No element found using locator: By(css selector, h1)"
I have tried to set a bigger timeout in order to give Cucumber more time to find the elements, but it doesn't seem to work
Here are the main components of teh test:
cardTitle.feature:
#cardTitle-feature
Feature: See card title
Display the card title
#cardTitle-scenario
Scenario: Card Page
Given I am on the card page
When I do nothing
Then I should see the card title
app.steps.ts:
// Go to the card - Display the title
Given(/^I am on the card page$/, async () => {
await page.navigateToCard();
});
When(/^I do nothing$/, () => {
});
Then(/^I should see the card title$/, async () => {
expect(await page.getCardTitleText()).to.equal('Profile');
});
app.po.ts:
navigateToCard() {
this.sleep(3000);
return browser.get('/card');
}
getCardTitleText() {
this.sleep(3000);
return element(by.css('h1')).getText();
}
card.html:
<div class="profile-container">
<!-- EXAMPLE TOP NAV -->
<h1>Profile</h1>
...
I think that this could happen because "card" may not be accessible without login in the application. If this is the problem, how could I perform a test that logs into the application and then checks the "h1" element?
Thank you!
Great write up! Thanks for all the detail, that helps a lot. Here are my thoughts:
Don't you need to await the sleeps in your page object? Or promised chain the sleep to the get, if you're avoiding async functions in your page object for some reason.
Given that you're using protractor, and presumably this is an angular app, I'm surprised that you would need sleeps at all. Usually the built in waitForAngular that runs as part of every browser.get should wait until the page is fully loaded, before continuing
Your css looks perfect, that should work just fine. You can locate both html tags and attribute with css.
I'm assuming this.sleep is a method in your page object base class or something? I was kind of expecting browser.sleep.
Btw, I've gotten a lot of mileage out of the $ and $$ aliases in protractor, they're so clean. You could shorten
return element(by.css('h1')).getText();
To just
return $('h1').getText();
If you prefer.

Making a Chrome extension for a site that uses React. How to persist changes after re-rendering?

I want my extension to add elements to a page. But the site uses React which means every time there's a change the page is re-rendered but without the elements that I added.
I'm only passingly familiar with React from some tutorial apps I built a long time ago.
I implemented #wOxxOm's comment to use MutationObserver and it worked very well.
static placeAndObserveMutations (insertionBoxSelector) {
let placer = new Placement ();
placer.place(insertionBoxSelector);
placer.observeMutations(insertionBoxSelector);
}
place (insertionBoxSelector) {
let box = $(insertionBoxSelector)
this.insertionBox = box; //insertionBox is the element that the content
// will be appended to
this.addedBox = EnterBox.addInfo(box); //addedBox is the content
// Worth noting that at this point it's fairly empty. It'll get filled by
// async ajax calls while this is running. And all that will still be there
// when it's added back in the callback later.
}
observeMutations(insertionBoxSelector) {
let observer = new MutationObserver (this.replaceBox.bind(this));
// this.insertionBox is a jQuery object and I assume `observe` doesn't accept that
let insertionBox = document.querySelector(insertionBoxSelector);
observer.observe(title, {attributes: true});
}
replaceBox () {
this.insertionBox.append(this.addedBox);
_position (this.addedBox);
}
That being said someone suggested adding the content above the React node (i.e. the body) and just positioning the added content absolutely relative to the window. And as this will actually solve a separate problem I was having on some pages of a site I'll probably do that. Also, it's far simpler.
But I thought this was still an interesting solution to a rare problem so I wanted to post it as well.

How to deal with DOM elements?

I am learning about writing custom JavaScript for my Odoo 10 addons.
I've written the following piece of code:
odoo.define('ioio.io', function(require) {
'use strict'
const e = $('div.o_sub_menu_footer')
console.log('--testing--'.repeat(7))
console.log(e)
// the "Powered by Odoo" down the secondary menu
e.remove()
})
The code is well loaded and I can see my testing string in the console.
However when this code is being loaded before the target div, so e empty/not yet filled and thus its content is not removed.
Doing it manually from the console works.
My question is what is the right way to do that? And how to know exactly when the code gets executed?
You can
put your html code before the script tag in your file
use jQuery $(document).ready(...);
Place your script at the bottom of the <body> tag to make sure the DOM renders before trying to manipulate it.
This is an Odoo specific question, so you should use the Odoo standard way, which is via its base JS class. That class contains a ready() method which does exactly what you need.
In your case, to use that function, you need to require the class first. Then you can use ready().
Updating your code, it should look like this:
odoo.define('ioio.io', function(require) {
'use strict'
// require base class
var base = require('web_editor.base');
//use its ready method
base.ready().done(function () {
// put all the code you want to get loaded
// once the DOM is loaded within this block
const e = $('div.o_sub_menu_footer')
console.log('--testing--'.repeat(7))
console.log(e)
// the "Powered by Odoo" down the secondary menu
e.remove()
});
})
While your accepted answer leads to the same outcome, you might want to update it to this one since this is the Odoo way. It's generally advised to work within the Odoo framework as much as possible and customise only if really needed. (Though it can be tough to learn what features Odoo already provides because of its poor documentation.)

Qt function runJavaScript() does not execute JavaScript code

I am trying to implement the displaying of a web page in Qt. I chose to use the Qt WebEngine to achieve my task. Here's what I did :
Wrote a sample web page consisting of a empty form.
Wrote a JS file with just an API to create a radio button inside the form.
In my code, it looks like this :
View = new QWebEngineView(this);
// read the js file using qfile
file.open("path to jsFile");
myJsApi = file.Readall();
View->page()->runjavascript (myjsapi);
View->page()->runjavascript ("createRadioButton(\"button1\");");
I find that the runJavaScript() function has no effect on the web page. I can see the web page in the output window, but the radio button I expected is not present. What am I doing wrong?
I think you will have to connect the signal loadFinished(bool) of your page() to a slot, then execute runJavaScript() in this slot.
void yourClass::mainFunction()
{
View = new QWebEngineView(this);
connect( View->page(), SIGNAL(loadFinished(bool)), this, SLOT(slotForRunJS(bool)));
}
void yourClass::slotForRunJS(bool ok)
{
// read the js file using qfile
file.open("path to jsFile");
myJsApi = file.Readall();
View->page()->runJavaScript(myjsapi);
View->page()->runJavaScript("createRadioButton(\"button1\");");
}
I had this problem, runJavascript didn't have any effect. I had to put some html content into the view (with page().setHtml("") before running it.
Check the application output, it might contain JavaScript errors. Even if your JS code is valid, you might encounter the situation where the script is run before DOMContentLoaded event, that is document.readyState == 'loading'. Therefore, the DOM might not be available yet, as well as variables or functions provided by other scripts. If you depend on them for your code to run, when you detect this readyState, either wait for the event or try calling the function later, after a timeout. The second approach with timeout might be needed if you need to get the result of the code execution, as this can be done only synchronously.

Testing tab navigation order

In one of our tests, we need to make sure that the tab keyboard navigation inside a form is performed in the correct order.
Question: What is the conventional way to check the tab navigation order with protractor?
Currently we are solving it by repeating the following step for as many input fields existing in a form (code below):
check the ID of the currently focused element (using getId())
send TAB key to the currently focused element
Here is the example spec:
it("should navigate with tab correctly", function () {
var regCodePage = new RegCodePage();
browser.wait(protractor.ExpectedConditions.visibilityOf(regCodePage.title), 10000);
// registration code field has focus by default
expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Remember Registration Code
regCodePage.registrationCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.rememberRegistrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Request Code
regCodePage.rememberRegistrationCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.requestCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Cancel
regCodePage.requestCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.cancelButton.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved back to the input
regCodePage.cancelButton.sendKeys(protractor.Key.TAB);
expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
});
where regCodePage is a Page Object:
var RegCodePage = function () {
this.title = element(by.css("div.modal-header b.login-modal-title"));
this.registrationCode = element(by.id("regCode"));
this.rememberRegistrationCode = element(by.id("rememberRegCode"));
this.requestCode = element(by.id("forgotCode"));
this.errorMessage = element(by.css("div.auth-reg-code-block div#message"));
this.sendRegCode = element(by.id("sendRegCode"));
this.cancelButton = element(by.id("cancelButton"));
this.closeButton = element(by.css("div.modal-header button.close"));
};
module.exports = RegCodePage;
It is working, but it is not really explicit and readable which makes it difficult to maintain. Also, another "smell" in the current approach is a code duplication.
If the current approach is how you would also do it, I would appreciate any insights about making it reusable.
I think the PageObject should define a tab order list, since that is really a direct property of the page, and should be expressible as simple data. An array of items seems like a sufficient representation, so something like:
this.tabOrder = [ this.registrationCode, this.rememberRegistrationCode, this.requestCode, this.cancelButton ];
Then you need a bit of generic code that can check a tab order.
function testTabOrder(tabOrder) {
// Assumes TAB order hasn't been messed with and page is on default element
tabOrder.forEach(function(el) {
expect(el.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
el.sendKeys(protractor.Key.TAB);
});
}
Then your test would be something like:
it('has correct tab order', function() {
var regCodePage = new RegCodePage(); // this should probably be in the beforeEach
testTabOrder(regCodePage.tabOrder);
});
Of course, this assumes each element has a "getId()" method that works. (That seems like a reasonable assumption to me, but some environments may not support it.)
I think this keeps the tab-order nicely isolated on the PageObject (so its easy to keep in sync with the page content and doesn't get lost in the code that verifies the order). The testing code seem "optimistic" (I suspect the real world will introduce enough problems that you will end up expanding this code a bit).
I haven't tried any of this yet, so feel free to downvote if this doesn't work. :)
Also, I believe the forEach loop will work as-is, but I wouldn't be surprised if it needs some more explicit promise handling to make the dependencies explicit.

Categories

Resources