How do I pass an object to, or set a variable in a TVML template file I am using? - apple-tv

I'm working with the TVMLCatalog sample files from Apple, and am stuck trying to pass an object to a template file I am loading in the presenter (javascript file). This seems like it should be a totally rudimentary thing to accomplish, but it has me beat.
I have the following, which loads a template with the resource loader, and pushes it to the view.
resourceLoader.loadResource('http://localhost/mytemplate.xml.js',
function(resource) {
if (resource) {
var doc = self.makeDocument(resource);
doc.addEventListener("select", self.load.bind(self));
navigationDocument.pushDocument(doc);
}
}
);
Where do I define an object or set a variable that will be in the document when the template file is loaded in the view?

Yes! You can inject variables into your TVML templates.
First, you have to create a string that contain the same TVML template, and use ${variable} to inject values.
Then, use DOMParser object to convert this string into XML DOM element.
Finally, present the document with help of presentModal method (main object navigationDocument)
Your function will look like this:
function catalogTemplate(title, firstMovie, secMovie) {
var xmlStr = `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<catalogTemplate>
<banner>
<title>${title}</title>
</banner>
<list>
<section>
<listItemLockup>
<title>All Movies</title>
<decorationLabel>2</decorationLabel>
<relatedContent>
<grid>
<section>
<lockup>
<img src="http://a.dilcdn.com/bl/wp-content/uploads/sites/2/2014/03/Maleficent-Poster.jpg" width="250" height="376" />
<title>${firstMovie}</title>
</lockup>
<lockup>
<img src="http://www.freedesign4.me/wp-content/gallery/posters/free-movie-film-poster-the_dark_knight_movie_poster.jpg" width="250" height="376" />
<title>${secMovie}</title>
</lockup>
</section>
</grid>
</relatedContent>
</listItemLockup>
</section>
</list>
</catalogTemplate>
</document>`
var parser = new DOMParser();
var catalogDOMElem = parser.parseFromString(xmlStr, "application/xml");
navigationDocument.presentModal(catalogDOMElem );
}
PS: I used Catalog template as an example. You can use any template
In the onLaunch function, you can call the catalogTemplate function by passing any variable.
App.onLaunch = function(options) {
catalogTemplate("title", "Maleficent.", "The Dark knight");
}
You can add a listener and pass an function to move to another page or trigger an action using addEventListener
function catalogTemplate(title, firstMovie, secMovie, cb) {
var xmlStr = `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<catalogTemplate>
<banner>
<title>${title}</title>
</banner>
<list>
<section>
<listItemLockup>
<title>All Movies</title>
<decorationLabel>2</decorationLabel>
<relatedContent>
<grid>
<section>
<lockup>
<img src="http://a.dilcdn.com/bl/wp-content/uploads/sites/2/2014/03/Maleficent-Poster.jpg" width="250" height="376" />
<title>${firstMovie}</title>
</lockup>
<lockup>
<img src="http://www.freedesign4.me/wp-content/gallery/posters/free-movie-film-poster-the_dark_knight_movie_poster.jpg" width="250" height="376" />
<title>${secMovie}</title>
</lockup>
</section>
</grid>
</relatedContent>
</listItemLockup>
</section>
</list>
</catalogTemplate>
</document>
`
var parser = new DOMParser();
var catalogDOMElem = parser.parseFromString(xmlStr, "application/xml”);
catalogDOMElem.addEventListener("select", cb, false);
navigationDocument.presentModal(catalogDOMElem );
}
Let's create another template just to showcase how we jump to another page by selecting a specific item.
function ratingTemplate(title) {
var xmlStr = `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<ratingTemplate>
<title>${title}</title>
<ratingBadge value="0.8"></ratingBadge>
</ratingTemplate>
</document>`
var parser = new DOMParser();
var ratingDOMElem = parser.parseFromString(xmlStr,"application/xml");
navigationDocument.presentModal(ratingDOMElement);
}
In our onLaunch function.
App.onLaunch = function(options) {
catalogTemplate("title", "Maleficent.", "The Dark knight", function() {
navigationDocument.dismissModal();
ratingTemplate(“rating template title")
});
}
Check this list for more tutorials.

Related

Using compiled bindings with Prism

I want to use compiled bindings in my Xamarin Forms app in combination with Prism.
I created a small xamarin forms app with a simple view, viewmodel and prism (prism:ViewModelLocator.AutowireViewModel="True"). Classic binding works as expected.
How should I implemented compiled binding without creating the binding context twice?
Classic binding with prism: HomePage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="CompiledBinding.Views.HomePage">
<StackLayout>
<!-- Place new controls here -->
<Label Text="{Binding Name}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
HomePageViewModel.cs:
using Prism.Mvvm;
using Prism.Navigation;
using System;
using Xamarin.Forms;
namespace CompiledBinding.ViewModels
{
public class HomePageViewModel : BindableBase
{
string _name = "Compiled binding test";
public HomePageViewModel(INavigationService navigationService)
{
var nav = navigationService;
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
// Do something
Name = DateTime.Now.ToString("yyyy MMMM dd hh:mm:ss");
return true; // True = Repeat again, False = Stop the timer
});
}
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
}
}
Adding the binding context to the xaml page again, is not an option:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:viewModels="clr-namespace:CompiledBinding.ViewModels"
x:Class="CompiledBinding.Views.HomePage"
x:DataType="viewModels:HomePageViewModel">
<ContentPage.BindingContext>
<viewModels:HomePageViewModel />
</ContentPage.BindingContext>
<StackLayout>
<!-- Place new controls here -->
<Label Text="{Binding Name}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
Besides of defining the binding context again, it also results in the error: no public parameterless constructor.
Do I oversee something? Does anyone know how to work with compiled bindings together with prism?

add second filter without removing the existing ones

Hi i have a searchfield with this code behind it:
onSearch : function (oEvent) {
if (oEvent.getParameters().refreshButtonPressed) {
// Search field's 'refresh' button has been pressed.
// This is visible if you select any master list item.
// In this case no new search is triggered, we only
// refresh the list binding.
this.onRefresh();
} else {
var andFilter = [];
var sQuery = oEvent.getParameter("query");
if (sQuery && sQuery.length > 0) {
// add filters
var oTableSearchState = [];
oTableSearchState = [new Filter("Supplier", FilterOperator.Contains, sQuery), new Filter("BusArea", FilterOperator.Contains, sQuery), new Filter("CostCenter", FilterOperator.Contains, sQuery),
new Filter("FuncArea", FilterOperator.Contains, sQuery)];
andFilter.push(new Filter(oTableSearchState, false));
}
this._applySearch(andFilter);
}
},
And a filter button that should add aditional filters. Something like this:
onSetFilter : function(oEvent) {
var andFilter = [];
andFilter.push(new Filter("BusArea", FilterOperator.EQ, "5000"));
this._applySearch(andFilter);
},
But of course the "BusArea" part should be dependent on what filters are selected. It could be more than 1 filter. the _applySearch function looks like this:
_applySearch: function(andFilter) {
var oViewModel = this.getModel("worklistView");
this._oTable.getBinding("items").filter(andFilter, true);
// changes the noDataText of the list in case there are no filter results
if (andFilter.length !== 0) {
oViewModel.setProperty("/tableNoDataText",
this.getResourceBundle().getText("worklistNoDataWithSearchText"));
}
}
the problem is that when i add a filter via the filter button, the filters from the searchbar disappear and the other way arround. how can i change my code so that i can add the filters without removing the existing ones?
One solution is to get the Filters from the binding info and push back together with the new Filter using and.
this._oTable.getBindingInfo("items").filters.aFilters;
After our conversation on the chat, I have made this snippet using a global model.
https://jsbin.com/pewavuhonu/edit?html,output
The ComboBox and the Button simulates your Dialog.
The input simulates the SearchField
Both are binded against the global "filtersModel", and both call the _calculateFilters() function when submiting the info
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta charset="utf-8">
<title>MVC with XmlView</title>
<!-- Load UI5, select "blue crystal" theme and the "sap.m" control library -->
<script id='sap-ui-bootstrap'
src='https://sapui5.hana.ondemand.com/resources/sap-ui-core.js'
data-sap-ui-theme='sap_bluecrystal'
data-sap-ui-libs='sap.m'
data-sap-ui-xx-bindingSyntax='complex'></script>
<!-- DEFINE RE-USE COMPONENTS - NORMALLY DONE IN SEPARATE FILES -->
<!-- define a new (simple) View type as an XmlView
- using data binding for the Button text
- binding a controller method to the Button's "press" event
- also mixing in some plain HTML
note: typically this would be a standalone file -->
<script id="view1" type="sapui5/xmlview">
<mvc:View xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" controllerName="my.own.controller">
<Panel headerText="Filters">
<VBox>
<HBox>
<Label text="Filter by Customer:" class="sapUiSmallMarginTop sapUiSmallMarginEnd"/>
<ComboBox id="comboBox" selectedKey="{filtersModel>/customerFilter}">
<items>
<core:Item key="VINET" text="VINET" />
<core:Item key="TOMSP" text="TOMSP" />
<core:Item key="HANAR" text="HANAR" />
<core:Item key="VICTE" text="VICTE" />
<core:Item key="SUPRD" text="SUPRD" />
</items>
</ComboBox>
<Button text="Apply this Filter" press="_calculateFilters"></Button>
</HBox>
</VBox>
<VBox>
<HBox>
<Input value="{filtersModel>/shipAddressFilter}" id="input" submit="_calculateFilters" width="500px" placeholder="Filter by ShipAddress: Write and enter for filtering"/>
</HBox>
</VBox>
</Panel>
<Panel>
<List id="list" items="{/Orders}">
<StandardListItem title="{CustomerID}" info="{ShipAddress}"/>
</List>
</Panel>
</mvc:View>
</script>
<script>
// define a new (simple) Controller type
sap.ui.controller("my.own.controller", {
onInit: function(){
var oFiltersModel = new sap.ui.model.json.JSONModel();
sap.ui.getCore().setModel(oFiltersModel, "filtersModel");
},
_calculateFilters: function(){
var oSelect = this.getView().byId("comboBox"),
oListBinding = this.getView().byId("list").getBinding("items"),
oFiltersModel = sap.ui.getCore().getModel("filtersModel"),
oCustomerFilterValue = oFiltersModel.getProperty("/customerFilter"),
oShipAddressValue = oFiltersModel.getProperty("/shipAddressFilter"),
oFilters = [];
if(oCustomerFilterValue){
oFilters.push(new sap.ui.model.Filter("CustomerID", "EQ", oCustomerFilterValue));
}
if(oShipAddressValue){
oFilters.push(new sap.ui.model.Filter("ShipAddress", "Contains", oShipAddressValue));
}
oListBinding.filter(oFilters);
}
});
/*** THIS IS THE "APPLICATION" CODE ***/
// create some dummy JSON data
var data = {
actionName: "Say Hello"
};
// instantiate the View
var myView = sap.ui.xmlview({viewContent:jQuery('#view1').html()}); // accessing the HTML inside the script tag above
// create a Model and assign it to the View
var uri = "https://cors-anywhere.herokuapp.com/services.odata.org/Northwind/Northwind.svc"; // local proxy for cross-domain access
var oModel = new sap.ui.model.odata.ODataModel(uri, {
maxDataServiceVersion: "2.0"
});
myView.setModel(oModel);
// put the View onto the screen
myView.placeAt('content');
</script>
</head>
<body id='content' class='sapUiBody'>
</body>
</html>

Display sum of bound numeric values in XML

I am using an OData model to bind UI controls with values. I need to sum two values from model values.
<Input id="__input8" class="rt1" value="{D1}" maxLength="1" type="Number" placeholder="" enabled="true" editable="true" />
<Input id="__input9" class="rt1" value="{D2}" maxLength="1" type="Number" placeholder="" enabled="true" editable="true" />
<Text id="__input15" class="rt1" text="{D1} + {D2}" />
I need to sum the D1 and D2 values in the Text control. I am using XML for view and JS for controller.
var oModel = new sap.ui.model.odata.ODataModel(sServiceUrl, true);
var oJsonModel = new sap.ui.model.json.JSONModel();
oModel.read("/xxxSet?", null, null, true, function (oData,repsonse) {
oJsonModel.setData(oData);
});
this.getView().setModel(oModel);
This is my OData connection.
You can use an Expression Binding:
<Text id="__input15" class="rt1" text="{= ${D1} + ${D2}}" />
Nice, finally a post where i can help.
First, i would set a formatter. Since my root usually is sap.ui.app, and my formatter is inside a model folder and called formatter, i can call it like "sap.ui.app.model.formatter"
You can check whats the name of your root inside your index.
data-sap-ui-resourceroots='{"sap.ui.app": "./"}'>
Thats your root. Create a folder inside sap.ui.app named model, and inside the model create a file called formatter.js and inside the file write this code.
jQuery.sap.declare("sap.ui.app.model.formatter");
sap.ui.app.model.formatter =
{
function1: (a, b)
{
return a+b;
}
};
Next, you should call the formatter from your view.
<Text id="__input15" class="rt1" text="{parts:[{path : 'D1'}, {path : 'D2'}], formatter:'sap.ui.app.model.formatter.function1'}" />
Thats it. It should work now.
EDIT: I'm glad my answer helped.

TVML: adding new lines to a description text

Experimenting with Apple TV's TVML: I'm using a Product Template, and in the description field I'd like to add carriage returns, to make it look somewhat like a list.
Here is a simple example:
var Template = function() { return `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<productTemplate>
<banner>
<infoList>
</infoList>
<stack>
<title>Big Title</title>
<description>
Line one
Line two
</description>
</stack>
</banner>
</productTemplate>
</document>`
}
I've tried \n, &#xD, &#xA between the lines, and even something like this:
<![CDATA[
Line 1 <br />
Line 2 <br />
]]>
But none of these work. Is there a way to incorporate line breaks in TVML descriptions?
Having this code in a template.xml.js and loading it via the Presenter.js in the TVMLCatalog example from apple:
<stack>
<description>Insert your \n username (tipically your ID)</description>
</stack>
It renders
This also works:
var Template = function() {
const description = `
Line 1
Line 2
`.trim();
return `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<productTemplate>
<banner>
<infoList>
</infoList>
<stack>
<title>Big Title</title>
<description>
${description}
</description>
</stack>
</banner>
</productTemplate>
</document>`
}

How to dynamically load an XML fragment in XML view?

Suppose I have the following XML view:
<mvc:View xmlns:mvc="sap.ui.core.mvc" ...>
<Page>
<content>
<l:VerticalLayout>
<l:content>
<core:Fragment fragmentName="my.static.Fragment" type="XML" />
</l:content>
</l:VerticalLayout>
</content>
</Page>
</mvc:View>
The fragment my.Fragment is statically loaded. However, I now want to dynamically change the to-be-loaded fragment (ideally using data binding the fragmentName property, but any other means should be ok as well), ie. something like this:
<mvc:View xmlns:core="sap.ui.core.mvc" ...>
<Page>
<content>
<l:VerticalLayout>
<l:content>
<core:Fragment fragmentName="{/myDynamicFragment}" type="XML" />
</l:content>
</l:VerticalLayout>
</content>
</Page>
</mvc:View>
However, the latter does not work, and the Fragment definitions don't allow for data binding... I might have missed something, but how should I dynamically change the Fragment in my XML view based on a parameter/model property/etc?
For now, I have resorted to a custom control instead of directly using a fragment in my view, and have that control do the dispatching to the appropriate Fragment, but I feel there should be an easier, out-of-the-box way...
I think the only solution will be initialization of fragment from onInit method of controller:
sap.ui.controller("my.controller", {
onInit : function(){
var oLayout = this.getView().byId('mainLayout'), //don't forget to set id for a VerticalLayout
oFragment = sap.ui.xmlfragment(this.fragmentName.bind(this));
oLayout.addContent(oFragment);
},
fragmentName : function(){
return "my.fragment";
}
});
The fragment name can also result from a binding, including an expression binding which evaluates to a constant. As formatter functions return strings, and not booleans, === 'true' has been added in the following example:
Example: Dynamic Fragment Name
<core:Fragment fragmentName="{= ${path: 'facet>Target', formatter: 'sap.ui.model.odata.AnnotationHelper.isMultiple'} === 'true'
? 'sap.ui.core.sample.ViewTemplate.scenario.TableFacet'
: 'sap.ui.core.sample.ViewTemplate.scenario.FormFacet' }" type="XML"/>