Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

We've moved the forum!

Please use forum.silverstripe.org for any new questions (announcement).
The forum archive will stick around, but will be read only.

You can also use our Slack channel or StackOverflow to ask for help.
Check out our community overview for more options to contribute.

Form Questions /

Moderators: martimiz, Sean, Ed, biapar, Willr, Ingo, swaiba

Persisting Multiple FieldGroup's Data with InlineFormAction


Go to End


3 Posts   2630 Views

Avatar
dalesaurus

Community Member, 283 Posts

27 August 2009 at 10:15am

Edited: 27/08/2009 10:19am

I have a fairly common forms problem that I've spend a few hours hacking at, and I'm stumped.

I want to have list of items that have a FieldGroup associated with each one. Imagine a list of items with a checkbox and textbox each, and each with a Delete button. I want the Delete button to be an InlineFormAction that will 1. Save any form changes , and 2. delete the item before returning to the form.

The User Case would be a shopping cart, User adds 3 items, is displayed the above form, changes text in the TextBox for Item 1, then deletes Item 3.

With typical SS forms processing those changes are lost when the form data isn't bound to a DataObjects you can SaveInto(). I'm currently holding the data in the session as this is a collection process that I need all the data before it can be saved into a DataObject.

SO WHAT TO DO? Also known as WHAT I HAVE TRIED

Option 1. Indexed Form Actions, trapped with PHP's magic methods

	$formFields = new FieldSet();
	foreach($items as $idx => $item){
		// Add other item elements
		$formFields->push( new FieldGroup(
					new LiteralField('ItemName',$item['Name'],
					new InlineFormAction('removeitem_'.$idx,'Remove'),
					new TextField('text_'.$idx,'Name',null,15)
				));
	}

Now this should call the function removeitem_3() on the controller or Form object. But that doesn't extend well because you'd have to declare all these removeitem_x() functions. Well this is exactly why PHP has magic methods. I should be able to use __call kind of like this:

	public static function removeitem($idx,$args){
		// Args should contain the $data, $response, and $form objects to do parsing/saving with
		// Use $idx to remove the item from the list in the session
	}

	public function __call($name, $arguments) {
		if( strpos($name,'removeitem') === 0){
			$x = explode('_',$name);
			return self::remove($x[1],$arguments);
		}
		return parent::__call($name,$arguments);	// Lets not screw with upstream calls
	}

This does not work. I can't figure out how the __call method never gets used. The parent should be delegating to the child function, but it doesn't happen. I think it has to do with Sapphire's core Object (Line 567) that checks with method_exists or the internal self::extra_methods array. When it's not found instead of delegating to __call() it just returns false.

So on to attempt 2:

Option 2. Indexed Form Actions, mapped to custom Methods
So this time building upon what we know about the Sapphire's Object and self::extra_methods array we should be able to use Object::createMethod to map some code to be executed (Note: Currently the only documentation on createMethod is in the unit tests and the code itself). Looks like this:

	$formFields = new FieldSet();
	foreach($items as $idx => $item){
		// Add other item elements, same function as above
		$dynamicMethods['removeitem_'.$idx] = 'self::removeFromCart('.$idx.',$args); $obj->Controller()->redirectBack();';
		$formFields->push( new FieldGroup(
					new LiteralField('ItemName',$item['Name'],
					new InlineFormAction('removeitem_'.$idx,'Remove'),
					new TextField('text_'.$idx,'Name',null,15)
				));
	}

	// create $actions ...

	$obj = parent::__construct($controller, 'ItemForm', $formFields, $actions);

	// Must set these up after parent constructs or they get clobbered
	foreach($dynamicMethods as $method => $code) {
		$this->createMethod($method, $code);
	}

	return $obj;

Now THIS code actually works (for the most part) as insane as it looks. What doesn't work here is index offsetting of the Item list and the removeitem_$idx. Removing an item will work the first time but after that the correct method will not be created that corresponds. So you would end up running removeitem_2, number 2 would be deleted, but the next time you delete an item there will not be a removeitem_$idx method found for that item. Its as if the method list stays cached between requests or in the objects passed back.

Ok, deep breath. Relax.

At this point try to remember that I want to SAVE the data before deleting the item. I have since given up and taken cues form the Ecommerce module to just wipe any changes on a delete. I'm not even expecting help with such a lengthy post...I'm just venting at this point :)

BUT, I do think there should be some mechanism to deal with Forms that have a list of items with a series of FieldGroups or similar. There is a need to deal with each one consistently with some kind of indexing. Anyone encountered or tackled a similar issue, with some tips?

Avatar
Taffy

Community Member, 119 Posts

28 August 2009 at 11:20pm

Hi Dale, Welcome to the community. I cant help you with your problem i'm afraid but it may be worth trying to catch a core dev on the irc channel http://irc.silverstripe.org 8:30am GMT +12 is a good time :)

Avatar
dalesaurus

Community Member, 283 Posts

29 August 2009 at 2:35am

Thanks for the reply Taffy, I'll have to work with my GMT -5 to catch someone. :)