ID:               34094
 User updated by:  csaba at alum dot mit dot edu
 Reported By:      csaba at alum dot mit dot edu
-Status:           Feedback
+Status:           Open
 Bug Type:         Feature/Change Request
 Operating System: Windows
 PHP Version:      5CVS-2005-08-12 (dev)
 New Comment:

I'm sure most of what I'm going to write below is already clear to you,
but better to err with too much info.  If it's still lacking, please let
me know.  I've only tried this on Win XP Pro, SP 2 and it's to be run
with CLI php.  the popup function is so that I get a MsgBox window to
the foreground to help with debugging.  The calls to popup in the code
below are there just to illustrate execution order.

The first block of code right after the class definition instantiates
IE and stuffs the web page.  It is now set up, except for hooking in
the event handlers.

The next block of 3 lines sets up variable space within IE ($window is
the window object within IE's DOM).  Without it, IE doesn't like
foreign apps setting values on its window.varName

The next block sets up the event handlers.  The first line where the
class is created is so that IE has a local copy of this PHP object. 
(In VB, I would assign an instance of the class that contains the event
handler function to the event handler (eg.
window.document.getElementById('domElemId').onclick=vbClassInstance 
When that event is fired, the previously designated default function
within the class (thus I'd need three classes for the three event
handlers) would be called).  Trying the same type of assignment that VB
uses did not work however.
Now the hooking up happens in the next three lines (the third one
(escape key detecter) being an extended version for illustrative
purposes of showing what happens with additional arguments).
The first one expands like so:
$window->ExecScript("document.getElementById('btnOK').onclick=function(){window.evtHandler.dummyVar('clickOK')}")
The point of that line is so that the string will execute as javascript
local to the browser.  The reason I needed to do that was so I could
assign an anonymous function (the function(){...}) to the event
handler, and the reason I needed to that was so that window.evtHandler
didn't get referenced till an event fired, else it would error or try
to execute code.  Thus, I asked IE to do:
document.getElementById('btnOK').onclick=function(){window.evtHandler.dummyVar('clickOK')}
So when the button gets clicked and the .onclick fires, the ... part of
function() {...} is executed in the context of the element clicked on
(that means that 'this' is set to the button element - but we don't
make use of that here)
Thus, when the button is clicked we execute:
window.evtHandler.dummyVar('clickOK')
Now we are at the reason for the bug report.  It looks like .dummyVar
is being called like a function (The bug.  Furthermore, if I replaced
.dummyVar with .clickOK or any other function from the class I'd get an
error).  Instead, the last defined function within the evtHandler's
class is called (this is conceivably correct here since VB uses this
paradigm (and it's not even distressing since I can pass arguments),
but it is an issue in the other bug (33386)), namely __construct. 
Furthermore, that 'clickOK' is passed in as an argument (VERY cool.  VB
does not let us pass arguments to the default function in this context).
 In all the "calls" to dummyVar, my convention is that the first
argument passed is the name of the real function within the evtHandler
class that I want executed.  That's really what I would much rather
replace 'dummyVar' with.  Ie.  I'd rather have
window.evtHandler.clickOK() than window.evtHandler.dummyVar('clickOK')


On the receiving end, __construct is called when the class is first
created, but with no arguments, so we filter out this first invocation
by checking the number of arguments.  Every subsequent call from IE,
with the silly .dummyVar style, will have the first argument indicating
the real function that we want to handle the event.  The rest of the
arguments (by my convention) are destined for the real intended event
handler function named by the first argument (thus $argsRest).  Then
the next line makes the actual call.  Note that I use
"".func_get_arg(0) as the name of the  function to call.  That is
because that 'string' (func_get_arg(0)) is coming from javascript and
PHP views it as an object so I've got to coerce it or else
call_user_func_array is not happy.

Finally, three comments.
(1)  The printing of the $ie->hwnd is simply to let me know that PHP is
alive and still in the loop.
(2)  The code above works.  Run it with php.exe and the browser (IE)
should come up and you should be able to click the buttons and get
popups indicating what you did.  This is all working really well for me
and there is no indication to the viewer that there are any problems. 
The reason for filing the report is that the use of that .dummyVar
indicates to me that something funny is going on under the hood, and
I'd rather have it taken away now, if that's going to happen, than to
suddenly realize a year later that I've been using a loophole.  But
really, this is a fabulous technique so I hope the main pieces stay as
is.

===>    Just to be clear, the above code accomplishes everything (in
fact more than) I wanted.  But it seems incorrect to me that attempting
to access a variable of the class (with arguments, no less), will cause
a seemingly unrelated function of the class to be called, whereas
attempting to access that function (or any other) directly will result
in an error.   <===

(3).  There was no real cause to have __construct be the final
function.  If it turns out that it is only a single, final function
that can be called as an event handler, then a developer should use a
separate one for that purpose and not mix in class creation. 
__contruct being last in my code is an artifact left over from my
initial testing when I had a single popup in __construct and couldn't
figure out why the class instance kept getting recreated each time I
tried to access it...

Thanks,
Csaba


Previous Comments:
------------------------------------------------------------------------

[2005-08-12 20:01:16] [EMAIL PROTECTED]

Can you clarify your sample script?
I've been staring at it for 5 minutes and can't figure it out.  I
understand that you're trying to call into PHP objects from the
browser, but can't tell quite what you're doing and what you expect it
should be doing instead.



------------------------------------------------------------------------

[2005-08-12 11:49:07] csaba at alum dot mit dot edu

Description:
------------
This report seems related to http://bugs.php.net/bug.php?id=33386 but
as far as I can tell a distinct phenomenon is occurring.  In this
report, there is also only external access to the last function, (but
in the VB analogue, that is OK here as opposed to there) but the access
here is through a var of the class (as opposed to the class itself or a
function of the class) and that seems wrong.

Let me summarize the VB analagous situation, the acutal situation, and
then the distinction between the report above and this one.  

1.  Summary of the VB situation: In VB, documentation says and testing
confirms that if you connect a DOM event handler directly to a class
built with VB in an .OCX, then if you have marked one of the functions
as default within the class (using Tools / Procedure Attributes /
Advanced ... note that this means one event handler per class), that is
the one that will be executed when the event occurs (note that this
differs from the ScriptControl approach in the prior report where we
expect access to all the class functions).  An example connection is
made by: IE.Document.getElementById('elemId').onclick=classInstance
Note also that there is no facility for passing an argument to the
class.


2.  Summary of actual situation:  With PHP in this report, we have to
set a variable in the DOM to the class instance (OK to here) and then
to access the class (here comes the bug:), ===> we must name a var in
the class <=== (as opposed to the class instance itself or the function
we want to access or for that matter (as in the earlier report) any
function).  At least we are able to pass arguments.


3.  Comparison with prior report:  In both reports, we are only able to
access (from an external COM object) the last function in the class, but
in the earlier report that was not expected (in comparison to VB)
whereas it is conceivable here.  In the earlier report, to access that
last function any function of the class had to be named whereas here
(and the reason for this report), a var (and not a function) of the
class needs to be named.  Frankly, I would expect that other functions
are accessible since some external agent (PHP? COM? javascript?) is
discriminating based on what has been named.


Thanks for considering this,
Csaba Gabor from Vienna


Reproduce code:
---------------
The code below (watch for possible line wrap) manages to successfully
hook a PHP class to event handlers within the IE DOM (as opposed to
event handlers of the IE COM object).  See following section for
issues.

<?
function popup ($text, $title="PHP popup",
                $timeout=4, $style=131120) {
  $oWSH = new COM("WScript.Shell");
  $oWSH->Popup($text, $timeout, $title, $style); }
function hookHandler ($wnd, $elemId, $hndlrName) {
  $wnd->ExecScript(
    "document.getElementById('$elemId')." .
       "onclick=function(){window.evtHandler." .
         "dummyVar('$hndlrName')}"); }

class evtHandler {
  function clickOK()    { popup ("OK was clicked"); }
  function clickBtn2()  { popup ("Test click"); }
  function keyPress($window) {
    if ($window->event->keyCode==27)  // ESC
      $window->doneP=true; }
  var $dummyVar;
  function __destruct() { popup ("destruction"); }
  function __construct() {
    if (func_num_args(0)) {
      popup("in event handler __construct: ");
      $argsRest = array_slice(func_get_args(),1);
      call_user_func_array (array(&$this,
                       "".func_get_arg(0)), $argsRest);
    } else popup("initialization __construct");
  }
}

$ie = new COM("InternetExplorer.Application");
$ie->visible = true;
$ie->Navigate2("about:blank");
$body ="<button id=btnOK " .
       "accesskey=O><u>O</u>K</button>&nbsp;" .
       "<button id=btn2 " .
       "accesskey=T><u>T</u>est me</button>&nbsp;" .
       "<button accesskey=c " .
       "onclick='window.doneP=true'>" .
       "<u>C</u>ancel</button>";
$ie->Document->body->innerHTML .= $body;

$window = $ie->Document->parentWindow;
$window->ExecScript ("window.doneP=false");
$window->ExecScript ("window.evtHandler=false");

$window->evtHandler = new evtHandler();
hookHandler ($window, 'btnOK', 'clickOK');
hookHandler ($window, 'btn2', 'clickBtn2');
$window->ExecScript("document.onkeypress=function(){" .
     "window.evtHandler.dummyVar('keyPress',window)}");

try { while (!$window->doneP) {
        com_message_pump(600);
        print "\n" . $ie->hwnd; }
      $ie->Quit(); }
catch (Exception $e) { popup ("IE has gone away"); }
popup("Done with php program");
?>

Expected result:
----------------
I expect to name the (non private) function within the class that I'd
like to call for a DOM event handler, and have that function be
executed.  If it turns out (because of COM restrictions et al) that an
arbitrary function can't be called, then I would expect to have to name
exactly the function that will be called and not an arbitrary variable
(dummyVar) within the class.


Actual result:
--------------
1.  I need to refer to dummyVar on lines 10, 50
2.  All calls (attempt to) go to the final function defined, even if
it's marked private (in this latter case, the call would fail)

I REALLY like being able to pass arguments to PHP event handlers (e.g.
pressing the Escape key in this example), thanks for building it in.  I
hope that is here to stay.


------------------------------------------------------------------------


-- 
Edit this bug report at http://bugs.php.net/?id=34094&edit=1

Reply via email to