I'm having trouble binding an event handler on a dynamically-generated element in a Chaplin view, and I can't figure out what's going on. This seems like the most explicit possible implementation:
var MyView = Chaplin.View.extend({
/* using jQuery to bind the event because Chaplin.View.listen
and Chaplin.View.delegate aren't working... */
render: function() {
Chaplin.View.prototype.render.apply(this, arguments);
this.$('#the-button').click(function() { console.log('clicked'); });
console.log('breakpoint here');
}
});
In Chrome Dev Tools:
> this.$('#the-button').attr('unique-attr', 'blah');
< [ <button id="the-button" unique-attr="blah">Text</button>]
> this.$('#the-button').click()
clicked
< [ <button id="the-button" unique-attr="blah">Text</button>]
Unpause the application, make sure we're looking at the same element:
> $('#the-button')
< [ <button id="the-button" unique-attr="blah">Text</button>]
> $('#the-button').click()
[ no output ]
< [ <button id="the-button" unique-attr="blah">Text</button>]
Can anybody please explain why the onclick event handler for the button is being triggered in the scope of the "render" function but not being triggered in the global scope? Thanks.
Solved it.
I was referring to MyView.$el.html() in a parent view. That returns the innerHTML of the element. But the event handler was binding to the root of $el, which was no longer being rendered into the document, thus no event handlers were being called.
Related
Hi I'm playing around with vue directives and I'm trying to prevent click event if the element is <a> or <button> tag. So my question is, is this possible to do using vue directive?
Html
<a
#click.stop.prevent="displayModal()"
v-noclick="test">
I'm a link
</a>
Vue directive
Vue.directive('noclick', {
bind( elem, binding, vnode ) {
let user = {'name': 'John Doe'}
if( user ) {
let hasAccess = false
if( !hasAccess ) {
if( elem.tagName === 'A' ) {
elem.addEventListener( 'click', ( event ) => {
//this should prevent the element on click event
//but not working
event.stopPropagation()
event.preventDefault()
event.stopImmediatePropagation()
return false
})
return true
}
}
}
}
}
Vue registers the #click event first before v-noclick where you add a second click event listener on that element. When the <a> is clicked, the displayModal() function will be executed first, then the listener in the v-noclick directive will be executed. If the listeners were registered in the opposite order, then it would probably work.
That aside though, it doesn't look like what you are trying to do should be done inside a custom directive. Instead you can do the same logic in the click handler itself:
<a #click="onClick">Foo</a>
onClick() {
if (whatever) {
this.displayModal()
}
}
i couldn't find a vue way to do this. but with js you can use conditional sequence like this
<a #click="cond && onClick"></a>
in this case if cond be equals to true then onClick will called
I am trying to trigger a custom event in a parent element from the child elements event. The parent element is HelpMenuHeader and it's custom event is defined in HTML as "onsubmenu_click".
Here's a snippet of the HTML that just shows one menu tree.
<span class="formMenu" id="HelpMenuHeader" onsubmenu_click="OnMenuClick()">Help
<div class="formMenu" id="HelpAbout" onmouseup="MenuChildClick()">About us...</div>
</span>
In the child element, HelpAbout, the MenuChildClick event needs to trigger the parent's onsubmenu_click event so that that will execute (that event handler uses the parent's information).
Here's a snippet of the javascript I have for MenuChildClick:
function MenuChildClick()
{
var srcElement = this.event.srcElement;
if (srcElement.id != "spacer" && srcElement.tagName != "HR")
{
// NONE OF THE LINES BELOW WORK
//parent.$(srcElement).trigger('onsubmenu_click');
//$(srcElement).trigger('onsubmenu_click');
//var event = document.createEvent('Event');
//event.initEvent('submenu_click', true, true, null);
//srcElement.dispatchEvent(event);
//oEvent = createEventObject();
//oEvent.result = srcElement.id;
//onsubmenu_click.fire(oEvent);
}
}
I'm having a problem getting a reference to the correct parent element in the MenuChildClick event because when I check the parent reference doesn't have the parent ID.
And then once I have the correct parent reference I need to execute the parent's onsubmenu_click custom event. (The parent event is already being listened to since it's defined in the HTML, right?)
I have to support IE compatibility view so I need it to work for previous IE versions as well.
Anyone tell me how I can do these things (1 & 2 above) leaving the HTML as it is?
Thanks in advance.
You can use jQuery methods .on() and .trigger() instead of event handler attribute
$(function() {
function parentHandler(event, args) {
console.log(event.type, args)
}
$("#HelpMenuHeader").on("submenu_click", parentHandler);
$("#HelpAbout").on("mouseup", function() {
$(this).parent().trigger("submenu_click"
, ["triggered from #" + this.id])
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
<span class="formMenu" id="HelpMenuHeader">Help
<div class="formMenu" id="HelpAbout">About us...</div>
</span>
First you have to pass the element that is triggering the event in your HTML by changing your HTML to this:
<span class="formMenu" id="HelpMenuHeader" onsubmenu_click="OnMenuClick()">Help
<div class="formMenu" id="HelpAbout" onmouseup="MenuChildClick(this); return false;">About us...</div>
</span>
Notice that I pass the element that is triggering the function call by passing 'this' through the onmouseup function call.
Then you can use the passed element to define which elements you want to monitor as follows:
function MenuChildClick(element)
{
var srcElement = element;
var parent = element.parentNode
if (srcElement.id != "spacer" && srcElement.tagName != "HR")
{
//parent.trigger('onsubmenu_click');
}
}
I'm using the following directive to detect when a click is made outside a div:
app.directive('clickOut', function ($window, $parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var clickOutHandler = $parse(attrs.clickOut);
angular.element($window).on('click', function (event) {
if (element[0].contains(event.target)) return;
clickOutHandler(scope, {$event: event});
scope.$apply();
});
}
};
});
In this div:
<div class="panel-body" click-out="closeMyPopup()">
<div class="row clearfix">
<div class="col-md-12">
<div class="form-inline pull-right">
<button type="button" class="form-control btn" ng-click="onCancelAnnouncement()">Cancel</button>
<button type="submit" class="form-control btn" ng-click="onSaveAnnouncement()">Save</button>
</div>
</div>
</div>
</div>
It works well, when you click outside the div, the function closeMyPopup() is triggered, the issue is that the div has buttons that triggers other functions. By some reason when other function is called, (like when the buttons are clicked) the event click outside is triggered calling the closeMyPopup(), the buttons are inside the div so the event click outside should not be called. There's another directive that I can use, that has the correct behavior and not trigger the click outside when you fire another function? Or how can I workaround this?
I also use this other directive, with the same issue:
app.directive("outsideClick", ['$document', '$parse', function ($document, $parse) {
return {
link: function ($scope, $element, $attributes) {
var scopeExpression = $attributes.outsideClick,
onDocumentClick = function (event) {
var isChild = $element.find(event.target).length > 0;
if (!isChild) {
$scope.$apply(scopeExpression);
}
};
$document.on("click", onDocumentClick);
$element.on('$destroy', function () {
$document.off("click", onDocumentClick);
});
}
}
}]);
Its because the event is being propagated to Window object.
- Window
- document
- dialog
- button
In the above hierarchy, if a click event happens on the last button element, the event will be propagated to the parent element until it reaches Window and then will close your dialog.
Solution 1:
Stop event propagation in each controller function by passing the event as a parameter and calling event.stopPropagation() function:
<button type="button" class="form-control btn" ng-click="onCancelAnnouncement($event)">Cancel</button>
...
$scope.onCancelAnnouncement($event) {
$event.stopPropagation();
}
Solution 2:
Let the event be propagated and check the target element:
angular.element($window).on('click', function (event) {
var target = $(event.target);
if(target.attr("id") === "anyid") { // or target.hasClass("someclass")
// or target.closest(".some-selector")
// just ignore
} else {
// Do whatever you need
}
});
Exactly: events will be presented to every object in the nested DOM-hierarchy unless and until you stop their propagation. This, of course, is by design: JavaScript doesn't assume that "the innermost guy I can find who's listening for this event" is the only guy who might be interested in it. Everyone who says he's listening for it, who is in the position to hear it, is going to hear it, each in their turn ... unless one of them explicitly quashes further propagation, at which JS will stop looking for anyone else to send it to. (No one has to "send the event to the outer container." Instead, they only have to tell JS not to send it on.)
Currently dojo uses on method to connect event to handler.
btn = new Button();
btn.on('click', function () {console.log('do something');});
this will call the attached function when the button gets clicked.
however, according to the documents, removing existing handlers should be done in the following way
handler = btn.on('click', function () {console.log('do something');});
handler.remove();
this is not the way I want to remove event handler.
I do not store the handler reference anywhere. But I want to add a new 'click' event by doing
btn.on('click', function () {console.log('do something different');});
so that it replaces the existing 'click' event handler and add a new one.
Is there any way to achieve what I want?
Thanks!
That's not possible, the framework tells you to do it in the way by creating a reference to the event handler. This is similar to how other frameworks like jQuery work.
jQuery has of course a mechanism to remove all event handlers by using the off() function, but that's not available in Dojo either. Like Chris Hayes suggested in the comments, you can implement such a feature by yourself, either by wrapping it inside another module, or by using aspects on the dojo/on module.
For example, you can wrap it inside a new module:
// Saving the event handlers
var on2 = function(dom, event, callback) {
on2.handlers = [];
if (on2.handlers[event] === undefined) {
on2.handlers[event] = [];
}
var handler = on(dom, event, callback);
on2.handlers[event].push({
node: dom,
handler: handler
});
return handler;
};
// Off functionality
lang.mixin(on2, on, {
off: function(dom, event) {
if (this.handlers[event] !== undefined) {
array.forEach(this.handlers[event], function(handler) {
if (handler.node === dom) {
handler.handler.remove();
}
});
}
}
});
And then you can use it:
on2(dom.byId("test"), "click", function() {
console.log("test 1 2 3"); // Old event handler
});
on2.off(dom.byId("test"), "click"); // Remove old event handlers
on2(dom.byId("test"), "click", function() {
console.log("test 4 5 6"); // New event handler
});
This should work fine, as you can see in this fiddle: http://jsfiddle.net/X7H3F/
btn = new Button();
btn.attr('id','myButton');
query("#myButton").on('click', function () {console.log('do something');});
Do the same thing when you want to replace your handler. Like,
query("#myButton").on('click', function () {console.log('do something different');});
Hope that helps :)
I am learning backbone.js and am quite new. I have a view that acts as a button:
simpleButton = Backbone.View.extend({
template: "<button class='${classes}'>${text}</button>",
el: $("body"),
events: {
"click": "onClick",
"focus": "onFocus",
"blur": "onBlur"
},
initialize: function (args) {
_.bindAll(this, 'render');
this.rendered = false;
this.text = args.text || 'button';
this.classes = args.classes || [];
this.classes.push('ui-button');
//console.debug("Wh.views.simpleButton.initialize classes ",this.classes);
if (args.autoRender === true) this.render();
},
render: function () {
//console.debug("Wh.views.simpleButton.render classes ",this.classes);
if (this.rendered === false) {
$.tmpl(
this.template, {
classes: this.classes.join(' '),
text: this.text
}
).appendTo(this.el);
this.rendered = true;
}
},
//event handlers
onClick: function (ev) {
console.debug(this);
alert("click on ", ev, this);
},
onFocus: function (ev) {
////console.debug(ev);
},
onBlur: function (ev) {
}
});
My problem is that if I create two buttons, and click just one of them, I get the alert box two times, and the debug showing me "this" shows the first button first, and the second button next.
Am I missing something?
The events you define are bound to the "el" property of your view. In your case it is "body" so when you fire up click with 2 simpleButton views instantiated, you have 2 of them listening for the same event.
Each view you instantiate should represent one and only one DOM element defined by the el property. So if you want to create a button view (not sure this is 'best practice' in a real program) you could have :
SimpleButton = Backbone.View.extend({
template : "<button class='${classes}'>${text}</button>",
tagName : "div", // defines the html tag that will wrap your template
className: ".buttonbox",
...
});
mybtn = new SimpleButton();
mybtn.render().appendTo('body')
That way your click event will only concern the one div.buttonbox inside of which your button lives.
Notice : Backbone idea of the render function is creating an html string you'll use afterwards to append prepend or whatever in the DOM. That way if you create many you can do it so you only refresh the DOM once (refreshing the DOM is expensive)...
Use this in your View .it will unbind the click events
initialize : function() {
$(this.el).unbind("click");
}
Just a thought that creating a Backbone.View for each and every button in your app could be a performance overkill and you can't leverage the "delegate" feature in jQuery. I'd instead create a Backbone.View for the parent element of those buttons instead.
Of course, if you have a few special buttons with complicated logic then they probably do deserve their own View classes. :)
Give your buttons unique ids, for example <button id="button1"> and <button id="button2">, then in your events hash, you need to specify the click event and the id of the button you want to handle that event for, e.g:
events : {
"click #button1" : "onClick",
"click #button2" : "doSomethingElse"
}
Now this will call onClick() only when you click on the button with id=button1 and call doSomethingElse() when you click on the button with id=button2