1

Has anyone got the Mobiscroll calendar e.g. https://demo.mobiscroll.com/javascript/range/date# working inside a Lightning Web Component (LWC)?

The version I am using is the "For JavaScript" trial.

This template:

<template>
    <p>Before input</p>
    <input id="xyz" class="abc" placeholder="Please Select...">
    <p>After input</p>
</template>

and code:

import { LightningElement } from 'lwc';
import { loadStyle, loadScript } from 'lightning/platformResourceLoader';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import MOBISCROLL from '@salesforce/resourceUrl/mobiscroll';

export default class Mobiscroll extends LightningElement {

    initialized = false;

    // After component is rendered
    renderedCallback() {

        if (this.initialized) return;
        this.initialized = true;

        Promise.all([
            loadScript(this, MOBISCROLL + '/js/mobiscroll.javascript.min.js'),
            loadStyle(this, MOBISCROLL + '/css/mobiscroll.javascript.min.css')
        ])
            .then(() => {
                this.initialize();
            })
            .catch(error => {
                this.dispatchEvent(
                    new ShowToastEvent({
                        title: 'Error loading Mobiscroll',
                        message: error.message,
                        variant: 'error'
                    })
                );
            });
    }

    initialize() {
        // The D3 documentation example doesn't have "window." but needed here for some reason.
        window.mobiscroll.settings = {
            display: 'inline'
        };
        window.mobiscroll.calendar('#xyz-10');
        window.mobiscroll.calendar('#xyz-7', { display: 'inline'});
        window.mobiscroll.calendar('input.abc');
    }
}

don't generate any errors but also don't generate any calendar output.

This message appears in the browser console:

The trial is being authenticated with remote calls made to https://trial.mobiscroll.com. This is strictly necessary for the trial. No remote calls are made from the licensed version. More info on the trial and its limitations: http://help.mobiscroll.com/trials/what-are-the-limitations-of-the-trial

though I don't see such a call being made even with that setup as a "CSP Trusted Site".

Adding lwc:dom="manual" to the input doesn't help either.

PS

Via some JavaScript debugging, I think I have found at least my "Cannot read property 'call' of undefined" problem.

This Mobiscroll code https://github.com/acidb/mobiscroll/blob/4b03e3d746a3fa83f07b3101eaa7ad12100e4933/src/js/core/dom.js#L1123:

var matchesSelector = element.matchesSelector || element.webkitMatchesSelector || element.mozMatchesSelector || element.msMatchesSelector;

return matchesSelector.call(element, selector);

will fail because this HTMLElementTheGoodPart LWC code https://github.com/salesforce/lwc/blob/master/packages/lwc/types.d.ts#L16 does not implement matchesSelector. Hence the "Cannot read property 'call' of undefined".

But HTMLElementTheGoodPart does implement querySelectorAll so this polyfill https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill would work.

Trying to add the polyfill to the LightningElement, not surprisingly given the locked-down security model of LWC, results in this error:

TypeError: Cannot add property matchesSelector, object is not extensible

So it looks like the fix needs to be done in Mobiscroll. But a simple change may not be possible - see Can DOM matches logic be applied to an LWC LightingElement?.

2 Answers 2

1

The mobiscroll initialization function excepts a selector string or a DOM element on which the component will be initialized.

Since the Lightning Element is a web component, it generates a custom html element and its template will be inside the custom element's shadow DOM, so the mobiscroll query won't find that element.

You will need to use the Lightning template to get the input, as described here. So your code will change to:

initialize() {
  window.mobiscroll.calendar(this.template.querySelector('input'), {
    display: 'inline'
  });
}

I hope this helps.

2
  • Thanks istvan; changing to that fails with a "Cannot read property 'call' of undefined" error. Is there any way to turn on verbose logging or can I grab a non-minified version of the code to make JavaScript debugging easier?
    – Keith C
    Commented Jan 6, 2020 at 17:48
  • See the PS I added to the question where one problem is identified.
    – Keith C
    Commented Jan 6, 2020 at 21:13
1

It looks like to me that unless Mobiscroll removes the dependency on matches (perhaps just in the logic that identifies the DOM node to add children to), the calendar won't work inside an LWC.

But thanks to advice from Mobiscroll's Isti Halmen and with a small change that hopefully Mobiscroll will add to their main distribution, the calendar will work inside an Aura component... So the fallback from the ideal of hosting inside an LWC, is to host inside an Aura component that can inter-operate with LWCs.

Component:

<aura:component implements="forceCommunity:availableForAllPageTypes" access="global">

    <ltng:require styles="{!$Resource.mobiscroll + '/css/mobiscroll.javascript.min.css'}" />
    <ltng:require scripts="{!$Resource.mobiscroll + '/js/mobiscroll.javascript.min.js'}" afterScriptsLoaded="{!c.afterScriptsLoaded}" />

    <div style="height:500px">
        <p>Aura before input</p>
        <input aura:id="xyz" placeholder="Please select a date..."></input>
        <p>Aura after input</p>
    </div>

</aura:component>

Controller:

({
    afterScriptsLoaded : function(component, event, helper) {

        var m = mobiscroll || window.mobiscroll;

        var now = new Date();
        var week = [now, new Date(now.getFullYear(), now.getMonth(), now.getDate() + 6, 23, 59)];

        var el = component.find("xyz").getElement();

        m.range(el, {
            display: 'inline',
            months: 4,
            controls: ['calendar'],
            onInit: function (event, inst) {
                inst.setVal(week, true);
            }
        });
    }
})

PS

It turned out to be easier than I expected to write a native LWC that displays multiple date pickers using the standard SLDS formatting. The current template is below, with the JavaScript controller building the data that this template iterates over. Surprisingly fast too: with 40 calendars rendered (as a pathological test case), moving them all by a month so they all change is pretty much instantaneous.

So cleaner to leverage the platform's own framework than to embed a big chunk of JavaScript.

<template>

    <!-- One or more consecutive month SLDS date pickers -->
    <div class="slds-grid slds-wrap">
        <template for:each={pickers} for:item="picker">

            <!-- Responsive grid -->
            <div key={picker.key} class="slds-col slds-size_1-of-1 slds-medium-size_1-of-2 slds-large-size_1-of-3">

                <!-- Date picker per month -->
                <div class="slds-form-element slds-p-around_small">

                    <!-- Want the dropdown look but relative layout -->
                    <div class="slds-datepicker slds-dropdown" style="position: relative;">

                        <!-- Selection area -->
                        <div class="slds-datepicker__filter slds-grid">
                            <div class="slds-datepicker__filter_month slds-grid slds-grid_align-spread slds-grow">
                                <div class="slds-align-middle">
                                    <lightning-button-icon
                                        icon-name="utility:left"
                                        variant="bare"
                                        data-relative-month={picker.relativeMonth}
                                        data-month-offset={picker.monthOffset}
                                        onclick={handleLeftClick}
                                    ></lightning-button-icon>
                                </div>
                                <div class="slds-align-middle">
                                    {picker.monthYear}
                                </div>
                                <div class="slds-align-middle">
                                    <lightning-button-icon
                                        icon-name="utility:right"
                                        variant="bare"
                                        data-relative-month={picker.relativeMonth}
                                        data-month-offset={picker.monthOffset}
                                        onclick={handleRightClick}
                                    ></lightning-button-icon>
                                </div>
                            </div>
                        </div>

                        <!-- Month of dates table area -->
                        <table>
                            <!-- Headings -->
                            <tr>
                                <template for:each={picker.headings} for:item="heading">
                                    <th key={heading.key}>{heading.name}</th>
                                </template>
                            </tr>
                            <!-- Rows -->
                            <template for:each={picker.weeks} for:item="week">
                                <tr key={week.key}>
                                    <template for:each={week.days} for:item="day">
                                        <!-- onclick on td didn't appear to work -->
                                        <td key={day.key} class={day.classes}>
                                            <span class="slds-day" data-day-number={day.dayNumber} onclick={handleDayClick}>{day.dayOfMonth}</span>
                                        </td>
                                    </template>
                                </tr>
                            </template>
                        </table>

                        <!-- Jump to today area -->
                        <button
                            class="slds-button slds-align_absolute-center slds-text-link"
                            data-relative-month={picker.relativeMonth}
                            data-month-offset={picker.monthOffset}
                            onclick={handleTodayClick}
                        >Today</button>
                    </div>     
                </div>
            </div>
        </template>
    </div>

</template>

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .