Page 1 of 1

Hyperlink in webgadget calls PB procedure?

Posted: Fri Nov 09, 2007 2:04 am
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.

Posted: Fri Nov 09, 2007 2:08 am
by citystate

Posted: Fri Nov 09, 2007 2:37 am
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.

Posted: Fri Nov 09, 2007 10:53 am
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

Posted: Fri Nov 09, 2007 2:54 pm
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?

Posted: Fri Nov 09, 2007 3:05 pm
by srod
Return #False from the callback to prevent the navigation etc.

Posted: Fri Nov 09, 2007 5:46 pm
by naw
@srod, cheers

Posted: Fri Nov 09, 2007 8:42 pm
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?

Posted: Fri Nov 09, 2007 9:10 pm
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?

Posted: Fri Nov 09, 2007 10:48 pm
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


Posted: Sat Nov 10, 2007 12:49 am
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.

Posted: Sat Nov 10, 2007 10:56 am
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.

Posted: Sat Nov 10, 2007 12:41 pm
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_().

Posted: Sat Nov 10, 2007 8:07 pm
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

Posted: Sun Nov 11, 2007 8:08 pm
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.