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

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

Related

How can I extends a Modal in Bootstrap 4?

Firstly, I have a problem with OOP in javascript, and I understand very little about this, but I need to solve the problem...
Well, I am trying use, on Bootstrap 4, a library made for Bootstrap 3: https://github.com/nakupanda/bootstrap3-dialog
I get the following error: "Cannot call a class as a function"
Looking the code, I discovered somethings:
1 - classCallCheck: is the function that throws the error. I suppose that it forces the user use "new" and instantiate an object and never call like a function;
2 - createClass: is a function that constructs classes, so the classes in Bootstrap 4 is not defined conventionally.
3 - inherits: is another function and shows that the inheritance is not conventional too.
4 - The library has this code to extends the Bootstrap Modal:
var BootstrapDialogModal = function (element, options) {
Modal.call(this, element, options);
};
But, Modal.call trigger the error: "Cannot call a class as a function".
I suppose the only problem is BootstrapDialogModal inherits Modal in inheritance conditions imposed by Bootstrap 3 and, when Bootstrap 4 is active, these conditions are not the same.
Follow a jsfiddle: http://jsfiddle.net/g6nrnvwu/
Someone know how can I adjust this code?
Thanks.
You may take a look at this pull request that aims to update the plugin to BS 4: https://github.com/nakupanda/bootstrap3-dialog/pull/281/files?diff=unified
Ok it's not exactly an answer, but an example that maybe can help you. This is a class i made to handle modal dynamically inserting it's markup on the fly. It's not BS4 but you can use as a guide maybe?
ModalUtils

How to re-evaluate a script that doesn't expose any global in a declarative-style component

I have been writing a reusable script, let's call it a plugin although it's not jQuery, that can be initialised in a declarative way from the HTML. I have extremely simplified it to explain my question so let's say that if a user inserts a tag like:
<span data-color="green"></span>
the script will fire because the attribute data-color is found, changing the color accordingly.
This approach proved very handy because it avoids anyone using the plugin having to initialise it imperatively in their own scripts with something like:
var elem = document.getElementsByTagName('span')[0];
myPlugin.init(elem);
Moreover by going the declarative way I could get away without defining any global (in this case myPlugin), which seemed to be a nice side effect.
I simplified this situation in an example fiddle here, and as you can see a user can avoid writing any js, leaving the configuration to the HTML.
Current situation
The plugin is wrapped in a closure like so:
;(function(){
var changeColor = {
init : function(elem){
var bg = elem.getAttribute('data-color');
elem.style.background = bg;
}
};
// the plugin itslef looks for appropriate HTML elements
var elem = document.querySelectorAll('[data-color]')[0];
// it inits itself as soon as it is evaluated at page load
changeColor.init(elem);
})();
The page loads and the span gets the correct colour, so everything is fine.
The problem
What has come up lately, though, is the need to let the user re-evaluate/re-init the plugin when he needs to.
Let's say that in the first example the HTML is changed dynamically after the page is loaded, becoming:
<span data-color="purple"></span>
With the first fiddle there's no way to re-init the plugin, so I am now testing some solutions.
Possible solutions
Exposing a global
The most obvious is exposing a global. If we go this route the fiddle becomes
http://jsfiddle.net/gleezer/089om9z5/4/
where the only real difference is removing the selection of the element, leaving it to the user:
// we remove this line
// var elem = document.querySelectorAll('[data-color]')[0];
and adding something like (again, i am simplifying for the sake of the question):
window.changeColor = changeColor;
to the above code in order to expose the init method to be called from anywhere.
Although this works I am not satisfied with it. I am really looking for an alternative solution, as I don't want to lose the ease of use of the original approach and I don't want to force anyone using the script adding a new global to their projects.
Events
One solution I have found is leveraging events. By putting something like this in the plugin body:
elem.addEventListener('init', function() {
changeColor.init(elem);
}, false);
anybody will be able to just create an event an fire it accordingly. An example in this case:
var event = new CustomEvent('init', {});
span.dispatchEvent(event);
This would re-init the plugin whenever needed. A working fiddle is to be found here:
http://jsfiddle.net/gleezer/tgztjdzL/1/
The question (finally)
My question is: is there a cleaner/better way of handling this?
How can i let people using this plugin without the need of a global or having to initialise the script themselves the first time? Is event the best way or am I missing some more obvious/better solutions?
You can override Element.setAttribute to trigger your plugin:
var oldSetAttribute = Element.prototype.setAttribute;
Element.prototype.setAttribute = function(name, value) {
oldSetAttribute.call(this, name, value);
if (name === 'data-color') {
changeColor.init(this);
}
}
Pros:
User does not have to explicitly re-initialize the plugin. It will happen automatically when required.
Cons:
This will, of course, only work if the user changes data-color attributes using setAttribute, and not if they create new DOM elements using innerHTML or via some other approach.
Modifying host object prototypes is considered bad practice by many, and for good reasons. Use at your own risk.

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'
);
};
}
}

jquery.fn getting overwritten

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));

Categories