Category: "Ajax/JSON"

eZ publish - Creative Commons Licensed Image - Class

Using Creative Commons licensed images allows you to include some beautiful photos and illustrations on your site.

It’s really important to add the appropriate credits when you’re using the images, to comply with the license, but more importantly, to give credit to the creator. To properly credit the work, review this.

To make it easier to credit the images in eZ publish, I created a class and supporting templates. The content class package export (the link above) includes the class definition. The templates I used are below:

design/site/override/templates/full/cc_image.tpl

{* CC_Image - Full view *}
{let sort_order=$node.parent.sort_array[0][1]
     sort_column=$node.parent.sort_array[0][0]
     sort_column_value=cond( $sort_column|eq( 'published' ), $node.object.published,
                             $sort_column|eq( 'modified' ), $node.object.modified,
                             $sort_column|eq( 'name' ), $node.object.name,
                             $sort_column|eq( 'priority' ), $node.priority,
                             $sort_column|eq( 'modified_subnode' ), $node.modified_subnode,
                             false() )
     previous_image=fetch_alias( subtree, hash( parent_node_id, $node.parent_node_id,
                                                class_filter_type, include,
                                                class_filter_array, array( 'cc_image' ),
                                                limit, 1,
                                                attribute_filter, array( and, array( $sort_column, $sort_order|choose( '>', '<' ), $sort_column_value ) ),
                                                sort_by, array( array( $sort_column, $sort_order|not ), array( 'node_id', $sort_order|not ) ) ) )
     next_image=fetch_alias( subtree, hash( parent_node_id, $node.parent_node_id,
                                            class_filter_type, include,
                                            class_filter_array, array( 'cc_image' ),
                                            limit, 1,
                                            attribute_filter, array( and, array( $sort_column, $sort_order|choose( '<', '>' ), $sort_column_value ) ),
                                            sort_by, array( array( $sort_column, $sort_order ), array( 'node_id', $sort_order ) ) ) )}

<div class="content-view-full">
    <div class="class-image">

        <h1>{$node.name|wash}</h1>

        {if is_unset( $versionview_mode )}
        <div class="content-navigator">
            {if $previous_image}
                <div class="content-navigator-previous">
                    <div class="content-navigator-arrow">&laquo;&nbsp;</div><a href={$previous_image[0].url_alias|ezurl} title="{$previous_image[0].name|wash}">{'Previous image'|i18n( 'design/base' )}</a>
                </div>
            {else}
                <div class="content-navigator-previous-disabled">
                    <div class="content-navigator-arrow">&laquo;&nbsp;</div>{'Previous image'|i18n( 'design/base' )}
                </div>
            {/if}

            {if $previous_image}
                <div class="content-navigator-separator">|</div>
            {else}
                <div class="content-navigator-separator-disabled">|</div>
            {/if}

            {let forum=$node.parent}
                <div class="content-navigator-forum-link"><a href={$forum.url_alias|ezurl}>{$forum.name|wash}</a></div>
            {/let}

            {if $next_image}
                <div class="content-navigator-separator">|</div>
            {else}
                <div class="content-navigator-separator-disabled">|</div>
            {/if}

            {if $next_image}
                <div class="content-navigator-next">
                    <a href={$next_image[0].url_alias|ezurl} title="{$next_image[0].name|wash}">{'Next image'|i18n( 'design/base' )}</a><div class="content-navigator-arrow">&nbsp;&raquo;</div>
                </div>
            {else}
                <div class="content-navigator-next-disabled">
                    {'Next image'|i18n( 'design/base' )}<div class="content-navigator-arrow">&nbsp;&raquo;</div>
                </div>
            {/if}
        </div>
        {/if}

        <div class="attribute-image">
            <p>{attribute_view_gui attribute=$node.data_map.image image_class=large}</p>
        </div>

        <div class="attribute-caption">
            {attribute_view_gui attribute=$node.data_map.caption}
        </div>

        <div class="attribute-attribution">
            {'Image credit'|i18n( 'design/base' )}&nbsp;&raquo;&nbsp;{attribute_view_gui attribute=$node.data_map.attribution}
        </div>

        <div class="attribute-creative-commons-license">
                {'Creative Commons License'|i18n( 'design/base' )}:&nbsp;{attribute_view_gui attribute=$node.data_map.creative_commons_license}
        </div>

    </div>
</div>
{/let}

design/site/override/templates/line/cc_image.tpl

{* CC_Image - Line view *}

<div class="content-view-line">
    <div class="class-image">

    <div class="content-image">
        <p style="float:left;width:125px">{attribute_view_gui attribute=$node.data_map.image image_class=small href=$node.url_alias|ezurl()}&nbsp;</p><h2>{$node.name}</h2>
        {attribute_view_gui attribute=$node.data_map.caption}
    </div>

    </div>
</div>

design/site/override/templates/galleryslide/cc_image.tpl

{* CC_Image - Gallery slide view *}

<div class="content-view-galleryslide">
    <div class="class-image">

    <h1>{$parent_name|wash()}: {$node.name|wash()}</h1>

    <div class="attribute-image">
        <p>{attribute_view_gui attribute=$node.data_map.image image_class=large}</p>
    </div>

    <div class="attribute-caption">
        {attribute_view_gui attribute=$node.data_map.caption}
    </div>

    </div>
</div>


design/site/override/templates/embed/cc_image.tpl

<div class="content-view-embeddedmedia">
<div class="class-image">

<div class="attribute-image">
<p>
{if or( is_set( $link_parameters.href ),$object.data_map.attribution.has_content )}
    {attribute_view_gui attribute=$object.data_map.image image_class=first_set( $object_parameters.size,ezini( 'ImageSettings', 'DefaultEmbedAlias', 'content.ini' ), '' ) href=first_set( $link_parameters.href,$object.main_node.url )|ezurl target=$link_parameters.target link_class=first_set( $link_parameters.class, '' ) link_id=first_set( $link_parameters['xhtml:id'], '' ) link_title=first_set( $link_parameters['xhtml:title'], $object.data_map.attribution.content.data_text, '' )}
{else}
  {if is_set($object_parameters.size)}
    {attribute_view_gui attribute=$object.data_map.image image_class=$object_parameters.size}
  {else}
    {attribute_view_gui attribute=$object.data_map.image image_class=ezini( 'ImageSettings', 'DefaultEmbedAlias', 'content.ini' )}
  {/if}
{/if}
</p>
</div>

{if $object.data_map.caption.has_content}
{if is_set($object.data_map.image.content[$object_parameters.size].width)}
<div class="attribute-caption" style="width: {$object.data_map.image.content[$object_parameters.size].width}px">
{else}
<div class="attribute-caption">
{/if}
    {attribute_view_gui attribute=$object.data_map.caption}
</div>
{/if}
</div>
</div>

Template override settings. I used the default image class for some cases.

settings/siteaccess/site/override.ini.append.php


[cc_image_full]
Source=node/view/full.tpl
MatchFile=full/cc_image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_galleryslide]
Source=node/view/galleryslide.tpl
MatchFile=galleryslide/cc_image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_embed]
Source=content/view/embed.tpl
MatchFile=embed/cc_image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_embed-inline]
Source=content/view/embed-inline.tpl
MatchFile=embed-inline/image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_embed_node]
Source=node/view/embed.tpl
MatchFile=embed/cc_image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_embed-inline_node]
Source=node/view/embed-inline.tpl
MatchFile=embed-inline/image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_line]
Source=node/view/line.tpl
MatchFile=line/cc_image.tpl
Subdir=templates
Match[class_identifier]=cc_image

[cc_image_galleryline]
Source=node/view/galleryline.tpl
MatchFile=galleryline/image.tpl
Subdir=templates
Match[class_identifier]=cc_image

I tested this with a custom site design, but it was based on one of the eZ site styles, and very few of the templates were modified. It should integrate well with most designs, but customization is ‘eZ’ enough. :)

I used the standard, default eZ publish image class as the base for the templates.

Multiple dojox grids on a single page

I’ve been working on a page that allows an administrator to configure mobile device reimbursement expenses.  The page offers three types of expenses (voice, messages, and data), and five different reimbursement options for each (no reimbursement, flat rate, per unit, per unit with a maximum, and a tiered approach).  There’s also a maximum amount setting to limit the total reimbursement.  For more information about mobile expense reimbursement, visit http://mobiso.com The remainder of this post describes the technical implementation.

I used dojox DataGrids to allow the administrator to set the tiered values.  Each tier has a minimum value and an amount.  The minimum value is the lowest number of units the device must have in order to receive the corresponding amount.  The tier values, both minimums and amounts, must ascend.

To simplify the page submission and display logic, I used hidden textareas to submit the grid data to the server and return it to the client.

The following code is the XHTML  It includes Smarty template code, because it’s inside a loop. I left the Smarty code because it’s used in the javascript to add and remove rows, as well as to move the data between the textareas.  If you only have one or two grids, you could just hard code the indexes or identifiers.

<div class="divAltButtons">
<a href="javascript:AddRow({$smarty.foreach.type.index})">
<img src="images/cp/16x16/actions/edit_add.png" />
</a>
<a href="javascript:RemoveRows({$smarty.foreach.type.index})">
<img src="images/cp/16x16/actions/cancel.png" />
</a>
</div>
<div class="break"></div>
<div dojoType="dojo.data.ItemFileWriteStore" data="storeData_{$sUT}"
jsId="store_{$smarty.foreach.type.index}" id="store_{$smarty.foreach.type.index}">
</div>
<table  dojoType="dojox.grid.DataGrid" id="grid_{$smarty.foreach.type.index}"
store="store_{$smarty.foreach.type.index}" jsId="grid_{$smarty.foreach.type.index}"
clientSort="true"
singleClickEdit="true"
rowSelector="24px"
style="width:200px; height: 117px;"
query="{ldelim}{rdelim}">
<thead>
<tr>
<th field="row" width="40px" hidden="true">Row</th>
<th width="auto" field="minimum" editable="true">Minimum</th>
<th width="auto" field="amount" editable="true">Amount</th>
</tr>
</thead>
</table>
<textarea name="tGridData[{$sUT}]" id="tGridData{$smarty.foreach.type.index}" style="display:none">{$tGridData[$sUT]}
</textarea>

Much of the code came from http://www.dojotoolkit.org/reference-guide/dojo/data/ItemFileWriteStore.html, including itemToJS, which is not listed here.

These functions takes the code from the grid and puts it into the texarea, and gets it back out.

/* Prior to submitting the data to the server, call putGridDataInTextAreas(); to copy the grid data into the text area */
/* When the response is received, use getGridDataFromTextAreas(response); to get the data into the grid */ 

function getGridDataFromTextAreas(response)
{
var i,grid,store,txtArea;
for (i=0;i<iGridCount;i++)
{
txtArea=dojo.byId('tGridData'+i);
txtArea.value=response[txtArea.name];
grid=dijit.byId('grid_'+i); store = new dojo.data.ItemFileWriteStore({jsId: 'store_'+i,data:dojo.fromJson(txtArea.value), urlPreventCache: true});
RefreshGrid(store,grid);
GridFocus(grid);
}
}

function putGridDataInTextAreas()
{
var i,grid,txtArea;
for (i=0;i<iGridCount;i++)
{
grid=dijit.byId('grid_'+i);
GridFocus(grid);
txtArea=dojo.byId('tGridData'+i);
txtArea.value=makeData(grid.store);
}
}

function AddRow(i)
{
grid=dijit.byId('grid_'+i);store=grid.store;
oDefaultItem.row=grid.rowCount+1;
// Blur the current cell to preserve the value
GridFocus(grid);
// set the properties for the new item:
// Insert the new item into the store:
store.newItem(oDefaultItem);
// Set the focus on the new row
grid.focus.setFocusIndex(grid.rowCount-1,0);
RefreshGrid(store,grid);
setDataChanged();
}

function RemoveRows(i)
{
grid=dijit.byId('grid_'+i);store=grid.store;
// Get all selected items from the Grid:
var items = grid.selection.getSelected();
if(items.length){
//if (!confirm(C_confirm_remove)) return;
bRemoving=true;
// Iterate through the list of selected items.
// The current item is available in the variable
// "selectedItem" within the following function:
dojo.forEach(items, function(selectedItem) {
if(selectedItem !== null) {
// Delete the item from the datastore:
store.deleteItem(selectedItem);
} // end if
}); // end forEach
} // end if
RefreshGrid(store,grid);
setDataChanged();
}

function GridFocus(grid)
{
if (!bRemoving)
{
try
{grid.focus.setFocusIndex(0,0);}
catch(err)
{var i=0; /* nop */}
}
bRemoving=false;
}

function RefreshGrid(store,grid)
{
// Refresh the data in the grid
grid.setStore(store);
grid.update();
grid.selection.deselectAll();
}
function makeData(store)
{
var data = [];
for (var i in store._arrayOfAllItems) {
var item=null;
item = store._arrayOfAllItems[i];
data.push(itemToJS(store, item));
}
return dojo.toJson(data, true);
}

/* Create empty grid data */
$aGridData=array();
foreach ($aUsageType as $k => $v)
$aGridData[$v]['items']=array();
/* If there is data in the database, place it in the grid data. Note that there may be several rows of data */
case 'tiered':
$aGridData[$sUsageType]['items'][$row]['row']=$row;
$aGridData[$sUsageType]['items'][$row]['minimum']=$v['usage_minimum_value'];
$aGridData[$sUsageType]['items'][$row]['amount']=number_format($v['method_amount'],2);
$row++;
break;
/* Convert the array to JSON */
require_once 'Zend/Json.php';
foreach ($aGridData as $k => $v)
$aReturn['tGridData['.$k.']']=Zend_Json::encode($v);
/* The code to decode and store the data in the database is application specific, and is not included, it begins with a JSON decode. */

As stated above, the code to extract the data from the textarea and store it is left to the reader.

Key development strategies:

  • Hard code the JSON data to get the grid code running.
  • Use FireBug (FireFox plugin) to examine the data and store.
  • Use var_dump on the server side, frequently.
  • Be sure to sanitize and validate the data, as well as filter it prior to sending it to the client.
  • Deleted rows are submitted as empty braces, so as you loop through the grid rows, discard any empty rows or rows with zero cells of data.
  • Keep an eye on the page load time, it may be good to delay creation of the grid until the page has finished rendering other elements.

This post is courtesy of Lyrix, Inc. http://lyrix.com

Debugging AJAX

  • Use the transport mechanism to send debug information. For example, if you’re sending back an array of data, add a new element (or several), and put in the SQL query so you can see exactly what’s being executed.
  • Store the returned data in global variables on the client side and use Firebug to examine them. console.debug and alert are helpful, too.
  • Use var_dump and echo if you get frustrated. They’ll probably throw an error on the client side, but you’ll get to see what you want to.
  • Use var_export($variable,true) if you want to do a var_dump to a string or to a file. file_put_contents(’/tmp/file’,$data,FILE_APPEND); is a great way to monitor script execution. Open a new SSH window and use tail -f /tmp/file to see it.
  • Cut and paste output into Notepad so you can easily verify it. Often you need several chunks of text and data to truly understand what is happening and why.
  • Develop complex pieces in a standalone environment. This is good for assembling arrays of data out of complex SQL queries. Simplifying the task so you can focus on the difficult parts will speed development.
  • Watch out for extra commas in object assignments. IE will crash.
  • If you have Visual Studio, use it. It can save you a tremendous amount of time when debugging under IE.
  • Test for the presence of variables, objects, and attributes before using them. It isn’t really right, but you can use if (variable_exists) to see if the variable has been set up. A better solution is to initialize everything, or to use if (typeof variable != ‘undefined’).
  • To remove something from a page, use style="display:none". It works, it’s simple, and it’s very low risk. If anyone is playing with your code, they’ll find it, but it shouldn’t be an issue unless it had security issues, or restricted content.
  • Add a debug setting to your code that allows you to receive more information when errors occur. You can disable it for the production release.

dojox/charting - notes

Notes on using dojox Charting.

  • Read the documention at the link above.
  • Assemble the page as AJAX from the beginning. Don’t bother building an HTML version first, the number of requests required for charting is large enough that these pages should be AJAX pages. Zend Framework’s Dojo Data (http://framework.zend.com/manual/en/zend.dojo.data.html) component is an excellent way to encode the data for transport. Zend Framework can be used as a library.
  • Consider using a pulldown to allow the viewer to select the theme used. This is nice for appearance and fun, but may also be valuable for people that have difficulty distinguising colors. A simple loop that lists the available themes, uses a dojo.require to fetch the code, and then applies them to the charts and legends with chart.SetTheme, chart.render, and legend.refresh is a nice feature.
  • Think carefully about what charts you’d like to use. This link
    http://archive.dojotoolkit.org/nightly/dojotoolkit/dojox/charting/tests/test_chart2d.html has great sample code. Take the time to look at the charts available. Moving up a level or two in the directory tree yields additional, valuable code.
  • Use updateSeries if possible to redraw charts, if necessary, use destroy, then recreate them.
  • Offer good explanatory text to help people interpret the displays.
  • Write the queries carefully, it’s often faster to query, then use arithmetic to find out other portions of the data. Don’t forget to fill in gaps of missing data for series that illustrate usage or events across time spans.
  • Be sure to sanitize and validate the data. Always.

Let Go of Logic and "It Should"

Sometimes, code doesn’t do what it is supposed to, or what you expect. This is particularly common with open source code you didn’t write.

You can’t change the basic code architecture or data structures.

Key techniques to adapt and recover with javascript are instanceof and typeof. This allows you to test if an object or property has been created or assigned, and what type it is, then you can use it properly. Often, the best way to find out what types are being used is FireBug.

Take the time to look for examples in the code, so you don’t have to figure it all out from scratch. Usually, modifications to open source code involve code similar to that which is already in there, for example reassigning values.