Wednesday, October 6, 2010

Code snippet - Array.splice according to ECMA

I've really missed the proper Array.splice in SSJS. To avoid writing a loop for every time I want to do a "splice operation", I've made a function that should work according to spec.

// $splice -> Array.splice according to ECMA standards
function $splice( array, startIndex, numItems ){
try {
var endIndex = startIndex + numItems;
var itemsBeforeSplice = [], splicedItems = [], itemsAfterSplice = [];
for( var i = 0; i < array.length; i++ ){
if( i < startIndex ){ itemsBeforeSplice.push( array[i] ); }
if( i >= startIndex && i < endIndex ){ splicedItems.push( array[i] ); }
if( i >= endIndex ){ itemsAfterSplice.push( array[i] ); }
}

// Insert all arguments/parameters after numItems
for( i = 3; i < arguments.length; i ++ ){
itemsBeforeSplice.push( arguments[ ''+i ] );
}

// Combine before/after arrays
var remainingItems = itemsBeforeSplice.concat( itemsAfterSplice );

// Rewrite array. Arrays can't be overwritten directly in SSJS
for( i = 0, len=Math.max( array.length, remainingItems.length ); i < len; i++ ){
if( remainingItems.length > i ){
array[i] = remainingItems[i];
} else {
array.pop();
}
}

return splicedItems;
} catch(e){ /*Debug.logException( e );*/ }
}
Note! If you want to do a splice on a scoped field that's an array, you first have to copy it, or your script might crash. E.g.
viewScope.put( 'someArray', [1,2,3,4] ); 
...
var array = viewScope.someArray.concat(); // concat copies the array
$splice( array, 1, 2, 12, 22 ); // modifies the array, returns the removed items [2,3]
viewScope.put( 'someArray', array );// viewScope.someArray -> [1,12,22,4]
I really hope IBM fixes the SSJS API/makes it follow ECMA-262 Edition 3 (or newer editions), but only time will tell.

7 comments:

Anonymous said...

"I really hope IBM fixes the SSJS API/makes it follow ECMA-262 Edition 3 (or newer editions)"

Yes. Please don't Lotusscript the Javascript.

Sjef Bosman said...

I based this code on yours. Add this to a library and you'll have a working splice() !

if(!Array.prototype.splice) {
Array.prototype.splice= function (startIndex, numItems ){
try {
var endIndex = startIndex + numItems;
var itemsBeforeSplice = [], splicedItems = [], itemsAfterSplice = [];
for( var i = 0; i < this.length; i++ ){
if( i < startIndex )
itemsBeforeSplice.push( this[i] );
if( i >= startIndex && i < endIndex )
splicedItems.push( this[i] );
if( i >= endIndex )
itemsAfterSplice.push( this[i] );
}

// Insert all arguments/parameters after numItems
for( i = 3; i < arguments.length; i ++ )
itemsBeforeSplice.push( arguments[ ''+i ] );
// Combine before/after arrays
var remainingItems = itemsBeforeSplice.concat( itemsAfterSplice );
// Rewrite array. Arrays can't be overwritten directly in SSJS
for( i = 0, len=Math.max( this.length, remainingItems.length ); i < len; i++ ) {
if( remainingItems.length > i )
this[i] = remainingItems[i];
else
this.pop();
}

return splicedItems;
} catch(e){ /*Debug.logException( e );*/ }
}
}

Tommy Valand said...

The reason I don't overwrite Array.splice or any other native methods is to avoid confusion. If a developer works on an application with a "fixed" splice, then works on an application with an IBM splice or vice versa, they might think that something is wrong with domino on the app that splice doesn't work correctly.

Also, if there's a bug in my code (I've tested it according to the spec, but you never know), and IBM fixes Array.splice, the developers that inherits my code wouldn't get the benefit of the fixed code.

If IBM fixes the API, the native Array.splice will most certainly be faster than a SSJS version as they will implement it in Java.

Sjef Bosman said...

I understand your reasons, and I had hoped that the first line would prevent the problems you mention, i.e. the line reading

if(!Array.prototype.splice) {

The code seems ok...

Tommy Valand said...

Array.prototype.splice is already implemented by IBM in XPages, so the method will not be overwritten. Not sure if this is what you wanted?

Regarding not yet implemented methods to native objects. I'm not quite sure where I stand.

I've created apps with JS libraries that extend native objects, and have had developers getting confused by code working in one app, and not another (they were not aware of which methods were native/extended).

It's nice to have, but the time one saves in typing might be miniscule compared to the time future developers spend trying to understand why something doesn't work in one app/page, but not another.

Johann Paul Echavarria Zapata said...

Thank you for share it. It's amazing that Array.prototype.splice() doesn't works in Domino 8.5.3. ¿Do you know if it works in Domino 9? I'd love full JS in XPages SSJS. I'm using a collection of polyfills for normal JS methods in SSJS: I just added $splice()

Johann Paul Echavarria Zapata said...

This is my collection of SSJS Polyfills for XPages: https://gist.github.com/katio/08bf3f5e058b950cd957