Source: https://github.com/enepomnyaschih/mt/tree/mt-2.3-2 (Git branch).
In this part we meet BindableArray class. We will learn how to use it to display child UI component arrays. Our goal is to render an array of tweets developed in the previous part.
Let's dive directly to the view. Define class TweetFeed.
<div jwclass="mt-tweet-feed"> <div jwid="header">Tweets</div> <div jwid="tweets"></div> <div jwid="footer">...</div> </div>
import BindableArray from "jwidget/BindableArray"; import Component from "jwidget/Component"; import template from "jwidget/template"; import Tweet from "../model/Tweet"; import TweetView from "./TweetView"; @template(require("./TweetFeed.jw.html")) export default class TweetFeed extends Component { constructor(private tweets: Tweet[]) { super(); } protected renderTweets() { const tweetViews = this.tweets.map(tweet => new TweetView(tweet)); return this.own(new BindableArray(tweetViews)).ownValues(); } }
Let's review renderTweets method in details. Similarly to TweetView component, we've defined method render<ChildId> for element with jwid="tweets". But now this method not just fills the element with data, but renders an array of child components into it. This array is created from an array of tweet models with the following steps:
jWidget components recognize jWidget bindable arrays of components. If you return a bindable array as a result of render<ChildId> method, then this array gets rendered into the corresponding element as an array of child components.
Aggregation methods own and ownValues control the life time of child components. If object A owns object B, then destruction of object A automatically triggers destruction of object B. In our case, destruction of TweetFeed automatically triggers destruction of tweet view array. Thanks to ownValues call, destruction of the array triggers destruction of all its elements, i.e. tweet views. It is a good practice to destroy child UI components when you don't need them anymore, because any UI component may initialize its own bindings you can be unaware of. See Common practices in child component management for more instructions about how this can be achieved.
Let's define styles.
.mt-tweet-feed background #fff border 1px solid rgba(0,0,0,0.45) border-radius 6px box-sizing border-box width 522px &-header color #333 font-family Arial, sans-serif font-size 18px font-weight bold padding 10px text-shadow 0 1px 0 #fff &-footer border-top 1px solid #e8e8e8 padding 8px text-align center
Add the file to index.styl:
// All Stylus files should be imported here in the preferred order @import "view/TweetFeed" @import "view/TweetView"
And prepare new test data.
import "core-js/stable"; import "regenerator-runtime/runtime"; import "./index.styl"; import $ from "jquery"; import {createTweetByJson} from "./model/Tweet"; import TweetFeed from "./view/TweetFeed"; $(function () { const tweets = [ { "fullName": "Road Runner", "shortName": "roadrunner", "avatarUrl48": "backend/avatar-48.png", "contentHtml": "jWidget documentation is here <a href=\"https://enepomnyaschih.github.com/jwidget\" target=\"_blank\">enepomnyaschih.github.com/jwidget</a>", "timeAgo": 215000, "like": false, "retweet": true }, { "fullName": "Road Runner", "shortName": "roadrunner", "avatarUrl48": "backend/avatar-48.png", "contentHtml": "Tweet feed is growing", "timeAgo": 515000, "like": false, "retweet": false } ].map(createTweetByJson); new TweetFeed(tweets).renderTo("body"); });
Running the application in the browser displays the expected result.
Let's review one more way of child component rendering, without render<ChildId> method definition. Let's remove renderTweets method and override afterRender method instead:
import BindableArray from "jwidget/BindableArray"; import Component from "jwidget/Component"; import template from "jwidget/template"; import Tweet from "../model/Tweet"; import TweetView from "./TweetView"; @template(require("./TweetFeed.jw.html")) export default class TweetFeed extends Component { constructor(private tweets: Tweet[]) { super(); } protected afterRender() { super.afterRender(); const tweetViews = this.tweets.map(tweet => new TweetView(tweet)); const bindableTweetViews = this.own(new BindableArray(tweetViews)).ownValues(); this.addArray(bindableTweetViews, "tweets"); } }
This code is equivalent to the original one, but child component array is added dynamically with addArray method. This method takes element "jwid" as second argument, which should be used as a container for child components passed in the first argument. If we won't pass second argument, the array will be rendered into root element. Use the way you like more. I'll stick to the first way, utilizing render<ChildId> method.
Tutorial. Part 3. Named child components