blog
毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第9回。
「あなだのコードがー しぬほどスキダカダー!」(@チャン・ドンゴン)
komagata a.k.a. DJです。
今回紹介するのはコレ。
Ham Cutlet Chrome Extension – Help me, hackers!
HTMLのインデントを綺麗にする誰得サービスHam CutletのGoogle Chrome Extension。ブラウザの「このページのソースを見る」の代わりにインデントを綺麗にしたソースを表示するボタンをOmniBoxの右に追加するExtensionだ。
mongorian_chop a.k.a. 自由人が作ってくれた。ボタンのアイコンはmachida a.k.a. 漫★画太郎。軽いコラボ。

コードはココ。
mongorian-chop’s hamcutlet-extension at master – GitHub
manifest.json
[cc lang="javascript"]{
“name”: “Ham Cutlet Extension”,
“version”: “0.1″,
“description”: “Ham Cutletを使用したHTMLソース表示”,
“background_page”: “background.html”,
“browser_action”: {
“default_icon”: “icon.png”,
“defailt_title”: “Ham Cutlet”
},
“permissions”: [
"tabs"
]
}[/cc]
background.html
[cc lang="html"]
[/cc]
Chrome Extensionの作り方なんて知らないって?それならDJに任せろ!当然の如くDJも知らないからお前らの代わりに泣きながら覚えてやる!
モダンブラウザ・JS好きなら知らぬものはいないid:os0xの連載、続・先取り! Google Chrome Extensions:第1回 Chrome ExtensionsのAPI#1|gihyo.jp … 技術評論社で丁寧に説明されてるのでこれでわかりそうだ。
DJが超訳するとこんな感じだ。
- manifest.jsonの入ったディレクトリを作る
- 上のバーの右にあるボタンはbrowser_actionという
- browser_actionのボタンを押した時の動きはbackground_pageで指定したhtmlに書く
- Chromeで.crxパッケージを作れる。(単なるzipなので解凍も出来る)(CUI野郎にはConstellation’s crxmake at master – GitHubというパッケージングコマンドもあるらしい。)
作れる気がしてきた!早速コードを見ていこう。
6行目のbrowser_actionでタイトルとアイコンを設定してる。
10行目のpermissionsはクロスドメインでアクセスできるURLを指定するようだが、tabsはなんだろう?
5行目のbackground_pageで実際の動作を書くファイルを指定している。
background.htmlを見ていこう。
10行目のchrome.browserAction.onClicked.addListenerがキモだ。こうやってbrowser_actionをクリックした時の動作を設定するのだろう。
7行目のchrome.tabs.createでURLを指定してタブを開けるらしい。何かグリモンでも思ったけどクロスブラウザ問題が無いとJSってこんなにシンプルに書けるのかという・・・。
URLは以前、「HTMLのインデントを綺麗にするAPI – Help me, hackers!」でkyanny a.k.a. 刺身ブーメランが作ってくれたHam Cutlet APIを使って該当ページのソースをインデントを綺麗にした上で取得している。
これは簡単・・・。グリモンやFF拡張もそうだけど、実用的なものが簡単に作れるのがイイ!ECMAScripter的にもクロスブラウザに悩まされず思いっきりJSを書けるのも気持ちがイイ。
毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第8回。
「おでのからだはぼどぼどだー!」
komagata a.k.a. DJです。
今週はコレ。
WordPress.orgのプラグイン一覧でダウンロード数に目印をつけて欲しい – Help me, hackers!
登録してくれたのはmonoooki a.k.a. 前田製作所。速攻解決してくれたのはazu_re a.k.a. Firefoxの人(DJ主観)。
ソースコード:
[cc lang="javascript"]// ==UserScript==
// @name WordPress.org highlight counter
// @namespace http://efcl.info/
// @description WordPress.orgのタグ/単語検索のダウンロード数によって色を変える
// @include http://wordpress.org/extend/plugins/tags/*
// @include http://wordpress.org/extend/plugins/search*
// ==/UserScript==
/* TEST URL
http://wordpress.org/extend/plugins/search.php?q=word
http://wordpress.org/extend/plugins/tags/widget
*/
GM_addStyle(<>
.GM_downloads_count_50000{
background: #cc0000; color: #fff; padding: 1px 3px; -moz-border-radius: 3px;
}
.GM_downloads_count_10000{
background:#ffb0b0; padding: 1px 3px; -moz-border-radius: 3px;
}
.GM_downloads_count_5000{
background:#ffd792; padding: 1px 3px; -moz-border-radius: 3px;
}
.GM_downloads_count_2000{
background:#f9f49d; padding: 1px 3px; -moz-border-radius: 3px;
}
]]>>);
function hilightCounter(node){
var downloadsSpan = $X(‘//span[@class="info-marker"][text()="Downloads"]‘ ,node);
for(var i=0,len=downloadsSpan.length;i
var downloadCount = parseInt(countTextNode.textContent.replace(",","","g"), "10");// 数値化
var parDownload = downloadsSpan[i].parentNode;
var span = document.createElement("span");
if(downloadCount > 50000){
span.setAttribute(“class” , “GM_downloads_count_50000″);
}else if(downloadCount > 10000){
span.setAttribute(“class” , “GM_downloads_count_10000″);
}else if(downloadCount > 5000){
span.setAttribute(“class” , “GM_downloads_count_5000″);
}else if(downloadCount > 2000){
span.setAttribute(“class” , “GM_downloads_count_2000″);
}else{
continue;
}
span.textContent = countTextNode.textContent;
parDownload.replaceChild(span , countTextNode);
}
}
document.body.addEventListener(‘AutoPagerize_DOMNodeInserted’,function(evt){
var node = evt.target;
//var requestURL = evt.newValue;
//var parentNode = evt.relatedNode;
hilightCounter(node);
}, false);
hilightCounter(document);
// $X on XHTML
// @target Freifox3, Chrome3, Safari4, Opera10
// @source http://gist.github.com/184276.txt
function $X (exp, context) {
context || (context = document);
var _document = context.ownerDocument || context,
documentElement = _document.documentElement,
isXHTML = documentElement.tagName !== ‘HTML’ && _document.createElement(‘p’).tagName === ‘p’,
defaultPrefix = null;
if (isXHTML) {
defaultPrefix = ‘__default__’;
exp = addDefaultPrefix(exp, defaultPrefix);
}
function resolver (prefix) {
return context.lookupNamespaceURI(prefix === defaultPrefix ? null : prefix) ||
documentElement.namespaceURI || “”;
}
var result = _document.evaluate(exp, context, resolver, XPathResult.ANY_TYPE, null);
switch (result.resultType) {
case XPathResult.STRING_TYPE : return result.stringValue;
case XPathResult.NUMBER_TYPE : return result.numberValue;
case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
// not ensure the order.
var ret = [], i = null;
while (i = result.iterateNext()) ret.push(i);
return ret;
}
}[/cc]
このブログを読んでる人達ならそろそろグリモン見慣れてきたんじゃないか?泣きながらソース読んでたDJも慣れてきたよ。
12行目からはxmlリテラルでstyleを追加。でも、先頭の<>ってのはなんだろう?
61行目の$X関数はXPathのためのラッパー。こうして見ると色々な人が色々なXPathユーティリティーを選んでるのがわかる。$X関数は比較的昔からあって有名な方なんじゃないかと思う。
26行目のhilightCounterで注文通り、ダウンロード数別に色を変えるためにclassを割り当ててる。
41行目はびっくり。AutoPagerizeのイベントにひっかけてる。ブログのソースコードのSyntax HilightingとかJSのonloadでやってる場合に、AutoPagerizeの2ページ目からは反映されなくてガッカリ・・・ってことが多かったんだけどこれを使えばイケる!
これはDJ得した気分。
WordPressのプラグインページは見る人が多いからuserscripts.orgにきちんと登録してある点も素敵だ。
毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第7回。
今、流行の24シーズン1を見ているkomagata a.k.a. DJ(@流行に疎い方)です。
今回紹介するコードはコレ。
任意の期間、ブログが更新されないとメールが飛ぶwordpressのplugin – Help me, hackers!
出題したのは@machida a.k.a. お米嫌い。議論に対していきなりコードで回答を示したのが@terakuma a.k.a. 法務系PHPer。
コード:
[cc lang="php"]
/*
Plugin Name: Remember The WordPress
Plugin URI: http://github.com/e2esoundcom/Remember-The-WordPress
Description: If you forgot write a new post,this plugin send you E-mail.
Version: 0.2
Author: Yuya Terajima
Author URI: http://www.e2esound.com/
*/
/* When Activate Plugin
==================================================================== */
function rtw_activate() {
$time = time();
$terms = 7; //default terms
$unixtime_per_day = 86400;
$email = get_bloginfo('admin_email');
$subject = "Remember the WordPress!!";
$message = "Blogの更新が滞っているようです。サイトの更新をお願いします。";
if(!get_option('rtw_initialized') || get_option('rtw_initialized') !== $time) {
update_option('rtw_initialized',$time);
}
if(!get_option('rtw_terms')) {
update_option('rtw_terms',$terms * $unixtime_per_day);
}
if(!get_option('rtw_email')) {
update_option('rtw_email',$email);
}
if(!get_option('rtw_subject')) {
update_option('rtw_subject',$subject);
}
if(!get_option('rtw_message')) {
update_option('rtw_message',$message);
}
wp_schedule_event($time + 86400, 'daily', 'rtw_cron');
}
register_activation_hook(__FILE__, 'rtw_activate');
function get_latest_post_time() {
global $wpdb;
$query = "SELECT post_date FROM ".$wpdb->posts.”
WHERE post_status = ‘publish’
ORDER BY `”.$wpdb->posts.”`.`post_date` DESC LIMIT 0,1″;
$query = $wpdb->prepare($query);
return strtotime($wpdb->get_var($query));
}
function rtw_sendmail()
{
$subject = get_option(‘rtw_subject’);
$mailbody = get_option(‘rtw_message’);
$to_email = get_option(‘rtw_email’);
mb_send_mail($to_email,$subject,$mailbody);
}
function rtw_compare_time(){
$unixtime_per_day = 86400;
$latest = get_latest_post_time();
$terms = intval(get_option(‘rtw_terms’)) + intval($latest);
$time = time();
if(intval($terms) < intval($time)){
rtw_sendmail();
}
}
add_action('rtw_cron', 'rtw_compare_time');
/* When New Post or Edited Post.
==================================================================== */
function rtw_new_posted(){
update_option('rtw_initialized',time());
}
add_action('publish_post','rtw_new_posted');
/* When De-Activate Plugin
==================================================================== */
function rtw_deactivate() {
wp_clear_scheduled_hook('rtw_cron');
delete_option('rtw_initialized');
delete_option('rtw_terms');
delete_option('rtw_message');
delete_option('rtw_subject');
delete_option('rtw_email');
}
register_deactivation_hook(__FILE__, 'rtw_deactivate');
/* Admin
==================================================================== */
function rtw_add_admin_menu(){
add_options_page('Remember The WordPress','Remember The WP','administrator',__FILE__,'rtw_add_admin_page');
}
add_action('admin_menu','rtw_add_admin_menu');
function rtw_add_admin_page(){
if(isset($_POST['posted']) === FALSE){
$posted = FALSE;
}elseif(isset($_POST['posted']) === TRUE){
$posted = TRUE;
}
$unixtime_per_day = 86400;
if($posted) {
//Validation
if(preg_match('/[1-3][0-9]|[1-9]/',intval($_POST['terms']) AND intval($_POST['terms']) <= 30)){
update_option('rtw_terms',intval($_POST['terms'] * $unixtime_per_day));
update_option('rtw_email',stripslashes($_POST['email']));
update_option('rtw_subject',stripslashes($_POST['subject']));
update_option('rtw_message',stripslashes($_POST['message']));
$rtw_error = FALSE;
}else{
$rtw_error = TRUE;
}
}
?>
//Admin Page Start
//Updated Message
if($posted === TRUE AND $rtw_error === FALSE) : ?>
設定を保存しました
アラート発生日数は1-30の間の値を入力して下さい。
Remember The WordPress Settings
[/cc]
少々長いがへこたれずに頑張ってみていくことにしよう。(自分自身を鼓舞)
WordPressプラグインの作成方法
前提としてこれはWordPressプラグインなのでその大まかな作り方を紹介しよう。
まず、単体のphpファイルか、もしくは同名のディレクトリにプラグイン用のファイルを作る。
(例:foo_plugin/foo_plugin.php)
プラグインの各種情報は決まったコメントの書き方があるのでそれにしたがってファイル内先頭に書く。これがWordPressのプラグイン管理画面に出てくる情報になる。
一番単純なプラグインとしては、プラグインを管理画面から有効にすると、プラグインのphpファイルがWordPressにincludeされる。よって適当な関数をプラグイン内で定義しておき、テーマの中で使うといったことが可能だ。(モチロン名前はぶつかる可能性がある。プラグイン名をprefixにした関数を作るか、プラグイン名のクラスに閉じ込めることが推奨されている。)
これでWordPressの関数や変数、DBをプラグイン内からでも使うことが出来る。また、プラグイン用にactionやfilterといったフックをかける事ができる。この辺は公式ドキュメントに全関数リファレンスやプラグイン用のフックの説明がちゃんとあるのでPHPが分かれば理解するのは容易だ。(Main Page – WordPress Codex 日本語版)
擬似cron
今回のコードを理解する上でもう一つ必要になるのが、wp_schedule_event関数を使った擬似cronの仕組みだ。WordPressにはwp_schedule_event関数でcronのように定期スケジュールを登録することが出来る。こういう処理はcron daemonのように常に起動しているプロセスと連携する必要があり、一見単純なPHPウェブアプリだけでは無理に思えるが、Webからのユーザーリクエストのみをトリガーにcronのようなスケジュール実行を可能にしている。
もちろんWebからのリクエスト頼みなのでアクセスがまったく来なければスケジュールした時間には実行されない擬似的な機能なので厳密な処理には向かない。しかし、一般的なサイトであれば大抵毎分1アクセスぐらいはあるので大体このぐらいの頻度でやって欲しいといった処理なら実用上は問題なさそうだ。
参照:Function Reference/wp schedule event « WordPress Codex
こんないい加減な仕組み使うなんてありえねー!とヤルタイプからは言われそうだが、phpのsession削除は同様の仕組みで行われている。(アクセスが来なければ永久にセッションファイルは消えない)
根本的なこの仕組の是非は兎も角、PHPの流儀に従った実装方法ではあると言える。
Remenber The WordPress
まず、38行目でregister_activation_hook関数で自分自身のファイルのrtw_activate関数をactivation_hookに登録している。activationとはプラグイン管理画面でそのプラグインを「有効」にしたときのことだ。データベーステーブルを独自に持つようなプラグインでは大抵この場所でテーブルをCREATEしている。(WordPressはORMを提供していないので、各種プラグインのこの場所にはMySQLに特化したSQLが大抵使用されている。WordPressはプラグインエコシステムがキモであるため、コレのせいで他DBへの対応が非常に難しくなっている。)
rtw_activate関数の中でwp_schedule_eventを使ってdailyで実行されるようにrtw_cronを登録している。
あとは、40行目のget_latest_post_time関数で最終投稿日を取得して、管理画面で設定できる「何日更新が無かったらメールを送るか?」という値と比較してmb_send_mailでメールを送っている。
殆どの行数は管理画面用なので長く見えるし大変そうだが、管理画面からプラグインの設定が出来るというのがWordPressユーザーに非常に訴える箇所なので重要だ。
議論にコードで答えた事。(これが結構貴重なんだが)WordPress流儀に則った実装。管理画面まで手を抜かない所。等々、@terakuma a.k.a. 法務系PHPerは素晴らしい。それにRemember The MilkをパクったRemember The WordPressというプラグイン名も素敵だ。
WordPressプラグインで困ったことがあったら@terakuma a.k.a. 法務系PHPerに相談してみたらいいかもしれない!
毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第6回。
おい!ワールドカップ見てない癖に適当に話を合わせるのは辞めろ!DJ?モチロン見てたよ。本田のシュートは凄かったよね!・・・くらいやがれ!これが俺のワールドカップに対する知識の全てだ!
komagata a.k.a. DJです。
今回紹介するのはコレ。
無印良品のサイトの表示をデフォルトで「安い順、150件表示」にする – Help me, hackers!
まさに「プログラマーにとって解きたくなるような問題」といったセンスの良い問題を投稿してくれたのは@monoooki a.k.a. 前田製作所。
そして解決してくれたのはお馴染み@milk1000cc a.k.a. 牛乳嫌いプログラマ on Rails。(@milk1000ccはDJのストーキング情報によれば最近PONPONというグルーポン情報まとめサイトをRails, Herokuで公開したらしいRails野郎だ。)
二人ともありがとう!
しかし、ちょっと待ってくれ。コイツをどう思う?(@阿部高和)
% heroku console
>> Task.find(106).created_at
=> Tue, 06 Jul 2010 04:16:07 UTC +00:00
>> Task.find(106).comments.correct.first.created_at
=> Tue, 06 Jul 2010 07:02:54 UTC +00:00
「すごく・・・早いです・・・。」(@道下正樹)
タスクが登録されてからわずか3時間足らずで解決されている。(@荒巻)
たしかにDJも「これは解きたくなる問題だな」とは思ってはいたが、あまりにもはやすぐるでしょう?
本題のタスクの内容は、
こんな感じで無印良品ネットストアのデフォルトは「おすすめ順」「50件表示」になってる。それを「安い順」「150件表示」をデフォルトになるようにして欲しいというタスク。
あるある。確かにハードに使ってるサイトでこういうちょっとしたところを俺仕様にしたいことある。
それを通常ならありえない速さできょうきょ参戦(@ブロントさん)してくれたグリモンコードがコレ。
http://gist.github.com/465095
(右上のrawのリンクをクリックでグリモンスクリプトインストール)
// ==UserScript==
// @name AutoMujiSort
// @namespace http://www.milk1000.cc/
// @include http://www.muji.net/store/cmdty/section/*
// ==/UserScript==
(function() {
var OLD_PATTERN = { sort: 4, count: 12 };
var NEW_PATTERN = { sort: 0, count: 150 };
var $ = function(selector) { return document.querySelector(selector); };
if ($('select[name="sort"]') && $('select[name="sort"]').value == OLD_PATTERN.sort &&
$('select[name="count"]') && $('select[name="count"]').value == OLD_PATTERN.count) {
$('select[name="sort"]').value = NEW_PATTERN.sort;
$('select[name="count"]').value = NEW_PATTERN.count;
(typeof unsafeWindow != 'undefined' ? unsafeWindow : window).sort();
}
})();
きれいなコード。DJ以前はグリモンのコード「嫌だなぁ、嫌だなぁ、怖いなぁ」(@稲川淳二)なんて思ってたけどこの連載のお陰でむしろ好きになってきた。読み易い。
11行目のquerySelectorを$に割り当てるのはECMAScripterお馴染みパンチライン。最初のvarはconstにしたら速くなるのかも?
17行目のunsafeWindowを見てるのは多分ブラウザによって違うところなんだろう。
DJそろそろ気付いてきたんだが、今、タスクの需要と供給は依頼が少ない状態にある。特にJavascriptのタスクは監視してるイカレタHacker達が飢えた獣のように跳びかかり、瞬時に解決されてしまう。
成果物の公開方法やパッケージング方法、環境作りが楽なグリモン・JavascriptはHacker側にとって非常にやりやすい。そういう事情を踏まえた依頼の仕方もセンスが問われている。
センスを磨くか、金を積むか。そういう仕組みになっているらしい。
毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第5回。
TimeMachineで自動バックアップを取っていても結局クリーンインストール後は手でデータを復旧させる昭和の男、komagata a.k.a. DJです。
今回はコレ、フォームのデフォルトの文字をフォーカスが移ったら消す – Help me, hackers!
コレの経緯はこんな感じだ。
- F5V a.k.a. PHPerよりHam CutletにTwitter経由で上記の要望が入る。
- DJが要望をHelp me, hacekrs!に登録する。
- mongorian_chop a.k.a. 自由人からjQuery Watermark Pluginの情報を貰う。
- USTREAMの開発生放送でDJが上記プラグインでの実装に敗北。
- 見るに見かねたmongorian_chopが実装してpull request。
100万語の言葉より1つのpull request。コードは偉大だ。
「コード!コード!コード!」
「よし、ならばコードだ。」
今回のキモはココ。
$(document).ready(function(){
$.updnWatermark.attachAll();
$('.updnWatermark > label').click(function(){
$(this).toggle();
$("textarea").select();
}).blur(function(){
$(this).toggle();
});
});
$(document).ready(function(){ … }) はお馴染み、DOMの構築が終わった瞬間に実行してくれるjQueryの書き方だ。$(function() { … }) でも同じ。画像の読み込み等を待たない分、onloadより速いのでjQuerystはドンドン使ってこ!
今回、DJが挫折してたのはjQuery Watermark Pluginをtextareaに使った時に、そのWatermarkのテキスト自身をクリックした場合にWatermakが消えないところだ。
mongorian_chopはそこをちゃんと消すコードを書いてくれた。ありがとう!
jQueryだとちょっと面倒に思えた処理もこんなにスマートな見た目で書けるなんて。DJこの連載のおかげで強制的にJS勉強させられてるね!
DJ、今現在もHelp me, hacekers!上で複数のコード野郎共とコラボ中だ。議論する暇があったらコードを送りつけろ!
help me, hackers!でも色々参考に(ときにパクったり)させていただいた@func09さんの美味しそう!を共有するサービス「ラペコ」。
美味しいそうな食べ物の写真が並んでて見ていて楽しい、サイトのデザインも可愛いくて素敵なサイトです。モニターに映るラペコを前に僕らの間は嫉妬の空気がいつも充満。
ラペコにアップした@komagataさんの肉の写真がかなり長い間人気のペコフォトランキングで上位に。
@komagataさんが「komagata a.k.a. rapeco」の肉番長の名を欲しいままにしています。
2010年6月26日現在2位!
ちなみに写真の右側にいるのが僕で、左がアクトインディ社長の@lupin13さんです。ついでに僕の当時の携帯にもその肉の写真が入ってました(ココ)。
@func09さんはhelp me, hackers!にあげたハムカツ改良タスクを見事な方法で解決にしていただいたり、生放送に付き合ってくれたり、本当ナイスガイなプログラマーさん。いつもありがとうございます。
「komagata a.k.a. ラペコの肉番長」への挑戦者求む!
Macのマイクから獣の吠え声の様なものしか聴こえないkomagata a.k.a. DJです。
毎週火曜日、Help me, hackers!に上がったコードを紹介していくコードDJ 第4回。
今回はコレ、HTMLのインデントを綺麗にするAPI – Help me, hackers!
HTMLのインデントを綺麗にするサービスHam CutletにAPIを付けるというタスク。やってくれたのはkyanny a.k.a. 刺身ブーメラン。
コードはコレ。
source = NKF.nkf('-w', expand_tab( open(params[:url]){ |f| f.read } ) )
38行目のココでurlで指定したページのソースを取ってきてタブをスペースに変換。NKFでutf-8に変換。-wはそういうオプションみたいだ。後はそれを表示すればいい。
erb :created, :layout => false
49行目でerbを使ってHTMLを表示している。sinatraではget ‘/’ do; endなどのメソッドは文字列を返せばそれが表示されるというシンプル設計。他でも使ってるhamlメソッドなども文字列を返すメソッドだ。
なのでココは、
@html
とやるだけでもいい。
このタスクのお陰でAPIが出来たので、誰得Chrome拡張などを作ってもらう事が出来た。
他のプログラマーにpushしたりpullしたりするのは何故か訳も無く楽しい。コードを介したコミュニケーションはプログラマーにしか出来ない。kyanny a.k.a. 刺身ブーメランとのコラボレーションも無性にテンション上った。コレは騙されたと思って試してみて欲しい。何かテンション上るんだ。
ちょっとDJ、Windowsプログラマーとか色んなプログラマーにpushしたりpullしたりしたくなって来た。
父の日前に「電子書籍リーダーに興味がある」ってiPad送れってことか?
汚い。流石親父汚い。
komagata a.k.a. DJです。
今週はコレ:COOK後にHTMLをクリップボードに保存したい – Help me, hackers!
やってくれたのは@func09 a.k.a. ラペコの中の人だ。
Ham CutletはHTMLのインデントを綺麗にする誰得サービスだ。タグのネストが綺麗に揃うと尋常じゃない行数になることがあるので全選択してコピーするのが非常に面倒臭い。これを解決したいという依頼。(というかほとんどバグかってぐらい苦痛)
コレ、textareaだったら
elem.select()
で完了なんだけど、コードはpre, codeタグで出したいというDJと@machida a.k.a. マークアップエンジニアのわがままで実装方法がわからず心が折れてた。
それをエレガントに解決してくれた@func09のコードがコレ。
* HTTP * Git Read-Only http://github.com/func09/hamcutlet.git This URL has Read-Only access
#created.section
- if flash[:error]
.error
%h2 HTMLに下記の問題があります。
%p= flash[:error]
.to_top
%a{:href => '/'}トップページに戻る
- else
%h2 インデントの美しいHTMLが出来ました
%button.raw plane text
.source
%textarea{:style => 'display:none;'}
=h @html
%pre
%code=h @html
:javascript
$(function(){
$('.source textarea').height($('.source pre').height());
$('button.raw').click(function(){
if ($('.source textarea').css('display') == 'none') {
$('button.raw').text('html')
$('.source textarea').fadeIn();
$('.source pre').hide();
$('.source textarea').focus().select();
} else {
$('button.raw').text('plane text')
$('.source textarea').hide();
$('.source pre').fadeIn();
}
});
});
このテンプレはお馴染みのHaml。:javascriptというのはjavascript用のhaml_filterで、この中はHaml記法から離れて自由にJavascriptが書ける。Hamlの構文チェッカーから逃れてホッとするという記法だ。
13行目で非表示のtextareaを作っておいて、22行目以降のボタンのonclickでpre, codeタグ表記とtextarea表記を入れ替えてる。
そうか、選択する時だけtextareaに変えちゃえばいいのか。そういわれてみればたしかにブラウザのwysiwygエディタでよく見る手法だ。頭柔らかいね・・・。
23行目の表示状態をトグル条件にするってのも色々なところでパクろうと密かに思ったよ。
25, 31行目のfadeIn()はDJ、バファリンの半分と同じ成分だと思うな。
自分のコードを書くのも楽しいけど、人のコードにちょっかい出すのも楽しい。githubも素敵だ。
次回からも更にコードにフォーカスして行くのでみんなのコードを晒してくれ、そして読ませてくれ!
(随時更新中)
@machidaさんがAdobe CS5を買ったので遂に仕事環境をMacに移行することに。そこでどうせだったらガチの環境作ろうぜということで僕がここに手順を書いて、 @machidaさんがそれを見て環境を作っていくというのをやることにしました。 プログラマーと連携して仕事をするデザイナーの方の参考になれば幸いです。
クリーンインストール
- Snow LeopardのDVDを入れて「c」キーを押しながら再起動。
- ディスクユーティリティーを使ってパーテーションを全部削除。
- インストール。
Mac OS X Snow Leopardをクリーンインストールする方法。 | 僕がMacを使う理由。
Google日本語入力
ココからダウンロードしてインストール。
Adobe Creative Suite 5 WEB PREMIUM
インストール
開発環境をインストール
ターミナル環境
ターミナル.app(別名”黒い画面”)を使っていきます。
フォントや背景色、背景画像などカスタマイズできるので自分の好きな感じにカスタマイズします。ある程度の設定がセットになったテーマもあります。
MacPortsのインストール
MacPortsのページのAvailable DownloadからSnow Leopard用のMacPortsをダウンロードしてインストールします。
MacPortsの使い方
MacPortsはコマンド一つでソフトのダウンロード、インストールを自動でやってくれる便利なソフトです。portというコマンドがそれです。
まずはMacPorts自身を最新版にアップデートしましょう。
$ sudo ports selfupdate
sudoは後に続くコマンド(ここでは”ports selfupdate”)を管理者権限で実行するコマンドです。
MacPortsはダウンロード、インストールできるソフトの名前とバージョンのリストを持っています。それを最新にしたい場合にはsyncします。
$ sudo port sync
これでローカルのリストが最新の状態になったので欲しいソフトをインストールする準備ができました。
欲しいソフトを探すにはport searchを使います。vimという名前を含むソフトを探します。
$ port search vim
見つかったらインストールします。
$ sudo port install vim
Apacheをインストール
$ sudo port install apache2
PHPをインストール
MySQLをインストール
WordPressをインストール
@dandasoに「プロジェクト」と「タスク」の使い方がよく分からないと言われたので紹介したいと思います。
Help me, hackers!は要は「報酬付き公開BTS」なのでTracやRedmineと似たようなものです。
| 仕事のまとまり | 仕事 | |
|---|---|---|
| Trac | Project | Ticket |
| Redmine | Project | Issue |
| Help me, hackers! | プロジェクト | タスク |
Help me, hackers!を使う上でプロジェクトは必須ではありません。突発的なタスクは単純なTODOのように登録して問題ありません。
しかし、何かのアプリを作る。何かのWebサービスを作る。などといった大きめの仕事は細かいタスクをプロジェクトという形で一覧できた方が便利です。
例えば、これはHam CutletというWebサービスのプロジェクトですが、これに関係のあるタスクが一覧できるので、未解決のタスクをひたすら潰していくのに便利です。
Help me, hackers!ではプロジェクトもタスクも、編集・削除はいつでも出来るので適当にタスクを登録していて、これはまとめた方が便利かな?と思ったときにプロジェクトを作成すれば十分です。(タスクの登録・編集フォームでプロジェクトを選択できます)
「ドックフードを食べる」ということで僕はRedmineの代わりに使っているので、自分のやるべきタスクがわからなくなったのでプロジェクト機能を作りました。
普通のBTSと違うところは、どうせ自分がやるつもりのタスクでも公開されているので思いがけず解決してもらえることがあることです。Twitter経由で「それはコレ使えばいいんじゃ?」ということを教えてもらうこともあります。
値段は何時でも修正できるので、適当に1000円とか登録しておいて、急に解決してもらって慌てて金額を増やして振込んだりしています。(数百円でも設定しておくと、解決する方にとっては「あ、この人はホントに解決を望んでるんだな」「勝手にこのタスクを解決しちゃっても迷惑にならない」とわかるので少額でも設定しておくのがコツだということに最近気付きました。)



