Pavel
2 September 2016

Using the MVVM pattern with Ext Scheduler

My name is Pavel and I recently joined the Bryntum development team. I have used Ext JS as my main […]

My name is Pavel and I recently joined the Bryntum development team. I have used Ext JS as my main JS framework for more than three years and today I’m going to show you how you can use the MVVM pattern in your application.

Let’s start by looking at what MVVM is and why we need it. As you may know, MVVM is the Model-View-ViewModel pattern. A Model holds data and a View is part of the UI. In our case, the ViewModel is a layer which is observing both the Model and the UI and helps to exchange data between them. All changes in the Model will reflect on our View instantly and the same is true in reverse. Immediately after changing data in the View, your Model will have updated values. The ViewModel allow us to control these changes and implement our custom logic, we don’t need to have extra event listeners doing manual work.

The picture below is from our WeekView example. Any changes to the edited Event record will be applied instantly to the Form. Any changes in the Form will be applied to the Event record. We can show this schematically:

ViewModel

The main focus of this post is the ViewModel and how it is used in our WeekView example. We will explore two ViewModel features called explicit binding and settable formulas, detailed information about these binding tools can be found in the Sencha docs. By using a ViewModel and these features we will workaround some flaws of the DateField and TimeField.

Creating an event form with ViewModel binding

We want to have a basic form to edit the details of a scheduled event. Something looking like the one below:

details

We also want the form to be bound to the ViewModel to automatically sync changes to and from the Model. For this we need to:

Now we face a slight problem with the date and timefields that we want to fix using a ViewModel…

Datefield doesn’t keep time, timefield doesn’t keep date

Oops… Who could imagine that this is not basic functionality supported by Ext JS? Unfortunately we will have to take care of it manually, let’s dig deep into the Ext JS sources.

Lets start from Ext.form.field.Date component. There are two visual ways how you can change value of datefield: by typing and by using the datepicker. If you type, the field will keep the time info only if the format of date field contains time information, and the date picker doesn’t care even if the format contains time. If you look under the hood, you will find out that safeParse method of datefield checks if not utilDate.formatContainsHourInfo(format) then utilDate.clearTime(parsedDate); and setValue method of the DatePicker class clears time instantly this.value = Ext.Date.clearTime(value || new Date(), true);

Now let’s move to the Ext.form.field.Time component. It clears the date as soon as the setValue method of the TimeField is called. Only the time information is used for the value v = me.getInitDate(v.getHours(), v.getMinutes(), v.getSeconds()); and this getInitDate uses the initDateParts property that is equal to initDateParts: [2008, 0, 1]. TimeField inherits from ComboBox, so initComponent calls me.store = Ext.picker.Time.createStore(me.format, me.increment); to prepare the picker store. This createStore method of timepicker uses the initDate property that is equal to initDate: [2008,0,1]

It seems we located a few date and time field flaws which we want to work around in the demo using a ViewModel. Let’s continue by talking about how we can control getting and setting data using the ViewModel. The main tools that will help us is:

Explicit Binding and Settable Formulas

Formula is a very powerful tool that enables you to control get and set operations on a ViewModel. Formulas can be described with different approaches, let’s take a look at a few of them:

// Simple function notation. Implicit binding.
// ExtJS analyzes which data was gotten from viewModel
// and creates dependencies.
// Each time data is changed this formula is called.
formulas: {
	foo: function (get) {
		return get('bar_record.some_data_field');
	}
}
// Object notation with get function. The same as previous one.
formulas: {
	foo: {
		get: function (get) {
			return get('bar_record.some_data_field');
		}
	}
}
// Object notation with get and set functions.
// The same, but now also set method is called, before data is changed.
formulas: {
	foo: {
		get: function (get) {
			return get('bar_record.some_data_field');
		},
		set: function (value) {
			this.set('bar_record.some_data_field', value);
		}
	}
}
// Object notation with explicit binding, get and set functions.
// We bind on exact data property, so there is no analyzing.
// Also get function takes a value as a parameter instead of function.
formulas: {
	foo: {
		bind: '{bar_record.some_data_field}',
		get: function (value) {
			return value;
		},
		set: function (value) {
			this.set('bar_record.some_data_field', value);
		}
	}
}
Workaround using Formulas and Binding

Because of the date- and timefield limitations described above we need to use a formula each time a record field is changed. We will apply explicit binding for our Form. Here we will describe formulas only for Start Date, but the same is true for End Date. Do not forget to use clone method when you need to modify original date object.

formulas : {
	StartDate : {
		bind : '{MyRecord.StartDate}',
		get  : function (date) { return date; },
		set  : function (date) {
			var MyRecord = this.get('MyRecord'),
				original = MyRecord.get('StartDate');

			// Apply time from original record
			Sch.util.Date.copyTimeValues(date, original);
			MyRecord.set('StartDate', date);
		}
	},
	StartTime : {
		bind : '{MyRecord.StartDate}',
		get  : function (date) { return date; },
		set  : function (date) {
			var MyRecord = this.get('MyRecord'),
				// Make a clone of the date to avoid changes by reference
				original = Ext.Date.clone(MyRecord.get('StartDate'));

			// Apply time to original record
			Sch.util.Date.copyTimeValues(original, date);
			MyRecord.set('StartDate', original);
		}
	}
}

Now we need to bind form fields on formulas.

{ xtype : 'datefield', bind : '{StartDate}' },
{ xtype : 'timefield', bind : '{StartTime}' }

That’s it. You can use formulas and customize your app logic to be as flexible as you need. Also I want to warn you about Binding Timings, so if you need to execute some logic instantly you need manually call viewModel.notify();

I hope this article was helpful and that you are not tired of reading it :). As a bonus you can check out our Weekview DEMO to see how MVVM pattern works in an Ext JS application.

Pavel

Ext JS Ext Scheduler Tips 'n tricks