I figured it out, thanks for your help!
For anyone who finds this later, here's some sample code (because everything's better with a sample
![Smiley LOL Smiley LOL](/i/smilies/16x16_smiley-lol.png)
):
Disclaimer: This is has been simplified for posting here and is not guaranteed to run as-is, but should give you the general idea of how to do things.
loadMovies.xml
<?xml version="1.0" encoding="UTF-8"?>
<component name="Movies" extends="Task">
<interface>
<field id="inbox" type="node" />
</interface>
<script type="text/brightscript" uri = "pkg:/components/Movies/loadMovies.brs"/>
</children>
</component>
loadMovies.brs
Sub Init()
m.navItems = m.top.findNode("Movies")
m.top.functionName = "loadNavContent"
m.top.observeField("inbox","loadContent")
End Sub
Sub loadContent()
m.movies = m.top.inbox
m.content = m.top.findNode(m.movies.id)
m.content = m.movies
End Sub
Sub loadNavContent()
oneRow = APICall("main-navigation") 'Make an API call to fetch the lists of movies you will be loading
movieArray = {}
for each jsonItem in oneRow
if jsonItem.DoesExist("api")
movieArray[jsonItem.id] = loadMovieContent(jsonItem.api) 'Make an API call for each item returned in the "main-navigation" API Call
m.movies = CreateObject("roSGNode", "ContentNode")
m.movies = movieArray[jsonItem.id]
m.movies.id = jsonItem.id
m.top.inbox = m.movies 'Assign the movie Node to the inbox for catching and processing from the main script
end if
end for
End Sub
Function APICall(action as string) 'This function retrieves and parses the feed and stores each content item in a ContentNode
account = [INSERT ACCOUNT TOKEN HERE]
url = CreateObject("roUrlTransfer") 'component used to transfer data to/from remote servers
url.SetCertificatesFile("common:/certs/ca-bundle.crt")
url.AddHeader("X-Roku-Reserved-Dev-Id", "")
url.InitClientCertificates()
requestUrl = Substitute("https://[YOUR-WEBSITE-HERE]?accountToken={1}&action={0}", action, account)
url.SetUrl(requestUrl)
rsp = url.GetToString()
responseJSON = parseJSON(rsp)
responseArray = responseJSON.value
return responseArray
End Function
Function loadMovieContent(movieAction as string)
myMovies = GetMovieFeed(movieAction)
rows = {}
myMoviesList = []
for each row in myMovies.keys() '.key() forces the associative array to loop in order
rowGroup = {
Title:row
ContentList:myMovies[row]
}
myMoviesList.push(rowGroup)
end for
return ParseXMLContent(myMoviesList)
End Function
Function GetMovieFeed(callType as string) 'This function retrieves and parses the feed and stores each content item in a ContentNode
account = [INSERT ACCOUNT TOKEN HERE]
url = CreateObject("roUrlTransfer") 'component used to transfer data to/from remote servers
url.SetCertificatesFile("common:/certs/ca-bundle.crt")
url.AddHeader("X-Roku-Reserved-Dev-Id", "")
url.InitClientCertificates()
requestUrl = Substitute("https://[YOUR-WEBSITE-HERE]?accountToken={0}&action={1}&group=alpha", account, callType)
url.SetUrl(requestUrl)
rsp = url.GetToString()
responseJSON = parseJSON(rsp)
responseArray = responseJSON.value
result = {}
for each row in responseArray
itemGroup = []
for each jsonItem in responseArray[row]
item = {}
item.ContentType = 1 'Roku Values: 1 = Movie, 2 = Series, 3 = Season, 4 = Episode, 5 = Audio, as per https://sdkdocs.roku.com/display/sdkdoc/Content+Meta-Data
item.title = jsonItem.title
item.description = strReplace(jsonItem.description,"<br><br>", chr(10))
item.Rating = jsonItem.item_rating
item.NumEpisodes = jsonItem.duration
item.StarRating = jsonItem.item_rate
item.Categories = jsonItem.genre
item.Directors = jsonItem.director
item.Actors = jsonItem.actors
item.url = jsonItem.streams.default
'item.stream = {url : jsonItem.streams.default}
item.streamFormat = "mp4"
item.HDPosterURL = jsonItem.poster
item.HDBackgroundImageUrl = jsonItem.poster1
itemGroup.push(item)
end for
result[row] = itemGroup
end for
return result
End Function
Function ParseXMLContent(list As Object) 'Formats content into content nodes so they can be passed into the RowList
RowItems = createObject("RoSGNode","ContentNode")
'Content node format for RowList: ContentNode(RowList content) --<Children>-> ContentNodes for each row --<Children>-> ContentNodes for each item in the row)
for each rowAA in list
row = createObject("RoSGNode","ContentNode")
row.Title = rowAA.Title
for each itemAA in rowAA.ContentList
item = createObject("RoSGNode","ContentNode")
item.SetFields(itemAA)
row.appendChild(item)
end for
RowItems.appendChild(row)
end for
return RowItems
End Function
'******************************************************
'Replace substrings in a string. Return new string
'******************************************************
Function strReplace(basestr As String, oldsub As String, newsub As String) As String
newstr = ""
i = 1
while i <= Len(basestr)
x = Instr(i, basestr, oldsub)
if x = 0 then
newstr = newstr + Mid(basestr, i)
exit while
endif
if x > i then
newstr = newstr + Mid(basestr, i, x-i)
i = x
endif
newstr = newstr + newsub
i = i + Len(oldsub)
end while
return newstr
End Function
*Those last 2 functions are borrowed from a Roku example
HomeScene.brs (just the pertinent functions):
Sub init()
m.RowList = m.top.findNode("RowList")
m.navigation = CreateObject("roSGNode", "Movies") 'Create API Call and Parsing task node
m.navigation.control = "RUN" 'Run the task node
m.navigation.observeField("inbox","movieCollection") 'Listen for changes to the inbox
m.RowList.setFocus(true)
End Sub
Sub movieCollection()
m.newMovies = m.navigation.inbox
m.movies = CreateObject("roSGNode", "ContentNode") 'Create a new node for the movies
m.movies = m.newMovies
m.movies.id = m.newMovies.id
m.top.appendChild(m.movies) 'Add the new movies to the parent node for later use
m.findMovies = m.top.findNode(m.newMovies.id)
End Sub
Function onKeyEvent(key as String, press as Boolean) as Boolean
'Do some processing to tell it what node to display
'Example:
'm.newMovies = m.top.findNode(m.mainNavItems.getChild(m.mainNavItems.buttonFocused).getField("id")) 'Grab the ID of the select navMenu item
'm.RowList.content = m.newMovies
'm.RowList.setFocus(true)
End Function
I don't claim this is the best way to do it, but it does work for me and my purposes and I welcome any constructive criticism that will make this better.