- Fast Response Time : Speed is usually top of the requirement for apps and when tallking about native apps you want people to do what they want to to on the go.
- Fault Tolerant : If you have millions of users, you want all of them to have a great user experience.
- Concurrent : From the technology stand point everyboy wants concurrency, and with Elixir/Phoenix you get to spawn thousands and millions of processes and the speed you get in doing things the same time than doing it one at a time gives your app an even insane speed.
- Scalable : You want your backend not to ever die. You want your app to scale and your team to scale as well because of the community that is passionate about the Phoenix framework.
- Maintainable : It’s easy to maintain, having hot-code reloading allows you to deploy without disturbing the users experience. The expressive nature of the code also makes it easy to maintain even as your code base grows.
using fuse tools and elixir lang
a blog about android apps dev with fuse and real time backend systems with elixir.
Saturday, August 5, 2017
Why Phoenix Framework is great for building realtime backends for mobile apps
How to create a user authentication systems with email verification in phoenix framework
$ mix phx.new sample --no-html --no-brunch
* creating sample/config/config.exs
* creating sample/config/dev.exs
* creating sample/config/prod.exs
* creating sample/config/prod.secret.exs
* creating sample/config/test.exs
* creating sample/lib/sample/application.ex
* creating sample/lib/sample/web/channels/user_socket.ex
* creating sample/lib/sample/web/views/error_helpers.ex
* creating sample/lib/sample/web/views/error_view.ex
* creating sample/lib/sample/web/endpoint.ex
* creating sample/lib/sample/web/router.ex
* creating sample/lib/sample/web/web.ex
* creating sample/mix.exs
* creating sample/README.md
* creating sample/test/support/channel_case.ex
* creating sample/test/support/conn_case.ex
* creating sample/test/test_helper.exs
* creating sample/test/sample/web/views/error_view_test.exs
* creating sample/lib/sample/web/gettext.ex
* creating sample/priv/gettext/en/LC_MESSAGES/errors.po
* creating sample/priv/gettext/errors.pot
* creating sample/lib/sample/repo.ex
* creating sample/priv/repo/seeds.exs
* creating sample/test/support/data_case.ex
* creating sample/.gitignore
Fetch and install dependencies? [Yn] n
We are almost there! The following steps are missing:
$ cd sample
$ mix deps.get
Then configure your database in config/dev.exs and run:
$ mix ecto.create
Start your Phoenix app with:
$ mix phx.server
You can also run your app inside IEx (Interactive Elixir) as:
$ iex -S mix phx.server
# In your config/config.exs file
config :sample, Sample.Mailer,
adapter: Swoosh.Adapters.Mailgun,
api_key: "x.x.x"
# In your lib/sample folder create mailer.ex
defmodule Sample.Mailer do
use Swoosh.Mailer, otp_app: :sample
end
defmodule StockitServer.UserEmail do
import Swoosh.Email
# In your lib/sample folder create user_email.ex
defmodule StockitServer.UserEmail do
import Swoosh.Email
def welcome(user) do
new()
|> to({user.name, user.email})
|> from({"your app name", "your-appxxx@gmail.com"})
|> subject("Your sample verification email")
|> html_body("<p>Thanks for signing up with us</p>
<p>Please click the link below to verify your email address</p>
<a href=https://sxxxx.com/v1/api/auth/verify_email/# {user.token}>Verify address</a>"
)
end
end
# In your config/config.ex
config :guardian, Guardian,
allowed_algos: ["HS512"], # optional
verify_module: Guardian.JWT, # optional
issuer: "Sample",
ttl: { 30, :days },
allowed_drift: 2000,
verify_issuer: true, # optional
secret_key: System.get_env("GUARDIAN_SECRET") || "xx...xxxx",
serializer: Sample.Web.GuardianSerializer
# In your lib/sample/web folder create guardian_serializer.ex
defmodule Sample.Web.GuardianSerializer do
@behaviour Guardian.Serializer
alias Sample.Repo
alias Sample.Account.User
def for_token(user = %User{}), do: { :ok, "User:#{user.id}" }
def for_token(_), do: { :error, "Unknown resource type" }
def from_token("User:" <> id), do: { :ok, Repo.get(User, id) }
def from_token(_), do: { :error, "Unknown resource type" }
end
#In your lib/sample create account/account.ex
defmodule Sample.Account do
@moduledoc """
The boundary for the Account system.
"""
import Ecto.Query, warn: false
alias Sample.Repo
alias Sample.Account.User
@doc """
Returns the list of users.
## Examples
iex> list_users()
[%User{}, ...]
"""
def list_users do
Repo.all(from u in "account_users",
order_by: u.inserted_at,
select: %{name: u.name, username: u.username, email_verified: u.email_verified, token: u.token, email: u.email, id: u.id})
end
@doc """
Gets a single user.
Raises `Ecto.NoResultsError` if the User does not exist.
## Examples
iex> get_user!(123)
%User{}
iex> get_user!(456)
** (Ecto.NoResultsError)
"""
def get_user!(id), do: Repo.one(from u in "account_users",
where: u.id == ^String.to_integer(id),
select: %{name: u.name, username: u.username, email_verified: u.email_verified, token: u.token, email: u.email, id: u.id})
@doc """
Creates a user.
## Examples
iex> create_user(%{field: value})
{:ok, %User{}}
iex> create_user(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_user(attrs \\ %{}) do
%User{}
|> User.registration_changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a user.
## Examples
iex> update_user(user, %{field: new_value})
{:ok, %User{}}
iex> update_user(user, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_user(%User{} = user, attrs) do
user
|> User.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a User.
## Examples
iex> delete_user(user)
{:ok, %User{}}
iex> delete_user(user)
{:error, %Ecto.Changeset{}}
"""
def delete_user(%User{} = user) do
Repo.delete(user)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking user changes.
## Examples
iex> change_user(user)
%Ecto.Changeset{source: %User{}}
"""
def change_user(%User{} = user) do
User.changeset(user, %{})
end
def get_by(username) do
Repo.get_by(User, username: username)
end
def get_by_token(token) do
Repo.get_by(User, token: token)
end
def verify_email(user) do
user
|> User.verify_changeset()
|> Repo.update()
end
def get!(id) do
Repo.get!(User, id)
end
end
#In your lib/sample create account/user.ex for our user schema
defmodule Sample.Account.User do
use Ecto.Schema
import Ecto.Changeset
alias Sample.Account.User
schema "account_users" do
field :email, :string
field :name, :string
field :username, :string
field :password, :string, virtual: true
field :password_hash, :string
field :email_verified, :boolean
field :token, :string
timestamps()
end
def changeset(%User{} = user, params) do
user
|> cast(params, [:name, :email, :username])
|> validate_required([:name, :username, :email])
|> validate_length(:username, min: 5, max: 20)
|> validate_format(:email, ~r/@/)
|> unique_constraint(:email)
|> put_not_verified()
|> put_token()
end
defp put_not_verified(changeset) do
case changeset do
%Ecto.Changeset{valid?: true} ->
put_change(changeset, :email_verified, false)
_ ->
changeset
end
end
defp put_token(changeset) do
case changeset do
%Ecto.Changeset{valid?: true} ->
put_change(changeset, :token, SecureRandom.urlsafe_base64())
_ ->
changeset
end
end
def registration_changeset(model, params \\ %{}) do
model
|> changeset(params)
|> cast(params, [:password])
|> validate_length(:password, min: 4, max: 200)
|> put_pass_hash()
end
defp put_pass_hash(changeset) do
case changeset do
%Ecto.Changeset{valid?: true, changes: %{password: password}} ->
put_change(changeset, :password_hash, Hasher.salted_password_hash(password))
_ ->
changeset
end
end
def verify_changeset(%User{} = user, params \\ %{}) do
user
|> cast(params, [])
|> put_verify_email()
end
defp put_verify_email(changeset) do
case changeset do
%Ecto.Changeset{valid?: true} ->
put_change(changeset, :email_verified, true)
_ ->
changeset
end
end
end
# Let create our migration from your command line
$ mix ecto.gen.migration create_user
defmodule Sample.Repo.Migrations.CreateAccountUser do
use Ecto.Migration
def change do
create table(:account_users) do
add :name, :string
add :username, :string
add :email, :string
add :token, :string
add :email_verified, :boolean
add :password_hash, :string
timestamps()
end
create unique_index(:account_users, [:email, :username])
end
end
$ mix ecto.migrate
defmodule Sample.Web.Auth do
import Hasher
alias Sample.Account
def login_by_username_and_pass(username, given_pass) do
user = Account.get_by(username)
if user do
cond do
user.email_verified && check_password_hash(given_pass, user.password_hash) ->
{:ok, user}
user.email_verified && check_password_hash(given_pass, user.password_hash) == false ->
{:err, :unauthorized}
not user.email_verified ->
{:err, :notverified}
end
else
{:err, :notfound}
end
end
end
defmodule Sample.Web.Router do
use Sample.Web, :router
pipeline :api do
plug :accepts, ["json", "json-api"]
plug JaSerializer.Deserializer
end
pipeline :api_auth do
plug :accepts, ["json", "json-api"]
plug Guardian.Plug.VerifyHeader, realm: "Bearer"
plug Guardian.Plug.LoadResource
plug JaSerializer.Deserializer
end
scope "/", Sample.Web do
pipe_through :api # Use the default browser stack
get "/", PageController, :index
end
scope "/v1/api/auth", Sample.Web do
pipe_through :api
get "/verify_email/:token", AccessTokenController, :verify_email
end
end
#In your lib/sample/web/controllers/access_token_controller.ex
defmodule Sample.Web.AccessTokenController do
use Sample.Web, :controller
alias Sample.Account
alias Sample.Account.User
action_fallback Sample.Web.FallbackController
def verify_email(conn, %{"token" => token}) do
user = Account.get_by_token(token)
if user do
with {:ok, %User{} = user} <- Account.verify_email(user) do
conn
|> put_status(200)
|> render("verify_email.json", user: user)
end
else
conn
|> put_status(:not_found)
|> json(%{error: "invalid token"})
end
end
end
defmodule Sample.Web.AccessTokenView do
use Sample.Web, :view
def render("verify_email.json", %{user: user}) do
%{
verified: user.email_verified
}
end
end
#In lib/sample/web/channels create access_token_channel.ex
defmodule Sample.Web.AccessTokenChannel do
use Sample.Web, :channel
alias Sample.Account
alias Sample.UserEmail
alias Sample.Mailer
def join("access:lobby", _payload, socket) do
{:ok, socket}
end
def handle_in("create:user", %{"user" => user_params}, socket) do
case Account.create_user(user_params) do
{:ok, user} ->
UserEmail.welcome(user)|>Mailer.deliver
resp = %{data: %{id: user.id,
name: user.name,
username: user.username,
email: user.email,
token: user.token,
verified: user.email_verified }
}
push socket, "create:user", resp
{:reply, :ok, socket}
{:error, _changeset} ->
{:reply, {:error, %{errors: "Could not create user"}}, socket}
end
end
def handle_in("create:login", %{"user" => %{"username" => username, "password" => password}}, socket) do
case Sample.Web.Auth.login_by_username_and_pass(username, password) do
{:ok, user} ->
{:ok, jwt, _claims} = Guardian.encode_and_sign(user, :access)
resp = %{data: %{access_token: jwt}}
push socket, "create", resp
{:noreply, socket}
{:err, :notverified} ->
{:reply, {:error, %{errors: "please verify your email address"}}, socket}
{:err, :unauthorized} ->
{:reply, {:error, %{errors: "user password incorrect"}}, socket}
{:err, :notfound} ->
{:reply, {:error, %{errors: "user not found"}}, socket}
end
end
end
#In your lib/sample/web/channels/user_socket.ex file add to the list of channels
channel "access:*", Sample.Web.AccessTokenChannel
Friday, August 4, 2017
Instacalc - A sales calculator app
Soccer Fans Chat app
Stock Taking app for small shops
This is stockIT a stock management app for small shops with a real time backend written in Elixir lang. The backend is hosted in heroku. Shops can have up to date inventory of products in their shops at anytime.
Thursday, August 3, 2017
Realtime native apps made easy with Fuse tools and Phoenix Channels
Elixir’s Phoenix framework is an ideal choice for building real-time applications and Fuse tools is even easier for building native mobile apps.
In this tutorial i'll show how to build an app using Elixir's Phoenix framework channels and Fuse tools.We won't worry about persistance or authentication.
Installation and Setup
This tutorial will assume the following language and framework versions:
- Erlang 19.0
- Elixir 1.4
- Phoenix 1.2
- Fuse 1.0
If you need to insall Erlang, Elixir and Fuse tools, i recommend checking out their websites.
To install Phoenix Framework and its dependencies, follow the Phoenix installation docs. Once you are able to install Phoenix withIn the same file, we’ll also need to uncomment line 5, which I’ll explain next:
$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez
you should be ready to proceed.
Create New Phoenix App
I'will call it "MessageGram", and create it with
$ mix phoenix.new message_gram
When asked “Fetch and install dependencies?” enter “Y”. Finally, when the new app is generated, you should see the following message:
```
We are all set! Run your Phoenix application:
$ cd message_gram
$ mix phoenix.server
Before moving on, configure your database in config/de.exs and run:
$ mix ecto.create
```
Next, we’ll need to modify the default MessageGram.UserSocket module. In the same file, we’ll also need to uncomment line 5, which I’ll explain next:
```
Channels
channel "room:*", MessageGram.RoomChannel
```
Of course, our MessageGram.RoomChannel module doesn’t exist yet. Let’s fix that
```
defmodule MessageGram.RoomChannel do
use MessageGram.Web, :channel
def join("room:lobby", payload, socket) do
{:ok, socket}
end
def join("room:" <> _private_room_id, _params, _socket) do
{:error, %{reason: "Unauthorized"}}
end
def handle_in("new_msg", %{"message" => message, "at" => at}, socket) do
broadcast! socket, "new_msg", %{message: message, at: at}
{:noreply, socket}
end
def handle_out("new_msg", payload, socket) do
push socket, "new_msg", payload
{:noreply, socket}
end
end
```
It's finally time to write our Fuse app.
First we'll need the Phoenix Channel javascript client to enable our Fuse app communicate with our Phoenix realtime backend. Since the client that comes with Phoenix by default is written in Es6, we'll need an Es5 version because Fuse tools only support Es5.
Go ahead and download a Phoenix Channel javascript client Es5 version
We are all set! Create our Fuse app:
$ fuse create app MessageGram
Let's add the phoenix-common.js file to the root of our app and modify our MessageGram.unproj file:
```
{
"RootNamespace":"",
"Packages": [
"Fuse",
"FuseJS"
],
"Includes": [
"*",
"phoenix-common.js:Bundle",
]
}
```
This will bundle the phoenix javascript client with our app to enable us use require("phoenix-common") in our ux file.
Lets build our message app in our MainView.ux file:
```
var Observable = require("FuseJS/Observable");
var messages= Observable();
var txt = Observable("");
var Phoenix = require("phoenix-common");
var channel = null;
var socket = new Phoenix.Socket("ws://localhost:4000/socket",{params: {userToken: "123"}})
fetch("http://localhost:4000", {
method: "get"
}).then(function(resp) {
socket.connect();
}).catch(function(err) {
console.log("Error connecting to server")
})
channel = socket.channel("products:lobby", {});
channel.on("new_msg", function(payload) {
console.log("Main mesage: "+payload.message)
messages.add(payload)
})
channel.join().receive("ok", function(resp) {
console.log("Joined successfully")
}).receive("error", function(resp) {
return console.log("Unable to join", resp)
})
function sendClick() {
channel.push("new_msg", {message: txt.value, at: new Date().toDateString()})
txt.value = ""
}
module.exports = {
sendClick,
txt,
messages
};
</JavaScript>
<Panel Background="#183f">
<Grid Rows="1*,1*">
<ScrollView>
<StackPanel ItemSpacing="10">
<Each Items="{messages}">
<StackPanel Orientation="Horizontal" ItemSpacing="5">
<Text TextColor="White" Value="{message}"/>
<Text TextColor="White" Value="{at}"/>
</StackPanel>
</Each>
</StackPanel>
</ScrollView>
<StackPanel HitTestMode="LocalBoundsAndChildren">
<TextInput PlaceholderText="text" PlaceholderColor="#ffffff80" TextColor="White" Width="250" Value="{txt}" />
<Rectangle Color="#127799" Height="40" Margin="20" Width="100" CornerRadius="80">
<Text Value="Send" Alignment="Center" TextColor="White" Clicked="{sendClick}"/>
</Rectangle>
</StackPanel>
</Grid>
```
Then run our app with:
$ fuse preview
And that’s it! We can test this by opening our app in multiple browser tabs to simulate multiple users talking to each other in real-time and with our fuse native app.
Wednesday, January 4, 2017
Building an ecommerce android app with fuse tools
Fuse Tools is a javascript framework for building world-class ios and android apps using UX markup and javascript. UX markup is for navigation, layout, animation while javascript is for business and application logic. To learn more visit fusetools.com
Getting Started
To get started you need to download Fuse Tools installer from fusetools.com and if you are using windows after Fuse Tools is installed; from the command line you run fuse install android to instal android sdk and its dependencies. Create a new project Once you have fuse tools installed, to create a new project you simply call
$ fuse create app ProjectName
Then run your app by calling:
fuse preview
So this point lets jump into the application and see what we are going to build out today — A Bookstore app. In the app you can browse the list of books in the store, select one or more books to add to the cart where you can then checkout to see your purchases. When you bootstrap a new fuse app, what you see is two files; MainView.ux and a unoproj file. The MainView.ux file is the entry to your application while the unoproj file list your app dependecies. When you open MianView.ux what you see
What you notice is that instead of Html what you will be using is UX markup. Compared to other javascript frameworks like ReactNative and Nativescript which where built specifically for javacript developers, fuse tools is built with collaboration of designers and developers in mind. Now it’s good practice to structure project folder into different folders depending on its functions. Components : will host our shared components Modules : our context and backend files definitions Pages : our app pages Assets : our images, fonts Lets start by adding some data to our app. We’ll be creating a mock backend for now.
So create a Backend.js file in Modules folder
var books = [
{
id: 1,
title: “Beginning Android Programming”,
author: “J.F DiMarzio”,
authorbio: “About DiMarzio”,
publicationdate: “2017 by John Wiley & Sons”,
introduction: “This book is written to help start beginning Android developers “,
picture: “Assets/books/android.png”,
cost: 25.00
},
{
id: 2,
title: “ES6 & Beyound”,
author: “Kyle Simpson”,
authorbio: “Kyle Simpson is a thorough pragmatist.”,
publicationdate: “2015–5–5”,
introduction: “This book is about shaking up your sense of understanding by exposing you “,
picture: “Assets/books/es6.png”,
cost: 35.99
},
{
id: 3,
title: “ng-book 2”,
author: “Ari Lerner”,
authorbio: “Full stack web developer and trainer.”,
publicationdate: “2016–5–10”,
introduction: “A complete refernce book on angular 2. “,
picture: “Assets/books/ngbook21.png”,
cost: 25.99
},
{
id: 4,
title: “Pro Git”,
author: “Scott Chacon and Ben Straub”,
authorbio: “Full stack web developer and trainer.”,
publicationdate: “2016–5–10”,
introduction: “Welcome to the second edition of Pro Git. “,
picture: “Assets/books/progit.png”,
cost: 45.99
},
{
id: 5,
title: “Reactjs Blueprints”,
author: “Sven A. Robbestad”,
authorbio: “Sven A. Robbestad is a developer with a keen interest in the Web .”,
publicationdate: “2016–7–10”,
introduction: “ReactJS was developed as a tool to solve a problem with the application state. “,
picture: “Assets/books/reactjsblue.png”,
cost: 20.99
},
{
id: 6,
title: “ReAwaken The Giant Within”,
author: “Tony Robins”,
authorbio: “Tony Robbins is one of the great influences of this generation.”,
publicationdate: “2013–5–10”,
introduction: “I’m sending you this gift of a condensed version of my 544-page original book in the hope”,
picture: “Assets/books/awaken.png”,
cost: 22.00
},
{
id: 7,
title: “SurviveJS”,
author: “Juho Vapsalainen”,
authorbio: “Full stack web developer and trainer.”,
publicationdate: “2016–5–10”,
introduction: “Front-end development moves forward fast. “,
picture: “Assets/books/survivejs.png”,
cost: 25.99
},
{
id: 8,
title: “Switching To Angular2”,
author: “Minko Gechev”,
authorbio: “Minko Gechev is a software engineer who strongly believes in open source software. “,
publicationdate: “March 2016”,
introduction: “It is the modern framework you need to build performant and robust web applications.”,
picture: “Assets/books/switchingto.png”,
cost: 21.00
},
{
id: 9,
title: “Unlimited Sales Success”,
author: “Brian Tracy”,
authorbio: “A world class motivational and sales consultant.”,
publicationdate: “2013–2–10”,
introduction: “A complete refernce book on todays selling. “,
picture: “Assets/books/selling.png”,
cost: 25.99
},
{
id: 10,
title: “Web Development with Node and ExpressJS”,
author: “Ethan Brown”,
authorbio: “A senior software engineer at PoP Art.”,
publicationdate: “2014–6–27”,
introduction: “Learn to build modern web applications with node and expressjs “,
picture: “Assets/books/node.png”,
cost: 19.99
}
];
Lets create a promise to return the books
function getBooks() {
return new Promise(function(resolve, reject) {
resolve(books)
})
}
//Now lets export these functions
module.exports = {
getBooks: getBooks
}
Now lets create a context, this will allow our pages view model to interface with it rather than directly with our backend thereby allowing object caching which will reduce unnecceary call to our backend server and so reducing bandwidth cost and saving battery.
Lets create a Context.js file in the Modules folder
first we create an observable to store app state
var Backend = require(“./Backend”)
var Observable = require(“FuseJS/Observable”)
var store = Observable() // this will hold our data coming from the backend
var books = Observable() // this will hold our initial books from the store
var cart = Observable() // this will hold the contents of our cart
var total = Observable(0) // total price of cart items
var isCartEmpty = Observale(true) // whether the cart is full
var totalAmount = Observable() // total amount expression
Lets get the books from our backend first
function getBooks() {
Backend.getBooks()
.then(function(books) {
store.replaceAll(books)
//Now pass the books to our books observable
store.forEach(function(book) {
books.add(book)
})
})
.catch(function(error) {
console.log(“can not load books”)
})
}
Now call this function to load the books when the app starts getBooks() Now lets create a function to add books to cart pasing the id, cost, title, picture, author and a harcoded qty of 1
function addToCart(id, cost, title, picture, author) {
cart.add({id: id, cost: cost, title: title, picture: picture, author: author, qty: 1})
// Now calculate the total amount of cart items
cart.forEach(function(book) {
total.value = (total.value + (book.price * book.qty))
})
isCartEmpty.value = cart.length ? false : true
totalAmount.value = “You Paid : $ “ + total.value.toFixed(2)
}
Now lets add a function to remove an item from the cart
function removeFromCart(item) {
cart.remove(item)
total.value = 0
isCartEmpty.value = cart.length ? false : true
totalAmount.value = “You Paid : $ “ + total.value.toFixed(2)
}
Now lets export these functions and observables
module.exports = {
totalAmount,
cart,
isCartEmpty,
addToCart,
removeFromCart
}
We now have some data we can add to our app components view model Since this app i a multi-page app, we will create a navigation component to handle routing to each of the page.
In our MainView.ux file
Lets create our home page Home.ux in Pages folder and also Home.js for the buisness logic for this page First our business and data logic for the home page
Now we create the Home.js file in the Pages folder
var Context = require(“Modules/Context”);
function getBookDetail(args) {
var book = args.data
router.push(“detail”, book)
}
function goToCart() {
router.goto(“cart”)
}
module.exports = {
books: Context.books,
getBookDetail: getBookDetail,
goToCart: goToCart
}
Home.ux
Lets create our detail page Detail.ux which will contain more details about a particular book we want to buy. First we create the buisness and data logic for the detail page.
Detail.js
var Context = require(“Modules/Context”);
var book = this.Parameter; // the parameter data we passed from the home page which is an observable
var title = book.map(function(x) {
return x.title
});
var id = book.map(function(x) {
return x.id
});
var publicationdate = book.map(function(x) {
return x.publicationdate
});
var author = book.map(function(x) {
return x.author
});
var authorbio = book.map(function(x) {
return x.authorbio
});
var picture = book.map(function(x) {
return x.picture
});
var cost = book.map(function(x) {
return x.cost
});
var introduction = book.map(function(x) {
return x.introduction
});
var price = book.map(function(x) {
return “Price : $” +x.cost
});
function addToCart() {
Context.addToCart(id.value, cost.value, title.value, picture.value, author.value);
router.goBack()
}
module.exports = {
title,
id,
introduction,
picture,
cost,
authorbio,
publicationdate,
author,
addToCart,
price
}
Detail.ux
Now we create our cart page along with the cart business and data logic. First the Cart.js file in the Pages folder for the cart page business logic
var Context = require(“Modules/Context”);
function remove(args) {
var item = args.data
Context.removeFromCart(item)
}
function goHome() {
router.goto(“home”)
}
var todaysDate = “Purchases made on “ + new Date().toDateString() + “ at “ + new Date().toLocaleTimeString();
var qty = Context.cart.count() // this will return the number of items in cart as an observable
module.exports = {
goHome: goHome,
noItems: Context.isCartEmpty,
cart: Context.cart,
remove: remove,
todaysDate: todaysDate,
totalAmount: Context.totalAmount,
qty: qty
}
Cart.ux
To build our app on our android device for testing; make sure you have android adb usb driver installed. Then connect your device to the pc and use the command:
Fuse build bookstore-tutorial.unoporj -tandroid -DGRADLE -r
Please checkout the tutorial app on github.com/egaleme/bookstore-tutorial
Talk to Me And that’s it, a scalable and performant e-commerce app that you can easily add to and enhance for your needs. If you have any questions or comments then please let me know.