0

Summary: I have an observable controlling a loading page element that hides everything during a long running query. The loading page doesn't show even though the observable is changed before the query. Putting a placeholder setTimeout works, but the page seems to wait for the query to run to update the page.

Edit 3: I should mention that I'm doing this in Node.js.

Edit2: What I want to happen:

1) User clicks button.
2) Loading Screen covers table.
3) Calculations run
4) Loading Screen disappears and table is populated with the results

What is currently happening:
1) User clicks button.
2) Screen freezes for 3-4 seconds as the calculations run.
3) Table is populated with the results

Explanation We have a couple long-ish running queries on our page (takes about 3-4 seconds), and we want to put a loading screen up to hide the controls and inform the people that the page is running.

Here's an extender I wrote (with some help from the internet):

ko.bindingHandlers.Loading = {
init: function (element) {
    $(element).append("<div id='loading_wrap' class='loader' style='height:100%; width:100%;text-align:center; margin-top: 20%'><img src='./resources/ajax-loader.gif'><br>Loading, please wait.</div>")
},
update: function (element, valueAccessor) {
    var isLoading = ko.utils.unwrapObservable(valueAccessor());
    var $element = $(element);
    var $children = $element.children(':not(.loader)');
    var $loader = $(element).find('.loader');

    if (isLoading) {
        $children.hide();
        $loader.show();
    } else {
        $children.show();
        $loader.hide();

    }
}

I attach the bindingHandler to a div containing the results I want to hide until they're ready:

<div class="row table__row" data-bind="Loading:isLoading">

I set the this.isLoading(true) before I run the query and then set it back to false after the query. The following below doesn't work:

this.load =  _ => {
        var _self = this
        this.isLoading(true)
        this.search()
        this.isLoading(false)
    }

However if I put in a timeout, the loading page appears properly.

this.load = _ => {
        var _self = this
        this.isLoading(true)
        setTimeout(function () {
            _self.isLoading(false);
        }, 3000)
    }

I've tried a couple async methods also, but I don't have a good understanding of that. So, how do I make the loading page appear before the query runs and then disappear after the query is finished? Thanks.

Edit: Got some request for our search function. It looks something like this (we're not using Ajax)

this.search = async _ => {
//bunch of filters/calculations on large arrays
this.results(the results of our large calculations)
}
Michael Fan
  • 97
  • 1
  • 11
  • 1
    are you using ajax for your queries? if so could you set it in beforesend ajax method? – Bryan Dellinger Jul 22 '17 at 20:32
  • just saying "... doesn't work" is somewhat too broad, cold you please refine? If you are using ajax, please provide the code of your async method. – deblocker Jul 22 '17 at 20:49
  • Thank you all for your quick responses: No, we're not using Ajax, I have a Javascript function that does some calculations. (I'm not sure what the correct terminology is). It takes about 3-4 seconds for the calculations to run. It looks something like `this.search = _ => { //bunch of filters on large arrays //calculations //set some observableArray values }` – Michael Fan Jul 22 '17 at 21:23

1 Answers1

1

Using a binding handler isn't strictly necessary as you have actions just only in one direction.

Keep it simple:

this.isLoading.subscribe(function(value) {
  ... get elements
  $children.toggle(!value);
  $loader.show(value);
});

After your comment, I believe what you need is just to slightly postpone your long running job, after the loading spinner has been shown, something like that:

    this.isLoading(true);
    setTimeout(function () {
        this.search();
        this.isLoading(false);
    }, 100);

Here is a great post about the difference between promises and asynchronous code: https://stackoverflow.com/a/20930199/4845566

BTW, here is the Knockout references for extenders and here for binding handlers.

deblocker
  • 7,629
  • 2
  • 24
  • 59
  • Thank you for the response. I'm trying to make this loading screen available to other pages as well, not just for the page I'm working on. Using a bindingHandler seemed to make it easy to attach onto a DIV, but I can also do the same for an extender? – Michael Fan Jul 22 '17 at 21:46
  • IMHO your approach would be great if you have more than one loading spinner in one page, otherwise the consolidated approach is to put the loading wrapper in markup with a [visible binding](http://knockoutjs.com/documentation/visible-binding.html) – deblocker Jul 22 '17 at 21:54
  • BTW, now your question sounds totally different now. Are you sure that your `async`call to a heavy synchronous calculation will behave like you are expecting? i believe you have anyway to wait for the synchronous blocking computation inside the async call to complete... just postponed. Not clear for me what should be the workflow by using `async` here. could you please explain? – deblocker Jul 22 '17 at 22:06
  • The search function doesn't have to be async; I thought that's what needed for my loading screen to appear. I just want the following to happen: 1) User clicks button: 2) Loading Screen comes up covering table: 3) Calculations run: 4) Calculations are complete, results show on table. So, far all that happens is: 1) User clicks button. 2) Screen seems to freeze for 3-4 seconds. 3) Table populates – Michael Fan Jul 22 '17 at 22:10
  • Thank you, it's close. Currently, the behavior of the function is now: 1) User clicks button 2) Loading screen shows up, with the spinning gif frozen. 3) Results show. But an issue is that it only works once. Then, it's back to the old behavior (no loading screen, just freezes until the results display) – Michael Fan Jul 22 '17 at 22:29
  • oh, you have a chain of long-running jobs... then you should wrap them all in a timeout to give the browser a chance to repaint. Just a last note: the spinning GIF is know to be not working the same way in all browsers for such a situation. The solution is to break the computation in small pieces, but this is a whole new story. – deblocker Jul 22 '17 at 22:35
  • Adding 100 to the timeout seems to work. The spinner gif freezes, but at least there's a notification. Thanks for the help. – Michael Fan Jul 22 '17 at 22:42
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/149911/discussion-between-deblocker-and-michael-fan). – deblocker Jul 22 '17 at 22:44