Datetimepicker in a new component
Question
I apologize for the length of this question! It's a whole new component an I'm almost done writing it, I just need a little bit of help and I believe this could be useful for others.
I am creating a date select input that uses the daterangepicker lib.
gatherDateOptions() gathers all date type fields via the options. I have date, datetime, daterange, and datetimerange fields. I then later use their name and split on "-" to get the field type.
I load the scripts one by one to avoid dependency issues (like "$ is not defined"). If you believe there's a more efficient way to do this, please advise.
Once daterangepicker is loaded, I can run the initDateRange function and indeed it will run daterangepicker on the field and generate the date select dropdown in the correct place (under the input field), but I will explain later why this seems like a waste.
Since I have a select that allows to switch between fields and their type, I need a function to listen to that change and update the input field. So below defaults I added the init() function and I'm listening to the change event and running handleDateInputsOptions.
Problem No. 1: When executing daterangepicker initialization in default: script: the daterangepicker select pop up appears in the correct place - underneath the input field. However, when initializing daterangepicker inside handleDateInputsOptions it appends daterangepicker to the outer body and not below the input inside our grapesjs iframe.
Looking at https://www.daterangepicker.com/#options you will see that we can add a parentEl, however setting it to iframe appends daterangepicker inside the iframe, but I'm unable to place it inside the body of the iframe. I have a feeling that the reason it appends the daterangepicker to the main body and not the iframe body is because I initialize daterangepicker outside the model? I have a feeling my init() function should happen inside default: script:? if so, how do I do that?
Problem No. 2: To properly initialize daterangepicker on the input on load, I can use default: script: but like I wrote inside my script below, if I want to initialize daterangepicker there, I will have to write most of the handleDateInputsOptions fucntion twice to check for what type of date field the input is. This seems like a waste.
Any help will be much appreciated!
function gatherDateOptions() {
var customDateFieldInputs = c.customDateFields.split(',');
var customDateRangeInputs = c.customDateRangeFields.split(',');
var customDatetimeInputs = c.customDatetimeFields.split(',');
var customDatetimeRangeInputs = c.customDatetimeRangeFields.split(',');
const dateInputsOptions = []
// Add date fields
if (c.customDateFields.length){
$.each(customDateFieldInputs, function( index, field ) {
dateInputsOptions.push({
value: field + " - Date", name: field + " - Date"
})
});
}
// Add dateRange fields
if (c.customDateRangeFields.length){
$.each(customDateRangeInputs, function( index, field ) {
dateInputsOptions.push({
value: field + " - Date Range", name: field + " - Date Range"
})
});
}
// Add datetime fields
if (c.customDatetimeFields.length){
$.each(customDatetimeInputs, function( index, field ) {
dateInputsOptions.push({
value: field + " - Datetime", name: field + " - Datetime"
})
});
}
// Add datetimeRange fields
if (c.customDatetimeRangeFields.length){
$.each(customDatetimeRangeInputs, function( index, field ) {
dateInputsOptions.push({
value: field + " - Datetime Range", name: field + " - Datetime Range"
})
});
}
return dateInputsOptions;
}
let dateInputsOptions = gatherDateOptions();
domc.addType('date', {
model: defaultModel.extend({
defaults: {
...defaultModel.prototype.defaults,
'custom-name': c.labelDateName,
tagName: 'input',
type: "date",
draggable: 'form, form *', // Can be dropped only inside `form` elements
droppable: false, // Can't drop other elements inside
copyable: false, // Do not allow to duplicate the component
traits: [
'placeholder',
{
type: 'select',
label: 'Select date field',
name: 'dateInputsSelect',
changeProp: 1,
options: dateInputsOptions
},
{ type: 'checkbox', name: 'required' },
],
script: function () {
var input = this;
// console.log("Working input", input);
var initDateRange = function() {
// If I want to initialize daterangepicker here, I will have to write most of the handleDateInputsOptions fucntion twice to check for what type of date field the input is. This seems like a waste.
// $(input).daterangepicker({
// singleDatePicker: true,
// showDropdowns: true,
// });
}
if (typeof daterangepicker == 'undefined' || typeof jQuery == 'undefined' || typeof moment == 'undefined') {
if (typeof jQuery == 'undefined'){
var jquery = document.createElement('script');
jquery.src = '//cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js';
document.body.appendChild(jquery);
jquery.onload = function(){
loadMoment();
}
} else {
loadMoment();
}
function loadMoment(){
if (typeof moment == 'undefined'){
var moment = document.createElement('script');
moment.src = '//cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js';
document.body.appendChild(moment);
moment.onload = function(){
loadDatePicker();
}
} else {
loadDatePicker();
}
}
function loadDatePicker(){
if (typeof $ == 'undefined'){window.$ = jQuery};
var link = document.createElement('link');
link.rel = "stylesheet";
link.href = "//cdnjs.cloudflare.com/ajax/libs/bootstrap-daterangepicker/3.0.5/daterangepicker.min.css";
document.body.appendChild(link);
var daterangepicker = document.createElement('script');
daterangepicker.src = '//cdnjs.cloudflare.com/ajax/libs/bootstrap-daterangepicker/3.0.5/daterangepicker.min.js';
document.body.appendChild(daterangepicker);
daterangepicker.onload = initDateRange;
}
} else {
// console.log("all libs present");
initDateRange();
}
},
},
init() {
// this.handleDateInputsOptions(); <<<< This will fail because it doesn't know what the view is at this stage. Maybe it's supposed to load something still?, which brings me to the next line:
// this.listenTo(this, 'this.view.el is loaded?????', this.handleDateInputsOptions); <<<< What is that event to listen to the moment this.view.el is loaded?
this.listenTo(this, 'change:dateInputsSelect', this.handleDateInputsOptions);
},
handleDateInputsOptions() {
if (typeof $(this.view.el).data('daterangepicker') != 'undefined'){
$(this.view.el).data('daterangepicker').remove();
}
function initDateRange(input, options) {
$(input).daterangepicker(options);
}
let dateOption = this.get('dateInputsSelect');
var val = dateOption.split(' - ');
var fieldName = "Custom." + val[0];
var type = val[1].trim().toLowerCase();
var fieldOptions = {};
this.set('attributes', {
name: fieldName,
id: val[0],
type: "text",
});
if (type == "date"){
fieldOptions = {
singleDatePicker: true,
showDropdowns: true,
}
} else if (type == "datetime"){
fieldOptions = {
singleDatePicker: true,
showDropdowns: true,
timePicker: true,
locale: {
format: 'MM/DD/YYYY hh:mm A'
}
}
} else if (type == "datetimerange"){
fieldOptions = {
timePicker: true,
locale: {
format: 'MM/DD/YYYY hh:mm A'
}
}
}
initDateRange(this.view.el, fieldOptions);
},
}, {
isComponent(el) {
var dateType;
if (typeof el.dataset != "undefined" && el.dataset.type == "date") {
dateType = true;
} else {
dateType = false;
}
if(el.tagName == 'INPUT' && dateType){
return {type: 'date'};
}
},
}),
view: defaultView
});
Answers (1)
Most of the logic you're doing in handleDateInputsOptions should be inside your component script.
Your dateInputsSelect prop contains the input type, so use the interpolation in your script then apply the logic of handleDateInputsOptions.
One other trick you'd need is to trigger script update in the canvas, so do this in your model.init
init() {
this.on('change:dateInputsSelect', () => this.trigger('change:script'));
}
ps. do a favor to yourself... use the new Component API to define custom types, with the old one is quite easy to break stuff
Related Questions and Answers
Continue research with similar issue discussions.
Issue #1576
Select element that has several blocks as one unit.
I've created new block (like form or dialog, etc) that has several blocks such as search box, button, text fields, ... This element doesn't...
Issue #393
Two little things
Hi @artf @arthuralmeidap @sonnylloyd @cmcintosh @daniel-farina Thank you so much for developing such a awesome framework. I tried to achiev...
Issue #951
[QUESTION] Selection of custom components not possible from canvas.
Hey, I tried to figure this out by myself, but I'm feeling I'm going a little bit in circles. So I hope you will have some time to read thi...
Issue #913
[Question] Using Style tag instead of css
Hi, I've been taking a look inside of the dom_components, but cant seem to figure out how to manually override the styling aspect. I need a...
Paid Plugins That Match This Issue
Curated by issue keywords and label relevance to help you ship faster.
Loading paid plugin recommendations...
Browse Plugin Categories
Jump directly to plugin category pages on the marketplace.