Related
We are creating a new version our API (v2) adopting the JSON:API specification (https://jsonapi.org/). I'm not being able to port the ExtJS model associations (belongs_to) to the new pattern.
The ExtJS documentation only shows how to use a nested relation in the same root node (https://docs.sencha.com/extjs/4.2.2/#!/api/Ext.data.association.Association).
v1 data (sample):
{
"data": [
{
"id": 1,
"description": "Software Development",
"area_id": 1,
"area": {
"id": 1,
"code": "01",
"description": "Headquarters"
}
},
],
"meta": {
"success": true,
"count": 1
}
}
v2 data (sample):
{
"data": [
{
"id": "1",
"type": "maint_service_nature",
"attributes": {
"id": 1,
"description": "Software Development",
"area_id": 1
},
"relationships": {
"area": {
"data": {
"id": "1",
"type": "area"
}
}
}
}
],
"included": [
{
"id": "1",
"type": "area",
"attributes": {
"id": 1,
"code": "01",
"description": "Headquarters"
}
}
],
"meta": {
"success": true,
"count": 1
}
}
My model:
Ext.define('Suite.model.MaintServiceNature', {
extend: 'Ext.data.Model',
fields: [
{ desc: "Id", name: 'id', type: 'int', useNull: true },
{ desc: "Area", name: 'area_id', type: 'int', useNull: true },
{ desc: "Description", name: 'description', type: 'string', useNull: true, tableIdentification: true }
],
associations: [
{
type: 'belongsTo',
model: 'Suite.model.Area',
foreignKey: 'area_id',
associationKey: 'area',
instanceName: 'Area',
getterName: 'getArea',
setterName: 'setArea',
reader: {
type: 'json',
root: false
}
}
],
proxy: {
type: 'rest',
url: App.getConf('restBaseUrlV2') + '/maint_service_natures',
reader: {
type: 'json',
root: 'data',
record: 'attributes',
totalProperty: 'meta.count',
successProperty: 'meta.success',
messageProperty: 'meta.errors'
}
}
});
Any ideias on how to setup the association to work with the v2 data?
I'm honestly taking a stab at this one... I haven't used Ext JS 4 in years, and I wouldn't structure my JSON like JSON:API does, but I think the only way you can accomplish this is by rolling your own reader class. Given that you have generic properties for your data structure, this reader should work for all scenarios... although, I'm not too familiar with JSON:API, so I could be totally wrong. Either way, this is what I've come up with.
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.define('MyReader', {
extend: 'Ext.data.reader.Json',
alias: 'reader.myReader',
root: 'data',
totalProperty: 'meta.count',
successProperty: 'meta.success',
messageProperty: 'meta.errors',
/**
* #override
*/
extractData: function (root) {
var me = this,
ModelClass = me.model,
length = root.length,
records = new Array(length),
dataConverter,
convertedValues, node, record, i;
for (i = 0; i < length; i++) {
node = root[i];
var attrs = node.attributes;
if (node.isModel) {
// If we're given a model instance in the data, just push it on
// without doing any conversion
records[i] = node;
} else {
// Create a record with an empty data object.
// Populate that data object by extracting and converting field values from raw data.
// Must pass the ID to use because we pass no data for the constructor to pluck an ID from
records[i] = record = new ModelClass(undefined, me.getId(attrs), attrs, convertedValues = {});
// If the server did not include an id in the response data, the Model constructor will mark the record as phantom.
// We need to set phantom to false here because records created from a server response using a reader by definition are not phantom records.
record.phantom = false;
// Use generated function to extract all fields at once
me.convertRecordData(convertedValues, attrs, record, me.applyDefaults);
if (me.implicitIncludes && record.associations.length) {
me.readAssociated(record, node);
}
}
}
return records;
}
});
Ext.define('Suite.model.Area', {
extend: 'Ext.data.Model',
fields: [{
name: 'type',
type: 'string'
}]
});
Ext.define('Suite.model.MaintServiceNature', {
extend: 'Ext.data.Model',
fields: [{
desc: "Id",
name: 'id',
type: 'int',
useNull: true
}, {
desc: "Area",
name: 'area_id',
type: 'int',
useNull: true
}, {
desc: "Description",
name: 'description',
type: 'string',
useNull: true,
tableIdentification: true
}],
associations: [{
type: 'belongsTo',
model: 'Suite.model.Area',
associatedName: 'Area',
foreignKey: 'area_id',
associationKey: 'relationships.area.data',
instanceName: 'Area',
getterName: 'getArea',
setterName: 'setArea'
}],
proxy: {
type: 'rest',
url: 'data1.json',
reader: {
type: 'myReader'
}
}
});
Suite.model.MaintServiceNature.load(null, {
callback: function (record) {
console.log(record.getData(true));
}
});
}
});
I have two tables:
const attr = {
name: {
type: DataTypes.STRING,
},
};
const Tags = createModel('Tags', attr, {});
and:
const attr = {
tagId: {
type: DataTypes.INTEGER,
references: { model: 'Tags', key: 'id' },
}
}
const Client = createModel('Client', attr, {})
Client.belongsTo(Tag, { foreignKey: 'tagId', as: 'tags' });
and my query is this:
const clientCount = await Client.findAll({
include: [ { model: Tags, as: 'tags' } ],
attributes: { exclude: 'tagId' }
});
and this is my response:
{
"id": 1,
"createdAt": "2020-01-20T00:00:00.000Z",
"updatedAt": "2020-01-22T00:00:00.000Z",
"tags": {
"id": 1,
"name": "New tag",
"createdAt": "2020-01-20T00:00:00.000Z",
"updatedAt": "2020-01-20T00:00:00.000Z"
}
}
but I want my tags to be an array, so I guest I have to define a one to many association, but everything I tried so far failed.
What I want is tags to be an array, where I can add multiple tag objects:
{
"id": 1,
"createdAt": "2020-01-20T00:00:00.000Z",
"updatedAt": "2020-01-22T00:00:00.000Z",
"tags": [
{
"id": 1,
"name": "New tag",
"createdAt": "2020-01-20T00:00:00.000Z",
"updatedAt": "2020-01-20T00:00:00.000Z"
}
]
}
Method1
We need new model as Client_Tag
const attr = {
clientId: {
type: DataTypes.INTEGER,
},
tagId: {
type: DataTypes.INTEGER,
},
};
const Client_Tag = createModel('Client_Tag', attr, {});
Client.belongsToMany(Tag, {
foreignKey: 'clientId',
otherKey: 'tagId',
through: models.Client_Tag,
as: 'tags'
});
const clientCount = await Client.findAll({
include: [ { model: Tags, as: 'tags' } ],
attributes: { exclude: 'tagId' }
});
Method2
const attr = {
name: {
type: DataTypes.STRING,
},
clientId: { // need clientId in tag model, and remove 'tagId' from client model
type: DataTypes.INTEGER,
}
};
const Tags = createModel('Tags', attr, {});
Client.belongsToMany(Tag, { foreignKey: 'tagId', as: 'tags' });
I have a few tables and I want to do some includes on a joined table, but I can't seem to figure it out. Here are my models:
/* Staff model */
const model = {
fisrName: {
type: DataTypes.INTEGER,
references: { model: 'Roles', key: 'id' },
},
lastName: {
type: DataTypes.INTEGER,
references: { model: 'Profiles', key: 'id' },
}
};
const Staff = createModel('Staff', model, { paranoid: true });
export default Staff
/* Service model */
const model = {
name: {
type: DataTypes.STRING,
},
category: {
type: DataTypes.STRING,
},
description: {
type: DataTypes.STRING,
}
};
const Service = createModel('Service', model, {});
export default Service;
/* Appointment model */
const model = {
endDate: {
type: DataTypes.DATE,
},
startDate: {
type: DataTypes.DATE,
},
day: {
type: DataTypes.DATE,
},
};
const Appointment = createModel('Appointment', model, {})
Appointment.belongsToMany(Service, { through: 'Products', as: 'products' });
export default Appointment;
/* Products model */
const model = {
serviceId: {
type: DataTypes.INTEGER,
},
appointmentId: {
type: DataTypes.INTEGER,
},
staffId: {
type: DataTypes.INTEGER,
references: { model: 'Staff', key: 'id' },
}
};
const Product = createModel('Product', model, {});
Product.belongsTo(Staff, { foreignKey: 'staffId', as: 'staff' });
export default Product;
This is my appointment query, where I include the services array, and on this services array, I have a Products object, and in this object I have a staffId that I want to populate, and I'm not sure how. I have tried different ways, but nothing worked.
const appointment = await Appointment.findByPk(req.params.id, {
include: [
{
model: Service,
as: 'services',
through: { attributes: { include: ['id', 'staffId', 'serviceId', 'appointmentId'], exclude: ['createdAt', 'updatedAt', 'AppointmentId', 'ServiceId'] },
},
],
});
And this is my response:
{
"startDate": "date",
"endDate": "date",
"day": "date",
"services": [{
"id": 1,
"name": "Service name",
"category": "service category",
"description": "service description",
"Products": {
"id": 1,
"staffId": 2,
"serviceId": 1,
"appointmentId": 1
}
}]
}
What I want is to do is to populate the staffId from Products with the model from the collection, something like this:
{
"startDate": "date",
"endDate": "date",
"day": "date",
"services": [{
"id": 1,
"name": "Service name",
"category": "service category",
"description": "service description",
"Products": {
"id": 1,
"staffId": {
"firstName": "First Name",
"lastName": "Last Name"
},
"serviceId": 1,
"appointmentId": 1
}
}]
}
const appointment = await Appointment.findByPk(req.params.id, {
include: [
{
model: Service,
as: 'services',
through: {
attributes: { include: ['id', 'staffId', 'serviceId', 'appointmentId'], exclude: ['createdAt', 'updatedAt', 'AppointmentId', 'ServiceId'] },
},
include: [{
as: 'Products', model: Product,
include: 'staff'
}]
}
]
});
After a join operation among three models I received a valid result but I would rename the attributes generated by the join operation of the findAll
Query:
const orchards = await db.Area.findAll({
include: [db.AreaCoordinate, db.Crop],
attributes: ['id', 'name']
});
AreaCoordinate Model:
module.exports = function (sequelize, DataTypes) {
var AreaCoordinate = sequelize.define('AreaCoordinate', {
latitude: {
type: DataTypes.STRING(45),
allowNull: true
},
longitude: {
type: DataTypes.STRING(45),
allowNull: true
}
}, {
classMethods: {
associate: function (models) {
AreaCoordinate.belongsTo(models.Area, {foreignKey: 'areaId'});
}
}
});
return AreaCoordinate;
};
Crop Model:
module.exports = function (sequelize, DataTypes) {
var Crop = sequelize.define('Crop', {
name: {
type: DataTypes.STRING(45),
allowNull: true
},
lang: {
type: DataTypes.STRING(45),
allowNull: true
}
}, {
classMethods: {
associate: function (models) {
Crop.hasMany(models.Area, {foreignKey:'cropId'})
}
}
});
return Crop;
};
Area Model:
module.exports = function (sequelize, DataTypes) {
var Area = sequelize.define('Area', {
name: DataTypes.STRING
}, {
classMethods: {
associate: function (models) {
// example on how to add relations
Area.belongsTo(models.Crop, {foreignKey: 'cropId'});
Area.belongsTo(models.Orchard, {as: 'orchard'});
Area.hasMany(models.AreaCoordinate, {foreignKey:'areaId'})
}
}
});
return Area;
};
I would receive from the query a JSON like this:
{
"status": 200,
"status_message": "OK",
"data": {
"orchard": [
{
"name": "pantano",
"coordinates": [
{
"id": 115,
"latitude": "1",
"longitude": "2",
"createdAt": "2017-08-29T12:03:11.000Z",
"updatedAt": "2017-08-29T12:03:11.000Z",
"areaId": 28
},
{
"id": 116,
"latitude": "1",
"longitude": "2",
"createdAt": "2017-08-29T12:03:11.000Z",
"updatedAt": "2017-08-29T12:03:11.000Z",
"areaId": 28
}
],
"cropId": 10
}
]
}
}
But what I receive is (look AreaCoordinates and Crop):
{
"status": 200,
"status_message": "OK",
"data": {
"orchard": [
{
"name": "pantano",
"AreaCoordinates": [
{
"id": 115,
"latitude": "1",
"longitude": "2",
"createdAt": "2017-08-29T12:03:11.000Z",
"updatedAt": "2017-08-29T12:03:11.000Z",
"areaId": 28
},
{
"id": 116,
"latitude": "1",
"longitude": "2",
"createdAt": "2017-08-29T12:03:11.000Z",
"updatedAt": "2017-08-29T12:03:11.000Z",
"areaId": 28
}
],
"Crop": 10
}
]
}
}
I tried to set some alias for AreaCoordinates and Crop but I couldn't find a solution. Thank you in advance for your support.
Write query like this:
const result = await Table.findAll({
attributes: ['id', ['foo', 'bar']] //id, foo AS bar
});
By default in Sequelize association, it will set the attribute name as the related model name. For your case, the related model named AreaCoordinates, so the attribute name in return will be AreaCoordinates. You should use as. Modify yours and Try this:
###findAll Query:
const orchards = await db.Area.findAll({
include: [
{
model: db.AreaCoordinate,
as: 'coordinates'
}, {
model: db.Crop,
as: 'cropId',
attributes: ['id']
}],
attributes: ['id', 'name']
});
###Area Model
module.exports = function (sequelize, DataTypes) {
var Area = sequelize.define('Area', {
name: DataTypes.STRING
}, {
classMethods: {
associate: function (models) {
// example on how to add relations
Area.belongsTo(models.Crop, {
foreignKey: 'cropId',
as: 'cropId'
});
Area.belongsTo(models.Orchard, {as: 'orchard'});
Area.hasMany(models.AreaCoordinate, {
foreignKey:'areaId',
as: 'coordinates'
})
}
}
});
return Area;
};
I want to read json data from file - content of the json file shown below
{
"form": {
"fields" : [
{
"field":"textfield",
"name": "username",
"constrain": "5-10",
"value": ""
},
{
"field":"textfield",
"name": "password",
"constrain": "5-10",
"value": ""
},
{
"field":"datepickerfield",
"name": "Birthday",
"constrain": "5-10",
"value": "new Date()"
},
{
"field":"selectfield",
"name": "Select one",
"options":[
{"text": "First Option", "value": 'first'},
{"text": "Second Option", "value": 'second'},
{"text": "Third Option", "value": 'third'}
]
},
]
}
}
Model
Ext.define('dynamicForm.model.Form', {
extend: 'Ext.data.Model',
config: {
fields: [
{name: 'field', type: 'string'},
{name: 'name', type: 'string'},
{name: 'constrain', type: 'string'},
{name: 'value', type: 'string'}
],
hasMany: {model: 'dynamicForm.model.SelectOption', name: 'options'}
}
});
Ext.define('dynamicForm.model.SelectOption', {
extend: 'Ext.data.Model',
config: {
fields: [
{name: 'text', type: 'string'},
{name: 'value', type: 'string'}
]
}
});
store
Ext.define('dynamicForm.store.FormStore', {
extend : 'Ext.data.Store',
storeId: 'formStore',
config : {
model : 'dynamicForm.model.Form',
proxy : {
type : 'ajax',
url : 'form.json',
reader : {
type : 'json',
rootProperty : 'form.fields'
}
},
autoLoad: true
}
});
This what i tried so for.
var fromval = Ext.create('dynamicForm.store.FormStore');
fromval.load(function (){
console.log(fromval);
// i added register view which having form panel with id "testForm"
Ext.Viewport.add({
xtype : 'register'
});
for(i=0; i< fromval.getCount(); i++) {
console.log("------");
Ext.getCmp('testForm').add({
xtype: fromval.getAt(i).data.field,
label: fromval.getAt(i).data.name,
value: fromval.getAt(i).data.value,
options: [
{text: "First Option", value: "first"},
{text: "Second Option", value: "second"},
{text: "Third Option", value: "third"}
]
});
}
});
two text fileds and date are woking good, but i don't know how to get options for select field from store, just heard coded now.
over all Based on the above json data, i need to create sencha form dynamically.
Better to follow MVC structure:
Create a model:
Ext.define('MyApp.model.FormModel', {
extend: 'Ext.data.Model',
config: {
fields: ["field","name"]
}
});
A store with proxy:
Ext.define('MyApp.store.FormStore',{
extend: 'Ext.data.Store',
config:
{
model: 'MyApp.model.FormModel',
autoLoad:true,
proxy:
{
type: 'ajax',
url : 'FormData.json', //Your file containing json data
reader:
{
rootProperty:'form.fields'
}
}
}
});
The formData.json file:
{
"form": {
"fields" : [
{
"field":"textfield",
"name": "username"
},
{
"field":"textfield",
"name": "password"
},
]
}
}
And then use the FormStore to fill the form data as you need.
Ext.define('MyApp.view.LoginPage', {
extend: 'Ext.form.Panel',
config: {
items:{
xtype:'fieldset',
layout:'vbox',
items:[{
flex:1,
xtype:'textfield',
id:'namefield',
placeHolder:'Username'
},{
flex:1,
xtype:'passwordfield',
id:'passwordfield',
placeHolder:'Password'
}]
},
listeners:{
painted:function()
{
var store=Ext.getStore('FormStore');
while(!store.isLoaded())
{
console.log("loading...");
}
var record=store.getAt(0);
Ext.getCmp('namefield').setValue(record.data.name);
Ext.getCmp('passwordfield').setValue(record.data.password);
}
}
}
});
{
"data":[
{
"xtype":"textfield",
"title":"UserName",
"name": "username"
},
{
"xtype":"textfield",
"title":"password",
"name": "password"
},
{
"xtype":"textfield",
"title":"phone no",
"name": "birthday"
},
{
"xtype":"textarea",
"title":"address",
"name": "address"
}
]
}
Ext.define('dynamicForm.model.FormModel', {
extend: 'Ext.data.Model',
fields: ['field', 'name']
});
Ext.define('dynamicForm.store.FormStore', {
extend : 'Ext.data.Store',
model : 'dynamicForm.model.FormModel',
proxy :
{
type : 'ajax',
url : 'data/user.json',
reader :
{
type : 'json',
rootProperty:'data'
},
autoLoad: true
}
});
Ext.define('dynamicForm.view.DynaForm',{
extend:'Ext.form.Panel',
alias:'widget.df1',
items:[]
});
Ext.application({
name:'dynamicForm',
appFolder:'app',
controllers:['Users'],
launch:function(){
Ext.create('Ext.container.Viewport',{
items:[
{
xtype:'df1',
items:[]
}
]
});
}
});
Ext.define('dynamicForm.controller.Users',{
extend:'Ext.app.Controller',
views:['DynaForm'],
models:['FormModel'],
stores:['FormStore'],
refs:[
{
ref:'form1',
selector:'df1'
}
],
init:function(){
console.log('in init');
this.control({
'viewport > panel': {
render: this.onPanelRendered
}
});
},
onPanelRendered: function() {
var fromval=this.getFormStoreStore();
var form=this.getForm1();
fromval.load({
scope: this,
callback: function(records ,operation, success) {
Ext.each(records, function(rec) {
var json= Ext.encode(rec.raw);
var response=Ext.JSON.decode(json);
for (var i = 0; i < response.data.length; i++) {
form.add({
xtype: response.data[i].xtype,
fieldLabel: response.data[i].title,
name: response.data[i].name
});
}
//form.add(Ext.JSON.decode(json).data);
form.doLayout();
});
}
});
}
});
It will be done automatically if you insert it into any extjs component content :
var jsonValues = "{
"form": {
"fields" : [
{
"field":"textfield",
"name": "username"
},
{
"field":"textfield",
"name": "password"
},
]
}
}";
var panel = new Ext.Panel({
id : 'myPanel',
items : [jsonValues]
});
panel.show();