Ready to start creating your own templates system!
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Search Photos!
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
user name
54 |
55 |
1000
56 |
15
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
user name
65 |
66 |
1000
67 |
15
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
user name
76 |
77 |
1000
78 |
15
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
Search Videos!
87 |
88 |
89 |
90 |
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';
--------------------------------------------------------------------------------