---
title: "JQuery Timeline Meal Planner Example | Mobiscroll"
component: "Timeline"
framework: "jQuery"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/jquery/timeline/meal-planner"
---

# Meal Planner - jQuery Timeline demo

Build a weekly meal planner with the JQuery Timeline — meal types as color-coded resource rows, custom add/edit popover for creating or updating meals. For jQuery or jQuery Mobile.

- [Live demo](https://demo.mobiscroll.com/jquery/timeline/meal-planner)
- [View code on GitHub](https://github.com/acidb/mobiscroll-demos-jquery/tree/main/src/demos/eventcalendar/timeline/meal-planner)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/timeline/meal-planner.md), [Angular](https://demo.mobiscroll.com/angular/timeline/meal-planner.md), [React](https://demo.mobiscroll.com/react/timeline/meal-planner.md), [Vue](https://demo.mobiscroll.com/vue/timeline/meal-planner.md)

## Description

Use the [timeline summary mode/event listing](https://demo.mobiscroll.com/jquery/timeline/event-listing) to create a weekly meal planner. Meal types are represented as resources with a [custom template](https://demo.mobiscroll.com/jquery/scheduler/custom-resource-header-template). Events are the actual meals with custom properties, like calories or notes.

The add/edit form shows up in a custom popover that opens on double click for meal creation and on click for editing.

The popover header is customized with a custom header renderer function using the [`renderHeader`](https://mobiscroll.com/docs/jquery/eventcalendar/api#renderer-renderHeader) option.

By default the second dimension of the timeline (vertical axis) is reserved for resources, however it can be configured and used as "time slots" if the times are not relevant.

## What this demo shows

- Displays a weekly timeline-based meal planner where days are shown horizontally and meal types are shown as resource rows.
- **Resources** Each resource row represents a meal type, and each meal type has its own color so related events are easy to distinguish.
- **Events** Events represent the planned food item for a specific day and meal, using the resource color across the timeline cell.
- **Editing** Clicking an existing meal opens a popup with an edit form for updating the meal name, calories, and notes.
- **Deleting** The edit popup includes a delete action that removes the selected meal immediately, then shows a snackbar confirmation with an Undo action to restore it.
- **Creating** Double-clicking an empty timeline cell opens a popup for creating a new meal for the selected day and meal row.
- **Form behavior** The create popup lets users enter the meal name, calories, and notes before saving the new event.

## Best for

- **Meal planning** Weekly meal planning workflows for restaurants, schools, kindergartens, nursing homes, and similar institutions.
- **Day-based planning** Scenarios where items need to be planned by day without showing exact start and end times.
- **Resource-based organization** Setups where color-coded meal categories or resource rows make planned items easier to scan.
- **Direct calendar editing** Applications that need users to add, edit, delete, and restore planned items directly from the calendar UI.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-themeVariant) - option
- [`setOptions`](https://mobiscroll.com/docs/jquery/popup/api#method-setOptions) - method
- [`buttons`](https://mobiscroll.com/docs/jquery/popup/api#opt-buttons) - option
- [`updateEvent`](https://mobiscroll.com/docs/jquery/eventcalendar/api#method-updateEvent) - method
- [`close`](https://mobiscroll.com/docs/jquery/popup/api#method-close) - method
- [`cssClass`](https://mobiscroll.com/docs/jquery/popup/api#opt-cssClass) - option
- [`renderHeader`](https://mobiscroll.com/docs/jquery/popup/api#renderer-renderHeader) - renderer
- [`open`](https://mobiscroll.com/docs/jquery/popup/api#method-open) - method
- [`view`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-view) - option
- [`resources`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-resources) - option
- [`dragToCreate`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-dragToCreate) - option
- [`dragToResize`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-dragToResize) - option
- [`dragToMove`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-dragToMove) - option
- [`clickToCreate`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-clickToCreate) - option
- [`extendDefaultEvent`](https://mobiscroll.com/docs/jquery/eventcalendar/api#opt-extendDefaultEvent) - option
- [`onEventCreate`](https://mobiscroll.com/docs/jquery/eventcalendar/api#event-onEventCreate) - event
- [`onEventClick`](https://mobiscroll.com/docs/jquery/eventcalendar/api#event-onEventClick) - event
- [`isVisible`](https://mobiscroll.com/docs/jquery/popup/api#method-isVisible) - method
- [`renderResource`](https://mobiscroll.com/docs/jquery/eventcalendar/api#renderer-renderResource) - renderer
- [`renderTimelineEventContent`](https://mobiscroll.com/docs/jquery/eventcalendar/api#renderer-renderTimelineEventContent) - renderer
- [`setEvents`](https://mobiscroll.com/docs/jquery/eventcalendar/api#method-setEvents) - method
- [`display`](https://mobiscroll.com/docs/jquery/popup/api#opt-display) - option
- [`fullScreen`](https://mobiscroll.com/docs/jquery/popup/api#opt-fullScreen) - option
- [`onClose`](https://mobiscroll.com/docs/jquery/popup/api#event-onClose) - event
- [`removeEvent`](https://mobiscroll.com/docs/jquery/eventcalendar/api#method-removeEvent) - method
- [`responsive`](https://mobiscroll.com/docs/jquery/popup/api#opt-responsive) - option
- [`width`](https://mobiscroll.com/docs/jquery/popup/api#opt-width) - option
- [`touchUi`](https://mobiscroll.com/docs/jquery/popup/api#opt-touchUi) - option
- [`showOverlay`](https://mobiscroll.com/docs/jquery/popup/api#opt-showOverlay) - option
- [`addEvent`](https://mobiscroll.com/docs/jquery/eventcalendar/api#method-addEvent) - method

## Code

### JS

```js
mobiscroll.setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

$(function () {
  var oldMeal;
  var tempMeal;
  var deleteMeal;
  var restoreMeal;
  var formatDate = mobiscroll.formatDate;
  var $name = $('#meal-name-input');
  var $calories = $('#meal-calories-input');
  var $notes = $('#meal-notes-textarea');
  var $deleteButton = $('#meal-delete');
  var $types = $('#meal-type-segmented');

  var types = [
    {
      id: 1,
      name: 'Breakfast',
      color: '#e20f0f',
      kcal: '300 - 400 kcal',
      icon: '🍳',
    },
    {
      id: 2,
      name: 'Elevenses',
      color: '#157d13',
      kcal: '100 - 200 kcal',
      icon: '🍌',
    },
    {
      id: 3,
      name: 'Lunch',
      color: '#32a6de',
      kcal: '500 - 700 kcal',
      icon: '🍜',
    },
    {
      id: 4,
      name: 'Dinner',
      color: '#e29d1d',
      kcal: '400 - 600 kcal',
      icon: '🥙',
    },
    {
      id: 5,
      name: 'Snack',
      color: '#68169c',
      kcal: '100 - 200 kcal',
      icon: '🥨',
    },
  ];

  function addMealPopup() {
    // Hide delete button inside add popup
    $deleteButton.hide();
    deleteMeal = true;
    restoreMeal = false;

    // Set popup header text and buttons for adding
    popup.setOptions({
      buttons: [
        'cancel',
        {
          text: 'Add',
          keyCode: 'enter',
          handler: function () {
            calendar.updateEvent(tempMeal);

            deleteMeal = false;
            popup.close();
          },
          cssClass: 'mbsc-popup-button-primary',
        },
      ],
      renderHeader: function () {
        return (
          '<div class="md-meal-planner-header-primary">New meal</div>' +
          '<div class="md-meal-planner-header-date">' +
          formatDate('DDDD, DD MMMM YYYY', new Date(tempMeal.start)) +
          '</div>'
        );
      },
    });

    // Fill popup with a new event data
    $name.mobiscroll('getInst').value = tempMeal.title;
    $calories.mobiscroll('getInst').value = '';
    $notes.mobiscroll('getInst').value = '';

    $('.meal-planner-type').each(function (i, elm) {
      $(elm).mobiscroll('getInst').checked = +elm.value == tempMeal.resource;
    });

    popup.open();
  }

  function editMealPopup(args) {
    var ev = args.event;
    var resource = args.resourceObj;

    // Show delete button inside edit popup
    $deleteButton.show();

    deleteMeal = false;
    restoreMeal = true;

    // Set popup header text and buttons for editing
    popup.setOptions({
      buttons: [
        'cancel',
        {
          text: 'Save',
          keyCode: 'enter',
          handler: function () {
            // Update event with the new properties on save button click
            calendar.updateEvent({
              id: ev.id,
              title: tempMeal.title,
              calories: tempMeal.calories,
              notes: tempMeal.notes,
              start: ev.start,
              end: ev.end,
              resource: tempMeal.resource,
              allDay: true,
            });

            restoreMeal = false;
            popup.close();
          },
          cssClass: 'mbsc-popup-button-primary',
        },
      ],
      renderHeader: function () {
        return (
          '<div class="md-meal-planner-header-primary">' +
          resource.name +
          '</div>' +
          '<div class="md-meal-planner-header-date">' +
          formatDate('DDDD, DD MMMM YYYY', new Date(tempMeal.start)) +
          '</div>'
        );
      },
    });

    // Fill popup with the selected event data
    $name.mobiscroll('getInst').value = ev.title || '';
    $calories.mobiscroll('getInst').value = ev.calories || '';
    $notes.mobiscroll('getInst').value = ev.notes || '';

    $('.meal-planner-type').each(function (i, elm) {
      $(elm).mobiscroll('getInst').checked = +elm.value == tempMeal.resource;
    });

    popup.open();
  }

  var calendar = $('#demo-meal-planner')
    .mobiscroll()
    .eventcalendar({
      view: {
        timeline: {
          type: 'week',
          eventDisplay: 'fill',
        },
      },
      resources: types,
      dragToCreate: false,
      dragToResize: false,
      dragToMove: true,
      clickToCreate: true,
      extendDefaultEvent: function () {
        return {
          title: 'New meal',
          allDay: true,
        };
      },
      onEventCreate: function (args) {
        // Store temporary event
        tempMeal = args.event;
        setTimeout(function () {
          addMealPopup();
        }, 100);
      },
      onEventClick: function (args) {
        oldMeal = $.extend({}, args.event);
        tempMeal = args.event;

        if (!popup.isVisible()) {
          editMealPopup(args);
        }
      },
      renderResource: function (resource) {
        return (
          '<div class="md-meal-planner-cont">' +
          '<div class="md-meal-planner-title" style="color:' +
          resource.color +
          '">' +
          '<span class="md-meal-planner-icon">' +
          resource.icon +
          '</span>' +
          resource.name +
          '</div>' +
          '<div class="md-meal-planner-kcal">' +
          resource.kcal +
          '</div>' +
          '</div>'
        );
      },
      renderTimelineEventContent: function (args) {
        var event = args.original;
        return (
          '<div class="md-meal-planner-event">' +
          '<div class="md-meal-planner-event-title">' +
          event.title +
          '</div>' +
          (event.calories ? '<div class="md-meal-planner-event-desc">Calories: ' + event.calories + ' kcal</div>' : '') +
          '</div>'
        );
      },
    })
    .mobiscroll('getInst');

  $.getJSON(
    'https://trial.mobiscroll.com/meal-planner/?callback=?',
    function (events) {
      calendar.setEvents(events);
    },
    'jsonp',
  );

  var popup = $('#meal-planner-popup')
    .mobiscroll()
    .popup({
      display: 'bottom',
      contentPadding: false,
      fullScreen: true,
      onClose: function () {
        if (deleteMeal) {
          calendar.removeEvent(tempMeal);
        } else if (restoreMeal) {
          calendar.updateEvent(oldMeal);
        }
      },
      responsive: {
        medium: {
          display: 'center',
          width: 400,
          fullScreen: false,
          touchUi: false,
          showOverlay: false,
        },
      },
    })
    .mobiscroll('getInst');

  function appendTypes() {
    var segmented = '<div mbsc-segmented-group>';

    for (var i = 0; i < types.length; ++i) {
      var type = types[i];
      segmented +=
        '<label>' +
        type.name +
        '<input type="radio" mbsc-segmented name="meal-planner-type" value="' +
        type.id +
        '" class="meal-planner-type" ' +
        '/></label>';
    }

    segmented += '</div>';
    $types.append(segmented);
    mobiscroll.enhance($types[0]);
  }

  appendTypes();

  $('.meal-planner-type').on('change', function (ev) {
    tempMeal.resource = +ev.target.value;
  });

  $name.on('change', function (ev) {
    tempMeal.title = ev.target.value;
  });

  $calories.on('change', function (ev) {
    tempMeal.calories = ev.target.value;
  });

  $notes.on('change', function (ev) {
    tempMeal.notes = ev.target.value;
  });

  $deleteButton.on('click', function () {
    // Delete current event on button click
    calendar.removeEvent(tempMeal);

    // Save a local reference to the deleted event
    var deletedMeal = tempMeal;

    popup.close();

    mobiscroll.snackbar({
      button: {
        action: function () {
          calendar.addEvent(deletedMeal);
        },
        text: 'Undo',
      },
      duration: 10000,
      message: 'Meal deleted',
    });
  });
});
```

### HTML

```html
<div id="demo-meal-planner" class="md-meal-planner-calendar"></div>

<div id="meal-planner-popup" class="md-meal-planner-popup">
    <div id="meal-type-segmented" class="mbsc-form-group"></div>
    <div class="mbsc-form-group">
        <label>
            Name
            <input mbsc-input id="meal-name-input" />
        </label>
        <label>
            Calories
            <input mbsc-input id="meal-calories-input" type="number" />
        </label>
        <label>
            Notes
            <textarea mbsc-textarea id="meal-notes-textarea"></textarea>
        </label>
    </div>
    <div class="mbsc-button-group">
        <button class="mbsc-button-block" id="meal-delete" mbsc-button data-color="danger" data-variant="outline">Delete meal</button>
    </div>
</div>
```

### CSS

```css
.md-meal-planner-header-primary {
    font-size: 16px;
    line-height: 20px;
}

.md-meal-planner-header-date {
    font-size: 14px;
    font-weight: 600;
    line-height: 20px;
    opacity: .6;
}

.md-meal-planner-cont {
    position: relative;
    padding-left: 34px;
    text-align: left;
}

.md-meal-planner-title {
    font-size: 20px;
}

.md-meal-planner-kcal {
    color: #929292;
}

.md-meal-planner-icon {
    position: absolute;
    left: 0;
}

.md-meal-planner-calendar.mbsc-calendar .mbsc-schedule-event-all-day-inner {
    height: 40px;
    display: flex;
    align-items: center;
}

.md-meal-planner-calendar .md-meal-planner-event-title {
  font-size: 12px;
  line-height: 18px;
}

.md-meal-planner-calendar .md-meal-planner-event-desc {
  opacity: 0.6;
  line-height: 18px;
}

.md-meal-planner-popup .mbsc-segmented-item:first-child .mbsc-selected .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:first-child .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:first-child .mbsc-segmented-selectbox-inner {
    background: #f08786;
}

.md-meal-planner-popup .mbsc-segmented-item:nth-child(2) .mbsc-selected .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(2) .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(2) .mbsc-segmented-selectbox-inner {
    background: #8abe89;
}

.md-meal-planner-popup .mbsc-segmented-item:nth-child(3) .mbsc-selected .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(3) .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(3) .mbsc-segmented-selectbox-inner {
    background: #99d3ef;
}

.md-meal-planner-popup .mbsc-segmented-item:nth-child(4) .mbsc-selected .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(4) .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(4) .mbsc-segmented-selectbox-inner {
    background: #f0ce8e;
}

.md-meal-planner-popup .mbsc-segmented-item:last-child .mbsc-selected .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:last-child .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:last-child .mbsc-segmented-selectbox-inner {
    background: #b48bce;
}
```

## Implementation notes

- Use the timeline view with `type: 'week'` and `eventDisplay: 'fill'` so each meal event fills the full timeline cell for its day.
- Define resources for each meal type (Breakfast, Elevenses, Lunch, Dinner, Snack) with a `color`, a recommended `kcal` range, and an emoji `icon`. Resources serve as the vertical axis; no time grid is shown.
- Use `renderResource` (Angular: `resourceTemplate`, Vue: `#resource` slot) to display the meal type icon, name (in the resource color), and recommended calorie range in each resource row header.
- Use `renderTimelineEventContent` (Angular: `timelineEventContentTemplate`, Vue: `#timelineEventContent` slot) to render a custom event body showing the meal title and calorie count inside the timeline cell.
- Enable `clickToCreate` so a single click on an empty cell creates a new meal. Set `dragToMove: true` to allow moving meals to different days or resource rows. Set `dragToCreate: false` and `dragToResize: false`.
- Use `extendDefaultEvent` to pre-populate newly created events with a default title and `allDay: true`.
- Use `onEventCreated` to open the add popup. Read `args.event` to set the popup header date and derive the meal type from the event's `resource`.
- Use `onEventClick` to open the edit popup. Read `args.resourceObj.name` and format `args.event.start` with `formatDate` to populate the popup header.
- Use `onEventDeleted` to handle keyboard-triggered deletions by calling the same delete helper as the Delete button in the popup.
- Build the popup with the `Popup` component; default to `display="bottom"` with `fullScreen: true` on mobile, and use the `responsive` option to switch to `display: 'center'` at the medium breakpoint.
- Render a custom two-line popup header via the Popup's `renderHeader` prop (Angular: `headerTemplate` input on `<mbsc-popup>`, Vue: `#header` slot on `<MbscPopup>`) - the Popup's own renderer, distinct from the calendar's header renderer: a primary line showing the meal type name or "New meal", and a secondary line with the formatted date.
- Include a `SegmentedGroup` with one `Segmented` per meal type so users can change the category from within the popup. On change, update the in-progress event's `resource` to the selected ID.
- Add `Input` fields for name and calories and a `Textarea` for notes. Show a "Save" button in edit mode and an "Add" button in create mode, both alongside a Cancel action.
- For deletion, clicking the Delete button in the edit popup removes the event from state and shows a `Snackbar` with an Undo action that restores the meal.
- On popup close when the add form was canceled, refresh the events list to remove the temporary placeholder event that was added to the timeline.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-jquery.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config jquery
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   npm run dev
   ```

On a trial, add `--trial` to the config command: `mobiscroll config jquery --trial`.

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/jquery/getting-started/installation

## Related demos

- Previous: [Restaurant Shift Scheduling](https://demo.mobiscroll.com/jquery/timeline/restaurant-shift-management.md) - Build a restaurant shift planning interface with the JQuery Timeline — named shift columns with live occupancy counts and hierarchical staff groups by role.
- Next: [Dynamic Resource Availability](https://demo.mobiscroll.com/jquery/timeline/dynamically-color-and-invalidate.md) - Color-code resource rows and restrict drop zones by task type on a JQuery Timeline — compatible resources turn green, incompatible ones gray during drag.
