We got a simple jQuery script on Drupal site that injects a div class with content:
(function($) {
Drupal.behaviors.myHelpText = {
attach: function (context, settings) {
//code starts
//change placeholder text
$('.form-item-quantity').append('<span class="help-block">For orders over 10 call for volume pricing</span>');
$('.help-block').css("flex-basis", "100%");
//code ends
}
};
})(jQuery);
The page has Drupal Commerce and various product attribute fields that gets processed by Ajax every time selecting an attribute. And when doing that our script injects same duplicate line each time on Ajax load/update.
How to avoid that? We just want jQuery code work once on page load.
Only add the element if it doesn't exist, otherwise do nothing.
(function($) {
Drupal.behaviors.myHelpText = {
attach: function (context, settings) {
if (!document.getElementById('help')) {
$('.form-item-quantity').append(
'<span id="help" class="help-block">For orders over 10 call for volume ricing</span>'
);
$('.help-block').css("flex-basis", "100%");
}
}
};
})(jQuery);
You have to understand that drupal.behaviors fire on page load and when ajax returns results. It is designed this way because you may want your code to run again on the ajax results, for example, if you are updating part of the page via ajax and it needs event listeners applied, or a class added.
The context variable is the key here.
on first page load, the context will be the whole window, but when ajax returns the result, the context will just be what is returned by the ajax.
Knowing this, you should be using context in your jquery selectors.
eg.
(function($) {
Drupal.behaviors.myHelpText = {
attach: function (context, settings) {
//code starts
//change placeholder text
$('.form-item-quantity', context).append('<span class="help-block">For orders over 10 call for volume pricing</span>');
$('.help-block', context).css("flex-basis", "100%");
//code ends
}
};
})(jQuery);
For added protection against something processing multiple times, you can use jquery once(), but this is usually not needed if using the context variable in the selector. jQuery once() separate library that must be loaded.
Why don't use jQuery once? My thought — it's a classic approach. A bunch of examples lives in docs on drupal.org
$('.form-item-quantity').once('help-appended').append('<span class="help-block">For orders over 10 call for volume pricing</span>');
And I'm not sure you need to apply styles via js. A css file is a better place for it. And jquery once should be available in your environment. That's it.
Related
I have Jquery function that executes AJAX query to server.
How can I call this after load page in the specified url page? May I bind this to element HTML, I mean:
<div id="graph" onload="function()"></div>
jQuery handles the HTML file with a variable called document.
Document has two popular event states
load when the page has been loaded
ready when the page has been loaded and all other decorations to the HTML have been applied.
jQuery provides hooks for these states.
To run javascript code after each of the events listed above, you have to put the function within the appropriate event scope.
For loading, this would be…
$(document).load(function() {
// javascript code you want to execute
})
After the page has been ready, but not yet rendered, you can apply some other javascript code using
$(document).ready(function() {
// javascript code you want to execute
})
One way using jQuery:
$(document).ready( function() {
//do whatever you need, you can check if some element exists and then, call your function
if($("#graph").length > 0)
callfunction();
});
No jQuery, only vanilla js:
window.onload = function() {
if(document.getElementById("graph"))
callfunction();
}
I populate many parts of my website using
$("#theDivToPopulate").load("/some/api/call.php", callBackToBindClickEventsToNewDiv);
Where /some/api/call.php returns a built list, div, or some other HTML structure to place directly into my target div. The internet has been running slow lately and I've noticed that the time between a button click (which kicks off these API calls) and the div populating is several seconds. Is there an easy way to globally wrap all the load calls so that a div containing "Loading..." is displayed before the call is even made and hidden once the API call is complete.
I can not simply put the code to hide the div into the callBackToBindClickEventsToNewDiv as some load events have different call backs. I would have to copy the code into each function which is ugly and defeats the purpose. I want the flow of any .load to go as follows:
1) dispplayLoadingDiv()
2) Execute API call
3) Hide loading div
4) do callback function.
The loading div must be hidden first as the callback contains some animations to bring the newly loaded div in nicely.
EDIT:
Expanding on jacktheripper's answer:
var ajaxFlag;
$(document).ajaxStart(function(){
ajaxFlag = true;
setTimeout(function (e) {
if(ajaxFlag) {
hideAllDivs();
enableDivs(['loading']);
}
}, 500);
}).ajaxStop(function(){
ajaxFlag = false;
var load = $("#loading");
load.css('visibility','hidden');
load.css('display','none');
load.data('isOn',false);
});
This way loading is only displayed if the page takes more than 500 MS to load. I found the loading flying in and out real fast made things kind of choppy for fast page loads.
Use the following jQuery:
$(document).ajaxStart(function(){
$('#loader').show();
}).ajaxStop(function(){
$('#loader').hide();
});
Where you have an element called #loader that contains what you want to show when an AJAX request is being performed. It could be a span with text, an image (eg a gif), or anything similar. The element should be initially set to display: none
You do not even need to call the function anywhere else.
Try this
$("#someButtonId").click(function(e){
e.preventDefault();
$("#theDivToPopulate").html("Loading...");
$.get("/some/api/call.php",function(data){
$("#theDivToPopulate").fadeOut(100,function(){
$("#theDivToPopulate").html(data).fadeIn(100,function(){
//Do your last call back after showing the content
});
});
});
});
I'm working on a project, which in some cases requires to hide all small text (eg. less than 12px), and on some other event, bring them back. It's not a website, but something happening on the webkit browser. I don't have control over the content on the page (developed by the third party developers), but have control to modify it. I know I can loop through all tag elements and check font sizes and hide them if smaller than 12px, but it's not only inefficient, but the text can be changed to be shown again, say after an ajax call, which is "prohibited". Other solution would be to run that loop every couple seconds, but it's an expensive process.
Other task is to show small text on some other event, which is not too difficult to implement by just using simple custom class.
You can run the code on page-load, and then when any AJAX call completes using jQuery's Global AJAX Event Handlers: http://api.jquery.com/category/ajax/global-ajax-event-handlers/
$(function () {
function findSmallText($root, state) {
if (typeof $root == 'undefined') {
$root = $(document);
}
if (typeof state == 'undefined') {
state = 'none';
}
$.each($root.find('p, div, span, font, button, a'), function () {
if ($(this).css('font-size').replace(/(px|pt|em)/gi, '') <= 12) {
$(this).css('display', state);
}
});
}
//run the function when the DOM is ready
findSmallText();
//also run the function when any AJAX request returns successfully
$(document).ajaxSuccess(findSmallText);
});
You can pass the findSmallText function two arguments:
$root: (jQuery object) the root element to start looking for small text, limit this as much as possible to increase performance (so unnecessary elements don't have to be scanned).
state: (string) the display property to add to the elements with small text, you can use block to show the elements.
if the HTML structure doesnt change (no extra containers added thru AJAX) simply analyze the page onLoad (kinda like what Jasper suggests) but instead of re-running the analysis after each AJAX call you add a new class - let's call it .HideMeInCertainCases for the fun of it. That way you can hide / show everything you want with a simple selector whenever you want.
So instead of this line: $(this).css('display', state); use $(this).addClass('HideMeInCertainCases');
When the event you were talking about occurs you can then toggle the display state with this selector $("HideMeInCertainCases").toggleClass("hideMe"). Changing the display-attribute directly might break your layout as the nodes containing text might have different displays to begin with (block, inline, inline-block...). Of course .hideMe { display:none; } should be somewhere in your stylesheet. If you want the layout to stay the same and only hide the content use visibility instead of display
I would like to refire the styling and processing.js scripts that i linked to in the head so that they display correctly when brought in through an ajax-request. I see where in the ajax request this code needs to be, but i don't know how to tell the code to simply reapply the script. I've seen people using getScript() to do this, but from what i can tell this reloads the script, rather than simply telling it repeat or refire. Do all of the scripts need their own reinitialization? I found the syntax highlighters .highlight() method, but i am yet to get the processing script to load. currently, Processing.loadSketchFromSources($('#processing'), ['mysketch.pde']); does not work. I am using current versions of all libraries. Surprised i haven't been able to find the answer yet, as a lot of people seem to have the same problem. Thanks for your help!
index page:
$(document).ready(function () {
// put all your jQuery here.
//Check if url hash value exists (for bookmark)
$.history.init(pageload);
//highlight the selected link
$('a[href=' + document.location.hash + ']').addClass('selected');
//Search for link with REL set to ajax
$('a[rel=ajax]').live("click",function(){
//grab the full url
var hash = this.href;
//remove the # value
hash = hash.replace(/^.*#/, '');
//for back button
$.history.load(hash);
//clear the selected class and add the class class to the selected link
$('a[rel=ajax]').removeClass('selected');
$(this).addClass('selected');
//hide the content and show the progress bar
//$('#content').hide();
$('#loading').show();
//run the ajax
getPage();
//cancel the anchor tag behaviour
return false;
});
});
function pageload(hash) {
//if hash value exists, run the ajax
if (hash) getPage();
}
function getPage() {
//generate the parameter for the php script
var data = 'page=' + encodeURIComponent(document.location.hash);
$.ajax({
url: "loader.php",
type: "GET",
data: data,
cache: false,
success: function (html) {
//hide the progress bar
$('#loading').hide();
//add the content retrieved from ajax and put it in the #content div
$('#content').html(html);
//display the body with fadeIn transition
$('#content').fadeIn('fast');
//reapply styles?
//apply syntax highlighting. this works
SyntaxHighlighter.highlight();
//relaod processing sketch, currently displays nothing
Processing.loadSketchFromSources($('#processing'), ['mysketch.pde']);
}
});
}
This the ajax-loaded content:
<!--ajax'd content-->
<??>
<h2>code</h2>
<pre class="brush: php">
$last_modified = filemtime("header.php");
echo("last modified: ");
echo(date("m.j.y h:ia", $last_modified));
</pre>
<script type="application/processing">
</script>
<canvas data-processing-sources="mysketch.pde" id="processing">
</canvas>
</div>
</body>
</html>
<??>
So, let's analyze what usually happens when you include an (external or internal) Javascript code: It will automatically execute only the code that is available in the global scope. "Good" scripts will only add one command to the global scope which will then execute the initialization code somewhere in a function/method.
All you need to do is view the external Javascript file and find out what is being executed from the global scope. There is no general answer to that ... some scripts use an object and call its init() method ... but that is totally subject to the imagination of the developer.
If you have javascript that needs to trigger, you MUST add this to the head element:
var head = document.head || document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.innerHTML = "your AJAX-obtained js code";
head.appendChild(script);
The same trick goes for CSS. Add a element to the head with your CSS declarations as innerHTML. So: make sure to preprocess your AJAX response and split out the JavaScript and CSS elements, then add those to the document header. It's probably easier to make your response a JSON object along the lines of:
{
html: "<html>string<goes>here</goes></html>",
scripts: ["url1","url2","url2",...],
style: ...
}
and then parsing that JSON for the html (which you use as innerHTML for a new document.createElement("div") or something, and then append wherever it needs appending), the scripts (which you turn into elements for HEAD insertion) and the style declarations (which you turn into elements for HEAD insertion).
(On a functional note, your example AJAX response looks like it has PHP code in it. I have no idea what you're using it for, but that looks like a bad response)
Just incase anyone stumbles upon this:
If you have processing.js already loaded, simply call Processing.reload() in your AJAX success/complete function.
Perhaps you already have an element with id="processing" on your page. In that case $("#processing") will only return the first one. If that is the case, change the id or use a class instead.
The other option, which I don't recommend, is to use $("[id=processing]"). That will return every element on the page with id="processing". But, don't use it. Use unique ids in your page, or switch to using classes, whichever works best for you.
I'm loading remote data using dynamic script tags and JSON. the remote page that I'm displaying on my website has a div in it that I use to load content into.
The problem is the javascript functions do not see the div as the page loads because it is remote data. If I set a timeout of about 300, it usually works and my javascript can see the div. But sometimes it takes longer and it breaks the javascript.
I'm tring this:
function load_content() {
if (document.getElementById('remote_div') == null) {
setTimeout('load_content()', 300);
} else {
document.getElementById('remote_div').innerHTML = 'Content goes here'
}
}
but it just doesn't work. What am I doing wrong?
You may want to do this using setInterval. Something like:
var intrval = setInterval( function(){
if(document.getElementById('remote_div')) {
load_content();
clearInterval(intrval);
}, 50);
function load_content() {
//loading content here
}
This way you don't have to estimate the loading time. load_content is executed when div#remote_div can be found in the DOM tree.
Edited based on comments, forgot to assign the interval, so it wouldn't ever clear indeed.
Are you using iframe?
If so, try
document.getElementById('YOUR_IFRAME_ID').contentWindow.document.getElementById('remote_div')