yamachan Ajax/.NET/C# メモ

2007-02-27

[Ajax] 入力フィールドにSuggest機能をつけてみる (2)

週末に時間がとれましたので、前回 の Suggest機能を書き直してみました。 今回はわりと JavaScript っぽくなってきている、気がする。

本サイト右上の検索バーには反映されています。 今のところ、見た目の違いは選択キーワードの背景がグレーになったぐらいですかね。

Suggest付の検索ボックスの例

まずは検索バーの html なんですが、よりシンプルになっています。

<form action="http://www.google.co.jp/search">
<input name="q" size="20" id="my-field" />
<input name="q" type="hidden"
value=" site:http://yamachan-cs.blogspot.com/"/>
<img src="search_glass.gif" alt="Search"
width="48" height="24" align="bottom"
onclick="document.getElementById('my-field')
.form.submit();"
</form>

<div id="my-help"
style="visibility:hidden;display:none;color:blue">
</div>

この部分にはJavaScriptが使用されていない、のが今回のポイントです。 普通の検索バーと違うのは、div要素 "my-help" がひそかに追加されていることぐらいですね。

引き続きましては、Suggest機能を追加する JavaScript 部分です。 辞書の定義部分をのぞけば、記述は1行とシンプルになっていますね。 これで動作します。

<script>
var dic = [
"Ajax",
"aptana",
...途中省略
"unescapeHTML"
];

var ys=new jp.rinco.Suggest('my-field','my-help',dic);
</script>

ただ、このままでは地味ですので、少し拡張してみましょう。 Suggestされたキーワードにマウスがくると、文字は赤色に、背景は灰色になるようにロジックを追加します。

作成したSuggestオブジェクトの onmouseover, onmouseout 関数を、オーバーライドすればOKです。 記述する処理は、ごく普通ですね~

<script>
ys.onmouseover = function() {
this.style.color = 'red';
this.style.backgroundColor = '#ddd';
}
ys.onmouseout = function() {
this.style.color = 'blue';
this.style.backgroundColor = 'white';
}
</script>

これで jp.rinco.Suggest オブジェクトの使い方の説明は終わりです。 前回からの改良点としては、利用方法・拡張方法がシンプルになったこと、そして1ページに複数利用することが可能になったことでしょうか。

実際のコードを簡単にご紹介します。 まずはオブジェクトの生成の部分です。

jp.rinco.Suggest = function(t, h, d) {
if (typeof(t) == 'string')
this.target = document.getElementById(t);
else
this.target = t;
if (typeof(h) == 'string')
this.help = document.getElementById(h);
else
this.help = h;
this.dic = d;

this.target.aYamachanSuggest = this;
this.target.onkeyup = function() {
this.aYamachanSuggest.updateHelp();
}
}

対象のフィールドやヘルプ領域は、id で指定しても、オブジェクトを直接指定してもOKです。 対象フィールドのオブジェクトに aYamachanSuggest なんて名前で、勝手に自分自身を登録しちゃってるのがトリッキーかもしれません。

引き続き、Java でいうところのインスタンス・メソッドの定義部分をご紹介します。 実際には、上記の生成部分に含まれて記述しています。

まずはSuggestキーワードをヘルプ領域に追加するための、keyElement() 補助関数です。

this.keyElement = function(k) {
var ke = document.createElement('div')
ke.innerHTML = k;
ke.aYamachanSuggest = this;
ke.onclick = function(){
this.aYamachanSuggest.click(this);
}
if (ke.aYamachanSuggest.onmouseover)
ke.onmouseover = ke.aYamachanSuggest.onmouseover;
if (ke.aYamachanSuggest.onmouseout)
ke.onmouseout = ke.aYamachanSuggest.onmouseout;
return ke;
}

で、実際にヘルプ領域を更新して表示/非表示する updateHelp() 関数です。

this.updateHelp = function() {
var k = jp.rinco.lastWord(this.target.value);
if (k == "") {
this.help.style.visibility = 'hidden';
this.help.style.display = 'none';
return;
}

var lk = k.toLowerCase();
var counter = 0;
this.help.innerHTML = "";

for (loop = 0; loop < this.dic.length; loop++) {
var dk = this.dic[loop];
if (dk.toLowerCase().indexOf(lk) == 0) {
this.help.appendChild(this.keyElement(dk));
counter++;
}
}

if (counter > 0) {
this.help.style.visibility = 'visible';
this.help.style.display = 'block';
} else {
this.help.style.visibility = 'hidden';
this.help.style.display = 'none';
}
}

最後は Java でいうクラス・メソッドのご紹介です。 ついに登場した prototype って感じですね。これは生成部分の外で記述されています。

click() は、Suggestキーワードがクリックされたときに呼び出される関数です。

jp.rinco.Suggest.prototype.click = function(ke) {
var te = ke.aYamachanSuggest.target;
var k = jp.rinco.lastWord(te.value);
if (k == "") return;

te.value=te.value.substring(0,te.value.length-k.length)
+ ke.innerHTML;
ke.aYamachanSuggest.updateHelp();
te.focus();
}

以上、まだ不安な部分は残しているものの、まぁ JavaScript で書いたプログラムだと言えるレベルになってきたのではないかと...。 そして僕も「趣味のJSプログラマーです」と言っても怒られないかも、というレベルに?

2007-02-26

[JS] ネームスペース jp.rinco

JavaScriptにはJavaのようなネームスペース(名前空間)が無いので不便だな、とか考えていた自分は甘かったです。 先人たちは適切なグローバルオブジェクトを作成し、代用している様子。

そこで僕もJava風に自分のドメインの逆順 jp.rinco というオブジェクトを作成し、関数などを保持するようにしました。 jsファイルの先頭に、以下のような定義文を追加します。

if (typeof jp == "undefined") {
var jp = {};
}
if (typeof jp.rinco == "undefined") {
jp.rinco = {};
}

以前は以下のように関数を定義していました。

function yamachan_display(id) {
...
}

今後は、以下のように関数を定義するようにします。

jp.rinco.display = function(id) {
...
}

過去のコードや記事も順次、修正していきたいと考えています。

2007-02-24

[Ajax] 入力フィールドにSuggest機能をつけてみる (1)

少し前から、本blogの右上に検索ボックスが表示されているのにお気づきでしょうか? この検索ボックスですが、ちょっと仕掛がしてあります。

キーワードを入れると、↓のように候補が下に表示されるようになっています。 キーワードをクリックすると、自動入力されます。 a,r,y とか入力してみてください。

Suggest付の検索ボックスの例

IE などのブラウザにも似た機能がありますが、こちらはAjaxで実装されています。 初めて使用する時も有効なこと、表示されるキーワードはサイト管理者が設定したものであること、あたりがメリットでしょうか。

正直、中身はあまりJavaScriptっぽくないコードで、書き直している最中なのですが、、、とりあえず動いているバージョンを、バックアップも兼ねて貼っておきます。

2007/2/27 追記: 書き直した版 を公開しました

実際の検索ボックスですが、まずは以下のようなscriptが貼ってあります。

<script>
function my_submit() {
var k = y_suggest_target.value;
location.href = 'http://www.google.co.jp/search?
q=site:http://yamachan-cs.blogspot.com/ ' +
encodeURIComponent(k);
}

function my_search_helpLine(key) {
return "<span
onclick=\"yamachan_suggest_click('"+key+"');\"
onmouseover=\"this.style.color='red';\"
onmouseout=\"this.style.color='blue';\">"
+ key + "</span><br />";
}

var my_dic = [
"Ajax",
"aptana",
...途中省略
"unescapeHTML"
];
</script>

my_submit() はSuggest機能とは関係なく、単に検索実行用の関数です。 Google をSite:オプションをつけてキーワード検索する単純なものです。

my_search_helpLine() は表示用の補助関数です。 必須ではありません。 Suggestされるキーワードの上にマウスがある場合、キーワードを赤色にするためのhtml出力を定義しています。

my_dic がSuggestされるキーワード文字列の配列になります。 これらの事前定義に続き、以下のhtmlが貼られています。

<form style="margin:0">
<input size="20" id="my-search-field"
onkeyup="yamachan_suggest(
'my-search-field','my-search-help',
my_dic, my_search_helpLine);"
/>
<img src="search.gif"
width="48" height="24" alt="Search"
onclick="my_submit();"
/>
</form>

<div id="my-search-help"
style="visibility:hidden;display:none;color:blue">
</div>

実際の入力欄に onkeyup が指定されていること、ヘルプ表示用のdivエリアが非表示で用意されていること、の二点がポイントでしょうか。

さて利用されている関数ですが、メイン部分は以下のようになっています。 最初のvarがとても恥ずかしいデスネ。

var yamachan_suggest_target;
var y_suggest_help;
var y_suggest_dic;
var y_suggest_func;

function yamachan_suggest(f, h, dic, func) {
y_suggest_target=document.getElementById(f);
y_suggest_help=document.getElementById(h);
y_suggest_dic = dic;
y_suggest_func = func;
var skey=yamachan_lastWord(y_suggest_target.value);
y_suggest_help.innerHTML=y_suggest_helpHTML(skey);
if (y_suggest_help.innerHTML == "") {
y_suggest_help.style.visibility = 'hidden';
y_suggest_help.style.display = 'none';
} else {
y_suggest_help.style.visibility = 'visible';
y_suggest_help.style.display = 'block';
}
}

yamachan_lastWord() は以前にご紹介したもので、文字列の最後の1単語を得る関数です。 また以下の補助的な関数を使用します。

function yamachan_suggest_helpHTML(skey){
if (skey == "") return "";
var sk = skey.toLowerCase();
var val = "";
for(loop=0;loop<y_suggest_dic.length;loop++){
var k = y_suggest_dic[loop];
if (k.toLowerCase().indexOf(sk) == 0) {
if (y_suggest_func ==null||y_suggest_func=='')
val = val +
"<span onclick=\"yamachan_suggest_click('"
+ k + "');\">" + k + "</span><br />";
else
val = val + y_suggest_func(k);
}
}
return val;
}

function yamachan_suggest_click(k) {
var skey=yamachan_lastWord(y_suggest_target.value);
if (skey != "") {
y_suggest_target.value =
y_suggest_target.value.substring(0,
y_suggest_target.value.length-skey.length) + k;
y_suggest_help.innerHTML =
yamachan_suggest_helpHTML(k);
y_suggest_target.focus();
}
}

yamachan_suggest_helpHTML() はキーワードリストのhtmlを作成する関数です。 yamachan_suggest_click() はその生成されたhtmlに埋め込まれ、キーワードをクリックしたときに呼び出されます。

今回はベタに検索ボックスに使用してみましたが、いろいろ活用方法はあるとおもいます。 例えば製品名を入力する欄で、代表的な製品をSuggestしてみるとか。

2007-02-19

[JS] 最後のwordを得る関数

もうひとつ、紹介し忘れていた関数がありました。 文字列を単語(word)単位で分割し、その最後のwordを返す関数です。

jp.rinco.lastWord = function(str) {
if (str == "") return "";
var s=str.replace(/(\s|\u3000)+/g,' ').split(' ');
return s[s.length - 1];
}

replace() に指定している "/(\s|\u3000)+/g" という正規表現(RegExp)が少し複雑かもしれません。 split()の事前処理として、タブや改行や全角スペースも半角スペースに変換しています。

正規表現について簡単に説明します。 もっとも単純な変換だと str.replace(/\s/, ' '); という感じになるでしょうか。

ただ"\s" だけでは全角スペースに対応できない様子なので、全角スペースのUnicode表記 "\u3000" と組み合わせて "\s|\u3000" という表記にします。

複数連続したスペースが含まれる場合があります。 そこでそれらをまとめ、1つの対象として処理するために、1回以上の繰り返し "+" を使用して "(\s|\u3000)+" という表記にします。

最後に追加される "/g" はよく使いますね。 一致する対象を全て処理するオプション、グローバルマッチングです。

文字列操作において、正規表現は非常に便利ですので、ぜひ活用してみてください。

2007/2/27 追記: jp.rincoオブジェクト へ移動

2007-02-18

[JS] タグを処理する関数

Ajaxっぽいサンプルをいろいろ作成していますが、もう少し時間がかかりそうです。 今回は、その過程で作成した関数をご紹介します。

まずは単に & < > 文字を、&amp; &lt; &gt; に HTMLエスケープする escapeHTML() 関数。 そして戻す unescapeHTML() 関数です。

jp.rinco.escapeHTML = function(str) {
return str.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
jp.rinco.unescapeHTML = function(str) {
return str.replace(/&gt;/g, ">")
.replace(/&lt;/g, "<")
.replace(/&amp;/g, "&");
}

そして文字列から指定したタグの中身を取り出す、tagText() 関数です。

例えば str に "<title>aaa</title>"、tag_s に "<title>"、tag_e に "</title>" を指定し、その間に指定されたタグの中身 "aaa" を取り出します。

最後の引数は通常は false、str に指定した文字列が HTMLエスケープ されている場合に true を指定します。 例えば処理する文字列が "&lt;title&gt;aaa&lt;/title&gt;" のような場合に true にします。

※ これはレアケースのようですが、Domino サイトが対象の場合には有効です

jp.rinco.tagText = function(str,tag_s,tag_e,cflag){
if (tag_s == "" || tag_e == "")
return "";

var key_s=cflag?jp.rinco.escapeHTML(tag_s):tag_s;
var key_e=cflag?jp.rinco.escapeHTML(tag_e):tag_e;
var pos_s = str.indexOf(key_s);
var pos_e = str.indexOf(key_e);
if (pos_s < 0 || pos_e < 0)
return "";
else
return str.substring(pos_s+key_s.length, pos_e);
}

以上、ホントは String に prototype で組み込むのが JavaScript っぽいのですが。 単に関数にしちゃってる、弱気な僕なのでありました。

2007/2/27 追記: jp.rincoオブジェクト へ移動

2007-02-17

[TALK] JS関数の簡単な管理/デバッグ法

Java 等の開発では JUnit のお世話になっている僕なので、JavaScript でも似たようなことをやっています。 参考になるか不明ですが、ご紹介してみましょう。

まずは aptana で関数用に適当なhtmlファイルを作成し、以下のようにシンプルなテスト用の関数を定義しておきます。

渡す引数はラベル、評価する値1、評価する値2 となります。 値が違った場合に限り、ラベルを使用してエラーメッセージを表示する仕組みです。

<script>
function jsUnit(l, v1, v2) {
if (v1 != v2)
document.writeln(l+": "+v1+" != "+v2);
}
</script>

このhtmlの中に、以下のように関数をいろいろ定義していきます。

<h2>jp.rinco.parseURL(url, name, value)</h2>
ここに関数の説明
<script>
jp.rinco.parseURL = function(url, name, value) {
...実際の関数のロジック
}

jsUnit("1",jp.rinco.parseURL("","a","d"),"d");
jsUnit("2",jp.rinco.parseURL("a=b","a","d"),"b");
...
</script>

で、実際のテストは、このhtmlをプレビューしてみるだけです。 全部の関数がチェックされ、問題のある関数のセクションにはエラーメッセージが表示されるハズです。

jsUnit はできるだけ多くのパターンを登録しておきます。 関数のロジックはhtmlに直接記載せず、実際の js ファイルを読み込むようにしても良いでしょう。

ちょっとした工夫ですが、関数の管理が楽になり、かつクオリティを保つことができます。 コメントを書いておけば、そのままマニュアルがわりにもなります。 お手軽な開発には、わりとお勧めな方法です。

2007-02-04

[JS] クリックで表示/非表示する関数

yamachan GAMEメモ の投稿には、ネタバレを含むものがあります。 例えば [MSX] Shooting: 魔城伝説 の後半面&エンディングの情報とか。

ネタバレ部分を最初は隠しておいて、クリックすると表示/非表示を切り替える。 まぁ、JavaScript の初歩って感じですね。 実用になる最小のサンプルといいますか...。

よく使うので、メモとして貼っておきます。

jp.rinco.display = function(id) {
var target = document.getElementById(id);
if (target.style.visibility == 'hidden') {
target.style.visibility = 'visible';
target.style.display = 'block';
} else {
target.style.visibility='hidden';
target.style.display = 'none';
}
}

以下が使用例です。 クリックしてみてください。

エンディングについて (ネタバレのためクリックで表示)

以下のような html で実現しています。 複数使用するときには 'div_netabare1' を適当に変更してください。

<div
style="background-color:black;
color:white; padding:4px"
onclick="jp.rinco.display('div_netabare1');">
エンディングについて (ネタバレのためクリックで表示)
</div>
<div id="div_netabare1"
style="visibility:hidden; display:none;
border:solid 2px black; padding:4px">
ここにネタバレの内容!<br />
ここにネタバレの内容!
</div>

2007/2/27 追記: jp.rincoオブジェクト へ移動
2007/3/13 追記:

いろいろ多用していますので、ちょっと改良してみました

まず、対象の指定に element だけでなく、id値も指定できるように拡張してみました。 必要もないのに getDocumentById() するのも面倒ですしね。

また強制的に表示/非表示を指定できるようにしてみました。 第2引数に true を指定すると強制表示、false を指定すると強制非表示です。 むろん、指定しなければ従来どおりのトグル動作です。

jp.rinco.display = function(id, v) {
var target;
if (typeof(id) == 'string')
target = document.getElementById(id);
else
target = id;

var vflag;
if (v == undefined) {
if (target.style.visibility == 'hidden')
vflag = true;
else
vflag = false;
} else
vflag = v;

if (vflag) {
target.style.visibility = 'visible';
target.style.display = 'block';
} else {
target.style.visibility='hidden';
target.style.display = 'none';
}
}