Hyperlink in webgadget calls PB procedure?

Just starting out? Need help? Post your questions and find answers here.
Seymour Clufley
Addict
Addict
Posts: 1265
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Hyperlink in webgadget calls PB procedure?

Post by Seymour Clufley »

I've got HTML being streamed into the webgadget. When the user clicks a hyperlink, I want a PB procedure to run and do something with the hyperlink's target URL.

So far I've been trying to make this happen using a JavaScript procedure. The hyperlink calls the JS procedure, which in turn creates a file. PB is running a loop, checking each time for the existence of the file. If the file exists, PB deletes it and calls the procedure.

That method is hardly elegant. But it gets much worse. In order to create a file, JavaScript needs to use an ActiveX object. That yellow bar will appear when the user clicks the hyperlink!

Does anyone know any other ways to get a hyperlink to call a PB procedure? The URL of the hyperlink needs to be passed somehow to PB, so that it knows what it's dealing with.

Thanks for reading,
Seymour.
citystate
Enthusiast
Enthusiast
Posts: 638
Joined: Sun Feb 12, 2006 10:06 pm

Post by citystate »

there is no sig, only zuul (and the following disclaimer)

WARNING: may be talking out of his hat
Seymour Clufley
Addict
Addict
Posts: 1265
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Post by Seymour Clufley »

Thanks, but I was looking for a different method, to be honest. I'm finding the code in that thread is causing crashes. Also, you have to set up a new callback every time a hyperlink is added or removed, which will add to CPU overhead.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

I don't understand, why not use a navigation callback (PB 4.1 only) :

Code: Select all

Procedure NavigationCallback(Gadget, Url$)
  Debug Url$
  ProcedureReturn #True
EndProcedure

If OpenWindow(0, 0, 0, 600, 300, "WebGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) And CreateGadgetList(WindowID(0))
  WebGadget(0, 10, 10, 580, 280, "http://www.purebasic.com")
  SetGadgetAttribute(0, #PB_Web_NavigationCallback, @NavigationCallback())
  Repeat 
  Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
I may look like a mule, but I'm not a complete ass.
naw
Enthusiast
Enthusiast
Posts: 573
Joined: Fri Apr 25, 2003 4:57 pm

Post by naw »

@srod,

Very cool - didn't know this was possible. I notice that PB captures the event but the WebGadget still navigates to the link.

Is it possible for PB to capture the event and control wether or not the WebGadget navigates off to another location?
Ta - N
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Return #False from the callback to prevent the navigation etc.
I may look like a mule, but I'm not a complete ass.
naw
Enthusiast
Enthusiast
Posts: 573
Joined: Fri Apr 25, 2003 4:57 pm

Post by naw »

@srod, cheers
Ta - N
Seymour Clufley
Addict
Addict
Posts: 1265
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Post by Seymour Clufley »

After some research, it appears there is no way for a browser to silently create a file on the client's computer. One way or another, security blocks it. Therefore the only way seems to be the webgadget's navigation callback. I was reluctant to use this method because early experiments revealed complications. I'll describe them.

The webgadget is loading custom HTML. I've found that SetGadgetItemText causes a crash if used in a thread, so instead I'm saving the HTML to a file and then directing the webgadget to it using SetGadgetText. The clicking sound is a bugger, but better than a crash.

This custom HTML contains hyperlinks. The idea is that when the user presses a hyperlink, PB revises the HTML accordingly and the webgadget's contents changes. For example, the user clicks a hyperlink called "Create box" -> PB intercepts this and revises the HTML to include a box, then the webgadget is reloaded with the new HTML which is identical to the previous HTML except that it includes a box.

The trouble with using the navigation callback is with the return variable. You have to set it to #False to stop the webgadget from going to an invalid URL (since the hyperlink is just a code, signalling PB to do something). However, having set it to false, SetGadgetText is also blocked. It treats reloading by code the same way it treats reloading by user interaction: as a click.

I've tried to work around this by setting global variables that the main loop picks up on, but the problem persists.

To recap, the problem is: stopping the webgadget from responding to a hyperlink click, and making it load a completely different file (that has to be created after the click).

Can anyone think of a workaround?
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Well, a global variable wouldn't be my first choice; I'd prefer to send a message to the main window, but I thought I'd give it a try.

Run the following and click the 'Intoroduction' link and see it redirected to MSN instead of the Purebasic page :

Code: Select all

Global flag=0
Procedure NavigationCallback(Gadget, Url$) 
  If Url$ = "http://www.purebasic.com/index.php3"
    flag = 1
    ProcedureReturn #False
  EndIf
  ProcedureReturn #True 
EndProcedure 

If OpenWindow(0, 0, 0, 600, 300, "WebGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered) And CreateGadgetList(WindowID(0)) 
  WebGadget(0, 10, 10, 580, 280, "http://www.purebasic.com") 
  SetGadgetAttribute(0, #PB_Web_NavigationCallback, @NavigationCallback()) 
  Repeat 
  If flag = 1
    SetGadgetText(0, "www.msn.com")
    flag = 0
  EndIf
  Until WaitWindowEvent() = #PB_Event_CloseWindow 
EndIf 
I'm not sure if this is the kind of thing which will help you?
I may look like a mule, but I'm not a complete ass.
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

The webgadget is loading custom HTML. I've found that SetGadgetItemText causes a crash if used in a thread, so instead I'm saving the HTML to a file and then directing the webgadget to it using SetGadgetText. The clicking sound is a bugger, but better than a crash.
Then don't use it in a thread. Simple, huh?
This custom HTML contains hyperlinks. The idea is that when the user presses a hyperlink, PB revises the HTML accordingly and the webgadget's contents changes. For example, the user clicks a hyperlink called "Create box" -> PB intercepts this and revises the HTML to include a box, then the webgadget is reloaded with the new HTML which is identical to the previous HTML except that it includes a box.

The trouble with using the navigation callback is with the return variable. You have to set it to #False to stop the webgadget from going to an invalid URL (since the hyperlink is just a code, signalling PB to do something). However, having set it to false, SetGadgetText is also blocked. It treats reloading by code the same way it treats reloading by user interaction: as a click.
You should obviously check the URL before returning true/false... And you should of course use SetGadgetItemText() instead of loading the text from disk. :shock:


Here's the code with boxes, no threads, no files, no global variables and basically everything you (say you) need (except the realization that maybe you should use one gadget for each box?)

Code: Select all


Procedure WebGadgetCallback(Gadget, Url.s)
  Select Url
    Case "pb:addbox"
      A.s = GetGadgetItemText(0, #PB_Web_HtmlCode)
      Pos = FindString(A, "<!-- box -->", 0)-1
      A = Left(A, Pos) + "<br>Mike Tyson is a boxer" + Right(A, Len(A)-Pos)
      SetGadgetItemText(0, #PB_Web_HtmlCode, A)
    Default
      Debug Url
  EndSelect
  ProcedureReturn 0
EndProcedure

OpenWindow(0, 0, 0, 512, 384, "", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
CreateGadgetList(WindowID(0))
WebGadget(0, 0, 0, 512, 384, "")
SetGadgetAttribute(0, #PB_Web_NavigationCallback, @WebGadgetCallback())

While GetGadgetState(0) = #PB_Web_Busy : WindowEvent() : Delay(10) : Wend

SetGadgetItemText(0, #PB_Web_HtmlCode, PeekS(?Page))

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break
  EndSelect
ForEver

End

DataSection
  Page:
  Data.s "<html><head></head><body> <a href='pb:addbox'>Add box</a> <!-- box --> </body></html>"
EndDataSection

Seymour Clufley
Addict
Addict
Posts: 1265
Joined: Wed Feb 28, 2007 9:13 am
Location: London

Post by Seymour Clufley »

Seymour wrote:The webgadget is loading custom HTML. I've found that SetGadgetItemText causes a crash if used in a thread
Trond wrote:Then don't use it in a thread. Simple, huh?
Unfortunately, not possible. This is a plugin for a non-PB program. If I don't use threads, the host program gets frozen until the Repeat..Until loop finishes.

Besides, if SetGadgetItemText doesn't work in a thread even when SetGadgetText does work in exactly the same situation, then there is a bug with SetGadgetItemText. The solution isn't "don't use it in a thread", but "fix the bug".
Seymour wrote:The trouble with using the navigation callback is with the return variable. You have to set it to #False to stop the webgadget from going to an invalid URL (since the hyperlink is just a code, signalling PB to do something). However, having set it to false, SetGadgetText is also blocked. It treats reloading by code the same way it treats reloading by user interaction: as a click.
Trond wrote:You should obviously check the URL before returning true/false... And you should of course use SetGadgetItemText() instead of loading the text from disk. :shock:
I would much prefer to use SetGadgetItemText, but as I say, until the bug is fixed with using it in a thread, I'm forced to load the html from disk.
Here's the code with boxes, no threads, no files, no global variables and basically everything you (say you) need
Thanks for the code, Trond. There are a few tricks in there that I might use.
(except the realization that maybe you should use one gadget for each box?)
That might be better. But for now, given that the main app is not written in PB, I need to confine all activity to one plugin window = one webgadget.
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

This is a plugin for a non-PB program.
A plugin! So this is suddenly a whole different thing... It might be a good idea to give all the information on what you need.
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

Here's the code for using SetGadgetItemText() from a thread. I hope you don't have any further requirements that you haven't mentioned.

Code: Select all

Procedure WebGadgetCallback(Gadget, Url.s)
  Select Url
    Case "pb:addbox"
      A.s = GetGadgetItemText(0, #PB_Web_HtmlCode)
      Pos = FindString(A, "<!-- box -->", 0)-1
      A = Left(A, Pos) + "<br>Mike Tyson is a boxer" + Right(A, Len(A)-Pos)
      SetGadgetItemText(0, #PB_Web_HtmlCode, A)
    Default
      Debug Url
  EndSelect
  ProcedureReturn 0
EndProcedure

Procedure Main(None)
  OpenWindow(0, 0, 0, 512, 384, "", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
  CreateGadgetList(WindowID(0))
  WebGadget(0, 0, 0, 512, 384, "")
  SetGadgetAttribute(0, #PB_Web_NavigationCallback, @WebGadgetCallback())
  
  While GetGadgetState(0) = #PB_Web_Busy : WindowEvent() : Delay(10) : Wend
  
  SetGadgetItemText(0, #PB_Web_HtmlCode, PeekS(?Page))
  
  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_CloseWindow
        Break
    EndSelect
  ForEver
EndProcedure

WaitThread(CreateThread(@Main(), 0))

End

DataSection
  Page:
  Data.s "<html><head></head><body> <a href='pb:addbox'>Add box</a> <!-- box --> </body></html>"
EndDataSection
To see which thread you are in at any point, insert a Debug GetCurrentThreadId_().
User avatar
utopiomania
Addict
Addict
Posts: 1655
Joined: Tue May 10, 2005 10:00 pm
Location: Norway

Post by utopiomania »

Does anyone know any other ways to get a hyperlink to call a PB procedure? The URL of the hyperlink needs to be passed somehow to PB, so that it knows what it's dealing with.
I used the code below to check out the webgadget news in PB 4.10 yesterday, and it works fine. It basically shows you how to use the wegbadget callback as an eventhandler for a webgadget based user interface.

Code: Select all

;-program notes
;-initialize
;-
enumeration
  #WIN
  #WEB
endEnumeration

global winW = 800, winH = 600

declare openMainWindow()
declare navigationCallback(id, url.s)
declare.s userInterfacePage1()

;-program entry
openMainWindow()

;-program event handler
repeat
  event = waitWindowEvent()
  select event
    case #PB_EVENT_SIZEWINDOW
       winW = windowWidth(#WIN)
       winH = windowHeight(#WIN)
       resizeGadget(#WEB, 0, 0, winW, winH)
    case #PB_EVENT_CLOSEWINDOW
      ;-program exit
      ;-
      end
  endSelect
forever

procedure openMainWindow()
  if openWindow(#WIN, 0, 0, winW, winH, "program template", $CF0001)
    if createGadgetList(windowID(#WIN))
      webGadget(#WEB, 0, 0, winW, winH, "")
      ;load the html
      setGadgetItemText(#WEB, #PB_WEB_HTMLCODE, userInterfacePage1())
      ;callback to monitor navigation
      setGadgetAttribute(#WEB, #PB_WEB_NAVIGATIONCALLBACK, @navigationCallback())
    endIf
  endIf
endProcedure

procedure navigationCallback(id, url.s)
  url = lcase(url)
  if findString(url, "object1_clicked", 1)
      ;handle event...
      messageRequester("UI Event handler", "Object1 click event")
      ;cancel navigation
      procedureReturn 0
  else
    ;allow navigation
    procedureReturn 1
  endIf
endProcedure

procedure.s userInterfacepage1()
  ;html
  html.s + "<html><head>" + #CR$
  html + "<style type = 'text/css'>" + #CR$
  html + "  body{font-family:calibri; font-size:18px; font-weight:800}" + #CR$
  html + "</style>" + #CR$ + #CR$

  html + "<script language = 'vbscript'>" + #CR$
  html + "sub object1_onMouseOver()" + #CR$
  html + "  object1.style.cursor = " + #DQUOTE$ +"hand" + #DQUOTE$ + #CR$
	html + "  object1.style.color = " + #DQUOTE$ +"#FFFFFF" + #DQUOTE$ + #CR$
  html + "  object1.style.backgroundcolor = " + #DQUOTE$ +"#FF8040" + #DQUOTE$ + #CR$
  html + "end sub" + #CR$ + #CR$

  html + "sub object1_onMouseOut()" + #CR$
  html + "  object1.style.cursor = " + #DQUOTE$ +"arrow" + #DQUOTE$ + #CR$
  html + "  object1.style.color = " + #DQUOTE$ +"#FF8040" + #DQUOTE$ + #CR$
  html + "  object1.style.backgroundcolor = " + #DQUOTE$ +"#FFFFFF" + #DQUOTE$ + #CR$
  html + "end sub" + #CR$ + #CR$

  html + "sub object1_onClick()" + #CR$
  html + "  window.location = " + #DQUOTE$ +"object1_clicked" + #DQUOTE$ + #CR$
  html + "end sub" + #CR$
  html + "</script>" + #CR$
  html + "</head>" + #CR$ + #CR$

  html + "<span id = 'object1'" + #CR$ 
  html + "  style = 'position:absolute; left:120; top:80; width:100; height:22; color:#FF0000'>" + #CR$
  html + "  <center>Object1</center>" + #CR$
  html + "</span>" + #CR$
  html + "</body></html>"
  procedureReturn html
endProcedure
Last edited by utopiomania on Sun Nov 11, 2007 8:08 pm, edited 1 time in total.
User avatar
utopiomania
Addict
Addict
Posts: 1655
Joined: Tue May 10, 2005 10:00 pm
Location: Norway

Post by utopiomania »

To recap, the problem is: stopping the webgadget from responding to a hyperlink click, and making it load a completely different file (that has to be created after the click).
You can use my code above to do that if you only take the time to check it out. Or, did I miss something?

BTW, just changed it to demo some other possibilities.
Post Reply