Skip to content

[2.2] [Form] Enhanced DateType with support for different widgets types per date part (year, month, day) #3825

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

ruimarinho
Copy link

Bug fix: yes
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: Build status
Fixes the following tickets: #3490
Todo: discuss with @bschussek if it is possible to add support for padded dates per date type (not just globally, as it is now).

The DateType widget is incredibly powerful it offers developers a wealth of features and customization options. This PR builds on this work and adds some extra flexibility to this widget, without compromising BC:

Features:

  • The widget option now accepts an array for year, month and day date parts, which allows you to have something like a choice widget for month and text widgets for year and day. It defaults to choice if any of those parts is missing when using the array format.
  • Developers can now pass options for each of the date parts widgets - year_options, month_options and day_options. A typical use case is passing an attribute like data-mask to control user input client side using JavaScript masks.
  • Choose to pad dates or not - currently, dates are always displayed with the padded format (see issue [Form] Stop automatic left '0' str_padding on the 'days' field on a date choice widget. #3490), although this behavior is not really consistent since the DateTimeToArrayTransformer defaults to not pad dates. What happens is that when using choice widgets, the value is casted to int and it removes the padding, but the representation on the HTML still shows the padded date. This PR changes this by passing the correct padding option to the transform so that the output is either completely padded or not (both the value and its representation). This can be passed through the widget options option (padded: boolean).

Tests were added for these new features.

@webmozart
Copy link
Contributor

Interesting ideas! Scheduled for POST-2.1.

@fabpot
Copy link
Member

fabpot commented Jul 10, 2012

@ruimarinho The padded option must be removed as the issue in #3490 has now been resolved.

@ruimarinho
Copy link
Author

@fabpot, I'm going to setup a quick test page since the project where we needed this is running 2.1-dev and, since it's a seasonal project, it's not going to be upgraded to 2.1 beta or even 2.1 stable. I'll remove the padded option, test it and submit this with a new commit.

@stof
Copy link
Member

stof commented Oct 13, 2012

@ruimarinho Can you rebase this PR and update it according to the comments ?

@ruimarinho
Copy link
Author

@stof done - code fixed, including tests which have been rectified and are now passing. Removed the padded option which allowed me to keep this PR much simpler.

I'm going to add a new test case tomorrow to assert the validation of the widgets defined via the array notation. I look forward for your comments to see if I can improve that small code block using solely the OptionsResolver already available.

@fabpot
Copy link
Member

fabpot commented Dec 11, 2012

@bschussek Can we included this in 2.2?

@webmozart
Copy link
Contributor

@fabpot Yes we can, I really like this.

@ruimarinho Can you move the normalization of the "widget" option to an option normalizer? Basically you can do it the same way as in the $emptyValueNormalizer in DateType.

Furthermore, if we do this for DateType, we also need to implement the same features for DateTimeType and TimeType. Could add this?

@webmozart
Copy link
Contributor

To be precise, the same should be added to TimeType only. DateTimeType should then support the options date_options and time_options which are passed along to the date and time fields.

@ruimarinho
Copy link
Author

@bschussek I have moved the widget option processing to the normalizer and added the TimeType implementation counterpart. The normalizers share much of the same functionality, so maybe that can be improved.

Regarding DateTimeType, what do you think is the best approach to pass the widget options? You mention date_options and time_options, but I suppose those would eventually become ($part}_options on each widget?

An example of what I have in mind:

$builder->add('date', 'datetime', array(
    'date_widget'  => array(
        'year'  => 'text', 
        'month' => 'text', 
        'day'   => 'choice'
    ),
    'date_options' => array(
        'year' => array('attr' => array(
            'data-mask' => 'yyyy'
        ))
    )
));

$partOptions[$passOpt] = $options[$passOpt];
}

$builder->add($part, $widget, $partOptions);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this code change have any benefit? I think it is more complicated to read than the previous version and does not really add anything.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be more elegant to simply split the original if into three if-statements:

if ('choice' === $options['widget']['year']) {
    $yearOptions['choices'] = $this->formatTimestamps($formatter, '/y+/', $this->listYears($options['years']));
    $yearOptions['empty_value'] = $options['empty_value']['year'];
}
if ('choice' === $options['widget']['year']) {
    $monthOptions['choices'] = $this->formatTimestamps($formatter, '/M+/', $this->listMonths($options['months']));
    $monthOptions['empty_value'] = $options['empty_value']['month'];
}
if ('choice' === $options['widget']['year']) {
    $dayOptions['choices'] = $this->formatTimestamps($formatter, '/d+/', $this->listDays($options['days']));
    $dayOptions['empty_value'] = $options['empty_value']['day'];
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking to avoid verbosity, but I guess at the cost of clarity. Reverted back to original if-statements structure.

@webmozart
Copy link
Contributor

Your last question is a good one. Apparently we currently use several different ways of passing options to fields:

(a) By introducing dedicated options

$builder->add('date', 'datetime', array(
    'date_widget'  => ...,
));

(b) By letting existing options accept arrays

$builder->add('date', 'date', array(
    'empty_value'  => array(
        'day' => ...,
    ),
));

(c) By letting the user pass all options himself

$builder->add('password', 'repeated', array(
    'first_options'  => ...,
));

I think we should clean this up, but the question is how.

@webmozart
Copy link
Contributor

The most efficient and flexible way is definitely (c), but it clutters up the API a bit.

So instead of

$builder->add('date', 'datetime', array(
    'date_widget'  => 'choice',
));

you'd then write

$builder->add('date', 'datetime', array(
    'date_options'  => array(
        'widget' => 'choice',
    ),
));

or

$builder->add('date', 'datetime', array(
    'date_options'  => array(
        'month_options' => array(
            'widget' => 'choice',
        ),
    ),
));

if you want to have even more fine-grained control, which is a little less concise than (a theoretical)

$builder->add('date', 'datetime', array(
    'widget'  => array(
        'date' => array(
            'month' => 'choice',
        ),
    ),
));

or even

$builder->add('date', 'datetime', array(
    'widget'  => array(
        'month' => 'choice',
    ),
));

What do you think?

@ruimarinho
Copy link
Author

I actually noticed that and I even got confused when accessing the values to merge the widget part options (year_options, etc).

Regarding the different options you presented:

a) Looks concise and practical, but not very extensible. As the widgets get more complex, the harder it gets to maintain this facade.

b) This option requires you to learn about which options can accept an array structure. Unless we can assure we always offer support for this kind of structure, I don't think it is as practical as option a). Plus, on the DateTime widget, the key would either have to be date_day or a nested array.

c) More verbose but also more explicit. Users should not have to learn anything new if they already know how to use the Date or Time widgets. Less practical (more nesting) but more flexible.

A more complex example that didn't feel awkward to write:

$builder->add('date', 'datetime', array(
    'date' => array(
        'widget' => array(
            'year'  => 'text',
            'month' => 'text',
            'day'   => 'choice'
        ),
        'options' => array(
            'year' => array(
                'attr' => array(
                    'data-mask'  => 'yyyy'
                )
            ),
            'month' => array(
                'attr' => array(
                    'data-month' => 'MM'
                )
            ),
            'day' => array(
                'attr' => array(
                    'data-day'   => 'dd'
                )
            )
        )
    ),
    'time' => array(
        'widget' => array(
            'hour'    => 'text',
            'minute'  => 'choice',
            'seconds' => 'choice'
        ),
        'hours'       => range(0, 12),
        'empty_value' => array(
            'hour'    => 10,
            'minute'  => 20,
            'seconds' => 30
        )
    )
));

I also think hours should be moved inside another key (data, initial_value, available_value, choice_value, ...) to visually reassemble the singular part definition. For instance:

$builder->add('time', 'time', array(
    'data' => array(
        'hour' => range(0, 12)
    )
));

What do you think?

@webmozart
Copy link
Contributor

Let's save the question about the hours option for a later PR.

Your API sample is ambiguous. First, you renamed *_options to *, e.g., date instead of date_options, year instead of year_options etc. This can be a good thing, but it breaks consistency with RepeatedType.

Second, you nested a key options inside date (which originally was date_options) and that obviously doesn't make sense.

I'm alright with solution c) but we need to decide whether to name the options date or date_options, and then we need to decide how to cascade this pattern in the other compound core types (i.e. types consisting of more than one field).

@webmozart
Copy link
Contributor

Closed in favor of #9177

@webmozart webmozart closed this Sep 30, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants