やっぱり IE 対応がつらい
- カテゴリ:
- JavaScript
- IE
- コメント数:
- Comments: 0
◆ remove/CustomEvent/template を IE で扱いやすくしてみた
IE でも動くようにしておきたかったものがあったので IE も対応させました
IE11 なら アロー関数とか新しすぎなければ 対応してる機能も多いと思ったのですが思った以上に不便でした
よく使う機能では困ったもの 3 つを対策つきで紹介します
「if(document.documentMode)」 は IE の場合という意味になります
単純ですが IE のためにこうしないといけないのはかなりめんどうです
さすがに全部をこう書きたくないので remove メソッドを用意します
↑を IE 対応のページごとに用意するのもそれなりの手間です
とりあえずスニペット登録したい機能です
オブジェクト自体は存在するのですがコンストラクタが使えません
createEvent して initEvent する手間が入ります
組み込みの CutomEvent 関数自体いらないので prototype だけ付け替えて CustomEvent を自作します
使うときは
非対応でも HTMLUnknownElement になるだけなので content で DocumentFragment が取れるようにします
getter を定義して実体 __content__ にすでにあるならそれを返して __content__ がまだないなら innerHTML から DocumentFragment を作るようにします
多くの場合に問題ないのですが template に対応していないため普通の要素と同じように扱われて tr や td みたいな書ける場所が決まった要素が関わってくると位置が自動で書き換えられたりタグが消されたりしてしまいます
template 自体の位置が変わるのは id で取得するようにして template の位置がどこにあろうと問題ないようにしておけば対処可能です
しかし template の中のタグが消えるというのは対処できません
template の中に script タグで文字列で template を記述することはできますが そうすると IE のために Chrome などが見づらい書き方を強いられることになります
Chrome はそのままで IE を合わせるという考え方だと template の場合は限界があります
IE に合わせることで Chrome などで余計な処理を加えるのは気が進みませんが 仕方なく合わせてる IE は動けば遅くてもいいので IE の場合は自分のページ自身を XHR で取得してブラウザが改変する前のソースから正規表現で innerHTML を取り出し DocumentFragment を作るというのを考えました
ただ あんまりオススメ出来る方法でもないのでここでは省略します
一般的には data block を template にする方法になるかと思います
上側の script タグのように template を作ります
script タグではタダのテキストなので自動で中身は変わりません
また script タグ自身も移動されません
template タグのように content プロパティに DocumentFragment をいれるだけで完成です
DocumentFragment にいれるときに DocumentFragment には innerHTML がないので仮に親とするタグを指定する必要があります
デフォルトは div にしていますが div だと template のルートが td だったときに DocumentFragment を作る過程で自動で書き換えられる問題が起きます
なのでそのテンプレートを使うときに想定する親タグを指定します
つかうときはこんな感じ
IE のために template あるのに使えないのは気持ち悪いですが アロー関数など ES2015+α も使えないわけですしこの程度の妥協は必要なのかも
IE11 なら アロー関数とか新しすぎなければ 対応してる機能も多いと思ったのですが思った以上に不便でした
よく使う機能では困ったもの 3 つを対策つきで紹介します
「if(document.documentMode)」 は IE の場合という意味になります
remove
elem.remove()
をelem.parentElement.removeChild(elem)
こうしないといけません単純ですが IE のためにこうしないといけないのはかなりめんどうです
さすがに全部をこう書きたくないので remove メソッドを用意します
if(document.documentMode){
Element.prototype.remove = function(){
this.parentElement.removeChild(this)
}
}
Element.prototype.remove = function(){
this.parentElement.removeChild(this)
}
}
↑を IE 対応のページごとに用意するのもそれなりの手間です
とりあえずスニペット登録したい機能です
CustomEvent
new Event も new CustomEvent もできませんオブジェクト自体は存在するのですがコンストラクタが使えません
createEvent して initEvent する手間が入ります
if(document.documentMode){
var eve = document.createEvent("Event")
eve.initEvent("click", true, false)
elem.dispatchEvent(cheve)
}else{
elem.dispatchEvent(new CustomEvent("click", {bubbles: true}))
}
var eve = document.createEvent("Event")
eve.initEvent("click", true, false)
elem.dispatchEvent(cheve)
}else{
elem.dispatchEvent(new CustomEvent("click", {bubbles: true}))
}
組み込みの CutomEvent 関数自体いらないので prototype だけ付け替えて CustomEvent を自作します
if(document.documentMode){
!function(){
var prototype = CustomEvent.prototype
function CustomEvent(type, option){
var eve = document.createEvent("Event")
option = option || {}
eve.initEvent(type, !!option.bubbles, !!option.cancelable)
return eve
}
CustomEvent.prototype = prototype
window.CustomEvent = CustomEvent
}()
}
!function(){
var prototype = CustomEvent.prototype
function CustomEvent(type, option){
var eve = document.createEvent("Event")
option = option || {}
eve.initEvent(type, !!option.bubbles, !!option.cancelable)
return eve
}
CustomEvent.prototype = prototype
window.CustomEvent = CustomEvent
}()
}
使うときは
var eve = new CustomEvent("click", {bubbles: true})
window.dispatchEvent(eve)
Chrome などと一緒ですwindow.dispatchEvent(eve)
template
動的に増えたり減ったりする要素があると必須とも言えるタグなのに使えません非対応でも HTMLUnknownElement になるだけなので content で DocumentFragment が取れるようにします
if(document.documentMode){
Object.defineProperty(HTMLUnknownElement.prototype, "content", {
get: function (){
if(this.tagName === "TEMPLATE"){
if(!this.__content__){
var fragment = document.createDocumentFragment()
while(this.firstChild) fragment.appendChild(this.firstChild)
this.__content__ = fragment
}
return this.__content__
}else{
throw new Error("Not supported.")
}
}
})
}
Object.defineProperty(HTMLUnknownElement.prototype, "content", {
get: function (){
if(this.tagName === "TEMPLATE"){
if(!this.__content__){
var fragment = document.createDocumentFragment()
while(this.firstChild) fragment.appendChild(this.firstChild)
this.__content__ = fragment
}
return this.__content__
}else{
throw new Error("Not supported.")
}
}
})
}
template{
display: none !important;
}
display: none !important;
}
getter を定義して実体 __content__ にすでにあるならそれを返して __content__ がまだないなら innerHTML から DocumentFragment を作るようにします
多くの場合に問題ないのですが template に対応していないため普通の要素と同じように扱われて tr や td みたいな書ける場所が決まった要素が関わってくると位置が自動で書き換えられたりタグが消されたりしてしまいます
template 自体の位置が変わるのは id で取得するようにして template の位置がどこにあろうと問題ないようにしておけば対処可能です
しかし template の中のタグが消えるというのは対処できません
template の中に script タグで文字列で template を記述することはできますが そうすると IE のために Chrome などが見づらい書き方を強いられることになります
Chrome はそのままで IE を合わせるという考え方だと template の場合は限界があります
IE に合わせることで Chrome などで余計な処理を加えるのは気が進みませんが 仕方なく合わせてる IE は動けば遅くてもいいので IE の場合は自分のページ自身を XHR で取得してブラウザが改変する前のソースから正規表現で innerHTML を取り出し DocumentFragment を作るというのを考えました
ただ あんまりオススメ出来る方法でもないのでここでは省略します
一般的には data block を template にする方法になるかと思います
<script id="sample-template" type="text/x-template" data-parent-tag="tbody">
<tr><td>text</td></tr>
</script>
<script>
window.addEventListener("load", function(eve){
for(var i=0;i<document.scripts.length;i++){
var script = document.scripts[i]
var fragment = document.createDocumentFragment()
var parent = document.createElement(script.dataset.parentTag || "div")
parent.innerHTML = script.innerHTML
while(parent.firstChild) fragment.appendChild(parent.firstChild)
script.content = fragment
}
})
</script>
<tr><td>text</td></tr>
</script>
<script>
window.addEventListener("load", function(eve){
for(var i=0;i<document.scripts.length;i++){
var script = document.scripts[i]
var fragment = document.createDocumentFragment()
var parent = document.createElement(script.dataset.parentTag || "div")
parent.innerHTML = script.innerHTML
while(parent.firstChild) fragment.appendChild(parent.firstChild)
script.content = fragment
}
})
</script>
上側の script タグのように template を作ります
script タグではタダのテキストなので自動で中身は変わりません
また script タグ自身も移動されません
template タグのように content プロパティに DocumentFragment をいれるだけで完成です
DocumentFragment にいれるときに DocumentFragment には innerHTML がないので仮に親とするタグを指定する必要があります
デフォルトは div にしていますが div だと template のルートが td だったときに DocumentFragment を作る過程で自動で書き換えられる問題が起きます
なのでそのテンプレートを使うときに想定する親タグを指定します
つかうときはこんな感じ
var clone = document.querySelector("#sample-template").content.cloneNode(true)
window.body.appendChild(clone)
window.body.appendChild(clone)
IE のために template あるのに使えないのは気持ち悪いですが アロー関数など ES2015+α も使えないわけですしこの程度の妥協は必要なのかも