Firing a custom event in ExtJS 5.1 - javascript

I have been trying to fire a custom event when a file has been successfully uploaded using a modal window. A grid on the main page listens for the event and should reload its store when a file is successfully uploaded. Problem is, the grid never catches this event.
I think I have a fundamental misunderstanding of how custom events work. What steps should I take to get back on track?
SomeCommonUtilityClass.js
upload: function(args) {
Ext.create('Ext.window.Window', {
/* form with some controls */
buttons: [{
text:'Upload',
handler: function() {
var win = this.up('window');
var form = this.up('form').getForm();
form.submit ({
url: myAjaxCall,
success: function() {
/* fire event here */
win.fireEvent('uploadSuccess');
},
failure: function() {
/*...*/
}
});
}
},
/* etc. */
});
}
SomeOtherFileView.js
{
xtype:'grid',
itemId:'uploadedGrid',
listeners: {
uploadSuccess: 'reloadUploadStore'
},
bind: {
store:'{form}'
},
columns:[/*...*/]
}
SomeOtherFileViewController.js
reloadUploadStore: function() {
console.log("My event fired!") // Never gets here.
/* .... */
store.load({
params: ({
a: "a",
b: "b"
});
callback: function() {
/* do more stuff */
}
});
}

SomeCommonUtilityClass
win.fireEvent('uploadSuccess');
Example of custom event and Controller that listen on it:
SomeOtherFileViewController
init: function() {
this.listen({
// We are using Controller event domain here
controller: {
// This selector matches any originating Controller
'*': {
uploadSuccess: 'reloadUploadStore'
}
}
});
},
reloadUploadStore: function() {
//your code
}
or if you want pass a argument:
win.fireEvent('uploadSuccess',extraArgument);
Controller code is the same. Only your function definition changes:
reloadUploadStore: function(yourArgument) {
//Do your stuff with extraArgument
}

Related

RequireJS modules are undefined in Backbone view

I have been pretty much beginner at this part of javascript and I would appreciate any ideas how could be solved this problem.
I use requirejs to define my own modules where I also use backbone.js.
Let say I have the main module where I initialize my Backbone view which is rendered without any problem. Also, the click event where is calling method createSchemeForm creates the form correctly. The problem raises up in a situation when I call cancel method by click and the modules which are defined for Backbone view (e.g. "unicorn/sla/dom/helper"...) are undefined but when I called method createSchemeForm at the beginning the modules were executed without any problem.
Thank you in advance for any suggestions.
Backbone view
define("unicorn/sla/view/scheme", [
"unicorn/sla/dom/helper",
"unicorn/soy/utils",
"unicorn/sla/utils"
], function (DOMHelper, soyUtils, jsUtils) {
return Backbone.View.extend({
el: 'body',
inputData: {},
btnSaveScheme: 'btn-save-sla-scheme',
btnCancel: 'btn-cancel-sla-scheme',
btnCreate: 'btn-create-sla-scheme',
btnContainer: '#sla-scheme-buttons-container',
schemeContent: '#sla-scheme-content-section',
btnSpinner: '.button-spinner',
events: {
'click #btn-create-sla-scheme' : "createSchemeForm",
'click #btn-cancel-sla-scheme' : "cancel"
},
initialize: function(){
console.log("The scheme view is initialized...");
this.render();
},
createSchemeForm: function () {
this.spin();
DOMHelper.clearSchemeContent();
DOMHelper.clearButtonsContainer();
//Get button
$btnSave = soyUtils.getButton({isPrimary: 'true', id: this.btnSaveScheme, label: 'Save'});
$btnCancel = soyUtils.getButton({isPrimary: 'false', id: this.btnCancel, label: 'Cancel'});
//Append new created buttons
DOMHelper.addContent(this.btnContainer, AJS.format("{0}{1}", $btnSave, $btnCancel));
//Call service to get entry data for scheme creation form
AJS.$.ajax({
url: AJS.format('{0}={1}',AJS.I18n.getText('rest-url-project-scheme-input-data'), jsUtils.getProjectKey()) ,
type: "post",
async: false,
context: this,
global: false,
}).done(function (data) {
this.inputData = data;
$slaSchemeForm = soyUtils.getSchemeCreateForm({slaScheme : data, helpText: AJS.I18n.getText("sla-time-target-tooltip-text")});
DOMHelper.addContent(this.schemeContent, $slaSchemeForm);
jsUtils.scroll(this.schemeContent, 'slow');
}).fail(function () {
jsUtils.callFlag('error', AJS.I18n.getText("message-title-error"), AJS.I18n.getText("sla-error-load-scheme-input-data"));
}).always(function () {
this.stopSpin();
});
},
spin: function () {
AJS.$('.button-spinner').spin();
},
stopSpin: function () {
AJS.$('.button-spinner').spinStop();
},
cancel: function () {
jsUtils.clearButtonsContainer();
jsUtils.clearSchemeContent();
$btnCreateScheme = soyUtils.getButton({isPrimary: 'false', id: this.btnCreate, label: 'Create SLA Scheme'});
DOMHelper.addContent(this.btnContainer, $btnCreateScheme);
DOMHelper.addContent(this.schemeContent, soyUtils.getSchemesTable(new Array())); // TODO - get current data from server instead of empty array
}
});
});
Main module where is Backbone view initialize
define("unicorn/sla/project/batch", [
"unicorn/sla/utils",
"unicorn/sla/data/operations",
"unicorn/sla/data/validator",
"unicorn/sla/dom/helper",
"unicorn/sla/model/confirm/message",
"unicorn/sla/view/scheme",
"exports"
], function (jsUtils, operations, validator, DOMHelper, ConfirmMessage, SchemeView, exports) {
//Load project batch
exports.onReady = function () {
$schemeView = new SchemeView();
$schemeView.render();
}
});
AJS.$(function () {
AJS.$(document).ready(function () {
require("unicorn/sla/project/batch").onReady();
});
});

Backbone/Marionette - How to listen to events on Region's child view?

I have a View, a CollectionView is rendered inside a region of this View. How do I make View listen to events of CollectionView?
const ChildCollectionView = marionette.CollectionView.extend({
// ...
events: {
'click .bar': 'clickBar',
},
clickBar() {
this.trigger('clickBar');
},
});
const ParentView = marionette.View.extend({
// ...
regions: {
foo: '.foo',
},
// ...
onRender() {
const fooRegion = this.getRegion('foo');
fooRegion.on('show', function(view) {
// XXX: this does not work
view.on('childview:clickBar', function() {
console.log('click bar');
});
});
fooRegion.show(new ChildCollectionView({
// ...
}))
},
});
Looks like you're using Marionette 3.x. In short, you can use childViewEvents.
As to the specifics of your code, it would be better to have the childView of your CollectionView define the click event, as the listener to the child view event will receive the childView that was clicked. It would also be better to use the showChildView method in the ParentView's onRender.
const ChildView = marionette.View.extend({
// ...
triggers: {
'click .bar': 'click:bar',
},
// or
events: {
'click .bar': 'clickBar'
},
clickBar() {
// other child view stuff
this.trigger('click:bar');
},
});
const ChildCollectionView = marionette.View.extend({
// ...
childView: ChildView,
childViewEvents: {
'click:bar': 'barClicked',
},
barClicked(childView) {
// other view stuff
this.trigger('child:clicked:bar', childView);
// or
this.triggerMethod('child:clicked:bar', this, childView)
}
});
const ParentView = marionette.View.extend({
regions: {
foo: '.foo'
},
childViewEvents: {
'child:clicked:bar': 'clickBar'
},
clickBar(collectionChild, clickedChild) {
console.log('click bar', collectionChild.cid, clickedChild.model.cid);
},
onRender() {
this.showChildView('foo', new ChildCollectionView());
}
});
See JSFiddle below for different ways to see an example plus a couple different ways to trigger events. (forked from Marionette example)
https://jsfiddle.net/opyfvsfx/36/

CollapseSidebar is not a function

I am writing a script for a project, it has a function to collapse or open the sidebar on click on hamdurger icon but when I click it gives me error
TypeError: this.CollapseSidebar is not a function
The following is my code:
(function($) {
'use strict';
var Prtm = {
Constants: {
LEFTMARGIN:'315px',
COLLAPSELEFTMARGIN: '63px',
},
PrtmEle:{
BODY: $('body'),
SIDEBAR: $('.prtm-sidebar'),
SIDENAV: $('.sidebar-nav'),
MAIN: $('.prtm-main'),
HEADER: $('.prtm-header'),
CONTENTWRAP: $('.prtm-content-wrapper'),
CONTENT: $('.prtm-content'),
PRTMBLOCK: $('.prtm-block'),
FOOTER: $('.prtm-footer'),
HAMBURGER: $('.prtm-bars'),
},
Init:function(){
this.BindEvents();
},
BindEvents:function(){
this.PrtmEle.BODY.on('click',this.PrtmEle.HAMBURGER,function(){
this.CollapseSidebar();
});
},
CollapseSidebar: function(){
this.PrtmEle.HAMBURGER.toggleClass("prtm-sidebar-closed is-active");
this.PrtmEle.BODY.toggleClass("prtm-sidebar-closed is-active");
this.PrtmEle.SIDEBAR.toggleClass('collapse');
},
};
Prtm.Init();
})(jQuery);
When I change this.CollapseSidebar to Prtm.CollapseSidebar it works properly. What I am doing wrong here and how it can be resolved?
Why it do not work? Because a function() binds its own this.
One thing you can do is to use Arrow function which do not bind its own this - like this:
BindEvents:function(){
this.PrtmEle.BODY.on('click',this.PrtmEle.HAMBURGER,() => {
this.CollapseSidebar();
});
}
Inside you click function this will refer to the window - so either you can create a temporary variable like the below or use the arrow function:
BindEvents:function() {
let $this = this;
this.PrtmEle.BODY.on('click',this.PrtmEle.HAMBURGER,function() {
$this.CollapseSidebar();
});
}

Extjs MVC nested views events and async function

I'm developing an extJS 4.2 MVC app.
I've this context menu view object defined:
Ext.define('XApp.view.message.inbox.CtxMenu', {
extend : 'Ext.menu.Menu',
alias : 'widget.inboxctxmenu',
items : [ {
itemId : 'buttonSetFlags',
text : 'ToRead'
}]
});
this context menu is builded when i'm creating this grid (and other my extended grids):
Ext.define('XApp.view.message.inbox.Grid', {
extend: 'Ext.grid.Panel',
alias: 'widget.inboxgrid',
store: 'message.Inbox',
initComponent : function(){
this.menu = this.buildMenu();
this.callParent(arguments);
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
onItemContextMenu : function(grid, record, item, index, e, eOpts) {
console.log('onItemContextMenu');
e.stopEvent();
this.menu.showAt(e.getXY());
},
onDestroy : function(){
console.log('destroy grid and menu');
this.menu.destroy();
this.callParent(arguments);
},
buildMenu : function(){
return Ext.widget('inboxctxmenu');
}
});
this code is extracted from Sencha blog on point 2 to avoid memory leak on nested object.
Now in my controller i want to listen
Ext.define('XApp.controller.Inbox', {
extend : 'Ext.app.Controller',
init : function(application) {
this.control({
"inboxctxmenu #buttonSetFlags" : {
click : this.onFlagsSetter
}
});
},
onFlagsSetter : function(button, e, eOpts) {
this.getController('Message').SetMessageStatus(1,"ToRead",this.getStore('message.Inbox').load);
}
});
in this controller, i call another controller function and i want to reload 'message.Inbox' store:
Ext.define('XApp.controller.Message', {
extend : 'Ext.app.Controller',
SetMessageStatus: function(id,statusToSet,callback) {
Ext.Ajax.request({
url : XApp.util.Util.serverUrl + 'api/message/SetMessageStatus/' + id + "/" + statusToSet,
method : "GET",
failure : function(response, options) {
console.log('Failure' + response);
},
success : function(conn, response, options, eOpts) {
console.log('Success');
if (callback && typeof(callback) === "function") {
console.log('Calling callback');
callback();
}
}
});
}
});
in this function, i've an async call with AJAX, and i want to reload store of InboxController after ajax response, but with this notation, console throw an error.
There are best practices to call async function and launch a callback after success or failure?
Another question is:
what is the best pratices with ExtJs MVC to listen on nested view event (in example my ctxmenu is nested in a grid)? i read for fireevent and bubbleevent but i'm confused...Please bring me back to the right way...
JFYI the context menu in your example is not nested in the grid. Menus are floating objects, and as such they are outside of the usual component hierarchy.
The error you're having is because you're not passing a callback to SetMessageStatus, you're passing the result of expression this.getStore('message.Inbox').load - which evaluates to a function, but without a scope bound to it it's useless. Read this question's answers for more explanations on what the function scope is.
With a naïve head-on approach, the fix would look thusly:
onFlagsSetter: function(button, e) {
var me = this; // Important for the closure below
this.getController('Message').SetMessageStatus(1, 'ToRead', function() {
// Note that within the callback function, `this` is an object
// entirely different from `this` in the above line, so we call
// `getStore` on the captured scope instead.
me.getStore('message.Inbox').load();
});
}
However, a much better approach is to use Controller events:
Ext.define('XApp.controller.Inbox', {
extend: 'Ext.app.Controller',
init: function() {
this.listen({
component: {
'inboxctxmenu #buttonSetFlags': {
click: this.onFlagsSetter
}
},
controller: {
'*': {
statusmessage: this.onStatusMessage
}
}
});
},
onFlagsSetter: function(button) {
this.fireEvent('setstatus', 1, 'ToRead');
},
onStatusMessage: function(success, response) {
if (success) {
this.getStore('message.Inbox').load();
}
}
});
Ext.define('Xapp.controller.Message', {
extend: 'Ext.app.Controller',
init: function() {
this.listen({
controller: {
'*': {
setstatus: this.setMessageStatus
}
}
});
},
setMessageStatus: function(id, statusToSet) {
Ext.Ajax.request({
url: ...,
method: 'GET',
failure: function(response) {
this.fireEvent('statusmessage', false, response);
},
success: function(connection, response) {
this.fireEvent('statusmessage', true, response);
},
// We are setting the above callbacks' scope to `this` here,
// so they would be bound to the Controller instance
scope: this
});
}
});
As you can see, by using Controller events we have decoupled Inbox controller from the Message controller; they are no longer calling each other's methods directly but are passing information instead. The code is much cleaner, and concerns are properly separated.

Calling functions with click handlers in ExtJS 4

I have a function inside a toolbar, let's call it:
Ext.define('MyArchive.Toolbar', {
search: function() {
console.log('searching');
}
}
Now I'd like to call this function when clicking a button. So I'm adding some click handlers in the afterRender on the toolbar setup:
afterRender: function() {
Ext.getCmp('search-button').on('click', this.search);
}
However, this doesn't work and I ultimately need to go the full route of:
afterRender: function() {
Ext.getCmp('search-button').on('click', function() {
quick_search();
)};
}
Any particular reason why my first attempt doesn't apply the click handler as I expect?
Thanks for any explanations or refactorings! Additional patterns/idioms welcome...
Next try:
var panelOverall = new Ext.form.FormPanel({
html: 'bla',
search: function() {
console.log('searching');
},
buttons: [
{
text: 'Moo',
id: 'button1',
handler: function(){
//window.destroy();
}
}
],
afterRender: function() {
Ext.getCmp('button1').on('click', this.search);
}
});
is working for me.. am I missing something?

Categories