Optimisation

This commit is contained in:
anoshenko 2022-10-29 20:16:40 +03:00
parent 4523a9a427
commit d096a35af9
22 changed files with 166 additions and 137 deletions

View File

@ -4,7 +4,6 @@ package rui
import (
_ "embed"
"strings"
"syscall/js"
)
@ -22,40 +21,6 @@ func (app *wasmApp) Finish() {
app.session.close()
}
/*
func (app *wasmApp) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if ProtocolInDebugLog {
DebugLogF("%s %s", req.Method, req.URL.Path)
}
switch req.Method {
case "GET":
switch req.URL.Path {
case "/":
w.WriteHeader(http.StatusOK)
io.WriteString(w, app.getStartPage())
case "/ws":
if brige := CreateSocketBrige(w, req); brige != nil {
go app.socketReader(brige)
}
default:
filename := req.URL.Path[1:]
if size := len(filename); size > 0 && filename[size-1] == '/' {
filename = filename[:size-1]
}
if !serveResourceFile(filename, w, req) &&
!serveDownloadFile(filename, w, req) {
w.WriteHeader(http.StatusNotFound)
}
}
}
}
*/
func (app *wasmApp) startSession(this js.Value, args []js.Value) interface{} {
if app.createContentFunc == nil || len(args) == 1 {
return nil
@ -99,16 +64,16 @@ func StartApp(addr string, createContentFunc func(Session) SessionContent, param
app.brige = createWasmBrige()
js.Global().Set("startSession", js.FuncOf(app.startSession))
script := defaultScripts + wasmScripts
script = strings.ReplaceAll(script, "\\", `\\`)
script = strings.ReplaceAll(script, "\n", `\n`)
script = strings.ReplaceAll(script, "\t", `\t`)
script = strings.ReplaceAll(script, "\"", `\"`)
script = strings.ReplaceAll(script, "'", `\'`)
/*
script := defaultScripts + wasmScripts
script = strings.ReplaceAll(script, "\\", `\\`)
script = strings.ReplaceAll(script, "\n", `\n`)
script = strings.ReplaceAll(script, "\t", `\t`)
script = strings.ReplaceAll(script, "\"", `\"`)
script = strings.ReplaceAll(script, "'", `\'`)
//window := js.Global().Get("window")
//window.Call("execScript", `document.getElementById('ruiscript').text = "`+script+`"`)
js.Global().Call("execScript", `document.getElementById('ruiscript').text += "`+script+`"`)
js.Global().Call("execScript", `document.getElementById('ruiscript').text += "`+script+`"`)
*/
document := js.Global().Get("document")
body := document.Call("querySelector", "body")
@ -116,7 +81,8 @@ func StartApp(addr string, createContentFunc func(Session) SessionContent, param
<div class="ruiPopupLayer" id="ruiPopupLayer" style="visibility: hidden;" onclick="clickOutsidePopup(event)"></div>
<a id="ruiDownloader" download style="display: none;"></a>`)
js.Global().Call("execScript", "initSession()")
//js.Global().Call("execScript", "initSession()")
js.Global().Call("initSession", "")
//window.Call("execScript", "initSession()")
for true {

View File

@ -1161,6 +1161,12 @@ function blur(elementId) {
}
}
function blurCurrent() {
if (document.activeElement != document.body) {
document.activeElement.blur();
}
}
function playerEvent(element, tag) {
//event.stopPropagation();
sendMessage(tag + "{session=" + sessionID + ",id=" + element.id + "}");

View File

@ -1,7 +1,6 @@
package rui
import (
"fmt"
"strings"
)
@ -190,7 +189,7 @@ func (button *checkboxData) changedCheckboxState(state bool) {
defer freeStringBuilder(buffer)
button.htmlCheckbox(buffer, state)
button.Session().runScript(fmt.Sprintf(`updateInnerHTML('%v', '%v');`, button.htmlID()+"checkbox", buffer.String()))
button.Session().runFunc("updateInnerHTML", button.htmlID()+"checkbox", buffer.String())
}
func checkboxClickListener(view View) {

View File

@ -1,7 +1,6 @@
package rui
import (
"fmt"
"strings"
)
@ -114,7 +113,7 @@ func (picker *colorPickerData) set(tag string, value any) bool {
func (picker *colorPickerData) colorChanged(oldColor Color) {
if newColor := GetColorPickerValue(picker); oldColor != newColor {
if picker.created {
picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), newColor.rgbString()))
picker.session.runFunc("setInputValue", picker.htmlID(), newColor.rgbString())
}
for _, listener := range picker.colorChangedListeners {
listener(picker, newColor)

View File

@ -1,7 +1,6 @@
package rui
import (
"fmt"
"strconv"
"strings"
"time"
@ -98,7 +97,7 @@ func (picker *datePickerData) remove(tag string) {
delete(picker.properties, DatePickerValue)
date := GetDatePickerValue(picker)
if picker.created {
picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), date.Format(dateFormat)))
picker.session.runFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
}
for _, listener := range picker.dateChangedListeners {
listener(picker, date)
@ -224,7 +223,7 @@ func (picker *datePickerData) set(tag string, value any) bool {
if date, ok := setTimeValue(DatePickerValue); ok {
if date != oldDate {
if picker.created {
picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), date.Format(dateFormat)))
picker.session.runFunc("setInputValue", picker.htmlID(), date.Format(dateFormat))
}
for _, listener := range picker.dateChangedListeners {
listener(picker, date)

View File

@ -87,7 +87,7 @@ func (list *dropDownListData) remove(tag string) {
delete(list.properties, Current)
if oldCurrent != 0 {
if list.created {
list.session.runScript(fmt.Sprintf(`selectDropDownListItem('%s', %d)`, list.htmlID(), 0))
list.session.runFunc("selectDropDownListItem", list.htmlID(), 0)
}
list.onSelectedItemChanged(0)
}
@ -135,7 +135,7 @@ func (list *dropDownListData) set(tag string, value any) bool {
if current := GetCurrent(list); oldCurrent != current {
if list.created {
list.session.runScript(fmt.Sprintf(`selectDropDownListItem('%s', %d)`, list.htmlID(), current))
list.session.runFunc("selectDropDownListItem", list.htmlID(), current)
}
list.onSelectedItemChanged(current)
}

View File

@ -1,7 +1,6 @@
package rui
import (
"fmt"
"strconv"
"strings"
)
@ -137,7 +136,7 @@ func (edit *editViewData) remove(tag string) {
if oldText != "" {
edit.textChanged("")
if edit.created {
edit.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, edit.htmlID(), ""))
edit.session.runFunc("setInputValue", edit.htmlID(), "")
}
}
}
@ -211,11 +210,7 @@ func (edit *editViewData) set(tag string, value any) bool {
if GetEditViewType(edit) == MultiLineText {
updateInnerHTML(edit.htmlID(), edit.Session())
} else {
text = strings.ReplaceAll(text, `"`, `\"`)
text = strings.ReplaceAll(text, `'`, `\'`)
text = strings.ReplaceAll(text, "\n", `\n`)
text = strings.ReplaceAll(text, "\r", `\r`)
edit.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, edit.htmlID(), text))
edit.session.runFunc("setInputValue", edit.htmlID(), text)
}
}
}
@ -365,14 +360,7 @@ func (edit *editViewData) AppendText(text string) {
if textValue, ok := value.(string); ok {
textValue += text
edit.properties[Text] = textValue
text := strings.ReplaceAll(text, `"`, `\"`)
text = strings.ReplaceAll(text, `'`, `\'`)
text = strings.ReplaceAll(text, "\n", `\n`)
text = strings.ReplaceAll(text, "\r", `\r`)
edit.session.runScript(`appendToInnerHTML("` + edit.htmlID() + `", "` + text + `")`)
edit.session.runFunc("appendToInnerHTML", edit.htmlID(), text)
edit.textChanged(textValue)
return
}

View File

@ -2,7 +2,6 @@ package rui
import (
"encoding/base64"
"fmt"
"strconv"
"strings"
"time"
@ -109,7 +108,7 @@ func (picker *filePickerData) LoadFile(file FileInfo, result func(FileInfo, []by
for i, info := range picker.files {
if info.Name == file.Name && info.Size == file.Size && info.LastModified == file.LastModified {
picker.loader[i] = result
picker.Session().runScript(fmt.Sprintf(`loadSelectedFile("%s", %d)`, picker.htmlID(), i))
picker.Session().runFunc("loadSelectedFile", picker.htmlID(), i)
return
}
}

View File

@ -76,7 +76,7 @@ func (manager *imageManager) loadImage(url string, onLoaded func(Image), session
image.listener = onLoaded
image.loadingStatus = ImageLoading
manager.images[url] = image
session.runScript("loadImage('" + url + "');")
session.runFunc("loadImage", url)
return image
}

View File

@ -819,12 +819,6 @@ func (listView *listViewData) updateCheckboxItem(index int, checked bool) {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
buffer.WriteString(`updateInnerHTML('`)
buffer.WriteString(listView.htmlID())
buffer.WriteRune('-')
buffer.WriteString(strconv.Itoa(index))
buffer.WriteString(`', '`)
buffer.WriteString(listView.checkboxItemDiv(listView, checkbox, hCheckboxAlign, vCheckboxAlign))
if checked {
buffer.WriteString(onDiv)
@ -842,9 +836,8 @@ func (listView *listViewData) updateCheckboxItem(index int, checked bool) {
buffer.WriteString("ERROR: invalid item view")
}
}
buffer.WriteString(`</div></div>');`)
session.runScript(buffer.String())
buffer.WriteString(`</div></div>`)
session.runFunc("updateInnerHTML", listView.htmlID()+"-"+strconv.Itoa(index), buffer.String())
}
func (listView *listViewData) htmlProperties(self View, buffer *strings.Builder) {

View File

@ -472,11 +472,7 @@ func (player *mediaPlayerData) propertyChanged(tag string) {
case Muted:
value, _ := boolProperty(player, tag, player.Session())
if value {
player.Session().runScript("setMediaMuted('" + player.htmlID() + "', true)")
} else {
player.Session().runScript("setMediaMuted('" + player.htmlID() + "', false)")
}
player.Session().runFunc("setMediaMuted", player.htmlID(), value)
case Preload:
value, _ := enumProperty(player, tag, player.Session(), 0)
@ -659,15 +655,15 @@ func (player *mediaPlayerData) handleCommand(self View, command string, data Dat
}
func (player *mediaPlayerData) Play() {
player.session.runScript(fmt.Sprintf(`mediaPlay('%v');`, player.htmlID()))
player.session.runFunc("mediaPlay", player.htmlID())
}
func (player *mediaPlayerData) Pause() {
player.session.runScript(fmt.Sprintf(`mediaPause('%v');`, player.htmlID()))
player.session.runFunc("mediaPause", player.htmlID())
}
func (player *mediaPlayerData) SetCurrentTime(seconds float64) {
player.session.runScript(fmt.Sprintf(`mediaSetSetCurrentTime('%v', %v);`, player.htmlID(), seconds))
player.session.runFunc("mediaSetSetCurrentTime", player.htmlID(), seconds)
}
func (player *mediaPlayerData) getFloatPlayerProperty(tag string) (float64, bool) {
@ -731,7 +727,7 @@ func (player *mediaPlayerData) Duration() float64 {
}
func (player *mediaPlayerData) SetPlaybackRate(rate float64) {
player.session.runScript(fmt.Sprintf(`mediaSetPlaybackRate('%v', %v);`, player.htmlID(), rate))
player.session.runFunc("mediaSetPlaybackRate", player.htmlID(), rate)
}
func (player *mediaPlayerData) PlaybackRate() float64 {
@ -743,7 +739,7 @@ func (player *mediaPlayerData) PlaybackRate() float64 {
func (player *mediaPlayerData) SetVolume(volume float64) {
if volume >= 0 && volume <= 1 {
player.session.runScript(fmt.Sprintf(`mediaSetVolume('%v', %v);`, player.htmlID(), volume))
player.session.runFunc("mediaSetVolume", player.htmlID(), volume)
}
}

View File

@ -1,7 +1,6 @@
package rui
import (
"fmt"
"math"
"strconv"
"strings"
@ -117,7 +116,7 @@ func (picker *numberPickerData) set(tag string, value any) bool {
if f, ok := floatProperty(picker, NumberPickerValue, picker.Session(), min); ok && f != oldValue {
newValue, _ := floatTextProperty(picker, NumberPickerValue, picker.Session(), min)
if picker.created {
picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), newValue))
picker.session.runFunc("setInputValue", picker.htmlID(), newValue)
}
for _, listener := range picker.numberChangedListeners {
listener(picker, f)
@ -163,7 +162,7 @@ func (picker *numberPickerData) propertyChanged(tag string) {
case NumberPickerValue:
value := GetNumberPickerValue(picker)
picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%f')`, picker.htmlID(), value))
picker.session.runFunc("setInputValue", picker.htmlID(), value)
for _, listener := range picker.numberChangedListeners {
listener(picker, value)
}

View File

@ -610,19 +610,17 @@ func ShowPopup(view View, param Params) Popup {
func (manager *popupManager) updatePopupLayerInnerHTML(session Session) {
if manager.popups == nil {
manager.popups = []Popup{}
session.runScript(`updateInnerHTML('ruiPopupLayer', '');`)
session.runFunc("updateInnerHTML", "ruiPopupLayer", "")
return
}
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
buffer.WriteString(`updateInnerHTML('ruiPopupLayer', '`)
for _, p := range manager.popups {
p.html(buffer)
for _, popup := range manager.popups {
popup.html(buffer)
}
buffer.WriteString(`');`)
session.runScript(buffer.String())
session.runFunc("updateInnerHTML", "ruiPopupLayer", buffer.String())
}
func (manager *popupManager) showPopup(popup Popup) {
@ -637,7 +635,7 @@ func (manager *popupManager) showPopup(popup Popup) {
manager.popups = append(manager.popups, popup)
}
session.runScript(`if (document.activeElement != document.body) document.activeElement.blur();`)
session.runFunc("blurCurrent")
manager.updatePopupLayerInnerHTML(session)
updateCSSProperty("ruiPopupLayer", "visibility", "visible", session)
updateCSSProperty("ruiRoot", "pointer-events", "none", session)
@ -660,7 +658,7 @@ func (manager *popupManager) dismissPopup(popup Popup) {
manager.popups = []Popup{}
updateCSSProperty("ruiRoot", "pointer-events", "auto", session)
updateCSSProperty("ruiPopupLayer", "visibility", "hidden", session)
session.runScript(`updateInnerHTML('ruiPopupLayer', '');`)
session.runFunc("updateInnerHTML", "ruiPopupLayer", "")
} else {
manager.popups = manager.popups[:count-1]
manager.updatePopupLayerInnerHTML(session)

View File

@ -1,7 +1,5 @@
package rui
import "fmt"
// ScrollEvent is the constant for "scroll-event" property tag.
// The "scroll-event" is fired when the content of the view is scrolled.
// The main listener format:
@ -59,7 +57,7 @@ func ScrollViewTo(view View, subviewID string, x, y float64) {
view = ViewByID(view, subviewID)
}
if view != nil {
view.Session().runScript(fmt.Sprintf(`scrollTo("%s", %g, %g)`, view.htmlID(), x, y))
view.Session().runFunc("scrollTo", view.htmlID(), x, y)
}
}
@ -70,7 +68,7 @@ func ScrollViewToStart(view View, subviewID ...string) {
view = ViewByID(view, subviewID[0])
}
if view != nil {
view.Session().runScript(`scrollToStart("` + view.htmlID() + `")`)
view.Session().runFunc("scrollToStart", view.htmlID())
}
}
@ -81,6 +79,6 @@ func ScrollViewToEnd(view View, subviewID ...string) {
view = ViewByID(view, subviewID[0])
}
if view != nil {
view.Session().runScript(`scrollToEnd("` + view.htmlID() + `")`)
view.Session().runFunc("scrollToEnd", view.htmlID())
}
}

View File

@ -8,6 +8,7 @@ import (
)
type webBrige interface {
runFunc(funcName string, args ...any) bool
readMessage() (string, bool)
writeMessage(text string) bool
runGetterScript(script string) DataObject
@ -96,6 +97,7 @@ type Session interface {
setBrige(events chan DataObject, brige webBrige)
writeInitScript(writer *strings.Builder)
runFunc(funcName string, args ...any)
runScript(script string)
runGetterScript(script string) DataObject //, answer chan DataObject)
handleAnswer(data DataObject)
@ -321,6 +323,14 @@ func (session *sessionData) imageManager() *imageManager {
return session.images
}
func (session *sessionData) runFunc(funcName string, args ...any) {
if session.brige != nil {
session.brige.runFunc(funcName, args...)
} else {
ErrorLog("No connection")
}
}
func (session *sessionData) runScript(script string) {
if session.brige != nil {
session.brige.writeMessage(script)

View File

@ -39,15 +39,13 @@ func sizeConstant(session Session, tag string) (SizeUnit, bool) {
func updateCSSStyle(htmlID string, session Session) {
if !session.ignoreViewUpdates() {
if view := session.viewByHTMLID(htmlID); view != nil {
var builder viewCSSBuilder
builder.buffer = allocStringBuilder()
builder.buffer.WriteString(`updateCSSStyle('`)
builder.buffer.WriteString(view.htmlID())
builder.buffer.WriteString(`', '`)
builder := viewCSSBuilder{buffer: allocStringBuilder()}
//builder.buffer.WriteString(`updateCSSStyle('`)
//builder.buffer.WriteString(view.htmlID())
//builder.buffer.WriteString(`', '`)
view.cssStyle(view, &builder)
builder.buffer.WriteString(`');`)
view.Session().runScript(builder.finish())
//builder.buffer.WriteString(`');`)
session.runFunc("updateCSSStyle", view.htmlID(), builder.finish())
}
}
}
@ -66,8 +64,7 @@ func updateInnerHTML(htmlID string, session Session) {
script.Grow(32 * 1024)
view.htmlSubviews(view, script)
view.Session().runScript(fmt.Sprintf(`updateInnerHTML('%v', '%v');`, view.htmlID(), script.String()))
//view.updateEventHandlers()
session.runFunc("updateInnerHTML", view.htmlID(), script.String())
}
}
}
@ -75,8 +72,7 @@ func updateInnerHTML(htmlID string, session Session) {
func appendToInnerHTML(htmlID, content string, session Session) {
if !session.ignoreViewUpdates() {
if view := session.viewByHTMLID(htmlID); view != nil {
view.Session().runScript(fmt.Sprintf(`appendToInnerHTML('%v', '%v');`, view.htmlID(), content))
//view.updateEventHandlers()
session.runFunc("appendToInnerHTML", view.htmlID(), content)
}
}
}
@ -87,7 +83,7 @@ func updateProperty(htmlID, property, value string, session Session) {
buffer.WriteString(fmt.Sprintf(`element.setAttribute('%v', '%v');`, property, value))
buffer.WriteRune('\n')
} else {
session.runScript(fmt.Sprintf(`updateProperty('%v', '%v', '%v');`, htmlID, property, value))
session.runFunc("updateProperty", htmlID, property, value)
}
}
}
@ -98,7 +94,7 @@ func updateCSSProperty(htmlID, property, value string, session Session) {
buffer.WriteString(fmt.Sprintf(`element.style['%v'] = '%v';`, property, value))
buffer.WriteRune('\n')
} else {
session.runScript(fmt.Sprintf(`updateCSSProperty('%v', '%v', '%v');`, htmlID, property, value))
session.runFunc("updateCSSProperty", htmlID, property, value)
}
}
}
@ -112,10 +108,8 @@ func updateBoolProperty(htmlID, property string, value bool, session Session) {
buffer.WriteString(fmt.Sprintf(`element.setAttribute('%v', false);`, property))
}
buffer.WriteRune('\n')
} else if value {
session.runScript(fmt.Sprintf(`updateProperty('%v', '%v', true);`, htmlID, property))
} else {
session.runScript(fmt.Sprintf(`updateProperty('%v', '%v', false);`, htmlID, property))
session.runFunc("updateProperty", htmlID, property, value)
}
}
}
@ -126,7 +120,7 @@ func removeProperty(htmlID, property string, session Session) {
buffer.WriteString(fmt.Sprintf(`if (element.hasAttribute('%v')) { element.removeAttribute('%v');}`, property, property))
buffer.WriteRune('\n')
} else {
session.runScript(fmt.Sprintf(`removeProperty('%v', '%v');`, htmlID, property))
session.runFunc("removeProperty", htmlID, property)
}
}
}

View File

@ -825,8 +825,7 @@ func (table *tableViewData) htmlSubviews(self View, buffer *strings.Builder) {
defer session.setIgnoreViewUpdates(false)
}
var cssBuilder viewCSSBuilder
cssBuilder.buffer = allocStringBuilder()
cssBuilder := viewCSSBuilder{buffer: allocStringBuilder()}
defer freeStringBuilder(cssBuilder.buffer)
var view tableCellView

View File

@ -151,7 +151,7 @@ func (tabsLayout *tabsLayoutData) remove(tag string) {
return
}
if tabsLayout.created {
tabsLayout.session.runScript(fmt.Sprintf("activateTab('%v', %d);", tabsLayout.htmlID(), 0))
tabsLayout.session.runFunc("activateTab", tabsLayout.htmlID(), 0)
for _, listener := range tabsLayout.tabListener {
listener(tabsLayout, 0, oldCurrent)
}
@ -235,7 +235,7 @@ func (tabsLayout *tabsLayoutData) set(tag string, value any) bool {
return true
}
if tabsLayout.created {
tabsLayout.session.runScript(fmt.Sprintf("activateTab('%v', %d);", tabsLayout.htmlID(), current))
tabsLayout.session.runFunc("activateTab", tabsLayout.htmlID(), current)
for _, listener := range tabsLayout.tabListener {
listener(tabsLayout, current, oldCurrent)
}

View File

@ -1,7 +1,6 @@
package rui
import (
"fmt"
"strconv"
"strings"
"time"
@ -98,7 +97,7 @@ func (picker *timePickerData) remove(tag string) {
delete(picker.properties, TimePickerValue)
time := GetTimePickerValue(picker)
if picker.created {
picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), time.Format(timeFormat)))
picker.session.runFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
}
for _, listener := range picker.timeChangedListeners {
listener(picker, time)
@ -212,7 +211,7 @@ func (picker *timePickerData) set(tag string, value any) bool {
if time, ok := setTimeValue(TimePickerValue); ok {
if time != oldTime {
if picker.created {
picker.session.runScript(fmt.Sprintf(`setInputValue('%s', '%s')`, picker.htmlID(), time.Format(timeFormat)))
picker.session.runFunc("setInputValue", picker.htmlID(), time.Format(timeFormat))
}
for _, listener := range picker.timeChangedListeners {
listener(picker, time)

View File

@ -754,7 +754,7 @@ func viewHTML(view View, buffer *strings.Builder) {
buffer.WriteRune('"')
}
var cssBuilder viewCSSBuilder
cssBuilder := viewCSSBuilder{buffer: allocStringBuilder()}
view.cssStyle(view, &cssBuilder)
if style := cssBuilder.finish(); style != "" {

View File

@ -803,7 +803,7 @@ func colorStyledProperty(view View, subviewID []string, tag string, inherit bool
// The focused View is the View which will receive keyboard events by default.
func FocusView(view View) {
if view != nil {
view.Session().runScript("focus('" + view.htmlID() + "')")
view.Session().runFunc("focus", view.htmlID())
}
}
@ -811,21 +811,21 @@ func FocusView(view View) {
// The focused View is the View which will receive keyboard events by default.
func FocusViewByID(viewID string, session Session) {
if viewID != "" {
session.runScript("focus('" + viewID + "')")
session.runFunc("focus", viewID)
}
}
// BlurView removes keyboard focus from the specified View.
func BlurView(view View) {
if view != nil {
view.Session().runScript("blur('" + view.htmlID() + "')")
view.Session().runFunc("blur", view.htmlID())
}
}
// BlurViewByID removes keyboard focus from the View with the specified viewID.
func BlurViewByID(viewID string, session Session) {
if viewID != "" {
session.runScript("blur('" + viewID + "')")
session.runFunc("blur", viewID)
}
}

View File

@ -3,8 +3,10 @@
package rui
import (
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"github.com/gorilla/websocket"
@ -16,6 +18,7 @@ type wsBrige struct {
answerID int
answerMutex sync.Mutex
closed bool
buffer strings.Builder
}
var upgrader = websocket.Upgrader{
@ -43,6 +46,90 @@ func (brige *wsBrige) close() {
brige.conn.Close()
}
func (brige *wsBrige) argToString(arg any) (string, bool) {
switch arg := arg.(type) {
case string:
arg = strings.ReplaceAll(arg, "\\", `\\`)
arg = strings.ReplaceAll(arg, "'", `\'`)
arg = strings.ReplaceAll(arg, "\n", `\n`)
arg = strings.ReplaceAll(arg, "\r", `\r`)
arg = strings.ReplaceAll(arg, "\t", `\t`)
arg = strings.ReplaceAll(arg, "\b", `\b`)
arg = strings.ReplaceAll(arg, "\f", `\f`)
arg = strings.ReplaceAll(arg, "\v", `\v`)
return `'` + arg + `'`, true
case rune:
switch arg {
case '\t':
return `'\t'`, true
case '\r':
return `'\r'`, true
case '\n':
return `'\n'`, true
case '\b':
return `'\b'`, true
case '\f':
return `'\f'`, true
case '\v':
return `'\v'`, true
case '\'':
return `'\''`, true
case '\\':
return `'\\'`, true
}
if arg < ' ' {
return fmt.Sprintf(`'\x%02d'`, int(arg)), true
}
return `'` + string(arg) + `'`, true
case bool:
if arg {
return "true", true
} else {
return "false", true
}
case float32:
return fmt.Sprintf("%g", float64(arg)), true
case float64:
return fmt.Sprintf("%g", arg), true
default:
if n, ok := isInt(arg); ok {
return fmt.Sprintf("%d", n), true
}
}
ErrorLog("Unsupported agument type")
return "", false
}
func (brige *wsBrige) runFunc(funcName string, args ...any) bool {
brige.buffer.Reset()
brige.buffer.WriteString(funcName)
brige.buffer.WriteRune('(')
for i, arg := range args {
argText, ok := brige.argToString(arg)
if !ok {
return false
}
if i > 0 {
brige.buffer.WriteString(", ")
}
brige.buffer.WriteString(argText)
}
brige.buffer.WriteString(");")
if err := brige.conn.WriteMessage(websocket.TextMessage, []byte(brige.buffer.String())); err != nil {
ErrorLog(err.Error())
return false
}
return true
}
func (brige *wsBrige) readMessage() (string, bool) {
_, p, err := brige.conn.ReadMessage()
if err != nil {