Upload
-
View
420
Download
3
Embed Size (px)
DESCRIPTION
Citation preview
Parse BasicBuilding Web Apps WITHOUT Programming Server.
http://goo.gl/8IqkAa
2014 Spring Web Programming, NCCUAuthor: pa4373 (Licensed by CC-By 4.0)
So far,你的網頁都是一成不變的
http://www.pmichaud.com/toast/ (從1994年後都沒變過 )
但實際上,很多網站是隨時在改變的
為什麼網站可以不斷的、及時變化?
因為有後端啊。
*Web伺服器:接收http請求、產生或委派http回應並傳回client端
*應用程式伺服器:根據使用者的請求產生相對的回應
資料庫:像一張巨大的Excel表,存各地來的資料
Backend Technology Stack
What if?
如果有一個工具可以讓我不用寫後端程式碼,但還是可以使用後端的功能,該有多好啊......
Parse來拯救咧!
Parse是什麼
Parse是一個BaaS(Backend as Service)。只要會用Parse提供的SDK, 還有正確的設定。開發者毋需擔心後端的開發撰寫以及主機的擴張和維護。
這讓開發者免於開發者實現的繁瑣細節,而將高度提升到更接近心智建模(Mental Modeling)的層級。
Parse開發模式概覽
Your Code
Parse SDK (黑箱)
Parse Cloud
知道Parse SDK怎麼用就可以寫互動式網站了(SDK還跨平台喔!)
很好很強大!
● localStorage:
○ jsbin.com/rubej/1/watch?html,js,output
● Parse:
○ jsbin.com/fezuq/5/watch?html,js,output
Ex: localStorage vs. Parse
● Data Store○ Database + File
● User Management● Background Jobs● ……
○ (see also: https://parse.com/products)
What Parse can do?
Website vs. Web Application
Website vs. Web Application
● Information Oriented vs. Action Oriented
● Creation vs. Consumption
● Way of designing
● Anything else?
● http://www.visionmobile.com/blog/2013/07/web-sites-vs-
web-apps-what-the-experts-think/
Class vs. Object
(雖然JavaScript不是Class-based object-oriented programming language.)
Class -> 食譜
Object (instacne) -> 菜
Class -> 藍圖
Object (instacne) -> 建築物
Model-View-Controller
SOURCE: http://online.stanford.edu/course/developing-ios7-apps-fall-2013
範例:Parse Store
● 模仿 ‘GetMore 二次時尚’ (其實根本抄襲)
● 二手洋裝專賣網站
● 能瀏覽商品、放入購物車
● 每個使用者有自己專屬的購物車(登入才能使
用)
● 沒有結賬功能
● http://pa4373.github.io/parsestore_js/
Parse Store
商品 (Dress)購物清單 (Order)使用者 (User)
Parse Store (Model)
Parse Store (View)
選單
產品型錄
Parse Store (View)
選單
產品細項
Parse Store (View)
選單
登入 & 註冊
● 版型引擎 (Template Engine)○ 解決navbar困境○ Template Tag -> 編譯-> 能產生HTML的JS函數○ 以doT.js為例
● 路由器 (Router)○ Facebook Photo○ 網址和處理函數的對應
■ ex: ‘#mycart/’ -> 處理函數1■ ‘#login/’ -> 處理函數2
○ Hash (#) vs HTML5 pushState○ Provided by Backbone.js (Parse SDK是Backbone.js的變種)
● EventListener○ 監聽特定事件的發生,觸發行為○ DOM.addEventListener(事件行為, 處理函數);○ 重複綁定?
■ https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener
Parse Store (Controller)
Use Parse SDK
Use Parse SDK
Use Parse SDK
記下Application Key以及JavaScript Key
Use Parse SDK<!doctype html>
<head>
<meta charset="utf-8">
<title>My Parse App</title>
<meta name="description" content="My Parse App">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
…….
</body>
<!--匯入Parse SDK-->
<script type="text/javascript" src="http://www.parsecdn.com/js/parse-1.2.18.min.js"
></script>
<script type="text/javascript">
// 初始化SDK (把你剛剛抄下來的Application ID和 JavaScript Key放上去)
Parse.initialize("APPLICATION_ID", "JAVASCRIPT_KEY");
</script>
</html>
Parse App Dashboard
Analytics: App使用狀況分析Data Browser: 瀏覽儲存App的資料庫Cloud Code: Server Code (進階)Push Notifications: iOS、Android推播通知Settings: App設定
下載Startup Project
https://github.com/pa4373/parsestore_js/archive/startkit.zip
index.htmlcss/style.cssjs/app.js
Dig into HTML.<!doctype html>
…….
</body>
<!--網站各個組件的版型,包在script裏面讓template engine調用(稍後回提到)-->
<script id="loginTemplate" type="text/x-dot-template">
<div class='grid_6 prefix_3 suffix_3'>
…… </div>
</script>
<!--jQuery, required by Prase SDK-->
<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
<!--Prase SDK-->
<script src='https://www.parsecdn.com/js/parse-1.2.18.min.js'></script>
<!--doT.js, the template engine-->
<script src='./js/vendors/doT.min.js'></script>
<script src='./js/app.js'></script>
</html>
Dig into JavaScript. (app.js)
// 防止潛在和其他套件的衝突(function(){
初始化Parse SDK(); 將版型編譯(compile)並載入記憶體中(); 各個View相對應的處理函數(); 設定Router以及相對應的處理函數(); 初始化整個App();
})();
Template Engine (doT.js)● 解決navbar困境
● 編譯
○ var tempFn = doT.template("<h1>Here is a sample template {{=it.foo}}
</h1>");
○ tempFn = function(it) { var out='<h1>Here is a sample template '+(it.foo)
+'</h1>';return out; }
○ 調用doT.template是有翻譯成本的,把編譯好的存起來以供以後使用。
● 編譯在HTML裡的版型(Little DOM Magic)○ var tpl = document.getElementById("loginTemplate").text;
○ dot.template(tpl);
● 調用:var out = tempFN({foo: 'Sherlock'}); // <h1>Here is a sample template
Sherlock</h1>
○ 把它put回HTML裡面 (Little DOM Magic)
○ document.getElementById("content").innerHTML = out;
● 語法請參考:http://olado.github.io/doT/tutorial.html#intro
Router● linkable, bookmarkable, shareable URLs for important locations in the app● Hash vs. pushState (Why use Hash in the example?)
var App = Parse.Router.extend({
routes: {
'': 'index',
'page/:page/': 'catalog',
'dress/:dress_id/': 'dress_detail',
'mycart/': 'mycart',
'login/*redirect': 'login',
},
// If frontpage is requested, show the first page of catalog.
index: function(){
return handlers.catalog(1);
},
catalog: handlers.catalog,
dress_detail: handlers.dress_detail,
mycart: handlers.mycart,
login: handlers.login,
});
路徑規則和相對應的處理函數(從物件的其他方法找查)
處理函數名稱以及函數本體(匿名宣告 or 參照)
:page -> 參數 function catalog(page)(*redirect也是另外一種參數)
Ref: http://backbonejs.org/#Router
Router● 當訪問#page/1/發生了什麼事呢?
var App = Parse.Router.extend({
routes: {
'': 'index',
'page/:page/': 'catalog',
'dress/:dress_id/': 'dress_detail',
'mycart/': 'mycart',
'login/*redirect': 'login',
},
// If frontpage is requested, show the first page of catalog.
index: function(){
return handlers.catalog(1);
},
catalog: handlers.catalog,
dress_detail: handlers.dress_detail,
mycart: handlers.mycart,
login: handlers.login,
});
路徑匹配
呼叫handlers.catalog(1)函數。(1 = :page)
Ref: https://developer.mozilla.org/en-US/docs/Web/API/Window.onhashchange
Router● 讓Router活起來。
// this = windowthis.Router = new App();Parse.history.start();
Handler Function
var handlers = { A: function(){}, B: function(){}, C: function(){},};
vs.var handlerA = function(){};var handlerB = function(){};var handlerC = function(){};
Handler Function與Router相關:index: function(){
return handlers.catalog(1);
}, -> 視同瀏覽產品型錄第一頁
catalog: handlers.catalog, -> 顯示產品型錄
dress_detail: handlers.dress_detail, -> 顯示商品細項
mycart: handlers.mycart, -> 顯示購物車
login: handlers.login, -> 顯示
與Router無關:
navbar -> 根據使用者登入與否顯示navbar內容
Handler Function (Login / Signup)
if (登入了){ 重新導向到首頁();} else { 印出登入+註冊版型(); 綁定登入按鈕觸發事件(); // Parse User Object
綁定兩次密碼一致與否檢查事件(); 綁定註冊按鈕觸發事件(); // Parse User Object
}
Handler Function (Login / Signup)
// 綁定登入按鈕觸發事件();document.getElementById('loginForm').addEventListener('submit', function(){
Parse.User.logIn(document.getElementById('loginForm_username').value,
document.getElementById('loginForm_password').value, {
success: function(user) {
// Do stuff after successful login
postAction();
}, error: function(user, error) {
// The login failed. Check error to see why.
}
});
});
/* Parse.User : Parse SDK提供的User物件,讓開發者可以簡便的建立會員機制
* Parse.User.logIn(帳號, 密碼,
* {success: 登入成功的回調函數, error: 登入失敗的回調函數});
* 登入成功後,會在瀏覽器裡面留下session cookie, 可以透過Parse SDK調用的函數。
*/
Handler Function (Login / Signup)// 還記得這段語法嗎?
var currentUser = Parse.User.current();
if (currentUser) {
...
}else{
...
}
/* 如果使用者有登入的話,Parse.User.current()會回傳現今登入的
* 使用者物件,透過檢查物件物件的存在,我們能夠設計需要登入的函數。
*/
Handler Function (Login / Signup)
// 綁定兩次密碼一致與否檢查事件();document.getElementById('singupForm_password1').
addEventListener('keyup', function(){
// 動態抓密碼欄的值 (Why?)
var singupForm_password = document.getElementById('singupForm_password');
var message = (this.value !== singupForm_password.value) ? '密碼不一致,請再
確認一次。' : '';
document.getElementById('signupForm_message').innerHTML = message;
});
Handler Function (Login / Signup)// 綁定註冊按鈕觸發事件();document.getElementById('singupForm').addEventListener('submit', function(){
var user = new Parse.User();
user.set("username", document.getElementById('singupForm_username').value);
user.set("password", document.getElementById('singupForm_password').value);
user.set("email", document.getElementById('singupForm_emailAddress').value);
user.signUp(null, {
success: function(user) {
postAction();
// Hooray! Let them use the app now.
},
error: function(user, error) {
// Show the error message somewhere and let the user try again.
document.getElementById('signupForm_message').innerHTML =
error.message + '['+error.code+']';
}
});
}, false);
Handler Function (Login / Signup)// 綁定註冊按鈕觸發事件();// 在本地創建一個User物件
var user = new Parse.User();
// 設定帳號密碼電子郵件
user.set("username", 帳號);
user.set("password", 密碼);
user.set("email", 電子郵件地址);
/* 註冊一個新的使用者並直接登入(不用做兩次!)
* 第一個null是啥?
* Extra fields to set on the new user, or null.*/
user.signUp(null, {success: 登入成功的回調函數, error: 登入失敗的回調函數});
Handler Function (Catalog)
移動到文件最上方 // 按next時會怎麼樣?
設定分頁參數(); // pagination = skip + limit;
設定查詢參數(); // Parse Query
查詢Parse伺服器資料庫(); // 取回物件列表
印出產品型錄版型();設定查詢參數(); // 解除所有限制
印出分頁版型(); // Parse Dress Object (為什麼晚查?)
// 因為分頁版型要加附的DOM在型錄版型內
Handler Function (Catalog)var handler = function(page){
// page意指現今頁數
window.scrollTo(0,0); // 移動到文件最上方
var limit = 16; // 每頁顯示多少筆資料
var skip = (page-1) * limit; // 要略過多少筆之前的資料
var Dress = Parse.Object.extend("Dress"); // 取得Parse的Dress class
var query = new Parse.Query(Dress); // 創建一個找查Dress的Query物件
query.limit(limit); // 設定Query條件
query.skip(skip);
query.descending("createdAt"); // 按照創造時間降冪排序
// 執行query (網路連結直到此處才會觸發)
query.find({success: function(results){
// 處理回傳的結果,results變數指向回傳的物件列表
...
}});
}
Handler Function (Catalog){success: function(results){
var objList = results.map(function(e){ return e.toJSON() }); // 將物件列表轉化成版型能消化的格式 document.getElementById('content').innerHTML =
templates.catalogTemplate(objList); // 呼叫型錄的模板函數。
query.limit(0);
query.skip(0); // 設成0, 我們才能找到所有Dress object總數
var option = {};
query.count({success: function(count){
var totalPage = Math.ceil(count / limit); // Math.celi(3.1415926) = 4;
var currentPage = parseInt(page); // 轉型( string => int )
option = {
// Watch out the limit.
'previous': (currentPage === 1) ? 1 : currentPage-1,
'next': (currentPage === totalPage) ? currentPage : currentPage+1, // 不可以超過最前最後頁
'current': currentPage,
'last': totalPage,
};
document.getElementById('pagination').innerHTML =
templates.catalogPaginationTemplate(option); // 呼叫分頁的模板函數。
}, error: function(err){}
});
}
Handler Function (Dress Detail)
if(有洋裝Id參數){ 設定查詢參數(); // Parse Query
查詢Parse伺服器資料庫(); // 取回物件內容
印出產品細則版型(); 綁定加入購物車功能(); // Parse Relational Object
} else { 重新導向到首頁();}
Handler Function (Dress Detail)var handler = function(dress_id){
if(dress_id){
var Dress = Parse.Object.extend("Dress"); // 取得Parse的Dress class
var query = new Parse.Query(Dress); // 創建一個找查Dress的Query物件
query.get(dress_id, { // 執行query,注意get方法 -> 給定物件ID, 回傳物件 success: function(dress){
document.getElementById('content').innerHTML =
templates.dress_detialTemplate(dress.toJSON());
綁定加入購物車功能(); // 下一張slide會解釋
}, error: function(object, error){
}
});
} else {
window.location.hash = '';
}
}
Parse Relational Object
var John = { ‘height’: 180, ‘girlfriend’: Jenny}What is the relationship between John and Jenny?How tall is John’s girlfriend? John.girlfriend.height = 167
Why? Data Consistency.
var Jenny = { ‘height’: 167,}
Parse Relational Object
var order = { ‘user’: <User obj>, ‘dress’: <Dress obj>, ‘amount’: 10,}
Handler Function (Dress Detail)document.getElementById('addToCart').addEventListener('click', function(){
var currentUser = Parse.User.current(); // 檢查登入
if(currentUser){
var e = document.getElementById('amount');
var amount = parseInt(e.options[e.selectedIndex].value);
myCart.setAmountTo(currentUser, dress, amount, function(){
alert("此商品已加入到您的購物車。 ");
}); // 下一張slide會解釋
} else {
// 重新導向到登入頁面,登入後會回到商品
window.location.hash = 'login/'+ window.location.hash;
}
});
myCart.setAmoutTomyCart = {
setAmountTo: function(user, dress, amount, callback){
var Order = Parse.Object.extend("Order"); // 取得Parse的Order class
// 創建一個找查Order的Query物件
var query = new Parse.Query(Order);
// 設定Query條件(object的user欄位指向到給定的User object)
query.equalTo('user', user);
// 設定Query條件(object的dress欄位指向到給定的Dress object)
query.equalTo('dress', dress);
// 執行query,注意first方法 -> 給定物件ID, 回傳物件列表第一項(可能會沒有)
query.first({success: 查詢成功的回調函數, error: 查詢失敗的回調函數});
},
};
/*
* myCart.setAmountTo(User物件, Dress物件, 數量(int), 回調函數);
*/
myCart.setAmoutTo{
success: function(order){
if( amount === 0 && order ){ // 如果已經有存在的order,並收到將數量設成0的話,等於消滅order物件
order.destroy({ // 消滅Parse物件
success: function(order){
callback(); //調用當作參數的callback函數
}
});
} else {
if( order === undefined ){ // 如果order還不存在,創一個新的object
order = new Order();
order.set('user', user); // 指定新object的user欄位指向到給定的Dress object
order.set('dress', dress); // 指定新object的dress欄位指向到給定的Dress object
}
order.set('amount', amount);
order.save(null, { // 將新增或更改過的order object 存到Parse Server
success: function(order){
callback();
}
});
}
}, error: function(object, err){
}
}
Handler Function (My Cart)
if (登入了){ 設定查詢參數(); // Parse Query for Order 查詢Parse伺服器資料庫(); // 取回物件內容
迴圈印出各訂單並綁上修改數量和刪除的事件();} else { 重新導向到首頁();}
Handler Function (My Cart)mycart: function(){
var currentUser = Parse.User.current();
if (currentUser) {
var Order = Parse.Object.extend("Order");
var query = new Parse.Query(Order);
query.equalTo('user', currentUser);
query.include('dress');
query.find({success: 登入成功的回調函數, error: 登入失敗的回調函數});
} else {
window.location.hash = 'login/'+ window.location.hash;
}
}
Handler Function (My Cart){
success: function (results) {
var objList = results.map(function (e) {
return {
'dressId': e.get('dress').id,
'amount': e.get('amount'),
'name': e.get('dress').get('name'),
'previewUrl': e.get('dress').get('previewUrl'),
}
});
document.getElementById('content').innerHTML = templates.mycartTemplate(objList);
results.forEach(function (e) {
var changeAmount = document.getElementById('change_amount_' + e.get('dress').id);
changeAmount.addEventListener('change', function () {
var amount = parseInt(this.options[this.selectedIndex].value);
myCart.setAmountTo(currentUser, e.get('dress'), amount, function () {});
});
var cancelOrderBtn = document.getElementById('cancel_order_' + e.get('dress').id);
cancelOrderBtn.addEventListener('click', function () {
myCart.setAmountTo(currentUser, e.get('dress'), 0, function () {
if (cancelOrderBtn.parentNode.parentNode.childElementCount === 1) {
handlers.mycart();
} else {
cancelOrderBtn.parentNode.remove();
}
});
});
});
document.getElementById('payButton').parentNode.addEventListener('click', function () {
alert('沒做這功能喔');
});
}, error: function (error){ },
}
Handler Function
// See the pattern?function(){ 預處理(); // ex: 檢查登入狀況
載入模型(); // optional 使用樣板引擎將模型顯示到browser上(); 事件綁定(); // Event binding (eg. click)};
註:這樣的設計只是參考不是絕對,應按照合理的情況去撰寫相對應的程序
Privilege Issues
How to protect data?
Parse Class-Based Privilege (Data Browser)
Ref: https://parse.com/docs/data#security-classes
Parse Class-Based Privilege (Data Browser)
Parse ACL (more complicated!)
ACL: Access Control List“...each object has a list of users and roles along with what permissions that user or role has...”
user vs. roles
Ref: https://parse.com/docs/data#security-objects
Parse ACL
{ "*":{"read":true}, "SaMpLeUsErId":{"write":true,"read":true} }
SaMpLeUsErId 這個user可以讀寫這個物件
其他人只能讀
Parse ACL
How to make the certain ‘Order’ object available only to the owner?
var orderACL = new Parse.ACL();orderACL.setPublicReadAccess(false);orderACL.setPublicWriteAccess(false);orderACL.setReadAccess(user, true);orderACL.setWriteAccess(user, true);// 附加到物件實體(instance)上order.setACL(postACL);order.save();
Ref: http://parse.com/docs/js/symbols/Parse.ACL.html
Parse ACL
Parse Store
All Source codes are available on GitHub:https://github.com/pa4373/parsestore_js
using git to clone!
$ git clone https://github.com/pa4373/parsestore_js.git
More Topics…...
● Parse JavaScript Tutorial● Parse JavaScript SDK Reference● Pricing● Loading indicator● Backbone.js
○ Data-Binding