I have created a new model admin using SilverStripe 3.1.
I wish to add a button next to the 'Add' button on top of the grid view. Is there any way I can do this and create an action for this?
Here is what I found so far.
public function getEditForm($id = null, $fields = null)
{
/**
* #var $EditForm CMSForm
*/
$EditForm = parent::getEditForm($id, $fields);
$EditForm->Fields()->add(LiteralField::create('Sync', '<button> Sync </button>'));
return $EditForm;
}
Also I want to have a handler for this.
So after some time I have figured out what I need to do. There are threesteps to this.
Step one:
Create a new class Name it what ever and make it extend GridField_HTMLProvider
class GridFieldSyncButton implements GridField_HTMLProvider
{
/**
* #var string
*/
protected $targetFragment;
/**
* #param string $targetFragment
*/
public function __construct($targetFragment = 'before')
{
$this->targetFragment = $targetFragment;
}
/**
* #param $gridField
* #return array
*/
public function getHTMLFragments($gridField)
{
//-- The link to where the button links
$data = new ArrayData(array('Sync' => Controller::join_links('link')));
//--
return array
(
$this->targetFragment => $data->renderWith('GridFieldSyncButton')
);
}
}
here what you did is created a new grid field component which you will be able to attach
Step two: Create the template
<a href="$Sync" class="ss-ui-button ui-button ui-widget ui-state-default ui-corner-all" data-icon="add">
Sync Calculators
</a>
<br/><br/>
Step Three : Add it to your grid
$GridField = $EditForm->Fields()->items[0];
$GridField->getConfig()->addComponent(new GridFieldSyncButton());
Related
Imagine these 2 entities
Intervention
- items # OneToMany (no cascade)
addItem()
removeItem()
Item
- intervention # ManyToOne
When I'm doing an Intervention I want to select the Items concerned.
I use an Intervention form in which I can attach/unattach items
->add('items', EntityIdType::class, array(
'class' => Item::class,
'multiple' => true,
))
When the form is submitted, I see Doctrine calls my Intervention's addItem(), removeItem()
But when I empty any previously attached items (thus sending null as items), Doctrine tells me:
Neither the property "items" nor one of the methods "addItem()"/"removeItem()", "setItems()", "items()", "__set()" or "__call()" exist and have public access in class "AppBundle\Entity\Intervention".
The first question is: Why Doctrine is not finding my accessors when I send a null item list ?
My workaround for now is to implement a setItems() doing the adds/removes:
/**
* Set items
*
* #param $items
*
* #return Intervention
*/
public function setItems($items)
{
foreach ($this->items as $item) {
$this->removeItem($item);
}
if ($items) {
foreach ($items as $item) {
$this->addItem($item);
}
}
return $this;
}
I think you need to use ArrayCollection in your other side of the ManyToOne relationship.
Your AppBundle\Entity\Item entity class needs to have:
use AppBundle\Entity\Intervention;
//...
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Intervention", inverseBy="items")
*/
private $intervention;
/**
* #param Intervention $intervention
*/
public function setIntervention(Intervention $intervention){
$this->intervention = $intervention;
}
/**
* #return Intervention
*/
public function getIntervention(){
return $this->intervention;
}
Then in the AppBundle\Entity\Intervention entity class:
use Doctrine\Common\Collections\ArrayCollection;
//...
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Item", mappedBy="intervention")
*/
private $items;
public function __construct(){
$this->items = new ArrayCollection();
}
public function getItems(){
return $this->items;
}
public function setItems($items){
$this->items = $items;
}
I try to use the Select2 jQuery plugin with Symfony's form component and Doctrine to create a tag field where I can add existing tags and create new ones on the fly.
So far so good. I use the following Select2 init code:
$('select.tags').select2({
tags: "true"
});
All existing tags get loaded on each request. New one's should be created/persisted on submitting the whole form. (So no AJAX magic.)
I don't know where I can hook in now to achieve this so that not existing tags get persisted to the database and added to my parent entity.
It has to happend somewhere between:
if ($form->isSubmitted()) {
}
and
if ($form->isValid()) {
}
in my controller. But I can't imagine what's the best way to do this (or if there is any).
I read about form collection but somehow this is not really what I need. Because there you have one single input field for each tag. But I have a select field with new options added dynamically.
once i did the same i seperated the logic from the formbuilder, right now i dont have the code but in pseudo:
be sure your chosen tags are written into an textinput as a string with some separator, sth. like
<input name="tags" value="tag1|tag2|tag3|" ..>
in controller you check if form is submitted and valid
then you have your entity
$entity=$form->getData()
and the tags
$tags = explode("|",$request->get('tags'));
then you iterate over them and add them to your entity, if the tag does not exist yet you create it
foreach($tags as $tag){
$tag = $tagrepo->findOneByName($tag)
if(!$tag){
$newTag = new Tag();
$newTag->setName($tag);
$em->persist($newTag);
$entity->addTag($newTag);
}else{
$entity->addTag($tag);
}
$em->flush();
}
New one's should be created/persisted on submitting the whole form.
So why you don't want to persist them after this line:
if ($form->isValid()) {
}
EDIT
Once I created the transformer for tags. It changes collection of tags to single separated by commas. After submit the transformer turns back text to the collection Tags.
/**
* Class TagTransformer
* #package AppBundle\Form\Transformer
*
* #DI\Service("app.form.transformer.tag")
*/
class TagTransformer implements DataTransformerInterface{
/**
* #var EntityManager
*/
private $em;
/**
* #param EntityManager $em
* #DI\InjectParams(params={
* "em" = #DI\Inject("doctrine.orm.entity_manager")
* })
*/
public function __construct($em)
{
$this->em = $em;
}
/**
* #param ArrayCollection | Tag[] $value
* #return string
*/
public function transform($value)
{
if($value == null) $value = array();
$string = "";
foreach($value as $tag)
$string .= $tag->getName().",";
return $string;
}
/**
* #param string $value
* #return ArrayCollection
*/
public function reverseTransform($value)
{
$collection = new ArrayCollection();
if(strlen($value) == 0) return $collection;
$names = explode(",",$value);
foreach($names as $name){
$tag = $this->em->getRepository('AppBundle:Def\Tag')->findOneByName($name);
if(!$tag) $tag = new Tag();
$tag->setName($name);
$collection->add($tag);
}
return $collection;
}
}
I'm trying to show only selected fields in my REST action in controller.
I've found one solution - I can set groups in Entities/Models and select this group in annotation above action in my Controller.
But actually i don't want use groups, i want determine which fields i wanna expose.
I see one solution - I can create one group for every field in my Entities/Model. Like this:
class User
{
/**
* #var integer
*
* #Groups({"entity_user_id"})
*/
protected $id;
/**
* #var string
*
* #Groups({"entity_user_firstName"})
*/
protected $firstName;
/**
* #var string
*
* #Groups({"entity_user_lastName"})
*/
protected $lastName;
}
And then i can list fields above controller action.
My questions are:
Can I use better solution for this?
Can I list all groups? Like I can list all routes or all services.
This is mainly about serialization not about fosrestbundle itself.
The right way would be to create your own fieldserialization strategy.
This article got it down really nicely:
http://jolicode.com/blog/how-to-implement-your-own-fields-inclusion-rules-with-jms-serializer
It build a custom exclusion strategy as describeted here:
How do I create a custom exclusion strategy for JMS Serializer that allows me to make run-time decisions about whether to include a particular field?
Example code from first link for reference:
custom FieldExclusion strategy:
namespace Acme\Bundle\ApiBundle\Serializer\Exclusion;
use JMS\Serializer\Exclusion\ExclusionStrategyInterface;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\Context;
class FieldsListExclusionStrategy implements ExclusionStrategyInterface
{
private $fields = array();
public function __construct(array $fields)
{
$this->fields = $fields;
}
/**
* {#inheritDoc}
*/
public function shouldSkipClass(ClassMetadata $metadata, Context $navigatorContext)
{
return false;
}
/**
* {#inheritDoc}
*/
public function shouldSkipProperty(PropertyMetadata $property, Context $navigatorContext)
{
if (empty($this->fields)) {
return false;
}
$name = $property->serializedName ?: $property->name;
return !in_array($name, $this->fields);
}
}
Interface
interface ExclusionStrategyInterface
{
public function shouldSkipClass(ClassMetadata $metadata, Context $context);
public function shouldSkipProperty(PropertyMetadata $property, Context $context);
}
usage
in controller or where you need it:
$context = new SerializationContext();
$fieldList = ['id', 'title']; // fields to return
$context->addExclusionStrategy(
new FieldsListExclusionStrategy($fieldList)
);
// serialization
$serializer->serialize(new Pony(), 'json', $context);
You should be also able to mix and match with groups eg. you can also set $content->setGroups(['myGroup']) together with the fieldExclusio
Accessing my route /message/new i'm going to show a form for sending a new message to one or more customers. Form model has (among others) a collection of Customer entities:
class MyFormModel
{
/**
* #var ArrayCollection
*/
public $customers;
}
I'd like to implement automatic customers selection using customers GET parameters, like this:
message/new?customers=2,55,543
This is working now by simply splitting on , and do a query for getting customers:
public function newAction(Request $request)
{
$formModel = new MyFormModel();
// GET "customers" parameter
$customersIds = explode($request->get('customers'), ',');
// If something was found in "customers" parameter then get entities
if(!empty($customersIds)) :
$repo = $this->getDoctrine()->getRepository('AcmeHelloBundle:Customer');
$found = $repo->findAllByIdsArray($customersIds);
// Assign found Customer entities
$formModel->customers = $found;
endif;
// Go on showing the form
}
How can i do the same using Symfony 2 converters? Like:
public function newAction(Request $request, $selectedCustomers)
{
}
Answer to my self: there is not such thing to make you life easy. I've coded a quick and dirty (and possibly buggy) solution i'd like to share, waiting for a best one.
EDIT WARNING: this is not going to work with two parameter converters with the same class.
Url example
/mesages/new?customers=2543,3321,445
Annotations:
/**
* #Route("/new")
* #Method("GET|POST")
* #ParamConverter("customers",
* class="Doctrine\Common\Collections\ArrayCollection", options={
* "finder" = "getFindAllWithMobileByUserQueryBuilder",
* "entity" = "Acme\HelloBundle\Entity\Customer",
* "field" = "id",
* "delimiter" = ",",
* }
* )
*/
public function newAction(Request $request, ArrayCollection $customers = null)
{
}
Option delimiter is used to split GET parameter while id is used for adding a WHERE id IN... clause. There are both optional.
Option class is only used as a "signature" to tell that converter should support it. entity has to be a FQCN of a Doctrine entity while finder is a repository method to be invoked and should return a query builder (default one provided).
Converter
class ArrayCollectionConverter implements ParamConverterInterface
{
/**
* #var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
function apply(Request $request, ConfigurationInterface $configuration)
{
$name = $configuration->getName();
$options = $this->getOptions($configuration);
// Se request attribute to an empty collection (as default)
$request->attributes->set($name, new ArrayCollection());
// If request parameter is missing or empty then return
if(is_null($val = $request->get($name)) || strlen(trim($val)) === 0)
return;
// If splitted values is an empty array then return
if(!($items = preg_split('/\s*'.$options['delimiter'].'\s*/', $val,
0, PREG_SPLIT_NO_EMPTY))) return;
// Get the repository and logged user
$repo = $this->getEntityManager()->getRepository($options['entity']);
$user = $this->getSecurityContext->getToken()->getUser();
if(!$finder = $options['finder']) :
// Create a new default query builder with WHERE user_id clause
$builder = $repo->createQueryBuilder('e');
$builder->andWhere($builder->expr()->eq("e.user", $user->getId()));
else :
// Call finder method on repository
$builder = $repo->$finder($user);
endif;
// Edit the builder and add WHERE IN $items clause
$alias = $builder->getRootAlias() . "." . $options['field'];
$wherein = $builder->expr()->in($alias, $items);
$result = $builder->andwhere($wherein)->getQuery()->getResult();
// Set request attribute and we're done
$request->attributes->set($name, new ArrayCollection($result));
}
public function supports(ConfigurationInterface $configuration)
{
$class = $configuration->getClass();
// Check if class is ArrayCollection from Doctrine
if('Doctrine\Common\Collections\ArrayCollection' !== $class)
return false;
$options = $this->getOptions($configuration);
$manager = $this->getEntityManager();
// Check if $options['entity'] is actually a Dcontrine one
try
{
$manager->getClassMetadata($options['entity']);
return true;
}
catch(\Doctrine\ORM\Mapping\MappingException $e)
{
return false;
}
}
protected function getOptions(ConfigurationInterface $configuration)
{
return array_replace(
array(
'entity' => null,
'finder' => null,
'field' => 'id',
'delimiter' => ','
),
$configuration->getOptions()
);
}
/**
* #return \Doctrine\ORM\EntityManager
*/
protected function getEntityManager()
{
return $this->container->get('doctrine.orm.default_entity_manager');
}
/**
* #return \Symfony\Component\Security\Core\SecurityContext
*/
protected function getSecurityContext()
{
return $this->container->get('security.context');
}
}
Service definition
arraycollection_converter:
class: Acme\HelloBundle\Request\ArrayCollectionConverter
arguments: ['#service_container']
tags:
- { name: request.param_converter}
It's late, but according to latest documentation about #ParamConverter, you can achieve it follow way:
* #ParamConverter("users", class="AcmeBlogBundle:User", options={
* "repository_method" = "findUsersByIds"
* })
you just need make sure that repository method can handle comma (,) separated values
Is there any command or method that I can use to insert the contents of a form (e.g. the user registration form) into a block?
In Drupal 7, it looks like this:
function yourmodule_block_view($delta='')
{
switch($delta) {
case 'your_block_name':
$block['subject'] = null; // Most forms don't have a subject
$block['content'] = drupal_get_form('yourmodule_form_function');
break;
}
return $block;
}
The form array returned by drupal_get_form will be automatically rendered.
yourmodule_form_function is a function (in your module or an existing Drupal module) that returns the form array;
drupal_get_form($form_id) - put it in a module's hook_block ($op=='view') or even... shudder... inside a block with PHP filter on.
You need to find the form id first - look for a hidden input with the name form_id within the form. Its value should be the the form id.
Also, you could simply use the Form Block module.
Drupal 8+ solution
Create the form. Then, to create the block use something like this:
<?php
namespace Drupal\my_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\my_module\Form\MyForm;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the My Block block.
*
* #Block(
* id = "my_block",
* admin_label = #Translation("My Block")
* )
*/
class MyBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The form builder.
*
* #var \Drupal\Core\Form\FormBuilder
*/
protected $formBuilder;
/**
* Constructs a new MyBlock object.
*
* #param array $configuration
* A configuration array containing information about the plugin instance.
* #param string $plugin_id
* The plugin_id for the plugin instance.
* #param mixed $plugin_definition
* The plugin implementation definition.
* #param \Symfony\Component\DependencyInjection\ContainerInterface $container
* Our service container.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ContainerInterface $container) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->formBuilder = $container->get('form_builder');
}
/**
* {#inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container
);
}
/**
* {#inheritdoc}
*/
public function build() {
$form = $this->formBuilder->getForm(MyForm::class);
return $form;
// // Or return a render array.
// // in mytheme.html.twig use {{ form }} and {{ data }}.
// return [
// '#theme' => 'mytheme',
// "#form" => $form,
// "#data" => $data,
// ];
}
}