Crud: [Feature] Ability to define date_range (nested) fields inside "table" field

Created on 10 Feb 2018  路  2Comments  路  Source: Laravel-Backpack/CRUD

Bug report

What I did:

I followed instructions of @fabriciolangermt in issue #463 and created a child field in order to create a Master-Details form. Everything works great for text, number, select and hidden, but when I tried to create a child_date_range field type, I do not get the data back the the server.

What I expected to happen:

I expected that the values that I select would be passed back to the server in order to store them

What happened:

The previous values of the model were passed for the dates while the rest of the fields have the values that I changed in the form.

What I've already tried to fix it:

  • Values do not pass back to the server even if I use text instead of hidden inputs to store that values selected for the date range. They are only passed if I change their values by hand directly in those text fields
  • I tried to trigger events in the fields: focus, blur, input, keydown, keyup, keypress change
  • I tried to create child_datetime_picker but I had exactly the same problem

Backpack, Laravel, PHP, DB version:

  • Backpack/Crud: 3.3
  • Laravel: 5.5
  • PHP 7.0.0
  • DB: PostgreSQL

Resources:

  • The definition of my child field is the following
        // Use of flags on where libraries have been loaded for the child field types
        $this->crud->child_resource_included = ['select' => false, 'number' => false, "date_range" => false, "hidden" => false];
       // Definition of the child field for Tariffs
        $this->crud->addField([// Table
          'name' => 'fake_tariffs',
          'label' => 'Tariffs',
          'type' => 'child',
          'entity_singular' => 'Tariff', // used on the "Add X" button
          'columns' => [
            [
              'label' => "Id",
              'type' => "child_hidden",
              'name' => 'id',
              "default" => "NEW"
            ],
            [
              'label' => "Name",
              'type' => "child_text",
              'name' => 'name'
            ],
            [
              'label' => "Price",
              'type' => "child_number",
              'name' => 'price',
              "prefix" => "€",
              "suffix" => "per night",
              "attributes" => ["step" => "0.01", "min" => 0],
              "default" => 0
            ],
            [
              'name' => 'tariff_date_range', // a unique name for this field
              'start_name' => 'date_from', // the db column that holds the start_date
              'end_name' => 'date_to', // the db column that holds the end_date
              'label' => 'Date Range',
              'type' => 'child_date_range',
              // OPTIONALS
              'start_default' => date("Y-m-d H:i:s"), // default value for start_date
              'end_default' => date("Y-m-d H:i:s"), // default value for end_date
              'date_range_options' => [// options sent to daterangepicker.js
                'timePicker' => true,
                'locale' => ['format' => 'DD/MM/YYYY HH:mm']
              ]
            ]
          ],
          'max' => 25, // maximum rows allowed in the table
          'min' => 0, // minimum rows allowed in the table
        ]);
  • The blades that have been created and placed in the resources/views/vendor/backpack/crud/fields/ directory are included in the following zip:
    child_field_types.zip
Ask-It-On-Stack-Overflow

Most helpful comment

Hi @Gorbas ,

As @fabriciolangermt mentioned, there's currently no quick way to do this, other than creating a custom field type. @fabriciolangermt 's code should help you get started with that.

We do plan on making this A LOT easier in the future, we call it the matrix field, and it should allow you to use ANY type of field inside a table. But we're quite far away from achieving this, some core changes need to be made in order for that to be possible. There are plenty of issues that have not been resolved (ex: how to retrigger field JS after validation, how to show fields without labels in the table, etc). I'll keep you guys updated on this in the newsletter - it will definitely be time to celebrate when we implement this :-) It's my favorite big feature.

Thanks, cheers!

All 2 comments

you should create your implementation for this, I did for date_picker and some others, use as an example:

CrudController

['label' => trans('app.dt_tour'),
'type' => 'child_date_picker',
'name' => 'dt_tour',
'date_picker_options' => [
'todayBtn' => 'linked',
'format' => 'dd/mm/yyyy',
'language' => 'pt-BR',
'todayHighlight' => 'true',
],
'attributes' => ['convert-to-date' => ''],
],

table field implementation (my child.blade.php)



$max = isset($field['max']) && (int) $field['max'] > 0 ? $field['max'] : -1;
$min = isset($field['min']) && (int) $field['min'] > 0 ? $field['min'] : -1;
$item_name = strtolower(isset($field['entity_singular']) && !empty($field['entity_singular']) ? $field['entity_singular'] : $field['label']);

$items = old($field['name']) ? (old($field['name'])) : (isset($field['value']) ? ($field['value']) : (isset($field['default']) ? ($field['default']) : '' ));

// make sure not matter the attribute casting
// the $items variable contains a properly defined JSON
if (is_array($items)) {
    if (count($items)) {
        $items = json_encode($items);
    } else {
        $items = '[]';
    }
} elseif (is_string($items) && !is_array(json_decode($items))) {
    $items = '[]';
}

?>

@include('crud::inc.field_translatable_icon')
@foreach( $field['columns'] as $column ) @endforeach@foreach ($field['columns'] as $column) @endforeach
{{-- --}} {{-- --}}
sort item
{{-- HINT --}} @if (isset($field['hint']))

{!! $field['hint'] !!}

@endif

{{-- ########################################## --}}
{{-- Extra CSS and JS for this particular field --}}
{{-- If a field type is shown multiple times on a form, the CSS and JS will only be loaded once --}}
@if ($crud->checkIfFieldIsFirstOfItsType($field, $fields))

{{-- FIELD CSS - will be loaded in the after_styles section --}}
@push('crud_fields_styles')
{{-- @push('crud_fields_styles')
    {{-- YOUR CSS HERE --}}
@endpush

{{-- FIELD JS - will be loaded in the after_scripts section --}}

@push('crud_fields_scripts')
{{-- YOUR JS HERE --}}



@stack('child_after_scripts')

@endpush
@endif
{{-- End of Extra CSS and JS --}}
{{-- ########################################## --}}

child_date_picker.blade.php

// if the column has been cast to Carbon or Date (using attribute casting)
// get the value as a date string
if (isset($field['value']) && ( $field['value'] instanceof CarbonCarbon || $field['value'] instanceof JenssegersDateDate )) {
$field['value'] = $field['value']->format('Y-m-d');
}

$field_language = isset($field['date_picker_options']['language'])?$field['date_picker_options']['language']:\App::getLocale();

?>

@include('crud::inc.field_translatable_icon')
{{-- HINT --}} @if (isset($field['hint']))

{!! $field['hint'] !!}

@endif

$include_resource = true;
if (isset($crud->child_resource_included)) {
if (is_array($crud->child_resource_included)) {
if (in_array($crud->child_resource_included, 'child_date_picker')) {
if ($crud->child_resource_included['child_date_picker']) {
$include_resource = false;
}
}
}
}
?>
@if ($include_resource)

{{-- FIELD CSS - will be loaded in the after_styles section --}}
@push('crud_fields_styles')
<link rel="stylesheet" href="{{ asset('vendor/adminlte/plugins/datepicker/datepicker3.css') }}">
@endpush

{{-- FIELD JS - will be loaded in the after_scripts section --}}
@push('crud_fields_scripts')
<script src="{{ asset('vendor/adminlte/plugins/datepicker/bootstrap-datepicker.js') }}"></script>
@if ($field_language !== 'en')
    <script charset="UTF-8" src="{{ asset('vendor/adminlte/plugins/datepicker/locales/bootstrap-datepicker.'.$field_language.'.js') }}"></script>
@endif
<script>
    if (jQuery.ui) {
        var datepicker = $.fn.datepicker.noConflict();
        $.fn.bootstrapDP = datepicker;
    } else {
        $.fn.bootstrapDP = $.fn.datepicker;
    }

    var dateFormat=function(){var a=/d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,b=/\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,c=/[^-+\dA-Z]/g,d=function(a,b){for(a=String(a),b=b||2;a.length<b;)a="0"+a;return a};return function(e,f,g){var h=dateFormat;if(1!=arguments.length||"[object String]"!=Object.prototype.toString.call(e)||/\d/.test(e)||(f=e,e=void 0),e=e?new Date(e):new Date,isNaN(e))throw SyntaxError("invalid date");f=String(h.masks[f]||f||h.masks.default),"UTC:"==f.slice(0,4)&&(f=f.slice(4),g=!0);var i=g?"getUTC":"get",j=e[i+"Date"](),k=e[i+"Day"](),l=e[i+"Month"](),m=e[i+"FullYear"](),n=e[i+"Hours"](),o=e[i+"Minutes"](),p=e[i+"Seconds"](),q=e[i+"Milliseconds"](),r=g?0:e.getTimezoneOffset(),s={d:j,dd:d(j),ddd:h.i18n.dayNames[k],dddd:h.i18n.dayNames[k+7],m:l+1,mm:d(l+1),mmm:h.i18n.monthNames[l],mmmm:h.i18n.monthNames[l+12],yy:String(m).slice(2),yyyy:m,h:n%12||12,hh:d(n%12||12),H:n,HH:d(n),M:o,MM:d(o),s:p,ss:d(p),l:d(q,3),L:d(q>99?Math.round(q/10):q),t:n<12?"a":"p",tt:n<12?"am":"pm",T:n<12?"A":"P",TT:n<12?"AM":"PM",Z:g?"UTC":(String(e).match(b)||[""]).pop().replace(c,""),o:(r>0?"-":"+")+d(100*Math.floor(Math.abs(r)/60)+Math.abs(r)%60,4),S:["th","st","nd","rd"][j%10>3?0:(j%100-j%10!=10)*j%10]};return f.replace(a,function(a){return a in s?s[a]:a.slice(1,a.length-1)})}}();dateFormat.masks={default:"ddd mmm dd yyyy HH:MM:ss",shortDate:"m/d/yy",mediumDate:"mmm d, yyyy",longDate:"mmmm d, yyyy",fullDate:"dddd, mmmm d, yyyy",shortTime:"h:MM TT",mediumTime:"h:MM:ss TT",longTime:"h:MM:ss TT Z",isoDate:"yyyy-mm-dd",isoTime:"HH:MM:ss",isoDateTime:"yyyy-mm-dd'T'HH:MM:ss",isoUtcDateTime:"UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"},dateFormat.i18n={dayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat","Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],monthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec","January","February","March","April","May","June","July","August","September","October","November","December"]},Date.prototype.format=function(a,b){return dateFormat(this,a,b)};

    jQuery(document).ready(function($){
        initDatePicker();
    });

    function initDatePicker() {
        $('[data-bs-datepicker]').each(function(){
            var $fake = $(this),
            $field = $fake.parents('.datepicker-box').find('input[type="hidden"]'),
            $customConfig = $.extend({
                format: 'dd/mm/yyyy'
            }, $fake.data('bs-datepicker'));

            var $existingVal = $field.val();

            $picker = $fake.bootstrapDP($customConfig);

            if( $existingVal != null && $existingVal.length ){
                // Passing an ISO-8601 date string (YYYY-MM-DD) to the Date constructor results in
                // varying behavior across browsers. Splitting and passing in parts of the date
                // manually gives us more defined behavior.
                // See https://stackoverflow.com/questions/2587345/why-does-date-parse-give-incorrect-results
                var parts = $existingVal.split('-')
                var year = parts[0]
                var month = parts[1] - 1 // Date constructor expects a zero-indexed month
                var day = parts[2]
                preparedDate = new Date(year, month, day).format($customConfig.format);
                $fake.val(preparedDate);
                $picker.bootstrapDP('update', preparedDate);
            }

            $fake.on('keydown', function(e){
                e.preventDefault();
                return false;
            });

            $picker.on('show hide change', function(e){
                if( e.date ){
                    var sqlDate = e.format('yyyy-mm-dd');
                } else {
                    try {
                        var sqlDate = $fake.val();

                        if( $customConfig.format === 'dd/mm/yyyy' ){
                            sqlDate = new Date(sqlDate.split('/')[2], sqlDate.split('/')[1] - 1, sqlDate.split('/')[0]).format('yyyy-mm-dd');
                        }
                    } catch(e){
                        if( $fake.val() ){
                            PNotify.removeAll();
                            new PNotify({
                                title: 'Whoops!',
                                text: 'Sorry we did not recognise that date format, please make sure it uses a yyyy mm dd combination',
                                type: 'error',
                                icon: false
                            });
                        }
                    }
                }

                $field.val(sqlDate);
            });
        });

    }


@endpush

@endif
{{-- End of Extra CSS and JS --}}

get the

Hi @Gorbas ,

As @fabriciolangermt mentioned, there's currently no quick way to do this, other than creating a custom field type. @fabriciolangermt 's code should help you get started with that.

We do plan on making this A LOT easier in the future, we call it the matrix field, and it should allow you to use ANY type of field inside a table. But we're quite far away from achieving this, some core changes need to be made in order for that to be possible. There are plenty of issues that have not been resolved (ex: how to retrigger field JS after validation, how to show fields without labels in the table, etc). I'll keep you guys updated on this in the newsletter - it will definitely be time to celebrate when we implement this :-) It's my favorite big feature.

Thanks, cheers!

Was this page helpful?
0 / 5 - 0 ratings