I'm having an issue which makes me think I might be misinterpreting the use of the ExtJS class architecture a bit. Herewith an example of a simplified version which demonstrates the issue
Ext.define('Person', {
name : 'Default',
inventory : [],
addInventoryItem : function(item) {
Ext.Array.push(this.inventory, item);
},
inventoryList : function() {
console.log("Inventory: " + JSON.stringify(this.inventory));
},
setName : function(name) {
this.name = "name";
}
});
Ext.define('Student', {
extend : 'Person'
});
alex = Ext.create('Student');
alex.setName("Alex");
alex.addInventoryItem("Knifes");
alex.inventoryList();
david = Ext.create('Student');
david.setName("David");
david.addInventoryItem("Forks");
david.inventoryList();
Output:
Inventory: ["Knifes"]
Inventory: ["Knifes","Forks"]
Expected Output:
Inventory: ["Knifes"]
Inventory: ["Forks"]
I would've expected the actual result in the event of overriding the class, but fail to understand why my second instance affects the superclass, and in turn reflects changes made by my first instance.
The problem is that there is a single inventory array shared between all instances of your class. The array is created once at the time the class is defined, then each instance just gets a reference to that one array.
Basically you need to give each Person instance its own inventory array on creation, like this:
Ext.define('Person', {
...
constructor: function() {
this.inventory = [];
},
...
});
Related
I've been scratching my head on this one for hours worth of troubleshooting and I can't seem to figure it out so was wondering if any of you could help.
I have an array of objects in a json file, and I'm making a filtering menu based on different properties in the file that one can check/uncheck in view to filter the results. The issue I have is to be able to uncheck any items in the menu that hide as a result of not being available in the current results being displayed.
I have a plunker example here: https://plnkr.co/edit/KZmMiSisA1gKyahG5rHF
Sample from plunker:
$scope.list = [
{ parent : 'fruit', type : 'orange' },
{ parent: 'fruit', type : 'apple' },
{ parent : 'fruit', type : 'kiwi' },
{ parent : 'vegetable', type : 'kale' },
{ parent : 'vegetable', type : 'cabbage' }
];
$scope.filtered = $scope.list;
$scope.selectedType = [];
$scope.selectedParent = [];
$scope.$watch(function () {
return {
selectedType: $scope.selectedType,
selectedParent: $scope.selectedParent,
}
}, function (value) {
var filterType = {
parent : $scope.selectedParent,
type : $scope.selectedType,
};
var startFilter = $scope.list;
for (var i in filterType) {
startFilter = filter(startFilter, filterType[i], i);
}
$scope.filtered = startFilter;
}, true);
Basically, if someone selects "fruit" and then "orange", but then unchecks "fruit", I would want "orange" to uncheck as well.
I just checked your plunker. The code on the bottom is very complicated, but I might be able to help you with these snippets.
Add a ng-change attribute to the parents:
<input type="checkbox"
checklist-model="selectedParent"
checklist-value="key"
data="{{::key}}"
ng-change="checkParent(key, checked)"/>
Now you can detect the changes in your controller:
$scope.checkParent = function(parent, checked) {
if (!checked) {
$scope.list.filter(function(fruit) {
return fruit.parent === parent;
}).forEach(function(fruit) {
$scope.selectedType = $scope.selectedType.filter(function(_selectedType) {
return _selectedType != fruit.type;
});
});
}
};
Plunkr
Beware, that this is inefficient, as it filters Selected type for every fruit to be unselected, it can be refactored with some nice functional tools.
But in general I'd change the controller if possible, and create a map with this structure:
{
parent: {
name: "fruit"
selected: false,
children: [{
type: "organge"
selected: false
}]
...
}
This way you can make your controller code much more readable.
Edit:
I was checking the two filter what you wrote. I couldn't come up with a better code as I still think that you should change the data structure. Iterating over and over lists is an expensive process, and both of your filters has two nested for loops. I cannot think of an easy way of getting rid of them with your data structure.
I spent some time on refactoring your code, getting rid of the watches and utilizing lodash. Check the updated Plunk, I hope it helps.
I added this function to your plunker:
$scope.uncheck = function(key){
$scope.selectedType.splice(key)
}
And this to the parent:
<input type="checkbox" checklist-model="selectedParent" checklist-value="key" data="{{::key}}" ng-change="uncheck(key)" />
It works for me if this is in fact what you are trying to accomplish.
I have a question about how to just get a certain element of an array using MongoDB and MeteorJS. I have the following schema for the user document:
bankList:[
{
id: "34567890987654345678",
name: "xfgchjbkn",
type: "credit"
},
{
id: "09876543456789098767"
name: "65789876t8",
type: "debit"
}
]
I first subscribe to only part of the fields in the array, specifically I gather a list of all the ids. Then I have an edit screen that should subscribe to all of the fields for a specific element in the array with a matching id. I do not want to expose the rest of the array just the single element. Currently, I use the following to first gather a list of just the ids:
Meteor.users.find({_id: this.userId},
{fields:{'bankList.id': 1}});
And the following publication-subscription method to get just a specific element's information:
Publication:
Meteor.publish("userBankAdvanced", function(bankId){
check(bankId,String);
if(this.userId){
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {'bankList.$': 1});
}else{
this.ready();
}
});
Subscription:
this.route('edit_account', {
path: '/edit/account/',
waitOn: function(){
if(Session.get("bankId")){
return Meteor.subscribe('userBankAdvanced',Session.get("bankId"));
}
return null;
},
data: function(){
if(Session.get("bankId")){
return Meteor.users.findOne();
}
return null;
},
onBeforeAction: function(){
beforeHooks.isRevise(Session.get("bankId"));
}
});
The subscription method returns all of the elements of the array with all of the information.
I want, for example, just this (not the entire list with all of the information):
bankList:[
{
id: "34567890987654345678",
name: "xfgchjbkn",
type: "credit"
}]
It looks like you're just missing the "fields" specifier in your "userBankAdvanced" publish function. I wrote a test in meteorpad using your example and it seems to work fine. The bank id is hardcoded for simplicity there.
So instead of
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {'bankList.$': 1});
try using
return Meteor.users.find({_id:this.userId,"bankList.id": bankId}, {fields: {'bankList.$': 1}});
No luck, in meteor the "fields" option works only one level deep. In other words there's no builtin way to include/exclude subdocument fields.
But not all is lost. You can always do it manually
Meteor.publish("userBankAdvanced", function (bankId) {
var self = this;
var handle = Meteor.users.find({
_id: self.userId, "bankList.id": bankId
}).observeChanges({
added: function (id, fields) {
self.added("users", id, filter(fields, bankId));
},
changed: function (id, fields) {
self.changed("users", id, filter(fields, bankId));
},
removed: function (id) {
self.removed("users", id);
},
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
function filter(fields, bankId) {
if (_.has(fields, 'bankList') {
fields.bankList = _.filter(fields.bankList, function (bank) {
return bank.id === bankId;
});
}
return fields;
}
EDIT I updated the above code to match the question requirements. It turns out though that the Carlos answer is correct as well and it's of course much more simple, so I recommend using that one.
What is the idiomatic way to create a new Instance of an associated Model inside a Controller?
Motivation:
If I change the Class of my Model the code below doesn't get updated and I have to find all the breakages and fix them manually.
If I could use the the association in the controller, it gets updated automatically, but I can't find any documentation on the magical incantation to create a new instance from the controller reference.
Here is the relevant parts of my controller:
Ext.define('AdminApp.controller.SelectFilesController', {
extend: 'Ext.app.Controller',
models: [
'File'
],
// lots of stuff snipped for brevity
onFilefieldChange: function(filefield, value, eOpts) {
Ext.each(Ext.getDom('select-upload-button-fileInputEl').files, function(f) {
var fm = new AdminApp.model.File({
name: f.name,
size: f.size,
type: f.type,
md5: '',
status: 0
});
}
});
// lots more stuff snipped for brevity
}
What I want to know:
I found this in the documentation:
It’s important to note that the getters for both views and models
return a reference to the class (requiring you to instantiate your own
instances), while the getters for stores and controllers return actual
instances.
I can't find any examples of what the code to instantiate an instance should look like.
After much trial and error and just plain educated guessing ...
I figured out what the syntax should be.
First you need to create a reference to the class of the Model.
By convention this is the name of the model prefixed with get and suffixed with Model.
var fmc = this.getFileModel();
I had to do this outside the Ext.each() function that visits each item because the this reference pointed to an HTML5 File object instead of my Controller.
Then you simply use a regular Ext.create(fmc, { /* config */ } to get a new instance like so.
var fm = Ext.create(fmc, {
name: f.name,
size: f.size,
type: f.type,
md5: '',
status: 0
});
So the correct version of the onFilefieldChange function in the question should look like:
onFilefieldChange: function(filefield, value, eOpts) {
var fmc = this.getFileModel();
Ext.each(Ext.getDom('select-upload-button-fileInputEl').files, function(f) {
var fm = Ext.create(fmc,{
name: f.name,
size: f.size,
type: f.type,
md5: '',
status: 0
});
}
});
}
I'm using backbone.js and backbone relational 0.5.0 with a Rails 3.2 backend. I have a Card model which has_many Notes.
Here are my JS models and collections:
Workflow.Collections.Cards = Backbone.Collection.extend({
model: Workflow.Models.Card,
url: '/cards'
});
Workflow.Models.Card = Backbone.RelationalModel.extend({
modelName : 'card',
urlRoot : '/cards',
relations: [
{
type: Backbone.HasMany,
key: 'notes',
relatedModel: 'Workflow.Models.Note',
collectionType: 'Workflow.Collections.Notes',
includeInJSON: false,
reverseRelation: {
key: 'card',
includeInJSON: 'id'
}
}]
});
Workflow.Collections.Notes = Backbone.Collection.extend({
model: Workflow.Models.Note,
url: '/cards/74/notes' // intentionally hard-coded for now
});
Workflow.Models.Note = Backbone.RelationalModel.extend({
modelName : 'note',
urlRoot : '/notes'
});
Normal fetching works great, but when I try fetchRelated in the console, I get an empty array:
card = new Workflow.Models.Card({id: 74}) // cool
card.fetch() // hits the sever with GET "/cards/74" - works great
card.fetchRelated('notes') // [] - didn't even try to hit the server
What's weird is that this works:
card.get('notes').fetch() // cool - GET "/cards/74/notes"
I could use that method and parse the response text, but it feels really dirty.
Anyone know what I'm missing here?
Thanks in advance, this one is really torturing me!
Stu
You should create Card with Note ids array: card = new Workflow.Models.Card({id: 74, notes: [74, 75]}); and change the url method of Notes accordingly:
Workflow.Collections.Notes = Backbone.Collection.extend({
model: Workflow.Models.Note
});
Workflow.Models.Note = Backbone.RelationalModel.extend({
modelName : 'note',
urlRoot : function () {
return this.get('card').url() + '/notes';
}
});
card = new Workflow.Models.Card({id: 74, notes: [74, 75]});
card.fetchRelated('notes');
http://jsfiddle.net/theotheo/5DAzx/
I should have posted my solution a while back - there might well be a better way, but this is the convention I've gone with:
All of the following code is in the card view (which is where the notes are displayed).
First, I bind a renderNotes method to the 'reset' event on the card's notes collection:
initialize: function () {
_.bindAll(this);
this.model.get('notes').on('reset', this.renderNotes);
var self = this;
this.model.get('notes').on('add', function(addedNote, relatedCollection) {
self.renderNote(addedNote);
});
}
I also bind to the 'add' on that collection to call a singular renderNote.
The renderNotes and renderNote methods work like this:
renderNotes: function () {
if (this.model.get('notes')) {
this.model.get('notes').each(this.renderNote);
}
},
renderNote: function (note) {
var noteView = new Workflow.Views.Note({ model: note });
this.$('.notes').append(noteView.render().el);
},
Then, the last piece of the puzzle is to actually hit the server up for the card's notes (which will in turn fire the 'reset' event I bound to above). I do this in the card view's render method:
render: function () {
// render all of the eager-loaded things
this.model.get('notes').fetch();
return this;
},
As #user1248256 kindly helped me work out in the comments on my OP, the confusion was mainly in that I expected fetchRelated to pull down lazy-loaded records - that's actually not the case.
As a side-note, this view is actually a modal and be opened and closed (removed from the page). To prevent the zombie events problem described in this excellent post, I also manually unbind the events mentioned above.
Taking the following Model:
MyModel= Backbone.Model.extend({
defaults : {
myNestedModel:undefined,
},
initialize: function() {
this.set({myNestedModel: new MyNestedModel());
}
});
It has a single property named 'myNestedModel' which has the following definition:
MyNestedModel= Backbone.Model.extend({
defaults : {
myModel:undefined,
}
});
It too has a single Property name 'myModel'. Now if I create an instance of MyModel:
aModel = new MyModel();
The nested model will have been set in MyModel's initialize method. I then use JSON.stringify in a two step process:
// Use Backbone.js framework to get an object that we can use JSON.stringfy on
var modelAsJson = aModel.toJSON();
// Now actually do stringify
var modelAsJsonString = JSON.stringify(modelAsJson);
This works fine and I get the JSON representation of MyModel and it's property of MyNestedModel. The problem occurs when I use defaults, for example:
MyModel= Backbone.Model.extend({
defaults : {
new MyNestedModel(),
}
});
This causes a problem with JSON.stringify since it doesn't support circular references. I assume the circular reference is being created because all instances of MyModel share the same instance of MyNestedModel. Whereas the initialize method creates a new nested model for each instance.
Questions:
Is my understanding of defaults:{} being the 'cause' of the
problem correct?
From a question I posted recently I got the
impression I should be using defaults for all properties. If that is
the case, how should I be using defaults in the scenario presented
in this post/question?
Can someone clarify the use of defaults:{}
with regards to when the value applies, when it's overridden and
whether instances share the same default 'instances'?
Defaults is used only for attributes inside your model ( the data in the model ), and whenever you create your model it takes the values from defaults and sets the attributes. e.g.
User = Backbone.Model.extend({
defaults : {
rating : 0
}
})
User1 = new User({ name : 'jack', email : 'jack#gmail.com' });
User2 = new User({ name : 'john', email : 'john#gmail.com' });
User1.set({ rating : 2 });
Now your two models when called with toJSON will print
{
rating: 2,
name: 'jack',
email: 'jack#gmail.com'
}
{
rating: 0,
name: 'john',
email: 'john#gmail.com'
}
Since defaults is an object, every value you place there is evaluated immediately so :
defaults : {
rating : defaultRating()
}
will call defaultRating() - not everytime when you initialize the model, but immediately ( in the extend method )
You should use defaults for models where you need some data to exist on the creating of the model ( e.g. new myModel() )
In your example you have the following mistakes :
1.set a value without a property
defaults : {
PROPERTY : new Model()
}
2.you don't need such an option for your defaults - you should place there only attributes ( data ) for the model
Defaults applies always as long as it is not replaced by a new defaults in an extended model e.g.
var Model = Backbone.Model.extend({ defaults : { alpha : 'beta' } });
var myModel = Model.extend({ defaults : { beta : 'gama' } });
now your myModel when initialized will have
{ beta : 'gama' } // alpha : 'beta' will not be set as value, because it is replaced