Kotlin-JS interop - using language constructs - kotlin

I have a js interop function which is using the for in construct to iterate over the input elements, but it's throwing error at runtime.
native("document")
val ndoc: dynamic = noImpl
fun jsInterOp() {
js("console.log('JS inling from kotlin')")
val ies = ndoc.getElementsByTagName("input")
for (e in ies) {
console.log("Input element ID: ${e.id}")
}
}
Getting the following js error
Uncaught TypeError: r.iterator is not a functionKotlin.defineRootPackage.kotlin.Kotlin.definePackage.js.Kotlin.definePackage.iterator_s8jyvl$ # kotlin.js:2538
Any suggestions on how to fix this one?
Kotlin : M12
The generated js code for the function is,
jsInterOp: function () {
var tmp$0;
console.log('JS inling from kotlin');
var ies = document.getElementsByTagName('input');
tmp$0 = Kotlin.modules['stdlib'].kotlin.js.iterator_s8jyvl$(ies);
while (tmp$0.hasNext()) {
var e = tmp$0.next();
console.log('Input element ID: ' + e.id);
}
},

forEach didn't work because it's an Array function in JS, but getElementsByTagName returns HTMLCollection . So i changed the kotlin code to use the traditional for loop which iterate over this collection and is work as expected.
val ies = ndoc.getElementsByTagName("input")
for (i in 0..(ies.length as Int) - 1) {
console.log("InputElement-${i} : ${ies[i].id}")
}

Kotlin for-loop uses a lot of internal magic.forEach() is more straightforward on JS. Try this:
ies.iterator().forEach { ... }
It seems to be a bug in Kotlin M12, because I was unable to do a for-loop even on simple a list.
for(i in listOf(1, 2)); // TranslationInternalException
Also
I am not sure what is that document that you use here, but you may like the standard API:
import kotlin.browser.document
val ies = document.getElementsByTagName("input")

Related

Kotlin - assign value inside while to use it outside

I'm very new in kotlin and wanted to solve following problem with a do while:
I want to create a hash and want to check if there is the same hash stored in a key-value store as a key.
In java I would make it with a String variable which I declared outside the while. But that will only work with a var in Kotlin and I learned that it is common practise to avoid var.
My code looks as following (with var...)
var hash = ""
do {
hash = createHash(longUrl)
val optional = shortUrlRepository.findById(hash)
} while(optional.isPresent)
What would you say is the best way to solve this?
thank you a lot!
Maybe something like this?
val hash = generateSequence { createHash(longUrl) }
.first { !shortUrlRepository.findById(it).isPresent }
... and of course, you can always localize var and pass it outside as val.
val someVal = run {
var someVar: String = ""
// do super logic with var
someVar
}
...

Passing Multiple Mime Types to ActivityResultLauncher.launch()

I have following code
val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
//Some code here..
}
and somewhere else ,
getContent.launch("application/vnd.openxmlformats-officedocument.wordprocessingml.document")
I can successfully select the docx file . I need to select either pdf or doc or text or docx rather just to be able to select one kind(here docx).
I would recommend using OpenDocument instead of GetContent.
val documentPick =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
// do something
}
While launching the intent just add the mime types you want to get
documentPick.launch(
arrayOf(
"application/pdf",
"application/msword",
"application/ms-doc",
"application/doc",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"text/plain"
)
)
Using array doesn't work in my case. Instead, the following worked correctly.
Here custom class of ActivityResultContracts.GetContent is used. fun "createIntent" is overrided to customize method to make intent from the input.
// custom class of GetContent: input string has multiple mime types divided by ";"
// Here multiple mime type are divided and stored in array to pass to putExtra.
// super.createIntent creates ordinary intent, then add the extra.
class GetContentWithMultiFilter:ActivityResultContracts.GetContent() {
override fun createIntent(context:Context, input:String):Intent {
val inputArray = input.split(";").toTypedArray()
val myIntent = super.createIntent(context, "*/*")
myIntent.putExtra(Intent.EXTRA_MIME_TYPES, inputArray)
return myIntent
}
}
// define ActivityResultLauncher to launch : using custom GetContent
val getContent=registerForActivityResult(GetContentWithMultiFilter()){uri ->
... // do something
}
// launch it
// multiple mime types are separated by ";".
val inputString="audio/mpeg;audio/x-wav;audio/wav"
getContent.launch(inputString)

Javascript code not working after upgrade to karate 0.9.3

The below function in feature files was working for version 0.9.2. Upgraded to 0.9.3 and this gives error : javascript function call failed: Index: 0.0, Size: 0. Code below:
var cnd = ['test1','test2'];
function set_filter(arg)
{
var i;
var filter = {filterValues:[]};
for(i=0;i<arg.length;i++)
{
filter.filterValues[i] = arg[i];
}
return filter;
}
set_filter(cnd)
Also i was earlier able to push values in a javascript array using below, but this has also stopped working in 0.9.3. Get error:javascript function call failed: TypeError: arr.push is not a function
var arr = [];
arr.push('test1','test2');
Try the scenario below that works in 0.9.2 but reports error (mentioned above) in 0.9.3
Scenario: JS test
* def filter_template =
"""
function() {
var filter_params = {
filterValues:[]
};
return filter_params;
}
"""
* def template = call filter_template
* def filter_condition = ['test1','test2']
* def setFilter =
"""
function(arg) {
var i;
var filter = arg.filter_template;
for(i=0;i<arg.condition.length;i++)
{
filter.filterValues[i] = arg.condition[i];
}
return filter;
}
"""
* def getFilter = call setFilter { filter_template: '#(template)', condition: '#(filter_condition)' }
* print getFilter
Help is much appreciated.
We've made the JS conversions stricter, everything will be a Java collection behind the scenes. If you make this change, things will start working:
filter.filterValues.set(i, arg.condition.get(i));
The same goes for push() - use add() or karate.appendTo(varname, value) instead.
My strong recommendation is don't use too much of JS logic especially loops. Karate has functions such as map(), forEach() and repeat() to solve for these needs. Please refer the docs here: https://github.com/intuit/karate#loops
You will thank me later because it will make your scripts easier to understand and maintain. One reason why this is needed is to pave the way for us to change the JS engine in the future.

how to add Array index value in Kotlin?

first, I create empty Array(Kotlin) instance in companion object.
companion object {
var strarray: Array<String> = arrayOf()
var objectarray: LinkedHashMap<Int, List<Any>> = LinkedHashMap<Int, List<Any>>()
}
and I expected that I use empty array instance when read textString from CSV File.
fun csvFileToString():String {
val inputStream = File(Paths.get("").toAbsolutePath().toString()
.plus("/src/main/SampleCSVFile_2kb.csv")).inputStream()
val reader = inputStream.bufferedReader()
var iterator = reader.lineSequence().iterator()
var index:Int = 1;
while (iterator.hasNext()){
var lineText:String = iterator.next()
strarray.set(index, lineText)
index++
}
return ""
}
but when I run that source code
a.csvFileToString()
println(CsvParser.strarray)
occured exception
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
strarray.set(index, lineText) <<<<<<<<< because of this line
can I use Array(from kotlin collection) like ArrayList(from java collection)?
You can add a new item to an array using +=, for example: item += item
private var songs: Array<String> = arrayOf()
fun add(input: String) {
songs += input
}
Size of Array is defined at its creation and cannot be modified - in your example it equals 0.
If you want to create Array with dynamic size you should use ArrayList.
arrayOf gives you an array. Arrays have fixed length even in Java.
listOf gives you an immutable list. You cannot add or remove items in this list.
What you're looking for is mutableListOf<String>.
In your current approach, reusing a member property, don't forget to clear the list before every use.
Your code can be further simplified (and improved) like so:
out.clear()
inputStream.bufferedReader().use { reader -> // Use takes care of closing reader.
val lines = reader.lineSequence()
out.addAll(lines) // MutableList can add all from sequence.
}
Now imagine you wanted to consume the output list but needed to parse another file at the same time.
Consider working towards a pure function (no side effects, for now no accessing member properties) and simplifying it even further:
fun csvFileToString(): String { // Now method returns something useful.
val inputStream = File(Paths.get("").toAbsolutePath().toString()
.plus("/src/main/SampleCSVFile_2kb.csv")).inputStream()
inputStream.bufferedReader().use {
return it.lineSequence().joinToString("\n")
}
}
In this case we can totally skip the lists and arrays and just read the text:
inputStream.bufferedReader().use {
return it.readText()
}
I'm assuming that's what you wanted in the first place.
Kotlin has a lot of useful extension functions built-in. Look for them first.

Passing a JSON object to worklight java adapter

I would like to pass a complete JSON object to a java adapter in worklight. This adapter will call multiple other remote resources to fulfill the request. I would like to pass the json structure instead of listing out all of the parameters for a number of reasons. Invoking the worklight procedure works well. I pass the following as the parameter:
{ "parm1": 1, "parm2" : "hello" }
Which the tool is fine with. When it calls my java code, I see a object type of JSObjectConverter$1 being passed. In java debug, I can see the values in the object, but I do not see any documentation on how to do this. If memory serves me, the $1 says that it is an anonymous inner class that is being passed. Is there a better way to pass a json object/structure in adapters?
Lets assume you have this in adapter code
function test(){
var jsonObject = { "param1": 1, "param2" : "hello" };
var param2value = com.mycode.MyClass.parseJsonObject(jsonObject);
return {
result: param2value
};
}
Doesn't really matter where you're getting jsonObject from, it may come as a param from client. Worklight uses Rhino JS engine, therefore com.mycode.MyClass.parseJsonObject() function will get jsonObject as a org.mozilla.javascript.NativeObject. You can easily get obj properties like this
package com.mycode;
import org.mozilla.javascript.NativeObject;
public class MyClass {
public static String parseJsonObject(NativeObject obj){
String param2 = (String) NativeObject.getProperty(obj, "param2");
return param2;
}
}
To better explain what I'm doing here, I wanted to be able to pass a javascript object into an adapter and have it return an updated javascript object. Looks like there are two ways. The first it what I answered above a few days ago with serializing and unserializing the javascript object. The other is using the ScriptableObject class. What I wanted in the end was to use the adapter framework as described to pass in the javascript object. In doing so, this is what the Adapter JS-impl code looks like:
function add2(a) {
return {
result: com.ibm.us.roberso.Calculator.add2(a)
};
The javascript code in the client application calling the above adapter. Note that I have a function to test passing the javascript object as a parameter to the adapter framework. See the invocationData.parameters below:
function runAdapterCode2() {
// x+y=z
var jsonObject = { "x": 1, "y" : 2, "z" : "?" };
var invocationData = {
adapter : "CalculatorAdapter",
procedure : 'add2',
parameters : [jsonObject]
};
var options = {
onSuccess : success2,
onFailure : failure,
invocationContext : { 'action' : 'add2 test' }
};
WL.Client.invokeProcedure(invocationData, options);
}
In runAdapterCode2(), the javascript object is passed as you would pass any parameter into the adapter. When worklight tries to execute the java method it will look for a method signature of either taking an Object or ScriptableObject (not a NativeObject). I used the java reflection api to verify the class and hierarchy being passed in. Using the static methods on ScriptableObject you can query and modify the value in the object. At the end of the method, you can have it return a Scriptable object. Doing this will give you a javascript object back in the invocationResults.result field. Below is the java code supporting this. Please note that a good chunk of the code is there as part of the investigation on what object type is really being passed. At the bottom of the method are the few lines really needed to work with the javascript object.
#SuppressWarnings({ "unused", "rawtypes" })
public static ScriptableObject add2(ScriptableObject obj) {
// code to determine object class being passed in and its heirarchy
String result = "";
Class objClass = obj.getClass();
result = "objClass = " + objClass.getName() + "\r\n";
result += "implements=";
Class[] interfaces = objClass.getInterfaces();
for (Class classInterface : interfaces) {
result += " " + classInterface.getName() ;
}
result += "\r\nsuperclasses=";
Class superClass = objClass.getSuperclass();
while(superClass != null) {
result += " " + superClass.getName();
superClass = superClass.getSuperclass();
}
// actual code working with the javascript object
String a = (String) ScriptableObject.getProperty((ScriptableObject)obj, "z");
ScriptableObject.putProperty((ScriptableObject)obj, "z", new Long(3));
return obj;
}
Note that for javascript object, a numeric value is a Long and not int. Strings are still Strings.
Summary
There are two ways to pass in a javascript object that I've found so far.
Convert to a string in javascript, pass string to java, and have it reconstitute into a JSONObject.
Pass the javascript object and use the ScriptableObject classes to manipulate on the java side.