More robust form traversing and manipulation needed?

53 views
Skip to first unread message

Jonathon Menz

unread,
Mar 9, 2015, 5:41:22 PM3/9/15
to silverst...@googlegroups.com
I was working on a new module last week aimed at allowing users to choose files more easily and hit some roadblocks when it came time to update the Insert Media form.

What I wanted to do
Nest another level of tabs under the Insert Media > 'From CMS' tab. Keep the original fields there (under a new sub-tab) and add some additional options (recently uploaded and favourited files) 

What I thought I could do
Access the 'From CMS' tab with $form->Fields()->fieldByName('FromCms')

What went wrong
fieldByName() didn't traverse deep enough and returned an empty result - I don't think these functions account for the possibility of nested composite fields. Failing that I thought of chaining multiple fieldByName() calls together but couldn't because of the presence of an unnamed composite field in the chain.

What I actually did
$fromCMSTab = $form->Fields()->items[1]->fieldByName('MediaFormInsertMediaTabs')->fieldByName('FromCms');

Messy and fragile - if the original form is rearranged later, the items[1] part (which is the only way I could think of to access an unnamed CompositeField) could fail and break my extension.

Fix or upgrade?
I might be able to fix some of this with a PR but I wondered if there's a need to make form traversal more robust and work like DOM traversal with jQuery?

$field->parent() : returns the FieldSet or CompositeField that is it's direct parent, or false
$field->moveTo($compositeFieldName) : move a field, tab or tabset to a different composite field, tab or tabset

I'm sure there are some others that could be useful but these two, in addition to fixing the fieldByName logic to find a field now matter how far down it's nested, could make it a lot easier to manipulate forms.

Is there an easy way of doing this already that I'm missing?

Patrick Nelson

unread,
Oct 6, 2015, 7:51:07 PM10/6/15
to silverst...@googlegroups.com
I'll echo this complaint here so everyone else sees it and then point out a somewhat [tangentially] related discussion on this issue (regarding a but in TabSet):

That issue is only tangentially related since it breaks the return value of some of those methods which do happen to recursively find fields by name. It's a feature which I was originally trying to use because this method that Jon mentions doesn't exist and, naturally, I want to keep framework level stuff within the framework for better separation of concerns. Anyway, if that feature did exist, a lot of heartache could be avoided :) Then again I would have never found that bug in TabSet->insertAfter() and TabSet->insertBefore().

--
You received this message because you are subscribed to the Google Groups "SilverStripe Core Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to silverstripe-d...@googlegroups.com.
To post to this group, send email to silverst...@googlegroups.com.
Visit this group at http://groups.google.com/group/silverstripe-dev.
For more options, visit https://groups.google.com/d/optout.

Jonathon Menz

unread,
Jan 9, 2016, 4:12:09 PM1/9/16
to SilverStripe Core Development
Just thinking about form APIs a little today and wondering about a more flexible way of adding fields to FieldLists. Using getCMSFields() as an example below.

The standard way (will fail if 'Content' field is missing):

$fields->addFieldToTab('Root.Main', TextField::create('SubTitle'), 'Content');

An equivalent alternative (will also fail if 'Content' field is missing):

$fields->insertBefore('Content', TextField::create('SubTitle'));

The fact these methods fail if the Content field is missing makes them quite limiting. What if the Content field may be hidden, for instance on the home page? We might end up with something like this:

$before = $fields->dataFieldByName('Content') ? 'Content' : null;
$fields
->addFieldToTab('Root.Main', TextField::create('SubTitle'), $before);

What if we want to get really fussy?

if ($fields->dataFieldByName('Title')) {
 $fields
->insertAfter('Title', TextField::create('SubTitle'));
} else if ($fields->dataFieldByName('Content')) {
 $fields
->insertBefore('Content', TextField::create('SubTitle'));
} else {
 $fields
->addFieldToTab('Root.Main', TextField::create('SubTitle'));
}

Maybe we could achieve all this with one method?

$fields->add(TextField::create('SubTitle'), 'tab:Root.Main', 'after:Title', 'at:start');

This example would resolve to: in the Root.Main tab, after Title if possible, or at the start otherwise. You could perhaps enter multiple before/after items as fallbacks with the earliest ones taking priority, which could come in handy for extensions when you're not sure the exact structure of the form you'll be manipulating. 

This way you could easily change your preference about where fields are inserted without having to change the method your using or introduce complex logic.

Could be useful?

Note: some might say an array would be cleaner for the optional arguments... that might be true but above would be quicker to type and we use a similar syntax in SS for filters.
Reply all
Reply to author
Forward
0 new messages