I am using SmartTable in my project.
I need to request some data from backend at begin and then work on received data in frontend.
By data, that i need from backend i must send some filter.
So i need at begin the operationMode Server, and after data come change it to Client
My SmartTable xml
<smartTable:SmartTable id="ReportSmartTable" entitySet="OwnSet"
tableBindingPath="/OwnSet" tableType="AnalyticalTable"
beforeRebindTable="onBeforeRebindTable" >
onBeforeRebindTable
onBeforeRebindTable: function (oEvent) {
console.log("onBeforeRebindTable");
var oBindingParams = oEvent.getParameter("bindingParams");
oBindingParams.filters.push(new sap.ui.model.Filter("Prop", "EQ", "Value"));
},
in onInit i set listener, to change the operation mode after data receiving
var oTable = this.getView().byId("ReportSmartTable"); //Get Hold of the table control
oTable.attachDataReceived(function (oEvent) { //Hits when the data is received from back-end server
this.getModel().defaultOperationMode = "Client"; //Set operation mode to Client
var oSource = oEvent.getSource();
oSource.bClientOperation = true; //Set Client Operation to true
oSource.sOperationMode = "Client"; //Set operation mode to Client
}.bind(this));
i have also tried to change operationMode by following
this.getOwnerComponent().getModel().sDefaultOperationMode = "Client";
this.getOwnerComponent().getModel().defaultOperationMode = "Client";
this.getModel().sDefaultOperationMode = "Client"; //Set operation mode to Client
this.getModel().defaultOperationMode = "Client"; //Set operation mode to Client
but it doesn't work.
If i make some filter after data is received, there still comes request to backend.
By making Client operationMode from begin, onBeforeRebindTable is called before request, but the filter is not sended with batch
You can't update the operation mode after a model is created. Even if you update the private attribute sDefaultOperationMode, it will not affect existing bindings.
You can specify the operationMode per binding, for example in a list:
<List items="{path:'/myset',parameters:{operationMode:'Client'}}" ...>
and use ListBase.bindItems to re-create a binding with a different operation mode.
For a SmartTable, however, you'd have to modify the internal table bindings and this would probably break a lot of things, therefore it's discouraged. Maybe the Smart Table is not the best fit for your use case.
Related
This is regarding the mentioned methods of sap.ui.model.json.JSONModel in SAPUI5:
setJSON
setData
loadData
What is the difference between these 3 methods? When do we use these methods and can we use more than 1 of them for the same purpose?
Have a look at the well documented API Reference for JSONModel.
In summary (from SAP Documentation):
setData: Sets the data, passed as a JS object tree, to the model.
e.g
var data = {
"ProductCollection": [{
"titleId": 0,
"Name": "Olayinka O",
"ProductId": "001",
"chartValue": 75,
"ProductPicUrl": "sap-icon://competitor"
}]
};
var oModel = new sap.ui.model.json.JSONModel(data);
//OR
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData(data);
/*setdata, could also be a odata url in json format*/
loadData:
Load JSON-encoded data from the server using a GET HTTP request and store the resulting JSON data in the model. Note: Due to browser security restrictions, most "Ajax" requests are subject to the same origin policy, the request can not successfully retrieve data from a different domain, subdomain, or protocol.
e.g. you can use this to load/GET changes to the data/model and automatically updates the view if that specific model has being binded by reloading the url. If you use load, you don't need the other two in my opinion and loadData with not work on local json data.
var sURL = "https://cors-anywhere.herokuapp.com/https://services.odata.org/V3/Northwind/Northwind.svc/Products?$format=json";
var oModel = new sap.ui.model.json.JSONModel();
//if called in setInterval, all changes in the backend will be updated in the view if binded in this case every second
setInterval(oModel.loadData(sURL, true), 1000);
setJSON :
Sets the data, passed as a string in JSON format, to the model.
i.e. Same as Set Data but strict JSON
Luckily, the source code of UI5 is quite readable and often the better documentation than most of the API descriptions. Here is what each one of the APIs does basically:
setJSON
"Parse the JSON text and call setData"
JSONModel.prototype.setJSON = function(sJSON, bMerge) {
var oJSONData;
try {
oJSONData = jQuery.parseJSON(sJSON);
this.setData(oJSONData, bMerge);
} catch (e) {
// ...
}
};
Source
setData
"Store the data and notify all dependent bindings (checkUpdate)"
JSONModel.prototype.setData = function(oData/*plain JS object*/, bMerge){
if (bMerge) {
this.oData = /* merge with existing data */;
} else {
this.oData = oData;
} // ...
this.checkUpdate(); // notifies dependent bindings
};
Source
loadData
"Load data from the given remote URL and call setData" --> Please check the source here.
In short, they all call setData at some point.
Which API to call in which situation depends on in which format you have the data available.
The data are in JSON text --> setJSON
The data are somewhere else --> loadData
I already have the data in JS object / array ---> setData
setData
You have a JavaScript object and want to use this data as your model
const oJSONData = {
data: {
id: 4,
first_name: "Eve",
last_name: "Holt",
avatar: "https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg"
}
};
oJSONModel.setData(oData);
setJSON
You have a String that when parsed represents a JavaScript object and want to use this data as your model
const sJSONData = '{"data":{"id":4,"first_name":"Eve","last_name":"Holt","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg"}}';
oJSONModel.setJSON(sJSONData);
loadData
You want to access a remote API which returns data as JSON and want to use this data as your model
const sURL = "https://reqres.in/api/users/4";
oJSONModel.loadData(sURL);
I have a SAPUI5 application that uses OData V2.
In one part of the application for deleting of the items in a list I have to close change set after each call.
Then I use the following code:
sGroupId = "dmsch" + new Date().getTime();
oDataModel.setDeferredGroups([sGroupId]);
for (var i = 0; i < aSelectedContexts.length; i++) {
var sObjectPath = aSelectedContexts[i].getPath();
this._deleteObject(sObjectPath, sGroupId, fnAllRequestCompleted, fnAllRequestFailed);
}
oDataModel.submitChanges({
groupId: sGroupId
});
And in the _deleteObject function I set different changeSetId for each request, the b:
_deleteObject: function(sObjectPath, sGroupId, fnSuccessCallBackFunction, fnFailedCallBackFunction) {
var oDataModel = this.getModel();
var sChangeSetId = "cs" + (new Date().getTime() * (1 + Math.random()));
oDataModel.remove(sObjectPath, {
groupId: sGroupId,
changeSetId: sChangeSetId,
......
Now after a successful delete as soon as I create a new entry by using the createEntry function it tries to send the data of that entry to the server.
The question is how can I reset the effect of setDeferredGroups function.
Note: I need to use setDeferredGroups, and I am sure it is reason of sending newly created entries automatically to the server by each change. I need to set the setting of the ODataModel back to its original state.
Note2: Here is something regarding oData Version 4 that explain this automatic behavior after a failure.
The SAP docs here - I've tried to summarize below.
The default change groups are
{"*": {
groupId: "changes"
}
}
And the default deferred groups are
["changes"]
You can reset the data model change groups to default using
oModel.setChangeGroups({"*": {
groupId: "changes"
}
});
oModel.setDeferredGroups(["changes"]);
With this default configuration, all changes to all entity types will be collected in the changes group, and are deferred (not sent to the server automatically).
So oModel.setChangeGroups(...) is how change groups are defined, and oModel.setDeferredGroups is how each of those groups is determined to be deferred or not
The reason I mention the default change groups AND the default deferred groups, is because if not set properly, you may see unexpected behavior when using two way data binding.
For example: removing the default change group by calling oModel.setChangeGroups({}) will result in all changes to all entity types NOT getting collected into any change group, and thus not being deferred. You will see any changes made sent to the server automatically.
So lets say you have an entity type Employee and you want any changes made to this entity type to be collected in one group and be deferred:
var oChangeGroups = oModel.getChangeGroups();
oChangeGroups.Employee = {groupId: "employees"};
oModel.setChangeGroups(oChangeGroups);
var aDeferredGroups = oModel.getDeferredGroups();
aDeferredGroups.push("employees");
oModel.setDeferredGroups(aDeferredGroups);
Now you have two change groups, * with ID changes and Employee with ID employees. Any changes made to any Employee entities will be in the employees group, and all other changes will be in the changes group.
So now any create/delete/update of an employee can be submitted separately from any other changes to other entity types
oModel.createEntry("/EmployeeSet", {
groupId: "employees",
properties: {
name: "New Guy"
}
});
oModel.submitChanges({groupId: "employees"});
From this point, to go back to the default and get rid of the employees change group, you can use what I wrote above to reset everything back to default.
feathers-client 2.3.0
syncfusion-javascript 15.3.29
I have been trying for awhile to create a syncfusion custom adapter for the feathers socket.io version of it's client. I know I can use rest to get data but in order for me to do offline sync I need to use the feathers-offline-realtime plugin.
Also I am using this in an aurelia project so I am using es6 imports with babel.
Here is a code snippet I have tried, I can post the whole thing if needed.
I am also not sure if just using the Adapter vs UrlAdapter is correct as I need sorting and paging to hit the server and not just to do it locally. I think I can figure that part out if I can at least get some data back.
Note: Per Prince Oliver I am adding a clarification to the question I need to be able to call any methods of the adapter as well besides just proccessQuery such as onSort. When the datagrid calls the onSort method I need to be able to call my api using the feathers socket.io client since it handles socket.io in a special manner for offline capabilities.
import io from 'socket.io-client';
import * as feathers from 'feathers-client';
const baseUrl = 'http://localhost:3030';
const socket = io.connect(baseUrl);
const client = feathers.default()
.configure(feathers.hooks())
.configure(feathers.socketio(socket));
const customers = client.service('customers');
export class FeathersAdapter {
feathersAdapter = new ej.Adaptor().extend({
processQuery: function (ds, query) {
let results
makeMeLookSync(function* () {
results = yield customers.find();
console.log(results);
});
The result is undefined. I have tried several other ways but this one seems like it should work.
REVISED CODE:
I am now getting data but also strange error as noted in the picture when I call
let results = await customers.find();
The process then continues and I get data but when the result variable is returned there is still no data in the grid.
async processQuery(ds, query) {
let baseUrl = 'http://localhost:3030';
let socket = io.connect(baseUrl);
let client = feathers.default()
.configure(feathers.hooks())
.configure(feathers.socketio(socket));
let customers = client.service('customers');
let results = await customers.find();
var result = results, count = result.length, cntFlg = true, ret, key, agg = {};
for (var i = 0; i < query.queries.length; i++) {
key = query.queries[i];
ret = this[key.fn].call(this, result, key.e, query);
if (key.fn == "onAggregates")
agg[key.e.field + " - " + key.e.type] = ret;
else
result = ret !== undefined ? ret : result;
if (key.fn === "onPage" || key.fn === "onSkip" || key.fn === "onTake" || key.fn === "onRange") cntFlg = false;
if (cntFlg) count = result.length;
}
return result;
The processQuery method in the DataManager is used to process the parameter which are set in the ej.Query like skip, take, page before fetching the data. Then the data is fetched asynchronously based on these parameters and fetched data is processed in processResponse method to perform operations like filtering or modifying. The processQuery function operates synchronously and it does not wait for the asynchronous process to complete. Hence the returned data from the API did not get bound on the Grid and throws undefined error.
So, if you are using the socket.io to fetch the data from the API, then the data can be directly bound to the Grid control using the dataSource property. Once the dataSource is updated with the result, it will be reflected in Grid automatically through two-way binding.
[HTML]
<template>
<div>
<ej-grid e-data-source.bind="gridData" e-columns.bind="cols"> </ej-grid>
</div>
</template>
[JS]
let baseUrl = 'http://localhost:3030';
let socket = io.connect(baseUrl);
let client = feathers.default()
.configure(feathers.hooks())
.configure(feathers.socketio(socket));
let customers = client.service('customers');
let results = await customers.find();
this.gridData = results; // bind the data to Grid
I've setup a simple "product" model (ie {id:"string","name":string, etc}) and setup a datasource using the REST connector to a remote URL that returns a JSON blob containing dozens of fields, how do I go about mapping the fields from the remote response to my local model? Whenever I execute my method I'm getting back the raw response from the remote....I was expecting, at a minimum, to get back an empty version of my model.
I'm pretty sure you will have to override the find() method on your model and perform this mapping work manually.
Something like this:
module.exports = function(app) {
var Product = app.models.Product;
var find = Product.find;
Product.find = function(filter, cb) {
// invoke the default method
find.call(Product, function(err, original_results) {
var results = {}; // a placeholder for your expected results
results.name = original_results.id;
results.name = original_results.name;
results.description = original_results.long_description;
// and so on
cb(null, results)
});
}
}
I am currently trying to log user page views in meteor app by storing the userId, Meteor.Router.page() and timestamp when a user clicks on other pages.
//userlog.js
Meteor.methods({
createLog: function(page){
var timeStamp = Meteor.user().lastActionTimestamp;
//Set variable to store validation if user is logging in
var hasLoggedIn = false;
//Checks if lastActionTimestamp of user is more than an hour ago
if(moment(new Date().getTime()).diff(moment(timeStamp), 'hours') >= 1){
hasLoggedIn = true;
}
console.log("this ran");
var log = {
submitted: new Date().getTime(),
userId: Meteor.userId(),
page: page,
login: hasLoggedIn
}
var logId = Userlogs.insert(log);
Meteor.users.update(Meteor.userId(), {$set: {lastActionTimestamp: log.submitted}});
return logId;
}
});
//router.js This method runs on a filter on every page
'checkLoginStatus': function(page) {
if(Meteor.userId()){
//Logs the page that the user has switched to
Meteor.call('createLog', page);
return page;
}else if(Meteor.loggingIn()) {
return 'loading';
}else {
return 'loginPage';
}
}
However this does not work and it ends up with a recursive creation of userlogs. I believe that this is due to the fact that i did a Collection.find in a router filter method. Does anyone have a work around for this issue?
When you're updating Meteor.users and setting lastActionTimestamp, Meteor.user will be updated and send the invalidation signal to all reactive contexts which depend on it. If Meteor.user is used in a filter, then that filter and all consecutive ones, including checkLoginStatus will rerun, causing a loop.
Best practices that I've found:
Avoid using reactive data sources as much as possible within filters.
Use Meteor.userId() where possible instead of Meteor.user()._id because the former will not trigger an invalidation when an attribute of the user object changes.
Order your filters so that they run with the most frequently updated reactive data source first. For example, if you have a trackPage filter that requires a user, let it run after another filter called requireUser so that you are certain you have a user before you track. Otherwise if you'd track first, check user second then when Meteor.logginIn changes from false to true, you'd track the page again.
This is the main reason we switched to meteor-mini-pages instead of Meteor-Router because it handles reactive data sources much easier. A filter can redirect, and it can stop() the router from running, etc.
Lastly, cmather and others are working on a new router which is a merger of mini-pages and Meteor.Router. It will be called Iron Router and I recommend using it once it's out!