I'm adding a custom drop down to the userform module and everything seemed to work, however:
If I set the following:
[Textfield One] - Dropdown option one
[Textfield Two] - Dropdown option two
It seems to only save the the setting under Textfield One, and ignores Textfield Two. If I then go back and set Textfield Two again, it wipes out Textfield One.
Am I supposed to iterate over something save each value independently or ...?
The class;
class CustomEditableFormField extends DataExtension
{
public function updateFieldConfiguration(FieldList $fields)
{
$fields->push(DropdownField::create($this->owner->getSettingName('CustomTextId'), 'Custom field')
->setSource(array("1" => "One", "2" => "Two"))
->setEmptyString($this->owner->getSetting('CustomTextId')));
}
}
and my config.yml
EditableFormField:
extensions:
- CustomEditableFormField
The correct way to add a custom extension is:
public function updateFieldConfiguration(FieldList $fields)
{
$fields->push(DropdownField::create(
$this->owner->getSettingName('CustomTextId'),
'Custom field',
$this->getData(),
$this->owner->getSetting('CustomTextId'))
);
}
The main issue being in where the getSetting function is called.
I think you want to subclass EditableFormField, not decorate it.
The general rule of thumb is one field to one piece of data too (but not necessarily).
And that form fields are generic, not specific to a purpose, as that's why they're editable rather than an unending array of use-cases.
So I'm not sure this solution is what you're after.
Especially since EditableFormField::getFieldConfiguration() does not take any parameters, let alone an entire FieldList.
It seems like you're trying to just skip the configuration step of adding a form field, in which case a custom subclass is surely your answer.
Related
I'm using ActiveForm with Yii2 and by default it seems to generate default id's for fields if you don't set one, in the format of:
{action-name}-{field-name}
So for example if I had a field with the name of foo_bar used in an action of actionSettings then the id of this field would be generated as:
settings-foo_bar
I would prefer this to just be foo_bar.
Is this possible to change on a form by form basis?
Based on the answer provided by #Bizley I was investigating how the method calculated the name and found out there is another way to achieve this as well.
You can simply override the formName method of your respective model to return a blank value, such as:
public function formName() {
return '';
}
Whilst this has less overheads as you don't need to create a new class, it will also affect other things within your form such as the field names and also should not be used for forms which contain multiple different models as explained here.
Lastly, because this question was about changing how Yii formats the id, #Bizleys answer is the correct one; my solution is just another option of possibly achieving it another way.
ActiveField id is by default created based on the form's model name and field's name.
If you want to change it for the whole form override the method that does it:
protected function getInputId()
{
return $this->_inputId ?: Html::getInputId($this->model, $this->attribute);
}
and use this modified class in your form.
I have been going through the gridfield class documentation here;
http://doc.silverstripe.org/framework/en/reference/grid-field
Here is the code in question. While it does display a grid-field it adds a button on each columns. How would I edit this code to not display the buttons? The buttons are links to a non-existent page.
Link to rendered page; http://www.silverstripe.org/assets/Uploads/Capture28.JPG
public function AllPages() {
$gridField = new GridField('pages', 'All pages', SiteTree::get());
$dataColumns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
$dataColumns->setDisplayFields(array(
'Title' => 'Title',
'URLSegment'=> 'URL',
'LastEdited' => 'Changed'
));
return new Form($this, "AllPages", new FieldList($gridField), new FieldList());
}
The cause:
The SilverStripe GridField is pretty well built.
The Basic GridField has pretty much no features at all. Its just a plain table containing the data you want.
All other functionality is added by so called "Components" which are managed by the GridFieldConfig.
When you create a GridField like you did, without specifying a config, it will Create a config for you (GridFieldConfig_Base).
The class GridFieldConfig_Base is just a normal GridFieldConfig with some components already added.
One of those components that is already added for you is called the GridFieldSortableHeader which allows you to press on fields to sort the table (which is what produces those buttons that you see).
The reason the Links of the Buttons are dead is probably because there is some routing problem (The GridField is not that well tested in FrontEnd yet) or you maybe have forgotten to add the action AllPages to $allowed_actions.
Solutions:
Plain table
if you don't really need any feature of GridField, and you just want a plain table, the easiest way is to just set an empty config:
public function AllPages() {
$config = GridFieldConfig::create();
$dataColumns = GridFieldDataColumns::create();
$dataColumns->setDisplayFields(array(
'Title' => 'Title',
'URLSegment'=> 'URL',
'LastEdited' => 'Changed'
));
$config->addComponent($dataColumns);
$gridField = GridField::create('pages', 'All pages', SiteTree::get(), $config);
return Form::create($this, __FUNCTION__, FieldList::create($gridField), FieldList::create());
}
Remove just the Sortable Header
$gridField->getConfig()->removeComponentsByType('GridFieldSortableHeader');
// if you don't have a SortableHeader, you probably also don't want a filter
$gridField->getConfig()->removeComponentsByType('GridFieldFilterHeader');
Replace the Sortable Header with a text only header row
Unfortunately, there is no normal header in SilverStripe at this time, but the great gridfieldextensions module from Andrew Short brings you one.
Get the module on GitHub or Packagist
$gridField->getConfig()->removeComponentsByType('GridFieldSortableHeader');
// if you don't have a SortableHeader, you probably also don't want a filter
$gridField->getConfig()->removeComponentsByType('GridFieldFilterHeader');
$gridField->getConfig()->addComponent(new GridFieldTitleHeader());
fix the Sortable Header
if you wish to have the sort functionality, you will have to fix the routing.
It has been a while since I last used the GridField in frontend. I can only tell you that it did work at some time.
perhaps routing does not work because your form action (AllPages) is not accessible as URL, if that's the case, its pretty easy to fix: just add AllPages to your $allowed_actions of your Controller.
if the form is accessible, then its probably a bug in GridField, I would need to debug that to tell you any more. If that is the case, please reply via comment or contact me on IRC and I will take a look at it.
UPDATE: I just answered another frontend GridField question, and went a bit more in depth. perhaps this is also helpful to you: https://stackoverflow.com/a/22433159/1119263 (see Option 2)
where I want tu put an initial value.
I have seen that quicksearch has 'q' element but I can't access it, for example this does not find the q element:
$quickSearch->getElement('q');
How can I access the quicksearch in order to set an initial value?
Looking at the source of it can help you find things out. Agile Toolkit is designed in a way where developer should take advantage of the knowledge of the source code.
QuickSearch is derived from Filter which is derived from Form, so there should be addField somewhere. Looking at the QuickSearch, you'll find it inside recallAll() function. There are no calls to this functions so we should look into the parent class - Filter.
Filter sets up a hook in api to call recallAll after initialization have been finished. That means to be able to access the field you can either redefine a method or add a hook yourself.
Hook:
$this->api->addHook('post-init',function() use($quickSearch){
$quickSearch->getElement('q')->set('hello');
});
Extending
class MyQuicksearch extends QuickSearch {
function recallAll(){
parent::recallAll();
$this->getElement('q')->set('hello');
}
}
Finally you can take advantage of knowing where recallAll is loading their default values from and simply do this:
$quicksearch->memorize('q','hello');
to address this, we must first understand how the Search Field of the QuickSearch Class is added to the Grid Basic Class. so upon investigation of the source code, we can see that:
QuickSearch Class does not track (or save a PUBLIC reference of) the Form_Field q
Form_Field q is ONLY added DURING the Rendering phase of the grid
knowing these, we can now proceed adding the modifications to address item #1.
first, we need to add a variable to track the Form_Field q in the QuickSearch Class:
var $search_field=null; // add this line (1)
function recallAll(){
$ff=$this->addField('line','q','');
$this->search_field=$ff; // and this line (2)
parent::recallAll();
:
:
}
second, to address item #2, on our page where the grid is defined, we need add a follow-up hook, example:
class page_gridsearchtest extends Page {
var $search=null;
function init() {
parent::init();
$g = $this->add('MVCGrid');
$g->setModel('Employees');
if($g){
$this->search=$g->addQuickSearch(array('fullname'));
if($this->search)
$this->api->addHook('post-init',array($this,'MyHook')); // add hook
}
}
function MyHook(){ // hooked method
if($this->search->search_field) {
if($this->search->search_field->get()=='')
$this->search->search_field->set('Juan'); // set initial search if blank
$this->search->search_field->setCaption('Employee Name Search');
}
}
}
this will set a CAPTION beside the QuickSearch field and add a DEFAULT search text if the search field is empty.
if this is just a one-time thing, then this may be useful as a quick fix because directly making changes to the library source is very unorthodoxed and does not follow the OOP concept of extending and sub-classing as promoted by ATK.
I have a form for an object called AccountImport. This form lives in an admin-generated module. In addition to the fields that map directly to this object's attributes, I need a couple extra fields.
If I just add the fields to the AccountImport form, it won't save correctly because the form will no longer match the AccountImport object.
If I create a template manually and splice the extra fields in that way, I'm throwing away all the stuff the admin generator gives me for free (i.e. formatting, "Back to list" button, save buttons).
What's a "good" way to do what I'm trying to do?
If you define additional fields in generator.yml, you can override one of the admin generator actions to handle the fields however you want.
Look at the generated actions.class.php in cache/YOURAPP/YOURENV/modules/autoYOURMODULE/actions/actions.class.php . You can override any of those functions with your own in apps/YOURAPP/modules/YOURMODULE/actions/actions.class.php, because it inherits from that cached file. When you make changes to generator.conf, the cached file is updated but your code will still override it. You probably want to override processForm().
I have an example of this in step 5 at this blog post:
protected function processForm(sfWebRequest $request, sfForm $form)
{
$form->bind($request->getParameter($form->getName()), $request->getFiles($form->getName()));
if ($form->isValid())
{
$notice = $form->getObject()->isNew() ? 'The item was created successfully.' : 'The item was updated successfully.';
// NEW: deal with tags
if ($form->getValue('remove_tags')) {
foreach (preg_split('/\s*,\s*/', $form->getValue('remove_tags')) as $tag) {
$form->getObject()->removeTag($tag);
}
}
if ($form->getValue('new_tags')) {
foreach (preg_split('/\s*,\s*/', $form->getValue('new_tags')) as $tag) {
// sorry, it would be better to not hard-code this string
if ($tag == 'Add tags with commas') continue;
$form->getObject()->addTag($tag);
}
}
try {
$complaint = $form->save();
// and the remainder is just pasted from the generated actions file
When I realized I could read the generated files in the cache to see exactly what the admin generator was doing, and that I could override any part of them, it made me a lot more productive with the admin generator.
I asume you have added the extra fields as widgets to your form object, but have you also added their validators?
No matter which form fields you include in the form object, as long as the generator.yml file doesn't override the settings of the form (ie you don't set any value for the [new|form|edit].display key in that file) the object should get successfully saved on valid input.
I have a module that builds a form that includes a fieldset. Instead of using the <legend> element to render the fieldset title, I want to place this content in a <div> element instead. But I want to change the behavior only for the form returned by my module, so I don't want to place any new functionality into my theme's template.php file.
In mymod.module I have defined:
// custom rendering function for fieldset elements
function theme_mymod_fieldset($element) {
return 'test';
}
// implement hook_theme
function mymod_theme() {
return array(
'mymod_fieldset' => array('arguments' => array('element' => NULL)),
'mymod_form' => array('arguments' => array())
);
}
// return a form that is based on the 'Basic Account Info' category of the user profile
function mymod_form() {
// load the user's profile
global $user;
$account = user_load($user->uid);
// load the profile form, and then edit it
$form_state = array();
$form = drupal_retrieve_form('user_profile_form', $form_state, $account, 'Basic Account Info');
// set the custom #theme function for this fieldset
$form['Basic Account Info']['#theme'] = 'mymod_fieldset';
// more form manipulations
// ...
return $form;
}
When my page gets rendered, I expected to see the fieldset representing 'Basic Account Info' to be wholly replaced by my test message 'test'. Instead what happens is that the <fieldset> and <legend> elements are rendered as normal, but with the body of the fieldset replaced by the test message instead, like this:
<fieldset>
<legend>Basic Account Info</legend>
test
</fieldset>
Why doesn't my #theme function have the chance to replace the entire <fieldset> element? If I wrap a textfield in this function instead, I am able to completely replace the <input> element along with its label. Furthermore, if I provide an override in my site's template.php for theme_fieldset, it works as expected and I am able to completely replace the <fieldset>, so I know it is possible.
What's different about providing #theme functions to fieldsets inside a module?
Have you tried overriding theme_fieldset() instead of using the #theme function? I believe you could do something like this in your .module file:
function mymodule_fieldset($element) {
// do something;
return $html;
}
This would apply to all fieldsets. You could do some kind of check on $element for the fieldsets you want to affect and then use the default implementation for all others.
Take a look at: http://api.drupal.org/api/function/theme_fieldset/6
I know this is an old post -- but I've run into the same issue. I came up with an ugly work around. This is definitely a bug in the Form API. Maybe my temporary fix will be helpful to someone.
I found (and appended) a bug report here: http://drupal.org/node/225698
Worth checking that before trying my hacky fix.
I'm not sure what the children are in $form['Basic Account Info'] in this example, but basically what you can do is use drupal_render() on that fieldset's children, and then recreate a fieldset array separate from $form['Basic Account Info'], theme it with theme() and pass it back to the form array as markup..
$fieldsetElement = array(
//$child is the key of whatever child you need in the fieldset
//you may have to alter this for multiple children, stringing
//together multiple drupal_render calls on each children
//(#children needs to be a string.. unless your theme can handle the array)
'#children'=>drupal_render($form['Basic Account Info'][$child]),
'#attributes'=>array(),//set real attributes
'#title'=>$form['Basic Account Info']['#title']
);
$form['Basic Account Info'] = array(
'#type'=>'markup',//not really needed, is default
'#value'=>theme('mymod_fieldset',$fieldsetElement)
);
super-duper hacking, likely causes disconnect with form state and potential validation failure -- but both are fixable by trial and error with the form api. I wouldn't recommend this unless you really want to get your hands dirty with PHP and drupal form API, but that's really the only way, unless you can live without variable fieldset themes in your module... Maybe try prefix and suffix?
This is just off the top of my head but maybe the difference is because a fieldset is not a form element but just a seperator or a grouper, if you will. Maybe the #theme callback is only for form elements?
The concept of your code works, meaning you can do what you want to do.
There are some things that can explain why it doesn't work.
The fieldset is not $form['Basic Account Info'].
Need to clear cache.
$form['Basic Account Info']['#theme'] is lost/overridden later in the code execution.
Try to take a look at $form before you do any of the moderations. When I tried to copy your code I run into a bug:
user.pages.inc file needed to be loaded
I was having the same issue.
You need to use #theme_wrappers instead of #theme
'#type' => 'fieldset',
'#theme_wrappers' => array('mymodule_fieldset'),