Merge pull request #698 from trheyi/main
add event binding logic in SUI core
This commit is contained in:
commit
036553b43a
6 changed files with 208 additions and 47 deletions
|
|
@ -54,6 +54,9 @@ func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Docume
|
||||||
}
|
}
|
||||||
doc.Find("body").SetAttr("s:ns", namespace)
|
doc.Find("body").SetAttr("s:ns", namespace)
|
||||||
|
|
||||||
|
// Bind the Page events
|
||||||
|
page.BindEvent(ctx, doc.Selection)
|
||||||
|
|
||||||
warnings, err := page.buildComponents(doc, ctx, option)
|
warnings, err := page.buildComponents(doc, ctx, option)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ctx.warnings, err
|
return nil, ctx.warnings, err
|
||||||
|
|
@ -105,6 +108,7 @@ func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Docume
|
||||||
// Append the scripts and styles
|
// Append the scripts and styles
|
||||||
ctx.scripts = append(ctx.scripts, scripts...)
|
ctx.scripts = append(ctx.scripts, scripts...)
|
||||||
ctx.styles = append(ctx.styles, styles...)
|
ctx.styles = append(ctx.styles, styles...)
|
||||||
|
|
||||||
return doc, ctx.warnings, err
|
return doc, ctx.warnings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,6 +282,12 @@ func (page *Page) parseProps(from *goquery.Selection, to *goquery.Selection, ext
|
||||||
|
|
||||||
for _, attr := range attrs {
|
for _, attr := range attrs {
|
||||||
|
|
||||||
|
// Copy Event
|
||||||
|
if strings.HasPrefix(attr.Key, "s:event") || strings.HasPrefix(attr.Key, "data:") || strings.HasPrefix(attr.Key, "json:") {
|
||||||
|
to.SetAttr(attr.Key, attr.Val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(attr.Key, "s:") || attr.Key == "is" || attr.Key == "parsed" {
|
if strings.HasPrefix(attr.Key, "s:") || attr.Key == "is" || attr.Key == "parsed" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
85
sui/core/event.go
Normal file
85
sui/core/event.go
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"golang.org/x/net/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BindEvent is a method that binds events to the page.
|
||||||
|
func (page *Page) BindEvent(ctx *BuildContext, sel *goquery.Selection) {
|
||||||
|
matcher := NewAttrPrefixMatcher(`s:on-`)
|
||||||
|
sel.FindMatcher(matcher).Each(func(i int, s *goquery.Selection) {
|
||||||
|
page.appendEventScript(ctx, s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (page *Page) appendEventScript(ctx *BuildContext, sel *goquery.Selection) {
|
||||||
|
|
||||||
|
if len(sel.Nodes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page events
|
||||||
|
events := map[string]string{}
|
||||||
|
dataUnique := map[string]string{}
|
||||||
|
jsonUnique := map[string]string{}
|
||||||
|
id := fmt.Sprintf("event-%d", ctx.sequence)
|
||||||
|
ctx.sequence++
|
||||||
|
|
||||||
|
for _, attr := range sel.Nodes[0].Attr {
|
||||||
|
|
||||||
|
if strings.HasPrefix(attr.Key, "s:on-") {
|
||||||
|
name := strings.TrimPrefix(attr.Key, "s:on-")
|
||||||
|
handler := attr.Val
|
||||||
|
events[name] = handler
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(attr.Key, "s:data-") {
|
||||||
|
name := strings.TrimPrefix(attr.Key, "s:data-")
|
||||||
|
dataUnique[name] = attr.Val
|
||||||
|
sel.SetAttr(fmt.Sprintf("data:%s", name), attr.Val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(attr.Key, "s:json-") {
|
||||||
|
name := strings.TrimPrefix(attr.Key, "s:json-")
|
||||||
|
jsonUnique[name] = attr.Val
|
||||||
|
sel.SetAttr(fmt.Sprintf("json:%s", name), attr.Val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []string{}
|
||||||
|
for name := range dataUnique {
|
||||||
|
data = append(data, name)
|
||||||
|
sel.RemoveAttr(fmt.Sprintf("s:data-%s", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
json := []string{}
|
||||||
|
for name := range jsonUnique {
|
||||||
|
json = append(json, name)
|
||||||
|
sel.RemoveAttr(fmt.Sprintf("s:json-%s", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
dataRaw, _ := jsoniter.MarshalToString(data)
|
||||||
|
jsonRaw, _ := jsoniter.MarshalToString(json)
|
||||||
|
|
||||||
|
source := ""
|
||||||
|
for name, handler := range events {
|
||||||
|
source += pageEventInjectScript(id, name, dataRaw, jsonRaw, handler) + "\n"
|
||||||
|
sel.RemoveAttr(fmt.Sprintf("s:on-%s", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.scripts = append(ctx.scripts, ScriptNode{
|
||||||
|
Source: source,
|
||||||
|
Namespace: page.namespace,
|
||||||
|
Attrs: []html.Attribute{{Key: "event", Val: id}},
|
||||||
|
})
|
||||||
|
|
||||||
|
sel.SetAttr("s:event", id)
|
||||||
|
}
|
||||||
|
|
@ -7,15 +7,28 @@ const initScriptTmpl = `
|
||||||
var __sui_data = %s;
|
var __sui_data = %s;
|
||||||
} catch (e) { console.log('init data error:', e); }
|
} catch (e) { console.log('init data error:', e); }
|
||||||
|
|
||||||
function __sui_findParentWithAttribute(element, attributeName) {
|
|
||||||
while (element && element !== document) {
|
function __sui_event_handler(event, dataKeys, jsonKeys, elm, handler) {
|
||||||
if (element.hasAttribute(attributeName)) {
|
const data = {};
|
||||||
return element.getAttribute(attributeName);
|
dataKeys.forEach(function (key) {
|
||||||
|
const value = elm.getAttribute("data:" + key);
|
||||||
|
data[key] = value;
|
||||||
|
})
|
||||||
|
jsonKeys.forEach(function (key) {
|
||||||
|
const value = elm.getAttribute("json:" + key);
|
||||||
|
data[key] = null;
|
||||||
|
if (value && value != "") {
|
||||||
|
try {
|
||||||
|
data[key] = JSON.parse(value);
|
||||||
|
} catch (e) {
|
||||||
|
const message = e.message || e || "An error occurred";
|
||||||
|
console.error(` + "`[SUI] Event Handler Error: ${message}`" + `, elm);
|
||||||
}
|
}
|
||||||
element = element.parentElement;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
handler && handler(event, data, elm);
|
||||||
|
};
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
try {
|
try {
|
||||||
|
|
@ -31,39 +44,6 @@ const initScriptTmpl = `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll("[s\\:click]").forEach(function (element) {
|
|
||||||
const method = element.getAttribute("s:click");
|
|
||||||
const cn = __sui_findParentWithAttribute(element, "s:cn");
|
|
||||||
if (method && cn && typeof window[cn] === "function") {
|
|
||||||
const obj = new window[cn]();
|
|
||||||
if (typeof obj[method] === "function") {
|
|
||||||
element.addEventListener("click", function (event) {
|
|
||||||
try {
|
|
||||||
obj[method](element, event);
|
|
||||||
} catch (e) {
|
|
||||||
const message = e.message || e || "An error occurred";
|
|
||||||
console.error(` + "`[SUI] ${cn}.${method} Error: ${message}`" + `);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.error(` + "`[SUI] ${cn}.${method} Error: Method not found`" + `);
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (method && typeof window[method] === "function") {
|
|
||||||
element.addEventListener("click", function (event) {
|
|
||||||
try {
|
|
||||||
window[method](element, event);
|
|
||||||
} catch (e) {
|
|
||||||
const message = e.message || e || "An error occurred";
|
|
||||||
console.error(` + "`[SUI] ${method} Error: ${message}`" + `);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
});
|
});
|
||||||
%s
|
%s
|
||||||
|
|
@ -83,6 +63,14 @@ const i118nScriptTmpl = `
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const pageEventScriptTmpl = `
|
||||||
|
document.querySelector("[s\\:event=%s]").addEventListener("%s", function (event) {
|
||||||
|
const dataKeys = %s;
|
||||||
|
const jsonKeys = %s;
|
||||||
|
__sui_event_handler(event, dataKeys, jsonKeys, this, %s);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
|
||||||
func bodyInjectionScript(jsonRaw string, debug bool) string {
|
func bodyInjectionScript(jsonRaw string, debug bool) string {
|
||||||
jsPrintData := ""
|
jsPrintData := ""
|
||||||
if debug {
|
if debug {
|
||||||
|
|
@ -94,3 +82,7 @@ func bodyInjectionScript(jsonRaw string, debug bool) string {
|
||||||
func headInjectionScript(jsonRaw string) string {
|
func headInjectionScript(jsonRaw string) string {
|
||||||
return fmt.Sprintf(`<script type="text/javascript">`+i118nScriptTmpl+`</script>`, jsonRaw)
|
return fmt.Sprintf(`<script type="text/javascript">`+i118nScriptTmpl+`</script>`, jsonRaw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pageEventInjectScript(eventID, eventName, dataKeys, jsonKeys, handler string) string {
|
||||||
|
return fmt.Sprintf(pageEventScriptTmpl, eventID, eventName, dataKeys, jsonKeys, handler)
|
||||||
|
}
|
||||||
|
|
|
||||||
74
sui/core/matcher.go
Normal file
74
sui/core/matcher.go
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AttrMatcher is a matcher that matches attribute keys
|
||||||
|
type AttrMatcher struct {
|
||||||
|
prefix string
|
||||||
|
re *regexp.Regexp
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAttrPrefixMatcher creates a new attribute matcher that matches attribute keys with the given prefix
|
||||||
|
func NewAttrPrefixMatcher(prefix string) *AttrMatcher {
|
||||||
|
return &AttrMatcher{prefix: prefix}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAttrRegexpMatcher creates a new attribute matcher that matches attribute keys with the given regexp
|
||||||
|
func NewAttrRegexpMatcher(re *regexp.Regexp) *AttrMatcher {
|
||||||
|
return &AttrMatcher{re: re}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match returns true if the node has an attribute key that matches the matcher
|
||||||
|
func (m *AttrMatcher) Match(n *html.Node) bool {
|
||||||
|
if m.re == nil {
|
||||||
|
return m.prefixMatch(n)
|
||||||
|
}
|
||||||
|
return m.regexpMatch(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *AttrMatcher) regexpMatch(n *html.Node) bool {
|
||||||
|
for _, attr := range n.Attr {
|
||||||
|
if m.re.MatchString(attr.Key) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *AttrMatcher) prefixMatch(n *html.Node) bool {
|
||||||
|
for _, attr := range n.Attr {
|
||||||
|
if strings.HasPrefix(attr.Key, m.prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchAll returns all the nodes that have an attribute key that matches the matcher
|
||||||
|
func (m *AttrMatcher) MatchAll(n *html.Node) []*html.Node {
|
||||||
|
var nodes []*html.Node
|
||||||
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||||
|
if m.Match(c) {
|
||||||
|
nodes = append(nodes, c)
|
||||||
|
}
|
||||||
|
nodes = append(nodes, m.MatchAll(c)...)
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter returns all the nodes that have an attribute key that matches the matcher
|
||||||
|
func (m *AttrMatcher) Filter(ns []*html.Node) []*html.Node {
|
||||||
|
var nodes []*html.Node
|
||||||
|
for _, n := range ns {
|
||||||
|
if m.Match(n) {
|
||||||
|
nodes = append(nodes, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
@ -67,14 +67,14 @@ var allowUsePropAttrs = map[string]bool{
|
||||||
"s:if": true,
|
"s:if": true,
|
||||||
"s:elif": true,
|
"s:elif": true,
|
||||||
"s:for": true,
|
"s:for": true,
|
||||||
"s:click": true,
|
"s:event": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var keepAttrs = map[string]bool{
|
var keepAttrs = map[string]bool{
|
||||||
"s:ns": true,
|
"s:ns": true,
|
||||||
"s:cn": true,
|
"s:cn": true,
|
||||||
"s:ready": true,
|
"s:ready": true,
|
||||||
"s:click": true,
|
"s:event": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTemplateParser create a new template parser
|
// NewTemplateParser create a new template parser
|
||||||
|
|
|
||||||
|
|
@ -66,13 +66,13 @@ func TestTemplatePageTree(t *testing.T) {
|
||||||
|
|
||||||
assert.NotEmpty(t, pages)
|
assert.NotEmpty(t, pages)
|
||||||
assert.NotEmpty(t, pages[1].Children)
|
assert.NotEmpty(t, pages[1].Children)
|
||||||
if len(pages[1].Children) < 2 {
|
if len(pages[1].Children) < 3 {
|
||||||
t.Fatalf("Pages error: %v", len(pages[1].Children))
|
t.Fatalf("Pages error: %v", len(pages[1].Children))
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NotEmpty(t, pages[1].Children[0].Children)
|
assert.NotEmpty(t, pages[2].Children[0].Children)
|
||||||
if len(pages[1].Children[0].Children) < 2 {
|
if len(pages[2].Children[0].Children) < 2 {
|
||||||
t.Fatalf("Pages error: %v", len(pages[1].Children[0].Children))
|
t.Fatalf("Pages error: %v", len(pages[2].Children[0].Children))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue