SAPUI5 table binditems from expand performed in a bindelement - odata

I am trying a proof of concept in SAPui5 to check the content of a searchfield, bind a result into another field, if i don't have any result i display an error message that has been written in a message field of my odata
so far i succeeded that this way
this.getView().bindElement({
path: "/ExcSet('" + evt.getSource().getValue() + "')",
model: "EXCEPTION",
events: {
dataReceived: function(response) {
if (response.mParameters.data.Message !== '') {
MessageBox.error(response.mParameters.data.Message);
}
}
}
(If someone knows how to do that a better way, because using mParameters is not the best way, it is welcome)
Now, I want to extend my odata call with an expand navigation to display a table of results, by not using another odata call that the one i used already, so here is my code so far :
eanSearch: function(evt) {
var oView = this.getView();
var oTemplate = new ColumnListItem({
cells: [
new Text({
text: "{Volum}"
}),
new Text({
text: "{Voleh}"
})
]
});
this.getView().bindElement({
path: "/ExcSet('" + evt.getSource().getValue() + "')",
model: "EXCEPTION",
parameters: {
expand: "ExcMarmNav"
},
events: {
dataReceived: function(response) {
if (response.mParameters.data.Message !== '') {
MessageBox.error(response.mParameters.data.Message);
}
}
}
});
oView.byId("table").bindItems({
path : '/ExcMarmNav',
template : oTemplate
});
}
The data of the expand is loaded into my response as you can see here
data: {
"ExcSet('5410366897766')": {
"__metadata": {
"id": "http://...:8000/sap/opu/odata/sap/ZEXCEPTION_SRV/ExcSet('5410366897766')",
"uri": "http://...:8000/sap/opu/odata/sap/ZEXCEPTION_SRV/ExcSet('5410366897766')",
"type": "ZEXCEPTION_SRV.Exc"
},
"Matnr": "000000000040000000",
"Ean": "5410366897766",
"Message": "",
"ExcMarmNav": {
"__list": [
"MarmSet(Matnr='40000000',Meinh='EA')"
]
}
},
"MarmSet(Matnr='40000000',Meinh='EA')": {
"__metadata": {
"id": "http://...:8000/sap/opu/odata/sap/ZEXCEPTION_SRV/MarmSet(Matnr='40000000',Meinh='EA')",
"uri": "http://...:8000/sap/opu/odata/sap/ZEXCEPTION_SRV/MarmSet(Matnr='40000000',Meinh='EA')",
"type": "ZEXCEPTION_SRV.Marm"
},
"Matnr": "40000000",
"Meinh": "EA",
"Umrez": "1",
"Umren": "1",
"Eannr": "",
"Ean11": "5410366897766",
"Numtp": "HE",
"Laeng": "20.000",
"Breit": "20.000",
"Hoehe": "10.000",
"Meabm": "CM",
"Volum": "4000.000",
"Voleh": "CCM",
"Brgew": "2.500",
"Gewei": "KG",
"Mesub": "",
"Atinn": "0000000000",
"Mesrt": "00",
"Xfhdw": "",
"Xbeww": "",
"Kzwso": "",
"Msehi": "",
"BflmeMarm": "",
"GtinVariant": "",
"NestFtr": "0",
"MaxStack": 0,
"Capause": "0.000",
"Ty2tq": ""
}
}
But i don't know how to use resultset to bind it into a table, my code above is not working, if someone has an idea of the params to use for my binditems, or if there is another way to do it?
Best regards
Denis

I managed to do the binding of my table using an intermediate JSON Model based on the response of the Odata Model
eanSearch: function(evt) {
var oView = this.getView();
this.getView().bindElement({
path: "/ExcSet('" + evt.getSource().getValue() + "')",
model: "EXCEPTION",
parameters: {
expand: "ExcMarmNav"
},
events: {
dataReceived: function(response) {
if (response.mParameters.data.Message !== '') {
MessageBox.error(response.mParameters.data.Message);
} else {
var model = new JSONModel({
"items": response.mParameters.data.ExcMarmNav
});
oView.setModel(model, "itemModel");
}
}
}
});
And this is the XML view:
<Table id="table" items="{itemModel>/items}">
<columns>
<Column><Label/></Column>
<Column><Label/></Column>
</columns>
<ColumnListItem>
<cells>
<Text text="{itemModel>Volum}"/>
<Text text="{itemModel>Voleh}"/>
</cells>
</ColumnListItem>
</Table>
works fine but I would like to avoid using the intermediate JSON Model and bind directly the array of the response in the table

Ok I managed to find what I needed :)
so the controller:
eanSearch: function(evt) {
var oView = this.getView();
this.getView().bindElement({
path: "/ExcSet('" + evt.getSource().getValue() + "')",
model: "EXCEPTION",
parameters: {
expand: "ExcMarmNav"
},
events: {
dataReceived: function(response) {
if (response.mParameters.data.Message !== '') {
MessageBox.error(response.mParameters.data.Message);
}
}
}
});
}
and the view:
<Table noDataText="No Data" items="{EXCEPTION>ExcMarmNav}">
<columns>
<Column><Label/></Column>
<Column><Label/></Column>
</columns>
<items>
<ColumnListItem>
<cells>
<Text text="{EXCEPTION>Volum}"/>
<Text text="{EXCEPTION>Voleh}"/>
</cells>
</ColumnListItem>
</items>
</Table>

Related

Webix not showing the kids data on dynamic loading

I am using webix to show some tree table data.
webix.ready(function () {
grida = webix.ui({
container: "testB",
view: "treetable",
columns: [
{ id: "id", header: "", css: { "text-align": "right" } },
{
id: "SerialNo", header: "Serial No", width: 250,
template: "{common.treetable()} #SerialNo#"
}
],
url: "/Test/GetTreeItem",
autoheight: true,
});
});
This loads the items perfectly.
Parents;
[{"id":11583,"Id":11583,"SerialNo":"12476127654","webix_kids":1},{"id":11584,"Id":11584,"SerialNo":"125235463","webix_kids":1},{"id":11585,"Id":11585,"SerialNo":"21385423348956","webix_kids":1},{"id":11586,"Id":11586,"SerialNo":"253346346346","webix_kids":1},{"id":11587,"Id":11587,"SerialNo":"123123","webix_kids":1},{"id":11588,"Id":11588,"SerialNo":"52354263","webix_kids":1},{"id":11589,"Id":11589,"SerialNo":"12344444","webix_kids":1},{"id":11590,"Id":11590,"SerialNo":"12344444","webix_kids":1},{"id":11591,"Id":11591,"SerialNo":"12344444","webix_kids":1},{"id":11592,"Id":11592,"SerialNo":"151515","webix_kids":1}]
However when I click the plus button, server returns (I can see the json string when I debug the code) the json but webix not appending the data underneath the parent.
Kids of parent "id":11587;
[{"id":11583,"Id":11583,"SerialNo":"12476127654","webix_kids":1},{"id":11592,"Id":11592,"SerialNo":"151515","webix_kids":1}]
id of data object must be unique per component.
Currently, you have for top level
{
"id": 11583,
"Id": 11583,
"SerialNo": "12476127654",
"webix_kids": 1
},
and in kids data you have
{
"id": 11583,
"Id": 11583,
"SerialNo": "12476127654",
"webix_kids": 1
},
both items share the same id, so treetable doesn't add a new item.
Correcting the JSON output solved my problem.
For the parents;
{
"parent":"0",
"data":[
{
"Id":11584,
"id":11584,
"SerialNo":"125235463",
"webix_kids":1
},
{
"Id":11599,
"id":11599,
"SerialNo":"3444",
"webix_kids":1
}
]
}
For the kids;
{
"parent":11599,
"data":[
{
"id":11583,
"Id":11583,
"SerialNo":"12476127654",
"webix_kids":1
},
{
"id":11592,
"Id":11592,
"SerialNo":"151515",
"webix_kids":1
}
]
}

TFS 2017 Release Management: How to display parent PBI for Tasks under Release

Is there any way to show the parent PBI for a Task Work item under Release in the list under TFS2017?
The screenshot below shows two tasks associated with Release-3. Here I wish to be able to display the parent PBI for each of them. Either by expanding them or just by displaying an additional column with link to the parent PBI
I appreciate your help
Edit:
I know that that there is a possibility to create a query on TFS. The problem is that I need to display the information on the parent Work item that are related to a specific Release so the user can use them for reporting. I tried to to create a query for this purpose but I couldn't find a filtering option based on Release so I thought it might be possible to enable some additional columns or there might be an extension for that but I couldn't figure out how to do it..
The steps to achieve that with extension:
Get specify release to get build id
Get work items of that build per build id
Get related work items
There is simple code of extension to get work items of specific release that you can refer to:
HTML:
<!DOCTYPE html>
<html>
<head>
<title>Custom widget</title>
<meta charset="utf-8" />
<script src="node_modules/vss-web-extension-sdk/lib/VSS.SDK.js"></script>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles:true
});
VSS.require(["TFS/Dashboards/WidgetHelpers", "TFS/TestManagement/RestClient", "TFS/WorkItemTracking/RestClient", "TFS/Build/RestClient", "VSS/Service", "VSS/Identities/Contracts", "VSS/Identities/RestClient", "VSS/Authentication/Services"], function (WidgetHelpers, TFS_Test_WebApi, TFS_Work_WebApi, TFS_Build_Client, VSS_Service, idContracts, idRest, VSS_Auth_Service) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("WidgetStarain", function () {
var authHeader = "none";
var vstsAccount = "none";
var projectName = "none";
var releaseRestAPIPrex = "none"
var getReleaseWorkItems= function (widgetSettings) {
var c = VSS.getWebContext();
vstsAccount = c.account.name;
projectName = c.project.name;
releaseRestAPIPrex="https://" + vstsAccount + ".vsrm.visualstudio.com/DefaultCollection/" + projectName + "/_apis/release"
VSS.getAccessToken().then(function (token) {
authHeader = VSS_Auth_Service.authTokenManager.getAuthorizationHeader(token);
$.ajax({
type: 'GET',
url: releaseRestAPIPrex+'/definitions?api-version=3.0-preview.1',
cache: false,
dataType: 'json',
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", authHeader);
},
}).done(function (data) {
var v = data.value;
$("#releaseDefList").empty();
$("#releaseDefList").append('<option value="select">select</option>');
$.each(v, function (index, value) {
$("#releaseDefList").append('<option value="' + value.id + '">' + value.name + '</option>');
});
}).error(function (e) {
var s = "ss";
});
});
};
$("#releaseDefList").change(function () {
var str = "";
$("#releaseList").empty();
$("#releaseList").append('<option value="select">select</option>');
$("#releaseDefList option:selected").each(function () {
var releaseDefId = $(this).val();
if (releaseDefId != "select") {
$.ajax({
type: 'GET',
url: releaseRestAPIPrex+'/releases?definitionId=' + releaseDefId + '&api-version=3.0-preview.2',
cache: false,
dataType: 'json',
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", authHeader);
},
}).done(function (data) {
var v = data.value;
$.each(v, function (index, value) {
$("#releaseList").append('<option value="' + value.id + '">' + value.name + '</option>');
});
}).error(function (e) {
var s = "ss";
});
}
});
});
$("#releaseList").change(function () {
var str = "";
$("#releaseList option:selected").each(function () {
var releaseId = $(this).val();
if (releaseId != "select") {
$.ajax({
type: 'GET',
url: releaseRestAPIPrex+'/release/releases/' + releaseId + '?api-version=3.0-preview.2',
cache: false,
dataType: 'json',
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", authHeader);
},
}).done(function (data) {
var artifacts = data.artifacts;
$.each(artifacts, function (index, value) {
var buildId = value.definitionReference.version.id;
TFS_Build_Client.getClient().getBuildWorkItemsRefs(projectName, buildId).then(function (workitemRefs) {
var workItemIds = new Array();
$.each(workitemRefs, function (index, value) {
workItemIds.push(value.id);
});
var workitemString = "";
TFS_Work_WebApi.getClient().getWorkItems(workItemIds,null,null,"All").then(function (workitems) {
$.each(workitems, function (index, value) {
workitemString += "ID: " + value.id + "; Title: " + value.fields["System.Title"];
});
$("#workitems").text(workitemString);
});
});
});
}).error(function (e) {
var s = "ss";
});
}
});
});
return {
load: function (widgetSettings) {
getReleaseWorkItems(widgetSettings);
return WidgetHelpers.WidgetStatusHelper.Success();
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="widget">
<h2 class="title">widgets starain</h2>
<div class="token">none</div>
<select id="releaseDefList">
<option id="select">select</option>
</select>
<select id="releaseList">
<option id="select">select</option>
</select>
<div id="workitems">
none workitem
</div>
</div>
</body>
</html>
vss-extension.json:
{
"manifestVersion": 1,
"id": "sample-extension",
"version": "0.5.34",
"name": "My test extension",
"description": "my test extension description",
"publisher": "Starain",
"targets": [
{
"id": "Microsoft.VisualStudio.Services"
}
],
"icons": {
"default": "Images/logo.png"
},
"scopes": [
"vso.work",
"vso.build",
"vso.build_execute",
"vso.test",
"vso.test_write",
"vso.release"
],
"contributions": [
{
"id": "WidgetStarain",
"type": "ms.vss-dashboards-web.widget",
"targets": [ "ms.vss-dashboards-web.widget-catalog", "Starain.sample-extension.WidgetStarainConfiguration" ],
"properties": {
"name": "widget starain",
"description": "custom widget",
"catelogIconUrl": "Images/iconProperty.png",
"previewImageUrl": "Images/iconProperty.png",
"uri": "WidgetStarain.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
}
],
"supportedScopes": [ "project_team" ]
}
}
],
"files": [
{
"path": "node_modules/vss-web-extension-sdk/lib",
"addressable": true
},
{
"path": "Images",
"addressable": true
},
{
"path": "Scripts",
"addressable": true
},
{
"path": "WidgetStarain.html",
"addressable": true
}
]
}

Knockoutjs one to many mapping not working

In this following code snippet I am creating parent child view model using knockoutjs mapping plugin.I am not getting any error in mapping But when I am running the application it's throwing the error name is not defined.
data = {
"Id": 1,
"Name": "Microsoft",
"Address": "USA",
"WebPage": "SS",
"Employees": [
{
"Id": 1,
"FirstName": "sadsasa",
"LastName": "ADF",
"Twitter": "dfd",
"WebPage": "sdfdf"
},
{
"Id": 2,
"FirstName": "sadsasa",
"LastName": "ADF",
"Twitter": "dfd",
"WebPage": "sdfs"
},
{
"Id": 3,
"FirstName": "sadsasa",
"LastName": "ADF",
"Twitter": "dfd",
"WebPage": "sfdfs"
}
]
};
var mapping = {
'Employees': {
create: function (options) {
alert(options);
return new PersonViewModel(options.data);
}
}
}
function PersonViewModel(data) {
ko.mapping.fromJS(data);
}
function CompanyViewModel(data) {
ko.mapping.fromJS(data, mapping, this);
}
var company;
$(document).ready(function () {
$.ajax({
url: '/api/Greet',
type: 'GET',
dataType: 'json',
success: function (result) {
data = JSON.stringify(result);
company = new CompanyViewModel(data);
console.log(company);
ko.applyBindings(company);
},
error: function (e) {
alert(e);
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div>
FirstName: <span data-bind="text:Name"></span>
</div>
Well you need to do some small modifications in your code .
View Model :
function PersonViewModel(data) {
ko.mapping.fromJS(data,mapping,this);
}
function CompanyViewModel(data) {
self.childlist=ko.observableArray();
ko.mapping.fromJS(data, mapping, this);
self.childlist.push(new PersonViewModel()); //child array
}
View :
<div>
<b>FirstName:</b> <span data-bind="text:Name"></span>
<div data-bind="foreach:Employees">
<span data-bind="text:Id"></span>
</div>
</div>
Working fiddle here
Do let me know incase of any issues .

How do get an Ember.Select to be populated via Rails backend data?

I'm trying to write a select box in Ember, based on a Rails back end. When editing the model Recipes, I want to be able to select from a list of Sources in a dropdown. Right now in Ember I'm getting the message "The value that #each loops over must be an Array. You passed App.Sources" as a result of the following code.
I have tested the REST api and it is providing the response for Recipes and Sources both properly.
I'm new to Embers (and Javascript too, actually!) and I feel like I'm missing something basic. Thank you for any tips.
Here's my JS:
App.RecipeEditController = Ember.ObjectController.extend({
needs: ['sources'],
selectedSource: null,
actions: {
save: function() {
var recipe = this.get('model');
// this will tell Ember-Data to save/persist the new record
recipe.save();
// then transition to the current recipe
this.transitionToRoute('recipe', recipe);
}
}
});
App.RecipesRoute = Ember.Route.extend({
model: function() {
return this.store.find('recipe');
},
setupController: function(controller, model) {
this._super(controller, model);
this.controllerFor('sources').set('content', this.store.find('source'));
}
});
App.SourcesRoute = Ember.Route.extend({
model: function() {
return this.store.find('source');
}
});
DS.RESTAdapter.reopen({
namespace: "api/v1"
});
App.Recipe = DS.Model.extend({
title: DS.attr('string'),
url: DS.attr('string'),
rating: DS.attr('number'),
source: DS.belongsTo('source', {
async: true
}),
page_number: DS.attr('number'),
want_to_make: DS.attr('boolean'),
favorite: DS.attr('boolean')
});
App.Source = DS.Model.extend({
title: DS.attr('string'),
authorLast: DS.attr('string'),
recipes: DS.hasMany('recipe', {
async: true
})
});
App.Router.map(function() {
return this.resource("recipes");
});
App.Router.map(function() {
return this.resource("recipe", {
path: "recipes/:recipe_id"
});
});
App.Router.map(function() {
return this.resource("recipeEdit", {
path: "recipes/:recipe_id/edit"
});
});
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter
});
And here's the view:
{{view Ember.Select
contentBinding="controllers.sources.content"
optionLabelPath="content.title"
optionValuePath="content.id"}}
UPDATE And here's the JSON:
{
"recipes": [
{
"id": 3,
"title": "Did this make it through?",
"url": "www.hellyeahitdid.com/high-five/",
"rating": null,
"source_id": null,
"page_number": null,
"want_to_make": false,
"favorite": false
},
{
"id": 1,
"title": "Here's another totally crazy one for ya",
"url": "http://www.example.com/recipe/1",
"rating": null,
"source_id": null,
"page_number": null,
"want_to_make": false,
"favorite": false
},
{
"id": 2,
"title": "A Sample Recipe",
"url": "http://www.example.com/recipe/1",
"rating": null,
"source_id": null,
"page_number": null,
"want_to_make": false,
"favorite": false
}
]
}
{
"sources": [
{
"id": 1,
"title": "Joy of Cooking",
"author_last": null
},
{
"id": 2,
"title": "Everyday Food",
"author_last": null
}
]
}
Welcome to javacript/ember, here's an example using a select.
http://emberjs.jsbin.com/OxIDiVU/69/edit
you'll notice the i don't quote real properties, and do quote strings
{{ view Ember.Select
content=someSource
optionValuePath='content.id'
optionLabelPath='content.title'
}}
Additionally that appears to be a very old version of Ember Data. You may consider updating. https://github.com/emberjs/data/blob/master/TRANSITION.md
Your routing can go in a single call
App.Router.map(function() {
this.resource("recipes");
this.resource("recipe", { path: "recipes/:recipe_id"}, function(){
this.route('edit');
});
});
This should get you started
http://emberjs.jsbin.com/OxIDiVU/74/edit

knockout dynamic binding issue

Consider the following ViewModel that is generated through the knockout mapping plugin.
var originalData = {
"QuoteSelectedViewModel": {
"ProductName": "Select",
"CoverQuotesViewModel": [
{
"Code": 1,
"Label": "Première Assistance 24h/24 (GRATUITE)",
"IsMandatory": true,
"IsSelected": true,
"DependsOn": []
},
{
"Code": 2,
"Label": "Assistance PLUS 24h/24",
"IsMandatory": false,
"IsSelected": false,
"DependsOn": []
},
{
"Code": 8,
"Label": "Heurts Animaux / Force de la Nature",
"IsMandatory": false,
"IsSelected": false,
"DependsOn": [
2
]
},
]}
}
var viewModel = ko.mapping.fromJS(originalData);
ko.applyBindings(viewModel);
<div data-bind="with: QuoteSelectedViewModel">
selected quote is : <span data-bind="text: ProductName"></span>
<!-- ko foreach: CoverQuotesViewModel -->
<br/>
<div data-bind: if: IsVisible>
<input type="checkbox" data-bind="checked: IsSelected"></input>
<input type="text" data-bind="value: Label, enable: IsSelected"></input>
</div>
<!-- /ko -->
</div>
Now, I would like to hide the div when IsVisible returns false. IsVisible does not exist yet, and it should be a computed observable function on each element of the CoverQuotesViewModel array.
How do I generate this computed observable function on each element ?
Thanks
[EDIT]
I've added a jsfiddle here : http://jsfiddle.net/graphicsxp/fpKWM/
[EDIT2]
Actually knockout document is clear about how to do that:
Of course, inside the create callback you can do another call to
ko.mapping.fromJS if you wish. A typical use-case might be if you want
to augment the original JavaScript object with some additional
computed observables:
var myChildModel = function(data) {
ko.mapping.fromJS(data, {}, this);
this.nameLength = ko.computed(function() {
return this.name().length;
}, this); }
[EDIT]
Here's the full code following Paul's suggestion:
(getQuotesSuccess is an AJAX success handler)
viewModel.getQuotesSuccess = function (result) {
var myCoverQuotesViewModel = function (data, parent) {
var self = this;
ko.mapping.fromJS(data, {}, this);
self.IsVisible = ko.computed(function () {
var visible = true;
if (self.DependsOn().length > 0) {
$.each(self.DependsOn(), function (index, value) {
var dependency = viewModel.QuoteSelectedViewModel().CoverQuotesViewModel.filterByProperty("Code", value);
if (dependency().length > 0) {
visible = visible & dependency()[0].IsSelected();
} else {
visible = false;
}
});
}
return visible;
}, this);
}
var mapping = {
'CoverQuotesViewModel': {
create: function (options) {
return new myCoverQuotesViewModel(options.data, options.parent);
}
}
}
ko.mapping.fromJS(result, mapping, viewModel);
};
Ok, reverting back to my earlier answer, with your modifications, so anyone else looking at this answer actually gets the correct version!
You need to create a child viwe model, and use the mapping plugin to populate it automatically, then add in your computed observable:
function CoverQuotesViewModel(data)
{
var self = this;
ko.mapping.fromJS(data, {}, self);
// Copy the data to each property.
self.IsVisible = ko.computed(function()
{
// your logic for each quote
});
}
Then you need to use a create map for the mapping of the main view model, and in this you create your child view model:
var mapping = {
'CoverQuotesViewModel': {
create: function(options) {
var model = new CoverQuotesViewModel(options.data);
return model;
}
}
}
var viewModel = ko.mapping.fromJS(data, mapping);
You don't need to pass this into the computed, as you are referencing self, which is your stored version of this.

Resources