How do I add properties to a js-data object that I don't want persisted? - sails.js

I'm using js-data (and js-data-angular) in conjunction with sockets via sails.js. When a new item is created/updated via sockets I want to call attention to it in my ui.
I'd like to add an "updated" property to the object, but don't want to inadvertently persist it to the DB.
Is there a way to hang non-persisting properties on a js-data object?

Yes.
You can set this globally on the data store or per-resource by using the omit configuration setting. For instance, when instancing your data store, you can instruct JSData to ignore all properties that begin with an underscore:
var store = new JSData.DS({ omit: [ /^_/ ] });

The documentation for the meta property of the options passed to store.defineResource says:
Put anything you want here. It will never be used by the API.

Related

v2.ODataModel: which API is more preferred? "bindElement" or "read"?

I set the view.setModel(model), get the model for the view, and request for model.read("/entitySet('10000')").
The model is then filled up with /entitySet('10000')/properties.
But it is difficult to assign them to view fields, as now in the view, <Text text="{property}"> doesn't work. It has to be <Text text="{/entitySet('10000')/property}">.
On the other hand, if I set view's context binding to "/entitySet('10000')", then <Text text="{property}"> would start working.
Which one is the preferred method? When to use .read?
I almost never use .read if I want to use the results from an OData call directly in a binding context. The only time I use .read is if I want to manipulate the results before doing anything with them.
Look at this example from the sdk for instance: https://ui5.sap.com/#/entity/sap.ui.table.Table/sample/sap.ui.table.sample.OData
Syntax on this kind of binding is similar to read but with a few differences in events, and a few different types of methods depending on what you want to bind. Binding to a view for instance uses bindElement:
this.getView().bindElement("/entitySet('1000')");
After this, fields on that particular entity can be accessed as <Text text="{property}" />.
Here's an example from one of my current apps with events and some other call parameters:
this.getView().bindElement({
path: `/Orders('${currentOrderNumber}')`,
parameters: {
expand: 'Texts'
},
events: {
dataRequested: _ => this.getView().setBusy(true),
dataReceived: data => {
if (!this.getView().getBindingContext()) {
// navigate to `Not Found` view
}
},
change: _ => this.getView().setBusy(false)
}
});
For a table, it's slightly different, since it depends on the aggregation you wish to bind, such as
oTable.bindRows({
path: "properties"
});
Which is the same as:
<Table rows="{properties}" />
It's always important to be more expressive. Use the API that is specifically designed to do that one task.
Comparing the two variants:
myModel.read(sPath) with text="{/path/property}"
myControl.bindElement(sPath) with text="{property}"
I'd be perplexed about the 1st call whereas in the 2nd call, I'd know exactly what you want to achieve (You want to bind element. Alternatively, bindObject can be also used).
The same applies to the framework. Since you're telling exactly what you want to achieve, the framework can improve its behavior based on your intent. E.g.: in (route)PatternMatched handler when the user navigates to the same page, .bindElement with the same path won't trigger another request since the model already stored the entity from the previous call. It can show the result immediately.
With .read, however, the framework doesn't know what you want to achieve, so it sends the request right away regardless of the application state.
Additionally, the 1st variant is anything but future-proof. It relies on the cached results. It's almost a side-effect that it works at all. The problem is that there is no guarantee that this behavior will continue to work in later versions. Also there won't be read method in V4 ODataModel.
TL;DR
v2.ODataModel#read
Does not create context from the response. Repeating .read("<same path>") always sends a new request.
Less expressive. Encourages app developers to work with a client-side model (e.g. JSONModel).
Application loses context awareness, increasing TCO, less future-proof.
bindElement or bindObject
Creates context from the response and stores it internally so that the same request can return the data immediately.
Clearly expresses the intent; application as well as framework can work with the existing APIs.
More future-proof: v4.ODataModel does not support manual read. Imagine you've built your applications with the v2ODataModel.read-jsonModel.setData approach, and you need to migrate to v4. Have fun. :)
I honestly think that v2.ODataModel#read should have never become a public method. I wouldn't encourage anyone to use .read except of when reading the $count value manually.
If the entity values need to be formatted, there are formatters and binding types out of the box which are also easy to extend.
If the application needs to restructure the response body, usually it's a sign of a poor design of the data model or the service not conforming to the OData spec.
I almost agree with Jorg, but not entirely:
It really depends on what are you trying to achieve. If looking to display data from backend then the easiest way to go is with this.getView().bindElement()
but if you are in need to manipulate data before displaying (like formatting text, displaying images from base64 strings, etc) OR if you would like to create new entity using some of existing data, OR update the existing data - the this.getModel(sName).read() is the way to go - as you can set read entity with all its deep entities to JSONModel in successCallback and manipulate it from the localModel.
If using localModel the dataBinding is pretty much the same in the view - except you have to additionally give model name to take data from. So for example, if in successCallback of your Model.read() you set your data to Model named "localModel":
this.getModel().read(sObjectPath, {
urlParameters: {
$expand: ""
},
success: function (oData) {
// "localModel" is name you gave in onInit function to your new JSONMOdel, when setting it to View e.g. this.getView().setModel(oJSONMOdel, "localModel")
this.getModel("localModel").setData(oData);
}
})
then in XML view you would indicate that
<Text text="{localModel>/mainPropertyName}"/>
// for displaying deep entities as List items or in Table
<List items="{localModel>/deepEntityName}">
<StandardListItem title="{localModel>propertyNamefromDeepEntity}" />
</List>
From my experience working with more complex apps that Read-Only - I always use Model.read().

How to update information in an existing node instead of creating a new one using Dgraph?

I am writing a Golang application using Dgraph for persisting objects. From the documentation, I can infer that a new UID and hence a new node is created everytime I mutate an object/run the code.
Is there a way to update the same node data instead for creating a new node?
I tried changing the UID to use "_:name" for the UID field but even this creates a new node everytime the application is run. I wish to be able to update the existing node if it is already present in the DB instead of creating a new node for it.
Unfortunately the docs aren't very beginner friendly yet :/
To modify / mutate existing data you have to run a set operation and supply a rdf-triple like <uid> <predicate> "value" / <objectYouWantToModify> <attributeYouWantToModify> "quotedStringValue". If it is not an attribute but an edge, the value has to be another <uid>.
The full mutation would be for example
{
set {
<0x2> <name> "modified-name" .
}
}
The . terminates the sequence and there is an optional fourth parameter you can use to also assign a label.
Check https://www.w3.org/TR/n-quads/ for further details.

Getting properties from AEM multifieldpanel dialog stops working when a second entry is added

I have created an AEM Dialog which prompts the user for a set of links and labels.
These links and labels are stored in a jcr node and are used to generate a menu.
To avoid having to create a custom xtype, I am using the acs-commons multifieldpanel solution, which enables me to nest children under the fieldConfig node.
This works great with only 1 Label/Link pair, but when I add a second one - the property cannot be fetched anymore, since instead of a String, it returns the String hashcode.
The property generated by the multifieldpanel in the jcr node is of type String and is filled correctly when inspecting in CRXDE. The problem occurs when I try to fetch the value from within a Sightly HTML file.
Code
Dialog:
Definitions.js:
"use strict";
use(function () {
var CONST = {
PROP_URLS: "definitions",
};
var json = granite.resource.properties[CONST.PROP_URLS];
log.error(json);
return {
urls: json
};
});
Log output
1 element in multifieldpanel
jcr node variable content
definitions: {"listText": "facebook", "listPath": "/content/en"}
log output
{"linkText":"facebook","linkPath":"/content/en"}
Multiple elements in multifieldpanel
jcr node variable content
definitions: {"listText": "facebook", "listPath": "/content/en"},{"listText": "google", "listPath": "/content/en"}
log output
[Ljava.lang.String;#7b086b97
Conclusion
Once the multifieldpanel has multiple components and stores it, when accessing the property the node returns the String hashcode instead of the value of the property.
A colleague has pointed out that I should use the MultiFieldPanelFunctions class to access the properties, but we are using HTML+Sightly+js and are trying to avoid .jsp files at all cost. In JavaScript, this function is not available. Does anyone have any idea how to solve this issue?
That is because, when there is a single item in the multifield, it returns a String, where as it returns a String[] when there is more than a single item configured.
Use the following syntax to read the property as a String array always.
var json = granite.resource.properties[CONST.PROP_URLS] || [];
Additionally, you can also use TypeHints to make sure your dialog saves the value as String[] always, be it single item or multiple items that is configured.
Don't forget that the use() in JS is compiled into Java Byte code and if you are reading Java "primitives", make sure you convert them to JS types. It's part of the Rhino subtleties.
On another note, I tend to not use the granite.* because they are not documented no where, I use the Sightly global objects instead https://docs.adobe.com/content/docs/en/aem/6-0/develop/sightly/global-objects.html
To access properties, I use properties.get("key")
Hope this help.

SAPUI5 bindAggregation complete for a Table

I am binding an aggregation to a table . I couldn't find an event which is triggered after the binding is complete . There is "updateFinished" event for sap.m.List , which is exactly what I am looking for in a Table (and a dropodown). I thought of using attachRequestCompleted() on the model , but the model is used at other places where I do not want this event to trigger.
Is there anyway to trigger a event once the databinding is complete on a Table (and a dropdown)?
Any help is appreciated.
Thanks in advance.
update: There is "updateFinished" event for table extended from ListBase. I am still not sure how I missed it before I posted this question. But, the question is still valid for a dropdown and TableSelectDialog controls.
I also stumbled upon that problem, but in a different Context.
I have a Grid layout in which I dynamically load Panels via an oData Model.
Therefore I have entered the path in my XML Grid-View element.
<l:Grid id="grid" content="{some path...}">...</l:Grid>
Now I wanted to set the grid view busy and when the data is loaded revert this.
Therefore I use the Binding of the grid view.
In the Controllers onInit method I have added:
this._oGrid = this.getView().byId("grid");
this.getRouter().attachRouteMatched(this._onRouteMatch.bind(this));
Please note that the bind method is not available in every browser. You need to apply a polyfill. (See https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)
Also Bind has nothing to do with the binding :D
I needed to do this because the Binding is not available in the onInit function.
The _onRouteMatched function:
var oContent = this._oGrid.getBinding("content");
oContent.attachDataReceived(function(oData) {
this._oGrid.setBusy(false);
}.bind(this));
Now when the data is received the busy option is set to false.
If you want to show a 'loading' indicator for your table while the data is still loading (and thus not bound), I think the best approach is the following:
Use a dedicated JSONModel which holds only UI-specific stuff, like toggling enabled/readonly/busy/etc properties of controls.
In your case, something like:
var oUIModelData = {
tableIsBusy : false,
//etc, things like :
btnSubmitEnabled : false,
someControlVisible : true
};
var oUIModel = new sap.ui.model.json.JSONModel();
oUIModel.setData(oUIModelData);
sap.ui.getCore().setModel(oUIModel, "UIModel");
In your table definition, bind the busy property to {UIModel>/tableIsBusy} and set the table's property busyIndicatorDelay to 0 to avoid any delays
Just before you do your OData service call, set the property tableBusy to true. This will immediately show the busy overlay to your table:
sap.ui.getCore().getModel("UIModel").setProperty(tableIsBusy, true);
//here your OData read call
In your OData service's requestCompleted (and if needed, also in requestFailed) event handlers, set the busy property of the UIModel back to false:
sap.ui.getCore().getModel("UIModel").setProperty(tableIsBusy, false);
The big benefit of this approach is (IMHO) instead of relying on each control to check whether the data has been loaded, simply do it during the actual load.
And by binding these UI-related things to a (separate) model saves you from writing lots of extra code ;-)
In general you could solve the problem by using batch processing on the OData service. According to https://sapui5.netweaver.ondemand.com/docs/guide/6c47b2b39db9404582994070ec3d57a2.html:
Use OData model v2.
var oModel = new sap.ui.model.odata.v2.ODataModel(myServiceUrl);
Define a set of deferred batch groups.
oModel.setDeferredBatchGroups(["myGroupId", "myGroupId2"]);
Add the batch group information to the corresponding bindings, e.g:
{
path:"/myEntities",
parameters: {
batchGroupId: "myGroupId"
}
}
All read/query actions on bindings in a certain batch group will be held back until a .submitChanges(.) call on the batch group is made.
oModel.submitChanges({
batchGroupId: "myGroupId",
success: mySuccessHandler,
error: myErrorHandler
});
Use the success/error handlers to execute actions.
This rather generic approach gives you additional control such as trigger actions, grouping and event handling on the OData model.

Django Tastypie, Remove Elements From ManyToMany Fields

I am using Tastypie, Django for my project.
To Update a many to many field I have used save_m2m hook.
def save_m2m(self, bundle):
for field_name, field_object in self.fields.items():
if not getattr(field_object, 'is_m2m', False):
continue
if not field_object.attribute:
continue
if field_object.readonly:
continue
related_mngr = getattr(bundle.obj, field_object.attribute)
related_objs = []
print bundle.data[field_name]
for related_bundle in bundle.data[field_name]:
try:
stock = Stock.objects.get(nse_symbol = related_bundle.obj.nse_symbol)
print stock.__dict__
except Stock.DoesNotExist as e:
dataa = {"error_message": e}
raise ImmediateHttpResponse(response=HttpBadRequest(content=json.dumps(dataa), content_type="application/json; charset=UTF-8"))
related_objs.append(stock)
related_mngr.add(*related_objs)
Now I want to remove elements from the same many to many field.
How should I achieve this. Do I have to send a patch request or delete request and how to handle this.
I am begineer in tastypie. I googled it some time and I couldn't find a proper way. Please guide me how to complete this.
Thanks.
I've thought a lot about handing m2m relationships, since most of our app depends on m2m links.
I've settled for the approach of an update method. Pass in the all the references of the relationships you want changed (add and remove), then update the db accordingly. We only pass in the changed values, since if you have a paginated list, you only want to update the items the user has identified. Generally I use a custom hook for this defined in override_urls.
I used to have a separate add and remove method, which worked well until we changed the gui and allowed users simply to change checkboxes. In that approach having an update method was much more useful. You'll have to decide on which method suits your application the best.