GP SoftwareTwitter
Opus FAQsManualCommandsObjects

HowTo: Create a function-library with Windows Script Components (WSC)


#21

I think you are getting ahead of yourself and adding complexity before it is needed.

Most scripts don't need any utility functions. Some need one or two small ones which will probably never need updating. Very few will need this.

Keep it simple until/unless the complexity is really needed.


#22

Thanks leo — that's a very fair warning indeed. My quirky learning is such that I get a problem into my head, and in solving that I sort out all sorts of other things. I know very well that the problems don't necessarily occur to me in the best logical order. Anyway, I now regard function libraries as satisfactorily sorted.


#23

I should also say, if two scripts do have a lot of shared code, it will often make sense to combine them into a single script.

Each script can add multiple columns, commands and event handlers.


#24

Huh? I paused a little and need to see we are back at the eval() approach? o) You can use eval() of course, but you'll have hard times in any error situation, as the error is always going to be that eval() statement and not that specific line in your included file. Unless you are a perfect coder, this does not really simplify things from my perspective.

That said, maybe using JSON.Parse() is better to use than eval(), it maybe gives a hint on where in the JSON the error occured (when parsing at least).
You need to wrap the external resource in curly brackets to make this work "{ sum: function(a,b){ /* code*/ }}". Just in case you are still motivated to try new things. o)

I updated the button and wsc download with that CreateSum(); approach, maybe give it another try.

I think I like the "GetObject("<wsc-location\wsc-file.wsc>);" approach, getting around the registration can be handy. Read about it as well, but never tried myself, nice if that works out as described. Throwing in a DO alias to hit that specific DO WSC resource directory as you mentioned and voila, why not! o)

@Leo
Is it good advice to encourage people to put multiple commands and event-handlers into one script? Once your script collection grows, you loose track of what script offers what, you also can get into problems naming the script, the belonging thread etc., not to mention disabling/swapping specific bits in case of errors.

Did this thread start at "How to library" and reached "Better do not" already, although nobody is about to upload js-libs tomorrow?


#25

[quote="tbone"]@Leo
Is it good advice to encourage people to put multiple commands and event-handlers into one script? Once your script collection grows, you loose track of what script offers what, you also can get into problems naming the script, the belonging thread etc., not to mention disabling/swapping specific bits in case of errors.[/quote]

If the functionality is all related, I'd say it often makes sense to keep it in one script. You've done exactly this with some of your scripts and script-commands, some of which do hundreds of different things.

It's not an absolute. No programming advice is.

One situation where I'd make separate scripts is if someone wants to know how to do one particular thing, and might be looking for sample code which they can adapt for their own uses. It's a lot easier to understand a minimal script that just does what they want without hundreds of other things thrown in as well. OTOH, if people want to use a script without looking at it, the swiss army knife approach can give them a more powerful tool, and they won't care about the complexity under the hood. But we're talking about extremes here, anyway. Most scripts only do a couple of closely related things and most code shared between scripts is stuff like 3-line snippets where it makes more sense to copy & paste than worry about libraries and make everything harder to understand because it's not all in one place.

WSC bundles can still be very useful and are worth knowing about, though. That's why I stickied the thread.


#26

@tbone, thanks for the tip with eval() not throwing precise errors!!! I had only tried it with code that I already had working within a single script, so I didn't realise that this difficulty arises. (And I've seen a great multitude of errors thrown by code in a WSC.)

I tried the WSC approach once again, starting with clean copies of your revised code.

  • The procedure of creating the new object by code within the WSC means more typing, but at least it is formulaic, and quite distinct from the function itself.
  • I also wanted the function to be written exactly the same as it would be if it were at the bottom of a script, because otherwise it is a pita having to edit it differently if you wanted to swap between the two locations.
  • In particular, I wanted the parameters to be already contained within the object, so that .sum and .diff are properties and not methods — not for any good reason except that I wanted the function "DoSum" to be exactly as it was before.

After quite a bit of fiddling about, I came up with the following code within the WSC:

function CreateSum (a, b) { return new DoSum (a, b); } function DoSum (a, b) { this.sum = a + b; this.diff = Math.abs (a - b); }and the following code within the DOpus button:

var helper = new ActiveXObject("DOpusHelper.wsc"); var MySumObj = helper.CreateSum (15, 25); DOpus.Output("WSC-Sum: " + MySumObj.sum); DOpus.Output("WSC-Diff: " + MySumObj.diff);
I think this does the job off creating a new object "MySumObject" (it certainly gives the correct answers 40 and 10). By using the prefixes "Do-" and "Create-" it also sets up a completely formulaic way of writing the "CreateSum" function into the WSC, except only for the variables, which in this case are (a, b). Notice that:

  • DoSum is written exactly as it would be if it were in the master script file.
  • There is no "return" in the "DoSum" function because it is intended to be used in a constructor procedure.

This is a lot more work than eval(), but the absence of a detailed error report probably tips the balance towards it. So I now have two quite different solutions to the formation of a library.

By the way, last week I managed to make unregistered WSC libraries work, using DOpus aliases in the specification of the paths. I'm not at all clear, however, about what the advantages are of registering and unregistering.


#27

Wait, since I also wasn't really satisfied with that "CreateSum()" approach and also couldn't remember such a restriction, I gave it another go.

The reason why you cannot create a new instance from SumObj within the WSC is, because it's name has been registered as a method, remove that.
Instead just use "" in the declaration part of the WSC, after that you can use "var mySum = new helper.SumObj();" as one would expect. You could also get along without declaring methods at all it seems (declaring everything as property), but as long as other environments have not been tested, I would stick to declaring all those internal functions as methods, which should be callable directly and all the things you want to create new instances from, as properties.

So, no extra lines or wrapping function calls necessary. All is good! o)

Well, a disadvantage is, that you must know the path obviously. The advantage is, that you don't need to run regsvr32.exe prior to using the WSC, which might raise security/admin level related issues for some as we've seen here already.


#28

Success! I changed to in the XML section of the WSC file. Then I changed the DOpus button code (only the second line has changed) to:

var helper = new ActiveXObject("DOpusHelper.wsc"); var MySumObj = new helper.DoSum (15, 25); DOpus.Output("WSC-Sum: " + MySumObj.sum); DOpus.Output("WSC-Diff: " + MySumObj.diff);thus bypassing the "CreateSum (a, b)" fudge function completely, and attempting to construct a new object "MySumObj" directly from the function "DoSum (a, b)" in the WSC. It worked, outputting the answers 40 and 10 (and, as expected, it won't run if "new" is omitted on the second line). That seems to clear up the constructor problem, although the choice of "method" or "property" remains mysterious.

I then reread your post and deleted the two XML lines specifying the two parameters of "DoSum (a, b)":


The script ran as before. It also ran when I replaced the two lines, but gave the parameters wrong names. WSC seems not to be reading the XML specification of the parameters for "properties". This change from "method" to "property" certainly tips the balance from eval() to WSC — thanks very much, @tbone.


#29

@tbone, my WSC JScript libraries are going very nicely indeed, thank you very much. I so far have three registered libraries, JLBasic.wsc, JLMaths.wsc and JLDate.wsc, and it is making everything far more systematic. I now have three more questions about aspects of the WSC that I do not understand:

  1. PROPERTIES, METHODS AND PARAMETERS:
    (a) In the opening XML part of the *.wsc file, I seem to be able to leave the parameters out of the methods as well as the properties. Does naming the parameters ever matter?
    (b) It doesn't seem to matter whether a function is called a "method" or a "property" in the opening XML part of the file, unless you want to use "new". Is there any real distinction?

  2. PARAMETERS:
    Whether in a WSC or at the bottom of an ordinary script, it seems as if a function can always be written as "MyFunction ()" without any arguments at all, provided only that any arguments are then referred to as "arguments [0]", "arguments [1]", ... This is, I think, because the list of arguments is in fact an array called "arguments", with length "arguments.length" and so forth. Such notation is mostly very inconvenient, but it can be extremely useful in repetitive situations. For example, a function can have an indeterminate number of parameters, and begin with something such as:
    for (nn = 0; nn < arguments.length; nn++)
    Am I on the right track here?

  3. OUTPUT FROM THE WSC TO THE LOG FILE:
    You gave an example of using a DOpus function within the WSC, and I managed to condense your example down to a single line in the WSC file:

function DoLog (aa) {aa.DOpus.Output (TheText)}where "TheText" is some text. (And in the opening XML part I can call it either a "method" or a "property".) Within the script file, the *.wsc helper file object will already have been created by say var JLMaths = new ActiveXObject ("JLMaths.wsc"), so TheText can then be printed to the log file by

JLMaths.DoLog (this)

(a) I don't understand at all how this procedure is working. What exactly is the function of "this" here? And is the function a "property" or a "method"?

(b) How can two or more pieces of text be printed to the log file? Can this be done within one function, or must I use different functions with different names for each piece of text? I tried having two parameters aa and bb, but of course this doesn't work because aa and bb are not even mentioned when the function is called within the script.

(c) When I am writing a function within a script, I usually have to insert several "DOpus.Output (...)" commands, to diagnose what is going on (that is, going wrong), or just to record what happened. Is it possible to do this when writing a function within the WSC? That is, is it possible to communicate the meaning of "DOpus.Output ()" to the WSC?
WORKAROUND: 1. Create in the WSC three copies of the function DoLog, say DoLogA, DoLogB and DoLogC.
2. Insert the probes, "TheTextA", "TheTextB" and "TheTextC" at useful places within the function being written.
3. Insert the three lines JLMaths.DoLogA (this) and JLMaths.DoLogB (this) and JLMaths.DoLogC (this) at the bottom of the script file.
This works, but it's a bit clumsy.

(d) What are the "other methods that DOpus offers" that can be called using this procedure?


#30
  1. a+b
    Naming and declaring may be more important, if the WSC is to be used from other environments like C++ or PHP I guess (compilers like to know how a function has to be called, how many parameters it has etc). It allows the environment to see what's available in the WSC (probably that also serves as preparation to make "Reflection" work en.wikipedia.org/wiki/Reflectio ... rogramming), but these things seem to be not required to make the WSC itself work inside DO and JScript. Declaring and naming things nicely probably is also very useful if you are in some kind of development environment (like Visual Studio, Netbeans, Eclipse e.g.), which offers autocompletion of available method and property names while coding. These things would not work, if there's no place where all public and available methods/properties can be read from.

Yes. You can also combine unnamed and named parameters, look up how javascript does it, there should be plenty of examples out there.

  1. a+b+c+d
    You condensed a bit too much! o) Passing "this" from a DO script to the WSC should be done only once. Then, inside the WSC, save that to a variable that's local/accessible directly to all WSC functions. From that point on, this variable provides the same context which is available outside the WSC (the DO button script context). The log function from the WSC should use this variable (the local reference to the outer "this") to get access to the objects outside of the WSC ("DOpus" e.g.). No need for multiple log functions, but you need one step or function to carry the outer context into the WCS, the log function then just takes the text as parameter and calls the external method (DOpus.Output()) by using the reference to the outer scope. Maybe check my example again, maybe it's more clear after reading these lines.

Consider some additional reading regarding "this" (duckduckgo.com/?q=this+javascript), any javascript example should be fine. If you can't get your head around that, ask again. o)


#31

Thanks, @tbone. Success!!! I thought that "this" only ever referred to the present function — your post was quite a revelation! For the record, here is what I have done to demonstrate the procedure. In the WSC file JLMaths.wsc I have:

function DoToWSC (DO) {GetDO = DO} // ********************************************************* function DoDivision (TheDividend, TheDivisor) { GetDO.DOpus.Output ("Preparing to divide . . .") this.n = TheDividend // TheDividend this.d = TheDivisor // TheDivisor this.r = this.n % this.d if (this.r < 0) // The remainder should be positive. this.r = this.r + Math.abs (this.d) // This is the remainder this.q = Math.round ((this.n - this.r) / this.d) // This is the quotient. GetDO.DOpus.Output (this.n + " divided by " + this.d + " = " + this.q + " remainder " + this.r) } In the button script file, I have — and this can all be inside or outside function OnClick (ClickData) { . . . }

var JLMaths = new ActiveXObject ("JLMaths.wsc") JLMaths.DoToWSC (this) DivisionA = new JLMaths.DoDivision (35, 4) DivisionB = new JLMaths.DoDivision (-31, 7) The output to the log file is, as expected:
Preparing to divide . . .
35 divided by 4 = 8 remainder 3
Preparing to divide . . .
-31 divided by 7 = -5 remainder 4

Thanks also for your remarks about naming things. Having an "indeterminate" number of parameters is very useful, for example, in functions that search for things or replace them.


#32

Cool and congrats! o)
I'd rename "DoToWSC()" to "Init()" or something, but that's your very own decision of course. o)

I still wonder a bit, why you chose to create new objects for your division-calculation. A regular function, not using "this" and just "var n = TheDividend;" etc. would be totally sufficient here. You might be in exploration mode still, looking how things work out, but for simple function calls, this is not the recommended approach I'd say.
If you want your function to return the result and also the dividend and divisor, consider creating and returning objects on the fly and omit the "new" operator:

function Divide(a,b){ return { result: a/b, dividend:a, divisor:b }; } var res = Divide(10,2); DOpus.Output("Result is: " + res.result + " Divisor was: " + res.divisor); As long as whatever you create with "new" is not some complex object with multiple methods or properties, you should stick to plain functions - which can be combined in an object on the other hand, if they are related. o)

function StringHelper(){ this.Add = function(strA, strB){ return strA+strB; } this.Lower = function(strA){ return new String(strA).toLowerCase(); } //etc.. } var myStrHelper = new StringHelper(); var sentence = myStrHelper.Add("Brown foxes", " jump."); var sentenceLow = myStrHelper.Lower(sentence);It makes sense to create a new "StringHelper" object, as it provides multiple methods targeting a specifc purpose. It does not need "this" and mutliple instances though, so a more simple approach is thinkable:

var StringHelper = { Add : function(strA, strB){ return strA+strB; }, Lower : function(strA){ return new String(strA).toLowerCase(); } } var sentence = StringHelper.Add("Brown foxes", " jump."); var sentenceLow = StringHelper.Lower(sentence);Internally these probably end up being quite similar, but for the eye, anything that uses "this" gives the impression it serves as a protoype or object and can/should be created with "new ..". That said, this is my personal point of view. Don't know what others feel about it. o)


#33

The reasons that my "DoDivision" function is written with "this" and designed for "new" are:

  1. Yes, I was experimenting with object creation.

  2. Integer division has to output two numbers, the quotient and the remainder. For example, June has thirty days, which is 4 weeks, with 2 days remainder (and the rational quotient 4.2857... is irrelevant). I used "this" so that after setting
    Division7 = new DoDivision (30, 7)
    I could refer to the quotient 4 as "Division7.q" and the remainder 2 as "Division7.r". Your example 1 is an alternative approach.

  3. When I was working with dates, I had to juggle four different divisions at the same time — the year is divided by 400 and also by 4, various numbers are divided by 7, and parity checks need division by 2. Creating new "DoDivision" objects seemed more straightforward, because I could easily keep using the results and distinguish one from the other. Far more complicated division situations were involved when I was sorting out the HCF and LCM of two numbers a and b, and the integers x and y so that x * a + y * b = HCF.

Thanks, tbone, for your remarks and snippets of code. All three examples are new to me, and in particular, I wasn't aware that one could "return" a whole set of values so easily (example 1). Your second and third examples show that there is far more flexibility available than I was using — my code requires a new object to be created every time a division is done, rather than choosing each thime whether or not to create a new object. I keep learning.


#34