{"id":21585924,"url":"https://github.com/kawthare/birdclicker","last_synced_at":"2026-04-16T10:02:11.836Z","repository":{"id":214318216,"uuid":"128050527","full_name":"KawtharE/birdClicker","owner":"KawtharE","description":"Bird Clicker app: version 1 with jQuery, version 2 with vanillaJS, version 3 with KnockoutJS and version 4 with BackboneJS. Inspired from the Cow Clicker app the social game on Facebook by Ian Bogost.","archived":false,"fork":false,"pushed_at":"2018-04-09T11:12:28.000Z","size":34352,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-21T16:46:08.650Z","etag":null,"topics":["backbonejs","css3","flexbox","grunt","html5","javascript","jquery","knockoutjs","vanilla-js"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/KawtharE.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-04-04T11:06:43.000Z","updated_at":"2023-04-11T15:22:49.000Z","dependencies_parsed_at":"2023-12-27T11:45:38.313Z","dependency_job_id":null,"html_url":"https://github.com/KawtharE/birdClicker","commit_stats":null,"previous_names":["kawthare/birdclicker"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/KawtharE/birdClicker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KawtharE%2FbirdClicker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KawtharE%2FbirdClicker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KawtharE%2FbirdClicker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KawtharE%2FbirdClicker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KawtharE","download_url":"https://codeload.github.com/KawtharE/birdClicker/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KawtharE%2FbirdClicker/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31880883,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-16T09:23:21.276Z","status":"ssl_error","status_checked_at":"2026-04-16T09:23:15.028Z","response_time":69,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backbonejs","css3","flexbox","grunt","html5","javascript","jquery","knockoutjs","vanilla-js"],"created_at":"2024-11-24T15:12:11.353Z","updated_at":"2026-04-16T10:02:11.817Z","avatar_url":"https://github.com/KawtharE.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BirdClicker\nBird Clicker app: Inspired from the Cow Clicker app the social game on Facebook by **Ian Bogost**.\n\nThe application have a picture of a bird with the bird name, title and number of clicks, all displayed when clicking on one of the list item which is actually a small version of the same displayed image generated using **Grunt**, the number of clicks increment whenever we click on the displayed image.\n\nThe solution is responsive since I have adopted the **Flexbox** approach. \n\n**Version 1 with jQuery**\n\n**Version 2 with vanillaJS**\n\n**Version 3 with knockoutJS**\n\n**Version 4 with backboneJS**\n\nThe difference between these four versions is the way we implements the functionalities of the app. In the first version, with jQuery, we are not using any kind of libraries just a pure jQuery functions. In the other versions, we are writing more professional, clean and stable code using **organizational techniques**.\n\n## Version 1 -- jQuery\n\n![Starting Screen](https://github.com/KawtharE/birdClicker/blob/master/assets/birdClickerJQuery.gif)\n\nWith **jQuery** we are grabbing DOM elements that we are going to update conditionally by adding dynamically some HTML code, in order to display the data that we have initially save it in an array (in this case our data are static so we keep it in an array).\n\nAll we need to install is jQuery itself:\n\n      $ npm install jquery@3.3.1\n\n##### 1- Grabbing the DOM elements\n\n      // Getting the DOM elements with jQuery\n      var display_area = $('#display');\n      var display_name = $('#bird-name');\n      var display_title = $('#bird-title');\n      var display_counts = $('#bird-clicks-number');\n      var display_img = $('#bird-dislay-img');\n      var display_img_attr = $('#img-attr');\n      var list_area = $('#list');\n      var list_ul = $('#bird-list');\n\n##### 2- Initialize the Data\n\n      // Storing the birds list data in an array\n      var birds_list = [\n        {\n          name: 'Javelin',\n          listImg: 'images_small/bird1-200_small.jpg',\n          displayImg: 'images/bird1.jpg',\n          imgAttribution: 'https://www.flickr.com/photos/hyalella/38685545911/in/gallery-flickr-72157662070816797/'\n        },\n        .\n        .\n        .\n      ]\n      \n##### 3- Update the DOM\n\nInitialize the View with the Data of the first element in the array:\n\n      // set up the initial state of the display view\n      (function addBirdDisplay(){\n        var count = 0;\n        display_name.text('Bird Name: '+birds_list[0].name);\n        display_img.attr('src', birds_list[0].displayImg);\n        display_img_attr.attr('href', birds_list[0].imgAttribution);\n        display_counts.text('Clicks count: '+count);\n        count++;\n        setTheTitle(count)\n        display_img.on('click', function(){\n          display_counts.text('Clicks count: '+count);\n          setTheTitle(count)\n          count++;\n        });\n      })();\n\n\nAdding the whole array of birds data to be displayed as a scrolling list:\n\n      // set up the view\n      (function birdView(){\n        for(var i=0; i\u003c birds_list.length; i++){\n          var bird = birds_list[i];\n          list_ul.append('\u003cli class=\"list-item\" id=\"list-item'+i+'\"\u003e'+'\u003cimg class=\"img-list\" src=\"'+bird.listImg+'\"\u003e\u003c/li\u003e');\n          var item = $('#list-item'+i+'');\n          item.on('click',(function(bird, item){\n            return function(){\n              display_img.attr('src', bird.displayImg);\n              display_img_attr.attr('href', bird.imgAttribution);\n              display_name.text('Bird Name: '+bird.name);\n              var count = 0;\n              display_counts.text('Clicks count: '+count);\n              count++;\n              setTheTitle(count);\n              display_img.on('click', function(){\n                  display_counts.text('Clicks count: '+count);\n                  setTheTitle(count);\n                  count++;\n              });\n            }\n          })(bird, item));\t\t\t\n        };\n\n\n      })();\n      \nWith jQuery things get more complex every time we add a new functionalty and the code looks like a very big mess, for this reason adopting some **organizational techniques** will make our application **stable, bug-free and cleanly written**, and future changes will be much more easier and can be added in no time. \n\nNow with these organizational techniques we are separating things out, at this point we are talking about **separations of concerns**. So we are separating our code into three pieces: **Model, View, and * .**\n\n1- The Model represents the Data.\n\n2- The View represents the interface that the user interacts with.\n\n3- The * represents the connector between the Model and the View, the goal is to organize things together by separating the Model and the view and never connect them directly.\n\n## Version 2 -- vanillaJS\n\n![Starting Screen](https://github.com/KawtharE/birdClicker/blob/master/assets/birdClickerVanillaJS.gif)\n\n**VanillaJS** is not a library, nor a framework, it is a native JavaScript code with no additional extensions that adopt **separations of concerns**.\n\n1- The Model:\n\n\t// The Model of our application\n\tvar model = {\n\t\tcurrentBird: null,\n\t\tbirdItems: []\n\t};\n\nThe model is a **literal object**. In this case, it contain two properties, the *currentBird* which represents the current bird displayed in the display section of the DOM and that will be updated every time the user click on one of the list items, and the *birdItems* which is right now an empty array that will be filled soon with a bunch of data.\n\n2- The View:\n\nwe are separating the view here into two pieces, a view for the display section and a view for the list of items. That was a choice just to make things more clear.\n\nIn each view we are defining tow functions, **init** and **render**.\n\nIn the **init function** we are grabbing elements from the DOM that we will update and that we need to grab only once, also we are calling the render function.\n\nIn the **render function** we are implementing the functionalties that update the DOM and the Model by calling the functions defined in the connector.\n\n\t// the first View: the List View\n\tvar viewList = {\n\t\tinit: function(){\n\t\t\tthis.bird_list_container = document.getElementById('bird-list');\n\t\t\tthis.render();\n\t\t\tvar list_images = document.querySelectorAll('.list-item');\n\t\t\tlist_images[0].classList.add('active');\n\t\t},\n\t\trender: function(){\n\t\t\tvar all_birds = connectorVM.getAllBirds();\n\t\t\tfor(var i=0; i\u003call_birds.length; i++){\n\t\t\t\tvar bird = all_birds[i];\n\t\t\t\tvar bird_list_elem = document.createElement('li');\n\t\t\t\tvar bird_list_img = document.createElement('img');\n\t\t\t\tbird_list_img.src = bird.birdListImage;\n\t\t\t\tbird_list_img.classList.add('list-img');\n\t\t\t\tbird_list_elem.appendChild(bird_list_img);\n\t\t\t\tbird_list_elem.classList.add('list-item');\n\t\t\t\tbird_list_elem.addEventListener('click', (function(bird, bird_list_elem){\n\t\t\t\t\treturn function(){\n\t\t\t\t\t\tvar list = document.querySelectorAll('.list-item');\n\t\t\t\t\t\tlist.forEach(function(item){\n\t\t\t\t\t\t\titem.classList.add('initial-state');\n\t\t\t\t\t\t});\n\t\t\t\t\t\tbird_list_elem.classList.remove('initial-state');\n\t\t\t\t\t\tbird_list_elem.classList.add('active');\n\t\t\t\t\t\tconnectorVM.updateCurrentBird(bird);\n\t\t\t\t\t\tviewDisplay.render();\n\t\t\t\t\t}\n\t\t\t\t})(bird, bird_list_elem) );\n\t\t\t\tthis.bird_list_container.appendChild(bird_list_elem);\n\t\t\t}\n\t\t\t\n\t\t}\n\t};\n\n\t// the second View: the Display View\n\tvar viewDisplay = {\n\t\tinit: function(){\n\t\t\tthis.current_bird_container = document.getElementById('current-bird');\n\t\t\tthis.current_bird_name = document.getElementById('bird-name');\n\t\t\tthis.current_bird_img = document.getElementById('current-bird-image');\n\t\t\tthis.current_bird_clicks = document.getElementById('clicks-number');\n\t\t\tthis.current_bird_imgAttr = document.getElementById('bird-img-attr');\n\t\t\tthis.current_bird_title = document.getElementById('bird-title');\n\t\t\tthis.current_bird_img.addEventListener('click', function(){\n\t\t\t\tconnectorVM.updateClicksNumber();\n\t\t\t});\n\t\t\tthis.render();\n\t\t}, \n\t\trender: function(){\n\t\t\tvar current_bird = connectorVM.getCurrentBird();\n\t\t\tthis.current_bird_name.textContent = 'Bird Name: '+current_bird.birdName;\n\t\t\tthis.current_bird_img.src = current_bird.birdDisplayImage;\n\t\t\tthis.current_bird_clicks.textContent = 'Clicks count: '+current_bird.clickCounts;\n\t\t\tthis.current_bird_imgAttr.href = current_bird.birdImageAttribution;\n\t\t\tthis.current_bird_title.textContent = connectorVM.updateLevel(current_bird);\n\n\t\t}\n\t};\n \n 3- The * (in our case we are calling it connectorVM)\n \nThe connectorVM holds on all the functions that we need to achieve the features of the application, in this case: *seeting the current bird, getting the current bird, incrementing the click number, updating the title and getting all available data*. These functions are called by views in order to not connect directly to the Model and keep things separate. \n\nthe **init function** call the views init functions to setup the app.\n\n\t// The * in the MV* pattern\n\tvar connectorVM = {\n\t\tinit: function(){\n\t\t\tthis.getAllBirdItems();\n\t\t\tmodel.currentBird = model.birdItems[0];\n\t\t\tviewList.init();\n\t\t\tviewDisplay.init();\n\t\t},\n\t\tgetAllBirdItems: function(){\n\t\t\tvar birdNames = ['Javelin', 'Tweety', 'Azuryz', 'Dante', 'Owlbundy', 'Fanteriso', 'Mia', 'Lexiy', 'Archangel', 'Pearl'] ;\n\t\t\tvar listImagesUrl = ['images_small/bird1-200_small.jpg', 'images_small/bird2-200_small.jpg', 'images_small/bird3-200_small.jpg', 'images_small/bird4-200_small.jpg', 'images_small/bird5-200_small.jpg', 'images_small/bird6-200_small.jpg', 'images_small/bird7-200_small.jpg', 'images_small/bird8-200_small.jpg', 'images_small/bird9-200_small.jpg', 'images_small/bird10-200_small.jpg'];\n\t\t\tvar displayImagesUrl = ['images/bird1.jpg', 'images/bird2.jpg', 'images/bird3.jpg', 'images/bird4.jpg', 'images/bird5.jpg', 'images/bird6.jpg', 'images/bird7.jpg', 'images/bird8.jpg', 'images/bird9.jpg', 'images/bird10.jpg'];\n\t\t\tvar birdImageAttribution = ['https://www.flickr.com/photos/hyalella/38685545911/in/gallery-flickr-72157662070816797/','https://www.flickr.com/photos/mg_rogers/33229234452/in/gallery-flickr-72157662070816797/','https://www.flickr.com/photos/139235634@N04/34191401830/in/gallery-flickr-72157662070816797/','https://www.flickr.com/photos/130233070@N04/37425778714/in/gallery-flickr-72157662070816797/','https://www.flickr.com/photos/frau-specht/37362864975/in/gallery-flickr-72157662070816797/','https://www.flickr.com/photos/8805844@N02/32588187075/in/gallery-flickr-72157662070816797/','https://www.flickr.com/photos/12341100@N06/37152982943/in/gallery-flickr-72157662070816797/','https://www.flickr.com/photos/99052565@N03/34435356125/in/gallery-flickr-72157662070816797/','https://www.flickr.com/photos/melvinschar/38599526902/in/gallery-flickr-72157662070816797/','https://www.flickr.com/photos/moggl/37635059085/in/gallery-flickr-72157662070816797/'];\n\t\t\tfor (var i=0; i\u003cbirdNames.length; i++) {\n\t\t\t\tmodel.birdItems.push({\n\t\t\t\t\tbirdName: birdNames[i],\n\t\t\t\t\tbirdListImage: listImagesUrl[i],\n\t\t\t\t\tbirdDisplayImage: displayImagesUrl[i],\n\t\t\t\t\tclickCounts: 0,\n\t\t\t\t\tbirdImageAttribution: birdImageAttribution[i]\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t\tupdateCurrentBird: function(newBird){\n\t\t\tmodel.currentBird = newBird;\n\t\t},\n\t\tupdateClicksNumber: function(){\n\t\t\tmodel.currentBird.clickCounts++;\n\t\t\tviewDisplay.render();\n\t\t},\n\t\tgetCurrentBird: function(){\n\t\t\treturn model.currentBird;\n\t\t}, \n\t\tgetAllBirds: function(){\n\t\t\treturn model.birdItems;\n\t\t}, \n\t\tupdateLevel: function(birdItem){\n\t\t\tvar bird_level = 'Bird Clicker Level';\n\t\t\tif (birdItem.clickCounts \u003c 10) {\n\t\t\t\tvar bird_level = 'Bird Clicker Level 0';\n\t\t\t}\n\t\t\telse if ((birdItem.clickCounts \u003e= 10) \u0026\u0026 (birdItem.clickCounts \u003c 20)) {\n\t\t\t\tbird_level = 'Bird Clicker Level 1';\n\t\t\t}\n\t\t\telse if ((birdItem.clickCounts \u003e= 20) \u0026\u0026 (birdItem.clickCounts \u003c 30)) {\n\t\t\t\tbird_level = 'Bird Clicker Level 2';\n\t\t\t} else{\n\t\t\t\tbird_level = 'Bird Clicker Level 3';\n\t\t\t}\n\t\t\treturn bird_level;\n\t\t}\n\n\t};\n      \nWe could enter the data in the Model and not writing the function *getAllBirdItems*, but we choose too make the Model in its simplest version.\n\nNow to start the app, we call at the end the **init function of the connectorVM variable**\n\n      connectorVM.init();\n\n=\u003e In this version we are implementing **separation of concerns** without using any **organizational library or framework**. In the next two versions we are using two organizational libraries to achieve the goal : **KnockoutJS and BackboneJS**.\n\n## Version 3 -- knockoutJS\n\n![Starting Screen](https://github.com/KawtharE/birdClicker/blob/master/assets/birdClickerKnockoutJS.gif)\n\n**KnockoutJS** is an organizational library that adopt the **MVVM pattern**.\n\nNow the five Fundamental elements in any organizational library or framework are:\n\n1- Models: represents the data.\n\n2- Collections: represents an array of Models.\n\n3- * that could be C (Controller), P (Presenter), VM (ViewModel) or whatever depending on the pattern (MVC, MVP, MVVM, MV*), in all cases it represents the connector between the Model and the view. In the Knockout case it is **VM (ViewModel)**.\n\n4- View: represents the interface that the user interact with.\n\n5- Routers: represnts Views for URLs.\n\nTo get start with knockout of course we need to install it first:\n\n\t$ npm install knockout\n\t\nStarting by defining our data (Model), as showing, each property is defined using **ko.observable**. Well **Observables** help us keep track of our data and whenever it change the view update immediatly, without need to any additional functions just **bind** the value to the DOM by adding **data-bind** attribute to the corresponding HTML element.\n\nWe have used **ko.computed** to create the *title variable* since the *title* depends on *clickNumber* observable and in order to change it automatically whenever the *clickNumber* change. We are passing in a function as first argument that will return what we are asking for (the title value), and a second parameter, the keyword **this** to make sure we can use it inside the function.\n\n\n\tvar initialData = [\n\t\t\t{\n\t\t\t\tbirdName: 'Javelin',\n\t\t\t\tbirdListImg: 'images_small/bird1-200_small.jpg',\n\t\t\t\tbirdDisplayImg: 'images/bird1.jpg',\n\t\t\t\tbirdImgAttribution: 'https://www.flickr.com/photos/hyalella/38685545911/in/gallery-flickr-72157662070816797/',\n\t\t\t\tclicksNumber: 0,\n\t\t\t\tclicked: false,\n\t\t\t\tbirdNicknames: ['Buzby', 'Jinn', 'Kwatoko', 'Belphegor', 'Xavea', 'Poppadom', 'Alcatraz', 'Heresa', 'Dilly', 'Brennus']\n\n\t\t\t},\n\t\t\t.\n\t\t\t.\n\t\t\t.\n\t]\n\tvar Model = function(data){\n\t\tthis.birdName = ko.observable('Bird Name: '+data.birdName);\n\t\tthis.birdListImg = ko.observable(data.birdListImg);\n\t\tthis.birdDisplayImg = ko.observable(data.birdDisplayImg);\n\t\tthis.birdImgAttribution = ko.observable(data.birdImgAttribution);\n\t\tthis.clicksNumber = ko.observable(data.clicksNumber);\n\t\tthis.clicked = ko.observable(data.clicked);\n\t\tthis.birdNicknames = ko.observableArray(data.birdNicknames);\n\t\tthis.birdAttribution = ko.observable(data.birdImgAttribution);\n\n\t\tthis.birdTitle = ko.computed(function(){\n\t\t\tthis.bird_title = 'Bird Clicker Level 0';\n\t\t\tif (this.clicksNumber() \u003c 10){\n\t\t\t\tthis.bird_title = 'Bird Clicker Level 1';\n\t\t\t}\n\t\t\telse if ((this.clicksNumber() \u003e= 10) \u0026\u0026 (this.clicksNumber() \u003c 20)){\n\t\t\t\tthis.bird_title = 'Bird Clicker Level 2';\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthis.bird_title = 'Bird Clicker Level 3';\n\t\t\t}\n\t\t\treturn this.bird_title;\n\t\t}, this);\n\n\t}\n\t\nNext, we are defining the **ViewModel** part in which we create the bird list as an **observable array** from the Model data, then we initialize the display section by displaying the first element of the array. Now since functions and forEach statement both create new context we have saved the **this** keyword, which represents the ViewModel context, inside a ne variable **self** in order to be able to use the variables available in the this context inside functions and forEach statement. \n\n\tvar ViewModel = function(){\n\t\tvar self = this\n\n\t\tthis.birdsItems = ko.observableArray([]);\n\t\tinitialData.forEach(function(item){\n\t\t\tself.birdsItems.push(new Model(item));\n\t\t});\n\t\tthis.birdsItems()[0].clicked(true);\n\t\tthis.currentBird = ko.observable(this.birdsItems()[0]);\n\t\tthis.setCurrentItem = function(){\n\t\t\tself.birdsItems().forEach(function(item){\n\t\t\t\titem.clicked(false);\n\t\t\t});\n\t\t\tthis.clicked(true);\n\t\t\tself.currentBird(this);\n\t\t};\n\t\tthis.updateClicksNumber = function(){\n\t\t\tself.currentBird().clicksNumber(self.currentBird().clicksNumber() + 1);\n\t\t}\n\t}\n\t\nFinally for the View part, we are not defining it in the JS file because by using the attribute **bind-data** in the HTML code we are achieving what we need. [More information about data-bind](http://knockoutjs.com/documentation/binding-syntax.html)\n\n\t\t\t\u003cdiv class=\"display-bird\" data-bind=\"with: currentBird\"\u003e\n\t\t\t\t\u003ch2 class=\"bird-name\" data-bind=\"text: birdName\"\u003e\u003c/h2\u003e\n\t\t\t\t\u003ch3 class=\"bird-level\" data-bind=\"text: birdTitle\"\u003e\u003c/h3\u003e\n\t\t\t\t\u003cdiv class=\"clicks-number\" data-bind=\"text: clicksNumber\"\u003e\u003c/div\u003e\n\t\t\t\t\u003cimg class=\"bird-image\" src=\"\" alt=\"a picture of a bird\" data-bind=\"click: $parent.updateClicksNumber, attr: {src:birdDisplayImg}\"\u003e\n\t\t\t\t\u003cdiv\u003e\u003ca id=\"bird-img-attr\" href=\"\" data-bind=\"attr:{href:birdAttribution}\" target=\"_blank\"\u003eImage Attribution\u003c/a\u003e\u003c/div\u003e\n\t\t\t\t\u003cdiv class=\"bird-nicknames\"\u003e\n\t\t\t\t\t\u003ch3\u003eBird's Nicknames:\u003c/h3\u003e\n\t\t\t\t\t\u003cul class=\"nicknames\" data-bind=\"foreach: birdNicknames\"\u003e\n\t\t\t\t\t\t\u003cli data-bind=\"text: $data\"\u003e\u003c/li\u003e\n\t\t\t\t\t\u003c/ul\u003e\n\t\t\t\t\u003c/div\u003e\n\t\t\t\u003c/div\u003e\n\t\t\t\u003cdiv class=\"list-birds\"\u003e\n\t\t\t\t\u003cul class=\"birds\" data-bind=\"foreach: birdsItems\"\u003e\n\t\t\t\t\t\u003cli\u003e\u003cimg class=\"list-img\" src=\"\" data-bind=\"attr: {src:birdListImg}, click: $parent.setCurrentItem, css: {active: clicked}\"\u003e\u003c/li\u003e\n\t\t\t\t\u003c/ul\u003e\n\t\t\t\u003c/div\u003e\n\t\t\t\nEach HTML element with **data-bind** attribute bind the value of the indicated property of the bird observable object.\n\nNow for the first div element that contain the **'with' binding**, the binding context is not the **ViewModel context**, it is the **currentBird context** since the **'with'** creates a new binding context, so if we need to access a variable or a function that it is defined in the ViewModel context we need to add to the data-bind value **$parent** like we have done to the *updateClicksNumber* function, since it have been defined in the ViewModel context and not the currentBird context.\n\n=\u003e **The binding context** holds data that you can reference directly from your bindings. [More information about 'with' binding](http://knockoutjs.com/documentation/with-binding.html)\n\nSame for **forEach**, it creates a new binding context in the hierarchy of binding context. [More information about 'forEach' binding](http://knockoutjs.com/documentation/foreach-binding.html)\n\n## Version 4 -- backboneJS\n\n![Starting Screen](https://github.com/KawtharE/birdClicker/blob/master/assets/BirdClickerBackboneJS.gif)\n\nThe **BackboneJS** is an organizational library, that is made up of these five modules:\n\n   1- Views\n   \n   2- Events\n    \n   3- Models\n   \n   4- Collections\n   \n   5- Routers\n   \nBackbone is unique, it do things on its own way. It is not MVC, nor MVP and not even MVVM, but still from the MV* family.\n\nBackbone hard dependency is the library **underscore** and soft dependency is **jQuery**, so we are going to start by downloading these neccessary libraries and imported in the HTML file:\n\n\t$ npm install underscore\n\t$ npm install jquery@3.3.1\n\t$ npm install backbone\n\t$ npm install backbone.localstorage\n\t\n\n\t\u003cscript src=\"node_modules/jquery/dist/jquery.min.js\"\u003e\u003c/script\u003e\n\t\u003cscript src=\"node_modules/underscore/underscore.js\"\u003e\u003c/script\u003e\n\t\u003cscript src=\"node_modules/backbone/backbone.js\"\u003e\u003c/script\u003e\n\t\u003cscript src=\"node_modules/backbone.localstorage/build/backbone.localStorage.js\"\u003e\u003c/script\u003e\n\t\nThe structure of the whole project is like shown next:\n\n\t---CSS\n\n\t\t|---main.css\n\n\t---JS\n\n\t\t|---collections\n\n\t\t\t|---birds-collection.js\n\n\t\t|---models\n\n\t\t\t|---bird-model.js\n\n\t\t|---routers\n\n\t\t\t|---router.js\n\n\t\t|---views\n\n\t\t\t|---app-view.js\n\n\t\t\t|---display-view.js\n\n\t\t\t|---list-view.js\n\n\t\t|---script.js\n\n\t---index.html\n\nStarting with the **HTML file: index.html** where we need to import all the **js file** on addition to the previous libraries. **Note** that the order in which we import these files is important we have to keep the **script.js** file to the end, since it starts the whole app we need to setup first of all the models, views, etc.\n\n\t\u003cscript src=\"js/models/bird-model.js\"\u003e\u003c/script\u003e\n\t\u003cscript src=\"js/collections/birds-collection.js\"\u003e\u003c/script\u003e\n\t\u003cscript src=\"js/routers/router.js\"\u003e\u003c/script\u003e\n\t\u003cscript src=\"js/views/list-view.js\"\u003e\u003c/script\u003e\n\t\u003cscript src=\"js/views/display-view.js\"\u003e\u003c/script\u003e\n\t\u003cscript src=\"js/views/app-view.js\"\u003e\u003c/script\u003e\n\t\u003cscript src=\"js/script.js\"\u003e\u003c/script\u003e\n\t\nNext, we will be setting up the container, in which the views will be rendered:\n\n\t\t\u003cdiv class=\"container\" id=\"container\"\u003e\n\t\t\t\u003cdiv class=\"display\" id=\"display\"\u003e\u003c/div\u003e\n\t\t\t\u003cdiv class=\"list\"\u003e\u003cul id=\"list\"\u003e\u003c/ul\u003e\u003c/div\u003e\n\t\t\u003c/div\u003e\n\t\t\nNow, since the views will be rendered dynamically and in order to avoid coding the views template in the js files we will be using **Templates**, which is in fact a *script* with *text/template* type. The template with the *id* **item-template** is for the list of images and the template with the *id* **display-template** is for the display section in the container.\n\n\t\u003cscript type=\"text/template\" id=\"item-template\"\u003e\n\t\t\u003c% _.each(birds, function(bird){ %\u003e\n\t\t\t\u003cli class=\"list-item\" id=\"list-item\u003c%= bird.id %\u003e\" data-name=\"\u003c%= bird.birdName %\u003e\"\u003e\u003cimg id=\"list-item\" src=\"\u003c%= bird.birdListImg %\u003e\"\u003e\u003c/li\u003e\n\t\t\u003c% }); %\u003e\n\t\u003c/script\u003e\n\n\t\u003cscript type=\"text/template\" id=\"display-template\"\u003e\n\t\t\u003ch2\u003eBird Name: \u003c%- bird.birdName %\u003e\u003c/h2\u003e\n\t\t\u003ch3 id=\"title\"\u003eBird Clicker Level \u003c%= title %\u003e\u003c/h3\u003e\n\t\t\u003cdiv\u003eClicks Number: \u003cspan id=\"clicks-number\"\u003e \u003c%- bird.clicksNumber %\u003e \u003c/span\u003e\u003c/div\u003e\n\t\t\u003cimg class=\"displayed-item\" id=\"displayed-item\u003c%- bird.id %\u003e\" src=\"\u003c%- bird.birdDisplayImg %\u003e\" alt=\"a picture of a bird\"\u003e\n\t\t\u003cdiv\u003e\u003ca id=\"bird-img-attr\" href=\"\u003c%- bird.birdImgAttribution %\u003e\" target=\"_blank\"\u003eImage Attribution\u003c/a\u003e\u003c/div\u003e\t\n\t\u003c/script\u003e\n\t\nFor the rest of **js files** no matter it is Model, View, Collection or router the structer is the same:\n\n\t//verify if the app is already created or not, if not app will take an empty literal object as value\n\tvar app = app || {};\n\n\t//IIFE- Immediate Invoked Function Expressions\n\t(function(){\n\t\t.\n\t\t.\n\t\t.\n\t})()\n\t\n**Model:**\n\n\tvar app = app || {};\n\n\t(function(){\n\t\t'use strict';\n\n\t\tapp.Bird = Backbone.Model.extend({\n\t\t\tdefault: {\n\t\t\t\tbirdName: '',\n\t\t\t\tbirdListImg: '',\n\t\t\t\tbirdDisplayImg: '',\n\t\t\t\tbirdImgAttribution: '',\n\t\t\t\tclicksNumber: 0,\n\t\t\t\tid: 0\n\t\t\t},\n\t\t\t...\n\t\t});\n\t})();\n\t\n**Collection:**\n\n\tvar app = app || {};\n\n\t(function(){\n\t\t'use strict';\n\n\t\tapp.birds = new Backbone.Collection([\n\t\t\t{\n\t\t\t\tbirdName: 'Javelin',\n\t\t\t\tbirdListImg: 'images_small/bird1-200_small.jpg',\n\t\t\t\tbirdDisplayImg: 'images/bird1.jpg',\n\t\t\t\tbirdImgAttribution: 'https://www.flickr.com/photos/hyalella/38685545911/in/gallery-flickr-72157662070816797/',\n\t\t\t\tclicksNumber: 0,\n\t\t\t\tid: 0\n\t\t\t},\n\t\t\t...\n\t\t])\n\t})();\n\n**Router:**\n\n\tvar app = app || {};\n\n\t(function(){\n\t\t'use strict';\n\n\t\tvar BirdRouter = Backbone.Router.extend({\n\t\t\t...\n\t\t});\n\t\t\n\t\tapp.BirdRouter = new BirdRouter();\n\t})();\n\t\n**Views:**\n\nListView:\n\n\tvar app = app || {};\n\n\t(function(){\n\t\t'use strict';\n\n\t\tapp.ListView = Backbone.View.extend({\n\t\t\t...\n\t\t});\n\t\tapp.listView = new app.ListView();\n\t})();\n\t\nDisplayView:\n\n\tvar app = app || {};\n\n\t(function(){\n\t\t'use strict';\n\n\t\tapp.DisplayView = Backbone.View.extend({\n\t\t\t...\n\t\t});\n\n\t\tapp.displayView = new app.DisplayView();\n\t})();\n\t\nAppView:\n\n\tvar AppView = Backbone.View.extend({\n\t\tel: '#container',\n\t\tinitialize: function() {\n\t\t\tthis.listenTo(app.birds, 'all', this.render);\n\t\t},\n\t\trender: function() {\n\t\t\tthis.container = $('#container');\n\t\t\tthis.list = $('#list');\n\t\t\tthis.display = $('#display');\n\t\t\tthis.list.show();\n\t\t\tthis.display.show();\n\t\t\tthis.container.show();\n\t\t}\n\t});\n\t\n**script.js:**\n\n\tvar app = app || {};\n\n\t$(function(){\n\t\t'use strict';\n\n\t\tvar appView = new AppView();\n\t});\n\t\n\t\n## Responsive Design\n\nDeveloping responsive solutions is one of the most important step to the success of your application.\n\nNow since the most used element in this application is images, we need to think about techniques to make our images responsive and most important we are displaying the same image in two different size, normal size in the display section and small size in the list scrollbar.\n\nFirst of all, the HTML structure is represented in placing the display section and the list section inside a container:\n\n\t\t\u003cdiv class=\"container\"\u003e\n\t\t\t\u003cdiv class=\"display-bird\"\u003e\u003c/div\u003e\n\t\t\t\u003cdiv class=\"list-birds\"\u003e\u003c/div\u003e\n\t\t\u003c/div\u003e\n\t\t\nThe rest is achieved in the CSS file, by affecting the *flex* value to the display property for the *container* element, *justify-content* help us distribute the items inside the conatiner. For the elements inside the container, we are making their width responsive using the property *flex* which is better than using *percentage width*, by giving value 3 to the display section and 1 to the list section we are indicating that whatever the width of the DOM is, the display section will be 3 times bigger than the list section.\n\n\t\t.container{\n\t\t\tdisplay: flex;\n\t\t\tjustify-content: flex-start;\n\t\t}\n\t\t.display-bird{\n\t\t\tflex: 3;\n\t\t}\n\t\t.list-birds{\n\t\t\tflex: 1;\n\t\t}\n\t\t\n**Responsive Images:** To resize the images and generate the small images that we need it in the list, we have used **Grunt technique**.\n\n\t$ npm install -g grunt-cli\n\t$ npm install grunt-responsive-images --save\n\t$ npm install grunt-contrib-clean --save\n\t$ npm install grunt-contrib-copy --save\n\t$ npm install grunt-mkdir --save\n\t\nWe need *grunt-responsive-images, grunt-contrib-clean, grunt-contrib-copy and grunt-mkdir* to add tasks in **Gruntfile.js**:\n\n-grunt-responsive-images: to generate new sized images.\n\n-grunt-contrib-clean: to clean the directory where those new images places whenever we re-execute the grunt command.\n\n-grunt-mkdir: to create the destination directory if it is not already there.\n\nSo inside the **Gruntfile.js** we add those tasks at the end of the file:\n\n\t  grunt.loadNpmTasks('grunt-responsive-images');\n\t  grunt.loadNpmTasks('grunt-contrib-clean');\n\t  grunt.loadNpmTasks('grunt-contrib-copy');\n\t  grunt.loadNpmTasks('grunt-mkdir');\n\t  grunt.registerTask('default', ['clean', 'mkdir', 'responsive_images']);\n\t \nWe need to add the configuration part at the beginning, in which we indicate the new width of the images, the quality, the source, the destination etc:\n\n\tgrunt.initConfig({\n\t responsive_images: {\n\t      dev: {\n\t        options: {\n\t          engine: 'im',\n\t          sizes: [{\n\t            width: 200,\n\t            suffix: '_small',\n\t            quality: 100\n\t          }]\n\t        },\n\t        files: [{\n\t          expand: true,\n\t          src: ['*.{gif,jpg,png}'],\n\t          cwd: 'images/',\n\t          dest: 'images_small/'\n\t        }]\n\t      }\n\t    },\n\n\t    /* Clear out the images_small directory if it exists */\n\t    clean: {\n\t      dev: {\n\t        src: ['images_small'],\n\t      },\n\t    },\n\n\t    /* Generate the images_small directory if it is missing */\n\t    mkdir: {\n\t      dev: {\n\t        options: {\n\t          create: ['images_small']\n\t        },\n\t      },\n\t    },\n\t});\n\nNext, to finally get the new images we need to tap this command:\n\n\t$ grunt\n\t\nNow that we have the small images for the list section, back to the CSS file, and make sure that both the small images and the normal images are responsive. We can achieve that by just adding the following CSS rule:\n\n\tmax-width: 100%;\n\t\nThis is not optimal in all cases, actually adopting break points and media queries is much more optimal but in this application this solution works fine.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkawthare%2Fbirdclicker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkawthare%2Fbirdclicker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkawthare%2Fbirdclicker/lists"}