How to override a Bootstrap plugin definition - javascript

I want to make some adjustments (actually, fixes) to the Bootstrap 3 Button plugin, but I can't wait for the whole submit code/pull request/approval process. I also don't want to make any edits directly to the bootstrap.js file. I thought I could add another file called bootstrap-override.js which loads after bootstrap.js and contained another copy of the Buttons plugin code with my customizations, but this isn't working. The code from the original Buttons plugin fires.
What is the correct pattern for replacing an already defined plugin like this:
/* ========================================================================
* Bootstrap: button.js v3.3.0
* http://getbootstrap.com/javascript/#buttons
* ========================================================================
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// BUTTON PUBLIC CLASS DEFINITION
// ==============================
var Button = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Button.DEFAULTS, options)
this.isLoading = false
}
Button.VERSION = '3.3.0'
Button.DEFAULTS = {
loadingText: 'loading...'
}
Button.prototype.setState = function (state) {
var d = 'disabled'
var $el = this.$element
var val = $el.is('input') ? 'val' : 'html'
var data = $el.data()
state = state + 'Text'
if (data.resetText == null) $el.data('resetText', $el[val]())
// push to event loop to allow forms to submit
setTimeout($.proxy(function () {
$el[val](data[state] == null ? this.options[state] : data[state])
if (state == 'loadingText') {
this.isLoading = true
$el.addClass(d).attr(d, d)
} else if (this.isLoading) {
this.isLoading = false
$el.removeClass(d).removeAttr(d)
}
}, this), 0)
}
Button.prototype.toggle = function () {
var changed = true
var $parent = this.$element.closest('[data-toggle="buttons"]')
if ($parent.length) {
var $input = this.$element.find('input')
if ($input.prop('type') == 'radio') {
if ($input.prop('checked') && this.$element.hasClass('active')) changed = false
else $parent.find('.active').removeClass('active')
}
if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')
} else {
this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
}
if (changed) this.$element.toggleClass('active')
}
// BUTTON PLUGIN DEFINITION
// ========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.button')
var options = typeof option == 'object' && option
if (!data) $this.data('bs.button', (data = new Button(this, options)))
if (option == 'toggle') data.toggle()
else if (option) data.setState(option)
})
}
var old = $.fn.button
$.fn.button = Plugin
$.fn.button.Constructor = Button
// BUTTON NO CONFLICT
// ==================
$.fn.button.noConflict = function () {
$.fn.button = old
return this
}
// BUTTON DATA-API
// ===============
$(document)
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
var $btn = $(e.target)
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
Plugin.call($btn, 'toggle')
e.preventDefault()
})
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
$(e.target).closest('.btn').toggleClass('focus', e.type == 'focus')
})
}(jQuery);

You need to remove the original Bootstrap button plugin's event handlers using jQuery.off(). You need to .off() each of the original button plugin's .on()s. Example:
$('document').off('click.bs.button.data-api focus.bs.button.data-api blur.bs.button.data-api');

Related

Unable to load datepickers created after page is ready

I have a Django template in which I use bootstrap-datepicker-plus. However, I made a form in which the user can click a button in order to display as many datetime fields as needed.
The issue I'm encountering is that bootstrap-datetime-picker only loads the datetime fields present when the page charges, but if I want to add new datetime fields, then those are not loaded. After searching in the source code, I found out that it indeed was only loading the fields when the page is ready but doesn't look for other datetime fields after that.
I tried some things and saw that by reimporting the js file with
<script src="/static/bootstrap_datepicker_plus/js/datepicker-widget.js"></script>
each time I make a change it's working, but I can't do that for each situation, and I guess that's not really a good practice.
Is there a way to like refresh the bootstrap-datetime-picker's js file each time I create a new datetime field ?
I don't know if that's useful, but here's bootstrap-datetime-picker's js file that loads the datetime pickers :
(function (factory) {
if (typeof define === 'function' && define.amd)
define(['jquery'], factory)
else if (typeof module === 'object' && module.exports)
module.exports = factory(require('jquery'))
else
factory(jQuery)
}(function ($) {
var datepickerDict = {};
const bootstrapVersion = (window.bootstrap?.Collapse||$.fn.collapse.Constructor).VERSION;
var isBootstrap4 = bootstrapVersion.split('.').shift() === "4";
var isBootstrap5 = bootstrapVersion.split('.').shift() === "5";
function fixMonthEndDate(e, picker) {
e.date && picker.val().length && picker.val(e.date.endOf('month').format('YYYY-MM-DD'));
}
function initOnePicker(element, options) {
var $element = $(element), data = {};
try {
data = JSON.parse($element.attr('data-dp-config'));
}
catch (x) { }
$.extend(1, data.options, options);
if (data.id && data.options) {
data.$element = $element.datetimepicker(data.options);
data.datepickerdata = $element.data("DateTimePicker");
data.$element.next('.input-group-addon').on('click', function() {
data.datepickerdata.show();
});
if (isBootstrap4 || isBootstrap5) {
data.$element.on("dp.show", function(e) {
$('.collapse.in').addClass('show');
});
}
}
return data;
};
function initLinkedPickers(to_picker) {
var from_picker = datepickerDict[to_picker.linked_to];
from_picker.datepickerdata.maxDate(to_picker.datepickerdata.date() || false);
to_picker.datepickerdata.minDate(from_picker.datepickerdata.date() || false);
from_picker.$element.on("dp.change", function (e) {
to_picker.datepickerdata.minDate(e.date || false);
});
to_picker.$element.on("dp.change", function (e) {
if (to_picker.picker_type == 'MONTH') fixMonthEndDate(e, to_picker.$element);
from_picker.datepickerdata.maxDate(e.date || false);
});
if (to_picker.picker_type == 'MONTH') {
to_picker.$element.on("dp.hide", function (e) {
fixMonthEndDate(e, to_picker.$element);
});
fixMonthEndDate({ date: to_picker.datepickerdata.date() }, to_picker.$element);
}
};
$.fn.djangoDatetimePicker = function(options){
options = options || {};
var newPickers = {};
$.each(this, function (i, element) {
var picker = initOnePicker($(element), options);
newPickers[picker.id] = picker;
});
$.extend(datepickerDict, newPickers);
$.each(newPickers, function (i, picker) {
if (picker.linked_to)
initLinkedPickers(picker);
});
return this;
}
$(function(){
$("[data-dp-config]:not([disabled])").djangoDatetimePicker();
if (isBootstrap4 || isBootstrap5) {
$('body').on('show.bs.collapse','.bootstrap-datetimepicker-widget .collapse',function(e){
$(e.target).addClass('in');
});
$('body').on('hidden.bs.collapse','.bootstrap-datetimepicker-widget .collapse',function(e){
$(e.target).removeClass('in');
});
}
if(isBootstrap5){
$('.input-group-addon[data-target="#datetimepicker1"]').each(function (){
$(this).attr('data-bs-target','#datetimepicker1').removeAttr('data-target')
$(this).attr('data-bs-toggle','datetimepickerv').removeAttr('data-toggle')
})
}
});
}));

jQuery.AreYouSure Plugin not working

I have spent the last few days trying to properly install the jQuery.AreYouSure? plugin with inconsistent results.
I decided to use this plugin based on information from this post: Warn user before leaving web page with unsaved changes. I've also read through the plugin's documentation on GitHub: jQueryAreYouSure
Here's what I've done:
I refresh the page.
I've been copying the code from the custom.js file into the console in chrome.
I make a change to the form
I either click on the refresh button or another site link to navigate away without saving.
My result: Sometimes I get the alert message but more times then not, I do not. I also cannot not replicate or understand when I do receive the alert. Sometimes it may come when I first click to leave page and at other times it may appear when I clicked around a few times.
javascripts/application.js:
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require bootstrap-sprockets
//= require turbolinks
//= require areyousure/areyousure
//= require_tree .
javascripts/areyousure/areyousure.js:
/*!
* jQuery Plugin: Are-You-Sure (Dirty Form Detection)
* https://github.com/codedance/jquery.AreYouSure/
*
* Copyright (c) 2012-2014, Chris Dance and PaperCut Software http://www.papercut.com/
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Author: chris.dance#papercut.com
* Version: 1.9.0
* Date: 13th August 2014
*/
(function($) {
$.fn.areYouSure = function(options) {
var settings = $.extend(
{
'message' : 'You have unsaved changes!',
'dirtyClass' : 'dirty',
'change' : null,
'silent' : false,
'addRemoveFieldsMarksDirty' : false,
'fieldEvents' : 'change keyup propertychange input',
'fieldSelector': ":input:not(input[type=submit]):not(input[type=button])"
}, options);
var getValue = function($field) {
if ($field.hasClass('ays-ignore')
|| $field.hasClass('aysIgnore')
|| $field.attr('data-ays-ignore')
|| $field.attr('name') === undefined) {
return null;
}
if ($field.is(':disabled')) {
return 'ays-disabled';
}
var val;
var type = $field.attr('type');
if ($field.is('select')) {
type = 'select';
}
switch (type) {
case 'checkbox':
case 'radio':
val = $field.is(':checked');
break;
case 'select':
val = '';
$field.find('option').each(function(o) {
var $option = $(this);
if ($option.is(':selected')) {
val += $option.val();
}
});
break;
default:
val = $field.val();
}
return val;
};
var storeOrigValue = function($field) {
$field.data('ays-orig', getValue($field));
};
var checkForm = function(evt) {
var isFieldDirty = function($field) {
var origValue = $field.data('ays-orig');
if (undefined === origValue) {
return false;
}
return (getValue($field) != origValue);
};
var $form = ($(this).is('form'))
? $(this)
: $(this).parents('form');
// Test on the target first as it's the most likely to be dirty
if (isFieldDirty($(evt.target))) {
setDirtyStatus($form, true);
return;
}
$fields = $form.find(settings.fieldSelector);
if (settings.addRemoveFieldsMarksDirty) {
// Check if field count has changed
var origCount = $form.data("ays-orig-field-count");
if (origCount != $fields.length) {
setDirtyStatus($form, true);
return;
}
}
// Brute force - check each field
var isDirty = false;
$fields.each(function() {
$field = $(this);
if (isFieldDirty($field)) {
isDirty = true;
return false; // break
}
});
setDirtyStatus($form, isDirty);
};
var initForm = function($form) {
var fields = $form.find(settings.fieldSelector);
$(fields).each(function() { storeOrigValue($(this)); });
$(fields).unbind(settings.fieldEvents, checkForm);
$(fields).bind(settings.fieldEvents, checkForm);
$form.data("ays-orig-field-count", $(fields).length);
setDirtyStatus($form, false);
};
var setDirtyStatus = function($form, isDirty) {
var changed = isDirty != $form.hasClass(settings.dirtyClass);
$form.toggleClass(settings.dirtyClass, isDirty);
// Fire change event if required
if (changed) {
if (settings.change) settings.change.call($form, $form);
if (isDirty) $form.trigger('dirty.areYouSure', [$form]);
if (!isDirty) $form.trigger('clean.areYouSure', [$form]);
$form.trigger('change.areYouSure', [$form]);
}
};
var rescan = function() {
var $form = $(this);
var fields = $form.find(settings.fieldSelector);
$(fields).each(function() {
var $field = $(this);
if (!$field.data('ays-orig')) {
storeOrigValue($field);
$field.bind(settings.fieldEvents, checkForm);
}
});
// Check for changes while we're here
$form.trigger('checkform.areYouSure');
};
var reinitialize = function() {
initForm($(this));
}
if (!settings.silent && !window.aysUnloadSet) {
window.aysUnloadSet = true;
$(window).bind('beforeunload', function() {
$dirtyForms = $("form").filter('.' + settings.dirtyClass);
if ($dirtyForms.length == 0) {
return;
}
// Prevent multiple prompts - seen on Chrome and IE
if (navigator.userAgent.toLowerCase().match(/msie|chrome/)) {
if (window.aysHasPrompted) {
return;
}
window.aysHasPrompted = true;
window.setTimeout(function() {window.aysHasPrompted = false;}, 900);
}
return settings.message;
});
}
return this.each(function(elem) {
if (!$(this).is('form')) {
return;
}
var $form = $(this);
$form.submit(function() {
$form.removeClass(settings.dirtyClass);
});
$form.bind('reset', function() { setDirtyStatus($form, false); });
// Add a custom events
$form.bind('rescan.areYouSure', rescan);
$form.bind('reinitialize.areYouSure', reinitialize);
$form.bind('checkform.areYouSure', checkForm);
initForm($form);
});
};
})(jQuery);
I created javascripts/custom.js:
$('form').areYouSure( {'message':'You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?'} );
Removed turbolinks by removing
//= require turbolinks from the application.js file.
(Currently Jan 2018)
jquery.AreYouSure only works when the page is refreshed if turbolinks is active.

JQuery plugin - callback breaks other features

I'm trying to build a basic color picker plugin (mainly as an exercise to learn about plugin development). I have a callback called "onSelected" that fires when you pick a color, but it breaks another feature of the plugin (the ability to toggle the visibility of the swatch list).
I am new to plugin development so I'm sure it's a simple mistake I'm making...
jsfiddle
Plugin:
(function ($) {
$.colorPicker2 = function (el, options) {
// the wrapper around the colors
var $pickerContainer = $("<div>");
// To avoid scope issues, use 'base' instead of 'this'
// to reference this class from internal events and functions.
var base = this;
// Access to jQuery and DOM versions of element
base.$el = $(el);
base.el = el;
// Add a reverse reference to the DOM object
base.$el.data("colorPicker2", base);
base.init = function () {
console.log("base.init");
base.options = $.extend({}, $.colorPicker2.defaultOptions, options);
// Put your initialization code here
// code goes here
$.each(base.options.colors, function (index, value) {
var $item = $('<div class="colorPicker-colorOption">').css({
"background-color": "#" + value
})
$item.click(function () {
console.log("item.click");
base.selectColor(value);
})
$pickerContainer.append($item);
});
//$pickerContainer.hide();
base.$el.append($pickerContainer);
if (base.options.toggleElement != null) {
base.options.toggleElement.click(function (e) {
base.togglePicker();
e.preventDefault();
});
}
};
base.togglePicker = function()
{
$pickerContainer.toggle();
}
base.selectColor = function (color) {
base.togglePicker();
if (typeof base.options.onSelected == 'function') {
base.options.onSelected.call(this, color);
}
}
// Sample Function, Uncomment to use
// base.functionName = function(paramaters){
//
// };
// Run initializer
base.init();
};
$.colorPicker2.defaultOptions = {
colors: [
'000000', '993300', '333300', '000080', '333399', '333333', '800000', 'FF6600',
'808000', '008000', '008080', '0000FF', '666699', '808080', 'FF0000', 'FF9900',
'99CC00', '339966', '33CCCC', '3366FF', '800080', '999999', 'FF00FF', 'FFCC00',
'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0', 'FF99CC', 'FFCC99',
'FFFF99', 'CCFFFF', '99CCFF', 'FFFFFF'
],
toggleElement: null,
onSelected: function (color) { }
};
$.fn.colorPicker2 = function (options) {
return this.each(function () {
(new $.colorPicker2(this, options));
});
};
})(jQuery);
How I hook into the onSelected event:
$(function () {
$('#primaryColorPicker').colorPicker2({
toggleElement: $('#selectPrimaryColor'),
onSelected: function (color) {
$('#selectedPrimaryColor').html("(#" + color + ")");
}
});
});
The HTML:
<a id="selectPrimaryColor">Toggle Color Picker</a>
<span id="selectedPrimaryColor" />
<div id="primaryColorPicker"></div>
You just have to learn how to write valid HTML
replace
<span id="selectedPrimaryColor" />
with
<span id="selectedPrimaryColor"></span>
FIDDLE

Panel Visibility via JS

The tutorial at http://www.asp.net/web-forms/tutorials/ajax-control-toolkit/getting-started/creating-a-custom-ajax-control-toolkit-control-extender-vb gives a nice example of a custom extender based on a textbox and a button. Basically the button remains disabled until at least one character is typed into the textbox. If the text is removed from the textbox the button is disabled again.
I am trying to modify this so that the extender is based on a textbox and panel. Again I want the panel to become visible when text is present in a textbox.
This is how I amended code...
Type.registerNamespace('CustomExtenders');
CustomExtenders.ShowHidePanelBehavior = function (element) {
CustomExtenders.ShowHidePanelBehavior.initializeBase(this, [element]);
this._targetPanelIDValue = null;
}
CustomExtenders.ShowHidePanelBehavior.prototype = {
initialize: function () {
CustomExtenders.ShowHidePanelBehavior.callBaseMethod(this, 'initialize');
// Initalization code
$addHandler(this.get_element(), 'keyup',
Function.createDelegate(this, this._onkeyup));
this._onkeyup();
},
dispose: function () {
// Cleanup code
CustomExtenders.ShowHidePanelBehavior.callBaseMethod(this, 'dispose');
},
// Property accessors
//
get_TargetPanelID: function () {
return this._targetPanelIDValue;
},
set_TargetPanelID: function (value) {
this._targetPanelIDValue = value;
},
_onkeyup: function () {
var e = $get(this._targetPanelIDValue);
if (e) {
var visibility = ("" == this.get_element().style.value);
e.visibility = 'visible';
}
}
}
CustomExtenders.ShowHidePanelBehavior.registerClass('CustomExtenders.ShowHidePanelBehavior', Sys.Extended.UI.BehaviorBase);
When run the panel will not appear. No errors are produced.
Where have I gone wrong...
Try this code:
_onkeyup: function () {
var panel = $get(this.get_TargetPanelID());
if (panel) {
var visibilityValue = ("" == this.get_element().value) ? "hidden" : "visible";
panel.style.visibility = visibilityValue;
}
}

Making a method in a plugin accessible globally?

Given the jQuery dropdown plugin below. Is there a way to add a method that would allow for a separate function outside of the dropdown to 'hideMenu'? Thanks
For example, if I applied the plugin to a div with an ID like so:
$('#settings.dropdown').dropDownMenu();
How could I then call to close the dropDownMenu w hideMenu from outside of the plugin? Thanks
jQuery.fn.dropDownMenu = function() {
// Apply the Dropdown
return this.each(function() {
var dropdown = $(this),
menu = dropdown.next('div.dropdown-menu'),
parent = dropdown.parent();
// For keeping track of what's "open"
var activeClass = 'dropdown-active',
showingDropdown = false,
showingMenu,
showingParent,
opening;
// Dropdown Click to Open
dropdown.click(function(e) {
opening = true; // Track opening so that the body click doesn't close. This allows other js views to bind to the click
e.preventDefault();
if (showingDropdown) {
dropdown.removeClass(activeClass);
parent.removeClass(activeClass);
showingMenu.hide();
showingDropdown = false;
} else {
showingDropdown = true;
showingMenu = menu;
showingParent = parent;
menu.show();
dropdown.addClass(activeClass);
parent.addClass(activeClass);
}
});
// When you click anywhere on the page, we detect if we need to blur the Dropdown Menu
$('body').click(function(e) {
if (!opening && showingParent) {
var parentElement = showingParent[0];
if (!$.contains(parentElement, e.target) || !parentElement == e.target) {
hideMenu();
}
}
opening = false;
});
// hides the current menu
var hideMenu = function() {
if(showingDropdown) {
showingDropdown = false;
dropdown.removeClass(activeClass);
parent.removeClass(activeClass);
showingMenu.hide();
}
};
});
};
jQuery advises making multiple methods available through the plugin itself:
jQuery.fn.dropDownMenu = function(method) {
var methods = {
init: function() {
// Put all your init code here
},
hide: function() {
hideMenu();
}
};
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
function hideMenu() {
// ...
}
};
See http://docs.jquery.com/Plugins/Authoring#Plugin_Methods
Update: Use like this:
// Use the plugin normally to run the init method
$('#settings.dropdown').dropDownMenu();
// Call the hide method
$('#settings.dropdown').dropDownMenu('hide');
Sure. Give hideMenu to the global window object, like this:
window["hideMenu"] = function() {
if(showingDropdown) {
showingDropdown = false;
dropdown.removeClass(activeClass);
parent.removeClass(activeClass);
showingMenu.hide();
}
};
You can then call it as usual anywhere you need to.

Categories