Our pure JavaScript Scheduler component


Post by sephi2510 »

Hello dev friends, hopes your all good during this terrible time.

I put "ExtraItems" in my Edit windows. But, when i'm create new event, this extraitems "equipement" isn't send to my "create.php".

I'm not in very high level....

This is my code :

import {Scheduler, ResourceModel, WidgetHelper, Toast, DateHelper, DependencyModel, StringHelper, EventModel, StateTrackingManager, Store} from '../../build/scheduler.module.js?440867';
import shared from '../_shared/shared.module.js?440867';

// Set random PHP session ID if it doesn't exist
const
    cookie = 'PHPSESSID=scheduler-php';
if (!(document.cookie.includes(cookie))) {
    document.cookie = `${cookie}-${Math.random().toString(16).substring(2)}`;
}
setInterval(function () {
                scheduler.eventStore.load();
            WidgetHelper.toast('Planning Mis à Jour');
        }, 5000 * 60 );

const
    DATE_FORMAT = 'YYYY-MM-DD HH:00:00',
    serializeDate = (value) => DateHelper.format(value, DATE_FORMAT);
const
    DH        = DateHelper,
    today     = DH.clearTime(new Date()),
    start     = DH.startOf(today, 'week');

let highlight = true,
    center    = false,
    animate   = 1000;
var typeinter = ['maintenance_non_planifiee', 'maintenance_non_validee', 'maintenance', 'depannage', 'depannage_non_planifiee', 'formation', 'formation_non_planifiee', 'repos', 'repos_non_planifiee', 'analyses', 'analyses_non_planifiee', 'administratif', 'administratif_non_planifiee', 'aide_fumisterie', 'aide_fumisterie_non_planifiee', 'fumisterie', 'fumisterie_non_planifiee', 'mises_en_route', 'mises_en_route_non_planifiee'];

var equipements = ['FTII 1', 'FTII 2'];



let timeRanges = [
        { id : 1, startDate : '2020-07-14', endDate : '2020-07-15', name : 'Férié : 14 Juillet', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 2, startDate : '2020-08-15', endDate : '2020-08-16', name : 'Férié : 15 Aout', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 3, startDate : '2020-11-01', endDate : '2020-11-02', name : 'Férié : Toussaint', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 4, startDate : '2020-11-11', endDate : '2020-11-12', name : 'Férié : Armistice', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 5, startDate : '2020-12-25', endDate : '2020-12-26', name : 'Férié : Noël', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 7, startDate : '2021-01-01', endDate : '2021-01-02', name : 'Jour de l an', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 8, startDate : '2021-04-05', endDate : '2021-04-06', name : 'Lundi de Paques', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 9, startDate : '2021-05-01', endDate : '2021-05-02', name : 'Fête du Travail', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 10, startDate : '2021-05-08', endDate : '2021-05-09', name : 'Victoire des alliés', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 11, startDate : '2021-05-13', endDate : '2021-05-14', name : 'Jeudi de Ascension', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 13, startDate : '2021-07-14', endDate : '2021-07-15', name : 'Fête nationale', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 14, startDate : '2021-08-15', endDate : '2021-08-16', name : 'Assomption', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 15, startDate : '2021-11-01', endDate : '2021-11-02', name : 'La toussaint', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 16, startDate : '2021-11-11', endDate : '2021-11-12', name : 'Armistice 1918', style : 'background: rgba(255,165,0,.3);color : orange' },
        { id : 17, startDate : '2021-12-25', endDate : '2021-12-26', name : 'Noël', style : 'background: rgba(255,165,0,.3);color : orange' },
];

let stm = new StateTrackingManager({
    autoRecord : true,

listeners : {
    recordingstop() {
        updateUndoRedoControls();
    },
    restoringstop() {
        updateUndoRedoControls();
    },
    queueReset() {
        updateUndoRedoControls();
    }
},

getTransactionTitle(transaction) {
    const lastAction = transaction.queue[transaction.queue.length - 1];

    let { type, model } = lastAction;

    if (lastAction.modelList && lastAction.modelList.length) {
        model = lastAction.modelList[0];
    }

    let title = 'Transaction ' + stm.position;

    if (type === 'UpdateAction' && model instanceof EventModel) {
        title = 'Editer ' + model.name;
    }
    else if (type === 'UpdateAction' && model instanceof ResourceModel) {
        title = 'Editer ' + model.name;
    }
    else if (type === 'RemoveAction' && model instanceof EventModel) {
        title = 'Remove flight ' + model.name;
    }
    else if (type === 'RemoveAction' && model instanceof ResourceModel) {
        title = 'Annuler ' + model.name;
    }
    else if (type === 'AddAction' && model instanceof EventModel) {
        title = 'Ajouter ' + model.name;
    }
    else if (type === 'AddAction' && model instanceof DependencyModel) {
        title = `Link ${model.sourceEvent.name} -> ${model.targetEvent.name}`;
    }

    return title;
}
});

const [undoBtn, transactionsCombo, redoBtn] = WidgetHelper.append([
    {
        ref      : 'undoBtn',
        type     : 'button',
        icon     : 'b-icon b-fa b-fa-undo',
        color    : 'b-blue b-raised',
        tooltip  : 'Undo',
        disabled : true,
        onAction : () => {
            stm.canUndo && stm.undo();
        }
    },
    {
        ref        : 'transactionsCombo',
        width      : 1,
        valueField : 'idx',
        editable   : false,
        store      : new Store(),
        emptyText  : 'No items in the undo queue',
        onAction   : (combo) => {
            const value = combo.value;

        if (value >= 0)  {
            if (stm.canUndo && value < stm.position) {
                stm.undo(stm.position - value);
            }
            else if (stm.canRedo && value >= stm.position) {
                stm.redo(value - stm.position + 1);
            }
        }
    },

    displayValueRenderer : function(value) {
        const stmPos = stm.position || 0;

        return stmPos + ' annuler actions / ' + (this.store.count - stmPos) + ' retablir actions';
    }
},
{
    ref      : 'redoBtn',
    type     : 'button',
    icon     : 'b-icon b-fa b-fa-redo',
    color    : 'b-blue b-raised',
    tooltip  : 'Redo',
    disabled : true,
    onAction : () => {
        stm.canRedo && stm.redo();
    }
}
], { insertFirst : document.getElementById('tools') || document.body });

class Gate extends ResourceModel {
    static get fields() {
        return [
            'capacity'
        ];
    }
}

const
    scheduler = new Scheduler({
        adopt                  : 'container',
        minHeight              : '20em',
        allowOverlap           : true,
        dynamicRowHeight   : false,
        managedEventSizing : false,
        startDate              : new Date(2020, 5, 31),
        endDate                : new Date(2021, 11, 31),
        viewPreset             : 'dayAndWeek',
        zoomLevels: [
            { width: 50,    increment: 4,   resolution: 60, preset: 'dayAndWeek', resolutionUnit: 'day' },
            { width: 60,    increment: 3,   resolution: 60, preset: 'dayAndWeek', resolutionUnit: 'MINUTE' },
            { width: 80,    increment: 2,   resolution: 30, preset: 'dayAndWeek', resolutionUnit: 'MINUTE' },
            { width: 100,   increment: 1,   resolution: 15, preset: 'dayAndWeek', resolutionUnit: 'MINUTE' }
        ],
        rowHeight              : 50,
        barMargin              : 5,
        eventColor             : 'blue',
        passStartEndParameters : true,
        useInitialAnimation : 'slide-from-top',
        resourceImagePath: 'img/',
        readOnly : true,
	listeners: {
        
load() { console.log('loaded'); } }, features : { stripe : true, stripe : true, timeRanges : { showCurrentTimeLine : true, showHeaderElements : true, },
eventTooltip: { tools: [{ cls: 'b-fa b-fa-angle-left', handler: function() {} }, { cls: 'b-fa b-fa-angle-right', handler: function() {} }], header: { titleAlign: 'start' }, template: data => `<dl> <dt>Dossier #${data.eventRecord.id_dossier}</dt> ${data.eventRecord.resource.name} <dt>Date : ${DateHelper.format(data.eventRecord.startDate, 'ddd DD/MM/YYYY')}</dt> <a href="/dossier.php?ID_DOSSIER=${data.eventRecord.id_dossier}" target="_blank">${data.eventRecord.nomdossier}</a> <dt>${data.eventRecord.OT_DESCRIPTION}</dt> </dl> ` // You can also use Tooltip configs here, for example: // anchorToTarget : false, // trackMouse : true }, stripe: true, timeRanges: true, resourceTimeRanges : true, timeRanges: { enableResizing: true, showCurrentTimeLine: true, showHeaderElements: true }, nonWorkingTime: true, // Configure event editor to display 'brand' as resource name eventEdit : { items : { // ref for an existing field nameField : { // Change its label label : 'Descriptioddn' } }, // Uncomment to make event editor readonly from the start // readOnly : true, // Add extra widgets to the event editor extraItems : [ { type : 'combo', label : 'Type', name : 'eventColor', editable : false, index : 1, items : typeinter, }, { type : 'combo', label : 'Equipement', name : 'equipements', editable : true, index : 3, items : equipements, }, { type : 'text', label : 'Description', name : 'OT_DESCRIPTION', editable : true, index : 2, }, ] }, }, columns : [ { type: 'resourceInfo', text: 'Techniciens', field: 'name', width: 170, height: 400, }, ], eventBodyTemplate : data => ` ${data.iconCls ? `<i class="${data.iconCls}"></i>` : ''} <section> <div class="b-sch-event-header"><b>${data.footerText}</b> - ${data.status}</div> <div class="b-sch-event-footer"><b>${data.headerText} ${data.headerText3}</b> </div> </section> `, eventRenderer({ eventRecord, resourceRecord, tplData }) { return { headerId : eventRecord.id, headerText : eventRecord.nomdossier, status : eventRecord.status, headerText2 : DateHelper.format(eventRecord.startDate, 'LLLL'), headerText3 : eventRecord.OT_DESCRIPTION, footerText : eventRecord.name || '', iconCls : eventRecord.iconCls }; }, resourceStore : { // Add some custom fields odelClass : Gate, // Setup urls readUrl : 'php/resource/read.php', // Load and save automatically autoLoad : true, autoCommit : true, // Send as form data and not a JSON payload sendAsFormData : true }, eventStore : { fields : [ { name: 'startDate', type: 'date', dateFormat: DATE_FORMAT, serialize: serializeDate }, { name: 'endDate', type: 'date', dateFormat: DATE_FORMAT, serialize: serializeDate } ], // Add a custom field and redefine durationUnit to default to hours // Setup urls createUrl : 'php/event/create.php', readUrl : 'php/event/read.php', updateUrl : 'php/event/update.php', deleteUrl : 'php/event/delete.php', // Load and save automatically autoLoad : true, autoCommit : true, autoSync : true, // Send as form data and not a JSON payload sendAsFormData : true, onBeforeCommit() { // Make it read only since it only allows one commit at the time scheduler.readOnly = true; }, onCommit() { scheduler.readOnly = false; }, onException(event) { const { action, response } = event, serverMessage = response && response.parsedJson && response.parsedJson.msg, exceptionText = `Command "${action}" failed.${serverMessage ? ` Server response: ${serverMessage}` : ''}`; Toast.show({ html : exceptionText, color : 'b-red', style : 'color:white', timeout : 3000 }); if (!serverMessage) { console.error(`App Exception: ${exceptionText}`, event); } scheduler.readOnly = false; } }, timeRanges, }); //region Widgets WidgetHelper.append([ { type : 'button', ref : 'reloadButton', color : 'b-orange b-raised', icon : 'b-fa b-fa-calendar', tooltip : 'Reloads grid', onAction : ({ source : button }) => scheduler.scrollToDate(new Date(Date.now()),{ highlight, block : 'center', animate : { easing : 'easeFromTo', duration : animate }, }) }, { type : 'button', ref : 'reloadButton', color : 'b-orange b-raised', icon : 'b-fa b-fa-sync', tooltip : 'Reloads grid', async onAction() { await Promise.all([ scheduler.resourceStore.load(), scheduler.eventStore.load() ]); WidgetHelper.toast('Planning Mis à Jour'); } }, { type : 'button', cls : 'b-raised b-blue', text : 'Activer les modifications', toggleable : true, icon : 'b-fa-square', pressedIcon : 'b-fa-check-square', onAction : ({ source : button }) => scheduler.readOnly = button.unpressed }, { type : 'slider', ref : 'rowHeight', text : 'Taille lignes', showValue : true, min : 45, max : 70, onInput({ value }) { adjustTarget.rowHeight = value; barMargin.max = Math.max(0, (value - 10) / 2); } }, { type : 'dropdown', ref : 'scrollToTime', placeholder : 'Aller à', editable : false, items : [ [0, 'Aujourd hui'], [7, 'Dans une semaine'], [30, 'Dans un mois'], [-10, 'Semaines précédentes'], [-1, `Derniere Date`] ], onAction : ({ value }) => { scheduler.scrollToDate( value === -1 ? scheduler.timeAxis.last.startDate : DH.add(today, value, 'days'), { highlight, animate : { easing : 'easeFromTo', duration : animate }, block : 'center', } ); } }, { type : 'textfield', ref : 'r1rByName', icon : 'b-fa b-fa-filter', placeholder : 'Filtrer...', clearable : true, keyStrokeChangeDelay : 100, triggers : { filter : { align : 'start', cls : 'b-fa b-fa-filter' } }, onChange({ value }) { value = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Replace all previous filters and set a new filter scheduler.eventStore.filter({ filters : event => event.name.match(new RegExp(value, 'i')), replace : true }); } }, { type : 'button', ref : 'zoomInButton', cls : 'b-tool', icon : 'b-icon-search-plus', tooltip : 'Zoom in', onAction : () => scheduler.zoomIn() }, { type : 'button', ref : 'zoomOutButton', cls : 'b-tool', icon : 'b-icon-search-minus', tooltip : 'Zoom out', onAction : () => scheduler.zoomOut() } ], { insertFirst : document.getElementById('tools') || document.body }); //endregion const fillUndoRedoCombo = (combo, stm) => { combo.items = stm.queue.map((title, idx) => [idx, title]); }; const updateUndoRedoControls = () => { undoBtn.badge = stm.position; redoBtn.badge = stm.length - stm.position; undoBtn.disabled = !stm.canUndo; redoBtn.disabled = !stm.canRedo; fillUndoRedoCombo(transactionsCombo, stm); }; stm.addStore(scheduler.eventStore); stm.addStore(scheduler.resourceStore); scheduler.features.eventTooltip.tooltip.disabled = false; scheduler.eventStore.on('load', () => { stm.enable(); fillUndoRedoCombo(transactionsCombo, stm); }); scheduler.scrollToDate(new Date(Date.now()),{ highlight, block : 'center',
animate : { easing : 'easeFromTo', duration : animate }, }); const { rowHeight, barMargin, resourceMargin } = scheduler.widgetMap; let adjustTarget; function changeAdjustTarget(target) { adjustTarget = target; const rowHeightValue = target.rowHeight ?? scheduler.rowHeight; rowHeight.value = rowHeightValue; barMargin.value = target.barMargin ?? scheduler.barMargin; barMargin.max = Math.max(0, (rowHeightValue - 10) / 2); resourceMargin.value = target.resourceMargin ?? scheduler.resourceMargin; } changeAdjustTarget(scheduler);

And the send values :

Sans titre.png
Sans titre.png (81.52 KiB) Viewed 385 times

Huge thanks, i love your amazing tool !!!


Post by mats »

Try adding the additional fields in your Store / Model?

eventStore : {
            fields : [
                // <----- Add your custom fields here
                {
                    name       : 'startDate',
                    type       : 'date',
                    dateFormat : DATE_FORMAT,
                    serialize  : serializeDate
                },
                {
                    name       : 'endDate',
                    type       : 'date',
                    dateFormat : DATE_FORMAT,
                    serialize  : serializeDate
                }
            ],

Post by sephi2510 »

Mats,
Thanks you.
I learn Javascript with your tool in the same time.
Huge Thanks.


Post Reply