Resource Footers & Revenue Sidebar

The resources can be rendered vertically either as plain text or based on a custom grid template. This example utilizes the properties of the resource objects, which are conference rooms with capacities and pricing. Along with the resource template defined using renderResource , an additional sidebar is rendered on the opposite side of the row using renderSidebar , displaying the calculated revenue.

Both the resource and sidebar columns have customizable headers and footers, which are set using the renderResourceHeader, renderResourceFooter, renderSidebarHeader, renderSidebarFooter functions.

The occupancy percentages that can be seen for each day are set through the renderDayFooter in a similar way to the day header templates.

The headers are equipped with sorting functionality, allowing data to be sorted on click. Event listeners are attached to the resource, day, and sidebar headers, making them clickable. Each header has a custom icon indicating the current sorting state (ascending, descending, or unordered). Clicking a header cycles through these sorting states, with the corresponding icon reflecting the current state. The initial order in which the resources appear follows the order of the array passed to the component.

The resources column has a default width that may not be sufficient to fit its content. This can be adjusted by overriding it with a CSS rule:

.md-resource-details .mbsc-timeline-resource-col {
            width: 280px;
            }
Download and try example

Demo

Loading the live demo

Preview in full screen

Add custom resource column footers, a revenue sidebar, and per-day occupancy to a resource timeline — all column headers are sortable.

Code

mobiscroll.setOptions({
  locale:  ,
  theme: ' ',
  themeVariant: ' '
});

var formatDate = mobiscroll.formatDate;
var myEvents = [];
var sortColumn;
var sortDirection;
var sortDay;
var totalRevenue;

var myResources = [
  { id: 1, name: 'Horizon', seats: 1200, color: '#4a4a4a', price: 1000 },
  { id: 2, name: 'Apex Hall', seats: 90, color: '#fdf500', price: 600 },
  { id: 3, name: 'Jade Room', seats: 700, color: '#00aaff', price: 900 },
  { id: 4, name: 'Dome Arena', seats: 850, color: '#239a21', price: 750 },
  { id: 5, name: 'Forum Plaza', seats: 900, color: '#8f1ed6', price: 700 },
  { id: 6, name: 'Gallery', seats: 300, color: '#0077b6', price: 650 },
  { id: 7, name: 'Icon Hall', seats: 450, color: '#e63946', price: 850 },
  { id: 8, name: 'Broadway', seats: 250, color: '#ff0101', price: 800 },
  { id: 9, name: 'Central Hub', seats: 400, color: '#01adff', price: 1100 },
  { id: 10, name: 'Empire Hall', seats: 550, color: '#ff4600', price: 950 },
];

function getUTCDateOnly(d) {
  return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate());
}

function getDayDiff(d1, d2) {
  return Math.round((getUTCDateOnly(d2) - getUTCDateOnly(d1)) / (60 * 60 * 24 * 1000)) + 1;
}

function getRevenue(resource) {
  var days = 0;
  for (var i = 0; i < myEvents.length; ++i) {
    var event = myEvents[i];
    if (event.resource === resource.id) {
      days += getDayDiff(new Date(event.start), new Date(event.end));
    }
  }
  return days * resource.price;
}

function getSortArrow(column, day) {
  if (sortColumn === column && day == sortDay) {
    return sortDirection === 'asc' ? 'asc' : sortDirection === 'desc' ? 'desc' : 'def';
  }
  return 'def';
}

function getBusyHours(resource, startOfDay) {
  var endOfDay = startOfDay + 86400000;
  return myEvents.reduce(function (total, event) {
    if (event.resource === resource.id) {
      var eventStart = Math.max(startOfDay, new Date(event.start).getTime());
      var eventEnd = Math.min(endOfDay, new Date(event.end).getTime());
      return eventStart < eventEnd ? total + (eventEnd - eventStart) / (1000 * 60 * 60) : total;
    }
    return total;
  }, 0);
}

function refreshData(inst) {
  // Events for the current view
  myEvents = inst.getEvents();

  myResources.forEach(function (resource) {
    resource.revenue = getRevenue(resource);
  });

  totalRevenue = myResources.reduce(function (total, resource) {
    return total + resource.revenue;
  }, 0);
}

function sortResources(column, day) {
  if (sortColumn === column && day === sortDay) {
    sortDirection = sortDirection === 'asc' ? 'desc' : sortDirection === 'desc' ? 'def' : 'asc';
  } else {
    sortColumn = column;
    sortDirection = 'asc';
  }
  sortDay = day;

  myResources.forEach(function (resource) {
    resource.busyHours = getBusyHours(resource, day) - 24;
  });
  calendar.setOptions({ resources: myResources });

  myResources.sort(function (a, b) {
    if (sortDirection === 'asc') {
      return a[column] > b[column] ? 1 : -1;
    }
    if (sortDirection === 'desc') {
      return a[column] < b[column] ? 1 : -1;
    }
    return a.id - b.id;
  });
  calendar.setOptions({ resources: myResources.slice() });
}

var calendar = mobiscroll.eventcalendar('#demo-resource-details', {
  clickToCreate: true,
  dragToCreate: true,
  dragToMove: true,
  dragToResize: true,
  view: {
    timeline: {
      type: 'month',
    },
  },
  resources: myResources,
  renderResourceHeader: function () {
    return (
      '<div class="mds-resource-sort-header mds-resource-cell mds-resource-cell-name mds-resource-sort-' +
      getSortArrow('name') +
      '" data-sort="name">' +
      'Room' +
      '</div>' +
      '<div class="mds-resource-sort-header mds-resource-cell mds-resource-cell-seats mds-resource-sort-' +
      getSortArrow('seats') +
      '" data-sort="seats">' +
      'Capacity' +
      '</div>' +
      '<div class="mds-resource-sort-header mds-resource-cell mds-resource-cell-price mds-resource-sort-' +
      getSortArrow('price') +
      '" data-sort="price">' +
      'Price/day' +
      '</div>'
    );
  },
  renderResource: function (resource) {
    return (
      '<div class="mds-resource-cell mds-resource-cell-name">' +
      resource.name +
      '</div>' +
      '<div class="mds-resource-cell mds-resource-cell-seats">' +
      resource.seats +
      ' seats</div>' +
      '<div class="mds-resource-cell mds-resource-cell-price">$' +
      resource.price +
      '</div>'
    );
  },
  renderSidebarHeader: function () {
    return '<div class="mds-resource-sort-header mds-resource-sort-' + getSortArrow('revenue') + '" data-sort="revenue">Revenue</div>';
  },
  renderSidebar: function (resource) {
    return '<div class="mds-sidebar-cell">$' + resource.revenue + '</div>';
  },
  renderResourceFooter: function () {
    return '<div class="mds-resource-details-footer mds-resource-details-occuppancy">Occuppancy</div>';
  },
  renderTimelineDay: function (data) {
    var day = data.date;
    var timestamp = +day;
    return (
      '<div class="mds-resource-sort-header mds-resource-sort-' +
      getSortArrow('busyHours', timestamp) +
      '" data-sort="busyHours" data-day="' +
      timestamp +
      '">' +
      formatDate('D DDD', day) +
      '</div>'
    );
  },
  renderDayFooter: function (data) {
    var events = data.events;
    var occuppancy = 0;
    if (events) {
      var resourceIds = [];
      var nr = 0;
      for (var i = 0; i < events.length; ++i) {
        var event = events[i];
        if (resourceIds.indexOf(event.resource) < 0) {
          nr++;
          resourceIds.push(event.resource);
        }
      }
      occuppancy = ((nr * 100) / myResources.length).toFixed(0);
    }
    return '<div class="mds-resource-details-footer mds-resource-details-footer-day">' + occuppancy + '%</div>';
  },
  renderSidebarFooter: function () {
    return '<div class="mds-resource-details-footer mds-resource-details-total">$' + totalRevenue + '</div>';
  },
  onPageLoading: function (args, inst) {
    refreshData(inst);
  },
  onEventCreated: function (args, inst) {
    refreshData(inst);
    sortResources();
  },
  onEventDeleted: function (args, inst) {
    refreshData(inst);
    sortResources();
  },
  onEventUpdated: function (args, inst) {
    refreshData(inst);
    sortResources();
  },
});

document.getElementById('demo-resource-details').addEventListener('click', function (event) {
  var target = event.target.closest('.mds-resource-sort-header');
  if (target) {
    var sortColumn = target.getAttribute('data-sort');
    var selectedDay = target.getAttribute('data-day');
    sortResources(sortColumn, selectedDay);
  }
});

mobiscroll.getJson(
  'https://trial.mobiscroll.com/multiday-events/',
  function (events) {
    calendar.setEvents(events);
    refreshData(calendar);
  },
  'jsonp',
);
  

Duplicate Session

You have signed in on a different machine. To keep everything in sync, you have been signed out of this session.

If you need to invite team members, manage seats and accounts you can do it from the licenses page.

Duplicate Session

We noticed you are logged in on a different machine and have closed your old session.

If you need to invite team members, manage seats and accounts you can do it from the licenses page.