opitzconsulting / jquery-mobile-angular-adapter

jquery mobile angular adapter
MIT License
517 stars 114 forks source link

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

Closed tbosch closed 11 years ago

tbosch commented 12 years ago

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

shyblower commented 12 years ago

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

Tom

tbosch commented 12 years ago

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 commented 12 years ago

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 commented 12 years ago

Thanks! I'll look into this!

JohannesRudolph commented 11 years ago

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).

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 commented 11 years ago

@Rudolph, no I don't use it in production, it was just attempt, but I gave up jquery mobile and looking for full rewrite of code we have in it. Angular and JQuery are not best friends.

This post is worth reading in full but Vojta's comment in particular. https://groups.google.com/forum/?fromgroups=#!searchin/angular/third$20party/angular/RuWn5W3Q5I0/I9qiF9Oi2BUJ

Probably additional call to $compile() would help to light up your templates inside of a page.

On Tue, Nov 6, 2012 at 9:09 PM, Johannes Rudolph notifications@github.comwrote:

Hi @pavelsavara https://github.com/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?

— Reply to this email directly or view it on GitHubhttps://github.com/tigbro/jquery-mobile-angular-adapter/issues/59#issuecomment-10125704.

JohannesRudolph commented 11 years ago

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 commented 11 years ago

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 commented 11 years ago

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 commented 11 years ago

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

tbosch commented 11 years ago

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

Tobias

JohannesRudolph commented 11 years ago

any chance you can spin up a branch to share a preview? would be happy to take a look at it and help with ironing out bugs and specifying tests.

On Fri, Nov 9, 2012 at 11:40 AM, Tobias Bosch notifications@github.comwrote:

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

Tobias

— Reply to this email directly or view it on GitHubhttps://github.com/tigbro/jquery-mobile-angular-adapter/issues/59#issuecomment-10221840.

tbosch commented 11 years ago

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

audaki commented 11 years ago

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

rgds, Kira

tbosch commented 11 years ago

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

audaki commented 11 years ago

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 commented 11 years ago

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

audaki commented 11 years ago

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 commented 11 years ago

Hi Kira,

you may be facing an issue with the way jqm handles routing and "html5 compatible push state" routing. I don't know what the implementation in the adapter currently looks like (just started playing with it) but I basically want to do the same thing you are trying to do (hoping that I'm phrasing it correctly): A single root page page that has the layout and all javascript includes (jqm, angular, etc.) and a set of static html templates that can be loaded as pages.

When the jqm pushState plugin is enabled, the URLs in you application will omit the # part but instead look like plain URLs. So say we loaded the index and are now at example.com/index. When transitioning to a new page, the url in the adress bar is adjusted but the page is still loaded via ajax. You appear to be at example.com/index#page but the adress bar will show example.com/index/page. When a user refreshes now, a GET will be issued to example.com/index/page - which will only work if example.com/index/page actually returns a full html document (and not just a page template).

I found this jqm behavior totally confusing and absolutely useless, especially if you want to work without server side HTML generation and work with a JSON Api + static html. Try disabling the JQM push state plugin. Fingers crossed I can get the adapter to work with this too.

Regards, Johannes

On Wed, Nov 14, 2012 at 11:05 AM, Kira notifications@github.com wrote:

Hello Tobias,

the examples work fine but it’s a different structure. Currently I have a layout and in it is a 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 self-contained pages (with data-role="page"). So the concept with a layout and a view is no longer usable?

rgds, Kira

— Reply to this email directly or view it on GitHubhttps://github.com/tigbro/jquery-mobile-angular-adapter/issues/59#issuecomment-10360978.

audaki commented 11 years ago

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 commented 11 years ago

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 commented 11 years ago

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