DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
View Events Video Library
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Integrating PostgreSQL Databases with ANF: Join this workshop to learn how to create a PostgreSQL server using Instaclustr’s managed service

Mobile Database Essentials: Assess data needs, storage requirements, and more when leveraging databases for cloud and edge applications.

Monitoring and Observability for LLMs: Datadog and Google Cloud discuss how to achieve optimal AI model performance.

Automated Testing: The latest on architecture, TDD, and the benefits of AI and low-code tools.

Related

  • Angular Component Tree With Tables in the Leaves and a Flexible JPA Criteria Backend
  • Legacy Code Refactoring: Tips, Steps, and Best Practices
  • Common Mistakes to Avoid When Writing SQL Code
  • Data-Based Decision-Making: Predicting the Future Using In-Database Machine Learning

Trending

  • Cognitive AI: The Road To AI That Thinks Like a Human Being
  • Modular Software Architecture: Advantages and Disadvantages of Using Monolith, Microservices and Modular Monolith
  • Hugging Face Is the New GitHub for LLMs
  • Understanding Europe's Cyber Resilience Act and What It Means for You
  1. DZone
  2. Data Engineering
  3. Databases
  4. Developing with AngularJS - Part IV: Making it Pop

Developing with AngularJS - Part IV: Making it Pop

Matt Raible user avatar by
Matt Raible
·
Sep. 27, 13 · Interview
Like (1)
Save
Tweet
Share
20.75K Views

Join the DZone community and get the full member experience.

Join For Free

welcome to the final article in a series on my experience developing with angularjs . i learned its concepts, beat my head against-the-wall, and finally tamed it enough to create a "my dashboard" feature for a client. for previous articles, please see the following:

  • part i: the basics
  • part ii: dialogs and data
  • part iii: services

the last mile of development for the my dashboard feature was to spice things up a bit and make it look better. we hired a design company to come up with a new look and feel, and they went to work. within a week, we had a meeting with them and they presented a few different options. we picked the one we liked the best and went to work. below are screenshots that i used to implement the new design.

my dashboard - new design my dashboard with show more

at first, i thought implementing this design might take quite a bit of effort, since it looked like it used custom fonts. it's true we could use css3's @font-face , but i knew it might take awhile to find the right fonts with the appropriate licenses. when i received the screenshot below, i was pleased to see that all fonts were web-safe.

my dashboard fonts

design elements

there are a number of elements in this new design that i had to create. for example, if numbers were only one digit, we had to add a leading zero to them in the summary band. other design elements we needed to implement are listed below:

  • a background image that filled the page
  • fade to white on summary widget titles
  • a responsive grid for summary widgets
  • provide a colored background for odd rows in the summary grid
  • add a "show more" band at the bottom of tasks, summary and reports when there's more items to display

in addition to these elements, there was quite a bit of work to conform to the new colors, fonts and drop-shadows. i implemented all of these using css3 (border-radius, box-shadow, box-sizing, linear-gradient), and lots of trial-and-error. to use the best fonts across various devices, i used css-trick's font stacks .

new background

the new background shown in the screenshots above has a light source in the middle of it. therefore, it's impossible to tile/repeat it across the page, because it's not uniform. to make it work, i used a 1024 x 768 image and css3's background-size: cover . for more information on background-size, see sitepoint's how to resize background images with css3 . this worked great on smaller screens, but we noticed some issues on 30" monitors. therefore, we ended up getting a new repeatable background and stopped using background-size.

leadingzero filter

for the first leading zero feature, i wrote an angular filter. i put the code for this in filters.js :

filter('leadingzero', function() {
    return function(input) {
        if (input.length === 1) {
            return "0" + input;
        } else if (input.length > 2) {
            return "+99";
        } else {
            return input;
        }
    }
});

this filter is used in the html template as follows:

<div class="summary-value">{{widget.value | leadingzero}}</div>

text fade out

to implement the fade-to-white text in summary titles, i started with this tutorial . i quickly discovered that it worked best for vertical text blocks and not for horizontal text. then i found text ellipsis with gradient fade in pure css , which uses :after to position a block over the text that fades to white. since the title is not the right-most element (the numbers are), i had to figure out the best positioning that worked cross-browser. below is the css i used to implement this feature:

.dashboard .summary-title:after {
    display: block;
    position: absolute;
    right: 66px;
    top: 5px;
    bottom: 5px;
    width: 30px;
    background: -moz-linear-gradient(left,  rgba(255,255,255,0) 0%, #fff 20px); /* ff3.6+ */
    background: -webkit-linear-gradient(left,  rgba(255,255,255,0) 0%, #fff 20px); /* chrome10+,safari5.1+ */
    background: -o-linear-gradient(left,  rgba(255,255,255,0) 0%, #fff 20px); /* opera 11.10+ */
    background: -ms-linear-gradient(left,  rgba(255,255,255,0) 0%, #fff 20px); /* ie10+ */
    background: linear-gradient(to right,  rgba(255,255,255,0) 0%, #fff 20px); /* w3c */
    filter: progid:dximagetransform.microsoft.gradient( startcolorstr='#00ffffff', endcolorstr='#ffffff',gradienttype=1 ); /* ie6-9 */
    content: "";
}

responsive grid

to implement the responsive grid of summary widgets, i started with codrops' responsive full width grid tutorial . this proved to be a great model and i used the following css to position all the <li>'s appropriately. in the code below, .summary-item is the class on the <li> elements.

.dashboard .summary-item {
    border-right: 1px solid #d1d1d1;
    border-bottom: 1px solid #d1d1d1;
    /* put the border on the inside of the box */
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    -ms-box-sizing: border-box;
    box-sizing: border-box;
    font-family: constantia, "lucida bright", lucidabright, "lucida serif", lucida, "dejavu serif", "bitstream vera serif", "liberation serif", georgia, serif;
    font-size: 14px;
    color: #666;
    height: 50px;
    box-shadow: inset 0 0 6px rgba(0,0,0, 0.25);
    /* responsive grid */
    position: relative;
    float: left;
    overflow: hidden;
    width: 25% /* fallback */
    width: -webkit-calc(100% / 4);
    width: calc(100% / 4);
}
 
@media screen and (max-width: 1400px) {
    .dashboard .summary-item {
        width: 33.33333333333333%; /* fallback */
        width: -webkit-calc(100% / 3);
        width: calc(100% / 3);
    }
}
 
@media screen and (max-width: 1000px) {
    .dashboard .summary-item {
        width: 50%; /* fallback */
        width: -webkit-calc(100% / 2);
        width: calc(100% / 2);
    }
}

this worked great in most browsers, but we did find an issue with ie9. when squishing or expanding the browser window, sometimes there would be a blank column on the right side. to fix this, i changed the width on the default .summary-item to be 25%, and removed the lines with calc .

.dashboard .summary-item {
    ...
    width: 25%
}

coloring odd rows

coloring odd rows in a table is easy, but when the rows are in a responsive grid, that's a whole different story. for tables, the css rules are extremely simple:

tr:nth-child(even) {background: #ccc}
tr:nth-child(odd) {background: #fff}

via twitter, @tomaslin advised me that the nth-child selector could probably be used for this, but it'd likely require some javascript to make it responsive. i found the excellent master of the :nth-child and began trying to figure it out. the following function is what we now use to color odd rows in the summary bar.

function colorrows() {
    var lisinrow = 0;
    var items = $('.summary-items li');
    items.each(function() {
        if($(this).prev().length > 0) {
            if($(this).position().top != $(this).prev().position().top) return false;
            lisinrow++;
        }
        else {
            lisinrow++;
        }
    });
    var rows = items.length / lisinrow;
    for (var i = 0; i < rows; i++) {
        var selector = "nth-child(n+{x}):nth-child(-n+{y})";
        var x = (lisinrow * i) + 1;
        var y = x + (lisinrow - 1);
        selector = selector.replace('{x}', '' + x);
        selector = selector.replace('{y}', '' + y);
        if (i % 2) {
            $('.summary-items li:' + selector).addclass('odd');
        } else {
            $('.summary-items li:' + selector).removeclass('odd');
        }
    }
}

the above code is in dashboard.js and is called anytime the browser window is resized (to adapt to the responsive grid).

$(window).resize(colorrows);

it's also called when summary widgets are re-ordered, in the updateorder() function of widgetcontroller .

$scope.updateorder = function(event, ui) {
    ...
    preferences.savewidgetorder(type, {items: items});
    if (type === 'summary') {
        colorrows();
    }
};

i'd like to figure out how to make this more angular-esque, but all the "how to hook into window.resize" articles i found make it seem harder than this.

show more

the last feature i had to implement was the "show more" bar that appears when widgets are hidden. this was the most difficult thing to implement and i tried many different things before arriving at a solution that works. first of all, the widgets bars that can be expanded are put into their original (collapsed) state using max-height and overflow: hidden . from there, i look at the list inside the bar and compare the heights of the two elements. if the list is taller than the bar, the show more bar is added.

i originally looked at the list's :last-child to see if it was visible, but jquery's :hidden selector only works on items that are hidden by display: none rather than ones that are hidden by overflow .

as you can see from the code below, there's special logic needed to expand the min-height of the summary bar, because it doesn't have enough room at the bottom to add the bar in its collapsed state.

function showmore(element) {
    var bar = element.parent().parent();
    var list = element.parent();
    var barid = bar.attr('id');
    var listheight = list.height();
    var barheight = bar.height();
    var issummarybar = (barid.indexof('summary') > -1);
    var summarybarminheight = 260;
    var showmoreshouldbevisible = (issummarybar && element.position().top >= 200) ? true : listheight > barheight;
    if (showmoreshouldbevisible) {
        var messages = {};
        // the variables below are defined in the host page, before this file is loaded
        messages.more = showmoretext;
        messages.less = showlesstext;
 
        var showmore = $('<div class="show-more"/>').html(messages.more + " <b class='caret'></b>");
        showmore.appendto(bar);
        // summary bar doesn't have enough room for the show more bar in its collapsed state,
        // so change it from 242 to 260
        if (issummarybar) {
            bar.css({'min-height': summarybarminheight + 'px', 'max-height': ''});
        }
 
        showmore.bind('click', function (e) {
            var element = $(this);
            var parent = element.parent();
 
            if (element.hasclass('less')) {
                parent.css({"max-height": ''});
                if (issummarybar) {
                    parent.css({"min-height": summarybarminheight + 'px'}).animate(200);
                } else {
                    parent.css({"min-height": ''}).animate(200);
                }
                element.removeclass('less');
                element.html(messages.more + ' <b class="caret"></b>');
            } else {
                parent.css({
                    "max-height": 9999,
                    "min-height": 'auto'
                }).animate({
                        "min-height": parent.height() + 19
                    }, 200);
 
                element.addclass('less');
                element.html(messages.less + ' <b class="caret caret-up"></b>');
            }
 
            // prevent jump-down
            return false;
        });
    } else {
        // remove show-more in case it was previously added
        if (bar.find('.show-more').length > 0) {
            if (issummarybar) {
                bar.css('min-height', summarybarminheight - 18)
            } else {
                bar.attr('style', '');
            }
 
            bar.find('.show-more').remove();
        }
    }
}
 
function showmoreonresize() {
    var dataitems = $('.task-items,.summary-items,.report-items');
    dataitems.each(function() {
        var lastitem = $(this).find('li:last-child');
        if (lastitem.length > 0) {
            showmore(lastitem);
        }
    });
}

at first, i wrote this logic as a directive, but when i needed it for responsiveness, i moved it into dashboard.js . the showmoreonresize() function is called on window resize.

$(window).resize(showmoreonresize);

i also found that i had to add it to the preferences service after widgets were saved (since the number displayed could change).

factory('preferences', function ($filter) {
    return {
        ...
        // save hidden and visible (and order) widgets from config dialog
        savewidgetpreferences: function (type, widgets) {
            ...
            dwrfacade.savedashboardwidgetpreference(type, preferences, {
                callback: function() {
                    // recalculate show more bar
                    showmoreonresize();
                },
                errorhandler: function (errorstring) {
                    alert(errorstring);
                }
            });
        }
    }
});

to implement the .caret-up (the .caret class is from bootstrap), i found a caret-right howto and used it to create .caret-up :

.caret-up {
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 4px solid transparent;
    border-bottom: 4px solid black;
}

summary

the final my dashboard feature is something that i'm quite proud of. a fellow developer, vlad, did an excellent job of implementing the backend and admin portions. the product team's vision and desire to make it pop! created something great. the fact that we didn't have to support ie8 helped a lot in the implementation. below is a screenshot of how my dashboard looked when we completed the project.

my dashboard

angular isn't mentioned much in this article. that's because we didn't have to do much to the existing angular code to implement the new design. it was just a matter of writing/modifying some css, as well as introducing some javascript for colored rows and show more. if you know how these features could be written in a more angular way, i'd love to hear about it.

if you'd still like to learn more about angular and why it's good to integrate it little by little, i encourage you to read 5 reasons to use angularjs in the corporate app world .

AngularJS Database IT

Published at DZone with permission of Matt Raible, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Angular Component Tree With Tables in the Leaves and a Flexible JPA Criteria Backend
  • Legacy Code Refactoring: Tips, Steps, and Best Practices
  • Common Mistakes to Avoid When Writing SQL Code
  • Data-Based Decision-Making: Predicting the Future Using In-Database Machine Learning

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: