SlideShare a Scribd company logo
Parse Basic
Building Web Apps WITHOUT Programming Server.
http://goo.gl/8IqkAa
2014 Spring Web Programming, NCCU
Author: pa4373 (Licensed by CC-By 4.0)
So far,你的網頁都是一成不變的
http://www.pmichaud.com/toast/ (從1994年後都沒變過)
但實際上,很多網站是隨時在改變的
為什麼網站可以不斷的、及時變化?
因為有後端啊。
*Web伺服器:接收http請求、產生或委派http回應並傳回client
端
*應用程式伺服器:根據使用者的請求產生相對的回應
資料庫:像一張巨大的Excel表,存各地來的資料
Introduction to Parse JavaScript SDK
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.html
css/style.css
js/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 = window
this.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.setAmoutTo
myCart = {
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

More Related Content

PDF
OpenWebSchool - 03 - PHP Part II
PDF
Php More
PDF
深入淺出 Web 容器 - Tomcat 原始碼分析
PDF
OpenEJB - 另一個選擇
ODP
JavaScript Advanced Skill
PDF
Maintainable PHP Source Code
PPTX
JavaScript 80+ Programming and Optimization Skills
PPT
Javascript Training
OpenWebSchool - 03 - PHP Part II
Php More
深入淺出 Web 容器 - Tomcat 原始碼分析
OpenEJB - 另一個選擇
JavaScript Advanced Skill
Maintainable PHP Source Code
JavaScript 80+ Programming and Optimization Skills
Javascript Training

What's hot (20)

PPT
PHP & MySQL 教學
PPTX
Js的国(转载)
PDF
Php设计模式介绍
PPTX
PHPUnit + Xdebug 单元测试技术
PDF
Node way
PPT
Asp.net mvc 培训
PPT
2009 CSBB LAB 新生訓練
PDF
OpenWebSchool - 02 - PHP Part I
PDF
常見設計模式介紹
DOC
XMLHTTPRequest的属性和方法简介
PDF
Ejb工作原理学习笔记
PPT
ios分享
PDF
论 Python 与设计模式。
PPT
页游开发中的 Python 组件与模式
PDF
Python 于 webgame 的应用
PPTX
从问题开始,谈前端架构
PDF
PHPUnit 入門介紹
PPT
iPhone,ios,Object-C基础入门
PPT
改善程序设计技术的50个有效做法
PPTX
张所勇:前端开发工具推荐
PHP & MySQL 教學
Js的国(转载)
Php设计模式介绍
PHPUnit + Xdebug 单元测试技术
Node way
Asp.net mvc 培训
2009 CSBB LAB 新生訓練
OpenWebSchool - 02 - PHP Part I
常見設計模式介紹
XMLHTTPRequest的属性和方法简介
Ejb工作原理学习笔记
ios分享
论 Python 与设计模式。
页游开发中的 Python 组件与模式
Python 于 webgame 的应用
从问题开始,谈前端架构
PHPUnit 入門介紹
iPhone,ios,Object-C基础入门
改善程序设计技术的50个有效做法
张所勇:前端开发工具推荐
Ad

Viewers also liked (13)

PDF
Workshop: building your mobile backend with Parse - Droidcon Paris2014
PDF
Doc app cracker
PPTX
What's Parse
PDF
Building Android apps with Parse
PDF
PPTX
Introduction to Parse backend for mobile developers
PPT
Tinder
PPTX
Tinder powerpoint Final
ODP
Tinder, ou l'amour 2.0
PPTX
The secret of Tinder
PDF
Company Presentation: Tinder
PDF
Tinder
PDF
Comparison of Tinder, Match.com, Zoosk, Bumble and Other Dating Apps on Faceb...
Workshop: building your mobile backend with Parse - Droidcon Paris2014
Doc app cracker
What's Parse
Building Android apps with Parse
Introduction to Parse backend for mobile developers
Tinder
Tinder powerpoint Final
Tinder, ou l'amour 2.0
The secret of Tinder
Company Presentation: Tinder
Tinder
Comparison of Tinder, Match.com, Zoosk, Bumble and Other Dating Apps on Faceb...
Ad

Similar to Introduction to Parse JavaScript SDK (20)

PDF
程式人雜誌 -- 2015 年1月號
PDF
Parse, cloud code 介紹
ODP
Backbone js and requirejs
PDF
Backbone.js and MVW 101
PDF
Javascript
PDF
2016 前端潮玩意兒
PPTX
赶集团购开发总结4
PPT
HTML5概览
PDF
Yahoo! 應用程式 (YAP) 在前端的開發
PDF
Node.js 入門 - 前端工程開發實務訓練
PDF
Javascript 入門 - 前端工程開發實務訓練
PPT
Underscore
PDF
NodeJS基礎教學&簡介
PDF
給 iOS 工程師的 Vue.js 開發
PPT
Html5和css3入门
PPTX
180518 ntut js and node
PPTX
jQuery Mobile
PDF
JavaScript Engine
PPT
Html5
PDF
About web app development. Intro to basic web app development .pdf
程式人雜誌 -- 2015 年1月號
Parse, cloud code 介紹
Backbone js and requirejs
Backbone.js and MVW 101
Javascript
2016 前端潮玩意兒
赶集团购开发总结4
HTML5概览
Yahoo! 應用程式 (YAP) 在前端的開發
Node.js 入門 - 前端工程開發實務訓練
Javascript 入門 - 前端工程開發實務訓練
Underscore
NodeJS基礎教學&簡介
給 iOS 工程師的 Vue.js 開發
Html5和css3入门
180518 ntut js and node
jQuery Mobile
JavaScript Engine
Html5
About web app development. Intro to basic web app development .pdf

Introduction to Parse JavaScript SDK

  • 1. Parse Basic Building Web Apps WITHOUT Programming Server. http://goo.gl/8IqkAa 2014 Spring Web Programming, NCCU Author: pa4373 (Licensed by CC-By 4.0)
  • 9. Parse是什麼 Parse是一個BaaS(Backend as Service)。 只要會用Parse提供的SDK, 還有正確的設定。開發者毋需擔 心後端的開發撰寫以及主機的擴張和維護。 這讓開發者免於開發者實現的繁瑣細節,而將高度提升到更 接近心智建模(Mental Modeling)的層級。
  • 12. ● localStorage: ○ jsbin.com/rubej/1/watch?html,js,output ● Parse: ○ jsbin.com/fezuq/5/watch?html,js,output Ex: localStorage vs. Parse
  • 13. ● Data Store ○ Database + File ● User Management ● Background Jobs ● …… ○ (see also: https://parse.com/products) What Parse can do?
  • 14. Website vs. Web Application
  • 15. 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/
  • 16. Class vs. Object (雖然JavaScript不是Class-based object- oriented programming language.) Class -> 食譜 Object (instacne) -> 菜 Class -> 藍圖 Object (instacne) -> 建築物
  • 19. ● 模仿 ‘GetMore 二次時尚’ (其實根本抄襲) ● 二手洋裝專賣網站 ● 能瀏覽商品、放入購物車 ● 每個使用者有自己專屬的購物車(登入才能使 用) ● 沒有結賬功能 ● http://pa4373.github.io/parsestore_js/ Parse Store
  • 20. 商品 (Dress) 購物清單 (Order) 使用者 (User) Parse Store (Model)
  • 24. ● 版型引擎 (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)
  • 27. Use Parse SDK 記下Application Key以及JavaScript Key
  • 28. 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>
  • 29. Parse App Dashboard Analytics: App使用狀況分析 Data Browser: 瀏覽儲存App的資料庫 Cloud Code: Server Code (進階) Push Notifications: iOS、Android推播通知 Settings: App設定
  • 31. 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>
  • 32. Dig into JavaScript. (app.js) // 防止潛在和其他套件的衝突 (function(){ 初始化Parse SDK(); 將版型編譯(compile)並載入記憶體中(); 各個View相對應的處理函數(); 設定Router以及相對應的處理函數(); 初始化整個App(); })();
  • 33. 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
  • 34. 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
  • 35. 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
  • 36. Router ● 讓Router活起來。 // this = window this.Router = new App(); Parse.history.start();
  • 37. Handler Function var handlers = { A: function(){}, B: function(){}, C: function(){}, }; vs. var handlerA = function(){}; var handlerB = function(){}; var handlerC = function(){};
  • 38. 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內容
  • 39. Handler Function (Login / Signup) if (登入了){ 重新導向到首頁(); } else { 印出登入+註冊版型(); 綁定登入按鈕觸發事件(); // Parse User Object 綁定兩次密碼一致與否檢查事件(); 綁定註冊按鈕觸發事件(); // Parse User Object }
  • 40. 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調用的函數。 */
  • 41. Handler Function (Login / Signup) // 還記得這段語法嗎? var currentUser = Parse.User.current(); if (currentUser) { ... }else{ ... } /* 如果使用者有登入的話,Parse.User.current()會回傳現今登入的 * 使用者物件,透過檢查物件物件的存在,我們能夠設計需要登入的函數。 */
  • 42. 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; });
  • 43. 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);
  • 44. 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: 登入失敗的回調函數});
  • 45. Handler Function (Catalog) 移動到文件最上方 // 按next時會怎麼樣? 設定分頁參數(); // pagination = skip + limit; 設定查詢參數(); // Parse Query 查詢Parse伺服器資料庫(); // 取回物件列表 印出產品型錄版型(); 設定查詢參數(); // 解除所有限制 印出分頁版型(); // Parse Dress Object (為什麼晚查?) // 因為分頁版型要加附的DOM在型錄版型內
  • 46. 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變數指向回傳的物件列表 ... }}); }
  • 47. 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){} }); }
  • 48. Handler Function (Dress Detail) if(有洋裝Id參數){ 設定查詢參數(); // Parse Query 查詢Parse伺服器資料庫(); // 取回物件內容 印出產品細則版型(); 綁定加入購物車功能(); // Parse Relational Object } else { 重新導向到首頁(); }
  • 49. 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 = ''; } }
  • 50. 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, }
  • 51. Parse Relational Object var order = { ‘user’: <User obj>, ‘dress’: <Dress obj>, ‘amount’: 10, }
  • 52. 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; } });
  • 53. myCart.setAmoutTo myCart = { 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), 回調函數); */
  • 54. 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){ } }
  • 55. Handler Function (My Cart) if (登入了){ 設定查詢參數(); // Parse Query for Order 查詢Parse伺服器資料庫(); // 取回物件內容 迴圈印出各訂單並綁上修改數量和刪除的事件(); } else { 重新導向到首頁(); }
  • 56. 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; } }
  • 57. 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){ }, }
  • 58. Handler Function // See the pattern? function(){ 預處理(); // ex: 檢查登入狀況 載入模型(); // optional 使用樣板引擎將模型顯示到browser上(); 事件綁定(); // Event binding (eg. click) }; 註:這樣的設計只是參考不是絕對,應按照合理的情況去撰寫 相對應的程序
  • 59. Privilege Issues How to protect data?
  • 60. Parse Class-Based Privilege (Data Browser) Ref: https://parse.com/docs/data#security-classes
  • 61. Parse Class-Based Privilege (Data Browser)
  • 62. 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
  • 63. Parse ACL { "*":{"read":true}, "SaMpLeUsErId":{"write": true,"read":true} } SaMpLeUsErId 這個user可以讀寫這個物件 其他人只能讀
  • 64. 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
  • 66. 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
  • 67. More Topics…... ● Parse JavaScript Tutorial ● Parse JavaScript SDK Reference ● Pricing ● Loading indicator ● Backbone.js ○ Data-Binding