Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Create directives like ng-view and ng-include that add content after them (for loading pages with them) #59

Closed
tbosch opened this Issue · 22 comments

5 participants

@tbosch

The directives ng-view and ng-include add the loaded content as their children. If jqm pages are loaded by them (e.g. in a route) this does not work well, as jqm requires all pages to be under a common parent, usually the body of the page, and that common parent must not be removed if pages are loaded.

The solution for this problem would be to create directives similar to ng-view and ng-include that append their content after them, line ng-repeat does.

Tobias

@tscheibl

Using ng-view as an attribute or class (which is already supported in angular) instead of an html tag solves this issue. e.g. <body ng-view /> or <body class="ng-view" />

Tom

@tbosch

Well, not quite. The problem is in the part "...and that common parent must not be removed if pages are loaded". Using <body ng-view> will remove the complete content of the <body> tag whenever the current view changes. This is a problem as jquery mobile creates a wrapper <div> tag around the new jqm pages, and that wrapper element must not be removed when a new page is added.

Please note that using the normal mechanisms of jquery mobile to load pages is working very well, and this issue is just here for completeness, though it may not be really needed.

@pavelsavara

directive ng-view-after as proposed above for jqm with angular routing.
the trick is to mark page active with ui-page-active class

directives.js

'use strict';

angular.module('mydirectives', [])
    .directive('ngViewAfter', ['$http', '$templateCache', '$route', '$anchorScroll', '$compile', '$controller',
    function($http,   $templateCache,   $route,   $anchorScroll,   $compile, $controller) {
        return {
            restrict: 'ECA',
            terminal: true,
            link: function(scope, element, attr) {
                var lastScope, lastElement,
                    onloadExp = attr.onload || '';

                var parent=element.parent();

                scope.$on('$routeChangeSuccess', update);
                update();

                function destroyLastScope() {
                    if (lastScope) {
                        lastScope.$destroy();
                        lastScope = null;
                    }
                }

                function clearContent() {
                    if (lastElement){
                        lastElement.remove();
                        lastElement=null;
                    }
                    destroyLastScope();
                }

                function update() {
                    var locals = $route.current && $route.current.locals,
                        template = locals && locals.$template;

                    if (template) {
                        var newElement=$(template).appendTo(parent);

                        var link = $compile(newElement),
                            current = $route.current,
                            controller;

                        current.scope = scope.$new();
                        if (current.controller) {
                            locals.$scope = current.scope;
                            controller = $controller(current.controller, locals);
                            newElement.data('$ngControllerController', controller);
                        }

                        link(current.scope);
                        current.scope.$emit('$viewContentLoaded');
                        current.scope.$eval(onloadExp);
                        // $anchorScroll might listen on event...
                        $anchorScroll();

                        newElement.addClass('ui-page-active');

                        clearContent();
                        lastElement=newElement;
                        lastScope = current.scope;
                    } else {
                        clearContent();
                    }
                }
            }
        };
    }])
;

app.js

angular.module('mlmr.app', ['mydirectives','mycontrollers']).
  config(['$routeProvider','$locationProvider', function($routeProvider, $locationProvider) {
    $locationProvider.jqmCompatMode(false);
    $routeProvider.when('/home/main', {redirectTo: '/home/main'});
    $routeProvider.otherwise({redirectTo: '/home/main'});

    $routeProvider.when('/home/main', {templateUrl: 'partials/home/main.html', controller: 'mlmr_home_main'});
  }]);

index.html

    <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.min.js"></script>
    <script>
        (function(window) {
            if (window.mobileinit) {
                $(window.document).bind("mobileinit", function() {
                    window.mobileinit.apply(this, arguments);
                });
            }
        })(window);
    </script>
    <script src="https://ajax.aspnetcdn.com/ajax/jquery.mobile/1.1.1/jquery.mobile-1.1.1.min.js"></script>
    <script src="lib/angular/angular.js"></script>
    <script src="lib/angular/angular-resource.js"></script>
    <script src="lib/jqm-angular/jquery-mobile-angular-adapter-1.1.1.js"></script>
<body ng-app="myapp">
    <div ng-view-after></div>
</body>

home/main.html

<div data-role="page">
    <div data-role="header" data-theme="b">
        <h3>main</h3>
    </div>

    <div data-role="content">
        content
    </div>
</div>

contributed under MIT licence, mostly copy from angularjs ngView

@tbosch

Thanks! I'll look into this!

@JohannesRudolph

Hi @pavelsavara,
I did take a look into your work around and could get it to work - however I'm experiencing some issues. I'm just starting out with angular and jqm, so some of these may not be related to this workaround (but I guess they do).

  • Links with data-transition do not animate properly
  • There is a issue with the height of loaded pages (min-height is not set properly after page load) - they do not span full screen
  • I have an ng-repeat on a sub-page, the data for it gets loaded just fine but it seems the ng-repeat is not executed (my list element stays empty) even though there are elements in the underlying collection
  • The back-button behavior is broken for me. My app lives under example.com/app/#/... Say I'm on a sub-page example.com/app/#/page and navigate back, I'm taken to example.com/# instead of example.com/app/#

Any idea on how to fix these? Are you actually using this fix in production?

What are my alternatives for creating a deep-linking app with jqm and angular? I want to maintain rest-style URLs when possible (as they mirror my API). So that means I need a index page at /#, a list page at /#/categories/ and a detail page at /#/categories/animal. Is there any sample app available that does this?

@pavelsavara
@JohannesRudolph

Hi Pavel, thanks for this info. I think I see now why adding "ui-page-active" to the page does actually not solve the problem, because it completely kicks jqm out of the equation (so it doesn't have any chance to enhance the page).

A proper solution would probably hold the current page in a div and in a second div the next page, then call $mobile.changePage to do the transition and ensure jqm has had a chance to apply its enhancements.

This can probably be done by hooking into the $mobile.pagebeforechange event, I'll try to see if I can come up with something.

@tbosch

Hi,
thanks for your ideas! I am also working on this and am already finished.
The idea is to not use ng-view at all, but just instrument the $routeProvider so that it inserts the jqm pages at the right position. This will also use jqm to load the pages (and not angular via $http), so we have jqm transitions up and working.

This also simplified the internal integration between the $navigate service of angular and jquery mobile a lot!

As I said, almost done, but I want to create some further tests before I commit it.

This will also support specifying the transition to use for a route, e.g.

    $routeProvider.when('/somePage', {
        templateUrl:'someTemplate.html',
        jqmOptions: { transition: 'flip' }
    });

I will keep you updated!

Tobias

@JohannesRudolph

Sounds like I wasted half a day trying to do the same thing :-) I have written a $pageLoader that wraps $.mobile.loadPage to provide the correct URLs when templates are needed and supports routeParams. I got rid of ng-view too, it's not been the right approach.

I got page transitions working just fine.

Here's my implementation. https://gist.github.com/47e5c58128c0ff31bccc

When do you expect your changes to be "shareable"? Wouldn't mind pulling from a branch and give you feedback. Thanks for your effort!

@JohannesRudolph

Hi Tobias, any update on your implementation? Really eager to take a look at it!

@tbosch

Hi, sorry, but this will take until the end of next week, as I am very busy at work :-(

Tobias

@JohannesRudolph
@tbosch

Hi, sorry, not until Tuesday...
But it would be great if you could test it before I release the next version. I'll keep you posted...
Tobias

@kira-backes

I’ll also gladly help with testing as soon as you make it available :+1:

rgds, Kira

@tbosch tbosch referenced this issue from a commit
Tobias Bosch intermediate commit for #59. Almost done. 0104e49
@tbosch

Hi,
ok, here is an intermediate commit. This is almost complete, except for the history handling (e.g. going back in the history to a specific url). The README.md contains the docs for the new changes.

See the new section about "Navigation and routes".

It would be awesome if you could try this concept and report bugs or inconveniences with the API (although this should be the default angular API...).

Until then,
Tobias

@kira-backes

Sadly I could not get it to work at all (and I tried a lot of stuff), the only thing I’ve seen on the page was 'undefined'.

( I tried to use the code I have now working with an older version and jqmCompatMode(false) )

Removed the controllers, removed the ng-view (what’s the replacement for it anyway? Probably data-role="page" but I tried to put that everywhere I could imagine, didn’t help).

Maybe I’m just too used to angularJS way of thinking to not see the proper way how to do it now :)

PS: I used the latest compiled minified "standalone" file

@tbosch

Hello kirab,
here is a simple Todo-Example using the latest code: http://jsfiddle.net/Du2DY/190/
Please note: You do not need the angular routes, as there is the default, that every hash url matches to the jquery mobile page with the id of the hash.

However, this ticket was about explicitly using routes to control the url if you do not have this default mapping.
Here is an example that uses an explicit route: http://jsfiddle.net/ZHKBA/34/

Tobias

@kira-backes

Hello Tobias,

the examples work fine but it’s a different structure. Currently I have a layout and in it is an ng-view and then I’m using template files in the routes, which get loaded and inserted into the ng-view. It seems like with your proposed structure I would have to copy the layout into each of the template files and then I would have to have self-contained pages (with data-role="page"). So the concept of a layout and a view is no longer usable?

rgds, Kira

@JohannesRudolph
@kira-backes

Hi Johannes,

yes, that’s yet another problem. So we actually have 2 problems. Previously I disabled the jqmCompatMode, disabled the html5mode and removed the jqm hashhandlers and then everything worked fine in my PhoneGap app. The only thing I’d like to have now are the jqm transitions between pages (or views/routes).

PS: My app is also working offline with template files.

rgds, Kira

@tbosch

Hi,
ok, multiple answers here:

The current snapshot version disables the hash-listening and html5-pushstate support of jquery mobile in general. Instead, we are using the corresponding parts of angular. By this, we have no problems in that respect any more.

The sample above only contained inline pages, as jsfiddle only supports one html file :-(
Here is another example on Plunker that uses external templates, except for the start page, which still needs to be contained in the main index.html.

Version 1: Showing the default behaviour if no routes are registered: Just create a link to the external template and it will be loaded automatically.
http://plnkr.co/edit/hoqvGk

Version 2: Explicitly uses angular routes to change the default behavior:
http://plnkr.co/edit/f6Yh6f

Please note that in both versions, ng-view is not needed and also does not work, due to the restrictions I mentioned initially in creating this issue.

Did I understand you in the right way?

Tobias

@tbosch

Hi,
this should work well now with the current snapshot version, and I have a lot of tests to proof it :-)
Please have a look at the Readme.md for details about the new routing.

Closing this...
Tobias

@tbosch tbosch closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.