[Code] WebDialog communication
-
As I mentioned elsewhere, I was working on some code to improve communication between Ruby and WebDialogs, especially because the methods provided by the API are rather low-level.
This class provides wrapper methods that handle all escaping/ encoding and work around the message length limit.
They accept any amount of arguments of any data type of JSON:
%(#000000)[String, Fixnum/Integer, Float, Array, Hash/Object, Boolean, nil/null]
(no custom objects or JavaScript functions or SketchUp entities)Since JavaScript to Ruby is asynchronous, you can make use of the callback functions "
%(#000080)[success]
", "%(#000080)[error]
", "%(#000080)[complete]
" that trigger after the Ruby callback has finished. Thereof success/complete hold as argument the result of the Ruby callback, and error holds a string of the Ruby error.Ruby to WebDialog:
` dlg = Beta::UI::WebDialogX.new` * ` dlg.**call_to**( "functionName", arg1, arg2, ... )` This calls a JavaScript **function** and returns the result (synchronous). * ` dlg.**exec_script**( "JavaScriptCode" )` This transfers the given string of JavaScript **code** as a string (to suppress syntax/parsing errors), executes it and returns the result (synchronous). * ` dlg.**set_element_value**( element_id, argument )` Just for completion.
WebDialog to Ruby:
` %(#000080)[dlg = Beta.UI.WebDialogX]` * ` %(#000080)[dlg.callTo( "methodName", arg1, arg2, ... )]` or ` %(#000080)[dlg.**callTo**( optionsObject )]` This calls the given Ruby **method** and optionally returns the result to a JavaScript callback. **with ` %(#000080)[optionsObject={name: "methodName", data: arg, ...}]` (see below)** * ` %(#000080)[dlg.**execScript**( "codeString", optionsObject[optional] )]` This executes the given string of Ruby **code** and optionally returns the result to a JavaScript callback. * ` %(#000080)[dlg.**callback**( "callbackName", arg1, arg2, ... )]` or ` %(#000080)[dlg.callback( optionsObject )]` This calls the Ruby code block that has been created with dlg.**add_action_callback**. The action callback then receives as many arguments as you have specified: [ruby:2ajeudun]dlg.add_action_callback("doSomething"){|dlg, arg1, arg2, arg3| ... }[/ruby:2ajeudun]. **with [ruby:2ajeudun]%(#000080)[optionsObject = {name:"callbackName", data:{}, success:function, error:function, complete:function, direct:true/false}][/ruby:2ajeudun]
- [ruby:2ajeudun]direct: false[/ruby:2ajeudun] (or [ruby:2ajeudun]indirect: true[/ruby:2ajeudun])
Means the data is passed via a hidden input element and the Ruby method get_element_value
* [ruby:2ajeudun]direct: true[/ruby:2ajeudun] (or [ruby:2ajeudun]indirect: false[/ruby:2ajeudun])
Means the data is encoded, split in chunks smaller than 2083 characters (if necessary) and passed via an skp url**
- [ruby:2ajeudun]direct: false[/ruby:2ajeudun] (or [ruby:2ajeudun]indirect: true[/ruby:2ajeudun])
-
Seems we've done pretty much the same work. I've been implementing pretty much the same in the upcoming TT_Lib2.
It's something the API should have had natively though.
-
I based the namings a bit on jQuery.ajax(). I also found afterwards that it is surprisingly similar to Google's/SketchUp's
dcBridge.js
. Unfortunately they don't have the Ruby backend open source and it's a bit complex and overloaded to reverse-engineer. -
Does it clean up the
<SCRIPT>
element thatWebDialog.execute_script
adds to<BODY>
?
I use this snippet in the function that executes on the JS side:$('body script').detach();
-
That's good to add. Do the remaining script elements have an noticeable impact or is it just for cleanliness?
As for%(#000080)[execute_script]
, if the passed JavaScript contains errors they wouldn't be catched but display popups. I then pass the code as String and eval the string. -
@aerilius said:
That's good to add. Do the remaining script elements have an noticeable impact or is it just for cleanliness?
I remove them to be safe. Considering that a Ruby might call a WebDialogs thousands of times, depending, I want to ensure the DOM is clean.
-
Have either of you looked into doing callbacks via: document.external
-
@thomthom said:
I remove them to be safe. Considering that a Ruby might call a WebDialogs thousands of times, depending, I want to ensure the DOM is clean.
A major reason would be if there are limits to the number of certain elements. (For instance, there is a limit to the number of <STYLE> elements [StyleSheets collection members,] in MSIE.)
Can you limit the cleanup to elements that have NO id ?? (That way a author can prevent cleanup of a certain <SCRIPT> element, by using an id attribute.)
I think there was another topic recently where Chris and Jim discussed this ?
OK here it is: [ Code ] Cleanup After execute_script -
@dan rathbun said:
Have either of you looked into doing callbacks via: document.external
That appear to be MS only.
-
@thomthom said:
@dan rathbun said:
Have either of you looked into doing callbacks via: document.external
That appear to be MS only.
Yea.. I just spent some time trying to look up info on it.
There IS an External interface in the DOM, but it seems to be an interface for the window object.Is there a Safari & Webkit DOM/Js/HTML Reference website ??
-
(Devil's Advocate)
[A] exec() is both a Js method, and a global Ruby method.
In Js, exec() applies to Regular Expression searches.
In Ruby, exec() applies to executing an external process. (Although in embedded Ruby, as in SketchUp, it is worthless, because it kills the current Sketchup Process.)
So we have been teaching people to avoid using it.Since this is a subclass, and you are fixing
execute_script()
, you might as well just override it, fix the arguments and then pass the new arguments as:
super("Dialog._fromRuby(#{id}, \"#{function_name}\", \"#{arguments_string.inspect[1..-2]}\")")
Also you can alias the Ruby method as:**execScript**
as this seems to that the ruby side method was named after the Js and VBS method: (ie: [window.execScript](http://msdn.microsoft.com/en-us/library/ms536420(v) which is compatible with MSIE and Chrome, and is likely to be added as a standard. See Also: http://help.dottoro.com/ljoswolk.php )So I'm arguing against using
exec()
as method name.
[B] Also, I do not agree with your use of
call()
. You use it in a non-standard way.
Normally it runs against the receiver:
proc_obj.call( *args*... )
or
method_obj.call( *args*... )
Anyway... I suggest usingcall_to()
orcall_func()
[C] Errors.. whether or not errors are suppressed, should be controlled via an
@debug
instance var, owned by the subclass instance.
[D] Nice idea for adding block forms for the 'execute' type methods with auto return value! (I also noticed the glaring lack of a
set_element_value
method in the WebDialog API class.)
P.S. - In public library classes and modules, Ruby conventions should be followed, as closely as we can. (I would not nickpick about these things, if this was just a private class within your own namespace.)
-
[anchor=marriage:2kcacprz]I also[/anchor:2kcacprz] have been working on a
UI::WebDialog
subclass, but only briefly thought about communication issues, and decided to leave that for version 2.0 (... but then here you've already done the groundwork.)It's very possible we can marry your code and mine, into a mutually beneficial subclass.
For instance, I (& Driven,) have already solved injection issues.
As well as, fixing current bugs concerning the superclass'
show
&show_modal
methods in block forms, for both platforms. (On PC blocks run before content is ready, on Mac they never run.)
Meaning I have already overriden those methods in my edition, so you would not need to.We only need to decide, how to inject your base Js "WebDialog framework". You need to think about whether it will be easier to maintain as a pure .js file, injected into the <HEAD>, as a <SCRIPT src='path/to/dialogfunc.js'> element, rather than a heredoc text block embedded within the Ruby. (The reason I ask is I have already added in this ability to inject external css stylesheets, so this can be used for script files as well.)
-
(More Devil's Advocate)
I do realize it is beta.. but:
[E] Namespace: I will always crab about defining non-Ruby CORE classes within
TOPLEVEL_BINDING
. This should be considered Ruby's namespace.Just as ThomThom is defining his subclass within a module namespace
TT_Lib2
or whatever he decides, this class needs to go in some namespace.Temporarily ... with
Dev::Test::UI
orBeta::UI
, just something that does not show newbies that we define classes that will potentially clash with Ruby Core objects, or other wrongly written, custom classes, that are defined at the toplevel.Think about the
WxSU
/WxRuby
extensions, they haveDialog
class, but it is within theWx
namespace. (See the file "wxruby/lib/wx/keyword_defs.rb" in the WxRuby source.)
[F] Classname: The name
Dialog
, is potentially confusing. Most would think it will refer to native dialogs (Win32/MFC on Windows, Cocoa or Carbon on Mac.)
I was thinking of something like:WebDialogDeluxe
orWebDialogEx
as a classname. [ see next post ]Remember that even a class name is a Constant reference, and any plugin author, within their own namespace, can define their own reference to classes in other namespaces, to save typing, (if they so desire.) Ex:
module JoeCoder WDE = SKX;;GUI;;WebDialogEx @@dlg = WDE.new("Plugin Options", ... ) end # module
On the Javascript side, is using such a common word as a namespace wise ??
(Are we sure this will not clash with any possible framework that an author might wish to use: JQuery, DHTMLSuite, etc.)
-
I fixed [A], [\B], [C], [E].
@dan rathbun said:
[F] Classname: The name
Dialog
The classname was a placeholder to be changed when using the code (I corrected it to avoid misuse).
@dan rathbun said:
You need to think about whether it will be easier to maintain as a pure .js file, [...] rather than a heredoc text block embedded within the Ruby.
The reason was just to have it more compact (one file, no external requirements). But it has grown a lot, and a separate file gives also nicer JS code-highlighting
I have one issue with using an overriden
execute_script
instead my method because I pass the code string to JavaScript as a String and eval it there. Now this requires that the JS framework is already loaded, but originally I load it withexecute_script
in theshow
method... I'm curious about your external script/css injection.Marriage sounds always good, I think merging the code is better than having several competing less complete solutions.
-
[off:3k0yoz7n]Side Note: A "wrapper class" and a subclass are two different things.
A wrapper:
class Author;;WebDialog attr_reader(;dialog) def initialize(*args) if @dialog.nil? @dialog = UI;;WebDialog.new(*args) end # other init code end def execute_script(code) # do some manipulation to the code string result = @dialog.execute_script(code) end end # wrapper class
Basically a "wrapper" mimics the "wrapped class" (with modifications,) and so must contain a "wrapper instance method" for each of the "wrapped class's" instance methods.
A wrapper is usually more work and requires higher diligence in coding to get things to work correctly. But sometimes has advantages over subclassing. Some of the Extended Ruby Library classes are written as a "wrapper" class. (The
Delegate
classes come to mind.)[/off:3k0yoz7n] -
@aerilius said:
I fixed ...
OK.. looking over the new version.
@aerilius said:
But it has grown a lot, and a separate file gives also nicer JS code-highlighting.
Exactly what I was thinking...
[off:3qvw2q1w]I wish Notepad++'s lexer would recognize inline dialects from other languages, and highlight them correctly. It does seem to work for Js embedded into HTML, but not CSS embedded into HTML, everything within the style tag is white. (Maybe we can get the Notepad++ core guys to "invent" some editor hash codes for inline language changes?)[/off:3qvw2q1w]
@aerilius said:
I have one issue with using an overriden
execute_script
instead my method because I pass the code string to JavaScript as a String and eval it there. Now this requires that the JS framework is already loaded, but originally I load it withexecute_script
in theshow
method... I'm curious about your external script/css injection.This will need to be moved into the
show_handler()
, just after stylesheeet injection. It will need to be done PRIOR to any "interactive proc" running, in case an author wishes to use your Js framework, in that proc (if they define one.)@aerilius said:
The reason was just to have it more compact (one file, no external requirements). ... Marriage sounds always good, I think merging the code is better than having several competing less complete solutions.
OK I am reorganizing my code. (One big change is I was actually playing around with patching the actual
UI::WebDialog
class, but realized their was just no way I could release such a patch. It's a BIG "no-no".)So I have to make changes so it is a subclass. Which solves some things. We can now define the
readyState_callback
within theinitialize
method, simplifying theshow_handler
method, and other code as well.It gives access to the constructor arguments, which I need to try to solve another of the
UI::WebDialog
bugs, but that's low priority for now. Organization comes first.Multiple files: I learned multiple files are really best, especially while debugging.
[off:3qvw2q1w]Also I use my editor in 2 pane mode, left pane edit, and right pane for referring to code in other files of the plugin. Saves scrolling way up, and way back down, etc.
I move various files back and forth between the left and right editor panes, depending on which one I am editing.
I also use Mike Foster's NPP Session Manager plugin (with custom mouse context-menu,) to quickly load all the files in the project, and save changes (like which pane each file is loaded into.) See the latest help file for his plugin, if you use NPP.I hate scrolling so much.. that I often insert "section fold" brackets like:
` #{# INSTANCE METHODS#}#`
... and use the fold shortcuts ALT+level_number_key to quickly expand / contract block levels.[/off:3qvw2q1w]
Anyway I'll need a day or two to re-organize and make some changes, and merge some code.
-
This code would be nice to have on GitHub. @Aerilius : what you think?
-
@thomthom said:
This code would be nice to have on GitHub. @Aerilius : what you think?
No it would not. I have a GitHub acct. They force a commie-socialist GPL-like license on any code posted.
I currently have a week or more in my part, est ~2500 dollars; and I'm sure Aerilius has at least the same. John has put near 3 days at least into testing as well. We are approaching ~6000 dollars in value invested, and may have even reached this.
I am not willing to let some person(s) or company(ies) just take our code from GitHub, re-brand it, and sell it without compensation.
I know by the time we get through the beta phase, with testing, that we'll have over 10,000 dollars in value into this.
The other issue is forking. The world is full of idiots who do not understand how their fork can "throw a wench into the works."
Go to RubyGems.org, search on "SAX" (which stands for Simple API for XML,) and see how many forks there are for SAXMachine. If you take the time to go to the documentation page for each one, you'll see NONE of these "forkin' idiots" changed the namespace. Nor did any of them even bother to wrap their fork in their author namespace.
I'd like to retain some semblance of control, so I typed up a preliminary EULA, that declares copyright (by our group,) and prohibits re-branding, re-packaging, re-distribution, forking or subclassing. (I am not yet sure about wrapping, still need to think on this.)
But I do not think, at this time, anyone will NEED to do any of these things, as we are making this extension eXtremely fleXible.
I'm not sure but perhaps Google Codesite, may not force GPL-like license on projects, and may be able to use Git now ??
I'm all for making it easier to collaborate, but not at the expense of surrendering my rights. -
oookay.... BitBucket then?
-
@thomthom said:
oookay.... BitBucket.org then?
Now that looks alot better! I'll have to read their ToS alot closer, but like they say right up front, private repositories (with access control.) We won't be ready till later in the week for this, let's get past this first major merge here between Aerilius' code and mine.
Advertisement