jquery.fn getting overwritten - javascript

In my site I have a jquery function created by using the jQuery.fn method like so:
jQuery.fn.equalHeights = function() {
var currentTallest = 0;
jQuery(this).each(function(){
if (jQuery(this).height() > currentTallest) {
currentTallest = jQuery(this).height();
}
});
jQuery(this).css('min-height', currentTallest);
};
I use this function to equalize the size of sidebars on my site so I call it after the page has finished loading to make sure everything is in place before I do the sizing.
jQuery(window).load(function(){
jQuery('#sidebar-one, #sidebar-two, #content').equalHeights();
});
This works perfectly except for on some of my pages users will add another instance of the jQuery library to their page in the body area (for whatever reason). This second library causes my function to stop working properly. I'm assuming this is because the second jQuery overwrites any functions created with the jQuery.fn method. Is there any way to prevent that from happening?
Notes: My site is running Drupal 7 so I can't easily move the scripts to the bottom of the page. The equalHeights function was not written by me; I believe I found it here on stack so my knowledge of the fn method (and JS in general) is not that extensive.
EDIT: Based on all the great suggestions below, this is how I finally got it to work.
First, I give my "default" instance of jQuery a new variable to reference:
var JQ = jQuery.noConflict();
Second, I call the more efficient version of the equalHeights function using the new jQuery variable:
JQ.fn.equalHeights = function() {
var tallest = 0;
return this.each(function(){
var h = JQ(this).height();
tallest = h > tallest ? h : tallest;
}).css("minHeight", tallest);
};
Third, I call my function using the new jQuery variable:
JQ('#sidebar-one, #sidebar-two, #content').equalHeights();
So now any time I need to reference my original jQuery library I just use JQ and I don't have to worry about another library stepping on any of my functions.
I realize this isn't the best way to fix this problem and I'm going to work on eliminating the additional jQuery libraries but this will at least keep my sidebars properly sized in the mean time.

Wrap your snippet in a module IEFE, passing the "old" jQuery reference:
(function($) {
// $ will always point to the jQuery version with the `equalHeights` method
$(window).load(function(){
$('#sidebar-one, #sidebar-two, #content').equalHeights();
});
}(jQuery));
See also jQuery.noConflict for further tricks.

You could technically copy jQuery to a new variable just for your use - i.e. JQ = jQuery; then do JQ(this)...
Though you may just want to address the insertion of subsequent versions - stopping it, or ensuring your version is added last and all of your code is executed afterwards so they can't override it

This is because the second version of jQuery will overwrite the previous one. Unless you are using a legacy library that isn't compatible with later versions of jQuery, there's no reason to load two different versions.
To use a later version of jQuery with Drupal 7 you need to use the hook_js_alter script and then add something like the following to your Drupal theme (courtesy of oldwildissue):
function MYTHEME_js_alter(&$javascript) {
//We define the path of our new jquery core file
//assuming we are using the minified version 1.8.3
$jquery_path = drupal_get_path('theme','MYTHEME') . '/js/jquery-1.8.3.min.js';
//We duplicate the important information from the Drupal one
$javascript[$jquery_path] = $javascript['misc/jquery.js'];
//..and we update the information that we care about
$javascript[$jquery_path]['version'] = '1.8.3';
$javascript[$jquery_path]['data'] = $jquery_path;
//Then we remove the Drupal core version
unset($javascript['misc/jquery.js']);
}
This will allow you to use any version of jQuery with Drupal 7.
I also noticed that your plugin is using some pretty bad practices. Here's an optimized version of your plugin, using good and well established practices:
(function($){
$.fn.equalHeights = function() {
var tallest = 0;
return this.each(function(){
var h = $(this).height();
tallest = h > tallest ? h : tallest;
}).css("minHeight", tallest);
};
}(jQuery));

Related

How to initialize Tooltips in Bootstrap 4 with pure JavaScript? No jQuery

I'm somehow stuck with this code:
$(function () {
$('[data-toggle="tooltip"]').tooltip()
});
I'd like to change it to pure JavaScript. I'm not sure how to call the tooltip function, I already have some ideas like:
var tooltips = document.querySelectorAll("[data-toggle='tooltip']");
for(var i = 0; i < tooltips.length; i++){
//this gives an error
tooltips[i].tooltip();
}
I'm able to get all the tooltips, but I cannot initialize them. If I write something like this:
$(tooltips[i]).tooltip();
Then it works, but I want to remove the jQuery dependency since this is the only jQuery section that I have. Any idea? I've been searching and I cannot find anything useful.
For the new Bootstrap 5, this will be the new option without jQuery:
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
})
More info: https://getbootstrap.com/docs/5.0/components/tooltips/
Note:
In Bootstrap 4, you cannot initialize the Tooltips without jQuery. Here is an example:
https://jsfiddle.net/30c6kmnL/
There is a dependency on the function/class Tooltip that cannot be called (from my knowledge) without jQuery (you might need to rewrite the entire Tooltip function/class). I got errors like this one:
Uncaught TypeError: bootstrap.Tooltip is not a constructor
If you know how to fix it, please do it on the Snippet and share your results.
Thought I'd throw an answer out there using JS that also targets Bootstrap 4.x. The below code snippet will initialize all tooltips on the page in Bootstrap 4.x.
If you're unfamiliar with JavaScript classes and constructors then you may have trouble catching why the initialization didn't work in the original question.
Classes & Constructors: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor
Bootstrap 4.x —requires— JQuery (v3.2.1 slim at minimum) and the Tooltips component specifically require another library, Popper.js (v1.12.3 at minimum). However, the initialization can be written with simple JS (to call the JQuery based constructor for tooltips).
Note: the keyword new is missing from the OP's original question, this is needed to instantiate a new class instance for each element.
function setTooltips() {
let tooltips = document.querySelectorAll('[data-toggle="tooltip"]');
for(let i = 0; i < tooltips.length; i++) {
let tooltip = new bootstrap.Tooltip(tooltips[i]);
}
}
You could also use the mapping method shown by BS5 as it would still be calling the JQuery based constructor. As long as JQuery and Popper are present beforehand.
Also you don't necessarily need to save the instance into memory if you don't need to reference it later, just return it instead.
Preferably call this in a DOMContentLoaded event globally to target any tooltips on any page.
window.addEventListener('DOMContentLoaded', function() {
setTooltips();
}, false);
Bootstrap 5 does things differently, mainly dropping JQuery as a dependency in favor of more modern ES6 JS. Use Federico's answer for Bootstrap 5.x and up, which is the default example from the BS5 docs for initializing tooltips.
Bootstrap 5 is designed to be used without JQuery, but it's still
possible to use our components with JQuery. If Bootstrap detects
jQuery in the window object it will add all of our components in
jQuery’s plugin system...
See: https://getbootstrap.com/docs/5.0/getting-started/javascript/#still-want-to-use-jquery-its-possible

Override javascript library function

Im trying to modify the behavior of the jquery UI spinner, and want to avoid hacking the original js file. Theres a function in the js file called _doSpin that if I modify I can achieve the effect I want (I want it to change the value by a percentage - not like the built in percentage method which presents a real percentage to the user, but I need normal ints to be incremented or decremented by a percentage without the bounds of 100% to 0%).
But I don't want to hack the original file, so I want to override the function from within my page (which uses ui.spinner.js). I cant work out how to get to the namespace of the library file, from within my html page. I've tried loads of tips from stackoverflow already but none seem to work in my case. The library file starts with (function($, undefined) but I can't understand how its defined a namespace for me to define in my html page, so I can 'break in' and override the function.
BTW the function is used internally in the library file, maybe its not possible to override it in this case? I don't want to use it locally in my html page, I call higher methods to do my business, I just need to change how the library works internally.
First off, are you entirely sure that you can't solve your problem by specifying options to the spinner object? It seems that with the min, max and step options you could make the step be any percentage of the range that you wanted at the time you created the spinner or set the range. Here's the doc for the step option.
If this is an internal function (not exposed in a public API or as a method on a public object), then it all depends upon how it is coded. If it is not put into a public namespace and it is defined within some other function, then you can't get at it to redefine it. The internals of a function are not something you can get to from outside the function in Javascript unless they are explicitly make accessible to the public somehow.
If it is in a public API somewhere, then you can just assign a new function to whatever symbol the API is on and that will replace the original function (usually you save the original function first so you can still use it if needed).
We can't really help you more specifically without you showing us the actual code you're wanting to override. FYI, I can't find a function called _doSpin in the jQueryUI source repository, so I don't really know which function in which code you're trying to override.
Edit: In a Google search, I found another version of a jQuery UI spinner that does have a _doSpin() method. In that version the _doSpin() method ends up as a method on the prototype for the spinner widget object (which is a jQuery UI object structure it uses for all its objects). I don't personally have much experience with jQueryUI, but from reading their doc on the widget factory, it sounds like you can inherit from an existing widget and replace methods on the prototype. Or, you could probably hack on the prototype directly - though it would take some doing to figure out how/where jQueryUI stores it or how to get to it.
This did a better job in the end.
<html>
<script type='text/javascript'>
var mainarray = [1,2,3,4,5];
function scrolltxtbox (e) {
var evt=window.event || e;
var delta=evt.detail ? evt.detail*(-120) : evt.wheelDelta;
var targ;
if (evt.target) targ = evt.target;
else if (evt.srcElement) targ = evt.srcElement;
(delta<=-120)?multiplier=0.95:multiplier=1.05;
changeweight(targ.id, multiplier);
if (evt.preventDefault)
evt.preventDefault()
else return false
}
function changeweight(targid, multiplier) {
var u = targid.match(/myspinner(.*)/);
mainarray[u[1]] = mainarray[u[1]]*multiplier;
document.getElementById(targid).value = mainarray[u[1]].toPrecision(3);
}
var mousewheelevt=(/Firefox/i.test(navigator.userAgent))? "DOMMouseScroll" : "mousewheel"
var index;
for (index = 0; index < mainarray.length; ++index) {
document.write('<input type="text" size=6 id="myspinner'+index+'" value='+mainarray[index]+' autocomplete="off"><br>');
if (window['myspinner'+index].attachEvent) {
window['myspinner'+index].attachEvent("on"+mousewheelevt, scrolltxtbox);
} else if (window['myspinner'+index].addEventListener) {
window['myspinner'+index].addEventListener(mousewheelevt, scrolltxtbox, false);
}
}
</script>
</html>
Its probably (definitely!) oververbose, but it works nice.

Is there an equivilant of phpinfo for jquery?

server side background, getting deeper and deeper into client side.
I've got a site with a lot of legacy that I'm new to, and I'm just trying to get a handle on how things are working/what's available.
Is there a way to have jquery tell me(for a page/pages) all its current info and any plugins it can/is use/ing, similar to what phpinfo does?
Some proof of concept how you can get names for all plugins
var plugins = (function() {
var plugins = [];
for(var plugin in jQuery.fn) {
plugins.push(plugin)
}
return plugins;
}());
var filterValue = ['constructor', 'init', 'add', 'parents'] // // you must add all standard methods here
filterValue.forEach(function(value) {
var position = function(value) {
return plugins.indexOf(value);
}
while(position(value) >= 0) {
plugins.splice(position(value), 1)
}
})
console.log(plugins)
You can use the following for jQuery
console.log( jQuery.fn.jquery );
To answer your question directly, No jQuery does not have a mechanism that lists installed plug-ins.
jQuery does not keep such a registry of installed plugins. The jQuery plugin mechanism is to just add a method to the jQuery prototype (right along-side all the other jQuery methods). So, there's no separate list of which methods have been added by some outside agent (e.g. a plug-in). In addition, there's no one-to-one correspondence between methods and a particular plug-in as a single plug-in could add multiple methods.
It would be possible to create a master list (for any specific jQuery version) of what methods are there by default and then enumerate a jQuery object to find out which methods have been added since then, but you'd have to create that master list ahead of time and store it or create the master list immediately after jQuery was loaded before any plug-ins were loaded.
You can always test to see if any specific jQuery plug-in is loaded by just checking to see if any of its signature methods are available on a jQuery object.
So, if you really just wanted to know which of 10 plugins happen to be available in any given page, you could write a quick function that would test for each of the 10 plugins (by looking for the existence of known methods in those plugins) and would return a list of the ones installed, but this has to be done with specific knowledge of each plugin as there is no "generic plugin identification mechanism" since a plug-in is nothing more than a piece of code that adds methods to the jQuery prototype. It doesn't actually have any identity of its own.

Windows 8 Store App and jQuery - MSApp.execUnsafeLocalFunction

I am writing a Windows 8 JavaScript Store App (using Cordova). When I use jQuery with Windows 8, why is it I have to modify the jQuery library to use the following for a few of the functions?
MSApp.execUnsafeLocalFunction
Preferably I would prefer not to do this as it is a slight inconvenience whenever a new version of jQuery Mobile is release (which, thankfully, is not often) - is there a setting in the project to disable this?
I am using jQuery v1.9.1 and jQuery Mobile v1.3.2 (which are the latest stable releases). I read this is fixed as of jQuery 2.x - can someone confirm whether this is true?
If so, is it unsafe to use jQuery 2.x in my mobile app project? Are there functions in jQuery v1.9.1 which jQuery Mobile v1.3.2 rely on, which are unavailable in jQuery v2.0?
You need to use MSApp.execUnsafeLocalFunction for certain functions because jQuery adds HTML in an, for Microsoft, unsafe way. Some HTML elements and attributes are considered unsafe and the App crashes when jQuery unsafely adds them to the DOM, using for instance append or innerHTML. The unsafe HTML elements and attributes can be found here.
This problem is as I know not fixed in jQuery 2.x. I'm still struggling to get the async page changes with unsafe keywords to work. I'll let you know when I have a workaround or fix.
Update: MSOpenTech created a JavaScript dynamic content shim which solves this problem. Take a look at my blog post or at their Github page.
Call this function in deviceready event and alert and jQuery html method will work everywhere
function WinAppHack() {
if (MSApp && MSApp.execUnsafeLocalFunction) {
// override jquery html method
var old_html = $.fn.html;
$.fn.html = function () {
var args = arguments;
var This = this;
var result = null;
//$(this).css('font-size', 24)
MSApp.execUnsafeLocalFunction(function () {
result = old_html.apply(This, args);
});
return result;
};
// override alert method
alert = function (msg, Closed) {
if (!Closed)
Closed = function () { };
navigator.notification.alert(
msg,
Closed,
'Message',
'OK'
);
};
}
}

Javascript modules, passing jQuery when it may not be loaded

I'm learning the module pattern for javascript in order to tidy up my code and reduce the need for a long 'global' javascript file.
As a consequence of this, I have a top level 'namespace' module, and a utility module in the same file. The utility module has some functions that require jquery, and others that do not.
On lightweight pages that use no jQuery, I don't want to load the library (I have a very good reason for not doing so).
The problem arises when jQuery is passed as a parameter to the module as in the following:
MODULE.vars = (function (variables,$) {
variables.cdn = undefined; //global for clientside cdn
variables.version = undefined; //global for clientside cdn version
variables.isTouchScreen = undefined; //global for touchscreen status
//Check if jquery exists, if so patch the jquery dependent functions
if ($) {
$(document).ready(function () {
variables.isTouchScreen = $('html').is('.touch');
});
}
return variables;
}(MODULE.vars || {}, jQuery));
The code stops on pages that I don't load jquery, stating that jQuery is undefined - fair enough. If I change the last line to:
}(MODULE.vars || {}, jQuery || false));
the code still complains that jQuery is undefined. I thought, perhaps erroneously, that if jQuery was undefined, it would be passed as undefined in this instance and instead take up the value false (which logic dictates wouldn't be necessary anyway).
How do I get around this problem when jQuery may or may not be present? I attempted to put the following at the top of the script:
var jQuery = jQuery || false;
thinking that this would then take up the value of jQuery if it was loaded. It works in modern browsers with this, but IE8 complains as it gets set to false even if jQuery is being loaded first on a page.
The scripts are all loaded in the correct order in the html, jQuery first, then my module afterwards.
When checking for the cause, IE8 returns $ as an object and jQuery as false. If i do not set the above line in the script, jQuery returns as the same object as $.
Sadly I have to cater for IE8 as well, so how do I get around this issue of the optional presence of jQuery?
Edit: This is only a snippet of the module and there are other functions that depend on jquery, but simply won't get implemented if jquery is not available
I seem to have found an answer that works after I worked out how to implement elanclrs suggestion - I put the following at the top of my modules:
var jQ = jQ || false;
if (typeof jQuery !== 'undefined') {
jQ = jQuery;
}
Then in my module, I pass jQ instead of jQuery.
The reasoning behind the answer was pointed at in this question: Error when passing undefined variable to function?

Categories