jQuery Mobile は簡単だけど・・・

スマートフォン全盛ですね。こないだ、グーグルマップの地図を見ながら歩いていたら電柱にぶつかって恥ずかしい思いをしたことは内緒です(^^;; 便利です。

便利なので、少し前にjQuery Mobileのオライリー本を買ってHTMLを入力しつつ勉強しているのですが・・・正直めんどくさい。
なんかこう、jQuery Mobile UI の ビルダー みたいな、ソフトないのかなぁ? と思って探したら、Webサービスでそういうサイトがあるにはあるんですが有料でしかも英語だからよくわからん。

やっぱり今度出る Adobeの CS6 の DreamWeaver ぐらいしか選択肢ないのかな。。。
バージョンアップしたいけど、CS4 WebStandardからだと、10万円超。
使いたいのはDreamWeaverとFireWorksだけなので、単品で買うと、計9万円。
FireWorksだけ買って、Dreamweaverは年契約月額2200円プランにするとだと、初期コストは安くつくけど、トータルで考えると割高。でもこれってサブスクリプションバージョンとリテール版を一つのPCにインストールして使えるのかな・・・。

サブスクリプションの価格が高すぎるんだよなぁ。。。

というようなグチはともかく、、、
iPhone用になんか作ってみよう・・・というわけで、ちょっと前にテレビでやってた映画で見た、プレイフェア暗号を表示させるものをつくってみた。

暗号計算部分は、簡単にできると思ったけど、ほぼなんの工夫もなく適当に作ったので本当に合ってるかどうかの検証はしていないけど、まぁいいや(^_^;;;

プレイフェア暗号 with jQuery Mobile
(スマホ用。ブラウザでも見れるけどかなり間抜けな表示になる)

HTMLを手入力するのは正直ホント面倒だけど、CSSの編集とかボタンの画像の配置だとか、HTML入力以上に厄介な作業がゼロなので、javascriptのコーディングに集中できる、という点で、デザイナー向けというより、プログラマーやコーダ-にとっては非常にありがたいライブラリなんじゃないかと思いますね。

あとは・・・フリーのUI ビルダーなツールが欲しい。そのうち、テーマローラーみたく、本家サイトで公開してくれるのかも・・・という淡い期待をしてまつ。

windowでのマウスイベント

HTMLで超シンプルなスライダーをjQueryで実装していたら、ひさびさに「超トホホ・ミス」をやって1日以上ハマってしまいました。トホホというか、単なる見落としなんですが・・・。

なんか作るとき、chromeを使って確認しているんですが・・・さて出来た!と思ってクソ・ブラウザInternet Explorerで確認すると・・・案の定動かない。IE9ではうごいていたはずなんだが・・・と思ってなにが原因かソースを見ること数時間。あれこれ試行錯誤しても動かない。

で、下のような簡単なコードをIE8で確認したら、うんともすんとも言わない。たぶん、ほとんどの人はこう思ったでしょう、

おまえはあほか!

と。

$(window).click(function(ev)
                {
                  alert('window.click!');
                });

よく考えてみたら、Internet Explorer で windowオブジェクトにonclick とか onmousedown とか onmousemoveとかなかった!というオチ。
エラー吐いてくれればすぐ分かったのに・・・なまじIE9で動いてたもんだから分からなかった。

肝心のスライダーは、こんなん。テストページはこっち。

// 単純なスライダーの一部(とりあえずUI部品だけのテストコード)
// これにコールバックを登録するコードを付け足せばとりあえず完成。の一歩手前。
var $frame = jQuery(document);

jQuery('.slider').mousedown(function(e)
                            {
                              var $slider = jQuery(this);
                              var el = this;
                              
                              if(typeof this.slider !== 'object')
                                this.slider =
                                {
                                  drag  : false,
                                  pos   : 0,
                                  limit : $slider.parent().width() - $slider.width() - parseInt($slider.css('paddingLeft')) - parseInt($slider.css('paddingRight')),
                                  bgcolor: this.style.backgroundColor
                                };

                              this.slider.drag = true;
                              
                              $frame.bind('mousemove.slider',
                                        function(e)
                                        {
                                          if(!el.slider.drag)
                                            return;
                                          
                                          el.slider.prev = el.slider.prev ? el.slider.prev : e.pageX;
                                          
                                          var cur = el.slider.pos + (e.pageX-el.slider.prev);
                                          if(el.slider.limit >= cur && 0 <= cur)
                                            {
                                              el.slider.pos = cur;
                                              el.innerHTML = el.style.marginLeft = cur + 'px';
                                              el.slider.prev = e.pageX;
                                            }
                                          else if(el.slider.limit < cur)
                                            {
                                              el.slider.pos = el.slider.limit;
                                              el.innerHTML = el.style.marginLeft = el.slider.pos + 'px';
                                            }
                                          else if(0 > cur)
                                            {
                                              el.slider.pos = 0
                                                el.innerHTML = el.style.marginLeft = el.slider.pos + 'px';
                                            }
                                        })
                                .bind('mouseup.slider',
                                      function(e)
                                      {
                                        $slider.mouseup();
                                      });
                              
                              e.preventDefault();
                       })
                 .mouseup(function(e)
                       {
                         if(this.slider.drag)
                           {
                             this.slider.drag = false;
                             this.slider.prev = 0;
                             $frame.unbind('.slider');
                           }
                       });

やっつけスクリプトに、えらい時間かけてしまった・・・あ~あ orz

・・・とにかく、一刻も早くIE8を含め、IE10以下のバージョンのInternet Explorerがこの世から消え去ることを願うばかりなり。

HTML DOM access with C#

備忘録です。

System.Net.WebRequest / System.Net.WebResponse クラスを使ってWeb上のHTMLを取得するのは、非常に簡単にできます。MSDNドキュメントにもサンプルがありますし、ネット上でも豊富にサンプルがあるので問題はないです。

が!、取得したHTMLファイルを元に、DOMアクセスさせようとすると途端に.NET Frameworkプログラマーの初心者には、行き詰まってしまいます。 その原因として二つの壁にぶち当たりました。

  1. そもそもHTML の DOMパーサーが、標準でクラスライブラリにない。
  2. Web上から取得したHTMLファイル内に使われている文字コードの判別と、文字コード変換を担うクラスライブラリが標準で提供されていない。

このうち(1)のDOMパーサーについては・・・

Internet Explorer で使われている(正確には WebBrowser コントロールだけど)mshtml.dllが提供するパーサーに任せることができます。mshtml.dll のタイプライブラリが登録されているので、参照を追加することでDOMアクセスができます。具体的には・・・

//HTML内のリンクを列挙するサンプル
using Microsoft;

public static void DomAccessSample(string html)
{
  var hd = new mshtml.HTMLDocument();
  var hd2 = hd as mshtml.IHTMLDocument2;
  
  hd2.write(html);
  foreach (mshtml.IHTMLElement link in hd2.links)
    {
      Console.WriteLine((string)link.getAttribute("href", 0));
    }
}

こんな感じでしょうか。Visual StudioのIDEを使わず、コマンドラインからビルドするには、

#アセンブリの位置はあくまで僕の環境です。 
csc /r:C:\Windows\assembly\GAC\Microsoft.mshtml\7.0.3300.0__b03f5f7f11d50a3a\Microsoft.mshtml.dll ソースファイル.cs

のように、GACに登録されているアセンブリ(DLLファイル)を参照に加えてビルド。

さて、次は、(2)なんですが・・・

MSDNでのサンプルなんかでは、System.Net.WebResponse.GetResponseStream() メソッドから得られるストリーム(Webから取得したバイト列)を、適切なSystem.Text.EncodingとともにSystem.IO.StreamReaderクラスのコンストラクタに渡して、ReadLIneメソッドなどで文字列(string)などを得るようなコードが提示されています。↓のような感じ。

http://msdn.microsoft.com/ja-jp/library/system.net.webresponse.getresponsestream(v=VS.90).aspx

しかしながら・・・このMSDNのサンプルコードには不満があります。というか、このコードの実用性は全く無い。

(サンプルなんだから当然っちゃー、当然なんですけどね。)

だって、取得したストリームに使用されている文字コードをUTF8と決めうちしてるんだもんねぇ~。 Web上のテキスト・リソースは、日本に限っただけでも、主にShift_JIS/EUC-JP/ISO-2022-JP/UTF-8 があるし・・・。

Perlで言えばEncode モジュールのような文字コードを判別する処理が必須なんですけども .NET Framework の標準クラスライブラリにはそういったクラスが無い。まぁ、彼ら(.NET Framework の開発者)からしてみれば同じ言語なのに3つも4つも文字コード規格がある、なんてことにいちいち対応してられないんでしょうね。

で、ストリームから得られたバイト配列の文字コードを判別する方法は、検索してみると、下記に詳しく解説されていますね。

文字コードを判別する (DOBON.NET)

この、mlang を使用する方法がお手軽でいいんじゃないかと・・・。Internet Explorer で使われている mlang.dll を利用します。上記サイトで解説されているのように、mlang.idlファイル(WindowsSDKに同梱されています) から、タイプライブラリを生成してレジストリに登録し、Visual C#のプロジェクトに参照を加えることでmlang(MultiLanguage)を利用します。Visual StudioのIDEを使わない場合は、tlbimp.exe で タイプライブラリからプロキシ(ラッパー?)アセンブリ(DLLファイル)を生成して、コンパイルするときにそのDLLを/rオプションで読み込ませます。

//コードの大半は、上記サイトからのコピペ。
//バイト配列から文字コードを推測して、System.Text.Encoding を返す
using System.Text;
using MultiLanguage;

public static Encoding DetectEncoding(byte[] bytes)
{
  sbyte[] sb = (sbyte[])(object)bytes;
  int len = sb.Length;
  var ml = new CMultiLanguageClass() as IMultiLanguage2;
  int scores = 5; //実際には1で十分だけど
  var detects = new tagDetectEncodingInfo[scores];

  //文字コード判別
  ml.DetectInputCodepage(8|4,0,ref sb[0],ref len,out detects[0],ref scores);

  //DetectInputCodepageメソッドから返ると、scoresには実際にdetect配列に格納された候補数が入っている。
  //返された、DetectEncodingInfoとscores(候補数)を見てみる。
  //DetectEncodingInfo.nCodePageがコードページ。
  //Windows(日本語)なら932とかUTF-8なら65001とか入っているはず。
  Console.Error.WriteLine("Scores = {0}",scores);
  for(int i=0;i<scores;i++)
    {
      Console.Error.Write("CodePage[{0}] = {1}; ",i,(int)detects[i].nCodePage);
      Console.Error.Write("Confidence[{0}] = {1}; ",i,(int)detects[i].nConfidence);
      Console.Error.Write("Percentage[{0}] = {1}; ",i,(int)detects[i].nDocPercent);
      Console.Error.WriteLine("");
    }

  //一番初めのtagDetectEncodingInfoのEncodingを取得
  Encoding enc = Encoding.GetEncoding((int)detects[0].nCodePage);

  //後始末らしい。
  System.Runtime.InteropServices.Marshal.ReleaseComObject(ml);

  return enc;
}

COMインターフェイスのIMultiLanguage2::DetectInputCodepage() メソッドは、複数の候補を返すことができ、DetectEncodeInfo 構造体にどのくらい正確かを示すint値が入っているようで、それらから判断するようです。ちなみに、IMultiLanguage2::DetectInputCodepage() メソッドの第一引数には、判別するバイト列がどのようなものかを予め入力できて、僕は、4(MLDETECTCP_DBCS) | 8(MLDETECTCP_HTML) = 12 を指定しました。

ただ、僕の環境~WIndows7 x64版~では、WindowsSDK v7.0 + .NET Framework 3.5 だと、ソースをビルドするとき、ターゲットプラットフォームを X86 (つまり32ビットね) にしないと、メチャクチャなコードページ値が返るか、COM参照の例外(HRESULT: E_FAIL)が発生して落ちてしまいます。
ノートパソコン(VAIO Z)の方は、WindowsSDK v7.1 + .NET Framework 4 なんですが、こちらはエラーが出ず正常。

ちょっと原因分からず。タイプライブラリはちゃんと64bit,32bitとともに登録しているんですが・・・やり方が間違ってんでしょうかねぇ・・・IE9をインストールしているので、古いWindowsSDKだとダメなのかもしれませんね(あくまで推測)

ただ、.NET Framework から COM を利用するあたりのマーシャリングとかの仕組みがいまいち分かってないので、また勉強しないといけないな~。

Yahoo! ボックスのデスクトップ版アプリ

Web上でのファイルの置き場自体はDTIのVPS上にWebDAVとして確保してあるんですが・・・なんせVPS自体が遅くて正直使えない。ほとんど緊急用途ぐらいの使い道しかない。

マイクロソフトのSkydriveは、標準でWindowsのフォルダーのように扱えない。次期バージョンのWindows8(仮称)では、ちょっと違った感じでSkydriveとの連携が実装されるのかもしれないけど。

他にもいろんなDropBoxとか探せばいろんなサービスとかあるんですが・・・イマイチ。
悩んでいたところにYahoo!ボックスが使えるようになったので、YahooのブリーフケースとPhotoからデータを移行することにしました。

幸いなことに?Yahooはプレアカだったので50GBも使える! プラス300円で1TBの容量が! というわけで?パソコン用のアプリもインストール。iPhone用のアプリがまだリリースされていない、ということでちょっと使い勝手が悪いですが・・・パソコンでは十分に使えてます。

が、二つだけ不満あり。

一つ目は・・・
パソコン用のアプリ、フォルダーにアクセスする際になんか認証とかかけられてたらいいな、と思う。インストール時にYahooのアカウントを入力するだけ。家のデスクトップならいいんだけど、ノートパソコンや、会社のパソコンにインストールするにはセキュリティー的にちょっと不安です。

二つ目は・・・
ブラウザからのアップロード時のファイル指定。アップロードするファイルを一個一個指定していくやり方は、いまどきのスマートさが感じられず。せめてFlashを使った複数一括アップローダーか、どうせなら、ブラウザへのドラッグ&ドロップ(HTML5)に対応して欲しかったなぁ。

Yahooってテクニカルな部分で、進んでいるのか、フツーなのか、よく分からない。

javascript コードをウェブページに注入

ブックマークレットって、意外に便利ですよね。

でも、ネット(主にWeb)にベッタリ依存している人は別として(笑)、一般的にはあんまり浸透していないんじゃないかと思います。というか、言葉を知ってても、一体何なのか分かっている人は、もっと少ない感じ。

実は僕もブックマークレットの便利さを、つい最近知ったんで(^^;;;

ブックマークレットの実体はブラウザのブックマークに登録された、javascriptなどの極少量のスクリプトコードなんですが・・・、この「極少量」っていうところがミソで、FireFoxやChromeのブラウザのアドオンや拡張機能を実装するほどでもない機能をブックマークレットで・・・ってとこでしょうか。

要は、任意のページにjavascriptコードを注入できる、ということ。

僕がよく使うのはブログなどに表示される広告削除。。。だって読みづらいったらありゃしねー。ブログサイト毎に作ってブックマークに登録しとけば非常に便利。

特に、iPhone のSafariブラウザでのブログ閲覧は、もう最悪。狭い領域にパカパカ広告開いて、ご丁寧にもタッチ操作するたびに違う広告を呼び出すので、3G回線で使うときは固まっちまう。

iPhoneのSafariブラウザも、ブックマークレットは使えるのですが、ブックマークの登録時はURLを変更できないので、一度どこのサイトのどのページでもいいのでブックマークを登録して、そのブックマークを編集・修正することでブックマークレットを登録できます。2度手間なので、なんか一発で登録できるアプリないのかな・・・?

たとえば、アメブロのスマートフォン用ページ(iPhoneでしかアクセスしたことないので他のスマホは知らない)では、↓ のように、広告バー?が表示されます。

画像

アメバさんには悪いけど、これヒジョーにうっとおしい。固定されていれば文句はないんですが、タッチ操作するたびに消えたり・表示されたりするので、動作が遅くなるし、誤ってクリックしようもんなら・・・

ムカーーーーーー!金麦なんて、ぜってー、飲まねーーーー!

とくるんですよね(笑) ちなみに、僕はビール類とかお酒自体飲まないんで。そんなの知ったことねーか(^^;;; とにかく、どんなブログサイトでも、こんなスタイルで表示されてるんじゃないでしょうか? ブックマークレットで消しましょう。

あくまで、アメブロの例にしますが、他でも同じ感じです。 手順は簡単で、

  1. この広告に使用されているDOMノードを特定
  2. DOMノードを削除

これだけ。具体的なコードは・・・

javascript:(function(){var ads=document.getElementsByClassName("adBanner");for(var i=0;i<ads.length;i++){ads.item(i).parentNode.removeChild(ads.item(i));}})();

アメブロの広告は、<div class=”adBanner”>というタグが使われているので、getElementsByClassNameで取得して削除しているだけ。

DOMノードの解析に、僕はPCでGoogle Chromeの「デベロッパー・ツール」を使ってます。Chromeを起ち上げる際、起動オプションとして、

--user-agent="Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_5 like Mac OS X; ja-jp) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8L1 Safari/6533.18.5"

を付けて起動すると、ブラウザの表示がスマートフォンと同じようになりますので、そこでデベロッパーツールを表示させて広告表示に使用されているDOMノードを特定できます。FireFoxやIE/Operaでも同様のツールが使えると思います。

で、上記コードをコピペして、ブックマークに登録。
ただ問題は、ページ毎にブックマークを選択しないといけないので、PCのブラウザならワンクリックでいけますが、iPhoneだとメンドクサイですね~

ノードを特定したいときにjQueryとか使いたいとか、もっと長いコードを登録したい場合は、document.createElement(“script”)などで外部スクリプトファイルを読み込ます方法があります。

正しく使えば非常に有用なんですが、少し考えると分かりますが、非常に危険なコードを簡単に注入することが可能です。使用者本人のコードなら問題はないと思いますが、他人が作ったブックマークレットは、リスクが伴います。諸刃の剣ですかね。