I have created a class in mootools that when the initialize() method is first called it creates a div elements which is then appended to the document.body. I then attach a context menu handler which will call functions when an option from the context menu is selected in the browser.
The trouble I am having is that the context menu handler will not actually call any functions and I can't quite figure out why and was wondering if anyone here could spot the problem?
Here is the class I have created and the attached context-menu handler, some of the other code has been removed for brevity:
var uml_Canvas = new Class({
initialize: function()
{
this.mainCanvasDiv = document.createElement("div");
this.mainCanvasDiv.id = "mainCanvas";
this.mainAppDiv.appendChild(this.mainCanvasDiv);
this.paper = Raphael(this.mainCanvasDiv.id, 500, 400);
this.paper.draggable.enable();
$("#"+this.mainCanvasDiv.id).contextMenu('canvasPanel_Menu',
{
bindings:
{
'clear': function(t)
{
this.clearPaper();
}
}
});
},
clearPaper : function()
{
this.paper.clear();
}
});
So a quick overview, an object is created which creates a div and then appends it to the body. The div then has a context-menu assigned. When the 'clear' option is called the method clearPaper() should be called be for some reason it is not. If, however, I replace the this.clearPaper(); line with a simple alert() call, it does indeed run.
Can anyone see a reason why it is not possible to call a method?
BTW the error I get is this.clearPaper is not a function if that helps.
Try binding "this" to your clear function:
'clear': function(t)
{
this.clearPaper();
}.bind(this)
This takes the "this" scope and allows the anonymous function to use it as if it were a member of that class.
Note that you have to do this whenever you try to use "this." inside of any anonymous function. For instance, if you have inside a class:
method: function() {
button.addEvent('click', function(e) {
new Request({
onComplete: function(res) {
this.process_result(res);
}
}).send();
});
},
process_results: function(res) {...}
You have to bind all the way down:
method: function() {
button.addEvent('click', function(e) {
new Request({
onComplete: function(res) {
this.process_result(res);
}.bind(this)
}).send();
}.bind(this));
},
process_results: function(res) {...}
Notice the new bind()s on the event function and the onComplete function. It may seem like an annoying extra step, but without doing this, you'd have scope free-for-all. Mootools makes it extremely easy to take your class scope and attach it to an anonymous function.
Related
It's been a while since I've done a jQuery plugin and I'm working off a pretty common boilerplate with options and internal events. In one of the internal methods I need to trigger a custom event so that other pages can capture the event and work with it. More specifically in this usage, at the end of drawing on a canvas element I want to be able to capture the event outside of the plugin in order to grab the canvas content for sending elsewhere agnostic of the plugin itself.
However, it doesn't appear that the trigger call is either firing or finding the bound event from the other page. None of the console messages show up in Firebug.
Here's my sample plugin (simplified):
; (function ($, window, document, undefined) {
"use strict";
var $canvas,
context,
defaults = {
capStyle: "round",
lineJoin: "round",
lineWidth: 5,
strokeStyle: "black"
},
imgElement,
options,
pluginName = "myPlugin";
function MyPlugin(element, opts) {
this.imgElement = element;
this.options = $.extend({}, defaults, opts);
this._defaults = defaults;
this._name = pluginName;
this.init();
}
$.extend(MyPlugin.prototype, {
init: function () {
var $imgElement = $(this.imgElement);
$canvas = $(document.createElement("canvas")).addClass("myPluginInstances");
$imgElement.after($canvas);
context = $canvas[0].getContext("2d");
$canvas.on("mousedown touchstart", inputStart);
$canvas.on("mousemove touchmove", inputMove);
$canvas.on("mouseup touchend", inputEnd);
}
});
$.fn.myPlugin = function (opts) {
return this.each(function () {
if (!$.data(this, "plugin_" + pluginName)) {
$.data(this, "plugin_" + pluginName, new MyPlugin(this, opts));
}
});
};
function inputStart(event) {
//...processing code
}
function inputMove(event) {
//...processing code
}
function inputEnd(event) {
//...processing code
// Trigger custom event
$(this.imgElement).trigger("mydrawevent", [this.toDataURL()]);
}
}(jQuery, window, document));
Then from a separate page in the document.ready the event is bound:
$(".myPluginInstances").myPlugin().on("mydrawevent", function (e, data) {
console.log("mydrawevent");
console.log(data);
});
From Firebug I am seeing that the imgElement does have the listener bound:
mydrawevent
-> function(a)
-> function(e, data)
I've tried quite a few things such as calling the trigger on different DOM elements, passing the event parameter data in and out of the array, defining a callback method instead (which had its own issues), and more. I have a feeling the problem is something dumb and right in front of me but I could use more sets of eyes to double check my sanity.
As further explanation for my response to Vikk above, the problem was indeed scoping and understanding which objects were bound to what parts of the plugin. In my case, the internal methods that are private event handlers were bound to my canvas element but the plugin itself was being instantiated on the img element which is at least a temporary requirement of this particular implementation.
Based on this, from the internal event handler the use of $(this) meant that it was trying to use trigger on my canvas element and not the img element which had the mydrawevent listener attached to it from outside the plugin.
I'm using selecter jquery. I initialize it by typing the code
$("select").selecter();
I need to make sure that the formstone selecter jquery library has completed before i start appending elements. So what i did is to is use the $.when function
initialize: function(){
$.when($("select").selecter()).then(this.initOptions());
},
initOptions: function(){
this.$el.find('.selecter').addClass('something');
}
But this does not work. How can i wait while formstone selecter is doing its thing before i execute another function?
Thanks,
UPDATE
Here's the update of what i did but it does not work.
initialize: function(){
$("select").selecter({callback: this.initOptions });
},
initOptions: function(){
this.$el.find('.selecter').addClass('something');
}
There is a callback option.
The function passed as a callback will receive the newly selected value as the first parameter
Should be $("select").selecter(callback: function() { alert('callback fired.') });
or as shown
$("select").selecter({
callback: selectCallback
});
function selectCallback(value, index) {
alert("VALUE: " + value + ", INDEX: " + index);
}
The problem which I think regarding the callback edited code is that this can refer to anything. Try the following code
var selectorObj = {
initialize: function(){
$("select").selecter({callback: selectorObj.initOptions });
},
initOptions: function(){
this.$el.find('.selecter').addClass('something');
}
};
Created a working fiddler for you http://jsfiddle.net/6Bj6j/
The css is out of shape. Just select what is poping up when you click on the dropdown. You will get an alert which is written in the callback.
The problem with the provided snippet is the scope of the callback:
var selectorObj = {
initialize: function(){
$("select").selecter({ callback: selectorObj.initOptions });
},
initOptions: function(){
// 'this' refers to the "$('.selecter')" jQuery element
this.addClass('something');
}
};
However if you just need to add a class to the rendered element, you should use the 'customClass' option:
$("select").selecter({
customClass: "something"
});
If you need to do more, you can always access the Selecter element directly:
var $selecter = $("select").selecter().next(".selecter");
$selecter.addClass("something").find(".selecter-selected").trigger("click");
Sidenote: I'm the main developer of Formstone. If you have any suggestions for new features or better implementation, just open a new issue on GitHub.
I'm using require.js with backbone.js to structure my app. In one of my views:
define(['backbone', 'models/message', 'text!templates/message-send.html'], function (Backbone, Message, messageSendTemplate) {
var MessageSendView = Backbone.View.extend({
el: $('#send-message'),
template: _.template(messageSendTemplate),
events: {
"click #send": "sendMessage",
"keypress #field": "sendMessageOnEnter",
},
initialize: function () {
_.bindAll(this,'render', 'sendMessage', 'sendMessageOnEnter');
this.render();
},
render: function () {
this.$el.html(this.template);
this.delegateEvents();
return this;
},
sendMessage: function () {
var Message = Message.extend({
noIoBind: true
});
var attrs = {
message: this.$('#field').val(),
username: this.$('#username').text()
};
var message = new Message(attrs);
message.save();
/*
socket.emit('message:create', {
message: this.$('#field').val(),
username: this.$('#username').text()
});
*/
this.$('#field').val("");
},
sendMessageOnEnter: function (e) {
if (e.keyCode == 13) {
this.sendMessage();
}
}
});
return MessageSendView;
});
When keypress event is triggered by jquery and sendMessage function is called - for some reason Message model is undefined, although when this view is first loaded by require.js it is available. Any hints?
Thanks
Please see my inline comments:
sendMessage: function () {
// first you declare a Message object, default to undefined
// then you refrence to a Message variable from the function scope, which will in turn reference to your Message variable defined in step 1
// then you call extend method of this referenced Message variable which is currently undefined, so you see the point
var Message = Message.extend({
noIoBind: true
});
// to correct, you can rename Message to other name, e.g.
var MessageNoIOBind = Message.extend ...
...
},
My guess is that you've bound sendMessageOnEnter as a keypress event handler somewhere else in your code. By doing this, you will change the context of this upon the bound event handler's function being called. Basically, when you call this.sendMessage(), this is no longer your MessageSendView object, it's more than likely the jQuery element you've bound the keypress event to. Since you're using jQuery, you could more than likely solve this by using $.proxy to bind your sendMessageOnEnter function to the correct context. Something like: (note - this was not tested at all)
var view = new MessageSendView();
$('input').keypress(function() {
$.proxy(view.sendMessageOnEnter, view);
});
I hope this helps, here is a bit more reading for you. Happy coding!
Binding Scopes in JavaScript
$.proxy
I have a problem with event object passed to the function in drop event. In my code, div#dropArea has it's drop event handled by firstDrop function which does some animations and then calls the proper function dropFromDesktop which handles the e.dataTransfer.files object. I need this approach in two separate functions because the latter is also used further by some other divs in the HTML document (no need to duplicate the code). First one is used only once, to hide some 'welcome' texts.
Generally, this mechanism lets you drag files from desktop and drop them into an area on my website.
Here's, how it looks (in a shortcut):
function firstDrop(ev) {
var $this = $(this);
//when I call the function here, it passes the event with files inside it
//dropFromDesktop.call($this, ev);
$this.children('.welcomeText').animate({
opacity: '0',
height: '0'
}, 700, function() {
$('#raw .menu').first().slideDown('fast', function() {
//when I call the function here, it passes the event, but 'files' object is empty
dropFromDesktop.call($this, ev);
});
});
}
function dropFromDesktop(ev) {
var files = ev.originalEvent.dataTransfer.files;
(...) //handling the files
}
$('#dropArea').one('drop', firstDrop);
$('some_other_div').on('drop', dropFromDesktop);
The problem is somewhere in jQuery.animation's callback - when I call my function inside it, the event object is passed correctly, but files object from dataTransfer is empty!
Whole script is put inside $(document).ready(function() { ... }); so the order of function declarations doesn't matter, I guess.
I suspect your problem is related with the lifetime of the Event object. Unfortunately, I have no clue about the cause of it. But, there is a way to workaround it that I can think of and it is keeping a reference to Event.dataTransfer.files instead.
var handleFileList = function(fn) {
return function(evt) {
evt.preventDefault();
return fn.call(this, evt.originalEvent.dataTransfer.files);
};
};
var firstDrop = function(fileList) { ... }
var dropFromDesktop = function(fileList) { ... }
$('#dropArea').one('drop', handleFileList(firstDrop));
$('some_other_div').on('drop', handleFileList(dropFromDesktop));
Sorry for how stupid this is going to sound. My JS vocabulary is terrible and I had absolutely no idea what to search for.
I'm using jQuery.
So I've got this code:
var example = {
open: function(element){
alert(element.text());
},
init: function(){
$("a").click(example.open);
}
};
$(document).ready(function(){example.init();)
So here's the problem: I want to pass an argument to example.open() when I click the "a" element. It doesn't seem like I can, though. In order for the example.open method to just…exist on page-load and not just run, it can't have parentheses. I think. So there's no way to pass it an argument.
So I guess my question is…how do you pass an argument to a function that can't have parentheses?
Thanks so much.
Insert another anonymous function:
var example = {
open: function(element){
alert(element.text());
},
init: function(){
$("a").click(function()
{
example.open($(this));
});
}
};
You can also try this version because jQuery set the function's context (this) to the DOM element:
var example = {
open: function(){
alert($(this).text());
},
init: function(){
$("button").click(example.open);
}
};
Since jQuery binds the HTML element that raised the event into the this variable, you just have to pass it as a regular parameter:
var example = {
open: function(element){
alert(element.text());
},
init: function(){
$("a").click(function() {
// jQuery binds "this" to the element that initiated the event
example.open(this);
});
}
}
$(document).ready(function(){example.init();)
You can pass the anchor through its own handler:
var example = {
open: function( element ){
alert(element.text());
},
init: function(){
$("a").on("click", function() {
example.open( $(this) );
});
}
};
$(document).ready(function() {
example.init();
});
I don't understand what you actually want to do;
however, I can give a try:
var example = {
open: function(event){
event.preventDefault();
alert($(event.target).text()+' : '+event.data.x);
},
init: function(){
$("a").bind('click',{x:10},example.open);
}
};
$(example.init);
demo:
http://jsfiddle.net/rahen/EM2g9/2/
Sorry, I misunderstood the question.
There are several ways to handle this:
Wrap the call in a function:
$('a').click( function(){ example.open( $(this) ) } );
Where $(this) can be replaced by your argument list
Call a different event creator function, which takes the arguments as a parameter:
$('a').bind( 'click', {yourvariable:yourvalue}, example.open );
Where open takes a parameter called event and you can access your variable through the event.data (in the above it'd be event.data.yourvariable)
Errors and Other Info
However your element.text() won't just work unless element is a jQuery object. So you can jQueryify the object before passing it to the function, or after it's received by the function:
jQuery the passed object:
function(){ example.open(this) } /* to */ function(){ example.open($(this)) }
jQuery the received object:
alert(element.text()); /* to */ alert($(element).text());
That said, when calling an object without parameters this will refer to the object in scope (that generated the event). So, really, if you don't need to pass extra parameters you can get away with something like:
var example = {
open: function(){ // no argument needed
alert($(this).text()); // this points to element being clicked
},
init: function(){
$("a").click(example.open);
}
};
$(document).ready(function(){
example.init();
}); // your ready function was missing closing brace '}'