Knockout JS and Moment JS - an intro
I am going to give a BRIEF introduction to Knockout JS with Moment Js. If you want to go through some step-by-step tutorials, go to http://learn.knockoutjs.com to learn more about knockout. The real purpose of this post is to teach me about using Camtasia, playing with some dates, and make sure I can explain what I have written in the past. The code in this example is based on: http://learn.knockoutjs.com/#/?tutorial=loadingsaving.
I am going to skip a lot of the typing and step-by-step because that is available elsewhere.
Have you ever noticed that almost all demos avoid using dates? There is a reason for that. Dates are not easy. You have to consider, timezone, formats for different countries, native formats of C#, Java, JavaScript, and the list goes on. Sure you can store things as strings just for viewing the data. But then you can't do things like:
- is that 4 days ago
- set the date to two weeks from now
- is today's date before or after the entered date.
So let's get to the code and I'll show you why moment.js is the answer to JavaScript dates.
I am going to use the following frameworks for this example:
- knockout.js
- knockout.mapping.js (a plugin for ko that maps objects dynamically)
- moment.js
- jquery and jquery ui for the date picker only
Here is my include files to get started:
[code lang="html"] [/code]
Now, let's write some JavaScript:
[code lang="javascript"]
var TaskViewModel = function(data) { this.title = ko.observable(data.title); this.isDone = ko.observable(data.isDone); }
var TaskListViewModel = function(data) { // Data var self = this; self.tasks = ko.mapping.fromJS(data); self.newTaskText = ko.observable(); self.incompleteTasks = ko.computed(function() { return ko.utils.arrayFilter(self.tasks(), function(task) { return !task.isDone() }); });
// Operations self.addTask = function() { self.tasks.push(new TaskViewModel({ title: this.newTaskText()) })); self.newTaskText(""); }; self.removeTask = function(task) { self.tasks.remove(task) }; } var taskListViewModel = new TaskListViewModel(data); ko.applyBindings(taskListViewModel); [/code]
So that is simple enough right? Remember, this code came straight from learn knockout, I'm just expanding it with a date. We define a TaskViewModel with two properties, title and isDone. Both are ko.observables, which means knockout will know when they change and update the parameters correctly.
The next part we create a TaskListViewModel. This is an important difference. The list is actually a list of TaskViewModels. The incompleteTasks is a ko.computed (which is for another discussion, but just know that it identifies any task that is not done). Also, in the TaskListViewModel, there is an addTask function, that simply pushes a task to the task list and a removeTask function that removes the task from the list.
Currently I don't have any data, defined, so this won't work just yet. Plus we haven't done the html part either. Just wait, I'm getting there.
So now let's write the html stuff for the bindings of knockout.
[code lang="html"] Tasks Add task: Add Delete You have incomplete task(s) - it's party time! [/code]
So the list here loops through the tasks as long as we have a task: [code lang="html"] [/code]
the data-binds in the inputs are:
- checked (if it is done)
- value to show the title
- disable to grey it out if it is done
- click to allow you to delete a task
Let's add some simple data so you can see the format: [code lang="javascript"] var data = [ { 'title': 'My Test Task', 'isDone': true, 'dueDate': '05/16/2015' }, { 'title': 'My Test Task2', 'isDone': false, 'dueDate': '07/15/2015' }, ]; [/code]
So that is the simple beginning of the tasks from the tutorial. Now let's add some date functionality and use moment.
One thing to note, the dueDate in the data can be there, but nothing is using it yet, so it doesn't matter if it is there.
Ok, let's show the due date in the html
[code lang="html"] ... ... [/code]
Oh wait, there is no binding for date in knockout! So we have to create a custom binding... let's do that in the JavaScript
[code lang="javascript"] ko.bindingHandlers.date = { init: function (element, valueAccessor, allBindingsAccessor) { //initialize datepicker with some optional options var options = allBindingsAccessor().datepickerOptions || {}; $(element).datepicker(options);
}, update: function (element, valueAccessor) { var value = ko.utils.unwrapObservable(valueAccessor()), current = $(element).datepicker("getDate");
valueAccessor(current); } }; [/code]
This is pretty simple but allows you to use the datePicker from jquery to set the value. Simple enough.
However, this is because I'm getting a specific format back. If you are making server side calls (to get real data from .Net) then the format may not be what you want. Then you have to do some parsing and check if you are getting /Date(1230123898)/ back or "01/15/2015" or "15/01/2015", or... you get the point.
Now I want to add the dueDate to the add functionality too, so in the addTask function I add the dueDate in the format I want with moment:
[code lang="javascript"] self.tasks.push(new TaskViewModel({ title: this.newTaskText(), dueDate: moment().format('MM/DD/YYYY') })); [/code]
Then in the code for the ViewModel I add the dueDate
[code lang="javascript"] var TaskViewModel = function(data) { this.title = ko.observable(data.title); this.isDone = ko.observable(data.isDone); this.dueDate = ko.observable(data.dueDate); } [/code]
That should do it. A new date, with a jquery datepicker, and a custom binding to handle the datepicker binding.
You can download the entire project here: https://github.com/lotekmedia/sandbox/tree/master/knockout-moment I'm working on a video of this so that should be coming soon... but that is all for now.
Thanks for stopping by!