Background Information:
Our situation
At work we have created multiple apps and a few of them make use of an interactive grid inside of a modal dialog page. Last week we upgraded from Oracle APEX 19.1 to Oracle APEX 21.1 .
The problem described below didn't exist in version 19.1.
(If important: We have to support Google Chrome on Windows)
How to navigate to the IG
In all our cases the interactive grid can be reached by:
nagivating to page x
open modal dialog y
from modal dialog y open modal dialog z (via a branch after page processing)
=> the interactive grid can be found in modal dialog z
Aim and preparations
We only want to give the users access to a few interactive grid functionalities.
For this reason we gave each IG a static id.
What still works
The modal dialog opens and the interactive grid is displayed normally.
Our Problem
Up until the upgrade the following snippet removed some functionality of the respective IG:
$(window).on("load", function() {
var actions = apex.region("example_ig_id").widget().interactiveGrid("getActions");
actions.remove("selection-copy-down");
actions.remove("selection-fill");
actions.remove("selection-clear");
actions.remove("single-row-view");
actions.remove("selection-copy");
});
But since upgrading to version 21.1 apex.region("example_ig_id") returns null. The dom element also can't be found via $("#example_ig_id").
What we found out and tried
In the google chrome developer tools the div with id example_ig_id is visible under elements.
After loading the page, then clicking into the class attribute of the div (via developer tools) to edit it (no need to realy edit anything) and canceling it, apex.region("example_ig_id") isn't returning null anymore. That also works if I edit its direct or indirect parent elements (also tested with the iframe of the modal dialog containing the IG).
Since finding that out i tried to work around the problem with forcing a refresh or a reflow.
I tested multiple of the answers given here: Force DOM redraw/refresh on Chrome/Mac
The tests I tried (in the inline console) all didn't solve my problem:
$("iframe").each(function(){$(this).addClass("testClass"); $(this).removeClass("testClass");});
and:
$("iframe").each(function(){$(this).hide(); $(this).show(0);});
and:
$("iframe").each(function () {
$(this).css("opacity", .99);
setTimeout(function () {
$(this).css("opacity", 1);
}, 20);
});
I used the iframe of the modal dialog itself since it is selectable from the start.
I am not sure if I made a mistake but help would be much appreciated.
At this point I got help and the problem is solved.
Instead of defining this'
$(window).on("load", function() {
var actions = apex.region("example_ig_id").widget().interactiveGrid("getActions");
actions.remove("selection-copy-down");
actions.remove("selection-fill");
actions.remove("selection-clear");
actions.remove("single-row-view");
actions.remove("selection-copy");
});
inside the page under
Function and Global Variable Declaration
pasting just the function content
var actions = apex.region("P7_SAVED_JOBS_IG").widget().interactiveGrid("getActions");
actions.remove("selection-copy-down");
actions.remove("selection-fill");
actions.remove("selection-clear");
actions.remove("single-row-view");
actions.remove("selection-copy");
into the page under
Execute when Page Loads
did the trick.
I don't know if the execution time of the event handler was too early or if it had something to do with the iframe and its context.
When I load new pages via AJAX I need to of course update the HTML title to match the new page. The code below is something I have used successfully in past projects but for some reason it's not working here.
ajaxLoad = function(html) {
init();
// init google map
initMap();
// change html title
var HTMLtitle = $(".content > section:first-of-type").attr("data-title");
$(document).prop('title', HTMLtitle);
document.title = HTMLtitle;
// Used for popState event (back/forward browser buttons)
changedPage = true;
}
This is just a part of the javascript and you can view the rest in context if needed on the live site.
Live site: http://dma.nz/practice/
Click the top right links in the main nav to go to other pages. In my testing the title seems to update sometimes on the first page change but then never works on subsequent page changes. Most times it doesn't work at all and the title never updates. All of those top level pages have the proper HTML title defined in the data attribute, but some part of the function is failing and not updating the title and I cannot find where it's borked.
Can anyone please offer help on what I'm doing wrong or propose a better way to update the title on each AJAX page load?
As you're aware of the javascript error in initMap function
Uncaught TypeError: Cannot read property 'firstChild' of null
That exception breaks all your javascript below that line. Just fix it and your code will work, just like your main page: http://dma.nz/practice/
I'm adding a chat feature to a couple of our websites. The chat will connect users with people at our help desk to help them use the websites. Our help desk folks want the chat window to appear like a tab on the side of the page and slide out, rather than popping up in a new window. However, I want to allow the user to navigate around the site without losing the chat.
To do this, I've been trying to move the entire page into an iframe once the chat starts (with the chat outside the iframe), so the user can navigate around the site within the iframe without losing the chat.
I used this answer to get started, and that works great visually. However, some of the javascript in the background breaks.
One of the sites is ASP.NET web forms. The other is MVC. I've been working with the web forms one first. Stuff like calling __doPostBack breaks once the page is moved into the iframe since the javascript context is left behind.
Once the user clicks on a link (a real link, not a __doPostBack) and the iframe refreshes, then everything works perfectly.
How I see it, I have a few options:
Copy all javascript variables from window.top into the iframe somehow. Hopefully without having to know all the variable names. I tried this.contentWindow.__doPostBack = window.top.__doPostBack, which works, but other variables are missing so it ultimately fails:
Somehow switch the iframe's context to look at the top window context, if that's even possible? Probably not.
Another thought was to not move the page into an iframe right away, but to wait until the page changes and then load the new page into a new iframe. But I'm not sure how to hook into that event and highjack it.
Something else?
These are sites for use by our employees only, so I only have to support IE11 and Chrome.
Update:
Thanks to LGSon for putting me on the track of using the target attribute (so I can use approach #3). Below is what I ended up doing. When I pop out the chat, I call loadNextPageInIframe(). I'm using jQuery here since we already use it on our site, but everything could be done without. I set the target on all links that don't already have a target pointing to another frame or _blank. I left _parent out, but I don't think we use it anyway.
I have a reference to my chat window div in a global variable called 'chatwindow'.
There still could be some cases where this doesn't work, such as if there is some javascript that sets window.location directly. If we have anything in our sites that does this, I'll have to add a way to handle it.
function loadNextPageInIframe() {
var frame = $("<iframe id=\"mainframe\" name=\"mainframe\" />").css({
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
border: "none",
display: "none"
}).appendTo("body");
$("form, a:not([target]), a[target=''], a[target='_top'], a[target='_self']").attr("target", "mainframe");
var firstload = true;
frame.load(function () {
//Runs every time a new page in the iframe loads
if (firstload) {
$(frame).show();
//Remove all elements from the top window except the iframe and chat window
$("body").children().not(frame).not(window.top.chatwindow).remove();
firstload = false;
}
//Make the browser URL and title reflect the iframe every time it loads a new page
if (this.contentWindow && this.contentWindow.location.href && window.top.location.hostname === this.contentWindow.location.hostname && window.top.location.href !== this.contentWindow.location.href) {
var title = this.contentDocument.title;
document.title = title;
if (window.top.history.replaceState) window.top.history.replaceState(null, title, this.contentWindow.location.href);
}
});
}
May I suggest you do the following
get all links
attach an event click handler to intercept when someone click a link
on click event, check if chat is in progress, and if, feed the iframe with the new link
var links = querySelectorAll("a");
for (var i = 0; i < links.length; i++) {
links[i].addEventListener('click', function(e) {
if (isChatInProgress()) {
e.preventDefault(); //stop the default action
document.getElementById("your_iframe_id").src = e.target.href;
// anything else here, like toggle tabs etc
}
});
}
Update
To handle forms I see 4 ways at the moment
1) Add an onsubmit handler to your forms
function formIsSubmitted(frm) {
if (isChatInProgress()) {
frm.target = "the iframe";
}
return true;
}
<form id="form1" runat="server" onsubmit="return formIsSubmitted(this)">
2) Add a click handler to your buttons
function btnClick(btn) {
if (isChatInProgress()) {
btn.form.target = "the iframe";
}
return true;
}
<asp:Button runat="server" ID="ButtonID" Text="ButtonText"
OnClick="Button_Click" OnClientClick="return btnClick(this);" />
3) When a chat start, you iterate through each form and alter its target attribute
function startChat() {
var forms = querySelectorAll("form");
for (var i = 0; i < forms.length; i++) {
forms[i].target = "the iframe";
});
}
4) Override the original postback event (Src: intercept-postback-event)
// get reference to original postback method before we override it
var __doPostBackOriginal = __doPostBack;
// override
__doPostBack = function (eventTarget, eventArgument) {
if (isChatInProgress()) {
theForm.target = "the iframe";
}
// call original postback
__doPostBackOriginal.call(this, eventTarget, eventArgument);
}
Update 2
The absolute best way to deal with this is of course to use AJAX to load both page and chat content, but that likely means a quite bigger work load if your sites aren't already use it.
If you don't want to do that, you can still use AJAX for chat and if a user were to navigate to a new page, the chat app recreate the ongoing chat window with all its content again.
I suggest instead of loading content to and from iframes - build the chat as an iframe and use a jQuery modal popup on the page for chat.
You can fix the jquery modal to a fixed location and page scrolling is enabled by default. You need to modify css accordingly to make the popup remains on the same location.
If you go down your current path - you will need to worry a lot about how content is moved to the iframe and it might be difficult to re-use the chat on different pages depending on the content. For example, imagine you playing a video on the page and the user clicks chat - if you load the content to the iframe - the user will lose the status on how far he has viewed, etc.
as per my opinion, adding the whole website as an 'I-Frame' is not a good design practice, and not a good solution for the problem. My suggestion would be:
Ensure that the 'Chat' application is loaded in all the pages, across your website
Whenever the 'Chat' is started, either establish the 'web-socket' connection or somehow, maintain the State on the Server
Have the configuration of the 'Chat' as 'Minimized', 'Open' etc and store them in your cookie or session storage
On every page load, call the 'Chat' application too. Read the Chat related configuration from sessionstorage or cookie and maintain it's state as 'Minimized' or 'Open' etc, including the X and Y position, if you want to make it as 'Floated'
Every time, either fetch the entire conversation from the server via Ajax or try to store and fetch from 'Local Storage' and do Ajax only for any Updates from the other party
Use CSS based 'Float' related properties to make it float and sit at some side.
This will ensure that your chat is available for the user and yet he can navigate all through the site.
So I have a web page with some photos of people. When you click on a photo of the person the JavaScript is executed and produces a popup with some more detailed information such as a description etc.
The link for each photo is as follows:
First I want to start with the basics, of just extracting the description etc. from one single person. So I want to execute the JavaScript above to write the popup window, and when I'm on the popup window I can then extract the content of the div's on the popup.
I've looked at PhantomJS and I really don't know where to start. I've used Cheerio to get some simple information from the page, and I want to move on to executing the popup window through JS and then extracting data from that.
Any help would be appreciated as I'm a bit of a newbie to screen scraping in general.
You can do this analogous to how CasperJS does it.
page.open(yourUrl, function(success){
// mainPage is loaded, so every next page could be a popup
page.onPageCreated = function onPageCreated(popupPage) {
popupPage.onLoadFinished = function onLoadFinished() {
popupPage.evaluate(function(){
// do something in popup page context like extracting data
});
};
};
// click to trigger the popup
page.evaluate(function(){
document.querySelector("a.seeMore").click();
// or something from here: http://stackoverflow.com/questions/15739263/phantomjs-click-an-element
});
});
Do not forget to overwrite/nullify page.onPageCreated before navigating away from the main page.
I was successful to track all "uiStreamSource" that holds the link associated with each facebook post (status, image, etc) to add one extra button next to the date to do some other tasks using google chrome extensions. This button does a predefined job which not important to mention in this context.
I wrote this content script:
contentscript.js
var nodeslist=document.getElementsByClassName("uiStreamSource");
alert(nodeslist.length);
Each time a facebook page is loaded, the alert shows me 10 detected classes (i.e., 10 posts). But when I scroll down, new posts show up and this code fails to detect them. Since I need to update all uiStreamSource nodes in all posts, how can I solve this?
UPDATE
I tried this code in contentscript.js:
load();
document.addEventListener("DOMNodeInsertedIntoDocument", load, false);
function load(){
var nodeslist=document.getElementsByClassName("uiStreamSource");
alert(nodeslist.length);
}
The first time it runs, I get the correct number of the current nodes (current facebook posts) but once I scroll down and more posts are fetched, the alert shows up indicating that load function is called but the number of nodes printed is 0. Why is that?
Thanks on advance.
You can add a DOMNodeInserted event and check if the element that is going to be inserted is a post by checking its classes.
Or an easy solution is to use the jcade plugin. It can call a callback function when an element with specified selector is inserted.
Listen to the scroll event and add any new posts to nodeslist. You could use setInterval as well to catch any posts that are added when the user is not scrolling.