Twitter Bootstrap 3 dropdown menu disappears when used with prototype.js - magento

I have an issue when using bootstrap 3 & prototype.js together on a magento website.
Basically if you click on the dropdown menu (Our Products) & then click on the background, the dropdown menu (Our Products) disappears (prototype.js adds "display: none;" to the li).
Here is a demo of the issue:
http://ridge.mydevelopmentserver.com/contact.html
You can see that the dropdown menu works like it should without including prototype.js on the page at the link below:
http://ridge.mydevelopmentserver.com/
Has anyone else ran into this issue before or have a possible solution for the conflict?
EASY FIX:
Just replace Magento's prototype.js file with this bootstrap friendly one:
https://raw.github.com/zikula/core/079df47e7c1f536a0d9eea2993ae19768e1f0554/src/javascript/ajax/original_uncompressed/prototype.js
You can see the changes made in the prototype.js file to fix the bootstrap issue here:
https://github.com/zikula/core/commit/079df47e7c1f536a0d9eea2993ae19768e1f0554
NOTE: JQuery must be include in your magento skin before prototype.js.. Example:
<script type="text/javascript" src="/js/jquery.js"></script>
<script type="text/javascript" src="/js/prototype/prototype.js"></script>
<script type="text/javascript" src="/js/lib/ccard.js"></script>
<script type="text/javascript" src="/js/prototype/validation.js"></script>
<script type="text/javascript" src="/js/scriptaculous/builder.js"></script>
<script type="text/javascript" src="/js/scriptaculous/effects.js"></script>
<script type="text/javascript" src="/js/scriptaculous/dragdrop.js"></script>
<script type="text/javascript" src="/js/scriptaculous/controls.js"></script>
<script type="text/javascript" src="/js/scriptaculous/slider.js"></script>
<script type="text/javascript" src="/js/varien/js.js"></script>
<script type="text/javascript" src="/js/varien/form.js"></script>
<script type="text/javascript" src="/js/varien/menu.js"></script>
<script type="text/javascript" src="/js/mage/translate.js"></script>
<script type="text/javascript" src="/js/mage/cookies.js"></script>
<script type="text/javascript" src="/js/mage/captcha.js"></script>

I've also used code from here: http://kk-medienreich.at/techblog/magento-bootstrap-integration-mit-prototype-framework but without a need to modify any source. Just put code below somewhere after prototype and jquery includes:
(function() {
var isBootstrapEvent = false;
if (window.jQuery) {
var all = jQuery('*');
jQuery.each(['hide.bs.dropdown',
'hide.bs.collapse',
'hide.bs.modal',
'hide.bs.tooltip',
'hide.bs.popover',
'hide.bs.tab'], function(index, eventName) {
all.on(eventName, function( event ) {
isBootstrapEvent = true;
});
});
}
var originalHide = Element.hide;
Element.addMethods({
hide: function(element) {
if(isBootstrapEvent) {
isBootstrapEvent = false;
return element;
}
return originalHide(element);
}
});
})();

Late to the party, but found this github issue which links to this informational page which links to this jsfiddle which works really nicely. It doesn't patch on every jQuery selector and is, I think, the nicest fix by far. Copying the code here to help future peoples:
jQuery.noConflict();
if (Prototype.BrowserFeatures.ElementExtensions) {
var pluginsToDisable = ['collapse', 'dropdown', 'modal', 'tooltip', 'popover'];
var disablePrototypeJS = function (method, pluginsToDisable) {
var handler = function (event) {
event.target[method] = undefined;
setTimeout(function () {
delete event.target[method];
}, 0);
};
pluginsToDisable.each(function (plugin) {
jQuery(window).on(method + '.bs.' + plugin, handler);
});
};
disablePrototypeJS('show', pluginsToDisable);
disablePrototypeJS('hide', pluginsToDisable);
}

Using the * selector with jQuery is not advised. This takes every DOM object on the page and puts it in the variable.
I would advice to select the elements that use a Bootstrap component specific. Solution below only uses the dropdown component:
(function() {
var isBootstrapEvent = false;
if (window.jQuery) {
var all = jQuery('.dropdown');
jQuery.each(['hide.bs.dropdown'], function(index, eventName) {
all.on(eventName, function( event ) {
isBootstrapEvent = true;
});
});
}
var originalHide = Element.hide;
Element.addMethods({
hide: function(element) {
if(isBootstrapEvent) {
isBootstrapEvent = false;
return element;
}
return originalHide(element);
}
});
})();

Very late to the party: if you don't feel like having extra scripts running, you can add a simple CSS override to prevent it from getting hidden.
.dropdown {
display: inherit !important;
}
Generally the use of !important in CSS is advised against, but I think this counts as an acceptable use in my opinion.

see http://kk-medienreich.at/techblog/magento-bootstrap-integration-mit-prototype-framework/.
It's quite an easy fix to validate the namespace of the element clicked.
Add a validation function to prototype.js:
and after that, validate the namespace before the element will be hidden:
hide: function(element) {
element = $(element);
if(!isBootstrapEvent)
{
element.style.display = 'none';
}
return element;
},

Replacing Magento's prototype.js file with the bootstrap friendly version suggested by MWD is throwing an error that prevents saving configurable products:
Uncaught TypeError: Object [object Array] has no method 'gsub' prototype.js:5826
(Running Magento Magento 1.7.0.2)
evgeny.myasishchev solution worked great.
(function() {
var isBootstrapEvent = false;
if (window.jQuery) {
var all = jQuery('*');
jQuery.each(['hide.bs.dropdown',
'hide.bs.collapse',
'hide.bs.modal',
'hide.bs.tooltip'], function(index, eventName) {
all.on(eventName, function( event ) {
isBootstrapEvent = true;
});
});
}
var originalHide = Element.hide;
Element.addMethods({
hide: function(element) {
if(isBootstrapEvent) {
isBootstrapEvent = false;
return element;
}
return originalHide(element);
}
});
})();

This answer helped me to get rid of bootstrap and prototype conflict issue.
As #GeekNum88 describe the matter,
PrototypeJS adds methods to the Element prototype so when jQuery tries
to trigger the hide() method on an element it is actually firing the
PrototypeJS hide() method, which is equivalent to the jQuery
hide() method and sets the style of the element to display:none;
As you suggest in the question itself either you can use bootstrap friendly prototype or else you can simply comment out few lines in bootstrap as bellow,
inside the Tooltip.prototype.hide function
this.$element.trigger(e)
if (e.isDefaultPrevented()) return

I realise that this is a pretty old post by now, but an answer that no-one else seems to have mentioned is simply "modify jQuery". You just need a couple of extra checks in jQuery's trigger function which can be found around line 332.
Extend this if statement:
// Call a native DOM method on the target with the same name name as the event.
// Don't do default actions on window, that's where global variables be (#6170)
if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
... to say this:
// Call a native DOM method on the target with the same name name as the event.
// Don't do default actions on window, that's where global variables be (#6170)
// Check for global Element variable (PrototypeJS) and ensure we're not triggering one of its methods.
if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) &&
( !window.Element || !jQuery.isFunction( window.Element[ type ] ) ) ) {
"#6170" only seems to be mentioned in jQuery once so you can do a quick check for that string if you're working with a compiled (complete) jQuery library.

Related

Access root BASE API URL from a child (or deep sub-child) elements in Polymer 1.0

I have created many elements in my Polymer 1.0 environment. Some elements are lists based on data I get from my DB server using iron-ajax. Currently I am using my root application to pass in the API URL as an element property to each element (or sub-element) that requires it so it may make the call and display the results.
Something feels off about the approach though. Should I be passing in AJAX URLs from the root element to the child elements that require it or is there a way for child elements to know what the globally set app API URL is?
I've had the same issue, fortunately
There is an element for that :)
So I've created a custom element to wrap all of my iron-ajax elements in my application, called it my-app-api.
This my-app-api element is responsible to update nested iron-ajax url attribute.
<dom-module id="my-app-api">
<style>
:host {
display: none;
}
</style>
<template>
<content select="iron-ajax"></content>
</template>
</dom-module>
<script>
(function() {
Polymer({
is: 'my-app-api',
properties: {
baseUrl: {
type: String,
value: 'http://localhost:9000'
}
},
attached: function() {
this.updateUrlsAndSign();
},
updateUrlsAndSign: function() {
var ajaxElements = Polymer.dom(this).querySelectorAll('iron-ajax');
var ajaxElementsArray = [].slice.call(ajaxElements);
ajaxElementsArray.forEach(function(ajax) {
var urlAttr = ajax.getAttribute('url');
if ( !urlAttr ) { return; }
ajax.url = this.baseUrl + urlAttr.replace(window.location.origin, '');
ajax.headers = ajax.headers || {};
ajax.headers['Content-Type'] = 'application/json';
...
}.bind(this));
}
});
})();
</script>
Example usage,
<my-app-api>
<iron-ajax
url="/api/videos/"
params="{{requestParams}}"
handle-as="json"
on-response="handleResponse"></iron-ajax>
</my-app-api>
I haven't tested it with auto attribute set on iron-ajax elements.
In my case that wasn't an issue so the above code worked fine with me.
Hope this will help you implement a better solution.

Creating JQuery plugin

I've followed a tutorial to create a Website Menu, this involves JQuery to provide some transitions.
I've got it working as intended, but to prove I understand this new code and framework, and also, to follow development guidelines, I want to move to the Menu code to a plug-in.
Working (not-plug-in) version:
This appears in the .ascx, along with a UL tag, ID = MainMenu
<script type="text/javascript">
NavBarMenu();
</script>
In another file, NavBar.js, the following code is used to associate some JQuery events:
function NavBarMenu() {
$(function() {
var $menu = $('#MainMenu');
var $menu_items = $menu.children('li');
...
Not working version:
Now the plug-in code is created, the NavBarMenu function is called like this from the .ascx:
<script type="text/javascript">
$("#MainMenu").NavBarMenu();
</script>
In NavBar.js I now have:
(function($) {
$.fn.NavBarMenu = function() {
var $menu_items = this.children('li');
However, the $menu_items variable is unpopulated?
Shouldn't this (2nd example) be equivalent to $('#MainMenu') (1st example)
I followed this example, where a JQuery selector is switched for the this pointer. http://learn.jquery.com/plugins/basic-plugin-creation/
Thanks in advance :D
There must be other factor affecting my implementation, I will revisit it.
I have created 2 JSFiddle pages, as part of my investigation and it has proved that the JQuery Selector can be swapped for the this pointer in a plug-in.
Fiddle detailing 1st example: http://jsfiddle.net/rL7zpr15/1/
$('input[type=button]').click( function() {
$( "div" ).css( "background", "green" );
});
Is equivalent to
Fiddle detailing 2nd example: http://jsfiddle.net/hds7advx/
$.fn.greenify = function() {
this.css( "background", "green" );
};
$('input[type=button]').click( function() {
$( "div" ).greenify();
});

CKEDITOR - how to add permanent onclick event?

I am looking for a way to make the CKEDITOR wysiwyg content interactive. This means for example adding some onclick events to the specific elements. I can do something like this:
CKEDITOR.instances['editor1'].document.getById('someid').setAttribute('onclick','alert("blabla")');
After processing this action it works nice. But consequently if I change the mode to source-mode and then return to wysiwyg-mode, the javascript won't run. The onclick action is still visible in the source-mode, but is not rendered inside the textarea element.
However, it is interesting, that this works fine everytime:
CKEDITOR.instances['editor1'].document.getById('id1').setAttribute('style','cursor: pointer;');
And it is also not rendered inside the textarea element! How is it possible? What is the best way to work with onclick and onmouse events of CKEDITOR content elements?
I tried manually write this by the source-mode:
<html>
<head>
<title></title>
</head>
<body>
<p>
This is some <strong id="id1" onclick="alert('blabla');" style="cursor: pointer;">sample text</strong>. You are using CKEditor.</p>
</body>
</html>
And the Javascript (onclick action) does not work. Any ideas?
Thanks a lot!
My final solution:
editor.on('contentDom', function() {
var elements = editor.document.getElementsByTag('span');
for (var i = 0, c = elements.count(); i < c; i++) {
var e = new CKEDITOR.dom.element(elements.$.item(i));
if (hasSemanticAttribute(e)) {
// leve tlacitko mysi - obsluha
e.on('click', function() {
if (this.getAttribute('class') === marked) {
if (editor.document.$.getElementsByClassName(marked_unique).length > 0) {
this.removeAttribute('class');
} else {
this.setAttribute('class', marked_unique);
}
} else if (this.getAttribute('class') === marked_unique) {
this.removeAttribute('class');
} else {
this.setAttribute('class', marked);
}
});
}
}
});
Filtering (only CKEditor >=4.1)
This attribute is removed because CKEditor does not allow it. This filtering system is called Advanced Content Filter and you can read about it here:
http://ckeditor.com/blog/Upgrading-to-CKEditor-4.1
http://ckeditor.com/blog/CKEditor-4.1-Released
Advanced Content Filter guide
In short - you need to configure editor to allow onclick attributes on some elements. For example:
CKEDITOR.replace( 'editor1', {
extraAllowedContent: 'strong[onclick]'
} );
Read more here: config.extraAllowedContent.
on* attributes inside CKEditor
CKEditor encodes on* attributes when loading content so they are not breaking editing features. For example, onmouseout becomes data-cke-pa-onmouseout inside editor and then, when getting data from editor, this attributes is decoded.
There's no configuration option for this, because it wouldn't make sense.
Note: As you're setting attribute for element inside editor's editable element, you should set it to the protected format:
element.setAttribute( 'data-cke-pa-onclick', 'alert("blabla")' );
Clickable elements in CKEditor
If you want to observe click events in editor, then this is the correct solution:
editor.on( 'contentDom', function() {
var element = editor.document.getById( 'foo' );
editor.editable().attachListener( element, 'click', function( evt ) {
// ...
// Get the event target (needed for events delegation).
evt.data.getTarget();
} );
} );
Check the documentation for editor#contentDom event which is very important in such cases.

Init js after Ajax call

I have a small Javascript used to swap images using a href links. Everything is OK except when I call those a ref inside the page using Ajax. I need to find a way to initialize again the script after calling new links.
Here is the JS:
<script type="text/javascript">
$('a.thumbnail').click(function(){
var src = $(this).attr('href');
if (src != $('img#largeImg').attr('src').replace(/\?(.*)/,'')){
$('img#largeImg').stop().animate({
opacity: '0'
}, function(){
$(this).attr('src', src+'?'+Math.floor(Math.random()*(10*100)));
}).load(function(){
$(this).stop().animate({
opacity: '1'
});
});
}
return false;
});
Thank you
You need to delegate the events.
Turn this:
$('a.thumbnail').click(function(){ /*...*/ });
into this:
$('#someWrapperContainer').on('click', 'a.thumbnail', function(){ /*...*/ });
Make sure you're using jQuery 1.7+

jQuery plug in question

I'm using following script to mark current page with a.active on the URL.
$(function () {
var menus = $('#menu >li > a');
menus.removeClass('active');
var matches = menus.filter(function () {
return document.location.href.indexOf(this.href) >= 0;
});
matches.addClass('active');
});
I have following website: website
As we can it does work on all menu items exept 'massage treatments' - why??
Any help much appreciated. Pete
I guess <script type="text/javascript" src="js/index.js"></script> is missing on the 'massage treatments' page

Resources