Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
fix(sidenav): mdSideNav should support deferred or instant component …
Browse files Browse the repository at this point in the history
…lookups

Previous API does not support any way to determine if the component exists (has been instantiated) since the response will give a "fake" instance reference.
The current API supports `$mdSideNav().find(id)`, `$mdSideNav().waitFor(id)`, `$mdSideNav(id)` usages.

This fix will allow code like this:

```js
// If the component has already been instantiated
$scope.isOpenRight = function(){
      return $mdSidenav('right').isOpen();
};

var right;

// If the component will be created later
$scope.isOpenRight = function(){
  if ( angular.isUndefined(right) ) {
    $mdSidenav()
      .waitFor('right')
      .then(function(instance){
        right = instance;
      })
  }
  return right ? right.isOpen() : false;
};

```

Closes #7900
  • Loading branch information
ThomasBurleson committed Apr 8, 2016
1 parent 4bff2bb commit 877551c
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 100 deletions.
7 changes: 6 additions & 1 deletion src/components/sidenav/demoBasicUsage/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ angular
$scope.toggleLeft = buildDelayedToggler('left');
$scope.toggleRight = buildToggler('right');
$scope.isOpenRight = function(){
return $mdSidenav('right').isOpen();
var right = $mdSidenav('right');
return right && right.isOpen();
};

/**
Expand All @@ -31,6 +32,7 @@ angular
*/
function buildDelayedToggler(navID) {
return debounce(function() {
// Component lookup should always be available since we are not using `ng-if`
$mdSidenav(navID)
.toggle()
.then(function () {
Expand All @@ -41,6 +43,7 @@ angular

function buildToggler(navID) {
return function() {
// Component lookup should always be available since we are not using `ng-if`
$mdSidenav(navID)
.toggle()
.then(function () {
Expand All @@ -51,6 +54,7 @@ angular
})
.controller('LeftCtrl', function ($scope, $timeout, $mdSidenav, $log) {
$scope.close = function () {
// Component lookup should always be available since we are not using `ng-if`
$mdSidenav('left').close()
.then(function () {
$log.debug("close LEFT is done");
Expand All @@ -60,6 +64,7 @@ angular
})
.controller('RightCtrl', function ($scope, $timeout, $mdSidenav, $log) {
$scope.close = function () {
// Component lookup should always be available since we are not using `ng-if`
$mdSidenav('right').close()
.then(function () {
$log.debug("close RIGHT is done");
Expand Down
80 changes: 37 additions & 43 deletions src/components/sidenav/sidenav.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,58 +57,52 @@ angular
* $mdSidenav(componentId).isLockedOpen();
* </hljs>
*/
function SidenavService($mdComponentRegistry, $q) {
return function(handle) {
function SidenavService($mdComponentRegistry, $mdUtil, $q, $log) {
var errorMsg = "SideNav '{0}' is not available! Did you use md-component-id='{0}'?";
var service = {
find : findInstance, // sync - returns proxy API
waitFor : waitForInstance // async - returns promise
};

// Lookup the controller instance for the specified sidNav instance
var self;
var errorMsg = "SideNav '" + handle + "' is not available!";
var instance = $mdComponentRegistry.get(handle);
/**
* Service API that supports three (3) usages:
* $mdSidenav().find("left") // sync (must already exist) or returns undefined
* $mdSidenav("left").toggle(); // sync (must already exist) or returns undefined; deprecated
* $mdSidenav("left",true).then( function(left){ // async returns instance when available
* left.toggle();
* });
*/
return function(handle, enableWait) {
if ( angular.isUndefined(handle) ) return service;

if(!instance) {
$mdComponentRegistry.notFoundError(handle);
}
var instance = service.find(handle);
return !instance && (enableWait === true) ? service.waitFor(handle) : instance;
};

return self = {
// -----------------
// Sync methods
// -----------------
isOpen: function() {
return instance && instance.isOpen();
},
isLockedOpen: function() {
return instance && instance.isLockedOpen();
},
// -----------------
// Async methods
// -----------------
toggle: function() {
return instance ? instance.toggle() : $q.reject(errorMsg);
},
open: function() {
return instance ? instance.open() : $q.reject(errorMsg);
},
close: function() {
return instance ? instance.close() : $q.reject(errorMsg);
},
then : function( callbackFn ) {
var promise = instance ? $q.when(instance) : waitForInstance();
return promise.then( callbackFn || angular.noop );
/**
* Synchronously lookup the controller instance for the specified sidNav instance which has been
* registered with the markup `md-component-id`
*/
function findInstance(handle) {
var instance = $mdComponentRegistry.get(handle);
if(!instance) {
// Report missing instance
$log.error( $mdUtil.supplant(errorMsg, [handle || ""]) );

// The component has not registered itself... most like NOT yet created
// return null to indicate that the Sidenav is not in the DOM
return undefined;
}
};
return instance;
}

/**
* Asynchronously wait for the component instantiation,
* Deferred lookup of component instance using $component registry
*/
function waitForInstance() {
return $mdComponentRegistry
.when(handle)
.then(function( it ){
instance = it;
return it;
});
function waitForInstance(handle) {
return $mdComponentRegistry.when(handle);
}
};
}
/**
* @ngdoc directive
Expand Down
170 changes: 116 additions & 54 deletions src/components/sidenav/sidenav.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe('mdSidenav', function() {
var el;
inject(function($compile, $rootScope) {
var parent = angular.element('<div>');
el = angular.element('<md-sidenav ' + (attrs||'') + '>');
el = angular.element('<md-sidenav ' + (attrs || '') + '>');
parent.append(el);
$compile(parent)($rootScope);
$rootScope.$apply();
Expand Down Expand Up @@ -79,7 +79,6 @@ describe('mdSidenav', function() {
expect(backdrop.length).toBe(0);
}));


it('should focus sidenav on open', inject(function($rootScope, $material, $document) {
jasmine.mockElementFocus(this);
var el = setup('md-is-open="show"');
Expand All @@ -92,11 +91,11 @@ describe('mdSidenav', function() {
it('should focus child with md-sidenav-focus', inject(function($rootScope, $material, $document, $compile) {
jasmine.mockElementFocus(this);
var parent = angular.element('<div>');
var markup = '<md-sidenav md-is-open="show">'+
'<md-input-container><label>Label</label>' +
'<input type="text" md-sidenav-focus>' +
'</md-input-container>' +
'<md-sidenav>';
var markup = '<md-sidenav md-is-open="show">' +
' <md-input-container><label>Label</label>' +
' <input type="text" md-sidenav-focus>' +
' </md-input-container>' +
'<md-sidenav>';
var sidenavEl = angular.element(markup);
parent.append(sidenavEl);
$compile(parent)($rootScope);
Expand All @@ -110,11 +109,11 @@ describe('mdSidenav', function() {
it('should focus child with md-autofocus', inject(function($rootScope, $material, $document, $compile) {
jasmine.mockElementFocus(this);
var parent = angular.element('<div>');
var markup = '<md-sidenav md-is-open="show">'+
'<md-input-container><label>Label</label>' +
'<input type="text" md-autofocus>' +
'</md-input-container>' +
'<md-sidenav>';
var markup = '<md-sidenav md-is-open="show">' +
'<md-input-container><label>Label</label>' +
'<input type="text" md-autofocus>' +
'</md-input-container>' +
'<md-sidenav>';
var sidenavEl = angular.element(markup);
parent.append(sidenavEl);
$compile(parent)($rootScope);
Expand All @@ -125,16 +124,15 @@ describe('mdSidenav', function() {
expect($document.activeElement).toBe(focusEl[0]);
}));


it('should focus on last md-sidenav-focus element', inject(function($rootScope, $material, $document, $compile) {
jasmine.mockElementFocus(this);
var parent = angular.element('<div>');
var markup = '<md-sidenav md-is-open="show">'+
'<md-button md-sidenav-focus>Button</md-button>'+
'<md-input-container><label>Label</label>' +
'<input type="text" md-sidenav-focus>' +
'</md-input-container>' +
'<md-sidenav>';
var markup = '<md-sidenav md-is-open="show">' +
'<md-button md-sidenav-focus>Button</md-button>' +
'<md-input-container><label>Label</label>' +
'<input type="text" md-sidenav-focus>' +
'</md-input-container>' +
'<md-sidenav>';
var sidenavEl = angular.element(markup);
parent.append(sidenavEl);
$compile(parent)($rootScope);
Expand Down Expand Up @@ -208,22 +206,27 @@ describe('mdSidenav', function() {
$material.flushInterimElement();
}

beforeEach( inject(function(_$material_,_$rootScope_,_$timeout_) {
$material = _$material_;
$rootScope = _$rootScope_;
$timeout = _$timeout_;
beforeEach(inject(function(_$material_, _$rootScope_, _$timeout_) {
$material = _$material_;
$rootScope = _$rootScope_;
$timeout = _$timeout_;
}));


it('should open(), close(), and toggle() with promises', function () {
it('should open(), close(), and toggle() with promises', function() {
var el = setup('');
var scope = el.isolateScope();
var controller = el.controller('mdSidenav');

var openDone = 0, closeDone = 0, toggleDone = 0;
var onOpen = function() { openDone++; };
var onClose = function() { closeDone++; };
var onToggle = function() { toggleDone++; };
var onOpen = function() {
openDone++;
};
var onClose = function() {
closeDone++;
};
var onToggle = function() {
toggleDone++;
};

controller
.open()
Expand Down Expand Up @@ -253,14 +256,17 @@ describe('mdSidenav', function() {
expect(scope.isOpen).toBe(true);
});


it('should open() to work multiple times before close()', function () {
it('should open() to work multiple times before close()', function() {
var el = setup('');
var controller = el.controller('mdSidenav');

var openDone = 0, closeDone = 0;
var onOpen = function() { openDone++; };
var onClose = function() { closeDone++; };
var onOpen = function() {
openDone++;
};
var onClose = function() {
closeDone++;
};

controller
.open()
Expand Down Expand Up @@ -288,12 +294,10 @@ describe('mdSidenav', function() {
describe('$mdSidenav Service', function() {
var $rootScope, $timeout;


beforeEach( inject(function(_$rootScope_,_$timeout_) {
$rootScope = _$rootScope_;
$timeout = _$timeout_;
}));

beforeEach(inject(function(_$rootScope_, _$timeout_) {
$rootScope = _$rootScope_;
$timeout = _$timeout_;
}));

it('should grab instance', inject(function($mdSidenav) {
var el = setup('md-component-id="left"');
Expand Down Expand Up @@ -344,27 +348,85 @@ describe('mdSidenav', function() {
expect(instance.isLockedOpen()).toBe(true);
}));

it('should find a deferred instantiation', inject(function($mdSidenav) {
var instance;
});

it('should find an instantiation using `$mdSidenav(id)`', inject(function($mdSidenav) {
var el = setup('md-component-id="left"');
$timeout.flush();

// Lookup deferred (not existing) instance
$mdSidenav('left').then( function(inst) { instance = inst; });
expect(instance).toBeUndefined();
// Lookup instance still available in the component registry
var instance = $mdSidenav('left');
expect(instance).toBeTruthy();
}));

// Instantiate `left` sidenav component
var el = setup('md-component-id="left"');
it('should find a deferred instantiation using `$mdSidenav(id, true)`', inject(function($mdSidenav) {
var instance;

$timeout.flush();
expect(instance).toBeTruthy();
expect(instance.isOpen()).toBeFalsy();
// Lookup deferred (not existing) instance
$mdSidenav('left', true).then(function(inst) {
instance = inst;
});
expect(instance).toBeUndefined();

// Lookup instance still available in the component registry
instance = undefined;
instance = $mdSidenav('left');
// Instantiate `left` sidenav component
var el = setup('md-component-id="left"');
$timeout.flush();

expect(instance).toBeTruthy();
expect(instance).toBeDefined();
expect(instance.isOpen()).toBeFalsy();

}));
});
// Lookup instance still available in the component registry
instance = $mdSidenav('left', true);
expect(instance).toBeTruthy();
}));

it('should find a deferred instantiation using `$mdSidenav().waitFor(id)` ', inject(function($mdSidenav) {
var instance;

// Lookup deferred (not existing) instance
$mdSidenav().waitFor('left').then(function(inst) {
instance = inst;
});
expect(instance).toBeUndefined();

// Instantiate `left` sidenav component
var el = setup('md-component-id="left"');
$timeout.flush();

expect(instance).toBeDefined();
expect(instance.isOpen()).toBeFalsy();

// Lookup instance still available in the component registry
instance = undefined;
instance = $mdSidenav('left');

expect(instance).toBeTruthy();
}));

it('should not find a lazy instantiation without waiting `$mdSidenav(id)`', inject(function($mdSidenav) {
var instance = $mdSidenav('left');
expect(instance).toBeUndefined();

// Instantiate `left` sidenav component
var el = setup('md-component-id="left"');
$timeout.flush();

instance = $mdSidenav('left');
expect(instance).toBeDefined();
expect(instance.isOpen()).toBeFalsy();
}));

it('should not find a lazy instantiation without waiting `$mdSidenav().find(id)`', inject(function($mdSidenav) {
var instance = $mdSidenav().find('left');
expect(instance).toBeUndefined();

// Instantiate `left` sidenav component
var el = setup('md-component-id="left"');
$timeout.flush();

instance = $mdSidenav().find('left');
expect(instance).toBeDefined();
expect(instance.isOpen()).toBeFalsy();
}));

});
Loading

0 comments on commit 877551c

Please sign in to comment.