I try to display a loading alert on Meteor with modal package during loading of data.
'change .filterPieChart': function(evt){
Modal.show('loadingModal');
/* a little bit of work */
var data = MyCollection.find().fetch(); // takes 3 or 4 seconds
/* lot of work */
Modal.hide('loadingModal');
}
Normally, the alert is displayed at the beginning of the function, and disappears at the end. But here, the alert appears only after the loading time of the MyCollection.find(), and then disappears just behind. How to display it at the beginning of the function ??
I tried to replace Modal.show with reactive variable, and the result is the same, the changing value of reactive variable is detect at the end of the function.
From what you describe, what probably happens is that the JS engine is busy doing your computation (searching through the collection), and indeed blocks the UI, whether your other reactive variable has already been detected or not.
A simple workaround would be to give some time for the UI to show your modal by delaying the collection search (or any other intensive computation), typically with a setTimeout:
Modal.show('loadingModal');
setTimeout(function () {
/* a little bit of work */
var data = MyCollection.find().fetch(); // takes 3 or 4 seconds
/* lot of work */
Modal.hide('loadingModal');
}, 500); // delay in ms
A more complex approach could be to decrease the delay to the bare minimum by using requestAnimationFrame
I think you need to use template level subscription + reactiveVar. It is more the meteor way and your code looks consistent. As i can see you do some additional work ( retrive some data ) on the change event. Make sense to actually really retrive the data on the event instead of simulation this.
Template.TemplateName.onCreated(function () {
this.subsVar = new RelativeVar();
this.autorun( () => {
let subsVar = this.subsVar.get();
this.subscribe('publicationsName', this.subsVar);
})
})
Template.TemplateName.events({
'change .filterPieChart': function(evt){
Template.instance().collectionDate.subsVar.set('value');
Modal.show('loadingModal');
MyCollection.find().fetch();
Modal.hide('loadingModal');
}
})
Please pay attention that i didn't test this code. And you need to use the es6 arrow function.
Related
I'm using Cypress 7.7.0 (also tested on 8.0.0), and I'm running into an interesting race condition. I'm testing a page where one of the first interactions that Cypress does is click a button to open a modal. To keep bundle sizes small, I split the modal into its own prefetched webpack chunk. My Cypress test starts with cy.get('#modal-button').click() but this doesn't load the modal because the modal hasn't finished downloading/loading. It does nothing instead (doesn't even throw any errors to the console). In other words, Cypress interacts with the page too quickly. This was also reproduced with manual testing (I clicked on the button super fast after page load). I have tried setting the modal to be preloaded instead, but that didn't work either.
I am able to solve the problem by introducing more delay between page load and button interaction. For example, inserting any Cypress command (even a cy.wait(0)) before I click on the button fixes the solution. Cypress, however, is known for not needing to insert these brittle solutions. Is there a good way to get around this? I'd like to keep the modal in its own chunk.
FYI: I'm using Vue as my front end library and am using a simple defineAsyncComponent(() => import(/* webpackPrefetch: true */ './my-modal.vue')) to load the modal component. I figure that this problem is general to Cypress though.
There's nothing wrong with cy.wait(0).
All you are doing is handing control from the test to the next process in the JS queue, in this case it's the app's startup script which is presumably waiting to add the click handler to the button.
I recently found that this is also needed in a React hooks app to allow the hook to complete it's process. You will likely also come across that in Vue 3, since they have introduced a hook-like feature.
If you want to empirically test that the event handler has arrived, you can use the method given here (modified for click()) - When Can The Test Start?
let appHasStarted
function spyOnAddEventListener (win) {
const addListener = win.EventTarget.prototype.addEventListener
win.EventTarget.prototype.addEventListener = function (name) {
if (name === 'click') {
appHasStarted = true
win.EventTarget.prototype.addEventListener = addListener // restore original listener
}
return addListener.apply(this, arguments)
}
}
function waitForAppStart() {
return new Cypress.Promise((resolve, reject) => {
const isReady = () => {
if (appHasStarted) {
return resolve()
}
setTimeout(isReady, 0) // recheck "appHasStarted" variable
}
isReady()
})
}
it('greets', () => {
cy.visit('app.html', {
onBeforeLoad: spyOnAddEventListener
}).then(waitForAppStart)
cy.get('#modal-button').click()
})
But note setTimeout(isReady, 0) will probably just achieve the same as cy.wait(0) in your app, i.e you don't really need to poll for the event handler, you just need the app to take a breath.
It seems like your problem is that you're already rendering a button before the code backing it is loaded. As you noticed, this isn't only an issue for fast automated bots, but even a "regular" user.
In short, the solution is to not display the button early, but show a loading dialog instead. Cypress allows waiting for a DOM element to be visible with even a timeout option. This is more robust than a brittle random wait.
I ended up going with waiting for the network to be idle, although there were several options available to me.
The cypress function I used to do this was the following which was heavily influenced by this solution for waiting on the network:
Cypress.Commands.add('waitForIdleNetwork', () => {
const idleTimesInit = 3
let idleTimes = idleTimesInit
let resourcesLengthPrevious
cy.window().then(win =>
cy.waitUntil(() => {
const resourcesLoaded = win.performance.getEntriesByType('resource')
if (resourcesLoaded.length === resourcesLengthPrevious) {
idleTimes--
} else {
idleTimes = idleTimesInit
resourcesLengthPrevious = resourcesLoaded.length
}
return !idleTimes
})
)
})
Here are the pros and cons of the solution I went with:
pros: no need to increase bundle size or modify client code when the user will likely never run into this problem
cons: technically still possible to have a race condition where the click event happens after the assets were downloaded, but before they could all execute and render their contents, but very unlikely, not as efficient as waiting on the UI itself for indication of when it is ready
This was the way I chose solve it but the following solutions would have also worked:
creating lightweight placeholder components to take the place of asychronous components while they download and having cypress wait for the actual component to render (e.g. a default modal that just has a spinner being shown while the actual modal is downloaded in the background)
pros: don't have to wait on network resources, avoids all race conditions if implemented properly
cons: have to create a component the user may never see, increases bundle size
"sleeping" an arbitrary amount (although this is brittle) with cy.wait(...)
pros: easy to implement
cons: brittle, not recommended to use this directly by Cypress, will cause linter problems if using eslint-plugin-cypress (you can disable eslint on the line that you use this on, but it "feels ugly" to me (no hate on anyone who programs that way)
I'm working with a script that needs to delay (or have a setTimeout) before any animation loads or initializes, but can't seem to figure out where to put it.
As for delay, if I'm not mistaken, this is used mainly with jquery...So for example: $('id or class here').delay(2000); ...Correct?
As for the setTimeout, if I'm not mistaken, it would be with javascript correct? If so, wouldn't it look something similar to this: setTimeout(function () {function_name},2000); or a slightly different variation of that?
Regardless of these two approaches and trying to add it where I think it should go (using either variations mentioned above), for some reason it just doesn't work right. The console isn't really helping either to check for errors.
In a nutshell, I'm trying to set a delay of 2s (2000ms) before anything starts or initializes.
JS CODE (Where I believe the issue lies):
$(document).ready(function() {
// Additional code here...
// start
BG.init();
// Additional code here...
}
});
Where you have this:
$(document).ready(function() {
Put this:
$(document).ready(function() {
setTimeout(function() {
And then where you have this:
});
// wrapper for background animation functionality
var BG = {
Put this:
}, 2000);
});
// wrapper for background animation functionality
var BG = {
And then, if you don't want to incur the wrath of everyone in the world, indent the stuff inside that new function we just created by one more level. 'Cause indentation is the stuff of life.
There's a lot of 'useless' code for us to help you. Next time share only on a need-to-know bases :)
I've edited your $document.ready block to include the timeout, have a look-see:
$(document).ready(function() {
function initiationProcess() {
// setup logo image
BG.logo = new Image();
BG.logo.onload = function() {
BG.logo_loaded = true;
BG.showLogo();
}
// /../ more code /../
// wire ticker listener
Ticker.addListener(BG.tick);
// start
BG.init();
// /../ more code /../
}
setTimeout(initiationProcess, 2000);
});
Edit:
I'd also like to note that it's considered bad practise (not to mention that it might result in buggy code) to only partly use semicolons in your script file. There's points and counterpoints to using semicolons, but pick a standard and stick to it!
For several years I've used the waitForKeyElements() function to track changes in webpages from a userscript. However, sometimes I've found it doesn't trigger as expected and have worked around out. I've run into another example of this problem, and so am now trying to figure out what the problem is. The following is the barest example I can create.
Given a simple HTML page that looks like this:
<span class="e1">blah</span>
And some Javascript:
// function defined here https://gist.github.com/BrockA/2625891
waitForKeyElements('.e1', handle_e1, false);
function handle_e1(node) {
console.log(node.text());
alert(node.text());
}
setInterval(function() {
$('.e1').text("updated: "+Math.random());
}, 5000);
I would expect this code to trigger an alert() and a console.log() every 5 seconds. However, it only triggers once. Any ideas?
Here's a codepen that demonstrates this.
By design and default, waitForKeyElements processes a node just once. To tell it to keep checking, return true from the callback function.
You'll also want to compare the string (or whatever) to see if it has changed.
So, in this case, handle_e1() would be something like:
function handle_e1 (jNode) {
var newTxt = jNode.text ();
if (typeof this.lastTxt === "undefined" || this.lastTxt !== newTxt) {
console.log (newTxt);
this.lastTxt = newTxt;
}
return true; // Allow repeat firings for this node.
}
With the constant string comparisons though, performance might be an issue if you have a lot of this on one page. In that scenario, switching to a MutationObserver approach might be best.
This is the first time I get my hands on with automation instruments in xcode The script works well for all button taps but the one making server connection. I don't know the reason
Here is the script I tried so far
var target = UIATarget.localTarget();
target.pushTimeout(4);
target.popTimeout();
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
appScroll.logElementTree();
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
The above script works up to showing the UIActivityIndicator instead of moving to next controller after success
I know There must be a very simple point I am missing. So help me out
UIAutomation attempts to make things "easy" for the developer, but in doing so it can make things very confusing. It sounds like you're getting a reference to window, waiting for a button to appear, then executing .tap() on that button.
I see that you've already considered messing with target.pushTimeout(), which is related to your issue. The timeout system lets you do something that would be impossible in any sane system: get a reference to an element before it exists. I suspect that behind-the-scenes, UIAutomation repeatedly attempts to get the reference you want -- as long as the timeout will allow.
So, in the example you've posted, it's possible for this "feature" to actually hurt you.
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
What if the view changes during the 2-second delay? Your reference to target.frontMostApp().mainWindow.scrollViews()[0] may be invalid, or it may not point to the object you think you're pointing at.
We got around this in our Illuminator framework by forgetting about the timeout system altogether, and just manually re-evaluating a given reference until it actually returns something. We called it waitForChildExistence, but the functionality is basically as follows:
var myTimeout = 3; // how long we want to wait
// this function selects an element
// relative to a parent element (target) that we will pass in
var selectorFn = function (myTarget) {
var ret = myTarget.frontMostApp().mainWindow.scrollViews()[0];
// assert that ret exists, is visible, etc
return ret;
}
// re-evaluate our selector until we get something
var element = null;
var later = get_current_time() + myTimeout;
while (element === null && get_current_time() < later) {
try {
element = selectorFn(target);
} catch (e) {
// must not have worked
}
}
// check whether element is still null
// do something with element
For cases where there is a temporary progress dialog, this code will simply wait for it to disappear before successfully returning the element you want.
I'm using fullcalendar 1.6.3 along with Drupal 7 (thus the need, for now, to be back on 1.6.3). I have some code that I'd like to run every time the view of my calendar changes (via ajax requests) -- forward or backward in time, or between month/week/day view.
Based on some tests, I could do this by hacking the source for renderEvents:
function renderEvents(modifiedEventID) { // TODO: remove modifiedEventID hack
if (elementVisible()) {
currentView.setEventData(events); // for View.js, TODO: unify with renderEvents
currentView.renderEvents(events, modifiedEventID); // actually render the DOM elements
currentView.trigger('eventAfterAllRender');
// my_code_here();
}
}
but that would of course be Wrong. Unfortunately, I can't figure out any other way to do it, probably because of some obvious gap in my Javascript knowledge. I tried setting up an event handler on eventAfterAllRender:
var eventAfterAllRenderEvent = new Event('eventAfterAllRender');
document.addEventListener('eventAfterAllRender', function (e) {my_code_here() }, false);
document.dispatchEvent(eventAfterAllRenderEvent);
but that (for me) only runs on page load, not after the ajax events.
This is probably more of a Javascript question than a fullcalendar question, but is there any advice out there? I'm really trying to not hack core; thanks!
According to the documentation eventAfterAllRender is a callback, so you can do this:
$('#your-calendar-element').fullCalendar({
eventAfterAllRender: function (view) {
// add your code here
}
});