Category: "Dojo"

Presenting KnpMenus with Dojo

This Dojo module will traverse a menu delivered by the KnpMenuBundle and present it as a Dijitized menu bar.

There may be a few extraneous modules included, but this code will likely be extended.

define([
    "dojo/_base/declare",
    "dojo/dom",
    "dojo/dom-attr",
    "dojo/dom-construct",
    "dojo/on",
    "dojo/query",
    "dijit/registry",
    "dijit/MenuBar",
    "dijit/MenuBarItem",
    "dijit/PopupMenuItem",
    "dijit/PopupMenuBarItem",
    "dijit/MenuItem",
    "dijit/DropDownMenu",
    "dijit/form/Button",
    "dijit/Dialog",
    "app/lib/common",
    "dojo/i18n!app/nls/core",
    "dojo/NodeList-traverse",
    "dojo/domReady!"
], function (declare, dom, domAttr, domConstruct, on, query, registry,
        MenuBar, MenuBarItem, PopupMenuItem, PopupMenuBarItem, MenuItem, DropDownMenu, Dialog,
        lib, libGrid, core) {

    function run() {
        var menuBar = new MenuBar({}, "admin-top-menu");

        function createMenuItem(widget, parent, depth) {
            var children, node, item, i, label, nextNode;
            var popupMenuObj, labelObj, link;
            children = query(parent).children();
            for( i = 0; i < children.length; i++ ) {
                node = children[i];
                nextNode = (typeof children[i + 1] !== "undefined") ? children[i + 1] : null;
                switch( node.tagName ) {
                    case "SPAN":
                    case "A":
                        label = node.textContent.trim();
                        if( typeof node.href !== "undefined") {
                            link = node.href;
                        } else {
                            link = null;
                        }
                        if( nextNode !== null && nextNode.tagName === "UL" ) {
                            popup = new DropDownMenu();
                            popupMenuObj = {label: label, popup: popup};
                            if( depth <= 1 ) {
                                item = new PopupMenuBarItem(popupMenuObj);
                            } else {
                                item = new PopupMenuItem(popupMenuObj);
                            }
                            createMenuItem(popup, nextNode);
                        } else {
                            labelObj = {label: label};
                            if( depth <= 1 ) {
                                item = new MenuBarItem(labelObj);
                            } else {
                                item = new MenuItem(labelObj);
                            }
                        }
                        if( link !== null ) {
                            item.on("click", function () {
                                location.href = link
                            });
                        }
                        widget.addChild(item);
                        break;
                    case "LI":
                        createMenuItem(widget, node, depth + 1);
                        break;
                }
            }
        }

        var menuElements = query("#admin-top-menu ul");
        if( menuElements.length > 0 ) {
            createMenuItem(menuBar, menuElements[0], 0);
            domConstruct.destroy(menuElements[0]);
        }
        menuBar.startup();
    }
    return {
        run: run
    };
});

Symfony / Dojo - Prod and Dev environment management

Dojo has a great build process which allows you to create a optimized and minimized files for the client side (and more!).

In a production environment, this greatly improves performance.

However, building the code after every change will slow development significantly.

Since Dojo can load the required modules dynamically, you can load the source files and work with them directly, and maintain your profile file as you work. Running a build at the end of each development session will help to ensure the code and profile stay in sync.

admin.base.html.twig

This template provides the foundation page layout for all admin pages. If the application is running in a dev environment, it includes a page_footer_script, but in production, it includes dojo.js

{% if app.environment == 'dev' %}
    {% include 'admin/parts/page_footer_script.html.twig' %}
{% else %}
    <script data-dojo-config="async:1" src="/release/dojo/dojo.js"></script>
{% endif %}

{% block javascripts %}
{% endblock %}

{% if omit_menu is not defined %}
    <script>
        require([
            "app/admin/menu",
            "dojo/domReady!"
        ], function (menu) {
            menu.run();
        });
    </script>
{% endif %}

page_footer_script.html.twig

Used only in the dev environment


<script>
    var dojoConfig = {
        async: true,
        baseUrl: '/release',
        paths: {"lib": "/app/lib",
            "nls": "/app/nls"},
        packages: [
            {"name": 'app', "location": '/app'},
            'dojo',
            'dijit',
            'dojox',
            'dgrid',
            'dstore',
            'put-selector',
            'xstyle'
        ],
        selectorEngine: 'lite',
        tlmSiblingOfDojo: false,
        has: {
            "dojo-trace-api": false
        }
    };
</script>
<script data-dojo-config="async:1" src="/vendor/dojo/dojo/dojo.js"></script>

example-page.html.twig

Each page template has a javascripts block which includes the require call to bring in the client side code.


{% block javascripts %}
    <script>
        require(["app/admin/asset/brand"], function (brand) {
            brand.run();
        });
    </script>
{% endblock %}

Symfony / Dojo - Prod and Dev environment management

dojox/gauges/GlossyCircularGauge v1.9.1

Example of a Dojo GlossyCircularGauge

require(["dojox/gauges/GlossyCircularGauge", "dojo/query", "dojo/dom-attr", "dojo/NodeList-dom", "dojo/domReady!"], 
function(Gauge, query, domAttr){
        var rateGauge = document.getElementById("rateGauge");
        var glossyCircular = new Gauge({
                background: [255, 255, 255, 0],
                title: 'Value',
                id: "rateGauge",
                width: 300,
                height: 300,
                min: 0,
                max: 100}, rateGauge);
        glossyCircular.startup();
        glossyCircular.addRanges(
                [{size: 65, low: 0, high: 25, hover: 'Worth doing just because it\'s fun', useTooltip: true,
                        color: { type: 'linear', colors:[{offset:0, color: '#030'}, {offset:1, color: '#090'}]}},
                        {size: 65, low: 25.1, high: 50, hover: 'Fun, but requires skill',
                        color: { type: 'linear', colors:[{offset:0, color: '#090'}, {offset:1, color: '#ff0'}]}},
                        {size: 65, low: 50.1, high: 75, hover: 'Challenging, requiring skill and research, but still fun',
                        color: { type: 'linear', colors:[{offset:0, color: '#ff0'}, {offset:1, color: '#f90'}]}},
                        {size: 65, low: 75.1, high: 100, hover: 'Work, not fun',
                        color: { type: 'linear', colors:[{offset:0, color: '#f90'}, {offset:1, color: '#f00'}]}}]);
});

Putting the ranges in the options when the gauge was created worked, but the hover tooltips didn't appear on hover. Adding the ranges with .addRanges, after .startup made it work.

The range borders are the first of the gradient colors. Looks cool.

dgrid Print Shim

If you need to print a dgrid, you can query the store and create an HTML table with JavaScript.

Create and work with the dgrid as usual, but add a second div below it which will contain the table.


<div class="no-print" id="grid"></div>
<div class="print-only" id="grid_print"></div>

Then set up the CSS to hide the print-only by default. Create a print.css file and the appropriate media attribute hide the no-print class and display the print-only.

Relevant lines of print.css

.print-only {
        display: block;
}
.print-only table {
        width: 100%;
        table-layout: fixed;
}
.print-only table th {
        border: 1px solid #000;
        font-weight: bolder;
}

Relevant lines of admin.css (screen)


.print-only,.page-break {
        display:none;
}

Link tag for the print.css file

<link media="print” href=’/css/print.css’ rel=’stylesheet’ type=’text/css’>

JavaScript to create the table. This code is used on a page with several grids, the grids are in the grids object, with stores in the stores object.


                byId("print_button").onclick = function() {
                                /* This will find all the print_grids */
                                query("[id$='_grid_print']").forEach(function(node) {
                                                if (node.hasChildNodes()) {
                                                        node.removeChild(node.firstChild);
                                                }

                                                var grid, grid_id;

                                                grid_id = node.id.replace(/_grid_print/,'');
                                                grid = grids[grid_id];

                                                var store = stores[grid_id];
                                                var data = store.query({});

                                                if (data.length === 0) {
                                                        var no_data = document.createTextNode("None at this time");
                                                        node.appendChild(no_data);
                                                } else {
                                                        var table, tr, th, td, content, c;
                                                        var hasNumber = false;
                                                        table = document.createElement("table");
                                                        tr = document.createElement("tr");
                                                        for (c in grid.columns) {
                                                                content = document.createTextNode(grid.columns[c].label);
                                                                th = document.createElement("th");
                                                                th.appendChild(content);
                                                                tr.appendChild(th);
                                                                if (c === "number") hasNumber = true;
                                                        }
                                                        table.appendChild(tr);

                                                        if (hasNumber) {
                                                                data = store.query({},[{attribute:"number"}]);
                                                        }
                                                        var i = 1,j,l;
                                                        l = data.length;
                                                        for (j=0; j<l; j++) {
                                                                tr = document.createElement("tr");
                                                                for (c in grid.columns) {
                                                                        content = document.createTextNode(data[j][c]);
                                                                        td = document.createElement("td");
                                                                        td.appendChild(content);
                                                                        tr.appendChild(td);
                                                                }
                                                                table.appendChild(tr);
                                                                i++;
                                                        }
                                                        node.appendChild(table);
                                                }
                                        });
                                window.print()
                        };

The hasNumber code is used to order the rows by a number in one of the columns.

This post courtesy of Worktrainer.

Dynamic Banner - js and css

This post provides code to create a dynamic banner which sources images from Flickr.

Using a dynamic banner has several advantages:

  • The banner is assembled dynamically, it is more engaging
  • The images aren’t stored or managed on local servers
  • The banner images can be changed by modifying the images at the source
  • It would be possible to customize the source of the images for demonstrations
  • Clicking on the banner displays the image credits

This is an extension of the code listed in link above. It is running nicely with dojo 1.3.0, tested under IE7, FF11&12, and Chrome 17.

The banner is 160px high, and 1024px wide, it is overlaid with an image which includes a gradient to fade the banner images from full color to white, using transparency. The banner includes an icon for the product on the far right.

#header img,#header a
{
height:160px;
overflow:hidden;
}
#gradient-overlay
{
background:transparent url(images/banner.png) no-repeat;
height:160px;
width:1024px;
position:fixed;
}
#header
{
cursor:pointer;
height:160px;
width:100%;
margin:25px;
overflow:hidden;
}
#dlgCredit div p
{
display:none;
}
#dlgCredit div p:first-of-type,
#dlgCredit div p:nth-child(2)
{
display:block;
}

This is the javascript to create the banner. The remaining code should be collected from the link above.

The image list received from flickr is accessed randomly, using the splice function. This allows each banner to be created differently. Images are added to the banner until the width exceeds the width of the gradient overlay. The image credits are place in a dialog box.

                function gotItems(items, request){
                        var list = dojo.byId("header");
                        if(list){
                                var span=dojo.create("span",{'id':'gradient-overlay'});
                                list.appendChild(span);
                                var width,pick,credit_text='',done=false;
                                width=span.clientWidth;
                                while (!done)
                                {
                                        index=Math.floor(Math.random()*(items.length-1));
                                        pick=items.splice(index,1);
                                        if (pick.length>0)
                                        {
                                                var item=pick[0];
                                                var image = dojo.create("img",
                                                        { "src":flickrStore.getValue(item, "imageUrlMedium"),
                                                        "title": flickrStore.getValue(item, "title"),
                                                        "alt": flickrStore.getValue(item, "title")});
                                                if ((image.width==0)||(image.width>width))
                                                {
                                                        /* Limit the width of the banner */
                                                        dojo.destroy(image);
                                                        if (image.width>width)
                                                                done=true;
                                                        /* You can also continue to see if later images will fit */
                                                }
                                                else
                                                {
                                                        width-=image.width;
                                                        list.appendChild(image);
                                                        var str=flickrStore.getValue(item,'description');
                                                        var n=str.indexOf('');
                                                        n=str.indexOf('',n+1);
                                                        var truncated=str.substr(0,n);
                                                        credit_text+='<div>'+truncated+'</div>';
                                                }
                                        }
                                        else
                                                done=true;
                                }
                                var credit=dojo.create("div",{'id':'credit'});
                                var dlgCredit=new dijit.Dialog({
                                        'id': 'dlgCredit',
                                        'content':credit_text},
                                        'credit');
                                list.setAttribute('title','Click for image credits');
                                list.onclick=function(){dlgCredit.show()};
                                list.appendChild(credit);
                        }

The HTML creates the flickrStore and sets up the header div.

<div dojoType="dojox.data.FlickrStore" jsId="flickrStore"></div>
<div id="header"></div>

This post courtesy of Lyrix, Inc