vue-resource针对Vue的扩展,它可以给VM对象加上一个$resource属性,通过$resource来方便地操作API。
<script src="https://cdn.jsdelivr.net/vue.resource/1.0.3/vue-resource.min.js"></script>
读取TODO列表
var vm = new Vue({
el: '#vm',
data: {
title: 'TODO List',
todos: []
},
created: function () {
this.init();
},
methods: {
init: function () {
var that = this;
that.$resource('/api/todos').get().then(function (resp) {
// 调用API成功时调用json()异步返回结果:
resp.json().then(function (result) {
// 更新VM的todos:
that.todos = result.todos;
});
}, function (resp) {
// 调用API失败:
alert('error');
});
}
}
});
添加、修改、删除
var vm = new Vue({
...
methods: {
...
create: function (todo) {
var that = this;
that.$resource('/api/todos').save(todo).then(function (resp) {
resp.json().then(function (result) {
that.todos.push(result);
});
}, showError);
},
update: function (todo, prop, e) {
...
},
remove: function (todo) {
...
}
}
});
var vmAdd = new Vue({
el: '#vmAdd',
data: {
name: '',
description: ''
},
methods: {
submit: function () {
vm.create(this.$data);
}
}
});
<form id="vmAdd" action="#0" v-on:submit.prevent="submit">
<p><input type="text" v-model="name"></p>
<p><input type="text" v-model="description"></p>
<p><button type="submit">Add</button></p>
</form>
<div id="vm">
<h3>{{ title }}</h3>
<ol>
<li v-for="t in todos">
<dl>
<dt contenteditable="true" v-on:blur="update(t, 'name', $event)">{{ t.name }}</dt>
<dd contenteditable="true" v-on:blur="update(t, 'description', $event)">{{ t.description }}</dd>
<dd><a href="#0" v-on:click="remove(t)">Delete</a></dd>
</dl>
</li>
</ol>
</div>
package.json
{
"name": "view-koa",
"version": "1.0.0",
"description": "todo 2 example with vue",
"main": "app.js",
"scripts": {
"start": "node --use_strict app.js"
},
"keywords": [
"vue",
"mvvm"
],
"author": "Michael Liao",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/michaelliao/learn-javascript.git"
},
"dependencies": {
"koa": "2.0.0",
"koa-bodyparser": "3.2.0",
"koa-router": "7.0.0",
"nunjucks": "2.4.2",
"mime": "1.3.4",
"mz": "2.4.0"
}
}
static
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="learn javascript by www.liaoxuefeng.com">
<title>Vue</title>
<link rel="stylesheet" href="/static/css/bootstrap.css">
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/vue.js"></script>
<script src="https://cdn.jsdelivr.net/vue.resource/1.0.3/vue-resource.min.js"></script>
<script>
function showError(resp) {
resp.json().then(function (result) {
console.log('Error: ' + result.message);
});
}
$(function () {
var vm = new Vue({
el: '#vm',
http: {
timeout: 5000
},
data: {
title: 'TODO List',
todos: [],
loading: false
},
created: function () {
this.init();
},
methods: {
init: function () {
var that = this;
that.loading = true;
that.$resource('/api/todos').get().then(function (resp) {
that.loading = false;
resp.json().then(function (result) {
that.todos = result.todos;
});
}, function (resp) {
that.loading = false;
showError(resp);
});
},
create: function (todo) {
var that = this;
that.$resource('/api/todos').save(todo).then(function (resp) {
resp.json().then(function (result) {
that.todos.push(result);
});
}, showError);
},
update: function (todo, prop, e) {
var that = this;
var t = {
name: todo.name,
description: todo.description
};
t[prop] = e.target.innerText;
if (t[prop] === todo[prop]) {
return;
}
that.$resource('/api/todos/' + todo.id).update(t).then(function (resp) {
resp.json().then(function (r) {
todo.name = r.name;
todo.description = r.description;
});
}, function (resp) {
e.target.innerText = todo[prop];
showError(resp);
});
},
remove: function (todo) {
var that = this;
that.$resource('/api/todos/' + todo.id).delete().then(function (resp) {
var i, index = -1;
for (i=0; i<that.todos.length; i++) {
if (that.todos[i].id === todo.id) {
index = i;
break;
}
}
if (index >= 0) {
that.todos.splice(index, 1);
}
}, showError);
}
}
});
window.vm = vm;
var vmAdd = new Vue({
el: '#vmAdd',
data: {
name: '',
description: ''
},
methods: {
submit: function () {
vm.create(this.$data);
this.name = '';
this.description = '';
}
}
});
});
</script>
</head>
<body>
<header class="navbar navbar-static-top">
<div class="container">
<div class="navbar-header">
<a href="/" class="navbar-brand">Learn JavaScript</a>
</div>
<nav class="collapse navbar-collapse" id="bs-navbar">
<ul class="nav navbar-nav">
<li><a target="_blank" href="http://www.liaoxuefeng.com/">Get Courses</a></li>
<li><a target="_blank" href="https://github.com/michaelliao/learn-javascript">Source Code</a></li>
<li><a target="_blank" href="http://getbootstrap.com/">Resource</a></li>
</ul>
</nav>
</div>
</header>
<div id="important" style="color:#cdbfe3; background-color:#6f5499; padding:30px 0; margin:-20px 0 20px 0;">
<div class="container">
<h1 style="color:#fff; font-size:60px">Getting started</h1>
<p style="font-size:24px; line-height:48px">Learn JavaScript, Node.js, npm, koa2, Vue, babel, etc. at liaoxuefeng.com.</p>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="glyphicon glyphicon-th-list"></span> MVVM</h3>
</div>
<div class="panel-body">
<div id="vm">
<h3>{{ title }}</h3>
<p v-if="loading">Loading...</p>
<ol>
<li v-for="t in todos">
<dl>
<dt contenteditable="true" v-on:blur="update(t, 'name', $event)">{{ t.name }}</dt>
<dd contenteditable="true" v-on:blur="update(t, 'description', $event)">{{ t.description }}</dd>
<dd><a href="#0" v-on:click="remove(t)">Delete</a></dd>
</dl>
</li>
</ol>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="glyphicon glyphicon-plus"></span> Add New Todo</h3>
</div>
<div class="panel-body">
<form id="vmAdd" action="#0" v-on:submit.prevent="submit">
<div class="form-group">
<label>Name:</label>
<input type="text" v-model="name" class="form-control" placeholder="Enter name">
</div>
<div class="form-group">
<label>Description:</label>
<input type="text" v-model="description" class="form-control" placeholder="Enter description">
</div>
<button type="submit" class="btn btn-default">Add</button>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h1>Get more courses...</h1>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">JavaScript</h3>
</div>
<div class="panel-body">
<p>full-stack JavaScript course</p>
<p><a target="_blank" href="http://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000">Read more</a></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Python</h3>
</div>
<div class="panel-body">
<p>the latest Python course</p>
<p><a target="_blank" href="http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000">Read more</a></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">git</h3>
</div>
<div class="panel-body">
<p>A course about git version control</p>
<p><a target="_blank" href="http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000">Read more</a></p>
</div>
</div>
</div>
</div>
</div>
<footer style="background-color:#ddd; padding: 20px 0;">
<div class="container">
<p>
<a target="_blank" href="http://www.liaoxuefeng.com">Website</a> -
<a target="_blank" href="https://github.com/michaelliao/learn-javascript">GitHub</a> -
<a target="_blank" href="http://weibo.com/liaoxuefeng">Weibo</a>
</p>
<p>This JavaScript course is created by <a target="_blank" href="http://weibo.com/liaoxuefeng">@廖雪峰</a>.</p>
<p>Code licensed <a target="_blank" href="https://github.com/michaelliao/learn-javascript/blob/master/LICENSE">Apache</a>.</p>
</div>
</footer>
</body>
</html>
static-files.js
const path = require('path');
const mime = require('mime');
const fs = require('mz/fs');
function staticFiles(url, dir) {
return async (ctx, next) => {
let rpath = ctx.request.path;
if (rpath.startsWith(url)) {
let fp = path.join(dir, rpath.substring(url.length));
if (await fs.exists(fp)) {
ctx.response.type = mime.lookup(rpath);
ctx.response.body = await fs.readFile(fp);
} else {
ctx.response.status = 404;
}
} else {
await next();
}
};
}
module.exports = staticFiles;
controllers
api.js
const APIError = require('../rest').APIError;
var gid = 0;
function nextId() {
gid ++;
return 't' + gid;
}
var todos = [
{
id: nextId(),
name: 'Learn Git',
description: 'Learn how to use git as distributed version control'
},
{
id: nextId(),
name: 'Learn JavaScript',
description: 'Learn JavaScript, Node.js, NPM and other libraries'
},
{
id: nextId(),
name: 'Learn Python',
description: 'Learn Python, WSGI, asyncio and NumPy'
},
{
id: nextId(),
name: 'Learn Java',
description: 'Learn Java, Servlet, Maven and Spring'
}
];
module.exports = {
'GET /api/todos': async (ctx, next) => {
ctx.rest({
todos: todos
});
},
'POST /api/todos': async (ctx, next) => {
var
t = ctx.request.body,
todo;
if (!t.name || !t.name.trim()) {
throw new APIError('invalid_input', 'Missing name');
}
if (!t.description || !t.description.trim()) {
throw new APIError('invalid_input', 'Missing description');
}
todo = {
id: nextId(),
name: t.name.trim(),
description: t.description.trim()
};
todos.push(todo);
ctx.rest(todo);
},
'PUT /api/todos/:id': async (ctx, next) => {
var
t = ctx.request.body,
index = -1,
i, todo;
if (!t.name || !t.name.trim()) {
throw new APIError('invalid_input', 'Missing name');
}
if (!t.description || !t.description.trim()) {
throw new APIError('invalid_input', 'Missing description');
}
for (i=0; i<todos.length; i++) {
if (todos[i].id === ctx.params.id) {
index = i;
break;
}
}
if (index === -1) {
throw new APIError('notfound', 'Todo not found by id: ' + ctx.params.id);
}
todo = todos[index];
todo.name = t.name.trim();
todo.description = t.description.trim();
ctx.rest(todo);
},
'DELETE /api/todos/:id': async (ctx, next) => {
var i, index = -1;
for (i=0; i<todos.length; i++) {
if (todos[i].id === ctx.params.id) {
index = i;
break;
}
}
if (index === -1) {
throw new APIError('notfound', 'Todo not found by id: ' + ctx.params.id);
}
ctx.rest(todos.splice(index, 1)[0]);
}
}
controller.js
const fs = require('fs');
// add url-route in /controllers:
function addMapping(router, mapping) {
for (var url in mapping) {
if (url.startsWith('GET ')) {
var path = url.substring(4);
router.get(path, mapping[url]);
console.log(`register URL mapping: GET ${path}`);
} else if (url.startsWith('POST ')) {
var path = url.substring(5);
router.post(path, mapping[url]);
console.log(`register URL mapping: POST ${path}`);
} else if (url.startsWith('PUT ')) {
var path = url.substring(4);
router.put(path, mapping[url]);
console.log(`register URL mapping: PUT ${path}`);
} else if (url.startsWith('DELETE ')) {
var path = url.substring(7);
router.del(path, mapping[url]);
console.log(`register URL mapping: DELETE ${path}`);
} else {
console.log(`invalid URL: ${url}`);
}
}
}
function addControllers(router, dir) {
fs.readdirSync(__dirname + '/' + dir).filter((f) => {
return f.endsWith('.js');
}).forEach((f) => {
console.log(`process controller: ${f}...`);
let mapping = require(__dirname + '/' + dir + '/' + f);
addMapping(router, mapping);
});
}
module.exports = function (dir) {
let
controllers_dir = dir || 'controllers',
router = require('koa-router')();
addControllers(router, controllers_dir);
return router.routes();
};
rest.js
module.exports = {
APIError: function (code, message) {
this.code = code || 'internal:unknown_error';
this.message = message || '';
},
restify: (pathPrefix) => {
pathPrefix = pathPrefix || '/api/';
return async (ctx, next) => {
if (ctx.request.path.startsWith(pathPrefix)) {
console.log(`Process API ${ctx.request.method} ${ctx.request.url}...`);
ctx.rest = (data) => {
ctx.response.type = 'application/json';
ctx.response.body = data;
}
try {
await next();
} catch (e) {
console.log('Process API error...');
ctx.response.status = 400;
ctx.response.type = 'application/json';
ctx.response.body = {
code: e.code || 'internal:unknown_error',
message: e.message || ''
};
}
} else {
await next();
}
};
}
};
app.js
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const controller = require('./controller');
const rest = require('./rest');
const app = new Koa();
const isProduction = process.env.NODE_ENV === 'production';
// log request URL:
app.use(async (ctx, next) => {
console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
var
start = new Date().getTime(),
execTime;
await next();
execTime = new Date().getTime() - start;
ctx.response.set('X-Response-Time', `${execTime}ms`);
});
// static file support:
let staticFiles = require('./static-files');
app.use(staticFiles('/static/', __dirname + '/static'));
// parse request body:
app.use(bodyParser());
// bind .rest() for ctx:
app.use(rest.restify());
// add controllers:
app.use(controller());
app.listen(3000);
console.log('app started at port 3000...');