jQueryを使う場合に、まずやるのはセレクタの記述です。
その際にちょっと楽になるブックマークレットがあります。

有名なサイトにあるものなので、もうご存知の方も多いと思いますがまだ知らない人のために紹介します。

http://semooh.jp/jquery/ref/cont/selectors/

にある”jQuery Selectors Inspector”の部分のブックマークレットをブックマークして、実行すると入力ダイアログが出ます。

そこに試したいセレクタを書いて実行すると、該当個数が表示され、該当部分が点滅します。

対象の要素の状態(CSS)によってはうまく点滅してくれない場合もありますので、点滅してくれなくても必ずしも間違っているとは限りません。個数は正しいはずです。
が、非常に便利なので是非活用して下さい。

本日2010年1月12日、『Progression 4.0.1 Public Beta 1.3』がリリースされました。ぱちぱちぱち。

今回個人的にうれしい機能色々なので、勝手に紹介します。

導入について

注意しないといけないことというわけではありませんが僕が一瞬ひっかかった点があったので書きます。
ダウンロードしたファイル名は”Progression-ja-CS3.mxp”となっており、「あれ?CS4は?」と思われる方もいるかもしれませんが、これはCS3以上という意味なので、CS4も同じmxpで大丈夫。(とnium先生に教えてもらいました。)

追加機能① PreloadSWFクラス

LoadCommandで使用するクラスで、“PreloadSWF”というクラスが追加されました
リリースノートには以下のようにあります。

CastPreloader クラスの CastEvent.CAST_LOAD_START イベント内で、読み込まれる本体の監視処理を可能にする PreloadSWF クラスを追加しました。

これがどう便利かというと、Preloader内でIndex.swfと外部画像等を読み込む仕様の場合のロード進捗管理が大変楽になります。

とりあえず

  • Preloader.swf内でXMLを読み込む
  • それをもとに画像を読み込む
  • その画像とindex.swfとの合計を100%として進捗表示をする

という場合をサンプルとして、具体的にコードで示します。

まずPreloadSWFがない場合のコードです。

PreloadSWFがない場合のPreloader.as

package {
	import flash.net.*;
	import jp.progression.casts.*;
	import jp.progression.commands.display.*;
	import jp.progression.commands.lists.*;
	import jp.progression.commands.net.*;
	import jp.progression.commands.tweens.*;
	import jp.progression.commands.*;
	import jp.progression.data.*;
	import jp.progression.events.*;
	import jp.progression.executors.*;

	public class Preloader extends CastPreloader {

		private var _loadImageLength:Number;
		private var _myLoaderList:LoaderList;

		public function Preloader()
		{
			// プリローダーが読み込むファイルと、実行形式を指定します。
			super( new URLRequest( "index.swf" ), false, CommandExecutor );
		}

		override protected function atReady():void
		{
		}

		override protected function atCastLoadStart():void
		{
			addCommand(
				//XML読み込み
				new LoadURL(new URLRequest("assets/xml/images.xml")),

				function()
				{
					//XMLから情報取得
					//今回は画像の枚数が記述してある
					var myXml:XML = new XML(this.latestData);
					_loadImageLength = myXml.images.@_loadImageLengthgth;

					//LoaderListを生成
					//進捗表示用にonProgressイベント設定
					_myLoaderList = new LoaderList(
						{
							onProgress:function():void
							{
								//index.swfも含めた進捗を出したいので、分母を1個多くパーセントを換算する
								var tmpPercent:Number = this.percent / (_loadImageLength + 1) * _loadImageLength;

								//進捗表示
								trace(tmpPercent);
							}
						}
					);

					//XMLから取得した情報をもとに画像読み込みのLoadBitmapDataコマンド生成
					//LoaderListに追加
					var n:int = _loadImageLength;
					for (var i:int = 1; i <= n; i++)
					{
						_myLoaderList.addCommand(
							new LoadBitmapData(new URLRequest("assets/images/" + i.toString() + ".jpg"))
						);
					}

					//LoaderListを親であるSerialListに追加
					this.parent.addCommand(
						_myLoaderList
					);
				}
			);
		}

		override protected function atProgress():void
		{
			//進捗更新
			//index.swfの進捗を、画像の枚数+index.swfで100%となるよう、割る
			var tmpPercent:Number = _loadImageLength / (_loadImageLength + 1) + bytesLoaded / bytesTotal / (_loadImageLength + 1);
			trace(tmpPercent);
		}

		override protected function atCastLoadComplete():void
		{
			addCommand(
			);
		}
	}
}

理系脳の人ならほんとはもっとスマートに書けそうですが、とりあえず目的は達成できます。
文系脳である僕のロジックが馬鹿すぎてみなさんが理解できない場合のために一応解説します。

①まずXMLを読み込みます。するとそこには画像枚数が書いてあります。今回は計算しやすいように仮に9枚とします。

new LoadURL(new URLRequest("assets/xml/images.xml")),

②LoaderListを生成します。

_myLoaderList = new LoaderList(

③進捗管理のためonProgressを設定します。
ここで表示されるパーセントは9枚で100%です。ですが、目的としてはindex.swfを加えたパーセントを出したいので、9枚で100%の計算でLoaderListが出してくれているパーセントを、全体がindex.swfを加えた10枚だったら何パーセントにあたるのか・・・と計算しなおします。よって画像を全部読み終わったら今回は進捗は90%まで来ます。

onProgress:function():void
{
	var tmpPercent:Number = this.percent / (_loadImageLength + 1) * _loadImageLength;
	trace(tmpPercent);
}
);

④LoadBitmapDataコマンドを9個生成してLoaderCommandでまとめます。

var n:int = _loadImageLength;
for (var i:int = 1; i <= n; i++)
{
	_myLoaderList.addCommand(
		new LoadBitmapData(new URLRequest("assets/images/" + i.toString() + ".jpg"))
	);
}

⑤先程のLoaderListで画像を全部読み終わると次はindex.swfの読み込みに進みます。ここではindex.swfの読み込み状況が出力されます。これは当然、index.swfひとつを読み込み終わると100%です。これを画像9枚とあわせた10枚で100%にしたいので、10で割ります。そいで90を足します。これで全体の進捗がわかります。

var tmpPercent:Number = _loadImageLength / (_loadImageLength + 1) + bytesLoaded / bytesTotal / (_loadImageLength + 1);
trace(tmpPercent);

という感じです。なんかめんどくさいですね。
これを今回追加されたPreloadSWFを使って書いてみます。

PreloadSWFがある場合のPreloader.as

package {
	import jp.progression.casts.*;
	import jp.progression.commands.display.*;
	import jp.progression.commands.lists.*;
	import jp.progression.commands.net.*;
	import jp.progression.commands.tweens.*;
	import jp.progression.commands.*;
	import jp.progression.data.*;
	import jp.progression.events.*;
	import jp.progression.executors.*;

	public class Preloader extends CastPreloader
	{

		/**
		 * 新しい Preloader インスタンスを作成します。
		 */
		public function Preloader()
		{
			// プリローダーが読み込むファイルと、実行形式を指定します。
			super( new URLRequest( "index.swf" ), false, CommandExecutor );
		}

		/**
		 * SWF ファイルの読み込みが完了し、stage 及び loaderInfo にアクセス可能になった場合に送出されます。
		 */
		override protected function atReady():void
		{
		}

		/**
		 * オブジェクトが読み込みを開始した瞬間に送出されます。
		 * このイベント処理の実行中には、ExecutorObject を使用した非同期処理が行えます。
		 */
		override protected function atCastLoadStart():void
		{
			addCommand(
				//XML読み込み
				new LoadURL(new URLRequest("assets/xml/images.xml")),

				function()
				{
					//LoaderList作成
					//進捗管理用にonProgress設定
					var myLoaderList:LoaderList = new LoaderList(
						{
							onProgress:function():void
							{
								//進捗表示
								trace(this.percent);
							}
						}
					);

					//XMLをもとに画像読み込み用のLoadBitmapDataを生成、LoaderListに追加
					var myXml:XML = new XML(this.latestData);
					var n:int = myXml.images.@length;
					for (var i:int = 1; i <= n; i++)
					{
						myLoaderList.addCommand(
							new LoadBitmapData(new URLRequest("assets/images/" + i.toString() + ".jpg"))
						);
					}

					//★
					//同じLoaderListにPreloadSWFも追加します。
					myLoaderList.addCommand(
						new PreloadSWF()
					);

					//画像とIndex.swfを読む込むLoaderListを親SerialListに追加
					this.parent.addCommand(
						myLoaderList
					);
				}
			);
		}

		/**
		 * ダウンロード処理を実行中にデータを受信したときに送出されます。
		 */
		override protected function atProgress():void
		{
		}

		/**
		 * オブジェクトが読み込みを完了した瞬間に送出されます。
		 * このイベント処理の実行中には、ExecutorObject を使用した非同期処理が行えます。
		 */
		override protected function atCastLoadComplete():void
		{
			addCommand(
			);
		}
	}
}

①XMLを読み込みます。

new LoadURL(new URLRequest("assets/xml/images.xml")),

②画像とindex.swfを読み込むためのLoaderListを生成します。

var myLoaderList:LoaderList = new LoaderList(
);

③進捗管理用にonProgressを設定します。先程と違い、this.percentが画像9枚+index.swf全体を100%として出力してくれるので、何も考える必要ありません。

onProgress:function():void
{
	trace(this.percent);
}

④XMLをもとにLoaderListにLoadBitmapDataコマンドを追加していきます。

var myXml:XML = new XML(this.latestData);
var n:int = myXml.images.@length;
for (var i:int = 1; i <= n; i++)
{
	myLoaderList.addCommand(
		new LoadBitmapData(new URLRequest("assets/images/" + i.toString() + ".jpg"))
	);
}

⑤さらに、同じLoaderListでindex.swfの進捗も管理したいので、PreloadSWFも生成して追加します。

myLoaderList.addCommand(
	new PreloadSWF()
);

という感じです。
なんと、とてもわかやすい。というか何も考えなくてよい!
Progressionがどんどん進歩して、僕の脳はどんどん退化しそうです。

まとめ

今回追加されたPreloadSWFにより、画像等外部データとindex.swfの読み込みを一つのLoaderList内ですることができ、記述や管理が非常に簡単でわかりやすくなります。
素晴らしいですね。

サンプル

一応サンプルソースを置いておきます。上記内容にTextField置いて進捗表示している程度ですが。
サンプルをダウンロード

ほかに

  • ResourcePrefecherクラス
  • Listenクラス
  • foreground、backgroundへのアクセス

あたりがいいなって思うので、追って紹介したいと思います。

はじめに

Progressionで意図しないタイミングでmanagerがnullになります。
基礎中の基礎みたいなレベルなので、僕の組み方が悪いはずなのですが、どこが悪いのか見つけられません。
ということで賢い人誰か教えてくれたらいいなエントリ。

ちなみに環境はこんなもんです。Progressionのバージョン以外あんまり関係ないと思いますけど。

  • Adobe Flash CS4
  • FlashPlayer10
  • Progression4.0.1

サンプル

サンプルを開く
traceがわりにTextFieldに出力しています。
2度目の”[ ChildPage1 ] atCastAdded”の下の行のmanagerの出力が
“manager : null”になってしまっています。

ソース

ソースをダウンロード
ソースはサンプルとして極力単純化したつもりです。
省略してますが、かいつまむと全体のソースはこんな感じです。
2秒おきにChildScene1→ChildScene2と移動し、各ChildSceneではChildPageをAddしたりRemoveしたりしてるだけなつもりです。

IndexScene.as

public class IndexScene extends SceneObject
{
	private var _child1:ChildScene1;
	private var _child2:ChildScene2;
	private var _tf:CastTextField;

	public function IndexScene()
	{
		_tf = new CastTextField( { id:"output", x:600, width:200, height:600 } );

		_child1 = new ChildScene1("child1");
		_child2 = new ChildScene2("child2");

		this.addScene(_child1);
		this.addScene(_child2);
	}

	protected override function atSceneLoad():void
	{
		addCommand(
			new AddChild(container, _tf)
		);
	}

	protected override function atSceneInit():void
	{
		_tf.text += "\n" + "[ IndexScene ] atSceneInit";

		new SerialList(null,
			2,
			new Goto(_child1.sceneId),
			2,
			new Goto(_child2.sceneId),
			2,
			new Goto(_child1.sceneId),
			2,
			new Goto(_child2.sceneId)
		).execute();
	}
}

ChildScene1.as (ChildScene2もクラス名以外は同じ)

public class ChildScene1 extends SceneObject
{
	private var _page:ChildPage1;
	private var _tf:CastTextField;

	public function ChildScene1( name:String = null, initObject:Object = null )
	{
		_page = new ChildPage1();
		_tf = getInstanceById("output") as CastTextField;
	}

	protected override function atSceneInit():void
	{
		_tf.text += "\n" + "[ ChildScene1 ] atSceneInit";
		_tf.text += "\n" + "manager : " + manager;

		addCommand(
			new AddChild(container, _page)
		);
	}

	protected override function atSceneGoto():void
	{
		_tf.text += "\n" + "[ ChildScene1 ] atSceneGoto";

		addCommand(
			new RemoveChild(container, _page)
		);
	}
}

ChildPage1.as (ChildPage2もクラス名以外は同じ)

public class ChildPage1 extends CastSprite
{
	private var _tf:CastTextField;

	public function ChildPage1( initObject:Object = null )
	{
		_tf = getInstanceById("output") as CastTextField;
	}

	protected override function atCastAdded():void
	{
		_tf.text += "\n" + "[ ChildPage1 ] atCastAdded";
		_tf.text += "\n" + "manager : " + manager;
	}
}

大体一般的なProgressionのソースです。と僕は思い込んでます。
で、次にサンプルのtraceを見てみます。コメントをつけました。

[ IndexScene ] atSceneInit  		//IndexSceneに来た

[ ChildScene1 ] atSceneInit  		//ChildScene1に来た
manager : [Progression id="index"]  	//managerの参照あり
[ ChildPage1 ] atCastAdded  		//ChildPage1がAddChildされた
manager : [Progression id="index"]  	//managerの参照あり

[ ChildScene1 ] atSceneGoto  		//ChildScene1から離脱、ChildScene2に移動開始

[ ChildScene2 ] atSceneInit  		//ChildScene1に来た
manager : [Progression id="index"]  	//managerの参照あり
[ ChildPage2 ] atCastAdded  		//ChildPage2がAddChildされた
manager : [Progression id="index"]  	//managerの参照あり

[ ChildScene2 ] atSceneGoto  		//ChildScene2から離脱、ChildScene1に移動開始

[ ChildScene1 ] atSceneInit  		//ChildScene1に来た
manager : [Progression id="index"]  	//managerの参照あり
[ ChildPage1 ] atCastAdded  		//ChildPage1がAddChildされた
manager : null  			//★★★managerの参照なし★★★

[ ChildScene1 ] atSceneGoto  		//ChildScene1から離脱、ChildScene2に移動開始

[ ChildScene2 ] atSceneInit  		//ChildScene1に来た
manager : [Progression id="index"]  	//managerの参照あり
[ ChildPage2 ] atCastAdded  		//ChildPage2がAddChildされた
manager : [Progression id="index"]  	//managerの参照なし

ChildPage1.atCastAdded()内で参照したmanagerがnullです。
1回目は同じ箇所でちゃんととれています。
また、直前のChildScene1.atSceneInit()内でもとれています。
でも、ChildPage1.atCastAdded()内で参照したmanagerがnullです。

他にも以下のテストをしました。

  • もう一回り繰り返し、1→2→1→2→1→2にすると、2回目と3回目の同様の箇所で同様の現象が起きます。
  • シーン遷移の順を2→1→2→1にすると、今度は2回目のChildScene2の同様の箇所で同様の現象が起きます。
  • ChildPage3とかを作って、1→2→3→1→2→3にすると、やはり2回目のChildScene1の同様の箇所で同様の現象が起きます。

ものすごい基本的なレベルなので、きっと組み方が誤っていると思われますが、ちっともわからないのでエントリにしてみました。これぞ神待ちサイト。

追記

賢い人その①@soundkitchenさんが教えてくれました。バグだそうで、同じ現象に出会ったそうです。

賢い人その②(というか本家)@niumさんが教えてくれました。次のリリースではなおるそうです。やったね :D

先日公開してみたURLOpener、ブラウザの挙動のあまりの違いに落としどころがわからなくなっていましたがまとまりました。

当初はJSでwindow.openを呼んだ時の動きに合わせる!と思っていましたが、別にそんな必要ないなと。
使う側になってみて考えて、とりあえずよく使う機能を楽に使えればいいや、と切り替えました。

ダウンロード

SparkProjectに初コミットしてみましたので、Sparkのリポジトリからエクスポートして下さい。
http://www.libspark.org/wiki/ikekou/URLOpener

またはzip
http://ikekou.jp/blog/wp-content/uploads/2009/11/URLOpener.zip

よくやること

僕が考えるよくやることを列挙すると、せいぜいこんなもの。

  • target=_selfで開く
  • target=_blankで開く
  • target=_blank、サイズ指定で開く
  • target=_blank、サイズ、位置指定で開く
  • target=_blank、サイズ指定、位置は画面中央で開く
  • target=_blank、フルスクリーンで開く
  • alertでデバッグ

これらを楽にするということと、Safariでサイズ指定ある場合は先日発見した(つもりでいる)Safariでブロックを回避する方法を使って開くということ、その2点を目標としました。

できること

  • window.openよりnavigateToURLのほうが実感としてレスポンスがいいので、オプション指定ない場合はnavigateToURL、オプション指定ある場合はwindow.openと呼び分け
  • 今まであんまりできてなかったと思われる、ポップアップブロックON状態のSafariでの、Flashからのサイズ指定ポップアップを行う

できないこと

  • もともとJSでできないことができるわけではない
  • ブラウザによってはスクロールバーやらアドレスバーをオフと指定しても勝手に出る。それはどうしようもない
  • Operaはブロック設定が何段階かあるが、JavascriptではブロックされないがFlashからだとブロックされる設定がある。その設定でFlashから開けるようにしたかったが、できなかった。

使い方

使い方はこんなかんじです。

import jp.ikekou.net.urlopener.URLOpener;
import jp.ikekou.net.urlopener.URLOpenerFeatures;
import jp.ikekou.net.urlopener.URLOpenerTarget;

//引数は以下の3つ
//1
//開くURL
//2
//Target window name、一応定数用意しましたが、普通に"_blank"とか"hogeWindow"とかでOK
//3
//window.openの第三引数と同じ。よく使うセットとして
//URLOpenerFeatures.FULLSCREEN    メニューバーとか全部オフにしてフルスクリーン
//URLOpenerFeatures.DIALOG(width, height [, left, top])    メニューバーとか全部オフにしてサイズ指定の小窓、位置指定省略すると中央表示

//target=_self
URLOpener.open(url);

//target=_blank
URLOpener.open(url,URLOpenerTarget.BLANK);

//target=_blank、サイズ指定、位置指定省略すると中央
URLOpener.open(url,URLOpenerTarget.BLANK,URLOpenerFeatures.DIALOG(800,600));

//target=_blank、サイズ指定、位置指定nullにしても中央
URLOpener.open(url,URLOpenerTarget.BLANK,URLOpenerFeatures.DIALOG(800,600,null,100));

//target=_blank、サイズ指定、位置指定
URLOpener.open(url,URLOpenerTarget.BLANK,URLOpenerFeatures.DIALOG(800,600,100,100));

//target=_blank、フルスクリーン
URLOpener.open(url,URLOpenerTarget.BLANK,URLOpenerFeatures.FULLSCREEN);

//target=_blank、Featuresを自分で指定
URLOpener.open(url,URLOpenerTarget.BLANK,"width=800,height=600");

//alert出す
URLOpener.alert("hoge");

//alert出してるけど一時的にオフにして試したい
URLOpener.debugMode = false;
URLOpener.alert("fuga");

今後の予定

  • windowモード(transparentとか)によって挙動が変わるらしいので、調べる。もしかしたら全然いらんことしてるかもしれない。
  • console.log等のコンソールに出す関数を、クロスブラウザに一個の関数で出したい
  • URLOpenerFeatures.DIALOGを使ったときに色々オフする記述を入れているが、それで十分なのか、もっといい記述はないのか、見直す
  • バージョン表記ってどこにいれたらいいんだろうか!
  • 優れたライブラリを優れたものとしているのは、ステキなドキュメントの力が大きい。ステキなドキュメント作ろう。どうせちっこいちっこいライブラリだし、そんくらいできるはず。

本日仕事中に出会った現象です。
原因は自分のケアレスミスだったのですが、普通なら間違った記述ならそれなりのエラーが出るので問題箇所に気づくものの、今回はエラーも出ないので問題の記述に気づきづらかったので、書き残すことにしました。

環境

  • WinXP & FlashCS4 & FlashDevelop3.0.3RTM
  • DocumentClassで開発
  • AS3

(とはいえ他の環境でテストしてないのでCS3ならどうか、等はわかりません)

現象

  • 直前まで普通に開発をしていた
  • traceもいっぱい出してた
  • 何かを書いてパブリッシュしたら突然trace一切出ず、動きも一切なく、クラスを読んでないような雰囲気
  • ためしにコンストラクタにtraceを入れてもtrace出ず
  • Flashのデバッガ(Shift+Ctrl+Enterのやつね)を起動してみようとすると、「このSWFにはActionScriptが含まれていないためデバッグできません」と言われる
  • ドキュメントクラス記入欄の右にある鉛筆マークを押すとちゃんと該当のファイルが開くためパスやファイル名が間違っていることはない

と、よくわかんないことだらけなので、最初はflaファイルがぶっ壊れたのか、と思いDropBoxの履歴からちょっと前のファイルを取り出して試したりしました。が、やはり動かず。問題はどうやらflaファイルにはないようでした。

解決

そんなこんなで色々してたら解決しました。問題はクラスファイルのほうにありました。以下解決編。

これは動くクラスファイル↓

package
{
	import flash.display.MovieClip;

	public class Main extends MovieClip
	{
		public function Main()
		{
			var hoge:Number = 0.5;
			trace(hoge);
		}
	}
}

これは今回の現象を再現した動かないクラスファイル↓

package
{
	import flash.display.MovieClip;

	public class Main extends MovieClip
	{
		public function Main()
		{
			var hoge:Number = 0..5;
			trace(hoge);
		}
	}
}

このサンプルなら一目瞭然ですね。
そう、小数点が2個ついてます。
馬鹿ですね。

馬鹿でしょうもないミスとはいえ、エラーも出ないので原因がすぐには思い当たりづらく、traceもでないからエラー箇所の絞込みもできず、そもそもデバッグ起動すらできなくなるので、これが長いソースだとなかなか気づきづらいものです。(と、言い訳させてください)

「..」を使うのはまずXMLの時ですよね。あとはよく知りません。
なので、わかる人からしたらこの現象は当たり前なのかもしれませんが、僕としてはエラーくらい出て欲しいものです。

みなさんも、もし同様の現象に出会ったら、小数点が2個ないか、探してみてください!

原因がわからずにうんうんうなりながらtwitterで「困ったよ」って言ったら色々助言してくれた皆様。いつもながらありがとうございました!

おまけ

ただ単にその箇所のエラーが出ないだけでなく、そもそもクラスと認識してないので、ほかのエラーも出ません。たとえば

package
{
	import flash.display.MovieClip;

	public class Main extends MovieClip
	{

		piyopiyo

		public function Main()
		{
			fugafuga

			var hoge:Number = 0..5;
			trace(hoge);
		}
	}
}

のように、普段なら明らかにエラーが出る余計な文字列が入っていても、エラーが出ません。。。
packageより上に何か書くと、エラー出ました。

前回の記事「Flash、JavaScriptから新規ウィンドウをポップアップする際のブラウザ毎の挙動のまとめ」の続き。

Safariで、Flashから新規ウィンドウを開こうとしてwindow.openを使うとブロックされる。
サイズ指定等なしの普通の新規ウィンドウならnavigateToURL(url,”_blank”)を使えばいいけど、サイズ指定したい場合はどうしたらいいのか、という件です。

ネット上の情報を調査

前回もさらっと探しても見つからなかったのですが、まずはあらためて検索してみます。
グーグルで「flash ポップアップ safari」で検索。

FLASH (AS3.0)からポップアップ
Safariでのポップアップウィンドウ対策
AS3でポップアップウィンドウ(2009/春)
あと@_sakotsuさんに教えてもらったPopups Blocked in AS3 with navigateToURL()
とかを読みましたが、解決策は出てきませんでしたので、自分で作りました。

使い方

使い方は

//importしてもらって
import jp.ikekou.net.urlopener.URLOpener;

//urlのみ指定
URLOpener.open('http://google.com');
//url、target指定
URLOpener.open('http://google.com', '_blank');
//url、target、option指定
URLOpener.open('http://google.com', '_blank', 'width=200, height=200');

みたいなかんじです。これでSafariでもサイズ指定のポップアップができます
optionはJavaScriptのwindow.openに渡すオプションと同じ形式の文字列です。

他にも、自分が使うからつけた機能で

//Safariか?
URLOpener.isSafari; //return Boolean
//JavaScriptでalert出す
URLOpener.alert("xxx");

とかあります。おまけです。

実際の動作は、目指したのはHTMLのリンククリックでwindow.openを呼んだ時の各ブラウザの挙動と同じになるようにしてます。それについては前回の記事参照。
またnavigateToURLでいい場合は極力navigateToURLを使うこと(レスポンスが早いので)にしてます。

中で何してるかというと、こんなかんじです。
ExternalInterfaceからwindow.open呼んだら開かない。これはきっとユーザーアクションじゃないみたいな感じなんだ(と推測)。なのでこうしました。

function()
{
	//ダミーの要素作成しイベントハンドラ設置、内部にwindow.openを記述
	var E = document.createElement("div");
	E.onclick = function()
	{
		window.open(url, target, options);
	};
	//クリックイベント送出
	var evt = document.createEvent("MouseEvents");
	evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
	E.dispatchEvent(evt);
}

これをExternalInterfaceで呼んでいます。
これで、SafariのポップアップブロックがONの場合でも、ポップアップします。

こんなやりかたどうなの?という気もしますが、自分の中ではひとつの問題が解決したので、公開します。JavaScriptの書き方こうしたほうがいいよ、という指摘はあったらお願いします。
イベント関係はIEとか違いそうですが、Safariの時のみ呼ぶ前提なのでこれで。

まだブラウザ毎の挙動の吸収とかテストが完全ではないかもしれませんが、今回のクラスのコアはSafariでサイズ指定のウインドウ開けたよーうれしいなー、ってとこなので。コメントもちゃんと入ってないです。
残りはぼちぼち完璧にしていきます。挙動おかしかったら教えてください。現状でも一応大丈夫だと思いますが。

実験用ページを作りました。 → 実験用ページはこちら。

Safariは「ポップアップウィンドウを開かない」にチェックを入れて試してもらわないと違いがわかりづらいかと思います。
このページを各ブラウザで開いてみて、試した結果がこちら(相変わらずIE7,8がないですがあとで足す予定です・・・)

IE6 FF Chrome Safari
URLNavigator.open(’http://google.com’); 新規ウィンドウ 新規タブ 新規タブ 新規ウィンドウ
URLNavigator.open(’http://google.com’, ”); 新規ウィンドウ 新規タブ 新規タブ 新規ウィンドウ
URLNavigator.open(’http://google.com’, null); 新規ウィンドウ 新規タブ 新規タブ 新規ウィンドウ
URLNavigator.open(’http://google.com’, ‘_self’); 自ウィンドウ 自タブ 自タブ 自ウィンドウ
URLNavigator.open(’http://google.com’, ‘_blank’); 新規ウィンドウ 新規タブ 新規タブ 新規ウィンドウ
URLNavigator.open(’http://google.com’, ‘_blank’, ”); 新規ウィンドウ 新規タブ 新規タブ 新規ウィンドウ
URLNavigator.open(’http://google.com’, ‘_blank’, null); 新規ウィンドウ(optionオフ) 新規ウィンドウ(optionオフ) 新規タブ 新規ウィンドウ
URLNavigator.open(’http://google.com’, ‘_blank’, ‘width=200, height=200′); 新規ウィンドウ(option指定) 新規ウィンドウ(option指定) 新規ウィンドウ(option指定) 新規ウィンドウ(option指定)

Safariでも指定サイズポップアップができてるはずです。

ダウンロード→URLOpener.zip

結論

Safariでも指定サイズのポップアップ、できたよ!

Flashなのに華やかな表現のネタじゃないし、基礎も基礎、実務で出会わないわけのない問題の解決ネタなので、激しく既出だったり、てんでおかしなことを言ってたら恥ずかしいものです。一応調査もしたし、そうでないことを願ってます!

2009.10.26追記

twitterで皆様に色々指摘頂いたので、追記・修正していこうと思います。
とりあえずWinでのOperaと、Macでのテスト一切できてなかったので、そこやらないと、と思ってます。
Mac持ってないけど・・・

2009.10.31追記

Operaのポップアップブロック、突破できない予感です。
でもまぁ、Operaの場合は「今ブロックしましたよ」という表示がちゃんと出るし(Safariは出ない)、
ユーザーに認知してもらうことができるという点でSafariほど問題はない、という落としどころにするしかないかな、と考えています。

2009.11.04追記

会社のMac版Safariでは開けました。

Flashから他のアドレスに飛んだり、新しい画面を開く方法は主に2つ。

  • navigateToURL(url, target);
  • ExternalInterface.call(”window.open”, url, target, options);

だと思います。
そしてオプションを指定することはnavigateToURLではできない、と思ってます。
いやもっとあるよ、オプション指定もできるよ、となると記事の前提が崩れますが、調べても出てこなかったので、たぶん主にそんなもんだと思いますし、そういう前提で話を進めます。

試してみると、そもそもHTMLをクリックしてJavaScriptのwindow.openを呼んだ場合も含めてブラウザによって挙動が違うので、表にしました。

JavaScript

まずはJavaScript。JavaScriptは本職じゃないので完全に非のない書き方かどうかわかりませんが、とりあえずこういう記述です。

xxx

このonclickに、以下のパターンを入れて試しました。

window.open('http://google.com');
window.open('http://google.com', '');
window.open('http://google.com', null);
window.open('http://google.com', '_self');
window.open('http://google.com', '_blank');
window.open('http://google.com', '_blank', '');
window.open('http://google.com', '_blank', null);
window.open('http://google.com', '_blank', 'width=200, height=200');

引数に空白文字列入れたり、null入れたり、無駄っぽい、重複っぽいこともしてますが、調査なので見逃してください。
実際に押せるリンクはこちら。

window.open(’http://google.com’);
window.open(’http://google.com’, ”);
window.open(’http://google.com’, null);
window.open(’http://google.com’, ‘_self’);
window.open(’http://google.com’, ‘_blank’);
window.open(’http://google.com’, ‘_blank’, ”);
window.open(’http://google.com’, ‘_blank’, null);
window.open(’http://google.com’, ‘_blank’, ‘width=200, height=200′);

いろんなブラウザで押してみました。

以下、表内の表記は

  • 新ウ=新規ウィンドウ
  • 新タ=新規タブ
  • option指定=width等が指定された状態
  • optionなし=ツールバーやステータスバーに関する指定にoffが渡されたのと同じような状態

とします。

この表にはIE7、IE8、Opera、がないじゃないかという声もあると思います。
今の環境に入っていないのですが、会社で試した感じではFFに近い挙動です。
また会社ででも実験して追記します。

JavaScriptの結果

window.open(下の文字列); IE6 FF Chrome Safari
‘http://google.com’ 新ウ 新タ 新タ 新ウ
‘http://google.com’, ” 新ウ 新タ 新タ 新ウ
‘http://google.com’, null 新ウ 新タ 新タ 新ウ
‘http://google.com’, ‘_self’ 自ウ 自タ 自タ 自ウ
‘http://google.com’, ‘_blank’ 新ウ 新タ 新タ 新ウ
‘http://google.com’, ‘_blank’, ” 新ウ 新タ 新タ 新ウ
‘http://google.com’, ‘_blank’, null 新ウ(optionオフ) 新ウ(optionオフ) 新タ 新ウ
‘http://google.com’, ‘_blank’, ‘width=200, height=200′ 新ウ(option指定) 新ウ(option指定) 新ウ(option指定) 新ウ(option指定)

結果は第3引数にnullを渡したときにブラウザによって違いが出るようです。
特にIEとFFが、第3引数を省略した時や空白文字列を渡した時とは異なる動作をしました。

optionオフというのは、実際に見てもらうのが早いかと思いますが、optionにmenubar=no等をすべてオフ指定した場合のようなものものが出ます。

ActionScript : navigateToURL

次にFlashのnavigateToURLを使用する場合です。
こちらはtarget=”_blank”等の指定はできるものの、widthやheight、menubar等の指定はできません。(と思い込んでいますが実はできる?)
よって項目が少し減ります。

記述はこんな感じです。

navigateToURL(new URLRequest('http://google.com'));
navigateToURL(new URLRequest('http://google.com'), '_self');
navigateToURL(new URLRequest('http://google.com'), '_blank');

ActionScript : navigateToURLの結果

navigateToURL(下の文字列); IE6 FF Chrome Safari
new URLRequest(’http://google.com’) 新ウ 新タ 新タ 新ウ
new URLRequest(’http://google.com’), ‘_self’ 自ウ 自タ 自タ 自ウ
new URLRequest(’http://google.com’), ‘_blank’ 新ウ 新タ 新タ 新ウ

まあまあ、そんな感じですね。特に挙動に違いはありません

ActionScript : ExternalInterface

次にExternalInterface経由でwindow.openを呼んだ場合。

記述はこんな感じです。

ExternalInterface.call('window.open', 'http://google.com');
ExternalInterface.call('window.open', 'http://google.com', '');
ExternalInterface.call('window.open', 'http://google.com', null);
ExternalInterface.call('window.open', 'http://google.com', '_self');
ExternalInterface.call('window.open', 'http://google.com', '_blank');
ExternalInterface.call('window.open', 'http://google.com', '_blank', '');
ExternalInterface.call('window.open', 'http://google.com', '_blank', null);
ExternalInterface.call('window.open', 'http://google.com', '_blank', 'width=200, height=200');

ActionScript : ExternalInterfaceの結果

ExternalInterface.call(’window.open’, 下の文字列); IE6 FF Chrome Safari
‘http://google.com’ 新ウ 新タ 新タ 無視
‘http://google.com’, ” 新ウ 新タ 新タ 無視
‘http://google.com’, null 新ウ 新タ 新タ 無視
‘http://google.com’, ‘_self’ 自ウ 自タ 自タ 自ウ
‘http://google.com’, ‘_blank’ 新ウ 新タ 新タ 無視
‘http://google.com’, ‘_blank’, ” 新ウ 新タ 新タ 無視
‘http://google.com’, ‘_blank’, null 新ウ(optionオフ) 新ウ(optionオフ) 新タ 無視
‘http://google.com’, ‘_blank’, ‘width=200, height=200′ 新ウ(option指定) 新ウ(option指定) 新ウ(option指定) 無視

ここらに来て結果が割れました。

まず、IE、FFは第三引数まわりで、最初に試した、HTMLからJavaScriptを呼んだ場合と同じ動作をします。
ExternalInterfaceでJavaScriptを呼んでるので当たり前と言えば当たり前です。

Safariの話

しかし、Safariが、新規ウインドウ系をのきなみブロックします。
これはHTMLからの呼び出しとは挙動が違います

もちろんこれはポップアップブロックONの場合です。
OFFにしてもらえばいいじゃん、と言ってしまうと話が終わってしまうのですが
初期ダウンロード時にONであるということと、
IE等のポップアップブロックと違って完全にサイレント、何も画面に変化がなくブロックされてしまうのでブロックされていることにユーザーが気づいてすらくれない、という可能性があるので、やはりON時でもなんとかすることに意味があると考えます。

(完全に推測ですが、SafariではFlashで言うユーザーアクションにひもづかない新規ウィンドウオープンはブロックする仕様で、
かつFlash内からの呼び出しがユーザーアクションにひもづいていると認識してもらえてないのではないでしょうか。)

そのほか

調べていて他に気になったことと言えば、

  • HTMLからの呼び出し、Flashからの呼び出しともに、window.openで_selfを指定すると、遷移後の戻るボタンで何故か2回分戻る?気がする。実際ネットにもそういう情報があった気がする。
  • その現象はnavigateToURLでは起きない
  • ExternalInterfaceのwindow.openは、レスポンスが極端に悪い。特にSafariは、_selfは機能するが一見機能してないのではと思う程に遅れる

という点です。

結論

これらを考えると、Flashからページ移動、または新規ウィンドウのオープンをする場合

  • navigateToURLとwindow.openで結果が同じものはnavigateToURLを使う。理由は以下。
    • window.openよりnavigateToURLのほうがレスポンスが良い
    • window.openでは先述の戻るボタン問題が起きるブラウザがあるが、navigateToURLではおきない
  • 新規ウィンドウに対してオプション指定したい場合はwindow.openを使う。(navigateToURLじゃできないし)
  • Safariで新規ウィンドウに対してオプション指定して開きたい場合、なんとかする

というのが、現状とるべき対応なのかと。

とはいえ、これはいちいち判断するのは面倒。それにSafariでなんとかするって、どうするのか
たとえば、開かれるほうのウィンドウに記述を入れるとできるらしいです。
でも、開く先が既存ページで、自分ではさわれなかったり、数が多かったりすると面倒だなと感じました。
なんとかできるに越したことはないと。

ネットの情報では基本的にできない、あきらめてnavigateToURLで_blankでやる、呼ぶ側ではなく開き先のほうで記述を入れる、等ありましたが、根本的にやる方法は出てきませんでした。

なので、なんとかならないか試してみたいと思います。

コードを貼ることもあるので、やはりきれいに見せたいものです。
みなさんのよく利用していて見かける、SyntaxHighlighterを導入してみました。

目的

今回のゴールは

  • 一般的に普及していてかつ機能的にも充実しているSyntaxHighlighterを導入したい。
  • WordPressのSyntaxHighlighterプラグインには入力方式が主に2つあり、
    • preタグを使う<pre calss=”language”>hogehuga</pre>
    • 大括弧を使う[language]hogehuga[/language]で、自動で変換してくれる

    と主に二つあるようだが、前者のpreタグ方式のものを導入したい。

  • ActionScriptもカラーリングしたい。

とします。

調査

とりあえずググって調べて見てみると、現状は以下のようなものでした。

  • プラグインは色々あるが、もととなるのはJSのライブラリである
    SyntaxHighlighterであり、各プラグインではJSでの処理に至るまでの過程が異なるのみである。実際にはヘッダでJSを読み込み、初期化関数を呼べばいいだけなので、最も単純な方法としては自分でheader.phpに書いても簡単にできる。
  • また、ネットでの情報ではSyntaxHighlighterはASに対応していないとある過去記事が多いが、実際にはバージョンアップしてAS他、対応言語が増えている。
  • しかし、そのバージョンアップにプラグインが対応できているかというと、必ずしもそうではない。古いバージョンを載せていてASに対応していないものもある。
  • 大元のサイトによると、最新バージョンではpreタグの他にも<script type=”syntaxhighlighter”>でも使えるよう。

以上の結果から、まずは最新バージョンを取り込んでくれていて、かつpreタグ方式のもの、を探します。

管理画面のプラグイン新規追加で「syntax highlighter」で検索をかけ、順に見ていくことにしました。
インストール前にまずはダウンロードしてみて中身を見て判断します。

Google Syntax Highlighter for WordPress
SyntaxHighlighterのバージョンが古く、AS未対応でした。また原因はよくわかりませんが、上手く動かない時があります。過去に動かなかったことがあり、今回も実際インストールしてみても動かなかったです。

Dojox Wordpress Syntax Highlighter
説明ページがリンク切れだったので、却下。

Syntax Highlighter and Code Colorizer for Wordpress
こちらもSyntaxHighlighterのバージョンが古かったので、却下。

Easy Google Syntax Highlighter
これはバージョン番号を見ると2.1.364とあり、最新のSyntaxHighlighterを取り込んでいます。入力方式もpreタグです。
そしてプラグインのphpが他に比べて異様に長いな、と思っていたら、管理画面に「どの言語ファイルを読み込むか」「どのテーマを選ぶか」等の設定をできる画面が出てきます。
これはいい。
本体JSがバージョンアップした時に自分でマージするとしたら少々面倒そうですが、機能的にはここまでで間違いなく一番良いです。
条件を満たすものが見つかったので調査終了。

インストール

条件を満たしているのでEasy Google Syntax Highlighterをインストール。プラグインを有効化。
確認した所、無事に表示されました。めでたしめでたし。

自分でできないか?

ここまでで目的は一応完了しました。
しかし、もとのライブラリのバージョンアップにプラグインのバージョンアップがついていかなかった場合、自分でしなくてはいけないですし、
ごく単純な形のプラグイン作成の勉強のためにも自分でできないか、試してみました。

実際にしなくてはならないことは少なく、元々のライブラリをダウンロードしたらついているサンプルのhtmlを見るとわかりますが必要なのはこれだけなはず。
(ちなみに何故か、実際にはshBrushAS.js等がフォルダに用意されているにも関わらずサンプルには読み込まれていないので、追加しました)

<script type="text/javascript" src="scripts/shCore.js"></script>
<script type="text/javascript" src="scripts/shBrushAS3.js"></script>
<script type="text/javascript" src="scripts/shBrushBash.js"></script>
<script type="text/javascript" src="scripts/shBrushColdFusion.js"></script>
<script type="text/javascript" src="scripts/shBrushCpp.js"></script>
<script type="text/javascript" src="scripts/shBrushCSharp.js"></script>
<script type="text/javascript" src="scripts/shBrushCss.js"></script>
<script type="text/javascript" src="scripts/shBrushDelphi.js"></script>
<script type="text/javascript" src="scripts/shBrushDiff.js"></script>
<script type="text/javascript" src="scripts/shBrushErlang.js"></script>
<script type="text/javascript" src="scripts/shBrushGroovy.js"></script>
<script type="text/javascript" src="scripts/shBrushJava.js"></script>
<script type="text/javascript" src="scripts/shBrushJavaFX.js"></script>
<script type="text/javascript" src="scripts/shBrushJScript.js"></script>
<script type="text/javascript" src="scripts/shBrushPerl.js"></script>
<script type="text/javascript" src="scripts/shBrushPhp.js"></script>
<script type="text/javascript" src="scripts/shBrushPlain.js"></script>
<script type="text/javascript" src="scripts/shBrushPowerShell.js"></script>
<script type="text/javascript" src="scripts/shBrushPython.js"></script>
<script type="text/javascript" src="scripts/shBrushRuby.js"></script>
<script type="text/javascript" src="scripts/shBrushScala.js"></script>
<script type="text/javascript" src="scripts/shBrushSql.js"></script>
<script type="text/javascript" src="scripts/shBrushVb.js"></script>
<script type="text/javascript" src="scripts/shBrushXml.js"></script>
<link type="text/css" rel="stylesheet" href="styles/shCore.css"/>
<link type="text/css" rel="stylesheet" href="styles/shThemeDefault.css"/>
<script type="text/javascript">
    SyntaxHighlighter.config.clipboardSwf = 'scripts/clipboard.swf';
    SyntaxHighlighter.all();
</script>

要はshCore.jsをまず読み込み、その後必要な分の言語ファイルを読み込み、swfファイルの位置を指定、all()で初期化する、それだけです。

元々のJSライブラリをダウンロードしてきても、それ自体はWPともMTとも無関係なので、WPのプラグイン用のPHPファイルはありません。
逆に言うと、それを入れてあげるだけで、プラグインとして機能するようになり、管理画面にも出ます。
サンプルのhtmlと、既存のプラグインのPHPを参考に出来上がったのはこちら。PHPファイルをzipにしてます。

my_syntax_highlighter.zip

<?php
/**
 * @package My_Syntax_Highlighter
 * @author ikekou
 * @version 1.0
 */
/*
Plugin Name: My Syntax Highlighter
Plugin URI: http://wordpress.org/#
Description: <a href="http://alexgorbatchev.com/wiki/SyntaxHighlighter">Syntax Highlighter</a>の2.1.364です。
Author: ikekou
Version: 1.0
Author URI: http://ikekou.jp
*/

function insert_header() {
    $current_path = get_option('siteurl') .'/wp-content/plugins/' . basename(dirname(__FILE__)) .'/';
    ?>
    <link href="<?php echo $current_path; ?>styles/shCore.css" type="text/css" rel="stylesheet" />
    <link href="<?php echo $current_path; ?>styles/shThemeDefault.css" type="text/css" rel="stylesheet" />
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shCore.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushAS3.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushBash.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushColdFusion.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushCpp.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushCSharp.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushCss.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushDelphi.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushDiff.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushErlang.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushGroovy.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushJava.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushJavaFX.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushJScript.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushPerl.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushPhp.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushPlain.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushPowerShell.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushPython.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushRuby.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushScala.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushSql.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushVb.js"></script>
    <script type="text/javascript" src="<?php echo $current_path; ?>scripts/shBrushXml.js"></script>
    <script type="text/javascript">
        SyntaxHighlighter.config.clipboardSwf = '<?php echo $current_path; ?>scripts/clipboard.swf';
        SyntaxHighlighter.all();
    </script>
    <?php
}
add_action('wp_head','insert_header');

?>

最初の部分に書いてあるコメントがどうやらプラグインとして必要なもののようで、Hello DollyプラグインのPHPファイルからコピーしました。
このPHPファイルのファイル名は何でも良く、管理画面に出てくる名前は
Plugin Name: My Syntax Highlighter
の値のようです。ほかの項目も今回は適当です。

全部の言語は使わないので、実際には使いそうなものだけ残しています。

これを使ってSyntaxHighlighterを使うための手順をあらためて説明すると、

  1. まず大本のJSを配布しているhttp://alexgorbatchev.com/wiki/SyntaxHighlighterからJSをダウンロード
  2. 解凍したらフォルダ名が他のプラグインと名前が重複しないように適当にリネーム
  3. 上記のPHPファイルをそのフォルダ内に作成。utf8、bomなしで。
    フォルダごと/wp-content/plugins/にアップ。
  4. 管理画面にMy Syntax Highlighterが追加されるので、[使用する]をクリック。
    <pre class=”brush: php;>hogehoge</php>みたいな感じで投稿。
  5. 確認

という流れです。

結論

自分でできたのは勉強になったのでよかったです。プラグインが機能する基本的な仕組みは理解できました。
管理画面とからんでくる部分はわかりませんが。

しかしとりあえず、困るまではEasy Google Syntax Highlighterを使ってみます
便利そうですしね!

気に入っていない道具は使用頻度も下るもの。
以前のblogは内容も見た目も更新の使い勝手も全体的にイマイチだったので、イチから自分で作ろうと一度closeしました。

ところが、やっぱりその間にも書きたいことなどありまして。
とりあえずSImpleなテーマ(その名も“Simpla”)で急遽復活。

おまけにいずれ入れようと思ってとってあるDBのダンプデータは、なんかちょっと上手く行かずまた今度。
どうせ大した内容じゃないから優先順位低い。

全く違うテーマは一緒にするとなんかダメと前回学んだので、別のブログでやろうかなと思い、逆ドメイン(ぽく)名前をつけてみたので、次はjp.ikekou.blog.Ramenか何かでしょう。と予想。

ということでまたよろしくお願いいたします。