目標 使用Emberjs制作一個簡單的Todo應用,實現這樣一個效果:通過在文本框輸入文本,創建一條代辦事項,代辦事項可以選擇優先級,完成的事項可以刪除。
准備 完成這個應用,需要做點准備:
1、創建一個html頁面,暫時不管樣式;
2、腳本:emberjs,handlebars、jQuery。這三個腳本可以從網上獲得,我們將把他們加入到head標簽裡去。
制作 創建頁面,加入腳本,就可以開始制作應用。html代碼如下:
復制代碼 代碼如下:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Ember--第一個應用</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://cloud.github.com/downloads/wycats/handlebars.js/handlebars-1.0.rc.1.js"></script>
<script type="text/javascript" src="http://cloud.github.com/downloads/emberjs/ember.js/ember-1.0.0-pre.2.min.js"></script>
</head>
<body>
</body>
</html>
按照ember的要求,需要用Ember.Application.create()先創建應用實例,這也作為應用的命名空間。這個create方法可以傳遞一個對象屬性ready,屬性值是一個函數,在應用准備就緒時調用。Ember還可以使用縮寫Em來代替。
在Ember中有一個Em.Logger對象,相當於window.console,可以用來調試。我們可以在這個ready加入一個消息,顯示在控制台中。
現在,在head標簽裡再增加一個script標簽來寫應用的腳本,實例化一個ember應用,順便把MVC各模塊的區域也加上。腳本代碼如下:
復制代碼 代碼如下:
/********************
application
********************/
window.App = Ember.Application.create(
{
ready:function(){
Em.Logger.info('歡迎使用待辦事項應用');
}
}
);
/********************
model
********************/
/********************
view
********************/
/********************
controlle
********************/
然後,我們需要一個輸入框來輸入代辦事項,需要創建一個ember文本框視圖。ember視圖可以用Ember.View類來創建(使用create方法)或擴展(使用extend方法)一個新的視圖類。不過對於文本框視圖,ember提供了更直接的方式——Ember.TextField類,我們可以先使用這個類來擴展一個自定義的視圖,然後再實例化添加到頁面上。我們將這個文本框視圖類命名AddItemView 。
在腳本代碼裡的view區域添加上文本框視圖代碼:
復制代碼 代碼如下:
App.AddItemView = Ember.TextField.extend({
});
可以給它加個提示語,html5支持placeholder,可以拿來用。還需要在按下回車時將內容添加到代表事項列表,這裡需要用到一個屬性:insertNewline,在按下回車時會調用相應的函數。加入後的代碼如下:
復制代碼 代碼如下:
App.AddItemView = Ember.TextField.extend({
placeholder:'輸入待辦事項',
insertNewline:function(){}
});
由於現在還沒確定具體添加方法,函數體暫時先不寫。
用戶在按下回車時增加一條代辦事項,需要一個列表來顯示,在ember中可以創建CollectionView來存放列表項目視圖,對於CollectionView,默認會有一個content屬性用於存放列表項目對象,其屬性值是一個數組。為了讓其列表顯示為ul列表,需要定義CollectionView的標簽名(tagName)為“ul”。我們給這個列表視圖命名為ListView,並增加到文本框視圖的下方。最後代碼如下:
復制代碼 代碼如下:
App.ListView = Ember.CollectionView.extend({
content:[],
tagName:'ul'
});
現在如果打開頁面,是沒顯示任何內容的,因為視圖還沒被渲染,要將視圖顯示出來,需要handlebar模板的支持。
現在來修改html頁面的body塊,加入剛創建的兩個視圖,看看效果。
添加handlebar模板的方法是<script type="text/x-handlebars">/*視圖助手*/</script>,還可以指定模板名稱,在data-template-name屬性裡定義,待會我們添加列表項目時會需要用到。
在模板裡需要通過視圖助手(helper)來添加視圖,語法也很簡單,用兩個花括號對包裹,裡面通過模板關鍵字來指定要顯示的視圖,如:{{view App.AddItemView}}。其他模板助手可以在handlebar網站查到:http://handlebarsjs.com/。
現在先把文本框跟列表視圖添加到頁面上,修改後的body代碼如下:
復制代碼 代碼如下:
<body>
<script type="text/x-handlebars">
<span>請輸入待辦事項:</span>{{view App.AddItemView}}<br/>
{{view App.ListView}}
</script>
</body>
現在刷新頁面,會顯示一句“請輸入代辦事項”跟一個文本框,列表由於沒有內容,不會顯示。
這個時候我們可以在content裡添加點內容,比如content:['a','b','c'],然後刷新頁面,你會發現列表區域只有三個小黑點(如果你沒重置列表樣式的話)。因為你在content裡添加了三項,但列表項還沒有指定一個顯示的模板,所以,顯示為空。為了讓你看到效果,我們來給列表項定義個顯示的模板吧。這裡需要處理兩個地方,第一是在頁面加指定名稱的模板,第二是在列表視圖裡定義列表項目的屬性。
定義列表項目,需要用到itemViewClass,它會將每個content項傳遞進去並用指定的模板顯示。先來修改列表視圖ListView 吧,給它增加itemViewClass屬性,這也是一種視圖,所以需要用Ember.View來創建,在創建時同時指定用來顯示的模板名稱為itemTemplate,這個名稱同時將為出現在html的handlebar模板名稱裡。修改後的代碼如下:
復制代碼 代碼如下:
App.ListView = Ember.CollectionView.extend({
content:['a','b','c'],
tagName:'ul',
itemViewClass: Ember.View.extend({
templateName:'itemTemplate',
})
});
還差一步就完成了,現在來修改html,我們需要在body裡再新建一個handlebar模板,並且會用到上面給出的模板名稱,代碼如下:
復制代碼 代碼如下:
<script type="text/x-handlebars" data-template-name="itemTemplate">
</script>
接著同樣是添加模板助手,要把每一個content項傳遞給助手,會用到view.content。添加如下代碼:
復制代碼 代碼如下:
<script type="text/x-handlebars" data-template-name="itemTemplate">
{{view.content}}
</script>
完成後,刷新頁面,現在終於把content裡的內容顯示出來了,而且,模板會自動加上li標簽。
繼續完善我們的應用。我們總不能把content的內容寫成固定的吧,這樣用戶還怎麼添加呢。所以,現在考慮把用戶要添加的項目保存到一個數組裡,然後content自己去取這個數組的內容。同時,ember框架支持雙向綁定,當數組內容修改時,通過綁定的content也會同時改變,反之亦然。現在,就創建一個ember數組,然後跟content綁定吧。
ember數組可以通過ArrayController類來創建,它會把你傳進去的普通javascript轉變為一個新的ember數組對象,我們把用來管理項目的數組命名為todoStore,放到html頁面的controller區域,創建的代碼如下:
復制代碼 代碼如下:
App.todoStore = Ember.ArrayController.create({
content:[]
});
現在可以把ListView 裡的content數組放到這個todoStore 的數組裡,然後綁定ListView 裡的content到todoStore 上,這兩個對象將修改為如下所示:
復制代碼 代碼如下:
App.ListView = Ember.CollectionView.extend({
contentBinding:'App.todoStore',
tagName:'ul',
itemViewClass: Ember.View.extend({
templateName:'itemTemplate'
})
});
/********************
controlle
********************/
App.todoStore = Ember.ArrayController.create({
content:['a','b','c']
});
Binding是個後綴,表示綁定,屬性值是綁定的對象,默認取該對象的content屬性。修改完成後刷新頁面,如果你看到的頁面跟修改之前的一樣,說明修改成功了。接著,是時候去掉content裡的值了,我們需要的數據將由用戶在文本框裡輸入。
考慮現在的交互過程,用戶在文本框輸入內容,按下回車,程序獲取到該事件,調用一個方法創建一個新對象,再把這個新對象送給todoStore ,由於綁定作用,列表會自動增加一項。
是時候改造下文本框視圖了,還記得insertNewline嗎?我們可以在這裡創建新的項目。我們會用到三個方法:set()設置屬性值、get()獲取屬性值、pushObject()添加數據,修改AddItemView 後的代碼如下:
復制代碼 代碼如下:
App.AddItemView = Ember.TextField.extend({
placeholder:'輸入待辦事項',
insertNewline:function(){
var item = this.get('value');
App.todoStore.pushObject(item);
this.set('value','');
}
});
現在刷新頁面,然後輸入內容,按回車,列表會添加輸入的內容,說明修改成功,如果你沒把todoStore 的content屬性裡的內容清空的話,現在會有4個列表項了。
距離我們的目標還有一半啊,我們還缺少兩個功能:選擇優先級跟刪除完成的項目。
要增加下拉列表,可以使用另一個方便的視圖:Ember.Select。我們可以直接在模板裡直接創建一個,同樣通過綁定,把下拉列表視圖的content綁定到另一個ember對象上,然後設置默認選中的優先級。優先級也需要用到綁定,這樣在頁面上選擇的時候,才會同時修改對應的ember對象裡的內容。先來創建這個ember對象,自定義該對象的selected屬性表示選中的值,其他名稱也行,這段代碼會加到todoStore對象的下面,命名為selectController,代碼如下:
復制代碼 代碼如下:
App.selectController = Ember.Object.create({
selected:'低',
content:['高','中','低']
});
然後增加一個模板助手,並綁定selectController 裡對應的屬性,選中項的綁定需要用到selectionBinding,順便給個文字提示,然後添加到文本框模板的下面,修改後的代碼如下:
復制代碼 代碼如下:
<script type="text/x-handlebars">
<span>請輸入待辦事項:</span>{{view App.AddItemView}}<br/>
<span>請選擇優先級:</span>{{view Ember.Select contentBinding="App.selectController.content" selectionBinding="App.selectController.selected"}}
{{view App.ListView}}
</script>
現在刷新頁面就會出現下拉列表了。
要想讓列表項也出現這個優先級,還得花點功夫啊。是時候用model了,我們來創建一個model類,當按下回車時,從這個類創建一個實例,再把實例扔到todoStore裡就可以了,另外,模板也要跟著修改,文本框視圖的創建方法也得改。這次改動比較多了點。另外,還會用到一個計算屬性的功能,當依賴的屬性變化時,自動更新。把這個model命名為TodoModel,放到model區域,創建代碼如下:
復制代碼 代碼如下:
/********************
model
********************/
App.TodoModel = Em.Object.extend({
status:'',
value:'',
title:function(){
return '['+this.get('status')+']'+this.get('value');
}.property('status','value')
});
status表示選擇的優先級,value表示文本框裡的值,title表示列表項目要顯示的內容,這些屬性名都是自定義的。其中title不需要提供,因為它設置為計算屬性,依賴於status跟value屬性,自動計算獲取,property()方法就是ember函數轉變為計算屬性的方法,後面的參數表示title依賴的屬性,當status或value變化時,就會自動給出。
接著修改AddItemView的insertNewline屬性,需要取到兩個數據,一個是文本框裡的內容,一個是下拉列表裡選中的項目。文本框的值已經知道怎麼獲取了,下拉列表的值呢?別忘了我們已經將選中項綁定到selectController裡的selected屬性了,直接從那裡取就可以了。修改後的AddItemView代碼如下:
復制代碼 代碼如下:
App.AddItemView = Ember.TextField.extend({
placeholder:'輸入待辦事項',
elementId:'add',
insertNewline:function(){
var item = App.TodoModel.create({
status:App.selectController.get('selected'),
value:this.get('value')
});
App.todoStore.pushObject(item);
this.set('value','');
}
});
現在可以通過TodoModel類來實例化一個待辦項目並添加到todoStore裡了,最後是修改項目列表的模板itemTemplate來顯示,在模板裡需要取到當前項目的title值來顯示,代碼如下:
復制代碼 代碼如下:
<script type="text/x-handlebars" data-template-name="itemTemplate">
{{view.content.title}}
</script>
現在添加的新待辦事項會顯示優先級了。
好了,最後一個功能,刪除。跟添加pushObject相反,刪除用到的是removeObject。因為它是顯示在每個列表項目裡的,所以,需要修改itemViewClass跟itemTemplate模板,我們在itemViewClass裡添加一個方法,當用戶點擊時調用,把該方法命名為removeItem,代碼如下:
復制代碼 代碼如下:
App.ListView = Ember.CollectionView.extend({
contentBinding:'App.todoStore',
tagName:'ul',
itemViewClass: Ember.View.extend({
templateName:'itemTemplate',
removeItem:function(){this.getPath( 'contentView.content' ).removeObject(this.get( 'content' ));}
})
});
最後是在itemTemplate模板裡增加一個接受點擊的鏈接,用到的是action助手,第一個參數是方法名,target屬性用來指定對象,點擊時調用指定對象下的方法。修改後的itemTemplate代碼如下:
復制代碼 代碼如下:
<script type="text/x-handlebars" data-template-name="itemTemplate">
{{view.content.title}} <a href="#" {{action removeItem target="this"}} >X</a>
</script>
現在新增加的項目都會有個叉在右邊,點擊時就把當前項目刪除。
最後還可以做點改進,當鼠標移動到項目上時才顯示刪除鏈接,完成這個功能,需要修改itemViewClass以及在模板裡增加邏輯判斷助手{{#if}}。你可以自己試著去做,也可以看看最後完整的代碼。
復制代碼 代碼如下:
full code
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Ember--第一個應用</title>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://cloud.github.com/downloads/wycats/handlebars.js/handlebars-1.0.rc.1.js"></script>
<script type="text/javascript" src="http://cloud.github.com/downloads/emberjs/ember.js/ember-1.0.0-pre.2.min.js"></script>
<script>
/********************
application
********************/
window.App = Ember.Application.create(
{
ready:function(){
Em.Logger.info('歡迎使用待辦事項應用');
}
}
);
/********************
model
********************/
App.TodoModel = Em.Object.extend({
status:'',
value:'',
title:function(){
return '['+this.get('status')+']'+this.get('value');
}.property('status','value')
});
/********************
view
********************/
App.AddItemView = Ember.TextField.extend({
placeholder:'輸入待辦事項',
elementId:'add',
insertNewline:function(){
var item = App.TodoModel.create({
status:App.selectController.get('selected'),
value:this.get('value')
});
App.todoStore.pushObject(item);
this.set('value','');
}
});
App.ListView = Ember.CollectionView.extend({
contentBinding:'App.todoStore',
tagName:'ul',
itemViewClass: Ember.View.extend({
templateName:'itemTemplate',
removeItem:function(){this.getPath( 'contentView.content' ).removeObject(this.get( 'content' ));},
mouseEnter:function(){this.set('hover',true);},
mouseLeave:function(){this.set('hover',false);}
})
});
/********************
controlle
********************/
App.todoStore = Ember.ArrayController.create({
content:[]
});
App.selectController = Ember.Object.create({
selected:'低',
content:['高','中','低']
});
</script>
</head>
<body>
<script type="text/x-handlebars">
<span>請輸入待辦事項:</span>{{view App.AddItemView}}<br/>
<span>請選擇優先級:</span>{{view Ember.Select contentBinding="App.selectController.content"
selectionBinding="App.selectController.selected"}}
{{view App.ListView}}
</script>
<script type="text/x-handlebars" data-template-name="itemTemplate">
{{view.content.title}} {{#if view.hover}}<a href="#" {{action removeItem target="this"}} >X</a>{{/if}}
</script>
</body>
</html>