Using `afterRender` hook with nested views - ember.js

Here is the jsFiddle if you like to see code first
What I want to do is simple: execute some (actually a lot) of view layer Javascript code from jQuery and jsPlumb libraries after ALL the views in the current route have finished rendering. After getting some help from this solution and finding that ember-latest has just been updated to include afterRender hook, I decided to upgrade. I now have the almighty afterRender hook.
All the views I am inserting into the dom have a block class associated with them. So the way I have been testing to see if all the views are in the dom is using $('.block').size() and seeing if it matches expected number.
However I am facing some issues when I try to use this hook in my application. I first tried to call the afterRender in the router, after the calls to connectOutlets. I always get only 1 block when I print out the number of blocks in dom while there should be 10.
If I put this code in didInsertElement of my local block view:
didInsertElement: () ->
knob = this.$("##{#get 'knobId'}")
name = this.$(".name")
main = this.$(".main") #block html content will be injected into main
knob.hide()
Ember.run.scheduleOnce('afterRender', this, ()->
console.log ">> block count: ", $(".block").size()
)
...
Then I get the following output:
>> block count: 1
>> block count: 10
>> block count: 10
>> block count: 10
>> block count: 10
>> block count: 10
>> block count: 10
>> block count: 10
>> block count: 10
>> block count: 10
For some reason the first iteration I only get 1 block in the dom and after that I get all 10. I am not sure why. But the main issue here is that if I put the hook inside didInsertElement, the code gets executed for an arbitrary number of views depending on data (in this example 10). However I only want to run this code once after all views have finished rendering.
Note that the view I am working with has nested data. I tried to reproduce this on jsFiddle, but I failed in the sense that it seems to work all find and dandy on the fiddle. Maybe because my views are big and complex it is causing some synchronization issues? In any event, I think we can use the fiddle as a way to discuss a solution and I can test it locally.
One hack I tried to do to get around the issue is to schedule my code to run after 500ms delay using Ember.run.later. That did solve the issue on my local machine. However, using a timer to do this is very sheep dung and does not work reliably since different browsers or machines may take longer to render the views.
This is a though one to reproduce and I have already spent much time trying to find a solution. I would appreciate any help in reproducing the problem here or finding a solution.
Edit (workaround): Thanks to look for helping me troubleshoot this problem, and by looking at this post about a similar problem, I came up with the following temporary workaround, which I put inside the router code:
# Keep trying until there are two or more blocks in DOM
blocksLoaded = ->
console.log "blocks number ...: ", $('.block').size()
if $('.block').size() > 1
console.log "Yay!...we have ", $('.block').size()
startTraversingBlocks()
else
Ember.run.next(this, ()->
blocksLoaded()
)
blocksLoaded()
This outputs:
blockies number ...: 0
blockies number ...: 1
blockies number ...: 1
blockies number ...: 1
...
blockies number ...: 1
blockies number ...: 10
Yay!...we have 10
As Luke pointed out, the problem here is that my nested views are being rendered over several RunLoops. when I refresh the browser I get a different number of blockies number ...: 1 output everytime, anywhere between 4 and 10 times during my tests.
In my opinion this is not a very good solution, but it seems to work for my use case. I feel that there is a need here for another hook that allows one to access DOM when it is guaranteed all the elements from views are accessible through jQuery selectors, but perhaps I am missing something here.

I ran this question past Kris Selden, and he said "afterRender is after the render queue is totally flushed for that run loop; I'm guessing this is over more than one loop"
The most likely possibility is that you have a record array loading. Perhaps it starts with one item and then 9 more load in later. If you rule out data loading, perhaps you have an Ember.run.next or Ember.run.later somewhere in your code?

Related

It's possible to perform db operations asynchronously in django?

I'm writing a command to randomly create 5M orders in a database.
def constrained_sum_sample(
number_of_integers: int, total: Optional[int] = 5000000
) -> int:
"""Return a randomly chosen list of n positive integers summing to total.
Args:
number_of_integers (int): The number of integers;
total (Optional[int]): The total sum. Defaults to 5000000.
Yields:
(int): The integers whose the sum is equals to total.
"""
dividers = sorted(sample(range(1, total), number_of_integers - 1))
for i, j in zip(dividers + [total], [0] + dividers):
yield i - j
def create_orders():
customers = Customer.objects.all()
number_of_customers = Customer.objects.count()
for customer, number_of_orders in zip(
customers,
constrained_sum_sample(number_of_integers=number_of_customers),
):
for _ in range(number_of_orders):
create_order(customer=customer)
number_of_customers will be at least greater than 1k and the create_order function does at least 5 db operations (one to create the order, one to randomly get the order's store, one to create the order item (and this can go up to 30, also randomly), one to get the item's product (or higher but equals to the item) and one to create the sales note.
As you may suspect this take a LONG time to complete. I've tried, unsuccessfully, to perform these operations asynchronously. All of my attempts (dozen at least; most of them using sync_to_async) have raised the following error:
SynchronousOnlyOperation you cannot call this from an async context - use a thread or sync_to_async
Before I continue to break my head, I ask: is it possible to achieve what I desire? If so, how should I proceed?
Thank you very much!
Not yet supported but in development.
Django 3.1 has officially asynchronous support for views and middleware however if you try to call ORM within async function you will get SynchronousOnlyOperation.
if you need to call DB from async function they have provided helpers utils like:
async_to_sync and sync_to_async to change between threaded or coroutine mode as follows:
from asgiref.sync import sync_to_async
results = await sync_to_async(Blog.objects.get, thread_sensitive=True)(pk=123)
If you need to queue call to DB, we used to use tasks queues like celery or rabbitMQ.
By the way if you really know what you are doing you can call it but on your responsibility
just turn off the Async safety but watch out for data lost and integrity errors
#settings.py
DJANGO_ALLOW_ASYNC_UNSAFE=True
The reason this is needed in Django is that many libraries, specifically database adapters, require that they are accessed in the same thread that they were created in. Also a lot of existing Django code assumes it all runs in the same thread, e.g. middleware adding things to a request for later use in views.
More fun news in the release notes:
https://docs.djangoproject.com/en/3.1/topics/async/
It's possible to achieve what you desire, however you need a different perspective to solve this problem.
Try using asynchronous workers, and a simple one would be rq workers or celery.
Use one of these libraries to process async long-running tasks defined in django in different threads or processes.
you can use bulk_create() to create large number of objects , this will speed up the process , additionally put the bulk_create() under a separate thread.

Capybara/Poltergeist DOM loading issues

I am writing a test suite using Capybara/Poltergeist combo for a website and I am coming across a situation where the DOM doesn't seem to be loading whatever I try to do.
I can see from the website snapshot that only the footer and the header are there, but not the main components of the page.
for example a page such as: https://www.udemy.com/courses/business/finance/all-courses/?lang=en&ordering=newest
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, js_errors: false, debug: false, # change this to true to troubleshoot
window_size: [1300, 1000] # this can affect dynamic layout
)
end
browser=Capybara.current_session
browser.visit "https://www.udemy.com/courses/business/finance/all-courses/?lang=en&ordering=newest"
sleep 15 ##giving it time to load
browser.save_screenshot('app/file1.png', full: true)
only the header and footer show up.
I suppose it is due to the fact that some DOM elements are loaded asynchronously, and on my current website I have the same problem.
Stuck here for 2 days now, any suggestions as to how to get the page to load properly? Thanks
You most likely need to update phantomjs to 2.0+. PhantomJS 1.9.8 which I'm guessing you're using is roughly equivalent to Safari 5 iirc and doesn't appear to work with the udemy site.

Why is javascript not checking my checkbox?

I am trying to test a simple header checkbox checks all checkboxes in a table.
The test is below:
scenario 'select all enrolments', js: true do
check 'all_enrolment_presentations'
expect(page).to have_css('.enrolment-presentation-listing input[type="checkbox"]:checked', count: 1)
end
This fails, however if I change the scenario a bit to
scenario 'select all enrolments', js: true do
sleep 3
check 'all_enrolment_presentations'
expect(page).to have_css('.enrolment-presentation-listing input[type="checkbox"]:checked', count: 1)
end
It works.
I have tested a couple of other selectors and they all pretty much have the same affect. I do not want to have to add sleep statements to get stuff like this to pass. I must be doing something incorrect.
When I run the test without the sleep clause, it seems to find the control (it gets highlighted), but it isn't checked. See screenshot for what I mean.
My question is why is this happening and how can I fix my issue?
Note I am using rails 4.2.4 and capybara 2.5.0
After some further investigation the bug was actually caused by upgrading selenium-webdriver from version 2.45.0 to 2.48.1. I haven't had enough time to investigate the root cause within the gem.
You have to wait until capybara to perform click operation.
So,
1. Use sleep
example sleep 5 with wait for 5 seconds
2. Change default wait time in capybara using
`Capybara.default_wait_time = some_value`

Django iterate through a list of webpages

Hi I'm relatively new to python and DJANGO and was wondering if there was a way to load a webpage from a currently loaded webpage after a time delay
For example i have 5 or 6 graphs/charts on separate pages within my django project and want them to be displayed for 5-10 seconds then switch to the next one
I may be just missing something simple here but any help would be appreciated
If you expect them to submit the form within 30 seconds then a meta-refresh is quite a simple way to achieve that.
Otherwise you can use a javascript redirect that fires after a delay. The redirect would check that no action (such as filling out the fields) has been performed during the delay. Something like:
setTimeout(function() {
if (noChange) {
window.location = "http://www.yoururl.com";
}
}, 30 * 1000);
You should use javascript to do that:
$(function(){
setTimeout(checkUserStatus, 30000);
})
function checkUserStatus(){
//Code to check if user hit/click anything
//if yes clear TimeInterval and do the operation
//If no clear Time Interval and redirect to home page
}

2 computed properties based on the same dependency fire only once

Having 2 computed properties based on the same dependency only one computed property runs. The docs says that it is cached what about a situation when I'd like to have the following:
foo: (->
console.log 'foo'
).property('dependency')
bar: (->
console.log 'bar'
).property('dependency')
Now the bar isn't called and I have to resort to observer. Can I make it work?
Edit
The question is about computed properties but it wasn't reflected in the example code - instead of property I used observes. It is now changed. Sorry for confusion.
Edit#2
I modified the great example by #MilkyWayJoe so that it now looks like my solution. Unfortunately (or fortunately) it works, but my solution didn't. Here's the gist what I was after:
With the help of a slider I could set a balance value to be transferred to another credit card provider. Let's say that the value is transferValue. Whenever it changed I had to calculate the annual interest to which the transfer fee was added.
So for example let's say that in my current credit card I have $1000 and the annual interest rate is 19%. It's way too much so I look for another, cheaper solution. It turns out that Bank X offers Balance Transfer Credit Card Y which interest rate is 10% + 3.5% transfer fee.
OK. So I set $1000 on the slider and here goes the magic. I want to calculate interest rate and transfer fee whenever the value changes.
In the modified example it works: http://jsfiddle.net/gqSMU/2/
but failed to do so in my first solution. It was kind of this one:
cardInterest: (->
apr = #get 'purchaseRate'
amount = #get 'transferValue'
#get('calculatedTransferFee') + #calculateInterest apr, amount
).property('transferValue', 'calculatedTransferFee')
As you can see it accesses calculatedTransferFee. The problem was that the value wasn't recalculated. I'm not sure it is worth of mentioning but in the first solution only cardInterest was requested by Handlebars template.
And this is my current solution with observer:
calculatedTransferFee: 0
transferValueDidChange: (->
if #get('isCurrentCardChosen')
transferFee = parseFloat #get('balanceTransferRate') / 100
transferValue = #get 'transferValue'
calculatedTransferFee = if isNaN(transferFee) then 0 else transferFee * transferValue
#set 'calculatedTransferFee', calculatedTransferFee
).observes('transferValue')
It isn't a nice solution, is it? That's why I thought it may be a better solution than to resort to an observer.
I hope that now it is clearer. I'd be grateful for any further feedback!
Not quite sure if I understand what you're trying to achieve since you're talking about property but your code is using observes (I realize this is a conceptual sample code, just not sure where you're going).
Usually, property will be a "reactive accessor" to a value, and 99.9% of the time you want it to be cached (it is by default, unless you say .property('whatever').volatile()), while observe will fire a function when whatever property it is watching changes. If you just want to have two properties firing for the same dependency you could:
App.SomeModel = Em.Object.extend({
someDependency: true,
foo: function() {
// all that's in here will fire only once when 'dependency' changes, and store
// the returning value in cache, and every time something reads this property,
// it will retrieve the cached value.
// A way to test this, is to run the following from your View:
// "alert(this.controller.get('content.foo'));"
// It will alert the string but will not log "whatever bro" again.
console.log('whatever bro');
return "this.foo %# a dependency".fmt(this.get('someDependency') ? "has" : "doesn't have");
}.property('someDependency'),
bar: function() {
// same as above
console.log('whatever dude');
return "this.bar %# a dependency".fmt(this.get('someDependency') ? "has" : "doesn't have");
}.property('someDependency'),
nope: function() {
// same as above, except this is volatile
// and will fire the console.log every time
console.log('y\'all need science');
return "this.nope %# a dependency".fmt(this.get('someDependency') ? "has" : "doesn't have");
}.property('someDependency').volatile()
});
(see fiddle)
If you need it to be logged everytime (perhaps for debugging or whatever reason), you could use volatile or observes.
If I'm tripping and this's not what you want, perhaps you could rephrase your question or refresh the sample code to something closer to your real-life scenario to clarify what's being asked.