開発メモ,主に補足by子連れ親父プログラマー

2010-11-12

くるくる回るGIFアニメを用意して、submitボタンをクリックした時にくるくる回したい

はぁ~丸二日くらい悩んだかも。
不毛だな。
ま、とにかく html をこんな感じにしておく。
<input id="next_button" type="submit" value="つぎへ" />
<span id="loading" style="display:none;"><img src="img/loader.gif" /> 処理中</span>

submitボタンの後ろにGIFアニメ画像を置いておいて、隠しておいて、クリックされたら出してやろう、というねらいだ。

で、最初俺はこう書いていた
$('input#next_button').click(function(e) {
  e.preventDefault();
  $('#next_button').hide(); 
  $('#loading').show();
  $('form').submit();
});
クリックで、
サブミットボタンのデフォルトの動作を禁止して、
サブミットボタンを消し、
ローディング画像を出し、
そしてフォームをサブミット!

これはまあ、動くんだが、IEで見ると何故かGIFアニメのアニメーションが動かない。静止してるのだ。
ということで順番を入れ替えてみた。

$('input#next_button').click(function(e) {
  e.preventDefault();
  $('form').submit();
  $('#next_button').hide(); 
  $('#loading').show();
});
先にサブミット。
これだと動く。
謎。
とにかく、submit() が先だ、先!

2010-11-09

phpにGDが入ってない!!って時にどうすればいいか

まあ、うちではよくある出来事で。
もう毎回の事なんだからさあ~って気もしないでもないが、やり方を教わったのでメモ。

サーバーに入って、rootになったら、
# yum search php
と打つ。
するとだーっとリストが出る。
その中に
php-gd.i386 : gd グラフィックライブラリを使用するための PHP アプリケーション用モジュール
こんなのがある。
それをこうする。
# yum install php-gd.i386

途中でyesかnoか聞かれるので、yesすると
Installed:
  php-gd.i386 0:5.1.6-27.el5

Complete!
というように入る。
あとは、
# /etc/init.d/httpd restart
再起動する。

2010-11-04

アパッチのエラーログをちらちら見ながら作業する

何回やっても憶えられないので記す。
tail -f /var/log/httpd/error_log

2010-10-26

jQuery UIのDialogで、開くウインドウの中身を別ファイルにしたい

ついでなので。
こちらもサンプルソースを見ると、
HTMLはただdialogってidでdiv作っておいて、
<div id="dialog" title="Basic dialog">
  <p>ここに中身</p>
</div>
JavaScript側はこんだけ。
$(function() {
  $( "#dialog" ).dialog();
});
そっけないなあ。
ドキュメントのoptionsとか見ても皆目見当が付かない。
ただやはりこれも同じHTMLのソース(テンプレ)の中にダイアログ用のソースをどんどん書いていくと、ごちゃごちゃになってくる。
1個、2個くらいならいいとは思うが。
ということで調べてみると、

Loading a page into a dialog

こういう記事があり、やり方はあることが分かった。

jqueryでまず一発 div を生成し、そこに .load() で別ファイルを読み込んで、そいつに dialog のオプションを設定し、そして .open() すれ、ってことらしい。

例として、id="add" のなにがしをクリックした場合として、
$('#add').click(function() {
  $('<div></div>')
    .load('add.php')
    .dialog({
      modal: true,
      autoOpen: false,
      buttons: {
        "登録": function() {
          $(this).dialog("close");
        }
      }
    })
    .dialog('open');
  return false;
});
こんな感じでしょうかー。

jQuery UIのTabsを使って切り替えるタブの中身のコンテンツ部分を別ファイルにしたい

他にいいのがなくて仕方なく使ってるjQuery UIですが、一番よく使う、いや、使いたくなるのが、Tabsを使ったタブレイアウトですね。
デザイナーがぱぱぱっと作ってくれればいいんだけど、いないし。
ということでそのTabsですが、サンプルのソースを見ると、
こんな感じになっていて、
<div id="tabs">
  <ul>
    <li><a href="#tabs-1">メニュー1</a></li>
    <li><a href="#tabs-2">メニュー2</a></li>
    <li><a href="#tabs-3">メニュー3</a></li>
  </ul>
  <div id="tabs-1">
    <p>一個目</p>
  </div>
  <div id="tabs-2">
    <p>2個目</p>
  </div>
  <div id="tabs-3">
    <p>3個目</p>
  </div>
</div>
切り替えるコンテンツの中身も全部同じ一個のファイル(テンプレ)に書く、それを出したり、隠したりする、ということになっている。
中身がちょびっとだったらいいけれども、量が多いとソース書く時に長くなるし、見づらくなるし、分割したい訳です!
別ファイルに。

ということで考えてみたんだが、、
HTMLの方は、
<div id="tabs">
  <ul>
    <li><a href="#tabs-1">メニュー1</a></li>
    <li><a href="#tabs-2">メニュー2</a></li>
    <li><a href="#tabs-3">メニュー3</a></li>
  </ul>
  <div id="tabs-1">
  </div>
  <div id="tabs-2">
  </div>
  <div id="tabs-3">
  </div>
</div>
コンテンツ部分を空っぽにしておいて、
$("#tabs").tabs({
  select: function(event, ui) {
    if (ui.index == 0) {
      $(ui.panel).load('index.php');
    }
    else if (ui.index == 1) {
      $(ui.panel).load('menu.php');
    }
    else if (ui.index == 2) {
      $(ui.panel).load('user.php');
    }
  }
});
とするのはどうだろうか。
もちろん、.load() で呼び出すindex.phpとかには<div>の間に入る内容を出力させます。
もっといい方法ありますか?

2010-10-25

postgreSQLで通し番号(シーケンス)をリセットする時に1から始めたい場合

postgreSQL で id とかのフィールドで自動的に番号が増えていく、auto_increment みたいな設定をテーブルにすると、シーケンスが作られる。
例えばこんなテーブルを作ると
CREATE TABLE users(
  id SERIAL PRIMARY KEY,
  name VARCHAR(255)
);
こんなんなる。
 Schema |     Name     |   Type   |  Owner
--------+--------------+----------+----------
 public | users        | table    | postgres
 public | users_id_seq | sequence | postgres
しばらく users にデータをインサートした後で、
このシーケンスに対してセレクトをかけると、
SELECT * FROM users_id_seq ;
こんな結果が返ってくる
 sequence_name | last_value | increment_by |      max_value      | ・・・
---------------+------------+--------------+---------------------+-------
 users_id_seq  |         34 |            1 | 9223372036854775807 | ・・・
この last_value というのが自動で増えていった id の最後の番号で、これが34になったまま、 users テーブルのデータを消してしまうと、次は id が34から始まってしまう。
1からにしたい、という時に、
select setval ('users_id_seq', 1);
とやるんだが、こうすると last_value が1になるので、次は2からになってしまう。
じゃあ、
select setval ('users_id_seq', 0);
としてみたらどうかと思うが、これはエラーになる。

ということでこうするとよい。
select setval ('users_id_seq', 1, false);

一回目は last_value は1のままで、is_called が true になり、二回目は last_value は2になる。

2010-10-21

2010-10-01

テーブルの一番右端の列の値だけを取り出したい

テーブルの一番右端の列の値だけを取り出したい テーブルの一番右端の列に文字が入っていて、中にはリンクが貼られている場合もあるとする。
リンクが貼られている場合はそのURLを、文字だけの場合は、文字を、取り出したい。
var val=''; 
$('tr td:last-child').each(function(n){ 
    if ($(this).find('a').attr('href') != '') { 
        val += $(this).find('a').attr('href')+"\n";
    } else {
        val += $(this).text()+"\n";
    }
});
$('body').append('
' + val + '
');

こんな感じにしてみた。

2010-09-15

jQuery で table をいじるときは tbody を忘れない

まぁ普通に会員の一覧検索みたいな管理画面を作っていて、普通に table でタグが組んであって、それを項目名をクリックしたらその項目名の昇順で並び替えたいとか、そんなような作業ってのは結構あるわけで。
なんかこう、テーブルのそのデータ行の部分だけを Ajax で load するようにしたいなと。
思ったわけです。

で、テーブルは普通に、こんな感じであって、
<table id="result">
  <tr>
    <th>ID</th>
    <th>氏名</th>
    <th>メールアドレス</th>
  </tr>
  <tr>
    <td>1</td>
    <td>山田</td>
    <td>yamada@・・・.jp</td>
  </tr>
  <tr>
    <td>2</td>
    <td>佐藤</td>
    <td>sato@・・・.jp</td>
  </tr>
</table>
このデータ行の部分だけを Ajax で書き換えたいってことなんですが、どうすればいいですかね?
テーブルの2行目以降を一旦 remove() とかで消して、そこに append() すりゃいいんじゃないかとか、いろいろ悩んでみたんだけど、結局、tbodyを使うってことで解決しました。
先のテーブルを以下のように書き直します。
<table id="result">
  <thead>
    <tr>
      <th>ID</th>
      <th>氏名</th>
      <th>メールアドレス</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>山田</td>
      <td>yamada@・・・.jp</td>
    </tr>
    <tr>
      <td>2</td>
      <td>佐藤</td>
      <td>sato@・・・.jp</td>
    </tr>
  </tbody>
</table>
そして、
$(document).ready(function() {
  $('#result tbody').load(
    './result.php',
    $('form').serialize(),
    function(data){}
  );
});
と jQuery で書きます。
result.php にはページ中のフォームの値がGETで渡っていくので、それを適当に処理してクエリーをかけて結果を元に
    <tr>
      <td>1</td>
      <td>山田</td>
      <td>yamada@・・・.jp</td>
    </tr>
    <tr>
      <td>2</td>
      <td>佐藤</td>
      <td>sato@・・・.jp</td>
    </tr>
の部分だけを出力してやればよいです。

2010-09-11

jQuery、Form Plugin を使って、file upload する、ファイルを選択したら自動でその画像が表示されるようにする

まあ、とにかくファイルアップロード系のプラグインってのはあるようでないようで、これというのがない。
もう探し疲れたよ、ほんとに。
ファイルのアップロードのインターフェースはこういうのがいいんだ、っていう部分で人によって意見が違いすぎるんだよな、結局。
だから作者によって考えてることが全然別々で。
ということで、一番シンプルで、機能がなくて、ただアップロードできるだけ、というものを考えてみた。
まず、HTML側はこうする。
<form id="upload" method="post" action="form.php" >
  <input type="file" name="sample" id="sample" />
  <img id="sample_img" src="img/sample.jpg">
</form>
sample.jpg はデフォルトの画像で用意してあって、最初から表示されているんだけど、ファイル参照で別の画像を選べばそれに置き換わる、というようなものを作ろうってことだ。

まず、
http://www.malsup.com/jquery/form/
こちらから Form Plugin を入手して、読み込む。
<script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="js/jquery.form.js"></script>
で、ファイル選択したらファイルアップロードをする php に処理を渡せばいいので、
$(document).ready(function() {
  $("input#sample").change(function(){
    var options = { 
      url:       'upload.php',
      dataType:  'xml', 
      success:   processXml 
    }; 
    $('form#upload').ajaxSubmit(options); 
  });
});
こんな感じに書く。中味は全部 form プラグインの記述。dataType で xml を選ぶのが味噌だ。
json にすると動かないので注意。
ajaxSubmit で form の action 先を upload.php に変更して、自動でサブミットしてくれる。

ということなので upload.php を書く。
いや、これはまあ、普通に $_FILES を使ったファイルアップロード処理を php で書けばいいだけなので詳細は略。
普段自分が使ってるやつでやってくれ。
なんやかんや処理した後に、
header('Content-type: text/xml'); 
echo <<<END
<root>
  <status>$status</status>
  <file_path>$file_path</file_path>
  <emes>$emes</emes>
</root>
END;
というように xml で結果を返す。上記では $status に成功したかどうか、$file_path にアップロードした画像を保存したファイルのパス、$emes にエラーメッセージ、を格納しているつもりである。

で、アップロードに成功すると
success:   processXml 
とあるように、processXml に処理が渡るので、processXml をごりごり書く。
function processXml(responseXML) {
  var status = $('status', responseXML).text();
  if (status == 'success') {
    var file_path = $('file_path', responseXML).text(); 
    $('#sample_img').attr('src', file_path);
  } else {
    var emes = $('emes', responseXML).text();
    alert(emes);
  }
}
xml のデータは
$('status', responseXML).text()
というような書き方で取得できる。
ので、jQuery の .attr を使って画像のパスを書き換える。
$('#sample_img').attr('src', file_path);
これでオッケーだ。
あとは好きなようにいじればよしと。

2010-08-31

jQueryでFCKEditorのvalueを取り出す

$.ajaxとかでpostしてたらFCKEditor部分の値が渡ってなかったので仕方なく。

$('iframe').contents().find('td#xEditingArea iframe').contents().find('body').html();

めちゃめちゃじゃないですか!

2010-08-12

window.openとかlocationとかsubmitとか、普通の昔から使ってたJavascriptをjQueryで書く

まあ、昔はみんな button に onclick つけたり、画像にjavascriptのリンク貼ったり、
して「戻る」ボタンにてた訳で。
大体もうデザイナーさんから上がってきた時点で、
<a href="javascript:history.back()"><img src="images/B.gif" width="170" height="36" alt="戻る" /></a>
こんなんになってたんだけども。
jQuery とか使い始めるとこういうソースがどうにも耐えられない。
html ソースの中に Javascript のコードが書いてあるのが許せない状態になってしまう。
ということで、これまではよく見かけたコードを書き換えるにはどうしたらいいか調べてみた。

まずはボタンクリックで特定のURLにリダイレクトさせるやつ。

こんなボタンがあるとして、
<input type="button" name="back" value="back" />
こんな感じっすか。
$(':button[name=back]').click(function() {
    window.location = 'index.php';
});
次、ボタンクリックで window.open サイズ指定も付けて。
$(':button[name=back]').click(function(e) {
    window.open('index.html','_blank',
        'width=360, height=500, menubar=no, toolbar=no, scrollbars=no');
    e.preventDefault();
    return false;
});
最後、ただ単にサブミットする。ついでに hidden の値を Javascript で指定。
こんなフォームがあるとして、
<form action="index.php" method="post">
<input type="hidden" name="key" value="" />
<input type="submit" />
</form>
こんな感じで。
$(':submit').click(function() {
    $(':hidden[name=key]').val('123');
    $('form').submit();
});
あと、追記だけども、HTML側で指定したフォームの飛び先(action)とは全然別のアドレスに飛ばしたい時もあるよね。
こんなんなってて、
<form action="index.php" method="post">
<input type="button" id="preview" value="preview" />
<input type="submit" />
</form>
プレビューボタンを押したら、preview.phpに飛ばしたい、っていう場合、
ついでにpostじゃなくてgetにしたい、っていう場合、
こうなる。
$(':button#preview').click(function() {
    $('form').attr('action', 'preview.php');
    $('form').attr('method', 'get');
    $('form').submit();
});

2010-08-11

phpとMySQLで、住所から郵便番号を検索する(逆引き)

大昔に会社のホームページ用に用意した原稿だけど、このまま埋もれてるのも何なのでこちらに載せておきます。

今回は、リアルな住所から郵便番号を検索するプログラムについて考えてみることにします。
例えば「札幌市清田区美しが丘一条N丁目M-1」というような、都道府県の部分だけを除いてあとは全部ひとつつながりの住所があるとする。
これがまあ、15万件くらいですかね、それくらいの件数の住所データを渡されて、「これの郵便番号を出せ」と、そういう指令が出たとする訳です。
さあどうしようかと。
話を簡単にするために、その住所データは以下のようなMySQLデータベースのテーブルに入っているとします。
CREATE TABLE users (
    id int(11) NOT NULL auto_increment,
    name varchar(100),
    address varchar(255),
    zip varchar(7),
    PRIMARY KEY (id)
)DEFAULT CHARSET=utf8;
住所はaddressのカラムで、郵便番号はzipですが、今はzipには何も入っていません。

で、まず考えるのは

日本郵便 http://www.post.japanpost.jp/zipcode/index.html

のページへ行って、郵便番号のデータをもらってくることです。ここに「郵便番号データのダウンロード」というリンクがありますのでクリックして、「全国一括」というやつをダウンロードします。
問題はその中味なのですが、詳しくは

郵便番号データの説明 http://www.post.japanpost.jp/zipcode/dl/readme.html

を読んでもらうことにして、この中で使えそうなのは8番目の市区町村名と9番目の町域名、そしてももちろん3番目の郵便番号(7桁)になりそうです。
その3つだけをインポートしてもよいのですが、それも面倒なので、せっかくのCSV形式のファイルですからそのままMySQLのLOAD DATAを使って一気に入れることにします。
最初に、以下のようなテーブルを作っておきます。
CREATE TABLE zips (
    zkd_code VARCHAR(255),
    kyu_zip VARCHAR(255),
    zip VARCHAR(255),
    ken_yomi VARCHAR(255),
    siku_yomi VARCHAR(255),
    cyou_yomi VARCHAR(255),
    ken VARCHAR(255),
    siku VARCHAR(255),
    cyou VARCHAR(255),
    fg_a INT,
    fg_b INT,
    fg_c INT,
    fg_d INT,
    fg_e INT,
    fg_f INT
);
次に、上記日本郵便のサイトでダウンロードした郵便番号データを適当なファイル名で(ここではyubin.csv)、サーバーに保存して、MySQLのコンソール画面から、
mysql> LOAD DATA LOCAL INFILE "/home/myhome/yubin.csv" INTO TABLE zips FIELDS TERMINATED BY ',' ENCLOSED BY '"';
と、やります。
これ一発で郵便番号データがzipsテーブルに入ります。

以上でデータは揃いましたので、usersテーブルの
+-------+--------------------------------------------------+
| id    | address                                          |
+-------+--------------------------------------------------+
|     1 | 札幌市清田区美しが丘一条N丁目M-1             |
+-------+--------------------------------------------------+
というデータと、zipsテーブルの
+---------+--------------------+--------------------+
| zip     | siku               | cyou               |
+---------+--------------------+--------------------+
| 0040811 | 札幌市清田区       | 美しが丘一条       |
| 0040813 | 札幌市清田区       | 美しが丘三条       |
| 0040812 | 札幌市清田区       | 美しが丘二条       |
| 0040815 | 札幌市清田区       | 美しが丘五条       |
| 0040814 | 札幌市清田区       | 美しが丘四条       |
+---------+--------------------+--------------------+
というようなデータを見比べて、郵便番号を照合するプログラムを組めばよいわけです。

zipsの方は市区町村名と町域名が別々のカラムになっていますので、 CONCAT を使って合体させて処理します。
一方リアルの住所には番地とか何丁目とかいうのが最後の方についていますので、それをどんどん削除していって、それが、 CONCAT で合体した市区町村名と町域名にマッチしたらその時の郵便番号を取得すればよい、という訳です。
文字を後ろから削除するには LEFT を使います。
SELECT 
    zip, siku, cyou 
FROM 
    zips
WHERE 
    CONCAT(siku, cyou) = LEFT('札幌市清田区美しが丘一条N丁目M-1', 10)
このクエリーはヒットしませんが、 LEFT の最後の数字を1ずつ増やして消していけば、いつかはヒットするはずです。

ということで、まずは対象となる住所データとその文字列長さを users テーブルから取得します。
$query = "SELECT address, CHAR_LENGTH(address) as len FROM users";
$result = mysql_query($query);
if ($result) {
    $n = mysql_num_rows($result);
    if ($n){
        while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
            for($i=$row['len'];$i>0;$i--){
                // ここに郵便番号テーブルとの照合を入れる
            }
        }
    }
}
forループを使って、最初に数えた文字列長さから始めて1文字すつ削っていこうという作戦です。
そして forループの中に以下の照合処理を入れます。
$query2 = "SELECT zip FROM zips WHERE CONCAT(siku, cyou) = LEFT('$address', $i)";
$result2 = mysql_query($query2);
if ($result2) {
    $n2 = mysql_num_rows($result2);
    if ($n2){
        while ($row2 = mysql_fetch_array($result2, MYSQL_ASSOC)) {
            echo $row2['zip'] . "\t";
        }
        break;
    }
}
breakを使って、マッチした時点で forループを抜けます。

で、これでテストしてみるとすぐに分かりますが、処理が大変に遅いです。私の試算では15万件のデータを処理するのに、18時間かかると出ました(笑)。
これはおそらく CONCAT でカラムを合体させてるのが原因でしょう。
ということで、市区町村名と町域名をバラバラにしておく意味はないので、カラムとして合体させます。
ALTER TABLE zips ADD COLUMN sikucyou VARCHAR(52);
UPDATE zips SET sikucyou=CONCAT(siku, cyou);
最初に新しいカラムを追加して、UPDATE をかけています。
ついでにインデックスも追加しておきます。
ALTER TABLE zips ADD INDEX (sikucyou);
PHPソース内のSQLの方も修正します。
SQLの CHAR_LENGTH もめためたに遅いのでPHP側での処理に変更します。
$query = "SELECT id, address FROM users";
$result = mysql_query($query);
if ($result) {
    $n = mysql_num_rows($result);
    if ($n){
        while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
            $address = mysql_real_escape_string($row['address']);
            $len = mb_strlen($row['address']);
            $zip = '';
            for($i=$len;$i>0;$i--){
                $query2 = "SELECT zip FROM zips WHERE sikucyou = LEFT('$address', $i)";
                $result2 = mysql_query($query2);
                if ($result2) {
                    $n2 = mysql_num_rows($result2);
                    if ($n2){
                        while ($row2 = mysql_fetch_array($result2, MYSQL_ASSOC)) {
                            $zip = $row2['zip'];
                        }
                        break;
                    }
                }
            }
            $query3 = "UPDATE users SET zip='$zip' where id='{$row['id']}'";
            $result3 = mysql_query($query3);
        }
    }
}

実行してみましょう。私の環境では15万件の住所の照合は、2分で終わりました。
問題はこの方法でやった郵便番号が本当に全部合っているのか、ということですが、全体の約14%は郵便番号を照合することができません。
リアルの住所自体には大字(おおあざ)などの表記のゆれがあるのと、それと、この方法では町域が「以下に掲載がない場合」の判別ができないからです。
照合できなかった住所でGoogle郵便番号検索をしてみましょう。
これはちゃんとヒットしますが、
郵便番号 渋谷区桜丘町
これはヒットしません。
郵便番号 千代田区丸の内
Google先生でも無理なんです。

2010-08-07

GROUP BYを使った表の数値の合計あって、その合計が10個以上とかいう条件で抽出したい時にHAVING句を使う

例えばメンバーIDがあって、それに紐付いたポイント数が複数保存してあるようなテーブルがあるとする。
 member_id | point
-----------+-------
         4 |     2
         4 |     2
         4 |     3
         4 |     3
         4 |     3
         4 |     1
         4 |     3
         4 |     2
         8 |     2
         8 |     0
         4 |     1
         8 |     3
         4 |     6
         8 |     0
これをGROUP BY を使って合計を出すと、
SELECT member_id,SUM(point) AS sum FROM points 
GROUP BY member_id;

 member_id | sum
-----------+-----
         4 |  43
         8 |   7
こんな結果になるわけですが、この結果表をさらに、合計が10個以上、とかそういう条件で絞り込みたい訳です。
もしかしてこんなのあり?
SELECT member_id, SUM(point) AS sum FROM points 
WHERE sum > 10 
GROUP BY member_id;
とかやってみましたが、これは、
ERROR:  column "sum" does not exist
になります。だめです。
ということで、HAVING句を使って、
SELECT member_id, SUM(point) AS sum FROM points 
GROUP BY member_id
HAVING SUM(point) > 10;
というように書きます。
結果。
 member_id | sum
-----------+-----
         4 |  43

Javascriptで配列の後ろからn個だけ要素を削除する

だいたいどこのサイトでも要素の削除にはspliceを使え、マイナスにすれば後ろから削除できる、と書いてあるんだが、そのマイナスの意味がよく分からないので実験してみた。
基本的にはこんな風にしておいて、アラートで結果を確認することとしよう。
items = new Array("1", "2", "3", "4", "5", "6");
items.splice(1, 2);
for(i = 0; i < items.length; i++){
   alert(items[i]);
}
これの結果は、
("1", "4", "5", "6")
じゃあこうすると、
items.splice(-1, 2);
こうなる。
("1", "2", "3", "4", "5")
どうもマイナスにすると、一番最後から数えるってだけのようです。
つまり、一番最後から3個削除したいんだったら、
items.splice(-3, 3);
こうすればいいと。これを
items.splice(-3);
こう書くと、Firefoxやchoromeでは同じく3個削除で動くがIEでは全く削除されない。

2010-07-26

phpでカンマ区切りのcsvを出力する

とりあえず、データの中に改行は含まれない、という前提にしてしまえば比較的楽なのではないかと。 いうことで、暫定版ですが。。
function add_comma($str) {
    if (preg_match('/"/', $str)) {
        $str = preg_replace('/"/', '""', $str);
    }
    if (preg_match('/[,"]/', $str)) {
        $str = '"' . $str . '"';
    }
    return $str;
}

2010-06-25

smartyの{section}関数に渡す配列は0から順番になってないと表示されない

smartyの{section}関数に渡す配列は0から順番になってないと表示されない 今見た感じバージョンはSmarty 2.6.18だけど。 以下のような{section}を使ったループ表示で、
{section name=key loop=$message}
 {$message[key]|escape}<br />
{/section}
php側を、
$message = array('123','456','abc');

$o_smarty -> assign("message", $message);
とすると、ちゃんと表示されるが、
$message[3] = '123';
$message[7] = '456';
$message[] = 'abc';

$o_smarty -> assign("emes", $emes);
とすると、何も表示されない。 悩まず、{foreach}に変更すべし。

2010-05-06

jQueryで作るタブのメニュー画面がリンクから飛んできた時も選択した状態にしたい

今開発してるシステムでちょっとおもしろそうな題材があったので紹介しておきます。
ソース全部載せると分かりづらいので一部だけで概念的に説明しますが、これはまあ、普通にレフトメニューがあって、そしてメインの部分にはタブでメニューを選択できるようなレイアウトの画面だと思って下さい。
レフトメニューはこんな感じ
<div class="left">
    <p>メニュー</p>
    <ul>
        <li><a href="sample.html#menu1">menu1</a></li>
        <li><a href="sample.html#menu2">menu2</a></li>
        <li><a href="sample.html#menu3">menu3</a></li>
        <li><a href="sample.html#menu4">menu4</a></li>
        <li><a href="sample.html#menu5">menu5</a></li>
    </ul>
</div>
メイン部分のタブはこんな感じ
<div class="menu-tab">
    <ul>
        <li><a href="#menu1" class="selected">menu1</a></li>
        <li><a href="#menu2">menu2</a></li>
        <li><a href="#menu3">menu3</a></li>
        <li><a href="#menu4">menu4</a></li>
        <li><a href="#menu5">menu5</a></li>
    </ul>
</div>
そして、このメイン部分のタブ表示の下に、以下のような、各タブをクリックした時に表示すべき内容を menu1 から menu5 まで全部並べておきます。
<div class="menu">
    <div id="menu1" class="list">
        menu1の内容
    </div>
    <div id="menu2" class="list">
        menu2の内容
    </div>
    <div id="menu3" class="list">
        menu3の内容
    </div>
    <div id="menu4" class="list">
        menu4の内容
    </div>
    <div id="menu5" class="list">
        menu5の内容
    </div>
</div>
これを jQuery を使って、menu2 のタブをクリックした時は <div id="menu2"> の内容だけを表示させ、かつ、
<a href="#menu2"> のタブには選択した状態のクラス(selected)をあてたい、しかもレフトメニューからのクリックでも同じ動きができて、レフトメニューはサイト内の他のページにもそのまま(同じまま)設置されるので、他のページから、
<a href="sample.html#menu2"> というリンクで飛んできた時にも menu2 の内容を表示したい、ということなんですね。

んで、最初に送られてきたソースがこんな感じでした。
$("div.list:not("+$(".menu-tab li a.selected").attr("href")+")").hide();
$(".menu-tab li a").click(function(){
    $(".menu-tab li a").removeClass("selected");
    $(this).addClass("selected");
    $("div.list").hide();
    $($(this).attr("href")).show();
    return false;
});
1行目で、クラスが selected になっている(=つまり選択状態)のタブの href 属性を取得して(この場合は #menu1 )、それ以外の $("div.list")、すなわち各メニューの内容を隠しています。
2行目でタブのクリックを取得して、クリックされたタブについてあれこれ処理しています。
3行目は全てのタブから selected クラスの除去。
4行目は選択されたタブに selected クラスを付加。
5行目は各メニューの内容を一旦全部隠して。
6行目でクリックされたタブの href 属性と同じ id のメニューの内容を表示。

このままでは確かにレフトメニューからクリックされた時も、このページに他のページからリンクで飛んできた時も動きません。
ということで、どうやって、<a href="sample.html#menu2"> の#以下の部分を取得するか、なんですが、とりあえず
location.hash
で取得できる、ということです。
あと、jQueryの .attr() の場合は単に、
.attr("hash")
でいけると。
今回の場合基本的に #menu2 という文字列がキーになっていて、あとはタブ部分のオブジェクトをなにがしか操作すればよさそうなので、メインの部分はこんな感じで。
$(function(){
    var selected_tab = location.hash;
    var link_tab = $(".menu-tab li a");

    tabchange(selected_tab, link_tab);

    $(".menu-tab li a, .left li a").click(function(){
        tabchange($(this).attr("hash"), link_tab);
    });
});
tabchange という function を別途用意することにして、それに #menu2 という文字列と、タブ部分のオブジェクトを渡してなんとかすることにします。
7行目は、タブをクリックしたか、あるいはレフトメニューをクリックしたか、で動かして、クリックしたリンクの .attr("hash") を渡す処理にしています。
ということで、function tabchange はこんな感じで。
function tabchange(selected_tab, link_tab){
    link_tab.removeClass("selected");
    link_tab.each(function(n){
        if ($(this).attr("hash") == selected_tab){$(this).addClass("selected");}
    });
    $(selected_tab).siblings().hide();
    $(selected_tab).show();
}
each とか、siblings とか使っちゃってますが、やってる内容は元々のやつと同じです。
一応完成っす。

2010-04-17

DISTINCT と ORDER BY を一緒に使う時の、PostgresとMySQLの違い

今やってるやつで、出てきた現象なのでちょっとメモ。
例えば items というようなテーブルがあって、id と name がある。
 id |  name
----+--------
  1 | test
  2 | sample
で、この items には複数のカテゴリーとサブカテゴリーが紐づけられている。
紐づけ用のテーブルは items_categories というようなもので、こんな感じになっている。
 item_id | category_id | sub_category_id | orders
---------+-------------+-----------------+--------
       1 |           1 |               1 |      2
       1 |           1 |               2 |      2
       2 |           1 |               2 |      1
アイテム1はサブカテゴリー1と2に、アイテム2はサブカテゴリー2に紐付いていて、どちらもカテゴリーは1だが、
そのカテゴリー1の中での並ぶ順番が orders ということで設定されている。
アイテム2が順番は1、アイテム1が2番と。

で、こいつをまず順番関係なしに、join してみると
SELECT ic.item_id, i.name 
FROM items_categories ic 
INNER JOIN items i ON ic.item_id=i.id
WHERE ic.category_id=1
こうなる。
 item_id |  name
---------+--------
       1 | test
       1 | test
       2 | sample
orders 順にしたいので、
SELECT ic.item_id, i.name 
FROM items_categories ic 
INNER JOIN items i ON ic.item_id=i.id
WHERE ic.category_id=1
ORDER BY ic.orders
とすると、
 item_id |  name
---------+--------
       2 | sample
       1 | test
       1 | test
これは動く。item_id が重複してるので、distinct を使うと、
SELECT DISTINCT ic.item_id, i.name 
FROM items_categories ic 
INNER JOIN items i ON ic.item_id=i.id
WHERE ic.category_id=1
ORDER BY ic.orders
Postgresの場合、エラーになる。
ERROR:  for SELECT DISTINCT, ORDER BY expressions must appear in select list
MySQLの場合、
 item_id |  name
---------+--------
       2 | sample
       1 | test
出る。
Postgresでも
SELECT DISTINCT ic.item_id, i.name, ic.orders 
FROM items_categories ic 
INNER JOIN items i ON ic.item_id=i.id
WHERE ic.category_id=1
ORDER BY ic.orders
とすればちゃんと出る。
 item_id |  name  | orders
---------+--------+--------
       2 | sample |      1
       1 | test   |      2
そりゃ、おかしいのはMySQLでしょう。この場合。

2010-04-13

デザイナーが作った.htmlファイルを一括して.tplファイルにする

find ./ -name '*.html' | while read line; do
mv $line `echo $line | sed -e 's,\.html$,.tpl,'`
done
いや、理解はできてない。。

2010-04-03

jQueryのWYSIWYGエディターでkeyupとかkeypressのイベントを取り出す

今週ずーっとやってた不毛なjQueryアプリですが、一番はまってたWYSIWYGエディター部分についてまとめておきます。
WYSIWYGエディターというと世間ではFCKeditorが有名ですが、まあ、あれはちょっと重いんじゃない?
ってことで、jQueryのプラグインで探す訳ですが、どーもね、これっていうのがない訳で。

最初に使ってみたのがこれ
LIGHTWEIGHT RICH TEXT EDITOR (RTE / WYSIWYG) PLUGIN FOR JQUERY

使い方はこんな感じ
<textarea name="info1" cols="100" rows="10"  class="rte1"></textarea>

<script type="text/javascript">
$(document).ready(function() {
 $('.rte1').rte({
  css: ['default.css'],
  controls_rte: rte_toolbar,
  controls_html: html_toolbar
 });
});
</script>
jquery.rte.tb.js にツールバーの設定がまとめられてるのでそれを編集する。
firebugで見てるとキーを入力する度に
!keyup イベントでは charCode プロパティの値には意味がないため使用すべきではありません。
の警告が出るのがいまいちです。

次がこれ
JHTMLAREA - WYSIWYG HTML EDITOR FOR JQUERY

使い方はこんな感じ
<script type="text/javascript">
$(function() {
 $("#info").htmlarea({
  toolbar: [
   ["bold", "italic", "underline", "|", "forecolor"],
   ["p", "h1", "h2", "h3", "h4", "h5", "h6"],
   ["link", "unlink", "|", "image"],
   [{
    css: "custom_disk_button",
    text: "Save",
    action: function(btn) {
     alert('SAVE!\n\n' + this.toHtmlString());
    }
   }]
  ],
  loaded: function() {
   //
  }
 });
});
</script>


<textarea id="info" cols="50" rows="15"></textarea>
loaded とかでエディターが起動した後になにか処理を入れられる。
ドキュメントがあまり詳しくなくていまいちですかねー。
Microsoft Public License というあまり聞いたことのない?ライセンスです。

最後にこれ
HTMLBOX

使い方はこんな感じ
<textarea id='hb'></textarea>

<script language="Javascript" type="text/javascript">
var hb = $("#hb").htmlbox({
    buttons:[
      ["separator_dots","bold","italic","underline"]
 ],
 icons:"default",
 skin:"green"
});
</script>
一応PDFのドキュメントがあるんですが、どうも説明が足りなくて使い方が分からない。
UndoRedoManager plugin とか。
おもしろそうなんだけど、さっぱりわからんのだー。

ま。最後のが一番よさそうですけどね。

で、今回の課題はこのエディターで文字を入力してる最中にそのイベントを取りたいってことなんだけども、
そもそもこういうエディターというのはフォームの textarea に設定するけれども、textarea そのものは非表示にして、その近くに iframe をつっこんで
<div>
 <iframe style="height: 305px; width: 415px;">
  <html>
   <head>
   </head>
   <body>
   </body>
  </html>
 </iframe>
</div>
みたいにして。

でhtmlを表示させて機能しているようで、DOMがどうなってるんだかさっぱり分からない。
どういうからくりになってるんだか・・・。

で、結論から言うと、
編集中の文字とかを取り出すには
$('iframe').contents().find('body').html()
keyupのイベントを取り出すには、
$('iframe').contents().keyup(function(){
 // なにか処理
}
でいけます。
試しに文字を入力する度にその中の文字数を表示するというのをやってみると、
<script language="Javascript" src="jquery-1.3.2.min.js" type="text/javascript"></script>
<script language="Javascript" src="htmlbox.min.js" type="text/javascript"></script>
<script language="Javascript" src="formState.js" type="text/javascript"></script>

<textarea id='hb'></textarea>
<script language="Javascript" type="text/javascript">
var hb = $("#hb").htmlbox({
 buttons:[
  ["separator_dots","bold","italic","underline"]
 ],
 icons:"default",
 skin:"green"
});

$(function() {
 $('iframe').contents().keyup(function(){
  $('#count').val($('iframe').contents().find('body').text().length);
 });
});
</script>
<input type="text" id="count" />
こんな感じになります。

2010-03-21

[見積システム:01]サイトマップ

最初にサイトマップくらい作っておこうってことで、作った。
かなりメニューが多くなってしまったが・・・。
見積もり編集のパターン選択のあたりのインターフェースをどうするか、まだ考えていないのでそこはちょっとまた後で直すことにして、とりあえず、おおまかにはこんな感じで行こう。

2010-03-20

gitを使ってデザイナーとの並行作業でテンプレートを修正する

テンプレートで修正が入った場合について、
例えば自分が作った index.tpl がこんなようなソースだったとする。
<div>
<h1>Colored Pencils</h1>
</div>
まずはコミットする。
$ git add index.tpl
$ git commit -m 'first commit'
ここで一旦リリースするが、その時にブランチを作っておく
$ git branch design_fix
その後なにか修正する。
例えば、以下のようにエラーメッセージの表示を追加した。
<div>
<h1>Colored Pencils</h1>
{$message|escape}
</div>
それをコミットする
$ git commit -a -m 'add message'
それを知らないデザイナーからファイルが届いた。
divにclass指定が追加になってる。
<div class="main">
<h1>Colored Pencils</h1>
</div>
作っておいたデザイナー用のブランチに移動して、
$ git checkout design_fix
この状態で、デザイナーが作った index.tpl をコピーしてからコミットする。
$ git commit -a -m 'design chenged'
再びマスターに戻り、マージする
$ git checkout master
$ git merge design_fix
以下のようにマージされた。
<div class="main">
<h1>Colored Pencils</h1>
{$message|escape}
</div>
ここでまたすかさず次のブランチを作っておくといいかもしれない。

問題なのは、デザイナーの修正が、以下のようなものだった場合、
<div>
<h1>Colored Pencils</h1>
<h2>12 colors</h2>
</div>
マージでエラーになる。
Auto-merging index.tpl
CONFLICT (content): Merge conflict in index.tpl
Automatic merge failed; fix conflicts and then commit the result.
そのindex.tplを見るとこんなんなってる。
<div>
<h1>Colored Pencils</h1>
<<<<<<< HEAD:index.tpl
{$message|escape}
=======
<h2>12 colors</h2>
>>>>>>> design_fix:index.tpl
</div>
message の行と h2 の行とどっちが上でどっちが下か分からないってことだろう。
以下のように順番を決めて編集保存して、
<div>
<h1>Colored Pencils</h1>
<h2>12 colors</h2>
{$message|escape}
</div>
コミットする。
$ git commit -a -m 'design fix'
無事マージされた
--- a/index.tpl
+++ b/index.tpl
@@@ -1,5 -1,5 +1,6 @@@
  <div>
  <h1>Colored Pencils</h1>
+ <h2>12 colors</h2>
 +{$message|escape}
  </div>

2010-03-18

MacPortsでOSXにphp5.3を入れる

OSXに、php5.3を入れようということで、
port installed
で見たところ、apache2とかはもうインストール済みになってるみたいだったので、もう一回以下のコマンドでphpのインストールに挑戦。
sudo port -d install php5 +apache2 +macox +mysql5 +pear +postgres +sockets +sqlite
最後に出た以下のメッセージを完全に見逃す
To customize php, copy
/opt/local/etc/php5/php.ini-development (if this is a development server) or
/opt/local/etc/php5/php.ini-production (if this is a production server) to
/opt/local/etc/php5/php.ini and then make changes.

If this is your first install, you need to activate PHP in your web server.

To enable PHP in Apache, run
  cd /opt/local/apache2/modules
  /opt/local/apache2/bin/apxs -a -e -n "php5" libphp5.so
こうして入れたシステム一式は、OSXの
/opt/local
の下に入るようです。
PHPのバージョン5.3.2になっている。
$ php -v
PHP 5.3.2 (cli) (built: Mar 18 2010 03:49:53) 
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
pearも入っている。
$ pear -V
PEAR Version: 1.9.0
PHP Version: 5.3.2
Zend Engine Version: 2.3.0
でアパッチを起動したいということで、
sudo launchctl load -w /Library/LaunchDaemons/org.macports.apache2.plist 
とりあえずこうするだけでapache動いた。
が、phpが動いていない。 そこで、以下を実行。
sudo cp php.ini-development php.ini
sudo /opt/local/apache2/bin/apxs -a -e -n "php5" libphp5.so
アパッチのhttpd.confに
以下を追加。
Include conf/extra/mod_php.conf
以下を付け足し。

    DirectoryIndex index.html index.php

で、
sudo /opt/local/apache2/bin/apachectl restart
したら、phpもオッケーに。
なったがな、しかし。だな。

2010-03-16

XAMPPのインストールと設定

Windowsのphpのバージョンを5.3.2にアップしようとしたらば、apacheまで動かなくなったので、もうあきらめて新規にXAMPPでインストールすることに。

apache friends

ここから XAMPP Windows版 1.7.3 をダウンロードしてインストール。

最初に環境変数に書かれている既存のMySQLとかphpのパスを消去してからやった方がよかったみたいですね。
既存のそれらのプログラムとごっちゃになってしまって、その修正に丸一日かかってしまった。
そもそもMySQLがサービスとして動いていたようで、XAMPPはそっちを見に行ってしまった模様。
マニュアルには最初に「セキュリティ」からMySQLのrootのパスワードを設定せよ、と書いてあるが、既存のMySQLを見に行ってるので既にパスワードがある状態になっている。
こりゃおかしい、ということで既存のMySQLのサービスを無効にして、環境変数の方は
C:\xampp\mysql\bin;
に書き換え、これでようやく正常に戻る。
改めて「セキュリティ」から進んで、rootのパスワードを設定。
あとphpMyAdminのDB接続設定をするために C:\xampp\phpMyAdmin\config.inc.php にrootのパスワードを設定
/* Authentication type and info */
$cfg['Servers'][$i]['auth_type']            = 'config';
$cfg['Servers'][$i]['user']                 = 'root';
$cfg['Servers'][$i]['password']             = 'password';
$cfg['Servers'][$i]['AllowNoPassword']      = true;
それからMySQLの文字コード関係設定。C:\xampp\mysql\bin\my.ini を開いて一番下に、
[mysqld]
skip-character-set-client-handshake
default-character-set = utf8
character-set-server = utf8
collation-server = utf8_unicode_ci
init-connect = SET NAMES utf8

[mysql]
default-character-set=utf8
を追加。

次にapacheの設定。
C:\xampp\apache\conf\httpd.conf を開いて、Directory の
<Directory />
    Options FollowSymLinks
    AllowOverride none
    Order deny,allow
    Deny from all
</Directory>
を以下に変更
<Directory />
    Options FollowSymLinks
    AllowOverride all
    Order deny,allow
    Deny from all
</Directory>
ついでにバーチャルホストの設定。C:\xampp\apache\conf\extra\httpd-vhosts.conf を開いて、
以下のコメントアウトを外す
NameVirtualHost *:80
一番下に以下を追加
<VirtualHost *:80>
    ServerAdmin someone@somedomain
    DocumentRoot "D:/project"
    ServerName project
    ErrorLog "logs/project-error.log"
</VirtualHost>
<Directory "D:/project">
    Options FollowSymLinks
    AllowOverride all
    Order deny,allow
    Allow from all
</Directory>
ここで、最後の Allow from が all になってないと見られないので注意。
さらに、C:\Windows\System32\drivers\etc\hosts を開いて、
127.0.0.1       localhost
127.0.0.1       project
を追加。
あと php.ini のこのヘンを修正
[mbstring]
mbstring.language = Japanese
mbstring.internal_encoding = UTF-8
mbstring.http_input = pass
mbstring.http_output = pass
mbstring.encoding_translation = Off
mbstring.detect_order = UTF-8,SJIS,EUC-JP,JIS,ASCII
mbstring.substitute_character = none;
mbstring.func_overload = 0
最後にpearも既存のphpのパスが悪さしてへんてこりんな設定になっていたので、C:\xampp\php に go-pear を置いて、再インストール
php go-pear.php
これはコマンドプロンプトを管理者モードで実行しないとだめ。
あ、そうそう、あと、こうやってインストールした状態では、apacheの mod_rewrite は最初から使えるようになってるし、phpの short_open_tag はOffになってるぞ。

2010-03-14

2つのsubmitボタンを横にまっすぐ並べる方法

まあ、よくある話なんですが、問い合わせフォームみたいなもので以下のようなボタンが2つ並んだソースがデザイナーさんから送られてきたりする訳です。
<div class="controls">
    <input type="button" value="戻る" />
    <input type="submit" value="送信" />
</div>

まあ、これはこれでそのままでもちゃんと横に並んでくれるし、普通はcssでセンタリングしてあったり、多少マージンがとってあったり、ひどいのになると、ボタンとボタンの間に全角スペース入れてそれで間隔を調整してたり(笑)、そんな感じだと思います。
ただ実際プログラムを入れていくと、これでは用をなさないわけで、以下のようなソースにしたいという場合が多々あります。
<div class="controls">
    <form action="form.php" method="post">
        <fieldset>
            <input type="hidden" name="mode" value="back" />
            <input type="submit" value="戻る" />
        </fieldset>
    </form>
    <form action="form.php" method="post">
        <fieldset>
            <input type="hidden" name="mode" value="send" />
            <input type="submit" value="送信" />
        </fieldset>
    </form>
</div>
「戻る」時と「送信」する時とでモードを切り替えて処理したいような場合ですね。
これの見た目はこうなってしまいます。

で、こういう時どうしたら、簡単に直せるか、ということですね。
まずこれはどうみてもformとfieldsetがブロック要素で右端まで行っちゃってるので、
form, fieldset {
    display: inline;
    border: none;
}

とします。あとは適当にマージンとかを調整します。
form, fieldset {
    display: inline;
    border: none;
    margin: 0px;
    padding: 0px;
}

一応、デザインさんに修正依頼する時間もない時のために。

2010-03-13

jQuery1.2の時のバグ

AJAXで生成したSELECTのoptionでselectedにした値をセレクトしてくれない
form, fieldset {
    display: inline;
    border: none;
    margin: 0px;
    padding: 0px;
}

if (isset($emes)) {
    $smarty->assign('emes', $emes);
    $smarty->display('form/form.tpl');
    exit;
}
  • test

ブログ アーカイブ

このブログを検索

Powered by Blogger.

ラベル

php (17) jQuery (13) OSX (10) MySQL (8) Javascript (7) Postgres (7) port (7) apache (6) Java (3) Smarty (2) html (2) pear (2) FCKEditor (1) XAMPP (1) css (1) git (1) perl (1) ruby (1)

Facebookバナー