Create Country List from JSON - json

I am trying to create a combo box with a list of countries in SAP UI5.
I have created a combo box and have created dynamic list of some countries, but to create more than 100 countries, the only easy way is to create a JSON file of countries and then populate in Controller.js.
I tried to create a JSON file but I am unsure whether I have to store it under model folder or root.
What changes do I have to make in my XML view and controller, and where should I attach countries.json file?

You are looking at something called as "Aggregation Binding" Aggregation Binding in XML views
Here is an example to refer to which explains
How to create a model using data from json file
How to Bind model data to the XML view control(you have to bind comboBox instead of table)
How to bind json data model to an XML view
Let me know if this helps.

Maybe you don't need to create the countries.json file at all :)
As UI5 leverages Common Locale Data Repository (CLDR) internally and provides the data via sap.ui.core.LocaleDataAPI, which includes language names, country names, currency names, singular/plural modifications, and more..
A list of supported regions for the locale data are stored in a JSON format here. In one of those files, if you look at the property "territories", you'll see that the country names are listed among them. You can filter every irrelevant territory out that is not considered a country, and then bind the rest in the items aggregation of the combo box.
Demo
sap.ui.getCore().attachInit(() => sap.ui.require([
"sap/ui/core/Locale",
"sap/ui/core/LocaleData",
"sap/ui/model/json/JSONModel",
"sap/ui/core/mvc/XMLView",
], function(Locale, LocaleData, JSONModel, XMLView) {
"use strict";
XMLView.create({
definition: `<mvc:View xmlns:mvc="sap.ui.core" xmlns="sap.m"
height="100%"
displayBlock="true">
<ComboBox class="sapUiTinyMargin"
width="15rem"
placeholder="Select a country.."
filterSecondaryValues="true"
showSecondaryValues="true"
items="{
path: '/',
templateShareable: false,
key: 'code',
sorter: { path: 'name' }
}">
<core:ListItem xmlns:core="sap.ui.core"
key="{code}"
text="{name}"
additionalText="{code}" />
</ComboBox>
</mvc:View>`,
models: createCountryModel(getCountries()),
}).then(view => view.placeAt("content"));
function createCountryModel(countries, sizeLimit = 300) {
const model = new JSONModel(countries);
model.setSizeLimit(sizeLimit);
model.setDefaultBindingMode("OneWay");
return model;
}
function getCountries() {
const territories = getTerritories();
return extractCountriesFrom(territories, byCustomCheck());
}
function getTerritories(localeId) {
const currentConfig = sap.ui.getCore().getConfiguration();
const locale = localeId ? new Locale(localeId) : currentConfig.getLocale();
const localeData = new LocaleData(locale);
return localeData.getTerritories(); // includes country names
}
function extractCountriesFrom(territories, customCheck = () => true) {
const isValidCountry = createCountryCheck(customCheck);
const toObject = code => Object.freeze({
code: code,
name: territories[code],
});
const countryObjects = Object.keys(territories)
.filter(isValidCountry)
.map(toObject);
return Object.freeze(countryObjects);
}
function createCountryCheck(customCheck, obviouslyNotCountries = [
"EU", // "European Union"
"EZ", // "Eurozone"
"UN", // "United Nations"
"ZZ", // "Unknown Region"
]) {
return territoryCode => territoryCode.length == 2
&& !obviouslyNotCountries.includes(territoryCode)
&& customCheck(territoryCode);
}
function byCustomCheck() { // returns a function that returns boolean
// E.g.: list of sanctioned countries you want to exclude
const list = [
"AF",
"KP",
"IR",
// ...
];
return countryCode => !list.includes(countryCode);
}
}));
<script id="sap-ui-bootstrap" src="https://ui5.sap.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.ui.core, sap.m"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-async="true"
data-sap-ui-compatversion="edge"
data-sap-ui-xx-waitfortheme="init"
></script>
<body id="content" class="sapUiBody sapUiSizeCompact"></body>
As you can see in the example, the ComboBox is successfully populated with the countries. When a new LocaleData instance is created, a request is sent immediately (currently via sync XHR) to get the data which are translated in the language that UI5 detects from the client settings. If no language could be detected, the en.json file will be retrieved.src
The above approach has the following advantages:
No need to create and maintain a separate "country" list. ✔️
Multilingual support ✔️
Reusability ✔️ - When UI5 tries to fetch the same locale data file, which is the case when e.g. a Calendar is used, the browser can serve the file quickly from the cache since the same file was already fetched before.
Note
When creating a JSONModel to store more than 100 country names, keep in mind to increase the size limit as well. The current default limit is 100.

Related

How to change contents of a column in each row of a table without using a props in Vue.js (Element-ui)?

I am stuck on this problem from quiet sometime. I have created a table with 3 columns out of which the for two columns I can use prop property to display the contents of each row for those two columns. Now, for the third column, I want to display contents from another array, how do I display this information in the third column.
Please find the example of the code below:
This is the HTML Code
<el-table :data = "data_table">
<el-table-column
prop = "Id"
label= "Unique ID">
</el-table-column>
<el-table-column
prop = "color"
label = "Color">
</el-table-column>
<el-table-column
prop = ""
label = "Count">
<template slot-scope="scope">
<el-button type="text" #click="dialogVisible = true; get_total_count_users(scope.row)">{{users.length}}</el-button>
<!--Skipping the code for dialog box..I have tested that part pretty well, so I know it works. -->
</template>
</el-table-column>
</el-table>
This is the javascript part of the code:
<script>
export default {
data(){
return{
data_table:[], // I fill the data_table array using some AJAX/Axios calls which has props Id and Color.
users: [], // I fill the users array using some AJAX/Axios calls which has multiple user information.
};
},
methods:{
get_total_count_users(row){
// axios call, etc. This part works successfully, I checked the values.
}
}
</script>
A little explanation for the above code:
I make an AJAX/Axios call to an API which return me a list/array of value in data-table array. It had two props in it, that is Id and Color. I make another axios/AJX call to an api which returns me the list of the users based on the Id present on that row of the table. Each row will have a unique Id. Using that Id, I make an axios call to an api .. example, www.example/{{Id}}.com .This returns me a list of users linked to that id. Now, my next task is to display the total users (by taking the length of the users array) and displaying it as a button. But, as I am not using prop to display the values (length of users array for each row), the value in the button is displayed the same through all the rows. It keeps changing for the entire column if I click a button on any of the rows.
get_total_count_users(scope.row) function is used to make an axios/AJAX call to www.example/{{Id}}.com and stores multiple user information tied with that Id in users array.
Please refer to the image below:
Initially, all values are 0, as the Id in the first row has 0 users attached to it.
When I click on the 3rd rows button (which initially has 0 in it), all the values in that column change to 2, as the Id in the 3rd row has two users tied to it.
Hence, the issue here is that, I simply want to display total number of users (count) each row based on that id without using the prop property.
Hope the above explanation and example is helpful to understand the problem.
Any help would be appreciated.
The answer will solve the problem but is not a specific solution that is defined to get data from multiple arrays into your data table.
You can chain your axios responses and then use a foreach to add the variables to your object inside array.
So I am assuming that you are calling a method on mount like this:
data: () => ({ tableData: [] }),
mounted() {
this.getData();
}
now within your getData() function make different axios calls and put your response into the table, with something like this.
methods: {
getData() {
var myTableData = [];
axios({ method: 'GET', url: 'http://first-url', headers: [...] }).then(res => {
//we have got the table data here
myTableData = res.data;
//looping through data and making axios calls based on data.
myTableData.foreach((row, index) => {
var newUrl = 'https://getdatafrom.com/'+ row.id+'/'; //using the data from previous call.
axios({ method: 'GET', url: newUrl, headers: [...]}).then(res2 => {
var counts = res.data;
// adding variable myCount to the table data array.
row.myCount = counts[index];
}).catch(err => { console.error(err)})
})
// setting data object
this.tableData = myTableData;
}).catch(err => { console.error(err) })
}
}
let me know if you have any issue.

yii2 with dhtmlx scheduler and select populated from the server

I am trying to populate the project select dropdown with data from the server.
I am using yii2.
My controller data action:
public function actionData()
{
$list = new OptionsConnector(null, "PHPYii");
$list->render_table("project", "id", "id(value),name(label)");
$connector = new JSONSchedulerConnector(null, "PHPYii");
$connector->set_options("project", $list);
$connector->configure(
new Booking(), "id", "start, end, activity, user, subsubproject, status, comment"
);
$connector->render();
}
I get an error message:
Exception 'Error' with message 'Call to a member function find() on
string'
And I think this line is the cause: $connector->set_options("project", $list);
What should I change?
UPDATE:
So I am here now:
public function actionData() {
$list = new JSONOptionsConnector(null, "PHPYii");
$list->enable_log("text1.log");
$list->configure(
new Subsubproject(),
"-","id, name"
);
$list->render();
$connector = new JSONSchedulerConnector(null, "PHPYii");
$connector->enable_log("text2.log");
$connector->set_options("subsubprojects", $list);
$connector->configure(
new Booking(),
"id", "start, end, activity, user, subsubproject, status,comment"
);
$connector->render();
}
and I get this:
0: Object { key: undefined, id: 1, name: "Thing1", … }
​1: Object { key: undefined, id: 2, name: "Thing2", … }
​2: Object { key: undefined, id: 3, name: "Thing3", … }
I don't have keys... How can I get some? :)
1) You don't need to call the render method of JSONOptionsConnector directly. Calling it ends processing of the request if I'm not mistaken, so the SchedulerConnector takes no effect
Try commenting out $list->render(); line.
2) The response format seems a bit off. This may be a bug of PHPYii wrapper of dhtmlx connector, I'm not sure
According to source codes the client-side needs value and label properties from options, and while handler returns id and name.
You can try something following:
public function actionData() {
$list = new JSONOptionsConnector(null, "PHPYii");
$list->enable_log("text1.log");
$list->configure(
new Subsubproject(),
"id","id(value), name(label)"
// or
// "id(value)","id(value), name(label)"
);
$connector->enable_log("text2.log");
$connector->set_options("subsubprojects", $list);
$connector->configure(
new Booking(),
"id", "start, end, activity, user, subsubproject, status,comment"
);
$connector->render();
}
This should produce a json response containing a list of booking and subprojects.
However, I can't test this code so something may still be wrong.
You can try it and see whether the result JSON looks right.
If it doesn't get you any closer, I honestly would produce json manually rather than using a connector with PHPYii wrapper. That way you'll have direct control over what is returned from your controller and won't have another black box there.
You'll need to return a json of the following structure from your action:
https://docs.dhtmlx.com/scheduler/data_formats.html#jsonwithcollections
so you'll have something like this in your action:
return $this->asJson([
"data"=> $preparedEventsArray
"collections" => [
"subprojects"=> $preparedSubprojects
]
]);
where $preparedEventsArray is an array of event objects as shown in docs, and $subprojects - your value/label objects
Note that names of properties in the data collection - "id","start_date","end_date","text" - are mandatory, you'll have to map your data model to this structure,
e.g.
start -> start_date
end -> end_date
activity -> text
all other properties can have their names unchanged.
The official docs don't have a sample code for Yii2, unfortunately.
There are common docs for server formats
https://docs.dhtmlx.com/scheduler/server_integration.html
And tutorials for PHP Slim framework and Laravel, which is not exactly what you need, but the closest thing the current documentation has.

Sailsjs MVC map params from external API to multiple models

I need to create a database of shopify orders so I can run advanced queries and sales reports that you can't do in the shopify admin area. I'm building in Sails .12 and mysql. Shopify lets you register a webhook so that every time an order is placed, it creates a POST to the specified URL with the order data in the body as JSON. The products ordered are an array of JSON objects as one of the values in the POST:
{
"id": 123456,
"email": "jon#doe.ca",
"created_at": "2017-01-10T14:26:25-05:00",
...//many more entires
"line_items": [
{
"id": 24361829895,
"variant_id": 12345,
"title": "T-Shirt",
"quantity": 1,
"price": "140.00",
},
{
"id": 44361829895,
"variant_id": 42345,
"title": "Hat",
"quantity": 1,
"price": "40.00",
},
]
}
I need to save the order into an Orders table, and the products ordered into a line_items table that is a one to many relation; one order can have many line_items (products ordered). There are over 100 key-value pairs sent by the webhook, and I'm saving all of it. I've created my two models where I define the data type, so now i have very long Order.js and Line_item.js files, and I'm using the
line_items: {
collection: 'line_item',
via: 'order_id'
},
in my Order.js, and
order_id: {
model: 'order'
},
in my Line_item.js models to relate them. Is this the correct way to denfine my two tables? Also, where would I put the code that maps the JSON to the model parameters? If I put that code in the controllers, would I have to type another 100+ lines of code to map each json value to its correct parameter. The how would I save to the two different models/tables? Eg:
var newOrder = {};
newOrder.id =req.param('id');
newOrder.email = req.param('email');
newOrder.name = req.param('name');
...//over 100 lines more, then Order.create(newOrder, ...)
var newLine_items = req.params('line_items'); //an array
_.forEach(newLine_items, function(line_item){
var newLine_item = {};
newLine_item.id = line_item.id;
newLine_item.order_id = newOrder.id;
newLine_item.title = line_item.title;
//etc for over 20 more lines, then Line_item.create(newLine_item, ...)
});
I need to save the order into an Orders table, and the products ordered into a line_items table that is a one to many relation; one order can have many line_items (products ordered).
That sounds completely reasonable, well, besides the use of the Oxford comma :)
There are over 100 key-value pairs sent by the webhook
I'm not sure that I understand exactly what this is or what it is used for within this process.
That being said, it might help to have a single attribute in your model for this which has a JSON value, then retrieve and work with it as JSON instead of trying to manually account for each attribute if that is what you're doing over there?
It really depends on your use case and how you'll use the data though but I figure if the format changes you might have a problem, not so if it's just being stored and parsed as a JSON object?
Also, where would I put the code that maps the JSON to the model parameters
In v0.12.x take a look at Services.
In v1, Services will still work but moving this logic into Helpers might be a good option but then, it seems that a custom model method would be a better one.
Here is a shorter version of your code:
var newOrder = req.allParams();
newLine_items = {};
_.forEach(newOrder.line_items, function(line_item) {
newLine_items.push(line_item);
});
Here is what your logic might look like:
var newOrder = req.allParams();
// Store the order
Order
.create(newOrders)
.exec(function (err, result) {
if (err) // handle the error
var newLine_items = {};
_.forEach(newOrder.line_items, function(line_item) {
// Add the order id for association
line_item.order_id = result.id;
// Add the new line item with the orders id
newLine_items.push(line_item);
});
// Store the orders line items
LineItems
.create(newLine_items)
.exec(function (err, result) {
if (err) // handle the error
// Handle success
});
});
And the lifecycle callback in the Order model:
beforeCreate: function (values, cb) {
delete(values.line_items);
cb();
}
But you really should look into bluebird promises as the model methods in version one of sails have opt in support for them and it helps to negate the pyramid of doom that is starting in my example and is also something that you want to avoid :P

Sequelize include (how to structure query)?

I have a query I'm trying to perform based on a one to many relationship.
As an example there is a model called Users and one called Projects.
Users hasMany Projects
Projects have many types which are stored in a type (enum) column. There are 4 different types that potentially a user may have that I want to load. The catch is I want to include the most recent project record (createdAt column) for all networks that potentially will be there. I have not found a way to structure the query for it to work as an include. I have however found a way to do a raw query which does what I want.
I am looking for a way without having to do a raw query. By doing the raw query I have to map the returned results to users I've returned from the other method, or I have to do a simple include and then trim off all the results that are not the most recent. The latter is fine, but I see this getting slower as a user will have many projects and it will keep growing steadily.
This allow serialize a json for anywhere action about a model. Read it, very well
sequelize-virtual-fields
// define models
var Person = sequelize.define('Person', { name: Sequelize.STRING });
var Task = sequelize.define('Task', {
name: Sequelize.STRING,
nameWithPerson: {
type: Sequelize.VIRTUAL,
get: function() { return this.name + ' (' + this.Person.name + ')' }
attributes: [ 'name' ],
include: [ { model: Person, attributes: [ 'name' ] } ],
order: [ ['name'], [ Person, 'name' ] ]
}
});
// define associations
Task.belongsTo(Person);
Person.hasMany(Task);
// activate virtual fields functionality
sequelize.initVirtualFields();

ExtJS: How do I load a ComboBox with the value in another ComboBox?

I'm a bit of an ExtJS noob. I have a loaded up an ExtJS ComboBox (call it CBa) where the value field contains a JSON string that, when selected, should be loaded into a second combobox (CBb).
All of the examples I can find tell me how to load up a ComboBox from an external URL, but in this case, I want to load it up from a string I already have locally.
What magic do I place on each ComboBox to make this happen?
You can create a local combo like so :
// The data store containing the list of states
var states = Ext.create('Ext.data.Store', {
fields: ['abbr', 'name'],
data : [
{"abbr":"AL", "name":"Alabama"},
{"abbr":"AK", "name":"Alaska"},
{"abbr":"AZ", "name":"Arizona"}
//...
]
});
var combo2 = Ext.create('Ext.form.ComboBox',{
// combo config;
});
// Create the combo box, attached to the states data store
Ext.create('Ext.form.ComboBox', {
fieldLabel: 'Choose State',
store: states,
queryMode: 'local', //makes it a local combo. Don't need a url.
displayField: 'name',
valueField: 'abbr',
renderTo: Ext.getBody(),
listeners : {
select : function() {
var value = this.getValue();
combo2.setValue(value);
}
}
});
Use the select event of combo to set the selected value into the second combo. Note that the selected value should be a value present in the data which is defined in the store of combo2 for it to be set. Read docs of setValue here for exact information.
EDIT after reading comment:
You can set the second combo's store dynamically. Change the select event mentioned above to this:
select : function()
{
var dataArray = [],data = this.getValue(); //Json string
dataArray.push(Ext.decode(data)); //In case there is only one object in the string instead of any array.
combo2.getStore().loadData(dataArray, false); //dataArray is automatically parsed into Model Objects. Second param to allow append to existing store data.
}
}