0

I'm using AppGyver Steroids and Supersonic to build an app and I'm having some issues navigating between views programmatically.

Based on the docs, you navigate between views like this:

var view_obj = new supersonic.ui.View("main#index");
supersonic.ui.layers.push(view_obj);

However, when I inspect things via the Chrome DevTools, it appears that a second duplicate view is created i.e. If I navigate away from the index page and then navigate back, I now have two index pages, instead of what [I think] should be one. It also doesn't close the previous view I was on.

How can I prevent this from happening and simply move to the existing view, instead of duplicating views? How do I close a view after I have navigated away from it?

Thanks.

ObiHill
  • 11,448
  • 20
  • 86
  • 135

3 Answers3

0

try

var view_obj = new supersonic.ui.View("main#index");
supersonic.ui.layers.replace(view_obj);

And take a look at supersonic.ui.layers.pop();

Ilya
  • 21
  • 3
  • Thanks. `replace` turned out to be the solution, but I had to create some more code to deal my specific challenges. Cheers. – ObiHill Mar 28 '15 at 07:04
0

The problem you're encountering is that you're creating a new supersonic.ui.View("main#index") every time you navigate. On top of this, I think you want to return to the same view when you navigate back to a view for the second time, i.e. you want the view to remain in memory even if it has been removed from the navigation stack with pop() (rather than pushing a new instance of that view). For this, you need to preload or "start()" the view, as described in the docs here.

I implemented my own helper function to make this easier; here is my code:

start = function(dest, isModal) {
  var viewId=dest,
      view=new supersonic.ui.View({
        location: dest,
        id: viewId
      });
  view.isStarted().then(function(started) {
    if (started) {
      if (isModal) {supersonic.ui.modal.show(view);}
      else {supersonic.ui.layers.push(view);}
    } else {
      // Start Spinner
      supersonic.ui.views.start(view).then(function() {
        if (isModal) {supersonic.ui.modal.show(view);}
        else {supersonic.ui.layers.push(view);}
        // Stop Spinner
      }, function(error) {
        // Stop Spinner
        A.error(error);
      });
    }
  });
};

Use it like start('module#view');. As a bonus, you can pass true as the second argument and it gets pushed as a modal instead.

It checks if you've already started a view - if so, it just pushes that view back onto the stack. If not, it start()s (i.e. preloads) it, then pushes it. This ensures that the view stays in memory (with any user input that has been modified) even when you pop() it from the stack.

You have to imagine that the layer stack is actually a stack in the Computer Science sense. You can only add and remove views at the top of the stack. The consequence of this is that complex navigations such as A > B > C > D > B are difficult/hacky to do (in this case, you'd have to pop() D and C in succession to get back to B).

Views will close if you pop() them, as long as you didn't start() them. If you did, and you pop() them, they remain in memory. To kill that view, you have to call stop() on it, as described in the docs I linked above.

benadamstyles
  • 467
  • 1
  • 4
  • 11
  • Thanks a lot for this bro, I'll give it a go. As an aside, I would expect a platform like AppGyver to really do a little extra work in abstracting these complex interactions to make it easier for developers. – ObiHill Mar 27 '15 at 21:39
  • Yeah I guess you're right... The problem I think they often come up against is that they need to work within the limitations of *two* operating systems (Android and iOS), while staying as native as possible in both. I think this means that they're restricted to standard, tried-and-tested software patterns like layer stack. The abstractions are all there, you just have to put a bit of work in if you want a really custom implementation. Given that we're not paying a cent, I guess that's fair enough! – benadamstyles Mar 27 '15 at 22:26
  • I see your point on being free, but just because water is free doesn't mean you'd drink it dirty. I appreciate all the work AppGyver has done on this, but it should just be easier. I tried your code but it didn't work. It did the same thing. Am I doing something wrong? I already have preloaded views from my `structure.coffee` and I am using ``, should I disable this functionality and use your function from the beginning? – ObiHill Mar 28 '15 at 03:06
  • Ok, after some tinkering, I disabled everything and used your function to preload the views (instead of using `structure.coffee`) and used controllers to enable movement from view to view (instead of using `` and it works well. However, I have a form that links to an AJAX function, and I want to redirect back to the previous view on success callback. I used your `start` function, but it doesn't navigate back to the view. Trying to figure out why this is happening. – ObiHill Mar 28 '15 at 03:47
  • One of the errors I get on the console.log is `supersonic.logger.error: supersonic.ui.layers.push rejected: {} Unhandled rejection Error: The specified child already has a parent. You must call removeView() on the child's parent first. `. Any idea what gives? Cheers. – ObiHill Mar 28 '15 at 03:59
  • Great, glad it's working. I personally think `super-navigate` is one abstraction too far, it requires such a simplified app structure (as far as I can see) that I've never used it. Anyway, your error: when you say 'navigate back', do you mean that the previous view is already on the navigation stack? If so, have you tried `supersonic.ui.layers.pop()` (docs [here](http://docs.appgyver.com/supersonic/api-reference/stable/supersonic/ui/layers/pop/))? That would be the standard correct way to navigate *back*. EDIT: just seen your answer! Glad you've sorted it :) – benadamstyles Mar 28 '15 at 11:26
  • Yeah, it was moving back that was my problem. I couldn't understand how everything worked, but I do now. Thanks for the guidance. – ObiHill Mar 30 '15 at 00:50
0

Thanks to LeedsEbooks for helping me get my head around this challenge. I was able to find a solution. Here is the code:

  var start = function(route_str, isModal) {

      var regex = /(.*?)#(.*)/g;
      var match_obj = regex.exec(route_str);

      var view_id_str = match_obj[2],
          view_location_str = route_str,
          view = new supersonic.ui.View({
              location: view_location_str,
              id: view_id_str
          });

      view.isStarted().then(function(started) {
          if (started)
          {
              if (isModal)
              {
                  supersonic.ui.modal.show(view);
              }
              else {
                  supersonic.ui.layers.push(view);
              }
          }
          else
          {
              // Start Spinner
              supersonic.ui.views.start(view).then(function() {
                  if (isModal)
                  {
                      supersonic.ui.modal.show(view);
                  }
                  else
                  {
                      supersonic.ui.layers.push(view);
                  }
                  // Stop Spinner
              }, function(error) {
                  // Stop Spinner
                  A.error(error);
              });
          }
      });
  };

You must ensure that your route has the format module#view as defined in the documentation.

PLEASE NOTE

There seems to some problem with the supersonic ui method for starting views. If you run the following code:

supersonic.ui.views.start("myapp#first-view");
supersonic.ui.views.find("first-view").then( function(startedView) {
    console.log(startedView);
});

You'll notice that your view id and location are identical. This seems to be wrong as the id should be first-view and location should be myapp#first-view.

So I decided to not use the AppGyver methods and create my own preload method instead, which I run from the controller attached to my home view (this ensures that all the views I want to preload are handled when the app loads). Here is the function to do this:

  var preload = function(route_str)
  {
      var regex = /(.*?)#(.*)/g;
      var match_obj = regex.exec(route_str);
      var view = new supersonic.ui.View({
          location: route_str,
          id: match_obj[2]
      });
      view.start();
  };

By doing this, I'm sure that the view will get loaded with the right location and id, and that when I use my start() function later, I won't have any problems.

You'll want to make sure that your structure.coffee file doesn't have any preload instructions so as not to create duplicate views that you'll have problems with later.

Finally, I have a view that is 2 levels in that is a form that posts data via AJAX operation. I wanted the view to go back to the previous view when the AJAX operation was complete. Using my earlier function resulted in the push() being rejected. It would be nice if AppGyver Supersonic could intelligently detect that pushing to a previous view should default to a layers.pop operation, but you don't always get what you want. Anyway, I managed to solve this using supersonic.ui.layers.pop(), which simply does what the Back button would have done.

Everything working as intended now.

ObiHill
  • 11,448
  • 20
  • 86
  • 135