I want to programmatically build a dynamic data object based on a context object, like in the example below.
const context = {
...other props...,
groups: [
{
heading: 'basic',
canHaveMultiple: false, // data.basic = {username: { type: 'text', value: '' }, password: { type: 'password', value: ''}}
inputs: [
{
name: 'username',
type: 'text',
placeholder: 'username',
},
{
name: 'password',
type: 'password',
placeholder: 'password',
},
],
},
{
heading: 'about',
canHaveMultiple: false, // data.about = {about: { type: 'textarea', value: '' }}
inputs: [
{
name: 'about',
type: 'textarea',
placeholder: 'about',
canHaveMultiple: false,
},
],
},
{
heading: 'hobbies',
canHaveMultiple: true, // data.hobbies = { model: { title: {type: 'text', value: ''}, description: {type: 'textarea', value: ''} }, values: [ { title: {type: 'text', value: ''}, description: {type: 'textarea', value: ''} }]
inputs: [
{
name: 'title',
type: 'text',
placeholder: 'about',
canHaveMultiple: false,
},
{
name: 'description',
type: 'description',
placeholder: null,
canHaveMultiple: false,
},
],
},
{
heading: 'friends',
canHaveMultiple: true, // data.friends = { model: {title: {type: 'text', value: '' }, description: { type: 'textarea', value: '' }} }, values: [{ name: {type: 'text', value: ''},hobbies: [{ title: {type: 'text', value: ''}, description: {type: 'textarea', value: ''}} }] }
inputs: [
{
name: 'name',
type: 'text',
placeholder: 'this is fine',
canHaveMultiple: false
},
{
name: 'hobbies',
type: 'nested',
canHaveMultiple: true,
inputs: [
{
name: 'title',
type: 'textarea',
placeholder: 'about',
canHaveMultiple: false,
},
{
name: 'description',
type: 'textarea',
placeholder: 'about',
canHaveMultiple: false,
},
]
}
],
},
],
}
The output data should be something like so:
data: {
basic: {
username: {
type: 'text',
value: '',
},
password: {
type: 'password',
value: ''
}
},
about: {
about: {
type: 'textarea',
value: '',
}
},
hobbies: {
model: {
title: {
type: 'text',
value: '',
},
description: {
type: 'textarea',
value: '',
}
},
values: [
{
title: {
type: 'text',
value: '',
},
description: {
type: 'textarea',
value: '',
}
}
]
},
friends: {
model: {
name: {
type: 'text',
value: '',
},
hobbies: {
title: {
type: 'text',
value: '',
},
description: {
type: 'textarea',
value: '',
}
}
},
values: [
]
},
}
In essence,
groups[int].heading becomes the top level property of the data object, and
each group along with each of the child from inputs has a canHaveMultiple property, which distinguishes whether the resulting object structure will be either:
canHaveMultiple == false
group.input.name: {
group.type,
value: ''
}
OR
canHaveMultiple == true
{
model: {
group.input.name: {
type: group.input.type,
value: ''
},
... etc
},
values: [{
group.input[0].name: {
type: group.input[0].type,
value: '',
},
group.input[1].name: {
type: group.input[1].type,
value: '',
}
}]
}
The model is there so that I can easily push a new copy of that object into the values array.
So here is my question:
Is there a way to recursively do this so that the program will create the data object from the context object, and keep looking down the context object chain for any 'nested' type (also within the inputs array) until there is none left? Is this do-able and efficient or am I thinking and going about this the wrong way?
*PS: I have been trying real hard and wrecking my head for a few days now on this but I cannot seem to get it working for Nested Objects beyond 3 levels because I am a newbie in recursion. Please help me :(
This function first group by heading (using reduce) then goes recursively over the inputs fields. That is if an input has inputs we loop that too.
const context={groups:[{heading:"basic",canHaveMultiple:!1,inputs:[{name:"username",type:"text",placeholder:"username"},{name:"password",type:"password",placeholder:"password"},]},{heading:"about",canHaveMultiple:!1,inputs:[{name:"about",type:"textarea",placeholder:"about",canHaveMultiple:!1},]},{heading:"hobbies",canHaveMultiple:!0,inputs:[{name:"title",type:"text",placeholder:"about",canHaveMultiple:!1},{name:"description",type:"textarea",placeholder:null,canHaveMultiple:!1},]},{heading:"friends",canHaveMultiple:!0,inputs:[{name:"name",type:"text",placeholder:"this is fine",canHaveMultiple:!1},{name:"hobbies",type:"nested",canHaveMultiple:!0,inputs:[{name:"title",type:"textarea",placeholder:"about",canHaveMultiple:!1},{name:"description",type:"textarea",placeholder:"about",canHaveMultiple:!1},]}]},]}
function transform(arr) {
var result = arr.reduce(function(agg, item) {
var heading = item.heading
var canHaveMultiple = item.canHaveMultiple
var parent;
agg[heading] = {}
if (canHaveMultiple === false) {
parent = agg[heading]
}
if (canHaveMultiple === true) {
agg[heading] = {
model: {},
values: []
}
parent = agg[heading]['model']
}
function do_inputs(parent, inputs) {
inputs.forEach(function(input) {
if (!input.inputs) {
parent[input.name] = {
type: input.type,
value: ''
// todo: placeholder and other properties
}
} else {
// nested
parent[input.name] = {}
do_inputs(parent[input.name], input.inputs)
}
})
}
do_inputs(parent, item.inputs)
return agg;
}, {})
return result;
}
console.log(transform(context.groups));
.as-console-wrapper {
max-height: 100% !important;
}
I have two snippets which might get you most of the way there.
The first one is perhaps too simple, ignoring your model/value part. But it should be easy to understand:
const convert = (xs) => Object .fromEntries (
xs .map (({name, type, inputs = []}) =>
[name, inputs .length ? convert (inputs) : {type, value: ''}]
)
)
const restructure = (context) => ({
data: Object .fromEntries (context .groups .map (
({heading, inputs}) => [heading, convert (inputs)]
))
})
const context = {other: "props", groups: [{heading: "basic", canHaveMultiple: !1, inputs: [{name: "username", type: "text", placeholder: "username"}, {name: "password", type: "password", placeholder: "password"}]}, {heading: "about", canHaveMultiple: !1, inputs: [{name: "about", type: "textarea", placeholder: "about", canHaveMultiple: !1}]}, {heading: "hobbies", canHaveMultiple: !0, inputs: [{name: "title", type: "text", placeholder: "about", canHaveMultiple: !1}, {name: "description", type: "description", placeholder: null, canHaveMultiple: !1}]}, {heading: "friends", canHaveMultiple: !0, inputs: [{name: "name", type: "text", placeholder: "this is fine", canHaveMultiple: !1}, {name: "hobbies", type: "nested", canHaveMultiple: !0, inputs: [{name: "title", type: "textarea", placeholder: "about", canHaveMultiple: !1}, {name: "description", type: "textarea", placeholder: "about", canHaveMultiple: !1}]}]}]}
console .log (restructure (context))
.as-console-wrapper {max-height: 100% !important; top: 0}
The second one does add the model/value part at the expense of some additional complexity. And it's not clear to me if it's entirely correct, although I think it's close:
const convert = (xs) => Object .fromEntries (
xs .map (({name, type, inputs = [], canHaveMultiple}) => [
name,
canHaveMultiple
? {model: convert (inputs), values: [convert (inputs)]}
: inputs.length ? convert (inputs) : {type, value: ''}
])
)
const restructure = (context) => ({
data: Object .fromEntries (context .groups .map (
({heading, inputs, canHaveMultiple}) => [
heading,
canHaveMultiple
? {model: convert (inputs), values: [convert (inputs)]}
: convert (inputs)
]
))
})
const context = {other: "props", groups: [{heading: "basic", canHaveMultiple: !1, inputs: [{name: "username", type: "text", placeholder: "username"}, {name: "password", type: "password", placeholder: "password"}]}, {heading: "about", canHaveMultiple: !1, inputs: [{name: "about", type: "textarea", placeholder: "about", canHaveMultiple: !1}]}, {heading: "hobbies", canHaveMultiple: !0, inputs: [{name: "title", type: "text", placeholder: "about", canHaveMultiple: !1}, {name: "description", type: "description", placeholder: null, canHaveMultiple: !1}]}, {heading: "friends", canHaveMultiple: !0, inputs: [{name: "name", type: "text", placeholder: "this is fine", canHaveMultiple: !1}, {name: "hobbies", type: "nested", canHaveMultiple: !0, inputs: [{name: "title", type: "textarea", placeholder: "about", canHaveMultiple: !1}, {name: "description", type: "textarea", placeholder: "about", canHaveMultiple: !1}]}]}]}
console .log (restructure (context))
.as-console-wrapper {max-height: 100% !important; top: 0}
In both of them, we could simplify a lot if you had a consistent interface. That is, if groups was called inputs and heading was called name, we could consolidate the two repetitive functions into a single one.
I have created a tabpanel, with images as titles, and a custom component as html. Those custom components use stores, but I'm having an error when updating a single variable (status), all the variables change. Here I show the code:
SelectableButtons component:
Ext.require('Cat3.view.fsm.data.ButtonsStore');
/**
* Selectable button with image
*/
Ext.define('Cat3.view.fsm.components.SelectableButtons', {
extend: 'Ext.view.View',
cls: 'selectable-buttons',
alias: 'widget.selectable-buttons',
tpl: [
'<tpl for=".">',
'<div class="thumb-wrap button button-{status}">',
'<img src="resources/images/cards/{type}/{status}/{name}.png">',
'<img src="resources/images/icons/icon_mandatory.png" class="button-tick button-tick-{status}">',
'</div>',
'</tpl>'
],
// Set both to false if you want single select
multiSelect: true,
simpleSelect: true,
trackOver: false,
itemSelector: 'div.thumb-wrap',
listeners: {
select: function(ths, record, eOpts) {
record.set('status', 'active');
debugAmenaButtonStatus(this);
},
deselect: function(ths, record, eOpts) {
record.set('status', 'passive');
},
selectionchange: function(selection) {
this.refresh();
},
containerclick: function(ths, e, eOpts) {
return false; // Stops the deselection of items
}
},
initComponent: function() {
var store = Ext.create('Cat3.view.fsm.data.ButtonsStore');
this.setStore(store);
this.callParent(arguments);
}
});
debugAmenaButtonStatus = function(ref) {
ref.up().up().items.items.forEach(function(tab) { // Tab
console.log(tab.items.items[0].getStore().data.items[0].data.status); // Amena Button Status
});
};
SelectableButtonsCarousel component (Tab panel). It uses another store but it isn't related:
var cardsImagePath = 'resources/images/cards/';
var ImageModel = Ext.define('ImageModel2', {
extend: 'Ext.data.Model',
fields: [{
name: 'name',
type: 'string'
}, {
name: 'type',
type: 'string'
}, {
name: 'status',
type: 'string'
}, ]
});
var store = Ext.create('Ext.data.Store', {
model: 'ImageModel2',
data: [{
name: 'amena',
type: 'operator',
}, {
name: 'movistar',
type: 'operator',
}, {
name: 'orange',
type: 'operator',
}, {
name: 'simyo',
type: 'operator',
}, {
name: 'yoigo',
type: 'operator',
}, {
name: 'vodafone',
type: 'operator',
}]
});
Ext.define('Cat3.view.fsm.components.SelectableButtonsCarousel', {
extend: 'Ext.tab.Panel',
xtype: 'basic-tabs',
cls: 'selectable-buttons-carousel',
alias: 'widget.selectable-buttons-carousel',
store: store,
resizeTabs: false,
defaults: {
bodyPadding: 10,
layout: 'fit'
},
require: [
'Cat3.view.fsm.components.SelectableButtons',
'Cat3.view.fsm.data.ButtonsStore'
],
titleTpl: function(info) {
return '<img src="resources/images/cards/operator/' + info.status + '/' + info.name + '.png">';
},
listeners: {
render: function(p) {
var tabpanel = this;
this.store.data.items.forEach(function(item, index) {
item.data.status = index === 0 ? 'active' : 'passive';
var buttons = new Cat3.view.fsm.components.SelectableButtons();
tabpanel.add(Ext.create('Ext.Panel', {
id: 'tab-' + index,
title: tabpanel.titleTpl(item.data),
items: [ buttons ],
cls: item.data.status,
info: item.data,
listeners: {
render: function(p) {
console.log('render');
}
}
}));
});
tabpanel.setActiveTab(0);
},
tabchange: function(tabPanel, newCard, oldCard, eOpts) {
newCard.info.status = 'active';
newCard.setTitle(this.titleTpl(newCard.info));
newCard.items.items[0].refresh();
if (oldCard) {
oldCard.info.status = 'passive';
oldCard.setTitle(this.titleTpl(oldCard.info));
}
}
}
});
SelectableButtons Store:
var ImageModel = Ext.define('ImageModel', {
extend: 'Ext.data.Model',
fields: [
{name: 'name', type: 'string'},
{name: 'type', type: 'string'},
{name: 'status', type: 'string'},
]
});
Ext.define('Cat3.view.fsm.data.ButtonsStore', {
extend: 'Ext.data.Store',
model: 'ImageModel',
data: [
{name: 'amena', type: 'operator', status: 'passive'},
{name: 'movistar', type: 'operator', status: 'passive'},
{name: 'orange', type: 'operator', status: 'passive'},
{name: 'simyo', type: 'operator', status: 'passive'},
{name: 'yoigo', type: 'operator', status: 'passive'},
{name: 'vodafone', type: 'operator', status: 'passive'}
],
listeners: {
datachanged: function() {
console.log('store data changed');
}
}
});
All works fine, but when I select a button of SelectableButtons (one tab), the same button of each tab changes its status, and only the one selected of the active tab has to change. Any ideas why? I've checked each store is created separately and that each store has a different id.
Just an idea, for a better guess I'd need to see it working best at http://fiddle.sencha.com:
If "select one selects all", my first idea is that all buttons are just one button referred to from all places. One instance with different names.
Notice the line on your Cat3.view.fsm.components.SelectableButtons view:
initComponent: function() {
var store = Ext.create('Cat3.view.fsm.data.ButtonsStore');
...
}
You might wanna change it to
initComponent: function() {
var store = new Ext.create('Cat3.view.fsm.data.ButtonsStore');
...
}
This will create a new instance of Data Store for your view.
I requesting products from my web service. And i want insert to object array's first index the new item.
my lounch function :
NewMobile.globals = {
mesaj: 'selam',
action: '',
server: '192.168.50.70',
branchCode: '0',
activeTable: '',
activeFolio: '0',
activeTableGroup: '',
activeMustGroup: -1,
activePid: 0,
activeMustGroupString: 0,
activeMustDesc: '',
activeMustArray: [],
activeCampProduct: '',
products: undefined,
rePrint: '',
activePax: 1,
uuid: 'tanimsiz',
activeSkin: 'Krem',
version:undefined,
minVersion:132
};
Its my request.
NewMobile.globals.products = Ext.create('NewMobile.store.PorductStore');
NewMobile.globals.products.setProxy({url: "http://" + NewMobile.globals.server + ':1002/zulu/newmobile/data.aspx?act=getAllProducts'});
NewMobile.globals.products.getProxy();
NewMobile.globals.products.load(function(a, records, c, d, e){
if (c !== true)
{
Ext.Viewport.setMasked(false);
Ext.Msg.alert('uyarı', NewMobile.message.connectionError, Ext.emptyFn);
return;
}
else
{
if(NewMobile.globals.version!==undefined)
{
if(NewMobile.globals.version.MinorRevision>=NewMobile.globals.minVersion)
{
var PopulerProducts=Ext.create('NewMobile.model.Products',
{ id:-65000,
name:"SIK KULLANILANLAR",
groupId:200000,
color:"#FFC673",
type:1,
order:-1,
mustModGroups:0,
mustModGrpCount:0
}
);
NewMobile.globals.products.unshift(PopulerProducts);
}
}
}
});
Product Model :
Ext.define('NewMobile.model.Products', {
extend: 'Ext.data.Model',
requires: [
'Ext.data.Field'
],
config: {
fields: [
{
name: 'id',
type: 'int'
},
{
name: 'name',
type: 'string'
},
{
name: 'groupId',
type: 'int'
},
{
name: 'price',
type: 'float'
},
{
name: 'color'
},
{
name: 'type',
type: 'int'
},
{
name: 'mustModGrpCount'
},
{
name: 'mustModGroups'
},
{
name: 'order',
type: 'int'
},
{
name: 'campCount',
type: 'int'
},
{
name: 'stockCode'
},
{
name: 'populer',
type: 'boolean'
}
]
}
});
Chrome console giving this error.
Object [object Object] has no method 'unshift'
I assume that your NewMobile.store.PorductStore is extending Ext.store.Store. To add items to a store you can either use the add or insert method.
add will add the items to the end of the store so what you want to use is insert and specify the index to be 0. Something like this:
myStore.insert(0, newRecord)
To keep the sorting use addSorted. Inserts the passed Record into the Store at the index where it should go based on the current sort information.
myStore.addSorted(newRecord)
You can read more about how to use stores in Ext.js here: http://docs.sencha.com/extjs/4.2.0/#!/api/Ext.data.Store
I am using Sencha Touch to display nested (associated) model data in a list template but I can only get the root model data to display. My models are an Appointment which belongs to a Customer, and Customers have many Appointments. My model code:
Customer = Ext.regModel('Customer', {
hasMany: { model: 'Appointments', name: 'appointments' },
fields: [
{ name: 'id', type: 'integer' },
{ name: 'firstName', type: 'string' },
{ name: 'lastName', type: 'string' },
{ name: 'email', type: 'string' },
{ name: 'secondary_email', type: 'string' },
{ name: 'homePhone', type: 'string' },
{ name: 'mobilePhone', type: 'string' },
{ name: 'dob', type: 'date', dateFormat: 'Y-m-d' },
{ name: 'allowLogin', type: 'boolean' },
{ name: 'emailReminders', type: 'boolean' },
{ name: 'reminders_to_stylist', type: 'boolean' },
{ name: 'fullName',
convert: function(value, record) {
var fn = record.get('firstName');
var ln = record.get('lastName');
return fn + " " + ln;
} }
]
});
Appointment = Ext.regModel('Appointment', {
belongsTo: { model: 'Customer', name: 'customer' },
fields: [
{ name: 'id', type: 'string' },
{ name: 'startTime', type: 'date', dateFormat: 'c' },
{ name: 'customer_id', type: 'integer' },
{ name: 'startTimeShort',
convert: function(value, record) {
return record.get('startTime').shortTime();
}
},
{ name: 'endTimeShort',
convert: function(value, record) {
return record.get('endTime').shortTime();
}
},
{ name: 'endTime', type: 'date', dateFormat: 'c' }
]
});
And my panel using an xtype: list looks like:
var jsonPanel = {
title: "Appointments",
items: [
{
xtype: 'list',
store: appointmentStore,
itemTpl: '<tpl for="."><span id="{id}">{startTimeShort} - {endTimeShort} <tpl for="customer"><span class="customer">{firstName}</span></tpl></span></tpl>',
singleSelect: true,
onItemDisclosure: function(record, btn, index) {
Ext.Msg.alert('test');
}
}
]
};
The nested data gets loaded from JSON and appears to be loading correctly into the store - when I debug the appointment store object loaded from the Appointment model, I see that the appointment.data.items array objects have a CustomerBelongsToInstance object and that object's data object does contain the correct model data. The startTime and endTime fields display correctly in the list.
I have a suspicion that I am either not using the item template markup correctly, or perhaps there is some weird dependency where I would have to start from the model that has the "has many" association rather than the "belongs to" as shown in the kitchen sink demo.
I wasn't able to find any examples that used this type of association so any help is appreciated.
Looks like your Customer hasmany association is assigning Appointments when it should be appointment which is the name of that model.