Closed MostHated closed 5 years ago
Neither worked and just gave an out of range error
What failed? I have no clue what the error was and what line if crashed. Only gueses:
absIndex := firstRow + i
...
d.data[i] = make([]string, columnInTable, columnInTable)
d.data[i][0] = tmpAssetData[absIndex].AssetCode`
1) Do you have enough items in tmpAssetData
, so len(tmpAssetData)
is firstRow+rowCount-1
?
2) Have you pre-filled tmpAssetData
?
If you set TableView row and col count to the same values(rowCount, colCount), you do not need to write anything for ActionNew
No, sorry, there is nothing in there at the time. I removed the data I created by hand in code. I am trying to find out how to create a new row if there are 0 rows. Pretend it's a brand new application with 0 data or rows. I am trying to figure out how to use "add new row" action to start fresh and add data to an empty dataset.
I see. I'll try this case myself
Here is what I have been attempting to come up with:
I feel like I might be close, but I am just not quite sure.
case ui.TableActionNew:
//action = "Add new row"
c := ev.Col
r := ev.Row
var editVal = TableEdit{Row: r, Col: c}
dlg := CreateEditableDialog(fmt.Sprintf("%s:", TxtNewAssetCodeValue), "") // --- A custom popup I made so I can make more adjustments than the default one had
dlg.View.SetSize(35, 10)
dlg.OnClose(func() {
switch dlg.Result() {
case ui.DialogButton1:
editVal.NewVal = dlg.EditResult()
if tmpAssetData == nil {
var newInfo = ui.ColumnDrawInfo{Row: 0, Col: 0}
cache.data = make([][]string, rowCount, rowCount) // ------ I have this because in preload I have at the beginning if tmpAssetData == nil { return }
(func(info *ui.ColumnDrawInfo) {
cache.data[newInfo.Row][newInfo.Col] = editVal.NewVal // ------------------------------195
info.Text = cache.data[newInfo.Row][newInfo.Col]
})(&newInfo)
//cache.NewValue(0, 0, editVal.NewVal)
} else {
cache.NewValue(len(tmpAssetData), 0, editVal.NewVal)
}
ui.PutEvent(ui.Event{Type: ui.EventRedraw})
}
})
panic: runtime error: index out of range
goroutine 1 [running]:
github.com/instance-id/GoUI/elements.CreateTableDialog.func3.3.1(...)
/home/mosthated/_dev/programming/go/src/github.com/instance-id/GoUI/elements/CreateTableDialog.go:195
github.com/instance-id/GoUI/elements.CreateTableDialog.func3.3()
/home/mosthated/_dev/programming/go/src/github.com/instance-id/GoUI/elements/CreateTableDialog.go:197 +0x22a
----------- This was my whole table page, just to give it some context.
package elements
import (
"fmt"
. "github.com/instance-id/GoUI/text"
. "github.com/instance-id/GoUI/utils"
ui "github.com/instance-id/clui"
term "github.com/nsf/termbox-go"
)
var tmpAssetData []*AssetDetails
type dbCache struct {
firstRow int // previous first visible row
rowCount int // previous visible row count
data [][]string // cache - contains at least 'rowCount' rows from DB
}
const columnInTable = 7
func (d *dbCache) preload(firstRow, rowCount int) {
if tmpAssetData == nil {
return
}
if firstRow == d.firstRow && rowCount == d.rowCount {
// fast path: view area is the same, return immediately
return
}
d.data = make([][]string, rowCount, rowCount)
for i := 0; i < rowCount; i++ {
absIndex := firstRow + i
d.data[i] = make([]string, columnInTable, columnInTable)
d.data[i][0] = tmpAssetData[absIndex].AssetCode
d.data[i][1] = tmpAssetData[absIndex].AssetName
d.data[i][2] = tmpAssetData[absIndex].AssetApiKey
d.data[i][3] = tmpAssetData[absIndex].AssetRole
d.data[i][4] = tmpAssetData[absIndex].AssetVersion
d.data[i][5] = tmpAssetData[absIndex].AssetReplaced
d.data[i][6] = tmpAssetData[absIndex].ReplaceDate
}
// do not forget to save the last values
d.firstRow = firstRow
d.rowCount = rowCount
}
// --- Custom function for editing place ---------------------------------
func (d *dbCache) NewValue(row, col int, newText string) {
d.data[row][col] = newText
}
func (d *dbCache) value(row, col int) string {
rowId := row - d.firstRow
if rowId >= len(d.data) {
return ""
}
rowValues := d.data[rowId]
if col >= len(rowValues) {
return ""
}
return rowValues[col]
}
// --- Window type for data table ----------------------------------------
func CreateTableDialog(btn *ui.ButtonNoShadow, tableTitle string) {
tableDialog := new(TableDialog)
// --- Obtain terminal overall size ----------------------------------
cw, ch := term.Size()
// --- Save current values to temp value until saved -----------------
tmpAssetData = Asset
// --- Create new popup window for table data ------------------------
tableDialog.View = ui.AddWindow(cw/2-75, ch/2-16, ui.AutoSize, ui.AutoSize, TxtAssetDetails)
ui.WindowManager().BeginUpdate()
defer ui.WindowManager().EndUpdate()
tableDialog.View.SetGaps(1, ui.KeepValue)
tableDialog.View.SetModal(true)
tableDialog.View.SetPack(ui.Vertical)
tableDialog.Frame = NewFramedWindowInput(tableDialog.View, "", nil)
// --- Create data table ---------------------------------------------
td := ui.CreateTableView(tableDialog.Frame, 145, 15, 1)
ui.ActivateControl(tableDialog.Frame, td)
cache := &dbCache{firstRow: -1}
rowCount := len(Asset)
td.SetShowLines(true)
td.SetShowRowNumber(true)
td.SetRowCount(rowCount)
cols := []ui.Column{
ui.Column{Title: "Asset Code", Width: 5, Alignment: ui.AlignLeft},
ui.Column{Title: "Asset Name", Width: 50, Alignment: ui.AlignLeft},
ui.Column{Title: "Asset APIKey", Width: 30, Alignment: ui.AlignLeft},
ui.Column{Title: "Asset RoleId", Width: 20, Alignment: ui.AlignLeft},
ui.Column{Title: "Version", Width: 7, Alignment: ui.AlignLeft},
ui.Column{Title: "Replaced?", Width: 10, Alignment: ui.AlignLeft},
ui.Column{Title: "Replace Date", Width: 12, Alignment: ui.AlignLeft},
}
td.SetColumns(cols)
td.OnBeforeDraw(func(col, row, colCnt, rowCnt int) {
cache.preload(row, rowCnt)
l, t, w, h := td.VisibleArea()
tableDialog.Frame.SetTitle(fmt.Sprintf("Caching: %d:%d - %dx%d", l, t, w, h))
})
td.OnDrawCell(func(info *ui.ColumnDrawInfo) {
info.Text = cache.value(info.Row, info.Col)
})
td.OnAction(func(ev ui.TableEvent) {
// btns := []string{TxtApplyBtn, TxtCancelBtn}
//var action string
switch ev.Action {
case ui.TableActionSort:
//action = "Sort table"
case ui.TableActionEdit:
c := ev.Col
r := ev.Row
var newInfo = ui.ColumnDrawInfo{Row: r, Col: c}
var editVal = TableEdit{Row: r, Col: c}
(func(info *ui.ColumnDrawInfo) {
editVal.OldVal = cache.value(info.Row, info.Col)
})(&newInfo)
dlg := CreateEditableDialog(fmt.Sprintf("%s: %s", TxtEditing, editVal.OldVal), editVal.OldVal)
dlg.View.SetSize(35, 10)
dlg.View.BaseControl.SetSize(35, 10)
dlg.OnClose(func() {
switch dlg.Result() {
case ui.DialogButton1:
editVal.NewVal = dlg.EditResult()
cache.NewValue(editVal.Row, editVal.Col, editVal.NewVal)
ui.PutEvent(ui.Event{Type: ui.EventRedraw})
}
})
return
case ui.TableActionNew:
//action = "Add new row"
c := ev.Col
r := ev.Row
var editVal = TableEdit{Row: r, Col: c}
dlg := CreateEditableDialog(fmt.Sprintf("%s:", TxtNewAssetCodeValue), "")
dlg.View.SetSize(35, 10)
dlg.View.BaseControl.SetSize(35, 10)
dlg.OnClose(func() {
switch dlg.Result() {
case ui.DialogButton1:
editVal.NewVal = dlg.EditResult()
if tmpAssetData == nil {
var newInfo = ui.ColumnDrawInfo{Row: 0, Col: 0}
cache.data = make([][]string, rowCount, rowCount)
(func(info *ui.ColumnDrawInfo) {
cache.data[newInfo.Row][newInfo.Col] = editVal.NewVal
info.Text = cache.data[newInfo.Row][newInfo.Col]
})(&newInfo)
} else {
cache.NewValue(len(tmpAssetData), 0, editVal.NewVal)
}
ui.PutEvent(ui.Event{Type: ui.EventRedraw})
}
})
case ui.TableActionDelete:
//action = "Delete row"
default:
//action = "Unknown action"
}
//dlg := ui.CreateConfirmationDialog(
// "<c:blue>"+action,
// "Click any button or press <c:yellow>SPACE<c:> to close the dialog",
// btns, ui.DialogButton1)
//dlg.OnClose(func() {})
})
btnFrame := ui.CreateFrame(tableDialog.Frame, 1, 1, ui.BorderNone, ui.Fixed)
btnFrame.SetPaddings(1, 1)
textFrame := ui.CreateFrame(btnFrame, 1, 1, ui.BorderNone, ui.Fixed)
textFrame.SetPack(ui.Vertical)
ui.CreateLabel(textFrame, ui.AutoSize, ui.AutoSize, TxtInstructs1, ui.Fixed)
ui.CreateLabel(textFrame, ui.AutoSize, ui.AutoSize, TxtInstructs2, ui.Fixed)
ui.CreateLabel(textFrame, ui.AutoSize, ui.AutoSize, TxtInstructs3, ui.Fixed)
// --- Window Controls -----------------------------------------------
ui.CreateFrame(btnFrame, 1, 1, ui.BorderNone, 1)
BtnSave := ui.CreateButton(btnFrame, 15, 1, TxtSaveBtn, ui.Fixed)
BtnSave.OnClick(func(ev ui.Event) {
Asset = tmpAssetData
btn.SetEnabled(true)
})
BtnClose := ui.CreateButton(btnFrame, 15, 1, TxtCloseBtn, ui.Fixed)
BtnClose.OnClick(func(ev ui.Event) {
ui.WindowManager().DestroyWindow(tableDialog.View)
btn.SetEnabled(true)
})
BtnSave.SetActive(false)
BtnClose.SetActive(false)
}
Thanks for the sources, but it is still unclear:
195
196 // --- Window Controls -----------------------------------------------
197 ui.CreateFrame(btnFrame, 1, 1, ui.BorderNone, 1)
That looks incorrect.
Asset
? I see rowCount := len(Asset)
, and if Asset
has fewer items than 7, it may fail inside preload
at any line that is like d.data[i][N] = tmpAssetData[absIndex].AssetCode
(L36-L42)Oh, sorry. I just opened your message in email and saw that you had marked the line that fails :)
But the question #2 is still unclear: What the size of Asset
? since rowcCount=len(Asset)
. if it is empty than it is clear why L195 panics.
Another point. Quotation from your code:
cache.data = make([][]string, rowCount, rowCount)
(func(info *ui.ColumnDrawInfo) {
cache.data[newInfo.Row][newInfo.Col] = editVal.NewVal // ------------------------------195
you allocated memory for parent slice. But where is allocation for the nested one? I mean something like this:
cache.data = make([][]string, rowCount, rowCount)
cache.data[0] = make([]string, colCount, colCount)
It is no worries. I think I almost got it. There were a few things I had to change. I think I should get it worked out shortly though!
I am super close. I have adding a new row working if at least some data exists. I just have to make it so it can add one if none exists. At least I am on the right track. : D
It is definitely not the most clean, I will have to go back through and clean it up. I noticed I forgot to ever actually update the tmpAssetData when I was updating the table. I was only updating d.data, so I had to create a way to update that, as well as the tables data. (It will probably never be more than 7 rows, most of the time it will be 1-2 at most anyways). I had to add another struct as a container for AssetData and create the test data first a new way.
type AssetContainer struct {
AD []AssetDetails
}
Asset = &AssetContainer{AD: []AssetDetails{
{AssetCode: "UFPS1",
AssetName: "UFPS : Ultimate FPS",
AssetApiKey: "123123112312312323123123123",
AssetRole: "123123123123123123",
AssetReplaced: "Yes",
AssetVersion: "1",
ReplaceDate: "2018-06-06"},
{
AssetCode: "UFPS2",
AssetName: "UFP2 : Ultimate FPS",
AssetApiKey: "123123112312312323123123123",
AssetRole: "123123123123123123",
AssetReplaced: "Yes",
AssetVersion: "1",
ReplaceDate: "2018-06-06"}},
}
Once I did that, when I updated the data from within the table I had to update both the actual data and the tables display of the data.
// --- This is in the TableActionEdit ----------------------------------------
dlg.OnClose(func() {
switch dlg.Result() {
case ui.DialogButton1:
editVal.NewVal = dlg.EditResult()
cache.UpdateData(editVal.Row, editVal.Col, editVal.NewVal)
ui.PutEvent(ui.Event{Type: ui.EventRedraw})
}
})
// --- This updates both the table and tmpAssetData ---------------------------
// -- There may be a cleaner / smaller way of doing this ----------------------
func (d *dbCache) UpdateData(row int, col int, data string) []AssetDetails {
switch col {
case 0:
tmpAssetData.AD[row].AssetCode = data
d.data[row][col] = tmpAssetData.AD[row].AssetCode
case 1:
tmpAssetData.AD[row].AssetName = data
d.data[row][col] = tmpAssetData.AD[row].AssetName
case 2:
tmpAssetData.AD[row].AssetApiKey = data
d.data[row][col] = tmpAssetData.AD[row].AssetApiKey
case 3:
tmpAssetData.AD[row].AssetRole = data
d.data[row][col] = tmpAssetData.AD[row].AssetRole
case 4:
tmpAssetData.AD[row].AssetVersion = data
d.data[row][col] = tmpAssetData.AD[row].AssetVersion
case 5:
tmpAssetData.AD[row].AssetReplaced = data
d.data[row][col] = tmpAssetData.AD[row].AssetReplaced
case 6:
tmpAssetData.AD[row].ReplaceDate = data
d.data[row][col] = tmpAssetData.AD[row].ReplaceDate
}
return tmpAssetData.AD
}
Creating the container for the actual AssetData then allowed me to do this:
func (a *AssetContainer) AddNewAsset(asset AssetDetails) []AssetDetails {
a.AD = append(a.AD, asset)
return a.AD
}
So now in TableACtionNew, I have this:
dlg.OnClose(func() {
switch dlg.Result() {
case ui.DialogButton1:
editVal.NewVal = dlg.EditResult()
if tmpAssetData.AD == nil {
var newInfo = ui.ColumnDrawInfo{Row: 0, Col: 0}
cache.data = make([][]string, rowCount, rowCount)
data := cache.UpdateData(newInfo.Row, newInfo.Col, editVal.NewVal)
tmpAssetData.AD = data
} else {
details := AssetDetails{AssetCode: editVal.NewVal}
tmpAssetData.AddNewAsset(details)
cache.AddNewRow(editVal.NewVal)
}
}
Seems to work pretty well in adding a new row. That 3rd one in the pic is the new one. I used this code to add it to the table:
func (d *dbCache) AddNewRow(newAsset string) {
data := []string{newAsset, "", "", "", "", "", ""}
d.data = append(d.data, data)
}
EDIT -- Nevermind about my question below, I created a work around that ends up doing pretty much the same thing by doing the following:
var newInfo = ui.ColumnDrawInfo{Row: 0, Col: 0}
var restrictor = 0
td.OnActive(func(active bool) {
if (func(info *ui.ColumnDrawInfo) string {
return cache.value(info.Row, info.Col)
})(&newInfo) == "" {
if restrictor == 0 {
cache.CreateNewData(tmpAssetData)
restrictor = 1
}
}
})
Is it possible to force a table event manually?
I tried this:
if Asset == nil {
func() ui.TableAction {
event := ui.TableEvent{Action: ui.TableAction(ui.TableActionNew)}
return event.Action
}()
}
I was hoping that if Asset didn't have data upon opening the table, before the data was populated and what not that I could trigger the TableActionNew event. I tried to use ui.SendEventToChild()
but it seems that table actions are not the right kind of action to be able to send that.
I can't seem to find a way to redraw the table with current data. I read that things like OnBeforeDraw
and OnDrawCell
are callbacks, but I am not sure what is actually telling the table to draw a new row. I add data to dbCache (as well as my tmpAssetData), but I always have to save (which takes the data from tmpAssetData and writes it to AssetData) close the table and reopen it (which then takes any data that is in AssetData and puts it into tmpAssetData, which then the table uses for its data to draw with initially.
So when I create a new row, I create it in dbCache and tmpAssetData, but nothing triggers for it to redraw with the newly added row. I tried to manually call Draw()
, td.OnBeforeDraw()
, td.OnDrawCell()
, cache.preload()
separetly, just to see if it would add the new column after I use ui.TableActionNew
but I am stumped on how to do it without closing and reopening the window.
Is there a way somehow in dlg.OnClose(func())
I can force it to happen once I add my new row? I could technically force the entire table window to close and reopen, but that would require saving the new row from tmpAssetData to AssetData automatically so that when the table opens again it can load the new row back into tmpAssetData because that gets wiped out when the table closes. Saving it automatically and opening/closing again quickly would defeat the purpose of having a manual save button, and storing values in tmpAssetData (which is in case the user adds a row, but then decides to cancel the action, they have to save manually with the button)
So when I create a new row, I create it in dbCache and tmpAssetData, but nothing triggers for it to redraw with the newly added row
I have not tested it, but I'd try the following flow on successful row addition: 1) update dbCache
and add a new data row to it 2) set new row count with SetRowCount
3) call screen refresh with ui.PutEvent(ui.Event{Type: ui.EventRedraw})
Looks like that did the job. Thanks!
Hey there, I am having a bit of trouble figuring this out as I am still fairly new to Go.
I am currently using the preload table and am able to create test data manually just fine to display it as well as edit it thanks to your first example, but I am having issue figuring out how I would start from scratch with 0 value in it though and making the table add a new entry using:
Are you able to perhaps add an extremely simple example? What I was using with test data was:
That worked just fine, at the start of the call of the table, I had:
I tried getting rid of
var AssetDetail = []*AssetDetails{{AssetCode: "SCT",
etc, and then at the start just putting either of these:Neither worked and just gave an out of range error. What might I have to put into:
For it to create a new row/entry into
var tmpAssetData []*AssetDetails
if there was not one in there to begin with?Not sure if it helps any, but here was how I was editing it currently: