<!DOCTYPE html>
<html>
<head>
<script data-require="jquery@3.0.0" data-semver="3.0.0" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.js"></script>
<script data-require="angular.js@1.5.6" data-semver="1.5.6" src="https://code.angularjs.org/1.5.6/angular.min.js"></script>
<script data-require="sanitize@1.4.8" data-semver="1.4.8" src="https://code.angularjs.org/1.4.8/angular-sanitize.js"></script>
<script data-require="ui-bootstrap@1.3.3" data-semver="1.3.3" src="https://cdn.rawgit.com/angular-ui/bootstrap/gh-pages/ui-bootstrap-tpls-1.3.3.js"></script>
<link data-require="bootstrap@3.3.2" data-semver="3.3.2" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
<script data-require="ui-select@0.18.1" data-semver="0.18.1" src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-select/0.18.1/select.js"></script>
<link data-require="ui-select@0.18.1" data-semver="0.18.1" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-select/0.18.1/select.css" />
<script src="decision-tree.js"></script>
<script src="script.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body ng-app="MainModule" ng-controller="MainController as ctrl">
<ui-select ng-model="ctrl.selectedDecisionTreeMode" theme="bootstrap" style="width: 250px;">
<ui-select-match allow-clear="false" placeholder=""><span ng-bind="$select.selected.label"></span></ui-select-match>
<ui-select-choices repeat="mode in (ctrl.decisionTreeModes | filter: { label: $select.search }) track by $index">
<div ng-bind-html="mode.label | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
<button class="btn btn-primary" ng-click="ctrl.openDecisionTreeWizard()">Launch Wizard</button>
<decision-tree-result result="ctrl.result"></decision-tree-result>
</body>
</html>
var mainModule = angular.module('MainModule', ['ui.select', 'ngSanitize', 'decision.tree']);
mainModule.controller('MainController', ['$decisionTree', function($decisionTree) {
var vm = this;
var RiskFactor = Object.freeze({ "HIGH": "HIGH", "MODERATE": "MODERATE", "LOW": "LOW"});
var riskAssessmentTree =
{
question: "Income:",
children: [
{ value: "£0 to £12K", result: { risk: RiskFactor.HIGH, description: "Low income" } },
{
value: "£12K to £30K",
question: "Credit History:",
children: [
{
value: "Unknown",
question: "Debt:",
children: [
{ value: "High", result: { risk: RiskFactor.HIGH, description: "Mid income, unknown credit history & high debt" } },
{ value: "Low", result: { risk: RiskFactor.MODERATE, description: "Mid income, unknown credit history & low debt" } }
]
},
{ value: "Bad", result: { risk: RiskFactor.HIGH, description: "Mid income, bad credit history" } },
{ value: "Good", result: { risk: RiskFactor.MODERATE, description: "Mid income, good credit history" } }
]
},
{
value: "Over £30K",
question: "Credit History:",
children: [
{ value: "Unknown", result: { risk: RiskFactor.LOW, description: "High income, unknown credit history" } },
{ value: "Bad", result: { risk: RiskFactor.MODERATE, description: "High income, bad credit history" } },
{ value: "Good", result: { risk: RiskFactor.LOW, description: "High income, good credit history" } }
]
}
]
};
function tweak(tree, tweaker) {
tweaker(tree);
angular.forEach(tree.children, function (value) {
tweak(value, tweaker);
});
return tree;
}
vm.decisionTreeModes = [
{ label: "ALL Buttons", tree: riskAssessmentTree },
{ label: "ALL Selects", tree: tweak(
angular.copy(riskAssessmentTree),
function (child) {
if (child.children) {
child.selector = "drop-down";
}
}
) },
{ label: "Mixed (DDs when > 4 options)", tree: tweak(
angular.copy(riskAssessmentTree),
function (child) {
if (child.children && child.children.length > 4) {
child.selector = "drop-down";
}
}
) }
];
vm.selectedDecisionTreeMode = vm.decisionTreeModes[0];
vm.openDecisionTreeWizard = function () {
$decisionTree.openWizard(
{
title: 'Selecty Thingy Wizard',
resultTemplateUrl: 'risk-assessment-result.html',
decisionTree: vm.selectedDecisionTreeMode.tree
}
).then(
function (result) {
vm.result = result;
}
);
};
}]);
mainModule.directive('decisionTreeResult', function () {
return {
restrict: 'E',
scope: {
result: '='
},
templateUrl: 'risk-assessment-result.html'
}
});
body {
margin: 50px;
}
button {
margin: 5px;
}
<div ng-if="result">
<label>Risk Assessment:</label>
<div class="alert" ng-class="{ 'alert-success': result.risk === 'LOW', 'alert-warning': result.risk === 'MODERATE', 'alert-danger': result.risk === 'HIGH'}" role="alert">
{{result.risk}}<span ng-if="result.description">: {{result.description}}</span>
</div>
</div>
angular.module('decision.tree', ['ui.select', 'ngSanitize', 'ui.bootstrap'])
.directive('decisionTree', ['$compile', '$timeout', function($compile, $timeout) {
return {
restrict: 'E',
scope: {
node: '=',
onResult: '&',
onClear: '&'
},
templateUrl: 'decision-tree.html',
link: function(scope, element, attributes) {
scope.decision = {};
// Grab container for next decision
var childDecisionContainer = element.find('.next-decision-container');
// Shallow copy all the immediate children so we can safely set extra properties
scope.children = [];
angular.forEach(scope.node.children, function (child) {
scope.children.push(angular.extend({}, child));
});
// Focus on the first "focusable" element found
$timeout(function() {
element.find('* [data-focusable]').first().focus();
scope.$broadcast('new-decision-appended');
});
scope.selectNode = function(node) {
scope.decision.selected = node;
};
scope.$watch('decision.selected', function (node) {
if (node) {
angular.forEach(scope.children, function (child) {
child.selected = (child === node);
});
childDecisionContainer.empty();
if (node.result) {
if (scope.onResult) {
$timeout(function(){
scope.onResult({result: node.result});
});
}
} else {
if (scope.onClear) {
scope.onClear();
}
var el = angular.element('<decision-tree node="decision.selected" on-result="onResult({result: result})" on-clear="onClear()"></decision-tree>');
$compile(el)(scope);
childDecisionContainer.html(el);
}
}
});
}
};
}])
.directive('modalBody', ['$compile', '$templateRequest', function ($compile, $templateRequest) {
return {
restrict: 'C',
link: function (scope, element, attributes) {
var resultContainer = element.find('div.decision-tree-result');
function injectResultTemplate(template) {
var el = angular.element(template);
$compile(el)(scope);
resultContainer.html(el);
}
if (scope.resultTemplate) {
injectResultTemplate(scope.resultTemplate);
} else if (scope.resultTemplateUrl) {
$templateRequest(scope.resultTemplateUrl)
.then(
function(template) {
injectResultTemplate(template);
}
);
}
}
};
}])
.factory('$decisionTree', ['$uibModal', '$timeout', function ($uibModal, $timeout) {
return {
openWizard: function (config) {
return $uibModal.open({
animation: false,
templateUrl: 'decision-tree-wizard-modal.html',
resolve: {
config: function () { return config; }
},
controller: function ($scope, $uibModalInstance, config) {
$scope.title = config.title;
$scope.decisionTree = config.decisionTree;
$scope.resultTemplate = config.resultTemplate;
$scope.resultTemplateUrl = config.resultTemplateUrl;
$scope.onDecisionResult = function (result) {
$scope.result = result;
$timeout(function() {
$('#acceptButton').focus();
});
};
$scope.onDecisionClear = function () {
$scope.result = undefined;
};
}
}).result;
}
};
}]);
<div class="decision-tree">
<div class="container-row">
<div class="form-group">
<label>{{node.question ? node.question : 'Please select an option:'}}</label>
<ng-switch on="node.selector">
<div ng-switch-when="drop-down" class="decision-tree-select-wrapper">
<ui-select autofocus focus-on="new-decision-appended" ng-model="decision.selected" theme="bootstrap" style="width: 330px;">
<ui-select-match allow-clear="false" placeholder=""><span ng-bind="$select.selected.value"></span></ui-select-match>
<ui-select-choices repeat="child in (children | filter: { value: $select.search })">
<div ng-bind-html="child.value | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
</div>
<div ng-switch-default class="decision-tree-buttons-container">
<button autofocus data-focusable ng-repeat="child in children" ng-click="selectNode(child)" ng-bind="child.value" class="btn" ng-class="{'btn-success': child.selected, 'btn-default': !child.selected}"></button>
</div>
</ng-switch>
</div>
</div>
<div class="next-decision-container"></div>
</div>
<div class="modal-header">
<h3 class="modal-title" ng-bind="title"></h3>
</div>
<div class="modal-body">
<decision-tree node="decisionTree" on-result="onDecisionResult(result)" on-clear="onDecisionClear()"></decision-tree>
<div class="decision-tree-result"></div>
</div>
<div class="modal-footer">
<button class="btn btn-primary" id="acceptButton" ng-click="$close(result)" ng-disabled="!result">Accept</button>
<button class="btn btn-default" ng-click="$dismiss()">Cancel</button>
</div>