0

In my project, users can book rooms. My rooms have disponibility hours (eg 08:00-17:00). I try to use Business Hours but disponibilities change in summer and winter.

I also tried to use inverse-background events with date ranges like this post but if I use selectConstraint, the range is not taken in account.

The best would be to add date range to business hours, but it seems not implemented yet.

Has anyone a solution for my needs?

Thanks

edit: here is my fullcalendar options

function FCInit(){
        var formatColumn, formatColumnWeek;

        // Entete des colonnes
        if ($(window).width() < 600) {
            formatColumn = 'ddd';
            formatColumnWeek = 'ddd\nDD/MM';
        }
        else {
            formatColumn = 'dddd';
            formatColumnWeek = 'dddd\nDD/MM';
        }

        var fcOpts = {

            header: {                               
                left: 'today,datePickerButton',
                center: 'prev,title,next',
                right: 'month,agendaWeek,agendaDay'
            },

            contentHeight: 'auto',                  
            eventLimit: false,                      
            allDaySlot: true,                       
            slotEventOverlap: false,                    
            nowIndicator: true,                     
            timeFormat: 'H:mm',                     
            columnFormat: formatColumn,             // Format des jours dans l'entete     ddd: Mon  /  ddd M/D : Mon 09/07  /  dddd : MOnday /
            navLinks: true,                         

            eventOverlap: false,                        
            selectable: true,
            selectHelper: true,
            selectOverlap: true,
            selectConstraint:999,
            unselectCancel: '#reservation',
            views: {                                
                week: {
                    columnFormat: formatColumnWeek
                }
            },

            events:[{
                     id:3,
                     title:"R\u00e9serv\u00e9",
                     start:"2017-11-02 08:00",
                     end:"2017-11-02 10:00",
                     overlap:false,
                     color:"#C41305"
            },{
                     id:999,
                     className:"fc-nonbusiness",
                     title:"",
                     start:"08:00",
                     end:"17:00",
                     dow:[4],
                     ranges:[
                        {
                          start:"2017-11-01",
                          end:"2017-11-30"
                        }
                     ],
                     rendering:"inverse-background",
            }],

            /* Ajout de datepicker (nécessite Jquery UI css et js) */
            customButtons: {
                datePickerButton: {
                    text: '',
                    click: function () {

                        var $btnCustom = $('.fc-datePickerButton-button'); // name of custom  button in the generated code
                        $btnCustom.after('<input type="hidden" id="hiddenDate" class="datepicker"/>');

                        $("#hiddenDate").datepicker({
                            flat: true,
                            showOn: "button",
                            dateFormat: "yy-mm-dd",
                            onSelect: function (dateText, inst) {
                                $('#full-calendar').fullCalendar('changeView', 'agendaDay', dateText);
                            }
                        });

                        var $btnDatepicker = $(".ui-datepicker-trigger"); // name of the generated datepicker UI
                        //Below are required for manipulating dynamically created datepicker on custom button click
                        $("#hiddenDate").show().focus().hide();
                        $btnDatepicker.trigger("click"); //dynamically generated button for datepicker when clicked on input textbox
                        $btnDatepicker.hide();
                        $btnDatepicker.remove();
                        $("input.datepicker").not(":first").remove();//dynamically appended every time on custom button click

                    }
                }
            },
            dayRender: function(date, cell){
                if(date.isBefore(new Date())){
                    cell.css('cursor','no-allowed');
                }
            },

            eventRender: function (event, element) {

                if(event.ranges) {
                    return (event.ranges.filter(function (range) { // test event against all the ranges

                        return (event.start.isBefore(range.end) &&
                            event.end.isAfter(range.start));

                    }).length) > 0;
                }

                if(event.rendering === "background"){
                    // Just add some text or html to the event element.
                    element.append("<div class='fc-title'>"+event.title+"</div>");
                }

            },
            dayClick: function(date, jsEvent, view){
                if(date.isSameOrAfter(new Date()) && view.name === 'month'){
                    $('#full-calendar').fullCalendar('changeView', 'agendaWeek', date);
                }
            },
            select: function(start, end, jsEvent, view){
                if(start.isSameOrAfter(new Date()) && view.name !== 'month'){
                    $('#reservation_dateFrom').val(start.format('DD/MM/YYYY HH:mm'));
                    $('#reservation_dateTo').val(end.format('DD/MM/YYYY HH:mm'));
                    $('#reservation').modal('show');
                }else if(start.isBefore(new Date())){
                    alert('Il n\'est pas possible de réserver dans le passé');
                    $('#full-calendar').fullCalendar('unselect');
                }

            }

        };
        $('#full-calendar').fullCalendar(fcOpts);

    };

and my symfony entities for storing the datas (where Horaire is a collection of business hours):

/*src/AppBundle/Entity/HoraireSalle.php*/

class HoraireSalle
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="dateFrom", type="datetime")
     */
    private $dateFrom;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="dateTo", type="datetime")
     */
    private $dateTo;

    /**
     * @ORM\ManyToOne(targetEntity="Horaire", inversedBy="salles")
     */
    private $horaire;

    /**
     * @ORM\ManyToOne(targetEntity="Salle", inversedBy="horaires")
     */
    private $salle;
    ...
}
Florian Thomi
  • 11
  • 1
  • 4
  • What did you do please share your code – Ferhat BAŞ Oct 24 '17 at 10:14
  • best thing would be for your code to check the date when it runs, and set the business hours accordingly. On the server you could store in the database the hours which apply at different times of the year. If the user changes the viewed dates in fullCalendar, handle that change event, and re-check the displayed date, and change the businessHours option if needed. Pretty sure that would work. Or you could use background events with a custom overlap rule to stop events going outside a certain range. – ADyson Oct 24 '17 at 10:46
  • Just seen your edit. You could replace `selectOverlap: true` with a custom function which checks whether the event being overlapped onto is an inverse-background event, and use that to decide whether the overlap is allowed. See https://fullcalendar.io/docs/selection/selectOverlap/ – ADyson Oct 24 '17 at 12:22
  • @ADyson the problem is that selectOverlap is not called on the inverse part of the event, so the check is not made outside of the event. – Florian Thomi Oct 24 '17 at 13:23
  • Interesting, didn't know that. How about using https://fullcalendar.io/docs/selection/selectAllow/ with some custom code which references your date and time ranges. – ADyson Oct 24 '17 at 13:27

2 Answers2

1

Thanks to @ADyson I do more or less what I want. Here is my solution.

function isAllowed(start, end) {

        var events = $('#full-calendar').fullCalendar('clientEvents', function (event) {
            return event.rendering === 'inverse-background' && event.start && event.end;
        });

        var allow = events.filter(function (event) {
            return (start.isBetween(moment(new Date(event.ranges[0].start)), moment(new Date(event.ranges[0].end)))
                && end.isBetween(moment(new Date(event.ranges[0].start)), moment(new Date(event.ranges[0].end)))
                && start.format("HH:mm") >= event.start.format("HH:mm") && end.format("HH:mm") <= event.end.format("HH:mm")
                && event.dow.indexOf(start.day()) > -1
                && event.dow.indexOf(end.day()) > -1)
        });

        events = $('#full-calendar').fullCalendar('clientEvents', function (event) {
            return event.rendering !== 'inverse-background' && event.start && event.end;
        });


        var overlap = events.filter(function (event) {
            return event.start.isBefore(end) && event.end.isAfter(start);
        });

        if (allow.length && overlap.length == 0) {
            return true;
        }
        return false;
    }

    function FCInit() {
        var formatColumn, formatColumnWeek;

        if ($(window).width() < 600) {
            formatColumn = 'ddd';
            formatColumnWeek = 'ddd\nDD/MM';
        }
        else {
            formatColumn = 'dddd';
            formatColumnWeek = 'dddd\nDD/MM';
        }

        var fcOpts = {
            header: {                               // Ordre des boutons de l'entete
                left: 'today,datePickerButton',
                center: 'prev,title,next',
                right: 'month,agendaWeek,agendaDay'
            },

            contentHeight: 'auto',                  
            eventLimit: false,                      
            allDaySlot: true,                   
            slotEventOverlap: false,            
            nowIndicator: true,                                     
            timeFormat: 'H:mm',                     
            columnFormat: formatColumn,             
            navLinks: true,                                             
            eventOverlap: false,
            selectable: true,
            selectHelper: true,
            {% if businessHours is defined and businessHours is not empty %}
            selectAllow: function (eventInfo) {
                return isAllowed(eventInfo.start, eventInfo.end);
            },
            {% else %}
            selectOverlap: false,
            {% endif %}
            unselectCancel: '#reservation',
            views: {                                
                week: {
                    columnFormat: formatColumnWeek
                }
            },

            events: [{
                 id:3,
                 title:"R\u00e9serv\u00e9",
                 start:"2017-11-02 08:00",
                 end:"2017-11-02 10:00",
                 overlap:false,
                 color:"#C41305"
        },{
                 id:999,
                 className:"fc-nonbusiness",
                 title:"",
                 start:"08:00",
                 end:"17:00",
                 dow:[4],
                 ranges:[
                    {
                      start:"2017-11-01",
                      end:"2017-11-30"
                    }
                 ],
                 rendering:"inverse-background",
                 }],

            /* Ajout de datepicker (nécessite Jquery UI css et js) */
            customButtons: {
                datePickerButton: {
                    text: '',
                    click: function () {

                        var $btnCustom = $('.fc-datePickerButton-button'); // name of custom  button in the generated code
                        $btnCustom.after('<input type="hidden" id="hiddenDate" class="datepicker"/>');

                        $("#hiddenDate").datepicker({
                            flat: true,
                            showOn: "button",
                            dateFormat: "yy-mm-dd",
                            onSelect: function (dateText, inst) {
                                $('#full-calendar').fullCalendar('changeView', 'agendaDay', dateText);
                            }
                        });

                        var $btnDatepicker = $(".ui-datepicker-trigger"); // name of the generated datepicker UI
                        //Below are required for manipulating dynamically created datepicker on custom button click
                        $("#hiddenDate").show().focus().hide();
                        $btnDatepicker.trigger("click"); //dynamically generated button for datepicker when clicked on input textbox
                        $btnDatepicker.hide();
                        $btnDatepicker.remove();
                        $("input.datepicker").not(":first").remove();//dynamically appended every time on custom button click

                    }
                }
            },
            dayRender: function (date, cell) {
                if (date.isBefore(new Date())) {
                    cell.css('cursor', 'no-allowed');
                }
            },

            eventRender: function (event, element, view) {

                if (event.rendering === 'inverse-background' && event.ranges) {
                    return (event.ranges.filter(function (range) { // test event against all the ranges

                        var start = moment(new Date(range.start));
                        var end = moment(new Date(range.end));
                        return (view.start.isSameOrBefore(end) &&
                            view.end.isSameOrAfter(start)) &&
                            view.start.day(event.dow[0]).isBetween(start, end);

                    }).length > 0);
                }

                if (event.rendering === "background") {
                    // Just add some text or html to the event element.
                    $(element).data("title",event.title);
                }

            },
            dayClick: function (date, jsEvent, view) {
                if (date.isSameOrAfter(new Date()) && view.name === 'month') {
                    $('#full-calendar').fullCalendar('changeView', 'agendaWeek', date);
                }
            },
            select: function (start, end, jsEvent, view) {
                if (start.isSameOrAfter(new Date()) && view.name !== 'month') {
                    $('#reservation_dateFrom').val(start.format('DD/MM/YYYY HH:mm'));
                    $('#reservation_dateTo').val(end.format('DD/MM/YYYY HH:mm'));
                    $('#reservation').modal('show');
                } else if (start.isBefore(new Date())) {
                    alert('Il n\'est pas possible de réserver dans le passé');
                    $('#full-calendar').fullCalendar('unselect');
                }

            }

        };
        $('#full-calendar').fullCalendar(fcOpts);
Florian Thomi
  • 11
  • 1
  • 4
0

working example fullcalendar dynamic dow range

Suppose that you need to requiring event in between following date range

start: "2018-06-01",

end: "2018-08-01"

http://jsfiddle.net/521wucLq/

  • In the future, please include all relevant code in your post and don't just include a link to a code hosting site. Your post should stand alone from any other resource; consider what would happen if that site went down in the future! – Tim Diekmann Jun 04 '18 at 11:14