ラベル HTML の投稿を表示しています。 すべての投稿を表示
ラベル HTML の投稿を表示しています。 すべての投稿を表示

2014年8月21日木曜日

HTML ファイルがなかなか更新されない場合、キャッシュを無効にする

とある EC サイトの HTML ファイルを更新したが、2 日以上経過しても更新されなかった。スーパーリロード、ローカルのキャッシュをクリアしてもダメ。

このような場合、HTML の meta タグにキャッシュに関する記述を行う。

HTMLタグ/ページ全般タグ/ページをキャッシュさせない - TAG index Webサイト によると、head タグ内に以下を記述する。

<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Expires" content="0">

meta タグの http-equiv 属性は、

The global structure of an HTML document によると、

http-equiv = name [CI]
This attribute may be used in place of the name attribute. HTTP servers use this attribute to gather information for HTTP response message headers.

2014年5月6日火曜日

固定幅のサイトにおける viewport の設定。iPhone で見た場合、横の隙間 (空白) をなくすには。

1. 横の隙間 (空白) をなくしたい

iPhone の画面に最適化されていないサイトを見ると、コンテンツの両脇に隙間 (空白) ができることがある。

例えば、Open Source Web Design - Three Quarters > Three Quarters は、スマートフォンに最適な表示がされない。iPhone で表示すると、左下の画像のように表示される。コンテンツの両脇に不要な空白ができてしまう。

これを右下の画像のように、できるだけコンテンツを表示するように、両脇の隙間を取り除きたい。

IMG_0112 IMG_0117

 

2. このテンプレートにおける HTML の構造

最初にこのテンプレートにおける HTML の構造を把握しておく。

コンテンツを囲む全体の幅は、body タグに対する CSS の指定で 850px の固定幅が指定されている。body タグの背景画像として、コンテンツ部分の白色と、影を含めた両脇に対応した画像が設定されている。

body タグ直下の div タグは 700px の横幅。影を除いた白色の部分の幅を測ると約 750px

SnapCrab_No-1001

両脇に影のあるデザインは古めかしく、簡単な変更で済ますために、予め、デザインをフラットなものに変更した。そのためには、CSS ファイル (screen.css) の body タグに対する背景の画像を削除。そして、背景の色を白に指定。幅を 750px に変更した。

  • body タグに対する CSS の指定
    • background-image を削除
    • width: 750px;
    • background-color: #fff;

これにより、以下のような表示となる。

SnapCrab_No-1002

 

3. Viewport とは表示される領域のこと

iPhone では横幅 980px として表示される

iPhone でサイトを見ると、横に隙間ができる理由は、iPhone に搭載されたブラウザの仕様による。

iPhone ではサイトに何も指定がなければ、PC のブラウザで横幅 980px の領域が表示できるウィンドウの大きさにしたときと同じように見える。iPhone では、PC のブラウザのようにウィンドウの大きさを変更するという操作がないため、横幅が固定されている。

iPhone用にサイトの幅と画面の幅を合わせるには によると、

iPhone4やiPhone4Sは、解像度が横980pxなので、… PC用に作られたPCサイトを表示させると、… 読みにくい表示になるだけでなく、右横に隙間が表示されてしまいます。

たとえば、… 横幅が900pxで作成しているため、80px分の隙間が出来てしまっています。

上例のサイトを PC 上のブラウザで 980px の横幅にして見てみると、以下のように表示される。

SnapCrab_No-1003

PC のブラウザと iPhone での表示を拡大縮小して重ね合わせると、表示されている領域が一致することが確認できる。

SnapCrab_No-1003

 

viewport

このような表示に関する概念として覚えておく言葉は `Viewport’.

viewport とは、現在見ている矩形領域のこと。ブラウザで言えば、ウィンドウ内に表示されている領域に相当する。たとえるなら、覗き窓。

viewport – Wiktionary によると、

(computer graphics) A rectangular region representing the range or area currently being viewed.

iPhone では、画面の解像度と、表示される領域が異なることに注意すること。また、Android では事情が異なってくる。

スマートフォン時代のWebサイト制作 第2回 スマートフォン特有の表示環境を理解する | デベロッパーセンター

例えば、iPhoneではどうでしょうか。結論から言うと、iPhoneでは“何もしなければ”横幅980pxでウェブサイトが表示されます。iPhoneのスクリーンはデスクトップのスクリーンのようには大きくないのに、なぜそのようになるのか不思議に思うかもしれません。ここで理解しないといけないのが「Viewport(ビューポート)」です。…

Viewportとはブラウザーの画面表示領域のことです。もう少し分かりやすく言えば、デスクトップでいうところのブラウザーウィンドウ上の表示サイズです。スマートフォンではデスクトップのようなリサイズできるウィンドウの概念はないため、その機種のスクリーンサイズがブラウザーウィンドウのサイズとなります。この通り素直に考えれば、iPhoneの3.5インチスクリーンの横幅320pxに合わせてViewportも320pxとなると考えてしまうのですが、実際にはこれが980pxとなります。

なぜそのようになっているかというと、スマートフォンブラウザーはデスクトップ向けサイトを閲覧できるのが特徴です。そのため、既存のウェブサイトをちょうど画面に収まるようにするには、Viewportのサイズをデスクトップ相当にしておく必要があります。

ちなみにAndroidの場合は、その機種のスクリーン幅以上のコンテンツでも、スクリーンに収まるように表示されます。

 

4. meta タグの viewport で幅を指定

width=device-width

viewport を指定するには、meta タグを利用する。

これがスマートフォン向けサイトを作るときの viewport 設定3パターンだ - てっく煮ブログ によると、

「スマホ向けにデザインしているよ」と主張するには <head> タグの中に viewport を 1 行追加すればよい。

<meta name="viewport" content="width=device-width">

width=device-width というのは「デバイスの横幅で描画してください」という意味である。

一昔前は iPhone 前提で width=320px と指定すれば十分だったが、解像度がまちまちな Android やタブレット端末が登場したため、width=device-width と指定するのが新常識になった。

HTML5/ページ全般/meta要素 表示領域を設定する - TAG index Webサイト によると、

meta要素name="viewport" を追加すると、文書の表示領域を設定することができます。…

content属性の値には、以下のプロパティを指定することができます。

width= 表示領域の幅 数値 ピクセル数 (20010000 の範囲、初期値は 980
device-width 端末画面の幅に合わせる

これより、viewport に

  • width=device-width

を指定した。その結果、iPhone でコンテンツが納まらなかった。横幅は、PC 上のブラウザで幅 640px を指定したときと同じようだ。

IMG_0119

 

width=[幅をピクセル数で指定]

これに対して、blog.鶯梭庵/links/たぶん、ほとんどの人は viewport meta タグの指定をまちがえてる によると、

先に結論を書いておく。適切な設定は、ページ幅が固定されているウェブページの場合、viewport meta タグを書かないか、

<meta name="viewport" content="width=(幅をピクセル数で指定)">

とする。ページ幅が可変(リキッドレイアウトとか RWD とか)のウェブページの場合、

<meta name="viewport" content="initial-scale=1.0">

とする。

そこで、iPhone に表示する領域として、body タグの幅を viewport の width に指定した。

<meta name="viewport" content="width=750">

これにより、iPhone でコンテンツが隙間なく納まるようになった。

IMG_0117

 

5. スマホサイトにおける viewport の設定に対する考え方

スマホサイトに対する設計には 2 つの考え方がある。

一つは全体を俯瞰し、必要に応じて拡大縮小するタイプ。もう一つは、ユーザによる拡大縮小操作を禁止し、スマホに特化した表示をすること。

それぞれ一長一短があるので、サイトの目的に合わせて最適化するのが良いようだ。

pxt | 考察:Appleはスマホサイトを作らない。 によると、

今まで気づかずにいたが、Appleはスマホ(スマートフォン)向けサイトを作っていない。それは何故か。…

Appleのウェブサイトの裏側をちょっとだけ覗いてみると、viewport に width=1024 が記述されている。これは、iPhoneの小さな画面でも1024px分の幅があるものと仮想してレンダリングされるようにする…

小さな画面でありながらページ上の構成要素の全体を一目で把握して、自分が必要な情報に、直感的なダブルタップの操作でフォーカスできる。興味の順に次々読み進められるし、斜め読みだってできてしまう。

これが、Appleが掲げたモバイルサイトのデザインであり、Mobile Safari のUI設計の本質なのではないだろうか。

スマートフォン時代のWebサイト制作 第2回 スマートフォン特有の表示環境を理解する | デベロッパーセンター によると、

またmeta viewportでは、他にもユーザによる拡大/縮小操作の倍率を指定したり、拡大/縮小操作自体を禁止することもできます。Yahoo! JAPANやlivedoorなどのサイトでは、下記のようにユーザによる拡大/縮小操作ができないようにしています。

<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0">

ケースバイケースではありますが、これらはユーザによる任意の操作を禁止することになるので、筆者としてはあまり好ましい指定と考えてはいません。「ユーザにも親切である」という意図がなければ、この制限のある指定は避けるようにしましょう。

 

参考サイト

2013年6月3日月曜日

HTML の「ブロックレベル、インライン要素」内における改行は空白となる場合がある

1. 「ブロックレベル要素」と「インライン要素」の違い

HTML のタグには「ブロックレベル要素」と「インライン要素」がある。

7.5.3 ブロックレベル要素と行内要素 によると、

HTMLの要素のうち、BODY要素に出現し得る要素のあるものは「ブロックレベル」と呼ばれ、他のものは「行内」 (これは「テキストレベル」としても知られている)と呼ばれる。

大雑把に言えば、ブロックレベル要素とインライン要素-HTMLの基本 によると、

ブロックレベル要素には、以下のものがあります。

<address>、<blockquote>、<center>、<div>、<dl>、<fieldset>、<form>、<h1>-<h6>、
<hr>、<noframes>、<noscript>、<ol>、<p>、<pre>、<table>、<ul>

インライン要素には、以下のものがあります。

<a>、<abbr>、<acronym>、<b>、<basefont>、<bdo>、<big>、<br>、<cite>、<code>、<dfn>、
<em>、<font>、<i>、<img>、<input>、<kbd>、<label>、<q>、<s>、<samp>、<select>、
<small>、<span>、<strike>、<strong>、<sub>、<sup>、<textarea>、<tt>、<u>、<var>

この2つの要素には「配置のルール」が存在する。

ブロックレベル要素の中には、他のブロックレベル要素やインライン要素を配置することができます。

一方、インライン要素の中には、文字データや他のインライン要素を配置することができますが、 インライン要素の中にブロックレベル要素を配置することはできません。

(同上より)

詳しくは、HTML 4 Document Type Definition を参照。

<!--
    HTML has two basic content models:

        %inline;     character level elements and text strings
        %block;      block-like elements e.g. paragraphs and lists
-->

上記の読み方は、Document Type Definition – Wikipedia を参考に。

 

2. HTML における改行、空白文字の扱われ方

a. インライン要素に改行を入れてしまった場合

注意することは、「改行」や「空白文字」の扱われ方。

例えば、インライン要素である a タグの中に span タグを、次のように配置してみる。

<div>
	<a href="">
		<span>
			ほげ
		</span>
	</a>
</div>
<div>
	<a href=""><span>ぴよ</span></a>
</div>
<div>
	<a href=""><span>ふが</span></a>
</div>

SnapCrab_No-0239ブラウザ(Firefox 21)に表示される「ほげ ぴよ ふが」の文字は、一見変わらないように見える。しかし、ソースを表示すると、「ほげ」と入力した文字の周囲に空白が表示されている。

Firefox 上で文字をコピーし、テキストエディターに貼り付ければ、空白があるのが分かる。ただし、ブラウザによって、コピーされた内容が異なる。Google Chrome 27, IE 10 では全く違う。

このように「空白」が表示される理由は、「インライン要素」に「改行」が含まれているため。

ブロックレベル要素とインライン要素 - Web標準普及プロジェクト > インライン要素 によると、

要素中に改行が混じるとやや複雑な形で表示されます。 これはインライン要素によって生成されるボックスは改行によって複数に分割されることがあるためです。

この場合、整形するつもりで、誤ってインライン要素である span タグ内に改行を2つ入れてしまった。そのため、改行が空白に変換された。

		<span>
			ほげ
		</span>

 

b. 改行が空白になる仕様

改行が空白に解釈される理由は、仕様による。

9.1 空白類 によると、

HTMLでは、次に挙げる文字だけが空白類文字であると定義される。

  • ASCIIスペース (&#x0020;)
  • 水平タブ (&#x0009;)
  • 書式送り (&#x000C;)
  • ゼロ幅スペース (&#x200B;)

行区切り類もまた、空白類文字である。

9.3.2 行区切り類の制御

1つの行区切りは、1つの復帰 (&#x000D;)か、1つの改行 (&#x000A;)か、1つの復帰/改行組であると定める。すべての行区切り類は、空白類である。

 

3. ブロックレベル要素内における改行

ブロックレベル要素でも、改行に注意しなければならない。改行が空白となる場合がある。

落とし穴になる空白文字と改行文字 によると、

以下のHTMLファイル(かなり簡略化してある)は2段落のテキストを表示する。表示後の見かけは段落ごとに同じか、違うか。違う場合はどこが違うか。

1: <html>
2: <body>
3: <p>abcdef</p>
4: <p>abc
5: def</p>
6: </body>
7: </html>

何が問われているのかピンとこなかった人は、少し考えてみよう。

正解は、「表示後の見かけは違う」「相違点は2段落目のcとdの間に空白が挿入されている」というものだ。

なぜ、こういう動作になるのかは、英語テキストを頭に描いてみれば分かるだろう。例えば、以下の2行があったとしよう。

1: The Extensible Markup Language (XML) is a subset
2: of SGML that is completely described in this document.

1行目のsubsetと2行目のofを結合するときには、そのまま結合して"subsetof"としてはおかしくなる。ここは単語の区切りなので、空白文字を入れねばならない。つまり、HTMLでは、改行は空白文字に等しい扱いを受けているのである。ただしここで補足しておくと、日本語などの文字を区切る改行は、空白扱いしないのが現在のHTMLである。

 

改行が問題とならない場合

ただし、ブロックレベル要素を見やすいように整形しても「空白」が入らない。

[HTML] ソース上の改行・タブ・空白(スペース)の扱い――インライン要素とブロック要素 | Ouka Studio

インライン要素以外の要素(ブロックレベル要素など)では、開始タグと終了タグそれぞれの前後の改行・タブ・半角スペースは、すべて無視される

よって、以下の整形は問題とならない。インライン要素とは異なることに注意する。

<div>
	<a href=""><span>ぴよ</span></a>
</div>

HTML における「空白」の仕様は、以下を参照。

 

4. 画像における空白

a タグimg タグを組み合わせるときは、特に注意が必要。両者とも「インライン要素」に分類されるため、上記の span タグと同じ扱いにしなければならない。

<div>
	<a href="">
		<img src="http://goo.gl/FgON8" alt="メニュー1">
	</a>
</div>
<div>
	<a href=""><img src="http://goo.gl/1SIHV" alt="メニュー2"></a>
</div>
<div>
	<a href=""><img src="http://goo.gl/5WFw0" alt="メニュー3"></a>
</div>

Firefox 21 で表示したところ、1番目と2番めの画像の間に隙間ができてしまった。

SnapCrab_No-0242

ただし、ブラウザによって表示が異なる。Google Chrome 27 では隙間ができない。

IE 10 では、画像にアンカーが付いていることを示すために枠が表示され、その分全体の高さが長くなる。ただし、1番目と2番目の画像に隙間はないようだ。

SnapCrab_No-0246

これに対して、HTML ソースの改行を半角スペースにしない方法 : full of universe には、以下の2つ書き方が述べられている。

タグ「後」の改行が表示されるんだったら、タグ「中」で改行してしまえ、という方法。

改行なんてコメントアウトしてしまえ、という方法。

しかし、どうもすっきりしないなぁ。。

タグの属性ごとに改行して書くことがある。属性が 0 個だと考えて、次のように記述するのも… うーん。

<ul>
	<li
		>1</li><li
		>2</li><li
		>3</li><li
		>4</li><li
		>5</li>
</ul>

 

参考サイト

2012年4月25日水曜日

ウェブサイトで利用する外部ファイルを Dropbox の Public フォルダに置く

1. ウェブサイトにファイルをアップロードできない場合

Dropbox には、ファイルを公開する機能がある。

ウェブサイトによっては、JavaScritp, CSS を定義したファイルをアップロードできない。特にブログサイトでは、自由にファイルをアップロードできないことが多い。その場合、

  1. 外部サイトにファイルを置き、
  2. HTML の head 要素内で、上記ファイルを指定する。

その際、外部ファイルの置き場として、Dropbox の Public フォルダを利用する方法がお手軽で良い。

なぜなら、Dropbox は、ローカルのファイルを更新すると、更新したファイルを Dropbox がアップロードしてくれるため。FTP を利用して、自分でアップロードする手間を省ける。

 

2. Dropbox の Public フォルダを利用する

  1. Dropbox 内の Public フォルダに、外部ファイルとして利用したいファイルを配置する。
  2. SnapCrab_No-0885当該ファイルを、右クリック > Dropbox > パブリックリンクのコピー を選択する。もしくは、https://www.dropbox.com/home/Public 内において、当該ファイルを右クリック > 公開リンクをコピー を選択。
  3. コピーした URL を、外部ファイルを読み込みたいサイトの head 要素内で指定する。

例えば、CSS, JavaScript を外部ファイルとして利用する場合、HTML の head 要素内に、以下のように記述する。

<link href=http://XXXXX/XXXXX.css type="text/css" rel="stylesheet" />
<script type="text/javascript" src="http://XXXXX/XXXXX.js"></script>

追記(2014/3/23): Public フォルダがない場合、https://www.dropbox.com/enable_public_folder より、Public フォルダを有効にする必要がある。

2011年4月27日水曜日

Blogger で記事を投稿するときに HTML のエラーが表示されたら

Blogger で記事を投稿する際、

  • 「HTML の編集」

を利用していると、以下のようなエラーが表示されることがある。

作成された HTML ファイルは承認できません: 終了タグが開始タグと一致していません: A

CropperCapture[174]

これはどこかで HTML のタグを書き間違えているということ。上例の場合、どこかの A タグに問題がある。

Blogger の投稿画面で A タグを検索しても良いけれど、記事が長いと結構大変。 (+_+)

 

HTML を検証するためのツールを手がかりに

代わりに Another HTML-lint gateway を使い、HTML の検証をすると問題の箇所を早く見つけることができる。

  1. 上記サイトにアクセスし、Blogger に投稿しようとした HTML のソースを DATA フィールドに貼り付け。
  2. DATA にチェックを入れ、「チェック」ボタンを押して検証。
http://openlab.ring.gr.jp/k16/htmllint/htmllint.html

Another HTML-lint gateway via kwout

結果がたくさん表示されるので、当該箇所を見つけるのに、

この場合、

<A>

で検索したら、

9: line 39: </A> に対応する開始タグ <A> が見つかりません。

をすぐに見つけることができた。

ちなみに、HTML のソースが一行にまとまっている場合、Windows Live Writer などを使い、HTML を整形してからの方が結果が見やすくなる。

 

参考サイト

2011年3月1日火曜日

Google ドキュメントビューアで PDF を埋め込む。特定ページを開くための URL.

1. Google ドキュメントビューアーで PDF ファイルを開く

PDF ファイルを、ブラウザに組込まれたプラグインを使わずに開くときは、

を利用する。

http://docs.google.com/viewer

Google ドキュメント - ビューア via kwout

ブラウザのデフォルトの動作として、 PDF ファイルを上記サービスで開きたいときは、Userscripts.org にある Greasemonkey スクリプトをインストールしておく。

 

2. PDF ファイルを示す URL の末尾でページを指定する

特定のページを開きたい

Google ドキュメントビューア で生成したリンクを開くと、PDF の「最初のページ」が開かれる。

特定のページを指定したい場合はどうするのだろう?

によると、例えば、

Google ドキュメントビューア を利用して開く場合のリンクは、

となる。

 

ページを指定する URL

3 ページ目を指定して開きたいときは、URL の末尾に

&embedded=true#:0.page.2

を追加する。

ページは最後の数字で示される。1 ページ目は 0 からはじまる

よって、開きたいページから 1 引いた数を指定する。

 

3. ウェブサイトに PDF ファイルを埋め込む場合

ウェブサイトに PDF を埋め込む場合、iframe タグを使う。

上記の例の場合、

<iframe src="http://docs.google.com/viewer?url=http%3A%2F%2Fwww.scala-lang.org%2Fdocu%2Ffiles%2FScalaReference.pdf&embedded=true&embedded=true" width="600" height="780" style="border: none;"></iframe>

 

ページを指定する URL

特定ページを開くための URL は、iframe タグの src 属性の末尾に

#:0.page.2

を追加する。

<iframe src="http://docs.google.com/viewer?url=http%3A%2F%2Fwww.scala-lang.org%2Fdocu%2Ffiles%2FScalaReference.pdf&embedded=true&embedded=true#:0.page.2" width="600" height="780" style="border: none;"></iframe>

 

4. ブラウザ内で開く PDF ファイルに対して、ページ指定をしたリンクを作成には

追記 (2011.3.16) : 普通にブラウザ内で PDF ファイルを開く場合、特定のページを指定したリンクを作成するには、PDF ファイルのリンクの末尾に、

#page=ページ数

を追加する。例えば、

の 3 ページ目を表示させるリンクは、

( 参考: PDFファイルに対するページ指定ハイパーリンク - 繰り言 )

2010年5月31日月曜日

NotePad++ で HTML の特殊文字をエスケープ

1. HTML で書いた文書をエスケープしたい

HTML のソースコードから

& " < >

を含む文字をエスケープしたい文字したい。目的は、 HTML の文書に HTML の文書を載せるとき、実体参照に変換する必要があるため。

 

2. HTML をエスケープする方法

ネット上のサービスとしては、

Ruby で HTML の特殊文字を実体参照に変換できる。

 

3. NotePd++ の場合

NotePad++ を使っているなら、

  1. 対象文字列を選択
  2. メニューより TextFX > TextFX Convert > Encode HTML (&<>”)

img05-31-2010[2]

これにより変換された文字列を AutoHotkey で Windows Live Writer に ソースコード を貼り付ける

 

TextFX のインストール

追記(2013/06/01):  メニューに TextFX が存在しない場合、メニューより、

  • プラグイン > Plugin Manager > Show Plugin Manager

を起動する。 Available タブにおいて、TextFX Characters を選択して install ボタンを押す。

SnapCrab_No-0241

CSS で画像と文字を揃える

1. リストのマーカーとして画像を設定する

HTML でリストを書くには、ul, li 要素を用いる。

<ul id="list0">
	<li>hoge</li>
	<li>piyo</li>
	<li>fuga</li>
</ul>

このリストの項目の前に、マーカーとして画像star(star.png) を付けるには、CSS で指定する。

#list0 { 
	list-style-image: url("star.png"); 
	list-style-position: inside;
} 

img05-30-2010[1]

これにより、右図のように表示される。

 

リスト項目の背景画像を設定

マーカーの中心に文字の位置を合わせるには、マーカーをリスト項目の背景画像として設定する。

#list0 li { 
	list-style: none;
	background: url("star.png") no-repeat top left;
	padding: 15px 0px 15px 60px;
} 

img05-30-2010[2]

文字が真ん中に位置するように padding で上下の間隔を調整する。

 

2. 画像と文字を揃える

a. 要素を垂直方向に対して、中央で揃える

画像と文字の位置を、垂直方向に真ん中で揃えたい場合は vertical-align を使う。

<img src="star.png" style="vertical-align:middle;" />
<span >hoge piyo fuga</span>

 img05-30-2010[4]

 

b. 要素を水平方向に対して、中央に配置

水平方向に対して、要素を中央に配置したい場合は text-align を用いる。

text-align が適用される対象は、

Applies to: block-level elements, table cells and inline blocks

(16.2 Alignment: the 'text-align' property より)

div 要素の子要素として画像を配置。

<div style="text-align:center;">
  <img src="star.png" />
</div>

または、display要素をセンタリング するための margin の設定を併用し、

<img src="star.png" style="display:block; text-align:center; margin:auto;" />

 

c. text-align と vertical-align

しかし、不思議なのは要素を中央に配置するの指定が

text-align

であって、なぜ horizontal-align ではないのだろう?

vertical-align

と意味的に対になるなら horizontal-align の方が忘れにくいのに。

CSS1

仕様を見ると Cascading Style Sheets, level 1 では、Text properties 節に各々含まれている。

CSS2.1

Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification では、別々の節に分かれて説明されている。

 

参考サイト

2009年12月15日火曜日

Google Sites における HTML の不要なタグや属性を一括で削除 - 正規表現を使って

1. フォントの装飾だけ取り除きたい

Google Sites は、Google ドキュメントのように文字に色を付けたり、サイズを変更することが簡単にできる。

SnapCrab_NoName_2013-1-8_0-13-30_No-00しかし、お手軽さが災いし、文字の装飾を繰り返すと、元の状態に戻したくても修正するのが面倒になる。(+_+)

メニューより、

「書式 > 書式をクリア

により、文字の装飾を削除することはできる。しかし、「見出し」や「リスト」を適用した段落設定も消えてしまう。

 

2. エディタを使い正規表現で一括削除

Google Site では、ツールバーの右端にある

  • 「HTML ソースを編集」ボタン

を選択し、フォントの装飾情報を直接削除することができる。しかし、一々手作業で削除するのは気が遠くなる。そこで、正規表現で文字列を置換できるエディタを使い、フォント情報を削除することにした。

SnapCrab_NoName_2013-1-8_0-15-34_No-00ここではエディタとして Aptana を利用する。

 

a. Firefox のアドオン It’s All Text! で HTML を編集

SnapCrab_NoName_2013-1-8_0-18-11_No-00Firefox のアドオン It's All Text! を使うと、ブラウザのテキストエリアをエディタで編集できる。

It's All Text! をインストールし、エディタを Aptana に設定した。

SnapCrab_NoName_2013-1-8_0-19-1_No-00次に、Google Sites で、メニューボタンの右端にある HTML を編集するボタンをクリック。

It’s All Text! がインストールされていると、右端に「編集」ボタンが表示される。これを右クリックし、

「.html」として編集

を選択する。

091215-021.png

 

b. 正規表現で置換

Aptana で編集対象の HTML が開かれたら、メニューより

  • Edit > Find/Replace

を選択する (Ctrl + F)。

091215-019.pngダイアログの Options で Regular expressions にチェックを入れる。 

例えば、 HTML における style 属性と Font タグを削除したいとする。

style 属性を正規表現で指定するには、

style=(.+?)"

Font タグを指定するには、

<font(.+?)>|</font>

よって、Find: フィールドには

style=(.+?)"|<font(.+?)>|</font>

と指定すれば良い。Replace With: フィールドは空にしておく。

後は、Replace/Find ボタンで確認しながら除去する。

ついでに、メニューより Edit > Format を適用しておくと、見やすくなる。

Aptana で編集したファイルを保存し、Google Site に戻り、HTML が更新されたのを確認したら、更新ボタンを押す。

2009年9月19日土曜日

JavaScript で a 要素を生成し、name 属性を設定するときは DOCTYPE 宣言と setAttribute メソッドを使用する

1. IE8 で a 要素の name 属性が生成されない

JavaScript で、HTML の「ページの特定の場所を示すアンカー要素」を生成した。

結果を Firefox で表示すると、アンカー要素が正常に生成された。しかし、Internet Explorer 8 では想定していた属性名が生成されなかった。

このとき、特定の場所を示すために a 要素の

  • name 属性

を利用していた。

例えば、次のように JavaScript で a 要素を生成し、name 属性を設定した後、HTML の要素として挿入する。

<html>
	<head></head>
	<body>
	</body>
	<script type="text/javascript">
	var anchor = document.createElement('a');
	anchor.name = "hoge";
	anchor.appendChild(document.createTextNode('hoge'));
	document.body.appendChild(anchor);
	</script>
</html>

Firefox において、Firebug で上記のHTML を確認すると、

<a name="hoge">hoge</a>

IE8 の DebugBar で確認すると

<a submitName="hoge">

name 属性ではなく、 submitName 属性になっている。これにより、アンカーが機能しない。

 

2. a 要素で name 属性を使うことは問題ない

name 属性を使うことは、問題があるのだろうか?

文書の特定の場所へのリンク」 によると、

リンク先には、文書中の特定の段落など具体的な場所(フラグメントといいます)を指定することもできます。フラグメントを示すには、対象となる要素にid属性を使って名前を付けます(古いブラウザとの互換性のためには、アンカー要素の2番目の役割であるname属性による名前付け機能を使うこともできます)

では、なぜ name 属性が勝手に変ってしまったんだろう?

 

3. DOM の createElement メソッドで生成したオブジェクトに対する属性の指定

アンカー要素を生成するために、createElements メソッドを利用した。

DOM の createElements で生成したオブジェクトは、その属性を直接設定できる。

Document Object Model (Core) Level 1 によると、

createElement

Creates an element of the type specified. Note that the instance returned implements the Element interface, so attributes can be specified directly on the returned object.

例えば、img タグに対応したオブジェクトを生成し、その src 属性を設定してみる。

var img = document.createElement('img');
img.src = "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkfqT1erWUZHM7qxkaPs5aIS-NDQx5dDTg4_VnLNKq8AP7b0iAJei3pGnwVMtnottNtucRtfl8YLKbb9vYjIoiD09LWvZYOrrqtAUcyrIIQJIo0vqy-KMJDL70gmC4Nf80e-snbvOqdZw/s220/profile.png";
document.body.appendChild(img);

上記のコードは、 Firefox, IE の両方で問題なく動作する。

先ほどの引用中に `the instance returned implements the Element interface’ とあった。よって、Interface ElementsetAttribute メソッドを使い、属性を設定することもできる。

var img = document.createElement('img');
img.setAttribute("src", "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkfqT1erWUZHM7qxkaPs5aIS-NDQx5dDTg4_VnLNKq8AP7b0iAJei3pGnwVMtnottNtucRtfl8YLKbb9vYjIoiD09LWvZYOrrqtAUcyrIIQJIo0vqy-KMJDL70gmC4Nf80e-snbvOqdZw/s220/profile.png");
document.body.appendChild(img);

setAttribute メソッドを使い、アンカー要素の  name 属性を設定してみた。

anchor.setAttribute("name", "hoge");

しかし、これだけでは、結果は変わらなかった。 (+_+)

 

4. DOCTYPE スイッチで Standards モードでレタリングする

文書の先頭で DOCTYPE を、キチンと設定しないとダメだろうか?

IE8 では、DOCTYPE を指定しないと、互換モードでレタリングされる。

IE8のレンダリングモードと互換表示 - page2 - builder by ZDNet Japan によると、

レンダリングモードはIE6の時代から採用されてきたDOCTYPE宣言による指定か、IE8で採用されたMETAタグまたはHTTPレスポンスヘッダによる指定で切り替えることができる。…

IE8ではDOCTYPE宣言によってIE8 StandardsモードとQuirksモードが切り替わる。

問題のソースコードを IE8 で開き、DebugBar を起動。メニューの右隅を見ると `Quirks’ と表示された。

090919-008

DOCTYPE 宣言による「解釈モード」の切り替え によると、

過去の慣習的な解釈を再現するモード (Quirks mode)

仕様準拠の厳格解釈モード (Standards mode)

では、Standards モードでは、アンカー要素を表示させることはできるだろうか?

モードを変更するには、IE8のレンダリングモードと互換表示 - page2 - builder by ZDNet Japan によると、

IE8 StandardsモードになるDOCTYPE宣言の記述
  • HTML4.01 Transitional/FramesetのDOCTYPE宣言でシステム識別子(DTDのURL)を記述している場合
    • (例)
      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  • HTML4.01 StrictのDOCTYPE宣言を記述している場合
    • (例)
      <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  • XHTML1.0のDOCTYPE宣言を記述している場合
    • (例)
      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

問題のコードの先頭に DOCTYPE 宣言を書き Standard モードでレタリングするようにした。

属性を直接設定する方法でアンカー要素を生成したら、a 要素が以下のようになった。 (@_@;)

<a propdescname="hoge">

これに対して、属性を setAttribute メソッドを使った場合、問題なくアンカー要素が生成された。

上記をまとめると、

  1. DOCTYPE 宣言により Standard モードでレタリングし、
  2. setAttribute メソッドを使えば良い。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
	<head></head>
	<body>
	</body>
	<script type="text/javascript">
	var anchor = document.createElement('a');
	anchor.setAttribute("name", "hoge");
	anchor.appendChild(document.createTextNode('hoge'));
	document.body.appendChild(anchor);
	</script>
</html>

上記の DOCTYPE は、IE6 でも標準準拠モードになる。

また、システム識別子がついていないだけで、動作ががらっと変ってしまう。

「システム識別子」とは、DOCTYPE 宣言による「解釈モード」の切り替え によると、

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">

"-//W3C//DTD HTML 4.01//EN" の部分は「 公開識別子 」と呼ばれます。…

"http://www.w3.org/TR/html4/strict.dtd" は、この W3C HTML 4.01 の文法を定義しているモノ (DTD) のありかです。「 システム識別子 」と呼ばれます。

 

5. クロスブラウザ対策

ところで、Setting the “name” attribute in Internet Explorer » Semicolon には、次のように MSDN における説明が引用されている。

I found an explanation in the MSDN DHTML reference, on the page describing the NAME Attribute.

“The NAME attribute cannot be set at run time on elements dynamically created with the createElement method. To create an element with a name attribute, include the attribute and value when using the createElement method.”

以下の記述により、Quirks モードとなり、 DOCTYPE を書かなくても良いようだ。

var anchor = document.createElement("<a name='hoge'>");
anchor.appendChild(document.createTextNode('hoge'));
document.body.appendChild(anchor);

しかし、残念ながら  Firefox では動かない。 (+_+)

には、クロスブラウザ対策がされているコードが書かれている。

 

6. jQuery で DOM エレメントを生成する

jQuery を使うなら、DOCTYPE を宣言しなくてもちゃんと表示された。

利用したメソッドは次の二つ。

<html>
	<head>
		<script type="text/javascript" src="jquery-1.3.2.js"></script>
		<script type="text/javascript">
		$(document).ready(function(){
			$("<a name='hoge'>hoge</a>").appendTo("body");
		});
		</script>
	</head>
	<body>
	</body>
</html>

ただし、IE8 では、先ほどの DOCTYPE を記述したり、

$("<a>").append("hoge").attr("name", "hoge").appendTo("body");

または、以下のように記述すると、

$("<a name='hoge'>").append("hoge").appendTo("body");

正常に表示されない。Firefox では、問題ないのだけれど。

 

7. まとめ

  • DOCTYPE 宣言して、setAttribute メソッド使う。
  • jQuery なら $ メソッドにおいて、属性と中身を含んだ HTML をごっそり引数として渡す。

てゆうか、name 属性は使わない方がいいのかな?

2008年9月6日土曜日

Google App Engine でダイエット表の作成 (4) - 完成 o(^^)o

追記(2008.9.6) : ダイエット表を作成するには → http://4diet.appspot.com/


Google App Engine でダイエット表の作成 (3)」の続き。前回は、実際にダイエット表を作成するクラスである DietTable を作成した。今回は、これをテンプレートから呼出し、ダイエット表を生成する部分、そして最初の設定画面、画面遷移を扱うコントローラ  ( webapp.RequestHandler のサブクラス ) を作成して完成。実際には、この部分から作りはじめているのだけれど ^^;

 

ダイエット表を生成するテンプレート

まずは、DietTable クラスをテンプレートで呼出している部分。シンプルに、

{{dietTable.toHtml}}

としているだけ。そして、このテンプレートにおいて、 DietTable クラスで設定した CSS のクラス指定に対して、CSS を設定している。 CSS における背景の色をプリンタで印刷する場合、デフォルトでは印刷されないのでブラウザの設定が必要。 (cf. Firefox, IE で背景色も印刷する )

 

diet_table.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional/EN">
<html>
  <head>
    <title>ダイエット表</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">

    <!-- Diet.py の Diet.DietTable#_addCssToHtmlTable() で設定したものに対応 -->
    <style type="text/css">
    <!--
    table {
        border-collapse: collapse;
        width: 175mm;
        height: 240mm;
        border-color: #000;
    }
    table, td {
        border: solid 1px;
        text-align: center;
    }
    td {
        height: 4mm;
        font-size: x-small;
    }
    
    .sunday{ background-color: #FFEEFD; }
    .saturday{ background-color: #EEF2FF; }

    .x_axis{ border-top: solid 3px; width:2em;}
    .y_axis{ border-right: solid 3px; }

    .x_axis_05 { border-top: dotted 2px; width:2em;}
    .x_axis_1 { border-top: solid 2px; width:2em;}
    .y_axis_10 { border-right: solid 2px; }
    .y_axis_05 { border-right: dotted 2px; }
    
    .target , .weightNow { background-color: #ffc; }

    h1 {
        color: #000;
        text-align: center;
    }
    
    .forPrint {
        text-align: center;
    }

    @media print {
        .forPrint {
            display: none;
        }
    }
    -->
    </style>
  </head>
  <body>

    <p class="forPrint">表の中の色を印刷するには
        <a href="http://jutememo.blogspot.com/2008/08/firefox_23.html#ie">
        こちら</a>の説明を参考にしてください。
        (Firefox の場合は
        <a href="http://jutememo.blogspot.com/2008/08/firefox_23.html#firefox">
        こちら</a>。)
    </p>

    <h1>{{dietTable.year}} / {{dietTable.month}}</h1>
    
    {{dietTable.toHtml}}
    
  </body>
</html>

 

最初に表示される設定画面

上記のダイエット表を出力するページや設定画面は、当然ながら最初にデザインを無視して作っている。ここでやっと設定画面をデザインすることにした。デザインするのに使ったのは Inkscape 。絵心はないので適当にシンプルなものを作った。 ^^;  以前に少しだけ使ったときのことを参考に。

とりあえず、何かスリムな感じがするようなイメージを… (@_@;)

080906-002

絵が描けたら、今度はフォームを送信する部分を、表示内容に応じて長さが変化するように設定しなければならない。入力に不備があった場合、エラーメッセージを表示するためだ。作成ボタンを含んでいる影のある四角の部分を切取り、 Gimp で縁を拡大して、CSS で指定して伸縮できるように画像を抜き出した。 (cf. CSS と JavaScript で伸縮する領域を作成 )

HTML のファイルは、フォームの部分だけ抽出して別ファイルとした。

 

ソースコード

 

setting_page.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional/EN">
<html>
  <head>
    <title>表の設定ページ | 計るだけダイエット</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
   
    <script type="text/javascript">
    <!--
    /* このページが読み込まれたときに呼出される関数 */
    function setup(){
        resizeContent();
    }
    /* ウィンドウをリサイズしたときに呼出される関数
     * id が topLeft の要素の幅を広げる
     */
    function resizeContent(){
        var topRightDiv = document.getElementById("topRightCorner");
        contentDiv  = document.getElementById("topLeft");
        contentDiv.style.width = topRightDiv.offsetWidth  + 11 + "px";
    }
    /* 入力された「現在の体重」に応じて「目標体重」を設定する */
    function setTargetValue(){
        var weight = parseFloat(document.forms["createTable"].weight.value)
        if (!isNaN(weight)){
            document.forms["createTable"].target.value = weight - getTargetValue();
        }
    }
    /* 設定できる目標体重の上限の 3/4 の値を返す */
    function getTargetValue(){
        return parseFloat(
            document.getElementById(
                "intervalLimit").firstChild.nodeValue) * 3 / 4
    }
    //-->
    </script>

    <style type="text/css">
    <!--
    body{
        background: #ffffff url("/img/title.png") top center no-repeat;
    }
    h1{ display: none; }
    .error{
        color: red;
        margin-bottom: 10px;
    }
    #content {
        margin: 300px auto 0px;
        text-align: center;
    }
    #input {
        margin: 0px auto;
        width: 450px;
    }
    #input table{
        text-align: left;
        margin: 0px auto;
    }
    .small { font-size: small }

    #createBtn{
        background: url('/img/createBtn.png');
        width: 338px;
        height: 114px;
        border: none;
        cursor: pointer;
    }
    #createBtn:hover{
        background: url('/img/createBtnActivated.png');
        border: none;
    }

    /*---------------------------------------------------------------
     * 影
     */
    /* 右側の縦の影のライン */
    #rightLine{
        position: relative;
        background: transparent url("/img/rightLine.png") right repeat-y;
    }
    /* 右上の角の影 */
    #topRightCorner{
        position: relative;
        background: transparent url("/img/topRightCorner.png") top right no-repeat;
    }
    /* 右下の角の影 */
    #bottomRightCorner{
        position: relative;
        top: 6px;
        background: transparent url("/img/bottomRightCorner.png") bottom right no-repeat;
    }
    /* 下の横の影のライン */
    #bottomLine{
        position: relative;
        left: -11px;
        background: transparent url("/img/bottomLine.png") bottom repeat-x;
    }
    /* 左下の角の影 */
    #bottomLeftCorner{
        position: relative;
        left: -11px;
        background: transparent url("/img/bottomLeftCorner.png") bottom left no-repeat;
    }
    /* 背景となる色を指定する領域。
     * リサイズにより、JavaScript でこの領域の幅を変更する。
     */
    #topLeft{
        position: relative;
        background-color: #fff;
        top: -6px;
    }
    /* 文字を表示する領域 */
    #content2{
        position: relative;
        padding: 10px 0px;
    }
    
    /* --------------------------- */
    #bottom_ad{
        text-align: center;
        margin: 30px auto 0px;
    }
    
    -->
    </style>

  </head>
  <body onresize="resizeContent()" onload="setup()">
    
    <h1>計るだけダイエットのための表を作成</h1>
    
    <div id="content">
        <p>以下の項目を半角数字で入力してください。</p>
        <!-- ダイエットの情報を設定したときのエラー表示 -->

        <div id="input">
        <div id="rightLine">
            <div id="topRightCorner">
                <div id="bottomRightCorner">
                    <div id="bottomLine">
                        <div id="bottomLeftCorner">
                            <div id="topLeft">
                                <div id="content2">

                                    {%include "setting_form.htm" %}
                                
                                </div><!-- end content -->
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        </div><!-- end input -->

    </div><!-- end content -->
    
  </body>
</html>

現在の体重を入力すると、設定できる上限の 3/4 の値が「目標体重」に自動的に入力されるように JavaScript で設定した。 (cf. JavaScript でキー入力に応じて HTML の要素の値とスタイルを変化させる )

 

setting_form.html

<div class="error">
{% if errors %}
    {% for error in errors %}
        {{error}}<br />
    {% endfor %}
{% endif %}
</div><!-- end error -->

<form name="createTable" action="/createTable" method="post">
    <table>
        <tr>
            <td>年 / 月</td>
            <td>
                <input type="text" name="year"
                    value="{{diet.year}}" maxlength="4" />
                 /
                <input type="text" name="month"
                    value="{{diet.month}}" maxlength="2" />
            </td>
        </tr>
        <tr>
            <td>現在の体重</td>
            <td><input type="text" name="weight"
                value="{{diet.weight}}" onkeyup="setTargetValue()" /></td>
        </tr>
        <tr>
            <td>目標体重</td>
            <td>
                <input type="text" name="target"
                    value="{{diet.target}}" />

                {% if targetLimit %}
                    <span class="error">
                        {{targetLimit}} より小さい値は設定できません。
                    </span>
                {% endif %}
            </td>
        </tr>
    </table>

    <p><input id="createBtn" type="submit" value=""></p>

</form>

<p class="small">
    ※「目標体重」は「現在の体重」より
    -<span id="intervalLimit">{{ dt.intervalLimit }}</span> kg
    以内に設定してください。
</p>

 

設定ファイル

背景となる画像を Google App Engine で参照できるように設定が必要となる。 (cf. Google App Engine で画像を扱うには )

 

app.yaml

application: 4diet
version: 1
runtime: python
api_version: 1
  
handlers:
- url: /img
  static_dir: img
- url: /.*
  script: dietsetting.py

 

コントローラ

最後になってしまったけれど、コントローラの部分。webapp.RequestHandler のサブクラス。

 

dietsetting.py

import os
import wsgiref.handlers
from google.appengine.ext.webapp import template
from google.appengine.ext import webapp
import Diet
import logging

class SettingPage(webapp.RequestHandler):
    """ 設定画面を表示する """
    def get(self):
       moveTo(self.response, 'setting_page.html', {
                                    "dt"    : Diet.DietTable(),
                                    "diet"  : Diet.Diet().setDefaultSetting()
                                    })

class TablePage(webapp.RequestHandler):
    """ ダイエット表を作成する """
    def post(self):
        diet = Diet.Diet(self.request.get("weight"),
                            self.request.get("target"),
                            self.request.get("year"),
                            self.request.get("month"))
        dt = Diet.DietTable()
        dt.setDiet(diet)
        try:
            # 入力された値を検証する
            diet.validate()
            # ダイエット表を作成する
            dt.createHtml()
            moveTo(self.response, 'diet_table.html', {'dietTable' : dt})
        except Diet.DietValErr, e:
            template_values = {
                'errors'        : e.errorMessages,      # エラーメッセージ
                'targetLimit'   : e.targetLowerLimit,   # 目標体重の下限
                'diet'          : diet,                 # ダイエット情報
                'dt'            : dt }                  # ダイエット表
            moveTo(self.response, 'setting_page.html', template_values)

def moveTo(response, tmpl, templateValues={}):
    u""" 画面遷移 """
    path = os.path.join(os.path.dirname(__file__), tmpl)
    response.out.write(template.render(path, templateValues))

def main():
    # ログの設定
    logging.getLogger().setLevel(logging.DEBUG)
    # URL と対応するクラス
    application = webapp.WSGIApplication(
                    [('/', SettingPage),            # 設定
                     ('/createTable', TablePage)],  # ダイエット表
                    debug=True)
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
    main()

うーん、エラー処理が何か今一 … (+_+)

 

アップロード

アプリケーションを登録後、作成したファイルをアップロードした。diet フォルダの中に作成していたので、コマンドラインより、

appcfg.py update diet/

 

完成!

http://4diet.appspot.com/

2008年9月4日木曜日

JavaScript でキー入力に応じて HTML の要素の値とスタイルを変化させる

JavaScript でブラウザのウィンドウのリサイズに合わせて HTML の要素の値を更新 のつづき

1. JavaScript によるHTML要素の動的な変化

JavaScript を用いて、HTML の要素を動的に変化させたい。

下図に示すように、左上のフォームのテキストフィールドに、ユーザがキーボードで入力したのに応じて、右下のフォームのフィールドの数値をインクリメントする。

同時にフィールドの下にある HTML の要素の幅を広げる。

080904-001-vert

キーの入力に対応させるには、keydown, keypress, keyup のイベントハンドラを利用する。

 

ソースコード
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional/EN">
<html>
  <head>
    <title>increment</title>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">

    <style type="text/css">
    <!--
    #frm2{ text-align: center;}
    #frm2 .bar{ margin: 0px auto; }
    .bar { width: 0px; }
    #abar{ background-color: #fbb;}
    #bbar{ background-color: #bfb;}
    #cbar{ background-color: #bbf; }
    
    -->
    </style>

    <script type="text/javascript">
    <!--
    var counter = 0
    /* 指定された名前のフォームの要素の値をインクリメントする */
    function increment(name){
        stmt = 'document.forms["frm2"].' + name + '.value = counter++;';
        eval(stmt);
        incWidth(name + "bar");
    }
    
    /* 指定された id の要素の幅を広げる */
    function incWidth(id){
        width = document.getElementById(id).offsetWidth;
        width++;
        document.getElementById(id).style.width = width + 'px';
    }
    //-->
    </script>

  </head>
  <body onresize="increment()">

    <form name="frm">
        <!-- 文字を入力するフィールド -->
        <input type="text"
                    onkeydown ="increment('a')"
                    onkeypress="increment('b')"
                    onkeyup   ="increment('c')" />
    </form>
    
    <!-- 上のフィールドに入力された文字数に応じて表示が変化する -->
    <form id="frm2" name="frm2">
        <input type="text" name="a" />
            <div class="bar" id="abar">&nbsp;</div>
        <input type="text" name="b" />
            <div class="bar" id="bbar">&nbsp;</div>
        <input type="text" name="c" />
            <div class="bar" id="cbar">&nbsp;</div>
    </form>

  </body>
</html>

 

2. HTML 要素の幅を取得する

JavaScript で HTML の要素の幅を取得する方法は、

を参照。以下で述べる DOM の一種。

 

3. eval() 関数で実行時に評価する

eval() 関数は、eval – MDC によると、

Evaluates a string of JavaScript code without reference to a particular object.

ただし、EfficientJavaScript - Dev.Opera - 効率的な JavaScript の「eval と with は隔離しよう」の説明には、次のように述べられている。

これらの構文は性能への影響がとても大きいため, 利用は最小限に留めたい.

追記(2008.9.4) : 上記の eval() 関数を使っている部分は、 JavaScripter さんに教えていただいた書き方をすれば必要なくなる。

追記 (2009.10.3): JavaScript: The Good Parts (p129) 「付録B 悪いパーツ, B.3 eval」の「添え字記法」を参照。

 

4. HTML Form Element

DOM により要素にアクセスする

HTML の要素を取得するには、

document.getElementById(“id名”)

のように、 DOM で定義されているメソッドを経由して取得する。

What is the Document Object Model? によると、

The Document Object Model (DOM) is an application programming interface (API) for valid HTML and well-formed XML documents.

DOM とは、キチンとした形の文書に対して、その要素にアクセスするための API を定義したもの。

 

forms により要素にアクセスする

HTML のフォームの要素にアクセスするのに 、

document.forms[0].名前

と書くことができる。これは先ほどの要素へアクセスする方法とは違って見える。

Gecko DOM Referenceelement によると、

While these interfaces are generally shared by most HTML and XML elements, there are more specialized interfaces for particular objects listed in the DOM HTML Specification—for example the HTML Table Element and HTML Form Element interfaces.

getElementById() は一般的な DOM であるのに対して、document.forms[] は HTML に特有な方法。

forms とは、Document Object Model HTML によると、

forms of type HTMLCollection, readonly
A collection of all the forms of a document.

HTMLCollection とは、1.5. Objects related to HTML documents によると、

An HTMLDocument is the root of the HTML hierarchy and holds the entire content. Besides providing access to the hierarchy, it also provides some convenience methods for accessing certain sets of information from the document.

つまり、form は要素にアクセスしやすくするために定義されている。

以下の中で forms が定義されている。

Firebug の DOM タブにおいて document を表示させると、上記に定義されているものが表示される。

080904-004-horz

Firebug の表示は長くて、まともに見たことがなかったけれど、この際チェックしておこう。 (@_@;)

2008年9月3日水曜日

Google App Engine でダイエット表の作成 (2)

追記(2008.9.6) : ダイエット表を作成するには → http://4diet.appspot.com/


Google App Engine でダイエット表の作成 (1)」の続き。

前回、Django のテンプレートタグを使って table 要素を生成するのを諦め、代わりに DietTable クラスで table 要素を生成するように変更した。しかし、実装しているうちにコードが複雑になり、実装を一時中断することに。 (+_+)

 

table 要素を生成することに専念した HtmlTable クラス

DietTable クラスが複雑になってしまった理由を考えてみると、

  1. ダイエットのための情報から「表」を作成するための「ルール」を管理している
  2. HTML の table 要素を生成する

という二つの異なることを担当しているからだと気がついた。ここで言う「ルール」というのは、例えば、作成するダイエット表において

  • 「土日の列は色を変える」
  • 「表の横線の一目盛は 0.1 kg 」
  • 「目標として設定できる体重には制限がある」

などのこと。これらは HTML の table 要素を生成することとは直接関係がない。関係がないので、HTMLを生成する責務を DietTable クラスから独立させ、 DietTable クラスはそのクラスを使うように変更してみた。 HTML の table 要素を生成するクラスなので HtmlTable と名付けた。これにより HtmlTable クラスは、保持する内容を HTMLとして書き出すことのみに専念し、 DietTable クラスはフォームに入力された Diet 情報から表として生成可能か、ルールに照らしてその妥当性を検証し、また、表のどこに何を書くかを決めることに専念できる。これにより役割分担がはっきりするようになり、誰に何を頼めばいいのか理解しやすくなった。

g5922

 

構造

HtmlTable クラスの構造は上図に示した通りである。クラスの内部は、シンプルに「表」の構造を表現。 HtmlTable クラスの直下にある Rows クラスは、表の各列を管理する Row クラスの集合を管理。そして、一つの Row オブジェクトは、表の列数だけ Cell を管理。 Cell クラスは、セルに設定された内容と、そこにおける複数のスタイルを管理するようにした。

HtmlTable クラスにおいては、

  • 表の大きさ (行・列数) を設定する
  • 特定のセルの内容とスタイルを設定する
  • 特定の行・列のスタイルを設定する
  • HTML の table 要素に変換する

の操作を持つ。

 

余談

当初は「列」とその集合を管理する Cols, Col クラスも作り、Cell へのリンクを持つようにしてた。しかし、結局必要がなかったので削除。 Cols, Col クラスの実装は、Rows, Row クラスとほぼ同じだった。もし、Cell に対して列方向からもリンクが必要であるならば、次元を表わす Dimention クラスを作成し、そのインスタンスとして「行・列」を表現するのがいいのかもしれない。

 

実装

Cell オブジェクトは、「内容やスタイル」が設定されたりする方が、何も設定されないものより圧倒的に少ない予定。だから、最初に HtmlTable オブジェクトを作成したときに、Cell オブジェクトを「行 × 列」の数だけ生成するのではなく、内容やスタイルが設定されたときに、はじめて Cell オブジェクトを生成するようにした。それは、Row オブジェクトについても同様にした。まぁ、どうせ表なんて大きくないのだから、最初から全て Cell オブジェクトを生成しておいてもいいような気もしたし、その方が実装がシンプルになったような気もするが、とりあえずよしとしておくか。 ^^;

 

ソースコード

以下にコードを示す。

HtmlTable.py

class HtmlTable:
    u""" HTML のtable 要素を作成するためのクラス

    構造: HtmlTable - Rows -* Row -* Cell
    """
    def __init__(self, row, col):
        self.rows = Rows()      # 行を管理するオブジェクト
        self.row = row          # 行数
        self.col = col          # 列数
        
    def setCell(self, row, col, content=""):
        u""" 指定された行・列番号のセルの内容を設定する

        セルを設定した場合、設定した Cell オブジェクトのみが存在する。
        """
        # 指定された行・列番号が生成したテーブルの範囲を超えてないことを確認する
        if self.row < row or self.col < col:
            raise OutOfRangeErr
        # セルを作成して、行方向からリンクする
        self._setLink(Cell(row, col, content))

    def _setLink(self, cell):
        u""" セルを行方向からリンクする """
        self.rows.setCellRow(cell)
        
    def addCssToCell(self, row, col, css):
        u""" セルに CSS を追加する"""
        # 指定された行・列番号のセルの存否を確認する
        c = self.findCell(row, col)
        if c: c.addCss(css)
        else:
            newCell = Cell(row, col, "")
            newCell.addCss(css)
            self._setLink(newCell)
    
    def addCssToCol(self, col, css):
        u""" 列に CSS を設定する """
        for i in range(0, self.row):
            self.addCssToCell(i, col, css)
            
    def addCssToRow(self, row, css):
        u""" 行に CSS を設定する """
        for i in range(0, self.col):
            self.addCssToCell(row, i, css)
        
    def findCell(self, row, col):
        u""" 行番号 row, 列番号 col のセルを取得する """
        return self.rows.findCell(row, col)
        
    def toHtml(self):
        u""" HTML に変換する """
        return "<table align='center'>\n" + \
                    self.rows.toHtml(self.row, self.col) +\
                "</table>"

class OutOfRangeErr(Exception):
    pass

class Rows:
    u""" 行の集合を管理するクラス """
    def __init__(self):
        self.rows = []      # 行のリスト
        
    def setCellRow(self, cell):
        u""" 指定されたセルを行に設定する """
        row = self.findRow(cell.row)
        if row:
            # 新しくセルが設定された場合、古いセルは上書き。
            row.setCell(cell)
        else:
            newRow = Row(cell.row)
            newRow.setCell(cell)
            self.add(newRow)

    def findRow(self, num):
        u""" 指定された番号の行が存在するか検索する """
        for row in self.rows:
            if row.num == num:
                return row
        return None

    def findCell(self, row, col):
        u""" セル(row,col) を取得する """
        r = self.findRow(row)
        if r: return r.findCell(col)
        else: return None

    def add(self, row):
        u""" 指定された行を追加する """
        if not self.rows:
            self.rows.append(row)
        else:
            for i,r in enumerate(self.rows):
                if r.num > row.num:
                    self.rows.insert(i-1, row)
                    return
            self.rows.append(row)
    
    def toHtml(self, row, col):
        u""" HTML に変換する

        HtmlTable#setCell() において設定されてないセルは、Cell オブジェクトが
        空である。その際、このオブジェクトが空の tr 要素を出力する責務がある。
        """
        result = ""
        for i in range(0,row):
            r = self.findRow(i)
            if r:
                result += r.toHtml(i, col)
            else:
                # 行の要素がない場合
                result += "\t<tr>\n\t\t" + "<td></td>"*col +"\n\t</tr>\n"
        return result

class Row:
    u""" 行を表わすクラス """
    def __init__(self, num):
        self.cells = []     # セルのリスト
        self.num = num      # 行番号

    def setCell(self, cell):
        u""" 列番号の順でセルのリストに追加する。 """
        for i,c in enumerate(self.cells):
            if c.col == cell.col:
                # 同じ列番号のセルが見つかった場合は上書きする。
                self.cells[i] = cell; return
            if c.col > cell.col:
                self.cells.insert(i-1, cell); return
        self.cells.append(cell)

    def findCell(self, col):
        u""" 指定された行番号のセルを検索する """
        for c in self.cells:
            if c.col == col: return c
        return None

    def toHtml(self, row, col):
        u""" HTML に変換する

        HtmlTable#setCell() において設定されてないセルは、Cell オブジェクトが
        空である。その際、このオブジェクトが空の td 要素を出力する責務がある。
        """
        result = "\t<tr>\n"
        for i in range(0,col):
            result += "\t\t"
            c = self.findCell(i)
            if c:
                result += c.toHtml()
            else:
                result += "<td></td>"
            result += "\n"
        result += "\t</tr>\n"
        return result

class Cell:
    u""" セルを表わすクラス """
    def __init__(self, row, col, content=""):
        self.row = row              # 行番号
        self.col = col              # 列番号
        self.content = content      # 内容
        self.csss = []              # CSS のリスト

    def addCss(self, css):
        u""" セルに CSS を追加 """
        self.csss.append(css)

    def toHtml(self):
        return "<td class='" + " ".join(str(x) for x in self.csss) + "'>" \
                    + str(self.content) + "</td>"

「セルの内容を削除する」など、今回必要のないメソッドは実装していない。

あ~、これでやっと各々のメソッドがシンプルになった。 ^^

 

ユニットテスト

ついでに ユニットテストもちょっとだけ書いておこう。 (cf. PyScripter で UnitTest を自動で生成)

import unittest
import HtmlTable

class TestHtmlTable(unittest.TestCase):

    def setUp(self): 
        #  2行3列のテーブルを作成
        self.tb = HtmlTable.HtmlTable(2,3)
        # セルの設定
        self.tb.setCell(0,0,"hoge")
        self.tb.setCell(0,1,"piyo")
        self.tb.setCell(1,0,"fuga")
        # セルにCSS を追加
        self.tb.addCssToCol(0, "sunday")
        self.tb.addCssToCol(2, "saturday")
        self.tb.addCssToCell(1,1,"sunday")
        # 行・列に CSS を追加
        self.tb.addCssToRow(1, "x_axis")
        self.tb.addCssToCol(1, "y_axis")

    def tearDown(self): 
        pass

    def testsetCell(self):
        tb = HtmlTable.HtmlTable(3,4)
        # 例外が投げられることを確認する
        self.assertRaises(HtmlTable.OutOfRangeErr, tb.setCell, 3, 5, "hoge")
        self.assertRaises(HtmlTable.OutOfRangeErr, tb.setCell, 5, 3, "hoge")
        self.assertRaises(HtmlTable.OutOfRangeErr, tb.setCell, 5, 5, "hoge")
        # 例外が投げられないことを確認する
        try:
            tb.setCell(3,3,"hoge")
        except OutOfRangeErr:
            fail("expected a OutOfRangeErr")
    
    def testtoHtml(self):
        self.assertEqual(self.tb.toHtml(), """\
<table align='center'>
 <tr>
  <td class='sunday'>hoge</td>
  <td class='y_axis'>piyo</td>
  <td class='saturday'></td>
 </tr>
 <tr>
  <td class='sunday x_axis'>fuga</td>
  <td class='sunday x_axis y_axis'></td>
  <td class='saturday x_axis'></td>
 </tr>
</table>\
""", self.tb.toHtml())

if __name__ == '__main__':
    unittest.main()

 

テストについて

久しぶりにちょっと本をひもといて、

私は、クラスがなすべきことをすべて調べてから、それらについて 1 つずつ、不具合を起こしそうな条件でテストするようにしています。プログラマによっては「すべての公開メソッドをテストする」よう勧めていますが、これとは違います。テストはリスク主導であるべきです。 (…)

テストをたくさん書こうとする余り、必要なテストを書き漏らしてしまうからです。(…)

大事なことは、一番怪しいと思う部分をテストすることです。

(リファクタリング , p97 より。太字は引用者による。)

なるほど。

あ、決してテストをあまりしてない言訳に使っているわけではありません…。 ^^;


関連記事

Google App Engine でダイエット表の作成 (1)

追記(2008.9.6) : ダイエット表を作成するには → http://4diet.appspot.com/


Google App Engine でシンプルなアプリを作成する計画」 の続き。

 

実装方法について考える

早速実装について考える。「設定画面」については、ただのフォームなので問題なし。では、「表」の方は何で作ればいいのか?表と言えば、 HTML の table 要素。 table 要素でマス目を細かくして、CSS で枠の太さや背景となる色を設定してやればいいのかな? JavaScritp で簡単にサクっとカスタマイズしたテーブルを作成するライブラリがあればいいけれど、 よく知らないのでとりあえず JavaScript は横に置いておく。

必要なメモを探して、実装開始。

 

画面遷移

まずは画面に対応した HTML ファイルを二つ作成し、「作成」ボタンを押したら、表が表示される HTML へと遷移するようにした。同時に、フォームの設定内容を受ける Diet クラスを作成し、遷移先のテンプレートでその情報にアクセスできるようにした。

path3258

 

表の作成

「表」のページでは、まずマス目を作成しなくてはいけない。 A4 の用紙で印刷した場合に、 1 ページで納まるマス目を「印刷プレビュー」を見ながら探る。

マス目を描くには、テンプレートファイルにおいて for タグ を使う。

{% for 変数 in リスト %}
    {{ 変数.プロパティ}}
{% endfor %}

これは上手くいった。 ^^

次に、表の枠・背景の色をカスタマイズするために td 要素において各々 class 指定をし、 CSS で設定を行う。土・日曜日の列を「青・赤」に設定するには、Diet オブジェクトから「年月」情報を得て、列番号とその月の日付の曜日に合わせて class 情報を設定しなくてはいけない。(cf. Python である月の日曜日の日付のリストを取得)

for タグの中で、 if タグを使って、

{% for 変数 in リスト %}
    {% if 変数.プロパティ == 値 %}
        処理 A
    {% else %}
        処理 B
    {% endif %}
{% endfor %}

としたら、あれ?(@_@;) エラーが…。 for タグで取り出した要素オブジェクトを使って、 if タグで値の検査をすることができないようだ。うーん、使い方が悪いのかなぁ。

少し記事が古いが、Pythoneer » Django’s template syntax, enhanced ifchanged にループの中で if タグを使う場合、

{% if forloop.counter%2==0 %}
    row1
{% else %}
    row2
{% endif %}

But that doesn’t work in Django templates :-(.

とあったので、やはりダメなのかもしれない…。

 

table 要素を作成するクラス

表を作成するために、テンプレートファイルにおいてテンプレートタグを使うのは諦めた。 (+_+) もしかすると、カスタムタグを使えばいいのかもしれないけれど、 Django については Google App Engine での説明でしか読んだことがないのでわからない。 ^^; また今度ちゃんと読んでから考えよう。

仕方がないので Diet オブジェクトからフォームに入力された情報をもらって、HTML の table 要素を文字列として出力する DietTable クラスを作成することにした。

g5240

入力されたフォームの値を検証するメソッド validate()と、table 要素を出力する html_trs() を実装。「土日」の背景色を設定するだけでなく、表の「枠の太さ」も条件によって変えるようにしたら、こんなことに…。 (@_@;)

(一つの要素に複数のスタイル情報をつけるには、「CSS で複数の class を指定する」を参照。)

class DietTable:
    COLS_NUM = 32
    START_COL_NUM = 1
    
    def __init__(self, year, month, weight, target):
        self.year, self.month, self.weight, self.target = year, month, weight, target
        self.rows = range(0,38)
        self.cols = range(0,32)

    # 入力された値を検証する
    def validate(self):
        # TODO
        error = []
        
        # 「年」が数字に変換できることを確認する
        if self.year.isdigit(): self.year = int(self.year)
        else: error.append("「年」を数字で入力してください。")
        # 「月が数字に変換できることを確認する
        if self.month.isdigit(): self.month = int(self.month)
        else: error.append("「月」を数字で入力してください。")
        
        # 目標体重が適切か?
        if error: raise DietValErr, error

    # HTML で TR 要素を出力
    def html_trs(self):
        result = ""
        for i in self.rows:
            result += ""
            if i == len(self.rows) - 1:
                # 最後の行は横軸方向を太くする
                result += self.html_tds(x_axis=True)
            else:
                result += self.html_tds()
            result += ""
        return result
    
    # HTML で TD 要素を出力
    def html_tds(self, x_axis=False):
        result = ""
        # 日曜日、土曜日の列に色を付ける
        # 色はこのオブジェクトを使用するテンプレートの CSS で指定
        for i in range(0, DietTable.COLS_NUM):
            if i in [self._dayColPosition(x)
                        for x in self._dayslist(calendar.SUNDAY)]:
                # 日曜日の場合
                if i == 0: result += self._html_td("", x_axis, y_axis=True)
                else: result += self._html_td("sunday", x_axis)
            elif i in [self._dayColPosition(x)
                            for x in self._dayslist(calendar.SATURDAY)]:
                # 土曜日の場合
                if i == 0: result += self._html_td("", x_axis, y_axis=True)
                else: result += self._html_td("saturday", x_axis)
            else:
                if i == 0: result += self._html_td("", x_axis, y_axis=True)
                else: result += self._html_td("", x_axis)
        return result

    # このオブジェクトの年月における指定された曜日の日付のリストを返す
    def _dayslist(self, dayOftheWeek):
        return [x[dayOftheWeek]
            for x in calendar.monthcalendar(self.year, self.month)]

    # td 要素を指定した曜日を入れて文字列として返す
    def _html_td(self, dayOftheWeek="", x_axis=False, y_axis=False):
        if   x_axis and y_axis:
            return " "
        elif x_axis and not y_axis:
            return " "
        elif not x_axis and y_axis:
            return " "
        else:
            return " "

    # 一日の列を基準にして、指定した日付の列の相対的な位置を返す
    def _dayColPosition(self, x):
        return x + self.START_COL_NUM - 1

 

もうだめだ。翌日になって見直したら、何が書いてあるのかよくわからなくなった。このまま実装を続けるのは放棄。 ^^; だいたいこんな if if して見にくいコードなんて読みたくもない。 (+_+)


関連記事