├── .gitignore ├── README.md ├── no-framework ├── css │ └── app.css ├── img │ ├── header.jpg │ ├── header2.jpg │ ├── pix1.jpg │ ├── pix2.jpg │ ├── pix3.jpg │ ├── pix4.jpg │ ├── templates.png │ └── user.png ├── index.html └── js │ ├── ajax.js │ ├── app.js │ ├── pixabay-api.js │ └── pixabay-config-sample.js └── with-your-framework ├── css └── app.css ├── img ├── header.jpg ├── header2.jpg ├── pix1.jpg ├── pix2.jpg ├── pix3.jpg ├── pix4.jpg ├── templates.png └── user.png ├── index.html └── js ├── ajax.js ├── app.js ├── dynamic-template.js ├── pixabay-api.js └── pixabay-config-sample.js /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | pids 4 | node_modules/ 5 | .idea/ 6 | .npm 7 | *.dat 8 | config.js 9 | databases/ 10 | config 11 | tmp/ 12 | mongoose-free-6.9.exe 13 | pixabay-config.js 14 | helpers-docs/ 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Create your own framewrok in less than 350 lines of code 2 | This repo contains all the code used in the tutorial "Create your own framework in less than 350 lines of code". You can find the tutorial in [my blog](http://blog.ibanyez.info) 3 | 4 | ### Install 5 | This application doesn't need any server. Just open index.html on each folder on your browser to run the app. 6 | 7 | If you have any trouble opening the file directly, you can use any kind of server that allows to use static files. If you don't have any server, you can use [Mongoose](https://cesanta.com/binary.html). It's a standalone web server, you can download and execute on the folder you want to run, no configuration needed. 8 | 9 | ### Pixabay key 10 | In order to execute the search on Pixabay you need to have a user on that platform, and [go to the API document page](https://pixabay.com/api/docs/) and go to the first API example. There is your API key for your user. Copy the file `/js/pixabay-config-sample.js` to `pixabay-config.js` and set you API key there. 11 | 12 | ## How the project is structured 13 | The repo has two main folder: 14 | 15 | * **no-framework:** Contains the original version before your framework. You can start with this code to follow the tutorial. 16 | * **with-framework:** This folder will contain the final result at the end of this tutorial. 17 | 18 | ### The Code 19 | 20 | * **/index.html** - Contains the whole interface. 21 | * **/js/app.js** - Where all the global functionality resides. These functions are: 22 | * **Navigation menu** - Receive the clicks on it and change the tab. 23 | * **Search process** - On keyup `enter` on search input box, calls the API, receive the results and calls the render function. 24 | * **Render function** - Receives the results and add them to the interface. 25 | * **/js/ajax.js** - A very basic ajax functionality for the purpose of the app. 26 | * **/js/pixabay-api.js** - Contains the API request functionality. Receive the search query and retrieve the data from the Pixabay API. Returns the results. 27 | * **/js/pixabay-config-sample.js** - You need to rename it to `/js/pixabay-config.js` and set your own Pixabay API key. You can find how to get your key in the readme file of this repo. 28 | 29 | ### The Interface 30 | 31 | * **Menu** - We have a navigation menu. When clicking on a menu item, we'll change the tab f the main content area. 32 | * **Tabs** - We have three tabs on the main content area. 33 | * **Home** - A dummy home page 34 | * **Photos** - To search photos 35 | * **Videos** - To search videos 36 | * **Search Bar** - On both photos and video tabs we have a search bar with an input text to write the search terms and submit when `enter` is pressed. 37 | -------------------------------------------------------------------------------- /no-framework/css/app.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | width: 100%; 3 | /*height: 100%;*/ 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | body{ 9 | background-color: #FAFAFA; 10 | color: #808080; 11 | font-family: 'Roboto', sans-serif; 12 | font-weight: 500; 13 | 14 | width: 80%; 15 | /* height: 100%; */ 16 | margin: auto; 17 | display: flex; 18 | flex-direction: column; 19 | align-content: center; 20 | justify-content: center; 21 | } 22 | 23 | ul{ 24 | margin: 0; 25 | padding: 0; 26 | list-style: none; 27 | } 28 | 29 | /******* 30 | PANELS 31 | */ 32 | .panel{ 33 | background-color: #FFF; 34 | box-shadow: 0 2px 6px rgba(0,0,0,0.15); 35 | padding: 15px; 36 | } 37 | 38 | 39 | 40 | #title-bar{ 41 | color: #808080; 42 | background-color: #FFF; 43 | font-size: 14px; 44 | padding: 30px; 45 | border-bottom: 1px solid #F0F0F0; 46 | font-weight: 400; 47 | text-align: center; 48 | margin-bottom: 30px; 49 | } 50 | 51 | /*** 52 | * MAIN 53 | */ 54 | #main{ 55 | width: 80%; 56 | height: 100%; 57 | margin: auto; 58 | display: flex; 59 | flex-direction: column; 60 | align-content: center; 61 | justify-content: center; 62 | } 63 | 64 | /*** 65 | * HEADER 66 | */ 67 | #header { 68 | width: 100%; 69 | height: 210px; 70 | padding: 0 0 3px 0; 71 | } 72 | #header .picture{ 73 | height: 150px; 74 | background-image: url('../img/header.jpg'); 75 | background-repeat: no-repeat; 76 | background-position: center; 77 | background-size: cover; 78 | line-height: 150px; 79 | padding: 0 60px; 80 | color: #FFFFFF; 81 | font-size: 20px; 82 | font-weight: 700; 83 | } 84 | 85 | /*** 86 | * NAV-BAR 87 | */ 88 | #header .nav-bar{ 89 | display: flex; 90 | justify-content: center; 91 | align-content: center; 92 | line-height: 60px; 93 | text-align: center; 94 | } 95 | 96 | #header .nav-bar > div{ 97 | margin: 0 20px; 98 | border-bottom: 3px solid transparent; 99 | cursor: pointer; 100 | } 101 | 102 | #header .nav-bar > div.selected{ 103 | margin: 0 20px; 104 | color: #F23C2F; 105 | border-bottom: 3px solid #F23C2F; 106 | } 107 | 108 | /*** 109 | * CONTENT 110 | */ 111 | #content{ 112 | flex: 1; 113 | } 114 | 115 | #content .tab{ 116 | display: none; 117 | width: 100%; 118 | } 119 | 120 | #content .tab.selected{ 121 | display: block; 122 | } 123 | 124 | #content .tab .title .fa { 125 | font-size: 20px; 126 | color: #D9D9D9; 127 | margin-right: 25px; 128 | } 129 | 130 | #content .tab .top-bar{ 131 | display: flex; 132 | padding: 20px; 133 | align-items: center; 134 | } 135 | 136 | #content .tab .title{ 137 | flex: 0.5; 138 | font-size: 16px; 139 | } 140 | 141 | #content .tab .search-bar{ 142 | flex: 0.5; 143 | display: flex; 144 | align-items: center; 145 | padding: 5px 20px; 146 | border: 2px solid #F0F0F0; 147 | border-radius: 40px; 148 | } 149 | 150 | #content .tab .search-bar input{ 151 | flex: 1; 152 | margin-right: 15px; 153 | border: none; 154 | padding: 10px 5px; 155 | background-color: transparent; 156 | } 157 | 158 | /*** 159 | * TAB HOME 160 | */ 161 | 162 | #content .illustration{ 163 | text-align: center; 164 | margin: 30px; 165 | } 166 | 167 | #content .illustration img{ 168 | width: 300px; 169 | margin: auto; 170 | } 171 | 172 | /*** 173 | * TAB SEARCH 174 | */ 175 | #content .tab .list{ 176 | display: flex; 177 | justify-content: space-between; 178 | flex-wrap: wrap; 179 | } 180 | 181 | #content .tab .list .item{ 182 | padding: 10px; 183 | flex: 0 0 235px; 184 | margin-bottom: 20px; 185 | } 186 | 187 | #content .tab .list .item .image{ 188 | background-size: cover; 189 | background-position: center; 190 | width: 235px; 191 | height: 170px; 192 | } 193 | 194 | #content .tab .list .item .details{ 195 | color: #999999; 196 | font-size: 13px; 197 | display: flex; 198 | padding-top: 10px; 199 | align-items: center; 200 | } 201 | 202 | #content .tab .list .item .details > div{ 203 | margin-right: 10px; 204 | } 205 | 206 | #content .tab .list .item .details .fa{ 207 | color: #D9D9D9; 208 | margin-right: 5px; 209 | } 210 | 211 | #content .tab .list .item .details .user{ 212 | flex: 1; 213 | display: flex; 214 | align-items: center; 215 | } 216 | 217 | #content .tab .list .item .details .thumb{ 218 | width: 30px; 219 | height: 30px; 220 | margin-right: 5px; 221 | border-radius: 30px; 222 | } -------------------------------------------------------------------------------- /no-framework/img/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/no-framework/img/header.jpg -------------------------------------------------------------------------------- /no-framework/img/header2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/no-framework/img/header2.jpg -------------------------------------------------------------------------------- /no-framework/img/pix1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/no-framework/img/pix1.jpg -------------------------------------------------------------------------------- /no-framework/img/pix2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/no-framework/img/pix2.jpg -------------------------------------------------------------------------------- /no-framework/img/pix3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/no-framework/img/pix3.jpg -------------------------------------------------------------------------------- /no-framework/img/pix4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/no-framework/img/pix4.jpg -------------------------------------------------------------------------------- /no-framework/img/templates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/no-framework/img/templates.png -------------------------------------------------------------------------------- /no-framework/img/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/no-framework/img/user.png -------------------------------------------------------------------------------- /no-framework/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My Pixabay Browser 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 25 | 26 |
27 |
28 |
29 |
Welcome to Pixabay Photo and Video Browser!
30 |
31 |
32 |
33 | 34 |
Ready to start creating your own templates system!
35 |
36 |
37 |
38 | 39 |
40 |
41 |
Search Photos!
42 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
user name
54 |
55 |
1000
56 | 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
user name
65 |
66 |
1000
67 | 68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
user name
76 |
77 |
1000
78 | 79 |
80 |
81 |
82 |
83 | 84 |
85 |
86 |
Search Videos!
87 | 91 |
92 |
93 |
94 |
95 |
96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /no-framework/js/ajax.js: -------------------------------------------------------------------------------- 1 | 2 | ( function(window) { 3 | 4 | 'use strict'; 5 | var ajax = { 6 | get : get 7 | }; 8 | 9 | function get(url, data, callback) { 10 | var xhr = new XMLHttpRequest(); 11 | xhr.open('GET', url + (data ? '?' + dataToUrl(data) : '')); 12 | xhr.onload = function () { 13 | if (xhr.status === 200) { 14 | callback(null, JSON.parse(xhr.responseText)); 15 | } 16 | else { 17 | callback(new Error('Request failed. Returned status of ' + xhr.status)); 18 | } 19 | }; 20 | xhr.send(); 21 | } 22 | 23 | function dataToUrl(object) { 24 | var encodedString = ''; 25 | for (var prop in object) { 26 | if (object.hasOwnProperty(prop)) { 27 | if (encodedString.length > 0) { 28 | encodedString += '&'; 29 | } 30 | encodedString += encodeURI(prop + '=' + object[prop]); 31 | } 32 | } 33 | return encodedString; 34 | } 35 | 36 | window.ajax = ajax; 37 | 38 | })(window); -------------------------------------------------------------------------------- /no-framework/js/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @date: December 30th, 2018 3 | * @author: David Ibañez 4 | */ 5 | 6 | document.addEventListener("DOMContentLoaded", function() { 7 | initButtons(); 8 | }); 9 | 10 | function initButtons(){ 11 | // Get menu-buttons from DOM 12 | var menu = document.querySelector('#menu-buttons'); 13 | 14 | // Add EventListener to the buttons 15 | menu.addEventListener('click', function(event){ 16 | 17 | // Check if the clicked element has the 'button' class 18 | // Because classList is not an array we do the trick to call an array method using the list as the 'this' element 19 | if([].indexOf.call(event.target.classList,'button') > -1) { 20 | 21 | // Remove selected class to the current selection 22 | menu.querySelector('.selected').classList.remove('selected'); 23 | 24 | // Add selected class to the button clicked 25 | event.target.classList.add('selected'); 26 | 27 | // Remove the selected class on the current tab 28 | document.querySelector('.tab.selected').classList.remove('selected'); 29 | 30 | // Add the selected class to the new tab selected 31 | document.querySelector('.tab.' + event.target.innerHTML.toLowerCase()).classList.add('selected'); 32 | } 33 | }); 34 | } 35 | 36 | var pixabay_page; 37 | 38 | function searchKeyUp(e, type){ 39 | if(e.keyCode == 13){ 40 | if(e.target.value.trim()){ 41 | 42 | var api_function = type === 'photos' ? px_api.searchPhotos : px_api.searchVideos; 43 | 44 | pixabay_page = 1; 45 | api_function(e.target.value.trim(), pixabay_page, function(results){ 46 | if(results){ 47 | renderResults(results, type); 48 | } 49 | }); 50 | } 51 | } 52 | } 53 | 54 | function renderResults(pictures, type) { 55 | 56 | // Select the list of photos or the list of videos depending on the type received 57 | var list = document.querySelector('#' + type + '-list'); 58 | 59 | // We reset the list 60 | list.innerHTML = ''; 61 | 62 | // If we received results and there are items 63 | if(pictures && pictures.items) { 64 | 65 | // We iterate for the entire items 66 | pictures.items.forEach(function(item) { 67 | 68 | // Create a new DIV element 69 | var newDiv = document.createElement("div"); 70 | 71 | // We add two classes for styling to our new div 72 | newDiv.classList.add('item'); 73 | newDiv.classList.add('panel'); 74 | 75 | // We fill the HTML for the new div using a concatenating a string and dynamic data 76 | newDiv.innerHTML = '
' + 77 | '
' + 78 | '
' + 79 | '
' + 80 | '
' + item.user + '
' + 81 | '
' + 82 | '
' + formatTotals(item.views) + '
' + 83 | '
' + formatTotals(item.likes) + '
' + 84 | '
'; 85 | 86 | // We append the new div to the selected list 87 | list.append(newDiv); 88 | }) 89 | } 90 | } 91 | 92 | function formatTotals(views){ 93 | if(views > 1000000){ 94 | return Math.floor(views / 10000) + 'M' 95 | } 96 | if(views > 1000){ 97 | return Math.floor(views / 1000) + 'K' 98 | } 99 | return views 100 | } -------------------------------------------------------------------------------- /no-framework/js/pixabay-api.js: -------------------------------------------------------------------------------- 1 | 2 | ( function(window) { 3 | 4 | 'use strict'; 5 | 6 | var api_connector = { 7 | searchPhotos : searchPhotos, 8 | searchVideos : searchVideos 9 | }; 10 | 11 | function searchPhotos(search_terms, page, callback) { 12 | var data = { 13 | key : pixabay_key, 14 | q : search_terms, 15 | image_type : 'photo', 16 | lang : 'en', 17 | page : page ? page : null 18 | }; 19 | ajax.get('https://pixabay.com/api/', data, function (err, result) { 20 | 21 | if(!err && result && result.hits){ 22 | var items = []; 23 | result.hits.forEach(function (item) { 24 | items.push({ 25 | type : 'photo', 26 | preview : item.previewURL, 27 | url : item.fullHDURL, 28 | user : item.user, 29 | user_img : item.userImageURL, 30 | views : item.views, 31 | likes : item.likes 32 | }); 33 | }); 34 | callback({ totalHits : result.totalHits, items : items }); 35 | } else { 36 | callback(); 37 | } 38 | }); 39 | } 40 | 41 | function searchVideos(search_terms, page, callback) { 42 | var data = { 43 | key : pixabay_key, 44 | q : search_terms, 45 | video_type : 'film', 46 | lang : 'en', 47 | page : page ? page : null 48 | }; 49 | ajax.get('https://pixabay.com/api/videos', data, function (err, result) { 50 | 51 | if(!err && result && result.hits){ 52 | var items = []; 53 | result.hits.forEach(function (item) { 54 | items.push({ 55 | type : 'video', 56 | preview : 'https://i.vimeocdn.com/video/' + item.picture_id + '_250x150.jpg', 57 | url : item.videos.large.url, 58 | user : item.user, 59 | user_img : item.userImageURL, 60 | views : item.views, 61 | likes : item.likes 62 | }); 63 | }); 64 | callback({ totalHits : result.totalHits, items : items }); 65 | } else { 66 | callback(); 67 | } 68 | }); 69 | } 70 | 71 | window.px_api = api_connector; 72 | 73 | })(window); 74 | 75 | -------------------------------------------------------------------------------- /no-framework/js/pixabay-config-sample.js: -------------------------------------------------------------------------------- 1 | // Copy this file to 'pixabay-config.js' and put your pixabay's key here 2 | pixabay_key = 'your-key-here'; -------------------------------------------------------------------------------- /with-your-framework/css/app.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | width: 100%; 3 | /*height: 100%;*/ 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | body{ 9 | background-color: #FAFAFA; 10 | color: #808080; 11 | font-family: 'Roboto', sans-serif; 12 | font-weight: 500; 13 | 14 | width: 80%; 15 | /* height: 100%; */ 16 | margin: auto; 17 | display: flex; 18 | flex-direction: column; 19 | align-content: center; 20 | justify-content: center; 21 | } 22 | 23 | ul{ 24 | margin: 0; 25 | padding: 0; 26 | list-style: none; 27 | } 28 | 29 | /******* 30 | PANELS 31 | */ 32 | .panel{ 33 | background-color: #FFF; 34 | box-shadow: 0 2px 6px rgba(0,0,0,0.15); 35 | padding: 15px; 36 | } 37 | 38 | 39 | 40 | #title-bar{ 41 | color: #808080; 42 | background-color: #FFF; 43 | font-size: 14px; 44 | padding: 30px; 45 | border-bottom: 1px solid #F0F0F0; 46 | font-weight: 400; 47 | text-align: center; 48 | margin-bottom: 30px; 49 | } 50 | 51 | /*** 52 | * MAIN 53 | */ 54 | #main{ 55 | width: 80%; 56 | height: 100%; 57 | margin: auto; 58 | display: flex; 59 | flex-direction: column; 60 | align-content: center; 61 | justify-content: center; 62 | } 63 | 64 | /*** 65 | * HEADER 66 | */ 67 | #header { 68 | width: 100%; 69 | height: 210px; 70 | padding: 0 0 3px 0; 71 | } 72 | #header .picture{ 73 | height: 150px; 74 | background-image: url('../img/header.jpg'); 75 | background-repeat: no-repeat; 76 | background-position: center; 77 | background-size: cover; 78 | line-height: 150px; 79 | padding: 0 60px; 80 | color: #FFFFFF; 81 | font-size: 20px; 82 | font-weight: 700; 83 | } 84 | 85 | /*** 86 | * NAV-BAR 87 | */ 88 | #header .nav-bar{ 89 | display: flex; 90 | justify-content: center; 91 | align-content: center; 92 | line-height: 60px; 93 | text-align: center; 94 | } 95 | 96 | #header .nav-bar > div{ 97 | margin: 0 20px; 98 | border-bottom: 3px solid transparent; 99 | cursor: pointer; 100 | } 101 | 102 | #header .nav-bar > div.selected{ 103 | margin: 0 20px; 104 | color: #F23C2F; 105 | border-bottom: 3px solid #F23C2F; 106 | } 107 | 108 | /*** 109 | * CONTENT 110 | */ 111 | #content{ 112 | flex: 1; 113 | } 114 | 115 | #content .tab{ 116 | display: none; 117 | width: 100%; 118 | } 119 | 120 | #content .tab.selected{ 121 | display: block; 122 | } 123 | 124 | #content .tab .title .fa { 125 | font-size: 20px; 126 | color: #D9D9D9; 127 | margin-right: 25px; 128 | } 129 | 130 | #content .tab .top-bar{ 131 | display: flex; 132 | padding: 20px; 133 | align-items: center; 134 | } 135 | 136 | #content .tab .title{ 137 | flex: 0.5; 138 | font-size: 16px; 139 | } 140 | 141 | #content .tab .search-bar{ 142 | flex: 0.5; 143 | display: flex; 144 | align-items: center; 145 | padding: 5px 20px; 146 | border: 2px solid #F0F0F0; 147 | border-radius: 40px; 148 | } 149 | 150 | #content .tab .search-bar input{ 151 | flex: 1; 152 | margin-right: 15px; 153 | border: none; 154 | padding: 10px 5px; 155 | background-color: transparent; 156 | } 157 | 158 | /*** 159 | * TAB HOME 160 | */ 161 | 162 | #content .illustration{ 163 | text-align: center; 164 | margin: 30px; 165 | } 166 | 167 | #content .illustration img{ 168 | width: 300px; 169 | margin: auto; 170 | } 171 | 172 | /*** 173 | * TAB SEARCH 174 | */ 175 | #content .tab .list{ 176 | display: flex; 177 | justify-content: space-between; 178 | flex-wrap: wrap; 179 | } 180 | 181 | #content .tab .list .item{ 182 | padding: 10px; 183 | flex: 0 0 235px; 184 | margin-bottom: 20px; 185 | } 186 | 187 | #content .tab .list .item .image{ 188 | background-size: cover; 189 | background-position: center; 190 | width: 235px; 191 | height: 170px; 192 | } 193 | 194 | #content .tab .list .item .details{ 195 | color: #999999; 196 | font-size: 13px; 197 | display: flex; 198 | padding-top: 10px; 199 | align-items: center; 200 | } 201 | 202 | #content .tab .list .item .details > div{ 203 | margin-right: 10px; 204 | } 205 | 206 | #content .tab .list .item .details .fa{ 207 | color: #D9D9D9; 208 | margin-right: 5px; 209 | } 210 | 211 | #content .tab .list .item .details .user{ 212 | flex: 1; 213 | display: flex; 214 | align-items: center; 215 | } 216 | 217 | #content .tab .list .item .details .thumb{ 218 | width: 30px; 219 | height: 30px; 220 | margin-right: 5px; 221 | border-radius: 30px; 222 | } 223 | 224 | #content .tab .list .item.video-class{ 225 | border: 2px solid #6daff2; 226 | border-radius: 10px; 227 | } -------------------------------------------------------------------------------- /with-your-framework/img/header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/with-your-framework/img/header.jpg -------------------------------------------------------------------------------- /with-your-framework/img/header2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/with-your-framework/img/header2.jpg -------------------------------------------------------------------------------- /with-your-framework/img/pix1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/with-your-framework/img/pix1.jpg -------------------------------------------------------------------------------- /with-your-framework/img/pix2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/with-your-framework/img/pix2.jpg -------------------------------------------------------------------------------- /with-your-framework/img/pix3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/with-your-framework/img/pix3.jpg -------------------------------------------------------------------------------- /with-your-framework/img/pix4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/with-your-framework/img/pix4.jpg -------------------------------------------------------------------------------- /with-your-framework/img/templates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/with-your-framework/img/templates.png -------------------------------------------------------------------------------- /with-your-framework/img/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriyeng/create-your-framework/bea3ea1d47b729d768d47bddb21081231d2902c1/with-your-framework/img/user.png -------------------------------------------------------------------------------- /with-your-framework/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My Pixabay Browser 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 25 | 26 |
27 |
28 |
29 |
Welcome to Pixabay Photo and Video Browser!
30 |
31 |
32 |
33 | 34 |
Ready to start creating your own templates system!
35 |
36 |
37 |
38 | 39 |
40 |
41 |
Search Photos!
42 | 46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
user name
54 |
55 |
1000
56 | 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
user name
65 |
66 |
1000
67 | 68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
user name
76 |
77 |
1000
78 | 79 |
80 |
81 |
82 |
83 | 84 |
85 |
86 |
Search Videos!
87 | 91 |
92 |
93 |
94 |
95 |
96 | 97 | 98 | 99 | 100 | 101 | 102 | 105 | 106 | 119 | 120 | -------------------------------------------------------------------------------- /with-your-framework/js/ajax.js: -------------------------------------------------------------------------------- 1 | 2 | ( function(window) { 3 | 4 | 'use strict'; 5 | var ajax = { 6 | get : get 7 | }; 8 | 9 | function get(url, data, callback) { 10 | var xhr = new XMLHttpRequest(); 11 | xhr.open('GET', url + (data ? '?' + dataToUrl(data) : '')); 12 | xhr.onload = function () { 13 | if (xhr.status === 200) { 14 | callback(null, JSON.parse(xhr.responseText)); 15 | } 16 | else { 17 | callback(new Error('Request failed. Returned status of ' + xhr.status)); 18 | } 19 | }; 20 | xhr.send(); 21 | } 22 | 23 | function dataToUrl(object) { 24 | var encodedString = ''; 25 | for (var prop in object) { 26 | if (object.hasOwnProperty(prop)) { 27 | if (encodedString.length > 0) { 28 | encodedString += '&'; 29 | } 30 | encodedString += encodeURI(prop + '=' + object[prop]); 31 | } 32 | } 33 | return encodedString; 34 | } 35 | 36 | window.ajax = ajax; 37 | 38 | })(window); -------------------------------------------------------------------------------- /with-your-framework/js/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @date: December 30th, 2018 3 | * @author: David Ibañez 4 | */ 5 | 6 | document.addEventListener("DOMContentLoaded", function() { 7 | initButtons(); 8 | }); 9 | 10 | function initButtons(){ 11 | // Get menu-buttons from DOM 12 | var menu = document.querySelector('#menu-buttons'); 13 | 14 | // Add EventListener to the buttons 15 | menu.addEventListener('click', function(event){ 16 | 17 | // Check if the clicked element has the 'button' class 18 | // Because classList is not an array we do the trick to call an array method using the list as the 'this' element 19 | if([].indexOf.call(event.target.classList,'button') > -1) { 20 | 21 | // Remove selected class to the current selection 22 | menu.querySelector('.selected').classList.remove('selected'); 23 | 24 | // Add selected class to the button clicked 25 | event.target.classList.add('selected'); 26 | 27 | // Remove the selected class on the current tab 28 | document.querySelector('.tab.selected').classList.remove('selected'); 29 | 30 | // Add the selected class to the new tab selected 31 | document.querySelector('.tab.' + event.target.innerHTML.toLowerCase()).classList.add('selected'); 32 | } 33 | }); 34 | } 35 | 36 | var pixabay_page; 37 | 38 | function searchKeyUp(e, type){ 39 | if(e.keyCode == 13){ 40 | if(e.target.value.trim()){ 41 | 42 | var api_function = type === 'photos' ? px_api.searchPhotos : px_api.searchVideos; 43 | 44 | pixabay_page = 1; 45 | api_function(e.target.value.trim(), pixabay_page, function(results){ 46 | if(results){ 47 | renderResults(results, type); 48 | } 49 | }); 50 | } 51 | } 52 | } 53 | 54 | function renderResults(pictures, type) { 55 | 56 | // Select the list of photos or the list of videos depending on the type received 57 | var list = document.querySelector('#' + type + '-list'); 58 | 59 | // We reset the list 60 | list.innerHTML = ''; 61 | 62 | // If we received results and there are items 63 | if(pictures && pictures.items) { 64 | list.innerHTML = dt.render('template-items', { items : pictures.items }); 65 | /* 66 | // We iterate for the entire items 67 | list.innerHTML = pictures.items.reduce(function(str_html, item) { 68 | // Appends the new item to the list calling our new dynamic template render function 69 | return str_html + dt.render('template-item', { item : item }); 70 | }, ''); 71 | */ 72 | } 73 | } 74 | 75 | function formatTotals(views){ 76 | if(views > 1000000){ 77 | return Math.floor(views / 10000) + 'M' 78 | } 79 | if(views > 1000){ 80 | return Math.floor(views / 1000) + 'K' 81 | } 82 | return views 83 | } -------------------------------------------------------------------------------- /with-your-framework/js/dynamic-template.js: -------------------------------------------------------------------------------- 1 | (function(window){ 2 | 3 | 'use strict'; 4 | var dynamicTemplate = { 5 | render : render 6 | }; 7 | 8 | // Gets the template, finds dynamic value queries, replaces with its value and return a string of the new HTML 9 | function render(template_selector, object) { 10 | 11 | // Similar as jQuery we distinct with a $ the variables that contains HTMLElements. 12 | // This can help to distinguish when you are dealing with strings or HTMLElements 13 | var $template = document.getElementById(template_selector); 14 | 15 | // Check if template exists 16 | if (!$template) { 17 | console.error("Template doesn't exists! Check Your template Selector: " + template_selector); 18 | return ''; 19 | } 20 | 21 | return applyTemplate($template, object); 22 | } 23 | 24 | function applyTemplate($template, object) { 25 | var str_html = $template.outerHTML.substr(0,30).indexOf('script')>-1 ? $template.innerHTML : $template.outerHTML; 26 | str_html = applyIterates(str_html, object); 27 | return applyDynamicValues(str_html, object); 28 | } 29 | 30 | // We create a function to replace the curly braces with data values 31 | function applyDynamicValues(str_html, object){ 32 | // Iterates over all the dynamic queries found and replace by its values 33 | return getDynamicVariables(str_html).reduce(function(html, dyn_value){ 34 | 35 | // Creates a regex to replace the value for each element found 36 | var regexp = new RegExp("{{" + dyn_value.replace('(','\\(').replace(')','\\)') + "}}", 'g'); 37 | 38 | // Gets the value of the dynamic query 39 | var value = getValue(dyn_value, object); 40 | 41 | // Apply the replace to all items in the original HTML string 42 | return html.replace(regexp, value !== null ? value : ''); 43 | }, str_html); 44 | } 45 | 46 | // Extract the dynamic values queried in curly braces 47 | function getDynamicVariables(str){ 48 | 49 | // Regex Explanation: 50 | // (?<={{) Require opening curly braces before match, but not include in the result 51 | // ([^]*?) Accept the minimum string length before the next condition below. 52 | // (?=}}) - Require closing curly braces after match 53 | 54 | // Returns matches or an empty array if there's no matches 55 | var result = (str.match(/(?<={{)([^]*?)(?=}})/g) || []); 56 | 57 | // Filter the results to remove duplicates 58 | return result.filter(function(item, pos) { 59 | return result.indexOf(item) === pos; 60 | }) 61 | } 62 | 63 | function getValue(var_string, object){ 64 | 65 | //Check if the dynamic variable is the type {{if:condition:value1:value2}} 66 | if (var_string.indexOf('if:') > -1) { 67 | // Retrieves the value from the conditional 68 | return getValueFromConditional(var_string, object); 69 | } else if (var_string.indexOf('compute:') > -1) { 70 | return getValueFromCompute(var_string, object); 71 | } else { 72 | return getValueFromObject(var_string, object); 73 | } 74 | } 75 | 76 | // Analize the condition: {{if:some-value:operator:value:option1:option2}} 77 | function getValueFromConditional(str_condition, object){ 78 | 79 | // Split conditional parameters into arrey 80 | var condition = str_condition.split(':'); 81 | 82 | // We get the value from the object to compare {{if:SOME-VALUE:operator:value:option1:option2}} 83 | var variable_value = getValueFromObject(condition[1], object); 84 | 85 | // We convert some possible string value to the javascript primitives {{if:some-value:operator:COMPARING-VALUE:option1:option2}} 86 | switch(condition[3]){ 87 | case "null": condition[3] = null; break; 88 | case "false": condition[3] = false; break; 89 | case "true": condition[3] = true; break; 90 | } 91 | 92 | // for the conditional values we can use objects indicated by {@object.property@} on {{if:some-value:operator:value:OPTION1:OPTION2}} 93 | condition[4] = checkVariableInContent(condition[4], object); 94 | condition[5] = checkVariableInContent(condition[5], object); 95 | 96 | // Finally we compute the conditional based on the operator {{if:some-value:OPERATOR:value:option1:option2}} 97 | // We use 'is' to compare values to true like: item.name ? 'value if true' : 'value if false' 98 | switch(condition[2]){ 99 | case 'is': 100 | return variable_value ? condition[4] : condition[5]; 101 | case '==': 102 | return (condition[3] == variable_value) ? condition[4] : condition[5]; 103 | case '!=': 104 | return (condition[3] != variable_value) ? condition[4] : condition[5]; 105 | case '>': 106 | return (condition[3] > variable_value) ? condition[4] : condition[5]; 107 | case '<': 108 | return (condition[3] < variable_value) ? condition[4] : condition[5]; 109 | case '<=': 110 | return (condition[3] <= variable_value) ? condition[4] : condition[5]; 111 | } 112 | } 113 | 114 | // We check if condition values has reference to the object to get dynamic data 115 | function checkVariableInContent(condition, object){ 116 | 117 | // Check if the values contains expressions to evaluate expressed as {@object@} or {@object.property@} 118 | var expr = condition.match(/{@(.*?)@}/); 119 | 120 | // Replace the expression by its value found in the object or return the string as it is 121 | return expr ? condition.replace(expr[0],getValueFromObject(expr[1], object)) : condition; 122 | } 123 | 124 | // Gets the value from the data 125 | function getValueFromObject(str_property, object){ 126 | // Clean white spaces 127 | str_property=str_property.trim(); 128 | 129 | // set the default value for the object 130 | var value = null; 131 | 132 | // We protect the code to throw an error when trying to access a property of undefined 133 | try { 134 | // Iterates through the dot notation checking if the object has the property 135 | value = str_property.split('.').reduce(function(props, item){ 136 | return props.hasOwnProperty(item) ? props[item] : null; 137 | }, object); 138 | } catch(e){ 139 | // We catch errors when trying to access properties for undefined objects 140 | console.warn("Tried to read a property of undefined, str_property: " + str_property); 141 | if(typeof object === 'undefined'){ 142 | console.warn("The Object is undefined. Check the Object passed to fill the data"); 143 | } 144 | value = null; 145 | } 146 | 147 | return value; 148 | } 149 | 150 | function getValueFromCompute(str_compute, object) { 151 | 152 | var regex = /compute:([\w.]*)\((.*)\)/; // compute:function_name(parameters) -> we create 2 group matches, one for the fn name an another for the parameters 153 | var parts = regex.exec(str_compute); 154 | var fn = parts[1]; 155 | 156 | // Analize the parameters passed to the function 157 | var values = parts[2].split(',').map(function(parameter, index){ 158 | 159 | // cleaning white spaces 160 | parameter = parameter.trim(); 161 | 162 | // Returning the value. If string, removing the ', if object, get the property of the object 163 | return parameter.indexOf('\'') > -1 ? parameter.replace(/\'/g, '') : getValueFromObject(parameter, object); 164 | }); 165 | 166 | if(typeof window[fn] === "function"){ 167 | return window[fn](values); 168 | }else { 169 | if(fn.indexOf('.')>-1){ 170 | 171 | // we break function into properties based on dot notations. object.props0.props1 172 | var props = fn.split('.'); 173 | 174 | // We verify if object.firstField exists and object.firstField.secondField if it is a function 175 | // If not we check if window.firstField exists and window.firstField.secondField if it is a function 176 | if (object[props[0]] && typeof object[props[0]][props[1]] === "function") { 177 | return object[props[0]][props[1]].apply(object[props[0]], values); 178 | } else if(window[props[0]] && typeof window[props[0]][props[1]] === "function") { 179 | return window[props[0]][props[1]].apply(window[props[0]], values); 180 | } else { 181 | // We create an advice that we are using a not existing function on this template 182 | console.log("Error: " + str_compute + " is not a function"); 183 | } 184 | } 185 | console.log("Error: " + str_compute + " is not a function"); 186 | } 187 | } 188 | 189 | function applyIterates(str_html, object) { 190 | // Creates dummy DOM to apply work with the template 191 | var newHTMLDocument = document.implementation.createHTMLDocument('preview'); 192 | var $html = newHTMLDocument.createElement('div'); 193 | 194 | //Sets the HTML content to the new dummy div 195 | $html.innerHTML = str_html; 196 | 197 | // Search for iterations on the HTML code. We define iterates using a class
198 | var $iterates = $html.querySelectorAll('.dt-iterate'); 199 | 200 | // We iterate through all iterations in the template 201 | while($iterates.length) { 202 | 203 | var $iterate = $html.querySelectorAll('.dt-iterate')[0]; 204 | // Avoid to repeat the iteration inside iterations remocing the attribute 205 | $iterate.classList.remove('dt-iterate'); 206 | 207 | // We get the data object to iterate with 208 | var iteration_data = $iterate.attributes["dt-data"].value.split(' in '); 209 | 210 | // we set the template to use as the iteration but check if there's a component to use as template 211 | var $template = $iterate; 212 | 213 | if ($iterate.attributes["dt-component"] && $iterate.attributes["dt-component"].value) { 214 | var $component = document.getElementById($iterate.attributes["dt-component"].value); 215 | if (!$component) { 216 | console.error('Component not found!: ' + $iterate.attributes["dt-component"].value); 217 | return $html; 218 | } else { 219 | $template = $component.cloneNode(true); 220 | } 221 | } 222 | 223 | // iterate over the object array 224 | // We use reduce to write every iteration to $temp_div 225 | var iterations_html = getValueFromObject(iteration_data[1], object).reduce(function (iterations_html, element) { 226 | // Creates a temp object that will be used on the iteration 227 | var item = {}; 228 | 229 | // set the name to this object based in the expression 'item in items'. -> item.item = element 230 | item[iteration_data[0]] = element; 231 | 232 | // contactenates the new html created in this iteration into the one obtained in previous iterations 233 | iterations_html += applyTemplate($template, item); 234 | 235 | // Returns the HTML obtained in the iterations so far 236 | return iterations_html; 237 | }, ''); 238 | 239 | // Sets the result HTML string to the original template object when we found the .dt-iterate class 240 | $iterate.parentNode.innerHTML = iterations_html; 241 | 242 | // We check if there're some more iteration to apply 243 | $iterates = $html.querySelectorAll('.dt-iterate'); 244 | } 245 | 246 | // Returns the full HTML when all iterations finished 247 | return $html.innerHTML; 248 | } 249 | 250 | window.dt = dynamicTemplate; 251 | 252 | })(window); -------------------------------------------------------------------------------- /with-your-framework/js/pixabay-api.js: -------------------------------------------------------------------------------- 1 | 2 | ( function(window) { 3 | 4 | 'use strict'; 5 | 6 | var api_connector = { 7 | searchPhotos : searchPhotos, 8 | searchVideos : searchVideos 9 | }; 10 | 11 | function searchPhotos(search_terms, page, callback) { 12 | var data = { 13 | key : pixabay_key, 14 | q : search_terms, 15 | image_type : 'photo', 16 | lang : 'en', 17 | page : page ? page : null 18 | }; 19 | ajax.get('https://pixabay.com/api/', data, function (err, result) { 20 | 21 | if(!err && result && result.hits){ 22 | var items = []; 23 | result.hits.forEach(function (item) { 24 | items.push({ 25 | type : 'photo', 26 | preview : item.previewURL, 27 | url : item.fullHDURL, 28 | user : item.user, 29 | user_img : item.userImageURL, 30 | views : item.views, 31 | likes : item.likes 32 | }); 33 | }); 34 | callback({ totalHits : result.totalHits, items : items }); 35 | } else { 36 | callback(); 37 | } 38 | }); 39 | } 40 | 41 | function searchVideos(search_terms, page, callback) { 42 | var data = { 43 | key : pixabay_key, 44 | q : search_terms, 45 | video_type : 'film', 46 | lang : 'en', 47 | page : page ? page : null 48 | }; 49 | ajax.get('https://pixabay.com/api/videos', data, function (err, result) { 50 | 51 | if(!err && result && result.hits){ 52 | var items = []; 53 | result.hits.forEach(function (item) { 54 | items.push({ 55 | type : 'video', 56 | preview : 'https://i.vimeocdn.com/video/' + item.picture_id + '_250x150.jpg', 57 | url : item.videos.large.url, 58 | user : item.user, 59 | user_img : item.userImageURL, 60 | views : item.views, 61 | likes : item.likes 62 | }); 63 | }); 64 | callback({ totalHits : result.totalHits, items : items }); 65 | } else { 66 | callback(); 67 | } 68 | }); 69 | } 70 | 71 | window.px_api = api_connector; 72 | 73 | })(window); 74 | 75 | -------------------------------------------------------------------------------- /with-your-framework/js/pixabay-config-sample.js: -------------------------------------------------------------------------------- 1 | // Copy this file to 'pixabay-config.js' and put your pixabay's key here 2 | pixabay_key = 'your-key-here'; --------------------------------------------------------------------------------