Is there a way to get a list of all objects using a specified plugin? I know i can add a class to each element when it's applied but i was wondering if there was an existing way...
thanks,
If you want to do this without using classes, you might want to sniff the plugin calls, like this:
var elemsCalled = []; // this will contain all elements upon which the plugin has been called
var orig = $.fn.somePlugin;
$.fn.somePlugin = function() {
elementsCalled.push(this);
return orig.apply(this, Array.prototype.slice.call(arguments)); // for chaining, as Alnitak noted
}
Now, whenever you call $.somePlugin, the element you call it on would be added to elemsCalled.
Related
I am trying to figure out how jQuery is both a function which accepts an argument and returns a value (selector -> array of elements), and also an object that can be extended with new methods:
$.fn.myMethod = function() {//do stuff}
As a company we are moving away from jQuery, because vanilla JS is so useable. However we have some jQuery plugins I wrote that we would like to keep using. I am re-writing them, but would like to keep the syntax for using them similar to how it was in JQ. I don't want to extend the Element object (element.myPlugin()) for obvious reasons, so I was thinking I would create my own wrapper like JQ. Ideally I could define the base/default function that returns an array of DOM elements like so:
const __ = function(element) {
if (typeof element === 'string') {
return document.querySelectorAll(element)
}
return [element]
}
But then later, this function could be extended with new methods:
__.myNewPlugin = function(text) {
this.forEach(el => el.innerText = text)
}
Where this is the array of DOM elements returned by the base/default function, so that later the rest of my team could use the new method like so:
__(document.querySelector('.thing')).myNewPlugin('Hi SO')
-or-
__('.thing').myNewPlugin('Hi SO')
If for some reason you think this is a bad idea, I'm happy to hear your reasoning, but please also post an example of how this is achieved. It's obviously possible (because JQ does it), so even if I decide not to go this route, I'd still like to learn how something like this could be implemented.
I'm using Famo.us + Angular. I'm able to retrieve the classList of a Surface by doing this:
$scope.findElement = function() {
var elem = $famous.find("#colored-bg")[0].renderNode; // Returns Surface
console.log(elem.classList); // RETURNS: ["purple-bg", "container-border", "box-shadow"]
};
You can't perform any of the operations on Famo.us object which you normal could to any other object on the DOM. For example, I thought I could add, remove, or replace classes, similar to this:
document.getElementById('id').classList.add('class');
document.getElementById('id').classList.remove('class');
add and remove do not exist, though. I can return the class list, and even individual items from the list (ex: Just the first class), but you cannot alter it. Any suggestions?
The setClasses method takes an array and you can set classes using:
renderNode.setClasses(['white-bg', 'big-text']);
Use addClass by passing the class name to add a class using:
renderNode.addClass('big-text');
Use removeClass by passing the class name to remove a class using:
renderNode.removeClass('big-text');
Use toggleClass by passing the class name to add/remove based on whether it exists:
renderNode.toggleClass('big-text');
Figured it out, courtesy of Tony Alves in the Famo.us Slack Chat:
renderNode.setClasses(['white-bg']);
This information was found in the github docs. So the entire function looks like this:
$scope.findElement = function() {
var elem = $famous.find("#colored-bg")[0].renderNode;
console.log(elem);
elem.setClasses(['white-bg']);
console.log(elem.classList); // RETURNS: ["white-bg"]
};
setClasses will accept an array of strings, which it them places into the Fa-Surface.
I was trying to use $.fn.show (and other jQuery functions) within higher-order functions.
What I originally wanted to have was a function that applies a given function to all elements returned by a collection of other functions applied to a given element. Something that would look like this:
function mapOn( func, genratingFunc, element ){
$(generatingFuncs).each(function(){
var buf = $(element);
while(buf.length){ // run as long as elements are returned
func(buf);
buf = this(buf);
}
});
}
I needed such a function to apply some functions to a couple of DOM nodes and their parents and/or children in a handy, expressive way. Let's say we want to hide the node with the ID hideMyFamily and its children. I don't know any handy way to do this with jQuery so I'd run hide() on $("#hideMyFamily").children() and on $("#hideMyFamily").children().children() and so on until the length of the collection was 0 (and on $("#hideMyFamily") itself of course).
Thing is, running mapOn( $.fn.show, [$.fn.children], $("#hideMyFamily") ) won't do the job since you apparently cannot just apply $.fn.show to an element/collection.
So what I came up with is this:
For each of the jQuery's functions that I need to specify another function (within global scope) that looks like this:
function _show(e){ $(e).show(); }
For each of the jQuery's "generating" functions I specify another "work-around function":
function _id(e){ return $(e); }
function _children(e){ return $(e).children(); }
And then I can specify my "multiMap" function which looks like this:
function multiMap(func, generators, elem){
$(generators).each(function(){
var buf = $(elem);
var buf2 = [];
while (buf.length && buf[0] !== buf2[0]) {
func(buf);
buf2 = buf;
buf = this(buf);
}
});
}
Now I can run my handy function multiMap(_show, [_id, _children], "hideMyFamily") to hide the element itself and all of its children.
Now, to get to the point, my question is: Is there any more elegant way to achieve the desired behaviour? Is there any jQuery magic I didn't take into account?
tl;dr Is there a handy way to use jQuery's functions like show() and hide() on nodes/collections in a way like $.fn.show( $("someElements") )?
Yes, it is possible.
You can do $.fn.show.call($("some-elements")).
I haven't fully gone through your more elaborate example, but that seems to be what you're looking for. And for your final example, you could write things as:
multiMap($.fn.show, [_id, _children], "hideMyFamily")
and then in mutliMap do f.call or something like that and I believe that would work.
I'm trying to add simple functions to the JavaScript DOM, e.g. an addClass function, I implemented this first with the following code:
Element.prototype.addClass = function(className) {
this.className += ' ' + className;
};
However after much reading (http://perfectionkills.com/whats-wrong-with-extending-the-dom/ was good) it seems this is a terrible way to extend the DOM for a number of reasons.
The above article states:
One of the most common alternatives to this whole mess of DOM
extension is object wrappers
Which is fine, apparently the general consensus is to use Object wrappers if you want to extend the DOM. The problem is I can't find any good examples anywhere on how you actually use object wrappers to extend the DOM ...
Can anybody give me an example of how to do so? Maybe using the above code?
Object wrappers are more expensive than extensions because you need to create a new object, but they are safer.
A simple implementation that wraps only a single element could look like this:
(function() {
window.wrap = function(el) {
return new Wrapper(el);
};
function Wrapper(el) {
this.element = el;
}
Wrapper.prototype.addClass = function(cls) {
if (this.element)
this.element.className += " " + cls;
}
Wrapper.prototype.swap = function(el) {
this.element = el;
}
})();
Then you could make a new wrapper, and to be more efficient, you could reuse it with various elements.
var wrp = wrap(document.body);
wrp.addClass("foo");
wrp.swap(document.body.firstElementChild);
wrp.addClass("bar");
Another feature you could implement would be to add return this; to all the wrapper methods. That way you could chain your function calls if you like.
var wrp = wrap(document.body);
wrp.addClass("foo")
.swap(document.body.firstElementChild)
.addClass("bar");
You could also implement your wrapper to hold multiple elements at numeric indices like an Array, or better, simply hold an Array of elements.
I think that jQuery is a big example of object wrapper. Mainly you just use it like $(domElement) to get some additional functionality.
You can do sth like:
var wrapper = function(el){
return {
go: function(){
console.log('go with', el);
}
}
};
wrapper(someEl).go();
I think that to extend the native behavior in javascript is not good.
And I find another post in the same website you post extending-built-in-native-objects-evil-or-not
So I'll say that I don't like to extend the stuff javascript provide us.
Here's what I'm aiming to achieve:
HTML
<fieldset id="addmore">
<p>blah</p>
<a class="remove">remove me</a>
</fieldset>
<a class="add">add more fieldsets</a>
Javascript
var addmore = new AddMore($('fieldset'));
addmore.buildCache(/*this will pull the innerHTML of the fieldset*/);
// bind the buttons
addmore.bind('add', $('a.add'));
addmore.bind('remove', $('a.remove'));
I've found myself having a lot more 'addmore' stuff in my HTML lately so I've been trying to build a class that will do all the leg work for me that I can just reuse in all my projects. The above code will, hopefully, be all I have to add each time and then the rest is done for me.
I've been winging this thing so, off the top of my head, here's what the class has to do:
Apply the jQuery bindings to the supplied 'button' objects so we can add/remove fieldsets
When a new fieldset is added, we have to recall the bind function so the new fieldset's 'a.add' button will work (I've found jQuery's .live() function to be buggy, for whatever reason, and try to avoid it)
It will hopefully do this with no memory leaks :}
Javascript Class
/*
Class to handle adding more data to the form array
Initialise the class by passing in the elements you want to add more of
Then bind 'add' and 'remove' buttons to the functions and the class will do the rest
*/
/*
Pass the jQuery object you want to 'addmore' of
Ex: var x = new AddMore($('fieldset.addmore'));
*/
function AddMore($element)
{
if (!$element || typeof($element) != 'object')
throw 'Constructor requires a jQuery object';
this.element = $element; // this is a jQuery object
this.cache = null;
}
/*
Supply clean HTML to this function and it will be cached
since the cached data will be used when 'adding more', you'll want the inputs to be emptied,
selects to have their first option selected and any other data removed you don't want readded to the page
*/
AddMore.prototype.buildCache = function(fieldset)
{
if (!fieldset)
throw 'No data supplied to cache';
this.cache = fieldset;
}
/*
use this to create the initial bindings rather than jQuery
the reason? I find .live() to be buggy. it doesn't always work. this usually means having to use a standard .bind()
and then re-bind when we add in the new set
that's what this class helps with. when it adds in the new data, it rebinds for you. nice and easy.
*/
AddMore.prototype.bind = function(type, $button)
{
if (!type || !$button && (type != 'add' && type != 'remove'))
throw 'Invalid paramaters';
// don't reapply the bindings to old elements...
if ($button.hasClass('addmore-binded'))
return;
// jQuery overwrites 'this' within it's scope
var _this = this;
if (type == 'add')
{
$button.bind('click', function()
{
_this.element.after(_this.cache);
});
}
}
I was going to have the .bind() method (in my class) call itself upon adding the new fieldset to reapply the binding but lost confidence with efficiency (speed/memory).
How should I tackle this? Do you have any pointers? Can you recommend improvements?
Thanks for the help.
In the most simplest form, you can do something like this:
var html = '{put html to add each time here}';
$('.add').click(function() {
$(html).insertAfter($('fieldset').last());
return false;
});
$('.remove').live('click', function() {
$(this).parent().remove();
return false;
});
You may need to tweak it based on your exact needs, but this should accomplish what you described in your example.
Update: sorry, remove should use the live method.
For creation of the new DOM elements, allow the specification/parameters to be any of the following:
simple HTML as a string (like the example above),
a function returning either a DOM element or HTML text. You can skip bind() or live() issues by adding
the onclick element when creating the HTML/element in the function. Although doing it in the AddMore() scope would be more tedious
if it's not a DOM element that gets returned.
inputs to a helper/factory method (maybe a template and name/value pairs) - postpone this unless you know enough patterns already.
Option #1 seems almost useless, but #3 might be hard unless you have extra time now.
Notes:
You might want to use $(theNewDomElement).insertBefore(_this.button); rather than _this.element.after(theNewDomElement); so that new items are append to the end of the list.
Actually, for insertBefore() you might just use this rather than _this.button since presumably the button (or anchor) is this, but then that limits the functionality - just make it some sort of DOM element (or a jQuery object that equates to one).
In most cases, I'd presume your DOM elements represent data that you'll want to save/send/transmit to your server, so provide a before-removal function, too. Even allow the function to skip removal -- like after a confirm().
Good luck.