jquery-ptmplを拡張して、HTMLタグのclass属性を読み書きしやすく指定する
jquery-ptmplでは、内部のほとんどの関数を簡単に置き換えられるようになっています。今日はその一例を紹介します。
<script src="jquery.js"></script> <script src="jquery.ptmpl.js"></script> <script> (function (jQuery, undefined) { var oldPtmplTranslateHtmlToLiteral = jQuery.ptmplTranslateHtmlToLiteral; jQuery.ptmplTranslateHtmlToLiteral = newPtmplTranslateHtmlToLiteral; function newPtmplTranslateHtmlToLiteral(str) { str = str .replace(/<(\w+)\.([\w-.]+)/g, function (all, tag, klass) { return '<'+tag+' class="'+klass.replace(/\./g, ' ')+'"'; }); return oldPtmplTranslateHtmlToLiteral(str); } })(jQuery); </script>
jQuery.ptmplTranslateHtmlToLiteral関数は、テンプレートのコンパイル時にのみ呼び出され、テンプレート中のテンプレートタグ以外の部分(=普通にHTMLが書いてある部分)を文字列リテラルに変換する関数です。
上記のコードでは、例えば
jquery-tmplとやや互換性のある、高速で柔軟なテンプレートエンジン jquery-ptmpl を作りました
jquery-tmplに対する不満を解消するため、テンプレートエンジンを自作しました。
ついでに、jquery-tmplより2倍から10倍以上高速に動作するようです。 (リポジトリのベンチマーク参照)
ソースコードはgithubで公開しています。
200行程度しかないので読みやすいはず。
jQuery Pluggable Templates Plugin
https://github.com/atsumu/jquery-ptmpl
サンプルコード
<div id="place"></div> <script id="tmpl-sample" type="text/x-jquery-tmpl"> <h2>this is sample.</h2> {{! you can use template local variable. }} {{$ var foo = '<span style="color:red">output raw html</span>'; }} {{html foo}} {{each(k, v) a}} {{if v % 2 == 0}} {{continue}} {{else v % 3 == 0}} {{break}} {{else}} <div>k={{= k}}, v={{= v}}</div> {{/if}} {{/each}} {{tmpl({ b:3 }) "#tmpl-other"}} </script> <script id="tmpl-other" type="text/x-jquery-tmpl"> here is other template. {{= b}} </script> <scirpt> jQuery(function ($) { $("#tmpl-sample").ptmpl({ a:[1,2,3,4] }).appendTo("#place"); }); </script>
jquery-ptmplの特徴
ローカル変数が使える
<script id="tmpl-sample" type="text/x-jquery-tmpl"> {{$ var foo = "bar"; }} {{= foo}} </script>
{{$ ...}}の中に書いたコードは、コンパイル後のコードの中にそのまま埋め込まれます。
1テンプレート=1関数としてコンパイルされるので、定義した変数は同じテンプレート内でのみ有効です。
タグを定義できる
<script> jQuery.ptmplDefineTag({ '=': function (code, str) { code.push('_PTMPL_HTML.push(jQuery.ptmplEscapeHtml((', str, ')));'); }}); </script>
のように、jQuery.ptmplDefineTag関数を使ってテンプレートタグを定義できます。
pixivのマンガの各ページをウィンドウサイズに合わせて縮小するgreasemonkey
説明
ウィンドウからはみ出るページを、自動的にウィンドウサイズに合わせて縮小するgreasemonkeyです。
スクロールしなくてもページ全体が見えるので、サクサク読み進められるかも。
特徴は以下の通りです。
・ボタンの位置を変えるので、縦幅をギリギリまで使える
・ウィンドウサイズを変えたとき、画像サイズを自動的に合わせる
・ウィンドウサイズを変えたとき、見てたページまで自動的にスクロールする
・ウィンドウサイズより小さい画像はそのまま
・この機能を無効化するボタンを追加する
HaH_modoki (Hit-a-Hint風greasemonkey)
追記 2009-04-21 01:51
window.openをGM_openInTabに変えたので、ポップアップブロックに引っかからなくなったか引っかかりにくくなったはずです。
追記
忘れるのでメモ。
偶数文字目と奇数文字目に使う文字を別々に指定できるようにすると、子音字が連続することを防げるので、ローマ字入力の人にとって入力しやすくなる。
概要
キーボード操作のみでリンクを開くためのgreasemonkeyです。
http://d.hatena.ne.jp/javascripter/20080531/1212190340 を元にしました。
document.link.hasOwnPropertyが呼び出せなくてイヤになったので、書きかけですが公開します。
firefox3以降で動きます。
たまにしかJavaScriptを書かないので、変なところがあったらコメントお願いします。
特徴
- フォームにフォーカスを移せる。
- フォームにフォーカスがあるときは動かない。
- フォームからフォーカスをはずすキーを割り当てれる。
- リンクの選択に使うキー・使わないキーを自由に選べる。
- 新しいウィンドウで開くかどうかを入力中にトグルするキーを割り当てれる。
- 最後にEnterを押さなくていい。
実はHit-a-Hintとか使ったことがないので差がわかりません。
マニュアル
先頭付近にある変数oのプロパティがオプションです。
プロパティ | 説明 |
---|---|
start_keys | リンクの選択を開始するキー |
cancel_keys | リンクの選択をキャンセルして終了するキー |
input_keys | リンクの選択に使用するキー |
shift_is_new_window | shift状態のときに新しいウィンドウで開くかどうか |
shift_on_keys | shift状態にするキー |
shift_off_keys | shift状態をやめるキー |
shift_toggle_keys | shift状態をトグルするキー |
blur_keys | フォーカスをはずすキー |
tip_offset_h | tipの表示位置のオフセット(縦) |
tip_offset_v | tipの表示位置のオフセット(横) |
tip_class | tipに割り当てるクラス名。他のグリモンとかぶってたら変える |
どの機能にも複数のキーを指定できます。
液晶が届いたので、残りは後で書きます。
ソースコード
// ==UserScript== // @name HaH_modoki // @namespace http://d.hatena.ne.jp/atsumu-t/ // @include * // ==/UserScript== var ESC = String.fromCharCode (27); var o = { start_keys: 'o', cancel_keys: 'o' + ESC, input_keys: 'abcdefghjklmnprstuvwxy', shift_is_new_window: false, shift_on_keys: '', shift_off_keys: '', shift_toggle_keys: 'i', blur_keys: '' + ESC, tip_offset_h: 0, tip_offset_v: 0, tip_class: '_HaHm_tip', }; function makeTipTemplate () { var t = document.createElement ('span'); t.className = o.tip_class; with (t.style) { color = 'white'; backgroundColor = 'rgba(0, 0, 0, 0.5)'; paddingLeft = '0.2em'; paddingRight = '0.2em'; borderColor = 'white'; borderStyle = 'solid'; borderWidth = '0.1em'; zIndex = '999999'; position = 'absolute'; } return t; } var g = { objs: new Array (), tip_digit: new Number (), enable: false, s: new State (), }; function State () { this.shift = false; this.input = new String (); } function open (href, shift) { if ((o.shift_is_new_window && shift) || (!o.shift_is_new_window && !shift)) //window.open (href, '_blank', ''); GM_openInTab (href); else location.href = href; } function log (x, base) { return Math.log (x) / Math.log (base); } function key_match (e, s) { return (s.indexOf (String.fromCharCode (e.which)) != -1 || s.indexOf (String.fromCharCode (e.keyCode)) != -1); } function keypress (e) { { var elem = e.target; var elem_name = elem.nodeName.toLowerCase (); if (key_match (e, o.blur_keys)) { elem.blur (); if (g.enable) { removeTip (); g.enable = false; } } if (elem_name == 'textarea' || elem_name == 'select' || (elem_name == 'input' && (elem.type == 'text' || elem.type == 'password')) || e.ctrlKey || e.altKey || e.button) return true; } var c = String.fromCharCode (e.which); if (!g.enable) { if (key_match (e, o.start_keys)) { if (!showTip ()) return true; g.enable = true; g.s = new State (); } } else { if (key_match (e, o.cancel_keys)) { removeTip (); g.enable = false; } else if (key_match (e, o.shift_on_keys)) { g.s.shift = true; } else if (key_match (e, o.shift_off_keys)) { g.s.shift = false; } else if (key_match (e, o.shift_toggle_keys)) { g.s.shift = !g.s.shift; } else if (key_match (e, o.input_keys)) { g.s.input += c; var tips = document.getElementsByClassName ('_HaHm_tip'); var removes = []; for (var i = 0; i < tips.length; ++i) { if (tips.item(i).textContent.indexOf (c) == 0) tips.item(i).style.color = 'yellow'; else removes.push (i); } if (removes.length == tips.length && g.s.input.length != g.tip_digit) { removeTip (); g.enable = false; return false; } for (var i = 0; i < removes.length; ++i) { var item = tips.item(removes[i] - i); item.parentNode.removeChild (item); } } if (g.s.input.length == g.tip_digit) { removeTip (); g.enable = false; var n = 0; for (var i = 0; i < g.tip_digit; ++i) { n = n * o.input_keys.length + o.input_keys.indexOf (g.s.input[i]); } if (n < g.objs.length) { if (g.objs[n].proc == 'open') open (g.objs[n].elem.href, g.s.shift); else if (g.objs[n].proc == 'focus') g.objs[n].elem.focus (); } } } return false; } function showTip () { var template = makeTipTemplate (); var w_left = window.scrollX; var w_right = window.scrollX + window.innerWidth; var w_top = window.scrollY; var w_bottom = window.scrollY + window.innerHeight; var w_width = w_right - w_left; var w_height = w_bottom - w_top; g.objs = new Array (); Array.filter ( document.links, function (l) { var pos = l.getBoundingClientRect (); if (0 <= pos.left && pos.right <= w_width && 0 <= pos.top && pos.bottom <= w_height) g.objs.push ({elem: l, proc: 'open'}); return true; }); { var inputs = document.getElementsByTagName ("input"); var textareas = document.getElementsByTagName ("textarea"); var selects = document.getElementsByTagName ("select"); var as = new Array (inputs.length + textareas.length + selects.length); for (var i = 0; i < inputs.length; ++i) as[i] = inputs[i]; for (var i = 0; i < textareas.length; ++i) as[inputs.length + i] = textareas[i]; for (var i = 0; i < selects.length; ++i) as[inputs.length + textareas.length + i] = selects[i]; Array.filter ( as, function (a) { var pos = a.getBoundingClientRect (); if (0 <= pos.left && pos.right <= w_width && 0 <= pos.top && pos.bottom <= w_height) g.objs.push ({elem: a, proc: 'focus'}); return true; }); } if (g.objs.length == 0) return false; g.tip_digit = (g.objs.length == 0 ? 0 : Math.floor (log (g.objs.length, o.input_keys.length)) + 1); g.objs.forEach ( function (link, i) { var l = link.elem; var pos = l.getBoundingClientRect (); var tip = template.cloneNode (true); var str = ""; var rest = i; for (var d = g.tip_digit; d > 0; --d) { str = o.input_keys[rest % o.input_keys.length] + str; rest = Math.floor (rest / o.input_keys.length); } with (tip) { textContent = str; style.left = pos.left + w_left + o.tip_offset_h * g.tip_digit + 'px'; style.top = pos.top + w_top + o.tip_offset_v + 'px'; } document.body.appendChild (tip); }); return true; } function removeTip () { var tips = document.getElementsByClassName (o.tip_class); while (tips.length) tips[0].parentNode.removeChild (tips[0]); } document.addEventListener ('keypress', keypress, false);