PDA

View Full Version : Grid update component


dendenners
05-29-2007, 10:03 AM
Hi,
I'm new to extjs, but the idea looks fairly appealing. I'm looking for an AJAX based component that would allow me to present an asynchronously updated grid-based list to a user. Basically what I have is a continually updated database table that stores transactions - occasionally a transaction will trigger an alert (e.g. if it is for over a certain size, or if it occurs in a location that is famous for having incredible amounts of fraud). Once an alert is triggered, I'd like to show that particular transaction to an operator via a AJAX-enabled grid. The operator will have links on the grid allowing him/her to perform some action on the transaction. Thus, the operator will have a constantly up-to-date grid of the currently suspect transactions that need to be acted upon, all without needing to refresh their browser.

I think yahoo have something similar on their stock portfolio screen (although there the actual information for each grid row is altered, rather than the rows themselves - I don't think it makes too much difference though).

Is there component available in extjs that would allow me to accomplish this, and if so, what is it?

Thanks in advance
Denis Mc.

brian.moeskau
05-29-2007, 10:40 AM
If you are referring to a server-push mechanism (commonly referred to as Comet by some folks where the client maintains a constant open HTTP connection) there is currently nothing built into the library that would handle that. However, I think most people would simply implement this is a standard polling process where the page would send a tiny Ajax request every X seconds requesting the next available record(s). If a record is returned, you can handle that however you want based on your business requirements. The edit/save process would be standard operations that would work through the normal Ext form mechanisms. The tricky part will be the concurrency issues you will run into trying to manage this process across a team of operators, but that is more of a back-end issue and not related to Ext.

dendenners
05-29-2007, 11:30 AM
Brian,
A polling process is what I had in mind. I'm aware of the concurrency issue but I think I'll be able to get around it. Would there be some sample code in the extjs library that shows how to implement such a polling-based AJAX solution?
Thanks
Denis

tryanDLS
05-29-2007, 11:51 AM
There is nothing in the base Ext examples, but I'm pretty sure this has been discussed before and sample code posted by someone. Try searching in Help and/or Examples.

KimH
05-30-2007, 03:04 AM
Maybe try searching for DWR in the forums and check out this link (http://ajaxian.com/archives/reverse-ajax-with-dwr).

Animal
05-30-2007, 07:34 AM
Try the code below. It gives you a startAutoRefresh method on your Store objects which will update any Grid attached to them.

Brian, do you think it would be worth addnig this to the Ext source?


Ext.override(Ext.data.Store, {
startAutoRefresh : function(interval, params, callback, refreshNow){
if(refreshNow){
this.load({params:params, callback:callback});
}
if(this.autoRefreshProcId){
clearInterval(this.autoRefreshProcId);
}
this.autoRefreshProcId = setInterval(this.load.createDelegate(this, [{params:params, callback:callback}]), interval*1000);
}
});

brian.moeskau
05-30-2007, 10:02 AM
Looks good to me. I would think you'd also want:


stopAutoRefresh : function(){
if(this.autoRefreshProcId){
clearInterval(this.autoRefreshProcId);
delete this.autoRefreshProcId;
}
},

isAutoRefreshing : function(){
return this.autoRefreshProcId ? true : false;
}


We'll see what Jack thinks about adding it.

papasi
05-30-2007, 02:07 PM
looks very useful

i second this!

thesilentman
05-31-2007, 02:11 AM
Looks useful, can we have it, Jack?

jack.slocum
05-31-2007, 06:07 AM
This is something TaskMgr is perfect for. Aaron Conran wrote a nice overview blog post (http://www.divergingpath.com/index.cfm/2007/5/25/Ext-ExtTaskMgr--Scheduling-Javascript-Tasks-to-Run-on-an-Interval).

Something like:

Ext.TaskMgr.start({
run: ds.reload,
scope:ds,
interval:2000
});

You can also set a 'repeat' to limit the number of refreshes.

If you want to bind args and use load:

Ext.TaskMgr.start({
run: ds.load,
scope:ds,
interval:2000,
args:[{params:params, callback:callback}]
});

To stop a running task:

var task = Ext.TaskMgr.start({
run: ds.reload,
scope:ds,
interval:2000
});
...
Ext.TaskMgr.stop(task);

or

var task = {
run: ds.reload,
scope:ds,
interval:2000
};

Ext.TaskMgr.start(task);
...
Ext.TaskMgr.stop(task);

thesilentman
05-31-2007, 06:41 AM
Jack......

..
..
..
..
..
I am stunned!!!

Your work is so usable, extendable, fantastic.... .... ...
I hope to make some more bucks using it, so I can maybe get the gold subscription some time. Your work is absolutely genius. =D>=D>=D>

Thank you very much,

Frank

rberger
07-30-2007, 05:16 PM
Let me start off and like others say how great it is that the EXT.js library exists and is so rapidly evolving!

My only wish is that there be more examples that show the whole context of how to get things to work. Many times a code snippet doesn't help too much as its not clear what needs to be done before hand to create the environment for that snippet to work.

So here is my humble contribution, as a beginning ext.js user (and javascript noobie), of how I solved the problem of having a grid that gets automatically updated with server data at a periodic rate. Elements of this have been discussed in several forum posts but it took me a while to figure out how to put all the pieces together.

This example has the data sent from the Rails server to the Grid via JSON. I use the TaskMgr pattern that Jack described in an earlier post to this thread. Its not clear to me why the TaskMgr is undocumented though. It seems like a powerful and necessary feature!

I also tried using the startAutoRefresh that Animal described. It looks like that feature has been put into the Ext.data.Store in 1.1rc1 as it worked without a hitch just by using it without me extending Ext.data.Store myself.

I.E. I could use it just by saying:
legs_ds.startAutoRefresh(4,{url: zipdx_leg_url, start:0, limit:20});

instead of:
Ext.TaskMgr.start({
run: legs_ds.reload,
scope: legs_ds,
interval:4000,
args:[{params:{start:0, limit:20}, add:false}]
});

Its not clear if there is any advantage of one over the other. But neither are documented in the API docs.

One thing I couldn't get to work is filterBy. I think I don't understand where to call it. You can see in the following code, I call it after legs_ds has all been set up and loaded the first time and before starting the TaskMgr. It is only called once though.

So I would appreciate any general feedback on how to do auto grid updates better, and how to properly use the filterBy so that the filter is applied after each auto reload.
Thanks!


var legs_grid;
var legs_ds;

Ext.onReady(function(){
init_legs_grid();
});

function init_legs_grid() {

// Set up the data store so it gets its data from the
// server whose url is in the var zipdx_leg_url
legs_ds = new Ext.data.Store(
{
proxy: new Ext.data.HttpProxy({url: zipdx_leg_url}),
reader: new Ext.data.JsonReader(
{
root: 'Legs',
id: 'leg_uid'
},
[
{name: 'name'},
{name: 'hand_raised'},
{name: 'last_talked'},
{name: 'hard_mute'},
{name: 'soft_mute'},
{name: 'vote_value'},
{name: 'exit_time'},
{name: 'conf_room'}
]
),
}
);

// Set up the columnModel, nothing special here
var legs_cm = new Ext.grid.ColumnModel(
[{
id: 'leg_uid',
header: "Name",
dataIndex: 'name',
width: 150
},
{
header: "Hand",
dataIndex: 'hand_raised',
width: 50
},
{
header: "Talked",
dataIndex: 'last_talked',
width: 50,
},
{
header: "Mute",
dataIndex: 'soft_mute',
width: 50
},
{
header: "Vote",
dataIndex: 'vote_value',
width: 50,
},
{
header: "Conf",
dataIndex: 'conf_room',
width: 50
}]
);

legs_cm.defaultSortable = true;

// Create the grid using the previously defined data store and columnModel
legs_grid = new Ext.grid.Grid(
'legs_grid',
{
ds: legs_ds,
cm: legs_cm,
stripeRows: false
}
);

// Render the grid
legs_grid.render();

// Load the data for the first time. I guess I really don't need to do
// this since it will be loaded by the TaskMgr.
legs_ds.load({params:{start:0, limit:20}});

// Should not display rows where the exit_time column is > 0
// But it doesn't do it. Its only called once
// Does it need to go somewhere else or am I using
// the filterBy incorrectly?
//
// I got arround this in my app by just never sending the record
// from the server if exit_time > 0
legs_ds.filterBy(function(record){
if (record.get('exit_time') > 0) {
return false
}
else {
return true
}
});

// This will call legs_ds.reload every 4 seconds
// in the form legs_ds.reload({params:{start:0, limit:20}, add:false})
// I'm not sure if I really needed the add:false
Ext.TaskMgr.start({
run: legs_ds.reload,
scope: legs_ds,
interval:4000,
args:[{params:{start:0, limit:20}, add:false}]
});

// The following would do the equivilant as the above TaskMgr block:
// legs_ds.startAutoRefresh(4,{url: zipdx_leg_url, start:0, limit:20});
// I don't know if its exactly the same or which is prefered.
};


Here's the Rails Controller code that is relevant:

class LegsController < ApplicationController
# Default index function, it will auto-magically (by Rails) render
# the /app/views/legs/index.rhtml view that creates the original
# page with appropriate DOM elements for the grid
def index
end

# This is the function that gets called in response to the
# Ajax request from the legs_ds.load or legs_ds.reload from the client
def grid_data
@legs = Leg.find_all_by_conference_id params[:conference_id]

return_data = Hash.new()

# Walk thru all the @legs records and assemble an array of
# hashes for each leg. The hash contains the key values that map to the name values
# for the data store on the client side
return_data[:Legs] = @legs.collect do |u|
# This is so as to not send any records who have already exited
next if u.exit_time > 0

# Build the hash for he record
h = {}
u.attributes.each do |k, v|
h[k] = v
end
end

# Remove any nil elements in the array created by skipping exit_time > 0 records
# Took me a day to figure out that having an extra nil / null in the json record
# was causing the record to keep showing up in the grid!
return_data[:Legs].compact!

# This generates the response to the ajax request from the
# load/reload from the client
render :text => return_data.to_json, :layout => false
end
end


And here's the Rails View for the grid:

<!-- The update_page_tag is using RJS to send a raw javascript line to the
client to initialize the var that contains the path for the Ajax call that the
legs_ds.load / legs_ds.reload proxy uses
-->

<%= update_page_tag { |page| page << "var zipdx_leg_url = \"#{grid_data_legs_path}\";"} %>

<!--
This just includes the code shown above that implements my grid and data store, etc.
-->
<%= javascript_include_tag "legs_grid.js" %>

<!--
And this creates the DOM element for the grid
-->
<div id="legs_grid" style="border:1px solid #99bbe8; overflow:hidden; width:400px;"></div>


And for those curious how to do RESTful routes used to generate the named route "grid_data_legs_path" my entry in /config/routes.rb is:

ActionController::Routing::Routes.draw do |map|
map.resources :conferences do |conference|
# The :legs entry makes legs a nested route to conference
# /conferences/:conference_id/legs
conference.resources :legs, :collection => {:grid_data => :post}
end

# Rest of your routes.rb table would be here
end

mystix
07-30-2007, 08:30 PM
would you like to put this in the wiki? :)

it'd be a good addition to the tutorials section

rberger
07-30-2007, 09:11 PM
I would be glad to put in the wiki. I was hoping someone more experienced with ext would let me know if there are things that I am doing that are not best practices or whatever.

I guess that can be done on the wiki too though..

I will do that shortly then!

Rob

Plater
10-24-2007, 07:17 AM
The Ext.TaskMgr.start works perfect for interval updating my grid.

There is one problem though. I have a Pagingtoolbar as well, and the page number is not being re-used. For example if i am browsing page 4 of 6, and the grid is reloaded i am returned to page 1. How can i fix this?

Thanx in advance for your help.

Greetings, Plater

Animal
10-24-2007, 03:27 PM
Are you actually using


Ext.TaskMgr.start({
run: legs_ds.reload,
scope: legs_ds,
interval:4000,
args:[{params:{start:0, limit:20}, add:false}]
});


?

Plater
10-25-2007, 02:53 AM
Hi, thanx for your help. Much appreciated.

Yes i am using the TaskMgr.

Ext.TaskMgr.start({
run: ds.reload,
scope: ds,
interval:4000,
args:[{params:{start:0, limit:list_count}, add:false}]
});
(list_count is defined in another part of my application)

It looks like the problem is the "start:0" param.
Somehow the script needs to be told what the current page is and use that start position, in stead of the 0.

Is there a difference between "run: ds.load" and "run: ds.reload"?

Plater
10-30-2007, 03:46 AM
Is there any way i can dynamicly access the "start" and "limit" parameter of PagingToolbar?
That would solve my problem.

Animal
10-30-2007, 04:18 AM
Use ds.lastOptions.params.start

lastOptions is useful. I;m going to document it as a public property of Store.

Plater
10-31-2007, 04:10 AM
Use ds.lastOptions.params.start

lastOptions is useful. I;m going to document it as a public property of Store.

Hi Animal, Thanx for your reply.

I have tried to use your solutions in my code but the lastOptions parameter of the ds object seems to return NULL. I have also tried to alert it, with no success.

I am using ext-2.0-beta1. Are we talking about the same version?

Any help much apreciated. Thanx in advance.

This is my code:
var refresher = {
run: ds.load,
scope: ds,
interval:4000,
args:[{params:{start:ds.lastOptions.params.start, limit:game_count}, add:false}]
};
Ext.TaskMgr.start(refresher);

Plater
11-05-2007, 08:42 AM
Am i doing something wrong or is this functionality still in beta version?

Animal
11-05-2007, 08:48 AM
Hi Animal, Thanx for your reply.

I have tried to use your solutions in my code but the lastOptions parameter of the ds object seems to return NULL. I have also tried to alert it, with no success.

I am using ext-2.0-beta1. Are we talking about the same version?

Any help much apreciated. Thanx in advance.

This is my code:
var refresher = {
run: ds.load,
scope: ds,
interval:4000,
args:[{params:{start:ds.lastOptions.params.start, limit:game_count}, add:false}]
};
Ext.TaskMgr.start(refresher);

Obviously, it won't be set first time through. You will need to use

var refresher = {
run: ds.load,
scope: ds,
interval:4000,
args:[{params:{start:ds.lastOptions ? ds.lastOptions.params.start : 0, limit:game_count}, add:false}]
};
Ext.TaskMgr.start(refresher);

Plater
11-05-2007, 11:38 AM
Thanx for your code again, but...
Still not working :(
I am still being redirected to the first page every refresh.

Is there no-one who uses the PagingToolbar with a Reload TaskMgr??
This must have been tried before.

Thank you in advance

Plater
11-07-2007, 05:16 AM
Do I need to change anything in the ds object to activate the lastOptions?

Animal
11-07-2007, 08:08 AM
The refresh using lastOptions should refresh whatever page you are on. If you have moved to page 2, it should refresh page 2.

Because lastOptions.params.start should be set to whatever the page was requested with.

Have yoiu debugged at all. I mean stopping in the code using a debugger, examining variables and fixing things. Or are you just looking at it?

rodiniz
11-07-2007, 09:51 AM
Just to answer a question...the reload method reloads the store using the last options, unless you have to send an extra parameter use the reload method

Animal
11-07-2007, 10:49 AM
Yes, but the suggested code uses Store::load

Plater
11-07-2007, 12:13 PM
Just to answer a question...the reload method reloads the store using the last options, unless you have to send an extra parameter use the reload method

That's what I expected but it does not seem to work.
I have tried to use the reload function as well, but it puts me back to the first page independent of the page I am on.
It seems like the "reload" method has the same functionality as the "load" method.

By the way: Isn't the currentPage something the PagingToolbar should handle?
It doesn't seem logical in data.Store (cause not every data.store has multiple pages)

Plater
11-07-2007, 12:17 PM
The refresh using lastOptions should refresh whatever page you are on. If you have moved to page 2, it should refresh page 2.

Because lastOptions.params.start should be set to whatever the page was requested with.

Have yoiu debugged at all. I mean stopping in the code using a debugger, examining variables and fixing things. Or are you just looking at it?

No I have not started debugging extjs files yet, i am only editing my own js where I am USING the extjs code. I have, however, dumped the objects to see their content, and lastOptions is not present in the ds object.

Plater
11-08-2007, 11:24 AM
OK, i have found a solution myself. This code seems to work for me :)

ds is my Ext.data.Store object
paging is my Ext.PagingToolbar object (added to the GridPanel with bbar)



function do_it()
{
ds.reload({args:[{params:{start:paging.getPageData().activePage, limit:game_count}, add:false}]});
}

var refresher = {
run: do_it,
scope: ds,
interval:4000
};
Ext.TaskMgr.start(refresher);


Thanks to anyone contributing to my topic :)

jumpo
01-22-2008, 03:07 AM
should the start point be (paging.getPageData().activePage - 1) * limit

Tomi21
06-03-2008, 08:39 AM
Is there a Ext.TaskMgr equivalent in Ext 1.x? If not, how can I create a task that will be executed periodically.

I need to make and AJAX call to a server in a periodic time basis.

Thanks form the help.

Tomi21
06-03-2008, 08:40 AM
Is there a Ext.TaskMgr equivalent in Ext 1.x? If not, how can I create a task that will be executed periodically.

I need to make and AJAX call to a server in a periodic time basis.

Thanks form the help.

mscdex
06-03-2008, 12:47 PM
I need to make and AJAX call to a server in a periodic time basis.

Have you tried using a plain old javascript timer?

Tomi21
06-04-2008, 02:47 AM
Do you mean 'window.setInterval()' function?