We're working on a web apps. To keep components pre-configured, I did a lot Ext.extend work.
Here is the pre-definitions.
In the entrance, in index.js
Ext.setup({
onReady: function() {
var app = new App();
}
});
The main class, in App.js, App will new some card components, such as:
App = Ext.extend(Ext.Panel,{
initComponent: function(){
this.sub1 = new Sub_Class_1();
this.sub2 = new Sub_Class_2();
this.sub3 = new Sub_Class_3();
this.items = [this.sub1, this.sub2, this.sub3];
App.superclass.initComponent.call(this);
this.sub1.on('Event1', this.Event1_fn, this)
},
Event1_fn: function() {
//some codes
}
});
The problem comes, in the definition of Sub_Class_1, in Sub_Class_1.js:
var firefun = function() {
this.fireEvent('Event1');
};
Sub_Class_1 = Ext.extend(Ext.Panel,{
//some similar init configure jobs.
//add *firefun* into this class
})
The problem is, if I fire the event in firefun, there is no reaction from the program. Is there something wrong with the scope? Can the community help me with suggestions that I can use to fix it?
I solved it. This is indeed due to the scope problem.
To solve it, I pass the eventName, eventListener, scope to the function. Let it can fire within the correct scope. Boom, it works!
Any better solution?
Related
here is my code for three Snackbars using the Material Design Lite component library :
(function() {
'use strict';
window['counter'] = 0;
var snackbarContainer = document.querySelector('#sb-message-box');
var showToastButton = document.querySelector('.button1');
showToastButton.addEventListener('click', function() {
'use strict';
var data = {
message: 'Example Message #1',
timeout: 6000
};
snackbarContainer.MaterialSnackbar.showSnackbar(data);
});
}());
(function() {
'use strict';
window['counter'] = 0;
var snackbarContainer = document.querySelector('#sb-message-box');
var showToastButton = document.querySelector('.button2');
showToastButton.addEventListener('click', function() {
'use strict';
var data = {
message: 'Example Message #2',
timeout: 6000
};
snackbarContainer.MaterialSnackbar.showSnackbar(data);
});
}());
(function() {
'use strict';
window['counter'] = 0;
var snackbarContainer = document.querySelector('#sb-message-box');
var showToastButton = document.querySelector('.button3');
showToastButton.addEventListener('click', function() {
'use strict';
var data = {
message: 'Example Message #3',
timeout: 6000
};
snackbarContainer.MaterialSnackbar.showSnackbar(data);
});
}());
Now I would like to hide one Snackbar as soon as another one is clicked. Currently, the divs are shown one after the other as soon as the 6000ms are up. Can you please help me on this one? Thanks so much!
I too ran into this issue. After some digging, I came up with a little hack.
Use the following code to update the current notification for a new one.
Replace where you'd normally use snackbarContainer.MaterialSnackbar.showSnackbar(data); with:
snackbarContainer.MaterialSnackbar.cleanup_();
setTimeout(function(){
snackbarContainer.MaterialSnackbar.showSnackbar(data);
}, snackbarContainer.MaterialSnackbar.Constant_.ANIMATION_LENGTH)
I'm new to javascript, but it seems to work for me.
Edit: It seems like this is not a foolproof method, when the time-out expires on the previous notification, it closes the new one. I don't have an answer for this yet.
Edit 2: Not foolproof, but gets you going, you need to make some changes to material.js for this to work:
Change 1: Somewhere in MaterialSnackbar = function MaterialSnackbar(element)
Change 2: At the start of MaterialSnackbar.prototype.cleanup_
Change 3: Replace your post code with this.
snackbarContainer.MaterialSnackbar.cleanup_();
snackbarContainer.MaterialSnackbar.skipClearing++;
setTimeout(function(){
snackbarContainer.MaterialSnackbar.showSnackbar(data);
}, snackbarContainer.MaterialSnackbar.Constant_.ANIMATION_LENGTH)
I hope this helps.
Given the API provided by this SnackBar component, it is only plossible to show the alert and not possible to hide it. I would suggest either switching to a better component (this one is excessively simplistic) or hacking your way around it by creating a function that directly destroys all the existing alerts before displaying the new one.
There is a slightly hacky approach to accomplish this.
var snackbarContainer = document.querySelector('#toast');
Let us define a handler which will handle the callback to hide the snackbar,
var handler = function(event) {
snackbarContainer.classList.remove('mdl-snackbar--active');
snackbarContainer.setAttribute("aria-hidden", "true");
};
Now, let's show the snackbar as follows,
var data = {
message: 'Snack time!',
timeout: 2000,
actionHandler: handler,
actionText: 'Dismiss'
};
snackbarContainer.MaterialSnackbar.showSnackbar(data);
That's all!
In the current version of Material Design lite you can just call
mySnackbar.MaterialSnackbar.cleanup_();
and it will hide smoothly.
Got a very odd issue coming up here with the new components. When we had a 1.4 directive we had the following code...
(function () {
'use strict';
angular.module('app.board').directive('dcCb', dcClipboardCopy);
function dcCb() {
return {
link : function(scope, elem) {
var clipboard = new Clipboard(elem[0]);
elem.on('$destroy', function() {
clipboard.destroy();
});
}
};
}
})();
Inside the clipboard.destroy() function is the following...
Clipboard.prototype.destroy = function(){
this.listeners.destroy();
}
In 1.4 this is the same as the element so...
<button class="btn btn-sm btn-menu-outline copy-button" ...
So this worked fine as the button element seemed to have the listeners property which could be invoked.
However after the upgrade to 1.5 and now we have a component like this....
(function() {
'use strict';
angular.module('app.board').component('dcCb', {
...
controller: [ '$element','$scope',function($element,$scope) {
var self = this;
self.$postLink = postLink;
function postLink(){
var clipboard = new Clipboard($element[0]);
...
$element.on('$destroy', clipboard.destroy);
}
}]
});
})();
this (when inside the destroy function of the Clipboard) is now the controller object. So trying to call this.listeners throws an error.
First Question :
I understand that this in new components is the component scope but in 1.4 it was the button element. Surely in both the button element should be $element? Were we doing something wrong in 1.4?
Second Question :
Shouldn't var clipboard = new Clipboard($element[0]) force the context of this inside the clipboard to always be the clipboard itself (due to the new keyword)?
You're handing a function, which is arbitrarily defined on a class, off to the window and event listeners to be executed in a different context than the instance of Clipboard:
$element.on('$destroy', clipboard.destroy);
This is a fundamental concept of execution context in javascript, and I'd recommend reading up on it. But you can easily solve your current problem by simply binding the context of the function you are passing:
$element.on('$destroy', clipboard.destroy.bind(clipboard));
To demonstrate my issue, here I have small demo Script:
/user.html =
<div id="user-box"></div>
js =
class UserComponent {
constructor({}) {
this.srcFile = "/user.html";
this.parentBox = $("#container-box");
this.childBox = $("#user-box");
};
show(){
this.parentBox.load(this.srcFile, function() {
this.childBox.html("<p>Hello</p>")
}.bind(this));
};
}
The problem is that this line does not work correctly:
this.childBox.html("<p>Hello</p>")
For me the problem seems to be that when this.chilBox is referenced in the constructor, it does not yet exist in the DOM.
When I rewrite my code to:
this.parentBox.load(this.srcFile, function() {
var childBox = $("#user-box");
childBox.html("<p>Hello</p>")
}.bind(this));
Then it works. But I would like to reference the element in the constructor.
How can I reference the element in the constructor and then use it later when it exists?
I tried this.childBox.find().html("<p>Hello</p>") but like this it did not work.
Thanks for your help!
try this
this.childBox.html($("<p>Hello</p>"))
instead of
this.childBox.html("<p>Hello</p>")
var PlaylistView = Backbone.View.extend({
el: '#expanded-container',
initialize: function() {
var playlistModel = this.model;
this.stopListening(playlistModel.get('songs'), 'add');
var form = this.$('input');
$(form).keypress(function (e) {
if (e.charCode == 13) {
console.log("hey")
var query = form.val();
playlistModel.lookUpAndAddSingleSong(query);
}
});
this.listenTo(playlistModel.get('songs'), 'add', function (song) {
var songView = new SongView({ model: song });
this.$('.playlist-songs').prepend(songView.render().el);
});
This is a snippet of my Backbone view and I cant figure out why sometimes the same songView is rendered twice. In other view, I call PlaylistView.initialize() manually without recreating the view. Because of that, I deregister all the events in the beginning of initialize to prevent it from listening to the same event multiple times. It does its job but only once in a while, the same songView is rendered twice. I suspect this might be some kind of a race condition but I haven't been able to figure out the reason. Does anyone have an idea?
That may happen if playlistModel.get('songs') are not returning the same objects, try to remove only the event whatever the objects are, like this:
this.stopListening(null, 'add');
I am new to javascript, but I've been hired to give maintenance to an application which is developed in Sencha ExtJS 4. One of the modules I've been asked to modify, is of a component in which I show a tooltip whenever I hover over it. This component can be present in more than one view, it is something like "Customer Details" that is present in many screens of the application. If I hover over this data, I need to show a tooltip, this tooltip shows information retrieved by server (REST). I implemented some logic, but this logic involves the use of many listeners in each of the components that will show the information. For instance, I added a listener in all of the views that requires showing the tooltip:
this.listeners = {
boxready: {
fn: this.onAfterRender,
scope: this
}
And I had to implement this method for every view as well, which is a mess and, for sure, a very bad practice:
/**
* This method is executed after panels are rendered in order to set ToolTip listeners on
* users and workgroups.
*
* #param {Object} scope
*/
onAfterRender: function(scope) {
Ext.defer(function() {
var usElements = Ext.get(Ext.query('.usertooltip', scope.el.dom));
usElements.on({
click: function (e) {
var item = Ext.get(e.target);
if (Ext.isEmpty(item.dom.innerHTML.trim())) {
item.removeCls('usertooltip');
return;
}
if (item.hasCls('usertooltip-clicked')) {
return;
}
item.addCls('usertooltip-clicked');
var user = item.getAttribute('data-info');
UserInfo.getUserInfo(user, false);
if (UserInfo.errorResponse) {
UserInfo.getWGroupInfo(user);
}
UserInfo.displayToolTip(this);
}
});
var wgElements = Ext.get(Ext.query('.wgtooltip', scope.el.dom));
wgElements.on({
click : function (e) {
var item = Ext.get(e.target);
if (Ext.isEmpty(item.dom.innerHTML.trim())) {
item.removeCls('wgtooltip');
return;
}
if (item.hasCls('wgtooltip-clicked')) {
return;
}
item.addCls('wgtooltip-clicked');
var wgroup = item.getattribute('data-info');
WGroupInfo.getWGroupInfo(wgroup, false);
if (UserInfo.errorResponse) {
WGroupInfo.getUserInfo(wgroup);
}
WGroupInfo.displayToolTip(this);
}
});
}, 1000, this);
},
What I do is simply detect if the item is selected based a css class, if so, I handle the events and proceed with logic. But I've been doing some research and I think this can be achieved using a "delegator" but I am not sure how to implement this for my scenario.
What I've been thinking of, so far is to create a "js" class which have a method like an "observer" and whenever listen to someone asking for this tooltip functionality, delegate it to the executing object. But since I am new to javascript and this Sencha ExtJS, my tries have been frustrated. If someone can help me I would really appreciate it.
Thanks in advance.
Best regards.
The best way would be to declare a plugin:
Ext.define('TipPlugin', {
alias: 'plugin.tip',
init: function(c) {
c.on('boxready', this.onBoxReady, this);
},
onBoxReady: function(c) {
var els = this.el.select('.usertooltip');
// Do stuff!
}
});
var c = new Ext.Component({
plugins: ['tip']
});