How to inject userscripts into embedded web page content? - javascript

I have a userscript I'd like to inject after clicking a link to open an embedded web page. Specifically, I'd like to know how I should go about injecting this script:
Array.from(document.querySelectorAll('span')).filter(i => /(\d+)\spoint/.test(i.innerHTML)).forEach(i => i.style.display = 'none');
Array.from(document.querySelectorAll('div')).filter(i => /moreComments-/.test(i.id)).forEach(i => i.querySelector('p').click());
setTimeout(function() {
Array.from(document.querySelectorAll('span')).filter(i => /(\d+)\spoint/.test(i.innerHTML)).forEach(i => i.style.display = 'none');
},7000)
Into all embeddable Reddit subpages, i.e. all linked threads loaded from a subreddit's main page:
An image illustrating an embedded Reddit subpage in contrast to a standard fully-loaded page
I assume this requires an event listener or MutationObserver, but I don't know how to go about specifying the solution. Reading the Reddit source is deeply confusing and I'm not a coder. This is just something I think would be useful to know for myself and other everyday web users.
What are the right steps to follow in problem-solving a case like this? What kind of code should I consider?

You need to process the userscript on a selected group of page nodes. Because your code is only executed once per page load event, the nodes that load dynamically later on remain unaffected.
There are several ways to achieve your goal. One of the simplest approaches involves the use of the setInterval function. This calls your function periodically (by using a reasonable time span, say 500ms). With this function, you have to select the nodes you want and process only new arrival nodes. You can mark your node's DOM objects with something like the _is_processed property. And finally this can be wrapped into another function, say doForEachOnce. For example:
function doForEachOnce(list,theFunc) {
list.forEach(i => {
if (!i._is_processed) { i._is_processed = true; return theFunc(i); } else { return null; }
});
}
doForEachOnce(Array.from(document.querySelectorAll('span')).filter(i => /(\d+)\spoint/.test(i.innerHTML)), i => i.style.display = 'none');
doForEachOnce(Array.from(document.querySelectorAll('div')).filter(i => /moreComments-/.test(i.id)), i => i.querySelector('p').click());
setInterval(function() {
doForEachOnce(Array.from(document.querySelectorAll('span')).filter(i => /(\d+)\spoint/.test(i.innerHTML)), i => i.style.display = 'none');
},500);
I tested this userscript successfully with Tampermonkey. It expands all comments, hides the karma on page load, and then hides the karma again for each new arrival. Hope this is what you need.

Your question is too general, but I assume you asking something basic. If targeting some specific platform, please clarify your question.
I assume you want just inject javascript code into the web HTML page or part of HTML page.
Usually this done via script tag
<script>
/* Your code is here */
Array.from(document.querySelectorAll.......
</script>
To force a link to call your code, you have to wrap your code inside function and format href attribute of a tag as javascript:func_name()
<script>
function my_func()
{
/* Your code is here */
Array.from(document.querySelectorAll.......
}
</script>
click me

Related

How to add a "pin it" button dynamically?

I'm trying to optimize my Wordpress install - and one of the culprits I'm seeing, is the "Pin It" button widget for Pinterest. I'm now trying to find a way to dynamically load the JS code (when they initially hover over the button), and then apply it to the page. So far I've only managed to get this far:
jQuery(document).on("mouseenter click",'.pinItSidebar', function() {
jQuery.getScript("http://assets.pinterest.com/js/pinit_main.js", function() {
// do something here?
});
});
I can't seem to find a function that I can call (as a callback, after the JS is loaded). Obviously I will only do this once per page load (the above is just a very basic version at the moment)
Does anyone have any suggestions on how I can achieve this? The end game of how I want it to function, is with:
Working solution:
Here is a working solution I've now got, which will allow you to load the pinterest stuff ONLY when they click the button (and then trigger the opener as well). The idea behind this, is that it saves a ton of Pinterest JS/CSS / onload calls, which were slowing the page down.
jQuery(document).on("click",'.pinItSidebar', function() {
if (typeof PinUtils == "undefined") {
jQuery.getScript("http://assets.pinterest.com/js/pinit_main.js", function() {
PinUtils.build();
PinUtils.pinAny();
});
} else {
PinUtils.pinAny();
}
});
...and just call with:
foo
Hopefully this helps save someone else some time :)
Extra tweak: I'm just playing around, to see if I can make this even more awesome :) Basically, I want to be able to decide which images are pinnable. Below this will do that for you:
jQuery("img").each(function() {
if (jQuery(this).data('pin-do')) {
// its ok, lets pin
} else {
jQuery(this).attr('nopin',"1");
}
});
All you need to do to make an image pinnable, is have the data-pin-do="1" param set the images you want to allow them to share :)

editing a Div that is around an iframe via jquery from within said iframe

I'm sure this sounds a little odd, but here's the background...
We utilize a company that loads their chat program, so we can support our customers, into our page. This is done via javascript and jquery, and creates a structure like this:
<div id="myid" style="...; right: 0px;..."><div><iframe></iframe></div></div>
There's a WHOLE lot more to that, but those are the important parts. Now the tool allows us to put custom scripting, which will be placed in the iframe. My goal is to just remove the "right: 0px", which I have done via the below code, but I don't want to put that code on every page that this tool integrates with. I would like to load it into the tool, and have it run when the iframe and divs are created.
working code on parent:
$(document).ready(function() {
function checkPos() {
$('#myId').each(function() {
var oldstyle = $('#myId').attr('style');
var newstyle = oldstyle.replace(' right: 0px;','');
$('#myId').attr('style', newstyle);
});
setTimeout(checkPos, 100);
};
$(document).ready(function() {
setTimeout(checkPos, 100);
});
});
Once placed in the code include method they provide, I have trouble having it wait until the div tag actually has the "right: 0px;" in its style tag. the only thing I need to run is the three lines in the $('#myId').each(function()
Basically, I need help with having the script in the iframe target the div that the iframe is nested in.
Assuming that whatever tool your using actually lets you pass in a custom script to the content rendered in the iframe (seems fishy to me), a better way of modifying the style in jquery is to use the css function:
$('#myId').css('right', '0px');
Notice I removed the $.each function as well. You are targeting an element with an id, so there isn't any need to iterate.
Edit:
Anyways, back to the problem of delaying execution to when the target, #myId, actually exists. If they are really injecting your javascript into their page (again, seems fishy), then attaching the above code to the $(document).ready() event should do the trick, as long as this listener is attached to their document.
If all else fails, try to use the waitUntilExists plugin, here:
Source:
https://gist.github.com/buu700/4200601
Relevant question:
How to wait until an element exists?

load a part of the page without refreshing it

I am pretty new to web design. I just finished my first static website.
What i dont like about it is that the page often reload (each time you change section)
My question is "how to make this kind of nav : http://www.doblin.com/work/#innovation-strategy"
As you see the page doesnt reload when you click on "Set Innovation Strategy / Design, Build + Launch Innovations / Become Better Innovators"
How it s done ?
Is it possible on a static website (html/css/jquery) without sql or so (it may require ajax or ...) ?
Thanks
Taking a look at the code, this page uses some jQuery functions to do this work, check it out:
$(window).hashchange( function(){
var hash = location.hash;
// if a hash has been set
if(hash !== '') {
showContent(hash, origSections);
} else {
// pass in "empty" hash
showContent('', origSections);
}
return false;
});
// call hashchange on initial page load
$(window).hashchange();
// ----------- SHOW CONTENT ----------- //
function showContent(active, all) {
if (jQuery.support.opacity) {
opacity = true;
} else {
opacity = false;
}
This can also be done using CSS3 transitions and animations. Here's a pen based on this Codrops tutorial. I particularly think this is a better approach
But if you want to get dynamic data from somewhere, i advise you to use Ajax(There are some cool jQuery stuff to handle this)
you can use Jquery on your static website. You can go through below link
Jquery
But if you wish to save data and retrieve from server then you need to use Ajax to avoid refreshing page.There are many tutorials available on how to use Ajax
Take a (hard) look at JQuery or Dojo, any of them will be your friend.
Your example uses JQuery.
It can be done on one static page without worries.
Here you have some demos: http://jqueryui.com/tabs/
You can also study jquery on http://w3schools.com/jquery/default.asp

shadowbox stops working after jquery function call

I have a shadowbox script. When I load the page everything works fine, but when I call this jquery load function and then try to trigger the shadowbox by clicking on the image, the large image opens in new window instead.
Here's the code:
<link href="CSS/main.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="shadowbox-3.0.3/shadowbox.js"></script>
<script type="text/javascript">
Shadowbox.init();
</script>
<p id="compas"></p>
Any idea why this is happening?
EDIT
So, we finally get the bottom of this. 15 hours after first commenting on this issue, and at least 50 iterations later, we finally have identified what the problem is and how to fix it.
It actually struck me suddenly when I was creating local aaa.html and bbb.html on my server. That was when it hit me that the element nodes for the content that was being replaced was being removed altogether from the DOM when $.load() runs the callback function. So, once the #menu-home content elements were replaced, they were removed from the DOM and no longer had Shadowbox applied to them.
Once I figured this out, it was just a matter of a single web search and I found:
Nabble-Shadowbox - Reinit Shadowbox
Specifically, the response from mjijackson. What he describes is how to "restart" (reinitialize) Shadowbox using:
Shadowbox.clearCache();
Shadowbox.setup();
So once the #menu-home content was reloaded, what needs to happen is the Shadowbox cache needs to be cleared (essentially, shutting it down on the page), then the Shadowbox.setup() is run, which will detect the elements all over again. You don't run the Shadowbox.init() method again either.
I noticed that you had tried to copy/paste the Shadowbox.setup() in after the $.load(), at least sequentially in the code. However, this wasn't going to work, due to the cache clearing that needs to happen first, and primarily because the .clearCache() and .setup() functions need to be run after the $.load() completes (finishes and runs any callbacks). Those two functions need to be run in the $.load() callback handler; otherwise, you're running it's immediately, but the $.load() is asynchronous and will complete at some later time.
I'm going to go over some other changes I made, just so you understand what, why and wherefore.
Note, I'm not sure if you're familiar with <base>, but the following is at the top of the HEAD element:
<base href="http://62.162.170.125/"/>
This just let's me use the resource files on your computer. You'll not want to use this on your actual site more than likely. If you copy/paste, make sure and remove this line.
<div id="menu">
<ul>
<li><a id="menu-home" href="index.html" rel="http://jfcoder.com/test/homecontent.html">Home</a></li>
<li><a id="menu-services" href="services.html" rel="http://jfcoder.com/test/servicescontent.html">Services</a></li>
<li><a id="menu-tour" href="tour.html" rel="http://jfcoder.com/test/tourcontent.html">Tour</a></li>
<li><a id="menulogin" href="login.html">Login</a></li>
</ul>
</div>
Here, you'll notice I have a relative url in the HREF attribute, and a link to some pages on my server. The reason for the links to my server is that I couldn't access your aaa.html and bbb.html files through AJAX due to cross-site scripting limitations. The links to my website should be removed as well.
Now, the reason I'm using the rel attribute here is that I want allow for the links by way of the href attribute to continue to work in case the JS doesn't function correctly or there's some other error. If you have separate files, one for full HTML document and another for just the fragments, this is what you'll want to do. If you can serve both the full document AND the content-only from the linked file, then you probably don't need the rel attribute, but you'll need to manage the request so the server knows how to respond (full document or just the content part).
var boxInitialize = function(){
try {
if (!Shadowbox.initialized) {
Shadowbox.init();
Shadowbox.initialized = true;
} else {
Shadowbox.clearCache();
Shadowbox.setup();
}
} catch(e) {
try {
Shadowbox.init();
} catch(e) {};
}
};
All I've done here is create a central location for the initialization/setup requests. Fairly straightforward. Note, I added the Shadowbox.initialized property so I could keep track of if the Shadowbox.init() had run, which can only be run once. However, keeping it all in one spot is a good idea if possible.
I also created a variable function which can be called either as a regular function:
boxInitialize();
Or as a function reference:
window.onload = boxInitialize; // Note, no () at the end, which execute the function
You'll probably notice I removed the $() and replaced them with jQuery() instead. This can turn into a real nightmare if you end up with an environment with multiple frameworks and libraries competing for $(), so it's best to avoid it. This actually just bit me real good the other day.
Since we have a closure scope within the .ready() callback, we can take advantage of that to save several "private" variables for ow use at different times in the scripts execution.
var $ = jQuery,
$content = jQuery("#content"), // This is "caching" the jQuery selected result
view = '',
detectcachedview = '',
$fragment,
s = Object.prototype.toString,
init;
Note the , at the end of all but the last line. See how I "imported" the $ by making it equal to the jQuery variable, which means you could actually use it in that#.
var loadCallback = function(response, status, xhr){
if (init != '' && s.call(init) == '[object Function]') {
boxInitialize();
}
if (xhr.success()
&& view != ''
&& typeof view == 'string'
&& view.length > 1) {
$fragment = $content.clone(true, true);
cacheContent(view, $fragment);
}
};
This runs when the $.load() completes the process of the AJAX request. Note, the content returned in the request has already been placed on the DOM by the time this runs. Note as well that we're storing the actual cached content in the $content.data(), which should never be removed from the page; only the content underneath it.
var cacheContent = function(key, $data){
if (typeof key == 'string'
&& key.length > 1
&& $data instanceof jQuery) {
$content.data(key, $data.html());
$content.data(detectcachedview, true);
}
};
cacheContent() is one a method you may not want; essentially, if it was already loaded on a previous request, then it will be cached and then directly retrieved instead of initiating another $.load() to get the content from the server. You may not want to do this; if so, just comment out the second if block in the menuLoadContent() function.
var setContent = function(html){
$content.empty().html(html);
if (init != '' && s.call(init) == '[object Function]') {
boxInitialize();
}
};
What this does is first empty the $content element of it's contents/elements, then add the specified string-based markup that we saved earlier by getting the $content.html(). This is what we'll re-add when possible; you can see once the different links have been clicked and loaded, reclicking to get that to redisplay is really quick. Also, if it's the same request as currently loaded, it also will skip running the code altogether.
(We use $content like because it is a reference to a variable containing a jQuery element. I am doing this because it's in a closure-scope, which means it doesn't show up in the global scope, but will be available for things like event handlers.
Look for the inline comments in the code.
var menuLoadContent = function(){
// This is where I cancel the request; we're going to show the same thing
// again, so why not just cancel?
if (view == this.id || !this.rel) {
return false;
}
// I use this in setContent() and loadCallback() functions to detect if
// the Shadowbox needs to be cleared and re-setup. This and code below
// resolve the issue you were having with the compass functionality.
init = this.id == 'menu-home' ? boxInitialize : '';
view = this.id;
detectcachedview = "__" + view;
// This is what blocks the superfluous $.load() calls for content that's
// already been cached.
if ($content.data(detectcachedview) === true) {
setContent($content.data(view));
return false;
}
// Now I have this in two different spots; there's also one up in
// loadCallback(). Why? Because I want to cache the content that
// loaded on the initial page view, so if you try to go back to
// it, you'll just pickup what was sent with the full document.
// Also note I'm cloning $content, and then get it's .html()
// in cacheContent().
$fragment = $content.clone(true, true);
cacheContent(view, $fragment);
// See how I use the loadCallback as a function reference, and omit
// the () so it's not called immediately?
$content.load(this.rel, loadCallback);
// These return false's in this function block the link from navigating
// to it's href URL.
return false;
};
Now, I select the relevant menu items differently. You don't need a separate $.click() declaration for each element; instead, I select the #menu a[rel], which will get each a element in the menu that has a rel="not empty rel attribute". Again, note how I use menuLoadContent here as a function reference.
jQuery("#menu a[rel]").click(menuLoadContent);
Then, at the very bottom, I run the boxInitialize(); to setup Shadowbox.
Let me know if you have any questions.
I think I might be getting to the bottom of this. I think the flaw is the way you're handling the $.load() of the new content when clicking a menu item, coupled with an uncaught exception I saw having to do with an iframe:
Uncaught exception: Unknown player iframe
This Nabble-Shadowbox forum thread deals with this error. I'm actually not getting that anymore, however I think it came up with I clicked on the tour menu item.
Now, what you're doing to load the content for the menu items really doesn't make any sense. You're requesting an entire HTML document, and then selecting just an element with a class="content". The only benefit I can see for doing this is that the page never reloads, but you need to take another approach to how to get and display the data that doesn't involve downloading the entire page through AJAX and then trying to get jQuery to parse out just the part you want.
I believe handling the content loading this way is the root cause of your problem, hence the $.load() toggling of menu views breaks your page in unexpected ways.
Question: Why don't you just link to the actual page and skip all the $.load() fanciness? Speed-wise, it won't make that much of an impact, if any at all. It just doesn't make sense to use AJAX like this, when you could just link them to the same content without issue.
There are two alternatives that would allow you to prevent roundtrip page reloads:
Setup your AJAX calls to only request the .content portion of the markup if you have the ?contentonly=true flag in the URL, not the entire HTML document. This is how it's traditionally done, and is usually relative simple to do if you have a scripting environment.
$(".content").load('index.html?contentonly=true');
Then your server responds only with the content view requested.
Serve all of the content views within the same HTML document, then show as appropriate:
var $content = $('.content');
$content.find('.content-view').hide();
$content.find('#services-content').show();
It doesn't look like you have a whole lot of content to provide, so the initial page load probably won't have that much of an impact with this particular approach. You might have to look into how to preload images, but that's a very well known technique with many quality scripts and tutorials out there.
Either one of these techniques could use the #! (hashbang) technique to load content, although I believe there are some issues with this for search engines. However, here is a link to a simple technique I put together some time ago:
http://jfcoder.com/test/hash.html
Also, this is just a tip, but don't refer to your "content" element with a class, ie, .content. There should only be one content-displaying element in the markup, right? There's not more than one? Use an id="content"; that's what ID attributes are for, to reference a single element. classes are meant to group elements by some characteristic they share, so above when I .hide() the inline content views (see #2), I look for all of the class="content-view" elements, which are all similar (they contain content view markup). But the $content variable should refer to $('#content');. This is descriptive of what the elements are.
This worked for us, we made a site that used vertical tabs and called in the pages with our shadowbox images using jQuery.load
Just give all of your anchor tags the class="sbox" and paste this script in the header.
<script>
Shadowbox.init({
skipSetup:true,
});
$(document).ready(function() {
Shadowbox.setup($('.sbox'));//set up links with class of sbox
$('a.sbox').live('click',function(e){
Shadowbox.open(this);
//Stops loading link
e.preventDefault();
});
});
</script>
Note: we had to put the .sbox class on all our rel="shadowbox" anchors as well as the on the anchor for the tab that called the .load
Thanks to this guy-> http://www.webmuse.co.uk/blog/shadowbox-ajax-and-other-generated-content-with-jquery-and-javascript/
Well, based on Shem's answer, this is my solution.
Every click on specific class, setup and open shadowbox with elements from same class:
jQuery('.sb-gallery a').live('click',function(e){
Shadowbox.setup(jQuery('.sb-gallery a'));
Shadowbox.open(this);
//Stops loading link
e.preventDefault();
});
Thanks to all

using javascript setTimeout to see if div has loaded

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

Categories

Resources