4
\$\begingroup\$

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>
\$\endgroup\$
2
\$\begingroup\$

From a once over,

  • s, v and t are unfortunately named properties of vCard
  • I am confused between the code in vCard.image and getImg, they both call '@Url.Action("GetImageThumb", "Person")', room for de-duping code ? It seems you never call getImg.
  • vCard has data, data retrieval functions and formatting functions. That is too much for 1 object
  • In getPersons you should have function (persons) { <- not person but persons
  • You never use var e in getPersons ( consider using JsHint.com ! )
  • You have a number of semicolon warnings in JsHint
  • You should clean up the commented out css lines
  • Using !important sometimes must be done, but it is a code smell. I like how you put /* !Important - overrides the inline left of 15px from jquery ui (ul) element*/ you should put a similar comment for the other places where you use !important
  • As reader, I see a lot of ( hard to follow ) code that is positioning elements. Automatically I wonder, did the author go all the way trying to solve this with css or did the author take the easy way out ? You should comment why you could not position your elements correctly without JavaScript ( or, try harder to place elements with css )
\$\endgroup\$
  • \$\begingroup\$ Thanks for your comments. Really appreciate them. As this is the first attempt really at writing javascript i'm please that your once over has deemed only a few results. Hopefully i'm heading in the right direction. \$\endgroup\$ – Scott White Apr 14 '14 at 13:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.