Typescript library based on vue.js, typescript and some utility classes


License
ISC
Install
npm install @plastique/core@1.6.34

Documentation

Component instance

Vue.js

Functional approach

Vue.component('PassEditor', {
  data: function () {
    return {
      login: '',
      password: ''
    }
  },
  methods: {
    save: function () {
      //...
    }
  }
  template: 
    `<section>
      <header>{{login}}</header>
      <div>
        <label>Login</label>
        <input v-model="password">
      </div>
      <button v-on:click="save">Save</button>
    </section>
    `
})

Plastique

Object-oriented approach

@Reactive(function(this: PassEditor){
`<section xmlns:v="http://github.com/codeplastique/plastique">
  <header v:text="${this.login}"></header>
  <div>
    <label>Login</label>
    <input v:model="${this.password}">
  </div>
  <button v:onclick="${this.save}">Save</button>
</section>
`})
class PassEditor{
  private password: string;
  constructor(private readonly login: string){
  }
  
  public save(): void{
    // ...
  }
}

Component inheritance

@Reactive(function(this: A){
`<section xmlns:v="http://github.com/codeplastique/plastique">
  <div v:text="${this.getText()}"></div>
</section>
`})
class A{
  public getText(): string{
    return 'text from A'
  }
}

///template automatically expands from A
@Reactive
class B extends A{
  public getText(): string{
    return 'text from B'
  }
}

///Overwrites the А template and getText()
@Reactive(function(this: C){
`<section xmlns:v="http://github.com/codeplastique/plastique">
  <div v:text="${this.getText()}"></div>
  <div v:text="${this.getSecondText()}"></div>
</section>
`})
class C extends A{
  //override
  public getText(): string{
    return supet.getText() +' and text from C'
  }
  public getSecondText(): string{
    return 'Second text from C'
  }
}

///Wrap the А template and overwrite getText()
@Reactive(function(this: D){
`<section xmlns:v="http://github.com/codeplastique/plastique">
  <div class="wrap-block">
    <v:parent/>
  </div>
</section>
`})
class D extends A{
  //override
  public getText(): string{
    return supet.getText() +' and text from D'
  }
}

Nested components

@Reactive(function(this: Popup){
`<div xmlns:v="http://github.com/codeplastique/plastique">
  <div v:text="${this.welcomeText}"></div>
</div>
`})
class Popup{
  private welcomeText: string;
  constructor(welcomeText: string){
    this.setWelcomeText(welcomeText);
  }
  
  puclic setWelcomeText(welcomeText: string): void{
    this.welcomeText = welcomeText.toUpperCase();
  }
}

@Reactive(function(this: Page){
`<div xmlns:v="http://github.com/codeplastique/plastique">
  <button v:onclick="${this.showPopup}">Show popup</button>
  
  <div v:if="${this.popup != null}" class="popup-block">
  
    <!-- DIV or any tag name, not even real -->
    <div v:component="${this.popup}"></div>
    
    <button v:onclick="${this.changePopupText}">Change popup text</button>
    <button v:onclick="${this.closePopup}">Close popup</button>
  </div>
</div>
`})
class Page{
  private popup: Popup
  private showPopup(): void{
    this.popup = new Popup('Welcome!');
  }
  
  private changePopupText(): void{
    this.popup.setWelcomeText('Welcome text is changed!')
  }
  
  private closePopup(): void{
    this.popup = null;
  }
}

Template inheritance

import Reactive from "@plastique/core/component/Reactive";

@Reactive(function(this: A){
`<div xmlns:v="http://github.com/codeplastique/plastique">
  <div>Template of A object</div>
  <div v:text="${this.getNumber()}"></div>
  <div v:text="${this.getName()}"></div>
</div>
`})
abstract class A{
  protected getNumber(): number{
    return 1;
  }
  
  abstract protected getName(): string;
}

// Template of an A class will be inherited
class B extends A{
  // override 
  protected getNumber(): number{
    return 2;
  }
  
  protected getName(): string{
    return 'B name';
  }
}

// Override the class A template
@Reactive(function(this: C){
`<div xmlns:v="http://github.com/codeplastique/plastique">
  <span>Template of C object</span>
  <span v:text="${this.getCount()}"></span>
  <span v:text="${this.getName()}"></span>
</div>
`})
class C extends A{
  // override 
  protected getNumber(): number{
    return 3;
  }
  
  protected getName(): string{
    return 'C name';
  }
}

Events

@Reactive(function(this: Popup){
`<div xmlns:v="http://github.com/codeplastique/plastique">
  <div v:text="${this.welcomeText}"></div>
  
  <button v:onclick="${this.requestNewText}">Change welcome text</button>
  <button v:onclick="${this.close}">Close popup</button>
</div>
`})
class Popup{
  @InitEvent public static readonly CLOSE_EVENT: AppEvent<string>;
  @InitEvent public static readonly REQUEST_WELCOME_TEXT_EVENT: AppEvent<void>;
    
  private welcomeText: string;
  constructor(welcomeText: string){
    this.welcomeText = welcomeText;
  }
  
  public requestNewText(): void{
    this.fireEventOnParents(Popup.REQUEST_WELCOME_TEXT_EVENT)
        .then(newWelcomeText => this.welcomeText = newWelcomeText);
  }
  
  public close(): void{
    this.fireEventOnParents(Popup.CLOSE_EVENT, 'event argument');
  }
}
interface Popup extends Component{}

@Reactive(function(this: Page){
`<div xmlns:v="http://github.com/codeplastique/plastique">
  <button v:onclick="${this.showPopup}">Show popup</button>
 
  <div v:component="${this.popup}" v:if="${this.popup != null}"></div>
</div>
`})
class Page{
  private popup: Popup
  private showPopup(): void{
    this.popup = new Popup('Welcome!');
  }
  
  @Listener(Popup.REQUEST_WELCOME_TEXT_EVENT)
  private generateWelcomeText(): Promise<string>{
    ///...
    
    return Promise.resolve('Changed welcome text!');
  }
  
  @Listener(Popup.CLOSE_EVENT)
  private closePopup(arg: string): void{
    this.popup = null;
    console.log(arg)
  }
}

Built-in dependency injection

import Bean from "@plastique/core/base/Bean";
import Scope from "@plastique/core/base/Scope";

//...
@Bean
private getFirstInstance(): AnyClassOrInterface{
  return new AnyClassOrInterface();
}

@Bean
@Scope('PROTOTYPE') //SINGLETON by default
private getSecondInstance(): PrototypeClass{
  return new PrototypeClass();
}
//...
import Autowired from "@plastique/core/base/Autowired";

class A{
  @Autowired 
  private readonly AnyClassOrInterface first;
  @Autowired 
  private readonly PrototypeClass second;
  
  //...
}

Runtime interface implementation check

interface A{}

interface B extends A{}

class C implements A, B{}
import Type from "@plastique/core/base/Type";
import Types from "@plastique/core/base/Types";

//...
  let c = new C();
  console.log(c instanceof Type<A>()); //true
  let bType = Type<B>();
  console.log(c instanceof bType); //true
  console.log(Types.isObject(c)); //true
  console.log(Types.is(c, btype)); //true
  
  let bType2 = Type<B>();
  console.log(bType === bType2); //true
  console.log(bType === Type<A>()); //false

//...

Built-in reaction collections

import ReactiveMap from "@plastique/core/collection/ReactiveMap";
import SimpleMap from "@plastique/core/collection/SimpleMap";
import SimpleSet from "@plastique/core/collection/SimpleSet";

let mapCollection: ReactiveMap = SimpleMap.of(
  'key1', 'value1',
  'key2', 'value2',
  'key3', 'value1'
);
console.log(mapCollection.keys().join(',')); // key1,key2,key3

let setCollection = SimpleSet.of(...mapCollection.values());
setCollection.add('value3');
console.log(setCollection.values().join(',')); // value1,value2,value3

Progressive enum realisation

import Enum from "@plastique/core/enum/Enum";
import Enumerable from "@plastique/core/enum/Enumerable";

@Enum
class Color extends Enumerable{
  public static readonly RED = new Color('ff0000');
  public static readonly BLUE = new Color('0000ff');
  public static readonly BLACK = new Color('000000');
  
  private constructor(private readonly hex: string){}
  
  public getHex(): string{
    return '#'+ this.hex;
  }
}
console.log(Color.RED.name()); // RED
console.log(Color.BLACK.getHex()); // #000000
console.log(Color.values().length); // 3
console.log(Color.valueOf('BLUE').getHex()); // #0000ff

Powerful JSON ORM

import ToJson from "@plastique/core/hash/ToJson";
import JsonMerge from "@plastique/core/hash/JsonMerge";

class SymbolEditor {
  @ToJson 
  private a: string = 'a';
  @ToJson('b') 
  private uglyB: string = 'b';
  private c: string = 'c';
  private d: string = 'd';
  @ToJson
  private numbers: NumberEditor = new NumberEditor();
  @JsonMerge
  private punctuation: PunctuationMarksEditor = new PunctuationMarksEditor();
  
  @ToJson
  public getCd(): string{
    return this.c + this.d;
  }
}

class NumberEditor{
  private negative: number = -1;
  @ToJson 
  private one: number = 1;
  @ToJson 
  private two: number = 2;
}

class PunctuationMarksEditor{
  @ToJson 
  private dot: string = '.';
  @ToJson 
  private comma: string = ',';
}
import Serializator from "@plastique/core/hash/Serializator";

let editor = new SymbolEditor();
let json = new Serializator().toJson(editor);
console.log(json); 
/*
{
  "a": "a",
  "b": "b",
  "numbers": {
    "one": 1,
    "two": 2
  },
  "dot": ".",
  "comma": ",",
  "cd": "cd"
}
*/

Vue analogues

References

Vue refs
<base-input ref="usernameInput"></base-input>
methods: {
  any: function () {
    this.$refs.usernameInput
  }
}
Plastique refs
<base-input v:ref="${this.usernameInput}"></base-input>
import Inject from "@plastique/core/component/Inject";
...
@Inject
private usernameInput: HTMLElement;
...
public any() {
  this.usernameInput
}

Slots

Vue slots
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <!-- default slot -->
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
...
<base-layout>
  <template v-slot:header>
    <h1>Title</h1>
  </template>

  <p>For default slot</p>
  <p>For default slot</p>

  <template v-slot:footer>
    <p>Footer</p>
  </template>
</base-layout>
Plastique slots
<div xmlns:v="http://github.com/codeplastique/plastique" class="container">
  <header>
    <v:slot v:name.header></v:slot>
    <!-- or -->
    <!-- <v:slot v:name="'header'"></v:slot> -->
  </header>
  <main>
    <!-- default slot -->
    <v:slot></v:slot>
  </main>
  <footer>
    <v:slot v:name="${this.footerSlotName}"></v:slot>
    <!-- where footerSlotName is 'footer' -->
  </footer>
</div>
...
<div xmlns:v="http://github.com/codeplastique/plastique" v:component="...">
  <!-- DIV or any tag name, not even real -->
  <div v:slot.header>
    <!-- or -->
    <!-- <div v:slot="'header'"> -->
    ...
  </div>
  <any-tag-name v:slot>
    <!-- default slot -->
    ...
  </any-tag-name>
  <any-tag-name v:slot="${this.footerSlotName}">
    <!-- where footerSlotName is 'footer' -->
    ...
  </any-tag-name>
</div>