Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement an object with multiple constructors #340

Closed
LijieZhang1998 opened this issue Nov 19, 2021 · 6 comments
Closed

Implement an object with multiple constructors #340

LijieZhang1998 opened this issue Nov 19, 2021 · 6 comments
Labels

Comments

@LijieZhang1998
Copy link

LijieZhang1998 commented Nov 19, 2021

Hi,

I'm implementing Intl for i18n support. Intl is actually a namespace that has multiple constructors. Can I use goja to implement it? I tried the following code. I expected to print out true, but got false. If I remove new from new Intl.DateTimeFormat("haha"), then it's working. I want to have new before Intl.DateTimeFormat("haha") because that complies with specification.

func DateTimeFormat(date string) string {
	return date
}

type DateTimeFormatConstructor func(date string) string

type Intl struct {
	DateTimeFormat DateTimeFormatConstructor
}

func main(){
	vm := goja.New()
	var intl = Intl{
		DateTimeFormat: DateTimeFormat,
	}
	vm.Set("Intl", &intl)
	_, err := vm.RunString(`
			 function intlTest() {
				const result = new Intl.DateTimeFormat("haha");
				return result;
			};
	 `)
	if err != nil {
		panic(err)
	}

	var fn func() string
	err = vm.ExportTo(vm.Get("intlTest"), &fn)
	if err != nil {
		panic(err)
	}
	fmt.Println(fn()=="haha")
}
@dop251
Copy link
Owner

dop251 commented Nov 21, 2021

Hi.

To be more standard compliant, you need something like this:

	vm := New()
	intl := vm.NewObject()
	dateTimeFormat := vm.ToValue(func(call ConstructorCall) *Object {
		// initialise the instance (call.This) according to the arguments supplied to the constructor (call.Arguments)
		return nil
	})
	dateTimeProto := dateTimeFormat.(*Object).Get("prototype").(*Object)
	dateTimeProto.DefineDataProperty("format", vm.ToValue(func(call FunctionCall) Value {
		instance, _ := call.This.(*Object)
		// do the formatting using the instance parameters and the supplied arguments
		_ = instance
		result := vm.ToValue("passed")
		return result
	}), FLAG_TRUE, FLAG_TRUE, FLAG_FALSE)
	intl.DefineDataProperty("DateTimeFormat", dateTimeFormat, FLAG_TRUE, FLAG_TRUE, FLAG_FALSE)
	vm.GlobalObject().DefineDataProperty("Intl", intl, FLAG_TRUE, FLAG_TRUE, FLAG_FALSE)
	res, err := vm.RunString(`
		const formatter = new Intl.DateTimeFormat();
		formatter.format();
	`)

@dop251
Copy link
Owner

dop251 commented Nov 21, 2021

You can also use a Go type as an instance, like this:

	type formatter struct {

	}
	vm := New()
	intl := vm.NewObject()
	dateTimeFormat := vm.ToValue(func(call ConstructorCall) *Object {
		instance := &formatter{}
		// initialise the instance according to the arguments supplied to the constructor (call.Arguments)
                // instance.whatever = ....
		instanceValue := vm.ToValue(instance).(*Object)
		instanceValue.SetPrototype(call.This.Prototype())
		return instanceValue
	})
	dateTimeProto := dateTimeFormat.(*Object).Get("prototype").(*Object)
	dateTimeProto.DefineDataProperty("format", vm.ToValue(func(call FunctionCall) Value {
		instance, ok := call.This.Export().(*formatter)
		if !ok {
			panic(vm.NewTypeError("this should be a formatter"))
		}
                return vm.ToValue(formatter.format(....))
	}), FLAG_TRUE, FLAG_TRUE, FLAG_FALSE)

I'm going to add this to the documentation too.

@LijieZhang1998
Copy link
Author

LijieZhang1998 commented Nov 22, 2021

Awesome 🎉 This is very helpful. I have one more question about how I can get instance parameters inside the definition of "format" in the first solution. call.Arguments returns the arguments for format functionCall. How can I get instance parameters for dateTimeFormat? Sorry, i'm a newbie for goja. The second solution is working for me.

	vm := New()
	intl := vm.NewObject()
	dateTimeFormat := vm.ToValue(func(call ConstructorCall) *Object {
		// initialise the instance (call.This) according to the arguments supplied to the constructor (call.Arguments)
		return nil
	})
	dateTimeProto := dateTimeFormat.(*Object).Get("prototype").(*Object)
	dateTimeProto.DefineDataProperty("format", vm.ToValue(func(call FunctionCall) Value {
		instance, _ := call.This.(*Object)
		// do the formatting using the instance parameters and the supplied arguments
       	        _ = instance
		result := vm.ToValue("passed")
		return result
	}), FLAG_TRUE, FLAG_TRUE, FLAG_FALSE)
	intl.DefineDataProperty("DateTimeFormat", dateTimeFormat, FLAG_TRUE, FLAG_TRUE, FLAG_FALSE)
	vm.GlobalObject().DefineDataProperty("Intl", intl, FLAG_TRUE, FLAG_TRUE, FLAG_FALSE)
	res, err := vm.RunString(`
		const formatter = new Intl.DateTimeFormat();
		formatter.format();
	`)

@dop251
Copy link
Owner

dop251 commented Nov 22, 2021

You'd have to set them as properties of the instance (maybe Symbol properties to minimise the exposure). That's why the second solution is better.

@LijieZhang1998
Copy link
Author

Sounds good to me 👍 Thank you very much.

You'd have to set them as properties of the instance (maybe Symbol properties to minimise the exposure). That's why the second solution is better.

@dop251
Copy link
Owner

dop251 commented Nov 22, 2021

No worries!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants