Requirement
nodejs > 7.1
Install
npm install k -g
Scaffold
k has some quickly way to create a app, by the scaffold.
Here we used the k new [app]
.
$ cd ./home
$ k new appname
$ npm install
(wait for a while and completed)
Then we can see the files.
Run
Run the node index.js
could see the hello world
on http://127.0.0.1:3000 .
Modify some word
Open the /home/app/controller/main.js
, you can see the code below:
class main {
async index(ctx){
//let obj = ctx.model('user')
await ctx.render('welcome', {'welcome' : "hello world!"})
}
}
module.exports = main
Now we modify something:
class main {
async index(ctx){
//let obj = ctx.model('user')
await ctx.render('welcome', {'welcome' : "welcome to my app!"})
}
}
module.exports = main
Ctrl+c
the previous command and re-run the node index.js
.
so, we can see welcome to my app!
on the http://127.0.0.1:3000 .
Begin, we prepare a zip of the HTML pages and the style etc.
Download the pages for guestbook
In real world develop job, there would offered some semi-finished pages for you, and you can start up quickly.
In open source world, the Bootstrap is also a best tools for you to create the pages quickly.
Upzip the pages to the /home/public/
dir:
Move the guestbook.html
to the /home/app/view
we suppose you have some web development skills, so you can make a MySQL database to use.
First create a database for the guestbook.
CREATE TABLE `guestbook` (
`id` INT(11) NOT NULL AUTO_INCREMENT ,
`title` VARCHAR(50),
`contents` VARCHAR(200),
`username` VARCHAR(20),
`createtime` INT(11),
PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
Config the /home/app/config.js
with the database info.
const lodash = require('lodash')
const env = process.env.NODE_ENV || 'development'
let config = {
'default': {
'router_map': {
'/index.html': 'main/index',
'/': 'main/index',
},
'mysql' : {
host : 'localhost', // database host address
user : 'root', // database username
password : '123456', // database password
database : 'test' // database name
},
'view_opts' : {
'map' : {
html: 'twig'
}
},
},
'development': {},
'production': {}
}
module.exports = lodash.assign(config.default, config[env], {'default': {'env': env}})
Use the scaffold, the command k m [model]
.
$ cd ./home
$ k m guestbook
generated: /home/app/model/guestbook.js
ok, we got the guestbook model file:
the file contents:
module.exports = function (Model) {
class guestbook extends Model{
}
return guestbook
}
We should do with the write the post in guestbook, before we see it.
Open the template file /home/app/view/guestbook.html
, find the form
in about line 110:
<form method="POST" action="#">
modify to :
<form method="POST" action="/write">
now the form will post to the url /write
We set the url in the /home/app/config.js
, so it can directing to a action(main/write).
'router_map': {
'/index.html': 'main/index',
'/write' : 'main/write',
'/': 'main/index',
},
Open the controller /home/app/controller/main.js
, add a action:
class main {
async index(ctx){
//let obj = ctx.model('user')
await ctx.render('welcome', {'welcome' : "welcome to my app!"})
}
async write(ctx) {
console.log(ctx.request.body)
}
}
module.exports = main
by the config and the write() method, we can see the data posted by the form and received by the write() method.
the console.log(ctx.request.body)
show the posted data.
when post the form,
the console will display:
$ node index.js
{ title: 'the title',
contents: 'the contents',
username: 'my name' }
Next we write the data to the database:
async write(ctx) {
//console.log(ctx.request.body)
let newrow = {
"title": ctx.request.body.title,
"contents": ctx.request.body.contents,
"username": ctx.request.body.username,
"createtime": moment().format('X')
}
//console.log(newrow)
let guestbook = ctx.model('guestbook')
let newid = await guestbook.create(newrow)
if (newid) {
ctx.redirect('/index.html')
} else {
ctx.throw(502)
}
}
The createtime
we hoped to show in a pretty format, so we store the time in timestamp form, use the moment
lib.
- add a
dependencies
in the/home/package.json
,
"dependencies": {
"k": "latest",
"koa": "^2.3.0",
"moment": "^2.19.2",
"twig": "^1.10.5"
}
- require the
moment
in the/home/app/controller/main.js
const moment = require("moment")
class main {
...
- then we can use the
moment().format('X')
to get the timestamp for now.
Let's see the code main.js
above:
- we format a newrow object, and it's keys the same with the database field names. And it's values comes from the
ctx.request.body.[name]
, that is the HTML form input field name. For example the form data :<input type="text" name="title" ...>
is posted to thectx.request.body.title
. - then
let guestbook = ctx.model('guestbook')
, we create a model namedguestbook
. Thectx.model([model name])
is a quick helper for developer to create a db object. let newid = await guestbook.create(newrow)
, run thecreate
of the model, and receive the new increase idnewid
.- if the
newid
exists, we redirect the page to the front page:ctx.redirect('/index.html')
; if not, simply throw a 502 error.
you can post some new rows for next.
Let's focus on the index()
of main.js
, changed it to:
async index(ctx) {
let guestbook = ctx.model('guestbook')
let records = await guestbook.findAll()
moment.locale('zh-cn')
if (records) {
for (let r in records) {
records[r]['createtime'] = moment(records[r]['createtime'], "X").format('MMMM Do h:mm A')
}
}
//console.log(records)
await ctx.render('guestbook', {'records': records})
}
In the index() method:
- create the guestbook model.
- get all the data from the database table,
let records = await guestbook.findAll()
, by using the findAll(), witch is a function belongs to the model. moment.locale('zh-cn')
set the time field for locale.- if the records exists, then for each the
createtime
field to change to pretty format. - then render the
guestbook
template with records,ctx.render('guestbook', {'records': records})
.
Open the /home/app/view/guestbook.html
, in line 26, modify:
<div class="col-md-8">
{% for record in records %}
<div class="panel panel-default">
<div class="panel-body">
<div class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="/i/img/1.gif" alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading">{{record.title}}<button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></h4>
{{record.contents}}
<blockquote class="blockquote-reverse small">
<ul class="list-inline text-muted">
<li>by</li>
<li>{{record.username}}</li>
<li>{{record.createtime}}</li>
<li>
<button type="button" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> 100</button>
</li>
</ul>
</blockquote>
</div>
</div>
</div>
</div>
{% endfor %}
See the code above:
- Use the
twig
loop statementfor
to expand therecords
frommain.js
.
{% for record in records %}
...Loop the records data...
{% endfor %}
- Within the loop, replace some field to
{{Variable}}
, it means to show the Variable here.
Name | Meaning |
---|---|
{{record.title}} | the title |
{{record.contents}} | the post content |
{{record.username}} | username |
{{record.createtime}} | the time post is create, format by moment |
- Re-run the
node index.js
, so you can see the guestbook:
By now, we finish a guestbook simply.
CRUD is the four basic functions of Database. JQuery's syntax is designed to make it easier to navigate a document, select DOM elements, create animations, handle events, and develop Ajax applications.
First of all, we should create a new controller file by the scaffold, use k c [controllername]
$ cd ./home
$ k c single
generated: /home/app/controller/single.js
So, add a route rule '/single' : 'single/index',
in /home/app/config.js
:
'router_map': {
'/index.html': 'main/index',
'/write' : 'main/write',
'/single' : 'single/index',
'/': 'main/index',
},
and now visit the http://127.0.0.1:3000/single will go to the single
controller.
Open the /home/app/controller/single.js
, modify:
class single {
async index(ctx){
let condition = {"id" : ctx.request.query.upid}
let guestbook = ctx.model('guestbook')
let result = await guestbook.find(condition)
console.log(result)
console.log(guestbook.dumpSql())
}
}
module.exports = single
Here we ignore the display, instead to see the data.
- format the
condition
, it'sid
mapping to the GET parameterctx.request.query.upid
, that meaning we will find the condition is fieldid
equal thectx.request.query.upid
. let guestbook = ctx.model('guestbook')
get model as above.- next we
find
the condition, get a result, which show in the console.log(). - re-run
node index.js
, and visit http://127.0.0.1:3000/single?upid=1 , theupid
is some value exists in the database table. console.log(guestbook.dumpSql())
can see the executed SQL which is, very useful function.
now we get a single record in the table, which find by id
.
let's see the
main/index
has afindAll()
, and nowsingle/index
has afind()
, there are some different.
Addition we modify single.js
hoping to show the data on browser, by json format.
json JavaScript Object Notation. It is a very common data format used for asynchronous browser–server communication, including as a replacement for XML in some AJAX-style systems.
class single {
async index(ctx){
let condition = {"id" : ctx.request.query.upid}
let guestbook = ctx.model('guestbook')
let result = await guestbook.find(condition)
if ( result ){
ctx.set('Content-Type', 'application/json')
ctx.body = JSON.stringify(result)
}else{
ctx.throw(404)
}
}
}
module.exports = single
- when we get the
result
, check if it had a data, if not throw a404 NOT FOUND
error to browser. - if
result
got data, so change the content-type to json, then send json data byctx.body
now re-run and see on browser:
Next, we modify the guestbook.html
, on line about 116:
<div class="modal fade" id="showModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="show_title"></h4>
</div>
<div class="modal-body">
<p id="show_contents"></p>
<blockquote class="blockquote-reverse small">
<ul class="list-inline text-muted">
<li>by</li>
<li id="show_username"></li>
<li id="show_createtime"></li>
<li>
<button type="button" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> 100</button>
</li>
</ul>
</blockquote>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<script>
function showmsg(id){
$.getJSON("/single", { 'upid': id }, function(json){
$("#show_title").html(json.title);
$("#show_contents").html(json.contents);
$("#show_username").html(json.username);
$("#show_createtime").html(json.createtime);
$('#showModal').modal('show');
});
}
</script>
- modify the positions of fields, for example
<li id="show_username"></li>
is to show theusername
field. - create a javascript function
showmsg()
, it can send a request to/single
add upid, then receive the json in callback function. - the callback function
function(json){}
can execute in request return, then change the positions.
Also there are a job to do, find and change :
<h4 class="media-heading">{{record.title}}<button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></h4>
to
<h4 class="media-heading"><a href="javascript:void(0);" onclick="showmsg({{record.id}})">{{record.title}}</a><button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></h4>
- every title position we added a
onclick
event, it can raise theshowmsg()
function. - As the same time we bring the id to
showmsg()
.
Ok, now re-run and visit, click on every post title, we can see the Ajax data.
Now we create a LIKE function for the guestbook.
First we modify the database table to store the like number.
Run this SQL, we named the like for 'dig':
ALTER TABLE `guestbook` ADD `dig` INT(11) NULL DEFAULT '0' AFTER `username`;
Also, we modify the /home/app/controller/single.js
and add a like action.
async like(ctx){
let condition = {"id" : ctx.request.query.upid}
let guestbook = ctx.model('guestbook')
let result = await guestbook.find(condition)
if(result){
let dig = result["dig"]+1
let newrow = {"dig" : dig}
await guestbook.update(condition, newrow)
ctx.body = dig
}else{
ctx.throw(404)
}
}
Let's see it:
-
format the
condition
, it'sid
mapping to the GET parameterctx.request.query.upid
, that meaning we will find the condition is fieldid
equal thectx.request.query.upid
. -
let guestbook = ctx.model('guestbook')
get model as above. -
next we
find
the condition, get a result, and it's as the same as above. -
then, we let the dig dig value is add 1.
-
format
newrow
, mapping dig todig
field. -
guestbook.update
, the update the database. -
send dig value to the client
Also, we config the /home/app/config.js
for the like route, map the /like
to single/like
.
'router_map': {
'/index.html': 'main/index',
'/write' : 'main/write',
'/single' : 'single/index',
'/like' : 'single/like',
'/': 'main/index',
},
so, run the app, and see http://localhost:3000/like?upid=5 it is new like sum.
Modify the /home/app/view/guestbook.html
:
- add a js function
digmsg()
<script>
function showmsg(id){
...
}
function digmsg(id){
$.get("/like", {"upid" : id}, function(dig){
$("#dig-" + id).html(dig);
});
}
</script>
- modify the
like
position:
from:
<li><button type="button" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> 100</button></li>
to:
<li><button type="button" class="btn btn-default btn-xs" onclick="digmsg({{record.id}})"><span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> <span id="dig-{{record.id}}">{{record.dig}}</span></button></li>
we add a onclick
and the 'like dig show' .
addtion, we modify the popup window's like:
<span id="show_dig"></span>
and showmsg()
add a $("#show_dig").html(json.dig);
so, run app and see the like.
Last we introduce the delete
function.
we modify the /home/app/controller/single.js
and add a delete action.
async del(ctx){
let condition = {"id" : ctx.request.query.upid}
let guestbook = ctx.model('guestbook')
guestbook.delete(condition)
ctx.body = "ok"
}
- get
condition
andguestbook
the same as above. - call
guestbook.delete
- send ok to client
Then modify the config '/del' : 'single/del',
:
'router_map': {
'/index.html': 'main/index',
'/write' : 'main/write',
'/single' : 'single/index',
'/like' : 'single/like',
'/del' : 'single/del',
'/': 'main/index',
},
run the app and visit http://localhost:3000/del?upid=1 now the id 1 post is deleted.
Link to the template, modify the del button in /home/app/view/guestbook.html
:
code from:
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button>
<div class="panel panel-default">
to:
<button type="button" class="close" aria-label="Close" onclick="delmsg({{record.id}})"><span aria-hidden="true">×</span></button>
<div class="panel panel-default" id="panel-{{record.id}}">
add js function delmsg()
function delmsg(id){
$.get("/del", {"upid" : id}, function(){
$("#panel-" + id).fadeOut();
});
}
- we use the $.get() of JQuery.
- fadeOut() is a funny function.
ok, run the app, and check the delete.
When we has many posts, and the page would very long, so we can use the Pagination.
First we modify index()
of /home/app/controller/main.js
:
async index(ctx) {
let page = (ctx.request.query.p == undefined) ? 1 : ctx.request.query.p
let guestbook = ctx.model('guestbook')
let records = await guestbook.findAll("", "createtime DESC", "*", [page, 10])
moment.locale('zh-cn')
if (records) {
for (let r in records) {
records[r]['createtime'] = moment(records[r]['createtime'], "X").format('MMMM Do h:mm A')
}
}
await ctx.render('guestbook', {'records': records, 'page':guestbook.page})
}
}
- let the
page
value to 1 orctx.request.query.p
, that is the current page number. - change
findAll()
tofindAll("", "createtime DESC", "*", [page, 10])
, is means find all the posts, sort by createtime DESC, fields get all(*), and page, show every 10 posts in one page. - send the
guestbook.page
to template.
Then modify template part of <div class="pagination ...>
,to :
{% if page != undefined %}
<nav>
<ul class="pagination pull-right">
<li>
<a href="/?p={{page.prev_page}}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% for p in page.all_pages %}
<li {% if p == page.current_page %} class="active"{% endif %}>
<a href="/?p={{p}}">{{p}}</a>
</li>
{% endfor %}
<li>
<a href="/?p={{page.next_page}}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
{% endif %}
ok, the Pagination is show now.