How I can add functionality to a jQuery method? For example, when using hide on an element (<a> or <p> or something else):
HTML
click me
jQuery
$("a").hide()
I tried creating a type of plugin, but I want to keep the native functionality and then add more.
jQuery.fn.extend({
hide: function () {
alert("hidden element: " + $(this));
}
});
The easiest way, of course, would be to give it a different name. If you really don’t want to do that, you can keep a reference to the original method, then pass both this and any received arguments using Function.prototype.apply:
var originalHide = jQuery.fn.hide;
jQuery.fn.hide = function () {
alert('hidden element: ' + this); // == $(this); neither is meaningful
return originalHide.apply(this, arguments);
};
Related
I am trying to get access and modify this function (second one) in jqueryUI. I have tried everything. What I want to do is to add something in the function. I know it is possible and I need to do something like this :
var snapIt = $.ui.draggable.prototype.drag;
$.ui.draggable.prototype.drag = function() {
console.log("hello"); // exemple of a thing I want to add
// Now go back to jQuery's original function()
return snapIt.apply(this, arguments);
};
On top it will get the function add in console "hello" and then continue normally with the rest of the jQuery function. But I just can't find this function. I know this doesn't work: $.ui.draggable.prototype.start and dozens of others I tried.
$.ui.plugin.add("draggable", "snap", {
start: function( event, ui, i ) {
click.x2 = event.clientX;
click.y2 = event.clientY;
var o = i.options;
i.snapElements = [];
$(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() {
var $t = $(this),
$o = $t.offset();
if (this !== i.element[0]) {
//...........
I don't want the drag: function(event, ui) {..... I need to modify the function because I use ui.position = {left..... and it make the snap method not work. The only way was to change the drag method. I know it work because I tried manualy. But changing the library might be problematic for futur dev.
Don't know if I am clear but basically I want the path to $.ui.plugin.add("draggable", "snap", {//stuff}); in jqueryUI library
Thx in advance
There are 3 different sources of behaviors that are called on the different events in jquery-ui, each with its own structure.
First you have the "private" functions, that are defined on the prototype and that are called directly on native events. These are on $.ui.draggable.prototype and begin with a _ character. For example you have $.ui.draggable.prototype._mouseDrag function.
These are called directly and are the ones triggering the events. They are not directly accessible from the options.
Then you have the plugins functions. These are the ones that are added using add. Basically what add does is that it sets functions to be called on the events that are accessible via the options. And these plugins callbacks are called if their corresponding option is true. The structure goes like this:
Each plugin is an object that defines a callback for different
events. The events available are the same that are accessible in the options. For draggable, you have start, drag and stop.
These callbacks are pushed in arrays contained by
$.ui.draggable.plugins object, in which each property is one of the available event.
A function goes through the event array and validates if the plugin
should be ran based on the option set.
Once the plugins are done, the options callbacks are called. These are the ones that you set in the options.
So depending what ou want to modify, you can either change the prototype:
$.ui.draggable.prototype._mouseDrag
Or you can add a plugin. Like this:
$.ui.plugin.add( "draggable", "customPlugin", {
drag: function(event, ui, draggable){
console.log("I'm the custom plugin");
});
Or you can modify snap plugin. This one is a bit more complicated, and much less reliable since the functions are stored in arrays and not in an object, and they are added. The structure goes like this:
Each property key is an event, and every property is an array of
arrays.
Each of the array first element is the name of the option associated
with the callback, that is the second element of the array.
So the drag callback associated to snap is $.ui.draggable.prototype.plugins.drag[2], because it's the third callback that's been added to drag event. $.ui.draggable.prototype.plugins.drag[2][0] is the string "snap", which is used to check if the option was set to true. And the callback is $.ui.draggable.prototype.plugins.drag[2][1]. So you can modify it like this:
$.ui.draggable.prototype.plugins.drag[2][1] = function(){
console.log("I'm the modified plugin");
}
If you want a better control, you can iterate through $.ui.draggable.prototype.plugins.drag array and check the first element to make sure you modify the proper plugin.
Obviously, as you tried, you need to store the original callback if you want the behavior to work.
See here how this goes:
$.ui.plugin.add("draggable", "customPlugin", {
drag: function() {
console.log("%c I'm a custom plugin", 'color: blue');
}
});
var _temp = $.ui.draggable.prototype.plugins.drag[2][1];
$.ui.draggable.prototype.plugins.drag[2][1] = function() {
console.log("%c I'm the modified snap plugin drag callback", 'color: red');
_temp.apply(this, arguments);
}
$('div').draggable({
snap: true,
customPlugin: true,
drag: function() {
console.log("%c I'm the options callback", 'color: green');
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<div>Drag me</div>
<div>Or me</div>
I've inherited some JS (that I can't change) that fires a bunch of events:
jQuery(document).trigger('section:' + section);
// where "section" changes dynamically
And I want to observe for ALL of these events, and parse out the value for section, and do something different depending on it's contents.
If it didn't change I could do this:
jQuery(document).on('section:top', doStuff );
But how do I observe an event if I only know the first part of that event name?
You cannot listen for all events in the style of $().on('section:*'), unfortunately. If you can change the code, I would do the following:
jQuery(document).trigger({
type: 'section',
section: section
});
Then you listen for it and don't need to parse anything out
jQuery(document).on('section', function(e){
if (e.section === 'top') {
// Something happened to the top section
}
});
If you want to minimize your code changes, leave the old event in there, that way existing code will be unaffected.
A different approach would be to use event namespaces.
jQuery(document).trigger('section.' + section);
jQuery(document).on('section', function(e){
if (e.namespace === 'top') {
// Something happened to the top section
}
});
I, however, prefer the first approach because event namespaces are most commonly used for a different purpose: to be able to remove events without being forced to keep a reference to the handler itself. See http://css-tricks.com/namespaced-events-jquery/ and http://ejohn.org/apps/workshop/adv-talk/#13. I prefer to use styles that other developers are used to, if they do the job.
I'm really not sure about your use case but you could overwrite $.fn.trigger method:
(function ($) {
var oldTrigger = $.fn.trigger;
$.fn.trigger = function () {
if (arguments[0].match(/^section:/)) {
doStuff(arguments[0].split(':')[1]);
}
return oldTrigger.apply(this, arguments);
};
})(jQuery);
var section = "top";
jQuery(document).trigger('section:' + section);
function doStuff(section) {
alert(section);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Here's what I ended up doing.
It's a combination of Juan Mendes's solution, and using a method from the prototype library
Originally, there was a function that ran this code:
myObject.adjustSection(section) {
jQuery(document).trigger('section:' + section);
}
// I couldn't edit this function
So I extended the function with prototype's wrap method, since my project used prototype as well as jQuery.
// My custom function wrapper
// extend adjustSection to include new event trigger
myObject.prototype.adjustSection = myObject.prototype.adjustSection.wrap(
function(parentFunction, section) {
// call original function
parentFunction(section);
// fire event w/section info
jQuery(document).trigger({
type: 'adjustSection',
section: section
});
}
);
Then, it runs the original one, but also fires my custom event that includes the section info.
Now, I can do this to observe that event and get the section type:
jQuery(document).on('adjustSection', function(event) {
event.section; // contains the section I need
});
Of course, this means I have to utilize both prototype and jquery within the same scope, which isn't the best thing in the world. But it worked.
I have a number of different "control elements" on my application: dropdowns, tabs, menus, etc. On same pages, there are many of the same control. When writing JavaScript to handle the different events associated with each of these controls, I'm trying to make my code as DRY as possible. One of the challenges is modularizing my JQuery code so that events that occur within a specific control only effect that control.
Take this initial code for example, all it does is open a dropdown menu when it is clicked. I'm used to writing just a ton of different anonymous functions triggered by different events so this type of JQuery is really new to me.
var dropdown = {
init: function() {
$(".dropdown").click(".dropdown", dropdown.openDropdown);
},
openDropdown: function() {
$(this).children(".dropdown-menu").show();
$(this).addClass("open");
}
}
$(document).ready(dropdown.init);
My question is, within this dropdown variable, I want to be able to save/track different pieces of the dropdown control currently being acted upon. For example, I might want to write:
var menu = $(this).children(".dropdown-menu");
somewhere in this chunk so that I could refer back to this menu while calling different functions. I just cannot figure out syntactically how to do this. Any help/guidance is welcomed! Thanks.
Something I like about coffeescript is how it allows you to easily create classes. Classes in coffee are just a simplified way of generating "modules" using javascript's prototypal inheritance. More on that here: http://coffeescript.org/#classes
But how YOU could implement more modular jQuery code is by doing something like this:
http://jsfiddle.net/x858q/2/
var DropDown = (function(){
// constructor
function DropDown(el){
this.el = $(el);
this.link = this.el.find("a");
this.menu = this.el.find(".dropdown-menu");
this.bindClick();
}
// method binding click event listener
DropDown.prototype.bindClick = function(){
var _this = this;
this.link.click(function(e){
_this.openDropDown();
e.preventDefault();
});
};
// click event handler
DropDown.prototype.openDropDown = function(){
this.menu.show();
this.link.addClass("open");
};
return DropDown;
})();
$(function(){
// init each .dropdown element as a new DropDown
$(".dropdown").each(function(){
new DropDown(this);
});
});
You've touched on a pattern I've been leaning towards more and more. Basically, create a JavaScript object that acts as a controller given a root element on the page. Since this "dropdown" is pretty generic, it could probably have access to the whole page and be perfectly happy. I would also recommend making these "modules" instantiable objects, as this allows you to write unit tests easier:
function DropdownModule() {
this.handleClick = this.handleClick.bind(this);
}
DropdownModule.prototype = {
element: null,
$element: null
constructor: DropdownModule,
init: function(element) {
this.setElement(element);
this.$element.on("click", ".dropdown", this.handleClick);
},
handleClick: function(event) {
var $dropdown = $(event.currentTarget);
$dropdown.children(".dropdown-menu").show();
$dropdown.addClass("open");
this.someOtherFunction($dropdown);
},
someOtherFunction($dropdown) {
// do something with $dropdown
},
setElement: function(element) {
this.element = element;
this.$element = $(element);
}
}
Then to use it, just throw this anywhere after the definition for Dropdown:
var dropdown = new Dropdown()
.init(document.documentElement);
The document.documentElement property refers to the <html> tag and is available the moment JavaScript begins executing.
As a side note, I've built a whole framework around this approach: Foundry. Other frameworks, like Angular, take a similar approach as well.
What you want sounds like exactly what jQuery UI has already implemented in their Widget Factory.
I'd highly recommend you check it out since what you'd end up with it something like
$.widget( 'dropdown', {
_create: function() {
this.element.addClass( 'dropdown' );
this._on({
'click': '_clicked'
});
},
_clicked: function( event ) {
// `this` is an instance of dropdown here, not the element
this.clicked = !this.clicked;
this.element.toggleClass( 'clicked', this.clicked );
},
_destroy: function() {
this.element.removeClass( 'dropdown' );
}
});
Then you would use it like any other jQuery UI Widget
$( '#some-element' ).dropdown();
Consider this code running on page ready:
$("input.extraOption[checked]").each(function() {
console.log($(this));
$(this).closest('.questionRow').find('.date').attr("disabled", true);
$(this).closest('.questionRow').find('.dateSpan').hide();
$(this).closest('.questionRow').find('.date').val("");
$(this).closest('.questionRow').find('.textareaResize').attr("disabled", true);
$(this).closest('.questionRow').find('.textareaResize').val("");
$(this).closest('.questionRow').find('.text').attr("disabled", true);
$(this).closest('.questionRow').find('.text').val("");
$(this).closest('.questionRow').find('.checkbox').attr("disabled", true);
});
I want to refactor these calls as they are used elsewhere as well, so I created the following function:
jQuery.fn.extend({
toggleAnswers: function (disable) {
var group = $(this);
group.find('.date').attr("disabled", disable);
group.find('.date').val("");
group.find('.textareaResize').attr("disabled", disable);
group.find('.textareaResize').val("");
group.find('.text').attr("disabled", disable);
group.find('.text').val("");
group.find('.checkbox').attr("disabled", disable);
if(checkedStatus === true){
group.find('.dateSpan').hide();
}else{
group.find('.dateSpan').show();
}
return group;
}
});
I then proceed to changing the 8 $(this).closest(...) calls with:
$(this).closest('.questionRow').toggleAnswers(true);
Here's the problem: on a page with 5 elements that match the selector, only the first one suffers the changes (in other words I only get one console.log)! Before the refactor I get the expected change in all 5 elements.
What is being done wrong in this refactor?
checkStatus isn't defined anywhere, causing an exception. You seem to want to use disable instead.
On a side note, this already refers to the jQuery collection that this method is called on, so wrapping this in a jQuery object ($(this)) is redundant/unnecessary. Note that this is specifically inside of a $.fn method, not normal jQuery methods. For example, inside event handlers, this refers to the DOM element, so you need to wrap it in $(this) in order to call jQuery methods on it.
Also, disabling an element should be done with .prop("disabled", true/false): .prop() vs .attr()
You can also combine any selectors that you call the same jQuery method on. For example, group.find('.date').val(""); and group.find('.text').val(""); can be combined into: group.find(".date, .text").val("");
Putting all of those suggestions together, as well as iterating over this (for consistency and scalable sake), here's what I'd use:
jQuery.fn.extend({
toggleAnswers: function (disable) {
return this.each(function (idx, el) {
var $group = $(el);
$group.find(".date, .text, .textareaResize, .checkbox").prop("disabled", disable);
$group.find(".date, .textareaResize, .text").val("");
$group.find(".dateSpan").toggle(!disable);
});
}
});
And depending on how you use it, I'd set it up like:
var targets = $("input.extraOption[checked]"),
toggler = function () {
$(this).closest(".questionRow").toggleAnswers(this.checked);
};
targets.each(toggler).on("click", toggler);
DEMO: http://jsfiddle.net/XdNDA/
Note: This question uses jQuery but the question has nothing to do with jQuery!
Okay so I have this object:
var box = new BigBox();
This object has a method named Serialize():
box.AddToPage();
Here is the method AddToPage():
function AddToPage()
{
$('#some_item').html("<div id='box' onclick='this.OnClick()'></div>");
}
The problem above is the this.OnClick() (which obviously does not work). I need the onclick handler to invoke a member of the BigBox class. How can I do this?
How can an object refer to itself in an event handler?
You should attach the handler using jQuery:
function AddToPage()
{
var self = this;
$('#some_item').empty().append(
$("<div id='box'></div>")
.click(function() { self.OnClick(someParameter); })
);
}
In order to force the event handler to be called on the context of your object (and to pass parameters), you need to add an anonymous function that calls the handler correctly. Otherwise, the this keyword in the handler will refer to the DOM element.
Don't add event handlers with inline code.
function AddToPage()
{
$('#some_item').html("<div id='box'></div>");
$('#box').click(this.OnClick);
}
EDIT:
Another way (avoids the extra select):
function AddToPage()
{
var div = $('<div id="box"></div>'); // probably don't need ID anymore..
div.click(this.OnClick);
$('#some_item').append(div);
}
EDIT (in response to "how to pass parameters");
I'm not sure what params you want to pass, but..
function AddToPage()
{
var self = this, div = $('<div></div>');
div.click(function (eventObj) {
self.OnClick(eventObj, your, params, here);
});
$('#some_item').append(div);
}
In jQuery 1.4 you could use a proxy.
BigBox.prototype.AddToPage= function () {
var div= $('<div>', {id: box});
div.click(jQuery.proxy(this, 'OnClick');
div.appendTo('#some_item');
}
You can also use a manual closure:
var that= this;
div.click(function(event) { that.OnClick(event); });
Or, most simply of all, but requiring some help to implement in browsers that don't yet support it (it's an ECMAScript Fifth Edition feature):
div.click(this.OnClick.bind(this));
If you are using jQuery, then you can separate your code from your markup (the old seperation of concerns thing) like this
$(document).ready(function() {
var box = new BigBox();
$('#box').click(function() {
box.serialize();
});
});
You only need to add the click handler once for all divs with id of box. And because the click is an anonymous function, it gets the scope of the function it is placed in and therefore access to the box instance.