├── .gitignore ├── server ├── README.md ├── index.html ├── js ├── stores.js ├── helpers.js └── app.js └── css └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | gh-pages -------------------------------------------------------------------------------- /server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | browser-sync start --server --files="index.html, css/*, js/*" 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A simple to do list in a pure vanilla javascript 2 | 3 | [DEMO PAGE HERE, I hope you like :)](http://expalmer.github.io/todo-list-vanilla-js/) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo List 6 | 7 | 8 | 9 |

Todo List

10 |
11 |
12 | 13 | 14 |
15 |
16 |
17 | 0 items left 18 |
19 |
20 | 25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 |
33 |
34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /js/stores.js: -------------------------------------------------------------------------------- 1 | ;(function(context) { 2 | 3 | 'use strict'; 4 | 5 | var store = localStorage; 6 | 7 | function Stores( key ) { 8 | this.key = key; 9 | if( !store[key] ) { 10 | store[key] = JSON.stringify([]); 11 | } 12 | } 13 | 14 | Stores.fn = Stores.prototype; 15 | 16 | Stores.fn.find = function( id, cb ) { 17 | 18 | var items = JSON.parse(store[this.key]); 19 | var item = items 20 | .filter(function(item) { 21 | return id === item.id; 22 | }); 23 | cb.call(this, item[0] || {} ); 24 | }; 25 | 26 | Stores.fn.findAll = function( cb ) { 27 | cb.call(this, JSON.parse( store[this.key] )); 28 | }; 29 | 30 | Stores.fn.save = function( item, cb, options ) { 31 | 32 | var items = JSON.parse(store[this.key]); 33 | 34 | // Implementar Update Multiple 35 | // if ( options && options.multi ) { 36 | // } 37 | 38 | // Update 39 | if (item.id) { 40 | items = items 41 | .map(function( x ) { 42 | if( x.id === item.id ) { 43 | for (var prop in item ) { 44 | x[prop] = item[prop]; 45 | } 46 | } 47 | return x; 48 | }); 49 | // Insert 50 | } else { 51 | item.id = new Date().getTime(); 52 | items.push(item); 53 | } 54 | 55 | store[this.key] = JSON.stringify(items); 56 | 57 | cb.call(this, item); 58 | // this.findAll(cb); 59 | 60 | }; 61 | 62 | Stores.fn.destroy = function( id, cb ) { 63 | 64 | var items = JSON.parse(store[this.key]); 65 | items = items 66 | .filter(function( x ) { 67 | return x.id !== id; 68 | }); 69 | 70 | store[this.key] = JSON.stringify(items); 71 | 72 | cb.call(this, true); 73 | 74 | }; 75 | 76 | 77 | Stores.fn.drop = function( cb ) { 78 | store[this.key] = JSON.stringify([]); 79 | this.findAll(cb); 80 | }; 81 | 82 | context.Stores = Stores; 83 | 84 | })( this ); -------------------------------------------------------------------------------- /js/helpers.js: -------------------------------------------------------------------------------- 1 | ;(function(context) { 2 | 3 | 'use strict'; 4 | 5 | function $( selector, scope ) { 6 | return $.qsa( selector, scope, true ); 7 | } 8 | 9 | $['qsa'] = function( selector, scope, first ) { 10 | var e = ( scope || document).querySelectorAll( selector ); 11 | return first ? e[0] : e; 12 | }; 13 | 14 | $['noop'] = function() {}; 15 | 16 | 17 | $['each'] = function( array, cb ) { 18 | var len = array.length; 19 | var idx = -1; 20 | while( ++idx < len ) { 21 | cb.call(array, array[idx], idx, array); 22 | } 23 | }; 24 | 25 | $['pluralization'] = function( value ) { 26 | return +value === 1 ? "" : "s"; 27 | }; 28 | 29 | $['on'] = function (target, type, callback, useCapture) { 30 | target.addEventListener(type, callback, !!useCapture); 31 | }; 32 | 33 | $['delegate'] = function (target, selector, type, handler) { 34 | function dispatchEvent(event) { 35 | var targetElement = event.target; 36 | var potentialElements = $.qsa(selector, target); 37 | var hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0; 38 | if (hasMatch) { 39 | handler.call(targetElement, event); 40 | } 41 | } 42 | 43 | // https://developer.mozilla.org/en-US/docs/Web/Events/blur 44 | var useCapture = type === 'blur' || type === 'focus'; 45 | 46 | $.on(target, type, dispatchEvent, useCapture); 47 | }; 48 | 49 | $['parent'] = function (element, tagName) { 50 | if (!element.parentNode) { 51 | return; 52 | } 53 | if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) { 54 | return element.parentNode; 55 | } 56 | return $.parent(element.parentNode, tagName); 57 | }; 58 | 59 | context.$ = $; 60 | 61 | })(this); 62 | 63 | // Prototype 64 | NodeList.prototype.each = function( fn ) { 65 | var len = this.length; 66 | var idx = -1; 67 | while( ++idx < len ) { 68 | fn.call(this, this[idx], idx, this); 69 | } 70 | } 71 | 72 | Array.prototype.some = function( fn ) { 73 | 74 | var len = this.length; 75 | var idx = -1; 76 | while( ++idx < len ) { 77 | if( fn( this[idx], idx, this ) ) { 78 | return true; 79 | } 80 | } 81 | return false; 82 | 83 | }; 84 | 85 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | 5 | *, *:before, *:after { 6 | box-sizing: inherit; 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | body { 12 | margin: 0 auto; 13 | font-size: 100%; 14 | font-family: 'Roboto Slab', serif; 15 | color: #fff; 16 | background: linear-gradient(to right, #26232C 0%,#26232C 40%,#26232C 100%); 17 | } 18 | 19 | ::-webkit-input-placeholder { 20 | color: #3C3647; 21 | font-style: italic; 22 | } 23 | 24 | h1 { 25 | margin-top: 20px; 26 | text-align: center; 27 | color: #7DD180; 28 | text-shadow: 0px 2px #231C31; 29 | } 30 | 31 | a { 32 | text-decoration: none; 33 | } 34 | 35 | .limiter { 36 | margin: 20px auto; 37 | max-width: 600px; 38 | background: #8cbdc5; 39 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); 40 | } 41 | 42 | .row:before, 43 | .row:after { 44 | content: ""; 45 | display: table; 46 | } 47 | .row:after { 48 | clear: both; 49 | } 50 | .row { 51 | zoom: 1; 52 | } 53 | 54 | .col-1-2 { 55 | float: left; 56 | padding: 10px; 57 | width: 40%; 58 | } 59 | 60 | .col-1-4 { 61 | float: left; 62 | padding: 10px; 63 | width: 30%; 64 | } 65 | 66 | .insert { 67 | position: relative; 68 | padding: 30px; 69 | border-top: solid 1px #3B3841; 70 | overflow: hidden; 71 | background: #8cbdc5; 72 | } 73 | 74 | #js-insert { 75 | top: 0; 76 | left: 50px; 77 | width: 100%; 78 | height: 60px; 79 | position: absolute; 80 | border: none; 81 | outline: none; 82 | font-size: 23px; 83 | font-family: inherit; 84 | color:black; 85 | background: #8cbdc5; 86 | } 87 | 88 | #js-toggle-all { 89 | position: absolute; 90 | top: 20px; 91 | left: 20px; 92 | } 93 | 94 | .bar { 95 | color: #6C6777; 96 | background: #eee; 97 | border-top: solid 1px #ddd; 98 | border-bottom: solid 1px #ddd; 99 | box-shadow: inset 0 0 16px rgba(0,0,0,0.1); 100 | } 101 | 102 | .info { 103 | float: left; 104 | width: 33.333333333%; 105 | padding: 10px; 106 | font-size: 14px; 107 | } 108 | 109 | .info:last-child { 110 | text-align: right; 111 | } 112 | 113 | .total { 114 | display: inline-block; 115 | float: left; 116 | margin-top: 4px; 117 | } 118 | 119 | .filter { 120 | text-align: center; 121 | } 122 | 123 | .filter li { 124 | display: inline-block; 125 | } 126 | 127 | .button { 128 | display: inline-block; 129 | margin: 0 4px; 130 | padding: 2px 8px; 131 | font-size: 13px; 132 | color: #6C6777; 133 | border: solid 1px #6C6777; 134 | border-radius: 20px; 135 | cursor: pointer; 136 | outline: none; 137 | } 138 | 139 | .button.selected { 140 | color: #fff; 141 | background: #149f00; 142 | border: solid 1px #6C6777; 143 | } 144 | 145 | .button:active { 146 | /*transform: translate(0px,2px);*/ 147 | /*border-bottom: 1px solid;*/ 148 | } 149 | 150 | .button--clear { 151 | float: right; 152 | color: #fff; 153 | background: #EE1630; 154 | border: solid 1px #EE1630; 155 | } 156 | 157 | .list{ 158 | background-color:#f8bf9b; 159 | position:relative; 160 | } 161 | .list:nth-child(2n){ 162 | position: relative; 163 | background-color:#f37e9b; 164 | } 165 | 166 | .list:before { 167 | content: ''; 168 | position: absolute; 169 | right: 0; 170 | bottom: 0; 171 | left: 0; 172 | height: 21px; 173 | overflow: hidden; 174 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #25222B, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 17px 0 -6px #25222B, 0 17px 2px -6px rgba(0, 0, 0, 0.2); 175 | } 176 | 177 | .list li { 178 | position: relative; 179 | padding: 10px 20px; 180 | border-bottom: solid 1px #2C2933; 181 | list-style: none; 182 | overflow: hidden; 183 | color:black; 184 | } 185 | 186 | .list li:hover .destroy { 187 | display: inline-block; 188 | } 189 | 190 | .todo span { 191 | display: block; 192 | margin-left: 30px; 193 | font-size: 23px; 194 | transition: color 0.3s ease-out; 195 | } 196 | 197 | .completed .todo span { 198 | text-decoration: line-through; 199 | color:#a19a9ae0; 200 | } 201 | 202 | .toggle { 203 | position: absolute; 204 | top: 14px; 205 | left: 14px; 206 | -webkit-appearance: none; 207 | appearance: none; 208 | outline: none; 209 | } 210 | 211 | .toggle:after { 212 | content: ""; 213 | position: absolute; 214 | transform: rotateZ(-42deg); 215 | animation: checkOut .0s ease-out forwards; 216 | } 217 | 218 | .toggle:checked:after { 219 | animation: checkIn .3s ease-out forwards; 220 | } 221 | 222 | @keyframes checkIn { 223 | 0% { 224 | width: 24px; 225 | height: 24px; 226 | background: #3D3747; 227 | border-radius: 20px; 228 | box-shadow: inset 0 0 3px #2B2831; 229 | } 230 | 100% { 231 | height: 14px; 232 | width: 20px; 233 | background: transparent; 234 | border-radius: 0; 235 | border-left: solid 4px #7ED180; 236 | border-bottom: solid 4px #7ED180; 237 | } 238 | } 239 | 240 | @keyframes checkOut { 241 | 0% { 242 | height: 14px; 243 | width: 20px; 244 | background: transparent; 245 | border-radius: 0; 246 | border-left: solid 4px #7ED180; 247 | border-bottom: solid 4px #7ED180; 248 | } 249 | 100% { 250 | width: 24px; 251 | height: 24px; 252 | background: #3D3747; 253 | border-radius: 20px; 254 | box-shadow: inset 0 0 3px #2B2831; 255 | } 256 | } 257 | 258 | .edit { 259 | display: none; 260 | position: absolute; 261 | top: 0; 262 | left: 0; 263 | padding-left: 50px; 264 | width: 0%; 265 | height: 50px; 266 | opacity: 0; 267 | margin: auto 0; 268 | font-size: 23px; 269 | font-family: inherit; 270 | color: #fff; 271 | background: #7ED180; 272 | margin-bottom: 11px; 273 | border: none; 274 | outline: none; 275 | box-shadow: inset 0 0 40px rgba(0,0,0,0.1); 276 | z-index: 9; 277 | } 278 | 279 | .editing .edit { 280 | animation: anime .3s ease-out forwards; 281 | } 282 | 283 | @keyframes anime { 284 | 0% { 285 | opacity: 0; 286 | width: 0%; 287 | } 288 | 100% { 289 | opacity: 1; 290 | width: 100%; 291 | } 292 | } 293 | 294 | .destroy { 295 | position: absolute; 296 | display: block; 297 | top: 0px; 298 | right: 0; 299 | border: none; 300 | width: 50px; 301 | height: 50px; 302 | background: transparent; 303 | z-index: 2; 304 | outline: none; 305 | } 306 | 307 | .destroy:before, 308 | .destroy:after { 309 | content: ''; 310 | position: absolute; 311 | top: 12.5px; 312 | left: 24px; 313 | width: 1px; 314 | height: 25px; 315 | background: #EE1630; 316 | opacity: 0; 317 | box-shadow: 0 0 4px #24203D; 318 | transition: all .5s ease-out; 319 | transform: rotateZ(45deg); 320 | } 321 | 322 | .destroy:after { 323 | left: 25px; 324 | transform: rotateZ(-45deg); 325 | } 326 | 327 | .list li:hover .destroy:before, 328 | .list li:hover .destroy:after { 329 | opacity: 1; 330 | height: 25px; 331 | } 332 | 333 | .destroy:hover:before, 334 | .destroy:hover:after { 335 | box-shadow: 0 0px 8px #ee0000; 336 | } 337 | 338 | 339 | .editing .edit { 340 | display: block; 341 | } 342 | 343 | .editing .todo { 344 | visibility: hidden; 345 | } 346 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | ;(function(context) { 2 | 3 | 'use strict'; 4 | 5 | var ENTER_KEY = 13; 6 | var ESC_KEY = 27; 7 | 8 | function App( localStorageKey ) { 9 | 10 | this.stores = new Stores(localStorageKey); 11 | this.currentId = 0; 12 | this.$insert = $('#js-insert'); 13 | this.$toggleAll = $('#js-toggle-all'); 14 | this.$bar = $('#js-bar'); 15 | this.$list = $('#js-list'); 16 | this.$clearCompleted = $('#js-clear-completed'); 17 | this.$total = $('#js-total'); 18 | this.$filters = $('#js-filters'); 19 | this.addEventListeners(); 20 | this.render(); 21 | 22 | } 23 | 24 | App.fn = App.prototype; 25 | 26 | App.fn.addEventListeners = function() { 27 | 28 | $.on(this.$insert, 'keypress', this.onInsert.bind(this)); 29 | 30 | $.on(this.$toggleAll, 'click', this.onToggleAll.bind(this)); 31 | $.delegate(this.$list, '.toggle', 'click', this.onToggle.bind(this)); 32 | 33 | $.delegate(this.$list, '.destroy', 'click', this.onDestroy.bind(this) ); 34 | 35 | $.on(this.$clearCompleted, 'click', this.onClearCompleted.bind(this)); 36 | 37 | 38 | $.delegate(this.$filters, '.button', 'click', this.onFilter.bind(this)); 39 | 40 | $.delegate(this.$list, 'span', 'dblclick', this.onStartEditing.bind(this)); 41 | $.delegate(this.$list, '.edit', 'keyup', this.onEditingCancel.bind(this)); 42 | $.delegate(this.$list, '.edit', 'keypress', this.onEditingDone.bind(this)); 43 | $.delegate(this.$list, '.edit', 'blur', this.onEditingLeave.bind(this) ); 44 | }; 45 | 46 | App.fn.onStartEditing = function(event) { 47 | var li = $.parent(event.target, 'li'); 48 | var element = $('.edit', li); 49 | this.currentId = parseInt(li.dataset.id, 10); 50 | li.className += ' editing'; 51 | element.value = event.target.innerHTML; 52 | element.focus(); 53 | }; 54 | 55 | App.fn.onEditingCancel = function(event) { 56 | if( event.keyCode === ESC_KEY ) { 57 | console.log('onEditingCancel', event.target); 58 | event.target.dataset.isCanceled = true; 59 | event.target.blur(); 60 | } 61 | }; 62 | 63 | App.fn.onEditingDone = function(event) { 64 | if( event.keyCode === ENTER_KEY ) { 65 | event.target.blur(); 66 | } 67 | }; 68 | 69 | App.fn.onEditingLeave = function(event) { 70 | console.log('onEditingLeave'); 71 | var input = event.target; 72 | var id = this.getItemId( input ); 73 | var text = input.value.trim(); 74 | var li = this.getElementByDataId( id ); 75 | if( input.value.trim() ) { 76 | var item = { 77 | id: id, 78 | text: text 79 | }; 80 | this.stores.save(item,this.endEditing.bind(this, li, text)); 81 | } else { 82 | if( input.dataset.isCanceled ) { 83 | this.endEditing( li ); 84 | } else { 85 | this.destroy( id ); 86 | } 87 | } 88 | }; 89 | 90 | App.fn.endEditing = function( li, text ) { 91 | li.className = li.className.replace('editing', ''); 92 | $('.edit', li).removeAttribute('data-is-canceled'); 93 | if( text ) { 94 | $('span', li).innerHTML = text; 95 | } 96 | }; 97 | 98 | App.fn.getItemId = function( element ) { 99 | var li = $.parent(element, 'li'); 100 | return parseInt(li.dataset.id, 10); 101 | }; 102 | 103 | App.fn.getElementByDataId = function( id ) { 104 | return $('[data-id="' + id + '"]'); 105 | }; 106 | 107 | App.fn.onInsert = function( event ) { 108 | var element = event.target; 109 | var text = element.value.trim(); 110 | if( text && event.keyCode === ENTER_KEY ) { 111 | this.insert(text); 112 | element.value = ''; 113 | } 114 | }; 115 | 116 | App.fn.onToggleAll = function(event) { 117 | var checked = event.target.checked; 118 | var self = this; 119 | 120 | this.stores.findAll(function( items ) { 121 | $.each( items, function( item ) { 122 | item.completed = checked; 123 | self.stores.save( item, $.noop); 124 | }); 125 | self.render(); 126 | }); 127 | }; 128 | 129 | App.fn.onToggle = function(event) { 130 | var element = event.target; 131 | var id = this.getItemId( element ); 132 | var item = { 133 | id: id, 134 | completed: element.checked 135 | }; 136 | this.stores.save( item, function(item) { 137 | var li = this.getElementByDataId( item.id ); 138 | li.className = item.completed ? 'completed' : ''; 139 | $('.toggle', li).checked = item.completed; 140 | this.showControls(); 141 | }.bind(this)); 142 | }; 143 | 144 | App.fn.onDestroy = function(event) { 145 | var id = this.getItemId( event.target ); 146 | this.destroy( id ); 147 | }; 148 | 149 | App.fn.onClearCompleted = function(event) { 150 | var self = this; 151 | this.stores.findAll(function( items ) { 152 | items = items 153 | .filter(function( item ) { 154 | return item.completed; 155 | }) 156 | .forEach(function( item ) { 157 | self.destroy( item.id ); 158 | }); 159 | }); 160 | }; 161 | 162 | App.fn.onFilter = function(event) { 163 | document.location.hash = event.target.getAttribute('href'); 164 | this.render(); 165 | }; 166 | 167 | // Insert 168 | App.fn.insert = function( text ) { 169 | var item = { 170 | text: text, 171 | completed: false 172 | }; 173 | this.stores.save(item, function( item ) { 174 | var element = this.nodeItem( item ); 175 | this.$list.appendChild( element ); 176 | this.showControls(); 177 | }.bind(this)); 178 | }; 179 | 180 | // Destroy 181 | App.fn.destroy = function( id ) { 182 | this.stores.destroy( id, function() { 183 | var li = this.getElementByDataId( id ); 184 | this.$list.removeChild(li); 185 | this.showControls(); 186 | }.bind(this)); 187 | }; 188 | 189 | App.fn.filter = function() { 190 | var hash = document.location.hash; 191 | if( !hash ) return false; 192 | $.qsa( '.button', this.$filters ) 193 | .each(function( button ) { 194 | if ( button.getAttribute('href') === hash ) { 195 | button.className = 'button selected'; 196 | } else { 197 | button.className = button.className.replace('selected', ''); 198 | } 199 | }); 200 | hash = hash.split('#/')[1] 201 | return hash !== 'all' ? hash : false; 202 | }; 203 | 204 | // Render 205 | App.fn.render = function() { 206 | 207 | var filter = this.filter(); 208 | 209 | this.stores.findAll(function(items){ 210 | if( filter ) { 211 | items = items 212 | .filter(function(item) { 213 | return item.completed === ( filter === 'completed' ); 214 | }); 215 | } 216 | var nodes = this.nodeItemMulti( items ); 217 | this.$list.innerHTML = ""; 218 | this.$list.appendChild(nodes); 219 | this.showControls(); 220 | 221 | }.bind(this)); 222 | 223 | }; 224 | 225 | 226 | App.fn.showControls = function () { 227 | this.stores.findAll(function(items){ 228 | this.showBarAndToggleAll( items ); 229 | this.showTotalTasksLeft( items ); 230 | this.showClearCompleted( items ); 231 | }.bind(this)); 232 | }; 233 | 234 | App.fn.showBarAndToggleAll = function( items ) { 235 | var total = items.length; 236 | var completed = items.filter(function(item){ 237 | return item.completed; 238 | }); 239 | var value = total ? 'block' : 'none'; 240 | this.$toggleAll.style.display = value; 241 | this.$toggleAll.checked = total === completed.length; 242 | this.$bar.style.display = value; 243 | }; 244 | 245 | App.fn.showTotalTasksLeft = function(items) { 246 | items = items 247 | .filter(function( item ) { 248 | return !item.completed; 249 | }); 250 | var len = items.length; 251 | var text = [len,' item',$.pluralization( len ),' left'].join(''); 252 | this.$total.innerHTML = text; 253 | }; 254 | 255 | App.fn.showClearCompleted = function(items) { 256 | var some = items 257 | .some(function( item ) { 258 | return item.completed; 259 | }); 260 | this.$clearCompleted.style.display = some ? 'inline-block' : 'none'; 261 | }; 262 | 263 | App.fn.nodeItemMulti = function ( items ) { 264 | var fragment = document.createDocumentFragment(); 265 | $.each( items, function( item ) { 266 | fragment.appendChild( this.nodeItem(item) ); 267 | }.bind(this)); 268 | return fragment; 269 | }; 270 | 271 | App.fn.nodeItem = function( item ) { 272 | var li = document.createElement('li'); 273 | var div = document.createElement('div'); 274 | var toggle = document.createElement('input'); 275 | var span = document.createElement('span'); 276 | var destroy = document.createElement('button'); 277 | var edit = document.createElement('input'); 278 | 279 | li.setAttribute('data-id', item.id ); 280 | 281 | if ( item.completed ) { 282 | li.className = 'completed'; 283 | } 284 | 285 | div.className = 'todo'; 286 | 287 | toggle.setAttribute('type', 'checkbox'); 288 | toggle.className = 'toggle'; 289 | toggle.checked = item.completed; 290 | 291 | span.appendChild( document.createTextNode(item.text) ); 292 | 293 | destroy.className = 'destroy'; 294 | // destroy.appendChild( document.createTextNode('X') ); 295 | 296 | edit.setAttribute('type', 'text'); 297 | edit.className = 'edit'; 298 | 299 | 300 | div.appendChild(toggle); 301 | div.appendChild(span); 302 | div.appendChild(destroy); 303 | 304 | li.appendChild(div); 305 | li.appendChild(edit); 306 | 307 | return li; 308 | } 309 | 310 | // Initialization on Dom Ready 311 | window.addEventListener('DOMContentLoaded', function() { 312 | var app = new App('todo'); 313 | }); 314 | 315 | })(this); 316 | 317 | --------------------------------------------------------------------------------