Dynamically assigning different templates based on data - using helper or custom tag - jquery-templates

I render a jsrender template in template object.
var colorTmpl = {
"color": jQuery.templates("<span>{{:~val}}</span>"),
}
var jsrendertmpl = {};
jQuery.extend(jsrendertmpl, colorTmpl);
If I access the jsrendertmpl.colorTmpl.render() it will render the color template. If I include this tmpl template tag within other template like this
{colorArr: ["white", "blue", "black"]}
{{for colorArr tmpl="jsrendertmpl.colorTmpl"/}}
It will not include that subtemplate into parent template. So I add a custom tag to include this subtemplate within object
includetmpl: function(tmplname){
return Template.render(tmplVal, jObj);
},
If I include the subtemplate in parent template {{:~val}} cannot be recognised
Parent Template with customised include tag
{{for colorArr itemVar='~val'}}
{{includetmpl "color"}}
{{/for}}
Parent template without subtemplate (It works fine)
{{for colorArr itemVar='~val'}}
<span>{{:~val}}</span>
{{/for}}
Is there any way to access that value. I used a {{setvar}} and {{:~getvar}} for workaround but need a permanent solution.
Thanks in advance.

There are a few ways you can implement that scenario - i.e. to provide different templates based on a data parameter that you pass in.
For example you can pass in the template as a helper:
$.views.helpers("myTemplates", {
color: $.templates("<span>{{:}}</span>"),
colorVal: $.templates("<span>{{:~val}}</span>")
});
And use it as in
{{for colorArr tmpl=~myTemplates.color/}}
or if tmplVar is your chosen template: "color"
{{for colorArr tmpl=~myTemplates[tmplVar]/}}
You can also create a custom tag, as you suggest, in either of the following ways:
$.views.tags("includetmpl", {
init: function(tagCtx) {
this.template = myTemplates[tagCtx.props.template];
}
});
or
$.views.tags("includetmpl2", {
render: function(val) {
return myTemplates[this.tagCtx.props.template].render(val, this.ctx);
}
});
(Note how I pass the context also to my render function, in the second version, so that ~val will be available...)
Then use it as in the following:
{{for colorArr}}{{includetmpl template="color"/}}{{/for}}
or
{{for colorArr itemVar="~val"}}{{includetmpl template="colorVal"/}}{{/for}}
See https://jsfiddle.net/BorisMoore/coezx8xj/ for all of the above...
I will change the title of your question to make it clearer.

Related

How to change Standard Application label text in UI5 dynamically in view controller

i am extending SAP standard HCM Application and standard field(STRAS) label coming from ODATA is 'House Number and Street' but i want to change and assign new label text 'Address Line 1' in UI5 View controller at runtime instead of copying standard ODATA and creating custom ODATA for this small change.
Note : Label is not bound through i18n Model in standard Application
<!-- Street / STRAS -->
<Label text="{/#Address10/Street/#sap:label}" id="lblStreet" labelFor="txtStreet" visible="{FieldStates>/STRAS/Visible}"/>
<Text id="txtStreet" text="{Street}" visible="{FieldStates>/STRAS/Visible}"/>
Any help would be highly appreciated.
Regards,
You can try to implement a Controller Extension.
You just need to add the following code in your manifest.json file inside the "sap.ui5" object:
"extends": {
"component": "Standard App Namespace",
"extensions": {
"sap.ui.controllerExtensions": {
"standard.namespace.controller.ControllerName": {
"controllerName": "my.namespace.ControllerNameExt"
}
}
}
}
Just replace the namespaces with the correct ones for your use case.
In the controller implementation you can add (check is page for details on creating the controller):
onInit: function() {
this.getView().byId("lblStreet").setText("my new label");
}

How to wrap custom tag around {^{datepicker /}} and pass all props

I'd like to create a custom tag, containing a datepicker control. How can I pass all properties from the custom tag to the datepicker control?
The custom tag should look like this:
{^{mycustomdatepicker
aDate
label="Date"
dataFormat="yy-mm-dd"
dateFormat="dd.mm.yy"
_showOn="button"
_buttonImageOnly= true
_buttonText= "Choose middle date"
/}}
The template for the custom tag like this
<div>Caption: ~tagCtx.props.label</div>
{^{datepicker /}}
How do I pass all props thru to the datepicker control at once?
Note: I know, I could pass each prop on it's own, e.g.:
{^{datepicker dateFormat= ~tagCtx.props.dateFormat _showOn=~tagCtx.props._showOn ... /}}
It looks like what you need is to have your custom tag derive from the {{datepicker}} tag:
mycustomdatepicker: {
baseTag: "datepicker",
...
See Specifying tag inheritance: the baseTag option, and Custom datepicker tags.
Then, on your derived tag you can add additional UI by overriding the default template:
mycustomdatepicker: {
baseTag: "datepicker",
template: "<div>Caption: {^{:~tagCtx.props.label}}</div>"
+ "{^{include tmpl=#content/}}..."
}
See Rendering wrapped block content, and this sample.
By following that pattern you don't need to pass props and parameters through to a wrapped {{datapicker}}. The {^{include tmpl=#content/}} will render as the original datepicker did, but can have additional rendered content before and after...
It is also possible to use a render method for your derived tag, rather than overriding the template, along the lines of:
mycustomdatepicker: {
baseTag: "datepicker",
render: function(date) {
return "<div>Caption: " + this.tagCtx.props.label + "</div>"
+ this.tagCtx.render(date) + "...";
}
}
See this sample

How to seperate Vue logic in a laravel app based on layout and page templates

I have a laravel app and a Vue instance attached to the body (or a div, just inside the body).
const app = new Vue({
el: '#app'
});
I think it makes sense to use the Vue instance for stuff relating to the layout (eg header, nav, footer logic).
Now I have a form that is visible on a specific route (e.g. example.com/thing/create). I want to add some logic to it, e.g. hiding a field based on selected option in the form. It is logic meant for just this form (not to be reused). I prefer not to put all the logic inline with the form but put it in the app.js. I could put it in the Vue instance bound to the body but that sounds odd as it only applies to the form that is much deeper into the dom.
I want to leave the markup of the form in the blade template (that inherits the layout).
I tried creating a component but am not sure how to bind this inside the main Vue instance. What is the best way to handle things for this form, put it in the app.js and have it somewhat structured, putting the variables somewhat into scope. Or is it really necessary to remove the main Vue instance bound to the full layout code?
What I tried was something like this, but it does not work (attaching it to the <form id="object-form"> seems to fail:
var ObjectForm = {
template: function() { return '#object-form'},
data: function() {
return {
selectedOption: 1
}
},
computed: {
displayField: function() {
// return true or false depending on form state
return true;
}
}
};
Things do work if I remove the #app Vue instance or when I put everything directly in the app Vue instance. But that seems messy, if I have similar variables for another form they should be seperated somewhat.
I would appreciate some advice regarding the structure (differentiate page layout and page specific forms) and if possible some example to put the form logic inside the main app.js.
I hope this helps kind of break things down for you and helps you understand Vue templating.
It is best to take advantage of Vue's components. For you it would look something like this. Some of this code depends on your file structure, but you should be able to understand it.
In your app.js file (or your main js file)
Vue.component('myform',require('./components/MyForm.vue'));
const app = new Vue({
el: "#app"
});
Then create the MyForm.vue file
<template>
<form>
Put Your Form Markup Here
</form>
</template>
<script>
// Here is where you would handle the form scripts.
// Use methods, props, data to help set up your component
module.exports = {
data: function() {
return {
selectedOption: 1
}
},
computed: {
displayField: function() {
// return true or false depending on form state
return true;
}
},
methods: {
// add methods for dynamically changing form values
}
}
</script>
Then you will be able to just call in your blade file.
<myform></myform>
I found out how to do it. The trick was to use an inline template. Surround the form in the view with:
<object-form inline-template>
<form>...</form>
</object-form>
Where object-form is the name of the component. In the ObjectForm code above I remove the template, like this:
var ObjectForm = {
data: function() {
return {
selectedOption: 1
}
},
computed: {
displayField: function() {
// return true or false depending on form state
return true;
}
}
};
I attach the component within the root vue app like this:
const app = new Vue({
el: 'body',
components: {
'object-form': ObjectForm
}
});
This way I can use the form as it was generated from the controller and view and I can separate it from the root (attached to body) methods and properties.
To organize it even better I can probably store the ObjectForm in a seperate .vue file the way #Tayler Foster suggested.

How do I change the name of 'component0' in OpenUi5/SapUI5?

I've just jumped in to OpenUI5, and had a problem where I couldn't get navigation targets to open up in aggregations within sub views. I found an answer on Stack Overflow that said that I needed to specifically name each View in the parent hierarchy to stabilise the name - this mostly worked, and I've been able to get my routing and targets to function.
My problem now is that the ID prefix of my item is __component0. I cannot find any way to change the name of this part, so really I'm not fully in control of my IDs.
I've tried changing the sId of the component before and after initialisation, but then the app doesn't function. I've also set both sap.app.componentName and sap.ui5.componentId, and neither are used. It appears that component0 is constructed by getting the type name of the controller class, but that has to be called Component.js.
Does anybody know how to change this value, or otherwise derive a control ID in the DOM that is fully defined in code?
In response to boghyon, I don't doubt that setting the ID via the factory function works, I don't know how to apply that ID value or factory function to the style of code that I have. This style comes from the SAP starter template on GitHub. Simply specifying id: "mycomponentname" in the extend object does not work.
So how would I go about refactoring what I have to the factory function format?
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/Device",
"com/uk/ia/air/cpanel/model/models"
], function(UIComponent, Device, models) {
"use strict";
return UIComponent.extend("com.uk.ia.air.cpanel.Component", {
metadata: {
manifest: "json"
},
/**
* The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
* #public
* #override
*/
init: function() {
// call the base component's init function
UIComponent.prototype.init.apply(this, arguments);
// set the device model
this.setModel(models.createDeviceModel(), "device");
// create the views based on the url/hash
this.getRouter().initialize();
}
});
});
The ID of a component can be manually assigned when instantiating the component
Either with sap/ui/core/Component#createAPI
Or with new ComponentContainer(/*...*/)API
With ComponentContainer
E.g. in index.html when bootstrapping:
<head>
<!-- ... -->
<script id="sap-ui-bootstrap" src="https://ui5.sap.com/resources/sap-ui-core.js"
data-sap-ui-oninit="module:sap/ui/core/ComponentSupport"
data-sap-ui-async="true"
data-sap-ui-resourceroots='{"demo": './'}'
...
></script>
</head>
<body id="content" class="sapUiBody">
<div data-sap-ui-component
data-id="myComponentContainer"
data-name="demo"
data-height="100%"
data-settings='{"id": "myComponent"}'
></div>
</body>
The module, that can create a ComponentContainer in index.html, is sap/ui/core/ComponentSupport. Once it's loaded, it will look for the HTML element that has the attribute data-sap-ui-component. That HTML element should provide constructor settings for the ComponentContainer which provides settings for the component constructor same as in Component.create.
With Component.create()
Component.create({ // Component required from "sap/ui/core/Component"
id: "myComponent", // or this.getView().createId("myComponent") if applicable
name: "demo",
// ...
});
For more information, see Stable IDs: All You Need to Know.
Additionally, every view must have an ID as well so that the given component ID is included in the full ID of an element. Otherwise, the component ID won't be available even if we defined it in the component factory function. View IDs can be set via the property id and viewId respectively in the application descriptor (manifest.json):
sap.ui5 / rootView /id
sap.ui5 / routing / targets /targetName/viewId
You can change the autogenerated ID of the extended component if you override the constructor of the UIComponent. With your code it should look like this:
return UIComponent.extend("com.uk.ia.air.cpanel.Component", {
constructor: function(sId, mSettings) {
UIComponent.call(this, "MyComponentId", mSettings);
},
metadata: {
manifest: "json"
},
/**
* The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
* #public
* #override
*/
init: function() {
// call the base component's init function
UIComponent.prototype.init.apply(this, arguments);
// set the device model
this.setModel(models.createDeviceModel(), "device");
// create the views based on the url/hash
this.getRouter().initialize();
}
});
});

How to render a template from controller within template -- Java, Play 2.1

Is it possible to call a controller method to render a template within a template?
Or is that totally the wrong aproach?
In the div container there is only a sting displayed but not the redered html from my productTable template.
The displayed string inside the <div class="products">:
SimpleResult(200, Map(Content-Type -> text/html; charset=utf-8))
Template:
#categories.map {cat =>
<div>some html</div>
<div class="products">#controller.Products.getByCatergoyId(cat.id)</div>
}
Controller:
public static Result getByCatergoyId(Long catId) {
List<Product> products = Product.find.where().eq("category.id", catId).findList();
return ok(views.html.display.productTable.render(products));
}
If you want to get the code from the productTable view your method shouldn't return a Result but just a String containing rendered code.... aaaannnyyyyway , there is definitely much better way for rendering sub-templates in Play, check the Tags section of the documentation it does exactly what you want directly from the view, of course you will need to pass a product object to it.
Just create tags package in your view package and add there your sub-template (responsible for rendering only pat of page) it behaves exactly the same as common template.