Source: https://github.com/enepomnyaschih/mt/tree/mt-2.3-5 (Git branch).
Now it's the time to learn a new layer of jWidget: Property and its interfaces Bindable and IProperty. Property is a value that can notify the clients about its modification. So, Property is a class with two methods: get, set - and a dispatcher onChange. Whenever you change the value of the property with set method call, onChange is dispatched. The message is not dispatched if the value has not been changed in the result of the set method call.
So, in our Mini-Twitter example, we can simplify Like/Unlike and Retweet/Unretweet behaviour implementation by introducing two boolean properties.
First, let's change the model. We need to replace simple boolean fields with properties.
import IProperty from "jwidget/IProperty"; import Property from "jwidget/Property"; export default class Tweet { // ... readonly like: IProperty<boolean>; readonly retweet: IProperty<boolean>; constructor(config: TweetConfig) { this.fullName = config.fullName; this.shortName = config.shortName; this.avatarUrl48 = config.avatarUrl48; this.contentHtml = config.contentHtml; this.time = config.time; this.like = new Property(config.like); this.retweet = new Property(config.retweet); }
Please remove the corresponding dispatchers, getters and setters - it is quite a big chunk of code.
Let's modify the view now. Instead of listening property change message manually, we can utilize property bindings.
First, we're going to build string properties with "Like/Unlike" and "Retweet/Unretweet" values from original boolean properties. To do so, we can use map method. Since life time of the model can be longer than life time of the view (because the model can live without a view, and not vice versa), it is important to aggregate this binding in the view with own method.
Second, we're going to bind DOM elements to all these properties with bindText function. It doesn't make much sense to aggregate this binding, because the source property belongs to the view itself, and the binding is so simple that it doesn't create any objects in the output (see jWidget philosophy for better understanding when you need to call own method and when not).
Third, we should also bind "active" CSS class presence to the values of the corresponding properties with bindClass function. Since this is a binding to model, we must aggregate it with own method.
protected renderLike(el: JQuery) { const text = this.own(this.tweet.like.map(like => like ? "Unlike" : "Like")); bindText(el, text); this.own(bindClass(el, "active", this.tweet.like)); el.on("click", event => { event.preventDefault(); this.tweet.like.set(!this.tweet.like.get()); }); } protected renderRetweet(el: JQuery) { const text = this.own(this.tweet.retweet.map(retweet => retweet ? "Unretweet" : "Retweet")); bindText(el, text); this.own(bindClass(el, "active", this.tweet.retweet)); el.on("click", event => { event.preventDefault(); this.tweet.retweet.set(!this.tweet.retweet.get()); }); }
And you may delete methods _updateLike and _updateRetweet - we don't use them anymore.
Run the application.
The syntax of properties and bindings is clean and straightforward. They let you make code shorter and more readable. Take a look at Bindable documentation for full list of bindings.
Tutorial. Part 6. Collection bindings