TVML Add items dynamically? - tvos

I have seen this question (Force view to reload tvml content on Apple TV/tvos) and the answer describes how you could remove things from the DOM but is there any way to add them?
I know about the standard appendChild on a NodeList but how would you create the proper element to be appended? That is, when you create a document in TVML it is a special XML syntax that is then parsed into a document. Is there a way to parse just a portion of a document so that you can then add it into, say a section within a shelf to dynamically add more items to the row after the document has been presented?
P.S. I've tried using Presenter.parser.parseFromString() with the xml for the new item but it is throwing a IKDOMException with that syntax.

There are many approaches you can take to accomplish adding items dynamically. An important thing to note is that Apple's sample code is NOT structured for dynamic data very well.
After you create a template, you can change the document in a variety of ways. You should have ownership of the document after the template is created. In the following example, the variable doc contains a Stack Template document I'd like to manipulate.
var shelves = doc.getElementsByTagName("shelf"); //get all the shelves
var shelfToAddTo = shelves.item(0); //choose the index for the shelf you want to add to.
var sectionToAdd = `<section>
<lockup>
<img src="pathToImg" width="100" height="100"/>
<title>Title Goes Here</title>
</lockup>
</section>`;
shelfToAddTo.insertAdjacentHTML('beforeend', sectionToAdd); //this will add the new section before the </shelf> tag.
This will add a new section and lockup to the first shelf in your document.

UPDATE:
You can use DataItem API to manage your data with prototypes.
You can’t bind JSON objects directly to a template, you have to turn them into DataItem objects. Retrieve the desired section element and create a new data item for the section. Create new data items from the JSON objects. The setPropertyPath method is used bind the new data items to the section data item. Listing 5-4 shows a modified parseJson function that takes the information from the Images.json file, turns them into data items, and binds them to the appropriate section.
Using the prototype element, you can create a single lockup that contains like objects. Inside of the lockup, you define the structure of the lockup. Listing 5-5 shows a lockup that displays the objects defined as artwork in the type element. The URL and title for each image are pulled from the JSON object.
<prototypes>
<lockup prototype="artwork">
<img binding="#src:{url};" width="200" height="300"/>
<title binding="textContent:{title};" />
</lockup>
</prototypes>
<section binding="items:{images};" />
The following iterate and populate your prototype code using items from section:
function parseJson(information) {
var results = JSON.parse(information);
let parsedTemplate = templateDocument()
navigationDocument.pushDocument(parsedTemplate)
let shelf = parsedTemplate.getElementsByTagName("shelf").item(0)
let section = shelf.getElementsByTagName("section").item(0)
//create an empty data item for the section
section.dataItem = new DataItem()
//create data items from objects
let newItems = results.map((result) => {
let objectItem = new DataItem(result.type, result.ID);
objectItem.url = result.url;
objectItem.title = result.title;
return objectItem;
});
//add the data items to the section's data item; 'images' relates to the binding name in the protoype where items:{images} is all of the newItems being added to the sections' data item;
section.dataItem.setPropertyPath("images", newItems)
}
Template:
<document>
<stackTemplate>
<banner>
<title>JSON Shelf</title>
</banner>
<collectionList>
<shelf>
<prototypes>
<lockup prototype="artwork">
<img binding="#src:{url};" width="200" height="300"/>
<title binding="textContent:{title};" />
</lockup>
</prototypes>
<section binding="items:{images};" />
</shelf>
</collectionList>
</stackTemplate>
</document>
Reference:
https://developer.apple.com/library/content/documentation/TVMLKitJS/Conceptual/TVMLProgrammingGuide/GeneratingContentForYourApp.html
Hope it helps you.

Related

How can I use Render sub-template in a C# template in a Content module that uses lists within lists?

2SXC 10.25.2 / DNN 9.3.2
I have a 2sxc module that uses a C# template with "list" enabled. I have a content type called "pathway" and inside that I have 2 entity picker fields for "first step sessions" and then "next step sessions". These entity pickers use a "session" content type. Inside each of those "session" content types I also have an entity picker for "speaker(s)". All in all, it's a setup that I have lists within lists within lists.
When I create the loops for each of the sublists, I can easily do that within the 1 C# template but it becomes repetitive, long, and unruly because there's so much c# code where I'm looping the same session template for different entity picker sections. So, I tried using the "Render sub-template" code to simplify the template - I created new sub templates and inserted them in - it seemed to work at first, however, the template started outputting all "session" items into each item in the list.
I suspect that the subtemplate somehow loses the context of the item that it's in so it's outputting everything. Is there something special I need to know about using subtemplates with for each loops? Do I have to include params and, if so, how do I do that?
EDIT to include code sample:
Here is a small, simplified version of the code I'm working with:
#foreach(var Content in AsList(Data)) {
<h2>#Content.Title</h2>
<h3>Lead Sessions</h3>
<div class="lead-sessions text-green">
#foreach(var item in AsList(Content.LeadSessions as object)){
<h4>#item.LeadSessionTitle</h4>
<p>#item.LeadSessionText</p>
}
</div>
<h3>Next Sessions</h3>
<div class="next-sessions text-green">
#foreach(var nextitem in AsList(Content.NextSessions as object)){
<h4>#nextitem.LeadSessionTitle</h4>
<p>#nextitem.LeadSessionText</p>
}
</div>
}
I want to make a subtemplate so I don't have to repeat the same code for the sessions loop. How could I simplify this template to use a subtemplate for looping the sessions within the lead-sessions and next-sessions?
So based on the modified question, it's a bit like this
#foreach(var Content in AsList(Data)) {
<h2>#Content.Title</h2>
<h3>Lead Sessions</h3>
#RenderPage("_inner.cshtml", new { Items = Content.LeadSessions })
<h3>Next Sessions</h3>
#RenderPage("_inner.cshtml", new { Items = Content.NextSessions })
}
Second file _inner.cshtml
#{
var items = AsList(PageData["Items"]);
}
<div class="next-sessions text-green">
#foreach(var nextitem in items){
<h4>#nextitem.LeadSessionTitle</h4>
<p>#nextitem.LeadSessionText</p>
}
</div>
Yep, you can just use RenderPage without params, or pass in params like in the blog app:
#RenderPage("shared/_Category Filter.cshtml", new { MobileView = true, FilteredCategory = filteredCategory })
See https://github.com/2sic/app-blog/blob/master/_List.cshtml#L25
Then the template can retrieve the values like
#{
var filteredCategory = PageData["FilteredCategory"];
}
See https://github.com/2sic/app-blog/blob/master/shared/_Category%20Filter.cshtml#L6
You can pass around any amount of values/objects like this.
You can also create helpers - and then call those helpers. Like this
https://github.com/2sic/app-news/blob/master/shared/_Helpers.cshtml#L24-L33

Error on looping through a RelatedLinks property of dynamic Node in Razor

I have a Razor partial which displays my site navigation:
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage
#{
var home = CurrentPage.Site();
umbraco.NodeFactory.Node navigationSettingsNode = MySite.Umbraco.NavigationSettings;
dynamic navigationSettings = new umbraco.MacroEngines.DynamicNode(navigationSettingsNode.Id);
var settings = home.Children.Where("DocumentTypeAlias == \"Settings\"").First();
}
#if (navigationSettings.HasValue("topNavigation"))
{
<ul>
dynamic topNavigation = navigationSettings.topNavigation;
var topNavigation2 = settings.topNavigation;
<span>#topNavigation</span>
<span>#topNavigation2</span>
foreach(dynamic item in topNavigation)
{
<li>
#item.caption
</li>
}
</ul>
}
Initially I was looping through topNavigation2 items which worked fine and with no problem.
Now I'm looping through topNavigation items and it throws an error:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'char' does not contain a definition for 'link'
I don't want to use var settings anymore, I want to use only dynamic navigationSettings variable. In order to get the right node of navigationSettings I need to some operation and I don't fancy to paste the same code in every view I want to use it so I want it to be accessible from dll and available to use anywhere.
Also the navigationSettings node in my Umbraco is outside of main content tree so is not a child of Home.
Why isn't it working? Both
dynamic topNavigation = navigationSettings.topNavigation;
var topNavigation2 = settings.topNavigation;
produce the same json result and both are dynamic objects.
How to make it work correctly?
I'm using MVC 5.2.3
It looks like your topNavigation property is a string, and so when you call for each on it, it's iterating through the characters in the string.
Also, don't use NodeFactory, it's deprecated. You should be using IPublishedContent instead.
I'd use the strongly typed content objects instead of dynamic, as a) they're faster, and b) they're easier to work with.
Here's a great article explaining the different ways of getting content: https://24days.in/umbraco-cms/2015/strongly-typed-vs-dynamic-content-access/

Dynamically display a list of xml hotspots (for krpano) based on json list

A bit of a newbie question for xml/krpano,
I have a list of json items that I want to be dynamically loaded into XML <hotspots>. I can loop through each item in JavaScript but I have no clue how to do the same loop in XML!
Check out this picture:
Imagine that each rectangle with an image is one item in a JSON list. Each rectangle you see is a <hotspot>. Right now these three hotspots are hardcoded into the XML file, but I want to dynamically load hotspots based on how many JSON list items exist.
Here is one hotspot. If my json list has 16 items, I would expect 16 hotspots
to be loaded.
<!--* video image thumbnail *-->
<hotspot name="start" distorted="true"
url="/panorama/%$panoId%/thumb.png"
ath="0" atv="0"
ox="0" oy="36"
vr_timeout="2000"
zorder="99"
scale="0.8"
onclick="changepano( loadscene(video_scene, null, MERGE|KEEPVIEW|KEEPMOVING, BLEND(1)); );"
alpha="0.0"
onloaded="if(vr_start_done === true, removehotspot(start); start_vr(); , tween(alpha,1); );"
/>
Your question is about dynamically generating hotspots in KRPano from a JSON list.
It is not really clear to me the way you wrote your question if you want to read the JSON from KRPano XML file (let's say FROM KRPano) or if you are expecting to use Javascript to ask KRPano to produce the hotspots.
These are two completly distinct ways of doing it :)
Because I'm lazy and I suppose you want to deal with JSON in JS, I go for this solution...
Loading a JSON file from Javascript
Your KRPano project should look like a core HTML file presenting Javascript to embed the KRPano plugin.
There, you can declare a script content in your HTML in which you will parse your JSON content and you ask KRPano to generate a hotspot. This method should be called when you are sure KRPano is ready, or get it called from KRPano when it is ready, using "onready" attribute.
myHotspotList.json content:
var myHotspotList = [
{
name: "myFirstHotspot",
atv: 15.0,
ath: 56.5686,
url: "myHotspotImage.jpg"
}
];
tour.html content:
<html>
...
<script url="myHotspotList.json'></script>
<script>
function generateHotspots() {
// First, we get the KRPano plugin
var myKRPano = document.getElementById('krpanoSWFObject');
// Now we parse the JSON object
for(var idx in myHotspotList) {
// Get the current Hotspot data
var currHotspot = myHotspotList[idx];
// Ask KRPano to create a hotspot with our current name
myKRPano.call("addhotspot('"+ currHotspot.name +"');");
// Now set various attributes to this hotspot
myKRPano.call("set(hotpost['"+ currHotspot.name +"'].atv, "+currHotspot.atv+");");
myKRPano.call("set(hotpost['"+ currHotspot.name +"'].ath, "+currHotspot.ath+");");
myKRPano.call("set(hotpost['"+ currHotspot.name +"'].url, '"+currHotspot.url+"');");
}
}
</script>
...
// When you ask for pano creation, give your generation method as callback
embedpano({target:"krpanoDIV", onready:generateHotspots});
...
</html>
I hope this help and you got the trick with calling JSON object attributes and all.
Regards

Dynamic XML Template in TVML/TVJS

Does anyone know how to Dynamically generate a template in an apple tv app using TVJS/TVML? Basically I want to hit my API, get back an array of objects and then insert that data into my XML template.
I've been searching for info on how to accomplish it but have come up short. I've found many tutorials that use hard coded images, videos, etc but nothing dynamically generated.
Any help would be appreciated.
Finally, I've figured this out. It wouldn't be difficult to generate a template on-the-fly, but instead I wanted to reuse the Presenter and the ResourceLoader, and to have the template as a *.xml.js file. Here is the solution I managed to arrive at.
For the initial view, I used a catalogTemplate, as demonstrated in Ray Wenderlich's tutorial. Instead of conference talks, however, I was displaying categories of men's and women's merchandise. Once a category was selected, I wanted to display a stackTemplate with a number of options for that category. The problem was how to pass any information, the title of the category in the simplest case, to the second template.
In the first template, I had the lockups configured like so:
<lockup categoryTitle="Women: Dresses" categoryDir="w-dresses">
<img src="${this.BASEURL}images/dresses.jpg" width="230" height="288" />
<title>Dresses</title>
</lockup>
In application.js, I had a listener attached, in the same way how tutorials show:
doc.addEventListener("select", Presenter.load.bind(Presenter));
Here is the second template (Category.xml.js):
var Template = function(categoryTitle) {
return `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<stackTemplate>
<banner>
<title>${categoryTitle}</title>
</banner>
</stackTemplate>
</document>`
}
This is a JavaScript, so in your case you can pass into the function, say, an array of values and then construct the template accordingly. The tricky part was to pass a value.
First, I made a couple of changes to the ResourceLoader (this can be done better, of course, it's just a proof of concept). I simply added categoryTitle as an additional parameter to the top-level function and when calling the Template:
ResourceLoader.prototype.loadResource = function(resource, callback, categoryTitle) {
var self = this;
evaluateScripts([resource], function(success) {
if(success) {
var resource = Template.call(self, categoryTitle);
callback.call(self, resource);
} else {
var title = "Resource Loader Error",
description = `Error loading resource '${resource}'. \n\n Try again later.`,
alert = createAlert(title, description);
navigationDocument.presentModal(alert);
}
});
}
Finally, in the Presenter, in the load, I am passing categoryTitle to the resourceLoader:
load: function(event) {
var self = this,
ele = event.target,
categoryTitle = ele.getAttribute("categoryTitle");
if (categoryTitle) {
resourceLoader.loadResource(`${baseURL}templates/Category.xml.js`, function(resource) {
var doc = self.makeDocument(resource);
self.pushDocument(doc);
}, categoryTitle);
}
},
This works for me.
One final note: for some categories, I had titles with an ampersand, like 'Tops & T-shirts'. Naturally, I replaced the ampersand with an XML entity: 'Tops & T-shirts'. This, however, didn't work, probably because this string was decoded twice: the first time the entity was turned into an ampersand, and on the second pass the single ampersand was flagged as an error. What worked for me was this: 'Tops &amp; T-shirts'!
It is simple if you are using atvjs.
// create your dynamic page
ATV.Page.create({
name: 'homepage',
url: 'path/to/your/json/data',
template: function(data) {
// your dynamic template
return `<document>
<alertTemplate>
<title>${data.title}</title>
<description>${data.description}</description>
</alertTemplate>
</document>`;
}
});
// later in your app you can navigate to your page by calling
ATV.Navigation.navigate('homepage');
Disclaimer: I am the creator and maintainer of atvjs and as of writing this answer, it is the only JavaScript framework available for Apple TV development using TVML and TVJS. Hence I could provide references only from this framework. The answer should not be mistaken as a biased opinion.
I'm using PHP to generate the TVML files dynamically, configuring the output as text/javascript format:
<?php
header("Content-type: application/x-javascript");
[run your PHP API calls here]
$template = '<?xml version="1.0" encoding="UTF-8" ?>
<document>
... [use PHP variables here] ...
</document>';
echo "var Template = function() { return `". $template . "`}";
?>
You can dynamically generate a template by creating a dynamic string that represents the xml in a TVML template.
Review the code in here: https://developer.apple.com/library/prerelease/tvos/samplecode/TVMLCatalog/Listings/client_js_Presenter_js.html#//apple_ref/doc/uid/TP40016505-client_js_Presenter_js-DontLinkElementID_6
This file has functions that can be used to create an XML document that can represent a view.
You can make an XMLHttpRequest (ex: consuming API JSon calls through TVJS-tvOS) bring back some JSON data and then dynamically generate an XML document that conforms to one of the TVML templates. Parse it into an XML document and then navigate to the document.

jqPlot charts on page load

I have a form where I select the number of items. Upon clicking submit, it should take me to a new page where it would display the item selected and depending on the number of items selected, it would create those many jqPlots, one for each item.
Any suggestions on how do I go about doing this?
Thanks,
S.
It's hard to give any specifics without more detail about the items, but basically you would pass a JSON structure to your view with the items to be plotted. Then you would loop through the JSON structure, creating DIV tag for each item to be plotted and appending the DIV tags to the body.
The Javascript part would look something like this:
$.each(items, function(index, value) {
$myPlot = $("<div>");
$myPlot.attr("id", "item"+index);
$.jqplot($myPlot.attr("id"), ...);
$("body").append($myPlot);
});
This question is very general, but answering (specifically and only) the question of loading multiple charts:
You need a unique HTML div id for each chart; consider using an RFC 4122 UUID (generate as needed) for each chart/div rather than a sequential index for each. Use something that looks like this as a placeholder div for each:
<div class="chartdiv" id="chartdiv-${UID}">
<a rel="api" type="application/json" href="${JSON_URL}" style="display:none">Data</a>
</div>
This embeds the JSON URL for each div inside it, in a hidden hyperlink that can be discovered by JavaScript iterating over your multi-chart HTML page.
The matter of the UUID is inconsequential -- it just seems the most robust way to guarantee a unique HTML id addressable by JavaScript for each chart.
Subsequently, you should have JavaScript that looks something like:
jq('document').ready(function(){
jq('.chartdiv').each(function(index) {
var div = jq(this);
var json_url = jq('a[type="application/json"]', div).attr('href');
var divid = div.attr('id');
jq.ajax({
url: json_url,
success: function(responseText) { /*callback*/
// TODO: responseText is JSON, use it, normalize it, whatever!
var chartdata = responseText;
jq.jqplot(divid, chartdata.seriesdata, chartdata.options);
}
});
});
});