nikhilk / scriptsharp

Script# Project - a C# to JavaScript compiler, to power your HTML5 and Node.js web development.
http://scriptsharp.com
Other
660 stars 183 forks source link

Interop customization #10

Open erik-kallen opened 13 years ago

erik-kallen commented 13 years ago

It would be a really nice interop feature to allow the customization of interop through something like a [CustomInteropAttribute].

Problem Description

Dojo

For a Dojo Script# wrapper, I would want to be able to transform the C# code

widget.Disabled = true;
bool d = widget.Disabled;

to

widget.set('disabled', true)
var d = widget.get('disabled');

KnockoutJS

To use Script# with Knockout view models, a nice way to handle observables would be to transform

model.PropertyName = "PropertyValue";
string pn = model.PropertyName;

to

model.propertyName('PropertyValue');
var pn = model.propertyName();

JQuery

(This is not as important as the other ones since there is already a perfecly valid option, and one could argue that it is not even a good idea. However, I'm doing code generation and currently I have to handle JQuery events differently from all other events because of the different syntax, so it would definitely help me). JQuery event subscription could be made more .net-like by allowing the transform of

elem.Click += myClickHandler;
elem.Click -= myClickHandler;

to

elem.click(myClickHandler);
elem.unbind('click', myClickHandler);

Proposed feature

The [CustomInteropAttribute] could be used like this:

[Imported, Record]
public class MyKoJSViewModel {
    public string MyProperty {
        [CustomInterop("{this}.myProperty()")]
        get { return null; }
        [CustomInterop("{this}.myProperty({value})", Chainable = false)]
        set {}
    }

    [CustomInterop("{this}.myProperty.subscribe({value})")]
    public IDisposable OnMyPropertyChanged(Action<string> handler) {}
}

[Imported, Record]
public class MyDojoWidget {
    public string MyProperty {
        [CustomInterop("{this}.get('myProperty')")]
        get { return null; }
        [CustomInterop("{this}.set('myProperty', {value})", Chainable = false)]
        set {}
    }
}

The Chainable property would be required because the setter does not return a value, so

string x = object.MyProperty = "x"

would not work out of the box, and preferrably the compiler should either rewrite this, or issue an error.

Even better would be to be able to write a plugin for the code generation process so I could write a plugin that would enable me to say:

[Imported, Record]
public class MyKoJSViewModel {
    [KoJSObservable]
    public string MyProperty { get { return null; } set {} }

    [KoJSSubscription("MyProperty")]
    public IDisposable OnMyPropertyChanged(Action<string> handler) {}
}

[Imported]
public class MyDojoWidget {
    [DojoProperty]
    public string MyProperty { get { return null; } set {} }
}

or even

[Imported, Record, KoJSViewModel]
public class MyKoJSViewModel {
    public string MyProperty { get { return null; } set {} }
    public IDisposable OnMyPropertyChanged(Action<string> handler) {}
}
sagacity commented 13 years ago

The latter option (KoJSObservable / DojoProperty) would be very useful. The situation in which you would need to annotate every getter and setter with an interop attribute specifying the exact interop required (which can be fragile because you'd need to keep everything in sync) would be less desirable.

erik-kallen commented 13 years ago

@Roy: I agree, but if you are writing an import library, you might want to customize the usage of some really quirky property in that way. However, if writing a view model for KnockoutJS, I could not agree more.

sagacity commented 13 years ago

@erik-kallen: Certainly I agree with supporting both and I don't even have a preference for this specific syntax. But I definitely would love to be able to just mark a viewmodel as a KoJSViewModel and have Script# interpret properties and events in a certain way. Probably INotifyPropertyChanged needs to be shoehorned in as well :)

davidwengier commented 13 years ago

This sounds like a great idea! I have been struggling with writing mappings that will support Knockout, and have had to settle on GetValue and SetValue utility functions because I can't use the properties directly. Being able to finely tune the script output would be a godsend

ghost commented 13 years ago

I've partially written an Import Library for Knockout, and a code snippet to help with property definition. Here's a sample view model, that lets me program against it nicely:

public class MyViewModel
{
    protected Function firstName = Ko.Observable( "Paul" );
    public string FirstName { get { return (string)firstName.Call( this ); } set { firstName.Call( this, value ); } }

    protected Function lastName = Ko.Observable( "Hester" );
    public string LastName { get { return (string)lastName.Call( this ); } set { lastName.Call( this, value ); } }

    protected Function fullName;
    public string FullName { get { return (string)fullName.Call( this ); } set { fullName.Call( this, value ); } }
    protected void z_FullNameDependentObservable()
    {
        fullName = Ko.DependentObservable(
            delegate()
            {
                return this.FirstName + " " + this.LastName; 
            },
            this,
            new Dictionary( "write", new Action<string>( delegate( string value )
            {
                this.FirstName = value;
                this.LastName = "";
            } ) )
        );
    }

    public MyViewModel()
    {
        z_FullNameDependentObservable();
    }
}
ghost commented 13 years ago

I'm also about 1/8th through writing an import library for Dojo, if anyone is interested.