JavaScript: external interfaces vs. objects for options

Hi,

I keep tripping up when trying to construct an options-object for passing to a javascript-library.
I want to use classes rather than external interface because they are less verbose and typesafe in construction.
The problem is they often don’t work, because their prototype doesn’t descend from normal new Object() and therefore has no methods like hasOwnProperty.

Any thought on this?

This works

 val options = (js("new Object()") as Any).apply {
    asDynamic().theme = "test"
 }

26

This doesn’t work

class QOption(val theme: String)
val options2 = QOptions(
    theme = "test"
)

44

1 Like

Wrong. There’s no hasOwnProperty in JavaScript objects. It’s a property of Object (not instances!). I don’t know why they don’t work in your particular case. Where are you going to pass options? What about classes, declared in other languages, like ES6 or TypeScript, do they work normally in your use case?

I’m using the Quill editor. It seems like it is doing something funky with the options-object.

I’m getting

Uncaught TypeError: Cannot convert undefined or null to object
    at Function.keys (<anonymous>)

The code where the error occurs:

    var _this = _possibleConstructorReturn(this, 
        (Keyboard.__proto__ || Object.getPrototypeOf(Keyboard))
            .call(this, quill, options)
    );

    _this.bindings = {};

    /* CRASHES */
    Object.keys(_this.options.bindings).forEach(function (name) {
       ...
    });

. I don’t want to waste to much time on this though, I will just use interfaces…

Responding to an old thread, but just in case others trip over this.
I believe, the main problem is that Kotlin generates js code that always initializes all fields of a class. You can initialize with null and unitialized, but you can never leave a field unassigned.
Many js libraries on the other hand, will only test if a value is assigned and will then try to cast it (without checking for null), especially if libraries that iterate over options by keys.

I use the following solution:

import kotlin.reflect.KClass
import kotlin.reflect.KProperty

/**
 * Support for strongly typed creation of dynamic JS objects.
 *
 * Usage:
 * object MyOptions: DynamicType {
 *     val foo by number
 *     val bar by string
 *     val baz by obj(Other::class)
 *     }
 * external fun something(options: Dynamic<MyOptions>)
 * fun test() {
 *     something(MyOptions.create {
 *         it[foo] = 1
 *     })
 * }
 */
interface DynamicType

class PropertyType<S: DynamicType, T> {
    inline operator fun getValue(obj: Any, property: KProperty<*>) = Property<S, T>(property.name)
}

inline class Property<S: DynamicType, T>(val name: String)

val <S: DynamicType> S.any get() = PropertyType<S, Any>()
val <S: DynamicType> S.number get() = PropertyType<S, Number>()
val <S: DynamicType> S.boolean get() = PropertyType<S, Boolean>()
val <S: DynamicType> S.string get() = PropertyType<S, String>()
fun <S: DynamicType, T: Any> S.obj(type: KClass<T>) = PropertyType<S, T>()
fun <S: DynamicType, T: Any> S.array(type: KClass<T>) = PropertyType<S, Array<T>>()

inline fun <S:DynamicType> S.create(initializer: S.(Dynamic<S>) -> Unit)
        = Dynamic<S>().also { initializer(it) }

class Dynamic<S: DynamicType> {
    operator fun <T> set(key: Property<S, T>, value: T) {
        val obj: dynamic = this
        obj[key.name] = value
    }
}