Andereoo/TkinterWeb

Feature request: scroll to node matching CSS selector

rodneyboyd opened this issue · 13 comments

It would be great if one could run a find operation with a CSS selector as the search term instead of text/regex and have the document scroll to the node. Thanks!

You should be able to use frame.html.yview(frame.html.search("CSS_SELECTOR")[0]) to scroll to the first node that satisfies the CSS selector. Let me know if it works!

Hi, the good news is that it works, thanks. The bad news is that it didn't solve the problem I was hoping it might. Basically I'm using the frame as a help viewer for my tkinter app . One of the ways the help can be accessed is via [Help] buttons on various dialogs. When the user clicks one, it fires off a call to load_file(url, force=False) where the url ends with "#id", an id to the relevant help section. The problem is that the first time this happens, the help file is scrolled to a point that is a page or so above the desired id. I wondered if the frame is looking for the id before the relevant HTML is loaded (???) I tried a fewf solutions: the first was force=True; one was to define a callback with on_done_loading that does a second load_file. This didn't help. The second idea was to start the help viewer when the app starts up, but withdraw() it immediately so that the user doesn't see it, but the help is still loaded. Then when they click [Help], the viewer is diplayed again. This still doesn't work the first time, but all subsequent [Help] clicks in the session do arrive at the correct location. Unfortunately using yview works the same. Does this ring any bells for you?

By the way, may I suggest creating a convenience method so that one can do frame.scroll_to(selector) or something like that?

Thanks!

Here is a small app that shows the problem. My help file is attached. Launching the help via Ctrl+S scrolls to just before the location with the id "general" but once the help is displayed Ctrl+R and Ctrl+S go exactly to the id.

import tkinter as tk
from tkinter import *
from tkinter import ttk

import tkinterweb
import os.path
import sys

def show_help(e=None, id=None):
	help_window = tk.Toplevel(window)
	help_window.geometry('400x500')
	help_window.title('Help')
	
	global html_frame
	html_frame = tkinterweb.HtmlFrame(help_window, messages_enabled = False, width=300, height=350)
	
	global help_url
	help_url = 'file:///' + my_path + '/Help/picardy_help.html'
	
	url = help_url + (('#' + id) if id else '')
	# print(url)
	html_frame.pack()
	html_frame.load_file(url, force=True)

def scroll_to_id(e=None, id=None):
	if html_frame and id:
		html_frame.html.yview(html_frame.html.search('[id="' + id + '"]' )[0])
	
def jump_to_id(e=None, id=None):
	if html_frame and id:
		html_frame.load_file(help_url + '#' + id, force=False)
		
def main():
	global window
	window = tk.Tk()
	window.title("Help Test")
	global html_frame
	html_frame = None
	
	global my_path
	my_path = os.path.dirname(os.path.abspath(__file__)).replace('\\','/')
	
	text = tk.Text(window, height=15, width=30)
	text.pack()
	
	window.bind('<Control-h>', lambda e:show_help(id='general'))
	window.bind('<Control-r>', lambda e:scroll_to_id(id='general'))
	window.bind('<Control-s>', lambda e:jump_to_id(id='general'))
	window.mainloop()

	
if __name__ == "__main__":
	main()

Help.zip

Looks like all this needed was for me to uncomment one line of code in htmlwidgets.py. I wrote it down a while ago but didn't think that it had any effect. Looks like I was wrong there. Thanks for bringing this to my attention. Try upgrading Tkinterweb, and let me know if it works now.

Good point. It looks like on my machine the update made your code work more often, but after some more testing it looks like it didn't actually fix the issue. I've made another tweak. Try upgrading TkinterWeb. I'm pretty sure it should work now.

I also added the convenience method frame.yview_toelement as you suggested. Simply pass a CSS selector and the document should scroll to the first matching element. If you also pass an index number, you can control which element is scrolled to. Hope this helps!

Hi. The fix now works. That's fantastic! Thanks! Something I noticed is, when I tried it in my "main" app, at first I thought it wasn't working. Then it occurred to me that I was scrolling to the page location using the yview + selector technique, and what would happen if I went back to using load_file + url + id. It turns out that the second way does work, but the first doesn't. I am happy because I have something that works, but I'm not sure if there is still a bug there. The logic in my app is a bit more complicated since I am "pre-loading" the help file. I may be able to simplify it now that there is a technique that works.

Thanks for the new method. What do you mean by "index number"?

By the way, has something changed with CSS rendering? I am using frame.add_css to send code such as
[id=something] {background-color: yellow;} and as of 3.20.1 it stopped working. It's okay if I downgrade to 3.19.2. I haven't been able to isolate it in a sample app yet.

UPDATE: Ok, I narrowed down the CSS problem. If the add_css call is preceded by load_file(url#something, force=False) then the CSS is not applied. If force=True, then it works. Even better, if I use yview_toelement to scroll to the id instead of load_file, then the CSS works.

If you want to use the yview+selector strategy, you may need to call frame.update() before scrolling. This is because if you are trying to scroll to the node right after loading the website, the website may not have been fully loaded by the time the interpreter moves on.

The index number is useful if your CSS selector matches more than one element. In this case, you can choose which element in the list of matching elements is going to be selected for. For example, if your CSS selector is p, and your page has one hundred <p> tags, and you want to go to the tenth element, you can pass 9 as the index (since indexes in Python start at 0).

Thanks for noticing the problem with loading CSS. I adjusted how CSS is loaded in #77. Previously, code added via add_css while the page was loading was sometimes ignored. Now, any CSS added while TkinterWeb is downloading the page and sending it to the HTML engine will be saved and loaded once the page is being parsed. I forgot to unload the CSS code when the page is not actually loaded (eg. if redirecting to a different element on the same page via load_file(url#something, force=False). It should be fixed now; let me know if it works.

Hope this helps!

The latest fix seems to work. Thanks for the tips re frame.update() and index numbers.