I currently have endless paging setup like so:
events_controller.rb
class EventsController < ApplicationController
respond_to :html, :js
def index
#events = Event.page(params[:page]).per(5)
respond_with(#events)
end
end
events/index.html.erb
<div id="events-container">
<%= render :partial => 'events', :locals => {events: #events} %>
</div>
events/events.html.erb
<div id="events-table-container" data-events-url="<%= current_url %>">
<table id="events-tbl">
<tbody id="events-tbl-body">
<%= render events %>
</tbody>
</table>
<%= paginate events %>
</div>
assets/javascripts/events/events_endless_paging.js
$(function() {
var isScrolledIntoView;
isScrolledIntoView = function(elem) {
var docViewBottom, docViewTop, elemBottom, elemTop;
docViewTop = $(window).scrollTop();
docViewBottom = docViewTop + $(window).height();
elemTop = $(elem).offset().top;
elemBottom = elemTop + $(elem).height();
return (elemTop >= docViewTop) && (elemTop <= docViewBottom);
};
if ($('#events-container .pagination').length) {
$(window).scroll(function() {
var url;
url = $('#events-container .pagination .next a').attr('href');
if (url && isScrolledIntoView('#events-container .pagination')) {
$('#events-container .pagination').html("<span class='working-notice'>Fetching more...</span>")
return $.getScript(url);
}
});
return $(window).scroll();
}
});
events/index.js.erb
$('#events-tbl-body').append('<%= j render(#events) %>');
<% if (#events.current_page < #events.num_pages) %>
$('.pagination').replaceWith('<%= j paginate(#events) %>');
<% else %>
$('.pagination').remove();
<% end %>
This works like a charm and all. This issue becomes when I try to integrate setTimeout ajax polling to refresh the events page.
assets/javascripts/events/event_poller.js
$(document).on('ready', function() {
App.Pollers.Event.poll();
});
App.Pollers.Event = {
frequency: 170000,
poll: function() {
setTimeout(App.Pollers.Event.request, App.Pollers.Event.frequency);
},
request: function() {
eventsRequest = $.getScript($('#events-table-container').data('events-url'));
return eventsRequest;
},
};
Removing the endless paging code above, here's what the code in the events/index.js.erb would look like for just the refresh behavior to work properly:
events/index.js.erb
$('#events-container').html('<%= j(render :partial => 'events', :locals => {events: #events}, :formats => :html) %>');
App.Pollers.Event.poll();
My challenge is getting the endless paging code and the ajax refresh code working together. If I use the ajax refresh with the endless paging code, then what ends up happening is that duplicate events are appended to the events-tbl-body element. Another issue is let's say a user scrolls down the page and the endless paging appends page 2 results to page 1 results, then how does the ajax refresh code know how to display both pages 1 and 2? These are just a few of the challenges. Hoping that someone can provide guidance. I know this is a verbose question, so appreciate your attention.
I'm not entirely sure about the term 'endless paging', isn't there a limit to the amount of content, even though you don't want to show all content immediately?
This might work for you:
https://github.com/paulirish/infinite-scroll
I would use this, for scrolling and getting more results, which is tried and tested:
I see there being two different uses for the poller, correct me if I'm wrong:
Update existing, already on-page events with updates made to those events (including removal).
Add in new events.
I would suggest, strongly, that you split your poller into two different pollers each responsible for a single actions.
The first one would take all the event ids for those events currently on the page and check for updates to them. Deleted events would be removed from the page; updated events would be updated in place.
The second one would look for events created after a 'last-time-you-polled' timestamp and insert those at the top of the page.
I'm not sure how the second poller would affect the pagination of your events for the endless page, but I think that challenge is solvable.
My suggestion would be to create a shared AJAX method that checks how old the content is and then refreshes a particular piece of content (say an article, orderable item, etc.) and this piece of code is invoked either by the pagination code OR the automated timed poll.
Scenarios as examples
User loads page(1/4) and it contains 4 pieces. The user doesn't scroll and you have 4 pieces to be updated at 170000 ms. Your updated timer would loop through those 4 items and call the independent AJAX updater for those 4.
User loads page(1/4) and then scrolls down to page (2/4). Your pagination code renders out the new 4 pieces. The user keeps the page in place and so after 170000 ms you need to refresh those 4. Just like #1 above the timer call loops through items 5-8 (page 2's content items) and calls the independent updater for those 4.
User loads page(1/4) and then scrolls down to page (4/4). Your pagination code renders out all the pieces (16 sections out of 16). The user keeps the page in place and so after 170000 ms you need to refresh those 4. Just like #1 & 2 above the timer call loops through items 20-24 and calls the independent updated for those 4.
User loads page(1/4) and then scrolls down to page (2/4) and leaves it. Just like #2 above the 4 items are updated by the timer. The user then scrolls back up to page(1/4) and so the pagination/scroll code calls the independent function for (1-4) through a small (250ms) timer which checks to see if the content needs to be refreshed and does so.
User loads page(1/4) and then scrolls down to page (4/4) and leaves it. Just like #4 the content refreshes on the last page and then they scroll up. As the scroll event occurs it triggers the ajax updater through a small (250ms) timer for the already existing items which checks to see if they need to be refreshed and if so does the update.
Considerations
It allows you to break the "does this need to update" logic into a shared function that accommodates the behavior of both the pagination and the user leaving the page still.
It allows you to ensure that you're only updating the content that is displayed and t his logic isn't duplicated in 2 sets of functions so your logic can be updated at a later date with minimal impact.
Having a small delay of this function when called by the pagination code when "scrolling" back up allows for the event where a user scrolls past an element so fast that it isn't worth doing the call and using the server resources to refresh content that isn't on the screen.
Related
I'm trying to make Greasemonkey script that clicks on a button at intervals and also clicks on it at the first load without wait.
I made this code which clicks every 120 seconds but the first time the page loads I have to wait 120 seconds for the code to click the GO button or I have to manually do it. :
window.setTimeout(function(){document.getElementById("masterPage_cphPageBody_btnGo").click()},120000)
and here is the source code related to it from the website
<input id="masterPage_cphPageBody_btnGo" name="masterPage$cphPageBody$btnGo" value="Go" onclick="SetSearchDate();showWaitpage();" class="btn2" type="submit">
How can I add a line to click on the GO the first time the page loads and 120 seconds there after?
the webpage before and after pressing the GO button has the same exact URL so adding the click on GO button without wait time will send hte page into a loop of
URL loaded GO clicked
URL Loaded GO clicked
URL Loaded GO clicked
...
checked the differences of the page before and after the first time GO button is clicked...
so I need the script to click Go button immidiately only if the following page does NOT show on the page
<span id="masterPage_cphPageBody_lblSelectAvailableTime" class="bodyBold">Select an available time</span>
but if "Select an available time" text show on page the script should wait 120 secon
I'm running: Greasemonkey, Windows 8 64, Firefox
Use a named function instead on an anonymous one (Often a good idea anyway).
Then use document.querySelector to check if that element is present. (That function has much more flexibility than getElementById.)
So your code becomes:
if (null === document.querySelector ("#masterPage_cphPageBody_lblSelectAvailableTime") ) {
clickGoButton (); //-- Initial, immediate click.
}
window.setTimeout (clickGoButton, 120000);
function clickGoButton () {
document.getElementById ("masterPage_cphPageBody_btnGo").click ();
}
Considering the post and pre Go button URL are the same to avoid the boot loop I found a textual difference between post and pre click and used it to avoid the loop
here is the code that worked for me at last
if (/Select an available time/i.test (document.body.innerHTML) )
{
window.setTimeout (clickGoButton, 120000);
}
else {
clickGoButton ();
}
function clickGoButton () {
document.getElementById ("masterPage_cphPageBody_btnGo").click ();
}
I'm not using a Grid, just using the MvcContrib Pager. I have a partial view created for the Pager (so I can display it at top and bottom of the results easily), and it calls the #Html. Pager method as so:
#Html.Pager(Model.PagedPrograms).First("<<").Last(">>").Next(">").Previous("<").Format("Item {0} - {1} of {2} ")
This works without additional tweaking as long as all parameters are passed to the page via QueryString, since Pager knows to rebuild those back on the URLs.
I'd like to give the user the option to change the page size (say 20, 50, All) ... I can easily handle that on the controller end, and I could write something like
#if (Model is Foo) {
#Html.ActionLink<SearchController>(sc => sc.Foo(var1, var2, var3, 20), "20")
#Html.ActionLink<SearchController>(sc => sc.Foo(var1, var2, var3, 50), "50");
#Html.ActionLink<SearchController>(sc => sc.Foo(var1, var2, var3, -1), "All");
}
But I would have to do that for each Model type that might use this Pager... I might be overthinking this or coming at this completely backwards, but I thought I'd ask and see if anyone had insight.
Currently the Pager is only called from a view which takes IPagedProgramList (provides IPagination<ProgramDTO> { get; }), and I have two ViewModels implementing that interface (a simple search and an advanced search). But if this project grows and we add new ViewModels that use that Interface I would have to update the Pager partial view, and that seems bad / doesn't scale / etc.
So a nod to Ek0nomik who got me thinking outside the box on this one.
Step 1: Make sure all pages that are going to use the Pager controller are passing all parameters via GET not POST. Use RedirectToAction if you must accept post somewhere and just translate all the parameters into primitive types for the GET method.
Step 2: Don't worry about adding .Link() to the Pager. As long as everything's coming in via GET, you're fine. It will look at the URL for the page and adjust the page number parameter as it needs to when you're going forward/back.
Step 3 (optional but recommended): For consistency across your application, somewhere (probably your Global.ascx.cs file) you should define a list of the page sizes you will support. In my case I used Dictionary<int,string> so that I could pass -1 as the PageSize value but display All (and of course the data layer must know that -1 means disable paging).
Step 4: Add something like this to your pager partial view:
<ul class="pageSizeSelector">
#foreach (KeyValuePair<int,string> kvp in MvcApplication.AVAIL_PAGE_SIZES)
{
<li>#kvp.Value</li>
}
</ul>
Step 5: The javascript function changePageSize is so simple, I couldn't believe I hadn't thought of this first (note: no jQuery required... just in case you're not already using it, you don't need to just for this).
function changePageSize(size) {
var PSpattern = /PageSize=\d+/i;
var url = window.location.href;
url = url.replace(PSpattern, "PageSize=" + size);
window.location.href = url;
}
Step 6 (optional, unless you're an Internet Troll): Profit!
I have view books/browse which uses 3 elements (those elements don't use any requestAction, they just depend on variables set in books/browse controller. Each element is enclosed in separate div with id of this element. Now I want to have a link which will refresh with ajax 2 of 3 elements. For now I have something like this (this is in books/browse view):
echo $this->Js->link('Page 2', array('page' => 2), array('update' => '#element_id'));
and in books/browse controller I have:
...
$this->set('books', $books);
$this->set('author_keywords',$author_keywords);
$this->set('title_keywords',$title_keywords);
$this->set('languages',$languages);
$this->set('language_id',$language_id);
$this->set('limit',$limit);
$this->set('category_id',$category_id);
if ($this->RequestHandler->isAjax())
{
$this->autoRender = false;
$this->layout = 'ajax';
$this->render('/elements/element1');
}
return;
And this works fine, the element in div 'element_id' is refreshed with proper contents of elements/element1
Now I want to update 2 element in separate divs in a single request. So maybe is there option to pass to link 2 divs which should be updated - but how then use the controller to render two elements in those 2 divs?
--edit
the purpose of this is to ommit refreshing one element on the page as its database queries and rendering are slow, and it doesnt change...
-- edit
Okay I have an idea on how to do it - in controller I have to render two elements and save html returned by those elements in two variables, then return ajax data like some array with 2 variables containing those elements html. Then I have to use JS to update two divs... But how to do it in controller?
I have a grid that is locally initialized from an array data. after adding the rows to it I want to be able to get the event of the scrollbar when it reaches to the end and load more rows locally.
(the grid has certain height and 'overflow-y' : scroll)
How can this be done?
Thanks In Advance.
You can see the code for loading more data at line 1831 of grid.base.js:
case "local":
case "clientside":
beginReq();
ts.p.datatype = "local";
var req = addLocalData();
addJSONData(req,ts.grid.bDiv,rcnt,npage>1,adjust);
$(ts).triggerHandler("jqGridLoadComplete", [req]);
if(lc) { lc.call(ts,req); }
$(ts).triggerHandler("jqGridAfterLoadComplete", [req]);
if (pvis) { ts.grid.populateVisible(); }
endReq();
The good news is that the loadComplete event is called, so you can put your code there. The only complication is that loadComplete may also be called when the grid is initially constructed, so you have to take that into account as well.
I cannot find an answer on the following issue. I have managed to reload a specific part of my page (page A) by using AJAX (through document.getElementById). I would like to know if there is a way to choose which part of the document (page B) will be used to reload the content of page A. In other words, pick a specific DIV from a 2nd page (same domain) and use it to refresh the contents of my page. I have seen in other threads that it is not possible to do it with pages that aren't from the same domain. But in my case I will use a page from the same domain. Any ideas?
Thanks.
If you have page A loaded and your scripts run there, so this will do:
var ifr = document.createElement('iframe');
ifr.src = 'page B URL';
ifr.style.position = 'absolute';
ifr.style.left = '-1000px';
ifr.onload = function() {
document.getElementById('page A div id').innerHTML =
ifr.contentDocument.getElementById('page B div id');
}
document.getElementsByTagName('body')[0].appendChild(ifr);
Note: page B loads and runs completely. If you need the only div to be passed through the Internet connection, you need to implement server side logic.