Change standard appearance of CRUD UI


#1

Hey there,

this is not a call for help (NOT YET :slight_smile: ) I want to track my progress and maybe make a small tutorial.
Please share any thoughts you might have, as there are usually many ways to do something, but only a few which are really good. Also, if there is some documentation which could fit my needs, please share.

So, I want to change the standard layout of CRUD into a not so wide list, and when clicking an item in the list the edit form is shown on the right side of the list. In the end, it should look a bit like this (please don’t look at the HTML/CSS code):
http://www.spame.de/tourmanagement/guests.html

When doing this I want to focus on:

  • Good coding on PHP side to keep everything updateable
  • Especially use HTML/CSS which is already provided
  • And keep the CSS (Semantic UI) updateable

So far, so good, will hopefully start this evening.
Best regards
Philipp


#2

You don’t need CRUD for this. You can link up table with a Grid. There were some example on how to do this in demos, at least something similar. I’ll check to when I’m back near laptop.


#3

Hey Philipp, here is the demo I was mentioning:

http://ui.agiletoolkit.org/demos/loader2.php

and the source:

It has been quite a problem in the past where users try to “degrade” CRUD into a more simple component rather than learning about Grid, Table and Lister.

Unlike other CRUD solutions you might find, the one in Agile UI is not a Swiss-army knife, it is a simple extension on top of Grid.

You can also look at http://ui.agiletoolkit.org/demos/grid.php just to see how many features there are in Grid already, you can pretty-much put your CRUD together yourself just the way you like.


#4

Hi Romans,

thanks so much for your help! This is exactly the functionality I was looking for, works like a charm. Now I only have to style it and decide where to put the “New XXXXX” button :slight_smile:

Best regards
Philipp


#5

Ok, time to see progress. Basic functionality is done, I have a list on the left, when I click an Item a form to edit is opened on the right:

Done with the following code:
<?php

require 'lib.php';
$app = new TourManagement();

$c = $app->add('Columns');

$grid = $c->addColumn()->add(['Grid', 'menu' => false]);
$grid->paginator = false;
$grid->table->header = false;
$grid->setModel(new TourType($app->db), ['name']);


$tourtype_loader = $c->addColumn()->add(['Loader', 'loadEvent' => false, 'shim' => ['Text', 'Select a Tour Type to edit']]);

$grid->table->onRowClick($tourtype_loader->jsLoad(['id' => $grid->table->jsRow()->data('id')]));

$tourtype_loader->set(function ($p) use ($app) {
    $p->add('Form')->setModel(new TourType($app->db))->load($_GET['id']);
});

Some tasks are still ahead:

  1. Get rid of the paginator. Its set to false, still the paginator is shown with a … sign.
  2. On Form submission, a Message “Form Data has been saved” is shown. I want to keep the Form instead of the message.
  3. On Form submission, the left column is not updated yet -> needs to
  4. A Nice-to-have would be to be able to access each item via URL, e.g. http://localhost/TourManagement/tour_types.php?id=3. Also to update the URL in the browser as soon as an item is clicked would be great.
  5. Have a Delete button inside the form.

Ok, time to figure how to get this functionality, styling comes later…

Best regards
Philipp


#6
  1. Paginator is initialized during initialization of the Grid, so either you inject it just like 'menu'=>false or let Grid’s initializer construct paginator and then destroy it with $grid->paginator->destroy().
  2. Form allows you to specify onSubmit($fx) handler. Examples are in the doc. If it’s unspecified, then the succes() message is shown, if you do specify it, you can emit any action.
  3. Create reload action for the left column $left_reload = new jsReload($left), then pass it inside submit handler and return it, so form will automatically execute it.
  4. There are no standard means of location manipulating, you can probably write a bit of your own javascript for that. Alternatively you can use redirect and stickyGet(), although I’d recommend not to try it, you might loose some valuable time just poking around.
  5. form->addButton('Delete')->on('click', function(){ ... delete; return $left_reload; });

#7

Hi,

thanks again for the answer, saved me lots of searching.
1 and 2 are done, 4 is postproned… but I am stuck on 3 and 5. These "dynamic functions really need their time to get into my head :slight_smile: After reading alot of code in the last 2 hours my brain is done!

Current code first:

//Create Column Layout
$c = $app->add('Columns');
//Left column
$leftcolumngrid = $c->addColumn(6)->add(['Grid', 'menu' => false, 'paginator' => false]);
//right column
$rightcolumn_loader = $c->addColumn(10)->add(['Loader', 'loadEvent' => false, 'shim' => ['Text', 'Select a Tour Type to edit']]);


//Left column content
$leftcolumngrid->table->header = false;
$leftcolumngrid->setModel(new TourType($app->db), ['name']);
//$left_reload = $app->add(['jsReload', $leftcolumngrid]);
$left_reload = new \atk4\ui\jsReload($leftcolumngrid);

//Add JS action when clicking on a row
$leftcolumngrid->table->onRowClick($rightcolumn_loader->jsLoad(['id' => $leftcolumngrid->table->jsRow()->data('id')]))
	->addClass('active')
    ->siblings()->removeClass('active');
    
    
//Right column content, dynamically loaded using set() function of Loader class
$rightcolumn_loader->set(function ($p) use ($app) {
    $f = $p->add('Form');
    $f->setModel(new TourType($app->db))->load($_GET['id']);
	//$f->addButton('Delete');
	$f->onSubmit(function($f) {
		$f->model->save();
        return $left_reload;
    });
});

Ok, here is the problem with 3 (reloading the left side list):
When submitting the form the data is saved ($f->model->save();), but a Message is displayed and the left side is not reloaded:

Seems I could get around this by changing PHP error reporting, but I still don’t get why $left_reload should be undefined at that point…

5 (adding Delete button to form) also causes trouble:
As soon as I uncomment the line //$f->addButton(‘Delete’); the following error is displayed:

Another question regarding the “dynamic functions” (how would you call it correctly?):
I copied the code from the loader2 example but changed
xxxxloader->set(function ($p) use ($db)
to
xxxxloader->set(function ($p) use ($app)
as I started from another example and have the DB connection stored in $app->db so far. Any problem?

Thanks again
Philipp


#8

here you need to add use keyword, otherwise $left_reload is undeclared. That’s a PHP thing, hence the error message.

Regarding the button, try $form->add(‘Button’); or $form->layout->addButton(). It looks like a method we can actually proxy through form, it’s defined for the FormLayout, there might be other methods which can also be good to proxy. Perhaps we can even make use of dynamicMethod trait.


#9

Hi there,

after reading a lot about anonymous functions and closures I start to understand this code really :upside_down_face: But my brain is done, my PHP programming knowledge was mainly 9 years old until 2 weeks ago…

Ok, so now I pass $app (for database connection) and $left_reload as args to be able to use them inside the functions. Complete code first:

<?php

//Load Main App
require 'lib.php';
$app = new TourManagement();

//Create Column Layout
$c = $app->add('Columns');
//Left column
$leftcolumngrid = $c->addColumn(6)->add(['Grid', 'menu' => false, 'paginator' => false]);
//right column
$rightcolumn_loader = $c->addColumn(10)->add(['Loader', 'loadEvent' => false, 'shim' => ['Text', 'Select a Tour Type to edit']]);


//Left column content
$leftcolumngrid->table->header = false;
$leftcolumngrid->setModel(new TourType($app->db), ['name']);

$left_reload = new \atk4\ui\jsReload($leftcolumngrid);
//Add JS action when clicking on a row
$leftcolumngrid->table->onRowClick($rightcolumn_loader->jsLoad(['id' => $leftcolumngrid->table->jsRow()->data('id')]))
	->addClass('active')
    ->siblings()->removeClass('active');
    
    
//Right column content
$rightcolumn_loader->set(function ($p) use ($left_reload, $app) {
    $f = $p->add('Form');
    $f->setModel(new TourType($app->db))->load($_GET['id']);
	$f->layout->addButton('Del');
	$f->onSubmit(function($f) use ($left_reload) {
		$f->model->save();
        return $left_reload;
    });
});

Well, it does not work, it changed :slightly_smiling_face: Now it produces this error:

I am really too tired to dig into it now, tomorrow evening is another day.

But: The Delete Button creation works now!

Best regards
Philipp


#10

take another look through, on line 29 you execute “load” passing ‘null’ argument. Compare with “inspector” to make sure that you pass object ID properly.

the code you posted previously seems OK, but if you git the “Delete” with a call-back, it will loose the “id” parameter unless you make it sticky. See more on ‘stickyGet’.


#11

Hi romans,

as long as I do NOT return $left_reload, it works. So this problem seems somehow associated with the jsReload… Will dig my way into Javascript debugging, another skill to learn for me :slight_smile:

I know the delete button does not have any functionality yet, will implement wenn the issue is gone.

Thanks again for your help!
Philipp


#12

Getting interesting… I managed to get everything working except the reload of the list when saving the form / deleting a record.
The stickyGet() worked perfectly, deleting and adding new records works…

What I did: I added a button to reload the left side list with the same jsReload that I submit, when clicking the button it works flawlessly.

Wanted to do a screencast of this but didn’t find an easy way yet, so just another screenshot:

And the complete code:

<?php
//Load Main App
require 'lib.php';
$app = new TourManagement();


//Create Column Layout
$c = $app->add('Columns');


//Left column content: Grid
$leftcolumngrid = $c->addColumn(6)->add(['Grid', 'menu' => false, 'paginator' => false]);
$leftcolumngrid->table->header = false;
$leftcolumngrid->setModel(new TourType($app->db), ['name']);
$left_reload = new \atk4\ui\jsReload($leftcolumngrid->table);


//right column content: Loader (in which Form is loaded) 
$rightcolumn_loader = $c->addColumn(10)->add(['Loader', 'loadEvent' => false, 'shim' => ['Text', 'Select a Tour Type to edit']]);
$rightcolumn_loader->set(function($p) use ($app, $left_reload) {
    $form = $p->add('Form');
    $form->setModel(new TourType($app->db));
    
    //Load by id and add delete button if ID is specified (not if add-button is clicked)
    if(null !== $app->stickyGet('id')) {
		$form->model->load($app->stickyGet('id'));
	
		$form->layout->addButton('Delete')->on('click', function() use ($form, $app, $left_reload) {
			$form->model->delete($app->stickyGet('id'));
			$app->stickyForget('id');
			return $left_reload;  
		});
	}
	//Todo: Change Text of submit button to "Create new Tour Type"
	else {
		
	}
	$form->onSubmit(function($form) use ($left_reload) {
		$form->model->save();
        return $left_reload;
    });
});


//Add JS action when clicking on a row on left side: Load content on right side
$leftcolumngrid->table->onRowClick($rightcolumn_loader->jsLoad(['id' => $leftcolumngrid->table->jsRow()->data('id')]))
	->addClass('active')
    ->siblings()->removeClass('active');


//Add creation of new Tour Type to layout
$app->layout->menu->addItem('Add New Tour Type')->on('click', $rightcolumn_loader->jsLoad());


//Test: Button for reloading right side list
$button = $leftcolumngrid->add(['Button', 'Reload List']);
$button->on('click', $left_reload);

So the next thing I will do is compare the HTML/JS of the button reload and the non-working form reload.
But: getting close to where I want to get :slight_smile:

Best regards
Philipp


#13

Did a simple test: I added the working “Reload List” button to the form as well ($form->layout->addButton('Reload List')->on('click', $left_reload);) to check if that same functionality works from within the form - it doesn’t… Left “Reload List” button works, the one on the right inside the Form does not:

Comparing the JS linked with the ‘click’ event shows a difference.
JS of working button:

function(event) {
  event.preventDefault();
  event.stopPropagation();
  $("#atk_admin_columns_view_grid_table").atkReloadView({
    "uri": "tour_types.php?atk_admin_columns_view_grid_table_callbacklater=callback",
    "uri_options": []
  });
}

JS of non-working button inside form:

    function(event) {
      event.preventDefault();
      event.stopPropagation();
      $("#atk_admin_columns_view_grid_table").atkReloadView({
        "uri": "tour_types.php?atk_admin_columns_view_2_loader_callback=callback\x26id=10\x26atk_admin_columns_view_grid_table_callbacklater=callback",
        "uri_options": []
      });
    }

I do not have sufficient understanding (YET!) of the callbacks, but perhaps one of you can easily see where the problem is?

Thanks & regards
Philipp


#14

A good work-around for using reloads is

  1. Define reload on js event: $left->addClass('leftbar')->on('myevent', new jsReload($left));
  2. Trigger it with return (new jQuery('.leftbar'))->trigger('myevent')

Something along those lines anyway. Works when everything else fails.


Some JS Callbacks not working inside Loader
#15

Hi,

I like the workaround :slight_smile: Made everything work in 15 minutes. See code:

<?php
//Load Main App
require 'lib.php';
$app = new TourManagement();


//Create Column Layout
$c = $app->add('Columns');
$c->addClass('pmgcontent')->on('pmgcontentreload', new \atk4\ui\jsReload($c));


//Left column content: Grid
$leftcolumn = $c->addColumn(4)->add(['Grid', 'menu' => false, 'paginator' => false]);
$leftcolumn->table->header = false;
$leftcolumn->setModel(new TourType($app->db), ['name']);
$leftcolumn->model->setOrder('name');
$leftcolumn->addClass('pmgleftcolumn')->on('pmgleftcolumnreload', new \atk4\ui\jsReload($leftcolumn->table));


//middle column content: Loader (in which Form is loaded)
$middlecolumn = $c->addColumn(6)->add(['Loader', 'loadEvent' => false, 'shim' => ['Text', 'Select a Tour Type to edit']]);
$middlecolumn->set(function($p) use ($app) {
    $form = $p->add('Form');
    $form->setModel(new TourType($app->db));
    
    //Load by id and add delete button if ID is specified 
    if(null !== $app->stickyGet('id')) {
		$form->model->load($app->stickyGet('id'));
	
		$form->layout->addButton('Delete')->on('click', function() use ($form, $app) {
			$form->model->delete($app->stickyGet('id'));
			$app->stickyForget('id');
			return (new \atk4\ui\jQuery('.pmgcontent'))->trigger('pmgcontentreload') ;
		},  ['confirm'=>'Are you sure you want to delete '.$form->model['name'].'?']);
		$form->onSubmit(function($form) {
		$form->model->save();
        return (new \atk4\ui\jQuery('.pmgleftcolumn'))->trigger('pmgleftcolumnreload') ;
    });
	}
	//If no ID is specified, show create new Tour Type form
	else {
		$form->buttonSave->set('Create new Tour Type');
		$form->onSubmit(function($form) {
			$form->model->save();
			return (new \atk4\ui\jQuery('.pmgcontent'))->trigger('pmgcontentreload') ;
		});
	}
});


//Add right column (not used yet, TODO)
$rightcolumn = $c->addColumn(6);


//Add JS action when clicking on a row in the left column: Load content in the middle column
$leftcolumn->table->onRowClick($middlecolumn->jsLoad(['id' => $leftcolumn->table->jsRow()->data('id')]))
	->addClass('active')
    ->siblings()->removeClass('active');


//Add creation of new Tour Type to top menue
$app->layout->menu->addItem('Add New Tour Type')->on('click', $middlecolumn->jsLoad());

I added 2 reloads this way: 1 only for the left column and one for all columns. Why?

  • When saving an existing record, only the left column needs to be reloaded.

  • When deleting, everything needs to be reloaded.

  • When creating a new one, everything needs to be reloaded as well. Here I still am working on, as I need to find a way to pass the ID of the new record via GET when saving a new one, something like:

      $form->buttonSave->set('Create new Tour Type');
      $form->onSubmit(function($form) use ($app) {
      	$form->model->save();
      	$app->addstickyGet('id', $form->model->get('id'));
      	return (new \atk4\ui\jQuery('.pmgcontent'))->trigger('pmgcontentreload') ;
      });
    

I know the function addStickyGet does not exist :slight_smile: Thats the way I could see it work.

This way, i could set the new record active in the left side grid (add active class) and could have the form show the newly created record after submitting. This would fit with the implementation of opening a record via URL, like localhost/tour_types.php?id=12

Best regards
Philipp
Best regards
Philipp