1

Here is unusual scenario I'm trying to implement: I got gmap module where Google Maps is asynchronously loads. I'm using following code to properly attach Google Maps:

 composition.addBindingHandler('googleMap', {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            gmap = ko.utils.unwrapObservable(valueAccessor());

            var latLng = new gmaps.LatLng(37.774107,-122.420853);
            var mapOptions = {
                center:latLng,
                zoom: 17,
                disableDefaultUI: true,
                mapTypeId: gmaps.MapTypeId.ROADMAP
            };

            gmap.googleMap = new gmaps.Map(element, mapOptions);
            navigator.geolocation.getCurrentPosition(onSuccess, onError);
        }
    });

My gmap module is module which I want to show first after the user login. So I want to perform some actions before this module should be shown. In most cases I could use a Promise in canActivate method, but! In this case, my module doesn't attached. But I need to attach this module, perform some operations (get database records, determine user geolocation, add markers on the Google Map based on this data) and only when it's done show the module to user. How can I get this works? I tried Promises but it didn't works for me, because I listen to attached method which doesn't fires until the module is shown. So the question: How to activate and attach viewmodel and it's appropriate view (activate and attached callbacks should fire) but don't show it until the condition?

UPDATE: Okay, it seems to my question is not really clearly understandable. Here is the my scenario:
1. My SignIn module activated. User should type his credentials.
2. Once the authorization return success Gmap module should be activated and attached, but don't shown. User should still see the SignIn module and message like 'Loading data...'
3. Gmap module should be fully loaded (all the callbacks should fire). I need a possibility to interact with DOM, that means that module should be attached. But don't shown.
4. Once the some conditions is true, SignIn module comes invisible and Gmap is shown.

Promise in the activate call back doesn't fits this scenario, because view doesn't attached till the promise resolved. Basically I need something like this after the success authorization:

router.navigate('#gmap', { show: false });
...or...
router.attachModule('#gmap', ...{});

Why do I need to interact with DOM and why Promise in activate callback doesn't fits me? Because I need initiated Gmap object which can be initiate only with existing gmap canvas on the page:

 composition.addBindingHandler('googleMap', {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            gmap = ko.utils.unwrapObservable(valueAccessor());
            var mapOptions = {
             ...

            };

            gmap.googleMap = new gmaps.Map(element, mapOptions);

        }
    });

And HTML:

<div id="gmap-section">
    <div id="gmap-canvas" data-bind="googleMap:map"></div>
</div>

Bindings will work only once the view attached.

BillyZ
  • 45
  • 6
  • How about hiding it via knockout visible binding and setting true when you have completed all tasks? – zewa666 Aug 29 '14 at 13:00
  • When router navigates to my gmap module from my sign-in module it hides my sign-in module. So, I can't see the gmap module because it's not ready yet (hidden by display:none, for example) and I can't see my sign-in module cuz it was hidden by Durandal's router. – BillyZ Aug 29 '14 at 13:27

2 Answers2

0

I don't yet fully understand your question but I'll try to give you some food for thought. The ViewModel, in best practice, should only be responsible for pure visual related stuff. So any heavy calculation, DB connection or whatever else should be pulled out into a separate Service. In case of Durandal this would be just a simple RequireJS module. This one should be a singleton and can be required by multiple VMs.

By doing so it's not absolutely necessary to immediately pull in the new VM but start the calculation while in SignIn and when done continue loading the new GMapVM.

As for how to do that, either you launch the Service call in SignIn and on Promise done navigate to GMapVM, or make use of the Durandal built in Pub/Sub process if you dislike Promises.


As of Durandals internal system maybe canActivate from GMapVM would be also a suitable solution or alternatively canDeactivate from SignInVM

zewa666
  • 2,593
  • 17
  • 20
  • Promise in canActivate method is not a solution, because attached doesn't triggers. canDeactivate is doesn't a solution too, because it's going before canActivate, so even canActivate doesn't triggers. I want to do this, because I want to add markers on the map before gmap module is shown. So the user will see only the loaded map with all markers on it. As you can see, in this case view logic is tightly sits with viewmodel. Or can I be sure, that user will not see the process of adding markers if I load all data on 'activate', but add markers based on this data on 'attached'? – BillyZ Aug 29 '14 at 18:26
  • Sorry for double comment, but here is the another example: [link](https://developers.google.com/maps/documentation/javascript/examples/directions-simple) as you can see here I need gmap object to use Directions Services. For example, I want to calculate road based on some conditions and show it initially as my gmap module loads. So the user shouldn't see the process of building route. I may use estimated time (~5 minutes to point A, for example) for some purposes before gmap is shown. But I can't, because I can access gmap only AFTER my gmap module is attached and composition handler complete. – BillyZ Aug 29 '14 at 18:36
  • your layout file "index.html" or whatever called should have a div with id "applicationHost" so how about adding a visible binding there and setting it to false while calculation is done and afterwards true? – zewa666 Aug 30 '14 at 09:04
  • Making the applicationHost invisible makes all my views invisible too, because they are attached inside the applicationHost. I want to show Authorization window and only in case of success login make all calculation. – BillyZ Aug 31 '14 at 16:22
0

If your activate method returns a promise Durandal's router will wait until that promise is resolved before routing to the new route as mentioned in the docs - http://durandaljs.com/documentation/Using-The-Router.html

If you want to can even just create your own promise and use setTimeout to demonstrate in your app.

function activate () {
    var promise = Q.deferred();
    setTimeout(function () { promise.resolve(true); }, 5000);
    return promise;
}

This will return a promise to Durandal's router and then 5 seconds later it will resolve the promise. You should see the old route until the promise is resolved.

Edit

Ok, stating the obvious here but it sounds like from the comments that you are having a problem between the time activate is completed and the attached callback completes. Going off what I said before this a quick and easy way to do what it sounds like you are trying to accomplish, but it is difficult to be sure because I don't quite understand what you are asking...

var showGmapStuff = ko.observable(false);

function activate () {
    var promise = Q.deferred();
    initializeStuff(promise);
    return promise;
}

function initializeStuff(promise) {
    connecttoserver();
    dootherstuff().then(finished);

    function finished() {
        promise.resolve(true);
    }
}

function attached () {
    showGmapStuff(true);
}

And in your view -

<div data-bind="if: showGmapStuff">
    <div data-bind="gmap: mapdata"></div>
</div>

In this scenario the gmap binding won't exist until the activate returns a promise and the attached callback sets showGmapStuff to true. This way instead of it being visible false it will not be evaluated at all until you want it to.

PW Kad
  • 14,953
  • 7
  • 49
  • 82
  • Yes. But as mentioned in docs, module activation stopped on the "activate" phase. "attached" phase that I need is going later, and with use of promise it's doesn't fires. Here at the very bottom you can find callbacks order - [link](http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks) – BillyZ Aug 31 '14 at 16:18
  • What do you mean you need it, attached always fires after. – PW Kad Aug 31 '14 at 17:37
  • I said that too. So if I send a Promise in activate my attached callback will not fire until the promise will not be resolved. I need my module to be attached so its can manipulate with DOM. – BillyZ Aug 31 '14 at 17:54
  • For the record I don't need links to the Durandal documentation but I appreciate the gesture. Also the reason we can't understand what solution you are looking for is because we don't understand the problem. I have edited my question with some fairly trivial practices that should work for the situation I think you are describing. – PW Kad Aug 31 '14 at 20:07