AngularJS1.5に再挑戦 その12 Forms and ngSubmit
概要
AngularJS1.5に再挑戦 その12 Forms and ngSubmit
参考動画・参考サイト
AngularJS — Superheroic JavaScript MVW Framework
Forms and ngSubmit
[ {"title": "Ne Title", "id":1, "description": "This is a book", "publishDate": "2017-01-08", "comments":[ {"id":1, "text": "Nice book"}, {"id":2, "text":"Another comment"} ]}, {"title": "Title2", "id":2, "description": "This is a book"}, {"title": "Some Title3", "id":3, "description": "This is a book"}, {"title": "Some Title4", "id":4, "description": "This is a book"} ]
- blog-detai.htmlにコメント表示のviewを追記する。これは、jsonからcommentsの部分を取り出して、表示しているだけ。
<h1>{{ post.title }}</h1> <p>{{ post.description }}</p> <p>{{ post.publishDate }}</p> <h3>Comments</h3> <ul> <li ng-repeat='comment in post.comments'> {{ comment.text }} </ul> <form> <input type='hidden' ng-model='comment.id' value='5'> <textarea ng-model='comment.text'></textarea> </form>
- ng-submitを使ってフォームを作成してみる。
<h1>{{ post.title }}</h1> <p>{{ post.description }}</p> <p>{{ post.publishDate }}</p> <h3>Comments</h3> <ul> <li ng-repeat='reply in post.comments'> {{ reply.text }} </ul> <p style='color:red;' ng-if='reply'>Preview: {{ reply.text }}</p> <form ng-submit=''> <input type='hidden' ng-model='reply.id' value='5'> <textarea ng-model='reply.text'></textarea> <input type='submit'/> </form>
- blog-detail.component.jsに追記する。
... $scope.addReply = function(){ console.log($scope.reply) //この時点では、scope.replyをどこにもセットしていない。 } ...
- さらに、blog-detail.htmlにaddReply functionを挿入する。
<form ng-submit='addReply()'> # このaddReply()のカッコまでちゃんと入れる <input type='hidden' ng-model='reply.id' value='5'> <textarea ng-model='reply.text'></textarea> <input type='submit'/> </form>
- この状態で何かしら送信すると以下のように表示される。$scope.replyの箇所には、自動でsubmitされたtextが表示される。
Object { text: "dddd" }
- ここにさらにidを加えたいので、blog-detail.component.jsに以下のように追記する。これでidも表示されるようになる。
angular.forEach(data, function(post){ if (post.id == $routeParams.id){ $scope.notFound = false $scope.post = post $scope.reply = { "id": post.comments.length + 1, "text": "", }
- この機能を分離させて、関数にする。resetReply()というfunctionを作成する。
angular.module('blogDetail'). component('blogDetail', { templateUrl: '/templates/blog-detail.html', controller: function(Post, $http, $location, $routeParams, $scope){//Postモジュールを追加する。 Post.query(function(data){ //ここで定義しておかないと、forループに入らなかった時に、notFoundが定義されていないことになるため。 $scope.notFound = true angular.forEach(data, function(post){ if (post.id == $routeParams.id){ $scope.notFound = false $scope.post = post resetReply() } }) }) $scope.addReply = function(){ console.log($scope.reply) resetReply() } function resetReply(){ $scope.reply = { "id": $scope.post.comments.length + 1, "text": "", } }
- さらに、これをpushして表示させる。この時点では、jsonデータに追加はされていない。
$scope.addReply = function(){ console.log($scope.reply) //$scope.replyをpushでその配列に追加している。 $scope.post.comments.push($scope.reply) resetReply() }
- さらにfilter機能も簡単につけられる。
<h3>Comments</h3> <ul> <li> <input type='text' ng-model='query' placeholder='filter comments' /> </li> <li ng-repeat='comment in post.comments | filter: query'> {{ comment.text }} </ul>
- これはすごい!
AngularJS1.5に再挑戦 その11 ngClick & confirmClick Directives
概要
AngularJS1.5に再挑戦 その11 ngClick & confirmClick Directives
参考動画・参考サイト
AngularJS — Superheroic JavaScript MVW Framework
ngClick & confirmClick Directives
- ngClickを使ってみる。ngClickの詳しい解説は以下の通り。
- blog-list.htmlを修正する。ng-clickを定義する。ここでは、contextとしてgoToItem()というファンクションを代入しているので、goToItem()をclick-confirm.directives.jsで定義する。
<a ng-href='/blog/{{ item.id }}' ng-click='goToItem()' />{{ item.title }} {{ item.publishDate }}</a>
- goToItem()は、blog-list.components.jsで定義するらしい。
'use strict'; angular.module('blogList'). component('blogList', { templateUrl: '/templates/blog-list.html', controller: function(Post, $routeParams, $scope){ $scope.goToItem = function(){ console.log("some item") }; $scope.items = Post.query(); } });
- 再度blog-list.htmlを修正する。
<a href='#' ng-click='goToItem(item)' />{{ item.title }} {{ item.publishDate }}</a>
- またblog-list.components.jsを編集する。コードの追記内容は、goToItemをcontextとして渡すことを目的としている。
'use strict'; angular.module('blogList'). component('blogList', { templateUrl: '/templates/blog-list.html', controller: function(Post, $location, $routeParams, $rootScope, $scope){ // function(post) == goToItem(post)のため、htmlでは、postの箇所にオブジェクトを渡す。 $scope.goToItem = function(post){ // locationで、/blog/post.idにアクセスせよという指示 $location.path("/blog/" + post.id) // $rootScope.$apply(function(){ // }) }; $scope.items = Post.query(); } });
- blog-list.htmlもいかのように修正する。
<h1>Blog List</h1> <ul ng-if='items.length > 0'> <li ng-repeat='post in items'> <a ng-href='/blog/{{ post.id }}' confirm-click='Are you ready?' confirmed-click='goToItem(post)' />{{ post.title }} {{ post.publishDate }}</a> <!-- <a href='#' ng-click='goToItem(post)' />{{ post.title }} {{ post.publishDate }}</a> --> <!-- <confirm-click message='メッセージ!' eq='10+100+10' post='item'></confirm-click> --> </li> </ul> <span ng-if='items.length == 0'>Posts coming soon</span>
- ただ、このままでは、comfirmしてもしなくても、ページ遷移してしまう。click-confirm.direcitive.jsをクリックした時のデフォルトアクションを停止する設定を追記する。
'use strict'; angular.module('confirmClick'). directive('confirmClick', function(){ return { restrict: "A", link: function(scope, element, attr){ var msg = attr.confirmClick || "Are you Sure?"; var clickAction = attr.confirmedClick; element.bind("click", function(event){ event.stopImmediatePropagation(); event.preventDefault() if (window.confirm(msg)){ scope.$eval(clickAction) } else { console.log("Canceled") } }); } } });
これで、できるようになった!でも仕組みはさっぱりわからんな(^ ^;)ということで、これは参考書でちゃんと復習しておこう。
全体の流れは、デフォルトのアクションを停止して、特定のアクションが行われた時だけ、clickactionが呼び出されるようにするという仕様だと思われる。そうする方が、特定の機能を停止させるよりも楽なのかもしれない。
AngularJS1.5に再挑戦 その10 Custom Angular Directive for Confirmed Click
概要
AngularJS1.5に再挑戦 その10 Custon Angular Directive for Confirmed Click
参考動画・参考サイト
AngularJS — Superheroic JavaScript MVW Framework
Custon Angular Directive for Confirmed Click
- 公式サイトはこちらを参照。htmlタグを使って、DOMを操作するようなイメージ。
js/app/utils フォルダを作成する。その中に、confirm-click.module.js, confirm-click.directive.jsを作成する。
confirm-click.module.jsに以下のようにコードを追記する。
'use strict'; angular.module('confirmClick', []);
- confirm-click.directive.jsに以下のようにコードを追記する。
'use strict'; angular.module('confirmClick'). directive('confirmClick', function(){ return { link: function(scope, element, attr){ console.log(scope) console.log(element) console.log(attr) } } })
- app.module.jsに
confirmClick
を追記する。
'use strict'; angular.module('try', [ // external 'ngResource', 'ngRoute', // internal 'blogDetail', 'blogList', 'confirmClick' ]);
- blog-list.htmlのhtmltagにconfirm-clickを挿入してみる。
<h1>Blog List</h1> <ul ng-if='items.length > 0'> <li ng-repeat='item in items' confirm-click><a ng-href='/blog/{{ item.id }}'>{{ item.title }} {{ item.publishDate }}</li> </ul> <span ng-if='items.length == 0'>Posts coming soon</span>
Error: eslement is not defined が表示されてしまう。。。これは、なぜか不明。と思ってたら、log(element)と書くべきところを、log(eselement)と書いていただけだった。凡ミス〜!
confirm-click.directive.jsを修正する。
'use strict'; angular.module('confirmClick'). directive('confirmClick', function(){ return { link: function(scope, element, attr){ console.log(scope) console.log(eslement) console.log(attr.confirmClick) console.log(attr.ngHref) console.log(attr.href) } } });
- confirm-click.directive.jsを少し修正する。
'use strict'; angular.module('confirmClick'). directive('confirmClick', function(){ return { template: "<a href=''>Are you sure?</a>", link: function(scope, element, attr){ console.log(scope) console.log(element) console.log(attr.confirmClick) console.log(attr.ngHref) console.log(attr.href) } } });
- blog-list.htmlにconfirm-clickを追加する。これで、confirm-clickでrenderingされたページが表示される。
<h1>Blog List</h1> <ul ng-if='items.length > 0'> <li ng-repeat='item in items'> <a ng-href='/blog/{{ item.id }}'>{{ item.title }} {{ item.publishDate }}</a> <confirm-click></confirm-click> </li> </ul> <span ng-if='items.length == 0'>Posts coming soon</span>
- 試しに、
index.html
を以下のように修正してみる。
<h1>Blog List</h1> <ul ng-if='items.length > 0'> <li ng-repeat='item in items'> <a ng-href='/blog/{{ item.id }}' confirm-click>{{ item.title }} {{ item.publishDate }}</a> <confirm-click></confirm-click> </li> </ul> <span ng-if='items.length == 0'>Posts coming soon</span>
- そうすると、ページには以下のように表示される。
Are you sure? Are you sure? Are you sure? Are you sure? Are you sure? Are you sure? Are you sure? Are you sure?
つまり、directiveのmoduleをhtmlタグとして挿入すると、その内容を上書きできるということに何なる?
ここで上書きの制御に関する項目
restrict
を設定する。これを設定すると上書きはしないという設定なのかな?
'use strict'; angular.module('confirmClick'). directive('confirmClick', function(){ return { restrict: "E", template: "<a href=''>Are you sure?</a>", link: function(scope, element, attr){ console.log(scope) console.log(element) console.log(attr.confirmClick) console.log(attr.ngHref) console.log(attr.href) } } });
- 他にもscopeでcontextを設定できる。
'use strict'; angular.module('confirmClick'). directive('confirmClick', function(){ return { scope: { message: "@message", }, restrict: "E", template: "<a href=''>Are you sure?</a>", link: function(scope, element, attr){ console.log(scope.message) console.log(element) console.log(attr.confirmClick) console.log(attr.ngHref) console.log(attr.href) } } });
- blog-list.htmlで、messageに引数を渡す。
<h1>Blog List</h1> <ul ng-if='items.length > 0'> <li ng-repeat='item in items'> <a ng-href='/blog/{{ item.id }}' confirm-click>{{ item.title }} {{ item.publishDate }}</a> <confirm-click message='メッセージ!'></confirm-click> </li> </ul> <span ng-if='items.length == 0'>Posts coming soon</span>
- 同様に、文字列だけではなく、計算した数字なども渡せる。試しに、blog-list.htmlに設定してみる。
<h1>Blog List</h1> <ul ng-if='items.length > 0'> <li ng-repeat='item in items'> <a ng-href='/blog/{{ item.id }}' confirm-click>{{ item.title }} {{ item.publishDate }}</a> <confirm-click message='メッセージ!' eq='10+100+10'></confirm-click> </li> </ul> <span ng-if='items.length == 0'>Posts coming soon</span>
- confirm-click.directive.jsに渡してみる。これで、eqには計算された数字が代入される。
scope: { message: "@message", eq: "=eq", }, ... link: function(scope, element, attr){ console.log(scope.eq)
- 変数を代入することもできる。例えば、以下のように変数を代入してみる。
<confirm-click message='メッセージ!' eq='10+100+10' post='item'></confirm-click>
- confirm-click.directive.jsをいかのように設定する。
... return { scope: { message: "@message", eq: "=eq", post: "=post" }, ... console.log(scope.post)
- そうすると、コンソールには、以下のようにインスタンスの要素リストが表示される。
Object { title: "Some Title", id: 1, description: "This is a book", publishDate: "2017-01-08", $$hashKey: "object:6" }
- さらにこれを発展させて、この変数をdirectives内のtemplateに埋め込むこともできる。
... return { scope: { message: "@message", eq: "=eq", post: "=post" }, ... template: "<a href=''>{{ post.title }}</a>", link: function(scope, element, attr){ console.log(scope.post) ...
- blog-list.htmlの表示を担当していた箇所をそのまま、templateに埋め込んでみる。
template: "<a ng-href='/blog/{{ post.id }}'>{{ post.title }} {{ post.publishDate }}</a>",
console.log(scope.post), console.log(attr.post) の違いは、scopeは多分、valueを表示して、attrは属性(もしくはそのままの数値、式だったら、式をそのまま表示する)ということなのかな。
次に、clickした時のイベントを設定する。
var msg = scope.message || "Are you sure?" element.bind("click", function(event){ if (window.confirm(msg)){ console.log('/blog/' + scope.post.id) } })
- これは、comfirmするかしないかに関わらず、画面が遷移するので、confirmが会った時のみ画面を遷移するように設定し直す。
... // $rootScope, $locationをimportする。 directive('confirmClick', function($rootScope, $location){ ... //templateでのurl遷移はなくす template: "<a ng-href='#'>{{ post.title }} {{ post.publishDate }}</a>", link: function(scope, element, attr){ var msg = scope.message || "Are you sure?" element.bind("click", function(event){ if (window.confirm(msg)){ // console.log('/blog/' + scope.post.id) // rootScopeと、locationで、画面遷移を設定する。 $rootScope.$apply(function(){ $location.path("/blog/" + scope.post.id) }) } }) ...
- 最終的には以下のようになる。この部分の解説はなかったなぁ(^ ^;)
'use strict'; angular.module('confirmClick'). directive('confirmClick', function(){ return { restrict: "A", link: function(scope, element, attr){ var msg = attr.confirmClick || "Are you sure?"; var clickAction = attr.confirmedClick; element.bind("click", function(event){ if (window.confirm(msg)){ scope.$eval(clickAction) } }); } } });
AngularJS1.5に再挑戦 その9 ng-resource for mapping to a Restful API
概要
AngularJS1.5に再挑戦 その9 ng-resource for mapping to a Restful API
参考動画・参考サイト
AngularJS — Superheroic JavaScript MVW Framework
ng-resource for mapping to a Restful API
coreフォルダ、postフォルダ、post.service.js, post.module.jsを作成する。なぜcoreフォルダという名称のものを作成するかというと、ここにREST API向けへのmappingをするためのモジュールがここに入っており、その機能はangularのcoreだからとかそういう感じのニュアンスで言ってたと思う(^ ^;)
post.module.jsにコードを書いていく。
'use strict'; angular.module('post', []);
- post.service.jsにコードを書いていく(なんで、ここは、service.jsなんだろうな。)
'use strict'; angular. module('post'). # こっちは、post factory('Post', function($resource){ //こっちの名前は、Post var url = '/json/posts.json' return $resource(url, {}, { query:{ method: "GET", params: {}, isArray: true, cache: true, //cacheはスピードアップするからつけることが多いとのこと。 // transformResponse // interceptor } get:{ method: "GET", // params: {"id": @id}, isArray: true, cache: true, } }) });
- index.htmlに追記する。
<script src='/js/app/core/post/post.module.js' ></script> <script src='/js/app/core/post/post.service.js' ></script>
- blog-detail.module.jsにPost moduleを追加する。
'use strict'; angular.module('blogDetail', ["post"]); //モジュール名は、小文字
- blog-detail.components.jsにPostを追加する。
'use strict'; angular.module('blogDetail'). component('blogDetail', { templateUrl: '/templates/blog-detail.html', controller: function(Post, $http, $location, $routeParams, $scope){//Postモジュールを追加する。 console.log(Post.query())//これで、Postで定義したqueryメソッドが利用できる。 console.log(Post.get())//これで、Postで定義したgetメソッドが利用できる。 ...
この状態で、detail ページにアクセスすると、consoleにquery, getで指定したデータが表示される。
つまりここまでの流れは、以下の通りか?
* post.service.jsで、postモジュールを定義する。 * postモジュールは、'/json/posts.json'にアクセスして、データを整形する(mappingする)役割がある。 * そのためのメソッドをqueryメソッド, getメソッドでそれぞれ定義する。 * index.htmlにそのpostモジュール関連のファイルを読み込ませる。post.serviceとpost.module * blog-detail.module.jsに、postモジュールを読み込ませる。 * blog-detail.components.jsの関数に、Postモジュールを継承させて、queryメソッド、getメソッドを使えるようにする。 * queryメソッド、getメソッドで、取得したデータは、その関数内で自由に利用できる。
- 以下のようにコードを追記すると、$http.getで取得して整形した時と同じような結果が得られる。
... controller: function(Post, $http, $location, $routeParams, $scope){//Postモジュールを追加する。 console.log(Post.query())//これで、Postで定義したqueryメソッドが利用できる。 console.log(Post.get())//これで、Postで定義したqueryメソッドが利用できる。 Post.query(function(data){ angular.forEach(data, function(post){ if (post.id == $routeParams.id){ $scope.notFound = false $scope.post = post } }) }) ...
- 最終的に以下のようなすっきりしたコードになる。
'use strict'; angular.module('blogDetail'). component('blogDetail', { templateUrl: '/templates/blog-detail.html', controller: function(Post, $http, $location, $routeParams, $scope){//Postモジュールを追加する。 console.log(Post.query())//これで、Postで定義したqueryメソッドが利用できる。 console.log(Post.get())//これで、Postで定義したqueryメソッドが利用できる。 Post.query(function(data){ angular.forEach(data, function(post){ if (post.id == $routeParams.id){ $scope.notFound = false $scope.post = post } }) }) // notFoundがtrueの場合 if ($scope.notFound){ console.log("Not found") // change location。これには、$locationの継承が必要。 $location.path("/404") } } });
- blog-list.module.jsと、blog-list.components.jsも同様に処理してみる。
'use strict'; angular.module('blogList', ["post"]);
'use strict'; angular.module('blogList'). component('blogList', { templateUrl: '/templates/blog-list.html', controller: function(Post, $routeParams, $scope){ $scope.items = Post.query(); } });
すげー!!めっちゃシンプルになった。
使うときは、公式サイト確認した方がいいよとのこと。
AngularJS1.5に再挑戦 その8 HTTP Request in Angular - $http
概要
AngularJS1.5に再挑戦 その8 HTTP Request in Angular - $http
参考動画・参考サイト
AngularJS — Superheroic JavaScript MVW Framework
AngularJS1.5に再挑戦 その8 HTTP Request in Angular - $http
[ {"title": "Some Title", "id":1, "description": "This is a book", "publishDate": "2017-01-08"}, {"title": "Title2", "id":2, "description": "This is a book"}, {"title": "Some Title3", "id":3, "description": "This is a book"}, {"title": "Some Title4", "id":4, "description": "This is a book"}, ]
上記はjson形式に合うように、文字列は、ダブルクオーテーションで囲ってある。
config.ruファイルのurlsに、"/json"を追記する。
blog-detail.component.jsに、posts.jsonを取得して、そのresponseをconsole.logに表示するというcomponentsのコードを追記する。
'use strict'; angular.module('blogDetail'). component('blogDetail', { templateUrl: '/templates/blog-detail.html', controller: function($location, $routeParams, $scope){ $scope.notFound = true $http.get("/json/posts.json").then(successCallback, errorCallback); function successCallback(response, status, config, statusText){ console.log(response) } function errorCallback(response, status, config, statusText){ console.log(response) } // notFoundがtrueの場合 if ($scope.notFound){ console.log("Not found") // change location。これには、$locationの継承が必要。 $location.path("/404") } } });
SyntaxErrorが出て、取得ができない。。。。これは、posts.jsonのリストの最後に、
,
が入っていたためぽい。それを削除して再度トライしたら成功した。consolelogを見ると以下のように表示される。これがresponseの中身。
Object { data: Array[4], status: 200, headers: fd/<(), config: Object, statusText: "OK " }
- 試しに、console.log(response.data)として、再度トライして見る。以下の通りの返し。
Array [ Object, Object, Object, Object ]
- 一つ一つのobjectは、以下のようにデータが格納されている。
description:"This is a book" id:4 title:"Some Title4"
- 最終的に以下のようにforEachで、DetailViewのそれぞれのアイテムを表示させる。
function successCallback(response, status, config, statusText){ $scope.notFound = true var blogItems = response.data $scope.posts = blogItems //ここで、responseのリストをcontextのpostsに代入する。 angular.forEach(blogItems, function(post){ if (post.id == $routeParams.id){ $scope.notFound = false $scope.post = post } }) } function errorCallback(response, status, config, statusText){ $scope.notFound = true console.log(response) } // notFoundがtrueの場合 if ($scope.notFound){ console.log("Not found") // change location。これには、$locationの継承が必要。 $location.path("/404") }
ポイントは、
$scope.notFound = true
を各functionごとに毎回定義していること。この理由はよくわからない。この辺は、参考書などで要復習だな。
AngularJS1.5に再挑戦 その7 ForEach Loop in Angular JS Files
概要
AngularJS1.5に再挑戦 その7 ForEach Loop in Angular JS Files
参考動画・参考サイト
AngularJS — Superheroic JavaScript MVW Framework
ForEach Loop in Angular JS Files
- blog-detail.component.jsに追記する。
'use strict'; angular.module('blogDetail'). component('blogDetail', { templateUrl: '/templates/blog-detail.html', controller: function($routeParams, $scope){ var blogItems = [ {title: "Some Title", id:1, description: "This is a book", publishDate: "2017-01-08"}, {title: "Title2", id:2, description: "This is a book"}, {title: "Some Title3", id:3, description: "This is a book"}, {title: "Some Title4", id:4, description: "This is a book"}, ] // console.log($routeParams) $scope.title = "Blog " + $routeParams.id // notFound変数をtrueにしておく。一致したものは、falseにする。 $scope.notFound = true // forEach公文で、postのitemを総当たりして、一致したものをconsoleに表示する。 angular.forEach(blogItems, function(post){ if (post.id == $routeParams.id){ $scope.notFound = false $scope.post = post } console.log(post) }) // notFoundがtrueの場合 if ($scope.notFound){ console.log("Not found") } } });
- blog-detail.htmlに追記する。
<h1>{{ post.title }}</h1> <p>{{ post.description }}</p> <p>{{ post.publishDate }}</p>
- notFound=trueの時に、404ページに誘導する。blog-detail.component.jsにコードを追記する。
... controller: function($location, $routeParams, $scope){ ... if ($scope.notFound){ console.log("Not found") // change location。これには、$locationの継承が必要。 $location.path("/404") } ...
- $location便利やな〜!
AngularJS1.5に再挑戦 その6 Detail View with Parameters
概要
AngularJS1.5に再挑戦 その6 Detail View with Parameters
参考動画・参考サイト
AngularJS — Superheroic JavaScript MVW Framework
Detail View with Parameters
blog-detail componentを作成していく。まずは、app/blog-detail/フォルダを作成する。
blog-detail.module.jsファイルを作成する。
'use strict'; angular.module('blogDetail', []);
- blog-detail.component.jsファイルを作成する。
'use strict'; angular.module('blogDetail'). component('blogDetail', { templateUrl: '/templates/blog-detail.html', controller: function($routeParams, $scope){ var blogItems = [ {title: "Some Title", id:1, description: "This is a book", publishDate: "2017-01-08"}, {title: "Title2", id:2, description: "This is a book"}, {title: "Some Title3", id:3, description: "This is a book"}, {title: "Some Title4", id:4, description: "This is a book"}, ] // console.log($routeParams) $scope.title = "Blog " + $routeParams.id } });
- index.htmlファイルに読み込ませる。
<script src='/js/app/blog-detail/blog-detail.module.js' ></script> <script src='/js/app/blog-detail/blog-detail.component.js' ></script>
- app.module.jsに追記しておく。
'use strict'; angular.module('try', [ // external 'ngResource', 'ngRoute', // internal 'blogDetail', 'blogList' ]);
- app.config.jsにも、detailへのroutingを設定しておく。
when("/blog/:id", { template: "<blog-detail></blog-detail>" }).
- templates/blog-detai.htmlを作成する。
<h1>{{ title }}</h1>
- これで、titleが表示されるようになるはず。djangoに近くて覚えやすい。