-
Notifications
You must be signed in to change notification settings - Fork 0
/
view.js
159 lines (134 loc) · 4.12 KB
/
view.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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import reduce from "https://deno.land/x/lodash/reduce.js";
import Template from "./view/template.js";
/**
* A decorator for defining the `template` of a given view.
*/
export function template(name) {
return (target) => {
target.template = name;
};
}
/**
* View encapsulates the presentation code and template rendering for a
* given UI.
*/
export default class View {
/**
* Optional configuration for this View's template. This is the
* template file that will be rendered when the `render()` method is
* called on this View, out-of-box.
*
* @return string
*/
static template = null;
static get name() {
return `${this}`.split(" ")[1];
}
constructor(controller, context = {}) {
this.context = context;
this.controller = controller;
this.app = controller.app;
this.request = controller.request;
this.template = new Template(
this.constructor.template,
controller.format,
this,
);
this.urlFor = this.app.routes.resolve.bind(this.app.routes);
Object.entries(this.context).forEach((value, key) => (this[key] = value));
this.app.routes.forEach((route) => route.hydrate(this));
this.initialize();
}
/**
* Called when the `View` is instantiated, this method can be
* overridden in your class to provide additional setup functionality
* for the view. Views are instantiated when they are rendered.
*/
initialize() {}
cache(key, options = {}, fresh) {
return this.app.cache.fetch(key, options, fresh);
}
/**
* Render the given View's template as a partial within this View,
* using this View as context. You can also pass in other context as
* the last argument to this method.
*/
async partial(View, context = {}) {
const view = new View(this, { ...this.context, ...context });
const result = await view.toHTML();
this.app.log.info(`Rendering partial ${view.template.path}`);
return result.toString();
}
/**
* Render this View's template as a String of HTML.
*
* @return string
*/
toHTML() {
return this.template.partial(this);
}
/**
* Render this View's template as the response to a Controller
* request. This method can be overridden to return a String of HTML
* or JSX, rather than use the configured template.
*
* @return string
*/
render() {
return this.template.render(this);
}
/**
* Render a hash of options as HTML attributes.
*/
htmlAttributes(options = {}, prefix = null) {
return reduce(
options,
(value, option, memo) => {
if (typeof value === "object") {
value = this.htmlAttributes(value, option);
}
if (prefix) {
option = `${prefix}-${option}`;
}
return `${memo} ${option}="${value}"`;
},
"",
);
}
/**
* Render a `<form>` tag and hidden fields to verify authenticity
* token and make PATCH/PUT/DELETE requests.
*/
formTag({ action, method, ...options }) {
const attributes = this.htmlAttributes(options);
const token = this.app.authenticityToken;
if (method === "GET" && method === "POST") {
return `<form action="${action}" method="${method}" ${attributes}>`;
}
return `<form action="${action}" method="POST" ${attributes}>
<input type="hidden" name="_method" value="${method}" />
<input type="hidden" name="authenticity_token" value="${token}" />
`;
}
formFor({ model = null, action = null, method = null, ...options }) {
action = model ? this.urlFor(model) : action;
method = method || (model && model.persisted ? "PATCH" : "POST");
return this.formTag({ action, method, ...options });
}
/**
* Render an `<a>` tag pointing to a specific route. You can use
* `urlFor` syntax or a route helper in the `href` argument.
*/
linkTo(text, href, options) {
const attributes = this.htmlAttributes(options);
const url = this.urlFor(href);
return `<a href="${url}" ${attributes}>${text}</a>`;
}
get csrfMetaTag() {
const token = this.app.authenticityToken;
return `<meta name="authenticity_token" content="${token}" />`;
}
get endFormTag() {
return "</form>";
}
}