I have a view (I'll call parent) that has another view inside of it ( a child view). The parent view has elements that use a "context menu" event (when right clicked, a set of menu options show up). When a user selects an item from the menu, it fires a callback. From there, I trigger an event in the child view. Here is my trigger:
that.trigger("editFileFolder", {fileType: fileType});
In my child view's initialize function, I have this:
this.listenToOnce(this.options.parent,'editFileFolder', this.editFileObjectEvent);
The editFileObjectEvent function calls another view to be created (a dialog with some fields, a cancel button and a save button). If the user clicks cancel, everything works fine. If the use clicks the save button, the view does an ajax call, saves to the server, and closes the dialog. But, the next time the user right clicks and selects the same menu item, the editFileObjectEvent gets called twice (resulting in the dialog being added twice to the parent view).
Can anyone explain why it's getting called twice and how to resolve this? If you wish to see specific code, let me know and I can add it. There's a lot, so I don't want to overwhelm the question.
thanks
I think the function that calls this.listenToOnce is called twice. You should try to avoid this. However if this is not possible you can make sure the listeners is only bound once by unbinding it before binding:
this.stopListening(this.options.parent, 'editFileFolder', this.editFileObjectEvent);
this.listenToOnce(this.options.parent, 'editFileFolder', this.editFileObjectEvent);
Or you could have a property on your instance to prevent from binding twice:
if (!this.__boundListener) {
this.listenToOnce(this.options.parent, 'editFileFolder', this.editFileObjectEvent);
this._boundListener = true;
}
All listeners are registered in this._listeningTo and you might be able to search though that to check if the event is already bound.
You can also refactor your code:
MyModel.extend({
initialize: function() {
this.bindListenToOnce = _.once(this.bindListenToOnce);
},
abc: function() {
// do stuff
this.bindListenToOnce();
// do stuff
},
bindListenToOnce: function() {
this.listenToOnce(this.options.parent, 'editFileFolder', this.editFileObjectEvent);
}
});
Related
I'm working on a project using TypeScript and I'm facing an issue on elements having duplicate addEventListener to it.
The way the application works is that when the page first loads, nothing gets executed. All the TS classes keep alive waiting for an event to be dispatched. This event comes from the backend that will tell the application that it's ready to start.
const dbEvent = new CustomEvent("backendLoadEvent");
document.dispatchEvent(dbEvent);
When the TS class receives this event, it starts itself going through the dom getting the elements and applying all the eventListeners necessary for it to work properly.
Inside the TS file I will add the listener just as usual:
const btnOrder = document.getElementById('btn-order');
btnOrder.addEventListener('click', function (e) {
// Business logic here
console.log('debug button order click');
});
The problem is that the backend gets fired multiple times which is causing the button clicked to be executed multiple times as well. For example, sometimes that console.log inside the event click gets triggered twice simultaneously.
I tried to do something like this:
const btnOrder = document.getElementById('btn-order');
if (btnOrder.getAttribute('listener') !== 'true') {
btnOrder.addEventListener('click', function (e) {
const elementClicked = e.target;
elementClicked.setAttribute('listener', 'true');
});
}
The problem with this approach is that it relies on the click, so if the user clicks after the second event from the database, it's not going to work. I don't know when the event will arrive at the application nor when the user will click on the button.
Is there another approach I could use to prevent this behavior?
Note this is a simple example, but in some elements of the page there are a lot of events, this is a dashboard project. So we have like open/close modals, order and filter tables, add, remove and edit items on the table, and so on...
I have tried to extend the simple box widget tutorial
(http://docs.ckeditor.com/#!/guide/widget_sdk_tutorial_1) with some events but I don't really get it.
One of my goals is to trigger an event if an editable (eg. the simplebox-title) field within the widget get focused.
But unfortunately I'm only able to listen if the widget itself get focused:
editor.widgets.add('simplebox', {
// definitions for
// button, template, etc
init: function() {
this.on('focus', function(ev){
console.log('focused this');
});
}
});
or if the data is changed:
CKEDITOR.plugins.add('simplebox', {
// my plugin code
init: function (editor) {
editor.widgets.on( 'instanceCreated', function( evt ) {
var widget = evt.data;
widget.on('data', function(evt){
console.log("data changed");
});
});
}
//even more code
});
How do I listen to editable fields within widgets?
Another challenge for me is to fire an event if the widget is removed. Maybe someone also know how to listen to this event too?
How do I listen to editable fields within widgets?
There are no events for editable fields within widgets. They behave like the main editable, so when you change something in them the editor#change event is fired.
Another challenge for me is to fire an event if the widget is removed.
There's the widget#destroy event, but you will not find it very useful. The reason is that widgets are not always destroyed as you delete them because there are many ways to do so.
If you pressed backspace while having a widget selected, then yes - this event is fired, because deletion is made directly on the widget. However, it is fired after the widget was deleted.
If you select the entire content of editor and press backspace, then the browser deletes it, because the browser handles this key in this case. Therefore, CKEditor implements a small garbage collector which checks from time to time which widget instances were removed and destroys them. You can increase the frequency by calling editor.widgets.checkWidgtes() more often - e.g. on editor#change, but it won't change anything. In both cases the event is fired after the widget is deleted.
I've got a WinJS ListView that has its items created using a templating function (the itemTemplate option points to a function). The returned item has a div inside it that has the win-interactive class so that it can accept input. Specifically, that div needs to be able to scroll to show more content that can fit on the ListView item.
The scrolling works perfectly with the win-interactive class applied to the div. The problem I'm trying to solve is allowing a normal click (mouse down, mouse up) to still trigger the oniteminvoked event on the ListView, while still allowing the div inside a ListView item to be scrolled.
I figured this would be easy: I'd bind a click event listener to the div with the win-interactive class, and simple pass that click event up to another element on the ListView item, ensuring that eventually the oniteminvoked event would be trigger.
I've bound the click listener to the win-interactive div, and it's being triggered as expected. However, and I cannot figure out how to trigger a click/item invocation on another part of the ListView item. I've tried using properties of the event (currentTarget) to get parent or sibling elements and triggering events on them, but I simply can't figure out how to trigger an event on an element like event.currentTarget.
tl;dr:
How can I allow scrolling within a ListView item while still allowing a normal click to trigger the item invocation?
Overall, the ListView just handles clicks by passing them to whatever itemInvoked handler you register for the control. This means you should be able to just bypass that whole chain and invoke your handler directly with the appropriate item index, which is easy to obtain.
Within your item's click handler, if you have the ListView object handy, then list.currentItem will be the item with the focus (and obviously the one clicked), and its index property will be what normally gets passed to itemInvoked. With this you can then call your itemInvoked handler directly, building up the appropriate event object, or you can separate your handler's code into another method and call that one instead.
As a basic example, starting with a Grid app template project, I added win-interactive to the item-title element (which could by any other like your scrolling region) in pages/groupedItems/groupedItems.html:
<h4 class="item-title win-interactive" data-win-bind="textContent: title"></h4>
In the ready method of pages/groupedItems/groupedItems.js, I attached the ListView's object to the page control for later use:
var listElement = element.querySelector(".groupeditemslist");
var list = listElement.winControl;
this._list = list;
And then hooked up click listeners to the item-title elements as below. I'm doing it this way because the items are created through templates; if you have an item rendering function instead, then you can add the listeners in that piece of code directly.
var that = this;
list.addEventListener("loadingstatechanged", function () {
if (list.loadingState == "complete") {
var interactives = element.getElementsByClassName("item-title");
for (var i = 0; i < interactives.length; i++) {
interactives[i].addEventListener("click", that._itemClick.bind(that));
}
}
});
The implementation of _itemClick in my page control looks like this:
_itemClick: function (e) {
var item = this._list.currentItem;
//Can also get the index this way
var index = this._list.indexOfElement(e.currentTarget);
this._itemInvoked({"detail" : { itemIndex : item.index} })
},
where this._itemInvoked is the same handler that comes with the template.
Note that with win-interactive, you don't get the usual pointerDown/pointerUp behaviors for items that do the little down/up animations, so you might want to include those too if you deem it important (using WinJS.UI.Animation.pointerDown/pointerUp methods to do the effects).
Finally, it's probably possible to go up the chain from the e.currentTarget element inside _itemClick and simulate a click event on the appropriate parent, but I think that's more trouble than it's worth here having traced through the WinJS code that does all that. Much more direct to just call your own itemInvoked.
I'm wondering about where events should be set in a parent-child relationship. It's not a specific problem, just a best practices I guess. My specific case:
I have a Dropdown which contains of a Button and a List. Describing the action, you press the button and the list appears. You click an item in the list, and that item is selected and the list disappears.
So the first thing is that since the view is initialized as a Dropdown, the person making it shouldn't need to reach in and deal with the Button or List view. All methods should be called on the parent view, and events might need to be bubbled up from the child.
So for example, instead of doing: this.dropdown.button.press();, Dropdown provides its own press method which just calls Button's press method.
When the user presses the button, Button fires a press event that Dropdown is listening to.
onButtonPress : function () {
if (!this.isExpanded()) {
this.expand();
}
this.trigger('press');
},
And Dropdown triggers press on itself, so that developer can get the press event without reaching in to dropdown.button.
Here's where the first question comes. Should Dropdown expand itself in onButtonPress or should onButtonPress just trigger a press, and then have the expansion listening on Dropdown's own press event:
onButtonPress : function () {
this.trigger('press');
},
onPress : function () {
if (!this.isExpanded()) {
this.expand();
}
},
And then it gets more complicated, should Dropdown's expand method just trigger expand on itself:
expand : function () {
if (this.isEnabled()) {
this.setState('expanded', true);
this.trigger('expand');
}
return this;
},
onExpand : function () {
this.list.show();
},
or should it be the one that shows the List:
expand : function () {
if (this.isEnabled()) {
this.setState('expanded', true);
this.list.show();
this.trigger('expand');
}
return this;
},
I guess I'm just wondering about best practices for deciding where to bind events in a parent/child relationship. How to avoid confusing situations and possibly circular event calling.
Anyone have any thoughts?
I would simply use the DOM for this instead of triggering an event manually. If you want to have an event hit the parent first, then set the capture to true, or false if you want the event to bubble.
this allows an event handler to be called during the capture phase, so parent DOM elements get event first:
el.bind('someevent', somefunction, true);
this bubbles, hence children receive event first, and handler is called when children receive event.
el.bind('someevent', somefunction, false);
In general keep it simple. You can also use the delegation programming pattern where the parent will get the events for itself and the children, so you can avoid confusion and potentially can save memory with larger applications.
So I have a button inside a list row that is used to delete the row from the page (calls ajax stuff to delete the object represented by the row, but that's not important for my question). The whole row is bound to a click event which would redirect to another page.
In other words, the containing row is click bound and the inner button is click bound, which is causing me problems since clicking the inner button also triggers the containing row click event (as it should).
I've tried binding a hover event for all delete buttons that unbinds the row click on mouseover, and rebinds it on mouseout, like this pseudocode below:
$('.delete-button').hover(
function() {
$('.list-row').unbind();
$('.delete-button').bind('click', function() { /* delete action */ });
},
function() {
$('.delete-button').unbind();
$('.list-row').bind('click', function() { /* list row action */ });
}
);
This isn't working very well, and I'm convinced there is a better way to approach it. Should I take the button out of the containing list-row? It's way easier to have it in there since my list row contains custom attributes that have data I need for the ajax calls and I can just var rid = $('.delete-button).parent().attr('row-id'); to get the data, but I'm not opposed to change :)
Thanks!
In your click event handler for the button, you need to call e.stopPropagation(). This will prevent the event from bubbling up the DOM tree. More info here: http://api.jquery.com/event.stopPropagation/
edit: you already accepted (thanks!), but maybe this code snippet would help explain some of the concepts better:
$('.list-row').click(function() {
/* list row action */
});
$('.delete-button').click(function(e) {
// die, bubbles, die
e.stopPropagation();
// if you also need to prevent the default behavior for the button itself,
// uncomment the following line:
// e.preventDefault();
// note that if you are doing both e.stopPropagation() AND e.preventDefault()
// you should just `return false;` at the end of the handler (which is jQuery-
// sugar for doing both of these at once)
/* delete action */
})
There's a few ways of approaching this. As #jmar777 has already said you may attach an altered event to the click handler on the button, stopping propagation.
If you want to do this with the same function as you're applying to the div then you can approach it as such:
if($(event.target).is("input")) {
event.stopPropagation();
}
Another approach is to actually not bind the click event to the button, for any time the browser supports clicks on the containing element. As you will always trigger that, then you don't actually need the button to handle it too! This does require you to handle IE6 etc a little differently from everything else though...
Let your handler function return false