Skip to content
Browse files

wrote the shopppingcart example

git-svn-id: https://erlyaws.svn.sourceforge.net/svnroot/erlyaws/trunk/yaws@191 9fbdc01b-0d2c-0410-bfb7-fb27d70d8b52
  • Loading branch information...
1 parent 951bf0e commit 87b14560d250973ef27ecfc6b50522ade28f22cf @klacke committed Oct 6, 2002
View
13 include/yaws_api.hrl
@@ -50,16 +50,3 @@
-
-%% portal server has a queue of these
--record(ysession,
- {cookie, %% the cookie assigned to the session
- auth, %% the #auth structure as returned from portal_auth
- to, %% greg secs untill timeout death
- user, %% The user name
- passwd, %% And the passwd of that user
- starttime, %% When calendar:local_time() did sess start
- opaque %% any data the user supplies
- }).
-
-
View
14 man/yaws_api.5
@@ -249,18 +249,24 @@ Example usage could be:
.TP
-\fBnew_cookie_session(User, Passwd, Opaque)\fR
+\fBnew_cookie_session(Opaque)\fR
Create a new cookie based session, the yaws system will set the
-cookie.
+cookie. The new randomgenerated cookie is returned from this
+function. The Opaque argument will typically contain user data
+such as username and password
.TP
-\fBcookieval_to_session(CookieVal)\fR
+\fBcookieval_to_opaque(CookieVal)\fR
.TP
\fBprint_cookie_sessions() \fR
+
+.TP
+\fBreplace_cookie_session(Cookie, NewOpaque)\fR
+
.TP
-\fBreplace_cookie_session(Session, User)\fR
+\fBdelete_cookie_session(Cookie)\fR
.TP
View
23 src/yaws_api.erl
@@ -19,10 +19,10 @@
-export([find_cookie_val/2, secs/0, url_decode/1]).
-export([get_line/1, mime_type/1]).
-export([stream_chunk_deliver/2, stream_chunk_end/1]).
--export([new_cookie_session/3,
- cookieval_to_session/1,
+-export([new_cookie_session/1,
+ cookieval_to_opaque/1,
print_cookie_sessions/0,
- replace_cookie_session/2]).
+ replace_cookie_session/2, delete_cookie_session/1]).
-export([setconf/2]).
%% these are a bunch of function that are useful inside
@@ -617,21 +617,22 @@ stream_chunk_end(YawsPid) ->
-
-new_cookie_session(User, Passwd, Opaque) ->
- yaws_session_server:new_session(User, Passwd, Opaque).
+%% Return new cookie string
+new_cookie_session(Opaque) ->
+ yaws_session_server:new_session(Opaque).
%% as returned in #ysession.cookie
-cookieval_to_session(CookieVal) ->
- yaws_session_server:cookieval_to_session(CookieVal).
+cookieval_to_opaque(CookieVal) ->
+ yaws_session_server:cookieval_to_opaque(CookieVal).
print_cookie_sessions() ->
yaws_session_server:print_sessions().
-replace_cookie_session(Session, User) ->
- yaws_session_server:replace_session(Session, User).
-
+replace_cookie_session(Cookie, NewOpaque) ->
+ yaws_session_server:replace_session(Cookie, NewOpaque).
+delete_cookie_session(Cookie) ->
+ yaws_session_server:delete_session(Cookie).
%% to be used in embedded mode, make it possible
View
250 src/yaws_session_server.erl
@@ -21,10 +21,16 @@
-include("../include/yaws_api.hrl").
--record(state,
- {ss, %% time queue of sessions
- ttl = (1000 * 60 * 30) %% 30 minutes
+-define(TTL, (30 * 60)). % 30 minutes
+
+-record(ysession,
+ {cookie, %% the cookie assigned to the session
+ to, %% greg secs untill timeout death
+ starttime, %% When calendar:local_time() did sess start
+ opaque %% any data the user supplies
}).
+
+
@@ -42,16 +48,26 @@ stop() ->
gen_server:call(?MODULE, stop).
-new_session(User, Passwd, Opaque) ->
- gen_server:call(?MODULE, {new_session, User, Passwd, Opaque}).
-cookieval_to_session(C) ->
- gen_server:call(?MODULE, {cookieval_to_session, C}).
+%% will return a new cookie as a string
+new_session(Opaque) ->
+ gen_server:call(?MODULE, {new_session, Opaque}).
+
+cookieval_to_opaque(CookieString) ->
+ case ets:lookup(?MODULE, CookieString) of
+ [Y] ->
+ Y2 = Y#ysession{to = gnow() + ?TTL},
+ ets:insert(?MODULE, Y2),
+ {ok, Y#ysession.opaque};
+ [] ->
+ {error, no_session}
+ end.
+
+
print_sessions() ->
- Ss = qto_list(gen_server:call(?MODULE, sessions)),
- io:format("** ~p users logged in ~n~n", [length(Ss)]),
+ Ss = ets:tab2list(?MODULE),
+ io:format("** ~p sessions active ~n~n", [length(Ss)]),
N = gnow(),
lists:foreach(fun(S) ->
- io:format("User ~p ~n", [S#ysession.user]),
io:format("Cookie ~p ~n", [S#ysession.cookie]),
io:format("Start ~p ~n", [S#ysession.starttime]),
io:format("TTL ~p secs~n", [S#ysession.to - N]),
@@ -60,9 +76,19 @@ print_sessions() ->
end, Ss).
-replace_session(Session, User) ->
- gen_server:call(?MODULE, {replace_session, Session, User}).
+replace_session(Cookie, NewOpaque) ->
+ case ets:lookup(?MODULE, Cookie) of
+ [Y] ->
+ Y2 = Y#ysession{to = gnow() + ?TTL},
+ ets:insert(?MODULE, Y2),
+ ets:insert(?MODULE, Y#ysession{opaque = NewOpaque});
+ [] ->
+ error
+ end.
+
+delete_session(CookieVal) ->
+ ets:delete(?MODULE, CookieVal).
%%%----------------------------------------------------------------------
@@ -79,14 +105,18 @@ replace_session(Session, User) ->
init([]) ->
{X,Y,Z} = seed(),
random:seed(X, Y, Z),
- {ok, #state{ss = qnew()}}.
+ ets:new(?MODULE, [set, named_table, public, {keypos, 2}]),
+ {ok, undefined, to()}.
+to() ->
+ 2 * 60 * 1000.
+
%% pretty good seed, but non portable
seed() ->
case (catch list_to_binary(
- os:cmd("dd if=/dev/random ibs=12 count=1 2>/dev/null"))) of
+ os:cmd("dd if=/dev/urandom ibs=12 count=1 2>/dev/null"))) of
<<X:32, Y:32, Z:32>> ->
{X, Y, Z};
_ ->
@@ -107,87 +137,25 @@ seed() ->
-handle_call({new_session, User, Passwd, Opaque}, _From, State) ->
- Ss = State#state.ss,
- Now = gnow(),
- case qkeysearch(User, #ysession.user, Ss) of
- {value, S} ->
- tret({error, {has_session, S}}, Now, State);
- false ->
- N = random:uniform(16#ffffffffffffffff), %% 64 bits
- TS = calendar:local_time(),
- C = atom_to_list(node()) ++ [$-|integer_to_list(N)],
- NS = #ysession{cookie = C,
- user = User,
- passwd = Passwd,
- starttime = TS,
- opaque = Opaque,
- to = gnow() + State#state.ttl},
- tret({ok, NS}, Now, State#state{ss = qin(NS, Ss)})
- end;
-
-
-handle_call({cookieval_to_session, C}, _From, State) ->
- Q = State#state.ss,
+handle_call({new_session, Opaque}, _From, State) ->
Now = gnow(),
- case qkey_delete(C, #ysession.cookie, Q) of
- {value, Session, Q2} ->
- Q3 = qin(Session#ysession{to = Now + State#state.ttl}, Q2),
- tret({ok, Session}, Now, State#state{ss = Q3});
- false ->
- tret({error,no_session}, Now, State)
- end;
-
-handle_call({replace_session, S, User}, From, State) ->
- Q = State#state.ss,
- Now = gnow(),
- case qkey_delete(User, #ysession.user, Q) of
- {value, Session, Q2} ->
- Q3 = qin(S#ysession{to = Now + State#state.ttl}, Q2),
- tret(ok, Now, State#state{ss = Q3});
- false ->
- tret({error,no_session}, Now, State)
- end;
-
+ N = random:uniform(16#ffffffffffffffff), %% 64 bits
+ TS = calendar:local_time(),
+ C = atom_to_list(node()) ++ [$-|integer_to_list(N)],
+ NS = #ysession{cookie = C,
+ starttime = TS,
+ opaque = Opaque,
+ to = gnow() + ?TTL},
+ ets:insert(?MODULE, NS),
+ {reply, C, undefined, to()};
-handle_call(sessions, _From, State) ->
- tret(State#state.ss, gnow(), State);
handle_call(stop, _From, State) ->
{stop, stopped, State}.
-%% a timeout return value based on the oldest session in
-%% the queue
-
-tret(Val, Now, State) ->
- {S2, TO} = state_to_timeout(State, Now),
- {reply, Val, S2, TO}.
-
-
-
-state_to_timeout(State, Now) ->
- Q = State#state.ss,
- case qfirst(Q) of
- {{value, OldestSession}, Q2} ->
- TO = OldestSession#ysession.to,
- if
- TO > Now ->
- {State#state{ss = Q2}, (TO - Now) * 1000};
- true ->
- %% oldest item has timed out
- %% we need to remove it
- {{value, O}, Q3} = qout(Q2),
- report_timedout_sess(O),
- state_to_timeout(State#state{ss = Q3}, Now)
- end;
- {empty, _} ->
- {State, infinity}
- end.
-
-
report_timedout_sess(S) ->
- error_logger:info_msg("Session for user ~p timedout ", [S#ysession.user]).
+ error_logger:info_msg("Session timedout: ~p ", [S#ysession.opaque]).
%%----------------------------------------------------------------------
@@ -206,15 +174,8 @@ handle_cast(_Msg, State) ->
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_info(timeout, State) ->
- Q = State#state.ss,
- case qout(Q) of
- {{value, S}, Q2} ->
- report_timedout_sess(S),
- {S2, TO} = state_to_timeout(State#state{ss = Q2}, gnow()),
- {noreply, S2, TO};
- {empty, _} ->
- {noreply, State}
- end.
+ trav_ets(),
+ {noreply, undefined, to()}.
%%----------------------------------------------------------------------
@@ -229,85 +190,30 @@ terminate(_Reason, _State) ->
%%% Internal functions
%%%----------------------------------------------------------------------
+trav_ets() ->
+ N = gnow(),
+ trav_ets(N, ets:first(?MODULE)).
+trav_ets(N, '$end_of_table') ->
+ ok;
+trav_ets(N, Key) ->
+ case ets:lookup(?MODULE, Key) of
+ [Y] ->
+ if
+ Y#ysession.to > N ->
+ trav_ets(N, ets:next(?MODULE, Key));
+ true ->
+ report_timedout_sess(Y),
+ Next = ets:next(?MODULE, Key),
+ ets:delete(?MODULE, Key),
+ trav_ets(N, Next)
+
+ end;
+ [] ->
+ trav_ets(N, ets:next(?MODULE, Key))
+ end.
gnow() ->
calendar:datetime_to_gregorian_seconds(
calendar:local_time()).
-
-
-
-%% queue ops
-
-qnew() -> {[], []}.
-
-qin(X, {In, Out}) -> {[X|In], Out}.
-
-
-qout({In, [H|Out]}) ->
- {{value, H}, {In, Out}};
-qout({[], []}) ->
- {empty, {[],[]}};
-qout({In, _}) ->
- qout({[], lists:reverse(In)}).
-
-
-
-qfirst({In, [H|Out]}) ->
- {{value, H}, {In, [H|Out]}};
-qfirst({[], []}) ->
- {empty, {[],[]}};
-qfirst({In, _}) ->
- qfirst({[], lists:reverse(In)}).
-
-
-
-
-qto_list({In, Out}) ->
- lists:append(Out, lists:reverse(In)).
-
-
-qkey_delete(Key, Pos, {Head, Tail}) ->
- case qkey_search_delete(Key, Pos, Head, []) of
- false ->
- case qkey_search_delete(Key,Pos, Tail, []) of
- false ->
- false;
- {value, Val, L2} ->
- {value, Val, {Head, L2}}
- end;
- {value, Val, L3} ->
- {value, Val, {L3, Tail}}
- end.
-
-
-
-qkeysearch(Key, Pos, {Head, Tail}) ->
- case lists:keysearch(Key, Pos, Head) of
- {value, Val} ->
- {value, Val};
- false ->
- lists:keysearch(Key,Pos, Tail)
- end.
-
-
-
-qkeyreplace(Key, Pos, Item, {Head, Tail}) ->
- {lists:keyreplace(Key,Pos,Head,Item),
- lists:keyreplace(Key,Pos,Tail,Item)}.
-
-
-
-%% internal functions
-
-qkey_search_delete(_Key, _Pos, [], _Ack) ->
- false;
-qkey_search_delete(Key,Pos, [H|T], Ack) ->
- if
- element(Pos, H) == Key ->
- {value, H, lists:reverse(Ack) ++ T};
- true ->
- qkey_search_delete(Key,Pos, T, [H|Ack])
- end.
-
View
2 www/EXHEAD
@@ -10,7 +10,7 @@
<td> <A HREF="redirect.yaws">Redirect</a> </td>
<td> <A HREF="cookies.yaws">Cookies</a> </td>
<td> <A HREF="pcookie.yaws">Persistant Cookies</a> </td>
- <td> <A HREF="shopingcart/cart.yaws">Tiny shoping cart</a> </td>
+ <td> <A HREF="shopingcart/index.yaws">Tiny shoping cart</a> </td>
</tr>
</table>
<hr>
View
11 www/shopingcart/buy.yaws
@@ -0,0 +1,11 @@
+<erl>
+
+out(A) ->
+ case shopcart:top(A) of
+ ok ->
+ shopcart:buy(A);
+ X ->
+ X
+ end.
+
+</erl>
View
70 www/shopingcart/cart.yaws
@@ -1,70 +0,0 @@
-<erl>
-
-ensure_cart_server() ->
- case whereis(cart_server) of
- undefined ->
- proc_lib:start(?MODULE, pserv, []);
- _ ->
- ok
- end.
-
-pserv() ->
- catch begin
- register(cart_server, self()),
- T = ets:new(carts, [set, public, named_table]),
- ets:insert(T, {counter, 0})
- end,
- proc_lib:init_ack(ok),
- pserv_loop().
-
-pserv_loop() ->
- receive
- X ->
- pserv_loop()
- end.
-
-setcookie(A) ->
- Num = ets:update_counter(carts, counter, 1),
- Data = [],
- ets:insert(carts, {{c, Num}, Data}),
- yaws_api:setcookie("cart", integer_to_list(Num), "/").
-
-
-out(A) ->
- ensure_cart_server(),
- H = A#arg.headers,
- C = H#headers.cookie,
- case yaws_api:find_cookie_val("cart", C) of
- [] ->
- setcookie(A);
- NumStr ->
- Num = list_to_integer(NumStr),
- %% cookie allready set
- case ets:lookup(carts, {c, Num}) of
- [{{c, Num}, Data}] ->
- ok; %% cookie already set
- [] ->
- setcookie(A)
- end
- end.
-
-</erl>
-
-
-<html>
-
-<head>
-<title Cart </title>
-</head>
-
-<IMG SRC="/shopingcart/junk.jpg" WIDTH="400" ALT="cart">
-
-<h1> Shopcart example </h1>
-
-<p>This is a tiny (not yet working !!!) shop cart persitance example. It is possible
-to select random stuff from a series of forms. The yaws server will
-continously remember what has been selected. Try the cart
-at: <a href="mall.yaws">The Shopping Mall :-)</a>
-
-</html>
-
View
11 www/shopingcart/index.yaws
@@ -0,0 +1,11 @@
+<erl>
+
+out(A) ->
+ case shopcart:top(A) of
+ ok ->
+ shopcart:index(A);
+ X ->
+ X
+ end.
+
+</erl>
View
7 www/shopingcart/loginpost.yaws
@@ -0,0 +1,7 @@
+
+<erl>
+
+out(A) ->
+ shopcart:loginpost(A).
+
+</erl>
View
12 www/shopingcart/logout.yaws
@@ -0,0 +1,12 @@
+<erl>
+
+
+out(A) ->
+ case shopcart:top(A) of
+ ok ->
+ shopcart:logout(A);
+ X ->
+ X
+ end.
+
+</erl>
View
20 www/shopingcart/mall.yaws
@@ -1,20 +0,0 @@
-<IMG SRC="/shopingcart/junk.jpg" WIDTH="400" ALT="cart">
-
-<h1> The Mall </h1>
-
-<erl>
-
-
-
-out(A) ->
- H = A#arg.headers,
- C = H#headers.cookie,
- case yaws_api:find_cookie_val("cart", C) of
- [] ->
- f("<p>Gotta go to the top cart page to get your cookie set ");
- NumStr ->
- Num = list_to_integer(NumStr),
- %% cookie allready set
- case ets:lookup(carts, {c, Num}) of
- [{{c, Num}, Data}] ->
-
View
439 www/shopingcart/shopcart.erl
@@ -0,0 +1,439 @@
+
+%% a small shoppingcart example which tries to show
+%% a variety of tricks and tacticts to display a
+%% shopingcart style page with server side state.
+
+
+-module(shopcart).
+-author('klacke@hyber.org').
+
+-compile(export_all).
+-include("../../include/yaws_api.hrl").
+-include_lib("kernel/include/inet.hrl").
+
+
+%% this is the opaque structure we pass to the
+%% yaws cookie session server
+
+-record(sess, {
+ user,
+ passwd,
+ addr,
+ items = []}).
+
+
+%% this function extracts the session from the cookie
+check_cookie(A) ->
+ H = A#arg.headers,
+ case yaws_api:find_cookie_val("ssid", H#headers.cookie) of
+ Val when Val /= [] ->
+ case yaws_api:cookieval_to_opaque(Val) of
+ {ok, Sess} ->
+ {ok, Sess, Val};
+ {error, {has_session, Sess}} ->
+ {ok, Sess};
+ Else ->
+ Else
+ end;
+ [] ->
+ {error, nocookie}
+ end.
+
+
+%% this function is calle first in all out yaws files,
+%% it will autologin users that are not logged in
+top(A) ->
+ case check_cookie(A) of
+ {ok, _Session, _Cookie} ->
+ ok;
+ {error, Reason} ->
+ login(A)
+ end.
+
+
+
+%% generate a css head the title of the page set dynamically
+css_head(PageTitle) ->
+ Z =
+ [<<"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
+<html>
+
+<head>
+ <meta name=\"keywords\" content=\"Nortel Extranet VPN\">
+ <title>">>,
+ PageTitle,
+ <<"</title>
+ <meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">
+ <link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">
+</head>
+
+<body bgcolor=\"linen\">
+
+">>
+ ],
+ {html, Z}.
+
+
+
+%% the little status field in the upper left corner
+head_status(User) ->
+ T =
+ {ehtml,
+ {table, [],
+ {tr, [],
+ [{td, [{width, "30%"}],
+ {table, [ {border, "1"}, {bgcolor, beige},{bordercolor, black}],
+ [{tr, [], {td, [], pb("User: ~s", [User])}}
+ ]}
+ },
+ {td, [{align, right}], {img, [{src, "junk.jpg"}
+ ]}}
+ ]
+ }
+ }
+ }.
+
+
+%% bold paragraph according to style.css
+pb(Fmt, Args) ->
+ {p, [{class, pb}], io_lib:format(Fmt, Args)}.
+
+
+%% toprow of buttons to push
+toprow() ->
+ {ehtml,
+ {table, [{cellspacing, "4"},
+ {bgcolor, tan},
+ {width, "100%"}
+ ],
+ [
+ {tr, [],
+ [{td, [], {a, [{href, "buy.yaws"}] , {p, [{class, toprow}], "Buy"}}},
+ {td, [], {a, [{href, "logout.yaws"}], {p, [{class, toprow}], "Logout"}}},
+ {td, [{width, "70%"}], ""}
+ ]}
+ ]
+ }
+ }.
+
+
+
+%% kinda hackish since we us ehtml
+bot() ->
+ {html, "</body> \n </html> \n"}.
+
+
+
+%% This function displays the login page
+login(A) ->
+ CSS = css_head("Shopcart"),
+ Head = head_status("Not lgged in"),
+ Top = toprow(),
+ Login =
+ {ehtml,
+ [{h2, [], "Shopcart login"},
+ {form, [{method, get},
+ {action, "loginpost.yaws"}],
+ [
+ {p, [], "Username"},
+ {input, [{name, user},
+ {type, text},
+ {value, "Joe Junk shopper"},
+ {size, "48"}]},
+
+
+ {p, [], "Password"},
+ {input, [{name, password},
+ {type, text},
+ {value, "xyz123"},
+ {size, "48"}]},
+
+ {input, [{type, submit},
+ {value, "Login"}]},
+
+ {input, [{name, url},
+ {type, hidden},
+ {value, xpath((A#arg.req)#http_request.path, A)}]}
+ ]
+ }
+ ]},
+ [CSS, Head, Top, Login, bot()].
+
+
+
+
+logout(A) ->
+ {ok, Sess, Cookie} = check_cookie(A),
+ yaws_api:delete_cookie_session(Cookie),
+ {ehtml, {h3, [], "Yo, "}}.
+
+
+
+
+%% This is the function that gets invoked when the
+%% user has attempted to login
+%% The trick used here is to pass the original URL in a hidden
+%% field into this function, if the login is successful, we redirect
+%% to the original URL.
+
+loginpost(A) ->
+ L = yaws_api:parse_query(A),
+ case {lists:keysearch(user, 1, L),
+ lists:keysearch(url, 1, L),
+ lists:keysearch(password, 1, L)} of
+ {{value, {_, User}},
+ {value, {_, Url}},
+ {value, {_, Pwd}}} ->
+
+ %% here's the place to validate the user
+ %% we allow all users,
+ io:format("User ~p logged in ~n", [User]),
+ Sess = #sess{user = User,
+ passwd = Pwd},
+ Cookie = yaws_api:new_cookie_session(Sess),
+ [yaws_api:redirect(Url),
+ yaws_api:setcookie("ssid",Cookie)];
+ _ ->
+ login(A)
+ end.
+
+xpath({abs_path, P}, _A) ->
+ P.
+
+%% this is the function that gets the form when the user
+%% hits "update Cart"
+
+formupdate(A) ->
+ {ok, Sess, Cookie} = check_cookie(A),
+ J = junk(),
+ Items = Sess#sess.items,
+ L0 = yaws_api:parse_post_data(A),
+
+ %% One of the oddities of parse_post_data is that
+ %% it will make the first elem an atom, we don't want that
+ %% it is nice and convenient most of the time though
+ L = lists:map(fun({Name, Val}) -> {atom_to_list(Name), Val} end, L0),
+ io:format("L = ~p~n", [L]),
+ I2 = handle_l(L, Items),
+ Sess2 = Sess#sess{items = I2},
+ yaws_api:replace_cookie_session(Cookie, Sess2),
+ {redirect, "index.yaws"}. %% force browser to reload
+
+handle_l([], Items) ->
+ Items;
+handle_l([{Str, Num} | Tail], Items) ->
+ case catch list_to_integer(Num) of
+ Int ->
+ handle_l(Tail, [{Str, Int} | lists:keydelete(Str,1, Items)]);
+ _ ->
+ handle_l(Tail, Items)
+ end.
+
+
+ip(A) ->
+ S = A#arg.clisock,
+ case inet:peername(S) of
+ {ok, {Ip, Port}} ->
+ case inet:gethostbyaddr(Ip) of
+ {ok, HE} ->
+ io_lib:format("~s/~s", [fmtip(Ip), HE#hostent.h_name]);
+ Err ->
+ io_lib:format("~s", [fmtip(Ip)])
+ end;
+ _ ->
+ []
+ end.
+
+fmtip({A,B,C,D}) ->
+ io_lib:format("~w.~w.~w.~w", [A,B,C,D]).
+
+
+%% generate the final "you have bought page ... "
+buy(A) ->
+ {ok, Sess, Cookie} = check_cookie(A),
+ Css = css_head("Shopcart"),
+ Head = head_status(Sess#sess.user),
+ Top = toprow(),
+ BROWS = b_rows(Sess#sess.items),
+ Res =
+ if
+ length (BROWS) > 0 ->
+ {ehtml,
+ [{h4, [], "Congratulations, you have bought"},
+ {table, [],BROWS},
+ {hr},
+ {p , [{class, toprow}],
+ io_lib:format(
+ "Items are at this very moment being shipped to the"
+ " residens of the computer with IP: ~s~n", [ip(A)])}
+ ]
+ };
+ true ->
+ {ehtml,
+ [{h4, [], "Congratulations, you have bought nothing"}]}
+ end,
+
+
+ [Css, Head, Top, Res, bot()].
+
+
+b_rows(Items) ->
+ J = junk(),
+ Desc = {tr,[],
+ [
+ {th, [], pb("Description",[])},
+ {th, [], pb("Quantity",[])},
+ {th, [], pb("Sum ",[])}]},
+
+ [Desc | b_rows(Items, J, 0, [])].
+
+b_rows([{Desc, Num}|Tail], Junk, Ack, TRS) when Num > 0 ->
+ {value, {_, Price}} = lists:keysearch(Desc, 1, Junk),
+ A = Num * Price,
+ TR = {tr, [],
+ [{td, [], Desc},
+ {td, [], io_lib:format("~w", [Num])},
+ {td, [], io_lib:format("~w", [A])}
+ ]},
+ b_rows(Tail, Junk, A+Ack, [TR|TRS]);
+
+b_rows([{Desc, Num}|Tail], Junk, Ack, TRS) when Num == 0 ->
+ b_rows(Tail, Junk, Ack, TRS);
+
+b_rows([], _, Ack, TRS) when Ack > 0 ->
+ Tax = round(0.27 * Ack),
+ Empty = {td, [], []},
+ TaxRow = {tr, [],
+ [
+ {td, [], pb("Swedish VAT tax 27% ",[])},
+ Empty,
+ {td, [], pb("~w", [Tax])}
+ ]},
+ Total = {tr, [],
+ [
+ {td, [], pb("Total ",[])},
+ Empty,
+ {td, [], pb("~w", [Ack + Tax])}
+ ]},
+
+ lists:reverse([Total, TaxRow | TRS]);
+b_rows(_, _,_,_) ->
+ [].
+
+
+
+
+%% this is the main function which displays
+%% the shopcart .....
+%% the entire shopcart is one big form which gets posted
+%% when the user updates the shopcart
+index(A) ->
+ {ok, Sess, Cookie} = check_cookie(A),
+ io:format("Inside index: ~p~n", [Sess#sess.items]),
+ Css = css_head("Shopcart"),
+ Head = head_status(Sess#sess.user),
+ Top = toprow(),
+ Cart =
+ {ehtml,
+ {form,
+ [{name, form},
+ {method,post},
+ {action, "shopcart_form.yaws"}],
+ [
+ {table, [{bgcolor, linen}, {border, "2"}],
+ rows(Sess#sess.items)},
+
+ {input, [{type, submit}, {value, "Update Cart"}]}
+ ]
+ }
+ },
+
+ [Css, Head, Top, Cart, bot()].
+
+
+%% this function gets a list of
+%% {JunkString, Num} and displays the current shopcart
+
+rows(Items) ->
+ Junk = junk(),
+ First = {tr, [],
+ [{th, [], pb("Num Items", [])},
+ {th, [], pb("Item description", [])},
+ {th, [], pb("Price SEK ",[])}
+ ]},
+
+ L = lists:map(
+ fun({Desc, Price}) ->
+ {tr, [],
+ [{td, [],
+ {input, [{type, text},
+ {width, "4"},
+ {value, jval(Desc, Items)},
+ {name, Desc}]}},
+ {td, [], {p, [], Desc}},
+ {td, [], pb("~w ", [Price])}
+ ]}
+ end, Junk),
+
+ Total = total(Items, Junk, 0),
+ Tax = round(0.27 * Total),
+ T = [{tr, [],
+ [{td, [], pb("Sum accumulated",[])},
+ {td, [{colspan, "2"}], pb("~w SEK", [Total])}
+ ]
+ },
+ {tr, [],
+ [
+ {td, [], pb("Swedish VAT tax 27 % :-)",[])},
+ {td, [{colspan, "2"}], pb("~w SEK", [Tax])}
+ ]
+ },
+
+ {tr, [],
+ [
+ {td, [], pb("Total",[])},
+ {td, [{colspan, "2"}], pb("~w SEK", [Total + Tax])}
+ ]
+ }
+ ],
+
+ Rows = [First | L] ++ T.
+
+
+
+
+
+%% The Items are picked up by the
+%% formupdate function and set accordingly in the opaque state
+%% this function recalculates the sum total
+
+total([{Str, Num} | Tail], Junk, Ack) ->
+ {value, {Str, Price}} = lists:keysearch(Str, 1, Junk),
+ total(Tail, Junk, (Num * Price) + Ack);
+total([], _,Ack) ->
+ Ack.
+
+
+%% We need to set the value in each input field
+jval(Str, Items) ->
+ case lists:keysearch(Str, 1, Items) of
+ {value, {_, Num}} when integer(Num) ->
+ integer_to_list(Num);
+ false ->
+ "0"
+ end.
+
+
+%% the store database :-)
+%% {Description, Price} tuples
+junk() ->
+ [{"Toothbrush in rainbow colours", 18},
+ {"Twinset of extra soft towels", 66},
+ {"Hangover pill - guaranteed to work", 88},
+ {"Worlk-out kit that fits under your bed", 1900},
+ {"100 pack of headache pills", 7},
+ {"Free subscription to MS update packs", 999},
+ {"Toilet cleaner", 1111},
+ {"Body lotion 4 litres", 888},
+ {"Yello, a lifetime supply", 99}].
+
View
11 www/shopingcart/shopcart_form.yaws
@@ -0,0 +1,11 @@
+<erl>
+
+out(A) ->
+ case shopcart:top(A) of
+ ok ->
+ shopcart:formupdate(A);
+ X ->
+ X
+ end.
+
+</erl>
View
50 www/shopingcart/style.css
@@ -0,0 +1,50 @@
+<style TYPE="text/css">
+
+ body {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ color: black;
+ }
+
+ div.links {
+ background: green;
+ }
+
+
+ H1, H2, H4, H5 {
+ text-decoration: none;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ font-variant: small-caps
+ }
+
+
+ P { text-decoration: none;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-weight: normal;
+ font-size: small;
+ }
+
+ .toprow {
+ text-decoration: none;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ }
+
+ .pb {
+ text-decoration: none;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ font-size: small;
+ }
+
+
+ A { text-decoration: none;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-weight: normal;
+ color: green;
+ }
+ A:link { color: #0000f0 } /* unvisited link */
+ A:active { color: lime } /* active links */
+ A:hover { background: brown }
+
+</style>

0 comments on commit 87b1456

Please sign in to comment.
Something went wrong with that request. Please try again.