[JS] POSレジシステム改

タグ :

これまでの基礎知識を結集して簡易アプリを作成するシリーズです。
今回は前記事「POSレジシステム」に引き続き、お店等で使用できる Web 上での POSレジシステム を改善します。

前記事では CakePHP のみで作成したPOSレジシステムでしたが、今回は jQuery(Ajax) を使って色々と動的処理を施しました。今回の改善では、ページ遷移を減らすことにのみ力を入れました。商品選択の動作は前回とほぼ同じですが、ページ遷移しないで行けます。さらに会計ページも無くし、「会計へ」をクリックするとモーダルウィンドウが表示されます。前回同様にログインが必要ですが、店舗登録も売上登録も勝手にしちゃって大丈夫です。自由に遊んでくださいませ。

上記では操作しにくいという方は、こちら でお試しください。

アプリ概要

以下のような仕様とします。

ページ遷移させない
 - 商品クリック時のページ遷移を無くす
 - リストから削除時のページ遷移を無くす
 - 会計ページへのページ遷移を無くす

必要となる処理は以下になります。

  • クリック処理
  • POST送信(Ajax)
  • ページ切り換え
  • モーダルウィンドウの表示・非表示

ページ遷移は以下になります。会計ページ以外は前と同じです。

js_posreg_page

作成したサンプル

処理の流れとコードは以下になります。

[ 動作フロー ]
js_posreg_flow
[ コード ]
$(function() {
	$('.item-area li').click(function() {
		var link = $(this).attr('class').slice(4);
		$.ajax({
			type: 'POST',
			url: '/sample/cakejs/posreg_js/pos_items/index/' + link,
			data: null,
			success: function(data, dataType) {
				$('body').html(data);
			}
		});
	});
	$('.item-delete').click(function() {
		var link = $(this).attr('value');
		$.ajax({
			type: 'POST',
			url: '/sample/cakejs/posreg_js/pos_items/index/-' + link,
			data: null,
			success: function(data, dataType) {
				$('body').html(data);
			}
		});
	});
	$('.account-btn').click(function(){
		var w = $('.modal-body').innerWidth() / 2;
		var h = $('.modal-body').innerHeight() / 2;
		$('.modal-body').css({
			'margin-left' : -w,
			'margin-top' : -h
		});
		$('.modal').fadeIn(300);
	});
	$('.close,.modal-back').click(function(){
		$('.modal').fadeOut(300);
	});
});
<div class="posItems index">
	<h2><?php echo __('Pos Items'); ?></h2>
	<ul class="item-area">
<?php $i = 1; foreach ($posItems as $posItem): ?>
		<li class="item<?php echo $i; ?>"><?php echo $posItem['PosItem']['name']; ?></li>
<?php $i++; endforeach; ?>
	</ul>
	<h4 class="sale-area-h"><?php echo __('Sale Item List'); ?></h4>
	<table class="sale-area">
<?php $i = 1; foreach ($tables as $table): ?>
		<tr>
			<td class="item-name"><?php echo $table['name']; ?></td>
			<td class="item-value"><?php echo $table['value']; ?></td>
			<td class="item-price"><?php echo $table['price'].__('yen'); ?></td>
			<td class="item-delete" value="<?php echo $table['id']; ?>"><?php echo __('Delete'); ?></td>
		</tr>
<?php $i++; endforeach; ?>
		<tr>
			<td class="item-name-last"><?php echo __('Total'); ?></td>
			<td class="item-value-last"><?php echo $total['value']; ?></td>
			<td class="item-price-last"><?php echo $total['price'].__('yen'); ?></td>
			<td class="item-delete" value="0"><?php echo __('Clear'); ?></td>
		</tr>
	</table>
	<?php echo $this->Session->flash(); ?>
	<div class="account-btn">会計へ</div>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('My Shop'), array('controller' => 'users', 'action' => 'view/'.$user_id)); ?> </li>
		<li><?php echo $this->Html->link(__('Edit Pos'), array('action' => 'edit')); ?> </li>
		<li><?php echo $this->Html->link(__('Logout'), array('controller' => 'users', 'action' => 'logout')); ?> </li>
	</ul>
</div>
<div class="modal">
	<div class="modal-back"></div>
	<div class="modal-body">
		<p class="close">close</p>
		<div class="account">
			<h2><?php echo __('Account'); ?></h2>
			<h4><?php echo $date; ?></h4>
			<h3><?php echo __('Price').' '.$total['price'].' '.__('yen'); ?></h3>
			<h3><?php echo '( '.$total['value'].' '.__('item').')'; ?></h3>
			<?php
				echo $this->Form->create('PosSale');
				echo $this->Form->hidden('user_id', array(
					'value' => $user_id
				));
				echo $this->Form->hidden('date', array(
					'value' => $date
				));
				echo $this->Form->hidden('sales',array(
					'value' => $sales
				));
				echo $this->Form->hidden('vals',array(
					'value' => $vals
				));
				echo $this->Form->end(__('Receipt'));
			?>
		</div>
	</div>
</div>
#content {
	min-width: 800px;
}
ul.item-area {
	list-style-type: none;
	width: 305px;
}
ul.item-area li {
	float: left;
	padding-top: 10px;
	margin-top: 1px; margin-left: 1px; margin-right: 0px;
	width: 100px; height: 40px;
	text-align: center; background: #ccc; cursor: pointer;
	font-size: 20px;
}
ul.item-area li:hover {
	background: red;
}
h4.sale-area-h {
	position: absolute;
	width: 150px;
	margin-left: 350px;
}
table.sale-area {
	position: absolute;
	width: 300px;
	margin-top: 30px; margin-left: 330px;
}
table.sale-area td.item-name {
	width: 120px;
	text-align: center;
}
table.sale-area td.item-value {
	width: 20px;
	text-align: center;
}
table.sale-area td.item-price {
	width: 80px;
	text-align: right;
}
table.sale-area td.item-delete {
	width: 80px;
	text-align: center;
}
table.sale-area td.item-name-last {
	width: 120px;
	text-align: center;
	border: 0px; background: #eee;
}
table.sale-area td.item-value-last {
	width: 50px;
	text-align: center;
	border: 0px; background: #eee;
}
table.sale-area td.item-price-last {
	width: 80px;
	text-align: right;
	border: 0px; background: #eee;
}
form#PosItemIndexForm .submit {
	position: absolute;
	width: 250px;
	margin-top: -280px; margin-left: 480px;
}
dl.month-sale {
	margin-left: 20px;
}
.detail-m {
	position: absolute;
	margin-top: -83px; margin-left: 300px;
}
.detail-d {
	position: absolute;
	margin-top: -43px; margin-left: 300px;
}
dd.month-price, dd.day-price {
	width: 130px;
	text-align: right;
}
.account h2 {
	margin-left: 40%;
}
.account h3, .account h4 {
	margin-left: 30%;
}
.account .submit {
	margin-left: 50%;
}
.submit input {
	cursor: pointer;
}
.item-delete {
	cursor: pointer;
}
.item-delete:hover {
	color: red;
}
.modal {
	display:none;
}
.modal-body {
	position: fixed;
	left: 50%; top: 50%;
	width: 400px; height: 300px;
	background: white;
}
.modal-back {
	position: fixed;
	left: 0; top: 0;
	height: 100%; width: 100%;
	background: gray;
	opacity: 0.8;
}
.account-btn {
	margin-top: -15px; margin-left: 480px;
	width: 70px; height: 40px;
	background: green; line-height: 40px;
	text-align: center; font-size: 18px; color: white;
	cursor: pointer;
}
.close {
	cursor: pointer;
}
詳しい説明は後述します。
過去記事「予約管理システム」のPOST送信部分をほとんど流用して作ったものです。jQuery で行う場合に、もっと簡単に同じような動作が出来ると思いますが、CakePHP のコードになるべく変更を加えないようにというコンセプトで作成してみました。

コード解説

jQuery のコードの説明をします。

[ jQuery の説明 ]
[ 2行目〜12行目 お買い上げリストへの追加処理 ]
$('.item-area li').click(function() {

クリック操作により動作します。
クリック箇所は商品エリア ul 内の li 要素、つまり各商品一個一個に対して実行されます。

var link = $(this).attr('class').slice(4);
$.ajax({
	type: 'POST',
	url: '/sample/cakejs/posreg_js/pos_items/index/' + link,
	data: null,
	success: function(data, dataType) {
		$('body').html(data);
	}
});

POST送信を実行します。
送信先ページは同じページです。同じページへURLパラメータを付加してPOST送信することで、ページ遷移させずにパラメータを渡す事ができます。パラメータは、クリックされた要素のクラス名から抽出しています。さらに、POSTデータを「null」にすることで、フォームの submit と区別しています。
POST送信成功時には、body の HTML 要素をごそっと張り替える処理を実行します。

[ 13行目〜23行目 お買い上げリストからの削除処理 ]

上記の追加処理とほとんど同じです。
パラメータはクリックされた要素の value 値を持たせています。パラメータの付加時に「-(マイナス)」を付けることで削除という意味を表します。この削除に関しては PHP 側でも変更があるので後述します。

[ 24行目〜32行目 モーダルウィンドウを表示 ]
var w = $('.modal-body').innerWidth() / 2;
var h = $('.modal-body').innerHeight() / 2;
$('.modal-body').css({
	'margin-left' : -w,
	'margin-top' : -h
});
$('.modal').fadeIn(300);

モーダルウィンドウを表示させます。
モーダルウィンドウといっても、もともと非表示だった要素をフェードを使って表示させているだけです。表示する際に、body部分の表示位置を変更させています。ブラウザ画面の真ん中に来るようにしています。

[ 33行目〜35行目 モーダルウィンドウを隠す ]
$('.modal').fadeOut(300);

モーダルウィンドウを非表示にします。
フェードを使って非表示にします。

CakePHP コードの変更点

jQuery を用いたアプリに改変したことで、CakePHP のコントローラにも変更を加えています。その変更点だけをまとめます。また、ビューの変更としては、上記コードの HTML タブ内のコードですので割愛します。

[ コード(PosItemsController.php) ]
public function index($id = null) {
	$user = $this->Auth->user();
	// 商品データを取得
	$items = $this->PosItem->find('all', array(
		'conditions' => array('user_id' => $user['id']),
		'order' => array('table' => 'ASC')
	));
	$tables = $vals = array();
	$sales = $strvals = '';
	$total['value'] = $total['price'] = 0;
	// パラメータチェック
	if ($id) {
		// パラメータ有ならお買い上げリストに追加(セッション使用)
		if ($this->request->data) {
			// ポスト送信有なら売上にデータ登録
			$pos_data_id = split(",", $this->request->data['PosSale']['sales']);
			$pos_data_val = split(",", $this->request->data['PosSale']['vals']);
			$pos_data = $this->PosItem->find('all', array(
				'conditions' => array('PosItem.id' => $pos_data_id)
			));
			$i = 0;
			foreach($pos_data as $pos) {
				$data[$i]['PosSale']['user_id'] = $pos['PosItem']['user_id'];
				$data[$i]['PosSale']['date'] = $this->request->data['PosSale']['date'];
				$data[$i]['PosSale']['pos_item_id'] = $pos['PosItem']['id'];
				$data[$i]['PosSale']['value'] = $pos_data_val[$i];
				$data[$i]['PosSale']['price'] = $pos['PosItem']['price'] * $pos_data_val[$i];
				$i++;
			}
			if (!empty($data)) {
				$this->PosSale->create();
				if ($this->PosSale->saveAll($data)) {
					$this->Session->delete('pos_id');
					$this->Session->setFlash(__('The items sold.'));
					$this->redirect(array('action' => 'index'));
				} else {
					$this->Session->setFlash(__('Could not be sold. Please, try again.'));
				}
			}
		}
		if ($id > 0) {
			$sales = $this->Session->read('pos_id').$id.',';
			$this->Session->write('pos_id', $sales);
		} else if ($id < 0) {
			$sales = str_replace(substr($id, 1).',', '', $this->Session->read('pos_id'));
			$this->Session->write('pos_id', $sales);
		} else {
			$sales = '';
			$this->Session->write('pos_id', $sales);
		}
		$subs = split(",", $sales);
		foreach($items as $item) {
			foreach($subs as $sub) {
				if ($item['PosItem']['id'] == $sub) {
					$tables[$sub]['id'] = $item['PosItem']['id'];
					$tables[$sub]['name'] = $item['PosItem']['name'];
					if (!empty($tables[$sub]['value'])) {
						$tables[$sub]['value'] += 1;
						$tables[$sub]['price'] = $item['PosItem']['price'] * $tables[$sub]['value'];
						$vals[$sub] += 1;
					} else {
						$tables[$sub]['value'] = 1;
						$tables[$sub]['price'] = $item['PosItem']['price'];
						$vals[$sub] = 1;
					}
					if (!empty($total['value'])) {
						$total['value'] += 1;
						$total['price'] += $item['PosItem']['price'];
					} else {
						$total['value'] = 1;
						$total['price'] = $item['PosItem']['price'];
					}
				}
			}
		}
		foreach($vals as $val) {
			$strvals .= $val.',';
		}
	} else {
		// それ以外ならセッションクリア
		$this->Session->delete('pos_id');
	}
	$this->set('posItems', $items);
	$this->set('user_id', $user['id']);
	$this->set('tables', $tables);
	$this->set('total', $total);
	$this->set('sales', $sales);
	$this->set('vals', $strvals);
	$this->set('date', date("Y-m-d H:i:s"));
}
簡単に言うと、index 関数内に account 関数と delete 関数を追加しただけです。追加した関数には多少の変更があります。変更した部分だけを以下にまとめます。
[ 14行目〜40行目 会計処理 ]

会計を行い、データベースへ売上登録します。
前記事の関数「account」とほとんど同じですので割愛します。

[ 41行目〜50行目 お買い上げリストの変更 ]
if ($id > 0) {
	$sales = $this->Session->read('pos_id').$id.',';
	$this->Session->write('pos_id', $sales);

リストへの追加処理です。
パラメータが 1 以上である場合は、その値を商品IDとしてリストへ追加します。

} else if ($id < 0) {
	$sales = str_replace(substr($id, 1).',', '', $this->Session->read('pos_id'));
	$this->Session->write('pos_id', $sales);

リストからの削除処理です。
パラメータが -1 以下である場合は、その絶対値を商品IDとしてリストから削除します。

} else {
	$sales = '';
	$this->Session->write('pos_id', $sales);

リストの初期化処理です。
パラメータが 0 、もしくは null である場合はリストを初期化します。

まとめ

jQuery を用いる事でページ遷移を無くし、ユーザビリティの向上になりました。前回と比べても使い易いのではないかと思います。

ですが、商品クリック後のレスポンスが悪すぎです。jQuery で POST送信し、その後 PHP でデータベースアクセスを行い、さらにその後ページの張り替え処理を行っているので、遅くなって当然です。
せっかく使い易く改善したのに、遅くては逆に使いづらいものになってしまいます。

次回は処理速度にも気を配り、jQuery の使いどころを見極めて、より使い易いものに改善したいと思います。

Share

  • このエントリーをはてなブックマークに追加

Comment

コメントを残す

*がついている欄は必須項目です。

  • Twitter
  • Facebook
  • Google Plus
  • RSS Feed