├── .gitignore
├── 00--Introduccion
├── README.md
└── _docs
│ ├── planteamiento-refactor-frontend
│ ├── README.md
│ └── img
│ │ └── cover-que-aprenderas-este-curso.png
│ └── por-que-vuejs
│ ├── README.md
│ └── img
│ └── cover-por-que-vue.png
├── 01-HTML-CSS
├── _download
│ └── proyecto-base.zip
├── css
│ └── main.css
├── index.html
├── js
│ └── main.js
└── post.html
├── 02-WeatherApiService
├── _docs
│ └── escenario-HTML-CSS-JS-Nativo
│ │ ├── README.md
│ │ └── img
│ │ ├── api-keys-openweathermap.png
│ │ ├── blog.png
│ │ └── cover-escenario.png
├── css
│ └── main.css
├── index.html
└── js
│ ├── WeatherApiService.js
│ └── main.js
├── 03-Vue-basic-widget
├── _docs
│ └── migracion-widget-vueJS
│ │ ├── README.md
│ │ └── img
│ │ └── cover-migracion-widget.png
├── css
│ └── main.css
├── index.html
├── js
│ ├── WeatherApiService.js
│ └── widget-weather.js
└── post.html
├── 04-0-Estructura-componentes-devtools
└── _docs
│ └── estructura-componentes-y-dev-tools
│ ├── README.md
│ └── img
│ ├── comments-in-action.gif
│ ├── cover-devtools.png
│ └── cover-estructura-componentes.png
├── 04-CommentsItem
├── _docs
│ └── componente-comentario- CommentsItem
│ │ ├── README.md
│ │ └── img
│ │ └── cover-comments-item.png
├── css
│ └── main.css
├── index.html
├── js
│ ├── WeatherApiService.js
│ ├── components
│ │ └── CommentsItem.js
│ ├── widget-comments.js
│ └── widget-weather.js
└── post.html
├── 05-CommentsList
├── _docs
│ └── componente-comentario- CommentsList
│ │ ├── README.md
│ │ └── img
│ │ └── cover-comments-list.png
├── css
│ └── main.css
├── index.html
├── js
│ ├── WeatherApiService.js
│ ├── components
│ │ ├── CommentsItem.js
│ │ └── CommentsList.js
│ ├── widget-comments.js
│ └── widget-weather.js
└── post.html
├── 06-CommentsForm
├── _docs
│ └── componente-form- CommentsForm
│ │ ├── README.md
│ │ └── img
│ │ └── cover-comments-form.png
├── css
│ └── main.css
├── index.html
├── js
│ ├── WeatherApiService.js
│ ├── components
│ │ ├── CommentsForm.js
│ │ ├── CommentsItem.js
│ │ └── CommentsList.js
│ ├── widget-comments.js
│ └── widget-weather.js
└── post.html
├── 07-IDS-timestamps
├── _docs
│ └── uuid-timestamp-axios
│ │ ├── README.md
│ │ └── img
│ │ └── cover-buenas-practicas.png
├── css
│ └── main.css
├── index.html
├── js
│ ├── WeatherApiService.js
│ ├── components
│ │ ├── CommentsForm.js
│ │ ├── CommentsItem.js
│ │ └── CommentsList.js
│ ├── helpers
│ │ └── index.js
│ ├── widget-comments.js
│ └── widget-weather.js
└── post.html
├── 08-modular-components-ordered-comments
├── README.md
├── _docs
│ └── modulos-nativos-lodash
│ │ ├── README.md
│ │ └── img
│ │ └── cover-modulos-nativos.png
├── css
│ └── main.css
├── index.html
├── js
│ ├── components
│ │ ├── CommentsForm.js
│ │ ├── CommentsItem.js
│ │ └── CommentsList.js
│ ├── data
│ │ └── comments.js
│ ├── helpers
│ │ └── index.js
│ ├── services
│ │ └── WeatherApiService.js
│ ├── widget-comments.js
│ └── widget-weather.js
└── post.html
├── 09-cli-version-modular-components
├── README.md
├── _docs
│ └── cli-version-modular-components
│ │ ├── README.md
│ │ └── img
│ │ └── cover-spa.png
├── babel.config.js
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ ├── BlogPost.vue
│ │ ├── CommentsForm.vue
│ │ ├── CommentsItem.vue
│ │ ├── CommentsList.vue
│ │ ├── FeaturedPost.vue
│ │ ├── Footer.vue
│ │ ├── Header.vue
│ │ ├── LongFeatured.vue
│ │ ├── Post.vue
│ │ ├── Widget.vue
│ │ └── WidgetTemperature.vue
│ ├── css
│ │ └── main.css
│ ├── data
│ │ └── comments.js
│ ├── main.js
│ ├── pages
│ │ ├── Home.vue
│ │ └── Post.vue
│ └── services
│ │ └── WeatherApiService.js
├── yarn-error.log
└── yarn.lock
├── 10-routes-home-post
├── README.md
├── _docs
│ └── routes
│ │ ├── README.md
│ │ └── img
│ │ └── cover-routes.png
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
└── src
│ ├── App.vue
│ ├── assets
│ └── logo.png
│ ├── components
│ ├── BlogPost.vue
│ ├── CommentsForm.vue
│ ├── CommentsItem.vue
│ ├── CommentsList.vue
│ ├── FeaturedPost.vue
│ ├── Footer.vue
│ ├── Header.vue
│ ├── LongFeatured.vue
│ ├── Widget.vue
│ └── WidgetTemperature.vue
│ ├── css
│ └── main.css
│ ├── data
│ ├── comments.js
│ └── content_post.js
│ ├── main.js
│ ├── pages
│ ├── Home.vue
│ └── Post.vue
│ ├── router
│ └── index.js
│ └── services
│ └── WeatherApiService.js
├── 11-api-news
├── README.md
├── _docs
│ └── api-news
│ │ ├── README.md
│ │ └── img
│ │ └── cover-api-news.png
├── babel.config.js
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
└── src
│ ├── App.vue
│ ├── assets
│ └── logo.png
│ ├── components
│ ├── BlogPost.vue
│ ├── CommentsForm.vue
│ ├── CommentsItem.vue
│ ├── CommentsList.vue
│ ├── FeaturedPost.vue
│ ├── Footer.vue
│ ├── Header.vue
│ ├── LongFeatured.vue
│ ├── Widget.vue
│ └── WidgetTemperature.vue
│ ├── css
│ └── main.css
│ ├── data
│ ├── comments.js
│ └── content_post.js
│ ├── main.js
│ ├── pages
│ ├── Home.vue
│ └── Post.vue
│ ├── router
│ └── index.js
│ └── services
│ ├── NewsApiService.js
│ └── WeatherApiService.js
├── 12-improved-api-news
├── README.md
├── _docs
│ ├── deploy
│ │ ├── README.md
│ │ └── img
│ │ │ └── cover-deploy.png
│ └── routes
│ │ ├── README.md
│ │ └── img
│ │ └── cover-api-news-enhanced.png
├── babel.config.js
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ ├── BlogPost.vue
│ │ ├── CommentsForm.vue
│ │ ├── CommentsItem.vue
│ │ ├── CommentsList.vue
│ │ ├── FeaturedPost.vue
│ │ ├── Footer.vue
│ │ ├── Header.vue
│ │ ├── LongFeatured.vue
│ │ ├── Widget.vue
│ │ └── WidgetTemperature.vue
│ ├── css
│ │ └── main.css
│ ├── helpers
│ │ └── index.js
│ ├── main.js
│ ├── pages
│ │ ├── Home.vue
│ │ └── Post.vue
│ ├── router
│ │ └── index.js
│ └── services
│ │ ├── NewsApiService.js
│ │ └── WeatherApiService.js
└── yarn.lock
├── 13-ssr-nuxt
├── README.md
├── _docs
│ ├── deploy
│ │ ├── README.md
│ │ └── img
│ │ │ └── cover-deploy-ssr-vue.png
│ └── ssr-vue
│ │ ├── README.md
│ │ └── img
│ │ └── cover-ssr-vue.png
├── assets
│ ├── README.md
│ └── css
│ │ └── main.css
├── components
│ ├── BlogPost.vue
│ ├── CommentsForm.vue
│ ├── CommentsItem.vue
│ ├── CommentsList.vue
│ ├── FeaturedPost.vue
│ ├── Footer.vue
│ ├── Header.vue
│ ├── Logo.vue
│ ├── LongFeatured.vue
│ ├── README.md
│ ├── Widget.vue
│ └── WidgetTemperature.vue
├── dist
│ ├── .nojekyll
│ ├── 200.html
│ ├── README.md
│ ├── _nuxt
│ │ ├── 2c1ac2a53ef2ef833af7.js
│ │ ├── 4c4b38d6bd1c33e6ee8a.js
│ │ ├── 73e4f610bbb9fe8219ce.js
│ │ ├── 78b88a9ee31fef55d061.js
│ │ ├── 88d3e6aa3afa7eed289f.js
│ │ ├── 8b8055573c69ebb22744.js
│ │ ├── LICENSES
│ │ ├── c505c8736ecc73a90fe9.js
│ │ ├── d9747f26a86515fe0a2e.js
│ │ └── fe4e4908b97c1f500bed.js
│ ├── favicon.ico
│ └── index.html
├── helpers
│ └── index.js
├── layouts
│ ├── README.md
│ └── default.vue
├── middleware
│ └── README.md
├── nuxt.config.js
├── package-lock.json
├── package.json
├── pages
│ ├── README.md
│ ├── index.vue
│ └── post
│ │ └── _id.vue
├── plugins
│ └── README.md
├── server
│ └── index.js
├── services
│ ├── NewsApiService.js
│ └── WeatherApiService.js
├── static
│ ├── README.md
│ └── favicon.ico
├── store
│ └── README.md
└── yarn.lock
└── 14-general-questions-from-backend
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/00--Introduccion/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/00--Introduccion/_docs/planteamiento-refactor-frontend/img/cover-que-aprenderas-este-curso.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/00--Introduccion/_docs/planteamiento-refactor-frontend/img/cover-que-aprenderas-este-curso.png
--------------------------------------------------------------------------------
/00--Introduccion/_docs/por-que-vuejs/README.md:
--------------------------------------------------------------------------------
1 | # ¿Por qué VueJS?
2 |
3 | [](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0)
4 |
5 | _El curso [Migrando a VueJS progresivamente desde 0](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0) está disponible a través de [CodelyTV](https://pro.codely.tv/)_
6 |
7 |
8 | ## Vue es una buena opción de Framework
9 |
10 | - Delegación código del servidor al navegador → mucho código JS conectado con HTML/CSS que manejar
11 | - Los Frameworks Javascript (Angular, React o Vue) nos ayudan a estructurar esté código JS en el cliente
12 | - Caracteristicas de Vue
13 | - Fácil de utlizar (y de empezar a trabajar con él)
14 | - Versatil (nos ayuda en las tipicas operaciones necesarias en cliente)
15 | - Buen rendimiento
16 | - Mantenible
17 | - Testeable
18 |
19 | ## Ideas Claras de Vue
20 |
21 | - El core de view es muy sencillo (modulos aparte para features que no son básicas)
22 | - Vue es Progresivo → podemos aplicarlo sólo a una parte de nuestra aplicación
23 | - Aunque Vue tiene el ecosistema correspondiente si queremos hacer aplicación completa (Vuex + Vue-Router)
24 | - Con Vue podemos crear componentes reutilizables (cada uno con su JS, su HTML y su CSS) → y tenerlo todo junto en archivos `.vue`
25 | - Utiliza Virtual DOM para optimizar actualización del DOM
26 | https://vuejs.org/v2/guide/render-function.html#Nodes-Trees-and-the-Virtual-DOM
27 | - Parte de tecnologías clásicas (HTML y CSS) y las expande para simplificar el desarrollo (directivas y scoped)
28 |
29 | ## Comparativa con otros Frameworks
30 |
31 | En la propia página de Vue tienen un apartado para hablar sobre similitudes y diferencias con otros frameworks → [Comparison with Other Frameworks](https://vuejs.org/v2/guide/comparison.html)
32 |
33 | Ahí puedes leer la comparativa en detalle con React por ejemplo
34 |
35 | Aunque la elección del framework depende del proyecto y del equipo, en mi opinión, la decisión sobre un framework frontend se debe basar en:
36 | - fácil de aprender
37 | - escalable
38 | - buena performance
39 | - que permita buena organización de componentes
40 | - que permita buena gestion de estado (que se entienda bien la app)
41 | - buen ecosistema de librerias y plugins
42 | - buena comunidad detrás
43 |
44 | Y según estos parámetros VueJS es un winner! 😎
45 |
46 | ## Referencias
47 |
48 | - [Por qué elegir VueJS: 5 razones para considerarlo nuestro próximo framework de referencia](https://www.genbeta.com/desarrollo/por-que-elegir-vuejs-5-razones-para-considerarlo-nuestro-proximo-framework-de-referencia)
49 | - [Comparison with Other Frameworks | VueJS](https://vuejs.org/v2/guide/comparison.html)
50 | - [3 reasons to use Vue.js in your next web project](https://prismic.io/blog/3-reasons-to-use-vuejs-in-your-next-web-project)
51 | - [From Zero to Hero with Vue - But first, why Vue?](https://medium.freecodecamp.org/from-zero-to-hero-with-vue-why-vue-8c7e981b494)
--------------------------------------------------------------------------------
/00--Introduccion/_docs/por-que-vuejs/img/cover-por-que-vue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/00--Introduccion/_docs/por-que-vuejs/img/cover-por-que-vue.png
--------------------------------------------------------------------------------
/01-HTML-CSS/_download/proyecto-base.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/01-HTML-CSS/_download/proyecto-base.zip
--------------------------------------------------------------------------------
/01-HTML-CSS/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
--------------------------------------------------------------------------------
/01-HTML-CSS/js/main.js:
--------------------------------------------------------------------------------
1 | console.log("hey!")
--------------------------------------------------------------------------------
/02-WeatherApiService/_docs/escenario-HTML-CSS-JS-Nativo/README.md:
--------------------------------------------------------------------------------
1 | # Escenario - HTML+CSS+JS Nativo
2 |
3 | [](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0)
4 |
5 | _El curso [Migrando a VueJS progresivamente desde 0](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0) está disponible a través de [CodelyTV](https://pro.codely.tv/)_
6 |
7 |
8 | ---
9 |
10 | Vamos a tomar como punto de partida del curso uno de los [ejemplos que tenemos disponibles desde la página oficial de Bootstrap](https://getbootstrap.com/docs/4.3/examples/)
11 |
12 | Conretamente este:
13 |
14 | [](https://getbootstrap.com/docs/4.3/examples/blog/)
15 |
16 | Este código base lo podéis descargar en [un zip desde el repo](https://github.com/CodelyTV/vue-progressive-migration-course/raw/master/01-HTML-CSS/_download/proyecto-base.zip)
17 |
18 | A partir de este código HTML vamos a crear un widget utilizando la API de [OpenWeatherMap](https://openweathermap.org/)
19 |
20 | ## Cómo obtener la API Key de OpenWeatherMap
21 |
22 | 1. Desde la [Home de OpenWeatherMap](https://openweathermap.org/)
23 | 2. Nos registramos desde la opción [**Sign Up**](https://home.openweathermap.org/users/sign_up)
24 | 3. Una vez registrados, utilizamos nuestro usuario y contraseña para identificarnos en el site a través de la página [**Sign In**](https://home.openweathermap.org/users/sign_in)
25 | 4. Nos vamos a la página [**API Keys**](https://home.openweathermap.org/api_keys) y desde ahí generamos nuestra API Key
26 |
27 | [](https://home.openweathermap.org/api_keys)
28 |
29 | ## Cargando los `scripts`
30 |
31 | Nos encontramos estos `scripts` en nuestro proyecto base
32 |
33 | ```html
34 |
35 |
36 | ```
37 |
38 | - `vue.js` → Cargamos la libreria VueJS con [uno de los métodos que recomiendan desde su página oficial](https://vuejs.org/v2/guide/#Getting-Started)
39 | - `main.js` → Archivo dummy para comprobar que funciona la carga de JS desde el HTML
40 |
41 | Vamos a añadir un par mas
42 |
43 | ```html
44 |
45 |
46 | ```
47 |
48 | - `axios.min.js` → Carganmos [**axios**](https://github.com/axios/axios), una de las librerias más populares para gestionar peticiones AJAX
49 | - `WeatherApiService.js` → Aquí añadiremos la lógica de conexión con la API de OpenWeatherMap de manera agnostica a cualquier framework
50 |
51 |
52 | ## `WeatherApiService.js`
53 |
54 | ```js
55 | class WeatherApiService {
56 | constructor(api_key) {
57 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(this, api_key)
58 | }
59 | findWeather(location) {
60 | const url = this.getUrlApiWeatherSearch(location)
61 | return axios.get(url)
62 | .then(({data}) => data)
63 | .then(({ main }) => main)
64 | }
65 | getUrlApiWeatherSearch(api_key, location) {
66 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
67 | }
68 | }
69 | ```
70 |
71 | Mediante esta clase, que instanciaremos desde nuestro `main.js`, tendremos disponible el método `findWeather` el cual nos devolverá los datos del tiempo de la ciudad que le pasemos
72 |
73 | ---
74 |
75 | El código correspondiente a esta lección lo tienes disponible [aqui](https://github.com/CodelyTV/vue-progressive-migration-course/tree/master/02-WeatherApiService)
76 |
77 |
--------------------------------------------------------------------------------
/02-WeatherApiService/_docs/escenario-HTML-CSS-JS-Nativo/img/api-keys-openweathermap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/02-WeatherApiService/_docs/escenario-HTML-CSS-JS-Nativo/img/api-keys-openweathermap.png
--------------------------------------------------------------------------------
/02-WeatherApiService/_docs/escenario-HTML-CSS-JS-Nativo/img/blog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/02-WeatherApiService/_docs/escenario-HTML-CSS-JS-Nativo/img/blog.png
--------------------------------------------------------------------------------
/02-WeatherApiService/_docs/escenario-HTML-CSS-JS-Nativo/img/cover-escenario.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/02-WeatherApiService/_docs/escenario-HTML-CSS-JS-Nativo/img/cover-escenario.png
--------------------------------------------------------------------------------
/02-WeatherApiService/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
--------------------------------------------------------------------------------
/02-WeatherApiService/js/WeatherApiService.js:
--------------------------------------------------------------------------------
1 | class WeatherApiService {
2 | constructor(api_key) {
3 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(this, api_key)
4 | }
5 | findWeather(location) {
6 | const url = this.getUrlApiWeatherSearch(location)
7 | return axios.get(url)
8 | .then(({data}) => data)
9 | .then(({ main }) => main)
10 | }
11 | getUrlApiWeatherSearch(api_key, location) {
12 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
13 | }
14 | }
--------------------------------------------------------------------------------
/02-WeatherApiService/js/main.js:
--------------------------------------------------------------------------------
1 | console.log("hey!")
--------------------------------------------------------------------------------
/03-Vue-basic-widget/_docs/migracion-widget-vueJS/img/cover-migracion-widget.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/03-Vue-basic-widget/_docs/migracion-widget-vueJS/img/cover-migracion-widget.png
--------------------------------------------------------------------------------
/03-Vue-basic-widget/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
126 |
127 | .comments_form, .comments {
128 | margin-bottom: 50px;
129 | }
130 |
--------------------------------------------------------------------------------
/03-Vue-basic-widget/js/WeatherApiService.js:
--------------------------------------------------------------------------------
1 | class WeatherApiService {
2 | constructor(api_key) {
3 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(this, api_key)
4 | }
5 | findWeather(location) {
6 | const url = this.getUrlApiWeatherSearch(location)
7 | return axios.get(url)
8 | .then(({data}) => data)
9 | .then(({ main }) => main)
10 | }
11 | getUrlApiWeatherSearch(api_key, location) {
12 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
13 | }
14 | }
--------------------------------------------------------------------------------
/03-Vue-basic-widget/js/widget-weather.js:
--------------------------------------------------------------------------------
1 | const API_KEY = '627eb53a00b46c56672e5fef2aa41986'
2 | const service = new WeatherApiService(API_KEY)
3 |
4 | const DEFAULT_QUERY='Barcelona'
5 |
6 | new Vue({
7 | el: "#widget",
8 | data: {
9 | query: DEFAULT_QUERY,
10 | temp: 0,
11 | temp_max: 0,
12 | temp_min: 0,
13 | },
14 | created: function () {
15 | service.findWeather(DEFAULT_QUERY)
16 | .then(this.setWeatherData)
17 | },
18 | methods: {
19 | findWeather: function() {
20 | service.findWeather(this.query)
21 | .then(this.setWeatherData)
22 | },
23 | setWeatherData: function({ temp, temp_max, temp_min }) {
24 | this.temp = temp
25 | this.temp_max = temp_max
26 | this.temp_min = temp_min
27 | }
28 | }
29 | })
30 |
--------------------------------------------------------------------------------
/04-0-Estructura-componentes-devtools/_docs/estructura-componentes-y-dev-tools/README.md:
--------------------------------------------------------------------------------
1 | # Planteamiento estructura de componentes y Vue.js DevTools
2 |
3 | _El curso [Migrando a VueJS progresivamente desde 0](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0) está disponible a través de [CodelyTV](https://pro.codely.tv/)_
4 |
5 | ## Refactorizando a estructura componentes
6 |
7 | [](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0)
8 |
9 |
10 | En los siguientes videos vamos a construir una serie de componentes que nos permitan implementar esta funcionalidad
11 |
12 | 
13 |
14 | con este código en el HTML
15 |
16 | ```html
17 |
21 | ```
22 |
23 | ## Vue.js DevTools
24 |
25 | [](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0)
26 |
27 | Para trabajar con Vue en Chrome utilizaremos esta extensión que te puedes instalar desde [aqui](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=es)
28 |
--------------------------------------------------------------------------------
/04-0-Estructura-componentes-devtools/_docs/estructura-componentes-y-dev-tools/img/comments-in-action.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/04-0-Estructura-componentes-devtools/_docs/estructura-componentes-y-dev-tools/img/comments-in-action.gif
--------------------------------------------------------------------------------
/04-0-Estructura-componentes-devtools/_docs/estructura-componentes-y-dev-tools/img/cover-devtools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/04-0-Estructura-componentes-devtools/_docs/estructura-componentes-y-dev-tools/img/cover-devtools.png
--------------------------------------------------------------------------------
/04-0-Estructura-componentes-devtools/_docs/estructura-componentes-y-dev-tools/img/cover-estructura-componentes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/04-0-Estructura-componentes-devtools/_docs/estructura-componentes-y-dev-tools/img/cover-estructura-componentes.png
--------------------------------------------------------------------------------
/04-CommentsItem/_docs/componente-comentario- CommentsItem/img/cover-comments-item.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/04-CommentsItem/_docs/componente-comentario- CommentsItem/img/cover-comments-item.png
--------------------------------------------------------------------------------
/04-CommentsItem/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
126 |
127 | .comments_form, .comments {
128 | margin-bottom: 50px;
129 | }
130 |
--------------------------------------------------------------------------------
/04-CommentsItem/js/WeatherApiService.js:
--------------------------------------------------------------------------------
1 | class WeatherApiService {
2 | constructor(api_key) {
3 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(this, api_key)
4 | }
5 | findWeather(location) {
6 | const url = this.getUrlApiWeatherSearch(location)
7 | return axios.get(url)
8 | .then(({data}) => data)
9 | .then(({ main }) => main)
10 | }
11 | getUrlApiWeatherSearch(api_key, location) {
12 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
13 | }
14 | }
--------------------------------------------------------------------------------
/04-CommentsItem/js/components/CommentsItem.js:
--------------------------------------------------------------------------------
1 | const templateCommentsItem = `
2 |
15 | `
16 |
17 | Vue.component('comments-item', {
18 | props: {
19 | comment: {
20 | type: String,
21 | required: true
22 | },
23 | username: {
24 | type: String,
25 | required: true
26 | },
27 | avatar: {
28 | type: String,
29 | default: 'https://iupac.org/cms/wp-content/uploads/2018/05/default-avatar-300x300.png'
30 | },
31 | date: {}
32 | },
33 | template: templateCommentsItem
34 | })
35 |
36 | // '{{ username }}
',
--------------------------------------------------------------------------------
/04-CommentsItem/js/widget-comments.js:
--------------------------------------------------------------------------------
1 | new Vue({
2 | el: "#comments_block"
3 | })
--------------------------------------------------------------------------------
/04-CommentsItem/js/widget-weather.js:
--------------------------------------------------------------------------------
1 | const API_KEY = '627eb53a00b46c56672e5fef2aa41986'
2 | const service = new WeatherApiService(API_KEY)
3 |
4 | const DEFAULT_QUERY='Barcelona'
5 |
6 | new Vue({
7 | el: "#widget",
8 | data: {
9 | message: 'sdsdsdf',
10 | query: DEFAULT_QUERY,
11 | temp: 0,
12 | temp_max: 0,
13 | temp_min: 0,
14 | },
15 | created: function () {
16 | service.findWeather(DEFAULT_QUERY)
17 | .then(this.setWeatherData)
18 | },
19 | methods: {
20 | findWeather: function() {
21 | service.findWeather(this.query)
22 | .then(this.setWeatherData)
23 | },
24 | setWeatherData: function({ temp, temp_max, temp_min }) {
25 | this.temp = temp
26 | this.temp_max = temp_max
27 | this.temp_min = temp_min
28 | }
29 | }
30 | })
31 |
--------------------------------------------------------------------------------
/05-CommentsList/_docs/componente-comentario- CommentsList/img/cover-comments-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/05-CommentsList/_docs/componente-comentario- CommentsList/img/cover-comments-list.png
--------------------------------------------------------------------------------
/05-CommentsList/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
126 |
127 | .comments_form, .comments {
128 | margin-bottom: 50px;
129 | }
130 |
--------------------------------------------------------------------------------
/05-CommentsList/js/WeatherApiService.js:
--------------------------------------------------------------------------------
1 | class WeatherApiService {
2 | constructor(api_key) {
3 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(this, api_key)
4 | }
5 | findWeather(location) {
6 | const url = this.getUrlApiWeatherSearch(location)
7 | return axios.get(url)
8 | .then(({data}) => data)
9 | .then(({ main }) => main)
10 | }
11 | getUrlApiWeatherSearch(api_key, location) {
12 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
13 | }
14 | }
--------------------------------------------------------------------------------
/05-CommentsList/js/components/CommentsItem.js:
--------------------------------------------------------------------------------
1 | const templateCommentsItem = `
2 |
15 | `
16 |
17 | Vue.component('comments-item', {
18 | props: {
19 | comment: {
20 | type: String,
21 | required: true
22 | },
23 | username: {
24 | type: String,
25 | required: true
26 | },
27 | avatar: {
28 | type: String,
29 | default: 'https://iupac.org/cms/wp-content/uploads/2018/05/default-avatar-300x300.png'
30 | },
31 | date: {}
32 | },
33 | template: templateCommentsItem
34 | })
--------------------------------------------------------------------------------
/05-CommentsList/js/components/CommentsList.js:
--------------------------------------------------------------------------------
1 | const templateCommentsList = `
2 |
3 |
Comments
4 |
5 |
13 |
14 |
15 | `
16 |
17 | Vue.component('comments-list', {
18 | props: ['comments'],
19 | template: templateCommentsList
20 | })
--------------------------------------------------------------------------------
/05-CommentsList/js/widget-comments.js:
--------------------------------------------------------------------------------
1 | const comments_data = [
2 | {
3 | id: 1,
4 | username: "juanma",
5 | date: "Today, 20:00",
6 | comment: "sdfsdf"
7 | },
8 | {
9 | id: 2,
10 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/m103.jpg",
11 | username: "admin",
12 | date: "Today, 2:38",
13 | comment: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
14 | },
15 | {
16 | id: 3,
17 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/w102.jpg",
18 | username: "maslarino",
19 | date: "Yesterday, 5:03 PM",
20 | comment: "Sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
21 | }
22 | ]
23 |
24 | new Vue({
25 | el: "#comments_block",
26 | data: {
27 | comments: comments_data
28 | }
29 | })
--------------------------------------------------------------------------------
/05-CommentsList/js/widget-weather.js:
--------------------------------------------------------------------------------
1 | const API_KEY = '627eb53a00b46c56672e5fef2aa41986'
2 | const service = new WeatherApiService(API_KEY)
3 |
4 | const DEFAULT_QUERY='Barcelona'
5 |
6 | new Vue({
7 | el: "#widget",
8 | data: {
9 | message: 'sdsdsdf',
10 | query: DEFAULT_QUERY,
11 | temp: 0,
12 | temp_max: 0,
13 | temp_min: 0,
14 | },
15 | created: function () {
16 | service.findWeather(DEFAULT_QUERY)
17 | .then(this.setWeatherData)
18 | },
19 | methods: {
20 | findWeather: function() {
21 | service.findWeather(this.query)
22 | .then(this.setWeatherData)
23 | },
24 | setWeatherData: function({ temp, temp_max, temp_min }) {
25 | this.temp = temp
26 | this.temp_max = temp_max
27 | this.temp_min = temp_min
28 | }
29 | }
30 | })
31 |
--------------------------------------------------------------------------------
/06-CommentsForm/_docs/componente-form- CommentsForm/img/cover-comments-form.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/06-CommentsForm/_docs/componente-form- CommentsForm/img/cover-comments-form.png
--------------------------------------------------------------------------------
/06-CommentsForm/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
126 |
127 | .comments_form, .comments {
128 | margin-bottom: 50px;
129 | }
130 |
--------------------------------------------------------------------------------
/06-CommentsForm/js/WeatherApiService.js:
--------------------------------------------------------------------------------
1 | class WeatherApiService {
2 | constructor(api_key) {
3 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(this, api_key)
4 | }
5 | findWeather(location) {
6 | const url = this.getUrlApiWeatherSearch(location)
7 | return axios.get(url)
8 | .then(({data}) => data)
9 | .then(({ main }) => main)
10 | }
11 | getUrlApiWeatherSearch(api_key, location) {
12 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
13 | }
14 | }
--------------------------------------------------------------------------------
/06-CommentsForm/js/components/CommentsForm.js:
--------------------------------------------------------------------------------
1 | const templateCommentsForm = `
2 |
26 | `
27 |
28 | Vue.component('comments-form', {
29 | props: ['addComment'],
30 | data: function() {
31 | return {
32 | username: '',
33 | comment: ''
34 | }
35 | },
36 | methods: {
37 | handleSubmit() {
38 | this.$emit("add-comment", {
39 | username: this.username,
40 | comment: this.comment
41 | });
42 | }
43 | },
44 | template: templateCommentsForm
45 | })
--------------------------------------------------------------------------------
/06-CommentsForm/js/components/CommentsItem.js:
--------------------------------------------------------------------------------
1 | const templateCommentsItem = `
2 |
15 | `
16 |
17 | Vue.component('comments-item', {
18 | props: {
19 | comment: {
20 | type: String,
21 | required: true
22 | },
23 | username: {
24 | type: String,
25 | required: true
26 | },
27 | avatar: {
28 | type: String,
29 | default: 'https://iupac.org/cms/wp-content/uploads/2018/05/default-avatar-300x300.png'
30 | },
31 | date: {}
32 | },
33 | template: templateCommentsItem
34 | })
--------------------------------------------------------------------------------
/06-CommentsForm/js/components/CommentsList.js:
--------------------------------------------------------------------------------
1 | const templateCommentsList = `
2 |
3 |
Comments
4 |
5 |
13 |
14 |
15 | `
16 |
17 | Vue.component('comments-list', {
18 | props: ['comments'],
19 | template: templateCommentsList
20 | })
--------------------------------------------------------------------------------
/06-CommentsForm/js/widget-comments.js:
--------------------------------------------------------------------------------
1 | const comments_data = [
2 | {
3 | id: 1,
4 | username: "juanma",
5 | date: "Today, 20:00",
6 | comment: "sdfsdf"
7 | },
8 | {
9 | id: 2,
10 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/m103.jpg",
11 | username: "admin",
12 | date: "Today, 2:38",
13 | comment: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
14 | },
15 | {
16 | id: 3,
17 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/w102.jpg",
18 | username: "maslarino",
19 | date: "Yesterday, 5:03 PM",
20 | comment: "Sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
21 | }
22 | ]
23 |
24 | new Vue({
25 | el: "#comments_block",
26 | data: {
27 | comments: comments_data
28 | },
29 | methods: {
30 | addComment: function({ username, comment }) {
31 | comments_data.push({
32 | id: comments_data.length + 1,
33 | username,
34 | comment,
35 | date: "Today ..."
36 | })
37 | }
38 | }
39 | })
--------------------------------------------------------------------------------
/06-CommentsForm/js/widget-weather.js:
--------------------------------------------------------------------------------
1 | const API_KEY = '627eb53a00b46c56672e5fef2aa41986'
2 | const service = new WeatherApiService(API_KEY)
3 |
4 | const DEFAULT_QUERY='Barcelona'
5 |
6 | new Vue({
7 | el: "#widget",
8 | data: {
9 | message: 'sdsdsdf',
10 | query: DEFAULT_QUERY,
11 | temp: 0,
12 | temp_max: 0,
13 | temp_min: 0,
14 | },
15 | created: function () {
16 | service.findWeather(DEFAULT_QUERY)
17 | .then(this.setWeatherData)
18 | },
19 | methods: {
20 | findWeather: function() {
21 | service.findWeather(this.query)
22 | .then(this.setWeatherData)
23 | },
24 | setWeatherData: function({ temp, temp_max, temp_min }) {
25 | this.temp = temp
26 | this.temp_max = temp_max
27 | this.temp_min = temp_min
28 | }
29 | }
30 | })
31 |
--------------------------------------------------------------------------------
/07-IDS-timestamps/_docs/uuid-timestamp-axios/img/cover-buenas-practicas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/07-IDS-timestamps/_docs/uuid-timestamp-axios/img/cover-buenas-practicas.png
--------------------------------------------------------------------------------
/07-IDS-timestamps/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
126 |
127 | .comments_form, .comments {
128 | margin-bottom: 50px;
129 | }
130 |
--------------------------------------------------------------------------------
/07-IDS-timestamps/js/WeatherApiService.js:
--------------------------------------------------------------------------------
1 | class WeatherApiService {
2 | constructor(api_key) {
3 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(this, api_key)
4 | }
5 | findWeather(location) {
6 | const url = this.getUrlApiWeatherSearch(location)
7 | return axios.get(url)
8 | .then(({data}) => data)
9 | .then(({ main }) => main)
10 | }
11 | getUrlApiWeatherSearch(api_key, location) {
12 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
13 | }
14 | }
--------------------------------------------------------------------------------
/07-IDS-timestamps/js/components/CommentsForm.js:
--------------------------------------------------------------------------------
1 | const templateCommentsForm = `
2 |
26 | `
27 |
28 | Vue.component('comments-form', {
29 | props: ['addComment'],
30 | data: function() {
31 | return {
32 | username: '',
33 | comment: ''
34 | }
35 | },
36 | methods: {
37 | handleSubmit() {
38 | this.$emit("add-comment", {
39 | username: this.username,
40 | comment: this.comment
41 | });
42 | this.username = this.comment = ''
43 | }
44 | },
45 | template: templateCommentsForm
46 | })
--------------------------------------------------------------------------------
/07-IDS-timestamps/js/components/CommentsItem.js:
--------------------------------------------------------------------------------
1 | const templateCommentsItem = `
2 |
15 | `
16 |
17 | Vue.component('comments-item', {
18 | data: function () {
19 | const formattedTime = moment(this.date).fromNow()
20 | return { formattedTime }
21 | },
22 | props: {
23 | comment: {
24 | type: String,
25 | required: true
26 | },
27 | username: {
28 | type: String,
29 | required: true
30 | },
31 | avatar: {
32 | type: String,
33 | default: 'https://iupac.org/cms/wp-content/uploads/2018/05/default-avatar-300x300.png'
34 | },
35 | date: {}
36 | },
37 | template: templateCommentsItem
38 | })
--------------------------------------------------------------------------------
/07-IDS-timestamps/js/components/CommentsList.js:
--------------------------------------------------------------------------------
1 | const templateCommentsList = `
2 |
3 |
Comments
4 |
5 |
13 |
14 |
15 | `
16 |
17 | Vue.component('comments-list', {
18 | props: ['comments'],
19 | template: templateCommentsList
20 | })
--------------------------------------------------------------------------------
/07-IDS-timestamps/js/helpers/index.js:
--------------------------------------------------------------------------------
1 | const HELPERS = {
2 |
3 | }
4 |
5 | HELPERS.guid = function () {
6 | function s4() {
7 | return Math.floor((1 + Math.random()) * 0x10000)
8 | .toString(16)
9 | .substring(1);
10 | }
11 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
12 | }
--------------------------------------------------------------------------------
/07-IDS-timestamps/js/widget-comments.js:
--------------------------------------------------------------------------------
1 | const comments_data = [
2 | {
3 | id: HELPERS.guid(),
4 | username: "juanma",
5 | date: moment("2018-01-25").valueOf(),
6 | comment: "sdfsdf"
7 | },
8 | {
9 | id: HELPERS.guid(),
10 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/m103.jpg",
11 | username: "admin",
12 | date: moment("2018-03-25").valueOf(),
13 | comment: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
14 | },
15 | {
16 | id: HELPERS.guid(),
17 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/w102.jpg",
18 | username: "maslarino",
19 | date: moment("2018-05-25").valueOf(),
20 | comment: "Sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
21 | }
22 | ]
23 |
24 | new Vue({
25 | el: "#comments_block",
26 | data: {
27 | comments: comments_data
28 | },
29 | methods: {
30 | addComment: function({ username, comment }) {
31 | comments_data.push({
32 | id: HELPERS.guid(),
33 | username,
34 | comment,
35 | date: moment().valueOf()
36 | })
37 | }
38 | }
39 | })
--------------------------------------------------------------------------------
/07-IDS-timestamps/js/widget-weather.js:
--------------------------------------------------------------------------------
1 | const API_KEY = '627eb53a00b46c56672e5fef2aa41986'
2 | const service = new WeatherApiService(API_KEY)
3 |
4 | const DEFAULT_QUERY='Barcelona'
5 |
6 | new Vue({
7 | el: "#widget",
8 | data: {
9 | message: 'sdsdsdf',
10 | query: DEFAULT_QUERY,
11 | temp: 0,
12 | temp_max: 0,
13 | temp_min: 0,
14 | },
15 | created: function () {
16 | service.findWeather(DEFAULT_QUERY)
17 | .then(this.setWeatherData)
18 | },
19 | methods: {
20 | findWeather: function() {
21 | service.findWeather(this.query)
22 | .then(this.setWeatherData)
23 | },
24 | setWeatherData: function({ temp, temp_max, temp_min }) {
25 | this.temp = temp
26 | this.temp_max = temp_max
27 | this.temp_min = temp_min
28 | }
29 | }
30 | })
31 |
--------------------------------------------------------------------------------
/08-modular-components-ordered-comments/README.md:
--------------------------------------------------------------------------------
1 | # Modular Components and ordered comments
2 |
3 | ## Modules in the browser
4 |
5 | Simplify loading w/ the `type="module"` in the `script` tag to _enable_ es2015 modules in the browser
6 |
7 | **`post.html`**
8 |
9 | ```html
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ```
22 |
23 | So then we can do from our `type="module"` file...
24 |
25 | **`widget-comments.js`**
26 |
27 | ```javascript
28 | import CommentsForm from './components/CommentsForm.js'
29 | import CommentsList from './components/CommentsList.js'
30 |
31 | import comments_data from './data/comments.js'
32 | import {guid} from './helpers/index.js'
33 |
34 | new Vue({
35 | ...
36 | })
37 | ```
38 |
39 | ## Comments Order on `computed` property
40 |
41 | Computed Properties → https://vuejs.org/v2/guide/computed.html
42 | Computed Caching vs Methods → https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods
43 |
44 |
45 | We can use `_.orderBy` from the lodash library to provide an ordered comments array available from the template...
46 |
47 | **`components/CommentsList.js`**
48 |
49 | ```javascript
50 | ...
51 | export default {
52 | props: ['comments'],
53 | components: { CommentsItem },
54 | computed: {
55 | orderedComments: function () {
56 | return _.orderBy(this.comments, 'date', 'desc')
57 | }
58 | },
59 | template: templateCommentsList
60 | }
61 | ```
62 |
--------------------------------------------------------------------------------
/08-modular-components-ordered-comments/_docs/modulos-nativos-lodash/README.md:
--------------------------------------------------------------------------------
1 | # Usando módulos con soporte nativo de navegador y Lodash
2 |
3 | [](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0)
4 |
5 | _El curso [Migrando a VueJS progresivamente desde 0](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0) está disponible a través de [CodelyTV](https://pro.codely.tv/)_
6 |
7 | ---
8 |
9 | ## Modulos en el navegador
10 |
11 | En las última versiones de Chrome tenemos ya soporte nativo a los módulos de ES2015. Es decir que podemos reestructurar nuestro proyecto para importar (con [`import`](https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Sentencias/import)) y exportar (con [`export`](https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Sentencias/export)) cosas de nuestros archivos
12 |
13 | Para ello, hay que añadir `type="module"` en el tag `script` para _habilitar_ el uso de modulos es2015 desde en ese fichero
14 |
15 | 👉 El soporte en Navegadores a esta feature lo tienes [aqui](https://caniuse.com/#feat=es6-module)
16 |
17 | **`post.html`**
18 |
19 | ```html
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | ```
32 |
33 | Con esto, ya podemos hacer (en los archivos cargados con `type="module"`)...
34 |
35 | **`widget-comments.js`**
36 |
37 | ```javascript
38 | import CommentsForm from './components/CommentsForm.js'
39 | import CommentsList from './components/CommentsList.js'
40 |
41 | import comments_data from './data/comments.js'
42 | import {guid} from './helpers/index.js'
43 |
44 | new Vue({
45 | ...
46 | components: {}
47 | ...
48 | })
49 | ```
50 |
51 | Cargamos el objeto de configuración que define cada componente y [los registramos localmente con la propiedad `components`](https://vuejs.org/v2/guide/components-registration.html#Local-Registration) del objeto que le pasamos a `new Vue()`
52 |
53 | ## Ordenando los comentarios a través de una propiedad `computed`
54 |
55 | Computed Properties → https://vuejs.org/v2/guide/computed.html
56 | Computed Caching vs Methods → https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods
57 |
58 |
59 | Podemos utilizar [`_.orderBy`](https://lodash.com/docs/4.17.11#orderBy) de la librería [lodash](https://lodash.com/) para dejar disponible (el template de `comments-list`) el array de comentarios ordenado...
60 |
61 | **`components/CommentsList.js`**
62 |
63 | ```javascript
64 | ...
65 | export default {
66 | props: ['comments'],
67 | components: { CommentsItem },
68 | computed: {
69 | orderedComments: function () {
70 | return _.orderBy(this.comments, 'date', 'desc')
71 | }
72 | },
73 | template: templateCommentsList
74 | }
75 | ```
76 |
77 | Bajo la propiedad [`computed`](https://vuejs.org/v2/guide/computed.html) podemos dejar disponibles valores que se van a recalcular cuando alguno de los elementos que utilice para su cálculo, cambie (`this.comments` en nuestro caso)
78 |
79 |
80 | ---
81 |
82 | El código correspondiente a esta lección lo tienes disponible [aqui](https://github.com/CodelyTV/vue-progressive-migration-course/tree/master/08-modular-components-ordered-comments)
83 |
84 |
--------------------------------------------------------------------------------
/08-modular-components-ordered-comments/_docs/modulos-nativos-lodash/img/cover-modulos-nativos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/08-modular-components-ordered-comments/_docs/modulos-nativos-lodash/img/cover-modulos-nativos.png
--------------------------------------------------------------------------------
/08-modular-components-ordered-comments/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
126 |
127 | .comments_form, .comments {
128 | margin-bottom: 50px;
129 | }
130 |
--------------------------------------------------------------------------------
/08-modular-components-ordered-comments/js/components/CommentsForm.js:
--------------------------------------------------------------------------------
1 | const templateCommentsForm = `
2 |
26 | `
27 |
28 | export default {
29 | props: ['addComment'],
30 | data: function() {
31 | return {
32 | username: '',
33 | comment: ''
34 | }
35 | },
36 | methods: {
37 | handleSubmit() {
38 | this.$emit("add-comment", {
39 | username: this.username,
40 | comment: this.comment
41 | });
42 | this.username = this.comment = ''
43 | }
44 | },
45 | template: templateCommentsForm
46 | }
--------------------------------------------------------------------------------
/08-modular-components-ordered-comments/js/components/CommentsItem.js:
--------------------------------------------------------------------------------
1 | const templateCommentsItem = `
2 |
15 | `
16 |
17 | export default Vue.component('comments-item', {
18 | data: function () {
19 | const formattedTime = moment(this.date).fromNow()
20 | return { formattedTime }
21 | },
22 | props: {
23 | comment: {
24 | type: String,
25 | required: true
26 | },
27 | username: {
28 | type: String,
29 | required: true
30 | },
31 | avatar: {
32 | type: String,
33 | default: 'https://iupac.org/cms/wp-content/uploads/2018/05/default-avatar-300x300.png'
34 | },
35 | date: {}
36 | },
37 | template: templateCommentsItem
38 | })
--------------------------------------------------------------------------------
/08-modular-components-ordered-comments/js/components/CommentsList.js:
--------------------------------------------------------------------------------
1 | import CommentsItem from './CommentsItem.js'
2 |
3 | const templateCommentsList = `
4 |
5 |
Comments
6 |
7 |
15 |
16 |
17 | `
18 |
19 | export default {
20 | props: ['comments'],
21 | components: { CommentsItem },
22 | computed: {
23 | orderedComments: function () {
24 | return _.orderBy(this.comments, 'date', 'desc')
25 | }
26 | },
27 | template: templateCommentsList
28 | }
--------------------------------------------------------------------------------
/08-modular-components-ordered-comments/js/data/comments.js:
--------------------------------------------------------------------------------
1 | import {guid} from '../helpers/index.js'
2 |
3 | export default [
4 | {
5 | id: guid(),
6 | username: "juanma",
7 | date: moment("2018-01-25").valueOf(),
8 | comment: "Good Post!"
9 | },
10 | {
11 | id: guid(),
12 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/m103.jpg",
13 | username: "admin",
14 | date: moment("2018-03-25").valueOf(),
15 | comment: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
16 | },
17 | {
18 | id: guid(),
19 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/w102.jpg",
20 | username: "maslarino",
21 | date: moment("2018-05-25").valueOf(),
22 | comment: "Sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
23 | }
24 | ]
--------------------------------------------------------------------------------
/08-modular-components-ordered-comments/js/helpers/index.js:
--------------------------------------------------------------------------------
1 | export const guid = () => {
2 | function s4() {
3 | return Math.floor((1 + Math.random()) * 0x10000)
4 | .toString(16)
5 | .substring(1);
6 | }
7 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
8 | }
--------------------------------------------------------------------------------
/08-modular-components-ordered-comments/js/services/WeatherApiService.js:
--------------------------------------------------------------------------------
1 | export default class WeatherApiService {
2 | constructor(api_key) {
3 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(this, api_key)
4 | }
5 | findWeather(location) {
6 | const url = this.getUrlApiWeatherSearch(location)
7 | return axios.get(url)
8 | .then(({data}) => data)
9 | .then(({ main }) => main)
10 | }
11 | getUrlApiWeatherSearch(api_key, location) {
12 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
13 | }
14 | }
--------------------------------------------------------------------------------
/08-modular-components-ordered-comments/js/widget-comments.js:
--------------------------------------------------------------------------------
1 | import CommentsForm from './components/CommentsForm.js'
2 | import CommentsList from './components/CommentsList.js'
3 |
4 | import comments_data from './data/comments.js'
5 | import {guid} from './helpers/index.js'
6 |
7 | new Vue({
8 | el: "#comments_block",
9 | data: {
10 | comments: comments_data
11 | },
12 | components: {
13 | CommentsForm,
14 | CommentsList
15 | },
16 | methods: {
17 | addComment: function({ username, comment }) {
18 | comments_data.push({
19 | id: guid(),
20 | username,
21 | comment,
22 | date: moment().valueOf()
23 | })
24 | }
25 | }
26 | })
--------------------------------------------------------------------------------
/08-modular-components-ordered-comments/js/widget-weather.js:
--------------------------------------------------------------------------------
1 | import WeatherApiService from './services/WeatherApiService.js'
2 |
3 | const API_KEY = '627eb53a00b46c56672e5fef2aa41986'
4 | const service = new WeatherApiService(API_KEY)
5 |
6 | const DEFAULT_QUERY='Barcelona'
7 |
8 | new Vue({
9 | el: "#widget",
10 | data: {
11 | query: DEFAULT_QUERY,
12 | temp: 0,
13 | temp_max: 0,
14 | temp_min: 0,
15 | },
16 | created: function () {
17 | service.findWeather(DEFAULT_QUERY)
18 | .then(this.setWeatherData)
19 | },
20 | methods: {
21 | findWeather: function() {
22 | service.findWeather(this.query)
23 | .then(this.setWeatherData)
24 | },
25 | setWeatherData: function({ temp, temp_max, temp_min }) {
26 | this.temp = temp
27 | this.temp_max = temp_max
28 | this.temp_min = temp_min
29 | }
30 | }
31 | })
32 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/_docs/cli-version-modular-components/img/cover-spa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/09-cli-version-modular-components/_docs/cli-version-modular-components/img/cover-spa.png
--------------------------------------------------------------------------------
/09-cli-version-modular-components/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
--------------------------------------------------------------------------------
/09-cli-version-modular-components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
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.18.0",
12 | "lodash": "4.17.11",
13 | "moment": "2.22.2",
14 | "uuid": "3.3.2",
15 | "vue": "^2.5.17"
16 | },
17 | "devDependencies": {
18 | "@vue/cli-plugin-babel": "^3.0.4",
19 | "@vue/cli-plugin-eslint": "^3.0.4",
20 | "@vue/cli-service": "^3.0.4",
21 | "vue-template-compiler": "^2.5.17"
22 | },
23 | "eslintConfig": {
24 | "root": true,
25 | "env": {
26 | "node": true
27 | },
28 | "extends": [
29 | "plugin:vue/essential",
30 | "eslint:recommended"
31 | ],
32 | "rules": {},
33 | "parserOptions": {
34 | "parser": "babel-eslint"
35 | }
36 | },
37 | "postcss": {
38 | "plugins": {
39 | "autoprefixer": {}
40 | }
41 | },
42 | "browserslist": [
43 | "> 1%",
44 | "last 2 versions",
45 | "not ie <= 8"
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/09-cli-version-modular-components/public/favicon.ico
--------------------------------------------------------------------------------
/09-cli-version-modular-components/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | demo
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | We're sorry but demo doesn't work properly without JavaScript enabled. Please enable it to continue.
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
18 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/09-cli-version-modular-components/src/assets/logo.png
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/components/BlogPost.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Sample blog post
4 |
January 1, 2014 by Mark
5 |
6 |
This blog post shows a few different types of content that's supported and styled with Bootstrap. Basic typography, images, and code are all supported.
7 |
8 |
Cum sociis natoque penatibus et magnis dis parturient montes , nascetur ridiculus mus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Sed posuere consectetur est at lobortis. Cras mattis consectetur purus sit amet fermentum.
9 |
10 | Curabitur blandit tempus porttitor. Nullam quis risus eget urna mollis ornare vel eu leo. Nullam id dolor id nibh ultricies vehicula ut id elit.
11 |
12 |
Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.
13 |
Heading
14 |
Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
15 |
Sub-heading
16 |
Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
17 |
Example code block
18 |
Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa.
19 |
Sub-heading
20 |
Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.
21 |
22 | Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
23 | Donec id elit non mi porta gravida at eget metus.
24 | Nulla vitae elit libero, a pharetra augue.
25 |
26 |
Donec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue.
27 |
28 | Vestibulum id ligula porta felis euismod semper.
29 | Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
30 | Maecenas sed diam eget risus varius blandit sit amet non magna.
31 |
32 |
Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis.
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/components/CommentsForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/components/CommentsItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/components/CommentsList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Comments
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/components/FeaturedPost.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
World
5 |
8 |
Nov 12
9 |
This is a wider card with supporting text below as a natural lead-in to additional content.
10 |
Continue reading
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/components/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/components/LongFeatured.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Title of a longer featured blog post
6 |
Multiple lines of text that form the lede, informing new readers quickly and efficiently about what's most interesting in this post's contents.
7 |
Continue reading...
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/components/Widget.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
{{ title }}
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/components/WidgetTemperature.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Barcelona
5 | Madrid
6 | Murcia
7 |
8 |
Temp: {{ temp }} | Max: {{ temp_max }} | Min: {{ temp_min }}
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
126 |
127 | .comments_form, .comments {
128 | margin-bottom: 50px;
129 | }
130 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/data/comments.js:
--------------------------------------------------------------------------------
1 | import uuidv4 from 'uuid/v4'
2 | import moment from 'moment'
3 |
4 | export default [
5 | {
6 | id: uuidv4(),
7 | username: "juanma",
8 | date: moment("2018-01-25").valueOf(),
9 | comment: "Good Post!"
10 | },
11 | {
12 | id: uuidv4(),
13 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/m103.jpg",
14 | username: "admin",
15 | date: moment("2018-03-25").valueOf(),
16 | comment: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
17 | },
18 | {
19 | id: uuidv4(),
20 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/w102.jpg",
21 | username: "maslarino",
22 | date: moment("2018-05-25").valueOf(),
23 | comment: "Sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
24 | }
25 | ]
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 |
4 | import './css/main.css'
5 |
6 | Vue.config.productionTip = false
7 |
8 | new Vue({
9 | render: h => h(App)
10 | }).$mount('#app')
11 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/pages/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | From the Firehose
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/pages/Post.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | From the Firehose
12 |
13 |
14 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/09-cli-version-modular-components/src/services/WeatherApiService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export default class WeatherApiService {
4 | constructor(api_key) {
5 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(
6 | this,
7 | api_key
8 | )
9 | }
10 | findWeather(location) {
11 | const url = this.getUrlApiWeatherSearch(location)
12 | return axios
13 | .get(url)
14 | .then(({ data }) => data)
15 | .then(({ main }) => main)
16 | }
17 | getUrlApiWeatherSearch(api_key, location) {
18 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/10-routes-home-post/README.md:
--------------------------------------------------------------------------------
1 | # Routes version
2 |
3 |
4 | `Vue.use` → https://vuejs.org/v2/api/#Vue-use
5 | Plugins → https://vuejs.org/v2/guide/plugins.html
6 |
7 | Getting Started With Vue Router → https://scotch.io/tutorials/getting-started-with-vue-router
8 |
9 |
10 | Routes are handled by the library [`vue-router`](https://github.com/vuejs/vue-router)
11 |
12 | Routes are defined here...
13 |
14 | **`src/router/index.js`**
15 |
16 | ```javascript
17 | import Vue from 'vue'
18 | import Router from 'vue-router'
19 |
20 | Vue.use(Router)
21 |
22 | import Home from '../pages/Home.vue'
23 | import Post from '../pages/Post.vue'
24 |
25 | export default new Router({
26 | mode: 'history',
27 | routes: [
28 | {
29 | path: '/home',
30 | component: Home
31 | },
32 | {
33 | path: '/post',
34 | component: Post
35 | }
36 | ]
37 | })
38 | ```
39 |
40 | Here we tell Vue to manage the previously defined routes...
41 |
42 | **`src/main.js`**
43 |
44 | ```javascript
45 | import Vue from 'vue'
46 | import App from './App.vue'
47 | import router from './router'
48 |
49 | import './css/main.css'
50 |
51 | Vue.config.productionTip = false
52 |
53 | new Vue({
54 | ...App,
55 | router
56 | }).$mount('#app')
57 |
58 | ```
59 |
60 |
61 | And here we use them (where to display the content of the routes and links)...
62 |
63 | **`src/App.vue`**
64 |
65 | ```javascript
66 |
67 |
68 |
69 |
70 |
71 | Home
72 |
73 |
74 | Post
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
88 |
89 | ```
90 |
--------------------------------------------------------------------------------
/10-routes-home-post/_docs/routes/README.md:
--------------------------------------------------------------------------------
1 | # Migramos la gestión de rutas a VueJS
2 |
3 | [](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0)
4 |
5 | _El curso [Migrando a VueJS progresivamente desde 0](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0) está disponible a través de [CodelyTV](https://pro.codely.tv/)_
6 |
7 | ---
8 |
9 | # Rutas con Vue
10 |
11 |
12 | `Vue.use` → https://vuejs.org/v2/api/#Vue-use
13 | Plugins → https://vuejs.org/v2/guide/plugins.html
14 |
15 | Getting Started With Vue Router → https://scotch.io/tutorials/getting-started-with-vue-router
16 |
17 |
18 | Las rutas se definen y manejan con un módulo externo llamado [`vue-router`](https://github.com/vuejs/vue-router)
19 |
20 | ```
21 | npm i -S vue-router
22 | ```
23 |
24 | Las rutas se definen así...
25 |
26 | **`src/router/index.js`**
27 |
28 | ```javascript
29 | import Vue from 'vue'
30 | import Router from 'vue-router'
31 |
32 | Vue.use(Router)
33 |
34 | import Home from '../pages/Home.vue'
35 | import Post from '../pages/Post.vue'
36 |
37 | export default new Router({
38 | mode: 'history',
39 | routes: [
40 | {
41 | path: '/home',
42 | component: Home
43 | },
44 | {
45 | path: '/post',
46 | component: Post
47 | }
48 | ]
49 | })
50 | ```
51 |
52 | Una vez definidas las rutas, las añadimos como parte de la instancia para indicarle a Vue que se encargue de gestionar estas rutas...
53 |
54 | **`src/main.js`**
55 |
56 | ```javascript
57 | import Vue from 'vue'
58 | import App from './App.vue'
59 | import router from './router'
60 |
61 | import './css/main.css'
62 |
63 | Vue.config.productionTip = false
64 |
65 | new Vue({
66 | ...App,
67 | router
68 | }).$mount('#app')
69 |
70 | ```
71 |
72 | Y con `router-view` indicamos en que lugar de nuestra app va a aperecer el diferente contenido según la ruta
73 |
74 | **`src/App.vue`**
75 |
76 | ```javascript
77 |
78 |
79 |
80 |
81 |
82 | Home
83 |
84 |
85 | Post
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
99 |
100 | ```
101 |
102 |
103 | ---
104 |
105 | El código correspondiente a esta lección lo tienes disponible [aqui](https://github.com/CodelyTV/vue-progressive-migration-course/tree/master/10-routes-home-post)
106 |
107 |
--------------------------------------------------------------------------------
/10-routes-home-post/_docs/routes/img/cover-routes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/10-routes-home-post/_docs/routes/img/cover-routes.png
--------------------------------------------------------------------------------
/10-routes-home-post/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
--------------------------------------------------------------------------------
/10-routes-home-post/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
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.18.0",
12 | "lodash": "4.17.11",
13 | "moment": "2.22.2",
14 | "uuid": "3.3.2",
15 | "vue": "^2.5.17",
16 | "vue-router": "3.0.1"
17 | },
18 | "devDependencies": {
19 | "@vue/cli-plugin-babel": "^3.0.4",
20 | "@vue/cli-plugin-eslint": "^3.0.4",
21 | "@vue/cli-service": "^3.0.4",
22 | "vue-template-compiler": "^2.5.17"
23 | },
24 | "eslintConfig": {
25 | "root": true,
26 | "env": {
27 | "node": true
28 | },
29 | "extends": [
30 | "plugin:vue/essential",
31 | "eslint:recommended"
32 | ],
33 | "rules": {},
34 | "parserOptions": {
35 | "parser": "babel-eslint"
36 | }
37 | },
38 | "postcss": {
39 | "plugins": {
40 | "autoprefixer": {}
41 | }
42 | },
43 | "browserslist": [
44 | "> 1%",
45 | "last 2 versions",
46 | "not ie <= 8"
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/10-routes-home-post/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/10-routes-home-post/public/favicon.ico
--------------------------------------------------------------------------------
/10-routes-home-post/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | demo
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | We're sorry but demo doesn't work properly without JavaScript enabled. Please enable it to continue.
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
13 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/10-routes-home-post/src/assets/logo.png
--------------------------------------------------------------------------------
/10-routes-home-post/src/components/BlogPost.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ title }}
4 |
{{ date }} by {{ author }}
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/components/CommentsForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/components/CommentsItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/components/CommentsList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Comments
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/components/FeaturedPost.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
World
5 |
6 | Featured post
7 |
8 |
Nov 12
9 |
This is a wider card with supporting text below as a natural lead-in to additional content.
10 |
Continue reading
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/components/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/components/LongFeatured.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Title of a longer featured blog post
6 |
Multiple lines of text that form the lede, informing new readers quickly and efficiently about what's most interesting in this post's contents.
7 |
Continue reading...
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/components/Widget.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
{{ title }}
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/components/WidgetTemperature.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Barcelona
5 | Madrid
6 | Murcia
7 |
8 |
Temp: {{ temp }} | Max: {{ temp_max }} | Min: {{ temp_min }}
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
126 |
127 | .comments_form, .comments {
128 | margin-bottom: 50px;
129 | }
130 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/data/comments.js:
--------------------------------------------------------------------------------
1 | import uuidv4 from 'uuid/v4'
2 | import moment from 'moment'
3 |
4 | const comments = [
5 | {
6 | id: uuidv4(),
7 | username: "juanma",
8 | date: moment("2018-01-25").valueOf(),
9 | comment: "Good Post!"
10 | },
11 | {
12 | id: uuidv4(),
13 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/m103.jpg",
14 | username: "admin",
15 | date: moment("2018-03-25").valueOf(),
16 | comment: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
17 | },
18 | {
19 | id: uuidv4(),
20 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/w102.jpg",
21 | username: "maslarino",
22 | date: moment("2018-05-25").valueOf(),
23 | comment: "Sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
24 | }
25 | ]
26 |
27 | export default comments
28 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/data/content_post.js:
--------------------------------------------------------------------------------
1 | const content = `
2 | This blog post shows a few different types of content that's supported and styled with Bootstrap. Basic typography, images, and code are all supported.
3 |
4 | Cum sociis natoque penatibus et magnis dis parturient montes , nascetur ridiculus mus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Sed posuere consectetur est at lobortis. Cras mattis consectetur purus sit amet fermentum.
5 |
6 | Curabitur blandit tempus porttitor. Nullam quis risus eget urna mollis ornare vel eu leo. Nullam id dolor id nibh ultricies vehicula ut id elit.
7 |
8 | Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.
9 | Heading
10 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
11 | Sub-heading
12 | Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
13 | Example code block
14 | Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa.
15 | Sub-heading
16 | Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.
17 |
18 | Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
19 | Donec id elit non mi porta gravida at eget metus.
20 | Nulla vitae elit libero, a pharetra augue.
21 |
22 | Donec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue.
23 |
24 | Vestibulum id ligula porta felis euismod semper.
25 | Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
26 | Maecenas sed diam eget risus varius blandit sit amet non magna.
27 |
28 | Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis.
29 | `
30 |
31 | export default content
--------------------------------------------------------------------------------
/10-routes-home-post/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 |
5 | import './css/main.css'
6 |
7 | Vue.config.productionTip = false
8 |
9 | new Vue({
10 | ...App,
11 | router
12 | }).$mount('#app')
13 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/pages/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/10-routes-home-post/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | Vue.use(Router)
5 |
6 | import Home from '../pages/Home.vue'
7 | import Post from '../pages/Post.vue'
8 |
9 | export default new Router({
10 | mode: 'history',
11 | routes: [
12 | { path: '/', component: Home },
13 | { path: '/home', component: Home },
14 | { path: '/post', component: Post }
15 | ]
16 | })
--------------------------------------------------------------------------------
/10-routes-home-post/src/services/WeatherApiService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export default class WeatherApiService {
4 | constructor(api_key) {
5 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(
6 | this,
7 | api_key
8 | )
9 | }
10 | findWeather(location) {
11 | const url = this.getUrlApiWeatherSearch(location)
12 | return axios
13 | .get(url)
14 | .then(({ data }) => data)
15 | .then(({ main }) => main)
16 | }
17 | getUrlApiWeatherSearch(api_key, location) {
18 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/11-api-news/_docs/api-news/img/cover-api-news.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/11-api-news/_docs/api-news/img/cover-api-news.png
--------------------------------------------------------------------------------
/11-api-news/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
--------------------------------------------------------------------------------
/11-api-news/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
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.18.0",
12 | "lodash": "4.17.11",
13 | "md5": "2.2.1",
14 | "moment": "2.22.2",
15 | "uuid": "3.3.2",
16 | "vue": "^2.5.17",
17 | "vue-router": "3.0.1"
18 | },
19 | "devDependencies": {
20 | "@vue/cli-plugin-babel": "^3.0.4",
21 | "@vue/cli-plugin-eslint": "^3.0.4",
22 | "@vue/cli-service": "^3.0.4",
23 | "vue-template-compiler": "^2.5.17"
24 | },
25 | "eslintConfig": {
26 | "root": true,
27 | "env": {
28 | "node": true
29 | },
30 | "extends": [
31 | "plugin:vue/essential",
32 | "eslint:recommended"
33 | ],
34 | "rules": {},
35 | "parserOptions": {
36 | "parser": "babel-eslint"
37 | }
38 | },
39 | "postcss": {
40 | "plugins": {
41 | "autoprefixer": {}
42 | }
43 | },
44 | "browserslist": [
45 | "> 1%",
46 | "last 2 versions",
47 | "not ie <= 8"
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/11-api-news/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/11-api-news/public/favicon.ico
--------------------------------------------------------------------------------
/11-api-news/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | demo
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | We're sorry but demo doesn't work properly without JavaScript enabled. Please enable it to continue.
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/11-api-news/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
34 |
--------------------------------------------------------------------------------
/11-api-news/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/11-api-news/src/assets/logo.png
--------------------------------------------------------------------------------
/11-api-news/src/components/BlogPost.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ title }}
4 |
{{ date }} by {{ author }}
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/11-api-news/src/components/CommentsForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/11-api-news/src/components/CommentsItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/11-api-news/src/components/CommentsList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Comments
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/11-api-news/src/components/FeaturedPost.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ article.author }}
5 |
6 | {{ article.title }}
7 |
8 |
{{ publishedAt }}
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/11-api-news/src/components/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/11-api-news/src/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/11-api-news/src/components/LongFeatured.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Title of a longer featured blog post
6 |
Multiple lines of text that form the lede, informing new readers quickly and efficiently about what's most interesting in this post's contents.
7 |
Continue reading...
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/11-api-news/src/components/Widget.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
{{ title }}
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/11-api-news/src/components/WidgetTemperature.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Barcelona
5 | Madrid
6 | Murcia
7 |
8 |
Temp: {{ temp }} | Max: {{ temp_max }} | Min: {{ temp_min }}
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/11-api-news/src/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
126 |
127 | .comments_form, .comments {
128 | margin-bottom: 50px;
129 | }
130 |
131 | .featured-post-content {
132 | width: 70%;
133 | }
134 |
135 | .featured-post-image {
136 | object-fit: cover;
137 | width: 30%;
138 | }
--------------------------------------------------------------------------------
/11-api-news/src/data/comments.js:
--------------------------------------------------------------------------------
1 | import uuidv4 from 'uuid/v4'
2 | import moment from 'moment'
3 |
4 | const comments = [
5 | {
6 | id: uuidv4(),
7 | username: "juanma",
8 | date: moment("2018-01-25").valueOf(),
9 | comment: "Good Post!"
10 | },
11 | {
12 | id: uuidv4(),
13 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/m103.jpg",
14 | username: "admin",
15 | date: moment("2018-03-25").valueOf(),
16 | comment: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
17 | },
18 | {
19 | id: uuidv4(),
20 | avatar: "http://demos.themes.guide/bodeo/assets/images/users/w102.jpg",
21 | username: "maslarino",
22 | date: moment("2018-05-25").valueOf(),
23 | comment: "Sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo."
24 | }
25 | ]
26 |
27 | export default comments
28 |
--------------------------------------------------------------------------------
/11-api-news/src/data/content_post.js:
--------------------------------------------------------------------------------
1 | const content = `
2 | This blog post shows a few different types of content that's supported and styled with Bootstrap. Basic typography, images, and code are all supported.
3 |
4 | Cum sociis natoque penatibus et magnis dis parturient montes , nascetur ridiculus mus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Sed posuere consectetur est at lobortis. Cras mattis consectetur purus sit amet fermentum.
5 |
6 | Curabitur blandit tempus porttitor. Nullam quis risus eget urna mollis ornare vel eu leo. Nullam id dolor id nibh ultricies vehicula ut id elit.
7 |
8 | Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.
9 | Heading
10 | Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
11 | Sub-heading
12 | Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
13 | Example code block
14 | Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa.
15 | Sub-heading
16 | Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.
17 |
18 | Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
19 | Donec id elit non mi porta gravida at eget metus.
20 | Nulla vitae elit libero, a pharetra augue.
21 |
22 | Donec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue.
23 |
24 | Vestibulum id ligula porta felis euismod semper.
25 | Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
26 | Maecenas sed diam eget risus varius blandit sit amet non magna.
27 |
28 | Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis.
29 | `
30 |
31 | export default content
--------------------------------------------------------------------------------
/11-api-news/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 |
5 | import './css/main.css'
6 |
7 | Vue.config.productionTip = false
8 |
9 | new Vue({
10 | ...App,
11 | router
12 | }).$mount('#app')
13 |
--------------------------------------------------------------------------------
/11-api-news/src/pages/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/11-api-news/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | Vue.use(Router)
5 |
6 | import Home from '../pages/Home.vue'
7 | import Post from '../pages/Post.vue'
8 |
9 | export default new Router({
10 | mode: 'history',
11 | routes: [
12 | { path: '/', component: Home },
13 | { path: '/home', component: Home },
14 | { name:'PostDetails', path: '/post/:id', component: Post }
15 | ]
16 | })
--------------------------------------------------------------------------------
/11-api-news/src/services/NewsApiService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import md5 from 'md5'
3 |
4 | export default class NewsApiService {
5 | constructor(api_key) {
6 | this.getUrlApiNews = this.getUrlApiNews.bind(
7 | this,
8 | api_key
9 | )
10 | }
11 | getNews(query) {
12 | const url = this.getUrlApiNews(query)
13 | return axios
14 | .get(url)
15 | .then(({ data }) => data)
16 | .then(({ articles }) => articles)
17 | .then( articles => articles.reduce( (acc, article) => {
18 | const {url} = article
19 | const key = md5(url)
20 | return [ { key,...article },...acc]
21 | }, []) )
22 | }
23 | getUrlApiNews(api_key, query) {
24 | return `https://newsapi.org/v2/everything?q=${query}&language=en&sortBy=popularity&apiKey=${api_key}`
25 | }
26 | }
27 |
28 |
29 |
--------------------------------------------------------------------------------
/11-api-news/src/services/WeatherApiService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export default class WeatherApiService {
4 | constructor(api_key) {
5 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(
6 | this,
7 | api_key
8 | )
9 | }
10 | findWeather(location) {
11 | const url = this.getUrlApiWeatherSearch(location)
12 | return axios
13 | .get(url)
14 | .then(({ data }) => data)
15 | .then(({ main }) => main)
16 | }
17 | getUrlApiWeatherSearch(api_key, location) {
18 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/12-improved-api-news/_docs/deploy/README.md:
--------------------------------------------------------------------------------
1 | # Deploy
2 |
3 | [](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0)
4 |
5 | _El curso [Migrando a VueJS progresivamente desde 0](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0) está disponible a través de [CodelyTV](https://pro.codely.tv/)_
6 |
7 | ---
8 |
9 | # Deploy
10 |
11 | Para generar la versión de producción hacemos
12 |
13 | ```
14 | yarn build
15 | ```
16 |
17 | Esto nos generará en la carpeta `dist` una versión de nuestro proyecto ya preparada para subirla a producción (para ser servida estáticamente a través de un servidor web)
18 |
19 | Con [`now` de _zeist_](https://zeit.co/now) podemos hacer una subida rápida a producción a un servidor temporal
20 |
21 | Instalamos `now` globalmente con...
22 |
23 | ```
24 | npm i -g now
25 | ```
26 |
27 | Podemos configurar `now` para nuestro proyecto desde la raiz añadiendo un `now.json` como este...
28 |
29 | Con este modulo ya disponible globalmente, nos podemos ir a la carpeta `dist` (con nuestra versión de producción generada por `yarn build`) y hacer sencillamente
30 |
31 | ```
32 | now
33 | ```
34 |
35 | Para poder hacer esta operación directamente desde la raíz [podemos definir](https://cli.vuejs.org/guide/deployment.html#now) un[ `now.json`](https://zeit.co/blog/now-json) como este...
36 |
37 | ```
38 | ```
39 | {
40 | "name": "my-example-app",
41 | "type": "static",
42 | "static": {
43 | "public": "dist",
44 | "rewrites": [
45 | {
46 | "source": "**",
47 | "destination": "/index.html"
48 | }
49 | ]
50 | },
51 | "alias": "vue-example",
52 | "files": [
53 | "dist"
54 | ]
55 | }
56 | ```
57 | ```
58 |
59 |
60 | ## Recursos
61 |
62 | - [Deploy a Vue App con now](https://cli.vuejs.org/guide/deployment.html#now)
63 |
64 | ---
65 |
66 | El código correspondiente a esta lección lo tienes disponible [aqui](https://github.com/CodelyTV/vue-progressive-migration-course/tree/master/11-api-news)
67 |
68 |
--------------------------------------------------------------------------------
/12-improved-api-news/_docs/deploy/img/cover-deploy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/12-improved-api-news/_docs/deploy/img/cover-deploy.png
--------------------------------------------------------------------------------
/12-improved-api-news/_docs/routes/img/cover-api-news-enhanced.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/12-improved-api-news/_docs/routes/img/cover-api-news-enhanced.png
--------------------------------------------------------------------------------
/12-improved-api-news/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
--------------------------------------------------------------------------------
/12-improved-api-news/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
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.18.0",
12 | "lodash": "4.17.11",
13 | "md5": "2.2.1",
14 | "moment": "2.22.2",
15 | "uuid": "3.3.2",
16 | "vue": "^2.5.17",
17 | "vue-async-computed": "^3.4.1",
18 | "vue-router": "3.0.1"
19 | },
20 | "devDependencies": {
21 | "@vue/cli-plugin-babel": "^3.0.4",
22 | "@vue/cli-plugin-eslint": "^3.0.4",
23 | "@vue/cli-service": "^3.0.4",
24 | "vue-template-compiler": "^2.5.17"
25 | },
26 | "eslintConfig": {
27 | "root": true,
28 | "env": {
29 | "node": true
30 | },
31 | "extends": [
32 | "plugin:vue/essential",
33 | "eslint:recommended"
34 | ],
35 | "rules": {},
36 | "parserOptions": {
37 | "parser": "babel-eslint"
38 | }
39 | },
40 | "postcss": {
41 | "plugins": {
42 | "autoprefixer": {}
43 | }
44 | },
45 | "browserslist": [
46 | "> 1%",
47 | "last 2 versions",
48 | "not ie <= 8"
49 | ]
50 | }
51 |
--------------------------------------------------------------------------------
/12-improved-api-news/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/12-improved-api-news/public/favicon.ico
--------------------------------------------------------------------------------
/12-improved-api-news/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | demo
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | We're sorry but demo doesn't work properly without JavaScript enabled. Please enable it to continue.
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
13 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/12-improved-api-news/src/assets/logo.png
--------------------------------------------------------------------------------
/12-improved-api-news/src/components/BlogPost.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ title }}
4 |
{{ publishedAt }} at {{ source }}
5 |
6 |
🧐 Read Full Article
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/components/CommentsForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/components/CommentsItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/components/CommentsList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Comments
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/components/FeaturedPost.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/components/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/components/LongFeatured.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Title of a longer featured blog post
6 |
Multiple lines of text that form the lede, informing new readers quickly and efficiently about what's most interesting in this post's contents.
7 |
Continue reading...
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/components/Widget.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
{{ title }}
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/components/WidgetTemperature.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Barcelona
5 | Madrid
6 | Murcia
7 |
8 |
Temp: {{ temp }} | Max: {{ temp_max }} | Min: {{ temp_min }}
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
126 |
127 | .comments_form, .comments {
128 | margin-bottom: 50px;
129 | }
130 |
131 | .featured-post-content {
132 | width: 70%;
133 | }
134 |
135 | .featured-post-image {
136 | object-fit: cover;
137 | width: 30%;
138 | }
--------------------------------------------------------------------------------
/12-improved-api-news/src/helpers/index.js:
--------------------------------------------------------------------------------
1 | const regExpDomain = /^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/?\n]+)/igm
2 |
3 | export const getUrlDomain = url => url.match(regExpDomain)[0]
--------------------------------------------------------------------------------
/12-improved-api-news/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import AsyncComputed from 'vue-async-computed'
5 |
6 | import './css/main.css'
7 |
8 | import NewsApiService from './services/NewsApiService'
9 | window.NewsApiService = NewsApiService
10 |
11 | Vue.config.productionTip = false
12 | Vue.use(AsyncComputed)
13 |
14 | new Vue({
15 | ...App,
16 | router
17 | }).$mount('#app')
18 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/pages/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | Vue.use(Router)
5 |
6 | import Home from '../pages/Home.vue'
7 | import Post from '../pages/Post.vue'
8 |
9 | export default new Router({
10 | mode: 'history',
11 | routes: [
12 | { path: '/', component: Home },
13 | { path: '/home', component: Home },
14 | { name:'PostDetails', path: '/post/:id', component: Post }
15 | ]
16 | })
--------------------------------------------------------------------------------
/12-improved-api-news/src/services/NewsApiService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import md5 from 'md5'
3 |
4 | const API_KEY = 'ba2a4f83ceda44eaae1ae4d972c14fdb'
5 | const DEFAULT_QUERY = 'javascript'
6 |
7 | class NewsApiService {
8 | constructor(api_key) {
9 | this.getUrlApiNews = this.getUrlApiNews.bind(
10 | this,
11 | api_key
12 | )
13 | this.articles = null
14 | this.query = DEFAULT_QUERY
15 | }
16 |
17 | getArticle(key) {
18 | return this.getArticles(this.query).then( articles => articles[key])
19 | }
20 |
21 | addComment(key, comment) {
22 | return this.articles[key].comments.push(comment)
23 | }
24 |
25 | getArticles(query) {
26 | this.query = query || this.query
27 | const url = this.getUrlApiNews(this.query)
28 | return this.articles ?
29 | Promise.resolve(this.articles) :
30 | axios
31 | .get(url)
32 | .then(({ data }) => data)
33 | .then(({ articles }) => articles)
34 | .then( articles => articles.reduce( (acc, article) => {
35 | const {url} = article
36 | const key = md5(url)
37 | acc[key] = article
38 | acc[key].comments = []
39 | return acc
40 | }, {}) )
41 | .then( articles => {
42 | this.articles = articles
43 | return articles
44 | })
45 | }
46 |
47 | getUrlApiNews(api_key, query) {
48 | return `https://newsapi.org/v2/everything?q=${query}&language=en&sortBy=popularity&apiKey=${api_key}`
49 | }
50 | }
51 |
52 | export default new NewsApiService(API_KEY)
53 |
--------------------------------------------------------------------------------
/12-improved-api-news/src/services/WeatherApiService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export default class WeatherApiService {
4 | constructor(api_key) {
5 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(
6 | this,
7 | api_key
8 | )
9 | }
10 | findWeather(location) {
11 | const url = this.getUrlApiWeatherSearch(location)
12 | return axios
13 | .get(url)
14 | .then(({ data }) => data)
15 | .then(({ main }) => main)
16 | }
17 | getUrlApiWeatherSearch(api_key, location) {
18 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/README.md:
--------------------------------------------------------------------------------
1 | # demo-nuxt
2 |
3 | > My groovy Nuxt.js project
4 |
5 | ## Build Setup
6 |
7 | ``` bash
8 | # install dependencies
9 | $ yarn install
10 |
11 | # serve with hot reload at localhost:3000
12 | $ yarn run dev
13 |
14 | # build for production and launch server
15 | $ yarn run build
16 | $ yarn start
17 |
18 | # generate static project
19 | $ yarn run generate
20 | ```
21 |
22 | For detailed explanation on how things work, checkout [Nuxt.js docs](https://nuxtjs.org).
23 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/_docs/deploy/README.md:
--------------------------------------------------------------------------------
1 | # Deploy SSR con Nuxt
2 |
3 | [](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0)
4 |
5 | _El curso [Migrando a VueJS progresivamente desde 0](https://pro.codely.tv/library/migrando-a-vuejs-progresivamente-desde-0) está disponible a través de [CodelyTV](https://pro.codely.tv/)_
6 |
7 | ---
8 |
9 | Con [`nuxt build`](https://nuxtjs.org/guide/commands/) generamos la versión de producción de la parte frontend en una carpeta `dist` (necesaria para la versión de producción)
10 |
11 | Con esta carpeta `dist` generada, ya podemos subir nuestra app SSR-Nuxt con `now` haciendo sencillamente
12 |
13 | ```
14 | now
15 | ```
16 |
17 | `now` detectará que estamos subiendo una aplicación SSR y subirá el codigo a un servidor node que ejecutará nuestro `yarn start`
18 |
19 | Así que `now` hará el deploy correspondiente al tipo de servidor adecuado y nos dejará una URL de producción preparada
20 |
21 |
22 | ---
23 |
24 | El código correspondiente a esta lección lo tienes disponible [aqui](https://github.com/codelyTV/vue-progressive-migration-course/blob/master/13-ssr-nuxt/)
25 |
26 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/_docs/deploy/img/cover-deploy-ssr-vue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/13-ssr-nuxt/_docs/deploy/img/cover-deploy-ssr-vue.png
--------------------------------------------------------------------------------
/13-ssr-nuxt/_docs/ssr-vue/img/cover-ssr-vue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/13-ssr-nuxt/_docs/ssr-vue/img/cover-ssr-vue.png
--------------------------------------------------------------------------------
/13-ssr-nuxt/assets/README.md:
--------------------------------------------------------------------------------
1 | # ASSETS
2 |
3 | **This directory is not required, you can delete it if you don't want to use it.**
4 |
5 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript.
6 |
7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked).
8 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/assets/css/main.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable selector-list-comma-newline-after */
2 |
3 | .blog-header {
4 | line-height: 1;
5 | border-bottom: 1px solid #e5e5e5;
6 | }
7 |
8 | .blog-header-logo {
9 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
10 | font-size: 2.25rem;
11 | }
12 |
13 | .blog-header-logo:hover {
14 | text-decoration: none;
15 | }
16 |
17 | h1, h2, h3, h4, h5, h6 {
18 | font-family: "Playfair Display", Georgia, "Times New Roman", serif;
19 | }
20 |
21 | .display-4 {
22 | font-size: 2.5rem;
23 | }
24 | @media (min-width: 768px) {
25 | .display-4 {
26 | font-size: 3rem;
27 | }
28 | }
29 |
30 | .nav-scroller {
31 | position: relative;
32 | z-index: 2;
33 | height: 2.75rem;
34 | overflow-y: hidden;
35 | }
36 |
37 | .nav-scroller .nav {
38 | display: -ms-flexbox;
39 | display: flex;
40 | -ms-flex-wrap: nowrap;
41 | flex-wrap: nowrap;
42 | padding-bottom: 1rem;
43 | margin-top: -1px;
44 | overflow-x: auto;
45 | text-align: center;
46 | white-space: nowrap;
47 | -webkit-overflow-scrolling: touch;
48 | }
49 |
50 | .nav-scroller .nav-link {
51 | padding-top: .75rem;
52 | padding-bottom: .75rem;
53 | font-size: .875rem;
54 | }
55 |
56 | .card-img-right {
57 | height: 100%;
58 | border-radius: 0 3px 3px 0;
59 | }
60 |
61 | .flex-auto {
62 | -ms-flex: 0 0 auto;
63 | flex: 0 0 auto;
64 | }
65 |
66 | .h-250 { height: 250px; }
67 | @media (min-width: 768px) {
68 | .h-md-250 { height: 250px; }
69 | }
70 |
71 | /*
72 | * Blog name and description
73 | */
74 | .blog-title {
75 | margin-bottom: 0;
76 | font-size: 2rem;
77 | font-weight: 400;
78 | }
79 | .blog-description {
80 | font-size: 1.1rem;
81 | color: #999;
82 | }
83 |
84 | @media (min-width: 40em) {
85 | .blog-title {
86 | font-size: 3.5rem;
87 | }
88 | }
89 |
90 | /* Pagination */
91 | .blog-pagination {
92 | margin-bottom: 4rem;
93 | }
94 | .blog-pagination > .btn {
95 | border-radius: 2rem;
96 | }
97 |
98 | /*
99 | * Blog posts
100 | */
101 | .blog-post {
102 | margin-bottom: 4rem;
103 | }
104 | .blog-post-title {
105 | margin-bottom: .25rem;
106 | font-size: 2.5rem;
107 | }
108 | .blog-post-meta {
109 | margin-bottom: 1.25rem;
110 | color: #999;
111 | }
112 |
113 | /*
114 | * Footer
115 | */
116 | .blog-footer {
117 | padding: 2.5rem 0;
118 | color: #999;
119 | text-align: center;
120 | background-color: #f9f9f9;
121 | border-top: .05rem solid #e5e5e5;
122 | }
123 | .blog-footer p:last-child {
124 | margin-bottom: 0;
125 | }
126 |
127 | .comments_form, .comments {
128 | margin-bottom: 50px;
129 | }
130 |
131 | .featured-post-content {
132 | width: 70%;
133 | }
134 |
135 | .featured-post-image {
136 | object-fit: cover;
137 | width: 30%;
138 | }
--------------------------------------------------------------------------------
/13-ssr-nuxt/components/BlogPost.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ title }}
4 |
{{ publishedAt }} at
5 | {{ source }}
9 |
10 |
14 |
🧐 Read Full Article
18 |
19 |
20 |
21 |
62 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/components/CommentsForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
50 |
51 |
52 |
77 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/components/CommentsItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
52 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/components/CommentsList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Comments
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
38 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/components/FeaturedPost.vue:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
24 |
53 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/components/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/components/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
80 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/components/LongFeatured.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Title of a longer featured blog post
6 |
Multiple lines of text that form the lede, informing new readers quickly and efficiently about what's most interesting in this post's contents.
7 |
Continue reading...
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/components/README.md:
--------------------------------------------------------------------------------
1 | # COMPONENTS
2 |
3 | **This directory is not required, you can delete it if you don't want to use it.**
4 |
5 | The components directory contains your Vue.js Components.
6 |
7 | _Nuxt.js doesn't supercharge these components._
8 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/components/Widget.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
{{ title }}
7 |
8 |
9 |
10 |
11 |
35 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/components/WidgetTemperature.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | Barcelona
7 | Madrid
8 | Murcia
9 |
10 |
Temp: {{ temp }} | Max: {{ temp_max }} | Min: {{ temp_min }}
11 |
12 |
13 |
14 |
46 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/dist/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/dist/200.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | demo-nuxt
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/dist/README.md:
--------------------------------------------------------------------------------
1 | # STATIC
2 |
3 | **This directory is not required, you can delete it if you don't want to use it.**
4 |
5 | This directory contains your static files.
6 | Each file inside this directory is mapped to `/`.
7 |
8 | Example: `/static/robots.txt` is mapped as `/robots.txt`.
9 |
10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static).
11 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/dist/_nuxt/4c4b38d6bd1c33e6ee8a.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[4],{297:function(t,n,e){var o=e(314);"string"==typeof o&&(o=[[t.i,o,""]]),o.locals&&(t.exports=o.locals);(0,e(11).default)("59497dc0",o,!0,{sourceMap:!1})},298:function(t,n,e){var o=e(316);"string"==typeof o&&(o=[[t.i,o,""]]),o.locals&&(t.exports=o.locals);(0,e(11).default)("3d9b66cc",o,!0,{sourceMap:!1})},313:function(t,n,e){"use strict";var o=e(297);e.n(o).a},314:function(t,n,e){(t.exports=e(10)(!1)).push([t.i,"\n.VueToNuxtLogo{display:inline-block;-webkit-animation:turn 2s linear 1s forwards;animation:turn 2s linear 1s forwards;-webkit-transform:rotateX(180deg);transform:rotateX(180deg);position:relative;overflow:hidden;height:180px;width:245px\n}\n.Triangle{position:absolute;top:0;left:0;width:0;height:0\n}\n.Triangle--one{border-left:105px solid transparent;border-right:105px solid transparent;border-bottom:180px solid #41b883\n}\n.Triangle--two{top:30px;border-left:87.5px solid transparent;border-right:87.5px solid transparent;border-bottom:150px solid #3b8070\n}\n.Triangle--three,.Triangle--two{left:35px;-webkit-animation:goright .5s linear 3.5s forwards;animation:goright .5s linear 3.5s forwards\n}\n.Triangle--three{top:60px;border-left:70px solid transparent;border-right:70px solid transparent;border-bottom:120px solid #35495e\n}\n.Triangle--four{top:120px;left:70px;-webkit-animation:godown .5s linear 3s forwards;animation:godown .5s linear 3s forwards;border-left:35px solid transparent;border-right:35px solid transparent;border-bottom:60px solid #fff\n}\n@-webkit-keyframes turn{\nto{-webkit-transform:rotateX(0deg);transform:rotateX(0deg)\n}\n}\n@keyframes turn{\nto{-webkit-transform:rotateX(0deg);transform:rotateX(0deg)\n}\n}\n@-webkit-keyframes godown{\nto{top:180px\n}\n}\n@keyframes godown{\nto{top:180px\n}\n}\n@-webkit-keyframes goright{\nto{left:70px\n}\n}\n@keyframes goright{\nto{left:70px\n}\n}",""])},315:function(t,n,e){"use strict";var o=e(298);e.n(o).a},316:function(t,n,e){(t.exports=e(10)(!1)).push([t.i,"\n.container{min-height:100vh;display:flex;justify-content:center;align-items:center;text-align:center\n}\n.title{font-family:Quicksand,Source Sans Pro,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;display:block;font-weight:300;font-size:100px;color:#35495e;letter-spacing:1px\n}\n.subtitle{font-weight:300;font-size:42px;color:#526488;word-spacing:5px;padding-bottom:15px\n}\n.links{padding-top:15px\n}",""])},319:function(t,n,e){"use strict";e.r(n);e(313);var o=e(19),i=Object(o.a)({},function(){this.$createElement;this._self._c;return this._m(0)},[function(){var t=this.$createElement,n=this._self._c||t;return n("div",{staticClass:"VueToNuxtLogo"},[n("div",{staticClass:"Triangle Triangle--two"}),this._v(" "),n("div",{staticClass:"Triangle Triangle--one"}),this._v(" "),n("div",{staticClass:"Triangle Triangle--three"}),this._v(" "),n("div",{staticClass:"Triangle Triangle--four"})])}],!1,null,null,null);i.options.__file="Logo.vue";var r={components:{Logo:i.exports}},s=(e(315),Object(o.a)(r,function(){var t=this.$createElement,n=this._self._c||t;return n("section",{staticClass:"container"},[n("div",[n("logo"),this._v(" "),n("h1",{staticClass:"title"},[this._v("\n demo-nuxt\n ")]),this._v(" "),n("h2",{staticClass:"subtitle"},[this._v("\n My groovy Nuxt.js project\n ")]),this._v(" "),this._m(0)],1)])},[function(){var t=this.$createElement,n=this._self._c||t;return n("div",{staticClass:"links"},[n("a",{staticClass:"button--green",attrs:{href:"https://nuxtjs.org/",target:"_blank"}},[this._v("Documentation")]),this._v(" "),n("a",{staticClass:"button--grey",attrs:{href:"https://github.com/nuxt/nuxt.js",target:"_blank"}},[this._v("GitHub")])])}],!1,null,null,null));s.options.__file="__index.vue";n.default=s.exports}}]);
--------------------------------------------------------------------------------
/13-ssr-nuxt/dist/_nuxt/LICENSES:
--------------------------------------------------------------------------------
1 | /*!
2 | * Vue.js v2.5.17
3 | * (c) 2014-2018 Evan You
4 | * Released under the MIT License.
5 | */
6 |
7 | /**
8 | * vue-router v3.0.1
9 | * (c) 2017 Evan You
10 | * @license MIT
11 | */
12 |
13 | /**
14 | * vue-meta v1.5.5
15 | * (c) 2018 Declan de Wet & Sébastien Chopin (@Atinux)
16 | * @license MIT
17 | */
18 |
19 | /*
20 | object-assign
21 | (c) Sindre Sorhus
22 | @license MIT
23 | */
24 |
25 | /*!
26 | * Determine if an object is a Buffer
27 | *
28 | * @author Feross Aboukhadijeh
29 | * @license MIT
30 | */
31 |
32 | /**!
33 | * @fileOverview Kickass library to create and place poppers near their reference elements.
34 | * @version 1.14.4
35 | * @license
36 | * Copyright (c) 2016 Federico Zivolo and contributors
37 | *
38 | * Permission is hereby granted, free of charge, to any person obtaining a copy
39 | * of this software and associated documentation files (the "Software"), to deal
40 | * in the Software without restriction, including without limitation the rights
41 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
42 | * copies of the Software, and to permit persons to whom the Software is
43 | * furnished to do so, subject to the following conditions:
44 | *
45 | * The above copyright notice and this permission notice shall be included in all
46 | * copies or substantial portions of the Software.
47 | *
48 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
51 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
53 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
54 | * SOFTWARE.
55 | */
56 |
57 | /*!
58 | * vue-no-ssr v1.0.0
59 | * (c) 2018-present egoist <0x142857@gmail.com>
60 | * Released under the MIT License.
61 | */
62 |
63 | /**
64 | * @license
65 | * Lodash
66 | * Copyright JS Foundation and other contributors
67 | * Released under MIT license
68 | * Based on Underscore.js 1.8.3
69 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
70 | */
71 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/dist/_nuxt/c505c8736ecc73a90fe9.js:
--------------------------------------------------------------------------------
1 | !function(e){function t(t){for(var n,a,i=t[0],f=t[1],c=t[2],p=0,s=[];p url.match(regExpDomain)[0]
4 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/layouts/README.md:
--------------------------------------------------------------------------------
1 | # LAYOUTS
2 |
3 | **This directory is not required, you can delete it if you don't want to use it.**
4 |
5 | This directory contains your Application Layouts.
6 |
7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts).
8 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/layouts/default.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
56 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/middleware/README.md:
--------------------------------------------------------------------------------
1 | # MIDDLEWARE
2 |
3 | **This directory is not required, you can delete it if you don't want to use it.**
4 |
5 | This directory contains your application middleware.
6 | The middleware lets you define custom function to be ran before rendering a page or a group of pages (layouts).
7 |
8 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware).
9 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/nuxt.config.js:
--------------------------------------------------------------------------------
1 | const pkg = require('./package')
2 |
3 | module.exports = {
4 | mode: 'universal',
5 |
6 | /*
7 | ** Headers of the page
8 | */
9 | head: {
10 | title: pkg.name,
11 | meta: [
12 | { charset: 'utf-8' },
13 | { name: 'viewport', content: 'width=device-width, initial-scale=1' },
14 | { hid: 'description', name: 'description', content: pkg.description }
15 | ],
16 | link: [
17 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
18 | ]
19 | },
20 |
21 | /*
22 | ** Customize the progress-bar color
23 | */
24 | loading: { color: '#fff' },
25 |
26 | /*
27 | ** Global CSS
28 | */
29 | css: [
30 | '@/assets/css/main.css'
31 | ],
32 |
33 | /*
34 | ** Plugins to load before mounting the App
35 | */
36 | plugins: [
37 | ],
38 |
39 | /*
40 | ** Nuxt.js modules
41 | */
42 | modules: [
43 | // Doc: https://github.com/nuxt-community/axios-module#usage
44 | '@nuxtjs/axios',
45 | // Doc: https://bootstrap-vue.js.org/docs/
46 | 'bootstrap-vue/nuxt'
47 | ],
48 | /*
49 | ** Axios module configuration
50 | */
51 | axios: {
52 | // See https://github.com/nuxt-community/axios-module#options
53 | },
54 |
55 | /*
56 | ** Build configuration
57 | */
58 | build: {
59 | /*
60 | ** You can extend webpack config here
61 | */
62 | extend(config, ctx) {
63 | // Run ESLint on save
64 | if (ctx.isDev && ctx.isClient) {
65 | config.module.rules.push({
66 | enforce: 'pre',
67 | test: /\.(js|vue)$/,
68 | loader: 'eslint-loader',
69 | exclude: /(node_modules)/
70 | })
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo-nuxt",
3 | "version": "1.0.0",
4 | "description": "My groovy Nuxt.js project",
5 | "author": "JuanMa Garrido",
6 | "private": true,
7 | "scripts": {
8 | "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
9 | "build": "nuxt build",
10 | "start": "cross-env NODE_ENV=production node server/index.js",
11 | "generate": "nuxt generate",
12 | "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
13 | "precommit": "npm run lint"
14 | },
15 | "dependencies": {
16 | "@nuxtjs/axios": "^5.0.0",
17 | "bootstrap": "^4.1.3",
18 | "bootstrap-vue": "^2.0.0-beta",
19 | "cross-env": "^5.2.0",
20 | "express": "4.16.3",
21 | "md5": "2.2.1",
22 | "moment": "2.22.2",
23 | "nuxt": "^2.0.0"
24 | },
25 | "devDependencies": {
26 | "babel-eslint": "^8.2.1",
27 | "eslint": "^5.0.1",
28 | "eslint-config-prettier": "^3.1.0",
29 | "eslint-loader": "^2.0.0",
30 | "eslint-plugin-prettier": "2.6.2",
31 | "eslint-plugin-vue": "^4.0.0",
32 | "nodemon": "^1.11.0",
33 | "prettier": "1.14.3",
34 | "prettier-eslint": "^8.8.2"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/pages/README.md:
--------------------------------------------------------------------------------
1 | # PAGES
2 |
3 | This directory contains your Application Views and Routes.
4 | The framework reads all the `*.vue` files inside this directory and create the router of your application.
5 |
6 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing).
7 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/pages/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
28 |
29 |
30 |
31 |
48 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/plugins/README.md:
--------------------------------------------------------------------------------
1 | # PLUGINS
2 |
3 | **This directory is not required, you can delete it if you don't want to use it.**
4 |
5 | This directory contains your Javascript plugins that you want to run before mounting the root Vue.js application.
6 |
7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins).
8 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/server/index.js:
--------------------------------------------------------------------------------
1 |
2 | const express = require('express')
3 | const consola = require('consola')
4 | const { Nuxt, Builder } = require('nuxt')
5 | const app = express()
6 | const host = process.env.HOST || '127.0.0.1'
7 | const port = process.env.PORT || 3000
8 |
9 | app.set('port', port)
10 |
11 | // Import and Set Nuxt.js options
12 | let config = require('../nuxt.config.js')
13 | config.dev = !(process.env.NODE_ENV === 'production')
14 |
15 | async function start() {
16 | // Init Nuxt.js
17 | const nuxt = new Nuxt(config)
18 |
19 | // Build only in dev mode
20 | if (config.dev) {
21 | const builder = new Builder(nuxt)
22 | await builder.build()
23 | }
24 |
25 | // Give nuxt middleware to express
26 | app.use(nuxt.render)
27 |
28 | // Listen the server
29 | app.listen(port, host)
30 | consola.ready({
31 | message: `Server listening on http://${host}:${port}`,
32 | badge: true
33 | })
34 | }
35 | start()
36 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/services/NewsApiService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import md5 from 'md5'
3 |
4 | const API_KEY = 'ba2a4f83ceda44eaae1ae4d972c14fdb'
5 | const DEFAULT_QUERY = 'javascript'
6 |
7 | class NewsApiService {
8 | constructor(api_key) {
9 | this.getUrlApiNews = this.getUrlApiNews.bind(this, api_key)
10 | this.articles = null
11 | this.query = DEFAULT_QUERY
12 | }
13 |
14 | getArticle(key) {
15 | return this.getArticles(this.query).then(articles => articles[key])
16 | }
17 |
18 | addComment(key, comment) {
19 | return this.articles[key].comments.push(comment)
20 | }
21 |
22 | getArticles(query) {
23 | this.query = query || this.query
24 | const url = this.getUrlApiNews(this.query)
25 | return this.articles
26 | ? Promise.resolve(this.articles)
27 | : axios
28 | .get(url)
29 | .then(({ data }) => data)
30 | .then(({ articles }) => articles)
31 | .then(articles =>
32 | articles.reduce((acc, article) => {
33 | const { url } = article
34 | const key = md5(url)
35 | acc[key] = article
36 | acc[key].comments = []
37 | return acc
38 | }, {})
39 | )
40 | .then(articles => {
41 | this.articles = articles
42 | return articles
43 | })
44 | }
45 |
46 | getUrlApiNews(api_key, query) {
47 | return `https://newsapi.org/v2/everything?q=${query}&language=en&sortBy=popularity&apiKey=${api_key}`
48 | }
49 | }
50 |
51 | export default new NewsApiService(API_KEY)
52 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/services/WeatherApiService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export default class WeatherApiService {
4 | constructor(api_key) {
5 | this.getUrlApiWeatherSearch = this.getUrlApiWeatherSearch.bind(
6 | this,
7 | api_key
8 | )
9 | }
10 | findWeather(location) {
11 | const url = this.getUrlApiWeatherSearch(location)
12 | return axios
13 | .get(url)
14 | .then(({ data }) => data)
15 | .then(({ main }) => main)
16 | }
17 | getUrlApiWeatherSearch(api_key, location) {
18 | return `https://api.openweathermap.org/data/2.5/weather?q=${location}&units=metric&appid=${api_key}`
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/static/README.md:
--------------------------------------------------------------------------------
1 | # STATIC
2 |
3 | **This directory is not required, you can delete it if you don't want to use it.**
4 |
5 | This directory contains your static files.
6 | Each file inside this directory is mapped to `/`.
7 |
8 | Example: `/static/robots.txt` is mapped as `/robots.txt`.
9 |
10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static).
11 |
--------------------------------------------------------------------------------
/13-ssr-nuxt/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodelyTV/vue-progressive-migration-course/HEAD/13-ssr-nuxt/static/favicon.ico
--------------------------------------------------------------------------------
/13-ssr-nuxt/store/README.md:
--------------------------------------------------------------------------------
1 | # STORE
2 |
3 | **This directory is not required, you can delete it if you don't want to use it.**
4 |
5 | This directory contains your Vuex Store files.
6 | Vuex Store option is implemented in the Nuxt.js framework.
7 |
8 | Creating a file in this directory activate the option in the framework automatically.
9 |
10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).
11 |
--------------------------------------------------------------------------------
/14-general-questions-from-backend/README.md:
--------------------------------------------------------------------------------
1 | # Preguntas desde backend
2 |
3 | ## ¿Cual sería la manera correcta de gestionar el "estado" de la app?
4 |
5 | Lo interesante de nuestro proyecto es que hemos sido capaces de sacar la lógica de nuestra aplicación (los servicios) fuera del código vue (fuera del código de la interfaz)
6 |
7 | Para ello hemos montado un sistema "rudimentario" (pero funcional) con el que encapsular los métodos con los que pedir los datos y el "almacenaje" de dichos datos
8 |
9 | El siguiente paso natural de nuestra app sería utilizar [VueX](https://vuex.vuejs.org/) (o similar) para gestionar el estado de nuestra app
10 |
11 | ## async await
12 |
13 | La declaracicón [`async`](https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Sentencias/funcion_asincrona) define una función asíncrona que:
14 | - Devuelve siempre una promesa
15 | - Permite utilizar dentro `await` asociar a una variable el resultado de una operación asíncrona (cuando llegue)
16 |
17 | Básicamente con el [tandem `async-await`](https://developers.google.com/web/fundamentals/primers/async-functions) podemos expresar operaciones asíncronas como una sintaxis como si fueran operaciones síncronas
18 |
19 | Internamente `async-await` utiliza generator para permitirnos poder expresar promesas de esta manera.
20 |
21 | Más detalles aqui:
22 | - [Async/Await Specification](https://tc39.github.io/ecmascript-asyncawait/)
23 | - [How JavaScript Async/Await Works Under the Hood](https://medium.com/siliconwat/how-javascript-async-await-works-3cab4b7d21da)
24 |
25 | ## El objeto `window`
26 |
27 | El objeto global `window` hay que utilizarlo con cuidado para no caer en la "polución" del mismo y aumentar las probabilidades de colisión con otras apps que utilizen también este objeto global
28 |
29 | Más info al respecto:
30 | - [What does it mean global namespace would be polluted?](https://stackoverflow.com/questions/8862665/what-does-it-mean-global-namespace-would-be-polluted)
31 | - [Writing modular JavaScript without polluting the global namespace](https://marcofranssen.nl/writing-modular-javascript-without-polluting-the-global-namespace/)
32 | - [Use Cases for JavaScript's IIFEs](https://mariusschulz.com/blog/use-cases-for-javascripts-iifes)
33 |
34 | ## Gestión de las API keys
35 |
36 | En nuestra app estamos gestionando directamente las peticiones a una API externa para lo cual necesitamos gestionar desde el cliente las API keys. Y claro, estas API keys cualquieras las puede ver en el código
37 |
38 | Lo suyo sería que fuera nuestro backend el que gestionara estas peticiones a API's externas y que esta gestión de la API key se hiciera desde el servidor (así de entrada ya no se podrían ver estas claves desde cliente)
39 |
40 | Luego podríamos montar un sistema de autentificación para que sólo usuarios autorizados (previo registro por ejemplo) pudieran acceder a estas URL's (y por ende a estos datos externos → uso de API key)
41 |
42 | Lo bueno de haber sacado la lógica de la app (peticiones externas) fuera de Vue es que podemos cambiar la manera de obtener los datos sin que tengamos que tocar nuestro código Vue
--------------------------------------------------------------------------------