Does calling ko.mapping.fromJS two time is right thing to do? - json

I am using knockout mapping plugin to map JSON data to knockout view model. The issue is JSON comes from server data doesn't have all the properties always. But my computed obeservables refer them. So I creates all the observable in first mapping using an empty object(templateStructure) contains all properties and then doing seocond call with actual data to populate the observable with current data. This works fine but want to know that if there any better way to handle the situation?
This is how the two time call is happening right now. templateStructure is dummay object with all the properties and data is actual data.
ko.mapping.fromJS(templateStructure, {}, this);
ko.mapping.fromJS(data, {}, this);

Calling mapping.fromJS to update an existing view model is right. If you're receiving updates to your model using AJAX, it's the easiest way to do it (if you didn'd use mapping, you'd have to do it by hand, property by property).
Your approach of creating a "template viewmodel" with all the properties, so that they exist even if you don't receive it in you JSON responses is good. Besides, it's easier to understand the JavaScript code: you don't need to see the server side to discover which properties are in the view model, as would happen if you made the first mapping directly from the server.
However, if the received model is nearly complete, you can always customize the "create" of your mapping (You could look for missing observable properties using js hasOwnProperty and adding the missing ones). The last example of the docs in the link so precisely how to add a new observable (in this sample, a computed observable):
var myChildModel = function(data) {
ko.mapping.fromJS(data, {}, this); // this is the view model
this.nameLength = ko.computed(function() { // nameLength is added to the vm
return this.name().length;
}, this);
}
In this sample you could add the condition to create nameLength only if not present on the received data, like this:
if (!data.hasOwnProperty('nameLength')) {
/* add the missing observ. property here */
}
(NOTE: you can also customize the update, if needed).

I have solved it using jQuery extend method by merging the object before mapping. So I only needed one call to the mapping function.
var mergedData = jQuery.extend(true,data,templateStructure);
ko.mapping.fromJS(mergedData, {}, this);

Related

Setting value using UseState hook adds a "tableData" object to data fetched from API

I am fetching data from a flask API using Axios, then using the useState hook to use that data to build a table. The error I'm encountering is that after I use the setValue function to update the state, an extra "tableData:{id:0}" object is added to the JSON object the API returning, and it seems to happen after the setValue function is called.
I have already tried to use delete values.key.tableData when I was only dealing with single objects, but now that I have an array of objects it doesn't work anymore, and I don't understand why this key is even added in the first place.
I get an "undefined" error when I don't use the initial state like I did below.
The console shows the original string (not parsed yet) when i log the API's original response, but shows this when I log the state:
​
0: Object { Email: "", tableData: {…} }
​
length: 1
​
<prototype>: Array []
​
How do I keep the "tableData" key from being added to the response? Is there an issue with the way I'm using the hook?
The relevant parts of the code are down here:
React.useEffect(() => {
setLabelWidth(inputLabel.current.offsetWidth);
axios.get("http://127.0.0.1:5000/api",{params: { start: selectedstartDate, end: selectedendDate}}).then((response)=>{
console.log(response.data.details);
setValues(response.data.details);
console.log(values);
}, []);
After looking at the way MaterialTable behaves, the issue was created by it mutating whatever is passed as data, so in this case my state variable, and adding that extra key to render the table. To solve this, I added a second state variable which is a copy of the first one, that I only use for the table data prop, and kept using the original values for everything else.

Output my JSON data before I use Firebase $save or $add in console?

I've been trying to debug for hours a Firebase rule problem and was wondering if there is something easier available.
My problem is that I save my firebaseObject with $save (or create with $add) and get a permission denied because of my rules. However, both the rules and the object is pretty complex and there are dozens of rules which are involved. In my simulator, I think I got it all, but still get permission denied.
The problem is that I am not 100% sure how the JSON data actually looks which $save tries to send to Firebase. If I use the normal console.log(myObject), I get of course a list of all values and functions inside this object, but this isn't the same as the raw JSON I would expect (like { "name": "value" }).
Is there any way to display the actual plain JSON data $save sends to copy this into the rule simulator and debug? Or is there any other way to see which exact permission is denied?
Otherwise, I have to go one by one, switching my permissions off and on which would be a pretty long night for me. :(
If the value of the $firebaseObject is an object, the only difference (in addition to the prototype-wired methods) should be a number of $-prefixed properties (like $id and $resolved). So you should be able to see the actual JSON of what will be written to the database using something like this:
var written = {};
Object.keys(myObject).forEach(function (key) {
if (key.charAt(0) !== "$") { written[key] = myObject[key]; }
});
console.log(JSON.stringify(written));
The $$hashKey entries mentioned in your comment are added by AngularJS. A more general mechanism could be used to remove/ignore all $-prefixed keys throughout the object:
console.log(JSON.stringify(myObject, function (key, val) {
return key.charAt(0) === "$" ? undefined : val;
}));

Calling cross controller function

I have two pairs of controller and view. The first view contains a list of items, while the in second shows some details of a specific item. What I want to achieve is that a click on one list item, the function onSelect should call second controller of detail view and update its content with the selected list item.
So far I have following code:
//first list controller
onSelect : function () {
var secondController = sap.ui.controller("controller.Detail");
secondController.updateFunction("some text");
}
Then in second controller:
//second detail-controller
updateFunction: function (someText) {
var view = sap.ui.xmlview("view.Detail");
view.byId("someTextField").setText(someText);
}
The problem is that this is not working. It seems that sap.ui.xmlview is not returning the same view which is displayed.
When I execute following code:
var model = view.getModel(model);
console.log(model);
within 2 functions of detail controller, but first is called by outside controller and second is called by onInit or function called by detail view event, the id is different.
How can I achieve such a cross-controller function calling with updating content of different view? Or is my approach not proper?
I would recommend to use either the EventBus or Routing for inter view communication.
Routing is nice as it uses the hash part (#) of the url to communicate for example an event like the selection of an item (f.e. https://example.com/myUi5App/index.html#/item/123). The user can use the browser history and bookmarks to navigate through your app.
A view can register to the router to be notified when a specific url pattern is matched. The walkthrough in the SAPUI5 Developer Guide does nicely explain routing step by step and in detail here and here.
EventBus is a global object that can publish arbitrary events. Anyone interested can register to the EventBus. There is an EventBus on the Component which you should use if you have a component and a global EventBus.
Both techniques help decoupling your views. It does not matter if there are one, many or none views listening to the selection change. And it does not matter for the listeners who fired the event.
If both views have been called once you can achieve this via the view (from my opionion, this is quite hacky and should be solved otherway)
this.getView().byId("yourViewId").oController.yourMethod();
means in your case
onSelect : function () {
var secondController = this.getView().byId("view_id").oController;
secondController.updateFunction("some text");
}
maybe this helps you, he is passing the controller reference which would be a better option: Calling Controller Function from inside a press handler in sapui5
I have found solution.
sap.ui.getCore().byId("__xmlview1");
According to documentation var view = sap.ui.xmlview("view.Detail"); always creates a new view.
However I am still struggling about specifying id of xmlview. Since "___xmlview1" is dynamicly given name and the number 1 means serial number of views within application. So if I create another view before creation of "view.Detail", the id will point to the new one.
I am creating xmlview like this:
<mvc:XMLView viewName="view.Detail"></mvc:XMLView>

handle a String[] via the PortletPreferences (Liferay6.2)

I have built a MVCPortlet that runs on Liferay 6.2.
It uses a PortletPReferences page that works fine to set/get String preferences parameters via the top right configuration menu.
Now I would need to store there a String[] instead of a regular String.
It seems to be possible as you can store and get some String[] via
portletPreferences.getValues("paramName", StringArrayData);
I want the data to be stored from a form multiline select.
I suppose that I need to call my derived controller (derived from DefaultConfigurationAction) and invoke there portletPreferences.setValues(String, String[]);
If so, in the middle, I will neeed the config jsp to pass the String[] array to the controller via a
request.setAttribute(String, String[]);
Do you think the app can work this way in theory?
If so, here are the problems I encountered when trying to make it work:
For any reason, in my config jsp,
request.setAttribute("paramName", myStringArray);
does not work ->
actionRequest.getAttribute("paramName")
retrieves null in my controller
This is quite a surprise as this usually works.
Maybe the config.jsp works a bit differently than standard jsps?
Then, how can I turn my multiline html select into a String[] attribute?
I had in mind to call a JS function when the form is submitted.
this JS function would generate the StringArray from the select ID (easy)
and then would call the actionURL (more complicated).
Is it possible?
thx in advance.
In your render phase (e.g. in config.jsp) you can't change the state of your portlet - e.g. I wouldn't expect any attributes to persist that are set there. They might survive to the end of the render phase, but not persist to the next action call. From a rendered UI to action they need to be part of a form, not request attributes.
You can store portletpreferences as String[], no problem, see the API for getting and setting them
I think maybe you can use an array in client side, and you can update the javascript array, when user is selecting new values.
So you have the javascript array, then when user click on the action, you can execute the action from javascript also, something like this:
Here "products" is the array with your products.
A.io.request(url, {type: 'POST',
data: {
key: products
},
on: {
success: function(event, id, obj) {
}
}
});
From Action methd you can try to get the parameter with:
ParamUtil.getParameterValues(request,"key");

SAPUI5 copy model and break binding?

On initialization I read an oData service to get a small list of values and I store the model for further use in the application.
sap.ui.getCore().setModel(oODataJSONModel, "xlist");
At multiple stages, I want to make a copy of the original model, make changes to the values list and use it in a Select drop down. I've tried multiple different things, but every time I update/delete the copied model values, it is instantly reflected in the original model. This seems like a simple ask, but is there a way to break the link between the original model and the copied model, ideally I want to keep the original list intact so that list can be re-used over and over, regardless of what changes are made to the copies?
var oModelCpy = new sap.ui.model.json.JSONModel();
var cpyModelArray = oOrigModel.getData();
cpyModelJsonData = { results : [ cpyModelArray ] };
oModelCpy.setData(cpyModelJsonData );
When I remove entries from the copy model, it also removes entries from the original model, which in this case is not what i want.
Any suggestions?
A better approach is to save your data in the success handler:
oODataJSONModel.read("/yourService",
null,
null,
false,
function(oData, oResponse){
var oODataJSONModel = new sap.ui.model.json.JSONModel();
oODataJSONModel.setData(oData);
this.getView().setModel(oODataJSONModel, "jsonModel");
}
);
EDIT
I just stumbled upon this question while I was browsing through the list of UI5 questions, and it dawned to me what is causing your underlying copy issue! :-)
If you copy an array of objects to a new array (which is also happens if you copy model data to another model), you won't get a new array with new objects
Instead, you actually will get a new array, but with references to the old objects. So any change you make to a value in an object inside an array in model 1, will end up having that same value in model 2
So, in effect, you need to create new objects based on the old ones. Luckily, you don't need costly for loops and hardcoded value-copying logic to achieve this; one single line should be ok.
Let's say your original data is referenced by an array aData.
You then copy this data (a true copy) to a new array using JSON:
var aDataCopy = JSON.parse(JSON.stringify(aData));
If you now set this aDataCopy as the data for your second model, it will not have any references to the old model anymore.
Hope this helps!
Try using jquery extend() method to make a copy of the data. I had similar troubles earlier.
var newObject = $.extend({},oldObject);
Try this for once. Find the reference at http://api.jquery.com/jquery.extend/