Page 2

Javascript: あるオブジェクトのメソッドをほかのオブジェクトに呼んでもらうとthisはwindowになる

A = function(){
this.myName = "A";
}
A.prototype = {
trace:function(){
alert(this.myName);
}
}
B = function(){
this.myName = "B";
}
B.prototype = {
callme:function(callme){
callme();
}
}
$a = new A();
$b = new B();
$b.callme($a.trace);

AとBにはそれぞれmyNameを設定

BのインスタンスにAのインスタンスのメソッドtraceを渡し、Bのインスタンスに呼んでもらう。

結果はAでもBでもなく、undefined.

traceメソッド内のthisはwindowになる。traceが匿名関数になる。

なんどもなんどもつまずくところ。

ECCUBE 2.11 支払い方法に説明文を追加

代引きの手数料など一つ一つの支払い方法に説明文を入れたかったのでカスタマイズのメモ

データベーステーブルdtb_paymentにはmemo01とか02とか使われていないらしいカラムがあったのでそれを使うことにする。

1:管理画面のテンプレートを編集
data/Smarty/templates/admin/basis/payment_input.tpl
テーブルの行をひとつ追加

<tr>
<th>説明</th>
<!--{assign var=key value="memo01"}-->
<td><textarea name="<!--{$key}-->" cols="60" rows="5"><!--{$arrForm[$key].value}--></textarea></td>
</tr>

2:対象クラスを拡張
data/class_extends/pages/admin/basis/LC_Page_Admin_Basis_PaymentInput_Ex.php
フォームの内容を追加するためlfInitParamをオーバーライド

function lfInitParam($mode, &$objFormParam) {
parent::lfInitParam($mode,$objFormParam);
$objFormParam->addParam("説明", "memo01", STEXT_LEN, 'KVa', array("SPTAB_CHECK", "MAX_LENGTH_CHECK"));
}

3:公開側のテンプレートを編集
data/Smarty/templates/default/shopping/payment.tmpl

<!--{section name=cnt loop=$arrPayment}-->
<!--{$arrPayment[cnt].payment_memo01|h}-->
<!--{/section}-->

※section ってよくわからないな。。foreachにしたい。。

4:データベースから値を持ってくるようにHelper_Purchase_Exクラスを編集
data/class_extends/helper_extends/SC_Helper_Purchase_Ex.php
getPaymentsByPriceをオーバーライドし、クエリにmemo01も入れるようにする

function getPaymentsByPrice($total,$deliv_id){
//省略
$payments = $objQuery->select("payment_id, payment_method, rule, upper_rule, note, payment_image, charge, memo01", "dtb_payment", $where, $arrPaymentIds);
//省略
}

JQuery Balloonプラグイン: balloonの消し方

ツールチップを表示するこちらのJQueryプラグイン「jquery.balloon.js」について動的に消したいのになかなか消せなかったのでメモ。

経緯:

削除ボタンがあって、そのボタンにマウスをあわせると「削除する」とツールチップを表示するために上記のプラグインを用いていたのですが、クリック時にボタン自体を消したら、ツールチップだけ画面に残ってしまった。

解決:

Balloonにはballoonを設定したオブジェクトに対し、data(“balloon”)で取得できる模様。

deleteBtn.click(function(){
$(this).data("balloon").remove();
});
deleteBtn.balloon();

 

追記2011/11/11

 

上記の方法では不十分。消した後、ブラウザの画面サイズを変更するjavascriptエラーがでる。
理由はツールチップを表示したときに$.balloon.instancesに配列を追加していて、window.resizeイベントでそれらの配列の要素にアクセスしているため。

本当は拡張したかったのだが、面倒だったので直接プラグインに変更を加えた。

1:$.balloonにメソッドを二つ追加。

2:配列に常に突っ込むのをやめて、重複していないかのチェックをするように変更。

 

追記2 2011/11/11

と思ったが、adjustBalloonで位置が設定できない現象が。理由はこのメソッドが呼ばれたときに要素が表示されていないこと。表示する瞬間に位置情報を計算したほうがよいのではないかと思い、再び改造。

showメソッドの中で計算した。それによって$.balloon.instances関連のものが必要なくなった。

誰の助けになるかわかりませんが一応ソースを張っておきます。

 

EC-CUBE2.11(session_set_save_handler)+Ajaxでセッションがうまくいかない件。

2日悩んでまだうまくいっていないことですが、とりあえずこれまでわかったことをメモします。
※この情報は間違っている可能性が高いです。

EC-CUBEでは2.11ではセッション情報をデータベースに保存している。
通常、商品をカートに入れる際にはカートのページにリダイレクトされ、セッション情報にカートの内容が保存される。

カートの追加をAjaxで実装したところ、連続して複数の商品をカートに追加すると、タイミングによってカートに追加されない商品があるという現象が起きた。EC-CUBE2.11ではSC_Helper_Sessionクラスにてsession_set_save_handlerが呼ばれ、セッションに対して起こるイベント用のようなものが定義されて、オリジナルの処理(データベースへセッション情報を書く処理)がかかれている。

こうすることによって単純に$_SESSIONにプロパティをセットするだけでデータベースにセッション情報を残すことができる。

原因を探っていくと、どうやら$_SESSIONを操作した際に、その都度登録した処理(データベースへ書き込む処理)が呼ばれるわけではないことで、たとえばその処理の中で一度$_SESSIONを読み込み、それに対して何かを付けたし、そして再度$_SESSIONに書き込むというような処理を入れることが原因のようだ。

EC-CUBEではカートにある商品をID(連番の)で処理しているようなのだが、次の商品がカートに追加されるたびに一番最後のID(数字)に1を足して処理している。そのためカートに追加する際に一度カートの内容を読み込み、追加し、書き込むという処理をする。

Ajaxを使うことで「読み込み」「読み込み」「書き込み」という処理がありえるので、商品が0のときは「次はID:1」「次もID:1」「IDが二つかぶるので2つめのみ追加」となる。

通常は「読み込み」から「書き込み」まで同じ行に対する処理をロックできるので問題は起こらないのだが、読み込みは読み込み、書き込みは書き込みとデータベースへのアクセスが別々になっていることでこのようなことになってしまっている模様。

その辺のトランザクションを何とかするか。セッションを連番で処理するのを何とかするか。

どうすれば最善か。まだ考察中です。

追記(2011/11/10)

Ajaxを使うことで「読み込み」「読み込み」「書き込み」という処理がありえるので、商品が0のときは「次はID:1」「次もID:1」「IDが二つかぶるので2つめのみ追加」となる。

さらに調査した結果、こういうことではないようだ。

IDがかぶるから上書きされるのではなく、セッションが書き込まれるときには
「すべてを読み込み」→「変更(追加)」→「すべてを書き込み」となるので、
たとえばAが入っているセッションにB,Cを書き込みたい場合
「A」を読み込んでそれに「B」を足して「A,B」となり、それを書き込む前に「A」を読み込み「C」を足して「A,C」となり、最終的に「A,C」となってしまうということらしい。

これはsession_set_save_handlerでデータベースを使う際にまれにある現象らしい。

http://xoops.ec-cube.net/modules/newbb/viewtopic.php?viewmode=thread&topic_id=8247&forum=2&post_id=39923#forumpost39923

http://svn.ec-cube.net/open_trac/ticket/571

追記2(2011/11/11)

結局、非同期通信を行う場合、session_set_save_handlerを使うと読み込みと書き込みが入れ子になり、上記のような現象になることは避けられなかった。

また、Ajax側で通信を順番に行うようにすることでこれを回避することができた。
具体的には商品をaddしたときにqueueに追加し、順番に処理していく。

また処理が終わったらカートをリフレッシュしていたのだが、コレもまずかった。

カートのリフレッシュ時には当然最新のカートの情報を取得するのだが、それもこの現象の原因になった。
つまり読み込むだけであっても通信が入れ子になることでセッションの同期が取れず、セッション関連がある通信は常に順番に確実に行う必要がある。

session_set_save_handlerを使用しないような実装も考えたが、EC-CUBEの保守がややこしいことになりそうなのでやめた。

通信をすべて順番に行うことで、問題を解決することにした。

  1. 商品を追加ボタンを押す
  2. queueに追加
  3. queueがひとつだけならqueueを開始
  4. ajaxでpost
  5. javascriptでカートに追加中の表示(追加ボタンをロード中にし、カートに商品を半透明で追加表示)
  6. 応答があったら本当にカートに入っているかを一度チェックする
  7. 成功ならカートに追加済みの表示:失敗ならすべてを元に戻す。
  8. queueがまだあるなら次のqueueを開始し、4へ戻る。

追記3(2011/11/11)

resize_image.phpを動かすと、セッションが上書きされるようだ。なんで、画像をリサイズするためにセッションを操作しているのか不明。。
なんだか動作ももたつくので使用するのをやめた。

EC-CUBE2.11 静的URLにしたらソートができなくなった。

こちらのページ参考に商品詳細ページとリストページを静的URLにしたのですが、その影響でリストページがソートされなくなった。

調べてみると、どうやら
data/class/pages/products/LC_page_products_list.php

$this->arrForm = $_REQUEST;//時間が無いのでコレで勘弁してください。 tao_s
が影響している模様。

.htaccessを以下のように書き換えた。

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
#RewriteRule ^detail([0-9]+).html+ detail.php?product_id=$1 [L]
RewriteRule ^detail([0-9]+).html+ detail.php?product_id=$1&%{QUERY_STRING} [L]
#RewriteRule ^list([0-9]+).html+ list.php?category_id=$1 [L]
RewriteRule ^list([0-9]+).html+ list.php?category_id=$1&%{QUERY_STRING} [L]

Javascript 自身がフレーム内かどうかを判定する

フレーム内でないとwindowもwindow.parentも同じくwindowを参照しているようなので、

function isFrame($window){
return $window != $window.parent;
}

とした。

Colorbox iframe内からのリンクで親のフレームにColorboxを表示する

$("a.test").click(function(){
var href = $(this).attr("href");
parent.$.fn.colorbox({
width: "560px",
height: "700px",
href:href,
iframe: true ,
scrolling: "auto"
});
});

EC-CUBE 2.11 詳細ページをlightbox風に表示する

詳細ページなどをAjaxを使ってiframeで表示したいとき、共通のheader,footerを表示したくなかった。
でも管理画面のページレイアウト詳細設定で「共通のヘッダーを使用する」と「共通のフッターを使用する」のチェックボックスをはずしてしまうと、iframeで表示しない場合も表示されなくなってしまうので、iframeでの表示と通常の表示とで、読み込むヘッダーとフッタを変更したい。

1:まず
data/class_extends/SC_View_Ex.php
のassignTemplatePath()をオーバーライド
header_tpl_for_iframeとfooter_tpl_for_iframeとしてheader_iframe.tplとfooter_iframe.tplを新規で定義

function assignTemplatePath($device_type_id) {
parent::assignTemplatePath($device_type_id);
$templatePath = SC_Helper_PageLayout_Ex::getTemplatePath($device_type_id);
$header_tpl_for_iframe = $templatePath . "header_iframe.tpl";
$footer_tpl_for_iframe = $templatePath . "footer_iframe.tpl";

$this->assign("header_tpl_for_iframe", $header_tpl_for_iframe);
$this->assign("footer_tpl_for_iframe", $footer_tpl_for_iframe);
}

2:該当するクラス(今回の場合は詳細ページなので)
data/class_extends/page_extends/products/LC_Page_Products_Detail_Ex.php
のprocessをオーバーライドし、arrPageLayoutにisIframeプロパティを追加。
クエリにdisplayMode=iframeとなっていたときにtrueに設定。

function process() {
$this->arrPageLayout["isIframe"] = $this->getDisplayMode() == "iframe" ;
parent::process();
}

displayModeはほかでも遣う気がしたのでページクラスを拡張
data/class_extends/page_extends/LC_Page_Ex.php

function getDisplayMode() {
$pattern = '/^[a-zA-Z0-9_]+$/';
$mode = null;
if (isset($_GET['displayMode']) && preg_match($pattern, $_GET['displayMode'])) {
$mode =  $_GET['displayMode'];
} elseif (isset($_POST['displayMode']) && preg_match($pattern, $_POST['displayMode'])) {
$mode = $_POST['displayMode'];
}
return $mode;
}

3:メインテンプレート
data/Smarty/default/site_main.tpl
にヘッダーとフッターを読み込むところがあるので分岐する。

<!--{if $arrPageLayout.isIframe}-->
<!--{include file= $header_tpl_for_iframe}-->
<!--{else}-->
<!--{include file= $header_tpl}-->
<!--{/if}-->
<!--{if $arrPageLayout.isIframe}-->
<!--{include file= $footer_tpl_for_iframe}-->
<!--{else}-->
<!--{include file= $footer_tpl}-->
<!--{/if}-->

4:読み込む専用のヘッダーとフッターを追加する
data/Smarty/default/header_iframe.tpl
data/Smarty/default/footer_iframe.tpl

5:html/products/.htaccessの内容を変更する。※!詳細ページを静的にしていない場合は必要ないです。
こちらのページを参考に詳細ページを静的URLにしていたので下記のように記述していたんですが、
RewriteRule ^detail([0-9]+).html+ detail.php?product_id=$1 [L]
これだURLのクエリ情報が付与されなかったため下記のように変更。
RewriteRule ^detail([0-9]+).html+ detail.php?product_id=$1&%{QUERY_STRING} [L]

*追記 2011/11/31
この記事関係のない部分で別の問題もあったので最終的に.htaccessはこうなった

6:lightbox的に表示したいのでプロダクトリストを編集する。今回はcolorboxを使用。
data/Smarty/default/products/list.tpl
jqueryとcolorboxを読み込んでおく。

$(document).ready(function() {
var colorbox_ajax = $(".colorbox_ajax");
$(".colorbox_ajax").each(function(){
var href = $(this).attr("href");
href += "?displayMode=iframe";
$(this).attr("href",href);
});
$(".colorbox_ajax").colorbox({ width: "960px", height: "420px",iframe: true});
});
<a href="<!--{$smarty.const.P_DETAIL_URLPATH}--><!--{$arrProduct.product_id|u}-->.html" class="colorbox_ajax">product name</a>

aタグのhrefには記述せず(直接来た場合はiframeじゃない版を表示したいので)、わざわざjqueryでループし、displayModeを付与した。

EC-CUBE 2.11 追加したカートのセッション情報を注文完了時に消しておく

/data/class_extends/helper_extends/SC_Helper_Purchase_Ex.php
cleanupSessionをオーバーライド

function cleanupSession($orderId, &$objCartSession, &$objCustomer, $cartKey) {
$objCartSession->delAllAdditionalInfo();
parent::cleanupSession($orderId, $objCartSession, $objCustomer, $cartKey);
}

EC-CUBE 2.11 合計金額に基本料金を追加

あらかじめ基本料金を基本情報として追加しておく

合計金額に基本料金を追加する。
/data/class_extends/SC_CartSession_Ex.php

function calculate($productTypeId, &$objCustomer, $use_point = 0,
$deliv_pref = "", $charge = 0, $discount = 0, $deliv_id = 0) {
$results = parent::calculate($productTypeId, $objCustomer, $use_point, $deliv_pref, $charge, $discount, $deliv_id);

$objDb = new SC_Helper_DB_Ex();
$col    = SC_Utils_Ex::sfGetCommaList(array("basic_fee"));
$arrRet = $objDb->sfGetBasisData(true, $col);
$results['basic_fee'] = $arrRet["basic_fee"];
$results['payment_total'] += $results['basic_fee'];

return $results;
}

 

追記(2011/11/17)

culculateではなくて、getAllProductsTotalメソッド内で行ったほうがよさそうということで、変更しました。

function calculate($productTypeId, &amp;$objCustomer, $use_point = 0,
$deliv_pref = "", $charge = 0, $discount = 0, $deliv_id = 0) {
$results = parent::calculate($productTypeId, $objCustomer, $use_point, $deliv_pref, $charge, $discount, $deliv_id);

$objDb = new SC_Helper_DB_Ex();
$col    = SC_Utils_Ex::sfGetCommaList(array("basic_fee"));
$arrRet = $objDb-&gt;sfGetBasisData(true, $col);
$results['basic_fee'] = $arrRet["basic_fee"];
<del>//$results['payment_total'] += $results['basic_fee'];</del>

return $results;
}
function getAllProductsTotal($productTypeId) {
//省略

//基本料金を追加
$objDb = new SC_Helper_DB_Ex();
$col    = SC_Utils_Ex::sfGetCommaList(array("basic_fee"));
$arrRet = $objDb-&gt;sfGetBasisData(true, $col);
$total += $arrRet["basic_fee"];

return $total;
}