aurelia / templating-binding

An implementation of the templating engine's Binding Language abstraction which uses a pluggable command syntax.
MIT License
32 stars 26 forks source link

TypeScript enums #88

Closed RomkeVdMeulen closed 8 years ago

RomkeVdMeulen commented 8 years ago

I'm looking at using a TypeScript enum for error states, like this:

export enum ERRORS {
  INVALID
}

export class Mycomponent {
  public error = ERRORS.INVALID;
}

This compiles to something like:

define(["require", "exports"], function (require, exports) {
    "use strict";
    (function (ERRORS) {
        ERRORS[ERRORS["INVALID"] = 0] = "INVALID";
    })(exports.ERRORS || (exports.ERRORS = {}));
    var ERRORS = exports.ERRORS;
    var Mycomponent = (function () {
        function Mycomponent() {
            this.error = ERRORS.INVALID;
        }
        return Mycomponent;
    }());
    exports.Mycomponent = Mycomponent;
});

I tried simply doing <p if.bind="error = ERRORS.INVALID">Invalid!</p> but of course the enum isn't automatically available in the binding. Is there something I can do to make the enum available for comparisons?

EisenbergEffect commented 8 years ago

There is, if you have the latest. We recently added a way to extend the binding context using view engine hooks. Here's an example that shows how to add an array of countries.

import {viewEngineHooks} from './view-engine-hooks';

let countries = [
  ...list of contries...
];

@viewEngineHooks()
export class CountryBinder {
  beforeBind(view) {
    view.overrideContext.countries = countries;
  }
}

This plugs into the beforeBind hook and adds the countries to the overrideContext of the view that this data is required into. To make this work, you also need to implement a resource decorator:

import {resource} from 'aurelia-framework';

export function viewEngineHooks() {
  return resource({
    initialize(container, target) {
      this.target = target;
    },
    load() {},
    register(registry, name) {
      registry.registerViewEngineHooks(new this.target());
    }
  });
}

We will have the viewEngineHooks decorator in the framework itself in a future release. For now, you can just add it to your project. Once this is in place, to use the countries, you simply require the countries module in your view:

<template>
  <require from="./data/countries"></require>

  <ul>
    <li repeat.for="country of countries">${country}</li>
  </ul>
</template>

For your scenario, you might want to create a single module that simply adds all your enums onto the overrideContext.

davismj commented 8 years ago

Rather than creating our own decorator, is it possible to register ViewEngineHooks with a naming convention?

export class EnumViewEngineHooks {
  beforeBind(view) {
    view.overrideContext.countries = countries;
  }
}
EisenbergEffect commented 8 years ago

Not currently...but if you want to submit a PR for that, it seems like a reasonable addition :) Code would be added in the ModuleAnalyzer. You will see the other similar code there.

davismj commented 8 years ago

That's a good idea, I'll look into that now.

Edit: Just needed to require it in the view.

However, I couldn't get the above code to work. Code for reference. Anything seem wrong here?

export enum ConnectorDirection {
    Input,
    Output,
    TwoWay
}

@viewEngineHooks()
export class ConnectorDirectionBinder {
    beforeBind(view) {
        view.overrideContext.ConnectorDirection = ConnectorDirection;
    }
}

export function viewEngineHooks() {
    return resource({
        initialize(container, target) {
            this.target = target;
        },
        load() { },
        register(registry, name) {
            registry.registerViewEngineHooks(new this.target());
        }
    });
}
EisenbergEffect commented 8 years ago

My guess is that it has something to do with the way that TypeScript handles enums

davismj commented 8 years ago

Pending https://github.com/aurelia/binding/pull/423, you can now achieve this behavior using either of the following:

import { ViewEngineHooks } from 'aurelia-framework';
import { viewEngineHooks } from 'aurelia-binding';

// by convention
export class CountryViewEngineHooks implements ViewEngineHooks {
  beforeBind(view) {
    view.overrideContext.countries = countries;
  }
}

// by decoration
@viewEngineHooks()
export class CountryBinder implements ViewEngineHooks {
  beforeBind(view) {
    view.overrideContext.countries = countries;
  }
}
larvanitis commented 7 years ago

Sorry for the necrobump of a closed issue but in case somebody else sees this, you can simply expose the enum from your view model like this:

In your ViewModel:

import {ERRORS} from 'some/module';
// or...
export enum ERRORS {
  INVALID
}

export class MyViewModel {
  private ERRORS = ERRORS;
  private error: ERRORS = ERRORS.INVALID;
  // ...
}

In your template:

<template>
    <p if.bind="error === ERRORS.INVALID">Invalid!</p>
    <!--The OP had an error in his example. Comparison requires `==` or `===`-->
</template>
niemyjski commented 7 years ago

@larvanitis I tried this but couldn't get it to work and no errors are thrown.