import each from 'lodash-es/each.js'
import map from 'lodash-es/map.js'
import join from 'lodash-es/join.js'
import isarr from './isarr.mjs'
import isstr from './isstr.mjs'
import isfun from './isfun.mjs'
import genPm from './genPm.mjs'
import pmSeries from './pmSeries.mjs'
import waitFun from './waitFun.mjs'
import strright from './strright.mjs'
import delay from './delay.mjs'
let _paths = {}
let _pathItems = {}
function importResource({ tagName, path, attributes = {}, func }) {
//pm
let pm = genPm()
//check
if (_paths[path] === 'done') {
pm.resolve('loaded')
return pm
}
else if (_paths[path]) {
//載入中, 共用首位之載入結果(與其同成敗), 不輪詢
_paths[path]
.then(() => {
pm.resolve('loaded')
}, (err) => {
pm.reject(err)
})
return pm
}
//loading, 記錄載入中之Promise供後到者共用
_paths[path] = pm
//fail, 失敗時清除標記使之後之呼叫可重新載入, 並reject(載入中之後到者一併reject)
let fail = (err) => {
delete _paths[path]
pm.reject(err)
}
//ele attrs
let ele = document.createElement(tagName)
each(attributes, (v, k) => {
ele.setAttribute(k, v)
})
//ele type
if (attributes.rel && attributes.rel === 'stylesheet') {
ele.setAttribute('href', path)
setTimeout(() => {
//resolve
pm.resolve(ele)
//done
_paths[path] = 'done'
}, 1)
}
else {
ele.src = path
ele.addEventListener('load', () => {
let wf
if (isfun(func)) {
wf = waitFun(func)
}
else {
wf = genPm()
wf.resolve()
}
wf
.then(() => {
return delay(500) //無縫載入頁面會無法更新, 強制delay 0.5s使頁面可更新
})
.then(function() {
//resolve
pm.resolve(ele)
//done
_paths[path] = 'done'
})
.catch((err) => {
//func拋錯、其Promise被reject或超過嘗試次數: 視為載入失敗, 不得懸置
fail(err)
})
})
ele.addEventListener('error', () => {
//reject, 清除標記使之後可重新載入
fail(ele)
})
}
//appendChild
let head = document.getElementsByTagName('head')[0]
head.appendChild(ele)
return pm
}
/**
* 前端動態引入資源如Javascript或CSS檔案
*
* 同一路徑或同一組路徑載入中之呼叫,與首位共用其載入結果(同成敗);載入失敗(script之error事件、pathItem.func拋錯或其Promise被reject、等待func成立逾次)時reject並清除標記,之後之呼叫重新載入;CSS(link)不等待其載入事件,加入後即視為完成
*
* Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/importResources.test.mjs Github}
* @memberOf wsemi
* @param {String|Object|Array} pathItems 輸入資源字串、字串陣列、物件、物件陣列
* @returns {Promise} 回傳Promise,resolve回傳注入成功的HtmlElement陣列(已載入完成或與載入中者共用時為'loaded'),reject回傳載入失敗之script元素(error事件)、func之錯誤或錯誤訊息
* @example
* //need test in browser
*
* let pathItems
*
* pathItems = [
* 'https://cdn.jsdelivr.net/npm/ag-grid-community@22.0.0/dist/ag-grid-community.noStyle.js',
* 'https://cdn.jsdelivr.net/npm/ag-grid-vue@22.0.0/dist/ag-grid-vue.umd.js',
* 'https://cdn.jsdelivr.net/npm/w-aggrid-vue@1.0.28/dist/w-aggrid-vue.umd.js',
* ]
* importResources(pathItems)
* .then((res)=>{
* console.log(res)
* })
*
* pathItems = [
* {
* path: 'https://cdn.jsdelivr.net/npm/ag-grid-community@22.0.0/dist/ag-grid-community.noStyle.js',
* type: 'js',
* },
* {
* path: 'https://cdn.jsdelivr.net/npm/ag-grid-vue@22.0.0/dist/ag-grid-vue.umd.js',
* type: 'js',
* },
* {
* path: 'https://cdn.jsdelivr.net/npm/w-aggrid-vue@1.0.28/dist/w-aggrid-vue.umd.js',
* type: 'js',
* },
* ]
* importResources(pathItems)
* .then((res)=>{
* console.log(res)
* })
*
*/
function importResources(pathItems) {
//pm
let pm = genPm()
//check
if (!isarr(pathItems)) {
pathItems = [pathItems]
}
//check string
each(pathItems, (v, k) => {
if (isstr(v)) {
pathItems[k] = {
path: v
}
}
})
//key
let key = join(map(pathItems, 'path'), '|')
//check
if (_pathItems[key] === 'done') {
pm.resolve('loaded')
return pm
}
else if (_pathItems[key]) {
//載入中, 共用首位之載入結果(與其同成敗), 不輪詢
_pathItems[key]
.then(() => {
pm.resolve('loaded')
}, (err) => {
pm.reject(err)
})
return pm
}
//judge type
each(pathItems, (v, k) => {
if (!v.type) {
if (strright(v.path, 3) === '.js') {
v.type = 'js'
}
else if (strright(v.path, 4) === '.css') {
v.type = 'css'
}
else {
v.type = 'unknow'
}
}
})
//pmSeries
let pmAll = pmSeries(pathItems, (pathItem) => {
let pm = genPm()
if (pathItem.type === 'js') {
pm = importResource({
tagName: 'script',
path: pathItem.path,
attributes: { type: 'text/javascript' },
func: pathItem.func,
})
}
else if (pathItem.type === 'css') {
pm = importResource({
tagName: 'link',
path: pathItem.path,
attributes: { rel: 'stylesheet' },
})
}
else {
let err = 'invalid pathItem.type: ' + pathItem.type
console.log(err)
pm.reject(err)
}
return pm
})
.then((r) => {
//done
_pathItems[key] = 'done'
return r
}, (err) => {
//失敗時清除標記, 使之後之呼叫可重新載入
delete _pathItems[key]
return Promise.reject(err)
})
//loading, 記錄載入中之Promise供後到者共用
_pathItems[key] = pmAll
return pmAll
}
export default importResources