◆ remove/CustomEvent/template を IE で扱いやすくしてみた

IE でも動くようにしておきたかったものがあったので IE も対応させました
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)
}
}

↑を 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}))
}

組み込みの 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
}()
}

使うときは
var eve = new CustomEvent("click", {bubbles: true})
window.dispatchEvent(eve)
Chrome などと一緒です

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.")
            }
        }
    })
}
template{
    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>

上側の 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)


IE のために template あるのに使えないのは気持ち悪いですが アロー関数など ES2015+α も使えないわけですしこの程度の妥協は必要なのかも