Skip to content

Commit

Permalink
bugfix for #14 including refactoring of the create/delete event liste…
Browse files Browse the repository at this point in the history
…ning and fireing
  • Loading branch information
Tobias Bosch committed Feb 1, 2012
1 parent 1633d5c commit c0b0247
Show file tree
Hide file tree
Showing 23 changed files with 205 additions and 201 deletions.
2 changes: 1 addition & 1 deletion Changelog.md
Expand Up @@ -5,7 +5,7 @@ Changelog
------------ ------------
- Allow an object to be passed to `$navigate` service as first argument to leverage the full power - Allow an object to be passed to `$navigate` service as first argument to leverage the full power
of jqm `changePage` function. of jqm `changePage` function.

- ng:switch now works correctly.




1.0.4 1.0.4
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/jqm-angular.js
Expand Up @@ -12,6 +12,7 @@ define([
'jqmng/sharedController', 'jqmng/sharedController',
'jqmng/widgets/pageCompile', 'jqmng/widgets/pageCompile',
'jqmng/widgets/angularRepeat', 'jqmng/widgets/angularRepeat',
'jqmng/widgets/angularSwitch',
'jqmng/widgets/angularInput', 'jqmng/widgets/angularInput',
'jqmng/widgets/angularSelect', 'jqmng/widgets/angularSelect',
'jqmng/widgets/disabledHandling', 'jqmng/widgets/disabledHandling',
Expand Down
13 changes: 1 addition & 12 deletions src/main/webapp/jqmng/widgets/angularRepeat.js
Expand Up @@ -3,13 +3,11 @@ define(['jqmng/widgets/widgetProxyUtil'], function(proxyUtil) {
* Modify original ng:repeat so that all created items directly have a parent * Modify original ng:repeat so that all created items directly have a parent
* (old style repeat). This is slower, however simplifies the integration with jquery mobile a lot! * (old style repeat). This is slower, however simplifies the integration with jquery mobile a lot!
* <p> * <p>
* This will furthermore create the events "elementsAdded" and "elementsRemoved" if
* elements were added or deleted (only once per eval).
* <p>
* This also takes care for jquery widgets wrapping themselves into other elements * This also takes care for jquery widgets wrapping themselves into other elements
* (e.g. setting a div as new parent). * (e.g. setting a div as new parent).
*/ */
angular.widget('@ng:repeat', function(expression, element) { angular.widget('@ng:repeat', function(expression, element) {
element.attr('ngm:createwidgets', 'true');
element.removeAttr('ng:repeat'); element.removeAttr('ng:repeat');
element.replaceWith(angular.element('<!-- ng:repeat: ' + expression + ' -->')); element.replaceWith(angular.element('<!-- ng:repeat: ' + expression + ' -->'));
var linker = this.compile(element); var linker = this.compile(element);
Expand Down Expand Up @@ -39,8 +37,6 @@ define(['jqmng/widgets/widgetProxyUtil'], function(proxyUtil) {
collectionLength = angular.Array.size(collection, true), collectionLength = angular.Array.size(collection, true),
childScope, childScope,
key; key;
var addedElements = [];
var removedElements = [];


for (key in collection) { for (key in collection) {
if (collection.hasOwnProperty(key)) { if (collection.hasOwnProperty(key)) {
Expand Down Expand Up @@ -78,7 +74,6 @@ define(['jqmng/widgets/widgetProxyUtil'], function(proxyUtil) {
appendPosition.after(clone); appendPosition.after(clone);
lastIterElement = clone; lastIterElement = clone;
}); });
addedElements.push(lastIterElement);
} }
index ++; index ++;
} }
Expand All @@ -89,15 +84,9 @@ define(['jqmng/widgets/widgetProxyUtil'], function(proxyUtil) {
// Sencha Integration: Destroy widgets // Sencha Integration: Destroy widgets
var child = children.pop(); var child = children.pop();
var childElement = child.$element; var childElement = child.$element;
removedElements.push(childElement);
childElement.remove(); childElement.remove();
} }


if (addedElements.length>0) {
parent.trigger('elementsAdded', addedElements);
} else if (removedElements.length>0) {
parent.trigger('elementsRemoved', removedElements);
}
}, iterStartElement); }, iterStartElement);
}; };
}); });
Expand Down
12 changes: 12 additions & 0 deletions src/main/webapp/jqmng/widgets/angularSwitch.js
@@ -0,0 +1,12 @@
define(['jqmng/widgets/widgetProxyUtil'], function(proxyUtil) {
/**
* Modify the original ng:switch so that it adds the ngm:createwidgets attribute to all cases that will
* fire the jqm create event whenever a new scope is created.
*/
proxyUtil.createAngularWidgetProxy('ng:switch', function(element) {
element.children().attr('ngm:createwidgets', 'true');
return function(element, origBinder) {
return origBinder();
}
});
});
18 changes: 15 additions & 3 deletions src/main/webapp/jqmng/widgets/jqmListView.js
Expand Up @@ -25,10 +25,22 @@ define([
fn._create = function() { fn._create = function() {
var self = this; var self = this;
var res = oldCreate.apply(this, arguments); var res = oldCreate.apply(this, arguments);
// refresh the list when the children change // refresh the list when the children change.
this.element.bind('elementsAdded elementsRemoved', function(event) { this.element.bind('create', function(event) {
event.stopPropagation();
self.refresh(); self.refresh();
// register listeners when the children are destroyed.
// Do this only once per child.
var children = self.element.children('li');
var child, i;
for (i=0; i<children.length; i++) {
child = children.eq(i);
if (!child.data('listlistener')) {
child.data('listlistener', true);
child.bind("remove", function() {
self.refresh();
});
}
}
}); });
}; };
}); });
3 changes: 1 addition & 2 deletions src/main/webapp/jqmng/widgets/jqmSelectMenu.js
@@ -1,7 +1,6 @@
define(['jquery'], function($) { define(['jquery'], function($) {


// selectmenu may create: // selectmenu may create parent element and extra pages
// - parent element
var fn = $.mobile.selectmenu.prototype; var fn = $.mobile.selectmenu.prototype;
var oldDestroy = fn.destroy; var oldDestroy = fn.destroy;
fn.destroy = function() { fn.destroy = function() {
Expand Down
2 changes: 1 addition & 1 deletion src/main/webapp/jqmng/widgets/jqmSlider.js
@@ -1,5 +1,5 @@
define(['jquery'], function($) { define(['jquery'], function($) {
// Button wraps the actual button into another div that is stored in the // Slider wraps the actual input into another div that is stored in the
// "slider" property. // "slider" property.
var fn = $.mobile.slider.prototype; var fn = $.mobile.slider.prototype;
var oldDestroy = fn.destroy; var oldDestroy = fn.destroy;
Expand Down
12 changes: 7 additions & 5 deletions src/main/webapp/jqmng/widgets/pageCompile.js
Expand Up @@ -47,8 +47,6 @@ define(['jquery', 'angular', 'jqmng/globalScope'], function($, angular, globalSc
degradeInputs(page); degradeInputs(page);
angular.compile(page)(childScope); angular.compile(page)(childScope);
parentScope.$eval(); parentScope.$eval();
// The second pagecreate does only initialize
// the widgets that we did not already create by angular.
page.trigger("pagecreate"); page.trigger("pagecreate");
}); });


Expand Down Expand Up @@ -78,10 +76,14 @@ define(['jquery', 'angular', 'jqmng/globalScope'], function($, angular, globalSc
}); });


/** /**
* Create jquery elements when elements were added to the dom. * When scopes are created by angular, the elements within the scope need to be enhanced by jquery mobile.
* We do this by using a special directive that triggers the corresponding event.
* In a later release of angular there will be angular events that we can hook to.
*/ */
$(document).bind('elementsAdded', function(event) { angular.directive("ngm:createwidgets", function(expression) {
$(event.target).trigger('create'); return function(element) {
element.parent().trigger('create');
};
}); });


var currScope = null; var currScope = null;
Expand Down
10 changes: 9 additions & 1 deletion src/test/webapp/devSnippetPage.html
Expand Up @@ -23,7 +23,15 @@


<div data-role="page" id="start"> <div data-role="page" id="start">
<div data-role="content"> <div data-role="content">
<a href="" ngm:click="$navigate({target: 'page2', transition:'pop'})">Test</a> <input type="submit" ng:click="flag = true">
<a href="" ngm:click="list = [1,2]" data-role="button">Fill</a>
<a href="" ngm:click="list = []" data-role="button">Clear</a>
<ul data-role="listview" data-inset="true">
<li ng:repeat="l in list">
Test
<button>Test</button>
</li>
</ul>
</div> </div>
</div> </div>


Expand Down
27 changes: 11 additions & 16 deletions src/test/webapp/ui/selectmenuSpec.js
Expand Up @@ -5,10 +5,9 @@ define(function() {
var scope, dialog, dialogOpen; var scope, dialog, dialogOpen;
loadHtml('/jqmng/ui/test-fixture.html', function(frame) { loadHtml('/jqmng/ui/test-fixture.html', function(frame) {
var page = frame.$('#start'); var page = frame.$('#start');
// Note: Be sure to use ng:repeat, as this is the most problematic case!
page.append( page.append(
'<div data-role="content">' + '<div data-role="content">' +
'<select ng:repeat="item in [1]" name="mysel" id="mysel" data-native-menu="false"><option value="v1" default="true">v1</option><option value="v2">v2</option></select>' + '<select name="mysel" id="mysel" data-native-menu="false"><option value="v1" default="true">v1</option><option value="v2">v2</option></select>' +
'</div>'); '</div>');
}); });
runs(function() { runs(function() {
Expand Down Expand Up @@ -48,10 +47,9 @@ define(function() {
it('should save the ui value into the model when using non native menus', function() { it('should save the ui value into the model when using non native menus', function() {
loadHtml('/jqmng/ui/test-fixture.html', function(frame) { loadHtml('/jqmng/ui/test-fixture.html', function(frame) {
var page = frame.$('#start'); var page = frame.$('#start');
// Note: Be sure to use ng:repeat, as this is the most problematic case!
page.append( page.append(
'<div data-role="content">' + '<div data-role="content">' +
'<select ng:repeat="item in [1]" name="mysel" id="mysel" data-native-menu="false"><option value="v1" default="true">v1</option><option value="v2">v2</option></select>' + '<select name="mysel" id="mysel" data-native-menu="false"><option value="v1" default="true">v1</option><option value="v2">v2</option></select>' +
'</div>'); '</div>');
}); });
runs(function() { runs(function() {
Expand All @@ -72,13 +70,12 @@ define(function() {
}); });
}); });


it('should save the model value into the ui', function() { it('should save the model value into the ui when using non native menus', function() {
loadHtml('/jqmng/ui/test-fixture.html', function(frame) { loadHtml('/jqmng/ui/test-fixture.html', function(frame) {
var page = frame.$('#start'); var page = frame.$('#start');
// Note: Be sure to use ng:repeat, as this is the most problematic case!
page.append( page.append(
'<div data-role="content">' + '<div data-role="content">' +
'<select ng:repeat="item in [1]" name="mysel" id="mysel" data-native-menu="false"><option value="v1" default="true">v1</option><option value="v2">v2</option></select>' + '<select name="mysel" id="mysel" data-native-menu="false"><option value="v1" default="true">v1</option><option value="v2">v2</option></select>' +
'</div>'); '</div>');
}); });
runs(function() { runs(function() {
Expand All @@ -101,10 +98,9 @@ define(function() {
it('should use the disabled attribute', function() { it('should use the disabled attribute', function() {
loadHtml('/jqmng/ui/test-fixture.html', function(frame) { loadHtml('/jqmng/ui/test-fixture.html', function(frame) {
var page = frame.$('#start'); var page = frame.$('#start');
// Note: Be sure to use ng:repeat, as this is the most problematic case!
page.append( page.append(
'<div data-role="content">' + '<div data-role="content">' +
'<select ng:repeat="item in [1]" name="mysel" id="mysel" data-native-menu="false" ng:bind-attr="{disabled: \'{{disabled}}\'}"><option value="v1" default="true">v1</option><option value="v2">v2</option></select>' + '<select name="mysel" id="mysel" data-native-menu="false" ng:bind-attr="{disabled: \'{{disabled}}\'}"><option value="v1" default="true">v1</option><option value="v2">v2</option></select>' +
'</div>'); '</div>');
}); });
runs(function() { runs(function() {
Expand All @@ -122,24 +118,23 @@ define(function() {
}); });
}); });


it('should be removable when ng:repeat shrinks', function() { it('should be removable', function() {
loadHtml('/jqmng/ui/test-fixture.html', function(frame) { loadHtml('/jqmng/ui/test-fixture.html', function(frame) {
var page = frame.$('#start'); var page = frame.$('#start');
// Note: Be sure to use ng:repeat, as this is the most problematic case!
page.append( page.append(
'<div data-role="content" ng:init="mylist = [1,2]">' + '<div data-role="content">' +
'<select ng:repeat="item in mylist" name="mysel" id="mysel" data-native-menu="false" ng:bind-attr="{disabled: \'{{disabled}}\'}"><option value="v1" default="true">v1</option><option value="v2">v2</option></select>' + '<select name="mysel" id="mysel" data-native-menu="false" ng:bind-attr="{disabled: \'{{disabled}}\'}"><option value="v1" default="true">v1</option><option value="v2">v2</option></select>' +
'</div>'); '</div>');
}); });
runs(function() { runs(function() {
var page = testframe().$("#start"); var page = testframe().$("#start");
var scope = page.scope(); var scope = page.scope();
// ui select creates a new parent for itself // ui select creates a new parent for itself
var content = page.find(":jqmData(role='content')"); var content = page.find(":jqmData(role='content')");
expect(content.children('div').length).toEqual(2);
scope.mylist = [1];
scope.$root.$eval();
expect(content.children('div').length).toEqual(1); expect(content.children('div').length).toEqual(1);
// select creates a parent div. This should be removed when the select is removed.
content.find('select').eq(0).remove();
expect(content.children('div').length).toEqual(0);
}); });
}); });


Expand Down
3 changes: 2 additions & 1 deletion src/test/webapp/unit-tests.js
Expand Up @@ -18,5 +18,6 @@ define([
'unit/radioSpec', 'unit/radioSpec',
'unit/selectSliderSpec', 'unit/selectSliderSpec',
'unit/textInputSpec', 'unit/textInputSpec',
'unit/listViewSpec' 'unit/listViewSpec',
'unit/switchSpec'
]); ]);
14 changes: 6 additions & 8 deletions src/test/webapp/unit/buttonSpec.js
Expand Up @@ -2,8 +2,7 @@ define(["unit/testUtils"], function(utils) {


describe("button", function() { describe("button", function() {
it('should allow clicks via ng:click', function() { it('should allow clicks via ng:click', function() {
// Note: Be sure to use ng:repeat, as this is the most problematic case! var d = utils.compileInPage('<button id="mysel" ng:click="flag = true">Test</button>');
var d = utils.compileInPage('<button ng:repeat="item in [1]" id="mysel" ng:click="flag = true">Test</button>');
var page = d.page; var page = d.page;
var input = d.element; var input = d.element;
var scope = input.scope(); var scope = input.scope();
Expand All @@ -13,7 +12,7 @@ define(["unit/testUtils"], function(utils) {
}); });


it('should use the disabled attribute', function() { it('should use the disabled attribute', function() {
var d = utils.compileInPage('<button ng:repeat="item in [1]" id="mysel" ng:click="flag = true" ng:bind-attr="{disabled: \'{{disabled}}\'}">Test</button>'); var d = utils.compileInPage('<button id="mysel" ng:click="flag = true" ng:bind-attr="{disabled: \'{{disabled}}\'}">Test</button>');
var page = d.page; var page = d.page;
var input = d.element; var input = d.element;
var scope = input.scope(); var scope = input.scope();
Expand All @@ -26,15 +25,14 @@ define(["unit/testUtils"], function(utils) {
expect(parentDiv.hasClass('ui-disabled')).toBeTruthy(); expect(parentDiv.hasClass('ui-disabled')).toBeTruthy();
}); });


it('should be removable when ng:repeat shrinks', function() { it('should be removable', function() {
var d = utils.compileInPage('<div ng:init="mylist=[1,2]"><button ng:repeat="item in mylist" id="mysel" ng:click="flag = true">Test</button></div>'); var d = utils.compileInPage('<div><button>1</button><button>2</button></div>');
var page = d.page; var page = d.page;
// ui select creates a new parent for itself
var container = d.element; var container = d.element;
var scope = container.scope(); var scope = container.scope();
expect(container.children('div').length).toEqual(2); expect(container.children('div').length).toEqual(2);
scope.mylist = [1]; // removal of the button should also remove the parent div
scope.$eval(); container.find('button').eq(0).remove();
expect(container.children('div').length).toEqual(1); expect(container.children('div').length).toEqual(1);
}); });
}); });
Expand Down
22 changes: 3 additions & 19 deletions src/test/webapp/unit/checkBoxSpec.js
Expand Up @@ -2,8 +2,7 @@ define(["unit/testUtils"], function(utils) {


describe("checkbox", function() { describe("checkbox", function() {
it('should save the ui value into the model', function() { it('should save the ui value into the model', function() {
// Note: Be sure to use ng:repeat, as this is the most problematic case! var d = utils.compileInPage('<div>' +
var d = utils.compileInPage('<div ng:repeat="item in [1]">' +
'<input name="mysel" id="mysel" type="checkbox">{{mysel}}<label for="mysel" id="mylab">Entry</label>' + '<input name="mysel" id="mysel" type="checkbox">{{mysel}}<label for="mysel" id="mylab">Entry</label>' +
'</div>'); '</div>');
var page = d.page; var page = d.page;
Expand All @@ -19,9 +18,8 @@ define(["unit/testUtils"], function(utils) {
}); });


it('should save the model value into the ui', function() { it('should save the model value into the ui', function() {
// Note: Be sure to use ng:repeat, as this is the most problematic case!
var d = utils.compileInPage( var d = utils.compileInPage(
'<div ng:repeat="item in [1]">' + '<div>' +
'<input name="mysel" id="mysel" type="checkbox"><label for="mysel" id="mylab">Entry</label>' + '<input name="mysel" id="mysel" type="checkbox"><label for="mysel" id="mylab">Entry</label>' +
'</div>'); '</div>');
var page = d.page; var page = d.page;
Expand All @@ -39,9 +37,8 @@ define(["unit/testUtils"], function(utils) {
}); });


it('should use the disabled attribute', function() { it('should use the disabled attribute', function() {
// Note: Be sure to use ng:repeat, as this is the most problematic case!
var d = utils.compileInPage( var d = utils.compileInPage(
'<div ng:repeat="item in [1]">' + '<div>' +
'<input name="mysel" id="mysel" type="checkbox" value="false" ng:bind-attr="{disabled: \'{{disabled}}\'}"><label for="mysel" id="mylab">Entry</label>' + '<input name="mysel" id="mysel" type="checkbox" value="false" ng:bind-attr="{disabled: \'{{disabled}}\'}"><label for="mysel" id="mylab">Entry</label>' +
'</div>'); '</div>');
var page = d.page; var page = d.page;
Expand All @@ -55,19 +52,6 @@ define(["unit/testUtils"], function(utils) {
scope.$eval(); scope.$eval();
expect(parentDiv.hasClass('ui-disabled')).toBeTruthy(); expect(parentDiv.hasClass('ui-disabled')).toBeTruthy();
}); });

it('should enhance checkboxes when ng:repeat is used (not only during page compile)', function() {
var d = utils.compileInPage(
'<div><div ng:repeat="item in items" id="mydiv">' +
'<input name="mysel" id="mysel{{$index}}" type="checkbox" value="false"><label for="mysel{{$index}}" id="mylab{{$index}}">Entry</label>' +
'</div></div>');
var page = d.page;
var parentDiv = d.element;
expect(parentDiv.find('.ui-checkbox').length).toBe(0);
page.scope().$set('items', [1,2]);
page.scope().$eval();
expect(parentDiv.find('.ui-checkbox').length).toBe(2);
});
}); });


}); });
20 changes: 1 addition & 19 deletions src/test/webapp/unit/collapsibleSpec.js
Expand Up @@ -3,8 +3,7 @@ define(["unit/testUtils"], function(utils) {
describe("collapsible", function() { describe("collapsible", function() {


it('should collapse the content by a click', function() { it('should collapse the content by a click', function() {
// Note: Be sure to use ng:repeat, as this is the most problematic case! var d = utils.compileInPage('<div id="el" data-role="collapsible">' +
var d = utils.compileInPage('<div id="el" data-role="collapsible" ng:repeat="item in [1]">' +
'<h3>header</h3>' + '<h3>header</h3>' +
'<p>content</p>' + '<p>content</p>' +
'</div>'); '</div>');
Expand All @@ -15,23 +14,6 @@ define(["unit/testUtils"], function(utils) {
header.trigger('click'); header.trigger('click');
expect(content.hasClass('ui-collapsible-content-collapsed')).toBeFalsy(); expect(content.hasClass('ui-collapsible-content-collapsed')).toBeFalsy();
}); });

it('should be removable when ng:repeat shrinks', function() {
// Note: Be sure to use ng:repeat, as this is the most problematic case!
var d = utils.compileInPage('<div ng:init="mylist = [1,2]">' +
'<div id="el" data-role="collapsible" ng:repeat="item in mylist">' +
'<h3>header</h3>' +
'<p>content</p>' +
'</div>' +
'</div>');
var container = d.element;
var scope = container.scope();
expect(container.children('div').length).toEqual(2);
scope.mylist = [1];
scope.$eval();
expect(container.children('div').length).toEqual(1);
});

}); });


}); });

0 comments on commit c0b0247

Please sign in to comment.