Can I use i18n resource bundle within json data? - json

I have an SAPUI5 (V1.24) master-detail application wherein I have to display a list of about 25 static items and each item displays a static image when clicked.
I have the list titles stored in an i18n file which is instantiated as a ResourceBundle within the Component.js file.
Now instead of adding 25 rows of StandardListItem objects in my Master.xml.view file I was wondering if I could store all titles in a JSON file under mockdata folder and bind a JSONModel to my sap.m.List. But since the values in my JSON "key":"value"are nothing but the list titles I was looking for a way to bind the i18n texts with the JSON. Something like this:
{
"List": [
{
"Key": "'{i18n>value1}'"
},
{
"Key": "'{i18n>value2}'"
},
...
]
}
But it didn't work at runtime. Instead it displayed the value as-is, as shown below:
Adding as many list items in the view doesn't feel right. What if tomorrow the list increases from 25 to 50? Please help.
Thanks.

After our chat discussion I came up with the following solution
var aAllKeys = [],
aMasterKeys = [],
oProperties = {},
oJSON = {
items: []
};
// Get the current locale (for example "de-DE")
var sCurrentLocale = sap.ui.getCore().getConfiguration().getLanguage();
// This creates an array of locale fallback solutions.
// For example ["de-DE", "de", "en", ""]
var aFallbacks = jQuery.sap.resources._getFallbackLocales(sCurrentLocale);
// iterate all locales
for (var i = 0; i < aFallbacks.length; ++i) {
var sLocale = aFallbacks[i];
// try to load i18n file for each locale
oProperties = jQuery.sap.properties({
url: "i18n/i18n" + (sLocale ? "_" + sLocale : "") + ".properties"
});
// if the i18n file exists (i. e. contains keys)
if (oProperties.getKeys().length > 0) {
aAllKeys = oProperties.getKeys();
break;
}
}
// find all keys of items to display in master (the prefixed ones)
for (i = 0; i < aAllKeys.length; ++i) {
if (aAllKeys[i].indexOf("MyPrefix.") > -1) {
aMasterKeys.push(aAllKeys[i]);
}
}
// find all values of items to display in master
for (i = 0; i < aMasterKeys.length; ++i) {
oJSON.items.push({
key: aMasterKeys[i],
value: oProperties.getProperty(aMasterKeys[i])
});
}
You can then use oJSON to create a new JSON model which can be bound to your masterlist
Edit: I modified the beginning of the snippet. This adds a fallback solution if there is no i18n file for the current locale. This is tested against SAPUI5 v1.30.

See the solution I have provided in a similar topic: How is localized data binding set up with JSON files and XML views?
The best part, it requires only a single-line helper method :-)

Related

Firebase on('child_added' is adding more unwanted childs / .once returns only the last one in the array

Working on a node script to automatically call Google's pagespeed api upon giving the sites listed in a json file.
{
"1" : "https://bitbucket.org",
"2" : "https://www.catswhocode.com/blog/"
}
The aim is to add the results for the api call to firebase json database along with a top-level url node to store the firebase key for the sitestats entered. Here is example firebase data structure I need.
sites
-KrehhWxld7XlKCuFSHRY
stats { ... }
url: "https://bitbucket.org"
-KrehhXAWlYdjAOA9sd95
stats { ... }
url: "https://stackoverflow.com"
urls :
393957be871e209a76e0dc5df1f526ec : -KrehhWxld7XlKCuFSHRY
7f4c919540be6ec81cd37d9e61da6c37 : -KrehhXAWlYdjAOA9sd95
Within a promise I am adding a reference for the nodes in firebase json database.
Promise.all(allRes).then( function( values ) {
var ref = db.ref();
var sitesRef = ref.child("sites");
var urlsRef = ref.child("urls");
var psiresults = {};
SitesArray.map(function (sites, index) {
psiresults[sites] = values[index]
desktopStats = values[index].desktopStats;
mobileStats = values[index].mobileStats;
sitesRef.push({
url: sites,
stats: metricsResponse
});
sitesRef.once('child_added', function(snapshot) {
console.log(snapshot.key) //returns the last key only
});
});
When using once('child_added', ... it adds to the url top level node only for the very last item. However, if on('child_added', ... multiple copies of the same data and other childs are added. Not sure how to add the exact child key to the urls top level node when a child is added to sites node each time.
If you want to process all URLs once, use once('value' with snapshot.forEach():
sitesRef.once('value', function(snapshot) {
snapshot.forEach(function(child) {
console.log(child.key);
});
});
Finally, I had to do it using..
sitesRef.on('value', function(snapshot) {
...
});
This returns all the children and then I am able to filter the result based on my SitesArray values. I don't really find this an appropriate solution, however, I was able to make it work.

How can I loop JsonModel in sapui5?

I'm making my first app in sapui5.
I want create a login page with sign up and login.
In sing up I want save user name and password in a external JSON. How I can export the JSON Data?
In login I want validate password and username but i don't know how to loop the JsonModel.
I try this but of course didn't work.
var acountsJson = sap.ui.getCore().getModel("acountsModel");
for(var i = 0; i < acountsJson.length; i++) {
var obj = acountsJson[i];
console.log(acountsJson.id);
}
Can sap.ui.core has multiple Models?
Can sap.ui.core has multiple Models?
Yes, UI5-Core can hold multiple data models. In your case the model is named "acountsModel". You can add more models with different names.
Once you access your model, by sap.ui.getCore().getModel("acountsModel").getProperty("/"); you can either loop through an array using native for() or using jQuery:
$.each(acountsJson, function(value, index, array){
console.log(value.id);
});
Personally, I like the jQuery approach much more than the for-loop.
To get the JSON, you need to access your JSON Property, e.g. "/" for top node ...
var acountsJson = sap.ui.getCore().getModel("acountsModel").getProperty("/");
for(var i = 0; i < acountsJson.length; i++) {
var obj = acountsJson[i];
console.log(acountsJson[i].id);
}

BackboneJS - Multiple Collections in one view

I have a leftMenu section where I want to display 4 menus which are categorized by each subject. Every menu is a list and I want them to be displayed in the same View. I created 4 JSON files and to each one of them a Collection and a Model.
Usually i do it like this, first i define in router
this.mainMenuCollection = new MainMenuCollection();
this.mainMenuView = new MainMenuView({el:'#nav', collection:this.mainMenuCollection});
So, now I have these 4 collections defined in the router which I want in one view:
this.allcategoryMenuCollection = new AllCategoryMenuCollection();
this.natcategoryMenuCollection = new NatCategoryMenuCollection();
this.intcategoryMenuCollection = new IntCategoryMenuCollection();
this.topcategoryMenuCollection = new TopCategoryMenuCollection();
usually i render the collection in the View like this:
$(this.el).html(this.template({mainmenu:this.collection.toJSON()}));
Please help
Send in the collections as an object when you create your view:
this.mainView = new MainView({
el:'#nav'
});
this.mainView.collections = {
allcategoryMenuCollection: this.allcategoryMenuCollection
natcategoryMenuCollection: this.natcategoryMenuCollection
intcategoryMenuCollection: this.intcategoryMenuCollection
topcategoryMenuCollection: this.topcategoryMenuCollection
}
Access your collections inside the view by calling this.collections.collectionName
This may not seem like a semantically accurate way to do it, but to take advantage of backbone automatically assigning the model and collection properties in options, I do this:
var view = new MyView({
collection: {
comments: new CommentsCollection(),
images: new ImagesCollection()
}
});
Works for me. Otherwise you have to put a line in the initialize function to extend this, or use a base class. Personally I find this more elegant, even if it's missing a couple of pluralised properties.
Then, later on, you can use this.collection.comments.toJSON() for your template properties.
There's nothing wrong with housing a number of sub-views within an outer view. Your outer view could be something as simple as this. If you need to wait for all of your collections to be fetched before you render you could try:
var MyView = Backbone.View.extend({
initialize : function (options) {
var done, collections = options.collections;
done = _.invoke(collections, 'fetch');
$.when.apply($, done).done(this.onDataReady);
},
onDataReady : function () {
// Make a second pass at render after setting the flag.
this.ready = true;
this.render();
},
render : function () {
if(this.ready === true) {
// Now render the whole lot.
} else {
// Any non-data depending rendering.
}
}
});
Then you can either initialize the outer view and pass an array of collections in:
var view = new MyView({
collections: [...]
});
If you don't need all the collections to have fetched their data before hand it's even simpler. Just pass in the collections array and set them up as you need:
var MyView = Backbone.View.extend({
initialize : function (options) {
this.collectionA = options.collections[0];
},
render : function () {
}
});

Getting Current Data from KendoUI TreeView

I'm using a kendo UI tree with a remote data source from a JSON file.
I have a button on the tree page which gets the current data of the tree,sends it through a POST to a server and the server saves the current data to the JSON file so as the next time I reload the page,the changes I made will be kept.That's what I want to happen.
So I know the current data of the tree is in:
$("#treeview").data("kendoTreeView").dataSource.data()
Which means the data changes real time in there for example when someone drag and drops a node of the tree.
My problem starts when this data doesn't seem to change when I drag and drop nodes inside the tree,and only changes when I drag and drop a node on the root level of the tree and even then it doesn't do it correcly as the node should be moved in there as well but instead the node gets copied,leaving the past node in the tree as well...
For Example I have this tree:
If I make a drag and drop change like this:
And I send the data,save it and reload the change isn't made at all!
PS:Even when I view the current data after the change before sending it,I see that there is no change on the data at all even though I did a change visualy with a drag and drop.So it doesn't have to do with the sending,saving and the server.
On the other hand,if I make a change like this:
I can see in the current data that the moved node is added in the end of the data indeed but it is not deleted from it's initial position within the data!So if i send the current data to the server,save it and then refresh I get the result:
The code for viewing and sending the data is:
function sendData() {
var req = createRequest();
var putUrl = "rest/hello/treeData";
req.open("post", putUrl, true);
req.setRequestHeader("Content-type","application/json");
var dsdata = $("#treeview").data("kendoTreeView").dataSource.data();
alert(JSON.stringify(dsdata));
req.send(JSON.stringify(dsdata));
req.onreadystatechange = function() {
if (req.readyState != 4) {
return;
}
if (req.status != 200) {
alert("Error: " + req.status);
return;
}
alert("Sent Data Status: " + req.responseText);
}
}
Is this a Bug or am I doing something wrong?Has anyone been able to see the current data changing correctly on every drag and drop?
First and most important you have to use the latest version of KendoUI (Kendo UI Beta v2012.3.1024) still in beta but is where they have solved many problems.
Then, when you create the kendoTreeView you have to say something like:
tree = $("#treeview").kendoTreeView({
dataSource :kendo.observableHierarchy(data),
dragAndDrop:true
}).data("kendoTreeView");
Here the important is not using directly data array but wrapping it with kendo.observableHierarchy.
Then you will have the data updated with the result of drag & drops.
For me in addition to OnaBai answer I had to use the sync function on the save method. I am using Type Script.
this.treeData = new kendo.data.HierarchicalDataSource({
data: kendo.observableHierarchy([]),//Thanks OnaBai
schema: {
model: {
id: "MenuItemId",
children: "MenuItemChildren",
hasChildren: (e) => {
//this removes arrow next to items that do not have children.
return e.MenuItemChildren && e.MenuItemChildren.length > 0;
}
}
}
});
public save() {
this.treeData.sync().done(() => {
console.log("sync data");
var myType = this.treeData.view();
this.$http.post("/api/TreeViewPicker", myType)
.then((response) => {
});
});
}

Looping through JSON with node.js

I have a JSON file which I need to iterate over, as shown below...
{
"device_id": "8020",
"data": [{
"Timestamp": "04-29-11 05:22:39 pm",
"Start_Value": 0.02,
"Abstract": 18.60,
"Editor": 65.20
}, {
"Timestamp": "04-29-11 04:22:39 pm",
"End_Value": 22.22,
"Text": 8.65,
"Common": 1.10,
"Editable": "true",
"Insert": 6.0
}]
}
The keys in data will not always be the same (i've just used examples, there are 20 different keys), and as such, I cannot set up my script to statically reference them to get the values.
Otherwise I could state
var value1 = json.data.Timestamp;
var value2 = json.data.Start_Value;
var value3 = json.data.Abstract;
etc
In the past i've used a simple foreach loop on the data node...
foreach ($json->data as $key => $val) {
switch($key) {
case 'Timestamp':
//do this;
case: 'Start_Value':
//do this
}
}
But don't want to block the script. Any ideas?
You can iterate through JavaScript objects this way:
for(var attributename in myobject){
console.log(attributename+": "+myobject[attributename]);
}
myobject could be your json.data
I would recommend taking advantage of the fact that nodeJS will always be ES5. Remember this isn't the browser folks you can depend on the language's implementation on being stable. That said I would recommend against ever using a for-in loop in nodeJS, unless you really want to do deep recursion up the prototype chain. For simple, traditional looping I would recommend making good use of Object.keys method, in ES5. If you view the following JSPerf test, especially if you use Chrome (since it has the same engine as nodeJS), you will get a rough idea of how much more performant using this method is than using a for-in loop (roughly 10 times faster). Here's a sample of the code:
var keys = Object.keys( obj );
for( var i = 0,length = keys.length; i < length; i++ ) {
obj[ keys[ i ] ];
}
You may also want to use hasOwnProperty in the loop.
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
switch (prop) {
// obj[prop] has the value
}
}
}
node.js is single-threaded which means your script will block whether you want it or not. Remember that V8 (Google's Javascript engine that node.js uses) compiles Javascript into machine code which means that most basic operations are really fast and looping through an object with 100 keys would probably take a couple of nanoseconds?
However, if you do a lot more inside the loop and you don't want it to block right now, you could do something like this
switch (prop) {
case 'Timestamp':
setTimeout(function() { ... }, 5);
break;
case 'Start_Value':
setTimeout(function() { ... }, 10);
break;
}
If your loop is doing some very CPU intensive work, you will need to spawn a child process to do that work or use web workers.
If you want to avoid blocking, which is only necessary for very large loops, then wrap the contents of your loop in a function called like this: process.nextTick(function(){<contents of loop>}), which will defer execution until the next tick, giving an opportunity for pending calls from other asynchronous functions to be processed.
My most preferred way is,
var objectKeysArray = Object.keys(yourJsonObj)
objectKeysArray.forEach(function(objKey) {
var objValue = yourJsonObj[objKey]
})
If we are using nodeJS, we should definitely take advantage of different libraries it provides. Inbuilt functions like each(), map(), reduce() and many more from underscoreJS reduces our efforts. Here's a sample
var _=require("underscore");
var fs=require("fs");
var jsonObject=JSON.parse(fs.readFileSync('YourJson.json', 'utf8'));
_.map( jsonObject, function(content) {
_.map(content,function(data){
if(data.Timestamp)
console.log(data.Timestamp)
})
})
A little late but I believe some further clarification is given below.
You can iterate through a JSON array with a simple loop as well, like:
for(var i = 0; i < jsonArray.length; i++)
{
console.log(jsonArray[i].attributename);
}
If you have a JSON object and you want to loop through all of its inner objects, then you first need to get all the keys in an array and loop through the keys to retrieve objects using the key names, like:
var keys = Object.keys(jsonObject);
for(var i = 0; i < keys.length; i++)
{
var key = keys[i];
console.log(jsonObject.key.attributename);
}
Not sure if it helps, but it looks like there might be a library for async iteration in node hosted here:https://github.com/caolan/async
Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript. Although originally designed for use with node.js, it can also be used directly in the browser.
Async provides around 20 functions that include the usual 'functional' suspects (map, reduce, filter, forEach…) as well as some common patterns for asynchronous control flow (parallel, series, waterfall…). All these functions assume you follow the node.js convention of providing a single callback as the last argument of your async function.
Take a look at Traverse. It will recursively walk an object tree for you and at every node you have a number of different objects you can access - key of current node, value of current node, parent of current node, full key path of current node, etc. https://github.com/substack/js-traverse. I've used it to good effect on objects that I wanted to scrub circular references to and when I need to do a deep clone while transforming various data bits. Here's some code pulled form their samples to give you a flavor of what it can do.
var id = 54;
var callbacks = {};
var obj = { moo : function () {}, foo : [2,3,4, function () {}] };
var scrubbed = traverse(obj).map(function (x) {
if (typeof x === 'function') {
callbacks[id] = { id : id, f : x, path : this.path };
this.update('[Function]');
id++;
}
});
console.dir(scrubbed);
console.dir(callbacks);