-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmodel.js
145 lines (122 loc) · 3.6 KB
/
model.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
const joi = require('joi')
const path = require('path')
const schema = require('./schema')
const Page = require('./page')
const Parser = require('expr-eval').Parser
class Model {
constructor (def, options) {
const result = joi.validate(def, schema, { abortEarly: false })
if (result.error) {
throw result.error
}
// Make a clone of the shallow copy returned
// by joi so as not to change the source data.
def = JSON.parse(JSON.stringify(result.value))
// Add default lists
def.lists.push({
name: '__yesNo',
title: 'Yes/No',
type: 'boolean',
items: [
{
text: 'Yes',
value: true
},
{
text: 'No',
value: false
}
]
})
this.def = def
this.lists = def.lists
this.sections = def.sections
this.options = options
const { getState, mergeState } = options
this.getState = getState
this.mergeState = mergeState
if (options.defaultPageController) {
const defaultPageControllerPath = path.resolve(options.relativeTo, options.defaultPageController)
this.DefaultPageController = require(defaultPageControllerPath)
}
this.conditions = {}
def.conditions.forEach(conditionDef => {
const condition = this.makeCondition(conditionDef)
this.conditions[condition.name] = condition
})
// this.expressions = {}
// def.expressions.forEach(expressionDef => {
// const expression = this.makeExpression(expressionDef)
// this.expressions[expression.name] = expression
// })
this.pages = def.pages.map(pageDef => this.makePage(pageDef))
}
makeSchema (state) {
// Build the entire model schema
// from the individual pages/sections
let schema = joi.object().required()
;[undefined].concat(this.sections).forEach(section => {
const sectionPages = this.pages.filter(page => page.section === section)
if (section) {
let sectionSchema = joi.object().required()
sectionPages.forEach(sectionPage => {
sectionSchema = sectionSchema.concat(sectionPage.stateSchema)
})
schema = schema.append({
[section.name]: sectionSchema
})
} else {
sectionPages.forEach(sectionPage => {
schema = schema.concat(sectionPage.stateSchema)
})
}
})
return schema
}
makePage (pageDef) {
if (pageDef.controller) {
const pageControllerPath = path.resolve(this.options.relativeTo, pageDef.controller)
const PageController = require(pageControllerPath)
return new PageController(this, pageDef)
} else if (this.DefaultPageController) {
const DefaultPageController = this.DefaultPageController
return new DefaultPageController(this, pageDef)
} else {
return new Page(this, pageDef)
}
}
makeCondition (condition) {
const parser = new Parser()
const { name, value } = condition
const expr = parser.parse(value)
const fn = (value) => {
const ctx = new EvaluationContext(this.conditions, value)
try {
const isValid = expr.evaluate(ctx)
return isValid
} catch (err) {
return false
}
}
return {
name,
value,
expr,
fn
}
}
get conditionOptions () { return { allowUnknown: true, presence: 'required' } }
}
class EvaluationContext {
constructor (conditions, value) {
Object.assign(this, value)
for (let key in conditions) {
Object.defineProperty(this, key, {
get () {
return conditions[key].fn(value)
}
})
}
}
}
module.exports = Model