Showing posts with label Knockout. Show all posts
Showing posts with label Knockout. Show all posts

Wednesday, April 6, 2016

Knockout in Chrome Web Apps (Yes!)

In a previous post I lamented on the fact that you can't use Knockout when creating a Chrome web app. The problem is that Knockout doesn't play well with Chrome's content security policy (CSP) because its binding engine uses "new Function" to parse bindings. This could allow someone to inject malicious code into the Chrome app which has access to low level resources that normal HTML5 apps don't.

After looking around though I came upon a solution to the CSP problem in Knockout. It seems I wasn't the only one with the problem. I found that Brian Hunt created a binding provider for Knockout that is CSP compliant. The project, called Knockout Secure Binding (KSB) can be found on GitHub. It is a replacement for Knockout's built in binding provider.

KSB is not a complete replacement though. It is limited in the kind of bindings you can create. Since it can't use "new Function" to parse bindings you can't create more complex bindings such as passing parameters into functions, and only a subset of JS expressions are available. But these limitations can easily be resolved by being more mindful of your bindings.

For example, say you have a binding for a "like" button that sets a certain thing as being liked. It calls a function in your view model called "like" and passes in the thing to like and true or false.

<button data-bind="click: like("knockoutJS", true)">Like</button>
<button data-bind="click: like("knockoutJS", false)">Dislike</button>

This won't work when using KSB because it doesn't support passing parameters to functions. You will need to rewrite the binding so it doesn't have to pass parameters.

<button data-bind="click: likeKnockoutJS">Like</button>
<button data-bind="click: dislikeKnockoutJS">Dislike</button>

Now the expression is moved back into the view model into functions called likeKnockoutJS and dislikeKnockoutJS. The downside is that this is going to require your view model to have a lot more functions in it, but your bindings will probably be a lot cleaner in the end.

KSB supports most any logical expression including comparison and mathematical operators, so you can perform some basic binding expressions.

<button data-bind="visible: !knockoutJSLiked())">Like</button>
<button data-bind="visible: knockoutJSLikeCount() > 0">Dislike</button>

Here the like button is visible only when the knockoutJSLiked() function does not return true. The dislike button is only visible when the knockoutJSLikeCount() function returns a value greater than zero.

To use KSB you just need to tell Knockout to use it as your binding provider before applying your bindings. You create an instance of a ko.secureBindingsProvider passing in the options you need and set that as the ko.bindingProvider.

let options = {
    attribute: "data-bind",        // default "data-sbind"
    globals: window,               // default {}
    bindings: ko.bindingHandlers,  // default ko.bindingHandlers
    noVirtualElements: false       // default true
};
ko.bindingProvider.instance = new ko.secureBindingsProvider(options);
ko.applyBindings(this.viewModel);

For more info on using KSB see the GitHub page.

In my app (PowerChord) I had to do some reworking to get my bindings to work with KSB. I also had to rewrite some components to make them work, which was probably a good thing because they were a bit convoluted. KSB forced me to write my components the way they should have been written instead of trying to use shortcuts with complicated bindings in my view.

Overall I'm very pleased with KSB and highly recommend it if you are writing a Chrome web app or need to implement the CSP and want to use Knockout as your binding framework (which I highly recommend).

Thursday, February 25, 2016

Knockout in Chrome Web Apps (Not!)

Edit: I found out how to get around this problem. View this post.

My love of Knockout.js just took a big hit. I recently converted one of my old applications to TypeScript and in the process I was going to try to turn it into a Chrome web app.

Short story: Chrome web apps don't like Knockout.

Long story: I've created a couple other Chrome web apps in the past but they are the kind that are only links to the app on my website. There is another kind that is actually is able to run in its own window like a real application and can be run on other devices like Chromebooks. That's what O was trying to do.

So I got my app working and used Knockout to do some data binding. Since my app uses local storage I also had to convert it to use Chrome's version of local storage. If you're building one of these Chrome apps you must use their local storage, not the HTML5 kind. Since I wanted my app to run both in the browser and as a Chrome app I had to do a lot of work to make it use the correct storage based on the context.

After I got everything working I tried to test my Chrome app and that's when things went south. Apparently Chrome apps have a strict content security policy (CSP) which doesn't allow you to use things like eval() or new Function(). This is because a Chrome app has access to more resources than a normal web app running in your browser. Therefore someone could do potential harm to your computer if they were able to access it using one of those runtime evaluation methods.

I found out real quickly that Knockout doesn't work with CSP because it does have some runtime evaluation code. Ugh! A bunch of work down the drain. Well, I shouldn't say it was all a waste of time. I did learn to make my apps work in different contexts, how Chrome local storage works, and how to set up a Chrome web app.

I'm just disappointed that I can't use Knockout if I want to have my apps available in the Chrome web store. I've committed heavily to Knockout for my web apps. I think it has a great balance between enough functionality and being too opinionated, like say Angular is. I really enjoy using it.

I guess the search is on for a new MV* framework that isn't too hard to learn, isn't too opinionated, and works in Chrome web apps. Maybe it's back to using Angular, or maybe it's something else, I don't know. I'll keep you posted.

Monday, February 8, 2016

Knockout with TypeScript - Embedded View Models

In my last post about Knockout and TypeScript I talked about interacting with observable arrays. This time we'll take a look at using embedded view models.

Knockout uses the concept of view models at its core. View models link to the view to provide data binding and functionality. There are times when you may want a more complicated structure for your view model. You may want to have child view models inside of view models so you can partition your application more effectively. So today I'll talk about how you do that.

What I sometimes like to do is create a master view model for my application in general, and then add view models underneath it for specific parts of the application. Say we have an application with two different views that have different data. In this example one view shows a list of favorite books and another shows favorite authors. You could have one view model with everything in it, but you can get a problem with namespacing if both views have similar properties.

class AuthorsViewModel1 {
    public appName = "Two views demo";
    public currentView = ko.observable("authors");
    public authors = ko.observableArray<string>();
    public books = ko.observableArray<string>();
    public sortAuthorsAscending = ko.observable(true);
    public sortBooksAscending = ko.observable(true);
    constructor() {
        this.sortAuthorsAscending.subscribe(() => this.authors.reverse());
        this.sortBooksAscending.subscribe(() => this.books.reverse());
    }
}

In this case we need to keep track of the sort order for both lists. Since they are both in the same view model we are required to give them unique names. We could make this easier by using child view models. Let's make a view model for authors and one for books.

class AuthorsViewModel {
    public authors = ko.observableArray<string>();
    public sortAscending = ko.observable(true);
    constructor() {
        this.sortAscending.subscribe(() => this.authors.reverse());
    }
}
class BooksViewModel {
    public books = ko.observableArray<string>();
    public sortAscending = ko.observable(true);
    constructor() {
        this.sortAscending.subscribe(() => this.books.reverse());
    }
}
class FavoritesViewModel {
    public appName = "Two Views Demo";
    public currentView = ko.observable("Authors");
    public authorsVM = new AuthorsViewModel();
    public booksVM = new BooksViewModel();
}

What we did was move the properties that were specific to each view into their own view model. Then we reference those view models inside of our main view model, FavoritesViewModel. This makes it a lot easier to determine what data goes with which view.

Now we need a way to use those view models in our HTML file. Knockout has a control flow binding that can help us out with that. It's the "with" binding. The "with" binding specifies a view model to use for a specific section of HTML.

<body>
    <h1 data-bind="text: appName"></h1>
    <select data-bind="value: currentView">
        <option>Authors</option>
        <option>Books</option>
    </select>
    <div data-bind="visible: currentView() === 'Authors', with: authorsVM">
        <h2>Favorite Authors</h2>
        <label><input type="checkbox" data-bind="checked: sortAscending" /> Ascending</label>
        <ul data-bind="foreach: authors">
            <li data-bind="text: $data"></li>
        </ul>
    </div>
    <div data-bind="visible: currentView() === 'Books', with: booksVM">
        <h2>Favorite Books</h2>
        <label><input type="checkbox" data-bind="checked: sortAscending" /> Ascending</label>
        <ul data-bind="foreach: books">
            <li data-bind="text: $data"></li>
        </ul>
    </div>
</body>

If you look at the <div> for our favorite authors section it has a "with: authorsVM" binding. From that point down in the DOM the context for the data will be the AuthorsViewModel object. So now if you're in the authorsVM context and you click the sort order checkbox it's going to refer to the sortAscending property on the authorsVM model. Same for the favorite books section, it has a "with: booksVM" binding.

The "with" binding in Knockout makes it easy for us to separate our view models by view which make it easier to read and maintain our code when dealing with multiple views. We start with a primary application view model and add child view models to it then associate them with different views.

Tuesday, December 1, 2015

Knockout with TypeScript - Observable Arrays

In my first post about using Knockout with TypeScript I talked about the basics of using these two together. This time I want to look at using Knockout's observable arrays. Observable arrays allow you to track changes to arrays, like adding or removing elements. Then you can use a foreach binding in your markup to output the elements of the array.

A lot of times your application will have lists of items that will grow or shrink depending on user interaction. For this example we'll expand upon the first post and create a list of tasks. We'll create an observable array and then output it onto the page.

First let's define the Task class. It has task name, percent complete and is complete fields.

class Task {
    public taskName = ko.observable("");
    public pctComplete = ko.observable("0");
    public isComplete: KnockoutComputed<boolean>;
    constructor() {
        this.isComplete = ko.computed(() => this.pctComplete() === "100");
    }
}

Now let's take a look at the view model for our app. It has app name and tasks fields.

class AppViewModel {
    public appName = "Task List";
    public tasks = ko.observableArray<Task>();
}

Notice that the "tasks" field is defined as an observable array of Task objects. That means it is now being tracked by Knockout so whenever we add another task to the list it will update the page. The next thing we need is a way to add a task to the array. Let's create addTask and reset functions.

class AppViewModel {
    //...
    public addTask(): void
    {
        var task = new Task();
        task.taskName("Task " + this.tasks().length);
        this.tasks.push(task);
    }
    public reset(): void
    {
        this.tasks([]);
    }
}

In our addTask function we create a new Task object, set its name to the length of the tasks array, then add it to the end of the array using push(). Notice that to get the underlying array from a Knockout observable you have to execute the tasks function, e.g. tasks(). Just like any other observable it's a getter/setter.

We also added a reset function. This removes all of the tasks from the array by setting the tasks property to an empty array.

NOTE: There are some methods on the observable array object such as push() and pop(), which makes it seem like you're dealing with an actual array, but you're not. It is an object that wraps an array. This can get a little confusing at first, but you just need to remember that if you want to directly access the array you need to get it from the property first. Be careful there also! You must use the push() method of the observable object, not the underlying array, or it won't be observed by Knockout. My advice is don't access the underlying array unless you need something that the observable can't give you, like the length of the array.

Now let's go and write some markup that will display the task list on our page.

<body>
    <h1 data-bind="text: appName"></h1>
    <button data-bind="click: addTask">Add Task</button>
    <button data-bind="click: reset">Reset</button>
    <div data-bind="foreach: tasks">
        <div>
            <input type="text" data-bind="value: taskName" />
            % Comp:<input type="number" min="0" max="100" step="1" data-bind="value: pctComplete" />
            <input type="checkbox" data-bind="checked: isComplete" disabled />Completed
        </div>
    </div>
</body>

In our markup we define two buttons with click events. One calls the addTask() function in our view model and the other calls reset(). Next we define a div element with a foreach binding set to the tasks array. Now everything inside that div will be repeated for each Task object in the array.

Notice that when inside of a foreach we are in the context of the current element of the array. Therefore we can get the name of the current task by using "value: taskName" as well as the other properties of the Task object.

OK, we have everything we need so let's try it out. If we click the Add Task button a new task will appear on the page with the name "Task 0". If we click the button again another task will appear and so on.

Now change the percent complete of one of the tasks to 100. The completed checkbox will become checked because of our isComplete computed observable.


If you click the Reset button all of the tasks will be removed and the page updated.

Observable arrays in Knockout make it easy for us to create dynamic lists if elements on a page. With only a few lines of code we were able to create an interactive list of tasks. Just remember, an observable array object is a wrapper over a JavaScript array, not an array itself.

<jmg/>

Tuesday, October 6, 2015

Knockout with TypeScript

I write a lot of small single page apps and games using TypeScript. Even with games there is often a need to interact with input fields on the page, for example to allow the user to change settings or show a help panel.

In the past I have used Angular to do that. But the more I used Angular the more I felt like I was wasting a bunch of time fighting with the framework. Angular, in my opinion, is too heavy for small apps and introduces too much complexity and overhead. I love its templating features but don't like being locked into its way of doing things. It's like driving a semi truck when all I need is a compact car.

So I started looking around for a replacement and soon found that I liked Knockout.js. It's really easy to use and doesn't force you to use a monolithic framework. I found it to work excellently for the size of apps I'm usually writing.

Today I thought I would write about the experience I've had making Knockout and TypeScript work nice together. There is a type definition file in the Definitely Typed package for KO that gives you all of the tooling you need (there's a Nuget package available), so right out of the gate you're getting a better experience than using plain JavaScript. However, all of the online documentation for KO is for JavaScript so you need to make a few modifications to optimize it for TypeScript.

KO relies heavily on view models, which are just plain objects that KO uses to interact with the view. The view model can have constant values, observable values, or computed values. Constant values are object members that aren't tracked, therefore if you change them the view won't get updated.

Observables are at the heart of KO. These values are properties that track when the value of the property has changed. Therefore if you change its value the view will be notified to update the value. And vice versa; if you change an input in the view it will update the value in the model. You can also add your own handlers to track changes of observables if you want. You create an observable by calling ko.observable() passing it a default value (note: "ko" is the global Knockout object).

Computed values are computed from one or more observables. For example you might want to convert a value to a percentage to display in the view. Like observables computed values are tracked by the view and automatically updated. You create a computed value by calling ko.computed() and giving it a function that computes the value.

Let's take a look at a simple view model. You can use an object literal for a view model, and this may work for very simple ones, but more often than not you'll find yourself needing to define a class instead. I'll explain why a little later.

class ViewModel {
    public appName = "My Application";
    public taskName = ko.observable("");
    public pctComplete = ko.observable("0");
}

Here appName is a constant value, and taskName and pctComplete are observable values. Although I haven't explicitly specified a type for the observables, they are generics and it implicitly defines them as observable of strings (the type is KnockoutObservable<string>).

You're probably wondering why I didn't make pctComplete a number instead of a string. This is one of the areas where KO and TypeScript don't interact too well together. Since JavaScript doesn't know anything about types, neither does KO. You can make an observable of any type, but if you bind it to an input field in your view (even if type=number) it will replace the value with a string. So you might as well save yourself the trouble and always use a string, then convert it as you need to.

OK, great, we have a view model. Now we need to tell KO about it. For that you call ko.applyBindings() passing it an instance of the view model.

var viewModel = new ViewModel();
ko.applyBindings(viewModel);

Now KO will apply the view model to your view. Let's define a view that uses the model.

<h1 data-bind="text: appName"></h1>
<input type="text" data-bind="value: taskName" />
<input type="number" min="0" max="100" step="1" data-bind="value: pctComplete" />

KO makes use of the data-bind attribute to interact with it in HTML. First we're telling it to set the text of the h1 element to whatever the value of appName is in the view model. Then we set the value of the text input element to the value of taskName. Finally we set the value of the number input element to the value of pctComplete.

Now if you were to change any of the values of the input fields the values in the view model will automatically get updated as well.

Lets add a computed field now. Say we want a boolean value called isComplete and it will be set to true if and only if pctComplete is 100.

class ViewModel {
    // ...
    public isComplete: KnockoutComputed<boolean>;
    constructor() {
        this.isComplete = ko.computed(() => this.pctComplete() === "100");
    }
}

Remember earlier when I said you probably want to use a class instead of an object literal? Computed values are the reason why. You can't define computed values in an object literal that reference other properties of the object. Therefore you're going to need to do it in a constructor.

So what we did here was define an isComplete member of the object and type it as a computed boolean value using KnockoutComputed<boolean>. In the constructor is where we define the function behind the computed value. Here we are defining a function that returns true if the value of pctComplete is "100". Note that we need to use parens to get the value of pctComplete. That's because observables are actually property functions that either get or set the value of the property.

Now we can add a checkbox to the view that uses the computed value.

<input type="checkbox" data-bind="checked: isComplete" />

Here we tell the checkbox to become checked whenever isComplete is true. Now if we were to change the value of the pctComplete input to "100" the checkbox would become checked.

Lets look at one more basic feature of KO view models. In addition to the three properties of a view model I mentioned above you may also define functions. Functions are useful for handing click events in your view. Say we wanted to add a reset button, then we would need a function to reset the view model.

class ViewModel {
    // ...
    public reset(): void {
        this.pctComplete("0");
        this.taskName("");
    }
}

Here's the button in the view.

<button data-bind="click: reset">Reset</button>

We create a button and tell KO that on click to call the reset() function in the view model. Now if we click the reset button the pctComplete and taskName fields will be reset.

Those are the basics of using KO with TS. In another post I'll go over some more advanced topics like view models within view models and creating custom elements.

<jmg/>