Adobe
製品
Acrobat
Creative Cloud
Creative Suite
Digital Marketing Suite
Digital Publishing Suite
Elements
Photoshop
Touch Apps
その他の製品一覧
ソリューション
デジタルマーケティング
デジタルメディア
教育
金融機関
Web Experience Management
その他のソリューション
ラーニング サポート ダウンロード 会社情報
ご購入
アドビストア 安心のサポート& サービス
アカデミックストア 学生、教職員、個人向け
アドビライセンスストア 中小企業向け
ボリュームライセンスについて 企業、教育機関、官公庁向け
販売パートナー
キャンペーン情報
検索
 
情報 サインイン
ようこそ、 さん カート 注文状況 マイアカウント
マイアカウント
注文状況
アカウント情報の変更
コミュニケーションの設定を変更
サインアウト
サインインの目的 お客様のアカウントや体験版ダウンロード、製品の拡張機能、コミュニティエリアへのアクセスなどを管理するため
Adobe
製品 セクション ご購入   検索  
ソリューション 会社情報
サポート ラーニング
サインイン サインアウト 注文状況 マイアカウント
先行予約の提供開始予定日Date. 商品が発送されるまで、クレジットカードには課金されません。提供開始の予定日は変更される場合があります。 先行予約の提供開始予定日Date. ダウンロードの準備が整うまで、クレジットカードには課金されません。提供開始の予定日は変更される場合があります。
個数:
ご購入には学生・教職員個人版の購入資格の確認が必要です。
小計
カートの中身を見る
Adobe Developer Connection / Flashデベロッパーセンター /

新潟県弁護士会サイト:Flash+PHPでオリジナルCMSの実装

著者 漆原 尚氏

漆原 尚氏
  • 株式会社ポルトプラディア

作成日

23 June 2009

ページ ツール

Facebookでシェア
Twitterでツイート
LinkedInでシェア
ブックマーク
印刷

タグ

必要条件

ユーザーレベル

中級

サイト構築でFlashを使う大きな魅力の1つは、多彩な表現力です。しかし、更新頻度の高いコンテンツでは、Flashを取り入れるのは容易ではありません。Movable TypeやWordPressといった手軽に利用できるCMSが登場し、そうしたCMSとFlashを連携させるノウハウが確立されてはきたものの、プロジェクトの要件によっては合わないこともあります。

実際、弊社が最近手掛けた「新潟県弁護士会」サイトにおいて、更新頻度の高いコンテンツをFlashで作ることとなりました。いろいろと試行錯誤した結果、たどり着いたのはPHPを使ったオリジナルCMSです。本記事では、その試行錯誤の過程と、弊社オリジナルCMSの技術的なポイントを解説します。弊社の実装例はあくまでも1つの解でしかありませんが、みなさんの参考となればうれしいです。

「新潟県弁護士会」サイト

「新潟県弁護士会」サイト

新潟県弁護士会サイトについて

新潟県弁護士会サイトでは、当会の概要、法律相談の案内、イベントなど各種情報を提供しています。その中でも一番の特徴は、新潟県弁護士会に所属する弁護士を探せる「弁護士検索」機能です。名前、地域、取扱分野などから、希望する弁護士を探すことができます。この「弁護士検索」機能にフォーカスして解説していきます。

「弁護士検索」機能。条件を選んで検索を実行すると、該当する弁護士の結果として顔写真を付けたキャラクターが並び、キャラクターをクリックすると詳細情報が表示されます
「弁護士検索」機能。条件を選んで検索を実行すると、該当する弁護士の結果として顔写真を付けたキャラクターが並び、キャラクターをクリックすると詳細情報が表示されます
「弁護士検索」機能。条件を選んで検索を実行すると、該当する弁護士の結果として顔写真を付けたキャラクターが並び、キャラクターをクリックすると詳細情報が表示されます

クライアントから「弁護士検索」機能に関する主な要望として、以下の2点がありました。

  • クライアント側で弁護士情報を更新できるようにし、かつ更新頻度は高い
  • 各弁護士について扱う情報が多い(名前や事務所名など、20項目以上)

弁護士に関するサイトとなると、ただでさえ堅苦しい感じがします。そこに検索結果をテキストデータの一覧で表示させると、無機質で味気なく、利用者は弁護士をより一層遠い存在に感じてしまいそうです。そこで、サイトデザインだけでなく、検索機能でも親しみやすさを強調することをクライアントに提案しました。その提案は採用され、Flashを使って表現力豊かな検索機能を構築することになりました。

弁護士の顔写真を単純に表示するのではなく、弁護士をシンボル化した特徴のあるキャラクターデザインにし、さらにコミカルな動きつけることで、「身近な頼りになる弁護士」を演出しています。

Flashを使うことが決まったら、あとはどのようにCMSを実装するかです。開発当初、サーバ環境が未定でした。スペックの低いサーバとなった場合でも、動作・運営に支障がないように設計しなければなりません。また、サーバによってはデータベースを使用できない場合もあります。そのため、データソースをXMLとするCMSを構築することにしました。

Movable Typeの検討

その後、使用するサーバはPHPやMySQLを使える環境に決まったので、まず手始めにCMSとしてMovable Type 4を試してみました。クライアントは、Movable Typeの管理画面にあるブログ記事作成画面を使って弁護士情報入力を行うことになります。ただし、初期設定の入力項目では不十分なので、カスタムフィールド機能を使って、弁護士情報内容に合わせてカスタマイズしました。

いざ試験運用を行ってみると、カスタムフィールドを作りすぎたせいか(20項目以上)、動作速度が非常に遅くなっていました。また、現状のMovable Typeの管理画面UIでは、Movable Typeに不慣れなクライアントが扱いづらいため、管理画面のUIカスタマイズを試みるも、思うようにカスタマイズができませんでした。

こうした状況では、クライアントは安心して更新作業を行うことができません。そのため、CMSとしてMovable Typeを使用することは見送ることにしました。

PHPでオリジナルCMSを構築

次に検討したのが、PHPを使ってオリジナルCMSを構築することです。ゼロから作るという大変さはありますが、管理画面は分かりやすく、入力フォームもシンプルとなり、動作も快適なので、クライアント側での更新作業も無理なく行えます。以下は、実際の管理画面と入力フォームです。

オリジナルCMSの管理画面
オリジナルCMSの管理画面
オリジナルCMSの入力フォーム
オリジナルCMSの入力フォーム

このオリジナルCMSからXMLを出力します。以下は、XMLを書き出すPHPコードの例です。

$arraydata = mysql_query($sql, $db); while ($rowdata = mysql_fetch_array($arraydata)) {     $kana_index = mb_substr(mb_convert_kana(mb_convert_kana(mb_substr(EUCJP_to_UTF8($rowdata['bar_kana']), 0, 1), "h"), "H"), 0, 1);     fwrite($fp, "¥t".'<bar id="'.sprintf("%05d", $rowdata['id']).'" sex="'.$rowdata['bar_sex'].'" icon="'.$rowdata['bar_icon'].'" kana="'.make_kana_id($kana_index).'">'.PHP_EOL);     fwrite($fp, "¥t¥t".'<name>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['bar_name']), 'KVa')).'</name>'.PHP_EOL);     fwrite($fp, "¥t¥t".'<kana>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['bar_kana']), 'HVac')).'</kana>'.PHP_EOL);     fwrite($fp, "¥t¥t".'<email>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['bar_email']), 'rnas')).'</email>'.PHP_EOL);     fwrite($fp, "¥t¥t".'<career1>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['bar_career1']), 'KVa')).'</career1>'.PHP_EOL);     fwrite($fp, "¥t¥t".'<career2>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['bar_career2']), 'KVa')).'</career2>'.PHP_EOL);     fwrite($fp, "¥t¥t".'<career3>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['bar_career3']), 'KVa')).'</career3>'.PHP_EOL);     fwrite($fp, "¥t¥t".'<career4>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['bar_career4']), 'KVa')).'</career4>'.PHP_EOL);     fwrite($fp, "¥t¥t".'<career5>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['bar_career5']), 'KVa')).'</career5>'.PHP_EOL);     fwrite($fp, "¥t¥t".'<career6>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['bar_career6']), 'KVa')).'</career6>'.PHP_EOL);     fwrite($fp, "¥t¥t".'<career7>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['bar_career7']), 'KVa')).'</career7>'.PHP_EOL);     fwrite($fp, "¥t¥t".'<comment>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['bar_comment']), 'KVa')).'</comment>'.PHP_EOL);     fwrite($fp, "¥t¥t".'<elements id="');     $sql = "select C.id "             ."from M_TRATKIBNY T inner join M_CD C on T.cd1 = C.cd1 and T.cd2 = C.cd2 "             ."where T.id = '".$rowdata['id']."' "             ."order by C.id";     $arraydata2 = mysql_query($sql, $db);     if (mysql_num_rows($arraydata2) > 0) {         while ($rowdata2 = mysql_fetch_array($arraydata2)) {             fwrite($fp, ','.$rowdata2['id']);         }         fwrite($fp, ',');     }     fwrite($fp, '"></elements>'.PHP_EOL);          fwrite($fp, "¥t¥t".'<office areaid="'.$rowdata['office_area'].'">'.PHP_EOL);          fwrite($fp, "¥t¥t¥t".'<name>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['office_name']), 'KVa')).'</name>'.PHP_EOL);     fwrite($fp, "¥t¥t¥t".'<zip>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['office_postcode']), 'rnas')).'</zip>'.PHP_EOL);     fwrite($fp, "¥t¥t¥t".'<area>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['office_address1']), 'KVa')).'</area>'.PHP_EOL);     fwrite($fp, "¥t¥t¥t".'<address1>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['office_address2']), 'KVa')).'</address1>'.PHP_EOL);     fwrite($fp, "¥t¥t¥t".'<address2>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['office_address3']), 'KVa')).'</address2>'.PHP_EOL);     fwrite($fp, "¥t¥t¥t".'<tel>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['office_tel']), 'rnas')).'</tel>'.PHP_EOL);     fwrite($fp, "¥t¥t¥t".'<fax>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['office_fax']), 'rnas')).'</fax>'.PHP_EOL);     fwrite($fp, "¥t¥t¥t".'<hours>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['office_hours']), 'KVa')).'</hours>'.PHP_EOL);     fwrite($fp, "¥t¥t¥t".'<hp>'.trimFix(mb_convert_kana(EUCJP_to_UTF8($rowdata['office_url']), 'rnas')).'</hp>'.PHP_EOL);          fwrite($fp, "¥t¥t".'</office>'.PHP_EOL);          fwrite($fp, "¥t".'</bar>'.PHP_EOL); } fwrite($fp, '</bars>'.PHP_EOL); fclose($fp);

そして、以下のようなXMLが出力されます。

<bars>     <bar id="00083" sex="0" icon="0" kana="0">         <name>朝妻太郎</name>         <kana>あさづまたろう</kana>         <email></email>         <career1>新潟市出身</career1>         <career2>東北大学法学部卒業</career2>         <career3>平成20年 弁護士登録</career3>         <career4></career4>         <career5></career5>         <career6></career6>         <career7></career7>         <comment>弁護士法人新潟第一法律事務所新潟事務所所属です。取扱事件等は、事務所ホームページをご覧下さい。</comment>         <elements id=",0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,51,52,53,54,55,56,57,58,59,60,61,64,65,66,67,"></elements>         <office areaid="0">             <name>弁護士法人新潟第一法律事務所</name>             <zip>950-0965</zip>             <area>新潟市中央区</area>             <address1>新光町10番地2</address1>             <address2>技術士センタービル7階</address2>             <tel>025-280-1111</tel>             <fax>025-280-1112</fax>             <hours>9:30-17:00</hours>             <hp>http://www.n-daiichi-law.gr.jp/</hp>         </office>     </bar>          ...省略      </bars>

このように出力されたXMLをFlash側で解析します。解析用のメソッドは以下のようになっています。

/** *    選択弁護士の情報設定 */ function setText(bar:XMLList):void {     //trace(this.name + '.setText');     var id:String = bar.@id;     var tf:TextFormat;     // スクロール位置の初期化     Column1_mc.y = Column1_mc._startY;     Column2_mc.y = Column2_mc._startY;     // 写真の読み込み     BarPhoto_mc.stopLoad();     BarPhoto_mc.loadPortrait(MovieClip(this.parent).urlPrefix + 'portrait/' + id + '.jpg');     // 氏名     Column0_mc.BarName_mc.Text.text = bar.name.toString();     // ふりがな     Column0_mc.BarKana_mc.Text.text = bar.kana.toString();     // 性別     Column0_mc.BarSex_mc.Text.text=(bar.@sex=='0')?'男':'女';     // 事務所名     var barOfficeNameBox:MovieClip = Column1_mc.BarOfficeName_mc;     barOfficeNameBox.Text.width = 170;     barOfficeNameBox.Text.text = bar.office.name.toString();     // 住所     var barOfficeAddressBox:MovieClip = Column1_mc.BarOfficeAddress_mc;     var address:String = '';     if (bar.office.zip.toString() != '') {         // 郵便番号         address = '〒' + bar.office.zip.toString() + ' ';     }     if (bar.office.area.toString() != '') {         // 地域         address += bar.office.area.toString();     }     if (bar.office.address1.toString()!='') {         // 住所1         address+=bar.office.address1.toString();     }     if (bar.office.address2.toString()!='') {         // 住所2         address+=' '+bar.office.address2.toString();     }     barOfficeAddressBox.Text.width=170;     barOfficeAddressBox.Text.text=address;     barOfficeAddressBox.y=Math.floor(barOfficeNameBox.y+barOfficeNameBox.height)+SPACE_Y;     // TEL     var barOfficeTelBox:MovieClip=Column1_mc.BarOfficeTel_mc;     barOfficeTelBox.Text.text=bar.office.tel.toString();     barOfficeTelBox.y=Math.floor(barOfficeAddressBox.y+barOfficeAddressBox.height)+SPACE_Y;     // FAX     var barOfficeFaxBox:MovieClip=Column1_mc.BarOfficeFax_mc;     barOfficeFaxBox.Text.text=bar.office.fax.toString();     barOfficeFaxBox.y=Math.floor(barOfficeTelBox.y+barOfficeTelBox.height)+SPACE_Y;     // 営業時間     var barOfficeHoursBox:MovieClip=Column1_mc.BarOfficeHours_mc;     barOfficeHoursBox.Text.width=170;     barOfficeHoursBox.Text.text=bar.office.hours.toString();     barOfficeHoursBox.y=Math.floor(barOfficeFaxBox.y+barOfficeFaxBox.height)+SPACE_Y;     // メールアドレス     var barOfficeEmailBox:MovieClip=Column1_mc.BarOfficeEmail_mc;     barOfficeEmailBox.Text.text=bar.email.toString();     checkOverFlowText(barOfficeEmailBox.Text,170);     if (barOfficeEmailBox.Text.text!='') {         tf=barOfficeEmailBox.Text.getTextFormat();         tf.url='mailto:'+bar.email.toString();         tf.underline=true;         barOfficeEmailBox.Text.setTextFormat(tf);     }     barOfficeEmailBox.y=Math.floor(barOfficeHoursBox.y+barOfficeHoursBox.height)+SPACE_Y;     // ホームページ     var barOfficeHpBox:MovieClip=Column1_mc.BarOfficeHp_mc;     barOfficeHpBox.Text.text=bar.office.hp.toString();     checkOverFlowText(barOfficeHpBox.Text,170);     if (barOfficeHpBox.Text.text!='') {         tf=barOfficeHpBox.Text.getTextFormat();         tf.url=bar.office.hp.toString();         tf.target='_blank';         tf.underline=true;         barOfficeHpBox.Text.setTextFormat(tf);     }     barOfficeHpBox.y=Math.floor(barOfficeEmailBox.y+barOfficeEmailBox.height)+SPACE_Y;     // コメント     var barCommentBox:MovieClip=Column2_mc.BarComment_mc;     barCommentBox.Text.text=bar.comment.toString();     // 経歴     var barCareerBox:MovieClip=Column2_mc.BarCareer_mc;     var career:String=bar.career1.toString();     if (bar.career2.toString()!='') {         career+='¥n'+bar.career2.toString();     }     if (bar.career3.toString()!='') {         career+='¥n'+bar.career3.toString();     }     if (bar.career4.toString()!='') {         career+='¥n'+bar.career4.toString();     }     if (bar.career5.toString()!='') {         career+='¥n'+bar.career5.toString();     }     if (bar.career6.toString()!='') {         career+='¥n'+bar.career6.toString();     }     if (bar.career7.toString()!='') {         career+='¥n'+bar.career7.toString();     }     career=(career != ''?'・':'')+career.replace(/¥n*$/g,'').replace(/¥n/g,'¥n・');     barCareerBox.Text.width=205;     barCareerBox.Text.text=career;     barCareerBox.y=Math.floor(barCommentBox.y+barCommentBox.height)+SPACE_Y;     // 取扱分野     var barElementsBox:MovieClip=Column2_mc.BarElements_mc;     var e:Array=bar.elements.@id.split(',');     var elements:String='';     for (var i:uint = 1, count=e.length-1; i < count; i++) {         elements+=(elements != '' ? '¥n・' : '・')+Elements[uint(e[i])].toString();     }     barElementsBox.Text.width=205;     barElementsBox.Text.text=elements;     barElementsBox.y=Math.floor(barCareerBox.y+barCareerBox.height)+SPACE_Y;     // スクロールバー     displayScrollBar();     // 検索に戻るボタンを再開     changeEnabledButton(true); }

今回のFlash+CMSにおける各ファイルの関係は下図のようになります。

オリジナルCMSの管理画面

検索結果をランダムに表示する

サイト公開後、クライアントから追加の要望がありました。上述のXMLでは五十音順に弁護士データを格納してあり、検索結果は常に五十音順に弁護士が表示されるようになっていました。検索サイトの結果のように、最初の方に表示される情報ほど、利用者の目がいくものです。そこで公平な検索結果となるように、五十音順ではなく、ランダムに表示することとなりました。

しかし、ActionScript 3.0ではフィルタによってノードを取得する方法はあっても、結果をランダムに並び替えるメソッドはありません。FlashからPHPを経由して、データベース側でランダムな結果セットを取得する方法も考えられますが、追加要望を受けたときにはFlashがほぼ完成していました。そこで、PHPではそのまま五十音順のXMLを更新し、ActionScriptによるフィルタ結果をさらにランダムに並び替えるコードを書いて対処することにしました。

データソースであるXMLファイルはPHPによって五十音順に作成され、ActionScriptのXMLフィルタメソッドにより取得されるXMLListも五十音順のまま取得されます。このXMLListをそのまま使って弁護士MovieClipを表示すると、当然五十音順に表示されます。そこで、ページごとではなく、表示用XMLの弁護士をランダムに並び変える方法を採用しました。以下のようなランダム関数を利用して、五十音順に並んでいる検索結果XMLをランダムに並び変え、新しいXMLをswf内で作成するようにしています。

_xml = randomizeBar(_xml); function randomizeBar(xml:XMLList):XMLList {     var resultXML:XMLList=new XMLList();     while (xml.length() > 0) {         var randomIndex:uint = Math.floor(xml.length() * Math.random());         resultXML += xml[randomIndex];         delete xml[randomIndex];     }     return resultXML; }

XMLフィルタメソッドより取得された検索結果XMLから、for文を使い検索結果人数の回数だけ、ランダム関数で取得したインデックス番号の弁護士1人分のノードを取り出し、新しいXMLへ結合します。for文で回すという単純な力技で新しいXMLを作成していますが、速度的に問題がなかったのでこのロジックを採用しました。

まとめ

オリジナルCMSということで、ゼロから構築するのは大変でもあります。しかし、事前にクライアントにヒアリングを行い、更新が必要な項目や事項などしっかりと洗い出し、初期の段階から出てくるであろう問題点をあらかじめつぶすことができました。また、クライアント側としても入力項目のシンプルさゆえに、迷うことなく更新作業を行うことができています。

Movable Typeなどのような既存のCMSは手軽に使うことができます。しかし、今回のプロジェクトのように、要件内容によっては動作的な問題が発生することがあり得ます。オリジナルCMSにしてしまうのも選択肢の一つではないでしょうか。

製品

  • Acrobat
  • Creative Cloud
  • Creative Suite
  • Digital Marketing Suite
  • Digital Publishing Suite
  • Elements
  • モバイルアプリ
  • Photoshop
  • Touch Apps

ソリューション

  • デジタルマーケティング
  • コンテンツオーサリング
  • Web Experience Management

業種別ソリューション

  • 教育
  • 金融機関

サポート

  • ヘルプ&サポート
  • 注文と返品
  • ダウンロードに関するヘルプ
  • ユーザー登録に関するヘルプ

ラーニング

  • ADC: Adobe Developer Center
  • Adobe TV
  • Design Magazine
  • Photoshop Magazine
  • Focus In

ご購入方法

  • アドビストア
  • アカデミックストア
  • アドビライセンスストア
  • ボリュームライセンスについて
  • 販売パートナー
  • キャンペーン情報

ダウンロード

  • Adobe Reader
  • Adobe Flash Player
  • Adobe AIR
  • Adobe Shockwave Player

会社情報

  • プレスルーム
  • パートナープログラム
  • 企業の社会的責任(英語)
  • 採用情報
  • 投資家の皆様へ(英語)
  • イベント&セミナー
  • Legal(英語)
  • セキュリティ
  • お問い合わせ
国・地域および言語の選択 日本(変更)
国・地域および言語の選択 閉じる

North America

Europe, Middle East and Africa

Asia Pacific

  • Canada - English
  • Canada - Français
  • Latinoamérica
  • México
  • United States

South America

  • Brasil
  • Africa - English
  • Österreich - Deutsch
  • Belgium - English
  • Belgique - Français
  • België - Nederlands
  • България
  • Hrvatska
  • Česká republika
  • Danmark
  • Eastern Europe - English
  • Eesti
  • Suomi
  • France
  • Deutschland
  • Magyarország
  • Ireland
  • Israel - English
  • ישראל - עברית
  • Italia
  • Latvija
  • Lietuva
  • Luxembourg - Deutsch
  • Luxembourg - English
  • Luxembourg - Français
  • الشرق الأوسط وشمال أفريقيا - اللغة العربية
  • Middle East and North Africa - English
  • Moyen-Orient et Afrique du Nord - Français
  • Nederland
  • Norge
  • Polska
  • Portugal
  • România
  • Россия
  • Srbija
  • Slovensko
  • Slovenija
  • España
  • Sverige
  • Schweiz - Deutsch
  • Suisse - Français
  • Svizzera - Italiano
  • Türkiye
  • Україна
  • United Kingdom
  • Australia
  • 中国
  • 中國香港特別行政區
  • Hong Kong S.A.R. of China
  • India - English
  • 日本
  • 한국
  • New Zealand
  • 台灣

Southeast Asia

  • Includes Indonesia, Malaysia, Philippines, Singapore, Thailand, and Vietnam - English

Copyright © 2012 Adobe Systems Incorporated. All rights reserved.

利用条件 | プライバシーポリシーとCookie (更新)

Reviewed by TRUSTe: site privacy statement