Add RunTransactions() approach to application design
This commit is contained in:
1 parent
7b5603eb11
commit
78aaf72e84
9 files changed
+804
-24
No files matched your search
@@ -0,0 +1,96 @@
|
||||
// This file is part of https://github.com/racingmars/go3270/
|
||||
// Copyright 2025 by Matthew R. Wilson, licensed under the MIT license. See
|
||||
// LICENSE in the project root for license information.
|
||||
|
||||
// This example demonstrates using the RunTransactions() approach to
|
||||
// structuring a go3270 application.
|
||||
|
||||
// This file contains a mock in-memory database for the purposes of our
|
||||
// example application.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Username string
|
||||
Password string
|
||||
Name string
|
||||
SignupDate time.Time
|
||||
}
|
||||
|
||||
type DB interface {
|
||||
GetUser(username string) (User, error)
|
||||
CreateUser(user User) (User, error)
|
||||
UpdateUser(user User) (User, error)
|
||||
}
|
||||
|
||||
type dbstate struct {
|
||||
lock sync.Mutex
|
||||
users map[string]User
|
||||
}
|
||||
|
||||
func Connect() DB {
|
||||
return &dbstate{
|
||||
users: make(map[string]User),
|
||||
}
|
||||
}
|
||||
|
||||
var ErrUserExists = errors.New("username already exists")
|
||||
var ErrNotFound = errors.New("record not found")
|
||||
|
||||
func (db *dbstate) GetUser(username string) (User, error) {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
user, ok := db.users[username]
|
||||
if !ok {
|
||||
return User{}, ErrNotFound
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (db *dbstate) CreateUser(user User) (User, error) {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
if _, ok := db.users[user.Username]; ok {
|
||||
return User{}, ErrUserExists
|
||||
}
|
||||
|
||||
user.SignupDate = time.Now().UTC().Truncate(time.Second)
|
||||
|
||||
// Obviously in a real application you wouldn't store plaintext password,
|
||||
// but this is a simple example for the purposes of demonstrating go3270,
|
||||
// not demonstrating how to build a secure application.
|
||||
db.users[user.Username] = user
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (db *dbstate) UpdateUser(user User) (User, error) {
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
olduser, ok := db.users[user.Username]
|
||||
if !ok {
|
||||
return User{}, ErrNotFound
|
||||
}
|
||||
|
||||
// Make sure original signup date is maintained
|
||||
user.SignupDate = olduser.SignupDate
|
||||
|
||||
// Carry over existing password if we aren't setting a new password
|
||||
if user.Password == "" {
|
||||
user.Password = olduser.Password
|
||||
}
|
||||
|
||||
db.users[user.Username] = user
|
||||
|
||||
return user, nil
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// This file is part of https://github.com/racingmars/go3270/
|
||||
// Copyright 2025 by Matthew R. Wilson, licensed under the MIT license. See
|
||||
// LICENSE in the project root for license information.
|
||||
|
||||
// This example demonstrates using the RunTransactions() approach to
|
||||
// structuring a go3270 application.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/racingmars/go3270"
|
||||
)
|
||||
|
||||
type global struct {
|
||||
usercount int // number of connected users
|
||||
countlock sync.Mutex
|
||||
}
|
||||
|
||||
// session is the structure which holds global state for a user session of our
|
||||
// application. The various Transactions will be methods on this struct.
|
||||
type session struct {
|
||||
db DB
|
||||
user User
|
||||
|
||||
// We can also share global state between users
|
||||
g *global
|
||||
}
|
||||
|
||||
func main() {
|
||||
// "Connect" to our database (for this example, it's just a mock in-memory
|
||||
// database).
|
||||
dbconn := Connect()
|
||||
|
||||
gblstate := new(global)
|
||||
|
||||
ln, err := net.Listen("tcp", ":3270")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("LISTENING ON PORT 3270 FOR CONNECTIONS")
|
||||
fmt.Println("Press Ctrl-C to end server.")
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go handle(conn, dbconn, gblstate)
|
||||
}
|
||||
}
|
||||
|
||||
// handle is the handler for individual user connections.
|
||||
func handle(conn net.Conn, db DB, gblstate *global) {
|
||||
defer conn.Close()
|
||||
|
||||
// Create the state for this client's connection
|
||||
state := session{
|
||||
db: db,
|
||||
g: gblstate,
|
||||
}
|
||||
|
||||
// Add to the global user counter
|
||||
gblstate.countlock.Lock()
|
||||
gblstate.usercount++
|
||||
gblstate.countlock.Unlock()
|
||||
|
||||
// When the session ends, reduce the user count
|
||||
defer func() {
|
||||
gblstate.countlock.Lock()
|
||||
gblstate.usercount--
|
||||
gblstate.countlock.Unlock()
|
||||
}()
|
||||
|
||||
// Always begin new connection by negotiating the telnet options
|
||||
go3270.NegotiateTelnet(conn)
|
||||
err := go3270.RunTransactions(conn, state.login, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// This file is part of https://github.com/racingmars/go3270/
|
||||
// Copyright 2025 by Matthew R. Wilson, licensed under the MIT license. See
|
||||
// LICENSE in the project root for license information.
|
||||
|
||||
// This example demonstrates using the RunTransactions() approach to
|
||||
// structuring a go3270 application.
|
||||
|
||||
// This file contains the help transaction generator.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/racingmars/go3270"
|
||||
)
|
||||
|
||||
var helpScreen = go3270.Screen{
|
||||
{Row: 0, Col: 45, Intense: true, Content: "Online Help"},
|
||||
|
||||
{Row: 2, Col: 0,
|
||||
Content: "This help screen is an example of a transaction that"},
|
||||
{Row: 3, Col: 0,
|
||||
Content: "can be used as the next transaction from many other transactions"},
|
||||
{Row: 4, Col: 0, Content: "and returns to the transaction that called it."},
|
||||
|
||||
{Row: 6, Col: 0, Content: "Press"},
|
||||
{Row: 6, Col: 6, Content: "PF3", Color: go3270.White, Intense: true},
|
||||
{Row: 6, Col: 10,
|
||||
Content: "to return to the transaction from which you came."},
|
||||
|
||||
// Error message
|
||||
{Row: 21, Col: 0, Name: "errormsg", Color: go3270.Red, Intense: true},
|
||||
|
||||
// Key legend
|
||||
{Row: 23, Col: 1, Content: "F3=Exit"},
|
||||
}
|
||||
|
||||
// The help transaction is an example of returning a closure over a parameter,
|
||||
// in this case the desired next transaction. This allows us to use the same
|
||||
// transaction from multiple other transactions and return to the requested
|
||||
// transaction when the user leaves the help transaction.
|
||||
//
|
||||
// Any data passed to help will be passed back to the transaction it returns
|
||||
// to.
|
||||
//
|
||||
// The help transaction has no need for any global or session state, so this
|
||||
// generator function for it is a stand-alone function and not a method on the
|
||||
// session.
|
||||
func help(returnTransaction go3270.Tx) go3270.Tx {
|
||||
return func(conn net.Conn, data any) (go3270.Tx, any, error) {
|
||||
_, err := go3270.HandleScreen(
|
||||
helpScreen, // the screen to display
|
||||
nil, // (no) rules to enforce
|
||||
nil, // pre-populated values in fields
|
||||
[]go3270.AID{go3270.AIDPF3}, // keys we accept
|
||||
nil,
|
||||
"errormsg", // name of field to put error messages in
|
||||
23, 79, // cursor coordinates
|
||||
conn)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Any accepted key returns to the requested returnTransaction
|
||||
return returnTransaction, data, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
// This file is part of https://github.com/racingmars/go3270/
|
||||
// Copyright 2025 by Matthew R. Wilson, licensed under the MIT license. See
|
||||
// LICENSE in the project root for license information.
|
||||
|
||||
// This example demonstrates using the RunTransactions() approach to
|
||||
// structuring a go3270 application.
|
||||
|
||||
// This file contains the login and user registration transactions.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/racingmars/go3270"
|
||||
)
|
||||
|
||||
const loginUsername = "username"
|
||||
const loginPassword = "password"
|
||||
const loginPasswordConf = "password confirmation"
|
||||
const loginName = "name"
|
||||
const loginErr = "errormsg"
|
||||
|
||||
var loginScreen = go3270.Screen{
|
||||
{Row: 0, Col: 37, Intense: true, Content: "Logon"},
|
||||
{Row: 2, Col: 0,
|
||||
Content: "Welcome to the go3270 example application. Please log on."},
|
||||
|
||||
// Username
|
||||
{Row: 4, Col: 0, Content: "Username . . .", Color: go3270.Green},
|
||||
{Row: 4, Col: 15, Name: loginUsername, Write: true,
|
||||
Highlighting: go3270.Underscore, Color: go3270.Turquoise},
|
||||
{Row: 4, Col: 24, Autoskip: true}, // field "stop" character
|
||||
|
||||
// Password
|
||||
{Row: 5, Col: 0, Content: "Password . . .", Color: go3270.Green},
|
||||
{Row: 5, Col: 15, Name: loginPassword, Write: true, Hidden: true},
|
||||
{Row: 5, Col: 79}, // field "stop" character
|
||||
|
||||
// Registration instructions
|
||||
{Row: 7, Col: 0, Content: "If you don't yet have an account, press"},
|
||||
{Row: 7, Col: 40, Content: "PF5", Color: go3270.White, Intense: true},
|
||||
{Row: 7, Col: 44, Content: "to register a new account."},
|
||||
|
||||
// Error message
|
||||
{Row: 21, Col: 0, Name: loginErr, Color: go3270.Red, Intense: true},
|
||||
|
||||
// Key legend
|
||||
{Row: 23, Col: 1, Content: "F1=Help"},
|
||||
{Row: 23, Col: 14, Content: "F3=Exit"},
|
||||
{Row: 23, Col: 27, Content: "F5=Register"},
|
||||
}
|
||||
|
||||
var loginScreenRules = go3270.Rules{
|
||||
loginUsername: {Validator: go3270.NonBlank},
|
||||
loginPassword: {Validator: go3270.NonBlank, Reset: true},
|
||||
}
|
||||
|
||||
// login transaction accepts a string value in data if the login screen
|
||||
// should be initialized with an error message.
|
||||
func (sess *session) login(conn net.Conn, data any) (go3270.Tx, any, error) {
|
||||
|
||||
fieldValues := make(map[string]string)
|
||||
|
||||
if data != nil {
|
||||
if errmsg, ok := data.(string); ok {
|
||||
fieldValues[loginErr] = errmsg
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := go3270.HandleScreen(
|
||||
loginScreen, // the screen to display
|
||||
loginScreenRules, // the rules to enforce
|
||||
fieldValues, // pre-populated values in fields
|
||||
[]go3270.AID{go3270.AIDEnter}, // keys we accept -- validating
|
||||
[]go3270.AID{ // keys we accept -- non-validating
|
||||
go3270.AIDPF1,
|
||||
go3270.AIDPF3,
|
||||
go3270.AIDPF5,
|
||||
go3270.AIDClear},
|
||||
loginErr, // name of field to put error messages in
|
||||
4, 16, // cursor coordinates
|
||||
conn)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch resp.AID {
|
||||
case go3270.AIDClear:
|
||||
// re-run the transaction with empty values
|
||||
return sess.login, nil, nil
|
||||
case go3270.AIDPF1:
|
||||
// Display help transaction, then return to this transaction
|
||||
return help(sess.login), nil, nil
|
||||
case go3270.AIDPF3:
|
||||
// User wants to quit; return no next transaction
|
||||
return nil, nil, nil
|
||||
case go3270.AIDPF5:
|
||||
// Send user to registration transaction
|
||||
return sess.newuser, nil, nil
|
||||
}
|
||||
|
||||
// If we didn't get one of the other allowed keys, try to log the user in.
|
||||
username := resp.Values[loginUsername]
|
||||
password := resp.Values[loginPassword]
|
||||
|
||||
user, err := sess.db.GetUser(username)
|
||||
// NOTE: this is just an example application. Obviously in a real
|
||||
// application, password hashing would be in place.
|
||||
if err != nil || user.Password != password {
|
||||
// If we couldn't get the username from the DB, or if the password
|
||||
// doesn't match, re-run the login transaction with an error
|
||||
// message displayed.
|
||||
|
||||
return sess.login, "Username or password not valid.", nil
|
||||
}
|
||||
|
||||
// Login was successful. Update the session state to the logged-in user.
|
||||
sess.user = user
|
||||
|
||||
return sess.mainmenu, nil, nil
|
||||
}
|
||||
|
||||
var newuserScreen = go3270.Screen{
|
||||
{Row: 0, Col: 30, Intense: true, Content: "New User Registration"},
|
||||
{Row: 2, Col: 0,
|
||||
Content: "Please provide your user registration details."},
|
||||
|
||||
// Username
|
||||
{Row: 4, Col: 0, Content: "Username . . .", Color: go3270.Green},
|
||||
{Row: 4, Col: 15, Name: loginUsername, Write: true,
|
||||
Highlighting: go3270.Underscore, Color: go3270.Turquoise},
|
||||
{Row: 4, Col: 24, Autoskip: true}, // field "stop" character
|
||||
|
||||
// Password
|
||||
{Row: 5, Col: 0, Content: "Password . . .", Color: go3270.Green},
|
||||
{Row: 5, Col: 15, Name: loginPassword, Write: true, Hidden: true},
|
||||
{Row: 5, Col: 79, Autoskip: true}, // field "stop" character
|
||||
|
||||
// Password
|
||||
{Row: 6, Col: 0, Content: "Confirm Pass .", Color: go3270.Green},
|
||||
{Row: 6, Col: 15, Name: loginPasswordConf, Write: true, Hidden: true},
|
||||
{Row: 6, Col: 79, Autoskip: true}, // field "stop" character
|
||||
|
||||
// Name
|
||||
{Row: 7, Col: 0, Content: "Name . . . . .", Color: go3270.Green},
|
||||
{Row: 7, Col: 15, Name: loginName, Write: true,
|
||||
Highlighting: go3270.Underscore, Color: go3270.Turquoise},
|
||||
{Row: 7, Col: 46, Autoskip: true}, // field "stop" character
|
||||
|
||||
// Error message
|
||||
{Row: 21, Col: 0, Name: loginErr, Color: go3270.Red, Intense: true},
|
||||
|
||||
// Key legend
|
||||
{Row: 23, Col: 1, Content: "F1=Help"},
|
||||
{Row: 23, Col: 14, Content: "F3=Exit"},
|
||||
}
|
||||
|
||||
var newuserScreenRules = go3270.Rules{
|
||||
loginUsername: {Validator: go3270.NonBlank},
|
||||
loginPassword: {Validator: go3270.NonBlank, Reset: true},
|
||||
loginPasswordConf: {Validator: go3270.NonBlank, Reset: true},
|
||||
}
|
||||
|
||||
// newuserData is the data that can be passed into the newuser transaction
|
||||
type newuserData struct {
|
||||
username string
|
||||
name string
|
||||
errmsg string
|
||||
}
|
||||
|
||||
func (sess *session) newuser(conn net.Conn, data any) (go3270.Tx, any, error) {
|
||||
|
||||
fieldValues := make(map[string]string)
|
||||
|
||||
if data != nil {
|
||||
if newdata, ok := data.(newuserData); ok {
|
||||
fieldValues[loginUsername] = newdata.username
|
||||
fieldValues[loginName] = newdata.name
|
||||
fieldValues[loginErr] = newdata.errmsg
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := go3270.HandleScreen(
|
||||
newuserScreen, // the screen to display
|
||||
newuserScreenRules, // the rules to enforce
|
||||
fieldValues, // pre-populated values in fields
|
||||
[]go3270.AID{go3270.AIDEnter}, // keys we accept -- validating
|
||||
[]go3270.AID{ // keys we accept -- non-validating
|
||||
go3270.AIDPF1,
|
||||
go3270.AIDPF3,
|
||||
go3270.AIDClear,
|
||||
},
|
||||
loginErr, // name of field to put error messages in
|
||||
4, 16, // cursor coordinates
|
||||
conn)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch resp.AID {
|
||||
case go3270.AIDClear:
|
||||
// Re-run transaction with empty values
|
||||
return sess.newuser, nil, nil
|
||||
case go3270.AIDPF1:
|
||||
// Display help screen, returning to this transaction after
|
||||
return help(sess.newuser), nil, nil
|
||||
case go3270.AIDPF3:
|
||||
// User wants to cancel registration, go back to login screen.
|
||||
return sess.login, nil, nil
|
||||
}
|
||||
|
||||
// Otherwise, we'll try creating the user.
|
||||
|
||||
username := resp.Values[loginUsername]
|
||||
password := resp.Values[loginPassword]
|
||||
passwordConf := resp.Values[loginPasswordConf]
|
||||
name := resp.Values[loginName]
|
||||
|
||||
if password != passwordConf {
|
||||
// Re-run the newuser transaction with an error message
|
||||
return sess.newuser, newuserData{
|
||||
username: username,
|
||||
name: name,
|
||||
errmsg: "Passwords do not match.",
|
||||
}, nil
|
||||
}
|
||||
|
||||
user, err := sess.db.CreateUser(User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Name: name,
|
||||
})
|
||||
|
||||
if err == ErrUserExists {
|
||||
// Re-run the newuser transaction with an error message
|
||||
return sess.newuser, newuserData{
|
||||
username: username,
|
||||
name: name,
|
||||
errmsg: "Username already exists; please choose a new one.",
|
||||
}, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return sess.newuser, newuserData{
|
||||
username: username,
|
||||
name: name,
|
||||
errmsg: "Unknown error creating new user.",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Success! We'll stick the new user in the session state like login does
|
||||
// and go to the main menu.
|
||||
sess.user = user
|
||||
|
||||
return sess.mainmenu, nil, nil
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// This file is part of https://github.com/racingmars/go3270/
|
||||
// Copyright 2025 by Matthew R. Wilson, licensed under the MIT license. See
|
||||
// LICENSE in the project root for license information.
|
||||
|
||||
// This example demonstrates using the RunTransactions() approach to
|
||||
// structuring a go3270 application.
|
||||
|
||||
// This file contains the main menu and the example placeholder features
|
||||
// of the application.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/racingmars/go3270"
|
||||
)
|
||||
|
||||
const mainmenuOption = "option"
|
||||
const mainmenuUsername = "username"
|
||||
const mainmenuTime = "time"
|
||||
const mainmenuUsers = "users"
|
||||
const mainmenuError = "errormsg"
|
||||
|
||||
var mainmenuScreen = go3270.Screen{
|
||||
{Row: 0, Col: 31, Intense: true, Content: "Main Menu"},
|
||||
|
||||
// Option
|
||||
{Row: 1, Col: 0, Content: "Option ===>", Color: go3270.Green},
|
||||
{Row: 1, Col: 12, Name: mainmenuOption, Write: true,
|
||||
Highlighting: go3270.Underscore, Color: go3270.Turquoise},
|
||||
{Row: 1, Col: 79, Autoskip: true}, // field "stop" character
|
||||
|
||||
// Info
|
||||
{Row: 3, Col: 57, Content: "User ID . :", Color: go3270.Green},
|
||||
{Row: 3, Col: 69, Name: mainmenuUsername, Color: go3270.Turquoise},
|
||||
{Row: 4, Col: 57, Content: "Time. . . :", Color: go3270.Green},
|
||||
{Row: 4, Col: 69, Name: mainmenuTime, Color: go3270.Turquoise},
|
||||
{Row: 5, Col: 57, Content: "Users . . :", Color: go3270.Green},
|
||||
{Row: 5, Col: 69, Name: mainmenuUsers, Color: go3270.Turquoise},
|
||||
|
||||
// Options
|
||||
{Row: 3, Col: 0, Content: "1", Color: go3270.White},
|
||||
{Row: 3, Col: 3, Content: "Feature 1",
|
||||
Color: go3270.Turquoise, Intense: true},
|
||||
{Row: 3, Col: 17, Content: "A very cool feature", Color: go3270.Green},
|
||||
|
||||
{Row: 4, Col: 0, Content: "2", Color: go3270.White},
|
||||
{Row: 4, Col: 3, Content: "Feature 2",
|
||||
Color: go3270.Turquoise, Intense: true},
|
||||
{Row: 4, Col: 17, Content: "Another neat feature", Color: go3270.Green},
|
||||
|
||||
{Row: 5, Col: 0, Content: "3", Color: go3270.White},
|
||||
{Row: 5, Col: 3, Content: "Feature 3",
|
||||
Color: go3270.Turquoise, Intense: true},
|
||||
{Row: 5, Col: 17, Content: "This one's a boring feature",
|
||||
Color: go3270.Green},
|
||||
|
||||
{Row: 7, Col: 5, Content: "Enter", Color: go3270.Green},
|
||||
{Row: 7, Col: 11, Content: "X", Color: go3270.Turquoise, Intense: true},
|
||||
{Row: 7, Col: 13, Content: "to log off and exit.", Color: go3270.Green},
|
||||
|
||||
// Error message
|
||||
{Row: 21, Col: 0, Name: mainmenuError, Color: go3270.Red, Intense: true},
|
||||
|
||||
// Key legend
|
||||
{Row: 23, Col: 1, Content: "F1=Help"},
|
||||
{Row: 23, Col: 14, Content: "F3=Exit"},
|
||||
{Row: 23, Col: 27, Content: "F5=Refresh"},
|
||||
}
|
||||
|
||||
type mainmenuData struct {
|
||||
option string // pre-populated option field value
|
||||
errmsg string // error message to display
|
||||
}
|
||||
|
||||
// mainmenu transaction accepts a mainmenuData struct as the data if the
|
||||
// option field or error message should be populated.
|
||||
func (sess *session) mainmenu(conn net.Conn, data any) (go3270.Tx, any, error) {
|
||||
|
||||
fieldValues := make(map[string]string)
|
||||
|
||||
if data != nil {
|
||||
if d, ok := data.(mainmenuData); ok {
|
||||
fieldValues[mainmenuOption] = d.option
|
||||
fieldValues[mainmenuError] = d.errmsg
|
||||
}
|
||||
}
|
||||
|
||||
fieldValues[mainmenuUsername] = strings.ToUpper(sess.user.Username)
|
||||
fieldValues[mainmenuTime] = time.Now().UTC().Format("15:04:05")
|
||||
fieldValues[mainmenuUsers] = strconv.Itoa(sess.g.usercount)
|
||||
|
||||
resp, err := go3270.HandleScreen(
|
||||
mainmenuScreen, // the screen to display
|
||||
nil, // (no) rules to enforce
|
||||
fieldValues, // pre-populated values in fields
|
||||
[]go3270.AID{go3270.AIDEnter}, // keys we accept -- validating
|
||||
[]go3270.AID{ // keys we accept -- non-validating
|
||||
go3270.AIDPF1,
|
||||
go3270.AIDPF3,
|
||||
go3270.AIDPF5,
|
||||
},
|
||||
mainmenuError, // name of field to put error messages in
|
||||
1, 13, // cursor coordinates
|
||||
conn)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch resp.AID {
|
||||
case go3270.AIDPF1:
|
||||
// Display help, then return to this transaction
|
||||
return help(sess.mainmenu), nil, nil
|
||||
case go3270.AIDPF3:
|
||||
// Exit
|
||||
return nil, nil, nil
|
||||
case go3270.AIDPF5:
|
||||
// Refresh
|
||||
return sess.mainmenu, nil, nil
|
||||
}
|
||||
|
||||
// Handle the available options:
|
||||
switch resp.Values[mainmenuOption] {
|
||||
case "1":
|
||||
return sess.exampleFeature,
|
||||
"This is feature 1, a very cool feature.", nil
|
||||
case "2":
|
||||
return sess.exampleFeature,
|
||||
"This is feature 2, another neat feature.", nil
|
||||
case "3":
|
||||
return sess.exampleFeature,
|
||||
"This is feature 3, which is a boring one.", nil
|
||||
case "x", "X":
|
||||
// As if user hit PF3; exit
|
||||
return nil, nil, nil
|
||||
case "":
|
||||
// As if user hit PF5, refresh
|
||||
return sess.mainmenu, nil, nil
|
||||
default:
|
||||
// Run the transaction again with an error message
|
||||
return sess.mainmenu,
|
||||
mainmenuData{
|
||||
option: resp.Values[mainmenuOption],
|
||||
errmsg: "Unknown option",
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
const featureMessage = "message"
|
||||
|
||||
var exampleScreen = go3270.Screen{
|
||||
{Row: 0, Col: 30, Intense: true, Content: "Application Feature"},
|
||||
|
||||
// Info -- replicate the data shown on the main menu
|
||||
{Row: 3, Col: 57, Content: "User ID . :", Color: go3270.Green},
|
||||
{Row: 3, Col: 69, Name: mainmenuUsername, Color: go3270.Turquoise},
|
||||
{Row: 4, Col: 57, Content: "Time. . . :", Color: go3270.Green},
|
||||
{Row: 4, Col: 69, Name: mainmenuTime, Color: go3270.Turquoise},
|
||||
{Row: 5, Col: 57, Content: "Users . . :", Color: go3270.Green},
|
||||
{Row: 5, Col: 69, Name: mainmenuUsers, Color: go3270.Turquoise},
|
||||
|
||||
// Feature-specific message
|
||||
{Row: 12, Col: 0, Name: featureMessage},
|
||||
|
||||
{Row: 14, Col: 0, Content: "Press"},
|
||||
{Row: 14, Col: 6, Content: "PF3", Intense: true, Color: go3270.White},
|
||||
{Row: 14, Col: 10, Content: "to return to the main menu."},
|
||||
|
||||
// Error message
|
||||
{Row: 21, Col: 0, Name: mainmenuError, Color: go3270.Red, Intense: true},
|
||||
|
||||
// Key legend
|
||||
{Row: 23, Col: 1, Content: "F1=Help"},
|
||||
{Row: 23, Col: 14, Content: "F3=Exit"},
|
||||
{Row: 23, Col: 27, Content: "F5=Refresh"},
|
||||
}
|
||||
|
||||
// exampleFeature is a transaction that will act as a placeholder for real
|
||||
// application functionality. It accepts a string in the data which will
|
||||
// be displayed on the panel.
|
||||
func (sess *session) exampleFeature(conn net.Conn, data any) (go3270.Tx,
|
||||
any, error) {
|
||||
|
||||
fieldValues := make(map[string]string)
|
||||
|
||||
if data != nil {
|
||||
if message, ok := data.(string); ok {
|
||||
fieldValues[featureMessage] = message
|
||||
}
|
||||
}
|
||||
|
||||
fieldValues[mainmenuUsername] = strings.ToUpper(sess.user.Username)
|
||||
fieldValues[mainmenuTime] = time.Now().UTC().Format("15:04:05")
|
||||
fieldValues[mainmenuUsers] = strconv.Itoa(sess.g.usercount)
|
||||
|
||||
resp, err := go3270.HandleScreen(
|
||||
exampleScreen, // the screen to display
|
||||
nil, // (no) rules to enforce
|
||||
fieldValues, // pre-populated values in fields
|
||||
nil, // keys we accept -- validating
|
||||
[]go3270.AID{ // keys we accept -- non-validating
|
||||
go3270.AIDPF1,
|
||||
go3270.AIDPF3,
|
||||
go3270.AIDPF5,
|
||||
},
|
||||
mainmenuError, // name of field to put error messages in
|
||||
23, 79, // cursor coordinates
|
||||
conn)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch resp.AID {
|
||||
case go3270.AIDPF1:
|
||||
// Display help, then return to this transaction
|
||||
return help(sess.exampleFeature), data, nil
|
||||
case go3270.AIDPF3:
|
||||
// Exit
|
||||
return sess.mainmenu, nil, nil
|
||||
case go3270.AIDPF5:
|
||||
// Refresh
|
||||
return sess.exampleFeature, data, nil
|
||||
}
|
||||
|
||||
// ...there shouldn't be any actions not handled above. Just in case,
|
||||
// we'll just re-run this transaction as if refresh was hit.
|
||||
return sess.exampleFeature, data, nil
|
||||
}
|
||||
Reference in new issue
Block a user