Choosing and activating the right controls on an AJAX-driven site
Rather than just alter the script from the question, I hope to make a quick outline of how to script these kinds of pages and actions with Greasemonkey/Tampermonkey.
The steps are:
Take careful note of what you do manually. Take special note of elements added/altered by the page's javascript, and the needed sequence of steps, if any.
Using Firebug, and/or Firefox's inspector, and/or Chrome's Developer tools, determine CSS/jQuery selector's for all of the elements you will read or manipulate. This is especially easy to do using Firebug.
Use jQuery to manipulate static HTML. Use waitForKeyElements to handle nodes added or changed by javascript (AJAX). Use the Greasemonkey API -- which is also supported by Tampermonkey and partially supported by Chrome userscripts -- to do any cross-domain page calls, or to store any values between page loads for cross-domain sets of pages.
Specific example:
For the OP's target pages, the OP wants to: (a) automatically select the shoe size, (b) add the shoes to the shopping cart, and (c) click the checkout button.
This requires waiting for, and/or clicking on, five (5) page elements like so:
Using Firebug (or similar tool) we obtain the HTML structure for the key nodes. For example, the SIZE dropdown has HTML like this:
<div class="size-quantity">
<span class="sizeDropdown selectBox-open">
...
<label class="dropdown-label selectBox-label-showing">SIZE</label>
...
<a class="selectBox size-dropdown mediumSelect footwear selectBox-dropdown" ...>
...
</a>
</span>
</div>Where the link actually fires off a
mousedown
event, not a click.Firebug gives us a CSS path of:
html.js body div#body div#body-wrapper.fullheight div#body-liner.clear div#content div#pdp.footwear div#product-container.clear div.pdp-buying-tools-container div.pdp-box div.buying-tools-container div#PDPBuyingTools.buying-tools-gadget form.add-to-cart-form div.product-selections div.size-quantity span.sizeDropdown a.selectBox
Which we can pare down to:
div.footwear form.add-to-cart-form span.sizeDropdown a.size-dropdown
for a reasonable selector that's likely to survive trivial page changes and unlikely to trigger on unwanted pages/products.
~~~~~~~~~~~~~
Note that Firebug also helps us see what events are attached to what, which is crucial when determining what we need to trigger. For example, for that node, I see:That link has no
href
, nor does it listen forclick
events. In this case, we must trigger amousedown
(orkeydown
).~~~~~~~~~~~~~
Using a similar process for the other 4 key nodes, we obtain CSS/jQuery selectors of:Node 1: div.footwear form.add-to-cart-form span.sizeDropdown a.size-dropdown
Node 2: ul.selectBox-dropdown-menu li a:contains('10')
(But this will need an additional check)
Node 3: div.footwear form.add-to-cart-form span.sizeDropdown a.selectBox span.selectBox-label:contains('(10)')
Node 4: div.footwear form.add-to-cart-form div.product-selections div.add-to-cart
Node 5: div.mini-cart div.cart-item-data a.checkout-button:visibleFinally, we use
waitForKeyElements
to send the required events to the key nodes and to sequence through the proper order of operations.
The resulting, complete, working script is:
// ==UserScript==
// @name _Nike auto-buy shoes(!!!) script
// @include http://store.nike.com/*
// @include https://store.nike.com/*
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @require https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant GM_addStyle
// ==/UserScript==
/*- The @grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
var targetShoeSize = "10";
//-- STEP 1: Activate size drop-down.
waitForKeyElements (
"div.footwear form.add-to-cart-form span.sizeDropdown a.size-dropdown",
activateSizeDropdown
);
function activateSizeDropdown (jNode) {
triggerMouseEvent (jNode[0], "mousedown");
//-- Setup step 2.
waitForKeyElements (
"ul.selectBox-dropdown-menu li a:contains('" + targetShoeSize + "'):visible",
selectDesiredShoeSize
);
}
//-- STEP 2: Select desired shoe size.
function selectDesiredShoeSize (jNode) {
/*-- Because the selector for this node is vulnerable to false positives,
we need an additional check here.
*/
if ($.trim (jNode.text () ) === targetShoeSize) {
//-- This node needs a triplex event
triggerMouseEvent (jNode[0], "mouseover");
triggerMouseEvent (jNode[0], "mousedown");
triggerMouseEvent (jNode[0], "mouseup");
//-- Setup steps 3 and 4.
waitForKeyElements (
"div.footwear form.add-to-cart-form span.sizeDropdown a.selectBox "
+ "span.selectBox-label:contains('(" + targetShoeSize + ")')",
waitForShoeSizeDisplayAndAddToCart
);
}
}
//-- STEPS 3 and 4: Wait for shoe size display and add to cart.
function waitForShoeSizeDisplayAndAddToCart (jNode) {
var addToCartButton = $(
"div.footwear form.add-to-cart-form div.product-selections div.add-to-cart"
);
triggerMouseEvent (addToCartButton[0], "click");
//-- Setup step 5.
waitForKeyElements (
"div.mini-cart div.cart-item-data a.checkout-button:visible",
clickTheCheckoutButton
);
}
//-- STEP 5: Click the checkout button.
function clickTheCheckoutButton (jNode) {
triggerMouseEvent (jNode[0], "click");
//-- All done. The checkout page should load.
}
function triggerMouseEvent (node, eventType) {
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent (eventType, true, true);
node.dispatchEvent (clickEvent);
}
How to toggle this link on a webpage?
That toggle control is added by javascript (jQuery), so you must use AJAX-aware techniques to access it.
See Choosing and activating the right controls on an AJAX-driven site.
The link, that you indicated, has a CSS class (i_expanded
) that makes a great selector for waitForKeyElements()
.
So, the resulting complete script for that site is simple:
// ==UserScript==
// @name aniDB, auto-close the sidebar menu
// @match *://anidb.net/perl-bin/animedb*
// @require http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// @require https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant GM_addStyle
// ==/UserScript==
/*- The @grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
waitForKeyElements (
".filter_menu.expanded .i_expanded", clickNode, true
);
function clickNode (jNode) {
var clickEvent = document.createEvent ('MouseEvents');
clickEvent.initEvent ('click', true, true);
jNode[0].dispatchEvent (clickEvent);
}
Choosing and activating the right controls on an AJAX-driven site
Rather than just alter the script from the question, I hope to make a quick outline of how to script these kinds of pages and actions with Greasemonkey/Tampermonkey.
The steps are:
Take careful note of what you do manually. Take special note of elements added/altered by the page's javascript, and the needed sequence of steps, if any.
Using Firebug, and/or Firefox's inspector, and/or Chrome's Developer tools, determine CSS/jQuery selector's for all of the elements you will read or manipulate. This is especially easy to do using Firebug.
Use jQuery to manipulate static HTML. Use waitForKeyElements to handle nodes added or changed by javascript (AJAX). Use the Greasemonkey API -- which is also supported by Tampermonkey and partially supported by Chrome userscripts -- to do any cross-domain page calls, or to store any values between page loads for cross-domain sets of pages.
Specific example:
For the OP's target pages, the OP wants to: (a) automatically select the shoe size, (b) add the shoes to the shopping cart, and (c) click the checkout button.
This requires waiting for, and/or clicking on, five (5) page elements like so:
Using Firebug (or similar tool) we obtain the HTML structure for the key nodes. For example, the SIZE dropdown has HTML like this:
<div class="size-quantity">
<span class="sizeDropdown selectBox-open">
...
<label class="dropdown-label selectBox-label-showing">SIZE</label>
...
<a class="selectBox size-dropdown mediumSelect footwear selectBox-dropdown" ...>
...
</a>
</span>
</div>Where the link actually fires off a
mousedown
event, not a click.Firebug gives us a CSS path of:
html.js body div#body div#body-wrapper.fullheight div#body-liner.clear div#content div#pdp.footwear div#product-container.clear div.pdp-buying-tools-container div.pdp-box div.buying-tools-container div#PDPBuyingTools.buying-tools-gadget form.add-to-cart-form div.product-selections div.size-quantity span.sizeDropdown a.selectBox
Which we can pare down to:
div.footwear form.add-to-cart-form span.sizeDropdown a.size-dropdown
for a reasonable selector that's likely to survive trivial page changes and unlikely to trigger on unwanted pages/products.
~~~~~~~~~~~~~
Note that Firebug also helps us see what events are attached to what, which is crucial when determining what we need to trigger. For example, for that node, I see:That link has no
href
, nor does it listen forclick
events. In this case, we must trigger amousedown
(orkeydown
).~~~~~~~~~~~~~
Using a similar process for the other 4 key nodes, we obtain CSS/jQuery selectors of:Node 1: div.footwear form.add-to-cart-form span.sizeDropdown a.size-dropdown
Node 2: ul.selectBox-dropdown-menu li a:contains('10')
(But this will need an additional check)
Node 3: div.footwear form.add-to-cart-form span.sizeDropdown a.selectBox span.selectBox-label:contains('(10)')
Node 4: div.footwear form.add-to-cart-form div.product-selections div.add-to-cart
Node 5: div.mini-cart div.cart-item-data a.checkout-button:visibleFinally, we use
waitForKeyElements
to send the required events to the key nodes and to sequence through the proper order of operations.
The resulting, complete, working script is:
// ==UserScript==
// @name _Nike auto-buy shoes(!!!) script
// @include http://store.nike.com/*
// @include https://store.nike.com/*
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @require https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant GM_addStyle
// ==/UserScript==
/*- The @grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
var targetShoeSize = "10";
//-- STEP 1: Activate size drop-down.
waitForKeyElements (
"div.footwear form.add-to-cart-form span.sizeDropdown a.size-dropdown",
activateSizeDropdown
);
function activateSizeDropdown (jNode) {
triggerMouseEvent (jNode[0], "mousedown");
//-- Setup step 2.
waitForKeyElements (
"ul.selectBox-dropdown-menu li a:contains('" + targetShoeSize + "'):visible",
selectDesiredShoeSize
);
}
//-- STEP 2: Select desired shoe size.
function selectDesiredShoeSize (jNode) {
/*-- Because the selector for this node is vulnerable to false positives,
we need an additional check here.
*/
if ($.trim (jNode.text () ) === targetShoeSize) {
//-- This node needs a triplex event
triggerMouseEvent (jNode[0], "mouseover");
triggerMouseEvent (jNode[0], "mousedown");
triggerMouseEvent (jNode[0], "mouseup");
//-- Setup steps 3 and 4.
waitForKeyElements (
"div.footwear form.add-to-cart-form span.sizeDropdown a.selectBox "
+ "span.selectBox-label:contains('(" + targetShoeSize + ")')",
waitForShoeSizeDisplayAndAddToCart
);
}
}
//-- STEPS 3 and 4: Wait for shoe size display and add to cart.
function waitForShoeSizeDisplayAndAddToCart (jNode) {
var addToCartButton = $(
"div.footwear form.add-to-cart-form div.product-selections div.add-to-cart"
);
triggerMouseEvent (addToCartButton[0], "click");
//-- Setup step 5.
waitForKeyElements (
"div.mini-cart div.cart-item-data a.checkout-button:visible",
clickTheCheckoutButton
);
}
//-- STEP 5: Click the checkout button.
function clickTheCheckoutButton (jNode) {
triggerMouseEvent (jNode[0], "click");
//-- All done. The checkout page should load.
}
function triggerMouseEvent (node, eventType) {
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent (eventType, true, true);
node.dispatchEvent (clickEvent);
}
Scriptish script needs the page refreshed to run?
This is a classic problem with pages that change their content via AJAX.@run-at
has no effect because the targeted content is loaded long after the window load
event. And, "new" pages are really complete rewrites via AJAX -- until you refresh the page, at least.
Change the script to @run-at document-end
(or omit the line) and then, depending on the target page and how you are using it, it may be enough to fire off of hashchange
as shown on "I have to refresh the page for my Greasemonkey script to run?" or as shown on this other answer.
The go to tool is waitForKeyElements
More info, however. Your script might be as simple as:
// ==UserScript==
// @name _YOUR_SCRIPT_NAME
// @include http://YOUR_SERVER.COM/YOUR_PATH/*
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js
// @require https://gist.github.com/raw/2625891/waitForKeyElements.js
// ==/UserScript==
waitForKeyElements ("[name='skuAndSize']", setSizeIndex);
waitForKeyElements (".button-container.add-to-cart", clickAddToCart);
function setSizeIndex (jNode) {
var node = jNode[0];
for (var J = node.options.length - 1; J >= 0; --J) {
if (node.options[J].text == "11") {
node.selectedIndex = J;
break;
}
}
}
function clickAddToCart (jNode) {
var clickEvent = document.createEvent ('MouseEvents');
clickEvent.initEvent ('click', true, true);
jNode[0].dispatchEvent (clickEvent);
}
See, also, "Choosing and activating the right controls on an AJAX-driven site".
Why is this userscript only doing the first if?
It is unclear what you hope to accomplish. If you are trying to step through a sequence of controls, use the approach illustrated in Choosing and activating the right controls on an AJAX-driven site.
The kind of code shown in the question would just play "Whac-A-Mole" with whatever button "popped up" next. (And only if the preceding buttons had been deleted.)
Anyway, to answer the question: "why this code is only doing the first if?".
It's because userscripts (and javascript) stop running at the first critical error (with a few exceptions). Additionally:
noError.click
is not a function because noError is a collection of elements, not a button.- All the
getElementsByClassName
calls are only done once. If it's going to continually loop, you need to recheck inside the loop. - There is no such thing as
sleep()
. while (1)
is a very bad idea and can freeze your browser, or even help crash your PC. UsesetInterval
for polling the page.
Here's the "Whac-A-Mole" code with those errors corrected:
setInterval ( () => {
var noError = document.getElementsByClassName ("noMistakeButton");
var next = document.getElementsByClassName ("nextButton");
var wait = document.getElementsByClassName ("understoodButton");
var okko = document.getElementsByClassName ("buttonKo");
var exitOkko = document.getElementsByClassName ("exitButton");
if (noError.length) {
noError[0].click ();
}
if (next.length) {
next[0].click ();
}
if (wait.length) {
wait[0].click ();
}
if (okko.length) {
okko[0].click ();
}
if (exitOkko.length) {
exitOkko[0].click ();
}
},
222 // Almost 5 times per second, plenty fast enough
);
If you want to sequence clicks, use chained waitForKeyElements()
calls as shown in this answer.
How to make auto change Show Entries value with Tampermonkey or Stylebot?
get select element:
const select = document.querySelector('[name=example_length]');
change select value:
select.value = 50;
trigger change event:
select.dispatchEvent(new Event('change'));
How to click a Show More link with Greasemonkey?
This is a very common problem; See "Choosing and activating the right controls on an AJAX-driven site" for more details on how to solve this kind of thing.
Given that recipe, the only art/skill/difficult-part is choosing the right jQuery selector(s). In this case, the "Show More" link can be reliably had with:
#synopsis div.copy a.show-more-truncate
So, the complete working script would be:
// ==UserScript==
// @name _BBC episode, click "Show More" link
// @include http://www.bbc.co.uk/programmes/*
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @require https://gist.github.com/raw/2625891/waitForKeyElements.js
// @grant GM_addStyle
// ==/UserScript==
/*- The @grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
waitForKeyElements (
"#synopsis div.copy a.show-more-truncate", clickShowMoreLink, true
);
function clickShowMoreLink (jNode) {
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent ("click", true, true);
jNode[0].dispatchEvent (clickEvent);
}
Related Topics
When Is .Then(Success, Fail) Considered an Antipattern For Promises
Send Post Data Using Xmlhttprequest
Short Circuit Array.Foreach Like Calling Break
Get Difference Between 2 Dates in JavaScript
How to Get Client'S Ip Address Using JavaScript
Understanding Unique Keys For Array Children in React.Js
Check Variable Equality Against a List of Values
How to Trigger Event in JavaScript
How to Encode a String to Base64 in JavaScript
Convert a Unix Timestamp to Time in JavaScript
How to Create and Read a Value from Cookie With JavaScript
JavaScript Equivalent to Printf/String.Format
Convert Date to Another Timezone in JavaScript
Window.Onload VS $(Document).Ready()
How to Sort an Object Array by Date Property
Methods in Es6 Objects: Using Arrow Functions
Use of 'Prototype' Vs. 'This' in JavaScript
What's the Best Way to Detect a 'Touch Screen' Device Using JavaScript