Merge branch 'master' of git://github.com/Chill-project/CustomFields

This commit is contained in:
Marc Ducobu 2014-11-10 11:45:59 +01:00
commit 33be9bedfb
21 changed files with 522 additions and 10 deletions

View File

@ -244,4 +244,5 @@ class CustomFieldController extends Controller
->getForm()
;
}
}

View File

@ -146,7 +146,6 @@ class CustomFieldsGroupController extends Controller
'action' => $this->generateUrl('customfieldsgroup_update', array('id' => $entity->getId())),
'method' => 'PUT',
));
$form->add('submit', 'submit', array('label' => 'Update'));
return $form;
@ -221,4 +220,44 @@ class CustomFieldsGroupController extends Controller
->getForm()
;
}
/**
* This function render the customFieldsGroup as a form.
*
* This function is for testing purpose !
*
* The route which call this action is not in Resources/config/routing.yml,
* but in Tests/Fixtures/App/app/config.yml
*
* @param int $id
*/
public function renderFormAction($id, Request $request)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ChillCustomFieldsBundle:CustomFieldsGroup')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find CustomFieldsGroups entity.');
}
$form = $this->createForm('custom_field', null, array('group' => $entity));
$form->add('submit', 'submit', array('label' => 'POST'));
$form->handleRequest($request);
if ($form->isSubmitted()) {
var_dump($form->getData());
var_dump(json_encode($form->getData()));
}
$this->get('twig.loader')
->addPath(__DIR__.'/../Tests/Fixtures/App/app/Resources/views/',
$namespace = 'test');
return $this
->render('@test/CustomField/simple_form_render.html.twig', array(
'form' => $form->createView()
));
}
}

View File

@ -0,0 +1,156 @@
<?php
/*
* Chill is a software for social workers
* Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Chill\CustomFieldsBundle\CustomFields;
use Chill\CustomFieldsBundle\CustomFields\CustomFieldInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\CustomFieldsBundle\Entity\CustomField;
use Chill\CustomFieldsBundle\Form\Type\ChoicesType;
use Symfony\Component\HttpFoundation\RequestStack;
use Chill\CustomFieldsBundle\Form\Type\ChoicesListType;
use Chill\CustomFieldsBundle\Form\DataTransformer\CustomFieldDataTransformer;
use Chill\CustomFieldsBundle\Form\Type\ChoiceWithOtherType;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class CustomFieldChoice implements CustomFieldInterface
{
const ALLOW_OTHER = 'other';
const MULTIPLE = 'multiple';
const EXPANDED = 'expanded';
const CHOICES = 'choices';
/**
*
* @var RequestStack
*/
private $requestStack;
private $defaultLocale;
public function __construct(RequestStack $requestStack, $defaultLocale)
{
$this->requestStack = $requestStack;
$this->defaultLocale = $defaultLocale;
}
public function buildForm(FormBuilderInterface $builder, CustomField $customField)
{
//prepare choices
$locale = $this->requestStack->getCurrentRequest()->getLocale();
$choices = array();
foreach($customField->getOptions()[self::CHOICES] as $persistedChoices) {
if ($persistedChoices['active']){
$choices[$persistedChoices['slug']] = $persistedChoices['name'][$locale];
}
}
//prepare $options
$options = array(
'multiple' => $customField->getOptions()[self::MULTIPLE],
'choices' => $choices,
'required' => (count($choices) > 1) ? true : false //not required if only one
);
//if allow_other = true
if ($customField->getOptions()[self::ALLOW_OTHER] === 1) {
$builder->add(
$builder->create($customField->getSlug(), new ChoiceWithOtherType(), $options)
->addModelTransformer(new CustomFieldDataTransformer($this, $customField)));
} else { //if allow_other = false
//we add the 'expanded' to options
$options['expanded'] = $customField->getOptions()[self::EXPANDED];
$builder->add(
$builder->create($customField->getSlug(), 'choice', $options)
->addModelTransformer(new CustomFieldDataTransformer($this, $customField))
);
}
}
public function buildOptionsForm(FormBuilderInterface $builder)
{
$builder->add(self::MULTIPLE, 'choice', array(
'expanded' => true,
'multiple' => false,
'choices' => array(
1 => 'Multiple',
0 => 'Unique'
),
'empty_data' => 0
))
->add(self::EXPANDED, 'choice', array(
'expanded' => true,
'multiple' => false,
'choices' => array(
1 => 'Expanded',
0 => 'Non expanded'
),
'empty_data' => 0
))
->add(self::ALLOW_OTHER, 'choice', array(
'label' => 'Allow other',
'choices' => array(
0 => 'No',
1 => 'Yes'
),
'empty_data' => 0,
'expanded' => true,
'multiple' => false
))
->add(self::CHOICES, new ChoicesType(), array(
'type' => new ChoicesListType($this->defaultLocale),
'allow_add' => true
));
return $builder;
}
public function deserialize($serialized, CustomField $customField)
{
return $serialized;
}
public function getName()
{
return 'Choices';
}
public function render($value, CustomField $customField)
{
}
public function serialize($value, CustomField $customField)
{
return $value;
}
}

View File

@ -39,8 +39,9 @@ interface CustomFieldInterface
/**
*
* @param type $value
* @param mixed $value the raw value, **not deserialized** (= as stored in the db)
* @param \Chill\CustomFieldsBundle\CustomField\CustomField $customField
* @return string an html representation of the value
*/
public function render($value, CustomField $customField);

View File

@ -6,13 +6,14 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class ChillCustomFieldsExtension extends Extension
class ChillCustomFieldsExtension extends Extension implements PrependExtensionInterface
{
/**
* {@inheritDoc}
@ -33,4 +34,17 @@ class ChillCustomFieldsExtension extends Extension
$container->setParameter('chill_custom_fields.customizables_entities',
$config['customizables_entities']);
}
/* (non-PHPdoc)
* @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
*/
public function prepend(ContainerBuilder $container)
{
// add form layout to twig resources
$twigConfig['form']['resources'][] = 'ChillCustomFieldsBundle:Form:form_div_layout.html.twig';
$container->prependExtensionConfig('twig', $twigConfig);
}
}

View File

@ -28,7 +28,7 @@ class CustomFieldCompilerPass implements CompilerPassInterface
$taggedServices = $container->findTaggedServiceIds(
'chill.custom_field'
);
);
foreach ($taggedServices as $id => $tagAttributes) {
foreach ($tagAttributes as $attributes) {

View File

@ -0,0 +1,53 @@
<?php
namespace Chill\CustomFieldsBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* Return a choice widget with an "other" option
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*
*/
class ChoiceWithOtherType extends AbstractType
{
/* (non-PHPdoc)
* @see \Symfony\Component\Form\AbstractType::buildForm()
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
//add an 'other' entry in choices array
$options['choices']['_other'] = '__other__';
//ChoiceWithOther must always be expanded
$options['expanded'] = true;
$builder
->add('_other', 'text', array('required' => false))
->add('_choices', 'choice', $options)
;
}
/* (non-PHPdoc)
* @see \Symfony\Component\Form\AbstractType::setDefaultOptions()
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver
->setRequired(array('choices'))
->setAllowedTypes(array('choices' => array('array')))
->setDefaults(array('multiple' => false))
;
}
public function getName()
{
return 'choice_with_other';
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Chill\CustomFieldsBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class ChoicesListType extends AbstractType
{
private $defaultLocale;
public function __construct($defaultLocale)
{
$this->defaultLocale = $defaultLocale;
}
/* (non-PHPdoc)
* @see \Symfony\Component\Form\AbstractType::buildForm()
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$locale = $this->defaultLocale;
$builder->add('name', 'translatable_string')
->add('active', 'checkbox', array(
'required' => false,
'empty_data' => true
))
->add('slug', 'hidden', array(
))
->addEventListener(FormEvents::SUBMIT, function(FormEvent $event) use ($locale){
$form = $event->getForm();
$data = $event->getData();
$formData = $form->getData();
if (NULL === $formData['slug']) {
$slug = $form['name'][$locale]->getData();
$slug= strtolower($slug);
$slug= preg_replace('/[^a-zA-Z0-9 -]/','', $slug); // only take alphanumerical characters, but keep the spaces and dashes too...
$slug= str_replace(' ','-', $slug); // replace spaces by dashes
$data['slug'] = $slug;
$event->setData($data);
} else {
$data['slug'] = $formData['slug'];
$event->setData($data);
}
})
;
}
/*
*
* @see \Symfony\Component\Form\FormTypeInterface::getName()
*/
public function getName()
{
return 'cf_choices_list';
}
}

26
Form/Type/ChoicesType.php Normal file
View File

@ -0,0 +1,26 @@
<?php
namespace Chill\CustomFieldsBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
/**
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*
*/
class ChoicesType extends AbstractType
{
public function getName()
{
return 'cf_choices';
}
public function getParent()
{
return 'collection';
}
}

View File

@ -41,6 +41,14 @@ services:
- "@request_stack"
tags:
- { name: 'chill.custom_field', type: 'text' }
chill.custom_field.choice:
class: Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice
arguments:
- "@request_stack"
- %locale%
tags:
- { name: 'chill.custom_field', type: 'choice' }
chill.custom_field.address:
class: Chill\CustomFieldsBundle\CustomFields\CustomFieldAddress

View File

@ -1,5 +1,7 @@
{% extends '::base.html.twig' %}
{% block javascripts_head %}<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>{% endblock %}
{% block body -%}
{{ form(form) }}
{% endblock %}

View File

@ -4,7 +4,6 @@
<h1>CustomField creation</h1>
{{ form(form) }}
<ul class="record_actions">
<li>
<a href="{{ path('customfield') }}">

View File

@ -11,7 +11,7 @@
</tr>
<tr>
<th>Label</th>
<td>{{ entity.label }}</td>
<td>{{ entity.name(app.request.locale) }}</td>
</tr>
<tr>
<th>Type</th>

View File

@ -1,3 +1,97 @@
{% block custom_field_title_widget %}
dump(attr)
{% endblock custom_field_title_widget %}
{# suggestion : {{ dump(form) }} to see all attributes from the form and pick the value or label #}
{% endblock custom_field_title_widget %}
{# render an alement in a choice list #}
{% block cf_choices_list_widget %}
{{ form_row(form.name) }}
{{ form_row(form.active) }}
{{ form_row(form.slug) }}
{% endblock cf_choices_list_widget %}
{# render the possibility to add different elements in a choice list #}
{% block cf_choices_widget %}
{{ form(form) }}
<ul id="ul_{{ form.vars.id }}"></ul>
{# we use javascrit to add an additional element. All functions are personnalized with the id ( = form.vars.id) #}
<script type="text/javascript">
var $collectionHolder_{{ form.vars.id }};
// setup an "add a tag" link
var $addTagLink_{{ form.vars.id }} = $('<a href="#" class="add_tag_link">Add an element</a>');
var $newLinkLi_{{ form.vars.id }} = $('<li></li>').append($addTagLink_{{ form.vars.id }});
function initialize_{{ form.vars.id }}(ULelementId) {
// Get the ul that holds the collection of tags
$collectionHolder_{{ form.vars.id }} = $('#' + ULelementId);
// add the "add a tag" anchor and li to the tags ul
$collectionHolder_{{ form.vars.id }}.append($newLinkLi_{{ form.vars.id }});
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder_{{ form.vars.id }}.data('index', $collectionHolder_{{ form.vars.id }}.find(':input').length);
$addTagLink_{{ form.vars.id }}.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see next code block)
addTagForm_{{ form.vars.id }}($collectionHolder_{{ form.vars.id }}, $newLinkLi_{{ form.vars.id }});
});
};
function addTagForm_{{ form.vars.id }}(collection, newLinkLi) {
console.log($collectionHolder_{{ form.vars.id }});
// Get the data-prototype explained earlier
var prototype = $( '#' + '{{ form.vars.id }}').data('prototype');
console.log(prototype);
// get the new index
var index = collection.data('index');
// Replace '__name__' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// increase the index with one for the next item
collection.data('index', index + 1);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormLi = $('<li></li>').append(newForm);
newLinkLi.before($newFormLi);
};
jQuery(document).ready(initialize_{{ form.vars.id }}('ul_' + '{{ form.vars.id }}'));
</script>
{% endblock cf_choices_widget %}
{% block choice_with_other_widget %}
<h1>echo</h1>
<div {{ block('widget_container_attributes') }}>
{%- for child in form.children._choices %}
{%- if child.vars.value == '_other' -%}
{{- form_widget(child) -}} {{- form_widget(form.children._other) -}}
{%- else -%}
{{- form_widget(child) -}}
{{- form_label(child) -}}
{%- endif -%}
{% endfor -%}
</div>
{% endblock choice_with_other_widget %}

View File

@ -15,6 +15,7 @@ class AppKernel extends Kernel
new \Symfony\Bundle\AsseticBundle\AsseticBundle(),
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
new \Chill\MainBundle\ChillMainBundle,
new \Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle()
#add here all the required bundle (some bundle are not required)
);
}

View File

@ -1,3 +1,7 @@
{% if inputKeys is defined %}
{% for key in inputKeys %}
{{ form_row(form[key]) }}
{{ form_row(form[key]) }}
{% endfor %}
{% else %}
{{ form(form) }}
{% endif %}

View File

@ -5,6 +5,7 @@
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
{% block javascripts_head %}<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>{% endblock %}
</head>
<body>
{% block body %}{% endblock %}

View File

@ -27,4 +27,41 @@ doctrine:
auto_mapping: true
chill_main:
available_languages: [ fr, nl, en ]
available_languages: [ fr, nl, en ]
security:
providers:
chain_provider:
chain :
providers: [in_memory, users]
in_memory:
memory:
users:
admin: { password: olala, roles: 'ROLE_ADMIN' }
users:
entity:
class: Chill\MainBundle\Entity\User
property: username
encoders:
Chill\MainBundle\Entity\User:
algorithm: bcrypt
Symfony\Component\Security\Core\User\User: plaintext
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
default:
anonymous: ~
form_login:
csrf_parameter: _csrf_token
intention: authenticate
csrf_provider: form.csrf_provider
logout: ~
access_control:
#disable authentication for tests
#- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
#- { path: ^/admin, roles: ROLE_ADMIN }
#- { path: ^/, roles: ROLE_USER }

View File

@ -4,4 +4,8 @@ imports:
framework:
test: ~
session:
storage_id: session.storage.filesystem
storage_id: session.storage.filesystem
chill_custom_fields:
customizables_entities:
- { class: TEST, name: test }

View File

@ -2,3 +2,6 @@ cl_custom_fields:
resource: .
type: chill_routes
test_custom_field_form_render:
path: /customfieldsgroup/test/render/{id}
defaults: { _controller: ChillCustomFieldsBundle:CustomFieldsGroup:renderForm }

View File

@ -39,5 +39,8 @@
},
"extra": {
"symfony-app-dir": "Tests/Fixtures/App/app"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "~2.2@dev"
}
}