Reloading: Just reload a single Item of a Table/Grid/List etc


#1

Hi there,

I am up to one task which might be interesting for some others:
I am programming a data administration which makes quite extensive use of reloads to keep loading times as short as possible.
In nearly every page, I have a list of items, when clicking on one of the items a form is opened to edit the item.
When the form is saved, the list has to be reloaded of course to display the changed data.

At the moment, I reload the complete list.
As the lists will grow big, the reload will become very slow. With 250 test entries loading data from 3 tables I can already feel a bit of lag.

Ok, reloading a single item of a list is the thing I want to implement, and I want to do it the ATK way as much as possible so others can use it as well.

Please tell me if my understanding of the built in reloader is correct:

  • As far as I understand, the built-in reloader relies on a view object that is passed.
  • The reloader uses the unique view id (#parentview_parentview_view) to identify the HTML content to reload
  • The server side part of the callback just re-renders the view object and sends it back to the browser.

So theres some solutions I can think of:

  1. Create a view object for each list item, and use jsReload on this object. This wouldn’t require any additional implementations, but creating an object for each item seems very performance-unfriendly. (but I am anything but an expert on performance, “hardly any idea” is more correct)

  2. Implement a custom reload inside the view that creates the list. This would mean implementing an extra server-side callback I suppose, and of course some additional JS.

ATK experts, whats your opinion? Which direction should I start?

Best regards
Phliipp


#2

You might need to work on fine-tuning your list actually. In ATK we only load one page of data at a time, which relies on “limit” rather then pulling the full list and trying to preserve it through edits and pagination (as done in many Rest apps). I’ve got applications in ATK that compile tables from 5+ pages (joins) and having sub-selects and it works very quickly to load. Here is how you can debug it:

echo $table->model->action('select')->getDebugQuery(true);

after you get the query - put EXPLAIN in front of it and feed into SQL. Make sure that the query is efficient. Add some indices, it may help.

Actually - if your table and the form combination is consistent, you do not even need to reload! You just ned to get HTML for a single row and then use something like this:

$('#table').children('tbody').children('[data-id="+id+"]').html(new_html);

The only difficulty here is to get the formatted table row, and thanks to the refactoring we did just brief moment ago, you can do it:

Normally $table->model does not have to be loaded, but for single row, you would need to load it (which will probably happen anyway by the form), at which point you would need to call renderRow(). What you need back is the contents of the $table->t_row template, so the final code might be like this:

$form->setModel($table->model->load($_GET['id']));
$form->onSubmit(function($f) use($table) {
  $form->model->save();
  $table->renderRow(); // they use same model object
  $html = $table->t_row->render();

  return $table->js()->children('tbody')->children('[data-id="'.$table->model->id.'"]')->html($html)->effect('highlight');
});

It will take care of all the type-casting to automatically re-load model (which save() does) in case some expressions have changed in the database. You also avoid extra requests to the PHP or the database.

The other approach is to make a JS widget out of table, which could patch things up based on some JSON output, but I think it’s more complex this way.

This have been done. This is quite expensive, because you’ll now have as many objects as row, which is something we try to avoid at all costs for performance. The solution won’t scale well. Also ATK typically initializes all objects and even if you render one object selectively, this will make your callback slower.

That’s what I mentioned above. This is a similar approach to AutoComplete field - it executes call-back to get list of items and JS code can incorporate new results by replacing the old ones. You can use something like jqGrid on a frontend and provide a JSON feed from the backend. If you want to implement this, I suggest using some existing JS-grid add-on (which will come with it’s own pagination, sorting etc) and it is certainly good in some situations.

Looking forward to your solution!


#3

Wow, now thats an answer! Thanks a lot.

My solution is pretty simple:
I do not use the standard table but wrote a simple Lister class myself (in which I copied all the stuff I need from our table implementation :slight_smile: ). In this class, I added a function which renders a row with a given ID:

public function renderRow($tour_id) {
    ....
}

in the onSubmit of the form, I simply call this function to get the new content:

$guide_form->onSubmit(function($guide_form) use ($app, $tour, $lcl) {
	if($guide_form->fields['add_guide_to_tour']->getValue() !== null) {
		$gutt = new Pmg\GuideToTour($app->db);
		$gutt['guide_id'] = $guide_form->fields['add_guide_to_tour']->getValue();
		$gutt['tour_id'] = $tour->get('id');
		$gutt->save();
		
		//trigger reload for guide change
		$html = $lcl->renderRow($tour['id']);
		return ($lcl->js(true)->children('tbody')->children('[data-id="'.$tour['id'].'"]')->replaceWith($html));
		//return (new \atk4\ui\jQuery('.pmgreloadguidechange'))->trigger('pmgreloadguidechange');
	}
	else {
		//TODO: Should not be possible, deactivate Save button until choice is made in AutoComplete
	}
});

Simple and working!

Now theres only one thing, and I am a bit embarrassed to ask this as its surely a 1 line answer:
I want to trigger more than 1 JS event in the onSubmit: the current return and the old, commented return. I tried something like:

$guide_form->onSubmit(function($guide_form) use ($app, $tour, $lcl) {
	...
		//trigger reload for guide change
		$html = $lcl->renderRow($tour['id']);
		$lcl->js(true)->children('tbody')->children('[data-id="'.$tour['id'].'"]')->replaceWith($html);
		return (new \atk4\ui\jQuery('.pmgreloadguidechange'))->trigger('pmgreloadguidechange');
        ...
});

But this does not work.
Thanks again
Philipp

Btw: Do you ski Romans?


#4

1 line it is. :smiley:

return [$action1, $action2];

#5

Knew it, thanks a lot!
Wintersport question again: do you ski? :wink:

Cheers


#6

yep. rarely. no snow in UK!