# Model

Model 負責定義了資料的模樣,接下來的例子會開始說明 Model 的各種組合模式。

# Types

import Alas, { ContainerStructure, ContainerOptions, ModelStructure, ModelOptions, Loader } from 'alas'

// 定義 Attr Model

type AttrStructure = ModelStructure<{
    model: {
        age: number
    }
}>

const attrOptions: ModelOptions<AttrStructure> = {
    body: {
        age: []
    }
}

// 定義 Profile Model

type ProfileStructure = ModelStructure<{
    model: {
        name: string
        attr: AttrStructure['model']
        // 透過 $v 定義 views
        $v: Readonly<{
            age: number
        }>
        // 透過 $m 定義 methods
        $m: Readonly<{
            setAge: (age: number) => void
        }>
        // 透過 $o 定義 loader
        $o: Readonly<{
            fetch: Loader<void, {
                username: string
            }>
        }>
        // 透過 $init 控制 init 的物件型態
        $init: (params: { username: string }) => Structure['model']
    }
}>

const profileOptions: ModelOptions<ProfileStructure> = {
    body: {
        name: []
    },
    refs: {
        attr: 'attr'
    },
    init(self, { username }) {
        return {
            name: username
        }
    },
    views: {
        age: self => self.attr.age
    },
    methods: {
        setAge(self, age) {
            self.attr.age = age
        }
    },
    loaders: {
        async fetch(self, done, fail, { username }) {
            try {
                let data = await fetchUser(username)
                self.$init(data)
                done()
            } catch (error) {
                fail(error)
            }
        }
    }
}

// 將 Model 引入 Container

type UserContainerStructure = ContainerStructure<{
    models: {
        attr: AttrStructure
        profile: ProfileStructure
    }
}>

const userOptions: ContainerOptions<UserContainerStructure> = {
    models: {
        attr: attrOptions,
        profile: profileOptions
    }
}

// 將 Container 引入 Alas

type Containers = {
    user: UserContainerStructure
}

const alas = new Alas<Containers>({
    containers: {
        user: userOptions
    }
})

# Options

# body

定義 Model 的最基礎的屬性與其規則,建議由 String、Boolean、Number 等平面類型所組成,例如一個人的名稱與年齡。

  • type: { [key: string]: Array<string> }
  • required: false
const options = {
    body: {
        name: ['#ms.type|is:string'],
        age: ['#ms.type|is:number']
    }
}

警告

別宣告 Body 擁有 Function、Model 等屬性,原因在實體化 Model 時會執行 JSON 的序列化,因此過度複雜的方法會導致程式上的錯誤。

# defs

$init 執行後如果為 nullundefined 則使用相對應的 key 回傳的數值取代。

const options = {
    body: {
        name: ['#ms.type|is:string']
    },
    defs: {
        name: self => 'no name'
    }
}

# refs

定義某個屬性為相同 Container 底下的 Model、List、Dictionary。

  • type: { [key: string]: string }
  • required: false
const options = {
    refs: {
        user: 'user'
    }
}

只要將目標包裹[]就會轉換成 List,{}則為 Dictionary :

let user = {
    refs: {
       lAttributes: '[attributes]',
       dAttributes: '{attributes}'
    }
}

// ...

console.log(user.lattributes.size) // 0

# init

複寫原有的 $init 規則,初始規則是直接映射 key 的數值,而複寫可以使其擁有轉換不同鍵值的能力。

const options = {
    body: {
        name: ['#ms.type|is:string']
    },
    init(self, source = {}) {
        return {
            name: source.Name
        }
    }
}

# inited

觸發 $init 後再執行。

const options = {
    inited(self) {}
}

# export

複寫原有的 $export 規則,初始規則是直接映射 key 的數值,而複寫可以使其擁有轉換不同鍵值的能力。

const options = {
    body: {
        name: ['#ms.type|is:string']
    },
    export(self) {
        return {
            Name: self.name
        }
    }
}

# views

回傳經過計算的結果。

const options = {
    body: {
        name: ['#ms.type|is:string']
    },
    views: {
        name: self => self.name || '未命名'
    }
}

# defaultView

觸發的時機為 views key 沒被宣告或者是 view 對象返回值為 null or undefined

const options = {
    body: {
        name: ['#ms.type|is:string']
    },
    views: {
        name: self => self.name || 'no name'
    },
    defaultView(self, key) {
        return `${key} 未設定`
    }
}

# methods

專屬的方法,用途不一但可廣泛使用。

const options = {
    body: {
        name: ['#ms.type|is:string']
    },
    methods: {
        setName(self, name) {
            self.name = name
        }
    }
}

# loaders

基於 async 與加載狀態的管理模式。

const options = {
    loaders: {
        async fetch(self, done, fail, params) {
            try {
                let data = await getUser(params.username)
                self.$init(data)
                done()
            } catch (error) {
                fail(error)
            }
        }
    }
}

# self

self 並不是傳統的 private 屬性,而是規格外的屬性,在 self 定義的結構並沒有規則,執行的條件是在 $init 之後觸發:

const options = {
    // source 與 init 的對象是同一個
    self(self, source = {}) {
        return {
            name: source.Name
        }
    }
}

# errorMessage

回傳錯誤訊息的方法。

const options = {
    errorMessage(self, error) {
        return error ? error.meg || null
    }
}

# watch

可以在改變數值前觸發指定的方法。

注意

由於只限於附值時觸發,所以如果修改對象是多層級物件內的屬性時不會起作用。

const options = {
    body: {
        name: ['#ms.type|is:string']
    },
    watch: {
        name(self, value) {
            console.log('old name', self.name)
            console.log('new name', value)
        }
    }
}

# list

基於陣列結構的 Model,詳情可見 List 章節。

# dictionary

基於 Key:Value 結構的 Model,詳情可見 Dictionary 章節。

# Property

# $v

獲取 views 計算過的唯讀屬性。

  • type: { [key: views]: any }

# $m

獲取 methods 定義的泛用方法。

  • type: { [key: methods]: any }

# $o

獲取 loaders 定義的加載方法。

  • type: { [key: loaders]: Loader }

# $loader

更頂級的狀態管理。

  • type: LoaderCase

# $config

獲取所屬 Container 的 config

  • type: { [key: string]: any }

# $utils

返回 Utils 工具。

  • type: Utils

# $ready

是否執行過 $init

  • type: boolean

# $error

是否執行過 $setError, 如果執行過回傳 errorMessage 的結果。

  • type: any

# $parent

是否是被參照對象或是在 List、Dictionary 之中。

  • type: Model | List | Dictionary

警告

由於對象不一定務必小心操作。

# Methods

# $on

監聽一個事件。

model.$on = function(eventName: string, callback: EventCallback) => string
  • return: listener id

# $once

監聽一個事件,但觸發一次即結束。

model.$once = function(eventName: string, callback: EventCallback) => string
  • return: listener id

# $off

關閉指定 Id 的監聽對象。

model.$off = function(eventName: string, listenerId: string) => void

# $emit

發送一則事件。

model.$emit = function(eventName: string, ...params: any[]) => void

# $raw

返回現在存取的原始資料。

model.$raw = function() => any

# $meg

返回現在 alas 指定的語系文字訊息。

model.$meg = function(key: string, value?: { [key: string]: any }) => string

# $init

初始化 Model。

model.$init = function(data: any) => Model // 返回自己

# $copy

複製一份與當下狀態相同的 Model,注意 $self 的狀態並不會被複製。

model.$copy = function(options?: { save?: boolean }) => Model

# $body

複製並回傳當下 body 的狀態。

model.$body = function() => { [key: string]: any }

# $setAttr

批次更新常數屬性,意指更新屬性為 number | string | boolean | null | undefined 的值。

model.$setAttr = function(data: { [key: string]: any }) => void

# $keys

回傳 bodyrefs 的 key 值。

model.$keys = function() => string[]

# $reset

屬性資料回朔成宣告 $init 後或最後執行 $commit 的狀態。

model.$reset = function(key?: string) => void
  • key: 指定 key 重置,如沒指定則全部重置。

# $rules

回傳指定屬性驗證的方法組。

model.$rules = function(name: string) => Array<(value: any) => true | string>

# $commit

把 cache 資料轉換成現在的資料,將影響 $isChange$reset 兩種方法的結果。

model.$commit = function() => void

# $profile

將 Model 轉換並回傳成可視化的資料,由於 Model 是無法轉換成 JSON 的循環結構,因此可以使用本方法轉換成單純的物件,這是給予開發人員方便開發的功能。

model.$profile = function() => Object

# $export

回傳執行 export 的結果。

model.$export = function() => any

# $reload

重新替換 model 底層的所有資料,初始化所有 body 和 refs 的資料並再次觸發 init,重新呼叫 created 行為並發送 event。

警告

通常不建議使用 reload 來重置資料,但有時 ref 的對象需要重新賦予數值,請謹慎使用。

model.$reload = function() => void

# $isChange

當前數值是否與宣告 $init 時相同。

該方法在 options.save 宣告為 false 時無效。

model.$isChange = function(key?: string) => boolean
  • key: 指定某個屬性是否改變,如果省略則驗證全部。

# $validate

驗證目前 model 的數值是否符合 body 定義的規則。

model.$validate = function() => {
    success: boolean,
    result: {
        // 如果是屬性回傳驗證錯誤字串
        [key]: string | true,
        // 如果是 Ref 對象回傳 Validate 物件
        [key]: {
            success: boolean,
            result: object
        }
    }
}

# $generate

返回一個全新的 model,可以視為 core.make(thisModelContainer, thisModelName)

model.$generate = function(options?: { save?: boolean }) => Model

# $generateFrom

可以建立同一個 container 的 model、list、dictionary。

model.$generateFrom = function(target: string, options?: { save?: boolean }) => any

example

let model = model.$generateFrom('user')
let list = model.$generateFrom('[user]')
let dictionary = model.$generateFrom('{user}')

# $validateBy

驗證指定參數是否符合 body 定義的規則。

model.$validateBy = function(key: string) => true || string

# $setError

設定 Model 的錯誤狀態。

model.$setError = function(error: any) => void

# Events

# $error

執行 $setError 後觸發。

model.on('$error', (model: Model, context: { id: string }, error: any) => { ... })

# $ready

執行 $init 後觸發。

model.on('$ready', (model: Model, context: { id: string }) => { ... })