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

Commit

Permalink
Adding the indeterminate state to md-checkbox.
Browse files Browse the repository at this point in the history
  • Loading branch information
Derek Louie committed Mar 19, 2016
1 parent 5718b52 commit 702f828
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/components/checkbox/checkbox-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
&#{$checkedSelector} ._md-icon:after {
border-color: '{{primary-contrast-0.87}}';
}

& [indeterminate][disabled] {
._md-container {
color: '{{foreground-3}}';
}
}
}

md-checkbox.md-THEME_NAME-theme {
Expand Down
64 changes: 64 additions & 0 deletions src/components/checkbox/checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ angular
* @param {string=} ng-change Angular expression to be executed when input changes due to user interaction with the input element.
* @param {boolean=} md-no-ink Use of attribute indicates use of ripple ink effects
* @param {string=} aria-label Adds label to checkbox for accessibility.
* @param {boolean=} indeterminate Specifies that this checkbox can be rendered
* indeterminate. If true is passed in the checkbox renders in the indeterminate
* state. If no value is passed in this defaults to false. To use the other
* indeterminate-* attributes this attribute must be present.
* @param {expression=} indeterminate-when This determines when the checkbox
* should switch from an indeterminate to non-indeterminate state.
* @param {expression=} indeterminate-checked-when This determines when the
* indeterminate checkbox should appear as just a normal check box. If the
* checkbox has indeterminate="false" and is not checked, it appears as just a
* normal unchecked checkbox.
* @param {expression=} indeterminate-click This gets run when the
* indeterminate checkbox is clicked. This allows the user to trigger a function
* when an indeterminate checkbox is clicked.
* Defaults to checkbox's text. If no default text is found, a warning will be logged.
*
* @usage
Expand Down Expand Up @@ -69,6 +82,7 @@ function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $

function compile (tElement, tAttrs) {
var container = tElement.children();
var indeterminateStateEnabled = tElement[0].hasAttribute('indeterminate');

tAttrs.type = 'checkbox';
tAttrs.tabindex = tAttrs.tabindex || '0';
Expand All @@ -91,6 +105,13 @@ function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $
return function postLink(scope, element, attr, ngModelCtrl) {
ngModelCtrl = ngModelCtrl || $mdUtil.fakeNgModel();
$mdTheming(element);
if (indeterminateStateEnabled) {
// Bootstrap initial state
setIndeterminateState();
if (attr.indeterminateWhen) {
scope.$watch(attr.indeterminateWhen, setIndeterminateState);
}
}

if (attr.ngChecked) {
scope.$watch(
Expand Down Expand Up @@ -156,11 +177,18 @@ function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $
listener(ev);
}
}

function listener(ev) {
if (element[0].hasAttribute('disabled')) {
return;
}

if (indeterminateStateEnabled) {
if (attr.indeterminateClick) {
scope.$apply(element.attr('indeterminate-click'));
}
}

scope.$apply(function() {
// Toggle the checkbox value...
var viewValue = attr.ngChecked ? attr.checked : !ngModelCtrl.$viewValue;
Expand All @@ -171,12 +199,48 @@ function MdCheckboxDirective(inputDirective, $mdAria, $mdConstant, $mdTheming, $
}

function render() {
if (indeterminateStateEnabled) {
setIndeterminateState();
// The checkbox should never be in an indeterminate="true" AND checked state.
// To prevent this, if the checkbox is in an indetermiante
// state, we skip the code below.
return;
}

if(ngModelCtrl.$viewValue) {
element.addClass(CHECKED_CSS);
} else {
element.removeClass(CHECKED_CSS);
}
}

function setIndeterminateState() {
var isIndeterminate = element.attr('indeterminate') === 'true' || false;

if (attr.indeterminateWhen) {
isIndeterminate = scope.$eval(attr.indeterminateWhen);
}

// We never want a checkbox to be checked AND indeterminate, they are
// mutually exclusive states.
if (attr.indeterminateCheckedWhen) {
var isChecked = scope.$eval(attr.indeterminateCheckedWhen);
if (isChecked) {
element.addClass(CHECKED_CSS);
isIndeterminate = false;
} else {
element.removeClass(CHECKED_CSS);
}
}

if (isIndeterminate) {
element[0].indeterminate = true;
} else {
element[0].indeterminate = undefined;
}

element.attr('indeterminate', isIndeterminate);
}
};
}
}
18 changes: 18 additions & 0 deletions src/components/checkbox/checkbox.scss
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,24 @@ $checkbox-top: 12px !default;
cursor: default;
}

&[indeterminate="true"] ._md-icon{
&:after {
box-sizing: border-box;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: table;
width: $width * 0.6;
height: $border-width;
border-width: $border-width;
border-style: solid;
border-top: 0;
border-left: 0;
content: '';
}
}

}

.md-inline-form {
Expand Down
111 changes: 111 additions & 0 deletions src/components/checkbox/checkbox.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,5 +246,116 @@ describe('mdCheckbox', function() {
expect(isChecked(checkbox)).toBe(false);
expect(checkbox.hasClass('ng-invalid')).toBe(true);
});


describe('with the indeterminate attribute', function() {

it('should set indeterminate attr to false by default', function() {
var element = compileAndLink(
'<div>' +
'<md-checkbox indeterminate></md-checkbox>' +
'</div>');

var checkbox = element.find('md-checkbox');

pageScope.$apply();

expect(checkbox.attr('indeterminate')).toBe("false");
});

it('should set its HTMLInputElement.indeterminate to be undefined when not indeterminate', function() {
var element = compileAndLink(
'<div>' +
'<md-checkbox indeterminate></md-checkbox>' +
'</div>');

var checkbox = element.find('md-checkbox');

pageScope.$apply();

expect(checkbox[0].indeterminate).toBe(undefined);
});

it('should set its HTMLInputElement.indeterminate to be "true" when indeterminate', function() {
var element = compileAndLink(
'<div>' +
'<md-checkbox indeterminate="true"></md-checkbox>' +
'</div>');

var checkbox = element.find('md-checkbox');

pageScope.$apply();

expect(checkbox[0].indeterminate).toBe(true);
});

it('should be set indeterminate attr value according to indeterminate-when attr', function() {

pageScope.isIndeterminate = function() { return true; } ;

var element = compileAndLink(
'<div>' +
'<md-checkbox indeterminate indeterminate-when="isIndeterminate()"></md-checkbox>' +
'</div>');

var checkbox = element.find('md-checkbox');

pageScope.$apply();

expect(checkbox.attr('indeterminate')).toBe("true");
});

it('should be set checkbox checked value according to indeterminate-checked-when attr', function() {

pageScope.isChecked = function() { return true; } ;

var element = compileAndLink(
'<div>' +
'<md-checkbox indeterminate indeterminate-checked-when="isChecked()"></md-checkbox>' +
'</div>');

var checkbox = element.find('md-checkbox');

pageScope.$apply();

expect(checkbox.hasClass(CHECKED_CSS)).toEqual(true);
});

it('should call indeterminate-click function passed in when indeterminate checkbox is clicked', function() {
var clickIndet = jasmine.createSpy('clickIndet');

pageScope.clickIndet = clickIndet;

var element = compileAndLink(
'<div>' +
'<md-checkbox indeterminate="true" indeterminate-click="clickIndet()"></md-checkbox>' +
'</div>');

var checkbox = element.find('md-checkbox');

checkbox.triggerHandler('click');

pageScope.$apply();

expect(clickIndet).toHaveBeenCalled();
});

it('should never be indeterminate="true" and "md-checked" at the same time', function() {
pageScope.isChecked = function() { return true; };

var element = compileAndLink(
'<div>' +
'<md-checkbox indeterminate="true" indeterminate-checked-when="isChecked()"></md-checkbox>' +
'</div>');

var checkbox = element.find('md-checkbox');

pageScope.$apply();
expect(checkbox.attr('indeterminate')).toBe("false");
expect(checkbox[0].indeterminate).toBe(undefined);
expect(checkbox.hasClass(CHECKED_CSS)).toEqual(true);
});

});
});
});
12 changes: 12 additions & 0 deletions src/components/checkbox/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@
Checkbox (md-primary): No Ink
</md-checkbox>
</div>
<div flex-xs flex="50">
<md-checkbox indeterminate="true"
aria-label="Checkbox Indeterminate" class="md-primary">
Checkbox: Indeterminate
</md-checkbox>
</div>
<div flex-xs flex="50">
<md-checkbox indeterminate="true" aria-label="Checkbox Disabled Indeterminate"
ng-disabled="true" class="md-primary">
Checkbox: Disabled, Indeterminate
</md-checkbox>
</div>
</div>
</fieldset>

Expand Down
32 changes: 32 additions & 0 deletions src/components/checkbox/demoSelectAll/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<div ng-controller="AppCtrl" class="md-padding">
<div layout="row" layout-wrap>

<div flex="100" layout="column">
<div>
<!--
In IE, we cannot apply flex directly to <fieldset>
@see https://github.com/philipwalton/flexbugs#9-some-html-elements-cant-be-flex-containers
-->
<fieldset class="standard" >
<legend>Using &lt;md-checkbox&gt; with the 'indeterminate' attribute </legend>
<div layout="row" layout-wrap flex>
<div flex-xs flex="50">
<md-checkbox aria-label="Select All"
indeterminate
indeterminate-when="isIndeterminate()"
indeterminate-checked-when="isChecked()"
indeterminate-click="toggleAll()">
<span ng-if="isChecked()">Un-</span>Select All
</md-checkbox>
</div>
<div class="select-all-checkboxes" flex="100" ng-repeat="item in items">
<md-checkbox ng-checked="exists(item, selected)" ng-click="toggle(item, selected)">
{{ item }}
</md-checkbox>
</div>
</div>
</fieldset>
</div>
</div>

</div>
41 changes: 41 additions & 0 deletions src/components/checkbox/demoSelectAll/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

angular.module('checkboxDemo3', ['ngMaterial'])

.controller('AppCtrl', function($scope) {
$scope.items = [1,2,3,4,5];
$scope.selected = [1];
$scope.toggle = function (item, list) {
var idx = list.indexOf(item);
if (idx > -1) list.splice(idx, 1);
else list.push(item);
};

$scope.exists = function (item, list) {
return list.indexOf(item) > -1;
};

$scope.isIndeterminate = function() {
if ($scope.selected.length !== 0 &&
$scope.selected.length !== $scope.items.length) {
return true;
} else {
return false;
}
};

$scope.isChecked = function() {
if ($scope.selected.length === $scope.items.length) {
return true;
} else {
return false;
}
};

$scope.toggleAll = function() {
if ($scope.selected.length === $scope.items.length) {
$scope.selected = [];
} else if ($scope.selected.length === 0 || $scope.selected.length > 0) {
$scope.selected = $scope.items.slice(0);
}
};
});
13 changes: 13 additions & 0 deletions src/components/checkbox/demoSelectAll/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
legend {
color: #3F51B5;
}

fieldset.standard {
border-style: solid;
border-width: 1px;
height: 100%;
}

.select-all-checkboxes {
padding-left: 30px;
}

0 comments on commit 702f828

Please sign in to comment.