You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ViewModel pattern implementation with simple and effective form builder.
3
+
ViewModel pattern implementation with simple and effective form builder and view helpers.
4
4
5
5
## Installation
6
6
@@ -18,138 +18,176 @@ It uses `kilt` for template rendering so you also need to add template engine yo
18
18
19
19
### ViewModel
20
20
21
-
Putting page rendering into action class (like in kemalyst) could provide to creating fat helpers (like in rails) or putting a lot of view logic into itself or template. So much more suitable alternative will be have separate class which knows how to render corresponding object for some case (action or part of a page) and includes all representation logic.
21
+
Putting page rendering into action class ends with having fat helpers (like in rails) or putting a lot of view logic inside of templates. Also lack of native reusability in kilt makes you to define local variables with right name to be able to reuse them in a partials. Therefor much more suitable alternative is to have a separate class which encapsulates specific logic for a corresponding view. For such purpose this shard is created.
22
22
23
23
To do that load ViewModel
24
+
24
25
```crystal
25
26
require "view_model"
26
27
require "kilt/slang" # or any other template engine supported by kilt
27
28
```
28
29
29
-
Adds `views` folder under your `src` where all classes and templates will be defined. Also root location could be customized:
30
+
Create a base view class:
30
31
31
32
```crystal
32
-
ViewModel::Config.view_path "spec/fixtures"
33
+
class ApplicationView < ViewModel::Base
34
+
end
33
35
```
34
36
35
-
Here is example of folders structure:
37
+
By default layout path is `"src/views/layouts/layout.slang"` but this can be easily redefined by `.layout` macro:
36
38
39
+
```crystal
40
+
class ApplicationView < ViewModel::Base
41
+
layout "app/views/layouts/layout"
42
+
end
37
43
```
38
-
src
39
-
├── views
40
-
│ ├── comment
41
-
│ │ ├── index.slang
42
-
│ │ ├── show.slang
43
-
│ ├── post
44
-
│ │ ├── index.slang
45
-
│ ├── comment_view.cr
46
-
│ ├── post_view.cr
44
+
45
+
> Pay special attention - layout path doesn't include file extension.
46
+
47
+
If you'd like to render your view without a layout - pass `false` as an argument.
48
+
49
+
Next define specified layout:
50
+
51
+
```slang
52
+
html
53
+
head
54
+
title Page title
55
+
body
56
+
- yield_content
47
57
```
48
58
49
-
All view models are named like `<model name>_view.cr`. Here is example of such classes:
59
+
`yield_content`macro is just a alias for `yield(__kilt_io__)` - it yields `IO` to view `#content` method which renders content.
60
+
61
+
Now we can specify a view class.
50
62
51
63
```crystal
52
-
class CommentView < ViewModel::Base
53
-
def_template index
54
-
def_template show
55
-
end
64
+
# src/views/posts/show_view.cr
65
+
module Posts
66
+
class ShowView < ViewModel::Base
67
+
model post : Post
56
68
57
-
class PostView < ViewModel::Base
58
-
template_path "spec/fixtures"
59
-
getter model : Post
60
-
61
-
delegate some_method, to: model
62
-
63
-
def_template index do
64
-
template + "123"
69
+
delegate :title, :content, to: post
65
70
end
66
71
end
67
72
```
68
73
69
-
To create object of view-model you should pass object being rendered to constructor
74
+
`.model`macro creates getter for given attributes and generates constructor accepting them.
70
75
71
-
```crystal
72
-
c = Comment.new
73
-
CommentView.new(c)
76
+
Content for a post object:
77
+
78
+
```slang
79
+
.header
80
+
h3 = title
81
+
.content
82
+
= content
74
83
```
75
-
By default view-model will suppose that it will get object of class named same as it but without "View" part. This could be override adding to class definition direct declaration of `getter model : Post`. `model` - getter which give access to passed object. Also template location path for particular class could be redefined inside of it.
76
84
77
-
To declare actions that could be rendered use `def_template` macros which accepts name of action and optional block. Template for action will be loaded from file named after action located inside of the folder named after it at the same level.
85
+
By a convention this template file should be located in `<view_class_folder>/<view_class_name_without_view/content.slang>`, in our case it will be `src/views/posts/show/content.slang`.
78
86
79
-
Given block could access the rendered template via `template` local variable.
87
+
For a view rendering `.view` macro can be used - just pass view name and required arguments:
80
88
81
-
Render action of such view-model anywhere is easy - just add:
82
89
```crystal
83
-
c = Comment.new
84
-
view("comment", :index, c)
90
+
view("posts/show", post)
91
+
# or for a collection
92
+
collection_view("posts/show", posts)
85
93
```
86
94
87
-
`view` macros accepts 3 arguments: view-model name (without view part), action and object to represent.
95
+
### Partials
88
96
89
-
To render collection use
97
+
If you would like to define some shared templates or separate your view into several partials use `.def_partial` macro:
90
98
91
99
```crystal
92
-
c = [Comment.new, Comment.new]
93
-
collection_view(:comment, :index, c)
100
+
module SharedPartials
101
+
include ViewModel::Helpers
102
+
103
+
def_partial button, color
104
+
end
105
+
106
+
module Posts
107
+
class ShowView < ApplicationView
108
+
include SharedPartials
109
+
110
+
def_partial body
111
+
end
112
+
end
94
113
```
95
114
96
-
It will render each given object and concatenate all results.
115
+
If you need to define a module with partials - include `ViewModel::Helpers` module into it. `.def_partial` accepts partial name as a first argument and partial arguments as all others. All partial template paths are calculated same was as for content files of view objects. The only difference is that partial files name has a `_` symbol prefix: `src/views/shared_partials/_button.slang`.
97
116
98
-
### Html helpers
117
+
To render a partial use `.render_partial` macro:
99
118
100
-
Also this shard provided html generating helper methods for some tags. They look like in the rails `action_view` but because of crystal template rendering mechanism works in slightly in another way.
119
+
```slang
120
+
.buttons
121
+
- render_partial :button, :read
122
+
```
101
123
102
-
There are two modules:
103
-
- `Helers`- includes all methods to generate tags and form;
104
-
- `Macrosses`- includes macrosses to simplify calling helper methods with string builder inside of view (useful inside of templates.
124
+
### Html helpers
105
125
106
-
All helper methods have 2 variant: using given string builder to append content and just returning string. First one are much more effective so prefer to use it.
126
+
Also this shard provides HTML helper methods. All methods are automatically included in `ViewModel::Base`.
107
127
108
128
Methods description:
109
-
- `form_tag`- yields `FormBuilder` to generate flexible forms; append `_method` hidden input if given method is not `get` or `post`
110
-
- `content_tag`- builds given tag; could accept block
111
-
- `link_to`- builds `a` tag
129
+
130
+
- `content_tag`- builds given tag with given options; could accept block for nested content
131
+
- `link_to`- builds link
112
132
- `label_tag`- builds `label`
113
-
- `select_tag`- builds `select` tag; automatically generates `option` for given array
114
-
- `text_area_tag`- builds `text_are`
133
+
- `select_tag`- builds `select` tag; automatically generates `option` tags for given array
134
+
- `text_area_tag`
115
135
- `hidden_tag`
116
136
- `text_tag`
117
137
- `submit_tag`
118
138
- `file_tag`
119
139
- `password_tag`
140
+
- `email_tag`
120
141
- `checkbox_tag`
121
142
- `radio_tag`
122
143
- `time_tag`
123
144
- `date_tag`
124
145
- `number_tag`
125
146
126
-
All macrosses are named after correspond methods with adding `_for` at the end.
127
-
128
147
#### FormBuilder
129
148
130
-
To build form with automatically generated names and ids of elements:
149
+
To build form with automatically generated names and ids of inputs :
131
150
132
-
```slim
133
-
- form_tag_for(:some_form, "/posts", :post) do |f|
Because crystal loads template and build it inside of methods using `String::Builder` to generate content `-` should be used instead of `=` - content will be added to builder inside of method so there is no need to do it manually.
160
+
`.build_form`macro creates `ViewModel::FormBuilder` and passes it to the block. Form builder provides a set of methods similar to ones described above. All inputs will get own id and class based on it's name.
161
+
162
+
All form builder methods manipulate `__kilt_io__` directly and returns `nil` so it isn't important the way to call them: with `-`, `=` or `==`.
163
+
164
+
If you specify a form method different from `get` and `post` - form builder will add additional hidden input with name `_method` for the given method and set current form method to `post`.
165
+
166
+
### link_to
167
+
168
+
Also HTML helper includes `.link_to` macro. It allows to generate `<a>` tag with all needed data.
If you want to make a link to do a non-GET request (e.g. delete button), you can specify `method` argument and additionally load `libs/view_model/assets/view_model.js` file.
179
+
180
+
```slang
181
+
== link_to "delete", "/comments/56", :delete
182
+
```
142
183
143
184
## Development
144
185
145
186
There are still a lot of work to do. Tasks for next versions:
146
187
147
-
- [ ] add spec2 matchers
148
-
- [ ] rewrite to use spec2
188
+
- [ ] add spec matchers
149
189
- [ ] add more html helpers
150
-
- [ ] add `multiple` support for select
151
190
- [ ] add array support in name generation
152
-
- [ ] add `button_to`
153
191
154
192
## Contributing
155
193
@@ -163,7 +201,7 @@ Please ask me before starting work on smth.
163
201
164
202
Also if you want to use it in your application (for now shard is almost ready for use in production) - ping me please, my email you can find in my profile.
0 commit comments