Multiple remoteCommands in a dataList, but only one call - forms

This one is tricky and I do not understand why it works like this:
<p:dataList var="item" value="#{recb.friends}" type="definition">
<p:column>
<h:form>
<p:remoteCommand name="getTaste" process="#this"
actionListener="#{item.calculateTaste( recb.username )}"
autoRun="true" oncomplete="poll.start()" />
<p:poll autoStart="false" update="#form" interval="1"
widgetVar="poll" oncomplete="poll.stop()" />
</h:form>
</p:column>
</p:dataList>
So what I expect to happen is: for each ITEM it will call the calculateTaste method.
What happens is: there is only one call, just for the last item in the dataList.
I run out of ideas what is wrong. I added columns so the ID's are generated, still it's not working :(.

As to the cause of the problem, this construct generates multiple JS variables with exactly the same name getTaste in the same scope, basically like so:
<script>var getTaste = function() { ... }</script>
<script>var getTaste = function() { ... }</script>
<script>var getTaste = function() { ... }</script>
...
They're basically overriding each other in the order they're declared and when invoking getTaste() on DOM ready basically the last one would be actually invoked. This matches exactly the symtoms you're observing (looking in the generated HTML source yourself by rightclick, View Source in browser would also have told you that).
You'd like to give them each an unique JS variable name. You can make use of the varStatus attribute of the <p:dataList> to get the current iteration status with among others the getIndex() method.
<p:dataList ... varStatus="loop">
...
<p:remoteCommand name="getTaste#{loop.index}" ... />
This way the generated code ends up with unique JS variable names:
<script>var getTaste0 = function() { ... }</script>
<script>var getTaste1 = function() { ... }</script>
<script>var getTaste2 = function() { ... }</script>
...
I'd also apply the same solution on <p:poll widgetVar> by the way.
<p:dataList ... varStatus="loop">
...
<p:remoteCommand name="getTaste#{loop.index}" ...
oncomplete="poll#{loop.index}.start()" />
<p:poll ...
widgetVar="poll#{loop.index}" oncomplete="poll#{loop.index}.stop()" />

Related

Dynamic input checkbox ids

I generate input type=checkboxes dynamically. ie. have them in my template.
These might come one hundred and problem is that the label requires for=“id-name” and I don't know how to give that same name as the input has
<input type=“checkbox” name=“special” v-bind:id="‘mynew’ + special.id />
<label v-bind:for="‘mynew’" + special.id">
obviously v-bind:for does not work
You have to keep the value of your input by making a model for it like:
<input v-model="message" ... />
<script>
export default {
data: {
return {
message: ''
}
}
}
Then you can use the model inside your for="" attribute as:
<label v-bind:for="'id+name' + message">

Excepted int for property "maxLength" of sap fiori Input component

Current I am trying to metadata binding xml, following this blog. When I did the maxLength of Input. But I got the following error.
error screenshot
demo service
init model with destination:
initModel: function() {
var sServiceUrl = "/odsrv/V2/Northwind/Northwind.svc/";
var oModel = new OM(sServiceUrl, true);
this.setModel(oModel, "oRefModel");
sap.ui.getCore().setModel(oModel, "oRefModel");
}
xml view:
<content>
<Label text="{oRefModel>/#Category/CategoryName/#type}"/>
<Input maxLength="{oRefModel>/#Category/CategoryName/#maxLength}"/>
</content>
The Label for type works fine if remove Input.
How to solve this problem...
Version with expression binding could be like that
<Input maxLength="{= isNaN(${oRefModel>/#Category/CategoryName/#maxLength}) ? 0 : parseInt(${oRefModel>/#Category/CategoryName/#maxLength})" />
typeof check is needed cause at the start of binding process value of property probably is 'NaN' and it gives an error and whole process will stop.
If you can improve that version please do :)
<Input maxLength="{parts:[{path:'oRefModel>/#Category/CategoryName/#maxLength'}],formatter: 'your.formatter.toNum' }" />
Formatter Code
toNum : function(maxlen){
return parseInt(maxlen);;
}
Converting string to integer is the key
Even shorter and without writing a custom formatter function: Use the built-in parseInt function.
<Input
maxLength="{
path: 'oRefModel>/#Category/CategoryName/#maxLength',
formatter: 'parseInt'
}" />
For some reasons expression binding does not work, maybe someone could tell me why:
<Input maxLength="{= parseInt(${oRefModel>/#Category/CategoryName/#maxLength}) }" />

Using Meteor what is the JS equivelent of {{{member}}}

I have a meteor helper that uses a reactive variable in a find to get a unique document using an id. My item button template looks like this:
<template name = "itemButton" >
<div class = "itemButton" name = {{_id}}>
{{{title}}}
</div>
</template>
using a reactive variable:
Template.landing.onCreated(function _OnCreated() {
this.f = new ReactiveVar();
this.f.set(false);
const handle = Meteor.subscribe("Feed");
});
now I have a method in a template several itemButton.
Template.landing.events({
'click .itemButton' : function(event, template){
alert(event.target.name);
template.f.set(event.target.name);
}
});
and I would like to use that name in a helper that would use this value as the _id.
Template.landing.helpers({
"GetFocus": function(){
alert(Template.instance().f.get()); // alerts undefined...
return(items.find({'_id':Template.instance().f.get()}));
}
});
So where I expect GetFocus to give me the document that generated the button I don't seem to be so lucky. Let me know if I can provide any additional clarification, and as always your input is appreciated.
Where I have template.f.set(event.target.name); I needed template.f.set(event.currentTarget.getAttribute('data-id')); where the html uses data-id instead of name.

rubaxa:sortable (Meteor) dying with error "TypeError: templateInstance.collection.findOne is not a function"

Using Meteor I implemented a pretty basic rubaxa:sortable instance. Not using multiple sortable lists or anything fancy. Pre-loaded "order" data in my Mongo Collection with unique numbers 1, 2, 3, etc.
Everything displays fine, and lets you sort once then the package dies when it tries to save the new order to the DB.
Error in the browser console is:
TypeError: templateInstance.collection.findOne is not a function. (In 'templateInstance.collection.findOne(itemId)', 'templateInstance.collection.findOne' is undefined)
adjustOrdersrubaxa_sortable.js:1404
sortableUpdaterubaxa_sortable.js:1446
_dispatchEventrubaxa_sortable.js:1102
_onDroprubaxa_sortable.js:799
(anonymous function)
handleEventrubaxa_sortable.js:853
Relevant code snippets below...
Server:
Products = new Mongo.Collection('products');
Sortable.collections = ['products'];`
Client JS:
Meteor.startup(function() {
Mongo.Products = new Mongo.Collection('products');
});
Client HTML:
<div class="sortable target" id="object">
{{#sortable items=products animation="100" handle=".sortable-handle" ghostClass="sortable-ghost" sortField="order"}}
{{> productSettingsRow}}
{{/sortable}}
</div>`
<template name="productSettingsRow">
<div data-id="{{order}}" class="sortable-item well well-sm">
<div class="row">
...
<div class="col s5">
<div class="input-field">
<input id="{{sku}}-displayName" type="text" value="{{displayName}}">
</div>
</div>
...
<div class="col s1">
<i class="sortable-handle mdi-action-view-headline pull-right"></i>
</div>
</div>
</div>
Tried searching on the error; don't see anything quite like this though #578 here seems similar(?):
https://github.com/RubaXa/Sortable/issues/578
Any suggestions, or any other info I can provide to help debug? (Also posted this to the GitHub repo as suggested.)
You need to define collections both on the client and the server. So all you might be missing is to put:
Products = new Mongo.Collection('products');
Sortable.collections = ['products'];
in a shared place, such as /lib/common.js.
Partial Solution Found...
The helper that was passing in the "products" collection was set up like this:
Template.productSettings.helpers({
products : function() {
return Mongo.Products.find().fetch();
}
});
It should have been:
Template.productSettings.helpers({
products : function() {
return Mongo.Products.find();
}
});
This way it's a pointer rather than an array. rubaxa:sortable doesn't yet handle arrays.
(Still working through a DB storage problem; this plugin doesn't seem to work out of the box with Meteor -- gives "Access Denied" when you try to update. Will try to sort that out tomorrow.)

How to Bind OData $count from expanded collection in an XML view

May be this is a basic question, but I have trouble binding the OData count in XML view.
In the following example, I want to bind the count of products from the OData model.
<List items="{/Categories}"} >
<ObjectListItem
title="{CategoryName}"
number="{path : 'Products/$count'}"
numberUnit="Products"/>
</List>
Each category needs to display count of products in the respective category as in
/Categories(1)/Products/$count
/Categories(2)/Products/$count
I had a similar issue. Although I am not thrilled with my solution, it uses expression binding and works without the need for a separate formatter:
<List items="{/Categories}"} >
<ObjectListItem
title="{CategoryName}"
number="{= ${Products}.length }"
numberUnit="Products" />
</List>
Like #Jasper_07, you still need to include Products in the expand, but you are ignoring most of the data coming back.
I dont think its currently possible
- $count is an OData query option, the equivalent in ODataListBinding is length, eg Products.length I cant think of a way to bind to it
you can achieve the count in a couple of ways using a formatter
option 1 - the simplest, create a list binding which reads the total number of products, it does a synchronous call and returns only the $count
function productCount(oValue) {
//return the number of products linked to Category // sync call only to get $count
if (oValue) {
var sPath = this.getBindingContext().getPath() + '/Products';
var oBindings = this.getModel().bindList(sPath);
return oBindings.getLength();
}
};
<List items="{/Categories}"} >
<ObjectListItem
title="{CategoryName}"
number="{path : 'CategoryName',formatter:'productCount'}"
numberUnit="Products"
</ObjectListItem>
</List>
option 2 - use an expand and return a very small set of data, in this case only CategoryName and ProductID, the caveat here is whether you have to by pass table paging to get full list
function productCount(oValue) {
//read the number of products returned
if (oValue) {
return oValue.length;
}
};
<List items="{/Categories,parameters:{expand:'Products', select:'CategoryName,Products/ProductID'}}">
<ObjectListItem
title="{CategoryName}"
number="{path : 'Products',formatter:'productCount'}"
numberUnit="Products"
</ObjectListItem>
</List>
Well.. I had exactly the same requirement and didn't want to perform the clever solution from #jasper as it will load all Products collection from the oData service.
This was the way I solve it:
View
Use a controller
Give your list an ID
Use a function on list's updateFinished event.
<mvc:View
controllerName="view.Root"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
>
<List id="list"
headerText="Categories"
items="{/Categories}"
growing="true"
growingThreshold="4"
growingScrollToLoad="true"
updateFinished=".countProducts"
>
<ObjectListItem
title="{description}"
numberUnit="Products"
/>
</List>
</mvc:View>
Controller
Implement countProducts function
Use jQuery to request the $count for each list item - Notice how the URL is generated concatenating model's service URL with the item's binding context
As jQuery uses asynchronous requests, by the time you get the first response, your for will be finished. So it can use IIFE to avoid filling just the last list item with your AJAX response
countProducts: function(e){
var m = sap.ui.getCore().getModel();
var items = this.byId("list").getItems();
for (var item_index = 0; item_index < items.length; item_index++) {
var item = items[item_index];
(function(_item) {
$.get(
m.sServiceUrl + _item.getBindingContextPath() + "/Categorias/$count",
function(count) {
_item.setNumber(count);
}
);
})(item);
}
}
I´d another solution using Manifest.json, Component.js and Controller.js for similar Issue.
First, I defined the Id in App.view.xml, for example:
<Title id="titleId" text="" level="H2"/>
After, I check Manifest.json, in especial:
{
"sap.app": {
"dataSources": {
"AXXX": {
"uri": "https://cors-anywhere.herokuapp.com/https://services.odata.org/Northwind/Northwind.svc/",
Next, in Componente.js at init:function() I put:
var oDataServiceUrl = this.getMetadata().getManifestEntry("sap.app").dataSources["AXXX"].uri;
console.log("oDataServiceUrl = ", oDataServiceUrl);
localStorage.setItem('oDataServiceUrl', oDataServiceUrl);
This code read Manifest.json and get Url to oDataService called AXXX.
Finnaly, I created one function in App Controller, such as:
countCustomersInAXXX : function (oEvent) {
var suffix = 'Customers/$count';
var oDataServiceUrl = localStorage.getItem('oDataServiceUrl');
var oDataServiceUri = oDataServiceUrl.concat(suffix);
console.log('App.controller.js: oDataServiceUri', oDataServiceUri);
var count = $.ajax({type: "GET", url: oDataServiceUri, async: false}).responseText;
console.log('App.controller.js: countCustomersInAXXX:' , count);
this.getView().byId("titleId").setText(count);
}
This code get the quantity of Customers and set the value in titleId.
To start this process you can user a button or one event, in my case I use this Table property:
updateFinished="countCustomersInAXXX"