├── .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 | [![¿Por qué VueJS?](./img/cover-por-que-vue.png)](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 | [![Escenario - HTML+CSS+JS Nativo](./img/cover-escenario.png)](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 | [![Blog](./img/blog.png)](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 | [![API keys](./img/api-keys-openweathermap.png)](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 | [![Planteamiento estructura de componentes y Vue.js DevTools](./img/cover-estructura-componentes.png)](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 | ![Comments in action](./img/comments-in-action.gif) 13 | 14 | con este código en el HTML 15 | 16 | ```html 17 |
18 | 19 | 20 |
21 | ``` 22 | 23 | ## Vue.js DevTools 24 | 25 | [![Planteamiento estructura de componentes y Vue.js DevTools](./img/cover-devtools.png)](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 |
3 |
4 | avatar 6 |
7 |
8 |
{{ username }} {{ date }}
9 |
10 |

{{ comment }}

11 |
12 |
13 | 14 |
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 |
3 |
4 | avatar 6 |
7 |
8 |
{{ username }} {{ date }}
9 |
10 |

{{ comment }}

11 |
12 |
13 | 14 |
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 |
3 |

Add a comment

4 |
5 | 6 | 8 |
9 | 10 |
11 | 12 | 13 |
14 | 15 |
16 | 17 | 20 |
21 | 22 |
23 | 24 |
25 |
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 |
3 |
4 | avatar 6 |
7 |
8 |
{{ username }} {{ date }}
9 |
10 |

{{ comment }}

11 |
12 |
13 | 14 |
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 |
3 |

Add a comment

4 |
5 | 6 | 8 |
9 | 10 |
11 | 12 | 13 |
14 | 15 |
16 | 17 | 20 |
21 | 22 |
23 | 24 |
25 |
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 |
3 |
4 | avatar 6 |
7 |
8 |
{{ username }} {{ formattedTime }}
9 |
10 |

{{ comment }}

11 |
12 |
13 | 14 |
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 | [![Usando módulos con soporte nativo de navegador y Lodash](./img/cover-modulos-nativos.png)](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 |
3 |

Add a comment

4 |
5 | 6 | 8 |
9 | 10 |
11 | 12 | 13 |
14 | 15 |
16 | 17 | 20 |
21 | 22 |
23 | 24 |
25 |
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 |
3 |
4 | avatar 6 |
7 |
8 |
{{ username }} {{ formattedTime }}
9 |
10 |

{{ comment }}

11 |
12 |
13 | 14 |
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 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /09-cli-version-modular-components/src/App.vue: -------------------------------------------------------------------------------- 1 | 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 | 35 | 36 | -------------------------------------------------------------------------------- /09-cli-version-modular-components/src/components/CommentsForm.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /09-cli-version-modular-components/src/components/CommentsItem.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | -------------------------------------------------------------------------------- /09-cli-version-modular-components/src/components/CommentsList.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /09-cli-version-modular-components/src/components/FeaturedPost.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /09-cli-version-modular-components/src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /09-cli-version-modular-components/src/components/Header.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /09-cli-version-modular-components/src/components/LongFeatured.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /09-cli-version-modular-components/src/components/Widget.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /09-cli-version-modular-components/src/components/WidgetTemperature.vue: -------------------------------------------------------------------------------- 1 | 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 | 82 | 83 | -------------------------------------------------------------------------------- /09-cli-version-modular-components/src/pages/Post.vue: -------------------------------------------------------------------------------- 1 | 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 | 81 | 82 | 88 | 89 | ``` 90 | -------------------------------------------------------------------------------- /10-routes-home-post/_docs/routes/README.md: -------------------------------------------------------------------------------- 1 | # Migramos la gestión de rutas a VueJS 2 | 3 | [![Migramos la gestión de rutas a VueJS](./img/cover-routes.png)](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 | 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 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /10-routes-home-post/src/App.vue: -------------------------------------------------------------------------------- 1 | 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 | 8 | 9 | -------------------------------------------------------------------------------- /10-routes-home-post/src/components/CommentsForm.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /10-routes-home-post/src/components/CommentsItem.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | -------------------------------------------------------------------------------- /10-routes-home-post/src/components/CommentsList.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /10-routes-home-post/src/components/FeaturedPost.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /10-routes-home-post/src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /10-routes-home-post/src/components/Header.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /10-routes-home-post/src/components/LongFeatured.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /10-routes-home-post/src/components/Widget.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /10-routes-home-post/src/components/WidgetTemperature.vue: -------------------------------------------------------------------------------- 1 | 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 | 22 |

Donec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue.

23 |
    24 |
  1. Vestibulum id ligula porta felis euismod semper.
  2. 25 |
  3. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
  4. 26 |
  5. Maecenas sed diam eget risus varius blandit sit amet non magna.
  6. 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 | 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 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /11-api-news/src/App.vue: -------------------------------------------------------------------------------- 1 | 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 | 8 | 9 | -------------------------------------------------------------------------------- /11-api-news/src/components/CommentsForm.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /11-api-news/src/components/CommentsItem.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | -------------------------------------------------------------------------------- /11-api-news/src/components/CommentsList.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /11-api-news/src/components/FeaturedPost.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | -------------------------------------------------------------------------------- /11-api-news/src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /11-api-news/src/components/Header.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /11-api-news/src/components/LongFeatured.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /11-api-news/src/components/Widget.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /11-api-news/src/components/WidgetTemperature.vue: -------------------------------------------------------------------------------- 1 | 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 | 22 |

Donec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue.

23 |
    24 |
  1. Vestibulum id ligula porta felis euismod semper.
  2. 25 |
  3. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
  4. 26 |
  5. Maecenas sed diam eget risus varius blandit sit amet non magna.
  6. 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 | 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 | [![Deploy](./img/cover-deploy.png)](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 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /12-improved-api-news/src/App.vue: -------------------------------------------------------------------------------- 1 | 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 | 9 | 10 | -------------------------------------------------------------------------------- /12-improved-api-news/src/components/CommentsForm.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /12-improved-api-news/src/components/CommentsItem.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | -------------------------------------------------------------------------------- /12-improved-api-news/src/components/CommentsList.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /12-improved-api-news/src/components/FeaturedPost.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | -------------------------------------------------------------------------------- /12-improved-api-news/src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /12-improved-api-news/src/components/Header.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /12-improved-api-news/src/components/LongFeatured.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /12-improved-api-news/src/components/Widget.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /12-improved-api-news/src/components/WidgetTemperature.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | [![Deploy SSR con Nuxt](./img/cover-deploy-ssr-vue.png)](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 | 20 | 21 | 62 | -------------------------------------------------------------------------------- /13-ssr-nuxt/components/CommentsForm.vue: -------------------------------------------------------------------------------- 1 |