How to copy data from OData v4 to JSON with SAP UI5? - javascript

I'm using OData v4 to load data from my backend to my frontend (developed with SAP UI5) and I am using a form to display a detail page. When I click the "edit" button I'm able to edit the data. My implementation is similar to this example: https://sapui5.hana.ondemand.com/explored.html#/sample/sap.ui.layout.sample.Form354/code/Page.controller.js
When editing something, the data is directly edited at the model and, therefore, updated at the backend. However, I want to be able to choose if I want to save the changes or if I want to cancel the edit before it is updated at the backend.
I read on other questions that one can copy the ODataModel to a JSONModel and use that copy instead, by doing something like:
var oModel = this.getView().getModel();
var oModelJson = new sap.ui.model.json.JSONModel();
oModel.read("/Data", {
success: function(oData, response) {
oModelJson.setData(oData);
sap.ui.getCore().setModel(oModelJson, "oJSONModel");
alert("Success!");
},
error: function(response) {
alert("Error");
}
});
However, the read method seems not to be available for OData v4. the code of my controller where the data is loaded looks like following:
onInit: function() {
this.oModel = new ODataModel({
groupId : "$direct",
synchronizationMode : "None",
serviceUrl : '/odata/'
});
this.getView().setModel(this.oModel, 'oModel');
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.getRoute("details").attachPatternMatched(this._onObjectMatched, this);
this._showFormFragment("display");
},
_onObjectMatched: function (oEvent) {
this.getView().bindElement({
path: "/Data(" + oEvent.getParameter("arguments").dataPath + ")",
model: "oModel"
});
//I want to copy the data from the ODataModel to the JSONModel here
},
What's the best way to accomplish this? And how to do it with OData v4?

I suppose you want to resetChanges in case user cancels the save.
For V2 ODataModel, there is deferedGroup concept which you can use to resetChanges or submitChanges.
I have not much experience with V4. Though from the documentation, it is possible.
Please try to pass a updateGroupId in the constructor. Then you can choose resetChanges or submitBatch by group Id.
mParameters.updateGroupId? The group ID that is used for update requests. If no update group ID is specified, mParameters.groupId is used. Valid update group IDs are undefined, '$auto', '$direct' or an application group ID, which is a non-empty string consisting of alphanumeric characters from the basic Latin alphabet, including the underscore.
Thank you!

Related

Using a VSS Control on a TFS Dashboard Widget

I am attempting to add a Visual Studio Team Services (VSTS) Multivalue Combo Control in a custom tfs dashboard widget's configuration page. I am able to get the widget to appear and I can dynamically add values to it. But I am unable to "Save" the value. Below is the Javascript I am using to attempt to create and save this control's values. I recieve the error from the browsers console of "Uncaught TypeError: Cannot read property 'notify' of undefined" from line 34 (my "change" function inside my multiselect. I have attenpted to put the logic inside of the "load" function as that is genrally where you put the logic for the widget, but then i receive a error specifying that "create" is undefined in my var multiValueCombo = Controls.create(Combos.Combo, CustomContainer, multiValueOptions); line.
With how the current code stands. The "save button" does not enable when I make a change to the value in my control. I believe this is due to the "notify" function not firing.
VSS.require(["TFS/Dashboards/WidgetHelpers", "TFS/DistributedTask/TaskAgentRestClient", "VSS/Controls", "VSS/Controls/Combos"], function (WidgetHelpers, TFS_TaskAgentRestClient, Controls, Combos) {
VSS.register("DeploymentReports.Configuration", function () {
var CustomContainer = $("#custom-combo-container");
//Creating the Multiselect Control
var multiValueOptions = {
type: "multi-value",
width: 250,
source: [
"Ford"],
change: function () {
//What it does when you change the value of the multiselect
var customSettings = { data: JSON.stringify({ iteration: this.getText() }) };
var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
widgetConfigurationContext.notify(eventName, eventArgs); //I get an error for this line "Uncaught TypeError: Cannot read property 'notify' of undefined"
}
}
$("<label />").text("Select the supported languages:").appendTo(CustomContainer);
var multiValueCombo = Controls.create(Combos.Combo, CustomContainer, multiValueOptions);
var commandArea = $("<div style='margin:auto' />").appendTo(CustomContainer);
return {
load: function (widgetSettings, widgetConfigurationContext, Controls, Combos) {
//adding items to the multiselect drop down
multiValueOptions.source.push("Volvo");
multiValueOptions.source.push("Jeep");
return WidgetHelpers.WidgetStatusHelper.Success();
},
//Clicking the Save Button
onSave: function() {
var customSettings = {
data: JSON.stringify({
cars: multiValueOptions.getText()
})
};
return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
According to your description, seems you want to save the data in the combo control. VSTS extensions have the ability to store user preferences and complex data structures directly on Microsoft-provided infrastructure.
There are two ways to interact with the data storage service: REST APIs or a Microsoft-provided client service available as part of the VSS SDK. It is highly recommended that extension developers use the provided client service APIs, which provide a convenient wrapper over the REST APIs.
Take a look a the concept about Data storage. This ensures your user's data is secure and backed up just like other account and project data. It also means for simple data storage needs, you (as the extension provider) are not required to setup or manage (or pay for) third-party data storage services.
The service is designed to let you store and manage two different
types of data:
Settings: simple key-value settings (like user preferences)
Documents: collections of similar complex objects (documents)
More details about how to set storage please refer official tutorial.

Return all remote matches with typeahead without extra filtering

Can't seem to figure out a way to disable filtering with typeahead. Basically I just need the autocomplete (or rather drop-down search hint) functionality of it. I am doing a zip code search and resulting postal codes don't necessarily match the queried ones. How do I make it show all matches without doing extra filtering on those again?
Below is the code I have:
var dealers = new Bloodhound({
datumTokenizer: function (d) {
return Bloodhound.tokenizers.whitespace(d);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/form/find-dealer?postalCode=',
prepare: function (query, settings) {
settings.url += encodeURIComponent(query);
settings.type = 'POST';
settings.contentType = "application/json; charset=UTF-8";
return settings;
}
}
});
$('input[name=postalCode]').typeahead({
minLength: 3
}, {
name: 'dealers',
display: function (data) {
return data.title;
},
source: dealers.ttAdapter()
});
Note: I know it seems a bit awkward to do a zip code search that way, but the purpose of the designer was for users to search interchangeably by dealer name and zip code.
Additional Info: typeahead.bundle.js - v0.11.1
It seems showing all without any (matching) query isn't possible:
https://github.com/twitter/typeahead.js/issues/1308
Though some are trying it with minlength=0 like this:
https://github.com/twitter/typeahead.js/issues/1251
And it looks it was possible in an older version:
https://github.com/twitter/typeahead.js/pull/719
Btw the plugin is no longer being developed and the manual is incomplete. An improved one can be found at this fork: https://github.com/corejavascript/typeahead.js/tree/master/doc
Having said that, you may be better off with another autosuggest, or a plugin like select2, which does show results by default and can use external sources.
If you want send a query to AJAX & get data from your database & add all JSON result for result of typehead (You have a data filter with database & send clean data with AJAX & JSON, But type head has extra filtering & don't show anything or some of your data), You must do it :
Open bootstrap-typeahead.js & find
item.toLowerCase().indexOf(this.query.toLowerCase())
And replace it to :
item.toLowerCase().indexOf(item.toLowerCase())
Will you can show all results from Ajax JSON ...
Not pretty, since the library has been forked and no longer oficially supported by the creator, but this fix did it for me https://github.com/twitter/typeahead.js/pull/1212 . Basically when in remote mode, it returns all matches, which is actually the proper behavior as I see it.
This SO thread helped twitter typeahead ajax results not all shown

Backbone.js dynamic URL problems

I have this Backbone.js model and view code, where I am trying to get a value from a text field, and fetch data from REST api based on this value. I am having problems modifying the base URL.
Model with base URL:
var TodoItem = Backbone.Model.extend({
urlRoot : 'http://localhost/Codeigniter/index.php/testcontroller',
initialize: function(){
this.set('id', 1);
},
defaults: {
name: '',
age: ''
}
});
var todoitem = new TodoItem({name: "name"});
Function where I am setting new URL:
getUrl: function(celebname){
var urlstr = "http://localhost/Codeigniter/index.php/testcontroller/getdatabasedata?searchvalue="+celebname;
return urlstr;
},
Function that fetches data from the REST api.
getdata: function (event) {
var celebname = $('#celebname').val();
this.model.set({name: celebname});
this.model.save({}, { urlRoot: this.getUrl(celebname)});
this.model.fetch();
},
At the moment I am getting this error:
GET http://localhost/Codeigniter/index.php/testcontroller/1
I cannot change the base url using the getURL function to search for the value from input field.Instead is using the base url and the id at the end.
If I am not setting out the ID in the initialize function of the model, then I get this error:
POST http://localhost/Codeigniter/index.php/testcontroller/getdatabasedata?searchvalue=Rome
From what I have read online this is because there is no id assigned to the model.
How can I get the input field value, build the URL, and fetch data using the GET method?
Thank you
Backbone works incredibly well with true RESTful APIs. Part of the issue here is that the API you have does not really map well with the Backbone model i.e. it's not exactly RESTful. As a result, you're going to have to resort to "hacks" to have the client and server get along. Dynamically modifying the route of a model is an example of one such hack.
What might help get you better answers is if you could elaborate a bit more on the use-case you have in mind.
From what I can tell, you're not really trying to persist a TodoItem. Rather, you're trying to pre-populate it with some base data. If this is true, then you really should not be doing a save -- you should just be doing a fetch.
getdata: function (event) {
var celebname = $('#celebname').val();
var id = this.model.id;
this.model.id = 'getdatabasedata';
this.model.fetch({data: {searchvalue: celebname}});
this.model.id = id;
}
Passing the data option will tell jQuery to use it as a query string param.
Again, this is very hacky and I would not recommend it, but it will accomplish what you're trying to do.

how to pass an id containing / in backbone.js

HI my basic model which fetches data from server is working perfect. I want to implement a search feature. When user enters any data the request goes to browser and desired model is returned.
var Book = Backbone.Model.extend({
urlRoot: '/books'
});
render: function(options) {
books = new Book({id:options.name});
books.fetch();
}
where
name = "search/"+dynamic_data;
Request URL that is being formed when i pass --> 'life' in variable dynamic_data
http://host/path/search%2Flife
Request URL that i want
http://host/path/search/life
How can I encode/escape my string to achieve the desired result. I have tried escape(), encodeURI(), encodeURIComponents
A workaround to solve this is create one more model with urlRoot as /books/search and pass just name . I don't think this is correct. Should I use this ?
According to your additionnal precisions stating that life is actually a book name...
It looks like Backbone is better integrated with RESTful API's. In REST, your urls should not contain verbs and to search books, you would do a GET /books/?name=life.
In that case, you would only have to define a Backbone.Collection like:
var BooksCollection = Backbone.Collection.extend({
model: Book,
url: '/books'
});
The to fetch books:
var books = new BooksCollection();
books.fetch({data : {name: 'life'}}); //GET /books/?name=life
If you really want your search operation to target /search/:name, you will have to check the Backbone.Collection api, but I think you will want to look at http://backbonejs.org/#Sync
You could override your collection's sync method to something like:
Backbone.Collection.extend({
...
sync: function (method, model, options) {
//for read operations, call the search url
if (method === 'read') {
options.url = '/search/' + options.data.name;
delete options.data.name;
}
//call the default sync implementation
return Backbone.Collection.prototype.sync.apply(this, arguments);
}
});
In this cased calling books.fetch({data : {name: 'life'}}); will result in GET /books/life.
Here's a fiddle that shows the example.
this would work:
books = new Book({id:options.name}, {url: options.name));
decodeURIComponent() will decode http://host/path/search%2Flife to http://host/path/search/life.

backbone.js change url parameter of a Model and fetch does not update data fetched

I have the following Model:
window.MyModel = Backbone.Model.extend({
initialize: function(props){
this.url = props.url;
}
parse: function(){
// #override- parsing data fetched from URL
}
});
// instantiate
var mod = new MyModel({url: 'some/url/here'});
I use this global variable 'mod' to fetch some data into this model from backend.
// fetch
mod.fetch({
success: function(){ ...},
error: ...
});
All above works well....
My Issue: I want to reuse this model by changing resetting the url and call fetch but it does not update the url somehow. I have tried the following:
mod.fetch({
data: {url:'/some/other/url'},
postData: true,
success: function(){ //process data},
error: ...
});
mod.set({url: '/some/other/url'});
// called fetch() without data: and postData: attributes as mentioned in previous
How do I set the url for my model so that I could call fetch() and it fetches data from updated url? Am I missing something. Thanks for any pointers..
UPDATE 1: Basically, I am unable to get updated values if I did
model.set({url: 'new value'});
followed by
model.fetch();
'model' is a global variable. Creating a fresh instance of 'model' works:
model = new Model({url:'/some/other/url'});
model.fetch();
however, works as required. Does this mean that a model instance is permanently attached to a url and it cannot be reset?
ANSWER TO MY QUESTION in UPDATE 1 Model instance is not permanently attached to a url. It can be reset dynamically. Please read through #tkone's thorough explanation and then #fguillens' solution for a better understanding.
After have understood the #tkone 's explanation...
If you still want to have a dynamic Model.url you always can delay its construction to run time, try this:
window.MyModel = Backbone.Model.extend({
url: function(){
return this.instanceUrl;
},
initialize: function(props){
this.instanceUrl = props.url;
}
}
Well the answer here is that you want to do:
mod.url = '/some/other/url'
The URL isn't part of the instance of the model itself, but rather an attribute of the MyModel object that you're creating your model instance from. Therefore, you'd just set it like it was an normal JavaScript object property. set is used only when the data you're setting (or conversely getting with get) is actually an attribute of the data you want to send/receive from the server.
But why you're changing the URL is the question we should be asking. The idea behind Backbone's model/collection system is that you speak to a REST endpoint and each model has a corresponding endpoint.
Like you've got a blog and that blog has an "entry" object which is available at:
/rest/entry/
And you've got a Backbone model for Entry:
Entry = Backbone.Model.extend({urlBase: '/rest/entry'});
Now when you save or fetch Backbone knows how this works.
So like you're making a new model:
e = new Entry();
e.set({title: "my blog rulez", body: "this is the best blog evar!!!!1!!"});
e.save();
This would then make Backbone do an HTTP POST request to /rest/entry with the body:
{
"title": "my blog rulez",
"body": "this is the best blog evar!!!!1!!"
}
(When you do your mod.set({url: '/some/other/url'}); you're actually adding a field called url to the dataset, so the server would send "url": "/some/other/url" as part of that JSON POST body above:
{
"title": "my blog rulez",
"body": "this is the best blog evar!!!!1!!",
"url": "/some/other/url"
}
The server would then respond with an HTTP 200 (or 201) response with the same model, only with, like, say, and ID attached:
{
"id": 1,
"title": "my blog rulez",
"body": "this is the best blog evar!!!!1!!"
}
And that's not what you're looking for, right?)
Now you've got this model and it's got an ID. This means if you change it:
e.set('title', 'my blog is actually just ok');
e.save()
Backbone now makes an HTTP PUT request on /rest/entry/1 to update the resource on the server.
The server sees that you're talking about ID 1 on the /rest/entry/ endpoint, so knows to update an existing record (and send back an HTTP 200).
TL;DR
Don't change the URL, Backbone will. Make a new model for a new piece of data.
model.urlRoot = "/your/url"
OR
model.urlRoot = function(){ return "/your/url"; }
OR
model.url = "/your/url"
OR
model.url = function(){ return "/your/url"; }
Default 'url' property of a Backbone.Model object is as below. Backbone.js doc says:
Default URL for the model's representation on the server -- if you're using Backbone's restful methods, override this to change the endpoint that will be called.
url: function() {
var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
},
Clearly, It first gets the value of urlRoot, if not available, it will look at the url of the collection to which model belongs. By defining urlRoot instead of url has an advantage of falling back to collection url in case urlRoot is null.
You can set url as option in fetch function, like this:
var mod = new MyModel();
mod.fetch({
url: '/some/other/url',
data: {}
});

Categories

Resources