Solution for SAPUI5 error message because of templateShareable:true? - data-binding

Since an upgrade of SAPUI5 1.28.20 I receive the following error message:
A shared template must be marked with templateShareable:true in the
binding info
Code is in MangedObject.js and looks like this:
} else if ( oBindingInfo.templateShareable === MAYBE_SHAREABLE_OR_NOT ) {
// a 'clone' operation implies sharing the template (if templateShareable is not set to false)
oBindingInfo.templateShareable = oCloneBindingInfo.templateShareable = true;
jQuery.sap.log.error("A shared template must be marked with templateShareable:true in the binding info");
}
Value of oBindingInfo.templateShareable is true, value of MAYBE_SHAREABLE_OR_NOT is 1.
According to documentation the default of oBindingInfo.templateShareable is true.
So what is wrong here? A bug in the library? Or something with my code?
See also: https://sapui5.netweaver.ondemand.com/sdk/#docs/api/symbols/sap.ui.base.ManagedObject.html
Update for SAPUI5 version 1.32.x
With version 1.32.x the message has changed it is now:
A template was reused in a binding, but was already marked as
candidate for destroy. You better should declare such a usage with
templateShareable:true in the binding configuration. -
but according to the documentation the default should still be true:
{boolean} oBindingInfo.templateShareable?, Default: true option to
enable that the template will be shared which means that it won't be
destroyed or cloned automatically
Now it looks like, that this produces some endless loading, I got this message again and again till the browser crashes.
Anyone an idea what could be wrong?

Looks like the message occurs if the template was instantiated outside the binding. Example:
This code will work:
new sap.m.Select({
items : {
path : "/Items",
template : new sap.ui.core.Item({
text : "{Name}"
})
}
})
This code seems to produce the message:
var oTemplate = new sap.ui.core.Item({
text : "{Name}"
})
new sap.m.Select({
items : {
path : "/Items",
template :oTemplate
}
})
This seems to fix the problem:
var oTemplate = new sap.ui.core.Item({
text : "{Name}"
})
new sap.m.Select({
items : {
path : "/Items",
template :oTemplate,
templateShareable : true
}
})

The answer above marked as being correct is actually not correct at all because this here is wrong:
Looks like the message occurs if the template was instantiated outside
the binding. [...] This code seems to produce the message: [...]
To prove the answer above os wrong on your own just run this example (SAPUI5 1.28.20 has the same result):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SAPUI5 single file template | nabisoft</title>
<script
src="https://openui5.hana.ondemand.com/1.36.12/resources/sap-ui-core.js"
id="sap-ui-bootstrap"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-libs="sap.m"
data-sap-ui-compatVersion="edge"
data-sap-ui-preload="async"></script>
<!-- use "sync" or change the code below if you have issues -->
<script>
sap.ui.getCore().attachInit(function () {
"use strict";
var oModel = new sap.ui.model.json.JSONModel({
Items: [
{Name: "Michael"},
{Name: "John"},
{Name: "Frank"},
{Name: "Jane"}
]
});
sap.ui.getCore().setModel(oModel);
var oTemplate = new sap.ui.core.Item({
text : "{Name}"
});
new sap.m.Select({
items : {
path : "/Items",
template : oTemplate
}
}).placeAt("content");
});
</script>
</head>
<body class="sapUiBody">
<div id="content"></div>
</body>
</html>
Basically, a clear definition of the lifecycle for templates is (or was) missing in UI5. When this issue was detected there were already many older apps around... So now this new "feature" was introduced somewhen last year (which is kind of backwards compatible). UI5 tries to detect automatically the developer's intention about the lifecycle of a given template used in the binding (using some heuristic). If UI5 cannot clearly tell what the developer actually wanted then you will see this error log - which actually does not affect the functionality at all. It just tells the developer that there is a template somewhere which will not be destroyed by the UI5 runtime. In other words, if you set templateShareable=true then you should make sure to destroy the template in order to avoid memory leaks. So just setting templateShareable=true is not the whole story...
I have published a detailed blog about this: Understanding templateShareable in SAPUI5

Related

Meteor - TRIPLE template tag is not allowed in an HTML attribute error

I got error message when trying to run existing meteor project.
$meteor
=> Started proxy.
=> Started MongoDB.
=> Errors prevented startup:
While building the application:
client/coinmx.html:169: TRIPLE template tag is not allowed in an HTML attribute
...title="Totals: {{{get...
^
In Meteor 0.8, it's possible to return a Javascript object which is directly rendered into HTML attributes versus earlier versions, where you had to render it yourself.
Old version:
<input name={{name}} title={{title}}>
helpers:
Template.foo.name = "fooName";
Template.foo.title = "fooTitle";
New version:
<input {{attributes}}>
helpers:
Template.foo.attributes = {
name: "fooName",
title: "fooTitle"
};
All of these can be functions, and reactive, etc. Because the object is rendered directly into attributes, there is no need for you to SafeString some manually rendered content as before. This is the recommended way to go if need to render HTML attributes.
See also the following for how conditional attributes work under this scheme:
https://github.com/meteor/meteor/wiki/Using-Blaze#conditional-attributes-with-no-value-eg-checked-selected
The error is pretty much explanatory: you cannot use {{{something}}} inside a HTML attribute, you need to use {{something}} instead. Depending on what the something is (it's not known from your question as you didn't provide the code), that's either all you need to do, or you can achieve similar functionality by returning new Handlebars.SafeString("result") from your helper instead of just "result". However, if you do, you need to be super sure that the thing you'll return won't break the HTML structure.
Hugo's answer above gave me the missing piece I needed for the same issue-- triple stashes in 0.8 no longer supported. Here is an example that hopefully helps.
Where you might have had {{{resolve}}} in your template, you would now do:
<template name='thing'>
<ol>
{{#each all}}
{{resolve}}
{{/each}}
</ol>
<template>
The helper code then makes use of Spacebars.SafeString which looks to be preferred with Blaze:
Template.thing.helpers({
all: function () {
return Things.find();
},
resolve: function () {
var result = "<li>";
for (var i = 0; i < this.arrayOfClassNames.length; ++i)
result += <'div class='" + this.arrayOfClassNames[i] + "'></div>";
result += "</li>";
return new Spacebars.SafeString(result);
}
});
The key here is to return the 'new Spacebars.SafeString(result)' to wrap your HTML (which must be well formed).

Bootboxjs: how to render a Meteor template as dialog body

I have the following template:
<template name="modalTest">
{{session "modalTestNumber"}} <button id="modalTestIncrement">Increment</button>
</template>
That session helper simply is a go-between with the Session object. I have that modalTestNumber initialized to 0.
I want this template to be rendered, with all of it's reactivity, into a bootbox modal dialog. I have the following event handler declared for this template:
Template.modalTest.events({
'click #modalTestIncrement': function(e, t) {
console.log('click');
Session.set('modalTestNumber', Session.get('modalTestNumber') + 1);
}
});
Here are all of the things I have tried, and what they result in:
bootbox.dialog({
message: Template.modalTest()
});
This renders the template, which appears more or less like 0 Increment (in a button). However, when I change the Session variable from the console, it doesn't change, and the event handler isn't called when I click the button (the console.log doesn't even happen).
message: Meteor.render(Template.modalTest())
message: Meteor.render(function() { return Template.modalTest(); })
These both do exactly the same thing as the Template call by itself.
message: new Handlebars.SafeString(Template.modalTest())
This just renders the modal body as empty. The modal still pops up though.
message: Meteor.render(new Handlebars.SafeString(Template.modalTest()))
Exactly the same as the Template and pure Meteor.render calls; the template is there, but it has no reactivity or event response.
Is it maybe that I'm using this less packaging of bootstrap rather than a standard package?
How can I get this to render in appropriately reactive Meteor style?
Hacking into Bootbox?
I just tried hacked into the bootbox.js file itself to see if I could take over. I changed things so that at the bootbox.dialog({}) layer I would simply pass the name of the Template I wanted rendered:
// in bootbox.js::exports.dialog
console.log(options.message); // I'm passing the template name now, so this yields 'modalTest'
body.find(".bootbox-body").html(Meteor.render(Template[options.message]));
body.find(".bootbox-body").html(Meteor.render(function() { return Template[options.message](); }));
These two different versions (don't worry they're two different attempts, not at the same time) these both render the template non-reactively, just like they did before.
Will hacking into bootbox make any difference?
Thanks in advance!
I am giving an answer working with the current 0.9.3.1 version of Meteor.
If you want to render a template and keep reactivity, you have to :
Render template in a parent node
Have the parent already in the DOM
So this very short function is the answer to do that :
renderTmp = function (template, data) {
var node = document.createElement("div");
document.body.appendChild(node);
UI.renderWithData(template, data, node);
return node;
};
In your case, you would do :
bootbox.dialog({
message: renderTmp(Template.modalTest)
});
Answer for Meteor 1.0+:
Use Blaze.render or Blaze.renderWithData to render the template into the bootbox dialog after the bootbox dialog has been created.
function openMyDialog(fs){ // this can be tied to an event handler in another template
<! do some stuff here, like setting the data context !>
bootbox.dialog({
title: 'This will populate with content from the "myDialog" template',
message: "<div id='dialogNode'></div>",
buttons: {
do: {
label: "ok",
className: "btn btn-primary",
callback: function() {
<! take some actions !>
}
}
}
});
Blaze.render(Template.myDialog,$("#dialogNode")[0]);
};
This assumes you have a template defined:
<template name="myDialog">
Content for my dialog box
</template>
Template.myDialog is created for every template you're using.
$("#dialogNode")[0] selects the DOM node you setup in
message: "<div id='dialogNode'></div>"
Alternatively you can leave message blank and use $(".bootbox-body") to select the parent node.
As you can imagine, this also allows you to change the message section of a bootbox dialog dynamically.
Using the latest version of Meteor, here is a simple way to render a doc into a bootbox
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,MyCollection.findOne({_id}),box.find(".modal-body")[0]);
If you want the dialog to be reactive use
let box = bootbox.dialog({title:'',message:''});
box.find('.bootbox-body').remove();
Blaze.renderWithData(template,function() {return MyCollection.findOne({_id})},box.find(".modal-body")[0]);
In order to render Meteor templates programmatically while retaining their reactivity you'll want to use Meteor.render(). They address this issue in their docs under templates.
So for your handlers, etc. to work you'd use:
bootbox.dialog({
message: Meteor.render(function() { return Template.modalTest(); })
});
This was a major gotcha for me too!
I see that you were really close with the Meteor.render()'s. Let me know if it still doesn't work.
This works for Meteor 1.1.0.2
Assuming we have a template called changePassword that has two fields named oldPassword and newPassword, here's some code to pop up a dialog box using the template and then get the results.
bootbox.dialog({
title: 'Change Password',
message: '<span/>', // Message can't be empty, but we're going to replace the contents
buttons: {
success: {
label: 'Change',
className: 'btn-primary',
callback: function(event) {
var oldPassword = this.find('input[name=oldPassword]').val();
var newPassword = this.find('input[name=newPassword]').val();
console.log("Change password from " + oldPassword + " to " + newPassword);
return false; // Close the dialog
}
},
'Cancel': {
className: 'btn-default'
}
}
});
// .bootbox-body is the parent of the span, so we can replace the contents
// with our template
// Using UI.renderWithData means we can pass data in to the template too.
UI.insert(UI.renderWithData(Template.changePassword, {
name: "Harry"
}), $('.bootbox-body')[0]);

Meteor template.rendered - Why collection is empty?

Why in the following basic example returned collection inside rendered function is empty?
Autopublish is enabled. After the page loads calling command
Coll.find().fetch() inside javascript console returns correct set of entries
Here is the code
t.js
Coll = new Meteor.Collection("coll");
if (Meteor.isClient) {
Template.tpl.rendered = function(){
console.log(Coll.find().fetch()); // <-- This line prints empty array
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
if (Coll.find().count() === 0) {
var f = ["foo","bar"];
for (var i = 0; i < f.length; i++)
Coll.insert({f: f[i]});
}
});
}
And t.html file
<head>
<title>test</title>
</head>
<body>
{{> tpl}}
</body>
<template name="tpl">
Test tpl
</template>
Meteor is built off of a data-on-the wire type structure. This means when the app initially loads the HTML & JS is sent down first and the data later.
You have to use reactivity to check for data changes or check when the subscription to a collection is complete (which entails removing the autopublish package). (You can check out how to move your app to a manual subscription at the docs: http://docs.meteor.com/#publishandsubscribe)
The subscription callback tells you when the data is returned:
Meteor.subscribe("coll", function() {
//Data subscription complete. All data is downloaded
});
A template can also be made reactive (like the way you are doing) but .rendered isnt being called because Meteor first checks to see if a template's html has changed & only if it is different will it change its HTML and call the rendered callback.
What you have as an option here is to 1) use Deps.autorun instead, or
2) I'm not sure why you are using this in your rendered callback but if it is necessary to put it there you need to ensure that the HTML of the template changes, by introducing something into the html from your collection that makes it change when the new data is introduced.

Trigger.io + Angular.js and updating a view after calling forge.ajax

Having a problem, and so far couldn't get any solutions for seemingly similar SO questions to work. Problem is this:
Using Trigger.io's forge.ajax, my Angular.js view is not updated after the data is returned. I realize this is because forge.ajax is an asychronous function, and the data is returned after the view has already been displayed. I have tried to update the view by using $rootScope.apply(), but it doesn't work for me as shown in the many examples I have seen.
See the Controller code below:
function OfferListCtrl($scope) {
$scope.offers = [];
$scope.fetchOffers = function(callback) {
$scope.offers = [];
var successCallback = function(odataResults) {
var rawJsonData = JSON.parse(odataResults);
var offers = rawJsonData.d;
callback(offers);
};
var errorCallback = function (error){
alert("Failure:" + error.message);
};
forge.request.ajax({
type: 'GET',
url: 'https://www.example.com/ApplicationData.svc/Offers',
accepts: 'application/json;odata=verbose',
username: 'username',
password: 'password',
success: successCallback,
error: errorCallback
});
};
$scope.fetchOffers(function(offers) {
$scope.offers = offers;
forge.logging.info($scope.offers);
});
}
All the code there works fine, and $scope.offers gets populated with the Offer data from the database. The logging function shows the data is correct, and in the correct format.
I have tried using $rootScope.apply() in the logical places (and some illogical ones), but cannot get the view to update. If you have any ideas how I can get this to work, I would greatly appreciate it.
Edit: Added HTML
The HTML is below. Note the button with ng-click="refresh()". This is a just a workaround so I can at least see the data. It calls a one-line refresh function that executes $rootScope.apply(), which does update the view.
<div ng-controller="OfferListCtrl">
<h1>Offers</h1>
<ul>
<li ng-repeat="offer in offers">
<p>Description: {{offer.Description}}<br />
Id: {{offer.Id}}<br />
Created On: {{offer.CreatedOn}}<br />
Published: {{offer.Published}}<br />
</p>
</li>
</ul>
<input type="button" ng-click="refresh()" value="Refresh to show data" />
</div>
You need to change
$scope.fetchOffers(function(offers) {
$scope.$apply(function(){
$scope.offers = offers;
});
forge.logging.info($scope.offers);
});
It is because all changes to the $scope has to be made within the angular scope, in this case since you are calling ajax request using forge the callback is not executing within the angular framework, that is why it is not working.
You can use $scope.$apply() in this case to execute the code within angular framework.
Look at the $apply() methods doc
$apply() is used to execute an expression in angular from outside of
the angular framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the angular framework we need to perform proper scope life-cycle of
exception handling, executing watches.
do this
function MyController($scope, myService)
{
myService.fetchOffers(data){
//assign your data here something like below or whateever
$offers = data
$scope.$apply();
}
});
Thanks
Dhiraj
When I do that I have an error like : "$digest already in progress"...
I'm Working with $q...
Someone knwo how I can resolve this issue ?
yes, this is caused where ur data comes fast enough and angular has not finished his rendering so the update cant update "outside" angular yet.
so use events:
http://bresleveloper.blogspot.co.il/2013/08/angularjs-and-ajax-angular-is-not.html

Using Bladejs with Meteor

I recently added the node-blade smart package to my meteor and have static content displaying fine. However, I'm not able to use any template variables. Before I installed blade, the template variables worked fine with handlebars. Does anybody know what I'm doing wrong?
console output
ReferenceError: player is not defined
at ~/meteor/project/views/info.blade:1:1
1 > .player-name.large= player.name
2 | .alliance-name= alliance.name
3 | .world-name= world.name
4 |
at eval (eval at <anonymous> (/usr/local/lib/node_modules/blade/lib/compiler.js:138:23))
at /usr/local/lib/node_modules/blade/lib/runtime.js:323:5
at runtime.loadTemplate (/usr/local/lib/node_modules/blade/lib/runtime.js:272:6)
at /usr/local/lib/node_modules/blade/lib/blade.js:45:4
at Compiler.compile (/usr/local/lib/node_modules/blade/lib/compiler.js:185:2)
at compile (/usr/local/lib/node_modules/blade/lib/blade.js:41:12)
at Object.compileFile (/usr/local/lib/node_modules/blade/lib/blade.js:66:3)
at Object.runtime.loadTemplate (/usr/local/lib/node_modules/blade/lib/runtime.js:269:23)
at Object.runtime.include (/usr/local/lib/node_modules/blade/lib/runtime.js:320:22)
at eval (eval at <anonymous> (/usr/local/lib/node_modules/blade/lib/compiler.js:138:23))
Your application is crashing. Waiting for file change.
info.blade
.player-name.large= player.name
client.js
if(Meteor.is_client) {
Template.info.player = function(){
var data = Session.get( 'data' );
return data.player;
};
}
EDIT: Helpers are now permitted in body templates.
You cannot use helpers or certain global variables in head or body templates. You can't even use them in templates that are included by head or body templates.
Checkout these links for more information:
https://github.com/bminer/node-blade/issues/98
https://github.com/bminer/node-blade/wiki/Using-Blade-with-Meteor
EDIT: This answer is no longer accurate as of Blade 3.0.0 stable. body.blade templates may not contain dynamic stuff like helpers, references to Session, etc.
In 'Using Blade with Meteor' says that
References to Session are not allowed in head or body templates. This is by design, and it is not a bug. In Handlebars, you could use Session or Meteor within a tag, but not a tag. I didn't like the Handlebars implementation, so you're stuck with this one. The body.blade template is mostly for static content (i.e. a loading page or whatever). Once your application is loaded, you can do $("body").replaceWith(Meteor.ui.render(Template.homepage) ); from your application code.
So, this is saying that, on initialization, one could not have dynamic generated templates.
To workaround this, documentation suggests
$("body").replaceWith(Meteor.ui.render(Template.homepage) )
I replaced replaceWith method with html method. See an example that's working for me:
# ./the_cow.coffee
if Meteor.isClient
$ ->
$('body').html Meteor.render -> Template.test
user:
name: 'Pill'
# ./views/test.blade
#test Testing
p= user.name
See the compiled JavaScript:
if (Meteor.isClient) {
$(function() {
return $('body').html(Meteor.render(function() {
return Template.test({
user: {
name: 'Pill'
}
});
}));
});
}
Don't know if there is a shorter way to write it.

Resources