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"
Related
This has been driving me nuts - hoping someone can help me.
I have a multifield component called 'books' with a single textfield: 'title'.
Everything seems to be working; the dialog box contains the multifield then I add two title fields then enter 'title1' and 'title2'.
then in the HTML itself I go:
<div data-sly-repeat="${properties.books}">
<p>${item}</p>
<p>${itemList.index</p>
<p>${item.title}</p>
</div>
What I don't get is, ${item} correctly gives me:
{"title": "title1"} {"title": "title2"}
and ${itemList.index} correctly gives me: 0 1
but ${item.title} keeps coming up blank. I also tried ${item["title"]} and that comes up blank too.
What am I doing wrong here? In my desperation I contemplated using
<div data-title="${item}"></div>
and then using JS to process the JSON object but I don't really want to do that.
Someone help, please!
It looks like your books property is either a JSON array string or a multivalued property with each value being a JSON object string;
The easiest way to parse the property is via a JS model like the following:
You could simplify this script to match your specific case, I made it general to multi-value and non-multi-value string properties.
/path/to/your-component/model.js:
"use strict";
use(function () {
// parse a JSON string property, including multivalued, returned as array
function parseJson(prop){
if(!prop) return [];
var result =[];
if(prop.constructor === Array){
prop.forEach(function(item){
result.push(JSON.parse(item));
});
}
else {
var parsed = JSON.parse(prop);
if(parsed.constructor === Array){
result = parsed;
}
else result = [parsed];
}
return result;
}
var $books = properties.get("books", java.lang.reflect.Array.newInstance(java.lang.String, 1));
var books = parseJson($books);
return {
books: books
}
});
/path/to/your-component/your-component.html:
<sly data-sly-use.model="model.js"/>
<div data-sly-repeat="${model.books}">
<p>${item}</p>
<p>${itemList.index</p>
<p>${item.title}</p>
</div>
I'm trying to load data from an ajax source into angular datatable but it's not even hitting the ajax call.
var analyzer=angular.module('analyzer', ['datatables']);
analyzer.controller('WithAjaxCtrl', WithAjaxCtrl);
function WithAjaxCtrl(DTOptionsBuilder, DTColumnBuilder) {
var vm = this;
$scope.dtOptions = DTOptionsBuilder.fromSource('/analyzer/List')
$scope.dtColumns = [
DTColumnBuilder.newColumn('BuildName').withTitle('Name'),
DTColumnBuilder.newColumn('Total').withTitle('Total'),
DTColumnBuilder.newColumn('Passed').withTitle('Passed'),
DTColumnBuilder.newColumn('Failed').withTitle('Failed')
];
}
Here's the html code for the table-
<div ng-controller="WithAjaxCtrl">
<table datatable="" dt-options="dtOptions" dt-columns="dtColumns" class="row-border hover"></table>
</div>
data from ajax source is in the form -
{"responseCode":0,"responseData":[{"Name":"Rob","Total":6273,"Passed":5874,"Failed":399}]}
So will i have to define the datasrc ?
Yes, you need to specify the dataSrc since your rows is contained in responseData, not in the default or expected data property. In angular dataTables there is an option setter named withDataProp() :
$scope.dtOptions = DTOptionsBuilder.fromSource('/analyzer/List')
.withDataProp('responseData')
Cannot link directly but look at https://l-lin.github.io/angular-datatables/#/api
HTML
<ion-input [(ngModel)]="login.username" ngControl="username1" type="number" #username1="ngForm" id="userName" required>
</ion-input>
PROTRACTOR TEST CODE
let usern: ElementFinder = element.all(by.css('.text-input')).get(0);
usern.sendKeys('error');
expect(usern.getAttribute("value")).toEqual("error");
browser.sleep(500);
usern.clear();
browser.sleep(1000);
usern.sendKeys('12345');
The element is found but no text is entered into the field. If I change the element to type="text" the protractor command works.And the page view is 'e' and can't be clear.
Secondly if I send string like this: "we2124will", the actually send data is '2124' and the result from getAttribute("value") is 2124.
Thirdly even if I changed the sendKeys to number, the result is not full number string. For example:
Failures:
1) Login page should input username and password
Message:
Expected '125' to equal '12345'.
Stack:
Error: Failed expectation
There are some number missing.
Since you're using an <ion-input>, the actual HTML <input> tag will be nested within, and it won't have an id attribute. The effect is that the wrong element can get selected.
Try something like below to grab the nested input tag:
let username = element(by.id('userName')).all(by.tagName('input')).first();
username.sendKeys('fakeUser');
That worked for me.
As a workaround, you can introduce a reusable function that would perform a slow type by adding delays between send every key.
First of all, add a custom sleep() browser action, put this to onPrepare():
protractor.ActionSequence.prototype.sleep = function (delay) {
var driver = this.driver_;
this.schedule_("sleep", function () { driver.sleep(delay); });
return this;
};
Then, create a reusable function:
function slowSendKeys(elm, text) {
var actions = browser.actions();
for (var i = 0, len = text.length; i < len; i++) {
actions = actions.sendKeys(str[i]).sleep(300);
}
return actions.perform();
}
Usage:
var elm = $("ion-input#userName");
slowSendKeys(elm, "12345");
What version of protractor are you using?
Not sure this is the issue but try grabbing the element by ng-model
var elem = element(by.model('login.username'));
elem.sendKeys('error');
expect(elem.getAttribute("value")).toEqual("error");
elem.clear();
elem.sendKeys('12345');
expect(elem.getAttribute("value")).toEqual("12345");
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.
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()" />