Click here to Skip to main content
Click here to Skip to main content

A Prototype of Mobile Client for E-commerce Application

, 30 Jul 2011 BSD
Rate this:
Please Sign up or sign in to vote.
e-Commerce client application running on Android, Symbian, Samsung bada and Windows Mobile

Editorial Note

This article appears in the Third Party Products and Tools section. Articles in this section are for the members only and must not be used to promote or advertise products in any way, shape or form. Please report any spam or advertising.

e-commerce mobile - loading product list e-commerce mobile - homeView e-commerce mobile - productView

Introduction

Creating native applications for every available platform on the market can be very expensive. Therefore, many developers or small businesses choose and maintain only one platform for which they develop their applications. However, there are more effective ways of cross-platform development and they are discussed in this article.

Let’s pretend that we need to program a mobile client for e-commerce shop with clothing for instance. Our priority is to run this application on numerous smartphones and tablets and coding it in a native way. In addition, we require to keep our client brand consistent for every platform. Later on, the application will be optimized for product/clothing download and saved locally to database in order to keep this application functional even in off-line mode. However, our intension is to save time and money on development, and prevent spending additional resources on maintenance and porting of client’s future development.

So let’s create a prototype of client for e-commerce application. In this case, the client will connect through Internet to the e-commerce shop, download a list of products and enable users its viewing.

Background

To overcome platform differences, we will use Moscrif SDK (available free for open source and non-commercial projects). It allows us to create native client app using web technologies (JavaScript and json) and run it on Android (1.6 and newer), Symbian (S^1 or newer), Samsung bada and Windows Mobile 5+ (iOS is coming soon).

Server Side

In this article, we will simulate real online store by simple ASP.NET files. The getProducts.aspx will return a list of 100 products (product name, price, out-of-stock status, etc.). The list will be simple JSON.

getProducts.aspx

private Random rnd = new Random();
void Page_Load()
{
    Response.Clear();
    StringBuilder sb = new StringBuilder("[");
    for (int i = 1; i <= 94; i++)
    {
        sb.Append("{");
        sb.AppendFormat("id: {0}, ", i);
        sb.AppendFormat("name: \"{0}\", ", String.Format("Product{0}", i));
        sb.AppendFormat("description: \"{0}\", ", Ipsum.GetWords(13 + rnd.Next(10)));
        sb.AppendFormat("price: {0:0.00}, ",  
	((90 + rnd.Next(750)) / 10.0) + (rnd.Next(100) > 50 ? 0.05 : 0.00) );
        sb.AppendFormat("oos: {0}, ", rnd.Next(100) > 50 ? 1 : 0);
        sb.AppendLine("},");
    }
    sb.Append("]");
    Response.Write(sb.ToString());
}

The getProductImage.aspx will return product thumbnail image.

getProductImage.aspx

void Page_Load()
{
    try
    {
        Response.Clear();
        string productId = Request["id"];
        string path = Server.MapPath(Path.GetDirectoryName(Request.Path)) + 
			"\\" + Request["id"] + ".jpg";
        Bitmap bmp = CreateThumbnail(path, Int32.Parse(Request["w"]), 
			Int32.Parse(Request["h"]));
        bmp.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
        bmp.Dispose();
    }
    catch (Exception ex)
    {
        Response.StatusDescription = ex.Message;
    }
}

Client Side

Mobile Client will be programmed in (extended) JavaScript and Moscrif SDK will ensure compatibility with each mobile platform. Installation of Moscrif SDK is simple and requires .NET, or Mono and Gtk# environment. You can also watch the installation here. Practically, it means that JavaScript codes will be interpreted into virtual machine of any mobile device. Furthermore, Moscrif IDE will be responsible for creating installation package (apk, sisx, cab and zip). Included file eShop.zip is possible to load in IDE through menu Project->Import Project.

E-Commerce mobile client will not be using any classes from the Moscrif framework; consequently, client has to be created from scratch. Basic screen navigation is covered by two methods, which is the push method (for various screens / views); and the pop method (for switching from current screen view to previous view):

// helper array for application views (forms)
// this._views = new Array();

app.push = function(view)
{
	assert view != null : "View is required";
	this._views.push(view);
	this.add(view.native || view, #front);
}

app.pop = function()
{
	// pop last one -> quit application
	if (this._views.length == 0) {
		this._quit = true;
		return null;
	}
	var view = this._views.pop();
	view.native.detach();
	if (this._views.length == 0) {
		this._quit = true;
		return null;
	}
	return view;
}

Applying the mobile client to different resolutions is done by vector graphics (SVG). Also, every other part of UI will be calculated based on resolution of the device. For instance, height of the lower bar will be pro-rated to 1/10 from a display height:

// scaleFactor = Integer.max(System.width, System.height) / 240;

app.buildToolBar = function(buttons, paint = null)
{
	const icons = {
		#quit: Path.fromSVG("M 119.96875 -0.375 C 110.00577
		-0.375 101.875 7.2547812 101.875 16.5625 L 101.875 145.75
		C 101.875 155.0408 110.00577 162.65625 119.96875 162.65625
		L 136.03125 162.65625 C 145.97615 162.65625 154.125 155.0408
		154.125 145.75 L 154.125 16.5625 C 154.125 7.2547812 145.97615
		-0.375 136.03125 -0.375 L 119.96875 -0.375 z M 75.875 26.5
		C 31.177216 45.20005 -4.6259293e-018 87.05178 0 135.875
		C 0 202.06134 57.282755 255.625 128 255.625 C 198.64492 255.625
		255.94575 202.04442 256 135.875 L 255.9375 135.875 C 255.9375
		87.0687 224.77346 45.2313 180.09375 26.53125 L 180.09375 70.40625
		C 201.5024 85.33244 215.50825 108.95031 215.5625 135.875
		C 215.39976 181.11051 176.35028 217.77605 128 217.84375
		C 79.631647 217.77575 40.509826 181.11051 40.4375 135.875
		C 40.4375 108.96723 54.484434 85.34937 75.875 70.40625 L 75.875 26.5 z"),
		#back: Path.fromSVG("M 263.3125 540.1875 C 259.0635 540.1875
		255.625 543.626 255.625 547.875 C 255.625 552.124 259.0635 555.5625
		263.3125 555.5625 C 267.5615 555.5625 271 552.124 271 547.875
		C 271 543.626 267.5615 540.1875 263.3125 540.1875 z
		M 263.3125 541.3125 C 266.9335 541.3125 269.875 544.254 269.875
		547.875 C 269.875 551.497 266.9345 554.4375 263.3125 554.4375
		C 259.6905 554.4375 256.75 551.498 256.75 547.875 C 256.75 544.254
		259.6915 541.3125 263.3125 541.3125 z M 262.96875 544.15625
		C 262.80187 544.15625 262.628 544.21575 262.5 544.34375 L 259.4375
		547.40625 C 259.4295 547.41225 259.4425 547.4305 259.4375 547.4375
		C 259.4225 547.4505 259.386 547.45375 259.375 547.46875
		C 259.368 547.47875 259.382 547.487 259.375 547.5 C 259.365
		547.513 259.35175 547.5465 259.34375 547.5625 C 259.33675 547.5765
		259.3185 547.58175 259.3125 547.59375 C 259.3075 547.60575 259.3165
		547.616 259.3125 547.625 C 259.3095 547.633 259.28325 547.65125
		259.28125 547.65625 C 259.27425 547.66925 259.28525 547.6735
		259.28125 547.6875 C 259.27525 547.7055 259.255 547.70075 259.25
		547.71875 C 259.246 547.73175 259.252 547.76725 259.25 547.78125
		C 259.248 547.79825 259.25 547.82475 259.25 547.84375 C 259.248
		547.85775 259.25 547.861 259.25 547.875 C 259.249 547.893 259.248
		547.9195 259.25 547.9375 C 259.252 547.9505 259.248 547.95375 259.25
		547.96875 C 259.252 547.98475 259.245 547.984 259.25 548 C 259.254
		548.014 259.27825 548.0485 259.28125 548.0625 C 259.28625 548.0795
		259.27425 548.07775 259.28125 548.09375 C 259.28225 548.09875
		259.27925 548.122 259.28125 548.125 C 259.28425 548.134 259.3085
		548.14825 259.3125 548.15625 C 259.3195 548.17125 259.33575 548.1715
		259.34375 548.1875 C 259.35175 548.2005 259.33475 548.236 259.34375
		548.25 C 259.35175 548.262 259.363 548.27225 259.375 548.28125 
		C 259.385 548.29625 259.4245 548.2985 259.4375 548.3125 C 259.4425 
		548.3175 259.4315 548.33775 259.4375 548.34375 L 262.5 
		551.375 C 262.628 551.503 262.80275
		551.59375 262.96875 551.59375 C 263.13575 551.59375 263.27925 551.503
		263.40625 551.375 C 263.66025 551.121 263.66025 550.72475 263.40625
		550.46875 L 261.46875 548.53125 L 267.53125 548.53125 
		C 267.71225 548.53125
		267.85175 548.46275 267.96875 548.34375 C 268.08675 548.22575 268.1875
		548.055 268.1875 547.875 C 268.1875 547.739 268.1325 547.605 268.0625
		547.5 C 267.9445 547.327 267.75725 547.21875 267.53125 
		547.21875 L 261.46875
		547.21875 L 263.40625 545.28125 C 263.66025 545.02625 263.66025 
		544.59875 263.40625 544.34375 C 263.27875 544.21575 263.13563 
		544.15625 262.96875 544.15625 z"),
		#menu: Path.fromSVG("M 384.125 667.96875 C 383.575 667.96875 383.125
		668.41975 383.125 668.96875 L 383.125 670.96875 C 383.125 671.51875
		383.575 671.96875 384.125 671.96875 L 398.125 671.96875 
		C 398.675 671.96875 399.125 671.51875 399.125 670.96875 
		L 399.125 668.96875 C 399.125 668.41975 398.675 667.96875 398.125 
		667.96875 L 384.125 667.96875 z M 384.125 673.96875
		C 383.575 673.96875 383.125 674.41975 383.125 674.96875 
		L 383.125 676.96875 C 383.125 677.51875 383.575 677.96875 
		384.125 677.96875 L 398.125 677.96875 C 398.675 677.96875 399.125 
		677.51875 399.125 676.96875 L 399.125 674.96875
		C 399.125 674.41975 398.675 673.96875 398.125 673.96875 
		L 384.125 673.96875 z M 384.125 679.96875 C 383.575 679.96875 
		383.125 680.41975 383.125 680.96875
		L 383.125 682.96875 C 383.125 683.51875 383.575 683.96875 
		384.125 683.96875 	L 398.125 683.96875 C 398.675 683.96875 
		399.125 683.51875 399.125 682.96875	L 399.125 680.96875 
		C 399.125 680.41975 398.675 679.96875 398.125 679.96875
		L 384.125 679.96875 z"),
		#refresh: Path.fromSVG("M 551.6875 477.375 C 548.2465 477.375 545.46875
		480.183 545.46875 483.625 L 544.53125 483.625 C 544.09925 
		483.619 543.92925 	483.91425 544.15625 484.28125 L 546 487.25 
		C 546.228 487.616 546.60775 487.612 546.84375 487.25 L 548.75 
		484.3125 C 548.985 483.9515 548.806 483.66225 548.375
		483.65625 L 547.375 483.65625 C 547.37467 483.64609 547.375 
		483.63526 547.375 	483.625 C 547.374 481.243 549.3045 479.3125 
		551.6875 479.3125 C 552.7415 479.3125 553.71775 479.71575 
		554.46875 480.34375 L 555.5625 478.71875 C
		554.5005 477.87975 553.1425 477.375 551.6875 477.375 
		z M 557.03125 479.3125 C 556.87887 479.31075 556.743 479.3815 
		556.625 479.5625 L 554.6875 482.5 C 554.4525 482.863 554.6315 
		483.15125 555.0625 483.15625 L 556.0625 483.1875
		C 556.06282 483.19759 556.0625 483.20857 556.0625 483.21875 C 556.0635
		485.60175 554.133 487.53125 551.75 487.53125 C 550.696 
		487.53125 549.751	487.13 549 486.5 L 547.875 488.09375 
		C 548.937 488.93275 550.295 489.4375 551.75 489.4375 C 555.191 
		489.4375 558 486.66175 558 483.21875 L 558.90625
		483.21875 C 559.33625 483.22475 559.50825 482.9305 559.28125 482.5625
		L 557.4375 479.59375 C 557.324 479.41075 557.18363 
		479.31425 557.03125 479.3125 z");
	};

	if (paint == null) {
		paint = new Paint();
		paint.color = 0xffffffff;
	}

	var bar = new View();
	bar.visible = true;
	bar.stretch = #horz;
	bar.height  = System.height / 10;
	bar.onDraw = :sender, canvas { canvas.clear(0xff041b33); }

	for(var def in buttons) {
		var button = new Button(icons[def.icon], paint);
		button.height = button.width = bar.height - app.scaleFactor*12;
		button.onClick = def.onClick;
		bar.add(button.native, #front);
	}

	var layout = new StackLayout();
	layout.align = #center;
	layout.pack = #center;
	layout.orientation = #horz;
	layout.setMargin(app.scaleFactor*6,
	app.scaleFactor*6, app.scaleFactor*6, app.scaleFactor*6);
	layout.spacer = app.scaleFactor*20;
	bar.layout = layout;

	return bar;
}

The main list will be displayed by “HomeView“ class. It will read the list from the servers and match it up with products’ images which will be performed simultaneously when required:

function _doProductDraw(item, canvas)
{
	var product = item.product;
	canvas.clear(0xeeffffff); // semitransparent
	if (product.image == null) {
		// if image is not loaded yet, start retrieving on first show
		product.image = #loading;
		item.tick = System.tick;
		item.angle = 0;
		function onSuccess(image) {
			product.image = image;
			item.invalidate();
		}
		function onError(message) {
			product.image = #error;
			// something more could be here
			log("ERROR", message);
		}
		Server.retrieveProductImage
		(product, PRODUCT_IMAGE_WIDTH, 184, onSuccess, onError);
	} else if (product.image == #loading) {
		var matrix = new Matrix();
		// rotate around center
		matrix.setRotate(item.angle, app.loading.width / 2, 
		app.loading.height / 2);
		// center to image's areas
		matrix.postTranslate((PRODUCT_IMAGE_WIDTH -
		app.loading.width)/2, (item.height - app.loading.height)/2);
		canvas.drawBitmapMatrix(app.loading, matrix);
	} else if (product.image instanceof Bitmap) {
		canvas.drawBitmap(product.image, 0, 0);
	}
	canvas.drawText(product.name, 150, 30, this._productHeadPaint);
	canvas.drawTextBox(product.description, 150, 40, 
	System.width - 5*app.scaleFactor, item.height,  
	this._productDescrPaint, #start);
	canvas.drawText(String.printf("$ %.2f", product.price), 
	150, item.height - 15, this._productPricePaint);
	canvas.drawLine(0, item.height, item.width, item.height, this._productHeadPaint);
	if (product.oos) Button._drawPath(canvas, this._oosIcon, 
	this._productHeadPaint, 32, System.width - (40+40+40) - 
	5*app.scaleFactor, item.height - 40);
	Button._drawPath(canvas, this._cartIcon, this._productHeadPaint, 
	32, System.width - (40+40) - 5*app.scaleFactor, item.height - 40);
	Button._drawPath(canvas, this._detailIcon, this._productHeadPaint, 
	32, System.width - 40 - 5*app.scaleFactor, item.height - 40);
}

The product details are implemented by ProductView class. Currently, this class shows only a chosen product, product description, price, etc. For demonstration purposes, we will show icons of “add to cart”, product review, etc. Once we click on the arrow located in the right bottom of row, we will see a detailed description of our product.

function _onProductsRetrieved(products)
{
	this._list.angle = -1; // indicates finished loading
	// foreach retrieved product
	for(var p in products) {
		// create list item
		var item = new View();
		item.visible = true;
		item.stretch = #horz;
		item.height = PRODUCT_HEIGHT;
		// custom 'tag'
		item.product = p;
		item.onProcess = function(sender) {
			if (sender.product.image == 
			#loading && System.tick - sender.tick > 40) {
				sender.tick = System.tick; // milis
				sender.angle += 15;
				sender.invalidate();
			}
		}
		item.onPointerReleased = function(sender, x, y) {
			if (x > System.width - 120 - 
			5*app.scaleFactor && y > sender.height - 40)
			    app.push(new ProductView(sender.product)); // show detail
		}
		item.onDraw = function(sender, canvas) 
		{ this super._doProductDraw(sender, canvas); }
		this._list.add(item);
	}
}

Accessing files from server will be accomplished by using “Server” class- simple web client, who gets access to data located on the server (JSON), and then, it will send callbacks as parameters.

class Server
{
	const HOST = "services.moscrif.com";
	const PORT = 80;
	const RES_PRODUCTS = "/sample-eshop/getProducts.aspx";
	const RES_PRODUCT_IMAGE = "/sample-eshop/getProductImage.aspx?id=%d&w=%d&h=%d";

	function retrieveProducts(onSuccess, onError)
	{
		var wc = new WebClient();
		wc.open(Server.HOST, Server.PORT, false, ""); // host, port, 
							// secure, proxy
		wc.onError = function() { onError(this.errorMessage); }
		wc.onReceiving = function(received, total)
		{ // total can be 0, depends on server behaviour
			console << String.printf
			("Receving products data (%d bytes)\n", received); // just log
								// to console
		}
		wc.onReceived = function() {
			// convert received bytes to ut8
			var receivedContent = this.data.toString("utf8");
			// parseData - parses string (json) and 
			// returns javascript object
			onSuccess(parseData(receivedContent));
		}
		wc.getData(Server.RES_PRODUCTS);
	}

	function retrieveProductImage(product, width, height, onSuccess, onError)
	{
		var wc = new WebClient();
		wc.open(Server.HOST, Server.PORT, false, "");
		wc.onError = function() { onError(String.printf
		("Error retrieving productId=%d! %s", product.id, this.errorMessage)); }
		wc.onReceiving = function(received, total) {
			console << String.printf
			("Receiving image data (%d bytes)\n", received) << "\n";
		}
		wc.onReceived = function() {
			var img = null;
			try {
				// convert received bytes to bitmap
				img = Bitmap.fromBytes(this.data);
				onSuccess(img);
			} catch(e) {
				onError(String.printf
				("Error loading image for productId=%d! %s", 
					product.id, e.message));
			}
		}
		wc.getData(String.printf(Server.RES_PRODUCT_IMAGE, 
			product.id, width, height));
	}
}

You can also watch this video to see how the installation files are generated.

Conclusion

Mobile client is only a prototype of real application which can be further optimized or developed with additional features like “add to cart”, product review, etc. Please stay tuned for future articles where we will demonstrate how to save data locally using SQLite database.

License

This article, along with any associated source code and files, is licensed under The BSD License

Share

About the Author

Moscrif
Moscrif
Slovakia Slovakia
No Biography provided
Group type: Organisation

23 members

Follow on   Twitter

Comments and Discussions

 
QuestionNo too bad but ... PinmemberKanasz Robert7-Aug-11 22:24 
You've get very short paragraphs. It would be a better if you write a little bit more.
Remove all the screen shots and code snippets and read what's left. What's left should stand on its own to discuss the topic at hand. Screen shots and snippets should enhance the discussion, not provide it.
Regards
Robert
AnswerRe: No too bad but ... PinmemberJay Surovy17-Aug-11 18:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 30 Jul 2011
Article Copyright 2011 by Moscrif
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid