Skip to content

Instantly share code, notes, and snippets.

@iocat
Last active September 1, 2017 06:53
Show Gist options
  • Save iocat/35eb32eb8077ed41c6fd20628158759d to your computer and use it in GitHub Desktop.
Save iocat/35eb32eb8077ed41c6fd20628158759d to your computer and use it in GitHub Desktop.
A simple Gopherjs resource loader for dynamic resources added to the head of the document
// Package resloader loads resources and invokes a single callback for all packages
// useful for checking availability of loaded resources on the fly or all at once
package resloader
import (
"errors"
"path/filepath"
"time"
"github.com/gopherjs/gopherjs/js"
)
/*
Usage
loader := resloader.New()
loader.Add("https://code.jquery.com/jquery-3.2.1.min.js", nil)
loader.Add("https://cdn.jsdelivr.net/semantic-ui/2.2.9/semantic.min.css")
loader.SetOnAllLoaded(func(){
println("loaded all")
})
loader.LoadAll()
*/
type Type int
const (
JS Type = iota
CSS
)
const (
defaultCheckFreqForAll = 1 * time.Millisecond
)
type ResData struct {
Type Type
// The source of this resource
URL string
// Callback when this particular resource is loaded
Callback func()
}
type Loader struct {
resources []ResData
checkAllFreq time.Duration
onAllLoadedFn func()
count int
invokeCount int
}
func New() *Loader {
return &Loader{
resources: make([]ResData, 0),
checkAllFreq: defaultCheckFreqForAll,
onAllLoadedFn: nil,
count: 0,
invokeCount: 0,
}
}
func getType(path string) (Type, error) {
switch filepath.Ext(path) {
case ".js":
return JS, nil
case ".css":
return CSS, nil
default:
return -1, errors.New("Can't determine the type of file based on the path")
}
}
// AddWithType adds a resource where type is not a part of the url, callback
// can be nil
func (l *Loader) AddWithType(t Type, url string, callback func()) {
resData := ResData{
Type: t,
URL: url,
Callback: nil,
}
resData.Callback = func() {
l.count++ // safe because all executions are single threaded
if callback != nil {
callback()
}
}
l.resources = append(l.resources, resData)
}
// Add adds a resource where type can be inferred from the URL, callback can be nil
func (l *Loader) Add(url string, callback func()) {
t, err := getType(url)
if err != nil {
panic("can't determine type from the provided URL")
}
l.AddWithType(t, url, callback)
}
// ErrCallLoadTwice occurs when the loader loads everything twice which
// is not a purpose of this package
var ErrCallLoadTwice = errors.New("WARNING: Can't call LoadAll more than one")
// LoadAll starts loading all resources
func (l *Loader) LoadAll() {
l.invokeCount++
if l.invokeCount > 1 {
println(ErrCallLoadTwice)
panic(ErrCallLoadTwice)
}
if l.onAllLoadedFn != nil {
var repeatIfNotAll func()
repeatIfNotAll = func() {
if l.count != len(l.resources) {
time.AfterFunc(l.checkAllFreq, repeatIfNotAll)
} else {
l.onAllLoadedFn()
}
}
time.AfterFunc(l.checkAllFreq, repeatIfNotAll)
}
for _, res := range l.resources {
switch res.Type {
case JS:
AddScript(res.URL, res.Callback)
case CSS:
AddStyleSheet(res.URL, res.Callback)
default:
continue
}
}
}
// SetOnAllLoaded sets the callback when all resources are
// available
func (l *Loader) SetOnAllLoaded(callback func()) {
l.onAllLoadedFn = callback
}
// SetOnAllLoadedCheckFreq sets the frequency of which we check
// the availability of all loaded resources
func (l *Loader) SetOnAllLoadedCheckFreq(checkFreq time.Duration) {
l.checkAllFreq = checkFreq
}
func AddStyleSheet(url string, callback func()) {
link := js.Global.Get("document").Call("createElement", "link")
link.Set("href", url)
link.Set("type", "text/css")
link.Set("rel", "stylesheet")
link.Set("onload", callback)
js.Global.Get("document").Get("head").Call("appendChild", link)
}
func AddScript(url string, callback func()) {
script := js.Global.Get("document").Call("createElement", "script")
script.Set("src", url)
script.Set("type", "text/javascript")
script.Set("onload", callback)
js.Global.Get("document").Get("head").Call("appendChild", script)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment