...because you can get much the same hardware spec for a generic PC (i.e. a Windows) PC for much less. That may be true, but as Neil said, there's more to the picture than meets the eye....
NB; this post is extended from a reply that I gave on another blog. It's a UK blog, so I've given UK-based examples. I'm no lover of Apple the company, by the way. I think their constant use of the courts to try and suppress competition from the likes of Google/Android is reprehensible. But I have to admit, they make great products. And buying Microsoft because you don't like Apple's control-freakery is like cosying up to Hitler because you're pissed off with Stalin.
OS We all have our opinions on this one.
There are those that maintain that Windows 7 is every bit the equal of Mac OS X, if not superior. But when you look at those claims, they're usually along the lines of look and feel. Sure, Windows 7 has got that pin the app on the Start Bar thingie, and it's got some kind of a jump list feature. Whoopee-do. They don't ever talk about the underlying architecture, because they don't understand that. In the same way that some people buy a car based on the colour, the shape of the headlights, the knobs and dials on the dashboard, but without ever checking out the engine! The truth is that Windows 7 got the good reviews that it did for one reason and one reason only: it wasn't Vista.
Take it from me, the underlying architecture of Windows just plain sucks and always has. Sure, the NT-based versions - NT, 2000, XP, Vista (yes, even Vista!) and 7 - have a more reliable and scalable architecture than the DOS-based versions - 3.0, 3.1, 95, 98 and Me - but that's not saying a right lot, is it?
Support Apple's support is legendary. You can take any Apple product into any AppleStore in the world, and they will try to fix it on the spot for no charge. You don't need to produce any receipt or proof of ownership for this initial examination; you're holding an Apple product and that's enough. Sure, if it needs real work doing on it, like a part replaced, then you do need to produce your receipt, or pay up if you're out of warranty. But they'll genuinely try to fix it for free first.
And these guys know their shit! I've lost count of the number of times I've been in a generic PC store such as PC World (UK) or Harvey Norman (Australia), and wondered: does this in-house "expert" really believe the horse shit that he's telling that nice couple, or is he so stupid that he believes it himself?
It's not really that bad, you say? Then you seriously need to look at Sky's 2009 investigation into computer repairs. And I hope you have a strong stomach. They investigated six London high-street computer repair shops and found that only one of them was honest. How about a £230 charge for a "new motherboard" when all they did was push back a dislodged memory chip on the old one? Or your personal photos copied and passed around by a bunch of perverts? Or attempted hack-ins of your bank account?
Now ask yourself, how much would you pay to avoid all that?
Ongoing Costs What's Windows 8 going to cost you when it arrives? As a guide, the upgrade version of Windows 7 Home Premium (from XP or Vista) is £85 on Amazon. And don't forget to multiply that by the number of Windows PCs in your possession if you want to upgrade them all. Sure, there are sometimes multi-upgrade packs available from Microsoft, but better catch 'em quick! They don't hang around long - they're normally available only for a few month's after a major release, and then they're gone. They're usually restricted to 3 PCs only too.
Mac OS X costs £21 on apple.co.uk and for that one purchase you can install it on as many Macs as you own.
Depreciation Your Mac will hold its value way better than any generic PC. Have a look on ebay.co.uk. I found nearly 5-year old Macbook Pros going for £400-500! You'd be lucky to give away a PC that old! Subtract that money from the cost of a new one when you want to upgrade, and suddenly it doesn't seem so expensive, does it? I have a Macbook Pro from January 2007 that's still going strong. And I reckon it's good for another three years yet!
It's taken several months, but there is now an untethered jailbreak for iOS 5 (actually, 5.0.1). For those of you not familiar with this scene, "untethered" means that your phone still works after a reboot. With a "tethered" jailbreak, you can only reboot your iPhone if you have it connected to a PC or Mac running the jailbreak software. (So a tethered jailbreak is essentially, useless, IMHO.)
The new jailbreak doesn't work with A5-based devices as yet, so sorry, no iPhone 4S or iPad 2 but they're working on it. I've just done my iPhone 4 and it was the smoothest experience that I've ever had doing this process..
In the beginning there was AJAX, XML and JSON. But AJAX has a major restriction: you can only retrieve data from the same server on which you own code resides. Enter JSONP.
JSONP is way of retrieving data from via an AJAX call, but from any server you like!
Here's the code for a jQuery call that will fetch the last 15 Tweets from twitter.com, based on a search query. You simply set the dataType to "jsonp" instead of "json", and then specify a callback function for your success property as per normal.
JSONP works by exploiting a loophole in the web browser's cross-domain scripting security: namely, that a script src="http://server" tag can set to retrieve source files from any server that you like. It seems that JSONP does some background jiggery-pokery to pull in the AJAX into a new script tag, as if it were new JavaScript code... except it's data! As ever though, jQuery hides all that overhead from you.
And here's a live example. Just enter your query into the field and hit the button to retrieve the tweets:
For my sins, I've been tinkering with Object-oriented JavaScript again.
While doing so, I came across an interesting constructor function in John Resig's book, Pro JavaScript Techniques. The function demonstrates a technique where a JavaScript function can create and return other functions, on the fly. In this case, Resig's function was creating setter and getter functions for any properties that being passed into the constructor via an object call, appropriately enough, "properties".
I had some problems understanding the function until it dawned on me that he's made a couple of mistakes in it; something that I had not expected from such a source; this is the guy that invented jQuery, after all. But a quick check of the book's reader reviews on Amazon confirmed that some of the example code is, indeed, buggy. Somebody actually posted "if you can debug the errors in his code examples then you don't need hid book!" Is that me now then, I wonder?
Anyway, here's Resig's function, heavily modified by me. My modifications are:
Changed the object's function from public to private. I.e., instead of being directly assigned to the object's "this", they are assigned to a private (i.e "var =") variable called privateProperties.
Changed the setter/getter creation code to check whether setter/getter function about to be created already exists or not. This us allows us to override with hard-code functions - see the getage() and setteam() functions.
Changed the setter/getter creation code to check in the parameter being checked is not a function itself; we only need setters and getters for properties.
I corrected the bug he used "this" within the for/loop function that creates the setters/getters. The scope of "this" as this point does not refer to the User function, as was intended. Due to function scope in JavaScript, it actually refers to the global object, i.e. the DOM window. You can see how I've used the "var that = this;" trick to fix this problem.
I corrected his use of the "i" variable in the "return" of the setter/getter functions created in the User function. The "i" variable is of a higher scope and so is not the correct property when the getter/setter is later called. I fixed this by capturing the "i" at the correct point by using the "currentProperty" variable. This is a closure.
function User( properties ) { // From Pro JavaScript Techniques, John Resig, p.37 // Iterate through the properties of the object, and make sure // that it's properly scoped
var that = this; var privateProperties = [];
this.getage = function () { return "the age = " + privateProperties["age"]; } this.setteam = function () { console.log('Sorry, you cannot set the team property'); };
for ( var i in properties ) { if(typeof properties[i] === "function") { that[i] = properties[i]; } else { privateProperties[i] = properties[i]; (function(){ var currentProperty = i; // Create a new getter for the property if (typeof that[ "get" + i] !== "function") { that[ "get" + i ] = function() { return privateProperties[currentProperty]; // can't use "i" here }; } // Create a new setter for the property if(typeof that["set" + i] !== "function") { that[ "set" + i ] = function(val) { privateProperties[currentProperty] = val; // can't use "i" here }; } })(); } } }
Here's a sample call that instantiates a new object, "user", using the function User as its constructor:
var user = new User({ name: "Bob", age: 44, team: "Wests Tigers", });
And here's the console output produced by the code above:
Bob the age = 44 West Tigers the age = 22 Sorry, you cannot set the team property Wests Tigers
Finally, here's how our test "user" object, created from the "User" function/constructor, looks in the JavaScript debugger. Notice all our freshly created set and get functions on the right hand side:
This is a follow-up to two previous posts, Class Hierarchies in LotusScript - let's loosen things up! and Multiple constructors for LotusScript objects. It illustrates techniques that I hinted that I was going to show on the latter post, plus another technique that I had absolutely no idea about at the time, but was pointed out to me by one of the commenters on the Class Hierarchies post (thank you again, Lars).
I've continued with my rather naff cars and roads examples from the "Class Hierarchies" post. Please note that these examples are merely to illustrate what you can do using the techniques. Whether you would find it useful to do things this or that way is entirely up to you.
The Object Constructor Technique This is what I hinted at when I said that it would be nice to able to "pass in a different number of parameters, and of different types and have my constructor handle them" in my Multiple constructors post. The solution is to pass in a object constructor that is created from an "initialiser" class. So now each of the two classes, RoadClass and CarClass has a corresponding initialser class, called RoadClassInitialiserClass and CarClassInitialiserClass, respectively. These initialiser classes are included, naturally enough, in the same libraries where their corresponding main classes reside. These new classes define the constructor object that will be passed to the two main classes' constructors.
Those constructors (i.e. Sub New) for the two main classes have been rewritten to detect and handle the parameters passed in by the initialiser class. This is best illustrated in CarClass. The detection of the correct constructor type is handled by the incredibly handy TypeName function which I'd not previously come across. If you pass TypeName() an object, whether it's a built-in LotusScript one or one you've created yourself, then it will return the name of the class that you used to create that object. (Look up TypeName in the Designer help to see how it handles other data types.)
If you follow the constuctor (Sub New) for CarClass below, you can see that I've added an extra complication: the CarInitialiserClass may contain a parameter that is actually RoadInitialiserClass. This means that RoadClasses has to be included in the CarClasses library via a Use "RoadClasses" call, right? And if we tried the same thing the other way around, we'd need a Use "CarClasses" call in the "RoadClasses" library, so setting up a circular reference and Designer errors all around.
I thought so too, but it turns out there's a way around this...
Dynamic Script Library Loading This technique, first introduced by Dean Garyet in an appendix to an IBM Redbook . Dynamic Script Library Loading (DSLL) gets around the latter problem. It allows you to create an object of class from a different library without having to include that class in your code, whether that be library, agent etc. It uses LotusScript's Execute function, which allows us to create and run LotusScript on the fly. This "on the fly" LotusScript is where we include the LotusScript library from whose class we want to "borrow". There's a still a Use statement here, but because it's created on the fly, it doesn't trip up the Domino Designer's syntax checker, and we're allowed to save it.
This magic is carried out in a function called newObj(), which I've included in a library called libGlobalClasses in my examples here. Naturally, any LotusScript code that wants to call newObj() must (and does) have libGlobalClasses included via a Use "libGlobalClasses" call. You might notice that I've slightly enhanced the newObj() function over Dean Garyet's original in that I've allowed for an extra parameter to be passed in. (In fact, you will have to pass in this extra parameter or Nothing). This allows us to use the DSLL technique and the Object Constructor technque together, which is what I have done in the modified Car/Road Classes example.
The second part of the technique is that each class that you want to "borrow" in this way must include a Factory class that includes a method called "produce". The produce method returns a new instances of the "main" class. By convention, this Factory class is named to be the same as the corresponding "main" class but with the word "Factory" appended to it; e.g. RoadClassFactory for RoadClass.
And here's the code.
libGlobalClasses This contains the all important newObj() function. It needs to be in the hierarchy (via a Use statement) of any LotusScript code that's going to use the DSLL technique.
Option Public Option Declare
Public newObjFactory As Variant ' used by newObj function in this library Private factories List As Variant ' used by newObj function in this library
Function newObj( scriptName As String, className As String, parameterObj As Variant ) As Variant ' Allows creation of objects for classes that are not included in that particular library. ' See also IBM Redbook "Performance Considerations for Domino Applications", Appendix B-2, ' "Dynamic Script Library Loading". This is available at: ' http://www.redbooks.ibm.com/abstracts/sg245602.html ' It works by using the Execute command to run arbitrary chunks of LotusScript at runtime. Each class ' must have corresponding Factory class that contains a produce() function. ' The function refers to two variables, which are declared in this library: ' * newObjFactory - a new factory object, created for the given className, in the give scriptName, ' and which will be passed the given parameterObj into the factory's produce() ' * Private factories - stores a list of of factory objects that have already been created. Each time the ' function is called, this list is interrogated and if found, the factory objec there is ' used rather than going through the unnecessary overhead of creating a new one.
If Not Iselement( factories( className ) ) Then Dim exString As String exString = | Use "| & scriptName & |" Sub Initialize Set newObjFactory = New | & className & |Factory End Sub | Execute (exString) Set factories( className ) newObjFactory End If Set NewObj = factories( className ).produce(parameterObj) End Function
CarClasses Look at the addRoad() for the call that uses the DSLL technique. Note the absence of a Use "RoadClasses" statement anywhere in this library.
Option Public Option Declare Use "libGlobalClasses" Public Class CarClass Private m_Name As String Private m_TopSpeedKMH As Integer Private m_Weight As String Private m_PermittedRoads() As Variant Private m_RoadObj As Variant
Sub new(parameterObj As Variant) If Lcase(Typename(parameterObj)) = "carclassinitialiserclass" Then If Lcase(Typename(parameterObj.Name)) <> "empty" Then Me.m_Name = parameterObj.Name End If
If Lcase(Typename(parameterObj.TopSpeedKMH)) <> "empty" Then Me.m_TopSpeedKMH = parameterObj.TopSpeedKMH End If
If Lcase(Typename(parameterObj.Weight)) <> "empty" Then Me.m_Weight = parameterObj.Weight End If
If Lcase(Typename(parameterObj.RoadInitObj)) = "roadclassinitialiserclass" Then Call Me.addRoad(parameterObj.RoadInitObj) End If
End If End Sub
Public Function addRoad(parameterObj As Variant) On Error Goto AddRoadErrorHandler Dim roadObj As Variant Dim arraySize As Integer
' Creates new roadObj using the Dynamic Script Library Loading technique. Note how RoadClasses is ' *not* included in this (i.e. CarClasses) script library via a Use statement. Set roadObj = newObj("RoadClasses", "RoadClass", parameterObj) arraySize = Ubound(Me.m_PermittedRoads) Redim Preserve Me.m_PermittedRoads(arraySize + 1) Set Me.m_PermittedRoads(arraySize + 1) = roadObj Exit Function
AddRoadErrorHandler: If Err = 200 Then ' Attempt to access unitialised array. No way to test for this in LS without incurring an error arraySize = -1 Resume Next Else Messagebox "CarClass.addRoad() Error" & Str(Err) & ": " & Error$ End If End Function
Public Property Get theName As String theName = Me.m_Name End Property
Public Property Get topSpeedKMH As Integer topSpeedKMH = Me.m_TopSpeedKMH End Property
Public Property Get Weight As String Weight = Me.m_Weight End Property
Public Property Get PermittedRoads As Variant PermittedRoads = Me.m_PermittedRoads End Property End Class
Public Class CarClassFactory Public Function produce(parameterObj As Variant) As Variant Set produce = New CarClass(parameterObj) End Function End Class
Public Class CarClassInitialiserClass Public Name As String ' should use set and get rather than public, but what the hell.... Public TopSpeedKMH As Integer Public Weight As String Public RoadInitObj As Variant
' no constructor End Class
RoadClasses
Option Public Option Declare Use "libGlobalClasses"
Public Class RoadClass Private m_Name As String Private m_Length As String Private m_ChargeType As String Private m_CarTypes() As Variant
Sub new(parameterObj As Variant) If Lcase(Typename(parameterObj)) = "roadclassinitialiserclass" Then Me.m_Name = parameterObj.Name Me.m_Length = parameterObj.Length Me.ChargeType = parameterObj.ChargeType End If End Sub
Public Property Get theName As String theName = Me.m_Name End Property
Public Property Get theLength As String theLength = Me.m_Length End Property
Public Property Get chargeType As String ChargeType = Me.m_ChargeType End Property
Public Property Set chargeType As String If chargeType <> "" Then Me.m_ChargeType = "Freeway" Else chargeType = chargeType End If End Property
End Class
Public Class RoadClassFactory Public Function produce(parameterObj As Variant) As Variant Set produce = New RoadClass(parameterObj) End Function End Class
Public Class RoadClassInitialiserClass Public Name As String ' should use set and get rather than public, but what the hell.... Public Length As String Public ChargeType As String
' no constructor End Class
Test Agent Finally, a simple agent to test the techniques described above. The image below shows what you should see in the Domino Designer's Debugger when you've run the agent,
Option Public Option Declare Use "RoadClasses" Use "CarClasses" Sub Initialize Dim myCarObj As CarClass ' In CarClasses library Dim roadInitObj As New RoadClassInitialiserClass() ' In RoadClasses library Dim carInitObj As New CarClassInitialiserClass() ' In CarClasses library
' Set up in the intialiser objects roadInitObj.Name = "M5" roadInitObj.ChargeType = "Tollway" roadInitObj.Length = "500" carInitObj.Name = "Jazz" carInitObj.TopSpeedKMH = 100 carInitObj.Weight = "5" Set carInitObj.RoadInitObj = roadInitObj
' Create the CarClassObject, passing in the CarClassInitialiserClass object, which has a RoadClassInitialiserClass ' ojbect as one of its members Set myCarObj = New CarClass(carInitObj)
' Add another road to the CarClass object via a modified RoadClassInitialiserClass object roadInitObj.Name= "M4" roadInitObj.Length="400" Call myCarObj.addRoad(roadInitObj)
Delete carInitObj Delete roadInitObj
Messagebox "Name of first pemitted road is " & myCarObj.PermittedRoads(0).theName
Java allows you to have multiple constructors for your classes, as long as each one has a different "signature", i.e, a different list of parameters and/or parameter types.
LotusScript allows you only one object constructor, which must be called Sub New. You can, however, begin to approximate Java's multi-constructor functionality by declaring a your Sub New's parameter as a variant. You can then then use the TypeName function to identify what kind of data type the variant really is and so call different construction code accordingly.
Below is an example class, ExtendedNotesNameClass, where I use this technique to extend LotusScript's built-in NotesName class. (I say "extend" although you can't extend the built-in classes in Domino; you can only wrap them, which is what I've done via the m_NN class variable.) I want to able to instantiate this class by passing in a Notes name as either a string or as a built-in NotesName LotusScript object. You can see how I've used TypeName to handle both eventualities.
(FYI, I created the class in order to house the AbbreviatedReally function, now a method, of course. What this method does is strip the @ part off if the address is a Notes-style address, but not if it's an internet-style address. The Abbreviated property of the built-in NotesName class leaves this on, e.g. Michael Brown/IT/Acme@acme. I want the "@acme" part to be stripped off in this case.)
'libExtendedNotesNameClasses:
Option Public Option Declare
Public Class ExtendedNotesNameClass ' Adds some extra stuff to the built-in NotesName class Private m_NN As NotesName
Sub New (parameterObj As Variant) Dim parameterType As String parameterType = Typename(parameterObj) Select Case Lcase(parameterType) Case "notesname": Set Me.m_NN = parameterObj Case "string" Set Me.m_NN = New NotesName(parameterObj) End Select
End Sub
Public Property Get NN As NotesName Set NN = Me.m_NN End Property
Public Property Get AbbreviatedReally As String ' Returns a portion of the address that's *really* abbreviated. That is, for a Notes-style address, we only ' want what's to the left of the @ sign, if there is one. If it's an internet-style address, then we want the ' whole thing, of course. If Me.m_NN.Organization <> "" Then ' If it has an Organization property then it's a Notes-style email address If Instr (Me.m_NN.Abbreviated, "@") > 0 Then AbbreviatedReally = Strleftback(Me.m_NN.Abbreviated, "@") Else AbbreviatedReally = Me.m_NN.Abbreviated End If Else ' an internet-style address is easier AbbreviatedReally = Me.m_NN.Abbreviated End If End Property
End Class
This only gets me so far, however. I am still restricted to passing in only one parameter to your constructor. Wouldn't it be nice if I could pass in a different number of parameters, and of different types and have my constructor handle them? Impossible in LotusScript? Watch this space!
This was tough work, but somebody had to do it. Lucky that me and Gem were up to the job! All beers are marked out of 10. This was typed out on my iPhone at the time, so please forgive a certain compression of style!
Granville Brewing Company,1441 Cartwright St, Vancouver, BC V6H 3Y5
Island Lager - Mike: 8.5, Gem: 8 Sweet, almost honey. Very drinkable. A session beer.
English Bay Pale Ale - Malty version of Island Lager. Mike: 8, Gem: 8.
Cypress Honey Lager- they didn't really over-do the honey. Subtle to the point of non-existence. Nice, but non-descript. Mike: 7, Gem: 6.
Kitsilano Maple Cream Ale - like a Cadbury's Caramel. Gem in love initially, but a bit too sweet. Not a session beer. Gem & Mike: 7
False Creek Raspberry Ale - not sweet. Reminiscent of the Raspberry Ale at the Sloaney Pony (the White Horse nr Parsons Green, Fulham). Gem & Mike: 7.5
Yaletown Brewing Co, 1116 Hamilton Street, Vancouver
I was driving, but Gem still managed a full rack of tasters mainly for herself! I restrained myself to the merest of sips. We were joined at our table by a friendly local by the name of Moosetapha. We liked him so much, we brought him home to Australia with us!
Lifty Lager - nice, not too strong. A quaffer. Mike: 7, Gem: 7.5.
Wheat Beer (special) - Need a knife and fork. How a wheat beer should be. Not too sweet. Gem: 7, Mike: 7.5
Big Wolf Bitter - session pint. Gem: 8, Mike: 7.
Alta Lake Ale - quite drinkable. M says not as strong taste as BWB. G says other way around. G prefers BWB. Gem: 6, Mike: 7.5.
Grizzly Brown Ale - M says lovely. Creamy, but not too sweet. Mike: 8.5, Gem: 7.
5 Ring IPA - too strong a hoppy taste. Too strong on alcohol too. Deffo gone off the IPAs Mike & Gem: 5.
Steamworks Brewing Co, 375 Water Street, Vancouver
View Larger Map Lions Gate Lager - ok but nondescript. Mike: 6, Gem 6.5
Wheat Beer (Cascadia Cream Ale) - Mike thinks bland but G likes. Lemony and fizzy without being sickly. Mike: 6.5, Gem: 7
Raspberry (Ltd edition) - Not sweet or sickly. Strong taste. Both like. Gem: 8, Mike: 7.5
Nut Brown Ale - too much for G who called it "treacle". M says it's a quaffer. Mike: 8, Gem: 4
Coal Porter (stoat) - Light in a dark kind of way, says G. Like a lighter version of Murphys says M. Mike: 8, Gem: 7
Signature Pale Ale - Quite sweet, but nice for us both. Not a session beer. Mike: 7, Gem: 7.
Empress IPA - Tastes slightly like vomit says G. Not pleasant says M. Gem: 4, Mike: 4
Canoe Brewpub Marina & Restaurant. 450 Swift Street, Victoria
View Larger Map Red Canoe Lager - like! Up with a Granville Island Lager. Not sweet. Deffo a quaffer Had pints after tasting. Gem: 8.5, Mike: 8
Honey Wheat Ale - Bit bland for M, but grew on him after a while. G says pleasant but watery. Gem: 6.5, Mike: 7
River Rock Bitter - More powerful. Strong taste. Almost like an IPA, but G likes. Gem: 7.5, Mike: 7
Siren's Song Pale Ale - Quite sweet for a pale ale says G. It's a pale ale says M. Mike: 6.5, Gem: 7
Beaver Brown Ale - Not too sweet, not too overpowering. Guinness without the after taste. Yummo! A bit like a meal though. Gem: 8, Mike; 9
Swans Suite Hotel, 506 Pandora Avenue, Victoria View Larger Map
This one had the the most English aley beers so far, and it wasn't afraid to server them at room temperature. Had some good, local bands on too! Stayed here way too late!
Arctic Ale - very nice, slightly sweet but not too sweet. Quaffable. Gem: 8, Mike: 8.5.
Pandora Pale Ale - Slightly sweet, not overpowering for a pale ale. Mike : 7.5, Gem: 7.5.
Buckerfield ESB - Strong taste but actually only 5%. Choclaty says G. Not a quaffer. Gem: 7.5, Mike 7.
Appleton Brown Ale - Sweet but nice. Even more choclaty than previous. Not a session beer. Gem: 7.5, Mike: 8.
Oatmeal Stout - More choclaty than previous, which was more choclaty than *its* previous, and so on... Gem: 7, Mike: 7.5
Raspberry Ale - Very nice. A clean, crisp raspberry ale says G, the pretentious moo. Not too sweet. Deffo a quaffing raspberry "because I'm about to have one" says G. Gem: 9, Mike: 8.
The traditional way to pass arguments/parameters to a function is by a list of individual elements, like so:
function doFruitColour(banana, apple, tangerine) { apple = apple || "green"; // These OR statements will set defaults for missing parameters banana = banana || "yellow"; tangerine = tangerine || "orange";
console.log(args); }
You would then call such a function like this:
doFruitColour("yellow", "red");
This call will result in the following: apple = red banana = yellow tangerine = orange
While this works fine for functions that take only two or three parameters, it quickly becomes unworkable for functions that take more arguments than that. Managing the list becomes a major problem in that parameters must be passed into the function in the correct order. Optional parameters may be dropped in JavasScript (but not LotusScript), but only if they follow the mandatory arguments in the list order. If you want to omit a parameter that appears before a parameter that you need to include, then you must replace the omitted parameter with some kind of null value, and then handle that situation within the function itself (as I have done with the || statements above). Note that in the example above, I was able to drop the tangerine parameter, but not the banana parameter, even though I was only passing in the default value anyway. If I'd supplied nothing at all for banana, then it would picked up the value for apple, and we would have ended up with a red banana!
A much better to way to pass in the parameters is to pass in an object instead. (In his book, Javascript: The Good Parts, Douglas Crockford calls this an "Object Specifier", although I've also seen it referred to as a "Configuration Object").
This time, I'll show the function call first:
doFruitColour({"apple":"red"});
As you can see, it's passing in a single object. Further more, that object contains only one parameter: the "apple" parameter. The other parameters have been omitted, and in this case, the order of the supplied and omitted is not important because in the function we refer to them by their name rather than by their position in the list. Here's the modified function:
My doFruitColour({"apple":"red"}) call will again give the results: apple = red banana = yellow tangerine = orange
Although this works, and is infinitely preferable to the first function, the way it handles setting up the defaults is still somewhat verbose. There's a lot of || statements to set up: one for each parameter, in fact. It would be nice to able to able to set up a single, default arguments object and then || the entire parameters object against tis defaults object, e.g:
function doFruitColourNew(args) { // Do not use. This doesn't work!! var argsDefault = { apple :"green", banana :"yellow", tangerine :"orange"}; args = args || argsDefault; }
where args and argsDefault are both objects. Sadly, this results in one object replacing the other object completely rather than working at the object's element level, as we would like. The end result of another call doFruitColour({"apple":"red"}) call would end up with the apple set to "red" and the other two parameters not initialised at all; the args parameter passed into the function take total precedence.
Fortunately, the jQuery.extend() function does exactly what we want, ie., it merges two objects at their elemental level. Here's a reworked function that used $.extend to merge the actual arguments and default argumetns which are now defined in an object of their own:
function doFruitColourNew(args) { var argsNew = $.extend({ apple :"green", banana :"yellow", tangerine :"orange" }, args) ;
console.log(argsNew); }
The $.extend() call merges the two objects at an element level, with the elements in second object listed (args in the example above) taking precedence over those in the first object. However, the elements in the first object are still returned where there are no corresponding elements in the second object.
Yet again, a doFruitColour({"apple":"red"}) call will again give the desired results: apple = red banana = yellow tangerine = orange
If you're not using jQuery (you poor, benighted soul!) then here's a native JavaScript function that would achieve the same result:
function mergeObjects(obj1, obj2) { // merges two objects, returning all objects in obj1 unless those same elements are also in obj2 // in which case, the elements in obj2 will take precedence. This is the equivalent of jQuery.merge(). obj1 = obj1 || {}; obj2 = obj2 || {}; returnObj = {};
for (var index in obj1) { returnObj[index] = obj2[index] || obj1[index]; } return returnObj; }
It loops through all the elements of the default arguments object, and fills out the actual arguments objects elements for any of the latter that are "missing". Here's a modified version of our example function that makes use of the processArgsWithDefaults() function:
function doFruitColourNew(args) { args = mergeObjects({ apple :"green", banana :"yellow", tangerine :"orange" }, args) ;
Coming back to Object-Oriented programming in LotusScript after a lengthy spell programming in only JavaScript was a bit of a shock: LotusScript isso restricted! Everything has to be declared and set up in rigid class hierarchies that can't be rejigged on the fly like you can in JavaScript (which has no real classes, of course). And yet, with the judicious use of LotusScript's Variant type, it's possible to free yourself from some of these more obvious restrictions.
Problem - Class Hierarchies One restriction that's always bugged me about LotusScript OO, is that the class libraries are a one-way door. Class Library A can access the classes in Class Library B, or Class Library B can access teh classes in Class Library A. But they can't both access each other's classes. To do so would set up an infinite loop of Use statements, which the Domino Designer Compiler does not allow.
Here's an example of this problem. I set up two classes in two separate LotusScript class libraries: The Class CarClass is in the CarClasses library and the Class RoadClass is in the RoadClasses library. Here's the code for the two classes:
' in CarClasses LotusScript libary Public Class CarClass Private m_Name As String Private m_TopSpeedKMH As Integer Private m_Weight As String
Sub new(n, k, w) Me.m_Name = n Me.m_TopSpeedKMH = k Me.m_Weight = w
End Sub
Public Property Get theName As String theName = Me.m_Name End Property
Public Property Get topSpeedKMH As Integer topSpeedKMH = Me.m_TopSpeedKMH End Property
Public Property Get Weight As String Weight = Me.m_Weight End Property
End Class
' in RoadClasses LotusScript library Public Class RoadClass Private m_Name As String Private m_Length As String Private m_ChargeType As String
Sub new(n, l, c) Me.m_Name = n Me.m_Length = l Me.ChargeType = c
End Sub
Public Property Get theName As String theName = Me.m_Name End Property
Public Property Get theLength As String theLength = Me.m_Length End Property
Public Property Get chargeType As String ChargeType = Me.m_ChargeType End Property
Public Property Set chargeType As String If chargeType <> "" Then Me.m_ChargeType = "Freeway" Else chargeType = chargeType End If End Property
End Class
And here's a test agent that creates object of both classes. Obviously, that code must (and does) include Use statements that name the two libraries.
' Options section Option Public Option Declare Use "RoadClasses" Use "CarClasses"
Sub Initialize Dim myCarObj As New CarClass("Honda Jazz", 120, 100) Dim myRoadObj As New RoadClass("M4", 500, "Tollway")
Messagebox "objects defined!"
End Sub
And here's how the objects look in the LotusScript Debugger when I've run my test agent:
So far, so easy. Now I want to extend CarClass to include some info about the roads on which it can travel. I do this by creating an array of RoadClass objects as a member of CarClass, remembering to add the line Use "RoadClasses" to the Options section of the CarClasses library, so that CarClass knows about RoadClass.
' In Options Use "RoadClasses"
Public Class CarClass Private m_Name As String Private m_TopSpeedKMH As Integer Private m_Weight As String Private m_PermittedRoads() As RoadClass
Sub new(n, k, w) Me.m_Name = n Me.m_TopSpeedKMH = k Me.m_Weight = w Redim Me.m_PermittedRoads(0) End Sub
Public Function addRoad(newRoad As RoadClass) Dim arraySize As Integer If Me.m_PermittedRoads(0) Is Nothing Then Set Me.m_PermittedRoads(0) = newRoad Else arraySize = Ubound(Me.m_PermittedRoads) Redim Preserve Me.m_PermittedRoads(arraySize + 1) Set Me.m_PermittedRoads(arraySize + 1) = newRoad
End If End Function
Public Property Get theName As String theName = Me.m_Name End Property
Public Property Get topSpeedKMH As Integer topSpeedKMH = Me.m_TopSpeedKMH End Property
Public Property Get Weight As String Weight = Me.m_Weight End Property
End Class
I then modify the test agent to pass some RoadClass objects into my CarClass object. Here's how the code and its results look in the Domino Debugger:
You can see how the m_PermittedRoads property of the myCarObj contains an array of RoadClass objects, as we intended. But what happens if we also want to do the reverse of this; i.e. I want my RoadClass object to have an array of CarClass objects, which are the types of car that are permitted to drive on my road?
Here's how I might try to set that up in CarClass class. You can see that I've added a Use statement to pull in the CarClasses library. I've then setup a class variable that is an array of CarClass objects.
' In Options Use "CarClasses"
Public Class RoadClass Private m_Name As String Private m_Length As String Private m_ChargeType As String Private m_CarTypes() As CarClass ....
And here is where it all falls down. The Domino Designer won't save this code; it will throw the error "RoadClasses (Options): 3: Error loading USE or USELSX module: CarClasses".
Why won't it load the "CarClasses"? It's because the "CarClasses" library is already loading the "RoadClasses" library via its Use statement. And as I said earlier, two LotusScript libraries cannot "load" each other. That would put us you an infinite loop where CarClasses uses RoadClasses, which uses CarClasses, which uses RoadClasses .... and round, and round we go. Not suprising then that the Designer throws a wobbler.
Solution One possible solution is to put both classes in the same library, but this leads to an inflexibility that might end up causing more problems than it solves.
My answer is to instantiate my class objects as type Variant, rather than as their actual class-types. The Designer does not do compile-time type checking for Variants, so we don't then need to have each library contain a Use statement that tries to load up the other. Only the calling code - our test agent in this case - needs to load up the two libraries. This allows us to eat our cake and still have it.
The code below shows the changes: the m_PermittedRoads class variable and the newRoad parameter of the addRoad function are both now defined as Variants. This means that we no longer have to include a Use "RoadClasses" line in the Options section of the library; we merely need have both libraries loaded in the calling agent, as we did previously. The RoadClass objects are declared and instantiated in the calling test agent, and then passed into CarClass's addRoad method as a Variant.
I've also added a PermittedRoads Get Property so that we can access the variable later on and test what it is and what it can do. Here's the modified CarClass library. Note that the problematic Use "RoadClass" line is gone as we no longer need it.
Public Class CarClass Private m_Name As String Private m_TopSpeedKMH As Integer Private m_Weight As String Private m_PermittedRoads() As Variant
Sub new(n, k, w) Me.m_Name = n Me.m_TopSpeedKMH = k Me.m_Weight = w Redim Me.m_PermittedRoads(0) End Sub
Public Function addRoad(newRoad As Variant) Dim arraySize As Integer If Me.m_PermittedRoads(0) Is Nothing Then Set Me.m_PermittedRoads(0) = newRoad Else arraySize = Ubound(Me.m_PermittedRoads ) Redim Preserve Me.m_PermittedRoads( arraySize + 1) Set Me.m_PermittedRoads(arraySize + 1) = newRoad End If End Function
Public Property Get theName As String theName = Me.m_Name End Property
Public Property Get topSpeedKMH As Integer topSpeedKMH = Me.m_TopSpeedKMH End Property
Public Property Get Weight As String Weight = Me.m_Weight End Property
Public Property Get PermittedRoads As Variant PermittedRoads = Me.m_PermittedRoads End Property
End Class
Here's the modified calling agent as it appears on the LotusScript Debugger. I've called the new PermittedRoads property of myCarObj tor return the first of the RoadClass objects that I passed into it on the agent's two previous lines. Although this object is returned as type Variant (as displayed in the Degugger), I can still access it's theName property, which proves that it's really a RoadClass object.
If you need to post to two or more social networks simultaneously, there's a Chrome/Chromium browser extension called Publish sync for google+ & facebook that will do this for you. When you post from any of those three in your web browser, you will see checkboxes that allow you to also post to one or more of the other two (actually three if you count Plurk, which I've never heard of!):
Now, if we could get a mobile equivalent of this...