-
Notifications
You must be signed in to change notification settings - Fork 11
Wade.Go Quick Start Guide
#1. Installation
##Prerequisites
- Go 1.3 (older versions of Go will NOT work!)
- Latest fresh and gopherjs installed and working as commands.
Here's how to install them:
go get -u github.com/gopherjs/gopherjsgo get -u github.com/pilu/fresh
- bower installed and working as a command
##Installing wadereddi - the demo app
-
Install the package
go get -u github.com/phaikawl/wadereddi
Note: From here, all directory paths that are referred, for example /public/index.html, are relative path of wadereddi's package directory, which is $GOPATH/src/github.com/phaikawl/wadereddi.
- Install javascript dependencies:
- Go to
/public - Run
bower install
- Go to
##Running
###Fresh
fresh is a tool to watch for changes in the server-side Go code (located in testapp/ez/server), automatically compile them and reload the server.
Just go to /server and run fresh.
###Gopherjs
gopherjs is the Go to Javascript compiler. It has a -w flag to watch for changes in the client-side Go code and automatically compile them. The output file is public/app.js.
Run
gopherjs build -w=true -o="public/app.js"
or just run the shell script run_gopherjs inside / which does the same thing.
###It's up!
The demo is served on localhost:3000, it kinda looks like reddit, just a demo.
Hopefully everything went smoothly for you. Throughout this document there will mainly be references to the demo app's code to introduce the concepts. So please play around with it.
The demo app's server would render the site on server side when you set the constant ServersidePrerender in /server/servermain.go to true.
#2. Pages
Pages in Wade.Go associate with a route and represent what is displayed for a given address. The demo's pages are declared in /client/clientside.go in AppFunc
app.Router.
Handle("/", wade.Redirecter{"/posts/top"}).
Handle("/posts/:mode", wade.Page{
Id: "pg-posts",
Title: "Posts",
}).
Handle("/comments/:postid", wade.Page{
Id: "pg-comments",
Title: "Comments for %v",
}).
Otherwise(wade.Page{
Id: "pg-404",
Title: "Page Not Found",
})
Here we have 3 pages. The first one has id "pg-posts", associates with route "/posts/:mode" and has title "Posts". Further down, there's a controller function registered to handle this page.
app.Register.Controller("pg-posts", func(p *wade.PageScope) (err error) {
When we browse "/web/pg-posts/top" on the browser, it matches the route to "pg-posts" (with the named parameter :mode="top"), the controller functions that match the page are called and the page is displayed.
Where does the HTML content come from? It's the code inside those HTML files in /public.
Wade.Go uses a powerful page system for easier laying out and code reuse. The master HTML file which is always served by the server is /public/index.html.
<script type="text/wadin"> is called the template area where all an app's HTML code is put, it will be processed by Wade and the real HTML will be displayed in an element with directive w-app-container (in the demo's index.html it's a div inside body). Wade.Go doesn't care about anything outside the template area.
<body>
<!-- TEMPLATE AREA HERE -->
<script type="text/wadin">
<winclude src="/public/components.html"></winclude>
<winclude src="/public/pages.html"></winclude>
</script>
<div w-app-container></div>
<script async src="/public/app.js"></script>
</body>
Here it includes the code in /public/pages.html and /public/components.html. <winclude> does nothing surprising, it just tells Wade.Go to put all HTML code of the specified file in that place.
In /public/pages.html you can see the content, there's a layout with the sidebar's code and the main div #mainincludes 2 other files
<div w-belong="pg-posts">
<winclude src="/public/pg-posts.html"></winclude>
</div>
<div w-belong="pg-comments">
<winclude src="/public/pg-comments.html"></winclude>
</div>
w-belong="pageid" is a special directive that tells Wade.Go to display that element for the specified page or pages, and not display it for other pages. So when we're on pg-posts, the parent div and the content inside /public/pg-posts.html is displayed, while the div for "pg-comments" is not displayed. If an element doesn't have any ancestor with a w-belong, it's always displayed for every page.
There's also page group, in AppFunc there is
app.Register.PageGroup("grp-main", []string{"pg-posts", "pg-comments"})
Here we register a page group with id "grp-main", it represents "pg-posts" and "pg-comments". Note that inside the list you can also specify a page group as a child of another page group.
In "pages.html" we have
<div w-belong="grp-main">
<ul class="nav">
<li case="pg-home"><a @href="url('pg-posts', 'top')">Posts</a></li>
</ul>
</div>
This one is the "Posts" link on the left sidebar, it's displayed for both the Posts and the Comments page. Page groups is pretty useful when you have a lot of different pages that an element should be displayed in.
Note that you can also specify a list of ids (space-separated) in w-belong (w-belong="page1 page2").
Page groups and pages are treated equal, you can use a page group id instead of page id as the first argument to app.Register.Controller. Controller of page groups are called BEFORE the page's direct controller is called.
Inside a page controller:
- SetModel sets the model for the controller. Its exported fields are then available to be used in the corresponding page's HTML code.
- AddValue adds values to the scope, it is used for easily passing constants, non-changing data and small functions that don't have anything to do with the app's logic to the scope for use in the page's HTML code.
#3. Data binding
Wade.Go uses HTML data binding, like Angular.js, Ember.js and many javascript frameworks. HTML is automatically updated when the data model changes. The syntax is designed to be simple, strict and clearly defined.
An important thing to note is, in order to avoid unnecessary performance loss, Wade.Go lets you control whether a value is watched (an update is performed on the view whenever the value changes) or not. You must prefix a value with $ for it to be watched.
There are 2 kinds of binding: binder binding and property binding.
Binder binding is denoted by a leading #, a binder is executed on an element, it can do whatever logic it wants, freely modify the element.
The simplest binder is html binder. Inside /public/pg-posts.html, the link for each post is displayed with
<h3><a #html="post.Title" ...></a></h3>
The #html simply binds the content of the element to the value of the right-hand-side expression. So on the demo, when post.Title is "Crazy armlet on Techies...", we get a link with content Crazy armlet on Techies....
Binders can take extra parameters, in /public/components.html we have
<a #on(click)="@DoVote(1)">
The binder is used to call a specified function when an event occurs, here it calls func() { DoVote(1) } on "click" event. The event binder, named "on", takes 1 parameter that is the event name. So you may use #on(keyup)=, #on(dblclick)= or whatever you want.
Binders follow a defined standard and they are very powerful, can do whatever they want to the element. There's an API to write custom binders.
We loop and display a list with the each binder. It's purely a binder, there is no extra special syntax.
In /public/pg-posts.html:
<div #each(_,post)="$Posts">
<div class="col-sm-11">
<h3><a #html="post.Title" @href="GetLink(post)"></a></h3>
<h4>
We loop the "Posts" slice, display each element. The binder takes 2 extra parameters that are the names that each element's index and value would bind to. Here it binds the index number to _ (it's a valid name, conventionally used for ignoring) and the value to post. You can see that post is used in the code inside, look at those #html="post.Title", GetLink(post)...
Two-way binding is also available for input elements, the value binder. There are IfBinder and some other too, please refer to bind API docs for more information.
Property binding is denoted by a leading @, it binds a normal HTML property/attribute (or a Component's field) to a value.
In the demo there are binds executed on the href attribute
<a @href="url('pg-comments', post.Id)">
The <a>'s href above will have its value be the result of that url( helper function call.
This part explains the syntax of the right-hand-side value part (those post.Title, GetLink(post) and url('pg-comments', post.Id)).
A dot . is used for accessing struct field, map and even array subscripting. You can call function and use literals, as you can see from above, string literals are enclosed with '; integer, real numbers and true/false are also supported normally.
// struct field accessing
Post.Vote.Score
// access the first element of the Comments slice
Post.Comments.1
// call a function, use a string literal as first argument
url('pg-comments', post.Id)
To keep things clear and discourage logic in HTML code, operators and other kinds of programming expression are not supported. Only 1 statement is allowed for each bind expression. Why complicate HTML when you can do everything in Go?
Wade.Go doesn't treat function calls specially, functions are values themselves and the value of a function call is its return value. The event binder binds to a function, not a function call, so something like this will cause a panic
#on(click)="DoVote(1)"
It binds to nothing because DoVote is a method (defined in /client/clientside.go) that returns nothing, that "nothing" is the value given to the binder, which is faulty.
Instead we do this
#on(click)="@DoVote(1)"
The @ syntax here simply wraps the function call and produce a function, so it's like
#on(click)="func() { DoVote(1) }" (but of course Wade.Go doesn't allow something like that in the syntax).
url is a helper function, built-in with Wade.Go for convenience, can be called from anywhere. There are some other built-in helpers, they are defined here.
Wade.Go also supports the {{ expression }} interpolation syntax for text content, binding a text node to a value. It does what its supposed to do, used a lot in the demo.
// in /public/pg-posts.html
<small class="text-muted">{{ post.Time }} hours ago
• <a @href="url('pg-comments', post.Id)">
{{ len(post.Comments) }} Comments
</a></small>
You need to prefix a value with $ if you want Wade to watch it for changes and automatically update the view, otherwise the value is evaluated only once.
As you can see from the posts loop in pg-posts.html.
<div #each(_,post)="$Posts" class="row-fluid post-wrapper">
<div class="col-sm-1">
<votebox @Vote="post.Vote" @AfterVote="Rank.TopRefresh"
@VoteUrl="GetVoteUrl(post)"></votebox>
</div>
<div class="col-sm-11">
<h3><a #html="post.Title" @href="GetLink(post)"></a></h3>
<h4>
by {{ post.Author }}
...
</h4>
</div>
</div>
Here we only watch for changes to Posts. The each binder always perform shallow watch, that is, it only watch for changes to the field itself, no children are considered. Because of that, it only rebuilds the whole list when the field Posts is assigned a new slice value, for example
// assign it to a new slice
scope.Posts = make([]*Post, 0)
// or this
scope.Posts = append(scope.Posts, newpost)
You can watch individual items normally, in pg-posts.html's code above, the field post.Title is not watched. If you want to watch for its changes, just use $post.Title, the post's title link element would be updated whenever its Title field changes, no rebuild of the list is necessary.
Wade.Go always only perform shallow binding in general, for example when a struct pointer is watched, a change is only triggered when the pointer itself is assigned a new value, the fields are untouched unless we watch them too.
Watching is performed on the individual changing targets, not the whole thing, that is, for example if we want to watch for changes to Post.Comments in this bind
{{ len(Post.Comments) }}
We would use
// Watch for changes to "Post.Comments"
// when it's changed, the result of the len() call is updated accordingly
{{ len($Post.Comments) }}
NOT this
// This will raise a panic, function calls can't be watched
{{ $len(Post.Comments) }}
Another example, if we want to watch both NameA and NameB of this
{{ strJoin(NameA, NameB) }}
Then we use
{{ strJoin($NameA, $NameB) }}
#4. Components Wade.Go has a Components system inspired by ReactJs and the upcoming HTML5 Web Components. Each component contains some HTML code and some data, logic associated with it. They are self-contained and reusable.
The demo creates and uses a component named VoteBox, it's the upvote/downvote box on the left of each post or comment. It's declared in AppFunc
// Register the "votebox" component
app.Register.Components(com.Spec{
Name: "VoteBox",
Prototype: &VoteBoxModel{},
Template: com.DeclaredTemplate{"tmpl-votebox"},
})
The component associates with a prototype struct that is a *VoteBoxModel, which holds its necessary data and state.
// VoteBoxModel is the prototype for the "votebox" custom element
VoteBoxModel struct {
*wade.BaseProto
Vote *c.Score
VoteUrl string
AfterVote func() // function to be called after a vote is done
}
A separate instance of the prototype (a model) is created for each individual usage of the component.
Each component must have a "template" which defines what HTML content is displayed for the component. Above, with com.DeclaredTemplate we get the HTML from a HTML <template> element that has id tmpl-votebox. It is from /public/components.html
<template id="tmpl-votebox">
<div class="votebox">
<div class="row-fluid"> </div>
...
</template>
You can specify the template with a plain string instead, like this
Template: com.StringTemplate(`
<div class="votebox">
<div class="row-fluid"> </div>
...
`)
A component's instance is created when using its name as a HTML tag. Votebox is used in /public/pg-posts.html like this
<div #each(_,post)="$Posts" class="row-fluid post-wrapper">
<div class="col-sm-1">
// !!!we use the VoteBox component here
<VoteBox @Vote="post.Vote" @AfterVote="Rank.TopRefresh"
@VoteUrl="GetVoteUrl(post)"></VoteBox>
All exported fields in the component's prototype are considered valid HTML properties, we can assign value to them and can use property binding. Above we bind the Vote field to post.Vote, VoteUrl to GetVoteUrl(post).
Note that all the binding and watching logic are applied normally (which means you can use a $ for Wade to watch for changes to a value and automatically update the component's model field accordingly, otherwise it won't watch).
Component's prototype struct should always embed wade.BaseProto to satisfy com.Prototype and have access to the app instance via BaseProto.App.
type Prototype interface {
Init(parentScope *scope.Scope, element dom.Selection) error
ProcessContents(ContentsData) error
Update(ElemData) error
}
Those methods can be "overridden" on your component's prototype struct for more custom behaviors.
If you use a <wcontents></wcontents> inside the template, it will be replaced with the contents between the custom element's opening and closing tag on usage. For example if you have a <smiley> custom tag with the template
<span class="smiley">
>> <wcontents></wcontents> <<
</span>
In a certain place you use it like this:
<smiley>:D</smiley>
The rendered HTML will be
<smiley>
<span class="smiley">
>> :D <<
</span>
</smiley>
You can process the contents (the :D in this example) by declaring the ProcessContents method in your component's prototype, for example you could convert the text to a smiley image.
The Update method is called on construction and whenever some data watched via attribute binding changes. And the Init is for simple initialization.
Hope this covers all the necessary basics. The main thing is just play around with the demo.
Take care and don't hesitate to send me some feed backs or pull requests.