HtmlUnit not showing all injected JavaScript code - javascript

The goal is to download a file from a website using HtmlUnit. The download link is generated in JavaScript and dynamically injected into a dropdown menu, based on a previous selection in another dropdown menu. Both dropdown menus are dynamically injected via JavaScript and have the same element structure.
So in general, when loading the page, it shows only the first dropdown list. This sample shows the initial element structure when the page is loaded. When selecting an item from that list, the second dropdown list appears and the element's source structure has been updated to this. When clicking the second dropdown list, the page shows a list with several options containing a direct link to a file. This example shows the selection of the first dropdown list is made, showing the options of the second dropdown list (with a green download icon). At this moment, the source has been updated to with this code (i.e. similar to the previous div). Clicking on any of the list items will start an immediate download of the required file.
What happens in my HtmlUnit application is, when I trigger the same steps until the second dropdown list should appear, the div with id 'formatSelect' stays empty. It looks like the JavaScript code is not being executed for that part only, while it does so for the first part.
A brief sample of my code:
private static final String URL = "https://some.url.org/";
private static final String xpathDeltaSelect = "div[#id='div1']";
private static final String xpathFormatSelect = "div[#id='div2']";
private static final String xpathDropDownToggle = "div[#class='dropdown-toggle']";
private static final String xpathSearchButton = "div/input[#type='search']";
private static final String xpathListBox = "div/ul[#role='listbox']";
webClient_ = new WebClient(BrowserVersion.CHROME);
webClient_.getOptions().setRedirectEnabled(false);
webClient_.getOptions().setDownloadImages(false);
webClient_.getOptions().setThrowExceptionOnScriptError(false);
webClient_.getOptions().setThrowExceptionOnFailingStatusCode(false);
HtmlPage page = webClient_.getPage(URL);
webClient_.waitForBackgroundJavaScript(JAVASCRIPT_TIMEOUT);
final HtmlDivision divDeltaSelect = selectWrapper(page).getFirstByXPath(xpathDeltaSelect );
final HtmlDivision divDeltaDropdown = divDeltaSelect.getFirstByXPath(xpathDropDownToggle);
final HtmlInput inputDelta = divDeltaDropdown.getFirstByXPath(xpathSearchButton);
inputDelta.focus();
final HtmlUnorderedList ulDeltaSelect = divDeltaSelect.getFirstByXPath(xpathListBox);
Iterator<DomElement> iterator = ulDeltaSelect.getChildElements().iterator();
// Iterate to the desired item and 'click'
...
page = li.click();
break;
}
// So far, everything goes well.
final HtmlDivision divFormatSelect = selectWrapper(page).getFirstByXPath(xpathFormatSelect);
System.out.println(divFormatSelect.asXml());
// At this point, divFormatSelect is still empty...
The output of the sysout at the end is the following:
<div data-v-0d6d77a2="" id="formatSelect">
<!---->
</div>
The selectWrapper method is briefly doing the following (because there are 3 similar "selectWrapper" divs that return different data files, but I only need the first one):
final List<DomElement> elements = page.getElementsById("selectWrapper");
...
return elements.get(0);
I did notice that the initial page load at line
HtmlPage page = webClient_.getPage(URL);
throws the following JavaScript error:
com.gargoylesoftware.htmlunit.ScriptException: missing ) after formal parameters (https://some.url.org/path/to/js/javascript.js#1)
... + stacktrace
Could this be related to this bug? However, I do not understand why the first injected code can be retrieved, but not the second part, while the browser interface does the processing correctly. Am I missing something?

Related

Is there a way to run this script in a sequence?

For context I've made a script to assign a localstorage item based on what the user selects, this item may contain "S10" but could also contain many other values, for this example the user has selected "S10"
This has now stored into localstorage.
I have created this script that I have placed in each of the product grid includes, so this script will load multiple times for each product:
window.onload = function() {
const string = localStorage.getItem('MyfitmentList', JSON.stringify(myfitment) );
const attr = {{ product.metafields['fitment']['fitment'] | json }};
document.getElementById("fitment").innerHTML = (`${string.includes(attr) ? '✔' : 'not a fit' }`);
}
Each product grid also features this container <div id="fitment"></div>
The script above loads individually for each product grid card and has it's own seperate values for "attr". If "attr" doesn't contain "S10" (which the user previously selected) then it should show "not a fit", but if the product does contain "S10" then it should show the tick.
The problem I'm having is that this only works for the first script, on the first <div id="fitment"></div> container. I can see that each individual script has a different "attr" value which is good, but I don't know why only the first container is populated.
Each script should run individually and populate the "fitment" container within it's own product card. I'm a bit at a loss as to how to do it.
Also this is on Shopify - Someone below has recommended using a single script with a loop, but in that case how would I retrieve a unique "attr" value each time the script loops? this would still leave the question of how to populate each fitment container with the correct message for each product.
Any help would be really appreciated!

Populating HTML table based on checkboxes

I have multiple checkboxes on a HTML page, and I would like to be able to populate a table on a second HTML page based on what checkboxes have been clicked.
As a basic example, here is a div containing 2 of the checkboxes. I would like it so that if, say, awareness_checkbox was checked, the word Awareness and it's value (0.01 CPC) would be added to a table on my second HTML page.
How would be the best way to go about doing this? My intention is to have a HTML button that links to the second page (let's call it 'page2.html') so that when it is clicked, every checkbox that has been checked on the page has it's label and associated value added to a table that will appear on the second page.
Any help would be greatly appreciated.
<div class="campaignstrategy">
<h1>1. Campaign Strategy</h1>
<input type="checkbox" name="awareness" id="awareness_checkbox" value="0.01">Awareness<br>
<input type="checkbox" name="directresponse" id="directresponse_checkbox" value="0.01">Direct Response<br>
</div>
Note
okay, so I re-did all the code and tested locally on chrome, just save both files in the same directory and then open page1.html in chrome.
Code Extracts - Page 1
Assign a custom action to the input button, this will check all checkbox input type inputs,
it will then see whether or not it is checked, if it is checked then it will add an entry into table_info variable, after checking all inputs
it will then convert the javascript object into a json string, then we convert that json into base64 encoded string.
then we browse to the page2.html file and in the url we pass a key called "table_data" and its value is the base64 encoded string.
$('#next_page_button').click(function(){
let table_info = [];
$('.campaignstrategy input[type=checkbox]').each(
function(index, value){
if($(this).is(':checked')){
table_info.push(
{
name: $(this).attr('name'),
value: $(this).attr('value'),
}
);
}
});
let base64str=btoa(JSON.stringify(table_info));
window.location = "page2.html?table_data=" + base64str;
});
Code Extracts - Page 2
when this page is loaded into the browser, the following script will check the query keys, in this case we are looking for 'table_data' key.
this will be accomplished by a helper function (see pastbin for details, left out to keep the answer short)
once we have the data, we will decode it from base64 to json, then using the json.parse function we will recreate the javascript object.
after creating the object, we will loop through the array, remember the javascript object that we decode is an array of dictionaries,
[{'name':'awareness','value':'0.01'}] as you see, it has the checkbox's name and its associcated value.
then we create the table row, and append the name and value to the table row inside a table data element, one for each name and value.
// actual code
let table_data = getUrlParameter('table_data');
let data_from_page_1 = JSON.parse(atob(table_data));
for(let i=0;i<data_from_page_1.length;i++){
let row = $("<tr></tr>");
let recordName = $("<td></td>").text(data_from_page_1[i].name);
let recordValue = $("<td></td>").text(data_from_page_1[i].value);
row.append(recordName, recordValue);
$('#output_table').append(row);
}
Examples
Page 1
Page 2
Instructions
save the contents of this to a file called page1.html Code Link For Page 1
save the contents of this to a file called page2.html Code Link For Page 2

Saving the highlighting of current row in Oracle Apex Classic Report after closing the dialog window

Good evening to everybody here!
At this moment I'm working with one page in Oracle Apex (version 4.2.6.00.03). It consists of two Classic Reports — the first is a "master" one, and the second contains "details" of the former. Also there are several buttons for performing actions of inserting/updating/deleting the data. My purpose is not only to make the actions work and "details" report to refresh on choosing the row from the "master" one (I've completed these tasks), but also to manage to save the highlighting of reports' rows even after performing the actions (not only just refreshing the page).
Now I'll explain what I've already done. One can choose the row (and simultaneously highlight it) by means of the script I put in every Report's footer and it looks like this:
<script>
$(".t20data","#master_report").live("click",function(){
chooseMST($(this).find("span.MASTER").attr("id"));
});
</script>
There master_report is an ID of "master" report's region and MASTER stands for span class, in which I wrap all the cells in report to keep the value of row's ID. The function chooseMST is this:
function chooseMST(docID){
$.post('wwv_flow.show',
{'p_request' : 'APPLICATION_PROCESS=SET_MASTER',
'p_flow_id' : $v('pFLowId'),
'p_flow_step_id' : $v('pFlowStepId'),
'p_instance' : $v('pInstance'),
'x01' : docID},
function(data){
//refreshes "details" report
$('#detail_report').trigger('apexrefresh');
//deletes color from all the rows of "master" report
$(".t20data","#master_report").parent("tr").children().css("background-color","#F2F2F5");
//highlights the chosen row in "master" report
$("#" + String(docID)).parent("td").parent("tr").children().css("background-color","#A3BED8");
}
);
}
The action (say, AJAX callback) SET_MASTER is this:
begin
--clears the choice from "detail" report
APEX_UTIL.SET_SESSION_STATE(P_NAME => 'P400_DETAIL_RN'
,P_VALUE => NULL);
--makes the choice from "master" one
APEX_UTIL.SET_SESSION_STATE(P_NAME => 'P400_MASTER_RN'
,P_VALUE => APEX_APPLICATION.G_X01);
end;
To say about refreshing the page, I solved the problem of clearing the hidden items P400_DETAIL_RN and P400_MASTER_RN by having a process which goes before header with this PL/SQL code:
begin
:P400_DETAIL_RN := APEX_UTIL.GET_NUMERIC_SESSION_STATE(P_ITEM => 'P400_DETAIL_RN');
:P400_MASTER_RN := APEX_UTIL.GET_NUMERIC_SESSION_STATE(P_ITEM => 'P400_MASTER_RN');
end;
and the Javascript function recolorRows which is executed every time the page is loading:
function recolorRows(){
$(".t20data","#master_report").parent("tr").children().css("background-color","#F2F2F5");
if($("P400_MASTER_RN").val() != "") $("#" + String($("P400_MASTER_RN").val())).parent("td").parent("tr").children().css("background-color","#A3BED8");
$(".t20data","#detail_report").parent("tr").children().css("background-color","#F2F2F5");
if($("P400_DETAIL_RN").val() != "") $("#" + String($("P400_DETAIL_RN").val())).parent("td").parent("tr").children().css("background-color","#A3BED8");
}
The code concerning rows from "details" report is alike, so let me omit this part. Problems begin for me from performing the actions of manipulating the data. Here is the function which opens the dialog window for inserting or updating the row chosen in "master" report:
function MST_open(action){
//the part of code which finds out with what parameteres we should call the dialog window
$("#dialogFrame").attr("src",stringToCall);
$("#dialogWindow").dialog({
title:windowName,
modal:true,
width:500,
height:500,
resizable:true,
close:reloadMST //the action on closing the window
});
}
The code of reloadMST looks as follows:
function reloadMST(){
$("master_report").trigger('apexrefresh');
$("detail_report").trigger('apexrefresh');
}
And the Javascript function, which executes in the dialog window on the certain button click (for example, "Update"), is this:
function mstUpdate(){
$.post('wwv_flow.show',
{'p_request' : 'APPLICATION_PROCESS=MASTER_UPDATE',
'p_flow_id' : $v('pFLowId'),
'p_flow_step_id' : $v('pFlowStepId'),
'p_instance' : $v('pInstance'),
'x01' : apex.item("P402_SNAME").getValue()},
function(data){
//returns the ID of updated row in "msg" part of "res" variable
var res = eval("(" + data + ")");
if(res.status != "OK"){
//the code which catches the error, if it appears
} else {
parent.MST_close(res.msg);
}
}
);
}
where MST_close is this:
function MST_close(docID){
$("#dialogWindow").dialog("close");
//see this function above
chooseMST(docID);
}
So, this is a chain of Javascript and PL/SQL actions, which concerns the updating of the row from "master" report. The actions of inserting/updating/deleting the data work great, but I can't say the same about the saving of rows' color. The latter works good while I'm only choosing rows or refresh the page, but after performing, for example, the updating, the current row loses its highlighting. By debugging (say, adding the function console.log in Javascript code) I found out that the chain of actions, which must lead to saving the highlighting, executes nominally, but it looks like refreshing the report either goes after coloring or just prevents the latter.
Thus, my question is this: is there any way to save the highlight of the current row even after opening and closing the child dialog window?
I think that the problem is that after you update the value of a record in the modal window you refresh the data in the 2 reports in the main page and so you lose the highlight.
To fix this try to create a Dynamic Action on the event After Refresh on Region: Your Classic Reports that will execute the javascript function recolorRows(). You can also do it with javascript. The main ideea is that after you refresh the 2 reports (using reloadMST() or other method) you must trigger recolorRows().
Thank you, Cristian_I, very much. I've recently solved my problem. My mistake was that I hadn't done the hidden items binding in HTML-code - in other words, by means of Javascript only. Watching the behaviour of the hidden items, I'd discovered that when I tried to find their value with the help of jQuery function $("#hidden_item").val(), I got the previous values, but not the current ones (i.e. session state values). So that's why I had the highlighting unstable.
In addition to Dynamic Actions triggering right after refreshing the reports, I should have just add these strings to my function chooseMST before the "coloring" code itself:
$("#P400_MASTER_RN").val(docID); //binding to exact string
and
$("#P400_DETAIL_RN").val(""); //clearing the choice in the "details" report.
Due to this the problem with recoloring the rows have just gone away! Thus, now my page works excellent: the highlighting is stable, and even new rows are highlighted right after inserting them.

Jquery doesn't update an attribute as expected

I have an MVC App and on one of my pages as part of the display I will render one of two partials based on a user selection of a radio button. This bit all works ok, but I have a button on the main form where I need to set/reset values that will be passed to my controller based on the user selection. The button is declared on my main form as:
#Html.GlobalModalAuthorizeButton("Upload Document", "Upload", "Documents", new { serviceUserId = Model.ServiceUser.Id, folderId = Model.CurrentFolderId, viewType = Model.ViewTypes.ToString() }, new { #class = "icon upload btn-primary" })
when this page is initially rendered the viewtype is set to the default view that the user is initially presented with. when the user changes the view the viewtype doesn't seem to get updated. So from the partial that has been loaded I try to set the value to the correct value. Using the Chrome browsers Developer tools if I do the following:
$(this).parent().parent().parent().parent().find($('.upload')).attr('data-url').replace('FileView', 'TreeView');
it returns in the console window the value I want (idea is that i set the value on the button before the user presses it). The trouble is the above doesn't really seem to have changed the data-url attribute on the button so consequently when the controller is hit, 'FileView' is still passed through.
For full attribute:
var new_attr = "/ServiceUser/Documents/Upload?serviceUserId=21&viewType=FileView";
$(this).parent().parent().parent().parent().find($('.upload')).attr('data-url', new_attr);
Or, as #Rup already suggested, you should first get the original attribute value, modify that using replace method and then reassign the new_attr.
jQuery got a special method to read and write data attributes:
var uploadButton = $(this).parent().parent().parent().parent().find('.upload');
var currentUrl = uploadButton.data('url');
uploadButton.data('url', currentUrl.replace('FileView', 'TreeView'));

Ext JS removeAll(false) behaviour

I have been using the following snippet of code in my app to drive a menu that contains different trees depending upon what is selected. It tries to create tree panels only once and to then reuse them if they are selected again, i.e. to keep the trees expanded state.
var west = Ext.getCmp("west-panel");
west.removeAll(false);
west.doLayout();
var existingPanel = Ext.getCmp('component-tree-project-' + systemId);
if (existingPanel) {
west.add(existingPanel);
west.doLayout();
return;
}
//... code to add create and add a new tree panel
The problem is with west.removeAll(false), the false stops the nodes getting destroyed but they do not appear back in the panel. The panel sticks to showing what it last had in it.
Using west.removeAll(true) works fine except for the fact that new panels are always created.
Should not statement be west.removeAll(true);

Categories

Resources