diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/collections/VideoCollection.js b/collections/VideoCollection.js new file mode 100644 index 00000000..6db9215f --- /dev/null +++ b/collections/VideoCollection.js @@ -0,0 +1,19 @@ +var key = 'AIzaSyAPYWVgR_Q5sELYDsNvyPsUWdZnjZIrBRM'; +var VideoCollection = Backbone.Collection.extend({ + model: VideoModel, + + url: 'https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=5&type=video&videoEmbeddable=true&key=' + key, + + parse: function(response) { + return response.items.map(function (video){ + return { + id: video.id.videoId, + title: video.snippet.title, + text: video.snippet.description, + thumb: video.snippet.thumbnails.default.url + } + }) + } + + +}); \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..941098a1 --- /dev/null +++ b/index.html @@ -0,0 +1,64 @@ + + + + + + + + + + + Project Youtube + + +
+
+
+ +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + + diff --git a/main.js b/main.js new file mode 100644 index 00000000..37153a42 --- /dev/null +++ b/main.js @@ -0,0 +1,187 @@ + +var sampleData = { + "kind": "youtube#searchListResponse", + "etag": "jrLgj0XGHegO7nztkfHkqGu_dmo", + "nextPageToken": "CAUQAA", + "regionCode": "US", + "pageInfo": { + "totalResults": 1000000, + "resultsPerPage": 5 + }, + "items": [ + { + "kind": "youtube#searchResult", + "etag": "BJ_pZJtlXOdlNB7-r4tAl81I3C4", + "id": { + "kind": "youtube#video", + "videoId": "L5dM6NdZXd4" + }, + "snippet": { + "publishedAt": "2021-01-17T16:00:09Z", + "channelId": "UCBvnS6nyNGAl8EUNt-40xoQ", + "title": "Josh Bridges 2021 CrossFit Open Prep + Dave Castro Gets Called Out | Paying the Man Ep.083", + "description": "The CrossFit season is just around the corner so I show you how I am preparing for the 2021 CrossFit Open. I also call out Dave Castro, to get him and I in a ring, ...", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/L5dM6NdZXd4/default.jpg", + "width": 120, + "height": 90 + }, + "medium": { + "url": "https://i.ytimg.com/vi/L5dM6NdZXd4/mqdefault.jpg", + "width": 320, + "height": 180 + }, + "high": { + "url": "https://i.ytimg.com/vi/L5dM6NdZXd4/hqdefault.jpg", + "width": 480, + "height": 360 + } + }, + "channelTitle": "Josh Bridges", + "liveBroadcastContent": "none", + "publishTime": "2021-01-17T16:00:09Z" + } + }, + { + "kind": "youtube#searchResult", + "etag": "JFhM06PZ2Ck-Tu0ZALY18QhBKog", + "id": { + "kind": "youtube#video", + "videoId": "vcDsh9Sjm18" + }, + "snippet": { + "publishedAt": "2020-10-23T16:43:38Z", + "channelId": "UCRs1pHnES3QDdh43xbjOmzw", + "title": "Event 1 & 2 - 2007 Reload and Corn Sack Sprint - 2020 CrossFit Games", + "description": "The hardest test in CrossFit Games history begins here. Five men. Five women. Three days of grueling competition. Grab snacks. #CrossFitGames The CrossFit ...", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/vcDsh9Sjm18/default.jpg", + "width": 120, + "height": 90 + }, + "medium": { + "url": "https://i.ytimg.com/vi/vcDsh9Sjm18/mqdefault.jpg", + "width": 320, + "height": 180 + }, + "high": { + "url": "https://i.ytimg.com/vi/vcDsh9Sjm18/hqdefault.jpg", + "width": 480, + "height": 360 + } + }, + "channelTitle": "CrossFit Games", + "liveBroadcastContent": "none", + "publishTime": "2020-10-23T16:43:38Z" + } + }, + { + "kind": "youtube#searchResult", + "etag": "gW7BE77piDCVb3uIFDXHGyK1BNA", + "id": { + "kind": "youtube#video", + "videoId": "tzD9BkXGJ1M" + }, + "snippet": { + "publishedAt": "2010-07-23T15:09:19Z", + "channelId": "UCtcQ6TPwXAYgZ1Mcl3M1vng", + "title": "What is CrossFit?", + "description": "What is CrossFit? CrossFit is an effective way to get fit. Anyone can do it. It is a fitness program that combines a wide variety of functional movements into a timed ...", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/tzD9BkXGJ1M/default.jpg", + "width": 120, + "height": 90 + }, + "medium": { + "url": "https://i.ytimg.com/vi/tzD9BkXGJ1M/mqdefault.jpg", + "width": 320, + "height": 180 + }, + "high": { + "url": "https://i.ytimg.com/vi/tzD9BkXGJ1M/hqdefault.jpg", + "width": 480, + "height": 360 + } + }, + "channelTitle": "CrossFit®", + "liveBroadcastContent": "none", + "publishTime": "2010-07-23T15:09:19Z" + } + }, + { + "kind": "youtube#searchResult", + "etag": "8KgGsXPicxGt8j-2-OuRhutT9s4", + "id": { + "kind": "youtube#video", + "videoId": "860T3WYvyoc" + }, + "snippet": { + "publishedAt": "2020-10-23T20:16:34Z", + "channelId": "UCRs1pHnES3QDdh43xbjOmzw", + "title": "Event 3 - CrossFit Total - 2020 CrossFit Games", + "description": "The wait is finally over. Watch the three-day finale of the 2020 CrossFit Games right here. Buckle up for an unforgettable weekend. Every event of the final stage ...", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/860T3WYvyoc/default.jpg", + "width": 120, + "height": 90 + }, + "medium": { + "url": "https://i.ytimg.com/vi/860T3WYvyoc/mqdefault.jpg", + "width": 320, + "height": 180 + }, + "high": { + "url": "https://i.ytimg.com/vi/860T3WYvyoc/hqdefault.jpg", + "width": 480, + "height": 360 + } + }, + "channelTitle": "CrossFit Games", + "liveBroadcastContent": "none", + "publishTime": "2020-10-23T20:16:34Z" + } + }, + { + "kind": "youtube#searchResult", + "etag": "a53xv9Yh4o643yVpPbDCpFyCZIM", + "id": { + "kind": "youtube#video", + "videoId": "JZpVUNiSg5s" + }, + "snippet": { + "publishedAt": "2020-12-28T18:09:41Z", + "channelId": "UCEQi1ZNJiw3YMRwni0OLsTQ", + "title": "I TRIED CROSSFIT FOR THE FIRST TIME", + "description": "Trying a crossfit workout for the first time with the @Buttery Bros BUTTERY BROS https://www.youtube.com/channel/UCp00ppkfPFBUTiiNNd8kS9Q BUTTERY ...", + "thumbnails": { + "default": { + "url": "https://i.ytimg.com/vi/JZpVUNiSg5s/default.jpg", + "width": 120, + "height": 90 + }, + "medium": { + "url": "https://i.ytimg.com/vi/JZpVUNiSg5s/mqdefault.jpg", + "width": 320, + "height": 180 + }, + "high": { + "url": "https://i.ytimg.com/vi/JZpVUNiSg5s/hqdefault.jpg", + "width": 480, + "height": 360 + } + }, + "channelTitle": "Whitney Simmons", + "liveBroadcastContent": "none", + "publishTime": "2020-12-28T18:09:41Z" + } + } + ] +} + +var appModel = new AppModel(); + +var appView = new AppView({ model: appModel }); \ No newline at end of file diff --git a/models/AppModel.js b/models/AppModel.js new file mode 100644 index 00000000..f756efcc --- /dev/null +++ b/models/AppModel.js @@ -0,0 +1,16 @@ +var AppModel = Backbone.Model.extend({ + defaults: function() { + return { + videos: new VideoCollection(), + current_video: null, + }; + }, + + updateCurrentVideo: function(clickedVidId) { + var allVids = this.get('videos'); + + var currentVideo = allVids.findWhere({id: clickedVidId}); + + this.set('current_video', currentVideo); + } +}); \ No newline at end of file diff --git a/models/VideoModel.js b/models/VideoModel.js new file mode 100644 index 00000000..1b573a49 --- /dev/null +++ b/models/VideoModel.js @@ -0,0 +1,11 @@ +var VideoModel = Backbone.Model.extend({ + + defaults: { + id: null, + title: '', + text: '', + thumb: '', + }, + + urlRoot: "https://www.youtube.com/embed/" +}) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..61a6eb15 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,69 @@ +{ + "name": "backbone-youtube", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "backbone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.0.tgz", + "integrity": "sha512-RLmDrRXkVdouTg38jcgHhyQ/2zjg7a8E6sz2zxfz21Hh17xDJYUHBZimVIt5fUyS8vbfpeSmTL3gUjTEvUV3qQ==", + "requires": { + "underscore": ">=1.8.3" + } + }, + "bootstrap": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.0.tgz", + "integrity": "sha512-bs74WNI9BgBo3cEovmdMHikSKoXnDgA6VQjJ7TyTotU6L7d41ZyCEEelPwkYEzsG/Zjv3ie9IE3EMAje0W9Xew==" + }, + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "jquery": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "uglify-js": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.1.tgz", + "integrity": "sha512-JhS3hmcVaXlp/xSo3PKY5R0JqKs5M3IV+exdLHW99qKvKivPO4Z8qbej6mte17SOPqAOVMjt/XGgWacnFSzM3g==", + "optional": true + }, + "underscore": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", + "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..cace0d38 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "backbone-youtube", + "version": "1.0.0", + "description": "This project has been created by a student at Parsity, an online software engineering course. The work in this repository is wholly of the student based on a sample starter project that can be accessed by looking at the repository that this project forks.", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/zdresser/backbone-youtube.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/zdresser/backbone-youtube/issues" + }, + "homepage": "https://github.com/zdresser/backbone-youtube#readme", + "dependencies": { + "backbone": "^1.4.0", + "bootstrap": "^5.1.0", + "handlebars": "^4.7.7", + "jquery": "^3.6.0", + "underscore": "^1.13.1" + } +} diff --git a/style.css b/style.css new file mode 100644 index 00000000..c909f6ed --- /dev/null +++ b/style.css @@ -0,0 +1,14 @@ +.page-header { + margin-top: 40px; + text-align: center; +} + +.search-form { + margin-top: 40px; + margin-bottom: 40px; +} + +.large-vid { + height: 100%; + width: 100%; +} diff --git a/views/AppView.js b/views/AppView.js new file mode 100644 index 00000000..f0c2ef3b --- /dev/null +++ b/views/AppView.js @@ -0,0 +1,65 @@ +/*To fix +/ Alignment +/ Main video is tiny +/ Allow for a second search without refreshing +/ The five videos render multiple times +*/ + +var AppView = Backbone.View.extend({ + el: $('body'), + + mainVideo: null, + + initialize: function() { + this.$searchInput = this.$('#search-query'); + this.listenTo(this.model.get('videos'), 'add', this.renderVideos); + this.listenTo(this.model.get('videos'), 'add', this.renderInitVideo); + this.listenTo(this.model, 'change:current_video', this.renderMainVideo) + + }, + + events: { + 'click .search': 'ytSearch', + 'click .view-video': 'viewVideo' + }, + + viewVideo: function(e) { + var clickedVidId = $(e.currentTarget).data().id; + + this.model.updateCurrentVideo(clickedVidId); + }, + + ytSearch: function() { + $(".videos").empty(); + var query = this.$searchInput.val(); + this.model.get('videos').fetch({data: { q: query, reset: true}}); + + }, + + renderVid: function (video) { + var videoView = new VideoView({model: video}); + this.$('.videos').append(videoView.render().el); + }, + + renderVideos: function () { + this.model.get('videos').each(function(m) { + this.renderVid(m); + }, this); + }, + + + renderMainVideo: function() { + + if (this.mainVideo) { + this.mainVideo.remove(); + } + this.mainVideo = new MainVideoView({ model: this.model.get('current_video')}) + + this.$('.current-video').append(this.mainVideo.render().el); + }, + + renderInitVideo: function () { + this.model.set('current_video', this.model.get('videos').at(0)); + }, + +}) \ No newline at end of file diff --git a/views/MainVideoView.js b/views/MainVideoView.js new file mode 100644 index 00000000..8c25976f --- /dev/null +++ b/views/MainVideoView.js @@ -0,0 +1,11 @@ +var MainVideoView = Backbone.View.extend({ + className: 'main-video', + + template: Handlebars.compile($('#main-video-template').html()), + + render: function() { + this.$el.html(this.template(this.model.toJSON())); + + return this; + } +}) \ No newline at end of file diff --git a/views/VideoView.js b/views/VideoView.js new file mode 100644 index 00000000..44f34683 --- /dev/null +++ b/views/VideoView.js @@ -0,0 +1,11 @@ +var VideoView = Backbone.View.extend({ + className: 'video', + + template: Handlebars.compile($('#video-template').html()), + + render: function() { + this.$el.html(this.template(this.model.toJSON())); + return this; + } + +}); \ No newline at end of file