this.getModel.metadataLoaded()
What is the main purpose of using this metadataLoaded?
What are the advantage and disadvantage of metadataLoaded?
Metadata is the information about the oData service itself. It contains entity, entityset, association information, field labels and all the other configuration it needs. The oData model uses it, smart controls are built on top of it. You'll find calls to fetch this at the start of every application (look for $metadata in the console of the browser). Before this is loaded, you can't use the service. In most cases this will be ready before your view is displayed.
metadataLoaded() returns a promise you can use to do things if you want to make certain that the service is ready, like:
this.getModel().metadataLoaded().then(_ => {
//use the service here to load some data
this.getView().bindElement({
path: `/PathToMyEntitySet('Key')`,
events: {
dataRequested: _ => this.getView().setBusy(true),
dataReceived: data => console.log(data),
change: _ => this.getView().setBusy(false)
}
});
});
Related
I'm building a PWA application, and I have used workbox registerroute for a few api endpoints, as well as an explicit service worker fetch event listener. During the debugging on some caching issues, I've noticed that these two seems to interfere with each other. Specifically sometimes the fetch handler is not triggered - which causes me trouble on debugging - I'm assuming this is due to the registerroute caching policy I have set via workbox.
My question is that, can I only pick one or the other, instead of having both fetch handler and registerroute? In my case, I needed fetch handler to deal with some advanced caching related to POST requests. So I think if I can only pick one, I'll have to stick with the fetch handler.
First, here's some background information about what happens when there's multiple fetch event handlers in the active service worker.
With that background info in mind, there are a few approaches for accomplishing what you're describing.
Option 1a: Register your own fetch event handler first
As long as you register your own fetch handler first, before any calls to Workbox's registerRoute(), it's guaranteed to have the "first shot" at responding the incoming fetch event.
The thing to keep in mind is that your own fetch handler needs to make a synchronous decision about whether or not to call event.respondWith(), and when you do call event.respondWith(), then Workbox's routes will not get used to respond to a given request.
So, you could do the following:
self.addEventListener('fetch', (event) => {
// Alternatively, check event.request.headers,
// or some other synchronous criteria.
if (event.request.url.endsWith('.json')) {
event.respondWith(customResponseLogic(event));
}
});
// Then, include any Workbox-specific routes you want.
registerRoute(
({request}) => request.destination === 'image',
new CacheFirst()
);
// The default handler will only apply if your own
// fetch handler didn't respond.
registerDefaultHandler(new StaleWhileRevalidate());
Option 1b: Ensure Workbox routes won't match
This is similar to 1a, but the main thing is to make sure that you don't have a "catch-all" route that will match all requests, and that you don't use registerDefaultHandler().
Assuming your Workbox routes just match a specific set of well-defined criteria, and don't match any of the requests that you want to respond to in your own handler, it shouldn't matter how you order them:
// Because this will only match image requests, it doesn't
// matter if it's listed first.
registerRoute(
({request}) => request.destination === 'image',
new CacheFirst()
);
self.addEventListener('fetch', (event) => {
// Alternatively, check event.request.headers,
// or some other synchronous criteria.
if (event.request.url.endsWith('.json')) {
event.respondWith(customResponseLogic(event));
}
});
(What's going on "under the hood" is that if there isn't a Route whose synchronous matchHandler returns a truthy value, Workbox's Router won't call event.respondWith().)
Option 2: Use custom handler logic
It should be viable to use Workbox to handle all your routing, and run your custom response generation code in either a handlerCallback (more straightforward) or a custom subclass of the Strategy base class (more reusable, but overkill for simple use cases).
The one thing to keep in mind is that if you're dealing with POST requests, you need to explicitly tell registerRoute() to respond to them, by passing in 'POST' as the (optional) third parameter.
Here's an example of how you could do this, assuming as before that you custom logic is defined in a customResponseLogic() function:
registerRoute(
({request}) => request.destination === 'image',
new CacheFirst()
);
registerRoute(
// Swap this out for whatever criteria you need.
({url}) => url.pathname.endsWith('.json'),
// As before, this assumes that customResponseLogic()
// takes a FetchEvent and returns a Promise for a Response.
({event}) => customResponseLogic(event),
// Make sure you include 'POST' here!
'POST'
);
Maybe I don't really understand the this.getView().getModel().refresh(true) or updateBindings.. Somehow it doesn't refresh the model, or my main idea is wrong. I mean; I can do a workaround to call a function that reads the odata service again, but this is not really beautiful. So, I read the Model in the onInit
onInit: function () {
var that = this;
var oViewModel = new sap.ui.model.json.JSONModel({});
this.getView().setModel(oViewModel, "detailView");
sap.ui.getCore().setModel(oViewModel,"detailView");
var oFilter = [];
var zAppFilter = new sap.ui.model.Filter("XXX", sap.ui.model.FilterOperator.EQ, "XXXX");
oFilter.push(zAppFilter);
var oModel = that.getView().getModel();
oModel.setDefaultBindingMode("TwoWay");
oModel.read("/XXXXSet", {
filters: oFilter,
success: function (oData) {
that.getView().getModel("detailView").setData(oData.results);
},
// ...
});
},
I use this "detailView"-JSONModel model in my view for bindings. This works.. Now, the add or delete function for example:
onDelete: function (oEvent) {
var that = this;
var oModel = this.getOwnerComponent().getModel();
var oSelectedItem = oEvent.getSource().getParent();
var oSourceID = oSelectedItem.getBindingContext("detailView").getObject().Zid;
oModel.remove("/XXX(XXX='XXX',XXXX='" + XXXX+ "')", {
method: "DELETE",
success: function(data) {
that.getView().getModel("detailView").refresh(true);
sap.ui.getCore().getModel("detailView").refresh(true);
},
// ...
});
},
That does not work.. but why? I mean also when I do updateBindings or something else. Am I understanding or doing something wrong?
Your JSONModel is not connected to anything. It's just a bunch of JSON data. So if you tell it to refresh, how should it know where to get the new data?
What refresh does not do is getting new data.
What refresh actually does (in a JSONModel) is telling the bindings that it has new data. One of these bindings can be the items of a sap.m.List for example. The list then knows that it needs to rerender to show the new data.
If you don't fetch new data and call refresh nothing will happen. The actual data is still the same.
i can do a workaround to call a function that reads agean the odata service but this is not really beautyfull
Well using an additional JSONModel when you already have a perfectly fine ODataModel isn't beautiful in the first place. If you just dropped your JSONModel and bound your view to your ODataModel then the view would automatically update after calling remove.
To bind the view to your ODataModel you can start with
<Table id="table0" items="{/XXXXSet}">
Don't forget to remove detailView from your cells.
You're mixing a client-side model (JSONModel) with a server-side model (ODataModel), expecting them to synchronize.
Client-side models and server-side models are two separate models serving two different purposes.
Client-side models
The main purpose of the client-side models is to provide and to sync data that are only available during the runtime of the application. If the app is gone, the data are gone. Some of the prominent use cases of client side models are:
Device model via JSONModel which provides information about user's device and its states.
ResourceModel which provides client side translatable UI texts for i18n purposes.
Synchronizing states from UI or application
The models here are not aware of any server-side data, and they shouldn't since it's not their purpose.
When dealing with a remote data provider that complies with a certain specification (e.g. OData or FHIR), the appropriate server-side model should be used instead.
Server-side models
Server-side models, such as ODataModel, have the advantage that they're server aware.
They know how to fetch, delete, update, create data, and even call functions from the backend system. They can be used to share states between the client and the server efficiently.
How? Simply use the server-side model in the binding definition directly. With OData as the default model for example:
<List items="{
path: '/MyEntitySet',
filters: [
{
path: 'ThatProperty',
operator: 'EQ',
value1: 'something'
}
]
}"> <!-- given "MyEntitySet", "ThatProperty", "EntityTitle", and "EntityDesc" are defined in $metadata -->
<StandardListItem title="{EntityTitle}" description="{EntityDesc}" />
</List>
This creates an ODataListBinding instance which will send a request to the service with the following URL:
https://....svc/MyEntitySet?$filter=ThatProperty eq 'something'
When the request succeeds, the list will show the entities accordingly. Afterwards, when calling myODataModel.remove(...);, the corresponding list will be refreshed automatically.
TL;DR
Am I understanding or doing something wrong?
Yes. Having an intermediate JSONModel in such cases is a common anti-pattern creating high maintenance costs. Try using the ODataModel only. The framework will do the work for you.
I have an API endpoint at /movies/:movie_id/actors.
I'm trying to use Ember Data to fetch this endpoint. I'm not interested in modelling movies at this point, just actors. My route looks like this:
this.route('actors', { path: '/movies/:movie_id/actors' });
My actor model is plain:
DS.Model.extend({
name: DS.attr("name")
})
In my actors route, I have:
model: function(params) {
// params contains movie_id
return this.store.findAll('actor')
}
This will cause Ember to send a request for /actors. How can I tell Ember to send a request to /movies/:movie_id/actors instead?
My JSON is being returned in the format { "movies": [ { … } ] } and I'm using the DS.ActiveModelAdapter, if that's at all relevant. I'm using Ember 2.0.
DS.Store doesn't work around "path" concept. It's more of a data bucket, which - when supplemented - can take burden of working with provider (fetch/update/create/cache etc.) off developer. In your case it looks similar to this:
ActiveModelAdapter, which you're using right now is using specific convention for accessing and isn't compatible with your data provider. So, what options do you have?
Customize ActiveModelAdapter by overriding pathForType or buildURL methods (note - links are for RESTAdapter, since ActiveModelAdapter subclasses it)
Choose more compatible adapter or even write your own
Don't use adapter - fetch the data through AJAX and feed it to store directly using push()/pushPayload()
I am struggling to receive pubsub events in my client. The client store (reflux) gets the data from a project using its id. As I understand it this automatically subscribes the Sails socket for realtime events (from version 0.10), but I don't see it happening.
Here's my client store getting data from sails
(this is ES6 syntax)
onLoadProject(id) {
var url = '/api/projects/' + id;
io.socket.get(url, (p, jwres) => {
console.log('loaded project', id);
this.project = p;
this.trigger(p);
});
io.socket.on("project", function(event){
console.log('realtime event', event);
});
},
Then I created a test "touch" action in my project controller, just to have the modifiedAt field updated.
touch: function(req, res){
var id = req.param('id');
Project.findOne(id)
.then(function(project) {
if (!project) throw new Error('No project with id ' + id);
return Project.update({id: id}, {touched: project.touched+1});
})
.then(function(){
// this should not be required right?
return Project.publishUpdate(id);
})
.done(function() {
sails.log('touched ok');
res.ok();
}, function(e) {
sails.log("touch failed", e.message, e.stack);
res.serverError(e.message);
});
}
This doesn't trigger any realtime event in my client code. I also added a manual Project.publishUpdate(), but this shouldn't be required right?
What am I missing?
-------- edit ----------
There was a complication a result of my model touched attribute, since I set it to 'number' instead of 'integer' and the ORM exception wasn't caught by the promise error handling without a catch() part. So the code above works, hurray! But the realtime events are received for every instance of Project.
So let me rephrase my question:
How can I subscribe the client socket to an instance instead of a model? I could check the id on the client side and retrieve the updated instance data but that seems inefficient since every client receives a notification about every project even though they only should care about a single one.
----- edit again ------
So nevermind. The reason I was getting updates from every instance is simply because at the start of my application I triggered a findAll to get a list of available projects. As a result my socket got subscribed for all of them. The workaround would be to either initiate that call via plain http instead of a socket, or use a separate controller action for retrieving the list (therefor bypassing the blueprint route). I picked the second option because in my case it's silly to fetch all project data prior to picking one.
So to answer my own question. The reason I was getting updates from every instance is simply because at the start of my application I triggered a findAll to get a list of available projects. As a result my socket got subscribed for all of them.
The workaround would be to either initiate that call via plain http instead of a socket, or use a separate controller action for retrieving the list (therefor bypassing the blueprint route). I picked the second option because in my case it's silly to fetch all resources data prior to selecting one.
Here's the function I used to list all resources, where I filter part of the data which is not relevant for browsing the list initially.
list: function(req, res) {
Project.find()
.then(function(projects) {
var keys = [
'id',
'name',
'createdAt',
'updatedAt',
'author',
'description',
];
return projects.map(function(project){
return _.pick(project, keys);
});
})
.catch(function (e){
res.serverError(e.message);
})
.done(function(list){
res.json(list);
}, function(e) {
res.serverError(e.message);
});
},
Note that when the user loads a resource (project in my case) and then switches to another resource, the client is will be subscribed to both resources. I believe it requires a request to an action where you unsubscribe the socket explicitly to prevent this. In my case this isn't such a problem, but I plan to solve that later.
I hope this is helpful to someone.
I'm upgrading a custom solution where I can dynamically register and unregister Web Api controllers to use the new attribute routing mechanism. However, it seems to recent update to RTM break my solution.
My solution exposes a couple of Web Api controllers for administration purposes. These are registered using the new HttpConfigurationExtensions.MapHttpAttributeRoutes method call.
The solution also allows Web Api controllers to be hosted in third-party assemblies and registered dynamically. At this stage, calling HttpConfigurationExtensions.MapHttAttributeRoutes a second time once the third-party controller is loaded would raise an exception. Therefore, my solution uses reflection to inspect the RoutePrefix and Route attributes and register corresponding routes on the HttpConfiguration object.
Unfortunately, calling the Web Api results in the following error:
"No HTTP resource was found that matches the request URI".
Here is a simple controller that I want to use:
[RoutePrefix("api/ze")]
public sealed class ZeController : ApiController
{
[HttpGet]
[Route("one")]
public string GetOne()
{
return "One";
}
[HttpGet]
[Route("two")]
public string GetTwo()
{
return "Two";
}
[HttpPost]
[Route("one")]
public string SetOne(string value)
{
return String.Empty;
}
}
Here is the first solution I tried:
configuration.Routes.MapHttpRoute("ZeApi", "api/ze/{action}");
Here is the second solution I tried:
var type = typeof(ZeController);
var routeMembers = type.GetMethods().Where(m => m.IsPublic);
foreach (MethodInfo method in routeMembers)
{
var routeAttribute = method.GetCustomAttributes(false).OfType<RouteAttribute>().FirstOrDefault();
if (routeAttribute != null)
{
string controllerName = type.Name.Substring(0, type.Name.LastIndexOf("Controller"));
string routeTemplate = string.Join("/", "api/Ze", routeAttribute.Template);
configuration.Routes.MapHttpRoute(method.Name, routeTemplate);
}
}
I also have tried a third solution, whereby I create custom classes that implement IHttpRoute and trying to register them with the configuration to no avail.
Is it possible to use legacy-style route mapping based upon the information contained in the new routing attributes ?
Update
I have installed my controller in a Web Application in order to troubleshoot the routing selection process with the Web Api Route Debugger. Here is the result of the screenshot:
As you can see, the correct action seems to be selected, but I still get a 404 error.
Update2
After further analysis, and per Kiran Challa's comment below, it seems that the design of Web Api prevents mixing attribute routing and conventional routing, and that what I want to do is not possible using this approach.
I have created a custom attribute [RouteEx] that serves the same purpose of the Web Api [Route] attribute, and now my code works perfectly.
I guess, since this is not possible using the conventional attribute routing, none of the answers on this question could legitimately be consisered valid. So I'm not nominating an answer just yet.
You shouldn't be required to use reflection and inspect the attribute-routing based attributes yourself. Attribute routing uses existing Web API features to get list of controllers to scan through.
Question: Before the switch to attribute routing, how were you loading these assemblies having the
controllers?
If you were doing this by IAssembliesResolver service, then this solution should work even with attribute routing and you should not be needing to do anything extra.
Regarding your Update: are you calling MapHttpAttributeRoutes?