分散型共有スペース(のようなもの)を作ってみる
(Web技術の大きな流れについて補足)
ここまでは前回扱った。
res.render(など)の第2引数(オブジェクトの形式;右図参照)を通じて、
res.render('1',{a:a})
以下がテンプレートに追加すべき記述の例
- a.forEach(([x,y])=>{
div(style=`left:${x}px;top:${y}px;`)
//- ↑は pugの場合の記法(バッククォートで囲む ES6と同じ記法)
//- jadeの場合は下のように書く
// div(style="left:#{x}px;top:#{y}px;")
- })
ついでに、端末を区別するための隠しデータもタグとして送っておこう。
input(type="hidden",name="clientId", value=`${id}`)
//- ページテンプレートの末尾等に追加
なお、pug(jade)で変数 id として扱われる値は、 以下のように res.renderを介して渡す(前出の 呼出しを少し変更)。
res.render('1',{a:a, id:(new Date).getTime()})
クライアントに記録するデータ(のための要素)を1つ増やす。HTMLで表現するならば、
<input type="hidden" name="recentTime">
こうなるが、これを送信するための pugのコードは、
input(type="hidden", name="recentTime",value=`${id}`)
送信すべきものが id と同じでいいかどうか、 微妙なタイミングの問題があるかどうか、 これについてはあとで考えてみよう。。
図形生成時にサーバに送信するデータを追加する。
クライアント側の変更:
var id=$(':hidden[name="clientId"]').val()
$.get(`/b/${x}/${y}/${id}`) ')
サーバ側の変更:
app.get('/b/:x/:y/:clientId',(req,res)=>{
var {x,y,clientId}=req.params
サーバで配列aに記録するデータを(座標値 x,y に加えて)2つ追加する。 1つは前項で受け取った、clientId, もう1つは、 生成された時刻。
// 前項の app.getの続き:
var time=(new Date).getTime()
// 追加するデータ(現在時刻を表す数値)
console.log(`x=${x}, y=${y}, time=${time}, clientId=${clientId}`)
a.push([x,y,time,clientId])
res.send('')
})
ポーリング(クライアントからの定期的問合せ)を行う (ための、まずは枠組みだけ)。
function poll() {
var id=$(':hidden[name="clientId"]').val()
var recentTime=$(':hidden[name="recentTime"]').val()
$.get(`/q/${id}/${recentTime}`,(data)=>{
// ...
})
}
setInterval(1000,poll)
app.xxxx
や res.xxxx
を呼んでいれば
サーバ側のコード$
を多用していれば(jQueryの関数ですね)
クライアント側のコードという区別ができることは理解しておいて下さい。
このポーリングを受け付けるサーバ側の動作:
app.get('/q/:clientId/:recentTime',(req,res)=>{
var {clientId,recentTime}=req.params
var now=(new Date).getTime()
var r=a.filter(([x,y,time,id])=>{
return time>recentTime and id!=clientId
})
res.json({a:r})
})
次項に備えて、クライアントプログラムを(前に作ったものに対し)変更しておく。
function bx(x,y){ // ローカルで矩形生成
$('body').append(
$('<div/>').css({
left: x+'px', top: y+'px'
})
)
}
function sendBox(x,y){ // サーバに通知
var clientId=$(':hidden[name="clientId"]').val()
$.get(`/b/${x}/${y}/${clientId}`)
}
// 前回 bx() として1つだったものを2つの関数に分けた ので、これを呼ぶ側も
function c(ev) {
bx(ev.pageX,ev.pageY)
sendBox(ev.pageX,ev.pageY)
} // 別々に呼び出すことになる
これのレスポンスを受け取ったクライアントの動作:
(関数poll
の中で $.get
に渡したコールバック関数の部分のみ以下に書く)
(data)=>{
data.a.forEach(([x,y,time,dmy])=>{
bx(x,y)
})
var time=
Math.max(...data.a.map(e=>e[2]).concat(recentTime))
$(':hidden[name="recentTime"]').val(time)
}
data.a
でサーバから受け取った配列にアクセスする。プログラムの理解を助けるため、ここで 右図に沿って整理しておく。
このアプリケーションでは3種類のHTTP呼び出しが行われる。
/1
b/:x/:y/:clientId
/q/:clientId/:recentTime
(参考情報)上のプログラム(の一部)を当初は以下のように書いていた。
var time=
Math.max.apply(null,data.a.map(e=>e[2]))||recentTime
が、これでは動作が不安定になるようだ。
Math.max.apply
は、複数の引数の最大値を求めるメソッド(Math.max)を配列に適用する書き方(として知られていた)。
||
を使って代替の値を与えるようにしていた。-Infinity
を返すので、{a:r, recentTime:time}
のような形のJSONを作って応答として送ることができる。配列が空かどうかの判別、配列に対する繰り返し、など、 従来のプログラミング技術でも書ける
if(a.length>0) {
for(i=0;i<a.length;i++) {
var time=a[i][2]
...
が、いい機会なのですっきりする書き方を学んでおこう。
$
関数で取り出すのでなく、$(()=>{}
の中で)変数に取り込んでおいて、例えば recentTime という変数があったとき、
data.a.forEach(([x,y,time,dmy])=>{
bx(x,y)
recentTime=Math.max(time,recentTime)
})
のように1つずつ処理していく書き方もできるだろう。
の2か所で行っているが、
生成する要素がもっと複雑になったときに、
プログラムの変更すべき箇所が2か所あることは
望ましいことではない。対策としては、
といった可能性があるだろう。