var app = angular.module('navApp', ['lodash']);
var models = models || {};
var helpers = helpers || {};
var lodash = angular.module('lodash', []);
lodash.factory('_', function () {
return window._;
});
app.factory('personalInfo', function () {
return new models.PersonalInfo();
});
app.factory('navigator', function () {
return new helpers.Navigator();
});
app.controller('PersonalInfoCtrl', ['$scope', 'personalInfo', 'navigator', '_', function ($scope, personalInfo, navigator, _) {
$scope.search = {
text: ''
};
$scope.model = personalInfo;
$scope.navigator = navigator;
}]);
models.PersonalInfo = (function () {
function PersonalInfo() {
this.requiredFields = [];
this.require('firstName', 'lastName', 'email', 'addressLine1', 'city', 'state', 'zip', 'insuranceCompany', 'plicyNumber', 'emergencyContact', 'emergencyPhone');
}
PersonalInfo.prototype.require = function () {
this.requiredFields.push.apply(this.requiredFields, arguments);
};
PersonalInfo.prototype.isFieldValid = function (field) {
if (this.isFieldRequired(field)) {
return this[field] != undefined && this[field] != null && this[field].length > 0;
} else {
return false;
}
};
PersonalInfo.prototype.isFieldRequired = function (field) {
return this.requiredFields.indexOf(field) != -1;
};
return PersonalInfo;
})();
helpers.Navigator = (function () {
var Navigator = function () {
this.navTree = [];
self = this;
}
Navigator.prototype.addField = function (name, id, parentId) {
if (parentId) {
var parentField = this.searchField(this.navTree, parentId);
parentField.fields.push({
name: name,
id: id,
fields: []
});
} else {
this.navTree.push({
name: name,
id: id,
fields: []
});
}
};
var searchResult;
Navigator.prototype.searchField = function (fields, id) {
_.forEach(fields, function (field) {
if (field.id == id) {
searchResult = field;
return false;
}
if (field.fields) {
self.searchField(field.fields, id);
}
});
return searchResult;
};
return Navigator;
})();
//Directives
app.directive("tree", function ($compile) {
return {
restrict: 'E',
scope: {
fields: '=',
model: '='
},
template: "<ul class='tree'>" +
"<li ng-repeat='field in fields'>" +
"<div focus-input='{{field.id}}'>" +
"<i class='glyphicon glyphicon-exclamation-sign required' ng-show='model.isFieldRequired(field.id) && model.isFieldValid(field.id) == false'></i>" +
"<i class='glyphicon glyphicon-ok-sign valid' ng-show='model.isFieldValid(field.id)'></i>{{field.name}}</div>" +
"<tree ng-if='field.fields' fields='field.fields' model='model'></tree>" +
"</li>" +
"</ul>",
compile: function (element) {
var contents = element.contents().remove();
var compiledContent;
return function (scope, element) {
if (!compiledContent) {
compiledContent = $compile(contents);
}
compiledContent(scope, function (clone) {
element.append(clone);
});
};
}
};
});
app.directive("field", function () {
return {
restrict: 'A',
scope: false,
compile: function (element, attr) {
return {
pre: function (scope, element, attr, fieldCtrl) {
if (attr.field && attr.fieldId) {
parentFieldId = element.parents('[field]').first().attr('field-id');
scope.navigator.addField(attr.field, attr.fieldId, parentFieldId);
} else {
throw new Error('Name and Id are required for field.');
}
}
};
}
};
});
app.directive('focusInput', ['$timeout', function ($timeout) {
return function (scope, element, attrs) {
element.bind('click', function () {
$timeout(function () {
$("input", "[field-id='" + attrs.focusInput + "']").focus()
});
});
}
}]);
<div ng-app="navApp">
<div class="row">
<div class="col-xs-12 topbar"></div>
</div>
<div class="row" ng-controller="PersonalInfoCtrl">
<div class="col-xs-4 sidebar">
<tree fields="navigator.navTree" model="model"></tree>
</div>
<div id="content" class="col-xs-8">
<form role="form">
<legend>Contact Information</legend>
<div field="Contact Information" field-id="contactInfo">
<div class="row">
<div class="col-xs-6">
<div class="form-group" field="First Name" field-id="firstName">
<label for="firstName">First Name</label>
<input type="text" ng-model="model.firstName" class="form-control" id="firstName" placeholder="First Name">
</div>
</div>
<div class="col-xs-6">
<div class="form-group" field="Last Name" field-id="lastName">
<label for="lastName">Last Name</label>
<input type="text" ng-model="model.lastName" class="form-control" id="lastName" placeholder="Last Name">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group" field="Email" field-id="email">
<label for="email">Email</label>
<input type="email" ng-model="model.email" class="form-control" id="email" placeholder="Email">
</div>
</div>
<div class="col-xs-6">
<div class="form-group" field="Phone" field-id="phone">
<label for="phone">Phone</label>
<input type="text" ng-model="model.phone" class="form-control" id="phone" placeholder="Phone">
</div>
</div>
</div>
<!-- Address -->
<label for="address">Address</label>
<div id="address" field="Address" field-id="address">
<div class="row">
<div class="col-xs-6">
<div class="form-group" field="Line 1" field-id="addressLine1">
<input type="text" ng-model="model.addressLine1" class="form-control" placeholder="Line 1">
</div>
</div>
<div class="col-xs-6">
<div class="form-group" field="Line 2" field-id="addressLine2">
<input type="text" ng-model="model.addressLine2" class="form-control" placeholder="Line 2">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group" field="City" field-id="city">
<input type="text" ng-model="model.city" class="form-control" placeholder="City">
</div>
</div>
<div class="col-xs-3">
<div class="form-group" field="State" field-id="state">
<input type="text" ng-model="model.state" class="form-control" placeholder="State">
</div>
</div>
<div class="col-xs-3">
<div class="form-group" field="ZIP" field-id="zip">
<input type="text" ng-model="model.zip" class="form-control" placeholder="ZIP">
</div>
</div>
</div>
</div>
</div>
<legend>Medical Information</legend>
<div field="Medical Information" field-id="medicalInfo">
<div class="row">
<div class="col-xs-6">
<div class="form-group" field="Hospital" field-id="hospital">
<label for="hospital">Hospital</label>
<input type="text" ng-model="model.hospital" class="form-control" id="hospital" placeholder="Hospital">
</div>
</div>
<div class="col-xs-6">
<div class="form-group" field="Physicans Name" field-id="physiciansName">
<label for="physicansName">Physician's Name</label>
<input type="text" ng-model="model.physicansName" class="form-control" id="physicansName" placeholder="Physician's Name">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group" field="Insurance Company" field-id="insuranceCompany">
<label for="insuranceCompany">Insurance Company</label>
<input type="text" ng-model="model.insuranceCompany" class="form-control" id="insuranceCompany" placeholder="Insurance Company">
</div>
</div>
<div class="col-xs-6">
<div class="form-group" field="Policy Number" field-id="plicyNumber">
<label for="policyNumber">Policy Number</label>
<input type="text" ng-model="model.policyNumber" class="form-control" id="policyNumber" placeholder="Policy Number">
</div>
</div>
</div>
</div>
<legend>Emergency Contact</legend>
<div field="Emergency Contact" field-id="emergency">
<div class="row">
<div class="col-xs-6">
<div class="form-group" field="Name" field-id="emergencyContact">
<label for="name">Name</label>
<input type="text" ng-model="model.emergencyContactName" class="form-control" id="name" placeholder="Name">
</div>
</div>
<div class="col-xs-6">
<div class="form-group" field="Relationship" field-id="relationship">
<label for="relationship">Relationship</label>
<input type="text" ng-model="model.emergencyRelationship" class="form-control" id="relationship" placeholder="Relationship">
</div>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<div class="form-group" field="Phone" field-id="emergencyPhone">
<label for="emergencyPhone">Phone</label>
<input type="text" ng-model="model.emergencyPhone" class="form-control" id="emergencyPhone" placeholder="Phone">
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
</div>
</div>
.required {
color: #B54322;
}
.valid {
color: #319574;
}
.sidebar {
background-color: #EFEDDF;
}
.topbar {
background-color: #66746D;
height: 4px;
}
.tree {
list-style: none;
padding-left: 18px;
font-size: 11px;
}
.tree li span {
border: 1px solid #999;
border-radius: 2px;
display: inline-block;
padding: 1px 6px;
text-decoration: none;
}
.tree li div {
width: 100%;
background: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
color: #606e67;
font-size: 13px;
padding: 2px 4px 2px 12px;
margin-bottom: 1px;
cursor: pointer;
}
#content {
background-color: #F6F5ED;
}
External resources loaded into this fiddle: