新しいウィンドウ(またはタブ)で開いて前に出したい

# 新しいウィンドウ(またはタブ)で開いて前に出したい

～だけなんだけど、ブラウザによって色々違って苦労した記録。
ウィンドウ(またはタブ)の記述は面倒なので以降は必要な場合を除いて「ウィンドウ」で統一する。

## 新しいウィンドウで開くには

あるURLを新しいウィンドウで開きたい場合には何通りかの方法がある。

1. window.open関数の第2引数に名前を指定して実行
2. FORMタグのtarget属性に名前を指定してsubmit
3. Aタグのtarget属性に名前を指定してclick

以下に簡単な解説、わかりきっているなら飛ばしていい。

### window.open関数を利用

```js
var win = window.open('http://example.com', 'newtab');  //=> 新しいタブで開く
win = window.open('http://example.com', 'newwindow', 'width=400,height=300');  //=> 新しいウィンドウがポップアップ
console.log(win); //=> ウィンドウオブジェクト
win.close();  //openで作成したウィンドウオブジェクトはcloseが可能
```

第3引数にオプションを指定するとポップアップウィンドウで開くことが出来る。恐らくこれがポップアップウィンドウで開く唯一の方法。

また、windoe.openはウィンドウオブジェクトを作成して返す。

### FORMタグのsubmit

```html
<form target="newtab" action="/hoge" method="POST">
  <input type="submit" value="POST">
</form>
```

またはjsで

```js
var form = document.createElement('form'),
    form.setAttribute('target', 'newtab'),
    form.setAttribute('action', '/hoge'),
    form.setAttribute('method', 'POST');
document.body.appendChild(form).submit();
```

別ウィンドウにPOSTリクエストを送るには、このFORMを利用した方法が古典的で簡単か。FormData使った方がスマートかもしれないが今回は調査しなかった

### Aタグclick

```html
<a href="/moge" target="newtab">click me</a>
```

またはjsで

```js
var a = document.createElement('a'),
    a.setAttribute('target', 'newtab'),
    a.setAttribute('href', '/moge'),
    a.textContent = 'click me';
document.body.appendChild(a).click();
```

### 予約されたウィンドウ名(target)

* _blank : 必ず新しいウィンドウorタブで開く
* _self : 現在開いているウィンドウorタブ自身で開く
* _top : フレーム内からトップウィンドウで開く場合に使う
* _parent : フレーム内から親ウィンドウで開く場合に使う

## ブラウザによるの動作の違い

さて、ここでIE11(Win7), Firefox56(Win7), Chrome62(Win7), Safari10.1(OSX10.10) において

1. 別のウィンドウやタブが存在しない場合
2. 同名のタブが存在している場合
3. 同名のポップアップウィンドウが存在している場合

に新しくウィンドウを開くと、開いたウィンドウが前に出るか(フォーカスが移るか)を調査した。

### 調査結果

| 開く方法 | 同名ウィンドウ | IE | Chrome | Firefox | Safari |
|:-:|:-:|:-:|:-:|:-:|:-:|
|window.open |なし  | ○ |○|○|○|
|            |タブ有| :x: |○| :x:  |○|
|     |ポップアップ有|○|○| :exclamation: |○|
|〃(第3引数有)|なし  |○|○|○|○|
|            |タブ有| :x: |○| :x: |○|
|     |ポップアップ有|○|○| :exclamation: |○|
|FORMタグ    |なし  |○|○|○|○|
|            |タブ有| :x: | :exclamation: |○| :exclamation: |
|     |ポップアップ有|○| :exclamation: | :exclamation: | :exclamation: |
|Aタグ       |なし  |○|○|○|○|
|            |タブ有| :x: |○|○|○|
|     |ポップアップ有|○|○| :exclamation: |○|

:exclamation: 印はWindow#focus() をすればフォーカスが移ったという意味

まとめると

* 新しく開く場合は、当然フォーカスが移る
* 同名のタブやウィンドウが存在していると、フォーカスが移らない場合がある
* 作成したウィンドウオブジェクトに対して focus() を実行すればフォーカス移動することもある
* IEでは同名のタブが有ると、どの方法でもフォーカス移動出来ない(ハイライトはする)

## 前に出すにはどうするか

とにかく何が何でも前に出したい。そんな場合は……

### (1) target = "_blank" を使っちゃう

リンク先が前に出てくれないと困るけどブラウザでどうとかめんどい…… → そうだ毎回新しいウィンドウを開こう！

ある意味正解かもしれない。Amazonだってそうしてる。

### (2) IEはあきらめちゃう

IEの場合だけフォーカス移せないケースがあるわけだから、諦められれば話は簡単

### (3) window.openを再実装しちゃう

```js
var _open = function(url, name, opt) {
	var form = document.body.appendChild(document.createElement('form')),
		ua = navigator.userAgent.toLowerCase(),
		isIE = ua.indexOf('msie')>=0 || ua.indexOf('trident')>=0,
		isPopup, reopen, w, act, tid, c;
	isPopup = function(win) {
		return window.screenTop !== win.screenTop && window.screenLeft !== win.screenLeft;
	};
	reopen = function() {return window.open(url||"", name, opt);};
	act = function() {
		form.setAttribute('method', 'GET');
		form.setAttribute('action', url);
		form.setAttribute('target', name);
		form.submit();
		form.parentNode.removeChild(form);
	};
	w = reopen(url);
	if (!isIE || isPopup(w)) {act();w.focus();return w;}
	w.close();
	if (w.closed) {w=reopen();act();return w;}
	c = 0;
	tid = setInterval(function() {
		if (w && w.closed || c++>100) {clearInterval(tid);reopen();act();}
	}, 10);
	return null;
};

//使い方はwindow.openと同じ
_open("http://example.com/", "name"); //=> [Window Object] ただしIEの場合は null が返る
```

UserAgentを見てIEだったら一度 Window#close() して再び open。close 直後は同じ名前のウィンドウを作れない(?)ようなので数瞬後に実行している。
また、Firefoxの場合に window.open ではタブが切り替わらないことがあるので FORM タグの submit を利用している。

IE以外でもclose→openしちゃえば簡単なのだが、ポップアップブロックされる事があるのでこうしている。


## まとめ

元も子もないようだが、現状「新しいウィンドウを開く」(さらにフォーカスする)というのはあまりいい手ではない。シングルページで何とかするのがいいのではないか。
しかし、それでも何とかしたいのであれば上記の対応策(1)～(3)を試してみてほしい。