craue / craueformflowbundle Goto Github PK
View Code? Open in Web Editor NEWMulti-step forms for your Symfony project.
License: MIT License
Multi-step forms for your Symfony project.
License: MIT License
Hi ,
Is that possible to have, for one given step, another Form Object ?
like
step 1 / 2 / 3 -> CarForm
step 4 / DogForm
Thanks :-)
Hi there.
I was trying to use step-based options to add/remove form fields based on the data from a former step.
Dumping $formData
in the flow-class revealed that the entity is still the same as in the beginning. Shouldn't this be done?
I am actually developing and e-commerce site where the validation order has 4 steps
I am wonderring if it is possible to make the login/register work in the bundle and go to the 4th steps.
Any idea?
Thx folks
I have the following Entity (not Doctrine Entity, just a plain class):
class RegistrationEntity {
private $photo;
...
public function setPhoto($photo) {
if($photo instanceof UploadedFile) {
$this->photo = $photo->move('/tmp', 'foo.jpg')->getPathname();
}
else {
$this->photo = $photo;
}
}
}
With my action I'm trying to upload the photo on step 2 (of 3):
public function accountantAction() {
$values = new RegistrationEntity();
//Manage multi-step form:
$flow = $this->get('longtale.form.flow.registrationFlow'); // must match the flow's service id
$flow->bind($values);
$form = $flow->createForm($values);
if ($flow->isValid($form)) {
$flow->saveCurrentStepData();
if ($flow->nextStep()) {
// render form for next step
return array(
'form' => $flow->createForm($values)->createView(),
'flow' => $flow,
);
}
//All steps completed:
die(var_dump($values));
$flow->reset();
return $this->redirect($this->generateUrl('home')); // redirect when done
}
return array('form' => $form->createView(), 'flow' => $flow);
}
However in the next step the $photo property with the temp filename of the uploaded file get lost, I dump the entity and the value $photo is null (while the others are ok).
in postValidate e preBind events of my Flow class the value is present when I submit the step2 form, but it disappear in the next step.
Any clue?
Thanks!
Hi,
I want to change some attributes in the data if the user is logged, so I could do that in preBind Event if I have the formData available. When the preBind event is dispatched, formData is available, so if you want I can do a PR adding formData to FormFlowEvent and refactoring the other Events.
Hi,
I finally figured out what was causing the missing validation of some of our forms, it's the FormFlow
, more specifically:
<?php
public function getFormOptions($formData, $step, array $options = array()) {
$options['flowStep'] = $step;
if (!array_key_exists('validation_groups', $options)) {
$options['validation_groups'] = $this->validationGroupPrefix . $step;
}
return $options;
}
If there is no validation_groups
set (or passed when creating the form via the flow), one will be added, which in return disables validation of used form types in the form of the flow!
If you create a flow with dynamic step navigation, FormFlow::isStepDone returns an error when the flow is created for the very first time.
The following message is returned:
"Warning: array_key_exists() expects parameter 2 to be array, null given in /vendor/bundles/Craue/FormFlowBundle/Form/FormFlow.php line 157"
I think you should test that variable $sessionData is not null and then check if there is any data in session.
public function isStepDone($step) {
$sessionData = $this->session->get($this->sessionDataKey);
return ($sessionData && array_key_exists($step, $sessionData));
}
Is it possible to call a custom function of mine when the "Back" button is clicked in any of the flow steps?
I have left out the data_class
line because the form I am building does not have an associated entity. How can I handle such a case with this bundle?
How can i write my own function inside a form that i have extended the AbstractType ? I wrote a function but i cannot access it. Gives an error saying its an undefined method.
followed the instructions on the readme, but when i added a (custom) button to go back one step, I get this when I go from step 2 to step 1...
Currently i have defined my workflow and i would like to know if there is a mechanism to define the twig file for each step so that when creating the form in the controller class, it automatically includes the said twig file to master page?
Hello,
I am having troubles using craueFormFlowBundle combined with an ajax/jquery call to customize the form.
Is that something that can be done ?
I want my radioBox input to be function of the choice of a select, both in the same step.
The error I get is "This form should not contain extra fields". ( i am including the stepField.html.twig file)
How come is that ? I am not adding any new input field inside the form, only replacing input tags with new ones i get from the ajax function, so it should be working.
The problem occurs at step 4, so there is no need to check the other steps.
Here is the code :
<?php
namespace YOP\YourOwnPoetBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use YOP\YourOwnPoetBundle\Entity\MsgCategory;
use YOP\YourOwnPoetBundle\Entity\TraitCategory;
use YOP\YourOwnPoetBundle\Repository\SuperCategoryRepository;
use YOP\YourOwnPoetBundle\Repository\TraitCategoryRepository;
class PoemDataCollectorFormType extends AbstractType {
public function buildForm(FormBuilder $builder, array $options) {
switch ($options['flowStep']) {
case 1:
$builder->add('name');
$builder->add('sex', 'choice', array(
'empty_value' => 'Choose sex',
'choices' => array(1 => 'Male', 2 => 'Female'),
'label' => 'Receiver\'s sex',
));
$builder->add('userName');
$builder->add('userSex', 'choice', array(
'empty_value' => 'Choose sex',
'choices' => array(1 => 'Male', 2 => 'Female'),
'label' => 'Specify your sex',
));
break;
case 2:
$builder->add('city');
break;
case 3:
$builder->add('relationship', 'entity', array(
'class' => 'YOPYourOwnPoetBundle:Relationship',
'empty_value' => 'Choose one',
'required' => false,
'query_builder' => function(\Doctrine\ORM\EntityRepository $er)
{
return $er->createQueryBuilder('r')->orderBy('r.type', 'ASC');
},
));
$builder->add('relationshipFIB','text', array(
'required' => false,
'label' => 'or type in yourself:',
));
break;
case 4:
$builder->add('traitSuperCategory', 'entity', array(
'class' => 'YOPYourOwnPoetBundle:SuperCategory',
'query_builder' => function(\Doctrine\ORM\EntityRepository $er)
{
return $er->findByType(1);
},
));
$builder->add('traitCategory', 'entity', array(
'class' => 'YOPYourOwnPoetBundle:TraitCategory',
'expanded' => true,
'label' => ' ',
'query_builder' => function(\Doctrine\ORM\EntityRepository $er)
{
return $er->findBySuperCategoryName('a...');
},
));
$builder->add('traitFIB','text', array(
'required' => false,
'label' => 'YOU create the trait:',
));
break;
case 5:
$builder->add('msgCategory', 'entity', array(
'class' => 'YOPYourOwnPoetBundle:MsgCategory',
'empty_value' => 'Choose one',
));
$builder->add('msgFIB','text', array(
'required' => false,
'label' => 'Input fill in the blank:',
));
$builder->add('age','integer', array(
'required' => false,
'label' => 'Input age:',
));
break;
case 6:
$builder->add('poemTitle','textarea', array(
'required' => false,
));
$builder->add('poemClosing', 'textarea', array(
'required' => false,
));
$builder->add('poemPS', 'textarea', array(
'required' => false,
));
break;
}
}
public function getDefaultOptions(array $options)
{
$options = parent::getDefaultOptions($options);
$options['flowStep'] = 1;
$options['data_class'] = 'YOP\YourOwnPoetBundle\PoemBuilder\PoemDataCollector';
$options['intention'] = 'my_secret_key';
return $options;
}
public function getName() {
return 'poemDataCollector';
}
}
?>
collectPoemData.html.twig
{% extends 'YOPYourOwnPoetBundle::layout.html.twig' %}
{% block title %}
{% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}
{{ app.user.name }}'s Own Poet
{% endif %}
{% if dataCollector.userName is not null %}
{{ dataCollector.userName }}'s own Poet
{% endif %}
{% if not is_granted("IS_AUTHENTICATED_REMEMBERED") and dataCollector.userName is null %}
Quilly the Poet
{% endif %}
{% endblock %}
{% block content %}
<div>
{{ webpageVerse.webpageLine1 }}
</div>
<div>
{{ webpageVerse.webpageLine2 }}
</div>
<div>
{{ webpageVerse.webpageLine3 }}
</div>
<div>
{{ webpageVerse.webpageLine4 }}
</div>
<div>
{{ webpageVerse.webpageLine5 }}
</div>
<br><br><br><br>
<form method="post" action="{{ path(app.request.attributes.get('_route'),
app.request.query.all | craue_removeDynamicStepNavigationParameter(flow)) }}" {{ form_enctype(form) }}>
{% include 'CraueFormFlowBundle:FormFlow:stepField.html.twig' %}
{% include 'YOPYourOwnPoetBundle:thePoet:collectDataForm.html.twig' %}
{% include 'CraueFormFlowBundle:FormFlow:buttons.html.twig' %}
</form>
{% endblock %}
{% block javascripts %}
{{ parent() }}
{% include 'YOPYourOwnPoetBundle:thePoet:collectDataFormScript.html.twig' %}
{% endblock %}
collectDataForm.html.twig
{% if flow.getCurrentStep() == 1 %}
<div>
{{ form_label(form.name) }}
{{ form_widget(form.name) }}
</div>
<div id = 'nameError'></div>
<br>
<div id = "sexguess"></div>
<br> <br>
<div id = "sex">
{{ form_label(form.sex) }}
{{ form_widget(form.sex) }}
</div>
<div id = "sexError"></div>
<br><br>
<div id = 'userName'></div>
<div id = 'userNameDiv'>
{{ form_label(form.userName) }}
{{ form_widget(form.userName) }}
</div>
<div id = 'userNameError'></div>
<br>
<div id = 'userSexGuess'></div>
<div id = 'userSexDiv'>
{{ form_label(form.userSex) }}
{{ form_widget(form.userSex) }}
</div>
<div id = 'userSexError'></div>
<br> <br>
{% endif %}
{% if flow.getCurrentStep() == 2 %}
<div>
{{ form_label(form.city) }}
{{ form_widget(form.city) }}
</div>
<div id = 'cityError'></div>
{% endif %}
{% if flow.getCurrentStep() == 3 %}
<div id = 'relationship'>
{{ form_label(form.relationship) }}
{{ form_widget(form.relationship) }}
</div>
<br><br>
<div id = 'relationshipFIB'>
{{ form_label(form.relationshipFIB) }}
{{ form_widget(form.relationshipFIB) }}
</div>
<br><br>
<div id = 'relationshipError'></div>
{% endif %}
{% if flow.getCurrentStep() == 4 %}
<div>
{{ form_label(form.traitSuperCategory) }}
{{ form_widget(form.traitSuperCategory) }}
</div>
{{ form_label(form.traitCategory) }}
{{ form_widget(form.traitCategory) }}
<div id = 'selectError'></div>
<div id = FIBDiv>
{{ form_label(form.traitFIB) }}
{{ form_widget(form.traitFIB) }}
</div>
<div id = FIBError class = "error">
</div>
{% endif %}
{% if flow.getCurrentStep() == 5 %}
<div>
{{ form_label(form.msgCategory) }}
{{ form_widget(form.msgCategory) }}
</div>
<div id = 'selectError'></div>
<div id = "FIB">
{{ form_label(form.msgFIB) }}
{{ form_widget(form.msgFIB) }}
</div>
<div id = FIBError class = "error">
</div>
<div id = "age">
{{ form_label(form.age) }}
{{ form_widget(form.age) }}
</div>
<div id = ageError class = "error">
</div>
{% endif %}
{{ form_rest(form) }}
{{ form_errors(form) }}
collectDataFormScript.html.twig
{# Corrects the title of the page if the user's name ends with letter 's' #}
{#
<script>
var title = new String($("title").text());
var str = " \'s own Poet";
var pattern =/(?= own Poet)/g;
name = title.match(pattern);
alert(name);
if( title.charAt( title.length-21 ) == "z") {
document.title = title+"wouhou";
}
</script>
#}
{% if flow.getCurrentStep() == 1 %}
<script>
function stripTags(str) {
return str.replace(/<\/?[^>]+>/gi, '');
};
function toTitleCase(str) {
return str.replace(/(?:^|\s)\w/g, function(match) {
return match.toUpperCase();
});
}
$(document).ready(function() {
var selectSex = $("#poemDataCollector_sex");
var nameField = $("#poemDataCollector_name");
var sexDiv = $('#sex');
var sexGuess = $('#sexguess');
var form = sexDiv.parent();
var userNameField = $("#poemDataCollector_userName");
var userSexDiv = $('#userSexDiv');
var selectUserSex = $('#poemDataCollector_userSex');
nameField.focus();
sexDiv.hide();
sexGuess.hide();
$('#userNameDiv').hide();
userSexDiv.hide();
selectSex.val(0);
selectUserSex.val(0);
selectSex.keypress(function(event) {
if ( event.which == 13 ) {
$(".craue_formflow_button_last").click();
}
});
nameField.change(function() {
$("#nameError").text('');
});
userNameField.change(function() {
$("#userNameError").text('');
});
selectSex.change(function() {
$("#sexError").text('');
});
selectUserSex.change(function() {
$("#userSexError").text('');
});
selectUserSex.keypress(function(event) {
if ( event.which == 13 ) {
$(".craue_formflow_button_last").click();
}
});
$(".craue_formflow_button_last").click(function(event) {
event.preventDefault();
var sex = selectSex.val();
var name = nameField.val();
name = stripTags(name);
name = toTitleCase(name);
var userName = userNameField.val();
userName = stripTags(userName);
userName = toTitleCase(userName);
var userSex = selectUserSex.val();
if (name.length < 1){
$("#nameError").text("Please enter a name");
return false;
}
if (sexDiv.is(":visible") && sex == 0) {
$("#sexError").text('Please specify '+name+'\'s sex !');
return false;
}
if (userNameField.is(':visible') && userName.length < 1) {
$('#userNameError').text('Please enter your name');
return false;
}
if (userSexDiv.is(":visible") && userSex == 0) {
$("#userSexError").text('Please specify your sex !');
return false;
}
$.get("{{ path('get_sex_by_name') }}", { receiverName: name, receiverSex: sex, userName: userName, userSex: userSex }, function(data){
var result = jQuery.parseJSON(data);
if (result.isKnownName == 2)
{# Handle user's name #}
{
if (result.userSex == 1)
{
selectUserSex.val(1);
form.submit();
}
if (result.userSex == 2)
{
selectUserSex.val(2);
form.submit();
}
if (result.userSex == 0)
{
output = userName+' , please tell me your sex, i\'m bad at guessing !';
$('#userSexGuess').html(output);
userSexDiv.show('slow');
userNameField.attr('readonly', 'readonly');
selectUserSex.focus();
return false;
}
if (result.userSex == -1)
{
output = userName+' ?, that\'s an original name !\n I can\'t guess your sex, can you help me ?';
$('#userSexGuess').html(output);
userSexDiv.show('slow');
userNameField.attr('readonly', 'readonly');
selectUserSex.focus();
return false;
}
} else {
{# Handle receveiver's name #}
if (result.sex == 1)
{
if (result.isKnownName == 0)
{
selectSex.val(1);
selectSex.attr('readonly', 'readonly');
$('#userName').html('Hey, since you are not connected, we need to ask for your name !');
$('#userNameDiv').show('slow');
userNameField.focus();
return false;
} else {
selectSex.val(1);
form.submit();
}
}
if (result.sex == 2)
{
if (result.isKnownName == 0) {
selectSex.val(2);
selectSex.attr('readonly', 'readonly');
$('#userName').html('Hey, since you are not connected, we need to ask for your name !');
$('#userNameDiv').show('slow');
userNameField.focus();
return false;
} else {
selectSex.val(2);
form.submit();
}
}
if (result.sex == 0)
{
output = 'Please tell me '+name+' sex, i\'m bad at guessing !';
sexGuess.html(output);
sexDiv.show('slow');
sexGuess.show('slow');
nameField.attr('readonly', 'readonly');
selectSex.focus();
return false;
}
if (result.sex == - 1)
{
output = name+' ?, that\'s an original name !\n I can\'t guess its sex, can you help me ?';
sexGuess.html(output);
sexDiv.show('slow');
sexGuess.show('slow');
nameField.attr('readonly', 'readonly');
selectSex.focus();
return false;
}
}
});
});
});
</script>
{% endif %}
{% if flow.getCurrentStep() == 2 %}
<script>
$(document).ready(function() {
var cityField = $('#poemDataCollector_city');
cityField.focus();
cityField.keypress(function(event) {
if ( event.which == 13 ) {
if (cityField.val().length < 1){
$("#cityError").text("Please enter a city");
return false;
}
cityField.parent().parent().submit();
}
});
});
</script>
{% endif %}
{% if flow.getCurrentStep() == 3 %}
<script>
$(document).ready(function() {
var relationshipError = $('#relationshipError');
var relationshipSelect = $('#poemDataCollector_relationship');
relationshipSelect.focus();
var relationshipFIB = $('#poemDataCollector_relationshipFIB');
relationshipSelect.keypress(function(event) {
if ( event.which == 13 ) {
if (relationshipSelect.val() == 0) {
$('#relationshipError').text('Please select a relationship or type in one yourself');
return false;
}
relationshipSelect.parent().parent().submit();
}
});
$(".craue_formflow_button_last").click(function(){
if (relationshipSelect.val() == 0 && relationshipFIB.val().length < 1)
{
relationshipError.text('Please choose a relationship from the select box or type in one yourself');
return false;
}
});
relationshipSelect.on('change', function() {
relationshipError.text('');
});
relationshipFIB.on('change', function() {
relationshipError.text('');
});
});
</script>
{% endif %}
{% if flow.getCurrentStep() == 4 %}
<script>
function validateFIB(){
if($("#poemDataCollector_traitFIB").val().length < 1)
{
$("#FIBError").text("Please specify the trait");
return false;
} else {
return true;
}
}
function validateTraitSelect() {
if ( !$("input[name='poemDataCollector[traitCategory]']").is(":checked"))
{
$("#selectError").text('Please choose a trait');
return false;
} else {
return true;
}
}
$(document).ready(function() {
var superCategorySelect = $('#poemDataCollector_traitSuperCategory');
var FIBDiv = $("#FIBDiv");
var TraitCategoryDiv = $("#traitCategoryDiv");
var traitCtgCheckbox = $('#poemDataCollector_traitCategory');
FIBDiv.hide();
superCategorySelect.focus();
$('body').keypress(function(event) {
if ( event.which == 13 ) {
if (FIBDiv.is(":visible")) {
if (!validateFIB())
return false;
} else {
if (!validateTraitSelect())
return false;
}
superCategorySelect.parent().parent().submit();
}
});
superCategorySelect.on('change', function() {
$("#poemDataCollector_traitFIB").val('');
$("#FIBError").text('');
$("#selectError").text('');
var selectedSP = $(this).val();
$.ajax({
url: '{{ path('get_trait_categories') }}',
data: 'superCategoryID='+selectedSP,
dataType: 'json' ,
success: function(json) {
traitCtgCheckbox.empty();
$.each(json, function(index, value) {
var inp = "<input id='poemDataCollector_traitCategory_"+index+"' type='radio' value='"+index+"' required='required' name='poemDataCollector[traitCategory]'>";
var lbl = "<label class=' required' for='poemDataCollector_traitCategory_"+index+"'>"+value+"</label>";
traitCtgCheckbox.append(inp).append(lbl + ' ');
});
}
});
if ( $('select option:selected').text() == 'YOU create the trait...')
{
FIBDiv.show('slow');
} else {
FIBDiv.hide('slow');
}
});
$("input[name='poemDataCollector[traitCategory]']").live('change', function() {
$("#selectError").text('');
checkedBoxLabel = $('input[name=poemDataCollector[traitCategory]]:checked + label').text();
alert(checkedBoxLabel);
if (checkedBoxLabel.indexOf('___') != -1)
{
FIBDiv.show('slow');
} else {
FIBDiv.hide('slow');
}
});
$(".craue_formflow_button_last").click(function(){
if ( $('select option:selected').text() == 'YOU create the trait...' )
return validateFIB();
else
return validateTraitSelect();
});
$("#poemDataCollector_traitFIB").on('change', function() {
$("#FIBError").text("");
});
});
</script>
{% endif %}
{% if flow.getCurrentStep() == 5 %}
<script>
function validateFIB(){
//if it's NOT valid
if($("#poemDataCollector_msgFIB").val().length < 1){
$("#FIBError").text("Please specify your message");
return false;
}
//if it's valid
else{
return true;
}
}
function validateAge() {
//if not valid
if($("#poemDataCollector_age").val().length < 1) {
$("#ageError").text("Please specify the age");
return false;
} else {
return true;
}
}
function validateMsgSelect() {
if ( !$("input[name='poemDataCollector[msgCategory]']").is(":checked"))
{
$("#selectError").text('Please choose a message');
return false;
} else {
return true;
}
}
$(document).ready(function() {
var select = $("#poemDataCollector_msgCategory");
if ( (select.val() < 2) || (select.val() > 13))
$("#FIB").hide();
if ( select.val() != 1)
$("#age").hide();
select.focus();
$('body').keypress(function(event) {
$("#FIBError").text("");
if ( (select.val() > 1) && (select.val() < 14))
{
$("#FIB").show('slow');
$("#poemDataCollector_msgFIB").focus();
} else {
$("#FIB").hide('slow');
}
if (select.val() == 1 )
{
$("#age").show('slow');
$("#poemDataCollector_age").focus();
} else {
$("#age").hide('slow');
}
if ( event.which == 13 ) {
if ( select.val() == 0 ) {
$("#selectError").text("Please select a message");
return false;
}
if ($("#FIB").is(":visible")) {
if (!validateFIB())
return false;
}
if ($("#age").is(":visible")) {
if (!validateAge())
return false;
}
select.parent().parent().submit();
}
});
select.change(function() {
$("#poemDataCollector_msgFIB").val('');
$("#poemDataCollector_age").val('');
if ( (select.val() > 1) && (select.val() < 14))
{
$("#FIB").show('slow');
$("#poemDataCollector_msgFIB").focus();
} else {
$("#FIB").hide('slow');
}
if (select.val() == 1 )
{
$("#age").show('slow');
$("#poemDataCollector_age").focus();
} else {
$("#age").hide('slow');
}
});
$(".craue_formflow_button_last").click(function(){
if ( (select.val() > 1) && (select.val() < 14) )
return validateFIB();
if ( select.val() == 1 )
return validateAge();
});
select.focus(function() {
$("#FIBError").text("");
$("#ageError").text("");
});
$("#poemDataCollector_msgFIB").focus(function() {
$("#FIBError").text("");
$("#ageError").text("");
});
});
</script>
{% endif %}
And the controller :
public function collectPoemDataAction() {
/* Collector collects all the data inputs */
$collector = $this->get('yop.poem.datacollector');
$flow = $this->get('yop.form.flow.poemDataCollector');
$flow->bind($collector);
$webpageVerseSelector = $this->get('yop.poem.webpageVerseSelector');
$customizer = $this->get('yop.poem.poemcustomizer');
$form = $flow->createForm($collector);
if ($flow->isValid($form)) {
$flow->saveCurrentStepData();
echo 'en haut <br>';
echo "current step : ".$flow->getCurrentStep().'<br>';
echo "requested transition : ".$flow->getRequestedTransition().'<br>';
echo "requested step : ".$flow->getRequestedStep().'<br>';
echo 'determine current step : '.$flow->determineCurrentStep().'<br>';
if ($flow->nextStep()) {
var_dump($form->getErrors());
echo 'en haut aprรจs nextStep() <br>';
echo "current step : ".$flow->getCurrentStep().'<br>';
echo "requested transition : ".$flow->getRequestedTransition().'<br>';
echo "requested step : ".$flow->getRequestedStep().'<br>';
echo 'determine current step : '.$flow->determineCurrentStep().'<br>';
if (!$flow->isStepDone(5)) {
$webpageVerseSelector->selectWebpageVerse($flow->getCurrentStep());
$customizedWebpageVerse = new CustomizedWebpageVerse();
$customizedWebpageVerse = $customizer->customizePoem($customizedWebpageVerse, $webpageVerseSelector);
return $this->render('YOPYourOwnPoetBundle:thePoet:collectPoemData.html.twig',array(
'form' => $flow->createForm($collector)->createView(),
'flow' => $flow,
'webpageVerse' => $customizedWebpageVerse,
'dataCollector' => $collector,
));
}else {
$flow->setCurrentStep(6);
if ( $collector->getTraitCategory()->getFillInBlank())
{
$stringTool = new StringTools();
$collector->setTraitCtgWithFIBName($stringTool->buildCtgName($collector->getTraitCategory()->getName(), $collector->getTraitFIB()));
unset($stringTool);
}
if ( $collector->getMsgCategory()->getFillInBlank())
{
$stringTool = new StringTools();
$collector->setMsgCtgWithFIBName($stringTool->buildCtgName($collector->getMsgCategory()->getName(), $collector->getMsgFIB()), $this->getDoctrine());
unset($stringTool);
}
if ( $collector->getMsgCategory()->getAgeFIB())
{
$stringTool = new StringTools();
$ageObject = $this->getDoctrine()
->getRepository('YOPYourOwnPoetBundle:AgeDictionary')
->find($collector->getAge());
$collector->setMsgCtgWithAgeName($stringTool->buildCtgName($collector->getMsgCategory()->getName(), $ageObject->getAdjectiveString()));
unset($stringTool);
unset ($ageObject);
}
/* if user is connected, we have not collected sender name and sex,
* we have to get it from the user
*/
$user = $this->container->get('security.context')->getToken()->getUser();
if ($user instanceof User AND is_object($user))
{
$collector->setUserName($user->getName());
$collector->setUserSex($user->getSex());
}
/* before reviewing poem, set poem title, closing and ps in collector */
$collector->setPoemTitle($customizer->getCustomizedTitle());
$collector->setPoemClosing($customizer->getCustomizedClosing());
$collector->setPoemPS($customizer->getCustomizedPS());
return $this->render('YOPYourOwnPoetBundle:thePoet:reviewPoem.html.twig',array(
'form' => $flow->createForm($collector)->createView(),
'flow' => $flow,
'dataCollector' => $collector,
));
}
} else {
/* collector calculates the number of syllables
* in some values collected : name, city and relationship
*/
$collector->calculateSlb();
/* selector selects which raw verses will be used */
$selector = $this->get('yop.poem.verseSelector');
/* check in cookies if have a last verse for the category we want
* set selector if we find cookies
*/
$request = $this->get('request');
if ($request->cookies->has('lastIntroID')) {
$introVerse = $this->getDoctrine()
->getRepository('YOPYourOwnPoetBundle:IntroVerse')
->find($request->cookies->get('lastIntroID'));
$selector->setIntroVerse($introVerse);
}
if ($request->cookies->has('lastMsg'.str_replace(" ", "",$collector->getMsgCategory()->getName()).'ID')) {
$messageVerse = $this->getDoctrine()
->getRepository('YOPYourOwnPoetBundle:MessageVerse')
->find($request->cookies->get('lastMsg'.str_replace(" ", "",$collector->getMsgCategory()->getName()).'ID'));
$selector->setMessageVerse($messageVerse);
}
if ($request->cookies->has('lastTrait'.str_replace(" ", "",$collector->getTraitCategory()->getName()).'ID')) {
$traitVerse = $this->getDoctrine()
->getRepository('YOPYourOwnPoetBundle:TraitVerse')
->find($request->cookies->get('lastTrait'.str_replace(" ", "",$collector->getTraitCategory()->getName()).'ID'));
$selector->setTraitVerse($traitVerse);
}
/* select verses for the poem (and test if everything goes well */
if ($selector->selectVerses() == NULL)
throw new \Exception('Quilly was unable to create this poem for you...');
/* customizer customizes the raw poem with data
* input by the visitor to make the real poem
*/
$customizedPoem = new CustomizedPoem();
$customizedPoem = $customizer->customizePoem($customizedPoem);
/* store data collected from user in session
* to be able to reuse after page change
*/
$session = $request->getSession();
$session->set('name', $collector->getName());
$session->set('nameNbSlb', $collector->getNameNbSlb());
$session->set('sex', $collector->getSex());
$session->set('city', $collector->getCity());
$session->set('cityNbSlb', $collector->getCityNbSlb());
$session->set('relationship', $collector->getRelationship());
$session->set('relationshipFIB', $collector->getRelationshipFIB());
$session->set('relationshipNbSlb', $collector->getRelationshipNbSlb());
$session->set('traitID', $collector->getTraitCategory()->getId());
//$session->set('trait', $collector->getTraitCategory());
if ($collector->getTraitFIB()) {
$session->set('traitFIB', $collector->getTraitFIB());
}
//$session->set('message', $collector->getMsgCategory());
$session->set('messageID', $collector->getMsgCategory()->getId());
if ($collector->getMsgFIB()) {
$session->set('msgFIB', $collector->getMsgFIB());
}
if ($collector->getAge()) {
$session->set('msgAge', $collector->getAge());
}
/* store last verses seen by user in cookies */
$response = new Response();
$stringTool = new StringTools();
$response->headers->setCookie(new Cookie('lastIntroID', $selector->getIntroVerse()->getID(), time() + 3600 * 24 * 7));
$response->headers->setCookie(new Cookie('lastMsg'.$stringTool->cookieRename($collector->getMsgCategory()->getName()).'ID', $selector->getMessageVerse()->getID(), time() + 3600 * 24 * 7));
$response->headers->setCookie(new Cookie('lastTrait'.$stringTool->cookieRename($collector->getTraitCategory()->getName()).'ID', $selector->getTraitVerse()->getID(), time() + 3600 * 24 * 7));
$response->send();
unset($stringTool);
$flow->reset();
$changeIntroLink = $this->get('router')->generate('poem_change_intro');
$changeMessageLink = $this->get('router')->generate('poem_change_message');
$changeTraitLink = $this->get('router')->generate('poem_change_trait');
return $this->render('YOPYourOwnPoetBundle:thePoet:showPoem.html.twig',array(
'poem' => $customizedPoem,
'dataCollector' => $collector,
'changeIntroVerseLink' => $changeIntroLink,
'changeMessageVerseLink' => $changeMessageLink,
'changeTraitVerseLink' => $changeTraitLink,
));
}
}
var_dump($form->getErrors());
echo 'en bas <br>';
echo "current step : ".$flow->getCurrentStep().'<br>';
echo "requested transition : ".$flow->getRequestedTransition().'<br>';
echo "requested step : ".$flow->getRequestedStep().'<br>';
echo 'determine current step : '.$flow->determineCurrentStep().'<br>';
if ( $flow->getRequestedTransition() == 'reset' OR !$flow->isStepDone(1))
/* start data collection process from start again */
{
$webpageVerseSelector->selectWebpageVerse(1);
$customizedWebpageVerse = new CustomizedWebpageVerse();
$customizedWebpageVerse = $customizer->customizePoem($customizedWebpageVerse, $webpageVerseSelector);
return $this->render('YOPYourOwnPoetBundle:thePoet:collectPoemData.html.twig',array(
'form' => $flow->createForm($collector)->createView(),
'flow' => $flow,
'webpageVerse' => $customizedWebpageVerse,
'dataCollector' => $collector,
));
}
if ($flow->isStepDone(5) && $flow->determineCurrentStep() < 6) {
return $this->render('YOPYourOwnPoetBundle:thePoet:modifyPoemData.html.twig',array(
'form' => $flow->createForm($collector)->createView(),
'flow' => $flow,
'dataCollector' => $collector,
));
}
if ($flow->determineCurrentStep() == 6) {
return $this->render('YOPYourOwnPoetBundle:thePoet:reviewPoem.html.twig',array(
'form' => $flow->createForm($collector)->createView(),
'flow' => $flow,
'dataCollector' => $collector,
));
}
$flow->reset();
/* if commands arrive here, a problem has occured, throw error */
throw new \Exception('Quilly got confused.');
}
I have a scenario where I have a URL (/albums/edit/1
) and when I click on a specific step in my form flow it doesn't pass the ID ( in this case 1) into the route, and I receive an error from symfony2 saying that there is a missing parameter ID for the route.
In stepList_content.html.twig
shouldn't the code below take care of this?
<a href="{{ path(app.request.attributes.get('_route'),
app.request.query.all | craue_addDynamicStepNavigationParameter(flow, loop.index)) }}">
Is there a way to configure things to make the "BACK" button save the form data in a multi-step form?
I have users complaining that the form works fine when they click on "NEXT" but they lose their data when they hit the back button.
Hi there.
I'm currently struggling to find a solution for the following: I want to change the underlying entity of a form based on a step in the flow, e.g. due to usage of class table inheritance (CTI).
Has anyone done this yet?
Wondering if CraueFormFlowBundle would make sense for complex tabbed forms that don't require an input order. For example, a complex "Contact" form with different tabs/pages for personal and work details.
Or would FormFlow be overkill perhaps?
Why is it that i get this issue when i jump to a phase? The following is controller code i have written to jump to a particular step.
$flow = $this->get('experiment.form.flow');
$formData = new FormData();
//reset flow if previous was saved
$flow->reset();
//jump to required phase
for($i = 1; $i < $phaseid; $i++)
{
if($flow->nextStep())
{
$form = $flow->createForm();
}
}
$flow->bind($formData);
$form = $flow->createForm();
if ($this->getRequest()->getMethod() == 'POST') {
if ($flow->isValid($form)) {
$flow->saveCurrentStepData($form);
if ($flow->nextStep()) {
// create form for next step
$form = $flow->createForm();
} else {
// flow finished
$flow->reset();
return $this->redirect($this->generateUrl('home'));
}
}
}
$template = $flow->loadPhaseView($flow->getCurrentStepNumber());
return $this->render(
$template,
array(
'form' => $form->createView(),
'flow' => $flow,
'formData' => $formData,
)
);
return $this->render(ExperimentBundle:Default:flow.html.twig');
If you steps through the end (make some data changes) and steps back, for example to the first, all data are shown there correct.
Then if you goes forward again, all changes (from first run through) are lost between steps.
How to keep my changed data alive by walking for- and backward?
Try your self at: http://www.craue.de/sf2playground/en/CraueFormFlow/create-topic/
Hi,
I've created a 4 form step.
Everything is fine until i have to fill a tinyMCE input, when i try to submit the form (the tinymce input is in the last step) it tells me :
An invalid form control with name='offer_create[description]' is not focusable.
My HTML looks like :
http://screencast.com/t/6GMVERK7yd6
Any idea?
I have a UniqueEntity
-validation in one of my entities. However, this validation is not triggered by the flow. Anything special about it or have I overlooked something?
Entity.php:
<?php
// ... namespace, use etc.
/**
* @UniqueEntity(fields={"name", "type"}, message="Artist already available.")
*/
// ... rest of the class
I'm suffering from a very bizarre issue. I have created a three step flow using separate form types. With no validation rules applied I could move between the forms without issue, but once I wrote my validation something strange started happening.
I can submit valid data to step1, seemingly without issue. Step 2 is displayed, and the data from step 1 is in session. When I submit valid data to step 2, I get an exception from one of the validators in step 1, because apparently the data for step 1 was not loaded. What was loaded was an empty array, and an inspection of the session shows that the collection of form data has disappeared. Other data that I have manually entered into session using the Symfony session object is still present however.
After a lot of investigation I decided to put a call to debug-print-backtrace() into Craue\FormFlowBundle\Storage\SessionStorage::remove, because I wanted to track down what was removing my data from the session:
public function remove($key) {
xdebug_print_function_stack();exit;
return $this->session->remove($key);
}
Obviously this modification has to be inserted after the initial GET request to step 1, but once done my session data persisted and all three steps of the flow completed successfully.
I have tried this several times, and every time submitting step 2 fails because step 1 data is missing. And as many attempts with the above modification have been successful.
Please can anyone suggest what is going on here?
I get the following error:
Neither the property "numberOfWheels" nor one of the methods "getNumberOfWheels()", "isNumberOfWheels()", "hasNumberOfWheels()", "__get()" or "__call()" exist and have public access in class "Lucky\RNDBundle\Form\CreateVehicleStep1Form"
I'm getting this error constantly, and having tried disabling CSRF protection and removing all of my more complicated FormType's, I'm at somewhat of a loss as to what is causing the problem.
Note: I tried removing the event listener, no change.
My FormType looks something like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
switch ($options['flowStep']) {
case 1:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function($event) use ($builder) {
$form = $event->getForm();
$review = $event->getData();
$factory = $builder->getFormFactory();
if (!$review->getDealership()) {
$field = $factory->createNamed('dealership', 'dealership');
} else {
$field = $factory->createNamed('dealership', 'hidden', $review->getDealership()->getId());
}
$form->add($field);
});
$builder->add('ratings', 'collection', array(
'type' => new Rating,
));
$builder->add('comments');
break;
case 2:
$builder->add('department', 'department');
// $builder->add('employees');
$builder->add('visited', null, array(
'label' => 'Date of visited to dealership',
'format' => 'dd-MM-yyyy',
'years' => range(date('Y'), date('Y') - 2)
));;
break;
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'ACMEDemo\ReviewBundle\Entity\Review',
'flowStep' => 1
));
}
and my template features:
{% include 'CraueFormFlowBundle:FormFlow:stepField.html.twig' %}
Any thoughts on what the problem might be?
Also, when first loading the form, getCurrentStep returns null and I get a blank form, I have to click next to get to step 1, despite my setDefaultOptions looking like:
$resolver->setDefaults(array(
'data_class' => 'ACMEDemo\ReviewBundle\Entity\Review',
'flowStep' => 1
));
Any ideas?
With the current master of symfony 2.1 and CraueFormFlowBundle i get this error:
Catchable Fatal Error: Argument 1 passed to Craue\FormFlowBundle\Form\FormFlow::setStorage()
must implement interface Craue\FormFlowBundle\Storage\StorageInterface,
instance of Symfony\Component\HttpFoundation\Session\Session given,
called in /.../app/cache/dev/appDevDebugProjectContainer.php on line 612 and
defined in /.../vendor/craue/formflow-bundle/Craue/FormFlowBundle/Form/FormFlow.php line 130
An exception doesn't make any sense to me here:
CraueFormFlowBundle/Form/FormFlow.php
Line 252 in 303eb6c
public function getCurrentStepNumber() {
if ($this->currentStepNumber === null) {
throw new \RuntimeException('The current step has not been determined yet and thus cannot be accessed.');
}
return $this->currentStepNumber;
}
Why throw an exception if a special sequence of events hasn't happened yet? Couldn't you just determineCurrentStepNumber() if currentStepNumber isn't already set?
Would this have a negative impact that I'm overlooking?
Here's my scenario I am struggling with:
Problem: after submitting form 2 I am getting "this form should not contain extra fields".
Should this scenario work with the Flow Bundle?
Hallo.
I just found your bundle and like it alot. While setting up my first flow-form I stumbled over a flaw in the documentation:
In the section "Create an action" the last four lines read:
return array(
'form' => $form->createView(),
'flow' => $flow,
);
but should be:
return $this->render('StatusFaultBundle:Fault:new.html.twig', array(
'form' => $form->createView(),
'flow' => $flow,
));
Hi,
In my form I have an choice field not mapped to entity ('mapped' => false). The method FormFlow::getFormOptions() receives $formData, but that only contains data of entity. How to get value of these field not mapped?
With regard to the subject, i want to be able to change the step button names according to the step. E.g. In step 1, button.next should be changed and in step 2, another name likewise. Is this possible?
Dynamic navigation should have all steps available for editing existing objects.
I note that the method setCurrentStep()
has been removed from the FormFlow
in the 2.x branch - is there another available method to set the step from the outside, or is one now required to add skip closures to the steps config in order to select the correct step?
I need options on my loadStepsConfig() method, in order to know which form add on the different steps. How is-it possible?
Hi,
There has been lot of changes recently in FormComponent,
See : https://github.com/symfony/symfony/blob/master/UPGRADE-2.1.md#form
Thanks.
This probably isn't useful for everyone so this isn't a feature request. Just a question for @craue -
If you were to save a model from a form flow for later opening - how would you go about populating step data from that saved model? Since step data is stored in the format it comes through as in the request, I'm not sure how to recreate this data from the model.
Thanks
Is it possible to access the entity in the twig template. It would help in order to show some data the user already entered.
I want to add a custom transition to a form flow. The one I currently require is very simple, but there might be more complex transitions. I'm not even sure, whether this is a good way to accomplish it. I got a flow, which got a step, that allows the user to choose whether to skip a defined part (multiple steps) of the flow.
Flow with custom transition:
class UpdateLeadFormFlow extends AbstractFormFlow
{
const TRANSITION_SKIP_EDITOR = 'skip_editor';
protected function loadStepsConfig()
{
$fnSkipEditor = function($step, UpdateLeadFormFlow $flow) {
return UpdateLeadFormFlow::TRANSITION_SKIP_EDITOR === $flow->getRequestedTransition();
};
return [
[
// The first step, allowing to continue with 2nd or 5th step.
],
[
// ..
'skip' => $fnSkipEditor,
],
[
// ..
'skip' => $fnSkipEditor,
],
[
// ..
'skip' => $fnSkipEditor,
],
[
// ..
],
];
}
// ...
}
The buttons.html.twig
in use:
{% trans_default_domain "leads" %}
{% set prefix = 'update_lead.form_actions.step_' ~ flow.currentStepNumber ~ '.' %}
{% if flow.currentStepNumber == flow.firstStepNumber %}
<button type="submit" class="btn btn-success btn-large pull-right" name="{{ flow.formTransitionKey }}" value="skip_editor">
<i class="icon-circle-arrow-right"></i>
{{- (prefix ~ 'skip')|trans -}}
</button>
{% endif %}
<button type="submit" class="btn btn-success btn-large pull-right">
<i class="icon-circle-arrow-right"></i>
{{- (prefix ~ 'submit')|trans -}}
</button>
{% if flow.currentStepNumber in (flow.firstStepNumber + 1) .. flow.lastStepNumber %}
<button type="submit" class="btn btn-large pull-right" name="{{ flow.formTransitionKey }}" value="back" formnovalidate>
<i class="icon-circle-arrow-left"></i>
{{- (prefix ~ 'back')|trans -}}
</button>
{% endif %}
The thing I don't like is the unnecessary coupling customization of the template for the custom transition.
I would rather like to have a collection of transitions that are available on a given step. This collection contains the two default transitions already given reset
and back
. Each step may return a collection of additional transitions to be handled.
If a transition is active, the flow will handle it, e.g. by firing an event to delegate the changes in state. This would also allow to hook into resetting a flow or going backwards.
What are your thoughts? Am I missing some key elements to accomplish what I require? Are transitions the right way?
Regarding 'Remove Reset button', Can this be done from loading in every form?
Also, if i want to load the 3rd step defined in my workflow, can that be done? If so can you show me an example?
I managed to override the button.next text as follows:
{{- 'Home' | trans({}, 'CraueFormFlowBundle') -}}
But, how would i override the back button?
Currently, it's quite cumbersome to implement a step which can be skipped depending on input from previous steps. As for now, I'm just thinking about how to improve this functionality and would like to see ideas from other developers as well. ;)
This are the current flow class and form class of the live demo:
<?php
namespace Craue\Symfony2PlaygroundBundle\Form;
use Craue\FormFlowBundle\Event\PostValidateEvent;
use Craue\FormFlowBundle\Event\PreBindEvent;
use Craue\FormFlowBundle\Form\FormFlow;
use Craue\FormFlowBundle\Form\FormFlowEvents;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class CreateTopicFlow extends FormFlow implements EventSubscriberInterface {
protected $maxSteps = 4;
/**
* {@inheritDoc}
*/
public function setEventDispatcher(EventDispatcherInterface $dispatcher) {
parent::setEventDispatcher($dispatcher);
$dispatcher->addSubscriber($this);
}
/**
* {@inheritDoc}
*/
public static function getSubscribedEvents() {
return array(
FormFlowEvents::PRE_BIND => 'onPreBind',
FormFlowEvents::POST_VALIDATE => 'onPostValidate',
);
}
/**
* {@inheritDoc}
*/
protected function loadStepDescriptions() {
return array(
'basics',
'comment',
'bug_details',
'confirmation',
);
}
/**
* {@inheritDoc}
*/
public function getFormOptions($formData, $step, array $options = array()) {
$options = parent::getFormOptions($formData, $step, $options);
$options['cascade_validation'] = true;
if ($step > 1) {
$options['isBugReport'] = $formData->isBugReport();
}
return $options;
}
/**
* {@inheritDoc}
*/
public function reset() {
parent::reset();
$this->removeTempIsBugReport();
}
/**
* {@inheritDoc}
*/
public function createForm($formData, array $options = array()) {
if ($this->currentStep === 1) {
$this->removeSkipStep(3);
}
return parent::createForm($formData, $options);
}
protected function getTempIsBugReportSessionKey() {
return $this->id . '_isBugReport';
}
protected function setTempIsBugReport() {
$this->storage->set($this->getTempIsBugReportSessionKey(), true);
}
protected function isBugReport() {
return $this->storage->get($this->getTempIsBugReportSessionKey(), false);
}
protected function removeTempIsBugReport() {
$this->storage->remove($this->getTempIsBugReportSessionKey());
}
public function onPreBind(PreBindEvent $event) {
if (!$this->isBugReport()) {
$this->addSkipStep(3);
}
}
public function onPostValidate(PostValidateEvent $event) {
$formData = $event->getFormData();
if (empty($formData)) {
return;
}
if ($this->currentStep >= 1) {
if ($formData->isBugReport()) {
$this->setTempIsBugReport();
$this->removeSkipStep(3);
} else {
$this->removeTempIsBugReport();
$this->addSkipStep(3);
}
}
}
}
<?php
namespace Craue\Symfony2PlaygroundBundle\Form;
use Craue\Symfony2PlaygroundBundle\Entity\Topic;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CreateTopicForm extends AbstractType {
/**
* {@inheritDoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$isBugReport = $options['isBugReport'];
switch ($options['flowStep']) {
case 1:
$builder->add('title');
$builder->add('description', null, array(
'required' => false,
));
$builder->add('category', 'form_type_topicCategory');
break;
case 2:
$builder->add('comment', 'textarea', array(
'required' => false,
));
break;
case 3:
if ($isBugReport) {
$builder->add('details', 'textarea');
}
break;
}
}
/**
* {@inheritDoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'flowStep' => 1,
'data' => new Topic(),
'isBugReport' => false,
));
}
/**
* {@inheritDoc}
*/
public function getName() {
return 'createTopic';
}
}
I have a form with two steps and I need the user object to build the form in the first step.
In the controller I pass the user to the form.
$form = $flow->createForm($entity, array('user' => $user));
I set the option in the form type
public function getDefaultOptions(array $options) { $options = parent::getDefaultOptions($options); $options['flowStep'] = 1; $options['data_class'] = 'MyBundle\Entity\MyEntity'; $options['user'] = null; return $options; }
and I retrieve it in the buildForm function
public function buildForm(FormBuilder $builder, array $options) { $user = $options['user']; switch ($options['flowStep']) { ...
It works at the beginning, but if in the second step I click on the back button, I get an error because the user is not in options anymore.
How can I do that?
Thank you.
The flow class has several methods who act as events at the moment. What about moving replacing these methods with real events which can be triggered by the listener (who needs to be injected)? This would be a good move in terms of separation of concerns.
Hello, I'm trying to implement FormFlowBundle on my Product registration.
This form must be contain two steps.
I did all as the documentation says, but I'm got "This form should not contain extra fields" on the last step. All things are fine till I click in the finish button, then my form back to the first step with all fields in default value and with this error messages. This messagem appear one time to each step. In this case two times.
Any ideas?
Best regards,
Guilherme
Hi there.
I wanted to encapsulate my flow handling into an form handler to remove duplicate code I had in my controller.
Unfortunately I was not successful but got strange errors.
The formhandler consists only of the flow, entity manager and router. All injected via the constructor from the DIC. The logic from the controller is capsuled in an method dealing with all the stuff like binding, validating etc.
But it doesn't work. I always end up having errors after submitting the first step:
Method "titles" for object "Symfony\Component\Form\FormView" does not exist in FmdbReleaseBundle:Release:form.html.twig at line 34
Copying the logic back to the controller everything works fine.
Has anyone ever done something like this before?
Marcus
What's new? Are there performance enhancements and new features, or mainly just semantic changes?
These would be helpful in deciding if it's worth the upgrade. Upgrade help content would also be great.
My goal is to have the identifier of my entity after the first step.
I try this:
In my flow:
public function saveCurrentStepData($id = null) {
$stepData = $this->retrieveStepData();
$stepData[$this->currentStep] = $this->request->request->get($this->formType->getName(), array());
if ($id != null) {
$stepData[$this->currentStep]['id'] = $id;
}
$this->saveStepData($stepData);
}
In my controller:
if ($flow->getCurrentStep() == 1 && $user->getId() == null) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($user);
$em->flush();
}
$flow->saveCurrentStepData($user->getId());
I've add $builder->add('id', 'hidden');
too in every steps.
It works except for one case, when I reload the second page (F5).
It works for these cases:
Step 1 -> Step 2 -> Step 3 -> Step 2 -> Step 2(F5)
Step 1 -> Step 2 -> Step 1 -> Step 2 -> Step 2(F5)
It doesn't work for this case:
Step 1 -> Step 2 -> Step 2(F5)
Each time the step 2 is reload with F5, A new identifier is create.
Do you have any idea? I think this is a problem of session.
With my code, the identifier is store in session with the first call of the Step 2, but if we reload the step 2, I suppose the data session is not keep.
Hi,
I needed a particular field to be check with my own logic. So I created a Validator Callback.
I was wondering why my validator callback is not called using form flow :
<?php
* @ORM\Table(name="team")
* @ORM\Entity(repositoryClass="...\Entity\TeamRepository")
* @Gedmo\SoftDeleteable(fieldName="deletedAt")
* @Assert\Callback(methods={"isLevelValid"})
Class Team
{
public function isLevelValid(ExecutionContext $context)
{
die('not dying ?');
}
}
If you got any clues,
Thanks :-)
I have a three-step form:
I have the normal NEXT/BACK buttons working fine. It also works fine with the FINISH button if the card is approved.
However, I cannot figure out how to return to Step 2 if the credit card is declined after hitting the "FINISH" button.
I want to be able to return to step 2 and display the form data, along with an error message (so the user can retry their card).
There may be an easy way to do this, but I have not been able to figure it out. Ideally, I would like a setCurrentStepNumber()
method that would work something like this:
$formData = new OrderData();
$flow = $this->get('myCompany.form.flow.placeOrder');
$flow->bind($formData);
$form = $flow->createForm();
if ($flow->isValid($form)) {
$flow->saveCurrentStepData($form);
if ($flow->nextStep()) {
$form = $flow->createForm();
} else {
$result = $this->processOrder($formData);
if ($result == "OK") {
$flow->reset();
return $this->redirect($this->generateUrl('sale_completed'));
} else {
$flow->setCurrentStepNumber(2); // return to step 2
$this->get('session')->getFlashBag()->add('flash-notice', 'Card Declined');
}
}
}
Is it possible to populate a single entities fields throughout multiple steps? I found when I tried to use the same entity on 2 steps, the entities values that got set on the first step got set to NULL once the second step was submitted.
How do i load a particular step number from the flow and continue from there? (Without having to start from the beginning). Possible code?
Hi, I want to upload an image in the first step and then show this image in the second step. (In fact my real goal is to let the user crop and resize the image in the second step)
So I have to create a temporaly file. Remove every unused temporaly files when the image is changed or the flow is reseted.
What do you think about my code ? Do you think there is something simplier than my code ?
Maybe we can add a common code to handle upload file directly with the bundle. Plus I note that files are not stored in the session with the bind method.
Note: I use PHP 5.4
services.xml
<service id="MyProject.form.addNews"
class="MyProject\BackBundle\Form\AddNewsFormType">
<tag name="form.type" alias="addNews" />
</service>
<service id="MyProject.form.flow.addNews"
class="MyProject\BackBundle\Form\AddNewsFlow"
parent="craue.form.flow"
scope="request">
<call method="setFormType">
<argument type="service" id="MyProject.form.addNews" />
</call>
<call method="setEntityManager">
<argument type="service" id="doctrine.orm.entity_manager" />
</call>
</service>
FormFlow
class AddNewsFlow extends FormFlow
{
protected $maxSteps = 2;
protected $allowDynamicStepNavigation = true;
protected $em;
protected function loadStepDescriptions()
{
return [
'Step 1',
'Step 2',
];
}
public function setEntityManager(EntityManager $em)
{
$this->em = $em;
}
public function reset()
{
$image = $this->storage->get($this->stepDataKey.'_image', []);
if (!empty($image)) {
$tempFile = $this->em->getRepository('MyProjectBackBundle:TempFile')->find($image['id']);
if ($tempFile !== null) {
$this->em->remove($tempFile);
$this->em->flush();
}
$this->storage->remove($this->stepDataKey.'_image');
}
parent::reset();
}
}
FormType
class AddNewsFormType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
switch ($options['flowStep']) {
case 1:
// Avoid to ask the user to reload an image if the first step is display again
$builder->add('image', 'file', ['required' => $options['image_required']]);
break;
case 2:
$builder->add('X','integer', ['mapped' => false, 'attr' => array('value' => 0)]);
$builder->add('Y','integer', ['mapped' => false, 'attr' => array('value' => 0)]);
$builder->add('Zoom','integer', ['mapped' => false, 'attr' => array('value' => 0)]);
break;
break;
}
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'flowStep' => 1,
'data_class' => 'MyProject\CoreBundle\Entity\News', // should point to your user entity
'image_required' => true
));
}
public function getName() {
return 'addNews';
}
}
Template add.html.twig
<div>
Steps:
{% include 'CraueFormFlowBundle:FormFlow:stepList.html.twig' %}
</div>
<form method="post" {{ form_enctype(form) }}>
{% include 'CraueFormFlowBundle:FormFlow:stepField.html.twig' %}
{{ form_errors(form) }}
{{ form_rest(form) }}
{% if image is defined and flow.getCurrentStep() <= 2 %}
{% if flow.getCurrentStep() == 1 %}
{# Preview of the current image when you return the the step 1 #}
<img src="{{ image }}" width="200" />
{% else %}
{# show a big image to perform the crop and resize #}
<img src="{{ image }}" width="940" />
{% endif %}
{% endif %}
{% include 'CraueFormFlowBundle:FormFlow:buttons.html.twig' %}
</form>
And the controller:
public function addAction(Request $request)
{
$news = new News();
$flow = $this->get('MyProject.form.flow.addNews'); // must match the flow's service id
$flow->bind($news);
$storage = $flow->getStorage();
$image = $storage->get($flow->getStepDataKey().'_image', ['webpath' => null]);
// form of the current step
$form = $flow->createForm($news, ['image_required' => !isset($image['id'])]);
if ($flow->isValid($form)) {
$flow->saveCurrentStepData();
if ($flow->getCurrentStep() == 1) {
if (null !== $file = $request->files->get('addNews')['image']) {
if (!empty($image)) {
$oldTempFile = $this->getRepository('MyProjectBackBundle:TempFile')->find($image['id']));
if ($oldTempFile !== null) {
$em = $this->getDoctrine()->getEntityManager();
$em->remove($oldTempFile);
$em->flush();
}
}
$tempFile = new TempFile($file);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($tempFile);
$em->flush();
$image = [
'id' => $tempFile->getId(),
'path' => $tempFile->getPath(),
'webpath' => $tempFile->getWebPath(),
'name' => $tempFile->getFilename()
];
$storage->set($flow->getStepDataKey().'_image', $image);
}
}
if ($flow->nextStep()) {
// form for the next step
$form = $flow->createForm($news, ['image_required' => !isset($image['id'])]);
} else {
// flow finished
$target = $news->getImageDir().$image['name'];
if (!@rename($image['path'], $target)) {
// error
}
@chmod($target, 0666 & ~umask());
$news->setImage($image['name']);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($news);
$em->flush();
return $this->redirect($this->generateUrl('home')); // redirect when done
}
}
return [
'form' => $form->createView(),
'flow' => $flow,
'image' => $image['webpath']
];
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.