Recently I have been making an MVC application with a fluid, responsive design in mind. One of the views has been to implement a fast responsive active directory details page. This is the first time I've really gone head-first into using jQuery/JavaScript with JSON results. Usually I stick to backend coding and someone else does the front-end using Kendo/Telerik controls. I wanted to change it up a little by using jQuery/JavaScript and HTML5 whereever I can.
The page itself works as follows:
User types into [input:project]
, which fires the autocomplete to the server, gets the persons/groups details and builds the autocomplete based on results. If the user then selects a person/group from results the details are loaded to the right of the page. If it's a group then I attempt to load the data so it builds the elements on the DOM dynamically and evenly.
Everything works as expected, but I would appreciate your views/comments on my JavaScript here on what I could have done better, if anything. I'm pretty new to jQuery/JavaScript so not sure if I'm writing the functions efficiently etc.
Partial View
<div class="tile-area">
<div class="grid fluid">
<div class="row">
<div class="span2">
<div style="margin: 20px; margin-top: 0px; margin-right: 0 !important; ">
<div class="ui-widget">
<input id="project">
<input type="hidden" id="project-id">
<span class="icon-search" id="icon-btn-search"></span><div id="cheight"></div>
</div>
</div>
</div>
<!-- Body container -->
<div class="span9 person-container scroll-vertical" id="person-container">
<!-- Force span to fix ul issue on autocomplete -->
<div class="container">
<div id="peopleList">
<div class="listview-outlook">
<header>
<div class="p-container">
<div class="p-sub-container" id="sub-container">
</div>
</div>
</header>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="bg-transparent no-overflow" id="overflow"></div>
Styles
<style>
ol, ul {
list-style: none;
}
#project-label {
display: block;
font-weight: bold;
margin-bottom: 1em;
}
#project-icon {
float: left;
height: 32px;
width: 32px;
}
#project-description {
margin: 0;
padding: 0;
}
.ui-helper-hidden-accessible {
display: none;
}
.auto-list-content {
padding: 5px 0px 0px 5px;
position: relative;
display: block;
color: rgba(120,162,47,0.9);
height: 55px;
background-color: honeydew;
cursor:pointer;
}
.auto-data {
display: block;
padding: 0;
margin: 0;
margin-left: 45px;
min-width: 200px;
position: relative;
}
.auto-complete-title {
margin: 0 !important;
padding: 2px !important;
display: block !important;
overflow: hidden !important;
white-space: nowrap !important;
text-overflow: ellipsis !important;
}
.auto-complete-subtitle {
margin: 0 !important;
padding: 0px !important;
display: block !important;
overflow: hidden !important;
white-space: nowrap !important;
text-overflow: ellipsis !important;
}
.auto-complete-remark {
margin: 0 !important;
padding: 0px !important;
display: block !important;
overflow: hidden !important;
white-space: nowrap !important;
text-overflow: ellipsis !important;
}
/* !Important - overrides the inline left of 15px from jquery ui (ul) element*/
.metro ul,
.metro ol {
padding-left: 0px !important;
}
.ui-autocomplete {
width: 301px;
height: 55px;
display: block !important;
}
.ui-autocomplete li {
margin: 2px 0 2px 0;
padding: 5px 0 0 0;
width: 250px;
border: 1px solid rgba(120,162,47,0.9);
}
.tile-area {
padding: 0px !important;
}
.person-container {
padding: 5px;
/*background-image: url('images/ICO/large/User-No-Frame-Transparent.png');*/
background-repeat: no-repeat;
/*background-color: rgba(244,145,30,0.2);*/
background-color: rgba(120,162,47, 0.2);
}
/* CSS Partial view */
.p-container {
margin: 0px;
padding: 0px;
border: 1px solid #000;
}
.p-sub-container {
margin: 0px 0px 0px 0px;
padding: 5px 5px 5px 5px;
/*border: 1px solid rgba(120,162,47, 1);
background-color: rgba(120,162,47, 1);*/
}
.p-sub-container {
/*border: 1px solid green;*/
}
.p-floating {
position: relative;
top: -125px;
left: 130px;
}
.p-icon {
background-image: url('images/ICO/large/User-No-Frame-Transparent-125x135.png');
background-repeat: no-repeat;
height: 125px;
width: 125px;
}
.p-data {
text-align: left;
text-overflow: ellipsis;
display: block;
}
.p-list-item {
display: block;
position: relative;
}
.subChild {
position: relative;
top: 0px;
left: 0px;
padding: 5px;
}
.subChild {
height: 160px;
width: 500px;
border: 1px solid #000;
}
</style>
JavaScript
<script>
function setHeightOfContainer() {
var maxHeight = 800;
var wHeight = $(window).height();
var pContainerHeight = 700;
$('#person-container').css("height", pContainerHeight);
$('#overflow').css("height", calculateRemainder(wHeight, pContainerHeight));
};
function calculateRemainder(windowHeight, currentHeight) {
return (windowHeight - currentHeight - findAllNavBarElements());
}
function findAllNavBarElements() {
var numItems = $('.navigation-bar').length;
var heightTotal = 0;
if (numItems > 0) {
/* find height of elements */
for (var i = 0; i < numItems; i++) {
heightTotal += $('.navigation-bar').height();
}
}
return heightTotal;
}
$(function () {
var personData = null;
setHeightOfContainer();
findAllNavBarElements();
$("#project").autocomplete({
minLength: 0,
source: function (request, response) {
$.ajax({
type: "GET",
url: '@Url.Action("SearchFilterSimple", "Person")',
data: { wildcard: request.term },
async: true,
success: function (data) {
response($.map(data, function (item) {
return {
label: item.Name,
value: item.Sid,
desc: item.Department,
icon: item.Thumbnail
}
}));
},
error: function (error, txtStatus) {
}
});
},
create: function () {
$(this).autocomplete("search", '');
}
})
.data("ui-autocomplete")._renderItem = function (ul, item) {
return $('<li>')
.append('<div class="auto-list-content">'
+ '<img id="project-icon" src=images/ICO/medium/' + item.icon + ' class="icon" style="width:32px; height:32px;">'
+ '<div class="auto-data">'
+ '<span class="auto-complete-subtitle">' + item.label + '</span>'
+ '<span class="auto-complete-remark">' + item.value + '</span>'
+ '</div></li>')
.appendTo(ul)
.click(function () {
getPersons(item.value);
})
;
};
function getPersons(sid) {
var e = 0;
$(".p-sub-container").empty();
$.ajax({
type: "GET",
url: '@Url.Action("GetSelectedData", "Person")',
data: { sid: sid },
async: false,
cache: false,
success: function (person) {
processData(person);
},
error: function (error, txtStatus) {
}
});
}
var nameprefix = "sub-c-";
var firstChild = null;
function processData(personData) {
if (personData.length > 0) {
for (var i = 0; i < personData.length; i++) {
createSubContainer(i);
}
var child = $(".p-sub-container").find("[id*=" + nameprefix + "]"); // checks the attribute
if (child.length > 0) {
for (var i = 0; i < child.length; i++) {
var c = $(child[i]);
createSubContainerChildren(c, personData[i]);
}
// reset firstChild
firstChild = null;
}
}
}
var prev = 0;
/* functions */
function createSubContainerChildren(c, pd) {
var currentPerson = pd;
var parent = $(c).parent(); // should be the sub container.
var containerPos = $(parent).position();
var containerMaxHeight = $(parent).outerHeight(true);
var containerMaxWidth = $(parent).outerWidth(true);
//find top
var eWidth = 450;
var currentElementPosition = $(c).position();
var totalWidth = currentElementPosition.left + eWidth;
var previousChild = $(c).prevAll();
if (previousChild.length > 0) {
// not first child
var currentElement = firstChild.position()
var currentNewElementPosition = currentElement.top - ($(c).outerHeight()) - (currentElement.top);//minus itself
var currentNewElementLeft = currentElement.left + ($(c).outerWidth());
var gutter = 10;
var mycurrentleftposition = currentElementPosition.left;
if (prev >= 1) {
c.css({
'top': "0px",
'left': "0px", //reset left
'border': "1px solid #0ff",
'float': "left"
});
//reset prev
prev = 0;
} else {
prev = 1;
c.css({
'top': currentNewElementPosition + "px",
'left': "0px",
'border': "1px solid #ff0faa",
'float': "right"
});
}
} else {
firstChild = c;
// set its top/left too
firstChild.css({
'top': "0px",
'left': "0px",
'border': "1px solid #0f0"
});
}
var call = callCard(currentPerson);
$(c).append(call.displayName).append(call.t).append(call.s);
}
function callCard(currentPerson) {
var vCard = {
s: null,
v: null,
t: null,
image: function () {
$.ajax({
type: "GET",
url: '@Url.Action("GetImageThumb", "Person")',
data: { sname: currentPerson.SamAccountName },
async: false,
cache: false,
success: function (img) {
vCard.v = img.nImage;
},
error: function (error, txtStatus) {
}
});
},
displayName: "<div>" + currentPerson.DisplayName + "</div>",
imageThumbnail: function () {
vCard.image();
vCard.t = "<img class='p-icon' id='pid' src='data:image/png;base64," + vCard.v + "' />"
},
wrapper: function () {
vCard.s = "<div class='p-floating'><div class='p-data'>" +
vCard.email + vCard.telephone + vCard.sid + vCard.accountED
},
email: "<span class=\"p-list-item\">" + currentPerson.Email + "</span>",
telephone: "<span class=\"p-list-item\">" + currentPerson.VoiceTelephoneNumber + "</span>",
sid: "<span class=\"p-list-item\">" + currentPerson.Sid + "</span>",
accountED: "<span class=\"p-list-item\">" + currentPerson.AccountExpirationDate + "</span>"
}
vCard.imageThumbnail();
vCard.wrapper();
return vCard;
}
function getImg(currentPerson){
$.ajax({
type: "GET",
url: '@Url.Action("GetImageThumb", "Person")',
data: { sname: currentPerson.SamAccountName },
async: false,
cache: false,
success: function (img) {
return img.nImage;
},
error: function (error, txtStatus) {
}
});
}
function createSubContainer(a) {
$(".p-sub-container").addClass('sub').append($('<div />', {
id: nameprefix + a
}).addClass('subChild'));
};
});
</script>