How to get property of a JSONModel in my Controller? [duplicate] - sapui5

This question already has answers here:
How to get model on onInit?
(2 answers)
Closed 4 years ago.
I need to retrieve the property of my JSONModel in my controller.
The scenario is as follows:
I have a "questionnaire" model (questionnaire.json) which contains questions and answers. Then I have a controller (App.controller.js).
In this controller, I have created one more model in onInit with the name "currentQuestion" because I need to implement a scenario where the next question should be loaded when the user clicks on the next button.
I am trying to get the property of my "questionnaire" model but I am facing an error:
Cannot read property of 'getProperty' of undefined.
You can see the model data and my controller here:
questionnaire.json
{
"data": [{
"question": "Which pet do you like from the following?",
"answers": ["Cats", "Rabbits", "Dogs", "Hamsters"],
"correct": 1
}, {
"question": "Which pet do you prefer from the following?",
"answers": ["Cats", "Dogs"],
"correct": 1
}, {
"question": "What food brand does your pet eat?",
"answers": ["Pedigree", "Catfood", "Rabbitfood", "HamstersFood"],
"correct": 1
}]
}
App.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel"
], function(Controller, JSONModel){
Controller.extend("opensap.onlinequestionnaire.controller.App", {
onInit: function() {
this.questionSetUp();
this.getView().setModel(new JSONModel({
index: false,
question: {}
}), "currentQuestion");
var oCurrentQuestionModel = this.getView().getModel("currentQuestion");
var oQuestionModel = this.getView().getModel("questionnaire");
var iCurrentIndex = oCurrentQuestionModel.getProperty("/index");
var oQuestion = oQuestionModel.getProperty("/data/0/question");
console.log(oQuestion);
console.log(iCurrentIndex);
iCurrentIndex = iCurrentIndex ? ++iCurrentIndex : 0;
console.log(iCurrentIndex);
}
});
});
When I try to question property in oQuestion, it throws me the error. How do I get the 0th element's question property's value?

One more thing I was doing wrong was I was trying to get this model in init. [src]
Unfortunately yes. That was actually the main reason why the model was undefined in this case. But not because the model was not loaded.
We know from the documentation that models defined in the app descriptor (manifest.json) are set to the component and that those models get propagated to component's children. Hence, calling view.getModel from any controller should return the model seamlessly because it's set globally on the component. But: Something that is not obvious is that models set on the component cannot be retrieved in onInit of any Controller just by using the corresponding view because the view doesn't know its parent yet in that state. Calling view.getParent in onInit returns null. So the component model "questionnaire" was not propagated to your view yet. As you did it already, such component models can be easily retrieved in onInit by using the component instead of the view: this.getOwnerComponent().getModel("modelName") and the error
Cannot read property of 'getProperty' of undefined
is gone.

the method setModel(oModel, sName?) sets a model to your view. In your code you set a Model with the name "currentQuestion" to the view which contains the data from the questionnaire.json file. You get this model in the variable oCurrentQuestionModel.
Please try to get the property from this model by the following code:
oCurrentQuestionModel.getProperty("/data/0/question");
In your code snippet it is not clear where oQuestionModel is defined.

In your case. I think you should change
var oQuestionModel = this.getView().getModel("questionnaire");
to
var oQuestionModel = this.getView().getModel();
because you did not name your model so that it becomes the default model.

Related

OData error when bind to an element in a Master-Detail app

I have developed a Master-Detail app. In the Detail view, I am using a DynamicPage where within content, I am using an IconTabBar Element with 3 items. Each item is a different Fragment with a Smartform.
Master view is loading the data from an EntitySet (MasterEntity)
Detail view with a is using some fields from the Entity MasterEntity
Fragment 1: is using DetailEntitySet
Fragment 2: is using DetailEntitySet
Fragment 3: is using DetailEntitySet
Basically, when Detail View Controller is detecting the RouteMatch, I am receiving the selected line on Master View and I bind it to the View Detail. Once it is done, I am checking if the view was generated and then, calling a method to bind the Expanded entity to the iconTabBar Element which contains the 3 Fragments. The code is as follows:
function _onRoutePatternMatched(event) {
if (event.getParameter("name") === "detail") {
var path = event.getParameter("arguments").contextPath;
if (path !== " ") {
var path2 = "/" + path;
view.bindElement(path2);
if (view) {
this._setBindingToIconTab("Master2Detail");
}
} else {
view.unbindElement();
}
}
}
The _setBindingToIconTab function is as follows:
_setBindingToIconTab: function (sAssociation) {
view.byId("iconTabBar").bindElement(sAssociation);
}
iconTabBar is the ID I have assigned within the Detail view to the IconTabBar Element.
The problem is, when I execute it and it loads the first Fragment, all is ok. With the 2nd and 3rd, I got errors (but the values are displayed...). I am loading the Fragments when they are picked on the screen. If they were not generated, I instantiate and store them in an array. The errors I got are:
Assertion failed: The EDM property "DateFrom" was not found in the "ZZODATA_TEST_SRV.Master" entity type. -
sap.ui.comp.smartfield.ODataControlFactory
It is complaining about Fields from DetailEntitySet are not in MasterEntitySet.
Could you please give me a hand with this?
As there is no XML, running example etc. i can't tell you why this error occurs but..
Best practise is to biind also in the detail view the selected entity with expand to the detail and so on
MasterEntitySet->DetailEntitySet
As bindings are propagated to children there is no need to bind the iconTabBar again. Data is already there. Again i don't know your case, but most services look like this
MasterEntitySet->DetailEntitySet->DataVariantA(ForIconTab1)
->DataVariantB(ForIconTab2)
->DataVariantC(ForIconTab3)
In V4 your detail code looks like this
oView.bindObject({
path: "/MasterEntity(" + this._args.ID + ")",
parameters: {
$expand:`DetailEntityNavPath($expand=DataVariantANavPath()...`
},
events: {
dataReceived: (oEvent) => {...

AG-Grid with TreeData using Enterprise Row Model

I am trying to implement a grid with tree data using ag-grid. I am using the Enterprise Row Model. The problem is that when hard coding the data and setting it through setRowData the grid displays perfectly. However, when data is loaded through the enterprise row model, the grid does not render as a tree. In fact, the getDataPath callback is not even being called.
Did anyone manage to use the tree data feature with an enterprise data source as this does not seem to be documented?
Thanks
I am assuming that by Enterprise row model, you mean Serverside row model so you expect tree structured data from server. In that case, I have been able to combine following features in Ag-grid : Infinite scrolling + Tree data + server side row model.
I have even implemented custom filtering and it's working as expected.
Data flow:
We have to enable the server side row model on ag-grid using its configuration.
Implement a fake server and proxy data source objects in JavaScript. The object of data source must contain a method named "getRows" that ag-grid can call. Ag-grid will call this method every time user performs actions such as: scroll, filter, sorting, expanding a parent row to see child rows etc.
Implement a method called onGridReady() which will be called by ag-grid every time it's trying to render the grid first time, and then pass the server and dataSource objects to ag-grid's internal API inside onGridReady().
Implementation (using combination of ReactJS and plain JavaScript):
Enable serverSide row model in ag-grid.
<AgGridReact
columnDefs={this.columnDefs}
rowModelType={this.rowModelType}
treeData={true}
isServerSideGroup={this.isServerSideGroup}
getServerSideGroupKey={this.getServerSideGroupKey}
onGridReady={this.onGridReady}
cacheBlockSize={50}
/>
Implement a fake server and proxy data source objects in JavaScript.
To work with server side row model, you need to supply the data in an instance of ServerSideDataSource in JavaScript. Instance of ServerSideDataSource must have a method called getRows() which will be called by ag-grid every time user scrolls down to get next set of data or a row is expanded for retrieving its children records in Tree structure.
The constructor for ServerSideDataSource accepts a proxy data container, typcailly used in ag-grid example: a fakeServer instance. A singleton instance of fakeServer holds the data that was received from the real server and keeps it for following usage:
a) when ag-grid wants to display child records, it calls getRows. Because we supply a custom implementation of this ServerSideDataSource, we can write logic inside getRows to extract data from this fakeServer instance.
b) When ag-grid tries to display next set of data in infinite scrolling or paging, it checks how much data was last retrieved to ask for next block in infinite scrolling (using startRow and endRow variable).
Defining fake server and server side data source:
function createFakeServer(fakeServerData) {
function FakeServer(allData) {
this.data = allData;
}
FakeServer.prototype.getData = function(request) {
function extractRowsFromData(groupKeys, data) {
if (groupKeys.length === 0) {
return data; //child records are returned from here.
}
var key = groupKeys[0];
for (var i = 0; i < data.length; i++) {
if (data[i].employeeId === key) {
return extractRowsFromData(groupKeys.slice(1), data[i].children.slice());
}
}
}
return extractRowsFromData(request.groupKeys, this.data);
};
return new FakeServer(fakeServerData);
}
function createServerSideDatasource(fakeServer) {
function ServerSideDatasource(fakeServer) {
this.fakeServer = fakeServer;
}
ServerSideDatasource.prototype.getRows = function(params) {
console.log("ServerSideDatasource.getRows: params = ", params);
var rows = this.fakeServer.getData(params.request);
setTimeout(function() {
params.successCallback(rows, rows.length);
}, 200);
};
return new ServerSideDatasource(fakeServer);
}
Implement onGridReady()
Once this dataSource is ready, you have to supply this to ag-grid by calling its API method: params.api.setServerSideDataSource(). This API is available inside onGridReady() method that must be passed to Ag-grid as well. This method is mandatorily required if you're using serverSide row model.
onGridReady = params => {
...
var fakeServer = createFakeServer(jsonDataFromServer);
var dataSource = createServerSideDatasource(fakeServer);
params.api.setServerSideDatasource(dataSource);
}
Providing a key property that help ag-grid identify parent-child relationship. You have to supply these parameters to grid. Check the point (1) in Implementation that has HTML syntax and shows how to supply these methods to ag-grid.
var rowModelType = "serverSide";
var isServerSideGroup = function (dataItem) {
return !!dataItem.children;
};
var getServerSideGroupKey = function (dataItem) {
return dataItem.employeeId;
};
Notice that in getServerSideGroup(), we are returning a boolean value which checks whether children property of current row (i.e. dataItem) has any children or not.
I would request you to separately look through documents for server side row model for each feature and that means Tree data (client model) and Tree data (server side model) have two different approaches. We can't setup one model and expect it to work with data of other model.
Documentation for server side row model : https://www.ag-grid.com/javascript-grid-server-side-model/
Please try this and let me know. I had these requirements a month ago, so I contacted them for their help on Trial Support and they have prepared a plunker for this problem statement:
Working example for Tree data from server side with infinite scroll. https://next.plnkr.co/edit/XON5qvh93CpURbOJ?preview
Note:
Since the tree data is being retrieved from server, you can't use getDataPath.
Tree data would be in nested hierarchy per row unlike the client-side tree model. So unique column names are not encapsulated in an array.
Wrong :
var rowData = [
{orgHierarchy: ['Erica'], jobTitle: "CEO", employmentType: "Permanent"},
{orgHierarchy: ['Erica', 'Malcolm'], jobTitle: "VP", employmentType: "Permanent"}
...
]
Right :
[{
"employeeId": 101,
...
"children": [
{
"employeeId": 102,
...
"children": [
{
"employeeId": 103,
...
},
{
"employeeId": 104,
...
}
]},
]}
}]
There was one point when the grid was not being rendered at all in initial phase when I was just setting up the grid with Enterprise features and so it's their recommendation to use px instead of % for height and width of the wrapper DIV element that contains your Ag-grid element.
Edit:
If you prefer to fetch children record in separate API calls to save initial load time then you can make these API calls inside getRows() method. API call will have success and error callbacks. Inside the success callback method, once you receive the children data, you can pass them to ag-grid using:
params.successCallback(childrenData, childrenData.length);
Example: When a parent row is expanded, it sends a unique key of that parent row (which you must have configured already) through params.request.groupKeys. In this example, I am using JavaScript's fetch() to represent API calling. This method accepts ApiUrl, and optional request parameters' object in case of POST/PUT requests.
ServerSideDatasource.prototype.getRows = function(params) {
//get children data based on unique value of parent row from groupKeys
if(params.request.groupKeys.length > 0) {
fetch(API_URL, {...<required parameters>...})
.then(response => response.json(), error => console.log(error))
.then((childrenData) => {
params.successCallback(childrenData, childrenData.length);
});
}
else {
//this blocks means - get the parent data as usual.
params.successCallback(this.fakeServer.data, this.fakeServer.data.length);
}
};
Adding import 'ag-grid-enterprise' followed by initializing the enterprise key resolved the issue of getDataPath not working correctly.
Using example provided by Akshay Raut above, I was inspired to combine server side pagination, and client side grouping. Following his steps, with a slight change. On the getServerSideDatasource function, you can check if the parent node have the children, and rather than calling the server, just return the children directly. That way, you will be able to display the already loaded children as client side. Here is a sample code:
getServerSideDatasource(): IServerSideDatasource {
return {
getRows: (params) => {
if (params.request.groupKeys.length > 0) {
params.success({
rowData: params.parentNode.data.sales,
rowCount: params.parentNode.data.sales.length,
});
} else {
// Your regular server code to get next page
}
Here is a stackblitz:
https://stackblitz.com/edit/ag-grid-angular-hello-world-1gs4jx?file=src%2Fapp%2Fapp.component.ts
Make sure you are using gridOptions.treeData = true An example is here.
Hierarchy of data should be properly set when you implement the gridOptions.getDataPath(data)
Make sure you have implemented Enterperise.getRows Read more
If the above things don't work, share your code here to understand the overall picture better.
Infinite Scrolling or Enterprise/Serverside datasources are not compatible with Tree Data
https://www.ag-grid.com/javascript-grid-row-models/
So you have to either change your code to use Client Side Row Model or use Row Grouping (only available in enterprise)

Can we bind the ManagedObject to View? [duplicate]

This question already has answers here:
Is it possible to bind sap.ui.base.ManagedObject to the view?
(3 answers)
Closed 2 years ago.
I have prepared a object which by extending ManagedObject
and I have added these into the JSONModel of the view in controller as
var model = new JSONModel({
data : someOne.getManagedObjects()
});
How to bind these to view? I have tried these
1: <List id="mainLayerList"
selectionChange="onSelectionChange"
mode = "MultiSelect"
items="{/data}">
2:
<List id="mainLayerList"
selectionChange="onSelectionChange"
mode = "MultiSelect"
items="{
path: '/data',
factory: '.dataListItemFactory'
}">
Not getting the data on view.
Can we bind the ManagedObject to the view?
You can bind to any Javascript object. But you can only use attributes and properties on the bound objects. So you can't call functions like getName().
What do ManagedObjects do with the properties you declared in the metadata? It creates getXxx() and setXxx() methods and stores the data xxx somewhere inside a private object. Thats why you cant bind to {xxx} even though you have declared a xxx property on the ManagedObject.
As a workaround you could create getter properties and setter properties on your ManagedObject for all attributes you want to bind to:
get xxx() { return this.getXxx();}
set xxx(value) { this.setXxx(value);}

SailsJS best practice to seed database with data before other Models are initialized

There is a model that all other models assume its existence.
It should be initialized before any API function is called.
The way I do this (it doesn't work):
1) Define model in api/models, let's call it Location.js
2) Add the following to bootstrap.js
var Locations = require('../api/models/Locations.js');
module.exports.bootstrap = function (cb) {
// seed the database with Locations
var locationsObj = {
country: 'Australia',
states: ['Brisbane', 'Perth', 'Sydney']
};
Location.create(locationsObj, function locationsObj(err, locations) {
if (err) {
cb(err);
}
console.log('locations created: ', locations);
});
}
Question 1
Is it the right way to do initial database seeding?
I get this error:
Locations.create(locationsObj, function locationsObj(err, locations) {
^
TypeError: Object #<bject> has no method 'create'
Question 2
How does the cb function of bootstrap work?
what if there as an error, what to do?
The sails models are globally available; so you don't need to require at bootstrap.js.
This is what I use to seed my database. (See the links I enclose to go to the gists)
Include seed function at config/models.js. The methods you declare in this file will be extended to all your models.
Link: Seed method gist
Define de data the seed will consume in your model Link: Model's seed data
Call the seed method in config/bootstrap.js using async. Link: Calling method
UPDATE
Have a look at this threat also: Best way to migrate table changes to production sailsjs tables
From Cannot unit test my model in sailsjs:
"Once Sails app is lifted, you will have your models available automatically...
And in your case, your first line overrides the User model which would be otherwise constructed by Sails.js, that's why even though you have an object it's not a Waterline model."
I know this is old but, for completeness:
You set
var Locations = ...
But but you call
Location.create()
(no 's') so you just have a typo.
in config/bootstrap.js you can write your seeds directly. Take a look at the example below.
await sails.models.role.createEach([
{
name: 'Admin',
},
{
name: 'Normal-user',
},
]);
here 'role' is name of the table created and not the model name.

ASP.NET MVC2 - passing a parameter to a controller

This is for a project I'm doing in the university. We're using ASP.NET MVC2 to build a mini-prototype of a website just to show what we can do.
Now, I have a controller called ItemController.cs, a view Item/Index.aspx, a model Item.cs and a ViewModel ViewItems.cs. I use the ViewModel to pass information to the view from the controller. My question is - how do I call a controller method with parameters? Currently, I use
Html.ActionLink("View Event Items", "Index", "Item")
which points to ItemController's Index() method. Say I want it to take an int parameter (Index(int eventId)) How would I write the ActionLink to pass the parameter to it?
Also, if I have any errors in how I think this stuff works, please feel free to point them out.
You'll need to use the routevalues object (or RouteValuesDictionary) to pass values to your action.
In your example it would look like this:
Html.ActionLink("View Event Items", "Index", "Item", new { eventId = 1}, new {})
...where 2 is your event id. The second empty object (new {}) is for html attributes. Nate's answer is close, but I don't think there is an overload that takes a routevalues object as the second parameter.
I think that
Html.ActionLink("View Event Items", new { controller = "Item", action = "Index", id = 5 })
should get you in the vicinity of where you're looking to go.