Now I’ve got native tri-state checkboxes working cleanly using an AngularJS directive I put together. Now I can just use an html element to place them on my pages. Here’s an example where I’ve used them, in a “contacts manager” type tool:

The “tall people” box is already checked because all the selected contacts are already in that group. The two indeterminate checkboxes are in such a state because a subset of the selected contacts are in those groups. (And BTW, all those names and numbers are randomly generated. No real data here.)
Chris's information explained how to use javascript to natively put a checkbox into its indeterminate state. Namely you do this:
Or if you are using jQuery or jqLite:var checkbox = document.getElementById("mycheckbox");checkbox.indeterminate = true;
So we do the latter inside our Angular directive when the state is supposed to be indeterminate. I created an “api” variable as a gateway to the directive. You set the api ‘state’ to –1 for indeterminate, 0 for unchecked, and 1 for checked. The directive looks like this:$("#mycheckbox").prop("indeterminate", true);
angular.module('app').directive('tricheckbox', function() {
return {
restrict: 'A',transclude: false,
scope: {api: '&api',},link: function(scope, element, attrs) {
var api = scope.api();
scope.uid = 'chk_' + api.id;var el = angular.element(element);
var checked = (1==api.state) ? " checked=\"checked\"" : "";var chkbox = $("<input type='checkbox' id=\"" + scope.uid + "\"" + checked + ">");var clickHandler = function() {api.state = chkbox.prop('checked') ? 1 : 0;scope.$apply();};if ( -1==api.state ) {
chkbox.prop('indeterminate', true);
}chkbox.on('click', clickHandler);element.on('$destroy', function() {
chkbox.off('click', clickHandler);});el.append(chkbox);var label = $("<label for=\"" + scope.uid + "\"><span>" + api.name + "</span></label>");el.append(label);},};});
And the HTML looks like this:
And here’s what the menuGroups data structure looked like:<ul><li x-ng-repeat="g in menuGroups"><!-- tricheckbox api needs g.id=(unique), g.name=(any string), g.state=(-1,0,1) -->
<div x-tricheckbox x-api="g"></div></li></ul>
[{"id":3500031,
"name":"daves-i-know","state":-1
},{"id":3500032,
"name":"freds","state":0
},{"id":3500044,
"name":"snazzy dresser","state":-1
},{"id":3500045,
"name":"tall people","state":1
}]