From 04bbd47f665a788fb078ef35ea239ec3618f551a Mon Sep 17 00:00:00 2001 From: anoshenko Date: Thu, 4 Nov 2021 14:59:25 +0300 Subject: [PATCH] Added FilePicker --- appLog.go | 11 +- app_scripts.js | 44 +++++ demo/filePickerDemo.go | 39 ++++ demo/main.go | 1 + demo/splitViewDemo.go | 26 --- filePicker.go | 414 +++++++++++++++++++++++++++++++++++++++++ mediaPlayer.go | 2 +- mouseEvents.go | 4 +- pointerEvents.go | 2 +- propertySet.go | 1 + utils.go | 6 +- viewByID.go | 86 +++++++-- viewFactory.go | 1 + viewUtils.go | 22 +-- 14 files changed, 595 insertions(+), 64 deletions(-) create mode 100644 demo/filePickerDemo.go delete mode 100644 demo/splitViewDemo.go create mode 100644 filePicker.go diff --git a/appLog.go b/appLog.go index 7fe48a7..60919bd 100644 --- a/appLog.go +++ b/appLog.go @@ -45,8 +45,11 @@ func DebugLogF(format string, a ...interface{}) { } } +var lastError = "" + // ErrorLog print the text to the error log func ErrorLog(text string) { + lastError = text if errorLogFunc != nil { errorLogFunc(text) errorStack() @@ -55,12 +58,18 @@ func ErrorLog(text string) { // ErrorLogF print the text to the error log func ErrorLogF(format string, a ...interface{}) { + lastError = fmt.Sprintf(format, a...) if errorLogFunc != nil { - errorLogFunc(fmt.Sprintf(format, a...)) + errorLogFunc(lastError) errorStack() } } +// LastError returns the last error text +func LastError() string { + return lastError +} + func errorStack() { if errorLogFunc != nil { skip := 2 diff --git a/app_scripts.js b/app_scripts.js index 096ff97..e0c0762 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -989,6 +989,50 @@ function setInputValue(elementId, text) { } } +function fileSelectedEvent(element) { + var files = element.files; + if (files) { + var message = "fileSelected{session=" + sessionID + ",id=" + element.id + ",files=["; + for(var i = 0; i < files.length; i++) { + if (i > 0) { + message += ","; + } + message += "_{name=\"" + files[i].name + + "\",last-modified=" + files[i].lastModified + + ",size=" + files[i].size + + ",mime-type=\"" + files[i].type + "\"}"; + } + sendMessage(message + "]}"); + } +} + +function loadSelectedFile(elementId, index) { + var element = document.getElementById(elementId); + if (element) { + var files = element.files; + if (files && index >= 0 && index < files.length) { + const reader = new FileReader(); + reader.onload = function() { + sendMessage("fileLoaded{session=" + sessionID + ",id=" + element.id + + ",index=" + index + + ",name=\"" + files[index].name + + "\",last-modified=" + files[index].lastModified + + ",size=" + files[index].size + + ",mime-type=\"" + files[index].type + + "\",data=`" + reader.result + "`}"); + } + reader.onerror = function(error) { + sendMessage("fileLoadingError{session=" + sessionID + ",id=" + element.id + ",index=" + index + ",error=`" + error + "`}"); + } + reader.readAsDataURL(files[index]); + } else { + sendMessage("fileLoadingError{session=" + sessionID + ",id=" + element.id + ",index=" + index + ",error=`File not found`}"); + } + } else { + sendMessage("fileLoadingError{session=" + sessionID + ",id=" + element.id + ",index=" + index + ",error=`Invalid FilePicker id`}"); + } +} + function startResize(element, mx, my, event) { var view = element.parentNode; if (!view) { diff --git a/demo/filePickerDemo.go b/demo/filePickerDemo.go new file mode 100644 index 0000000..2f5cbf9 --- /dev/null +++ b/demo/filePickerDemo.go @@ -0,0 +1,39 @@ +package main + +import ( + "github.com/anoshenko/rui" +) + +const filePickerDemoText = ` +GridLayout { + width = 100%, height = 100%, cell-height = "auto, 1fr", + content = [ + FilePicker { + id = filePicker, accept = "txt, html" + }, + EditView { + id = selectedFileData, row = 1, type = multiline, read-only = true, wrap = true, + } + ] +} +` + +func createFilePickerDemo(session rui.Session) rui.View { + view := rui.CreateViewFromText(session, filePickerDemoText) + if view == nil { + return nil + } + + rui.Set(view, "filePicker", rui.FileSelectedEvent, func(picker rui.FilePicker, files []rui.FileInfo) { + if len(files) > 0 { + picker.LoadFile(files[0], func(file rui.FileInfo, data []byte) { + if data != nil { + rui.Set(view, "selectedFileData", rui.Text, string(data)) + } else { + rui.Set(view, "selectedFileData", rui.Text, rui.LastError()) + } + }) + } + }) + return view +} diff --git a/demo/main.go b/demo/main.go index 2bfb4a9..6f0488f 100644 --- a/demo/main.go +++ b/demo/main.go @@ -82,6 +82,7 @@ func createDemo(session rui.Session) rui.SessionContent { {"ListView", createListViewDemo, nil}, {"Checkbox", createCheckboxDemo, nil}, {"Controls", createControlsDemo, nil}, + {"FilePicker", createFilePickerDemo, nil}, {"TableView", createTableViewDemo, nil}, {"EditView", createEditDemo, nil}, {"ImageView", createImageViewDemo, nil}, diff --git a/demo/splitViewDemo.go b/demo/splitViewDemo.go deleted file mode 100644 index 54f8981..0000000 --- a/demo/splitViewDemo.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -/* -import ( - "github.com/anoshenko/rui" -) - -const splitViewDemoText = ` -SplitView { - width = 100%, height = 100%, orientation = vertical, anchor = bottom, padding = 2px, - view1 = GridLayout { width = 100%, height = 75%, content = ["View 1"], cell-vertical-align = center, cell-horizontal-align = center, - border = _{ width = 1px, style = solid, color = #FF000000 }, radius = 8px,}, - view2 = GridLayout { width = 100%, height = 25%, content = ["View 2"], cell-align = center, - border = _{ width = 1px, style = solid, color = #FF000000 }, radius = 8px,}, -} -` - -func createSplitViewDemo(session rui.Session) rui.View { - view := rui.CreateViewFromText(session, splitViewDemoText) - if view == nil { - return nil - } - - return view -} -*/ diff --git a/filePicker.go b/filePicker.go new file mode 100644 index 0000000..4cbff32 --- /dev/null +++ b/filePicker.go @@ -0,0 +1,414 @@ +package rui + +import ( + "encoding/base64" + "fmt" + "strconv" + "strings" + "time" +) + +const ( + // FileSelectedEvent is the constant for "file-selected-event" property tag. + // The "file-selected-event" is fired when user selects file(s) in the FilePicker. + FileSelectedEvent = "file-selected-event" + // Accept is the constant for "accept" property tag. + // The "accept" property of the FilePicker sets the list of allowed file extensions or MIME types. + Accept = "accept" + // Multiple is the constant for "multiple" property tag. + // The "multiple" bool property of the FilePicker sets whether multiple files can be selected + Multiple = "multiple" +) + +// FileInfo describes a file which selected in the FilePicker view +type FileInfo struct { + // Name - the file's name. + Name string + // LastModified specifying the date and time at which the file was last modified + LastModified time.Time + // Size - the size of the file in bytes. + Size int64 + // MimeType - the file's MIME type. + MimeType string +} + +// FilePicker - the control view for the files selecting +type FilePicker interface { + View + // Files returns the list of selected files. + // If there are no files selected then an empty slice is returned (the result is always not nil) + Files() []FileInfo + // LoadFile loads the content of the selected file. This function is asynchronous. + // The "result" function will be called after loading the data. + LoadFile(file FileInfo, result func(FileInfo, []byte)) +} + +type filePickerData struct { + viewData + files []FileInfo + fileSelectedListeners []func(FilePicker, []FileInfo) + loader map[int]func(FileInfo, []byte) +} + +func (file *FileInfo) initBy(node DataValue) { + if obj := node.Object(); obj != nil { + file.Name, _ = obj.PropertyValue("name") + file.MimeType, _ = obj.PropertyValue("mime-type") + + if size, ok := obj.PropertyValue("size"); ok { + if n, err := strconv.ParseInt(size, 10, 64); err == nil { + file.Size = n + } + } + + if value, ok := obj.PropertyValue("last-modified"); ok { + if n, err := strconv.ParseInt(value, 10, 64); err == nil { + file.LastModified = time.UnixMilli(n) + } + } + } +} + +// NewFilePicker create new FilePicker object and return it +func NewFilePicker(session Session, params Params) FilePicker { + view := new(filePickerData) + view.Init(session) + setInitParams(view, params) + return view +} + +func newFilePicker(session Session) View { + return NewFilePicker(session, nil) +} + +func (picker *filePickerData) Init(session Session) { + picker.viewData.Init(session) + picker.tag = "FilePicker" + picker.files = []FileInfo{} + picker.loader = map[int]func(FileInfo, []byte){} + picker.fileSelectedListeners = []func(FilePicker, []FileInfo){} +} + +func (picker *filePickerData) Files() []FileInfo { + return picker.files +} + +func (picker *filePickerData) LoadFile(file FileInfo, result func(FileInfo, []byte)) { + if result == nil { + return + } + + 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)) + return + } + } +} + +func (picker *filePickerData) Remove(tag string) { + picker.remove(strings.ToLower(tag)) +} + +func (picker *filePickerData) remove(tag string) { + switch tag { + case FileSelectedEvent: + picker.fileSelectedListeners = []func(FilePicker, []FileInfo){} + + default: + picker.viewData.remove(tag) + } +} + +func (picker *filePickerData) Set(tag string, value interface{}) bool { + return picker.set(strings.ToLower(tag), value) +} + +func (picker *filePickerData) set(tag string, value interface{}) bool { + if value == nil { + picker.remove(tag) + return true + } + + switch tag { + case FileSelectedEvent: + switch value := value.(type) { + case func(FilePicker, []FileInfo): + picker.fileSelectedListeners = []func(FilePicker, []FileInfo){value} + + case func([]FileInfo): + fn := func(view FilePicker, files []FileInfo) { + value(files) + } + picker.fileSelectedListeners = []func(FilePicker, []FileInfo){fn} + + case []func(FilePicker, []FileInfo): + picker.fileSelectedListeners = value + + case []func([]FileInfo): + listeners := make([]func(FilePicker, []FileInfo), len(value)) + for i, val := range value { + if val == nil { + notCompatibleType(tag, val) + return false + } + + listeners[i] = func(view FilePicker, files []FileInfo) { + val(files) + } + } + picker.fileSelectedListeners = listeners + + case []interface{}: + listeners := make([]func(FilePicker, []FileInfo), len(value)) + for i, val := range value { + if val == nil { + notCompatibleType(tag, val) + return false + } + + switch val := val.(type) { + case func(FilePicker, []FileInfo): + listeners[i] = val + + case func([]FileInfo): + listeners[i] = func(view FilePicker, files []FileInfo) { + val(files) + } + + default: + notCompatibleType(tag, val) + return false + } + } + picker.fileSelectedListeners = listeners + } + return true + + case Accept: + switch value := value.(type) { + case string: + value = strings.Trim(value, " \t\n") + if value == "" { + picker.remove(Accept) + } else { + picker.properties[Accept] = value + } + + case []string: + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + for _, val := range value { + val = strings.Trim(val, " \t\n") + if val != "" { + if buffer.Len() > 0 { + buffer.WriteRune(',') + } + buffer.WriteString(val) + } + } + if buffer.Len() == 0 { + picker.remove(Accept) + } else { + picker.properties[Accept] = buffer.String() + } + + default: + notCompatibleType(tag, value) + return false + } + return true + + default: + return picker.viewData.set(tag, value) + } +} + +func (picker *filePickerData) htmlTag() string { + return "input" +} + +func (picker *filePickerData) acceptCSS() string { + accept, ok := stringProperty(picker, Accept, picker.Session()) + if !ok { + accept, ok = valueFromStyle(picker, Accept) + } + if ok { + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + for _, value := range strings.Split(accept, ",") { + if value = strings.Trim(value, " \t\n"); value != "" { + if buffer.Len() > 0 { + buffer.WriteString(", ") + } + if value[0] != '.' && !strings.Contains(value, "/") { + buffer.WriteRune('.') + } + buffer.WriteString(value) + } + } + return buffer.String() + } + return "" +} + +func (picker *filePickerData) htmlProperties(self View, buffer *strings.Builder) { + picker.viewData.htmlProperties(self, buffer) + + if accept := picker.acceptCSS(); accept != "" { + buffer.WriteString(` accept="`) + buffer.WriteString(accept) + buffer.WriteRune('"') + } + + buffer.WriteString(` type="file"`) + if multiple, ok := boolStyledProperty(picker, Multiple); ok && multiple { + buffer.WriteString(` multiple`) + } + + buffer.WriteString(` oninput="fileSelectedEvent(this)"`) +} + +func (picker *filePickerData) htmlDisabledProperties(self View, buffer *strings.Builder) { + if IsDisabled(self) { + buffer.WriteString(` disabled`) + } + picker.viewData.htmlDisabledProperties(self, buffer) +} + +func (picker *filePickerData) handleCommand(self View, command string, data DataObject) bool { + switch command { + case "fileSelected": + if node := data.PropertyWithTag("files"); node != nil && node.Type() == ArrayNode { + count := node.ArraySize() + files := make([]FileInfo, count) + for i := 0; i < count; i++ { + if value := node.ArrayElement(i); value != nil { + files[i].initBy(value) + } + } + picker.files = files + + for _, listener := range picker.fileSelectedListeners { + listener(picker, files) + } + } + return true + + case "fileLoaded": + if index, ok := dataIntProperty(data, "index"); ok { + if result, ok := picker.loader[index]; ok { + var file FileInfo + file.initBy(data) + + var fileData []byte = nil + if base64Data, ok := data.PropertyValue("data"); ok { + if index := strings.LastIndex(base64Data, ","); index >= 0 { + base64Data = base64Data[index+1:] + } + decode, err := base64.StdEncoding.DecodeString(base64Data) + if err == nil { + fileData = decode + } else { + ErrorLog(err.Error()) + } + } + + result(file, fileData) + delete(picker.loader, index) + } + } + return true + + case "fileLoadingError": + if error, ok := data.PropertyValue("error"); ok { + ErrorLog(error) + } + if index, ok := dataIntProperty(data, "index"); ok { + if result, ok := picker.loader[index]; ok { + if index >= 0 && index < len(picker.files) { + result(picker.files[index], nil) + } else { + result(FileInfo{}, nil) + } + delete(picker.loader, index) + } + } + return true + } + + return picker.viewData.handleCommand(self, command, data) +} + +// GetFilePickerFiles returns the list of FilePicker selected files +// If there are no files selected then an empty slice is returned (the result is always not nil) +// If the second argument (subviewID) is "" then selected files of the first argument (view) is returned +func GetFilePickerFiles(view View, subviewID string) []FileInfo { + if picker := FilePickerByID(view, subviewID); picker != nil { + return picker.Files() + } + return []FileInfo{} +} + +// LoadFilePickerFile loads the content of the selected file. This function is asynchronous. +// The "result" function will be called after loading the data. +// If the second argument (subviewID) is "" then the file from the first argument (view) is loaded +func LoadFilePickerFile(view View, subviewID string, file FileInfo, result func(FileInfo, []byte)) { + if picker := FilePickerByID(view, subviewID); picker != nil { + picker.LoadFile(file, result) + } +} + +// IsMultipleFilePicker returns "true" if multiple files can be selected in the FilePicker, "false" otherwise. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func IsMultipleFilePicker(view View, subviewID string) bool { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + if view != nil { + if result, ok := boolStyledProperty(view, Multiple); ok { + return result + } + } + return false +} + +// GetFilePickerAccept returns sets the list of allowed file extensions or MIME types. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetFilePickerAccept(view View, subviewID string) []string { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + if view != nil { + accept, ok := stringProperty(view, Accept, view.Session()) + if !ok { + accept, ok = valueFromStyle(view, Accept) + } + if ok { + result := strings.Split(accept, ",") + for i := 0; i < len(result); i++ { + result[i] = strings.Trim(result[i], " \t\n") + } + return result + } + } + return []string{} +} + +// GetFileSelectedListeners returns the "file-selected-event" listener list. +// If there are no listeners then the empty list is returned. +// If the second argument (subviewID) is "" then a value from the first argument (view) is returned. +func GetFileSelectedListeners(view View, subviewID string) []func(FilePicker, []FileInfo) { + if subviewID != "" { + view = ViewByID(view, subviewID) + } + if view != nil { + if value := view.Get(FileSelectedEvent); value != nil { + if result, ok := value.([]func(FilePicker, []FileInfo)); ok { + return result + } + } + } + return []func(FilePicker, []FileInfo){} +} diff --git a/mediaPlayer.go b/mediaPlayer.go index 33138c1..d851f11 100644 --- a/mediaPlayer.go +++ b/mediaPlayer.go @@ -844,7 +844,7 @@ func (player *mediaPlayerData) handleCommand(self View, command string, data Dat case PlayerErrorEvent: if value := player.getRaw(command); value != nil { if listeners, ok := value.([]func(MediaPlayer, int, string)); ok { - code := dataIntProperty(data, "code") + code, _ := dataIntProperty(data, "code") message, _ := data.PropertyValue("message") for _, listener := range listeners { listener(player, code, message) diff --git a/mouseEvents.go b/mouseEvents.go index acc4092..e0d6b16 100644 --- a/mouseEvents.go +++ b/mouseEvents.go @@ -328,8 +328,8 @@ func getTimeStamp(data DataObject) uint64 { func (event *MouseEvent) init(data DataObject) { event.TimeStamp = getTimeStamp(data) - event.Button = dataIntProperty(data, "button") - event.Buttons = dataIntProperty(data, "buttons") + event.Button, _ = dataIntProperty(data, "button") + event.Buttons, _ = dataIntProperty(data, "buttons") event.X = dataFloatProperty(data, "x") event.Y = dataFloatProperty(data, "y") event.ClientX = dataFloatProperty(data, "clientX") diff --git a/pointerEvents.go b/pointerEvents.go index 60a6020..b97e261 100644 --- a/pointerEvents.go +++ b/pointerEvents.go @@ -277,7 +277,7 @@ func pointerEventsHtml(view View, buffer *strings.Builder) { func (event *PointerEvent) init(data DataObject) { event.MouseEvent.init(data) - event.PointerID = dataIntProperty(data, "pointerId") + event.PointerID, _ = dataIntProperty(data, "pointerId") event.Width = dataFloatProperty(data, "width") event.Height = dataFloatProperty(data, "height") event.Pressure = dataFloatProperty(data, "pressure") diff --git a/propertySet.go b/propertySet.go index accd8b3..492f762 100644 --- a/propertySet.go +++ b/propertySet.go @@ -55,6 +55,7 @@ var boolProperties = []string{ Loop, Muted, AnimationPaused, + Multiple, } var intProperties = []string{ diff --git a/utils.go b/utils.go index 4a10ec3..8fad6c3 100644 --- a/utils.go +++ b/utils.go @@ -52,13 +52,13 @@ func GetLocalIP() string { return "localhost" } -func dataIntProperty(data DataObject, tag string) int { +func dataIntProperty(data DataObject, tag string) (int, bool) { if value, ok := data.PropertyValue(tag); ok { if n, err := strconv.Atoi(value); err == nil { - return n + return n, true } } - return 0 + return 0, false } func dataBoolProperty(data DataObject, tag string) bool { diff --git a/viewByID.go b/viewByID.go index 074bf1e..91a2927 100644 --- a/viewByID.go +++ b/viewByID.go @@ -36,7 +36,7 @@ func viewByID(rootView ParanetView, id string) View { } // ViewsContainerByID return a ViewsContainer with id equal to the argument of the function or -// nil if there is no such View or View is not ViewsContainer +// nil if there is no such View or View is not ViewsContainer func ViewsContainerByID(rootView View, id string) ViewsContainer { if view := ViewByID(rootView, id); view != nil { if list, ok := view.(ViewsContainer); ok { @@ -48,7 +48,7 @@ func ViewsContainerByID(rootView View, id string) ViewsContainer { } // ListLayoutByID return a ListLayout with id equal to the argument of the function or -// nil if there is no such View or View is not ListLayout +// nil if there is no such View or View is not ListLayout func ListLayoutByID(rootView View, id string) ListLayout { if view := ViewByID(rootView, id); view != nil { if list, ok := view.(ListLayout); ok { @@ -60,7 +60,7 @@ func ListLayoutByID(rootView View, id string) ListLayout { } // StackLayoutByID return a StackLayout with id equal to the argument of the function or -// nil if there is no such View or View is not StackLayout +// nil if there is no such View or View is not StackLayout func StackLayoutByID(rootView View, id string) StackLayout { if view := ViewByID(rootView, id); view != nil { if list, ok := view.(StackLayout); ok { @@ -72,7 +72,7 @@ func StackLayoutByID(rootView View, id string) StackLayout { } // GridLayoutByID return a GridLayout with id equal to the argument of the function or -// nil if there is no such View or View is not GridLayout +// nil if there is no such View or View is not GridLayout func GridLayoutByID(rootView View, id string) GridLayout { if view := ViewByID(rootView, id); view != nil { if list, ok := view.(GridLayout); ok { @@ -84,7 +84,7 @@ func GridLayoutByID(rootView View, id string) GridLayout { } // ColumnLayoutByID return a ColumnLayout with id equal to the argument of the function or -// nil if there is no such View or View is not ColumnLayout +// nil if there is no such View or View is not ColumnLayout func ColumnLayoutByID(rootView View, id string) ColumnLayout { if view := ViewByID(rootView, id); view != nil { if list, ok := view.(ColumnLayout); ok { @@ -96,7 +96,7 @@ func ColumnLayoutByID(rootView View, id string) ColumnLayout { } // DetailsViewByID return a ColumnLayout with id equal to the argument of the function or -// nil if there is no such View or View is not DetailsView +// nil if there is no such View or View is not DetailsView func DetailsViewByID(rootView View, id string) DetailsView { if view := ViewByID(rootView, id); view != nil { if details, ok := view.(DetailsView); ok { @@ -108,7 +108,7 @@ func DetailsViewByID(rootView View, id string) DetailsView { } // DropDownListByID return a DropDownListView with id equal to the argument of the function or -// nil if there is no such View or View is not DropDownListView +// nil if there is no such View or View is not DropDownListView func DropDownListByID(rootView View, id string) DropDownList { if view := ViewByID(rootView, id); view != nil { if list, ok := view.(DropDownList); ok { @@ -120,7 +120,7 @@ func DropDownListByID(rootView View, id string) DropDownList { } // TabsLayoutByID return a TabsLayout with id equal to the argument of the function or -// nil if there is no such View or View is not TabsLayout +// nil if there is no such View or View is not TabsLayout func TabsLayoutByID(rootView View, id string) TabsLayout { if view := ViewByID(rootView, id); view != nil { if list, ok := view.(TabsLayout); ok { @@ -132,7 +132,7 @@ func TabsLayoutByID(rootView View, id string) TabsLayout { } // ListViewByID return a ListView with id equal to the argument of the function or -// nil if there is no such View or View is not ListView +// nil if there is no such View or View is not ListView func ListViewByID(rootView View, id string) ListView { if view := ViewByID(rootView, id); view != nil { if list, ok := view.(ListView); ok { @@ -144,7 +144,7 @@ func ListViewByID(rootView View, id string) ListView { } // TextViewByID return a TextView with id equal to the argument of the function or -// nil if there is no such View or View is not TextView +// nil if there is no such View or View is not TextView func TextViewByID(rootView View, id string) TextView { if view := ViewByID(rootView, id); view != nil { if text, ok := view.(TextView); ok { @@ -156,7 +156,7 @@ func TextViewByID(rootView View, id string) TextView { } // ButtonByID return a Button with id equal to the argument of the function or -// nil if there is no such View or View is not Button +// nil if there is no such View or View is not Button func ButtonByID(rootView View, id string) Button { if view := ViewByID(rootView, id); view != nil { if button, ok := view.(Button); ok { @@ -168,7 +168,7 @@ func ButtonByID(rootView View, id string) Button { } // CheckboxByID return a Checkbox with id equal to the argument of the function or -// nil if there is no such View or View is not Checkbox +// nil if there is no such View or View is not Checkbox func CheckboxByID(rootView View, id string) Checkbox { if view := ViewByID(rootView, id); view != nil { if checkbox, ok := view.(Checkbox); ok { @@ -180,7 +180,7 @@ func CheckboxByID(rootView View, id string) Checkbox { } // EditViewByID return a EditView with id equal to the argument of the function or -// nil if there is no such View or View is not EditView +// nil if there is no such View or View is not EditView func EditViewByID(rootView View, id string) EditView { if view := ViewByID(rootView, id); view != nil { if buttons, ok := view.(EditView); ok { @@ -192,7 +192,7 @@ func EditViewByID(rootView View, id string) EditView { } // ProgressBarByID return a ProgressBar with id equal to the argument of the function or -// nil if there is no such View or View is not ProgressBar +// nil if there is no such View or View is not ProgressBar func ProgressBarByID(rootView View, id string) ProgressBar { if view := ViewByID(rootView, id); view != nil { if buttons, ok := view.(ProgressBar); ok { @@ -203,8 +203,20 @@ func ProgressBarByID(rootView View, id string) ProgressBar { return nil } +// ColorPickerByID return a ColorPicker with id equal to the argument of the function or +// nil if there is no such View or View is not ColorPicker +func ColorPickerByID(rootView View, id string) ColorPicker { + if view := ViewByID(rootView, id); view != nil { + if input, ok := view.(ColorPicker); ok { + return input + } + ErrorLog(`ColorPickerByID(_, "` + id + `"): The found View is not ColorPicker`) + } + return nil +} + // NumberPickerByID return a NumberPicker with id equal to the argument of the function or -// nil if there is no such View or View is not NumberPicker +// nil if there is no such View or View is not NumberPicker func NumberPickerByID(rootView View, id string) NumberPicker { if view := ViewByID(rootView, id); view != nil { if input, ok := view.(NumberPicker); ok { @@ -215,8 +227,44 @@ func NumberPickerByID(rootView View, id string) NumberPicker { return nil } +// TimePickerByID return a TimePicker with id equal to the argument of the function or +// nil if there is no such View or View is not TimePicker +func TimePickerByID(rootView View, id string) TimePicker { + if view := ViewByID(rootView, id); view != nil { + if input, ok := view.(TimePicker); ok { + return input + } + ErrorLog(`TimePickerByID(_, "` + id + `"): The found View is not TimePicker`) + } + return nil +} + +// DatePickerByID return a DatePicker with id equal to the argument of the function or +// nil if there is no such View or View is not DatePicker +func DatePickerByID(rootView View, id string) DatePicker { + if view := ViewByID(rootView, id); view != nil { + if input, ok := view.(DatePicker); ok { + return input + } + ErrorLog(`DatePickerByID(_, "` + id + `"): The found View is not DatePicker`) + } + return nil +} + +// FilePickerByID return a FilePicker with id equal to the argument of the function or +// nil if there is no such View or View is not FilePicker +func FilePickerByID(rootView View, id string) FilePicker { + if view := ViewByID(rootView, id); view != nil { + if input, ok := view.(FilePicker); ok { + return input + } + ErrorLog(`FilePickerByID(_, "` + id + `"): The found View is not FilePicker`) + } + return nil +} + // CanvasViewByID return a CanvasView with id equal to the argument of the function or -// nil if there is no such View or View is not CanvasView +// nil if there is no such View or View is not CanvasView func CanvasViewByID(rootView View, id string) CanvasView { if view := ViewByID(rootView, id); view != nil { if canvas, ok := view.(CanvasView); ok { @@ -229,7 +277,7 @@ func CanvasViewByID(rootView View, id string) CanvasView { /* // TableViewByID return a TableView with id equal to the argument of the function or -// nil if there is no such View or View is not TableView +// nil if there is no such View or View is not TableView func TableViewByID(rootView View, id string) TableView { if view := ViewByID(rootView, id); view != nil { if canvas, ok := view.(TableView); ok { @@ -242,7 +290,7 @@ func TableViewByID(rootView View, id string) TableView { */ // AudioPlayerByID return a AudioPlayer with id equal to the argument of the function or -// nil if there is no such View or View is not AudioPlayer +// nil if there is no such View or View is not AudioPlayer func AudioPlayerByID(rootView View, id string) AudioPlayer { if view := ViewByID(rootView, id); view != nil { if canvas, ok := view.(AudioPlayer); ok { @@ -254,7 +302,7 @@ func AudioPlayerByID(rootView View, id string) AudioPlayer { } // VideoPlayerByID return a VideoPlayer with id equal to the argument of the function or -// nil if there is no such View or View is not VideoPlayer +// nil if there is no such View or View is not VideoPlayer func VideoPlayerByID(rootView View, id string) VideoPlayer { if view := ViewByID(rootView, id); view != nil { if canvas, ok := view.(VideoPlayer); ok { diff --git a/viewFactory.go b/viewFactory.go index f30f63e..e2a25a2 100644 --- a/viewFactory.go +++ b/viewFactory.go @@ -24,6 +24,7 @@ var viewCreators = map[string]func(Session) View{ "ColorPicker": newColorPicker, "DatePicker": newDatePicker, "TimePicker": newTimePicker, + "FilePicker": newFilePicker, "EditView": newEditView, "ListView": newListView, "CanvasView": newCanvasView, diff --git a/viewUtils.go b/viewUtils.go index 77fa9c6..f59b84d 100644 --- a/viewUtils.go +++ b/viewUtils.go @@ -11,8 +11,8 @@ func Get(rootView View, viewID, tag string) interface{} { } // Set sets the property with name "tag" of the "rootView" subview with "viewID" id by value. Result: -// true - success, -// false - error (incompatible type or invalid format of a string value, see AppLog). +// true - success, +// false - error (incompatible type or invalid format of a string value, see AppLog). func Set(rootView View, viewID, tag string, value interface{}) bool { if view := ViewByID(rootView, viewID); view != nil { return view.Set(tag, value) @@ -21,8 +21,8 @@ func Set(rootView View, viewID, tag string, value interface{}) bool { } // SetParams sets properties with name "tag" of the "rootView" subview. Result: -// true - all properties were set successful, -// false - error (incompatible type or invalid format of a string value, see AppLog). +// true - all properties were set successful, +// false - error (incompatible type or invalid format of a string value, see AppLog). func SetParams(rootView View, viewID string, params Params) bool { view := ViewByID(rootView, viewID) if view == nil { @@ -678,8 +678,8 @@ func GetTextTransform(view View, subviewID string) int { } // GetWritingMode returns whether lines of text are laid out horizontally or vertically, as well as -// the direction in which blocks progress. Valid values are HorizontalTopToBottom (0), -// HorizontalBottomToTop (1), VerticalRightToLeft (2) and VerticalLeftToRight (3) +// the direction in which blocks progress. Valid values are HorizontalTopToBottom (0), +// HorizontalBottomToTop (1), VerticalRightToLeft (2) and VerticalLeftToRight (3) // If the second argument (subviewID) is "" then a value from the first argument (view) is returned. func GetWritingMode(view View, subviewID string) int { if subviewID != "" { @@ -697,7 +697,7 @@ func GetWritingMode(view View, subviewID string) int { } // GetTextDirection - returns a direction of text, table columns, and horizontal overflow. -// Valid values are Inherit (0), LeftToRightDirection (1), and RightToLeftDirection (2). +// Valid values are Inherit (0), LeftToRightDirection (1), and RightToLeftDirection (2). // If the second argument (subviewID) is "" then a value from the first argument (view) is returned. func GetTextDirection(view View, subviewID string) int { if subviewID != "" { @@ -716,8 +716,8 @@ func GetTextDirection(view View, subviewID string) int { } // GetVerticalTextOrientation returns a orientation of the text characters in a line. It only affects text -// in vertical mode (when "writing-mode" is "vertical-right-to-left" or "vertical-left-to-right"). -// Valid values are MixedTextOrientation (0), UprightTextOrientation (1), and SidewaysTextOrientation (2). +// in vertical mode (when "writing-mode" is "vertical-right-to-left" or "vertical-left-to-right"). +// Valid values are MixedTextOrientation (0), UprightTextOrientation (1), and SidewaysTextOrientation (2). // If the second argument (subviewID) is "" then a value from the first argument (view) is returned. func GetVerticalTextOrientation(view View, subviewID string) int { if subviewID != "" { @@ -804,8 +804,8 @@ func GetPerspectiveOrigin(view View, subviewID string) (SizeUnit, SizeUnit) { // GetBackfaceVisible returns a bool property that sets whether the back face of an element is // visible when turned towards the user. Values: -// true - the back face is visible when turned towards the user (default value). -// false - the back face is hidden, effectively making the element invisible when turned away from the user. +// true - the back face is visible when turned towards the user (default value). +// false - the back face is hidden, effectively making the element invisible when turned away from the user. // If the second argument (subviewID) is "" then a value from the first argument (view) is returned. func GetBackfaceVisible(view View, subviewID string) bool { if subviewID != "" {