I'm a self studying student who is brand new to to both javascript and ExtJs.
What I am trying to do is create a main class animal, then have two sub classes inherit properties/methods from animal. Those are cat and dog... then I would like to make multiple objects (multiple cat's and dog's) inherit from the class cat and dog. The multiple objects, cat1, mycat, cat2, dog1, mydog, topdog etc... were supposed to be able to talk and listen to each other.
when the cat1, mycat etc talk, it's just alert (stuff), and when that happens the dog alerts "mydog heard that".
I did this in javascript already with help from the community (How to create listeners with javascript)
I want to do this now using ExtJS too. This is what I have so far
Ext.define('animal', {
extend: 'Ext.util.Observable',
constructor: function(config)
{
this.name = config.name;
this.addEvents({'makesound' :true});
this.listeners = config.listeners;
//animal.superclass.constructor.call(this, config);
this.callParent(arguments);
},
makesound: function()
{
alert(this.sound);
this.fireEvent('makesound'); // this is wrong isn't it?
// I was thinking that the make sound
// would trigger the event makesound
}
});
//var cat = Ext.create('animal');
//var dog = new animal;
//cat.makesound();
//dog.makesound();
Ext.define('cat', {
extend: 'animal',
name: name,
sound: 'meow'
});
Ext.define('dog', {
extend: 'animal',
name: name,
sound: 'woof'
});
var myCat = new cat({
name: 'myCat'});
var myDog = new dog({
name:'mydog',
listeners: {
makesound: function() {
alert('mydog heard that');
}
}
});
myCat.makesound();
myDog.fireEvent('makesound'); //
What I really want is for mycat to make sound, then mydog automatically hear that makesound (the 'meow') and alert my dog heard that. In extJS though. Any help or advice would be very much appreciated.
You seem to have your class definitions set up correctly, you just need to add listeners to the correct instances:
var myCat = new Cat({ name: 'myCat' });
var myDog = new Dog({ name: 'myDog' });
myCat.on('makesound', function() {
myDog.makesound();
});
The way you have it currently set up, your listener is on myDog, meaning that your "mydog heard that" will show up when myDog makes a sound, not myCat.
One other note: you should always name your classes with a capital letter (use Animal instead of animal).
Related
I'm working on a simple timer app. I'm getting 3 pieces of data from the server: timers, projects and users. I believe I'm looping through timers correctly, but I'm not sure if I should be passing in data this way. I want different parts of my app to use the same dataset for users and projects in case a project name changes for example. Here's the code so far with questions embedded. I would like to do a single call for now for all the data at once.
<script>
Vue.component('sidebar-timer', {
props: ['timer','projects','users'],
computed: {
/***** SHOULD PROJECT AND USER BE SET A DIFFERENT WAY? *****/
project: function () {
return this.projects[this.timer.project_id.toString()];
},
user: function () {
return this.users[this.timer.user_id.toString()];
}
},
template: '<li class="project-item"><div class="timer-proj-name"> #{{ project.name }}</div><div class="timer-name"> #{{ user.name }}</div> <button class="timer-start-btn">Start</button><div class="timer-duration">#{{ timer.duration }}</div><div class="timer-status">#{{ timer.status }}</div><div id="toggle-timer-notes"><div class="timer-task"> #{{ timer.notes }}</div><div>timer id: #{{ timer.id }}<input :value="timer.id"></li></div>',
})
var TimerSidebar = Vue.extend({
methods: {
updateData: function () { // GET DATA FROM THE SERVER
var self = this;
$.get('/timers/getJson', function(response){
var userObj = response.users;
var projectObj = response.projects;
var timerObj = response.timers;
var timerArr = Object.keys(timerObj).map(function (key) {return timerObj[key]; });
/***** IS THERE A WAY TO SET projects AND users AT A LEVEL OUTSIDE OF TimerSidebar? *****/
self.$set(self, 'users', userObj);
self.$set(self, 'projects', projectObj);
self.$set(self, 'timers', timerArr);
})
}
}
})
var timerSidebar = new TimerSidebar({
el: '#timer-sidebar',
data: {
timers: [],
projects: [],
users: []
},
})
methods: {
/***** HOW TO ONCLICK CALL updateTimers FROM OUTSIDE THE COMPONENT? *****/
updateTimers: function(){ // ADD TIME RECORD FROM CLICK EVENT
var newTimers = this.timers;
newTimers.push({id: 166, project_id: 123, user_id: 1});
newTimers.sort(function(timer1, timer2){
if(timer1.id > timer2.id){
return 1;
} else if(timer1.id < timer2.id){
return -1;
} else {
return 0;
}
});
this.timers = newTimers;
}
}
This is the standard case when you should be going for a centralised state management. As you have data which is going to be used by multiple components, If the data flow is just limited to one way: parent to child, it can be manageable, but as soon as you get the requirement of updating the parent data when child changes it, or worse, updating the sibling data when another sibling changes it, it becomes messy.
Vue provides it own Flux like implementation, but the general practice is to go with vuex. With this, you store all your projects/users etc in vuex state, and each component can read/update from the central state. If its changed by one component, updated version is available to all components reactively. You can initiate the data in one place using actions in vuex itself.
Following is the architecture diagram:
You can have a look at my answer here on similar question and have a look at example on how to call api and save data in store.
I have such a simple scenario,
App starts from Main View (/main), then click top right button to Sub View (/sub).
During app launching app.run(), user's profile will be loaded into a service userService, once if user went to Sub View, this profile will be read from that service userService then display, here is the code,
app.run(function($http, $rootScope, userService){
$http.get('/profile').then(function(result){
userService.setProfile(result.data.profile);
});
});
app.service('userService', function(){
var user = {}
this.setProfile(profile){
user.profile = profile;
};
this.getProfile(){
return user.profile;
}
});
In Sub View, getProfile() was invoked to display the info.
It works if user start from Main View -> button -> Sub View, however, if user manually refreshed Sub View or just start from Sub View, getProfile() will get nothing to display,I know that's because before the promise of getting profile returned, Sub View had been proceed.
I don't like to read profile from Sub View directly and dynamically because I have other pages need profile info as well, so is there any workaround or better design? thanks.
Instead of using app.run you should probably utilize your route provider for this. Whether you use ngRoute or ui-router they both have resolve functionality. Instead of getting your profile in app.run you should probably move that to userService as well.
app.service('userService', function(){
var self = this;
self.user = {};
self.getProfile = function() {
return self.user.profile;
};
self.init = function() {
return $http.get('/profile').then(function(result){
self.user.profile = result.data.profile;
});
};
});
Now that your service is more factory like, you can utilize the initialization of it in the route provider. I use ui-router but this can easily be applied to ngRoute as well.
I start by creating an abstract state that handles the resolve which I can than 'import' in whichever other states I need.
.state('init', {
abstract: true,
resolve: ['userService', function(userService) {
return userService.init();
}]
});
Now I just use it in other states and I can assure that the userService is initialized.
.state('subView', {
parent: 'init',
url: '/subView'
//other state stuff like template, controller, etc
});
The way I've worked around that is add the relevant data to $window.sessionStorage, roughly like this (you'll need to make $window available):
app.service('userService', function(){
var user = {}
if ($window.sessionStorage.profile)
this.user.profile = JSON.parse($window.sessionStorage.profile)
this.setProfile(profile){
user.profile = profile;
this.$window.sessionStorage.profile = JSON.stringify(profile)
};
this.getProfile(){
return user.profile;
}
});
Reading up on tutorials of Backbone, it seems that when the add event is fired from a collection, the item added is sent along with the event (same goes for remove). I can't find any documentation on this feature on the backbonejs.org site and was curious if there was a way I could send an object along with my custom events. Secondly, is something like this possible in Marionette?
Each object defined by Backbone mixes in Backbone.Events which means you can trigger events with object.trigger. It is defined as
trigger object.trigger(event, [*args])
Trigger callbacks for the given event, or space-delimited list of events. Subsequent arguments
to trigger will be passed along to the event callbacks.
You just have to pass additional arguments to get them in your callbacks.
For example,
var m = new Backbone.Model();
m.on('custom', function(more) {
console.log(more);
});
m.trigger('custom', 'more info');
will log more info
See http://jsfiddle.net/nikoshr/HpwXe/ for a demo
You would trigger an event with a reference to the object to emulate the behavior of backbone :
var m = new Backbone.Model();
m.on('custom', function(model, more) {
console.log(arguments);
});
m.trigger('custom', m, 'more info');
http://jsfiddle.net/nikoshr/HpwXe/1/
And in a derived model:
var M = Backbone.Model.extend({
custom: function() {
this.trigger('custom', this);
}
});
var m = new M();
m.on('custom', function(model, more) {
console.log(model);
});
m.custom();
http://jsfiddle.net/nikoshr/HpwXe/2/
Yes of course, you can use Backbone.Event
var collection = Backbone.Collection.extend();
collection = new collection();
collection.on("message", function(message){
console.log(message);
});
var model = new Backbone.Model();
collection.add(model);
model.trigger("message", "This is message");
About what types of events you can see to backbone documentation.
This is demo
Also you can use Event Aggregator from Marionette.js
An event aggregator implementation. It extends from Backbone.Events to provide the core event handling code in an object that can itself be extended and instantiated as needed.
var vent = new Backbone.Wreqr.EventAggregator();
vent.on("foo", function(){
console.log("foo event");
});
vent.trigger("foo");
I'm trying to learn Backbone by looking at an app that someone I know made together with the backbone documentation. The app has a Bucket model and a Company model (i.e. you put companies in the bucket). There's one thing in this bit that I'm unclear about, namely, how it uses the trigger method.
Backbone documentation has this to say about trigger:
trigger object.trigger(event, [*args])
Trigger callbacks for the given event, or space-delimited list of events. Subsequent arguments to trigger will be passed along to the event callbacks.
In the code I'm looking at, trigger is called like this:
this.trigger("add:companies", Companies.get(companyId));
Two questions:
The event I assume is adding a company, but at what point in the code below does that actually happen? Is it when this.set({ "companies": arr }, { silent: true }); is run or when this.save(); is run (or something else)?
If Companies.get(companyId) is the optional argument, what function is it actually passed to?
Excerpt from original code
window.Bucket = Backbone.Model.extend({
defaults: function() {
return {
companies: []
};
},
addCompany: function(companyId) {
var arr = this.get("companies");
arr.push(companyId);
this.set({ "companies": arr }, { silent: true });
this.save();
this.trigger("add:companies", Companies.get(companyId));
},
// ...
The companies property of the bucket is being updated in the addCompany method you describe. An annotated version of your example shows what's taking place:
// 1. get array of companies currently included in the bucket:
var arr = this.get("companies");
// 2. add a company to the array
arr.push(companyId);
// 3. replace the bucket's company list with the array,
// suppressing validation and events:
this.set({"companies": arr}, {silent: true});
// 4. save the bucket:
this.save();
trigger isn't actually affecting the model--it's just a way of letting other pieces of the application know that the company has been added. You could turn around and catch it somewhere else using on with the bucket model:
var bucket = new window.Bucket();
// do stuff
bucket.on('add:companies', function(company) {
alert('a new company has been added to the bucket.');
});
I'm facing a "change event not firing" issue on Backbone.js =/
Here my view of User model :
window.UserView = Backbone.View.extend({
...
initialize: function()
{
this.model.on('destroy', this.remove, this);
this.model.on('change', function()
{
console.log('foo');
});
},
render: function(selected)
{
var view = this.template(this.model.toJSON());
$(this.el).html(view);
return this;
},
transfer: function(e)
{
var cas = listofcas;
var transferTo = Users.getByCid('c1');
var transferToCas = transferTo.get('cas');
this.model.set('cas', cas);
console.log('current model');
console.log(this.model);
//this.model.change();
this.model.trigger("change:cas");
console.log('trigger change');
transferTo.set('cas', transferToCas);
console.log('transferto model');
console.log(transferTo);
//transferTo.change();
transferTo.trigger("change:cas");
console.log('trigger change');
}
});
Here, the User model :
window.User = Backbone.Model.extend({
urlRoot: $('#pilote-manager-app').attr('data-src'),
initialize: function()
{
this.set('rand', 1);
this.set('specialite', this.get('sfGuardUser').specialite);
this.set('name', this.get('sfGuardUser').first_name + ' ' + this.get('sfGuardUser').last_name);
this.set('userid', this.get('sfGuardUser').id);
this.set('avatarsrc', this.get('sfGuardUser').avatarsrc);
this.set('cas', new Array());
if (undefined != this.get('sfGuardUser').SignalisationBouclePorteur) {
var cas = new Array();
_.each(this.get('sfGuardUser').SignalisationBouclePorteur, function(value)
{
cas.push(value.Signalisation);
});
this.set('cas', cas);
}
}
});
In User model, there is "cas" attribute, which is an array of objects.
I read in others topics that change events are not fire on model.set if attributes are not a value.
So, I try to trigger directly the change event with model.change() method.
But, I have no "foo" log in my console ...
I'm pretty new to backbone and I was having this same problem.
After doing some research, I found a few posts that shed a little bit more light on why this was happening, and eventually things started to make sense:
Question 1
Question 2
The core reason has to do with the notion of reference equality versus set/member equality. It appears that to a large extent, reference equality is one of the primary techniques backbone uses to figure out when an attribute has changed.
I find that if I use techniques that generate a new reference like Array.slice() or _.clone(), the change event is recognized.
So for example, the following code does not trigger the event because I'm altering the same array reference:
this.collection.each(function (caseFileModel) {
var labelArray = caseFileModel.get("labels");
labelArray.push({ Key: 1, DisplayValue: messageData });
caseFileModel.set({ "labels": labelArray });
});
While this code does trigger the event:
this.collection.each(function (caseFileModel) {
var labelArray = _.clone(caseFileModel.get("labels")); // The clone() call ensures we get a new array reference - a requirement for the change event
labelArray.push({ Key: 1, DisplayValue: messageData });
caseFileModel.set({ "labels": labelArray });
});
NOTE: According to the Underscore API, _.clone() copies certain nested items by reference. The root/parent object is cloned though, so it will work fine for backbone. That is, if your array is very simple and does not have nested structures e.g. [1, 2, 3].
While my improved code above triggered the change event, the following did not because my array contained nested objects:
var labelArray = _.clone(this.model.get("labels"));
_.each(labelArray, function (label) {
label.isSelected = (_.isEqual(label, selectedLabel));
});
this.model.set({ "labels": labelArray });
Now why does this matter? After debugging very carefully, I noticed that in my iterator I was referencing the same object reference backbone was storing. In other words, I had inadvertently reached into the innards of my model and flipped a bit. When I called setLabels(), backbone correctly recognized that nothing changed because it already knew I flipped that bit.
After looking around some more, people seem to generally say that deep copy operations in javascript are a real pain - nothing built-in to do it. So I did this, which worked fine for me - general applicability may vary:
var labelArray = JSON.parse(JSON.stringify(this.model.get("labels")));
_.each(labelArray, function (label) {
label.isSelected = (_.isEqual(label, selectedLabel));
});
this.model.set({ "labels": labelArray });
Interesting. I would have thought that .set({cas:someArray}) would have fired off a change event. Like you said, it doesn't seem to, and I can't get it to fire with .change() BUT, I can get the events to work if I just do model.trigger('change') or model.trigger('change:attribute')
This would allow you to trigger the change event without that random attribute hack.
If someone could explain what is going on with events, Backbone, and this code, that would help me learn something too... Here is some code.
Ship = Backbone.Model.extend({
defaults: {
name:'titanic',
cas: new Array()
},
initialize: function() {
this.on('change:cas', this.notify, this);
this.on('change', this.notifyGeneral, this);
},
notify: function() {
console.log('cas changed');
},
notifyGeneral: function() {
console.log('general change');
}
});
myShip = new Ship();
myShip.set('cas',new Array());
// No event fired off
myShip.set({cas: [1,2,3]}); // <- Why? Compared to next "Why?", why does this work?
// cas changed
// general change
myArray = new Array();
myArray.push(4,5,6);
myShip.set({cas:myArray}); // <- Why?
// No event fired off
myShip.toJSON();
// Array[3] is definitely there
myShip.change();
// No event fired off
The interesting part that might help you:
myShip.trigger('change');
// general change
myShip.trigger('change:cas');
// cas changed
I find this interesting and I hope this answer will also spawn some insightful explanation in comments which I don't have.