How to Do Infinite Scrolling in Domino
Last week I linked to a Domino-based infinite-scrolling view demo and promised I'd write up how I did it. Even though the reaction to the demo was mixed I'm a man of my word, so here goes.
It always surprises (worries!) me when I'm asked to either share a downloadable copy of a demo or write a how-to about it. Particularly when it's something that's all but completely web-based. The code is there to see; just a click away.
That said, in this case, unless you're familiar with jQuery and/or jQuery plugins it might not make much sense and warrants some explaining, so here goes.
First bit of code you see is at the very bottom of the scrolling view page and looks like this:
//Setup the "infinite" scrolling view $(document).ready(function(){ $('#invoices').infiniteScroller({ URL: "(getInvoices)?OpenAgent", viewName: "InvoicesByDate", emptyArraySize:1, pageSize: 40, itemTemplate: "<tr><td><%=Document.Created.Formatted%><td><%=Title%></td><td><%=Customer.FullNameReversed%></td><td style=\"text-align: right;\"><%=Value.Formatted%></td>"+ "<td><span class=\"label <%=Status.LabelClass%>\"><%=Status.Description%></span></td>"+ "<td><a class=\"btn btn-link\" href=\"0/<%=Document.UNID%>?Open\">View »</a></td></tr>", endTemplate: "<tr><td colspan=\"6\" style=\"text-align:center\"><strong>There are no more invoices to load!</strong></td></tr>" }); });
You may recognise the .ready() bit as being jQuery's way of having the enclosed code run as soon as the document is done loading/rendered.
But what about that $().infiniteScroller() bit!? Well, that's a call to the jQuery plugin part that I wrote. That might sound difficult, but it's not.
In essence you can define a jQuery plugin, which I've done in the "Application.js" file, like this:
(function ($) { var Scroller = function (element, options) { this.$element = $(element); this.options = options; //Do what you like here. //Remember that this.$element always points to //the HTML element we "invoked" infiniteScroller on! } $.fn.infiniteScroller = function (options) { return new Scroller(this, options); }; })(window.jQuery);
Notice the part in bold! This is where we extend jQuery's $() selector with our own method name. Simple, but so powerful.
Obviously I've missed out most of the actual code above. All that the missing code does is call an Agent via Ajax and append the resulting data as HTML <tr> elements on to this.$element.
The only tricky part is handling the auto-scrolling. To do this we bind an event handler to the windows onScroll event when the page has loaded, like so:
$(window).bind('scroll', {scroller: this}, this.scrollHandler);
This adds the Scroller's scrollHandler method as a listener and passes a reference to the current Scroller in to the event's data. Which we can use in the scrollHandler method, like so:
Scroller.prototype.scrollHandler = function(event){ var $this = event.data.scroller; //Are we at (or as near) the bottom? if( $(window).scrollTop() + $(window).height() > $(document).height() - $this.options.pixelBuffer ) { $this.currentPage++; $this.loadData(); } };
Fairly simple, no? It just tests to see if you're at the bottom of the page. Using the "pixelBuffer" option you can make it so they don't need to be right at the bottom, if, for reason, scrolling needs to happen before they get to the bottom. You could even pre-empt them getting there by loading when they're getting close to the bottom and they'd never need to wait.
What About the Backend?
Back on the server things are actually quite simple. Perhaps you thought that wasn't the case?
To get it to work the first thing I did was extend the DocumentCollection wrapper class I talked about a few weeks back by adding a getNth() method to it.
Now, all my Agent needs to do is this:
Dim factory As New InvoiceFactory Dim invoices As InvoiceCollection Dim invoice As Invoice Dim start As Long, count As Integer, i As Integer Dim View As String start = CLng(web.query("start", "1")) count = CInt(web.query("count", "40")) view = web.query("view", "InvoicesByTitle") Set invoices = factory.GetAllInvoices(view) 'Invoice to start at Set invoice = invoices.getNth(start) Print "content-type: " + JSON_CONTENT_TYPE Print "[" While Not invoice Is Nothing And i<count Print invoice.AsJSON + |,| i = i + 1 Set invoice = invoices.getNext() Wend Print |null]| 'NASTY, NASTY hack to avoid dropping last , from array. Yack!
Note that the "web" object referred to above is based on the WebSession class which has been a mainstay of my Domino development for the past 5 or 6 years.
The more I use these new "wrapper classes" in LotusScript the more I kick myself for not having thought of them earlier on in my career. Although I am now using them on a daily basis and they're saving me time I keep thinking of how much time they could have saved over the years.
Having said that, just last week, for the first time in ages, I pressed Ctrl+N in Domino Designer and started a new Domino project from scratch (who says Notes is dead!?). The wrapper classes are an integral part of this new project.
Talking of the wrapper classes. I'll be updating and talking about them shortly. I've made significant changes to them and I think you'll like.
Wouldn't it be better to check if invoice is not nothing after the getNext() and then print the comma? Avoids the "hack".
Reply
Love it! So simple. Can't believe I never thought of that.
Reply
Nice one. I see you use your own little (JSP inspired) client side tagging system: %=Document.Created.Formatted%. While that's impressive, you might consider to switch to {{Mustache}} or fill.js (I like the later, since it allows for "repeatable fills") since they have broad adoption (and other people to debug them)
Reply
Funny you should mention mustache as I'd seen it somewhere before but never bother to look in to it. Then yesterday I saw it referred to it Bootstrap v3 (beta) documentation. Then an hour later you mention it. Must be a sign from above or something. I'll check it out.
Ok. I just did. Very quickly. First thing I noticed is that the JS file is ~600 lines long. That puts me off. The templating code I used in the above demo is ~20 lines long and does what I needed it to.
If mustache comes built in to Bootstrap 3 I'd use it but I'd be reluctant to add the extra weight if not.
Reply
Just a comment in general Jake.
You have some awesome code here on you site.
This is just another example.
Thanks for the really good code!
Steven
Reply
Hi Steven,
Thank you! It's comments like yours that keep me going.
Jake
Reply
Credit to where credit is due ;)
Your site is one of my favorite Domino resources on the web.
And I agree with you on the xpages issue.
I think its very good, but where am I going to use it is still to be seen.
Another thing that gets me is that when you learn new stuff then it becomes obsolete or it is replaced with something else.
e.g., Learningspace, Domino.doc.... to name but 2....
Steven
Reply
Learningspace! Ha, another sign from above. Just yesterday I wondered, whatever the name of that proprietary eLearning tool might have been, that never made it.
Ask codestore!
Reply