Skip to content

Commit

Permalink
introduced jqmCompatMode in , which is also able to deactivate jqm ha…
Browse files Browse the repository at this point in the history
…sh listening and changing
  • Loading branch information
Tobias Bosch committed Jul 5, 2012
1 parent 73a21da commit 7f45c31
Show file tree
Hide file tree
Showing 14 changed files with 260 additions and 142 deletions.
2 changes: 1 addition & 1 deletion Changelog.md
Expand Up @@ -5,7 +5,7 @@ Changelog
------------- -------------
- Updated to angular 1.0.1 - Updated to angular 1.0.1
- $location service can now be used again. Note that by default, this uses - $location service can now be used again. Note that by default, this uses
a new `plain` mode, which directly represents `window.location`. a new `jqmCompat` mode. For routes to work this has to be disabled.
- Added jsfiddle template for reporting issues (see Readme) - Added jsfiddle template for reporting issues (see Readme)
- Support for namespaces for jqm tags (via `$.mobile.ns`). - Support for namespaces for jqm tags (via `$.mobile.ns`).
- Better support for angular directives with template and templateUrl. - Better support for angular directives with template and templateUrl.
Expand Down
137 changes: 74 additions & 63 deletions README.md
Expand Up @@ -10,64 +10,6 @@ all widgets in jquery mobile can be used directly in angular, without further mo


Note that this adapter also provides special utilities useful for mobile applications. Note that this adapter also provides special utilities useful for mobile applications.


Integration strategy
---------------------

Jquery mobile has two kinds of markup:

- Stateless markup/widgets: Markup, that does not hold state or event listeners, and just adds css classes to the dom.
E.g. `$.fn.buttonMarkup`, which is created using `<a href="..." data-role="button">`
- Stateful markup/widgets: Markup that holds state (e.g. event listeners, ...). This markup uses the jquery ui widget factory.
E.g. `$.mobile.button`, which is created using `<button>`.

Integration strategy:

1. We have a `precompile` phase: This is called before the angular compiles does it's work, i.e. before
`$compile` is called, and before `directive.template` and `directive.templateUrl` is evaluated.
Here, we trigger the jqm `create` and `pagecreate` event.
Before this, we instrument all stateful jqm widgets (see above), so they do not
really create the jqm widget, but only add the attribute `jqm-create=<widgetName>` and `jqm-link=<widgetname>`
to the corresponding element. By this, all stateless markup can be used by angular for stamping (e.g. in ng-repeat),
without calling a jqm method again, so we are fast.
Furthermore, we have special handlers in the precompile phase for those jqm widgets that wrap themselves into new elements
(checkboxradio, slider, button, selectmenu, search input), as the angular compiler does not like this.
Finally, we also mark all jqm pages with the `jqm-page` attribute. This is needed as jqm pages are
represented as `data-role=page` in the dom and angular does not allow to create directives that only match
pages but not other jqm widgets.

2. We have the directive `ngmPage`:
This creates an own scope for every page. By this, we are able to disconnect the scope of the pages that
are not visible right now. This is important for optimizing performance.
This creates the jqm pages by calling `element.page()` in the pre link phase, however without the
`pagecreate` event. By this, we only create the page instance, but do not modify the dom
(as this is not allowed in the pre link phase). Furthermore, the `page` jqm widget instance is already
available for the other widgets, which are created in the post link phase.

3. We have the directive `ngmCreate`:
This will create the jqm widgets in the post link phase. For widgets that wrap themselves into new elements this
needs to be called for the wrapper, that was already created in the precompile phase. This is important as
the jqm widgets do more DOM transformations during creations that the angular compiler does not like
(e.g. the jqm widget `<input type="checkbox>"` enhances the sibling `<label>` element and wraps that element).
By calling the widget during the post link phase of the wrapper element those DOM modifications are ok with angular.

4. We have the directive `ngmLink`:
Here we listen for changes in the model and refresh the jqm widgets when needed and vice versa.
For elements that wrap themselves into new elements this will be called on the original element
(e.g. the `<input>` for `<input type="checkbox">` elements), in contrast to the `ngmCreate` directive.

4. All together: This minimizes the number DOM traversals and DOM changes

* We use angular's stamping for stateless widget markup, i.e. we call the jqm functions only once,
and let angular do the rest.
* We do not use the jqm `create` event for refreshing widgets,
but angular's directives. By this, we prevent unneeded executions of jquery selectors.
* We reuse the selectors in jqm for detecting which elements should be enhanced with which jqm widgets.

Ohter possibilities not chosen:

- Calling the jqm "create"-Event whenever the DOM changes (see the jqm docs). However,
this is very slow, as this would lead to many DOM traversals by the different jqm listeners
for the "create"-Event.


Dependencies Dependencies
---------------- ----------------
Expand Down Expand Up @@ -136,12 +78,24 @@ Running the tests
The ui-tests can be run via the url `localhost:8080/jqmng/UiSpecRunner.html` The ui-tests can be run via the url `localhost:8080/jqmng/UiSpecRunner.html`




Using `$location` service `$location` service, routes and jqm hashchange handling
--------------------- ---------------------
This changes the default `$location` service of angular to a new `plain` mode, which
directly represent `window.location`. I.e. By default, jqm listens for all hash changes and shows the the page with the id of the current location hash.
this is neither html5 mode nor hashbang mode (see the angular documentation for details). Also, if you navigate programmatically to a new page (e.g. by the `$navigate` service), the hash is also adjusted.
So setting `$location.hash('someHash')` directly sets `window.location.hash`. This mode of url handling is called jqm compatibility mode in the adapter. It is enabled by default.
Please note that this is different to both, the hashbang and the html5 mode of angular. For this to work,
the adapter replaces the default `$location` service of angular with new one that directly maps `window.location`
to `$location`. This mode is not useful together with angular routes.


However, you can also turn the jqm compatibility mode off. Then, jquery mobile will neither listen to hash changes
nor will it update the hash when pages are changed programmatically (e.g. by the `$navigate` service). This is useful
if you want to use routes in angular. For this, there is the function `jqmCompatMode(bool)` in the
`$locationProvider`. Here is an example for turning jqm compatibility mode off:

module.config(function($location) { location.jqmCompatMode(false); });



Scopes Scopes
----------- -----------
Expand Down Expand Up @@ -268,5 +222,62 @@ The following example shows an example for a paged list for the data in the vari
</ul> </ul>




Integration strategy
---------------------

Jquery mobile has two kinds of markup:

- Stateless markup/widgets: Markup, that does not hold state or event listeners, and just adds css classes to the dom.
E.g. `$.fn.buttonMarkup`, which is created using `<a href="..." data-role="button">`
- Stateful markup/widgets: Markup that holds state (e.g. event listeners, ...). This markup uses the jquery ui widget factory.
E.g. `$.mobile.button`, which is created using `<button>`.

Integration strategy:

1. We have a `precompile` phase: This is called before the angular compiles does it's work, i.e. before
`$compile` is called, and before `directive.template` and `directive.templateUrl` is evaluated.
Here, we trigger the jqm `create` and `pagecreate` event.
Before this, we instrument all stateful jqm widgets (see above), so they do not
really create the jqm widget, but only add the attribute `jqm-create=<widgetName>` and `jqm-link=<widgetname>`
to the corresponding element. By this, all stateless markup can be used by angular for stamping (e.g. in ng-repeat),
without calling a jqm method again, so we are fast.
Furthermore, we have special handlers in the precompile phase for those jqm widgets that wrap themselves into new elements
(checkboxradio, slider, button, selectmenu, search input), as the angular compiler does not like this.
Finally, we also mark all jqm pages with the `jqm-page` attribute. This is needed as jqm pages are
represented as `data-role=page` in the dom and angular does not allow to create directives that only match
pages but not other jqm widgets.

2. We have the directive `ngmPage`:
This creates an own scope for every page. By this, we are able to disconnect the scope of the pages that
are not visible right now. This is important for optimizing performance.
This creates the jqm pages by calling `element.page()` in the pre link phase, however without the
`pagecreate` event. By this, we only create the page instance, but do not modify the dom
(as this is not allowed in the pre link phase). Furthermore, the `page` jqm widget instance is already
available for the other widgets, which are created in the post link phase.

3. We have the directive `ngmCreate`:
This will create the jqm widgets in the post link phase. For widgets that wrap themselves into new elements this
needs to be called for the wrapper, that was already created in the precompile phase. This is important as
the jqm widgets do more DOM transformations during creations that the angular compiler does not like
(e.g. the jqm widget `<input type="checkbox>"` enhances the sibling `<label>` element and wraps that element).
By calling the widget during the post link phase of the wrapper element those DOM modifications are ok with angular.

4. We have the directive `ngmLink`:
Here we listen for changes in the model and refresh the jqm widgets when needed and vice versa.
For elements that wrap themselves into new elements this will be called on the original element
(e.g. the `<input>` for `<input type="checkbox">` elements), in contrast to the `ngmCreate` directive.

4. All together: This minimizes the number DOM traversals and DOM changes

* We use angular's stamping for stateless widget markup, i.e. we call the jqm functions only once,
and let angular do the rest.
* We do not use the jqm `create` event for refreshing widgets,
but angular's directives. By this, we prevent unneeded executions of jquery selectors.
* We reuse the selectors in jqm for detecting which elements should be enhanced with which jqm widgets.

Ohter possibilities not chosen:


- Calling the jqm "create"-Event whenever the DOM changes (see the jqm docs). However,
this is very slow, as this would lead to many DOM traversals by the different jqm listeners
for the "create"-Event.


43 changes: 30 additions & 13 deletions compiled/jquery-mobile-angular-adapter-1.0.7-rc3-SNAPSHOT.js
Expand Up @@ -830,14 +830,19 @@


})(window.angular, window.jQuery); })(window.angular, window.jQuery);
/** /**
* This is an extension to the locationProvider of angular: * This is an extension to the locationProvider of angular and provides a new mode: jqmCompat-mode.
* It allows normal urls to be used in $location, without hashbang or html5 mode. * <p>
* This is required for the hashchange handling of jquery mobile to work properly. * This mode allows to use the normal jquery mobile hash handling (hash = page id).
* Furthermore, this extends the $browser so that it reuses the hashchange handler of * For this to work, it maps window.location directly to $location, without hashbang or html5 mode.
* jqm for angular and ensures, that angular's handler is always called before the one from jqm. * Furthermore, this mode extends the $browser so that it reuses the hashchange handler of
* jqm and ensures, that angular's handler is always called before the one from jqm.
* By this, $location is always up to date when jquery mobile fires pagebeforecreate, ... * By this, $location is always up to date when jquery mobile fires pagebeforecreate, ...
* Note: In this mode, angular routes are not useful.
* <p>
* If this mode is turned off, the hash listening and chaning of jqm is completely deactivated.
* Then you are able to use angular's routes for navigation and `$navigate` service for jqm page navigation.
* <p> * <p>
* Configuration: $locationProvider.plainMode(true) enables this new mode. * Configuration: $locationProvider.jqmCompatMode(bool). Default is `true`.
* <p> * <p>
* Note: Much of the code below is copied from angular, as it is contained in an internal angular function scope. * Note: Much of the code below is copied from angular, as it is contained in an internal angular function scope.
*/ */
Expand Down Expand Up @@ -1027,6 +1032,13 @@
triggerAngularHashChange(); triggerAngularHashChange();
_hashChange(hash); _hashChange(hash);
}; };
var _setPath = $.mobile.path.set;
$.mobile.path.set = function(hash) {
var res = _setPath.apply(this, arguments);
triggerAngularHashChange();
return res;
};

urlChangeInit = true; urlChangeInit = true;
} else { } else {
res = _onUrlChange(callback); res = _onUrlChange(callback);
Expand All @@ -1038,27 +1050,27 @@


var ng = angular.module("ng"); var ng = angular.module("ng");
ng.config(['$locationProvider', function ($locationProvider) { ng.config(['$locationProvider', function ($locationProvider) {
var plainMode = true; var jqmCompatMode = true;
/** /**
* @ngdoc property * @ngdoc property
* @name ng.$locationProvider#plainMode * @name ng.$locationProvider#jqmCompatMode
* @methodOf ng.$locationProvider * @methodOf ng.$locationProvider
* @description * @description
* @param {string=} mode Use plain strategy. * @param {string=} mode Use jqm compatibility mode for navigation.
* @returns {*} current value if used as getter or itself (chaining) if used as setter * @returns {*} current value if used as getter or itself (chaining) if used as setter
*/ */
$locationProvider.plainMode = function (mode) { $locationProvider.jqmCompatMode = function (mode) {
if (angular.isDefined(mode)) { if (angular.isDefined(mode)) {
plainMode = mode; jqmCompatMode = mode;
return this; return this;
} else { } else {
return plainMode; return jqmCompatMode;
} }
}; };


var _$get = $locationProvider.$get; var _$get = $locationProvider.$get;
$locationProvider.$get = ['$injector', '$sniffer', '$browser', function ($injector, $sniffer, $browser) { $locationProvider.$get = ['$injector', '$sniffer', '$browser', function ($injector, $sniffer, $browser) {
if (plainMode) { if (jqmCompatMode) {
// Angular should not use the history api and use the hash bang location service, // Angular should not use the history api and use the hash bang location service,
// which we will extend below. // which we will extend below.
$sniffer.history = false; $sniffer.history = false;
Expand All @@ -1070,6 +1082,11 @@


return $location; return $location;
} else { } else {
// deactivate jqm hash listening and changing
$.mobile.pushStateEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.changePage.defaults.changeHash = false;

return $injector.invoke(_$get, $locationProvider); return $injector.invoke(_$get, $locationProvider);
} }
}]; }];
Expand Down
Expand Up @@ -32014,14 +32014,19 @@ angular.element(document).find('head').append('<style type="text/css">@charset "


})(window.angular, window.jQuery); })(window.angular, window.jQuery);
/** /**
* This is an extension to the locationProvider of angular: * This is an extension to the locationProvider of angular and provides a new mode: jqmCompat-mode.
* It allows normal urls to be used in $location, without hashbang or html5 mode. * <p>
* This is required for the hashchange handling of jquery mobile to work properly. * This mode allows to use the normal jquery mobile hash handling (hash = page id).
* Furthermore, this extends the $browser so that it reuses the hashchange handler of * For this to work, it maps window.location directly to $location, without hashbang or html5 mode.
* jqm for angular and ensures, that angular's handler is always called before the one from jqm. * Furthermore, this mode extends the $browser so that it reuses the hashchange handler of
* jqm and ensures, that angular's handler is always called before the one from jqm.
* By this, $location is always up to date when jquery mobile fires pagebeforecreate, ... * By this, $location is always up to date when jquery mobile fires pagebeforecreate, ...
* Note: In this mode, angular routes are not useful.
* <p>
* If this mode is turned off, the hash listening and chaning of jqm is completely deactivated.
* Then you are able to use angular's routes for navigation and `$navigate` service for jqm page navigation.
* <p> * <p>
* Configuration: $locationProvider.plainMode(true) enables this new mode. * Configuration: $locationProvider.jqmCompatMode(bool). Default is `true`.
* <p> * <p>
* Note: Much of the code below is copied from angular, as it is contained in an internal angular function scope. * Note: Much of the code below is copied from angular, as it is contained in an internal angular function scope.
*/ */
Expand Down Expand Up @@ -32211,6 +32216,13 @@ angular.element(document).find('head').append('<style type="text/css">@charset "
triggerAngularHashChange(); triggerAngularHashChange();
_hashChange(hash); _hashChange(hash);
}; };
var _setPath = $.mobile.path.set;
$.mobile.path.set = function(hash) {
var res = _setPath.apply(this, arguments);
triggerAngularHashChange();
return res;
};

urlChangeInit = true; urlChangeInit = true;
} else { } else {
res = _onUrlChange(callback); res = _onUrlChange(callback);
Expand All @@ -32222,27 +32234,27 @@ angular.element(document).find('head').append('<style type="text/css">@charset "


var ng = angular.module("ng"); var ng = angular.module("ng");
ng.config(['$locationProvider', function ($locationProvider) { ng.config(['$locationProvider', function ($locationProvider) {
var plainMode = true; var jqmCompatMode = true;
/** /**
* @ngdoc property * @ngdoc property
* @name ng.$locationProvider#plainMode * @name ng.$locationProvider#jqmCompatMode
* @methodOf ng.$locationProvider * @methodOf ng.$locationProvider
* @description * @description
* @param {string=} mode Use plain strategy. * @param {string=} mode Use jqm compatibility mode for navigation.
* @returns {*} current value if used as getter or itself (chaining) if used as setter * @returns {*} current value if used as getter or itself (chaining) if used as setter
*/ */
$locationProvider.plainMode = function (mode) { $locationProvider.jqmCompatMode = function (mode) {
if (angular.isDefined(mode)) { if (angular.isDefined(mode)) {
plainMode = mode; jqmCompatMode = mode;
return this; return this;
} else { } else {
return plainMode; return jqmCompatMode;
} }
}; };


var _$get = $locationProvider.$get; var _$get = $locationProvider.$get;
$locationProvider.$get = ['$injector', '$sniffer', '$browser', function ($injector, $sniffer, $browser) { $locationProvider.$get = ['$injector', '$sniffer', '$browser', function ($injector, $sniffer, $browser) {
if (plainMode) { if (jqmCompatMode) {
// Angular should not use the history api and use the hash bang location service, // Angular should not use the history api and use the hash bang location service,
// which we will extend below. // which we will extend below.
$sniffer.history = false; $sniffer.history = false;
Expand All @@ -32254,6 +32266,11 @@ angular.element(document).find('head').append('<style type="text/css">@charset "


return $location; return $location;
} else { } else {
// deactivate jqm hash listening and changing
$.mobile.pushStateEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.changePage.defaults.changeHash = false;

return $injector.invoke(_$get, $locationProvider); return $injector.invoke(_$get, $locationProvider);
} }
}]; }];
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

0 comments on commit 7f45c31

Please sign in to comment.