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.
I expected that the values that I select would be passed back to the server in order to store them
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.
// 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
]);
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 = '[]';
}
?>
| {{-- --}} | {{-- --}} |
|---|---|
| sort item |
{!! $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 --}}
window.angularApp = window.angularApp || angular.module('backPackTableApp', ['ui.sortable'], function($interpolateProvider){
$interpolateProvider.startSymbol('<%');
$interpolateProvider.endSymbol('%>');
});
window.angularApp.controller('tableController', function($scope){
$scope.sortableOptions = {
handle: '.sort-handle'
};
$scope.addItem = function(){
if( $scope.max > -1 ){
if( $scope.items.length < $scope.max ){
var item = {};
if (window['onAddChildItem'] != null) {
item = window['onAddChildItem'];
}
$scope.items.push(item);
} else {
new PNotify({
title: $scope.maxErrorTitle,
text: $scope.maxErrorMessage,
type: 'error'
});
}
}
else {
var item = {};
if (window['onAddChildItem'] != null) {
item = window['onAddChildItem'];
}
$scope.items.push(item);
}
}
$scope.removeItem = function(item){
var index = $scope.items.indexOf(item);
$scope.items.splice(index, 1);
}
$scope.$watch('items', function(a, b){
if( $scope.min > -1 ){
while($scope.items.length < $scope.min){
$scope.addItem();
}
}
if( typeof $scope.items != 'undefined' && $scope.items.length ){
if( typeof $scope.field != 'undefined'){
if( typeof $scope.field == 'string' ){
$scope.field = $($scope.field);
}
$scope.field.val( angular.toJson($scope.items) );
}
}
}, true);
if( $scope.min > -1 ){
for (var i = 0; i < $scope.min; i++){
$scope.addItem();
}
}
});
window.angularApp.directive('postRender', function($timeout) {
return {
link: function(scope, element, attr) {
$timeout(function() {
if (window['initIntegerNumber'] != null) {
initIntegerNumber();
}
if (window['initDatePicker'] != null) {
initDatePicker();
}
if (window['initTime'] != null) {
initTime();
}
$('.select2_field').each(function (i, obj) {
if (!$(obj).hasClass("select2-hidden-accessible"))
{
$(obj).select2({
theme: "bootstrap"
});
}
});
});
}
}
});
window.angularApp.directive('convertToNumber', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$parsers.push(function(val) {
return parseInt(val, 10);
});
ngModel.$formatters.push(function(val) {
return '' + val + '';
});
}
};
});
window.angularApp.directive('convertToInteger', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$parsers.push(function(val) {
return val.replace('.', '');
});
ngModel.$formatters.push(function(val) {
var aux = '' + val + '';
return aux.replace('.', '');
});
}
};
});
window.angularApp.directive('convertToDate', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$parsers.push(function(val) {
return val; //val.substr(0, 4) + '-' + val.substr(5, 2) + '-' + val.substr(8, 2);
});
ngModel.$formatters.push(function(val) {
if (val != null && val != "") {
return val.substr(8, 2) + '/' + val.substr(5, 2) + '/' + val.substr(0, 4);
} else {
return "";
}
});
}
};
});
window.angularApp.directive('convertToFloat', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$parsers.push(function(val) {
return val.replace('.', '').replace(',', '.');
});
ngModel.$formatters.push(function(val) {
return val.replace('.', ',');
});
}
};
});
angular.element(document).ready(function(){
angular.forEach(angular.element('[ng-app]'), function(ctrl){
var ctrlDom = angular.element(ctrl);
if( !ctrlDom.hasClass('ng-scope') ){
angular.bootstrap(ctrl, [ctrlDom.attr('ng-app')]);
}
});
});
@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();
?>
{!! $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!
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
matrixfield, 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!