Creating and removing Ember Models dynamically / at runtime - dynamic

I have been working on this for a couple of days now and have decided to bite the bullet and ask for help.
I am using ember-cli, and I'm trying to create Ember Models dynamically at runtime based on some metadata. An sample use case would be if a user wanted to specify a data set (query, file etc.); I would like to read the user's data file and setup a model based on the file's structure.
I have successfully setup an ember-cli application which uses Ember Data and Fixtures (for simplicity). My static example has three models and I can see the models and the data in the Store when I use the Chrome Ember debugger.
Now on to my dynamic example...
I have been able to successfully define a model at runtime by setting it up in IndexRoute
var IndexRoute = Ember.Route.extend({
init: function() {
// Create a simple model dynamically
var MyModel = DS.Model.extend({
defineAttributes: function(){
Ember.defineProperty(this, 'col1', DS.attr());
Ember.defineProperty(this, 'col2', DS.attr());
Ember.defineProperty(this, 'col3', DS.attr());
}.on('init')
});
// insert some data into our model
MyModel.reopenClass({
FIXTURES: [
{
id: '1',
col1: "key1",
col2: "value1",
col3: "value2"
},
{
id: '2',
col1: "key2",
col2: "value1",
col3: "value2"
},
{
id: '3',
col1: "key3",
col2: "value1",
col3: "value2"
}
]
});
}
});
The problem is that I don't see where the model has been placed, and it doesn't appear in the Chrome debugger in the Data (store).
If I change the definition of MyModel to Ember.Application.MyModel then I can see the Model is defined under Ember.Application in the console but it still isn't visible to the Ember Debugger.
I then tried to extend the above example to add the model hook as follows:
var IndexRoute = Ember.Route.extend({
init: function() {
...
},
model: function() {
return this.store.findAll('my-model');
}
});
But the call to this.store.findAll('my-model') fails in both cases. I am assuming that I am not wiring things up correctly.
In my example I am also aware that the name of my Model is not dynamic either so I really want to know how to create a name for my model programmatically at runtime.
I would really appreciate some help or guidance from those out there who are more experienced with the framework because I've been really impressed with Ember and the whole configuration over convention approach.
Thanks,
Dave

You're essentially defining something, then throwing it away. You'll need to attach it to your app's namespace (you can see it in your index file or configuration file if you're using Ember-cli).
Additionally, I doubt defining attributes on init will work, it might, but it isn't actually necessary.
Thirdly, do you really want to use Ember Data for this? Ember Data is very opinionated, and it sounds like you don't want to define things.

Related

Spartacus Configurable Product Integration: Custom Normalizer nested data being overridden

I am trying to implement a custom normalizer to the configurable product feature module. I have to include a custom field in the Attributes datatype. Currently only the OccConfigurationVariantNormalizer is available, which is quite high level form a data's point of view.
My problem occurs with the execution order of the normalizers. The default normalizer ist this: https://github.com/SAP/spartacus/blob/develop/feature-libs/product-configurator/rulebased/occ/variant/converters/occ-configurator-variant-normalizer.ts which is being called after my custom normalizer. Hence, the convertGroup() function is overriding my custom attribute field.
Here is my implementation:
#Injectable({
providedIn: 'root'
})
export class CustomConfiguratorNormalizerService extends OccConfiguratorVariantNormalizer{
convertAttribute(sourceAttribute: CustomOccAttribute, attributeList: CustomAttribute[]): void{
super.convertAttribute(sourceAttribute, attributeList);
attributeList[attributeList.length - 1].customField = sourceAttribute.customField;
}
}
Extending the original Normalizer seemed like the most promising solution for the time being, and is working quite like intended. So the customField ist being present at this point in time of execution.
Afterwards the OccConfiguratorVariantNormalizer kicks in, which is defining a new Attribute array in convertGroup(), erasing my custom attribute:
convertGroup([...]) {
const attributes: Configurator.Attribute[] = [];
if (source.attributes) {
source.attributes.forEach((sourceAttribute) =>
this.convertAttribute(sourceAttribute, attributes)
);
}
[...]
};
convertAttribute(
sourceAttribute: OccConfigurator.Attribute,
attributeList: Configurator.Attribute[]
): void {
const attribute: Configurator.Attribute = {
name: sourceAttribute.name,
label: sourceAttribute.langDepName,
required: sourceAttribute.required,
uiType: this.convertAttributeType(sourceAttribute.type),
values: [],
groupId: this.getGroupId(sourceAttribute.key, sourceAttribute.name),
userInput: sourceAttribute.formattedValue,
maxlength:
sourceAttribute.maxlength + (sourceAttribute.negativeAllowed ? 1 : 0),
numDecimalPlaces: sourceAttribute.numberScale,
negativeAllowed: sourceAttribute.negativeAllowed,
numTotalLength: sourceAttribute.typeLength,
selectedSingleValue: null,
images: [],
hasConflicts: sourceAttribute?.conflicts?.length > 0 ? true : false,
};
[...]
};
If my custom normalizer was the only one I could imagine it would work, which is why I tried to inject it like this:
{
provide: VARIANT_CONFIGURATOR_NORMALIZER,
useClass: CustomConfiguratorNormalizerService,
multi: false,
}
Throwing me Error: Multi-providers mixed with single providers.
Also, using the documentation from https://sap.github.io/spartacus-docs/connecting-to-other-systems/ I cannot get it to work without extending the original Normalizer, since target will always be undefined, which probably would not be the case if my custom normalizer came in second.
I feel like this https://github.com/SAP/spartacus/issues/9046 could be related.
Any help very much appreciated :)
I was able to solve this myself. Following the reference structure for spartacus applications at https://sap.github.io/spartacus-docs/reference-app-structure/ the problem disappeared.
My best guess is that it has to do with the import order of the modules. In my current working version I import the FeaturesModule last, which seems to solve the problem.

dojo1.10 Dynamic update of dijit.form.Select

I was trying to asynchronously update a Select field via Memory and ObjectStore. This doesn't work. Setting the data to the Memory object before creating the Select element works fine. Updating the Memory object after creating the Select element doesn't work anymore.
Example code:
require([
"dojo/ready",
"dijit/form/Select",
"dojo/store/Memory",
"dojo/store/Observable",
"dojo/data/ObjectStore",
'dojo/domReady!'
], function(ready, Select, Memory, Observable, ObjectStore, dom){
ready(function() {
var mymem = new Memory();
var myobs = new Observable(mymem);
var mystore = new ObjectStore({ objectStore: myobs });
/* updating memory here works :) */
//mymem.setData([ { id: 2, label: 'qwertz2' }, { id: 3, label: 'qwertz3' } ]);
var s = new Select({
store: mystore
}, 'appsAdminQueueContainer');
s.startup();
/* updating memory here doesn't work :( */
mymem.setData([ { id: 2, label: 'qwertz2' }, { id: 3, label: 'qwertz3' } ]);
});
}
);
Real working example: https://jsfiddle.net/mirQ/ra0dqb63/5/
Is this a bug or is there a solution to be able to update the content of the Select field after creating it - without having to access the Select field directly?
UPDATE
Thank you for your response.
The use of dojo/ready was just a missed leftover while simplifying my code, sorry.
That the use of the ObjectStore is not necessary was not clear to me. Thanks for clearing up.
Okay, the real problem seems to be indeed the last point. I think I have to extend my description.
Updated/extended problem description:
I'm using a grid. At first I was using dojox/grid/DataGrid, but then I switched to dgrid. Everything works well, but I want to use dijit.form.Select as editor for one column. This works also well if the data is static. But in one column I have to read dynamic data from the server. This data comes in JSON format.
First I tried to solve this with the use of dojo/data/ItemFileReadStore - that worked. But it's deprecated and I need to implement a formatter for that column that has to have access to the same JSON data read from the server. I don't have the code for that solution anymore, but it didn't work. I wasn't able to successfully query the data from within the formatter function.
Then I switched to Memory and xhr. The response from the server comes after the Memory object is created (and, as it seems, after creating the Select), so I had to use setData to bring my loaded data in the store. And because the Select is only an editor of a grid, I don't have access to the object itself to be able to re-set the store after updating the data.
I hope my extended description makes my real problem a bit clearer. Thanks in advance for your help!
Mirko
This works for me:
require([
'dijit/form/Select',
'dojo/store/Memory',
'dojo/store/Observable',
], function (Select, Memory, Observable) {
var mymem = new Memory({
data: [{
id: 2,
label: 'qwertz2'
}, {
id: 3,
label: 'qwertz3'
}]
});
var myobs = new Observable(mymem);
var s = new Select({
labelAttr: 'label',
store: myobs
}, 'appsAdminQueueContainer');
s.startup();
myobs.add({ id: 4, label: 'qwerty' });
});
Notable changes:
There's no reason to use dojo/ready in this code. The require callback already waits for modules to load, and as long as this script is at the bottom of the body, there's no need to wait for the DOM to load, either.
There's no need to use a dojo/data store adapter. dijit/form/Select supports dojo/store as well (as of 1.8 if I recall correctly). This might also have been why observation wasn't working. The only difference is labelAttr must be specified on the Select since dojo/store has no concept of a label property.
(Edit) now that I re-read the question, I notice you are calling setData. setData does not fire observers. setData completely resets the store's data, and to reflect that, you would need to actually reset the store on the select entirely (which requires calling setStore, not set('store', ...), if you are using 1.9 or earlier, because Select was never updated properly to support the set API until 1.10).
(Edit #2) Given that the primary reason you are calling setData is due to creating the store before actually having data for it, your case would probably be greatly simplified by using the RequestMemory store implementation from dojo-smore. It basically re-adds the url support that dojo/data/ItemFileReadStore had but dojo/store/Memory didn't.

Ext JS 4: Getters and setters in view

I've been thinking about this problem for a while, and I can't seem to come up with a reasonable solution. What I would like to do is create getters/setters for a textfield/its value in my view. I realize that the preferred Ext JS way is using a reference within the controller and getting it that way, but that doesn't feel very object-oriented to me. I'd also have to wrap these getters and setters because I want to output a message if the getter returns undefined. What I'd like to do is create my own getters/setters or somehow override the default getters/setters. Here are some ways I was thinking of accomplishing this.
I was thinking I could use the config {}, but that appears to only work for variables I want to define. I then was thinking of using an id somehow, but the community seems split on whether that's a good practice or not. Which leads to my current solution... wrapping. Here's my code:
LoginWindow
Ext.define('MyApp.view.LoginWindow', {
extend: 'Ext.window.Window',
alias: 'widget.loginWindow',
autoShow: true,
closable: false,
border: 0,
plain: true,
allowBlank: false,
title: "Enter your username",
modal: true,
config: {
buttons: [{
text: "Ok"
}],
items: [{
xtype: 'textfield',
fieldLabel: 'Username',
id: 'loginUserInput',
name: 'loginUserInput',
msgTarget: 'under',
validator: function(value) {
if (Ext.isEmpty(value)) {
return "You need to enter a username.";
}
return true;
}
}]
},
constructor: function(config) {
this.callParent(config);
},
getButton: function() {
console.log('here');
}
});
MyController
Ext.define('MyApp.controller.Chat', {
extend: 'Ext.app.Controller',
requires: [
'Views.ChatModule.view.LoginWindow'
],
refs: [{
ref: 'loginWindow',
selector: 'loginWindow',
xtype: 'loginWindow',
autoCreate: true
}, {
ref: 'loginUserInput',
selector: '#loginUserInput'
}],
init: function() {
// The events controller oversees
this.control({
'loginWindow button[text="Ok"]': {
'click': this.onSubmitLoginWindow
}
});
},
getLoginUserInputValue: function() {
var loginUserInput = this.getLoginUserInput();
if (loginUserInput) {
var username = loginUserInput.getValue();
if (username) {
console.log(username);
} else {
console.warn("username is undefined");
}
}
console.warn("loginUserInput is undefined");
},
onSubmitLoginWindow: function(button, event, eOpts) {
this.getLoginUserInputValue();
}
});
This works, and I realize it's a very nit-picky thing, but it just doesn't feel right to have the getter in the controller. I feel like it'd be more object-oriented if it was in the Window. However, if I put it in the Window, I believe my only option is to lean on ids or manually create the textfield in the Window's initComponent--which would involve saving off a reference of the textfield in there, but that seems a bit inefficient... as I would have to make a call to doLayout as well.
Just to reiterate, I'd love to have the getters/setters in the Window, and I'm looking for a quick way to reference it, similar to how the controller references objects. I believe the main answer will be to use ids and making a call to Ext.ComponentQuery.query('#loginUserInput') in the Window, but I'd like to know if there were any better approaches out there... like overriding the auto generated getters/setters or adding a simple getter/setter for an input's value.
Cross-post from the Sencha forums.
Edit
I guess I was a bit unclear with what I want. As a more general statement, instead of jamming all things related to my view in the controller, I'd like to store it all in the view itself, which includes things like getters/setters. One of these getters/setters just so happens to be the loginUserInput getter.
Using a model is an interesting idea, but I feel like that would be a whole lot of overhead for singleton values. I'm basically looking for something like Java's setters/getters in the LoginWindow view... and hopefully something as simple as (or close to) Java's.
The idea of including (encapsulating) it in the view makes the controller a bit cleaner, and if I delete the view, I'm deleting its functions as well, so I don't have to go hunting for the functions in the controller... all I have to worry about is removing the references (which should be minimal).
I think that the "OO" way that you're looking is to work with a Ext.data.Model for your form. If you look at the Ext.form.Basic you have methods to manipulate a model (called record) and also get the object with the values of your view. So you need:
When you create your form, use loadRecord() to bind your form to a Model.
At any time you need, use getValues() to retrieve the values of your form fields.
When submiting your form, use getRecord() and getValues() to sync your record.
Ext.define('MyApp.model.Login',{
fields : [{
name: 'username',
type: 'string'
},{
name: 'password',
type: 'string'
}]
});
Ext.define('MyApp.controller.Login',{
...
refs : [{
selector: 'window form',
ref: 'formPanel'
}],
...
openForm : function() {
//load your form and then bind the new record
var formPanel = this.getFormPanel(), //Ext.form.Panel
form = formPanel.getForm(); //Ext.form.Basic
form.loadRecord(Ext.create('MyApp.model.Login'));
},
save : function() {
//get the values in the view
var form = this.getFormPanel().getForm(),
vals = form.getValues(),
record = form.getRecord();
console.log(vals); //see the object representation of your view here
record.set(vals); //update your model
//do whatever you need with your model
}
...
});
This is an good example when you need to save the form data. In the login I think you can work directly with getValues() without binding it to a Ext.data.Model.
I am not quite certain what problem you are trying to solve to be honest with you.
If you do not like controllers listening to buttons within your window, you can have button handlers witin your view definition fire custom events that controllers can listen on. Use fireEvent method. And by the way initConfig is a recommended way to setup your views. You can break it up into methods if you wish, 'this' reference is available and is the View component being instantiated.
If you need to find inner components within the View there are many methods available from up /down to nextSibling and query .
For Components:
• Ext.getCmp(id)
• Ext.ComponentQuery.query()
• up()
• down()
• nextSibling()
• previousSibling()
• child()
• previousNode()
plus various find.. Methods
EDIT
I think I understood what you mean by getter and setters. Ext forms have the fields finders to make it easy to get and set data to individual fields. See these SO questions: Best way to access adjacent components / fields and EXT.JS getting a list of items from a fieldset
Also like Sergio said there is getRecord getValues and setRecord methods on the form to deal with data binding. Thats it.
EDIT2
The best starting point guide that shows clear and claen MVC patterns as well as form handling. http://docs.sencha.com/extjs/4.1.3/#!/guide/application_architecture
My thoughts are something like this:
...
items: [],
constructor: function(config) {
this.loginUserInput = Ext.create('Ext.form.field.Text', {
fieldLabel: 'Username',
id: 'loginUserInput',
name: 'loginUserInput',
msgTarget: 'under',
validator: function(value) {
if (Ext.isEmpty(value)) {
return "You need to enter a username.";
}
return true;
}
});
this.items.push(this.loginUserInput);
this.callParent(config);
},
getLoginUserInput: function() {
var loginUserInput = this.loginUserInput;
if (!loginUserInput) {
console.warn("LoginWindow::getLoginUserInput: loginUserInput is undefined");
}
return loginUserInput;
}
So instead of letting Ext do its magic, I am now instantiating the object on my own, which then allows me to store away a reference of it, so I can easily access it in my getter. I just wonder if this is creating any sort of performance hit. It doesn't seem like it'd be that much worse... it actually seems like it'd be a bit better because I'm not referencing this object by its ID, and I don't have to go searching for it when I need it.

How to use localstorage in Titanium/Alloy?

I am very new to Appcelerator/Titanium. Can anyone tell me how to use localstorage function in Alloy (Titanium). (Not getting any good solution on Web).
Thanks! :)
Titanium Alloy has a customized implemntación Backbone. This means that titanium uses Backbone for many things, but at the same time some important features have been left out.
One of the most used parts of Backbone by Titanium are models, while that not identical to those ofrese the js framework, they have lots of things in common.
To work with data models must define an adapter (this can be localStorage, sql, properties or custom sync adapters)
If you want to work with localStorage, your model should look something like this:
exports.definition = {
config: {
"defaults": {
"first_name": "",
"last_name": "",
"phone": "",
"email": ""
},
"adapter": {
"type": 'localStorage',
"collection_name": 'user'
}
},
extendModel: function(Model) {
_.extend(Model.prototype, {
}); // end extend
return Model;
},
extendCollection: function(Collection) {
_.extend(Collection.prototype, {
}); // end extend
return Collection;
}
};
to manipulate the data then you should use:
Create data
model = Alloy.createModel('user', {first_name: 'Pedro', last_name: Picapiedra});
// or model.save();
Alloy.Collections.user.add(model);
Read data
callection = Alloy.Collections.user.fetch()
model = Alloy.Collections.user.get(modelId)
Update data
user.set({
first_name : 'Pablo',
last_name : 'Marmol',
});
user.save();
Delete data
model.destroy();
collection.remove(model);
For more information:
Titanium Sync & apadters
Backbone Sync, collections, models & etc
See https://wiki.appcelerator.org/display/guides/Working+with+Local+Data+Sources for general guide.
Accessing files is done through the Ti.Filesystem. See documentation at http://docs.appcelerator.com/titanium/latest/#!/api/Titanium.Filesystem . You should also see the kitchen sink example, as it shows hot to read/write file https://github.com/appcelerator/KitchenSink/blob/master/Resources/ui/common/platform/filesystem.js.
If you simply want to store some data locally, many people use sqlite database. See http://docs.appcelerator.com/titanium/latest/#!/api/Titanium.Database .
The simplest way is to use properties. It is limited, but for many people that is enough. http://docs.appcelerator.com/titanium/latest/#!/api/Titanium.App.Properties

WCF table data to ExtJS Store

I've set up a WCF service to provide table data in JSON:
{
"d":{
"__type":"ExtJsDataResults:#MyProject.WebServices",
"rows":[
["TitleA","1.98","English"],
["TitleB","1.98","Spanish"],
["TitleC","1.98","Korean"]
],
"totalcount":10
}
}
How to read this into an ExtJS Store? I need a JsonStore to begin with, but then an ArrayReader-type type interpret the row data. Something like this:
var itemStore = new Ext.data.JsonStore({
proxy: new Ext.data.HttpProxy({
url: "../WebServices/ItemsService.svc/getData",
method: "GET"
}),
root: "d.rows",
totalProperty: "d.totalcount",
fields: ['Book Title', 'Unit Price', 'Language'],
reader: new Ext.data.ArrayReader({},
Ext.data.Record.create([
{name:'Book Title'},
{name:'Unit Price'},
{name:'Language'}
])
)
});
Of course, this doesn't work. When bound to a DataGrid w/ a paging toolbar, it displays blank rows, but the correct number of them, and the paging toolbar values are all correct.
Any ideas?
FIXED. Changed to a regular Store and added the "root" and "totalProperty" values to the config object of the ArrayReader.
cf. this Sencha Forum thread
I was also facing this problem but got through with this solution -
http://dotnetkeeda.blogspot.in/2013/11/working-with-sencha-extjs-and-wcf.html
Nice and important tips thanks to the author.