Handling "this" with RequireJS and JQuery onclick events - javascript

I am updating an app to use RequireJS and move functions/variables out of the global context. I am not sure how to structure things when using JQuery click handlers so that my functions have access to the data-* attributes on the HTML object that triggered the click AND have access to properties inside of my module.
Below is a simple test case I put together to demonstrate what I am trying to do.
I want a function called on the click event for the div that is able to access both the data-value attribute on the div that triggered it and the packageVar property inside the module I'm loading with RequireJS.
Here is my index.html:
<!DOCTYPE html>
<head>
<title>Test.Next</title>
</head>
<body>
<script data-main="js/test-require" src="js/require.js"></script>
<div id="test-no-bind" data-value="value">Test No Bind</div>
<div id="test-bind" data-value="value">Test Bind</div>
</body>
</html>
here is the test-require.js:
requirejs.config({
"baseUrl": 'js',
"paths": {
"jquery": "https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min",
"domReady": 'vendor/domReady'
}
});
requirejs(['maintest','domReady!'], function(main) {
main.initialize();
});
And finally my maintest.js:
define(['jquery','domReady!'], function($) {
return {
'packageVar' : 'test',
buttonClick : function() {
console.log($(this).attr("data-value"));
console.log(this.packageVar);
},
initialize: function() {
$("#test-no-bind").click(this.buttonClick);
$("#test-bind").click(this.buttonClick.bind(this));
}
};
});
The test-no-bind div results in:
value
undefined
The test-bind div results in:
undefined
test

You're trying to access a properties from two different objects using the same context this. So in the no-bind case you're getting correctly the div element value for data-value attribute because inside jquery click function the this context it's the element where the event is produced, however you're getting and undefined for the other object property because the element doesn't have the packageVar property. For bind case where you are using bind function you're passing your object as this context so you've a value for packageVar but not for element attribute.
Try using the correct context to call buttonClick and passing the element context to the function in order to get each value correctly from his source:
define(['jquery','domReady!'], function($) {
return {
'packageVar' : 'test',
buttonClick : function(element) {
console.log($(element).attr("data-value"));
console.log(this.packageVar);
},
initialize: function() {
var that = this;
$("#test-no-bind").click(function() {
that.buttonClick(this);
});
$("#test-bind").click(function(){
that.buttonClick(this);
});
}
};
});
Try with this JSFIDDLE
Hope this helps,

Related

Cannot read property 'dropdown' of undefined

I'm trying to transform my code into a more plugin type of code, so everything will be separated, in case I change class names in the future.
For some reason, in my code, I get Cannot read property 'dropdown' of undefined.
My guess is, the function Navigation.bindEvents() runs before I set the config, so It can't find it... But I don't know how to solve it.
Here's my Navigation.js file:
let Navigation = {
config: {},
init(config) {
this.config = config;
this.bindEvents();
},
bindEvents() {
$(this.config.trigger).on('click', this.toggleNavigation);
$(document).on('click', this.hideAllDropdowns);
},
toggleNavigation(event) {
// Store the current visible state
var visible = $(this).siblings(this.config.trigger).hasClass('visible');
// Hide all the drop downs
this.hideAllDropdowns();
// If the stored state is visible, hide it... Vice-versa.
$(this).siblings(this.config.content).toggleClass('visible', !visible);
event.preventDefault();
event.stopPropagation();
},
hideAllDropdowns() {
$(this.config.dropdown + ' ' + this.config.content).removeClass('visible');
}
}
export default Navigation;
And here's my app.js file which I run all the init functions.
window.$ = window.jQuery = require('jquery');
import Navigation from './layout/navigation.js';
Navigation.init({
dropdown: '.dropdown',
trigger: '.dropdown-trigger',
content: '.dropdown-content'
});
I guess you got problem with the scope $(document).on('click', this.hideAllDropdowns);
Let's try
bindEvents() {
$(this.config.trigger).on('click', this.toggleNavigation);
$(document).on('click', this.hideAllDropdowns.bind(this));
},
UPDATE:
bindEvents() {
$(this.config.trigger).bind('click', {self:this}, this.toggleNavigation);
$(document).on('click', this.hideAllDropdowns.bind(this));
},
And replace all this.config by event.data.self inside toggleNavigation function
this in the context of toggleNavigation refers to the clicked element.
That is why you can do $(this).siblings(...) to get the sibling elements.
You need to have a reference to the Navigation object. Perhaps you can use the on syntax that allows you to pass extra data $(this.config.trigger).on('click', this, this.toggleNavigation);
Then rewrite the handler
toggleNavigation(event) {
//get the navigation reference
var nav = event.data;
// Store the current visible state
var visible = $(this).siblings(nav.config.trigger).hasClass('visible');
// Hide all the drop downs
nav.hideAllDropdowns();
// If the stored state is visible, hide it... Vice-versa.
$(this).siblings(nav.config.content).toggleClass('visible', !visible);
event.preventDefault();
event.stopPropagation();
},
The behavior of this is one of the hardest things to understand in JavaScript. Here this is obviously dynamic, which means that its value depends on where your method has been called...
let module = {
config() {
console.log(`config(): 'this' is 'module' ---> ${Object.is(this, module)}`);
console.log(`config(): 'this' is 'document' ---> ${Object.is(this, document)}`);
},
init() {
console.log(`init(): 'this' is 'module' ---> ${Object.is(this, module)}`);
console.log(`init(): 'this' is 'document' ---> ${Object.is(this, document)}`);
module.config();
}
};
$(document).ready(module.init);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Accessing a method of element inside a callback of listener for a node inside the same element

I am having issues accessing a method inside a dom in the callback of a node that I've attached a click listener to and I can't figure out why. I have tried the followings.
Here's the piece of code responsible to accessing the method inside callback:
<dom-module id="my-view2">
<template>
...html codes ...
</template>
<script>
Polymer({
is: 'my-view2',
... properties, etc ...
//******* trying to access a method *******************
handleClick: function(e) {
var btn = document.createElement("paper-button");
btn.addEventListener("click", function() {
var el = Polymer.dom(this).querySelector('#my-view2');
el.methodToAccess();
});
}
methodToAccess: function() {
console.log('success');
},
});
I have tried all of these:
this.$$('#my-view2")
document.querySelector("#my-view2");
Polymer.dom(this.root).querySelector('#my-view2');
Polymer.dom(this).querySelector('#my-view2');
this.getElementById('my-view2');
none of them works and all result in Cannot read property 'methodToAccess' of null. What am I doing wrong?
If you bind the callback function to the current this scope, you will have direct access to methodToAccess.
handleClick: function(e) {
var btn = document.createElement("paper-button");
btn.addEventListener("click", function() {
this.methodToAccess();
}.bind(this));
}
That being said you may want to do it the polymer way using imperative add/remove listeners:
this.listen(this.$.myButton, 'tap', 'onTap');
this.unlisten(this.$.myButton, 'tap', 'onTap');
HTH

Polymer Node to element attribute

I have next polymer element:
<link rel="import" href="../../lib/polymer/polymer.html">
<polymer-element name="ss-timeline" attributes="musicChooser photoSelector">
<template>
<div></div>
</template>
<script type="text/javascript">
(function() {
"use strict";
Polymer('ss-timeline', {
created: function(){
this.musicChooser = this.musicChooser || {};
this.photoSelector = this.photoSelector || {};
},
ready: function () {
console.log(this.musicChooser, this.photoSelector);
},
musicChooserChanged: function(oldVal, newVal) {
console.log('musicChooserChanged', oldVal, newVal);
}
});
})();
</script>
</polymer-element>
So I have to pass into the musicChooser and photoSelector Node objects like so:
<ss-music-chooser id="musicChooser"></ss-music-chooser>
<ss-timeline musicChooser="{{$.musicChooser}}"></ss-timeline>
But in console output I get:
Object {} Object {}
How can I pass Node objects by the attributes? Please help.
P.S. I saw that core-dropdown has same relatedTarget attribute:
<core-icon-button id="trigger" icon="menu"></core-icon-button>
<core-dropdown relatedTarget="{{$.trigger}}">
<core-menu>
<core-item>Cut</core-item>
<core-item>Copy</core-item>
<core-item>Paste</core-item>
</core-menu>
</core-dropdown>
So this should work in theory...
P.P.S. By the way if I pass in attribute musicChooser="123" I get it in my element. But node object ignored for some reason.
Your changed watcher should work, here's an example.
I'm wondering if the issue has to do with your use of automatic node finding. The $ is only going to work inside of another Polymer element (and possibly also inside of an auto-binding template, but I'd need to double check).

I can't create a class based on actual HTML element

I'm trying to create a simple gallery with prototype.js and script.aculo.us. To handle left and right arrow I made this code, but it doesn't work
Gallery.Arrow = Class.create(document.createElement('a'), {
initialize: function(listener) {
this.on('click', listener);
this.addClassName('xjsl-arrow');
}
});
this.on is undefined. I tryed Class.create($(document.createElement('a')), ..., or even Element.extend(this) in the initialize function, but nothing works.
If I tryed Event.Handler(this, 'click', listener) to, but the error come from element.attachEvent inside prototype.js library.
Is it possible to create a class based on HTML element ?
Try building the Class based on the Element.Methods namespace like this
Gallery.Arrow = Class.create(Element.Methods, {
initialize: function(element,listener) {
this.on(element,'click', listener);
this.addClassName(element,'xjsl-arrow');
}
});
jsfiddle example http://jsfiddle.net/rPLa8/

jQuery $(this) does not insert text into my element

I am currently adding flagging functionality to a project of mine, and I can't get jQuery's $(this) selector to work.
The goal of this is to change the text in the div from flag to flagged when the user clicks it, and the ajax query runs successfully. My HTML/PHP is:
<div class="flag" post_to_flag='".$post_to_flag."'>Flag</div>
And my javascript that deals with the div is:
$('.flag').live('click', function () {
$.post('../php/core.inc.php', {
action: 'flag',
post_to_flag: $(this).attr('post_to_flag')
}, function (flag_return) {
if (flag_return == 'query_success') {
$(this).text('flagged');
} else {
alert(flag_return);
}
});
});
I can't replace the text with flagged, but if I replace the this selector with the .flag selector, it will replace everything with the class of flag on the page.
I have checked, and the $(this) selector is getting the attribute of 'post_to_flag' just fine. Why is this happening, and how can I fix it?
You should add a context variable:
$('.flag').live('click', function () {
var $context = $(this);
$.post('../php/core.inc.php', {
action: 'flag',
post_to_flag: $context.attr('post_to_flag')
}, function (flag_return) {
if (flag_return == 'query_success') {
$context.text('flagged');
} else {
alert(flag_return);
}
});
});
You are calling multiple functions within your jQuery selection call. When you go into that $.post() function, your scope changes. this now refers to a different scope from when you were inside one().
#Moak's suggestion, if you set a variable to a jQuery object, it's probably best to denote the variable with a beginning $ just for potential clarity for future readers or yourself.
this inside the ajax callback is not the element, but it is the Ajax object itself.
You can use $.proxy to pass in the context.
Ref $.proxy
$('.flag').live('click', function () {
$.post('../php/core.inc.php',
{action: 'flag', post_to_flag: $(this).attr('post_to_flag')},
$.proxy(function(flag_return) {
if(flag_return == 'query_success'){
$(this).text('flagged'); //Now this here will represent .flag
}else{
alert(flag_return);
}
},this)); //Now here you are passing in the context of `.flag`

Categories