├── .browserslistrc
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── .vscode
└── settings.json
├── README.md
├── babel.config.js
├── package.json
├── postcss.config.js
├── slides
├── SLIDES.md
├── css
│ ├── custom.css
│ ├── reveal.css
│ └── theme
│ │ └── black.css
├── index.tpl.html
├── js
│ ├── reveal.js
│ └── reveal.min.js
├── lib
│ ├── css
│ │ └── zenburn.css
│ └── js
│ │ ├── classList.js
│ │ ├── head.min.js
│ │ └── html5shiv.js
├── md2reveal.js
├── package.json
└── plugin
│ └── highlight
│ └── highlight.js
├── tasks
├── 1
│ ├── README.md
│ ├── babel.config.js
│ ├── jest.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── src
│ │ ├── App.vue
│ │ ├── components
│ │ │ ├── Hero.vue
│ │ │ └── HeroList.vue
│ │ └── main.js
│ └── tests
│ │ └── unit
│ │ ├── .eslintrc.js
│ │ └── Hero.spec.js
├── 2
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ └── src
│ │ ├── App.vue
│ │ ├── components
│ │ ├── APIFetchMixin.js
│ │ ├── PeopleList.vue
│ │ └── PlanetList.vue
│ │ └── main.js
├── 3
│ ├── README.md
│ ├── babel.config.js
│ ├── jest.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── src
│ │ ├── App.vue
│ │ ├── components
│ │ │ └── SelectOther.vue
│ │ └── main.js
│ └── tests
│ │ └── unit
│ │ ├── .eslintrc.js
│ │ └── SelectOther.spec.js
├── 4
│ ├── README.md
│ ├── babel.config.js
│ ├── jest.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── src
│ │ ├── App.vue
│ │ ├── components
│ │ │ └── Modal.vue
│ │ └── main.js
│ └── tests
│ │ └── unit
│ │ ├── .eslintrc.js
│ │ └── Modal.spec.js
├── 5
│ ├── README.md
│ ├── babel.config.js
│ ├── jest.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── src
│ │ ├── App.vue
│ │ ├── components
│ │ │ └── PageLayout.vue
│ │ ├── main.js
│ │ ├── pages
│ │ │ ├── Home.vue
│ │ │ ├── Page1.vue
│ │ │ ├── Page2.vue
│ │ │ └── Page3.vue
│ │ └── router
│ │ │ └── index.js
│ └── tests
│ │ └── unit
│ │ ├── .eslintrc.js
│ │ └── PageLayout.spec.js
├── 6
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ └── src
│ │ ├── App.vue
│ │ ├── components
│ │ ├── Hero.vue
│ │ └── HeroList.vue
│ │ └── main.js
├── 7
│ ├── README.md
│ ├── babel.config.js
│ ├── jest.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── src
│ │ ├── App.vue
│ │ ├── components
│ │ │ ├── Square.vue
│ │ │ └── ThemeProvider.vue
│ │ └── main.js
│ └── tests
│ │ └── unit
│ │ ├── .eslintrc.js
│ │ └── App.spec.js
├── 8
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ └── src
│ │ ├── App.vue
│ │ ├── components
│ │ └── MultiSelect.vue
│ │ └── main.js
├── 9
│ ├── README.md
│ ├── babel.config.js
│ ├── jest.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── src
│ │ ├── App.vue
│ │ ├── components
│ │ │ ├── Tab.vue
│ │ │ └── Tabs.vue
│ │ └── main.js
│ └── tests
│ │ └── unit
│ │ ├── .eslintrc.js
│ │ └── Tabs.spec.js
└── tpl
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ └── index.html
│ └── src
│ ├── App.vue
│ └── main.js
└── yarn.lock
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 8
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | extends: ['plugin:vue/essential', '@vue/prettier'],
7 | rules: {
8 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
9 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
10 | },
11 | parserOptions: {
12 | parser: 'babel-eslint',
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | #.vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
23 | slides/index.html
24 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "trailingComma": "es5"
5 | }
6 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "vetur.validation.template": false,
4 | "eslint.validate": [
5 | "javascript",
6 | "javascriptreact",
7 | {
8 | "language": "vue",
9 | "autoFix": true
10 | }
11 | ],
12 | "vetur.format.defaultFormatter.html": "none",
13 | "eslint.autoFixOnSave": true
14 | }
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Advanced Component Patterns in Vue.js Workshop
2 |
3 | ### Project setup
4 |
5 | ```
6 | yarn
7 | ```
8 |
9 | > Note: this repository only works with `yarn` because it uses [yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/). If you don't have it, install it with `npm -g install yarn`.
10 |
11 | ### Run a task
12 |
13 | ```
14 | yarn task1
15 | ```
16 |
17 | ### Task list
18 |
19 | 1. [Warm-up — props & events](tasks/1/README.md)
20 | 2. [Extracting common logic from components — mixins](tasks/2/README.md)
21 | 3. [Components with `v-model`](tasks/3/README.md)
22 | 4. [`Modal` component — slots](tasks/4/README.md)
23 | 5. [`PageLayout` component — named slots & lazy-loading](tasks/5/README.md)
24 | 6. [Customizing children UI — scoped slots](tasks/6/README.md)
25 | 7. [`ThemeProvider` component — provide / inject](tasks/7/README.md)
26 | 8. [Wrapping an [insert library here] component](tasks/8/README.md)
27 | 9. [`Tabs` & `Tab` components](tasks/9/README.md)
28 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/app'],
3 | };
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-advanced-components-workshop",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "task1": "yarn workspace task1 serve",
7 | "task1:test": "yarn workspace task1 test:unit",
8 | "task2": "yarn workspace task2 serve",
9 | "task3": "yarn workspace task3 serve",
10 | "task3:test": "yarn workspace task3 test:unit",
11 | "task4": "yarn workspace task4 serve",
12 | "task4:test": "yarn workspace task4 test:unit",
13 | "task5": "yarn workspace task5 serve",
14 | "task5-serve": "yarn workspace task5 build && yarn serve -s tasks/5/dist",
15 | "task5:test": "yarn workspace task5 test:unit",
16 | "task6": "yarn workspace task6 serve",
17 | "task7": "yarn workspace task7 serve",
18 | "task7:test": "yarn workspace task7 test:unit",
19 | "task8": "yarn workspace task8 serve",
20 | "task9": "yarn workspace task9 serve",
21 | "task9:test": "yarn workspace task9 test:unit",
22 | "slides": "yarn workspace slides serve"
23 | },
24 | "workspaces": [
25 | "tasks/*",
26 | "slides"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {},
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/slides/SLIDES.md:
--------------------------------------------------------------------------------
1 | ## Agenda
2 |
3 | - (very) short intro to components
4 | - ~9 tasks
5 | - present the concept
6 | - describe the task
7 | - 10 minutes for you to solve the task ⇄ 5 more minutes to solve the task together
8 | - (if there's time left) one freestyle task
9 | - 15' coffee break
10 |
11 | ## Components
12 |
13 | - What is an (UI) component?
14 | - & "a visual entity that encapsulates a piece of functionality"
15 | - & a custom DOM element
16 | - & "smart" vs. "dumb" / stateful vs. stateless / [presentational vs. container](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)
17 |
18 | ## Components in Vue.js
19 |
20 | - component object definition
21 |
22 | ```js
23 | export default {
24 | name: 'component',
25 | props: {
26 | /* ... */
27 | },
28 | data() {
29 | return {
30 | /* ... */
31 | };
32 | },
33 | methods: {
34 | /* ... */
35 | },
36 | /* ... */
37 | };
38 | ```
39 |
40 | - single file components `Component.vue`
41 | - component hierarchy
42 | - it's a tree
43 | - it has a main root instance
44 | - components have a `$parent` and zero or more `$children`
45 |
46 | ## Vue.js Components Internal API
47 |
48 | - data()
49 | - computed
50 | - methods
51 | - lifecycle hooks
52 | - watch
53 | - refs
54 | - render
55 | - mixins
56 | - $el, $parent, \$children [etc.](https://vuejs.org/v2/api/)
57 |
58 | ## Vue.js Components External API
59 |
60 | - props
61 | - events
62 | - **slots**
63 | - provide / inject
64 |
65 | ## Tasks
66 |
67 | 1. Warm-up — props & events
68 | 2. Extracting common logic from components — mixins
69 | 3. Components with `v-model`
70 | 4. `Modal` component — slots
71 | 5. `PageLayout` component — name slots & lazy-loading
72 | 6. Customizing children UI — scoped slots
73 | 7. `ThemeProvider` component — provide / inject
74 | 8. Wrapping an [insert library here] component
75 | 9. `Tabs` & `Tab` components
76 | 10. ???
77 |
78 | ## Tests
79 |
80 | - run with `yarn task#:test [--watch]`
81 | - using `jest` and `@vue/test-utils`
82 | - using `jsdom` to "emulate" DOM, might be different than browsers
83 |
84 | ## Warming Up - Task 1
85 |
86 | Create a component that receives props and sends events.
87 |
88 | - run with `yarn task1`
89 |
90 | ## Mixins
91 |
92 | - a way to extract common functionality out of components
93 | - a definition object, exactly like with components
94 | - when a component uses a mixin, its definition properties are merged with those of the mixin
95 |
96 | ## Task 2
97 |
98 | Extract the common API fetch logic from two components.
99 |
100 | ## Mixins Pros & Cons
101 |
102 | - pros
103 | - DRY
104 | - reusability
105 | - cons
106 | - hidden (implicit) dependencies — documentation
107 | - name clashes — "namespacing"
108 | - [Mixins Considered Harmful](https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html)
109 |
110 | ## Components and `v-model`
111 |
112 | ```html
113 |
114 | ```
115 |
116 | is syntactic sugar for
117 |
118 | ```html
119 |
120 | ```
121 |
122 | ## Task 3
123 |
124 | Create a component for an extended select that allows for an "Other" option and works with `v-model`.
125 |
126 | ## Slots
127 |
128 | - allow components to send markup to their children
129 | - change the render tree
130 |
131 | ## Default slot
132 |
133 | - in parent
134 |
135 | ```html
136 |
137 |
138 | some markup here
139 |
140 |
141 | ```
142 |
143 | - in child
144 |
145 | ```html
146 |
147 |
148 |
149 | ```
150 |
151 | - default content
152 |
153 | ```html
154 |
155 |
156 | some default content here
157 |
158 |
159 | ```
160 |
161 | ## Task 4
162 |
163 | Create a modal component.
164 |
165 | ## Named Slots
166 |
167 | - new syntax since 2.6
168 |
169 | - in parent
170 |
171 | ```html
172 |
173 |
174 | some markup here
175 |
176 |
177 | ```
178 |
179 | - in child
180 |
181 | ```html
182 |
183 |
184 | default header markup
185 |
186 |
187 | ```
188 |
189 | ## Task 5
190 |
191 | Create a `PageLayout` component that takes 3 slots — default, `header` and `footer`.
192 |
193 | ## PageLayout Note
194 |
195 | - navigating between pages repaints the whole layout (not really noticeable, though)
196 |
197 | ## Lazy-loaded Components
198 |
199 | - don't load **all** of your application upfront if you don't really need to
200 | - usually used with route components
201 |
202 | ```js
203 | const Page = () => import('./path/to/Page.vue');
204 | ```
205 |
206 | - that's pretty much it! 🙂 (thanks to webpack's magic)
207 |
208 | ## Task 5 - take 2
209 |
210 | Make the 3 pages from task 5 lazy-loaded.
211 |
212 | ## Scoped Slots
213 |
214 | - slots with parameters
215 | - in parent
216 |
217 | ```html
218 |
219 |
220 |
221 | ```
222 |
223 | - in child
224 |
225 | ```html
226 |
227 | default slot contents here
228 |
229 | ```
230 |
231 | ## Task 6
232 |
233 | Use scoped slots to customize how a `Hero` row from task 1 looks like.
234 |
235 | ## Provide / Inject
236 |
237 | - allows an up the tree component to send (provide) data to all its descendants
238 | - in the provider:
239 |
240 | ```js
241 | export default {
242 | /* ... */
243 | provide() {
244 | return {
245 | theme: {
246 | color: 'orangered',
247 | /* ... */
248 | },
249 | };
250 | },
251 | };
252 | ```
253 |
254 | - in a descendant:
255 |
256 | ```js
257 | export default {
258 | /* ... */
259 | inject: ['theme'], // accessible as this.theme
260 | };
261 | ```
262 |
263 | - the provided object is not reactive (but we can circumvent that)
264 |
265 | ## Task 7
266 |
267 | Define a `ThemeProvider` that provides a `theme` object, with `color` and `backgroundColor` properties. Use the provided `theme` in a descendant component.
268 |
269 | ## Provide / Inject Pros & Cons
270 |
271 | - pros
272 | - send data to descendants more than one level deep
273 | - cons
274 | - not obviously reactive
275 |
276 | ## Wrapping jQuery Components
277 |
278 | - initialization goes in `mounted()`
279 | - use `this.$el`, the component's root DOM element
280 | - (two-way) react to changes during the component's lifecycle
281 | - **cleanup goes in `beforeDestroy()`**
282 |
283 | ## Task 8
284 |
285 | Refactor a `MultiSelect` component to use [select2](https://select2.org/).
286 |
287 | ## Task 9 — and the last one! 🎉
288 |
289 | Implement `Tabs` and `Tab` components.
290 |
291 | ## Resources
292 |
293 | - Vue.js documentation, pretty much
294 | - Vue.js source code
295 | - [Renderless Components in Vue.js - Adam Wathan](https://adamwathan.me/renderless-components-in-vuejs/)
296 | - [7 Secret Patterns Vue Consultants Don’t Want You to Know - Chris Fritz](https://www.youtube.com/watch?v=7lpemgMhi0k)
297 |
298 | ## Thank you! 🙏
299 |
300 | Questions?
301 |
--------------------------------------------------------------------------------
/slides/css/custom.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #fefae8 !important;
3 | }
4 |
5 | .reveal h2,
6 | .reveal h3,
7 | .reveal p,
8 | .reveal ul,
9 | .reveal ol {
10 | font-family: 'Open Sans', sans-serif !important;
11 | color: #272c3c !important;
12 | }
13 |
14 | .reveal h2 {
15 | font-size: 0.9em !important;
16 | text-transform: none;
17 | }
18 |
19 | .reveal h3 {
20 | font-size: 0.7em !important;
21 | text-transform: none;
22 | }
23 |
24 | .reveal p {
25 | font-size: 0.7em !important;
26 | }
27 |
28 | .reveal pre code {
29 | font-size: 0.9em !important;
30 | line-height: 1.1em !important;
31 | max-height: 800px;
32 | padding: 10px;
33 | }
34 |
35 | .reveal ul,
36 | .reveal ol {
37 | align-self: flex-start;
38 | font-size: 0.65em !important;
39 | margin-left: 80px;
40 | }
41 |
42 | .reveal ul ul li {
43 | font-size: 1.4em !important;
44 | }
45 |
46 | .reveal ul ul {
47 | list-style-type: circle !important;
48 | }
49 |
50 | .reveal .title h2,
51 | .reveal .title p {
52 | color: #fff !important;
53 | }
54 |
55 | img.logo {
56 | background: none !important;
57 | border: none !important;
58 | box-shadow: none !important;
59 | position: absolute;
60 | left: 50px;
61 | top: 0;
62 | }
63 |
64 | .reveal .slides section {
65 | display: flex !important;
66 | align-items: center;
67 | flex-direction: column;
68 | justify-content: center;
69 | padding: 0 50px;
70 | min-height: 95% !important;
71 | width: 90%;
72 | }
73 |
74 | .slide-logo {
75 | color: #272c3c;
76 | display: flex;
77 | align-items: center;
78 | font-family: 'Open Sans', sans-serif !important;
79 | font-size: 0.4em !important;
80 | font-weight: bold !important;
81 | position: absolute;
82 | left: 50px;
83 | top: 0;
84 | }
85 |
86 | .slide-logo img {
87 | background: none !important;
88 | border: none !important;
89 | box-shadow: none !important;
90 | width: 50px;
91 | }
92 |
93 | .slide-logo div {
94 | margin-left: 10px;
95 | }
96 |
97 | img.simple {
98 | background: none !important;
99 | border: none !important;
100 | box-shadow: none !important;
101 | }
102 |
103 | a {
104 | color: #42b983 !important;
105 | }
106 |
107 | .muted {
108 | color: gray;
109 | font-weight: normal !important;
110 | }
111 |
112 | .reveal code.simple {
113 | background-color: #ff9;
114 | padding: 3px;
115 | }
116 |
--------------------------------------------------------------------------------
/slides/css/theme/black.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Black theme for reveal.js. This is the opposite of the 'white' theme.
3 | *
4 | * By Hakim El Hattab, http://hakim.se
5 | */
6 | /* @import url(../../lib/font/source-sans-pro/source-sans-pro.css); */
7 | @import url(http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600,400italic,600italic&subset=latin,latin-ext);
8 |
9 | section.has-light-background, section.has-light-background h1, section.has-light-background h2, section.has-light-background h3, section.has-light-background h4, section.has-light-background h5, section.has-light-background h6 {
10 | color: #222; }
11 |
12 | /*********************************************
13 | * GLOBAL STYLES
14 | *********************************************/
15 | body {
16 | background: #222;
17 | background-color: #222; }
18 |
19 | .reveal {
20 | font-family: "Source Sans Pro", Helvetica, sans-serif;
21 | font-size: 42px;
22 | font-weight: normal;
23 | color: #fff; }
24 |
25 | ::selection {
26 | color: #fff;
27 | background: #bee4fd;
28 | text-shadow: none; }
29 |
30 | ::-moz-selection {
31 | color: #fff;
32 | background: #bee4fd;
33 | text-shadow: none; }
34 |
35 | .reveal .slides section,
36 | .reveal .slides section > section {
37 | line-height: 1.3;
38 | font-weight: inherit; }
39 |
40 | /*********************************************
41 | * HEADERS
42 | *********************************************/
43 | .reveal h1,
44 | .reveal h2,
45 | .reveal h3,
46 | .reveal h4,
47 | .reveal h5,
48 | .reveal h6 {
49 | margin: 0 0 20px 0;
50 | color: #fff;
51 | font-family: "Source Sans Pro", Helvetica, sans-serif;
52 | font-weight: 600;
53 | line-height: 1.2;
54 | letter-spacing: normal;
55 | text-transform: uppercase;
56 | text-shadow: none;
57 | word-wrap: break-word; }
58 |
59 | .reveal h1 {
60 | font-size: 2.5em; }
61 |
62 | .reveal h2 {
63 | font-size: 1.6em; }
64 |
65 | .reveal h3 {
66 | font-size: 1.3em; }
67 |
68 | .reveal h4 {
69 | font-size: 1em; }
70 |
71 | .reveal h1 {
72 | text-shadow: none; }
73 |
74 | /*********************************************
75 | * OTHER
76 | *********************************************/
77 | .reveal p {
78 | margin: 20px 0;
79 | line-height: 1.3; }
80 |
81 | /* Ensure certain elements are never larger than the slide itself */
82 | .reveal img,
83 | .reveal video,
84 | .reveal iframe {
85 | max-width: 95%;
86 | max-height: 95%; }
87 |
88 | .reveal strong,
89 | .reveal b {
90 | font-weight: bold; }
91 |
92 | .reveal em {
93 | font-style: italic; }
94 |
95 | .reveal ol,
96 | .reveal dl,
97 | .reveal ul {
98 | display: inline-block;
99 | text-align: left;
100 | margin: 0 0 0 1em; }
101 |
102 | .reveal ol {
103 | list-style-type: decimal; }
104 |
105 | .reveal ul {
106 | list-style-type: disc; }
107 |
108 | .reveal ul ul {
109 | list-style-type: square; }
110 |
111 | .reveal ul ul ul {
112 | list-style-type: circle; }
113 |
114 | .reveal ul ul,
115 | .reveal ul ol,
116 | .reveal ol ol,
117 | .reveal ol ul {
118 | display: block;
119 | margin-left: 40px; }
120 |
121 | .reveal dt {
122 | font-weight: bold; }
123 |
124 | .reveal dd {
125 | margin-left: 40px; }
126 |
127 | .reveal blockquote {
128 | display: block;
129 | position: relative;
130 | width: 70%;
131 | margin: 20px auto;
132 | padding: 5px;
133 | font-style: italic;
134 | background: rgba(255, 255, 255, 0.05);
135 | box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2); }
136 |
137 | .reveal blockquote p:first-child,
138 | .reveal blockquote p:last-child {
139 | display: inline-block; }
140 |
141 | .reveal q {
142 | font-style: italic; }
143 |
144 | .reveal pre {
145 | display: block;
146 | position: relative;
147 | width: 90%;
148 | margin: 20px auto;
149 | text-align: left;
150 | font-size: 0.55em;
151 | font-family: monospace;
152 | line-height: 1.2em;
153 | word-wrap: break-word;
154 | box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); }
155 |
156 | .reveal code {
157 | font-family: monospace;
158 | text-transform: none; }
159 |
160 | .reveal pre code {
161 | display: block;
162 | padding: 5px;
163 | overflow: auto;
164 | max-height: 400px;
165 | word-wrap: normal; }
166 |
167 | .reveal table {
168 | margin: auto;
169 | border-collapse: collapse;
170 | border-spacing: 0; }
171 |
172 | .reveal table th {
173 | font-weight: bold; }
174 |
175 | .reveal table th,
176 | .reveal table td {
177 | text-align: left;
178 | padding: 0.2em 0.5em 0.2em 0.5em;
179 | border-bottom: 1px solid; }
180 |
181 | .reveal table th[align="center"],
182 | .reveal table td[align="center"] {
183 | text-align: center; }
184 |
185 | .reveal table th[align="right"],
186 | .reveal table td[align="right"] {
187 | text-align: right; }
188 |
189 | .reveal table tbody tr:last-child th,
190 | .reveal table tbody tr:last-child td {
191 | border-bottom: none; }
192 |
193 | .reveal sup {
194 | vertical-align: super;
195 | font-size: smaller; }
196 |
197 | .reveal sub {
198 | vertical-align: sub;
199 | font-size: smaller; }
200 |
201 | .reveal small {
202 | display: inline-block;
203 | font-size: 0.6em;
204 | line-height: 1.2em;
205 | vertical-align: top; }
206 |
207 | .reveal small * {
208 | vertical-align: top; }
209 |
210 | /*********************************************
211 | * LINKS
212 | *********************************************/
213 | .reveal a {
214 | color: #42affa;
215 | text-decoration: none;
216 | -webkit-transition: color .15s ease;
217 | -moz-transition: color .15s ease;
218 | transition: color .15s ease; }
219 |
220 | .reveal a:hover {
221 | color: #8dcffc;
222 | text-shadow: none;
223 | border: none; }
224 |
225 | .reveal .roll span:after {
226 | color: #fff;
227 | background: #068de9; }
228 |
229 | /*********************************************
230 | * IMAGES
231 | *********************************************/
232 | .reveal section img {
233 | margin: 15px 0px;
234 | background: rgba(255, 255, 255, 0.12);
235 | border: 4px solid #fff;
236 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); }
237 |
238 | .reveal section img.plain {
239 | border: 0;
240 | box-shadow: none; }
241 |
242 | .reveal a img {
243 | -webkit-transition: all .15s linear;
244 | -moz-transition: all .15s linear;
245 | transition: all .15s linear; }
246 |
247 | .reveal a:hover img {
248 | background: rgba(255, 255, 255, 0.2);
249 | border-color: #42affa;
250 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.55); }
251 |
252 | /*********************************************
253 | * NAVIGATION CONTROLS
254 | *********************************************/
255 | .reveal .controls {
256 | color: #42affa; }
257 |
258 | /*********************************************
259 | * PROGRESS BAR
260 | *********************************************/
261 | .reveal .progress {
262 | background: rgba(0, 0, 0, 0.2);
263 | color: #42affa; }
264 |
265 | .reveal .progress span {
266 | -webkit-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
267 | -moz-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985);
268 | transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); }
269 |
270 | /*********************************************
271 | * PRINT BACKGROUND
272 | *********************************************/
273 | @media print {
274 | .backgrounds {
275 | background-color: #222; } }
276 |
--------------------------------------------------------------------------------
/slides/index.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | reveal.js – The HTML Presentation Framework
7 |
8 |
12 |
13 |
14 |
15 |
19 |
20 |
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
53 |
58 |
63 |
69 |
70 |
71 |
77 |
78 | Advanced Vue.js Component Patterns
79 |
80 |
Bogdan Luca
81 |
guest host Dobromir Hristov
82 |
83 |
86 | April 10th, 2019
JSHeroes
87 |
88 |
89 |
90 |
91 |
92 |

96 |
Advanced Vue.js Component Patterns
97 |
98 | About Me, About You...
99 | Hello, I'm Bogdan!
101 |
102 | -
103 | been programming for ~25y
104 |
105 | -
106 | sysadmin, networking, backend, frontend, ...
107 |
108 | -
109 | dev&ops @CodeSandbox.io
112 | — "The online code editor for Web"
113 |
114 | -
115 | (Vue.js) Instructor at JSLeague
116 |
117 | -
118 | (used to) 24/7 support on
119 | Vue.js Official Chat
122 | — BogdanL
123 |
124 |
125 |
126 | Hello, I'm Dobri!
127 |
128 | - organizer of Vue Bulgaria
129 | - Vue.js instructor
130 | - creator of Vue Community
131 |
132 |
133 |
134 | ${CONTENT}
135 |
136 |
137 |
138 |
139 |
140 |
141 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/slides/js/reveal.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * reveal.js 3.7.0 (2019-01-25, 14:22)
3 | * http://revealjs.com
4 | * MIT licensed
5 | *
6 | * Copyright (C) 2018 Hakim El Hattab, http://hakim.se
7 | */
8 |
9 | !function(e,t){"function"==typeof define&&define.amd?define(function(){return e.Reveal=t(),e.Reveal}):"object"==typeof exports?module.exports=t():e.Reveal=t()}(this,function(){"use strict";var u,h,v,g,m,l,p,f,a,s=".slides section",y=".slides>section",b=".slides>section.present>section",w=".slides>section:first-of-type",k=navigator.userAgent,A={width:960,height:700,margin:.04,minScale:.2,maxScale:2,controls:!0,controlsTutorial:!0,controlsLayout:"bottom-right",controlsBackArrows:"faded",progress:!0,slideNumber:!1,hashOneBasedIndex:!1,showSlideNumber:"all",history:!1,keyboard:!0,keyboardCondition:null,overview:!0,disableLayout:!1,center:!0,touch:!0,loop:!1,rtl:!1,shuffle:!1,fragments:!0,fragmentInURL:!1,embedded:!1,help:!0,pause:!0,showNotes:!1,autoPlayMedia:null,autoSlide:0,autoSlideStoppable:!0,autoSlideMethod:null,defaultTiming:null,mouseWheel:!1,rollingLinks:!1,hideAddressBar:!0,previewLinks:!1,postMessage:!0,postMessageEvents:!1,focusBodyOnPageVisibilityChange:!0,transition:"slide",transitionSpeed:"default",backgroundTransition:"fade",parallaxBackgroundImage:"",parallaxBackgroundSize:"",parallaxBackgroundRepeat:"",parallaxBackgroundPosition:"",parallaxBackgroundHorizontal:null,parallaxBackgroundVertical:null,pdfMaxPagesPerSlide:Number.POSITIVE_INFINITY,pdfSeparateFragments:!0,pdfPageHeightOffset:-1,viewDistance:3,display:"block",dependencies:[]},L=!1,S=!1,t=!1,n=null,i=null,r=!1,o=!1,E=[],c=1,d={layout:"",overview:""},x={},q={},N=0,M=0,I=!1,T=0,C=0,P=-1,B=!1,H={startX:0,startY:0,startSpan:0,startCount:0,captured:!1,threshold:40},R={"N , SPACE":"Next slide",P:"Previous slide","← , H":"Navigate left","→ , L":"Navigate right","↑ , K":"Navigate up","↓ , J":"Navigate down",Home:"First slide",End:"Last slide","B , .":"Pause",F:"Fullscreen","ESC, O":"Slide overview"},D={};function z(e){var t="";if(3===e.nodeType)t+=e.textContent;else if(1===e.nodeType){var r=e.getAttribute("aria-hidden"),n="none"===window.getComputedStyle(e).display;"true"===r||n||K(e.childNodes).forEach(function(e){t+=z(e)})}return t}function O(){var e=ue(window.innerWidth,window.innerHeight),v=Math.floor(e.width*(1+A.margin)),g=Math.floor(e.height*(1+A.margin)),m=e.width,b=e.height;Z("@page{size:"+v+"px "+g+"px; margin: 0px;}"),Z(".reveal section>img, .reveal section>video, .reveal section>iframe{max-width: "+m+"px; max-height:"+b+"px}"),document.body.classList.add("print-pdf"),document.body.style.width=v+"px",document.body.style.height=g+"px",de(m,b),K(x.wrapper.querySelectorAll(y)).forEach(function(e,r){e.setAttribute("data-index-h",r),e.classList.contains("stack")&&K(e.querySelectorAll("section")).forEach(function(e,t){e.setAttribute("data-index-h",r),e.setAttribute("data-index-v",t)})}),K(x.wrapper.querySelectorAll(s)).forEach(function(e){if(!1===e.classList.contains("stack")){var t=(v-m)/2,r=(g-b)/2,n=e.scrollHeight,a=Math.max(Math.ceil(n/g),1);(1===(a=Math.min(a,A.pdfMaxPagesPerSlide))&&A.center||e.classList.contains("center"))&&(r=Math.max((g-n)/2,0));var i=document.createElement("div");if(i.className="pdf-page",i.style.height=(g+A.pdfPageHeightOffset)*a+"px",e.parentNode.insertBefore(i,e),i.appendChild(e),e.style.left=t+"px",e.style.top=r+"px",e.style.width=m+"px",e.slideBackgroundElement&&i.insertBefore(e.slideBackgroundElement,e),A.showNotes){var o=nt(e);if(o){var s="string"==typeof A.showNotes?A.showNotes:"inline",l=document.createElement("div");l.classList.add("speaker-notes"),l.classList.add("speaker-notes-pdf"),l.setAttribute("data-layout",s),l.innerHTML=o,"separate-page"===s?i.parentNode.insertBefore(l,i.nextSibling):(l.style.left="8px",l.style.bottom="8px",l.style.width=v-16+"px",i.appendChild(l))}}if(A.slideNumber&&/all|print/i.test(A.showSlideNumber)){var c=parseInt(e.getAttribute("data-index-h"),10)+1,d=parseInt(e.getAttribute("data-index-v"),10)+1,u=document.createElement("div");u.classList.add("slide-number"),u.classList.add("slide-number-pdf"),u.innerHTML=Re(c,".",d),i.appendChild(u)}if(A.pdfSeparateFragments){var p,f,h=it(i.querySelectorAll(".fragment"),!0);h.forEach(function(e){p&&p.forEach(function(e){e.classList.remove("current-fragment")}),e.forEach(function(e){e.classList.add("visible","current-fragment")});var t=i.cloneNode(!0);i.parentNode.insertBefore(t,(f||i).nextSibling),p=e,f=t}),h.forEach(function(e){e.forEach(function(e){e.classList.remove("visible","current-fragment")})})}else K(i.querySelectorAll(".fragment:not(.fade-out)")).forEach(function(e){e.classList.add("visible")})}}),ne("pdf-ready")}function W(e,t,r,n){for(var a=e.querySelectorAll("."+r),i=0;iKEY | ACTION | ",R)e+=""+t+" | "+R[t]+" |
";for(var r in D)D[r].key&&D[r].description&&(e+=""+D[r].key+" | "+D[r].description+" |
");e+="",x.overlay.innerHTML=["",'"].join(""),x.overlay.querySelector(".close").addEventListener("click",function(e){le(),e.preventDefault()},!1),setTimeout(function(){x.overlay.classList.add("visible")},1)}}function le(){x.overlay&&(x.overlay.parentNode.removeChild(x.overlay),x.overlay=null)}function ce(){if(x.wrapper&&!te()){if(!A.disableLayout){var e=ue();de(A.width,A.height),x.slides.style.width=e.width+"px",x.slides.style.height=e.height+"px",c=Math.min(e.presentationWidth/e.width,e.presentationHeight/e.height),c=Math.max(c,A.minScale),1===(c=Math.min(c,A.maxScale))?(x.slides.style.zoom="",x.slides.style.left="",x.slides.style.top="",x.slides.style.bottom="",Q({layout:x.slides.style.right=""})):1 .stretch")).forEach(function(e){var t=function i(e,t){if(t=t||0,e){var r,n=e.style.height;return e.style.height="0px",r=t-e.parentNode.offsetHeight,e.style.height=n+"px",r}return t}(e,s);if(/(img|video)/gi.test(e.nodeName)){var r=e.naturalWidth||e.videoWidth,n=e.naturalHeight||e.videoHeight,a=Math.min(o/r,t/n);e.style.width=r*a+"px",e.style.height=n*a+"px"}else e.style.width=o+"px",e.style.height=t+"px"})}function ue(e,t){var r={width:A.width,height:A.height,presentationWidth:e||x.wrapper.offsetWidth,presentationHeight:t||x.wrapper.offsetHeight};return r.presentationWidth-=r.presentationWidth*A.margin,r.presentationHeight-=r.presentationHeight*A.margin,"string"==typeof r.width&&/%$/.test(r.width)&&(r.width=parseInt(r.width,10)/100*r.presentationWidth),"string"==typeof r.height&&/%$/.test(r.height)&&(r.height=parseInt(r.height,10)/100*r.presentationHeight),r}function pe(e,t){"object"==typeof e&&"function"==typeof e.setAttribute&&e.setAttribute("data-previous-indexv",t||0)}function fe(e){if("object"==typeof e&&"function"==typeof e.setAttribute&&e.classList.contains("stack")){var t=e.hasAttribute("data-start-indexv")?"data-start-indexv":"data-previous-indexv";return parseInt(e.getAttribute(t)||0,10)}return 0}function he(){if(A.overview&&!ye()){t=!0,x.wrapper.classList.add("overview"),x.wrapper.classList.remove("overview-deactivating"),q.overviewTransitions&&setTimeout(function(){x.wrapper.classList.add("overview-animated")},1),dt(),x.slides.appendChild(x.background),K(x.wrapper.querySelectorAll(s)).forEach(function(e){e.classList.contains("stack")||e.addEventListener("click",Wt,!0)});var e=ue();n=e.width+70,i=e.height+70,A.rtl&&(n=-n),Ce(),ve(),ge(),ce(),ne("overviewshown",{indexh:h,indexv:v,currentSlide:m})}}function ve(){K(x.wrapper.querySelectorAll(y)).forEach(function(e,r){e.setAttribute("data-index-h",r),J(e,"translate3d("+r*n+"px, 0, 0)"),e.classList.contains("stack")&&K(e.querySelectorAll("section")).forEach(function(e,t){e.setAttribute("data-index-h",r),e.setAttribute("data-index-v",t),J(e,"translate3d(0, "+t*i+"px, 0)")})}),K(x.background.childNodes).forEach(function(e,t){J(e,"translate3d("+t*n+"px, 0, 0)"),K(e.querySelectorAll(".slide-background")).forEach(function(e,t){J(e,"translate3d(0, "+t*i+"px, 0)")})})}function ge(){var e=Math.min(window.innerWidth,window.innerHeight);Q({overview:["scale("+Math.max(e/5,150)/e+")","translateX("+-h*n+"px)","translateY("+-v*i+"px)"].join(" ")})}function me(){A.overview&&(t=!1,x.wrapper.classList.remove("overview"),x.wrapper.classList.remove("overview-animated"),x.wrapper.classList.add("overview-deactivating"),setTimeout(function(){x.wrapper.classList.remove("overview-deactivating")},1),x.wrapper.appendChild(x.background),K(x.wrapper.querySelectorAll(s)).forEach(function(e){J(e,""),e.removeEventListener("click",Wt,!0)}),K(x.background.querySelectorAll(".slide-background")).forEach(function(e){J(e,"")}),Q({overview:""}),qe(h,v),ce(),ct(),ne("overviewhidden",{indexh:h,indexv:v,currentSlide:m}))}function be(e){"boolean"==typeof e?e?he():me():ye()?me():he()}function ye(){return t}function we(){var e,t="/",r=m?m.getAttribute("id"):null;if(r&&(r=encodeURIComponent(r)),A.fragmentInURL&&(e=Ze().f),"string"==typeof r&&r.length&&e===undefined)t="/"+r;else{var n=A.hashOneBasedIndex?1:0;(0section>section").length?x.wrapper.classList.add("has-vertical-slides"):x.wrapper.classList.remove("has-vertical-slides"),1section").length?x.wrapper.classList.add("has-horizontal-slides"):x.wrapper.classList.remove("has-horizontal-slides")}}function Pe(){A.showNotes&&x.speakerNotes&&m&&!te()&&(x.speakerNotes.innerHTML=nt()||'No notes on this slide.')}function Be(){A.progress&&x.progressbar&&(x.progressbar.style.width=e()*x.wrapper.offsetWidth+"px")}function He(){if(A.slideNumber&&x.slideNumber){var e=[],t="h.v";switch("string"==typeof A.slideNumber&&(t=A.slideNumber),/c/.test(t)||1!==x.wrapper.querySelectorAll(y).length||(t="c"),t){case"c":e.push($e()+1);break;case"c/t":e.push($e()+1,"/",et());break;case"h/v":e.push(h+1),ke()&&e.push("/",v+1);break;default:e.push(h+1),ke()&&e.push(".",v+1)}x.slideNumber.innerHTML=Re(e[0],e[1],e[2])}}function Re(e,t,r){var n="#"+we();return"number"!=typeof r||isNaN(r)?''+e+"":''+e+''+t+''+r+""}function De(){var e=Ue(),t=Ye();x.controlsLeft.concat(x.controlsRight).concat(x.controlsUp).concat(x.controlsDown).concat(x.controlsPrev).concat(x.controlsNext).forEach(function(e){e.classList.remove("enabled"),e.classList.remove("fragmented"),e.setAttribute("disabled","disabled")}),e.left&&x.controlsLeft.forEach(function(e){e.classList.add("enabled"),e.removeAttribute("disabled")}),e.right&&x.controlsRight.forEach(function(e){e.classList.add("enabled"),e.removeAttribute("disabled")}),e.up&&x.controlsUp.forEach(function(e){e.classList.add("enabled"),e.removeAttribute("disabled")}),e.down&&x.controlsDown.forEach(function(e){e.classList.add("enabled"),e.removeAttribute("disabled")}),(e.left||e.up)&&x.controlsPrev.forEach(function(e){e.classList.add("enabled"),e.removeAttribute("disabled")}),(e.right||e.down)&&x.controlsNext.forEach(function(e){e.classList.add("enabled"),e.removeAttribute("disabled")}),m&&(t.prev&&x.controlsPrev.forEach(function(e){e.classList.add("fragmented","enabled"),e.removeAttribute("disabled")}),t.next&&x.controlsNext.forEach(function(e){e.classList.add("fragmented","enabled"),e.removeAttribute("disabled")}),ke(m)?(t.prev&&x.controlsUp.forEach(function(e){e.classList.add("fragmented","enabled"),e.removeAttribute("disabled")}),t.next&&x.controlsDown.forEach(function(e){e.classList.add("fragmented","enabled"),e.removeAttribute("disabled")})):(t.prev&&x.controlsLeft.forEach(function(e){e.classList.add("fragmented","enabled"),e.removeAttribute("disabled")}),t.next&&x.controlsRight.forEach(function(e){e.classList.add("fragmented","enabled"),e.removeAttribute("disabled")}))),A.controlsTutorial&&(!o&&e.down?x.controlsDownArrow.classList.add("highlight"):(x.controlsDownArrow.classList.remove("highlight"),!r&&e.right&&0===v?x.controlsRightArrow.classList.add("highlight"):x.controlsRightArrow.classList.remove("highlight")))}function ze(t){var n=null,a=A.rtl?"future":"past",i=A.rtl?"past":"future";if(K(x.background.childNodes).forEach(function(e,r){e.classList.remove("past"),e.classList.remove("present"),e.classList.remove("future"),r'}),n.appendChild(c)}else if(l&&!0!==t.excludeIframes){var d=document.createElement("iframe");d.setAttribute("allowfullscreen",""),d.setAttribute("mozallowfullscreen",""),d.setAttribute("webkitallowfullscreen",""),/autoplay=(1|true|yes)/gi.test(l)?d.setAttribute("data-src",l):d.setAttribute("src",l),d.style.width="100%",d.style.height="100%",d.style.maxHeight="100%",d.style.maxWidth="100%",n.appendChild(d)}}}}function Fe(e){e.style.display="none";var t=rt(e);t&&(t.style.display="none"),K(e.querySelectorAll("video[data-lazy-loaded][src], audio[data-lazy-loaded][src]")).forEach(function(e){e.setAttribute("data-src",e.getAttribute("src")),e.removeAttribute("src")}),K(e.querySelectorAll("video[data-lazy-loaded] source[src], audio source[src]")).forEach(function(e){e.setAttribute("data-src",e.getAttribute("src")),e.removeAttribute("src")})}function Ue(){var e=x.wrapper.querySelectorAll(y),t=x.wrapper.querySelectorAll(b),r={left:0T&&(T=1e3*e.duration/e.playbackRate+1e3)}),!T||B||Ee()||ye()||u.isLastSlide()&&!Ye().next&&!0!==A.loop||(C=setTimeout(function(){"function"==typeof A.autoSlideMethod?A.autoSlideMethod():bt(),ct()},T),P=Date.now()),a&&a.setPlaying(-1!==C)}}function dt(){clearTimeout(C),C=-1}function ut(){T&&!B&&(B=!0,ne("autoslidepaused"),clearTimeout(C),a&&a.setPlaying(!1))}function pt(){T&&B&&(B=!1,ne("autoslideresumed"),ct())}function ft(){A.rtl?(ye()||!1===st())&&Ue().left&&qe(h+1):(ye()||!1===lt())&&Ue().left&&qe(h-1)}function ht(){r=!0,A.rtl?(ye()||!1===lt())&&Ue().right&&qe(h-1):(ye()||!1===st())&&Ue().right&&qe(h+1)}function vt(){(ye()||!1===lt())&&Ue().up&&qe(h,v-1)}function gt(){o=!0,(ye()||!1===st())&&Ue().down&&qe(h,v+1)}function mt(){var e;if(!1===lt())if(Ue().up)vt();else if(e=A.rtl?K(x.wrapper.querySelectorAll(y+".future")).pop():K(x.wrapper.querySelectorAll(y+".past")).pop()){var t=e.querySelectorAll("section").length-1||undefined;qe(h-1,t)}}function bt(){if(!(o=r=!0)===st()){var e=Ue();e.down&&e.right&&A.loop&&u.isLastVerticalSlide(m)&&(e.down=!1),e.down?gt():A.rtl?ft():ht()}}function yt(e){for(;e&&"function"==typeof e.hasAttribute;){if(e.hasAttribute("data-prevent-swipe"))return!0;e=e.parentNode}return!1}function wt(e){A.autoSlideStoppable&&ut()}function kt(e){e.shiftKey&&63===e.charCode&&oe()}function At(e){if("function"==typeof A.keyboardCondition&&!1===A.keyboardCondition(e))return!0;var t=B;wt();var r=document.activeElement&&"inherit"!==document.activeElement.contentEditable,n=document.activeElement&&document.activeElement.tagName&&/input|textarea/i.test(document.activeElement.tagName),a=document.activeElement&&document.activeElement.className&&/speaker-notes/i.test(document.activeElement.className);if(!(r||n||a||e.shiftKey&&32!==e.keyCode||e.altKey||e.ctrlKey||e.metaKey)){var i,o=[66,86,190,191];if("object"==typeof A.keyboard)for(i in A.keyboard)"togglePause"===A.keyboard[i]&&o.push(parseInt(i,10));if(Ee()&&-1===o.indexOf(e.keyCode))return!1;var s=!1;if("object"==typeof A.keyboard)for(i in A.keyboard)if(parseInt(i,10)===e.keyCode){var l=A.keyboard[i];"function"==typeof l?l.apply(null,[e]):"string"==typeof l&&"function"==typeof u[l]&&u[l].call(),s=!0}if(!1===s)for(i in D)if(parseInt(i,10)===e.keyCode){var c=D[i].callback;"function"==typeof c?c.apply(null,[e]):"string"==typeof c&&"function"==typeof u[c]&&u[c].call(),s=!0}if(!1===s)switch(s=!0,e.keyCode){case 80:case 33:mt();break;case 78:case 34:bt();break;case 72:case 37:ft();break;case 76:case 39:ht();break;case 75:case 38:vt();break;case 74:case 40:gt();break;case 36:qe(0);break;case 35:qe(Number.MAX_VALUE);break;case 32:ye()?me():e.shiftKey?mt():bt();break;case 13:ye()?me():s=!1;break;case 58:case 59:case 66:case 86:case 190:case 191:Se();break;case 70:!function d(){var e=document.documentElement,t=e.requestFullscreen||e.webkitRequestFullscreen||e.webkitRequestFullScreen||e.mozRequestFullScreen||e.msRequestFullscreen;t&&t.apply(e)}();break;case 65:A.autoSlideStoppable&&xe(t);break;default:s=!1}s?e.preventDefault&&e.preventDefault():27!==e.keyCode&&79!==e.keyCode||!q.transforms3d||(x.overlay?le():be(),e.preventDefault&&e.preventDefault()),ct()}}function Lt(e){if(yt(e.target))return!0;H.startX=e.touches[0].clientX,H.startY=e.touches[0].clientY,H.startCount=e.touches.length,2===e.touches.length&&A.overview&&(H.startSpan=_({x:e.touches[1].clientX,y:e.touches[1].clientY},{x:H.startX,y:H.startY}))}function St(e){if(yt(e.target))return!0;if(H.captured)k.match(/android/gi)&&e.preventDefault();else{wt();var t=e.touches[0].clientX,r=e.touches[0].clientY;if(2===e.touches.length&&2===H.startCount&&A.overview){var n=_({x:e.touches[1].clientX,y:e.touches[1].clientY},{x:H.startX,y:H.startY});Math.abs(H.startSpan-n)>H.threshold&&(H.captured=!0,nH.threshold&&Math.abs(a)>Math.abs(i)?(H.captured=!0,ft()):a<-H.threshold&&Math.abs(a)>Math.abs(i)?(H.captured=!0,ht()):i>H.threshold?(H.captured=!0,vt()):i<-H.threshold&&(H.captured=!0,gt()),A.embedded?(H.captured||ke(m))&&e.preventDefault():e.preventDefault()}}}function Et(e){H.captured=!1}function xt(e){e.pointerType!==e.MSPOINTER_TYPE_TOUCH&&"touch"!==e.pointerType||(e.touches=[{clientX:e.clientX,clientY:e.clientY}],Lt(e))}function qt(e){e.pointerType!==e.MSPOINTER_TYPE_TOUCH&&"touch"!==e.pointerType||(e.touches=[{clientX:e.clientX,clientY:e.clientY}],St(e))}function Nt(e){e.pointerType!==e.MSPOINTER_TYPE_TOUCH&&"touch"!==e.pointerType||(e.touches=[{clientX:e.clientX,clientY:e.clientY}],Et())}function Mt(e){if(600",'','',"",'','','','','Unable to load iframe. This is likely due to the site\'s policy (x-frame-options).',"","
"].join(""),x.overlay.querySelector("iframe").addEventListener("load",function(e){x.overlay.classList.add("loaded")},!1),x.overlay.querySelector(".close").addEventListener("click",function(e){le(),e.preventDefault()},!1),x.overlay.querySelector(".external").addEventListener("click",function(e){le()},!1),setTimeout(function(){x.overlay.classList.add("visible")},1)}(t),e.preventDefault())}}function Ut(e){u.isLastSlide()&&!1===A.loop?(qe(0,0),pt()):B?pt():ut()}function Yt(e,t){this.diameter=100,this.diameter2=this.diameter/2,this.thickness=6,this.playing=!1,this.progress=0,this.progressOffset=1,this.container=e,this.progressCheck=t,this.canvas=document.createElement("canvas"),this.canvas.className="playback",this.canvas.width=this.diameter,this.canvas.height=this.diameter,this.canvas.style.width=this.diameter2+"px",this.canvas.style.height=this.diameter2+"px",this.context=this.canvas.getContext("2d"),this.container.appendChild(this.canvas),this.render()}return Yt.prototype.setPlaying=function(e){var t=this.playing;this.playing=e,!t&&this.playing?this.animate():this.render()},Yt.prototype.animate=function(){var e=this.progress;this.progress=this.progressCheck(),.8"),x.progressbar=x.progress.querySelector("span"),x.controls=W(x.wrapper,"aside","controls",''),x.slideNumber=W(x.wrapper,"div","slide-number",""),x.speakerNotes=W(x.wrapper,"div","speaker-notes",null),x.speakerNotes.setAttribute("data-prevent-swipe",""),x.speakerNotes.setAttribute("tabindex","0"),x.pauseOverlay=W(x.wrapper,"div","pause-overlay",''),x.resumeButton=x.pauseOverlay.querySelector(".resume-button"),x.wrapper.setAttribute("role","application"),x.controlsLeft=K(document.querySelectorAll(".navigate-left")),x.controlsRight=K(document.querySelectorAll(".navigate-right")),x.controlsUp=K(document.querySelectorAll(".navigate-up")),x.controlsDown=K(document.querySelectorAll(".navigate-down")),x.controlsPrev=K(document.querySelectorAll(".navigate-prev")),x.controlsNext=K(document.querySelectorAll(".navigate-next")),x.controlsRightArrow=x.controls.querySelector(".navigate-right"),x.controlsDownArrow=x.controls.querySelector(".navigate-down"),x.statusDiv=function t(){var e=document.getElementById("aria-status-div");return e||((e=document.createElement("div")).style.position="absolute",e.style.height="1px",e.style.width="1px",e.style.overflow="hidden",e.style.clip="rect( 1px, 1px, 1px, 1px )",e.setAttribute("id","aria-status-div"),e.setAttribute("aria-live","polite"),e.setAttribute("aria-atomic","true"),x.wrapper.appendChild(e)),e}()}(),function t(){A.postMessage&&window.addEventListener("message",function(e){var t=e.data;"string"==typeof t&&"{"===t.charAt(0)&&"}"===t.charAt(t.length-1)&&(t=JSON.parse(t)).method&&"function"==typeof u[t.method]&&u[t.method].apply(u,t.args)},!1)}(),function r(){setInterval(function(){0===x.wrapper.scrollTop&&0===x.wrapper.scrollLeft||(x.wrapper.scrollTop=0,x.wrapper.scrollLeft=0)},1e3)}(),function n(){K(x.wrapper.querySelectorAll(y)).forEach(function(e){var t=K(e.querySelectorAll("section"));t.forEach(function(e,t){0
4 | based on dark.css by Ivan Sagalaev
5 |
6 | */
7 |
8 | .hljs {
9 | display: block;
10 | overflow-x: auto;
11 | padding: 0.5em;
12 | background: #3f3f3f;
13 | color: #dcdcdc;
14 | }
15 |
16 | .hljs-keyword,
17 | .hljs-selector-tag,
18 | .hljs-tag {
19 | color: #e3ceab;
20 | }
21 |
22 | .hljs-template-tag {
23 | color: #dcdcdc;
24 | }
25 |
26 | .hljs-number {
27 | color: #8cd0d3;
28 | }
29 |
30 | .hljs-variable,
31 | .hljs-template-variable,
32 | .hljs-attribute {
33 | color: #efdcbc;
34 | }
35 |
36 | .hljs-literal {
37 | color: #efefaf;
38 | }
39 |
40 | .hljs-subst {
41 | color: #8f8f8f;
42 | }
43 |
44 | .hljs-title,
45 | .hljs-name,
46 | .hljs-selector-id,
47 | .hljs-selector-class,
48 | .hljs-section,
49 | .hljs-type {
50 | color: #efef8f;
51 | }
52 |
53 | .hljs-symbol,
54 | .hljs-bullet,
55 | .hljs-link {
56 | color: #dca3a3;
57 | }
58 |
59 | .hljs-deletion,
60 | .hljs-string,
61 | .hljs-built_in,
62 | .hljs-builtin-name {
63 | color: #cc9393;
64 | }
65 |
66 | .hljs-addition,
67 | .hljs-comment,
68 | .hljs-quote,
69 | .hljs-meta {
70 | color: #7f9f7f;
71 | }
72 |
73 |
74 | .hljs-emphasis {
75 | font-style: italic;
76 | }
77 |
78 | .hljs-strong {
79 | font-weight: bold;
80 | }
81 |
--------------------------------------------------------------------------------
/slides/lib/js/classList.js:
--------------------------------------------------------------------------------
1 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
2 | if(typeof document!=="undefined"&&!("classList" in document.createElement("a"))){(function(j){var a="classList",f="prototype",m=(j.HTMLElement||j.Element)[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;pn?(i.screensCss.gt&&r("gt-"+n),i.screensCss.gte&&r("gte-"+n)):tt);u.feature("landscape",fe?(i.browserCss.gt&&r("gt-"+f+e),i.browserCss.gte&&r("gte-"+f+e)):h2&&this[u+1]!==t)u&&r(this.slice(u,u+1).join("-").toLowerCase()+i.section);else{var f=n||"index",e=f.indexOf(".");e>0&&(f=f.substring(0,e));c.id=f.toLowerCase()+i.page;u||r("root"+i.section)}});u.screen={height:n.screen.height,width:n.screen.width};tt();b=0;n.addEventListener?n.addEventListener("resize",it,!1):n.attachEvent("onresize",it)})(window);
3 | /*! head.css3 - v1.0.0 */
4 | (function(n,t){"use strict";function a(n){for(var r in n)if(i[n[r]]!==t)return!0;return!1}function r(n){var t=n.charAt(0).toUpperCase()+n.substr(1),i=(n+" "+c.join(t+" ")+t).split(" ");return!!a(i)}var h=n.document,o=h.createElement("i"),i=o.style,s=" -o- -moz- -ms- -webkit- -khtml- ".split(" "),c="Webkit Moz O ms Khtml".split(" "),l=n.head_conf&&n.head_conf.head||"head",u=n[l],f={gradient:function(){var n="background-image:";return i.cssText=(n+s.join("gradient(linear,left top,right bottom,from(#9f9),to(#fff));"+n)+s.join("linear-gradient(left top,#eee,#fff);"+n)).slice(0,-n.length),!!i.backgroundImage},rgba:function(){return i.cssText="background-color:rgba(0,0,0,0.5)",!!i.backgroundColor},opacity:function(){return o.style.opacity===""},textshadow:function(){return i.textShadow===""},multiplebgs:function(){i.cssText="background:url(https://),url(https://),red url(https://)";var n=(i.background||"").match(/url/g);return Object.prototype.toString.call(n)==="[object Array]"&&n.length===3},boxshadow:function(){return r("boxShadow")},borderimage:function(){return r("borderImage")},borderradius:function(){return r("borderRadius")},cssreflections:function(){return r("boxReflect")},csstransforms:function(){return r("transform")},csstransitions:function(){return r("transition")},touch:function(){return"ontouchstart"in n},retina:function(){return n.devicePixelRatio>1},fontface:function(){var t=u.browser.name,n=u.browser.version;switch(t){case"ie":return n>=9;case"chrome":return n>=13;case"ff":return n>=6;case"ios":return n>=5;case"android":return!1;case"webkit":return n>=5.1;case"opera":return n>=10;default:return!1}}};for(var e in f)f[e]&&u.feature(e,f[e].call(),!0);u.feature()})(window);
5 | /*! head.load - v1.0.3 */
6 | (function(n,t){"use strict";function w(){}function u(n,t){if(n){typeof n=="object"&&(n=[].slice.call(n));for(var i=0,r=n.length;i/g, '');
12 | result = result.replace(/&/g, '');
14 | return result;
15 | }
16 |
17 | const revealSlides = slides.map(slide => {
18 | return `
25 |
26 |

30 |
Advanced Vue.js Component Patterns
31 |
32 | ${mdRender(slide)}
33 |
34 | `;
35 | });
36 |
37 | const template = fs.readFileSync('index.tpl.html', { encoding: 'utf8' });
38 |
39 | fs.writeFileSync(
40 | 'index.html',
41 | template.replace('${CONTENT}', revealSlides.join('\n'))
42 | );
43 |
--------------------------------------------------------------------------------
/slides/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "slides",
3 | "version": "0.1.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "node md2reveal",
8 | "serve": "yarn build && serve"
9 | },
10 | "devDependencies": {
11 | "markdown-it": "^10.0.0",
12 | "serve": "^11.2.0"
13 | }
14 | }
--------------------------------------------------------------------------------
/tasks/1/README.md:
--------------------------------------------------------------------------------
1 | # Task 1
2 |
3 | ## Goal
4 |
5 | Create a `Hero` component that shows the first name, last name and the framework, and also a checkbox synced to the `selected` property, and sends a `select` event when clicked, having the payload `{ id, value }`, where `id` is the hero's ID, and `value` is the new `selected` value.
6 |
7 | ## Hints
8 |
9 | - use the following markup:
10 |
11 | ```html
12 | First Name
13 | Last Name
14 | Framework
15 |
16 | ```
17 |
--------------------------------------------------------------------------------
/tasks/1/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/app'],
3 | };
4 |
--------------------------------------------------------------------------------
/tasks/1/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
3 | transform: {
4 | '^.+\\.vue$': 'vue-jest',
5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
6 | 'jest-transform-stub',
7 | '^.+\\.jsx?$': 'babel-jest',
8 | },
9 | transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\]'],
10 | moduleNameMapper: {
11 | '^@/(.*)$': '/src/$1',
12 | },
13 | snapshotSerializers: ['jest-serializer-vue'],
14 | testMatch: [
15 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)',
16 | ],
17 | testURL: 'http://localhost/',
18 | };
19 |
--------------------------------------------------------------------------------
/tasks/1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "task1",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint",
9 | "test:unit": "vue-cli-service test:unit"
10 | },
11 | "dependencies": {
12 | "core-js": "^3.4.0",
13 | "vue": "^2.6.10"
14 | },
15 | "devDependencies": {
16 | "@vue/cli-plugin-babel": "^4.0.5",
17 | "@vue/cli-plugin-eslint": "^4.0.5",
18 | "@vue/cli-plugin-unit-jest": "^4.0.5",
19 | "@vue/cli-service": "^4.0.5",
20 | "@vue/eslint-config-prettier": "^5.0.0",
21 | "@vue/test-utils": "1.0.0-beta.29",
22 | "babel-core": "7.0.0-bridge.0",
23 | "babel-eslint": "^10.0.3",
24 | "babel-jest": "^24.9.0",
25 | "eslint": "^6.6.0",
26 | "eslint-plugin-prettier": "^3.1.1",
27 | "eslint-plugin-vue": "^6.0.0",
28 | "vue-template-compiler": "^2.6.10"
29 | }
30 | }
--------------------------------------------------------------------------------
/tasks/1/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbogdan/vue-advanced-components-workshop/8b3e708df295e5fb920915a0f5d34858dfca7f3d/tasks/1/public/favicon.ico
--------------------------------------------------------------------------------
/tasks/1/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | repo
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tasks/1/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
17 |
18 |
28 |
--------------------------------------------------------------------------------
/tasks/1/src/components/Hero.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
TODO: here be hero
4 |
5 |
6 |
7 |
13 |
14 |
25 |
--------------------------------------------------------------------------------
/tasks/1/src/components/HeroList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
{{ selectedHeroes }}
10 |
11 |
12 |
13 |
14 |
71 |
72 |
77 |
--------------------------------------------------------------------------------
/tasks/1/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue';
3 |
4 | Vue.config.productionTip = false;
5 |
6 | new Vue({
7 | render: h => h(App),
8 | }).$mount('#app');
9 |
--------------------------------------------------------------------------------
/tasks/1/tests/unit/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | jest: true,
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/tasks/1/tests/unit/Hero.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from '@vue/test-utils';
2 | import Hero from '@/components/Hero.vue';
3 |
4 | let wrapper;
5 |
6 | afterEach(() => {
7 | wrapper.destroy();
8 | });
9 |
10 | function createWrapper(onSelect) {
11 | return mount(Hero, {
12 | propsData: {
13 | data: {
14 | id: 1,
15 | firstName: 'Bogdan',
16 | lastName: 'Luca',
17 | framework: 'Vue.js',
18 | selected: false,
19 | },
20 | },
21 | listeners: {
22 | select: onSelect || function() {},
23 | },
24 | });
25 | }
26 |
27 | describe('Hero.vue', () => {
28 | it('shows the hero data', () => {
29 | wrapper = createWrapper();
30 | expect(wrapper.text()).toContain('Bogdan');
31 | expect(wrapper.text()).toContain('Luca');
32 | expect(wrapper.text()).toContain('Vue.js');
33 | });
34 |
35 | it('shows a checkbox', () => {
36 | wrapper = createWrapper();
37 | const $checkbox = wrapper.find('input');
38 | expect($checkbox.exists()).toBe(true);
39 | expect($checkbox.element.type).toBe('checkbox');
40 | });
41 |
42 | it('updates the checkbox when "selected" property changes', () => {
43 | wrapper = createWrapper();
44 | wrapper.vm.data.selected = true;
45 | const $checkbox = wrapper.find('input');
46 | expect($checkbox.element.checked).toBe(true);
47 | });
48 |
49 | it('emits a "select" event with the specified payload when clicking the checkbox', () => {
50 | const onSelect = jest.fn();
51 | wrapper = createWrapper(onSelect);
52 | const $checkbox = wrapper.find('input');
53 | $checkbox.trigger('click');
54 | expect(onSelect).toHaveBeenCalledWith({ id: 1, value: true });
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/tasks/2/README.md:
--------------------------------------------------------------------------------
1 | # Task 2
2 |
3 | ## Goal
4 |
5 | Extract the API fetch logic from `PeopleList` and `PlanetList` to a mixin.
6 |
7 | ## Hints
8 |
9 | - the mixin should manage its own `data()`
10 | - the mixin should define a method that will be called from the components using it
11 |
--------------------------------------------------------------------------------
/tasks/2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "task2",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "axios": "^0.19.0",
12 | "vue": "^2.6.10"
13 | },
14 | "devDependencies": {
15 | "@vue/cli-plugin-babel": "^4.0.5",
16 | "@vue/cli-plugin-eslint": "^4.0.5",
17 | "@vue/cli-service": "^4.0.5",
18 | "@vue/eslint-config-prettier": "^5.0.0",
19 | "babel-eslint": "^10.0.3",
20 | "eslint": "^6.6.0",
21 | "eslint-plugin-prettier": "^3.1.1",
22 | "eslint-plugin-vue": "^6.0.0",
23 | "vue-template-compiler": "^2.6.10"
24 | }
25 | }
--------------------------------------------------------------------------------
/tasks/2/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbogdan/vue-advanced-components-workshop/8b3e708df295e5fb920915a0f5d34858dfca7f3d/tasks/2/public/favicon.ico
--------------------------------------------------------------------------------
/tasks/2/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | repo
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tasks/2/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
22 |
23 |
33 |
--------------------------------------------------------------------------------
/tasks/2/src/components/APIFetchMixin.js:
--------------------------------------------------------------------------------
1 | export default {
2 | // TODO
3 | };
4 |
--------------------------------------------------------------------------------
/tasks/2/src/components/PeopleList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
loading people...
4 |
5 | -
6 | {{ person.name }}
7 |
8 |
9 |
10 |
11 |
12 |
39 |
--------------------------------------------------------------------------------
/tasks/2/src/components/PlanetList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
loading planets...
4 |
5 | -
6 | {{ planet.name }}
7 |
8 |
9 |
10 |
11 |
12 |
39 |
--------------------------------------------------------------------------------
/tasks/2/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue';
3 |
4 | Vue.config.productionTip = false;
5 |
6 | new Vue({
7 | render: h => h(App),
8 | }).$mount('#app');
9 |
--------------------------------------------------------------------------------
/tasks/3/README.md:
--------------------------------------------------------------------------------
1 | # Task 3
2 |
3 | ## Goal
4 |
5 | Create a `SelectOther` component that receives an array of strings called `options`. There should be a `select` element, having all the provided options plus an additional `Other` option, that when selected, shows an input where you can type a custom value. Make the component work with `v-model`, i.e.
6 |
7 | ```html
8 |
9 | ```
10 |
11 | ## Bonus
12 |
13 | - when the `v-model`'s value is `null`, clear the select
14 |
15 | ## Hints
16 |
17 | - use a computed property with a getter and a setter for `