Optional routing parameters ar not passed on Prem - sapui5

Hello,
we are facing an issue with passing optional parameters using routing.
Parameters are passed to another view when the app runs on BTP, but not on Prem.
The route is defined as follows:
{
name: "routname",
pattern: "thisisname/{mandaroty1},{mandatory2}/:?optional:",
target: ["targetName"]
}
This is how I navigate:
oRouter.navTo("routname", {
mandatory1: "test",
mandatory2: "test2",
"?optional": {
optional1: "value1",
optional2: "value2"
}
});
In target view:
in onInit:
var oRouter = this.getRouter();
oRouter.getRoute("shapeIT").attachPatternMatched(this._onRouteMatched, this);
in _onRouteMatched:
_onRouteMatched: function (oEvent) {
var oArgs = oEvent ? oEvent.getParameter("arguments") : null;
console.log(oArgs);
}
In console of the onPrem I see as follows:
{
mandatory1: "test",
mandatory2: "test2",
"?optional": undefined
}
On BTP I see all values.
What do I miss?

You are trying to pass an object in the optional parameter. That's cleaver but I believe the problem is because of that. I would recommend using a JSON.stringify() to convert your optional parameters to a string and convert the string back to a JS object by using JSON.parse() on your _onRouteMatched handler.

Related

How to pass a key field as variable instead of hard-coded key value to OData operation?

I am calling the GetEntity OData read method from the SAP UI5 view controller and passing a key value in the request URL. I am getting the proper response from the back-end when I hardcode the key value.
However, when I try to pass the key value dynamically in a variable by appending it to the URL, it doesn't work. I get the following error
HTTP request failed 404
In below code, sGrant is the variable and it doesn't work. But if I replace the variable name with its value hard-coded in below code, for example, in the read method like this: "/GrantMasterSet('TY560003')", then it works:
var sGrant = this.byId("grantNbr").getValue();
var oMod = this.getOwnerComponent().getModel();
oMod.read("/GrantMasterSet('sGrant')", {
success: function(oData) {
var oJsonModel = new JSONModel();
oJsonModel.setData(oData);
this.getView().setModel(oJsonModel);
}.bind(this),
error: function(oError) {
MessageToast.show("Read Failed");
}
});
UI5 has a method to generate the right URI for you, no matter what is the data type of the key of your entity type.
The method is createKey of the sap.ui.model.odata.v2.ODataModel class. See its documentation
Inside your controller, use the following source code.
onInit: function () {
var oRouter = this.getOwnerComponent().getRouter();
oRouter.getRoute("routeName").attachPatternMatched( this.onPatternMatched , this );
},
onPatternMatched: function(oEvent){
var oParameters = oEvent.getParameters();
var oArguments = oParameters.arguments; // is not a function - without ()
var sKey = oArguments.id; // route parameter passed when using navTo
var oDataModel = this.getView().getModel(); // v2.ODataModel
oDataModel.metadataLoaded().then(function() {
var sPath = oDataModel.createKey("EntitySet", { Key: sKey });
this.getView().bindElement("/" + sPath);
}.bind(this)
);
}
Usually this is necessary in details pages, in order to apply element binding to a page. As the createKey method relies on the $metadata of your service, you must make sure that it is already loaded in your app. This can be achieved by using method metadataLoaded, provided in the snippet as well.
You should concatenate the variable to the rest of the string, like this:
oMod.read("/GrantMasterSet('" + sGrant + "')", {
Or, you can use a template literal, which comes down to the same thing (notice the backtics):
oMod.read(`/GrantMasterSet('${sGrant}')`, {
You should escape 'sGrant' so it can be evaluated.
It should be something like that :
var sGrant = this.byId("grantNbr").getValue();
var oMod = this.getOwnerComponent().getModel();
oMod.read("/GrantMasterSet("+sGrant+")", {
success: function(oData) {
var oJsonModel = new sap.ui.model.json.JSONModel();
oJsonModel.setData(oData);
this.getView().setModel(oJsonModel);
}.bind(this),
error: function(oError) {
MessageToast.show("Read Failed");
}
});

Stubbing byId() in SAPUI5-sinon

I'm filddling with sinonjs in SAPUI5. But there are some things I can't get my head around.
QUnit.module("Validation of Betaalwijze", {
beforeEach : function () {
this.oMainViewController = new MainViewController();
this.oViewStub = new ManagedObject();
var data = {
IBANPrimair: "123",
IBANSecundair: "456",
Betaalwijze: ""
};
var oModel = new JSONModel(data);
var fakeBetaalwijzeField = new Input();
sinon.stub(this.oViewStub, "getModel").returns(oModel);
sinon.stub(this.oViewStub, "byId").returns(fakeBetaalwijzeField);
sinon.stub(this.oMainViewController, "getView").returns(this.oViewStub);
},
afterEach : function() {
this.oMainViewController.destroy();
this.oViewStub.destroy();
this.fakeBetaalwijzeField.destroy();
}
});
QUnit.test("Should set an ValueState Error", function (assert) {
// Arrange
//All preparation here above.
// Act
this.oMainViewController._validateInput();
// Assert
//TODO
});
The getModel-stub works nicely when I use a "sap/ui/base/ManagedObject" for the oViewStub. But the byId-stub causes the message "Attempted to wrap undefined property byId as function" in that case.
When I use a "sap/ui/core/mvc/View" for the oViewStub, the getModel-stub is not found. (But this gives an error in the beforeEach also: Cannot read property 'viewData' of undefined.)
What is the right way to stub the View and it's methods getModel() and byId()?
The answer is quiete simple: sap.ui.base.ManagedObject does not have a method byId. This is a method of sap.ui.core.mvc.View. Just create a View instead of a ManagedObject in beforeEach and you should be fine.
BR
Chris

Meteor onRendered function and access to Collections

When user refresh a certain page, I want to set some initial values from the mongoDB database.
I tried using the onRendered method, which in the documentation states will run when the template that it is run on is inserted into the DOM. However, the database is not available at that instance?
When I try to access the database from the function:
Template.scienceMC.onRendered(function() {
var currentRad = radiationCollection.find().fetch()[0].rad;
}
I get the following error messages:
Exception from Tracker afterFlush function:
TypeError: Cannot read property 'rad' of undefined
However, when I run the line radiationCollection.find().fetch()[0].rad; in the console I can access the value?
How can I make sure that the copy of the mongoDB is available?
The best way for me was to use the waitOn function in the router. Thanks to #David Weldon for the tip.
Router.route('/templateName', {
waitOn: function () {
return Meteor.subscribe('collectionName');
},
action: function () {
// render all templates and regions for this route
this.render();
}
});
You need to setup a proper publication (it seems you did) and subscribe in the route parameters. If you want to make sure that you effectively have your data in the onRendered function, you need to add an extra step.
Here is an example of how to make it in your route definition:
this.templateController = RouteController.extend({
template: "YourTemplate",
action: function() {
if(this.isReady()) { this.render(); } else { this.render("yourTemplate"); this.render("loading");}
/*ACTION_FUNCTION*/
},
isReady: function() {
var subs = [
Meteor.subscribe("yoursubscription1"),
Meteor.subscribe("yoursubscription2")
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
data: function() {
return {
params: this.params || {}, //if you have params
yourData: radiationCollection.find()
};
}
});
In this example you get,in the onRendered function, your data both using this.data.yourData or radiationCollection.find()
EDIT: as #David Weldon stated in comment, you could also use an easier alternative: waitOn
I can't see your collection, so I can't guarantee that rad is a key in your collection, that said I believe your problem is that you collection isn't available yet. As #David Weldon says, you need to guard or wait on your subscription to be available (remember it has to load).
What I do in ironrouter is this:
data:function(){
var currentRad = radiationCollection.find().fetch()[0].rad;
if (typeof currentRad != 'undefined') {
// if typeof currentRad is not undefined
return currentRad;
}
}

Ampersand-rest-collection using multiple url end points

In standard ampersand-rest-collection you define the url endpoint as a property and [getOr]Fetch() queries using that url.
I have two endpoints for the same model. Essentially, when I am pulling a list of objects I use one url end point, but when I am pulling individual objects I need to use a different one. Is it possible to tell fetch() which url to use in each case?
DocStore = _.extend(docs, {
url: [could this be an object or array?]
getOne: function(docId) {
return this.fetchById(docId); //should use /doc/:docId
},
getMany: function(groupId) {
return DocStore.getOrFetch(groupId); //should use /group/:groupId/docs
}
});
Like many backbone properties you can use a function for the url property instead of a string to return whatever dynamic result you need.
For example you can do something like the following
url : function () {
return this.fetchingMany ? '/group/' : '/doc'
},
getOne: function(docId) {
this.fetchingMany = false;
this.fetch(); //should use /doc/:docId
},
getMany: function(groupId) {
this.fetchingMany = true;
this.fetch(); //should use /group/:groupId/docs
}

how to resolve optional url path using ng-resource

There are restful APIs, for instance:
/players - to get list for all players
/players{/playerName} - to get info for specific player
and I already have a function using ng-resource like:
function Play() {
return $resource('/players');
}
Can I reuse this function for specific player like:
function Play(name) {
return $resource('/players/:name', {
name: name
});
}
so I want to...
send request for /players if I didn't pass name parameter.
send request for /players/someone if I passed name parameter with someone
Otherwise, I have to write another function for specific play?
Using ngResource it's very, very simple (it's basically a two-liner). You don't need even need to create any custom actions here*.
I've posted a working Plunkr here (just open Chrome Developer tools and go to the Network tab to see the results).
Service body:
return $resource('/users/:id/:name', { id:'#id', name: '#name' })
Controller:
function( $scope, Users ){
Users.query(); // GET /users (expects an array)
Users.get({id:2}); // GET /users/2
Users.get({name:'Joe'}); // GET /users/Joe
}
of course, you could, if you really wanted to :)
This is how I did it. This way you don't have to write a custom resource function for each one of your endpoints, you just add it to your list resources list. I defined a list of the endpoints I wanted to use like this.
var constants = {
"serverAddress": "foobar.com/",
"resources": {
"Foo": {
"endpoint": "foo"
},
"Bar": {
"endpoint": "bar"
}
}
}
Then created resources out of each one of them like this.
var service = angular.module('app.services', ['ngResource']);
var resourceObjects = constants.resources;
for (var resourceName in resourceObjects) {
if (resourceObjects.hasOwnProperty(resourceName)) {
addResourceFactoryToService(service, resourceName, resourceObjects[resourceName].endpoint);
}
}
function addResourceFactoryToService (service, resourceName, resourceEndpoint) {
service.factory(resourceName, function($resource) {
return $resource(
constants.serverAddress + resourceEndpoint + '/:id',
{
id: '#id',
},
{
update: {
method: 'PUT',
params: {id: '#id'}
},
}
);
});
}
The nice thing about this is that it takes 2 seconds to add a new endpoint, and I even threw in a put method for you. Then you can inject any of your resources into your controllers like this.
.controller('homeCtrl', function($scope, Foo, Bar) {
$scope.foo = Foo.query();
$scope.bar = Bar.get({id:4});
}
Use Play.query() to find all players
Use Play.get({name:$scope.name}) to find one player