jQuery Conflict with Collapsible List - javascript

I'm currently using a small jQuery script, included infra, to make lists with the class collapsible-list collapsible/expandable. By default, the script collapses the list (and a bit of CSS, also included infra, makes this easily noticeable for visitors) and then expands it when a visitor clicks on expandable elements.
Here are the scripts:
The jQuery
jQuery(function($) {
function prepareList() {
$('.collapsible-list').find('li:has(ul)')
.click( function(event) {
if (this == event.target) {
$(this).toggleClass('collapsible-list-expanded');
$(this).children('ul').toggle('medium');
}
return false;
})
.addClass('collapsible-list-collapsed')
.children('ul').hide();
};
$(document).ready( function() {
prepareList()
});
});
The CSS
/* Collapsible Lists */
.collapsible-list, .collapsible-list ul, .collapsible-list li {
list-style: none;
}
.collapsible-list .collapsible-list-collapsed:before {
content: "+ ";
font-weight: bold;
color: #00AA00;
}
.collapsible-list .collapsible-list-expanded:before {
content: "- ";
font-weight: bold;
color: #AA0000;
}
The CSS is included for the sake of completeness, it functions exactly as expected. The issue lies with the jQuery (and likely some other script on the page, which I cannot seem to isolate).
The script does collapse all but the top-level sections of any list given the collapsible-list class, but that's where the proper functionality ends. Now, the script behaves as expected in my test environment (and also in JSFiddle); however, once the script is implemented in my site, the lists collapse, expand one click, but, instead of staying expanded after being clicked (as they should), they immediately collapse again. This, quite obviously, renders the script useless once implemented, as any list on which it is invoked by the collapsible-list class is made unusable for visitors.
Here is a link to a page where it is currently implemented: http://wpmudev.docs.omnifora.com/docs/plugins/wpmu-dev-dashboard/. Now, I'm sure there is a script conflict somewhere, but I cannot seem to figure out what script is conflicting.
Additional Information
This site uses Bootstrap, which, I suspect, might be the source of the conflict.
Updates
I've narrowed down the potential conflicts, and it seems that Bootstrap may not be the culprit. Here is a JSFiddle with the list behaving as expected within two different Bootstrap panel setups (nested within panel-body and nested within panel): enter link description here.
I've now managed to get the list working except as to formatting. The jQuery conflict seems to have been caused by a slight error in the way one script was calling another, which leads to the collapsible-list.js file being loaded twice. Here's a working JSFiddle: enter link description here

It might work to add a .toggleClass('collapsible-list-collapsed'); in your click function.

After fiddling with things for a few hours, I was able to resolve the conflicts:
One of the script references was improperly formatted, and that was resulting in two calls to collapsible-list.js, which was causing the unexpected immediately-contract-upon-expansion behaviour.
The original script used just expanded and collapsed as classes, which was interacting with another set of rules and causing unexpected behaviour; the modified script uses more specific classes, which avoid such conflicts.
and then amend the CSS:
Someone had thought it wise to put padding: 0; onto ul and li elements in the theme's primary CSS file; this was, obviously, easily overridden with a more specific rule for .collapsible-list.
The result is that the page is now functioning as expected: http://wpmudev.docs.omnifora.com/docs/plugins/wpmu-dev-dashboard/.

Related

Wordpress: Is it possible to use post-type as part of a css selector in block editor stylesheet?

I created a Wordpress theme and now I am working on an editor-stylesheet for the block editor to better reflect the look of the theme in the editor. For this, I need to be able to address different post types in there.
For the frontend, there is the body_class() function to be used in templates, which inserts - among others - a class which identifies the post-type and can be used in combined selectors to adress certain elements only in a particular post-type.
Now, the post-type class is in the body tag of the page, also in edit mode, but apparently the editor-stylesheet CSS is applied in a kind of "isolated" way – combined selectors which contain classes that are in the body tag won't work in the editor.
So I am looking for something similar which would work in the block editor, so that I can use it in combined selectors which only apply to certain elements in a particular post-type.
Any ideas how to do that?
BTW, I also tried to check the post-type using Javascript/jQuery:
wp.domReady(function() {
var postType = jQuery('form.metabox-base-form input#post_type').attr('value');
if(postType == 'post'){
alert("It's a post!");//in real life some other action...
}
});
But although it would be logical to at least trigger the alert I put in there, nothing happens when I load the edit page of a post, where that input element including its "post" value is clearly present. (?)
Addition: Trying everything I can think of to find a workaround solution, I also tried this script to just see if I can check for body classes at all when I am using the editor:
jQuery(document).ready(function() {
if(jQuery('body').hasClass('post-type-page')) {
alert("It's a page!");
} else {
alert("It's not a page");
}
});
The result on editor pages (i.e. the web page displaying the WP block editor): No alert at all! Why that??? Does the block editor Javascript block/prevent all other Javascript?
P.S.: I posted the first part of this question on StackExchange WordPress development before, but got no reactions at all, so i am trying it here...
I found a solution myself (but with a hint from #JobiWon9178 - thank you!!!). Not pure CSS, but involving some JS/jQuery. It's a script similar to the one I already posted in the question, with the necessary additions to add classes to relevant HTML elements dynamically:
$(document).ready(function() {
if($('body').hasClass('post-type-page')) {
$('#editor').addClass('post-type-page');
} else if($('body').hasClass('post-type-post')) {
$('#editor').addClass('post-type-post');
}
});
This adds the relevant post-type-xxxx class to the #editor DIV, which is one of the outer containers of the block editor. Contrary to the body classes, the classes of this element are relevant for the editor contents and can be used in combined selectors in the editor stylsheet.
(Note: Initially I tried to add the class to the wrapper DIV which has the classes edit-post-visual-editor editor-styles-wrapper, but that wouldn't work - the post-type class simply didn't get added.)
An example: The following CSS rule in the editor stylesheet will now apply to all paragraph blocks in the editor, but only when the post-type is a page:
.post-type-page .wp-block-paragraph {
/* (CSS settings here) */
}
An important detail, which #JobiWon9178 pointed out in a comment: The jQuery script above has to be added using admin_enqueue_scripts , not together with the scripts for the frontend. So I put that script into a file called admin_scripts.js and enqueued that as follows in functions.php:
function my_admin_scripts($hook) {
if ( 'post.php' != $hook ) {
return;
}
wp_register_script('adminscripts', get_template_directory_uri() . '/js/admin_scripts.js', '', null, true);
wp_enqueue_script('adminscripts');
}
add_action( 'admin_enqueue_scripts', 'my_admin_scripts' );
So that's my working solution. If someone still comes up with a pure CSS solution, I'd be very happy, but I guess for now (i.e. in Wordpress 5.3) there is no CSS-only method for this.
I was able to do this with purely CSS. Are you sure your CSS is getting added correctly?
add_action('admin_head', 'my_custom_fonts');
function my_custom_fonts() {
echo '<style>
.post-type-page .wp-block-paragraph {
font-size: 5rem;
}
</style>';
}
If I go into the editor this paragraph text is only modified under a page.
This is running WP 5.3.2.
add_editor_style is technically used for custom TinyMCE.
Using enqueue_block_editor_assets was added in WP 5.0 to add styles and functionality to blocks.
https://developer.wordpress.org/reference/hooks/enqueue_block_editor_assets/
Edit:
CSS only version
function custom_block_editor_styles() {
wp_enqueue_style( 'legit-editor-styles', get_theme_file_uri( '/css/style-editor.css' ), false, '1.0', 'all' );
}
add_action( 'enqueue_block_editor_assets', 'custom_block_editor_styles' );

Updating a simple jQuery / CSS code

I was hoping someone could help me out with this simple question: I’ve just started to learn jQuery and found a code to show hidden text after selecting an item.
I’d like to update it so that:
a.) The selected item is bold
b.) I can add placeholder text instead of starting off with a blank hidden text field
I foolishly assumed I could solve a.) by using the :active property in css, but that only works as long as the link is clicked on. (As soon as you release the mouse button it’s gone.) Just like b.), this is probably only possible by using jQuery as well? If so, would be really great if you could show me how to solve it. :)
The codes: http://jsfiddle.net/KUtY5/1/
JS
$(document).ready(function(){
$("#nav a").click(function(){
var id = $(this).attr('id');
id = id.split('_');
$("#menu_container div").hide();
$("#menu_container #menu_"+id[1]).show();
});
});
CSS
#menu_container {
width: 650px;
height: auto;
padding-left: 30px;
}
#menu_container div {
display:none;
}
HTML
<div id='nav'>
<a id="show_apps">Appetizers</a> | <a id="show_soups">Soups and Salads</a> | <a id="show_entrees">Entrees</a>
</div>
<div id="menu_container">
<div id="menu_apps">
Content of the App Section Here
</div>
<div id="menu_soups">
Content of the Soups Section Here
</div>
<div id="menu_entrees">
Content of the Entrees Section Here
</div>
</div>
Updated fiddle
You can realize a) using a custom class bold for example and the following code :
CSS
.bold{ font-weight: bold;}
JS
$(this).addClass('bold').siblings('a').removeClass('bold');
For b) I can't find any textfield in your code.
Hope this helps.
I have added some extra lines to your code and you can check it from here http://jsfiddle.net/KUtY5/483/.
You bold like this
$("#nav a").css("font-weight", 400); // First you make them thin
$(this).css("font-weight", 800); // Than you make them bold
You put placeholder like this
<div id="placeholder">
Placeholder
</div>
$("#placeholder").hide();
On the other hand I recommend you not to hide menu container. Rather hide the elements inside the menu_container. So you can put a plcaeholder in menu container and you can hide it.
To figure this out 2 questions must be asked / solved
how do you normally make text bold on a page... css right?
where do you want those styles to be defined? There are 2 places:
a. You can define it inside the javascript.
b. You can define it inside the projects css through normal methods (inline, external, embedded).
What's the difference? If you define it inside the javascript the code is self-contained. What i mean by that is you can copy/paste the JS code from one project to the next and you don't need to worry about copying related styles from the stylesheets or other sources because it's all in the JQuery that you've written.
In contrast if you define it outside the javascript in the regular places the code may not be self-contained however some find it easier to manage in the scope of that particular project because all your css is contained in one place (external stylesheet typically).
If you want to take option a, see the .css() method
If you want to take option b, see the style manipulation (toggle class in particular)
Note the examples in the above references should get you 90% of the way to understanding it.
Some final words. Learn Jquery, but i advise you to stay away from it as much as possible because it implements DOM thrashing instead of DOM caching (sizzle engine).
This video series will briefly go into why Jquery sucks for performance in the first video and the rest of the series is about how to create modular vanilla JS.
JQuery goes back and searches the DOM every time you need to make a change that is what
$.(*element*) is doing instead of just caching it.
The more nodes you have in the DOM the more processing power is used searching (because it has to go through the entire tree).
Then on top of that the more elements you have to make changes to (say if you use a css class selector) you have to add even more processing on top of that.
All this is fine if you're on a desktop, but what about a mobile platform? Where would you get all this processing power from?... It doesn't exist.

How do you run javascript with only a CSS class?

I am working on a web site project which requires that the web page display tooltips on hover. For reasons I won't go into here, I decided to use a library from a site called dyn-web.com. The library works great but I need to make one small change to the way it works, which will make it a perfect fit for my application. Trouble is, I can't figure out how it works!
Everything I've read says that you can't execute javascript code from within CSS. But that seems to be exactly what this library does. To create a tooltip for any element (anchor, div, span, etc), all this library requires you to do is:
Include a <script src= > tag to the library file
Add two class names to the HTML element that will host the tooltip, one called "showTip" and the other is a key into a JSON object containing the tooltip text
Add the tooltip text to the JSON object mentioned above
Create a style to format the tooltip how you want it to look
If you'll notice, nowhere in these steps is mention of any event handlers. Moreover, there's no class (that I can find) called showTip. There's no JQuery or other dependencies. So how does the javascript get executed?
I don't want to jump in and start changing the library willy-nilly without knowing how it works, and I've been pulling out my hair trying to figure it out. Can one of you smarter-than-me folks explain it?
You can't run JavaScript from a "CSS Class".
The example you give runs JavaScript by including a <script> element.
I'm not going to reverse engineer it because it is not formatted in a way that is particularly human readable. That said, searching the file finds showTip so it presumably searches the DOM for that HTML class (maybe with querySelectorAll since it certainly uses that method for something) and binds event handlers with JavaScript (probably with addEventLister since it contains a call to that method).
If you'll notice, nowhere in these steps is mention of any event handlers.
The JS file uses addEventListener
Moreover, there's no class (that I can find) called showTip.
You said you added it to an HTML element in a previous step. As mentioned above, the string appears in the JavaScript file.
From the steps you listed, it seems as if Dynamic Web Coding library uses the JavaScript to apply CSS attributes into elements. You can not run JavaScript inside CSS, but you can certainly apply CSS using JavaScript!
What the library probably does is look at all of the elements with class showTip with JavaScript using document.getElementsByClassName("showTip"). Then, it looks at the other class on that element and assumes that as the key to the tooltip with the tooltip text.
Then, it probably creates an element for the tooltip using document.createElement() and then injects that into the document using document.appendChild() or document.insertBefore(). They can probably add some class to the tooltip so your CSS rules are applied using the .className property.
It also likely used document.addEventListener() to listen for when the user hovers over and leaves the element. You don't need to add this code because the <script> tag you added has called document.addEventListener() for you.
All of these things require something known as the DOM which is something JavaScript coders use to manipulate the HTML document. This is actually really powerful and cool, so you can check out a good tutorial on it at TutorialsPoint.
It works with event delegation, it listens to events on the entire body and ignores events on elements without the magic class names (showTip).
Excerpt from http://www.dyn-web.com/code/tooltips/js/dw_tooltip_c.js:
initHandlers: function() {
var _this = dw_Tooltip;
if (_this.ready) {
return;
}
if (!_this.forTouch) {
if (_this.activateOnClick) {
dw_Event.add(document, 'click', _this.checkForActuator, true);
dw_Event.add(document, "mouseup", _this.checkDocClick, true);
} else {
dw_Event.add(document, 'mouseover', _this.checkForActuator, true);
}
dw_Event.add(document, "keydown", _this.checkEscKey, true);
if (!_this.activateOnClick && _this.activateOnFocus) {
if (window.addEventListener) {
dw_Event.add(document, 'focus', _this.checkForActuator, true);
} else if (window.attachEvent) {
dw_Event.add(document, 'focusin', _this.checkForActuator);
}
}
} else {
dw_Event.add(document, 'mouseover', _this.checkForActuator, true);
}
_this.ready = true;
}
}
There is one way by which you can make custom tool-tip(s). This is little out of box & requires only HTML and CSS to perform the magic.
"Use JS to add more interactivity".
<html>
<style>
h1 { position: relative; }
h1:hover:after {
content: attr(data‐hover‐response);
color: black;
position: absolute;
left: 250px;
top: -40px;
border: solid 2px gray;
background-color: red;
}
</style>
<body>
<br><br>
<h1 data‐hover‐response="I am tooltip !"> I will show tooltip on Hover! </h1>
<body>
</html>
CodePen : http://codepen.io/anon/pen/bdPQxP

Expand/Collapse Text

The code below works fine with ONE Reveal/Hide Text process
<div class="reveal">Click Here to READ MORE...</div>
<div style="display:none;">
<div class="collapse" style="display:none;">Collapse Text</div>
However if this code is duplicated multiple times, the Collapse Text shows up and doesn't disappear and in fact conflicts with the Expand to reveal even more text instead of collapsing as it should.
In this http://jsfiddle.net/syEM3/4/ click on any of the Click Here to READ MORE...
Notice how the Collapse Text shows up at the bottom of the paragraphs and doesn't disappear. Click on the Collapse and it reveal more text.
How do I prevent this and getting to work as it should?
The two slideDown function calls are not specific to the .reveal and/or .collapse that you are currently doing. i.e.
$(".collapse").slideDown(100);
will find all the elements with the class .collapse on the page, and slide them down. irrespective of what element you just clicked.
I would change the slideDown call to be relavant to the element you just clicked i.e. something like this
$('.reveal').click(function() {
$(this).slideUp(100);
$(this).next().slideToggle();
$(this).next().next(".collapse").slideToggle(100);
});
in your code
$('.reveal').click(function() {
$(this).slideUp(100);
$(this).next().slideToggle();
$(".collapse").slideDown(100);
});
$('.collapse').click(function() {
$(this).slideUp(100);
$(this).prev().slideToggle();
$(".reveal").slideDown(100);
});
this two rows doesn’t do what you want as they act on all elements of the specified class
$(".reveal").slideDown(100);
$(".collapse").slideDown(100);
When you do $(".collapse").slideDown(100);, jQuery runs slideDown on everything with the .collapse class, not just the one that's related to your current this. To fix this, refer to the collapse based on its location to $(this).
Do do this, use something like $(this).siblings(".collapse").slideDown(100);
Note that this particular selector will only work if you enclose each text block in its own div. With each text element in its own div, like you have it now, .siblings(".collapse"), which selects all the siblings of $(this) with the collapse class, will still select both of the collapse elements.
Okay, I think you should take a different approach to your problem.
See, jQuery basically has two purposes:
Selecting one or more DOM elements from your HTML page
manipulate the selected elements in some way
This can be repeated multiple times, since jQuery functions are chainable (this means you can call function after function after function...).
If I understood your problem correctly, you are trying to build a list of blog posts and only display teasers of them.
After the user clicks the "read more" button, the complete article gets expanded.
Keep in mind: jQuery selects your elements very much like CSS would do. This makes it extremely easy to
come up with a query for certain elements, but you need to structure your HTML in a good way, like
you would do for formatting reasons.
So I suggest you should use this basic markup for each of your articles (heads up, HTML5 at work!):
<article class="article">
<section class="teaser">
Hey, I am a incredible teaser text! I just introduce you to the article.
</section>
<section class="full">
I am the articles body text. You should not see me initially.
</section>
</article>
You can replace the article and section elements with div elements if you like to.
And here is the CSS for this markup:
/* In case you want to display multiple articles underneath, separate them a bit */
.article{
margin-bottom: 50px;
}
/* we want the teaser to stand out a bit, so we format it bold */
.teaser{
font-weight: bold;
}
/* The article body should be a bit separated from the teaser */
.full{
padding-top: 10px;
}
/* This class is used to hide elements */
.hidden{
display: none;
}
The way we created the markup and CSS allows us to put multiple articles underneath.
Okay, you may have noticed: I completely omitted any "read more" or "collapse" buttons. This is done by intention.
If somebody visits the blog site with javascript disabled (maybe a search engine, or a old mobile which doesn't support JS or whatever),
the logic would be broken. Also, many text-snippets like "read more" and "collapse" are not relevant if they don't actually do anything and are not part of the article.
Initially, no article body is hidden, since we didn't apply the hidden css class anywhere. If we would
have embedded it in the HTML and someone really has no JavaScript, he would be unable to read anything.
Adding some jQuery magic
At the bottom of the page, we are embedding the jQuery library from the google CDN.
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
This is a best practice and will normally speed up your page loading time. Since MANY websites are embedding
jQuery through this URL, chances are high that its already in the visitors browser cache and doesn't have
to be downloaded another time.
Notice that the http: at the beginning of the URL is omitted. This causes browsers to use the pages current protocol,
may it be http or https. If you would try and embed the jQuery lib via http protocol on a https website, some browsers will refuse to download the file from a unsecure connection.
After you included jQuery into the page, we are going to add our logic into a script tag. Normally we would
save the logic into a separate file (again caching and what not all), but this time a script block will do fine.
Finally some JavaScript
At first, we want to hide all elements with the css-class full, since only teasers should remain displayed. This is very easy with jQuery:
$('.full').hide();
The beginning of the script $('.full') tells jQuery: I need all elements with the CSS-class full. Then we call a function on that result, namingly hide() which purpose should be clear.
Okay, in the next step, we want to add some "read more" buttons, next to every teaser. Thats an easy task, too:
$('.teaser').after('<button class="more">Read more</button>');
We now select every element with the css-class teaser and append some HTML code after() each element - a button with the css-class more.
In the next step, we tell jQuery to observe clicks on every one of this freshly created buttons. When a user has clicked, we want to expand the next element with the css-class full after the clicked button.
$('.more').on('click', function(){
//"this" is a reference to the button element!
$(this).slideUp().next('.full').slideDown();
});
Phew, what did we do here?
First, we told jQuery that we wanted to manipulate this, which is a reference to the clicked button. Then we told
jQuery to hide that button (since its not needed anymore) slowly with slideUp().
We immediately continued telling jQuery what to do: Now take the next() element (with the css-class full) and make it visible by sliding it down with slideDown().
Thats the power of jQuerys chaining!
Hiding again
But wait, you wanted to be able to collapse the articles again! So we need a "collapse" button, too and
some more JavaScript:
$('.full').append('<button class="collapse">Collapse text</button>');
Note: we didn't use the after() function to add this button, but the append() function to place the button
INSIDE every element with the css-class full, rather than next to it. This is because we want the
collapse buttons to be hidden with the full texts, too.
Now we need to have some action when the user clicks one of those buttons, too:
$('.collapse').on('click', function(){
$(this).parent().slideUp().prev('.more').slideDown();
});
Now, this was easy: We start with the button element, move the focus to its parent() (which is the element that contains the full text) and tell jQuery to hide that element by sliding it up with slideUp().
Then we move the focus from the full-text container to its previous element with the css-class more, which is its expanding button that has been hidden when expanding the text. We slowly show that button again by calling slideDown().
Thats it :)
I've uploaded my example on jsBin.

Progressive enhancement and "flash of unhidden content"?

I am trying to build a progressively enhanced page that works for JS and non-JS users. I would like to hide some form controls initially for JS users, but always show them for non-JS users.
My question is about how to do this without creating a distracting "visible, then instantly hidden" flash of content for JS users.
For example, in the JS version, I want to collapse part of my search form, and instead show a 'click here for extra options' button. I do this as follows:
$(document).ready(function() {
$("#extra-options").hide();
...
$("#show-extra-options").click(function() {
$("#extra-options").slideToggle();
});
});
This works, but it means that for JS users, when the page loads, the extra options are visible for 500ms or so, then they vanish. It's rather distracting.
Is there any sensible way to get around this?
StackOverflow has just suggested this answer: is this sensible? Sorry if this is now a duplicate question, but I figure it's still worth writing this question in my own language, as I didn't find the answer during searching.
Add this in your script tag in the head:
$('html').addClass('js');
Then you can use that to show and hide elements:
.hasJs { display: none; }
.js .hasJs { display: block; }
.js .noJs { display: none; }
You can hide content for either users with or without Javascript:
<div class="hasJs">Some content only visible for JS users.</div>
<div class="noJs">Some content only visible for non-JS users.</div>
As the class and CSS are in the head, the elements will already be styled when they come into existance when the body is parsed.
Demo: http://jsfiddle.net/Guffa/YuAyr/
This is a similar approach to the one in the first answer to the question that you linked to, but this is somewhat cleaner because you don't have to add a class to the html element in the markup, and the code simply adds the class instead of removing it.

Categories