Look What Complete UI’s Grid Can Do, part one | March 26th, 2009
On a recent Nitobi web development project, we were tasked with improving a social network-ish site’s management and administration pages. Up to that point in the project, the administration panels, as I call them, were fairly neglected and had a couple of different problems:
- They were slow. As the site was gaining popularity and grew in size, the content that the site housed needed to be managed, maintained, edited, deleted, etc. more and more often. There was also more of this content to manage. These two things compounded together to start slowing the administration pages down.
- There was little to no design effort put into how the administration panels worked. This resulted in the workflow of the panels to be very cludgy.
First, I enlisted the help of Nitobi’s lead UX Designer, Chris, to help come up with some good designs for the administration panels. We looked at what these management pages provide and how our administrators use them to guide the design, and the end-result is a night-and-day improvement on the previous pages (worth a blog post in itself).
Before
Here is a thumbnail of the administration page before (click for bigger):
After
Here is a thumbnail of the page after we were done with it (again, click for bigger):
The new page is much more concise, with a nicer design and much more responsive user interaction. The grid only summarizes information in each of its rows - each element actually has a lot more content to manage than what is shown in the grid. The old administration page would show this extra content at the bottom of the page (as depicted in the screenshot), which made the page quite overwhelming. In the new page, I removed this section and made a JavaScript and DHTML-based edit ‘dialog’ that housed this extra information, cleaning up the layout and vastly improving the user experience.
Anyways, the point of this post is to show off how I used Nitobi’s own Complete UI component, Grid, in this redesign, and show off some little extra bits that I implemented to improve the user experience that much more. For those that don’t know about it, Complete UI is a sweet suite of open-source web components that you can plug into almost any web page. Grid, one of the components in this suite, is quite extensive, with a lot of little features that make it a powerful tool to be able to plug into any ol’ web page. I’m going to be getting into the details of this component, so it might be useful to have the Complete UI HTML Declaration Reference and Nitobi JavaScript APIs handy. I’ll break up the work done with the Grid over a couple of blog posts, as it is a lot of content. Today, I’ll talk about setting up your own pretty pagination controls.
Custom Paging Controls
One of the reasons the old administration pages were slow is because each page would load all relevant data right away. In today’s AJAX world, this is unnecessary and wasteful. Simple pagination would definitely help with this - why not just load the data we need, as we need it? “But wait a second, here, Filip,” you may be saying to yourself, “the Complete UI Grid already has paging! Why implement it again?” Not that I have anything against the built-in pagination in Grid, but I have something against the built-in pagination in Grid
Chris and I wanted the pagination controls to be big and salient and obvious, so he designed some and I implemented them. It was actually quite easy, a little HTML, a couple JavaScript functions and Grid. I’ve set up an archive that you folks out there can download, it contains files and source code necessary to get a skeleton of this example up and running.
- First, we disable the built-in button and pagination toolbar in the Grid. You can do this declaratively by specifying the toolbarenabled=”false” attribute of the <ntb:grid> tag.
- Here is the small HTML declaration for my custom paging controls that I added to my page:
<div id="pager_control" class="inline">
<div id="pager_first" class="inline FirstPage" onclick="Pager.First('myGrid');" onmouseover="this.className+=' FirstPageHover';" onmouseout="this.className='inline FirstPage';" style="margin:0;border-right:1px solid #B1BAC2;"></div>
<div id="pager_prev" class="inline PreviousPage" onclick="Pager.Previous('myGrid');" onmouseover="this.className+=' PreviousPageHover';" onmouseout="this.className='inline PreviousPage';" style="margin:0;"></div>
<div class="inline" style="height:22px;top:3px;">
<span class="inline">Page</span>
<span class="inline" id="pager_current">0</span>
<span class="inline">of</span>
<span class="inline" id="pager_total">0</span>
</div>
<div id="pager_next" class="inline NextPage" onclick="Pager.Next('myGrid');" onmouseover="this.className+=' NextPageHover';" onmouseout="this.className='inline NextPage';" style="margin:0;border-right:1px solid #B1BAC2;"></div>
<div id="pager_last" class="inline LastPage" onclick="Pager.Last('myGrid');" onmouseover="this.className+=' LastPageHover';" onmouseout="this.className='inline LastPage';" style="margin:0;"></div>
</div>
In a nutshell, it has a couple of spans that house the current and total page numbers, and the first/last/next/previous buttons (styles defined via the classes) that call methods in a JavaScript Pager object (more on that next). - Next, we have to somehow tell our server-side data handler to grab only a particular page of data. It doesn’t really matter what language you write your server-side scripts in. All we have to do is pass paging parameters to our server methods via the querystring, and then make sure our server-side methods parse these parameters and use them to retrieve the proper page of data. This is all controlled via the JavaScript Pager object I was talking about earlier, and here is the JS:
var Pager = {
PageSize:12,
CurrentPage:0,
TotalPages:0,
TotalRows:0,
Last:function(GridID) {
Pager.CurrentPage = Pager.TotalPages;
Pager.BindToGrid(GridID);
},
First:function(GridID) {
Pager.CurrentPage = 1;
Pager.BindToGrid(GridID);
},
Previous:function(GridID) {
if (Pager.CurrentPage > 1) {
Pager.CurrentPage--;
} else {
Pager.CurrentPage = Pager.TotalPages;
}
Pager.BindToGrid(GridID);
},
Next:function(GridID) {
if (Pager.CurrentPage < Pager.TotalPages) {
Pager.CurrentPage++;
} else {
Pager.CurrentPage = 1;
}
Pager.BindToGrid(GridID);
},
Reset:function(GridID) {
Pager.CurrentPage = 0;
Pager.TotalPages = 0;
Pager.BindToGrid(GridID);
},
// Uses the paging variables in the Pager object to re-bind the Grid with the right page.
BindToGrid:function(GridID) {
g = nitobi.getComponent(GridID);
if (Pager.TotalPages == 0) {
g.datatable.setGetHandlerParameter('StartRecord',0);
} else {
g.datatable.setGetHandlerParameter('StartRecord',(Pager.CurrentPage-1)*Pager.PageSize);
}
g.loadingScreen.show();
g.dataBind();
}}
All of the Pager functions take the Grid ID as a parameter, for easy re-use with other Grids, if one so desires. What happens when you call Next(), Previous() or any other function there is the querystring parameters for paging get set in the Grid’s datatable - this is the component that houses the Grid’s data and handles data retrieval from and communication with the data handler server methods (via XHRs). Potentially, you could also pass the pagination size as a parameter, if you wish to dynamically vary it too. Just remember that whatever parameters you set using the Grid’s setGetHandlerParameter (click here for more info - scroll down to the getDatasource() method to see a small example) need to be parsed by your data handler script, otherwise it won’t be used! In my example above, I set a parameter called ‘StartRecord’, which I parse in my data handler and then use in my data retrieval query. Then simply call dataBind() on the Grid to force Grid to re-connect to the handler and retrieve the data. - Finally, we need to update the Pager object with the right number of records retrieved from the database. I did this by assigning an error attribute to the root XML node of the data handler’s response, which would contain the number of rows the server processed, and then hooked in a JavaScript function to the Grid’s OnDataReadyEvent (which gets fired every time a data request from the server is complete) to parse this number back into the Pager object. There are other, probably more elegant ways to implement this part, but it seemed like the quickest way for me, and I didn’t use the error attribute for anything else, so why not? So, first I hooked in a function to the event via the HTML declaration: ondatareadyevent=”HandleReady(eventArgs);”. This is what the function looks like:
function HandleReady(eventArgs) {
var g = eventArgs.source;
Pager.TotalRows = parseInt(g.datatable.handlerError);
Pager.TotalPages = Math.ceil(Pager.TotalRows/Pager.PageSize);
$('pager_total').innerHTML = Pager.TotalPages;
if (Pager.TotalPages > 0) {
if (Pager.CurrentPage == 0) {
Pager.CurrentPage = 1;
}
} else {
Pager.CurrentPage = 0;
}
$('pager_current').innerHTML = Pager.CurrentPage;
// Hide Grid loading screen.
g.loadingScreen.hide();
}
The error attribute that we set in the server response is easily accessible via the datatable.handlerError member, as demonstrated in the above function. Once we parse the total rows, we just set the span contents that show off the current and total pages, hide the Grid loading screen, and we’re done!
That’s it for this week! Next week, I’ll post about improving the editing experience in Grid with the help of JavaScript, the ImageEditor column available in Grid, and doing your ‘own thang’ by manipulating the Grid’s DOM nodes.
EDIT: I’ve added before/after pictures of our work, as well as a downloadable example page of the Complete UI Grid with a custom paging control, available -> here <-. Please read the attached readme file to get the example running properly - enjoy!
March 29th, 2009 at 9:26 pm
nice post and nice work fil! would love to see a couple before and after screen shots or even screen cast to see some of the hotness you and chris added to grid!
March 30th, 2009 at 10:25 am
[...] Look What Grid Can Do, part one - March 26,2009 In this post, Filip talks about setting up your own custom paging controls for Grid. page_revision: 1, last_edited: 1238434060|%e %b %Y, %H:%M %Z (%O ago) edittags history files print site tools+ options edit sections append backlinks view source parent block rename delete help | terms of service | privacy | report a bug | flag as objectionable Hosted by Wikidot.com — get your free wiki now! Unless stated otherwise Content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License Click here to edit contents of this page. Click here to toggle editing of individual sections of the page (if possible). Watch headings for an “edit” link when available. Append content without editing the whole page source. Check out how this page has evolved in the past. If you want to discuss contents of this page - this is the easiest way to do it. View and manage file attachments for this page. A few useful tools to manage this Site. See pages that link to and include this page. Change the name (also URL address, possibly the category) of the page. View wiki source for this page without editing. View/set parent page (used for creating breadcrumbs and structured layout). Notify administrators if there is objectionable content in this page. Something does not work as expected? Find out what you can do. General Wikidot.com documentation and help section. Wikidot.com Terms of Service - what you can, what you should not etc. Wikidot.com Privacy Policy. _uff = false; _uacct = “UA-68540-5″; _udn=”wikidot.com”; urchinTracker(); _qoptions={ qacct:”p-edL3gsnUjJzw-” }; [...]
April 1st, 2009 at 5:16 pm
[...] Filip Maj’s Blog RSS « Look What Complete UI’s Grid Can Do, part one [...]
April 16th, 2011 at 9:11 am
Very nice!…
Wow you are very very talented!! keep up the awesome work. You are very talented & I only wish I could write as good as you do
…