EC CUBEで新着アイテムをajax取得で表示する方法

EC CUBE 2.12.6を編集した際に、recommend.tplにて
新着商品のajax取得をサポートしていますが、書き方が古かったので、
推奨とされている書き方で書き直したのが以下になります。

最初にロジックの方です。

```data/class/pages/frontparts/bloc/LC_Page_FrontParts_Bloc_Recommend.php
function action() {

			// 基本情報を渡す
			$objSiteInfo = SC_Helper_DB_Ex::sfGetBasisData();
			$this->arrInfo = $objSiteInfo->data;

			//おすすめ商品表示
			$this->arrBestProducts = $this->GetShinchakuItems();
			$objFormParam = new SC_FormParam_Ex();

			switch($this->getMode()){
				// default:
				case "getList":
					$this->lfInitNewitemParam($objFormParam);
					$objFormParam->setParam($_POST);
					$objFormParam->convParam();
					$this->arrErr = $objFormParam->checkError(false);
					if(empty($this->arrErr)){
						$json = $this->lfGetNewitemForJson($objFormParam);
						echo $json;
						exit;
					} else {
						echo $this->lfGetErrors($this->arrErr);
						exit;
					}
				break;
				default:
					$this->newitemCount = $this->lfGetNewitemCount();
				break;
			}
--中略--
	/**
	* 新着情報をJSON形式で取得する
	* (ページと表示件数を指定)
	*
	* @param array $objFormParam フォームパラメータークラス
	* @return String $json 新着情報のJSONを返す
	*/
	function lfGetNewitemForJson(&$objFormParam){
		$objQuery = SC_Query_Ex::getSingletonInstance();
		$objProduct = new SC_Product_Ex();
		$arrData = $objFormParam->getHashArray();

		$show_num = 4 * 3;
		$col = 'product_id';
		$table = 'dtb_products';
		$where = 'del_flg = 0 AND status = 1';

		$dispNumber = $arrData['disp_number'];
		$pageNo = $arrData['pageno'];
		if(!empty($dispNumber) && !empty($pageNo)){
			// $objQuery->setLimitOffset($dispNumber, (($pageNo - 1) * $dispNumber));
		}

		//更新日時でソートする
		$objQuery->setOrder('update_date DESC');
		// $objQuery->setLimit($show_num);
		$objQuery->setLimitOffset($show_num,(($pageNo - 1) * $dispNumber));
		$arrBestProducts_json = $objQuery->select($col, $table, $where);


		$objQuery =& SC_Query_Ex::getSingletonInstance();
		if (count($arrBestProducts_json) > 0) {
		// 	// 商品一覧を取得
		// 	// where条件生成&セット
			$arrProductId = array();
			$where = 'product_id IN (';
			foreach ($arrBestProducts_json as $key => $val) {
				$arrProductId[] = $val['product_id'];
			}

			// 取得
			$arrTmp = $objProduct->getListByProductIds($objQuery, $arrProductId);
			foreach ($arrTmp as $key => $arrRow) {
				$arrProductList[$arrRow['product_id']] = $arrRow;
			}
			// おすすめ商品情報にマージ
			foreach (array_keys($arrBestProducts_json) as $key) {
				$arrRow =& $arrBestProducts_json[$key];
				if (isset($arrProductList[$arrRow['product_id']])) {
					$arrRow = array_merge($arrRow, $arrProductList[$arrRow['product_id']]);
				} else {
					// 削除済み商品は除外
					unset($arrBestProducts_json[$key]);
				}
			}
		}

		//新着情報の最大ページ数をセット
		$newitemCount = $this->lfGetNewitemCount();
		$arrBestProducts_json["newitem_page_count"] = ceil($newitemCount / 3);

		$json =  SC_Utils_Ex::jsonEncode($arrBestProducts_json);    //JSON形式
		return $json;
	}

	/**
	* 新着情報の件数を取得する
	*
	* @return Integer $count 新着情報の件数を返す
	*/
	function lfGetNewitemCount(){
		$count = 0;
		$objQuery = SC_Query_Ex::getSingletonInstance();
		$count = $objQuery->count("dtb_products", "del_flg = '0'");
		return $count;
	}
	/**
	* エラーメッセージを整形し, JSON 形式で返す.
	*
	* @param array $arrErr エラーメッセージの配列
	* @return string JSON 形式のエラーメッセージ
	*/
	function lfGetErrors($arrErr) {
		$messages = '';
		foreach ($arrErr as $val) {
				$messages .= $val . "\n";
		}
		return SC_Utils_Ex::jsonEncode(array('error' => $messages));
	}
```

ここはデフォルトの記述がほとんどだと思いますが、
（何を編集したか忘れましたｗ）
件数の指定とかはいじったかもしれません。

最初に```$this->getMode```のgetListが渡されたら、jsonを取りに行きます(lfGetNewitemForJson)。
jsonでの取得は通常の取得と一緒ですが、
```$objQuery->setOrder('update_date DESC');```
のところは、通常の並びとあわせます。
作成日で表示していたら、create_date DESCですね。

次がテンプレートの表示箇所です。

```data/Smarty/templates/default/frontparts/bloc/recommend.tpl
<!-- ▼新着アイテム -->
<!--{if count($arrBestProducts) > 0 || $smarty.foreach.recommend_products.iteration <= 4}-->
	<div id="block2" class="frontRecommend">
		<h2 class="frontTitle new">NEW</h2>
		<ul id="newList01" class="newsList">
			<!--{foreach from=$arrBestProducts item=arrProduct name="recommend_products"}-->
			<!--{if $smarty.foreach.recommend_products.iteration % 2 == 0}-->
				<li class="odd">
			<!--{else}-->
				<li>
			<!--{/if}-->
					<a rel="external" href="<!--{$smarty.const.HTTPS_URL}-->products/detail.php?product_id=<!--{$arrProduct.product_id|u}-->" class="item-link">
						<div class="listImg">
							<img src="<!--{$smarty.const.IMAGE_SAVE_URLPATH}--><!--{$arrProduct.main_image|sfNoImageMainList|h}-->" alt="<!--{$arrProduct.name|h}-->">
						</div>
						<p class="itemTitle"><!--{$arrProduct.name|h}--></p>
					</a>
				</li>
			<!--{/foreach}-->
		</ul>
```

オンクリックでjsの関数を呼び出します。

```
<div class="moreLink">
  <a href="javascript:;" id="btn_more_newitem" class="btn_more" onclick="getNewitem(12); return false;">もっと見る</a>
</div>
```

サンプルはリンクとタイトルと画像を表示しているので、
これをjsonで取得し書き換えます。
データを取得して、それをIDかクラスで指定した箇所に置き換えていきます。
.textとか.attrの箇所です。

```
<script>
var newitemPageNo = 1;
var offset = 4;
function getNewitem(limit) {
	var i = limit;
	$("#loading").addClass("open");
	$.ajax({
		url: "<!--{$smarty.const.ROOT_URLPATH}-->frontparts/bloc/recommend.php",
		type: "POST",
		data: "mode=getList&pageno="+newitemPageNo+"&offset="+offset+"&disp_number="+i,
		cache: false,
		dataType: "json",
	})
	.then(
		function(data){
			for (var j = 0; j < i; j++) {
				if (data[j] != null) {
					var newitem = data[j];
					var maxCnt = $("#newList01 li").length - 1;
					var newitemEl = $("#newList01 li").get(maxCnt);
					newitemEl = $(newitemEl).clone(true).insertAfter(newitemEl);
					maxCnt++;
					//件名をセット
					var item_title = newitem.name;
					if(item_title.length > 30){
						item_title = item_title.substring(0,30) + "…";
					}
					$($("#newList01 li .itemTitle").get(maxCnt)).text(item_title);
					//画像をセット
					$($("#newList01 li .listImg img").get(maxCnt)).attr("src","<!--{$smarty.const.IMAGE_SAVE_URLPATH}-->" + newitem.main_image);
					//リンクをセット
					$($("#newList01 li .itemLink").get(maxCnt)).attr("href", "<!--{$smarty.const.HTTPS_URL}-->/products/detail.php?product_id=" + newitem.product_id +"");
				}
			}
			//すべての新着情報を表示したか判定
			var newsPageCount = data.newitem_page_count;
			if (parseInt(newsPageCount) <= newitemPageNo) {
			}
			newitemPageNo++;
		}
	)
	.always(function() {
		$("#loading").removeClass('open');
		$('#newList01 li').each(function(i){
			$(this).removeClass('last');
			if(((i + 1) % 4) == 0){
				$(this).addClass('last');
			}
		});
	})
	.fail(function(){
		alert('通信エラーが発生しました。もう一度お試しください');
	});
}
</script>
```

ほぼ同じ方法で、一覧ページの表示もajax表示が出来ます。

```/data/class/pages/products/LC_Page_Products_List.php
$this->arrProducts  =	$this->lfGetProductsList($arrSearchCondition, $this->disp_number, $this->objNavi->start_row,$this->tpl_linemax,$objProduct);
switch ($this->getMode()) {
	case 'json':
		$this->doJson($objProduct);
		break;

--中略--

function doJson(&$objProduct) {
	$this->arrProducts = $this->setStatusDataTo($this->arrProducts, $this->arrSTATUS, $this->arrSTATUS_IMAGE);
	$this->arrProducts = $objProduct->setPriceTaxTo($this->arrProducts);

	// 一覧メイン画像の指定が無い商品のための処理
	foreach ($this->arrProducts as $key=>$val) {
		$this->arrProducts[$key]['main_list_image'] = SC_Utils_Ex::sfNoImageMainList($val['main_list_image']);
	}

	$this->arrProducts = array_merge($this->arrProducts);


	echo SC_Utils_Ex::jsonEncode($this->arrProducts);
	SC_Response_Ex::actionExit();
}
```

スマホ用テンプレートにはデフォルトで搭載されているっぽいので、ajax部分のみを編集

```/data/Smarty/templates/sphone/products/list.tpl
<div class="moreLink">
	<!--{if count($arrProducts) < $tpl_linemax}-->
	<a href="javascript:;" id="btn_more_newitem" class="btn_more" onclick="getProducts(); return false;">もっと見る</a>
	<!--{/if}-->
	<p id="pagintationNum" class="paginationNum">全 <!--{$tpl_linemax}-->件</p>
</div>
<script>
	var max_item = <!--{$tpl_linemax}-->;
	var pageNo = 2;
	var postData = {};
	$('#form1').find(':input').each(function(){
		postData[$(this).attr('name')] = $(this).val();
	});
	postData["mode"] = "json";

	function getProducts() {
		postData["pageno"] = pageNo;
		$("#loading").addClass("open");
	$.ajax({
		url: "<!--{$smarty.const.ROOT_URLPATH}-->products/list.php",
		type: "POST",
		data:postData,
		cache: false,
		dataType: "json"
	})
	.then(
		function(data){
			var i = Object.keys(data).length - 1;
			for (var j = 0; j < i; j++) {
				if (data != null) {
					var newitem = data[j];
					var maxCnt = $("#itemList li").length - 1;
					var newitemEl = $("#itemList li").get(maxCnt);
					newitemEl = $(newitemEl).clone(true).insertAfter(newitemEl);
					maxCnt++;
					//件名をセット
					var item_title = newitem.name;

					$($("#itemList li .listName").get(maxCnt)).text(item_title);
					//画像をセット
					$($("#itemList li .listImg img").get(maxCnt)).attr("src","<!--{$smarty.const.IMAGE_SAVE_URLPATH}-->" + newitem.main_image);
					//リンクをセット
					$($("#itemList li a").get(maxCnt)).attr("href", "<!--{$smarty.const.HTTPS_URL}-->/products/detail.php?product_id=" + newitem.product_id +"");
				}
			}
			//すべての新着情報を表示したか判定
			if (parseInt(max_item) <= $("#itemList li").length) {
				$("#loading").removeClass('open');
				$('#btn_more_newitem').remove();
			}
			pageNo++;
		}
	)
	.always(function() {
		$("#loading").removeClass('open');
		$('#itemList li').each(function(i){
			$(this).removeClass('odd');
			if(((i + 1) % 2) == 0){
				$(this).addClass('odd');
			}
		});
	})
	.fail(function(XMLHttpRequest, textStatus, errorThrown) {
			alert('通信エラーが発生しました。もう一度お試しください');
		});
	}
</script>
```

ec cube2系はSmartyのフレームワークなので仕方ないところですが、
ロジックとテンプレが別なのは良いとして、
保存箇所の階層が深く、いちいち面倒でしたｗ
個人的には<!--{$arg}-->の仕様が最後まで納得出来ず。。
ほとんどのエディタではコメントアウトのハイライトになりますし。