How to get the first visible html element in a WebBrowser? - html

I'm using the .Net 4.0 WebBrowser (from System.Windows.Controls). In my application the user can modify the html data and the webbrowser shows an update. The application should then automatically scroll to the previous element that was displayed.
I'm using WPF / c# 4.0 on windows 7.
In detail:
In the code I call WebBrowser.NavigateToString(htmlData1);
the user scrolls to an arbitrary position
the user changes (somehow) the html document
now I would like to find (and remember) the first html element that is displayed
I call WebBrowser.NavigateToString(htmlData2);
now I would like to use the memorized element to automatically scroll the html document (I know that there is IHTMLElement.scrollIntoView() to do this)
So the question is: How do I get the first visible html element in a WebBrowser?
You may say that the updated html will not have the memorized element anymore. Right. But I have timestamps in the document and will use the memorized html element to find the best position in the new html doc.

I think I found the right method to use. There are actually two possibilities to retain the scroll position when the document is being refreshed.
Assume the following members:
WebBrowser _browser;
HTMLDocument HtmlDoc { get { return (HTMLDocument)_browser.Document; } }
(1) If the document didn't really change (in my example only changed in style) I use the current scroll position to auto-scroll after an update:
before refresh:
_scrollPos = ((IHTMLElement2)(HtmlDoc.documentElement)).scrollTop;
after refresh:
HtmlDoc.parentWindow.scrollTo(0, _scrollPos);
(2) If the document has changed I use the left upper element of my document (that is of known structure)
before refresh:
IHTMLElement el = HtmlDoc.elementFromPoint(50, 0);
Do some magic with that el (requires knowledge of the doc) and remember some string that can be used to search for the best element after the update.
string beforeRefresh = fromElement(el);
after refresh use the remembered string and search for the best html element in the reloaded html doc.
IHTMLElement newEl = fromString(beforeRefresh);
newEl.scrollIntoView(true);
So the actual method I was missing was elementFromPoint(). The rest is "custom logic".

Related

IE11 Automation via Excel VBA - Forms

This is part 2 from my original post here. So now once logged in, I have to make two clicks to display the information I'm ultimately trying to scrape. I cannot seem to figure out the proper way to drill down to get to the clicks to work. It is all buried within a form. The 1st image shows the form structure. The 2nd image shows all of the code where I'm trying to get to the Current Situation link below the Supervisor span. I had to use the following (as you see from my previous post) to get IE to stay connected so not sure if that has any impact:
Dim objIE As Object
Set objIE = GetObject("new:{D5E8041D-920F-45e9-B8FB-B1DEB82C6E5E}")
In an attempt to automate the Current Situation tab click, I've tried the following with no luck (with and without the 0)
objIE.document.getElementById("1617")(0).Click
I'm more interested in getting educated in addition to getting an answer. Is there a method to drilling down into information within a form? I was under the impression that ALL the elements of a webpage were pulled in during loading. So is this not truly an element in my minimal understanding with webpage automation?
As a note, I do have to click Supervisor to get the tree below it to display. Thanks in advance for your help!
UPDATE:
Ok a major oversight here I think. Based on my previous post, the login functions perfectly. But once logged in, a new window gets created. So I believe that is the challenge, right? It can't find any of these IDs or Elements because it's looking at the original IE object, not the new window. So how do I get it to activate/access the new window? Sorry I missed that earlier!
Method getElementById returns a single element, not a collection, unlike for example getElementsByClassName (look at "Element" vs "Elements" in method name). Therefore (0) or any other index of collection should not be used.
Correct syntax is:
objIE.document.getElementById("1617").Click
For pop-up windows try this:
Dim wURL As String
Dim myWindow As Object
For Each myWindow In CreateObject("Shell.Application").Windows
wURL = myWindow.LocationURL
If InStr(wURL, "constant_part_of_popup_window_link") <> 0 Then
'do stuff
myWindow.Quit
Exit For
End if
Next myWindow
For accessing an element within iFrame:
myWindow.document.getElementById("frameMainMenu").contentWindow.document.getElementById("1617").Click

Kendo MVC non-unique id issues

Example: We have an employee list page, that consists of filter criteria form and employee list grid. One of the criteria you can filter by is manager. If the user wants to pick a manager to filter by, he uses the lookup control and popup window is opened, that also has filter criteria and employee list grid.
Now the problem is, that if the popup window is not an iframe, some of the popup elements will have same names and ids as the owner page. Duplicate ids cause Kendo UI to break as by default MVC wrapper generates script tags with $("#id").kendoThingie.
I have used iframe in the past, but content that does not fit in iframe window like long dropdown lists gets cut off and now IE11 especially causes various issues like https://connect.microsoft.com/IE/feedback/details/802251/script70-permission-denied-error-when-trying-to-access-old-document-from-reloaded-iframe.
What would be the best solution here? Generate unique ids for all elements on Razor pages? Modify partial page content that is retrieved by Ajax making ids unique? Something else?
It sounds like you are using a partial page as the content to a Kendo window. If this is the case then just provide your partial with a prefix like so at the top of the page.
#{
ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix"
}
Now when you create a kendo control via the MVC wrapper like so
#(Html.Kendo().DropDownListFor(o => o.SomeProperty)
.....
)
The name attribute will be generated as "MyPrefix.SomeProperty" and the id attribute will be generated as "MyPrefix_SomeProperty". When accessing it within Jquery I like a shorter variable name so I usually do
string Prefix = ViewData.TemplateInfo.HtmlFieldPrefix
After setting the prefix. Then use
var val = $('##(Prefix)_SomeProperty').data('kendoDropDownList').value();
Note after this change. If you are posting a form from that partial you will need to add the following attribute to your model parameter on the controller method like so. So that binding happens correctly.
[HttpPost]
public ActionResult MyPartialModal([Bind(Prefix = "MyPrefix")] ModeViewModel model) {
.....
}
Now with all of that said. As long as you keep your prefixes different for each partial your control ids and names will be unique. To ensure this I usually make my prefix name be the same as my cshtml page that I am creating. You would just need to worry about JS function names. Also, note when closing a kendo window all DOM still exist. You just hide it. If this causes you the same issue you just need to be sure to clear the DOM of the modal on close. Similar to how BurnsBA mentioned. Note because of this is the reason why I try to make sure I use the least amount of kendo windows as possible and just reuse them via the refresh function pointing to a different URL.
$('#my-window').data('kendoWindow').refresh({
url: someUrlString
, data: {
someId: '#Model.MyId'
}
}).open().center();
Then on the modal page itself. When posting I do the following assuming nothing complicated needs to happen when posting.
var form = $('#my-form'); //Probably want this to be unique. What I do is provide a GUID on the view model
$('#my-window').data('kendoWindow').refresh({
url: form.attr('action')
, data: form.serialize()
, type: 'POST'
}).open().center();
We do something similar, and have the same problem. We have create/edit/delete popups that fetch data via ajax. Different viewmodels might reference the same model on the same page, and if you open multiple popups (create item type 1, create item type 2) then the second and subsequent popups can be broken (kendo ui error such that a dropdown is now just a plain textbox). Our solution is to delete all dom entries when the popup is closed so there are no conflicts between ids in different popups. We use bootstrap, so it looks like
<script type="text/javascript">
$('body').on(
// hook close even on bootstrap popup
'hidden.bs.modal', '.modal',
function () {
$(this).removeData('bs.modal');
$(this).find('.modal-content').html(''); // clear dom in popup
});
</script>
Note that our popup has some outer html elements and identifiers, but the content is all in
<div class="modal-content"> ... </div>

Get HTML Element from WebBrowser Control in VB

I am using Visual Studio 2010 and coding in VB.
I have a form with a WebBrowser object in it.
I have control over the HTML on the pages.
I need the user to be able to select a portion of text in the WebBrowser object and save it.
In the save process, I need to capture the selected text, the ID of the HTML element that contains the selected text, and the URL. Then when the user goes back to that page, the program can place an icon on the page right next to that object.
I do not have any code to accomplish this yet, I do not have much experience with the WebBrowser object so I didn't even know where to start. I can however give the names of my form objects and inform you that all of the html elements have an ID.
Form : frmContent
WebBrowser : wbContent
I am guessing that I could even record coordinates of the selected portion of text instead of the html element. Either way, the final outcome needs to save a list of these locations paired with their url so that for every saved "bookmark", there will be an icon placed on the page of that url in the saved location of the page.
Found that this works rather well for me.
GetElementFromPoint(e.ClientMousePosition)
I was able to set this to a variable and then able to get the attributes from the element.
Dim bookmarkElement = wbContent.Document.GetElementFromPoint(e.ClientMousePosition)
Dim elementID = bookmarkElement.GetAttribute("id").ToString()
Problem solved.

Get the div of current scroll offset of UIWebView

I have two different HTML files, two different UIWebViews & a UISegmentControl. On segmentChanged, I had displayed the WebView by loading a HTML file.
Both HTML files contains common sections only the section content is changed. Now I want to implement the functionality that, when user reads section 1.1 of first WebView & clicks Segment for loading second WebView, then the second WebView needs to scroll upto the section 1.1 which he reads in first WebView and viceversa. Also there are n number of sections.
I used following javacript but it needs the parameter of current div id. But on scroll, how can I get the current visible div id. I had given the id for each div.
function pointInView(id)
{
var divid = document.getElementById(id);
divid.scrollIntoView(true);
return false;
}
Anyone please help me.
I think jquery might be able to come to your rescue.
Could you use jQuery, since it's cross-browser compatible?
http://stackoverflow.com/questions/5353934/check-if-element-is-visible-on-screen
Check out answer form BenM
You don't really need a db entry for that. Just catch the click event via something like
$('.action').click(function(){
document.location = yourSecondFIleLocation+'?offset = ' + $('selectorDiv').detOffsetFromParentDiv(); //or get the scroll offset from the page.
});
and catch it on the second page either with php or jQuery to scroll to it.
Heck that is even achievable if you put in your code the paragraph marks and link correctly to them.
I searched a lot to get the div id from current scroll offsetbut didn't get the solution for it. So I go with the other option that, stored the start & end offsets of each section in both files. So I had created one look-up table in database & stored the start & end offsets of each section in both files.
ex. When user scrolls the first webview to section 1.1 & clicks the switch to open the second webview, then I scrolled the second webview to the section 1.1 in the second file.
Please try below,
To scroll any scroll view at any position we set the content offset of the scroll view.
To solve your problem we have two solutions,
In your case when user is viewing the first Web view, save the content offset of the first web view in any variable like below
CGFloat offset = webView1.scrollView.contentOffset.y; Then when you
open the second web view then provide this offset value to second
web view scroll offset.
Set
webView1.scrollView.contentOffset=webView2.scrollView.contentOffset
or vice versa according to your need
I hope this will help you.

Binding to HTML elements in GWT

I'm trying to figure out how to bind a javascript event to a select element in GWT, however the select element isn't being built in GWT, but comes from HTML that I'm scraping from another site (a report site from a different department). First, a bit more detail:
I'm using GWT and on load, I make an ajax call to get some HTML which includes, among other things, a report that I want to put on my page. I'm able to get the HTML and parse out the div that I'm interested in. That's easy to display on my page.
Here's where I get stuck: On the portion of the page I'm using, there's a select element which I can easily locate (it has an id), but would like to capture event if my user changes that value (I want to capture changes to the select box so I can make another ajax call to replace the report, binding to the select on that page, and starting the whole process again).
So, I'm not sure how, once I get the HTML from a remote site, how to bind an event handler to an input on that fragment, and then insert the fragment into my target div. Any advice or pointers would be greatly appreciated!
How about this:
Element domSelect = DOM.getElementById("selectId");
ListBox listBox = ListBox.wrap(domSelect);
listBox.addChangeHandler(new ChangeHandler() {
void onChange(ChangeEvent event) {
// Some stuff, like checking the selected element
// via listBox.getSelectedIndex(), etc.
}
});
You should get the general idea - wrap the <select> element in a ListBox. From there, it's just a matter of adding a ChangeHandler via the addChangeHandler method.