script.aculo.usを読み解く

第7回 slider.js

この記事を読むのに必要な時間:およそ 12.5 分

連載第7回は,slider.jsを読み解きます。このライブラリはスライダーというGUI部品を提供します。スライダーは,オーディオ機器でボリュームのところによく見かける,目盛の付いた溝にツマミがついたものです。GUI部品としては,やはりボリュームの調整や,あるいはペイントソフトで色を合成するときなどに使われます。連続的な値を,気軽に上げたり下げたりするのに便利です。その反面,細かい調整をするには向きません。

スライダーの各部の名称

コードの解説に入る前に,部品の各部の名称を確かめましょう。以下の画像をご覧ください。ツマミを英語ではハンドルといい,溝をトラックといいます。スライダーの範囲をレンジといいます。複数のハンドルの間にできる区間をスパンといいます。

画像

実際の動作例もご覧ください(script.aculo.usの機能テストに含まれるslider_test.htmlを和訳したものです。

Control.Slider

それではコードを見ていきましょう。

0001:// script.aculo.us slider.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
0002:
0003:// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs 
0004://
0005:// script.aculo.us is freely distributable under the terms of an MIT-style license.
0006:// For details, see the script.aculo.us web site: http://script.aculo.us/
0007:

1~7行目は,著作権表示です。

0008:if (!Control) var Control = { };
0009:

8行目で,将来的に,いろいろなGUI部品を実装する構想があって,それらをControlというクラス以下にまとめようとしているのがわかります。いまのところはスライダーしかありません。

0010:// options:
0011://  axis: 'vertical', or 'horizontal' (default)
0012://
0013:// callbacks:
0014://  onChange(value)
0015://  onSlide(value)

10~15行目で,オプションとフックについてごく簡単なコメントがあります。詳細を以下で説明します。

0016:Control.Slider = Class.create({
0017:  initialize: function(handle, track, options) {
0018:    var slider = this;
0019:    
0020:    if (Object.isArray(handle)) {
0021:      this.handles = handle.collect( function(e) { return $(e) });
0022:    } else {
0023:      this.handles = [$(handle)];
0024:    }
0025:    
0026:    this.track   = $(track);
0027:    this.options = options || { };
0028:
0029:    this.axis      = this.options.axis || 'horizontal';
0030:    this.increment = this.options.increment || 1;
0031:    this.step      = parseInt(this.options.step || '1');
0032:    this.range     = this.options.range || $R(0,1);
0033:    
0034:    this.value     = 0; // assure backwards compat
0035:    this.values    = this.handles.map( function() { return 0 });
0036:    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
0037:    this.options.startSpan = $(this.options.startSpan || null);
0038:    this.options.endSpan   = $(this.options.endSpan || null);
0039:
0040:    this.restricted = this.options.restricted || false;
0041:
0042:    this.maximum   = this.options.maximum || this.range.end;
0043:    this.minimum   = this.options.minimum || this.range.start;
0044:
0045:    // Will be used to align the handle onto the track, if necessary
0046:    this.alignX = parseInt(this.options.alignX || '0');
0047:    this.alignY = parseInt(this.options.alignY || '0');
0048:    
0049:    this.trackLength = this.maximumOffset() - this.minimumOffset();
0050:
0051:    this.handleLength = this.isVertical() ? 
0052:      (this.handles[0].offsetHeight != 0 ? 
0053:        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : 
0054:      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : 
0055:        this.handles[0].style.width.replace(/px$/,""));
0056:
0057:    this.active   = false;
0058:    this.dragging = false;
0059:    this.disabled = false;
0060:
0061:    if (this.options.disabled) this.setDisabled();
0062:
0063:    // Allowed values array
0064:    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
0065:    if (this.allowedValues) {
0066:      this.minimum = this.allowedValues.min();
0067:      this.maximum = this.allowedValues.max();
0068:    }
0069:
0070:    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
0071:    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
0072:    this.eventMouseMove = this.update.bindAsEventListener(this);
0073:
0074:    // Initialize handles in reverse (make sure first handle is active)
0075:    this.handles.each( function(h,i) {
0076:      i = slider.handles.length-1-i;
0077:      slider.setValue(parseFloat(
0078:        (Object.isArray(slider.options.sliderValue) ? 
0079:          slider.options.sliderValue[i] : slider.options.sliderValue) || 
0080:         slider.range.start), i);
0081:      h.makePositioned().observe("mousedown", slider.eventMouseDown);
0082:    });
0083:    
0084:    this.track.observe("mousedown", this.eventMouseDown);
0085:    document.observe("mouseup", this.eventMouseUp);
0086:    document.observe("mousemove", this.eventMouseMove);
0087:    
0088:    this.initialized = true;
0089:  },

16~89行目の,initializeは,インスタンスの初期化を行う関数です。引数として,1番めのhandleにハンドルにする要素のDOM id(またはそれらの配列)をとります。2番めのtrackに,トラックにする要素のDOM idをとります。3番めのoptionsに,オプション設定のオブジェクトをとります。

20行目で,1番めの引数のhandleにDOM idの配列が与えられた場合は,配列の中身のそれぞれにPrototype.jsの$関数を適用して,その要素たちをハンドルとして取得します。

23行目で,handleに単にDOM idが与えられた場合は,同様にして,その要素をハンドルとして取得します。

26行目で,2番めの引数のtrackに与えられたDOM idから,同様にして,その要素をトラックとして取得します。

29行目で,axisは,スライダーの軸が垂直か水平かを表します。options.axisで設定できます。デフォルトは'horizontal'(水平)です。

30行目で,incrementは,今のところ,実装されていません。公式Wikiの説明では,これはピクセルとスライダーの値との換算量で,例えば1にすると,1pxのマウスドラッグでスライダーの値が1動くそうです。

31行目で,stepも,今のところ,実装されていません。公式Wikiにも説明がありません。

32行目で,rangeは,スライダーの可動範囲を表します。例えば0~100とするにはoptions.rangeに$R(0,100)と与えます。デフォルトでは$R(0,1)です。範囲は両端の値を含みます。

34行目で,valueは,後方互換性のために用意されている値で,ハンドルの現在値を示していたものです。これはハンドルが1個しかなかったときの名残で,今ではハンドルが複数あるので,代わりにvaluesを使ってください。後方互換性のために,setValue関数が常にこの値をvalues[0]の値に更新します。

35行目で,valuesは,ハンドルたちの現在値を示します。全て0で初期化します。

36行目で,spansは,スパンの要素たちです。options.spansにDOM idの配列を与えると,その要素たちをスパンとして取得します。デフォルトではfalseです。

37行目で,startSpanは,先頭のスパンに指定の要素を使うオプションです。options.startSpanに,先頭のスパンになる要素のDOM idを与えます。

38行目で,endSpanは,最後尾のスパンに指定の要素を使うオプションです。options.endSpanに,最後尾のスパンになる要素のDOM idを与えます。

40行目で,restrictedは,ハンドル同士の交差を制限するかどうかです。options.restrictedで設定します。デフォルトではfalseで,制限しません。

42行目で,maximumは,スライダーの最大値です。options.maximumで与えます。デフォルトではレンジの終点です。後ほどのコードで,allowedValuesがあるときはその最大値が設定されるようになっています。

43行目で,minimumは,スライダーの最小値です。options.minimumで与えます。デフォルトではレンジの始点です。後ほどのコードで,allowedValuesがあるときはその最小値が設定されるようになっています。

46行目で,alignXは,水平なスライダーの,トラックの0点位置を横にずらすためのオプションです。options.alignXで与えます。デフォルトは0です。

47行目で,alignYは,垂直なスライダーの,トラックの0点位置を縦にずらすためのオプションです。options.alignYで与えます。デフォルトは0です。

49行目で,trackLengthは,トラックの表示の長さです。単位はpxです。ここででてくるmaximumOffsetは,トラックのCSSでの終点の位置を返す関数です。minimumOffsetは,トラックのCSSでの始点の位置を返す関数です。これらは後述します。

51行目で,handleLengthは,ハンドルの表示の長さです。単位はpxです。スライダーが垂直ならハンドルの縦幅,水平なら横幅のことです。通常はハンドルのoffsetHeightやoffsetWidthの値を使いますが,スライダーがブラウザに表示されていないときにoffsetHeightなどが取得できないことがありますticket4011)。このときはCSSのheightやwidthプロパティの値を,単位である'px'を取り除いて使います。

57行目で,activeは,スライダーがアクティブかどうかのフラグです。ハンドルのドラッグを開始したときにtrueになります。

58行目で,draggingは,ハンドルをドラッグしている間,trueになるフラグです。

59行目で,disabledは,trueにするとハンドルをドラッグできなくなるオプションです。ライブラリの利用者のために用意されています。

61行目で,options.disabledがtrueなら,setDisabledを呼んで,上記のthis.disabledをtrueにします。

64行目で,allowedValuesは,スライダーが取れる数値を列挙した配列です。options.valueに数値の配列を与えることで設定します。ハンドルをドロップしたときなどに,そこに最も近い数値がこの配列から選ばれます。デフォルトではfalseで,この場合は,スライダーは範囲内の全ての数値を取ることができます。

66,67行目で,前述のスライダーの取りうる最小値,最大値のthis.minimumとthis.maximumは,これらの配列の最小値,最大値に設定しなおされます。

70~72行目で,イベントリスナとなる関数を代入しておきます。

75~83行目で,ハンドルを初期化します。次のような処理をします。

76行目で,インデックスを逆順にします。そうする必要は実はないのですが,74行目のコメントで,最終的に1番めのハンドルが選択中になるように念を押しているのだとわかります。

77行目で,そのインデックスのハンドルの値を後述のsetValueを呼んで更新します。options.sliderValueが,配列で与えられていたら,それらを初期値にします。数値で与えられていたら,一律にその値を初期値にします。何も与えられていなければ,レンジの始点の値を使います。

81行目で,ハンドルを,makePositionedでposition: 'relative'にして移動に備えます。さらにマウスクリックのイベントリスナを設定します。これはドラッグの開始を扱うイベントリスナです。

84行目で,トラックをマウスクリックするとドラッグの開始をするようにします。

85行目で,ページ上でマウスボタンを離すとドラッグを終了(ドロップ)するようにします。

86行目で,ページ上でマウスを動かすと,スライダーの値の変更や描画をするようにします。

88行目で,initializedフラグをtrueにします。このフラグが立っているときだけ,onSlideフックやonChangeフックが呼ばれます。

著者プロフィール

源馬照明(げんまてるあき)

名古屋大学大学院多元数理科学研究科1年。学部生のときにSchemeの素晴らしさを知ったのをきっかけに,関数型言語の世界へ。JavaScriptに,ブラウザからすぐに試せる関数型言語としての魅力と将来性を感じている。

ブログ:Gemmaの日記

コメント

コメントの記入