diff --git a/db/manifest b/db/manifest index 69f76d7bf..7cde015fe 100755 --- a/db/manifest +++ b/db/manifest @@ -104,4 +104,5 @@ large_photo_url.sql add_secret_to_user_authorization.sql track_connection_id_not_null.sql recordings_all_discarded.sql -recordings_via_admin_web.sql \ No newline at end of file +recordings_via_admin_web.sql +relax_band_model_varchar.sql \ No newline at end of file diff --git a/db/up/relax_band_model_varchar.sql b/db/up/relax_band_model_varchar.sql new file mode 100644 index 000000000..bd87312f1 --- /dev/null +++ b/db/up/relax_band_model_varchar.sql @@ -0,0 +1 @@ +ALTER TABLE bands ALTER COLUMN state TYPE VARCHAR(100); diff --git a/web/app/assets/javascripts/band_setup.js b/web/app/assets/javascripts/band_setup.js index f2ec4bb99..0bfb9d0ba 100644 --- a/web/app/assets/javascripts/band_setup.js +++ b/web/app/assets/javascripts/band_setup.js @@ -1,555 +1,567 @@ -(function(context,$) { +(function (context, $) { - "use strict"; + "use strict"; - context.JK = context.JK || {}; + context.JK = context.JK || {}; - // TODO: MUCH OF THIS CLASS IS REPEATED IN THE FOLLOWING FILES: - // createSession.js.erb - // accounts_profiles.js + // TODO: MUCH OF THIS CLASS IS REPEATED IN THE FOLLOWING FILES: + // createSession.js.erb + // accounts_profiles.js - context.JK.BandSetupScreen = function(app) { - var logger = context.JK.logger; - var rest = context.JK.Rest(); - var friendSelectorDialog = null; - var invitationDialog = null; - var autoComplete = null; - var userNames = []; - var userIds = []; - var userPhotoUrls = []; - var selectedFriendIds = {}; - var nilOptionText = 'n/a'; - var bandId = ''; + context.JK.BandSetupScreen = function (app) { + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var friendSelectorDialog = null; + var invitationDialog = null; + var autoComplete = null; + var userNames = []; + var userIds = []; + var userPhotoUrls = []; + var selectedFriendIds = {}; + var nilOptionText = 'n/a'; + var bandId = ''; - function resetForm() { + function resetForm() { - // name - $("#band-name").val(''); - var $tdName = $("#tdBandName"); - $tdName.find('.error-text').remove(); - $tdName.removeClass("error"); + // name + $("#band-name").val(''); + var $tdName = $("#tdBandName"); + $tdName.find('.error-text').remove(); + $tdName.removeClass("error"); - // country - $("#band-country").empty(); - $("#band-country").val(''); - var $tdCountry = $("#tdBandCountry"); - $tdCountry.find('.error-text').remove(); - $tdCountry.removeClass("error"); + // country + $("#band-country").empty(); + $("#band-country").val(''); + var $tdCountry = $("#tdBandCountry"); + $tdCountry.find('.error-text').remove(); + $tdCountry.removeClass("error"); - // region - $("#band-region").empty(); - $("#band-region").val(''); - var $tdRegion = $("#tdBandRegion"); - $tdRegion.find('.error-text').remove(); - $tdRegion.removeClass("error"); - - // city - $("#band-city").empty(); - $("#band-city").val(''); - var $tdCity = $("#tdBandCity"); - $tdCity.find('.error-text').remove(); - $tdCity.removeClass("error"); + // region + $("#band-region").empty(); + $("#band-region").val(''); + var $tdRegion = $("#tdBandRegion"); + $tdRegion.find('.error-text').remove(); + $tdRegion.removeClass("error"); - // description - $("#band-biography").val(''); - var $tdBiography = $("#tdBandBiography"); - $tdBiography.find('.error-text').remove(); - $tdBiography.removeClass("error"); + // city + $("#band-city").empty(); + $("#band-city").val(''); + var $tdCity = $("#tdBandCity"); + $tdCity.find('.error-text').remove(); + $tdCity.removeClass("error"); - resetGenres(); + // description + $("#band-biography").val(''); + var $tdBiography = $("#tdBandBiography"); + $tdBiography.find('.error-text').remove(); + $tdBiography.removeClass("error"); - $("#hdn-band-id").val(''); + resetGenres(); - $("#band-setup-step-1").show(); - $("#band-setup-step-2").hide(); + $("#hdn-band-id").val(''); - $('#band-invitee-input') - .unbind('blur') - .attr("placeholder", "Looking up friends...") - .prop('disabled', true) + $("#band-setup-step-1").show(); + $("#band-setup-step-2").hide(); + + $('#band-invitee-input') + .unbind('blur') + .attr("placeholder", "Looking up friends...") + .prop('disabled', true) + } + + function resetGenres() { + $('input[type=checkbox]:checked', '#band-genres').each(function (i) { + $(this).removeAttr("checked"); + }); + + var $tdGenres = $("#tdBandGenres"); + $tdGenres.find('.error-text').remove(); + $tdGenres.removeClass("error"); + } + + function getSelectedGenres() { + var genres = []; + $('input[type=checkbox]:checked', '#band-genres').each(function (i) { + var genre = $(this).val(); + genres.push(genre); + }); + + return genres; + } + + function validateGeneralInfo() { + var isValid = true; + + // name + var $name = $("#band-name"); + var $tdName = $("#tdBandName"); + var name = $.trim($name.val()); + if (name.length === 0) { + $tdName.find('.error-text').remove(); + $tdName.addClass("error"); + $name.after(""); + isValid = false; + } + else { + $tdName.removeClass("error"); + } + + // country + var $country = $("#band-country"); + var $tdCountry = $("#tdBandCountry"); + var country = $.trim($country.val()); + if (country.length === 0) { + $tdCountry.find('.error-text').remove(); + $tdCountry.addClass("error"); + $country.after(""); + isValid = false; + } + else { + $tdCountry.removeClass("error"); + } + + // region + var $region = $("#band-region"); + var $tdRegion = $("#tdBandRegion"); + var region = $.trim($region.val()); + if (region.length === 0) { + $tdRegion.find('.error-text').remove(); + $tdRegion.addClass("error"); + $region.after(""); + isValid = false; + } + else { + $tdRegion.removeClass("error"); + } + + // city + var $city = $("#band-city"); + var $tdCity = $("#tdBandCity"); + var city = $.trim($city.val()); + if (city.length === 0) { + $tdCity.find('.error-text').remove(); + $tdCity.addClass("error"); + $city.after(""); + isValid = false; + } + else { + $tdCity.removeClass("error"); + } + + // genres (no more than 3) + var $genres = $(".band-setup-genres"); + var $tdGenres = $("#tdBandGenres"); + var selectedGenres = getSelectedGenres(); + if (selectedGenres.length === 0 || selectedGenres.length > 3) { + $tdGenres.find('.error-text').remove(); + $tdGenres.addClass("error"); + $genres.after(""); + isValid = false; + } + else { + $tdGenres.removeClass("error"); + } + + // description + var $biography = $("#band-biography"); + var $tdBiography = $("#tdBandBiography"); + var biography = $.trim($biography.val()); + if (biography.length === 0) { + $tdBiography.find('.error-text').remove(); + $tdBiography.addClass("error"); + $biography.after(""); + isValid = false; + } + else { + $tdBiography.removeClass("error"); + } + + return isValid; + } + + function saveBand() { + var band = {}; + band.name = $("#band-name").val(); + band.website = $("#band-website").val(); + band.biography = $("#band-biography").val(); + band.city = $("#band-city").val(); + band.state = $("#band-region").val(); + band.country = $("#band-country").val(); + band.genres = getSelectedGenres(); + + if (bandId.length === 0) { + rest.createBand(band) + .done(function (response) { + createBandInvitations(response.id, function () { + context.location = "/client#/bandProfile/" + response.id; + }); + }) + .fail(function (jqXHR) { + app.notifyServerError(jqXHR, "Unable to create band") + }); + ; + } + else { + band.id = bandId; + rest.updateBand(band) + .done(function (response) { + createBandInvitations(band.id, function () { + context.location = "/client#/bandProfile/" + band.id; + }); + }) + .fail(function (jqXHR) { + app.notifyServerError(jqXHR, "Unable to create band") + }); + } + } + + function createBandInvitations(bandId, onComplete) { + var callCount = 0; + var totalInvitations = 0; + $('#selected-band-invitees .invitation').each(function (index, invitation) { + callCount++; + totalInvitations++; + var userId = $(invitation).attr('user-id'); + rest.createBandInvitation(bandId, userId) + .done(function (response) { + callCount--; + }).fail(app.ajaxError); + }); + + function checker() { + if (callCount === 0) { + onComplete(); + } else { + context.setTimeout(checker, 10); + } + } + + checker(); + return totalInvitations; + } + + function beforeShow(data) { + userNames = []; + userIds = []; + userPhotoUrls = []; + //bandId = "1158c8b6-4c92-47dc-82bf-1e390c4f9b2c"; + bandId = $("#hdn-band-id").val(); + resetForm(); + } + + function afterShow(data) { + friendSelectorDialog.setCallback(friendSelectorCallback); + loadFriends(); + + if (bandId.length > 0) { + $("#band-setup-title").html("edit band"); + $("#btn-band-setup-save").html("SAVE CHANGES"); + + // retrieve and initialize band profile data points + loadBandDetails(); + } + else { + loadGenres(); + + rest.getResolvedLocation() + .done(function (location) { + loadCountries(location.country, function () { + loadRegions(location.region, function () { + loadCities(location.city); + }); + }); + }); + + + $("#band-setup-title").html("set up band"); + $("#btn-band-setup-save").html("CREATE BAND"); + + $("#band-change-photo").unbind('click'); + $("#band-change-photo").html('Set up band and then add photo.'); + } + } + + function loadBandDetails() { + rest.getBand(bandId).done(function (band) { + $("#band-name").val(band.name); + $("#band-website").val(band.website); + $("#band-biography").val(band.biography); + + if (band.photo_url) { + $("#band-avatar").attr('src', band.photo_url); } - function resetGenres() { - $('input[type=checkbox]:checked', '#band-genres').each(function(i) { - $(this).removeAttr("checked"); + loadGenres(band.genres); + + loadCountries(band.country, function () { + loadRegions(band.state, function () { + loadCities(band.city); + }); + }); + + // TODO: initialize avatar + }); + } + + // TODO: this is repeated in createSession.js.erb + function loadFriends() { + rest.getFriends({ id: context.JK.currentUserId }) + .done(function (friends) { + $.each(friends, function () { + userNames.push(this.name); + userIds.push(this.id); + userPhotoUrls.push(this.photo_url); + }); + + var autoCompleteOptions = { + lookup: { suggestions: userNames, data: userIds }, + onSelect: addInvitation + }; + + $('#band-invitee-input').attr("placeholder", "Type a friend\'s name").prop('disabled', false); + + if (!autoComplete) { + autoComplete = $('#band-invitee-input').autocomplete(autoCompleteOptions); + } + else { + autoComplete.setOptions(autoCompleteOptions); + } + + $(".autocomplete").width("150px"); + }) + .fail(function () { + $('#band-invitee-input').attr("placeholder", "Unable to lookup friends"); + app.ajaxError(arguments) + }); + } + + function loadGenres(selectedGenres) { + $("#band-genres").empty(); + + rest.getGenres().done(function (genres) { + $.each(genres, function (index, genre) { + var genreTemplate = $('#template-band-setup-genres').html(); + var selected = ''; + + if (selectedGenres) { + var genreMatch = $.grep(selectedGenres, function (n, i) { + return n.id === genre.id; }); - var $tdGenres = $("#tdBandGenres"); - $tdGenres.find('.error-text').remove(); - $tdGenres.removeClass("error"); + if (genreMatch.length > 0) { + selected = "checked"; + } + } + var genreHtml = context.JK.fillTemplate(genreTemplate, { + id: genre.id, + description: genre.description, + checked: selected + }); + + $('#band-genres').append(genreHtml); + }); + }); + } + + function loadCountries(initialCountry, onCountriesLoaded) { + var $country = $("#band-country"); + + var nilOption = $(''); + nilOption.text(nilOptionText); + $country.append(nilOption); + + rest.getCountries().done(function (response) { + $.each(response["countries"], function (index, country) { + if (!country) return; + var option = $(''); + option.text(country); + option.attr("value", country); + + if (initialCountry === country) { + option.attr("selected", "selected"); + } + + $country.append(option); + }); + + context.JK.dropdown($country); + + if (onCountriesLoaded) { + onCountriesLoaded(); } + }); + } - function getSelectedGenres() { - var genres = []; - $('input[type=checkbox]:checked', '#band-genres').each(function(i) { - var genre = $(this).val(); - genres.push(genre); - }); + function loadRegions(initialRegion, onRegionsLoaded) { + var $region = $("#band-region"); + $region.empty(); + var selectedCountry = $("#band-country").val(); - return genres; + var nilOption = $(''); + nilOption.text(nilOptionText); + $region.append(nilOption); + + if (selectedCountry) { + rest.getRegions({'country': selectedCountry}).done(function (response) { + $.each(response["regions"], function (index, region) { + if (!region) return; + var option = $(''); + option.text(region); + option.attr("value", region); + + if (initialRegion === region) { + option.attr("selected", "selected"); + } + + $region.append(option); + }); + + + context.JK.dropdown($region); + + if (onRegionsLoaded) { + onRegionsLoaded(); + } + }); + } + } + + function loadCities(initialCity) { + var $city = $("#band-city"); + $city.empty(); + var selectedCountry = $("#band-country").val(); + var selectedRegion = $("#band-region").val(); + + var nilOption = $(''); + nilOption.text(nilOptionText); + $city.append(nilOption); + + if (selectedCountry && selectedRegion) { + rest.getCities({'country': selectedCountry, 'region': selectedRegion}).done(function (response) { + $.each(response["cities"], function (index, city) { + if (!city) return; + var option = $(''); + option.text(city); + option.attr("value", city); + + if (initialCity === city) { + option.attr("selected", "selected"); + } + + $city.append(option); + }); + + context.JK.dropdown($city); + }); + } + } + + function friendSelectorCallback(newSelections) { + var keys = Object.keys(newSelections); + for (var i = 0; i < keys.length; i++) { + addInvitation(newSelections[keys[i]].userName, newSelections[keys[i]].userId); + } + } + + function addInvitation(value, data) { + if ($('#selected-band-invitees div[user-id=' + data + ']').length === 0) { + var template = $('#template-band-invitation').html(); + var invitationHtml = context.JK.fillTemplate(template, {userId: data, userName: value}); + $('#selected-band-invitees').append(invitationHtml); + $('#band-invitee-input').select(); + selectedFriendIds[data] = true; + } + else { + $('#band-invitee-input').select(); + context.alert('Invitation already exists for this musician.'); + } + } + + function removeInvitation(evt) { + delete selectedFriendIds[$(evt.currentTarget).parent().attr('user-id')]; + $(evt.currentTarget).closest('.invitation').remove(); + } + + function events() { + $('#selected-band-invitees').on("click", ".invitation a", removeInvitation); + + // friend input focus + $('#band-invitee-input').focus(function () { + $(this).val(''); + }); + + $('#btn-band-setup-cancel').click(function () { + resetForm(); + context.location = "/client#/home"; + }); + + $('#btn-band-setup-next').click(function () { + if (validateGeneralInfo()) { + $("#band-setup-step-2").show(); + $("#band-setup-step-1").hide(); } - - function validateGeneralInfo() { - var isValid = true; - - // name - var $name = $("#band-name"); - var $tdName = $("#tdBandName"); - var name = $.trim($name.val()); - if (name.length === 0) { - $tdName.find('.error-text').remove(); - $tdName.addClass("error"); - $name.after(""); - isValid = false; - } - else { - $tdName.removeClass("error"); - } - - // country - var $country = $("#band-country"); - var $tdCountry = $("#tdBandCountry"); - var country = $.trim($country.val()); - if (country.length === 0) { - $tdCountry.find('.error-text').remove(); - $tdCountry.addClass("error"); - $country.after(""); - isValid = false; - } - else { - $tdCountry.removeClass("error"); - } - - // region - var $region = $("#band-region"); - var $tdRegion = $("#tdBandRegion"); - var region = $.trim($region.val()); - if (region.length === 0) { - $tdRegion.find('.error-text').remove(); - $tdRegion.addClass("error"); - $region.after(""); - isValid = false; - } - else { - $tdRegion.removeClass("error"); - } - - // city - var $city = $("#band-city"); - var $tdCity = $("#tdBandCity"); - var city = $.trim($city.val()); - if (city.length === 0) { - $tdCity.find('.error-text').remove(); - $tdCity.addClass("error"); - $city.after(""); - isValid = false; - } - else { - $tdCity.removeClass("error"); - } - - // genres (no more than 3) - var $genres = $(".band-setup-genres"); - var $tdGenres = $("#tdBandGenres"); - var selectedGenres = getSelectedGenres(); - if (selectedGenres.length === 0 || selectedGenres.length > 3) { - $tdGenres.find('.error-text').remove(); - $tdGenres.addClass("error"); - $genres.after(""); - isValid = false; - } - else { - $tdGenres.removeClass("error"); - } - - // description - var $biography = $("#band-biography"); - var $tdBiography = $("#tdBandBiography"); - var biography = $.trim($biography.val()); - if (biography.length === 0) { - $tdBiography.find('.error-text').remove(); - $tdBiography.addClass("error"); - $biography.after(""); - isValid = false; - } - else { - $tdBiography.removeClass("error"); - } - - return isValid; - } - - function saveBand() { - var band = {}; - band.name = $("#band-name").val(); - band.website = $("#band-website").val(); - band.biography = $("#band-biography").val(); - band.city = $("#band-city").val(); - band.state = $("#band-region").val(); - band.country = $("#band-country").val(); - band.genres = getSelectedGenres(); - - if (bandId.length === 0) { - rest.createBand(band).done(function(response) { - createBandInvitations(response.id, function() { - context.location = "/client#/bandProfile/" + response.id; - }); - }); - } - else { - band.id = bandId; - rest.updateBand(band).done(function(response) { - createBandInvitations(band.id, function() { - context.location = "/client#/bandProfile/" + band.id; - }); - }); - } - } - - function createBandInvitations(bandId, onComplete) { - var callCount = 0; - var totalInvitations = 0; - $('#selected-band-invitees .invitation').each(function(index, invitation) { - callCount++; - totalInvitations++; - var userId = $(invitation).attr('user-id'); - rest.createBandInvitation(bandId, userId) - .done(function(response) { - callCount--; - }).fail(app.ajaxError); - }); - - function checker() { - if (callCount === 0) { - onComplete(); - } else { - context.setTimeout(checker, 10); - } - } - checker(); - return totalInvitations; - } - - function beforeShow(data) { - userNames = []; - userIds = []; - userPhotoUrls = []; - //bandId = "1158c8b6-4c92-47dc-82bf-1e390c4f9b2c"; - bandId = $("#hdn-band-id").val(); - resetForm(); - } - - function afterShow(data) { - friendSelectorDialog.setCallback(friendSelectorCallback); - loadFriends(); - - if (bandId.length > 0) { - $("#band-setup-title").html("edit band"); - $("#btn-band-setup-save").html("SAVE CHANGES"); - - // retrieve and initialize band profile data points - loadBandDetails(); - } - else { - loadGenres(); - - rest.getResolvedLocation() - .done(function(location) { - loadCountries(location.country, function() { - loadRegions(location.region, function() { - loadCities(location.city); - }); - }); - }); - - - $("#band-setup-title").html("set up band"); - $("#btn-band-setup-save").html("CREATE BAND"); - - $("#band-change-photo").unbind('click'); - $("#band-change-photo").html('Set up band and then add photo.'); - } - } - - function loadBandDetails() { - rest.getBand(bandId).done(function(band) { - $("#band-name").val(band.name); - $("#band-website").val(band.website); - $("#band-biography").val(band.biography); - - if (band.photo_url) { - $("#band-avatar").attr('src', band.photo_url); - } - - loadGenres(band.genres); - - loadCountries(band.country, function() { - loadRegions(band.state, function() { - loadCities(band.city); - }); - }); - - // TODO: initialize avatar - }); - } - - // TODO: this is repeated in createSession.js.erb - function loadFriends() { - rest.getFriends({ id: context.JK.currentUserId }) - .done(function(friends) { - $.each(friends, function() { - userNames.push(this.name); - userIds.push(this.id); - userPhotoUrls.push(this.photo_url); - }); - - var autoCompleteOptions = { - lookup: { suggestions: userNames, data: userIds }, - onSelect: addInvitation - }; - - $('#band-invitee-input').attr("placeholder", "Type a friend\'s name").prop('disabled', false); - - if (!autoComplete) { - autoComplete = $('#band-invitee-input').autocomplete(autoCompleteOptions); - } - else { - autoComplete.setOptions(autoCompleteOptions); - } - - $(".autocomplete").width("150px"); - }) - .fail(function() { - $('#band-invitee-input').attr("placeholder", "Unable to lookup friends"); - app.ajaxError(arguments) - }); - } - - function loadGenres(selectedGenres) { - $("#band-genres").empty(); - - rest.getGenres().done(function(genres) { - $.each(genres, function(index, genre) { - var genreTemplate = $('#template-band-setup-genres').html(); - var selected = ''; - - if (selectedGenres) { - var genreMatch = $.grep(selectedGenres, function(n,i) { - return n.id === genre.id; - }); - - if (genreMatch.length > 0) { - selected = "checked"; - } - } - var genreHtml = context.JK.fillTemplate(genreTemplate, { - id: genre.id, - description: genre.description, - checked: selected - }); - - $('#band-genres').append(genreHtml); - }); - }); - } - - function loadCountries(initialCountry, onCountriesLoaded) { - var $country = $("#band-country"); - - var nilOption = $(''); - nilOption.text(nilOptionText); - $country.append(nilOption); - - rest.getCountries().done(function(response) { - $.each(response["countries"], function(index, country) { - if(!country) return; - var option = $(''); - option.text(country); - option.attr("value", country); - - if (initialCountry === country) { - option.attr("selected", "selected"); - } - - $country.append(option); - }); - - context.JK.dropdown($country); - - if (onCountriesLoaded) { - onCountriesLoaded(); - } - }); - } - - function loadRegions(initialRegion, onRegionsLoaded) { - var $region = $("#band-region"); - $region.empty(); - var selectedCountry = $("#band-country").val(); - - var nilOption = $(''); - nilOption.text(nilOptionText); - $region.append(nilOption); - - if (selectedCountry) { - rest.getRegions({'country': selectedCountry}).done(function(response) { - $.each(response["regions"], function(index, region) { - if(!region) return; - var option = $(''); - option.text(region); - option.attr("value", region); - - if (initialRegion === region) { - option.attr("selected", "selected"); - } - - $region.append(option); - }); - - - context.JK.dropdown($region); - - if (onRegionsLoaded) { - onRegionsLoaded(); - } - }); - } - } - - function loadCities(initialCity) { - var $city = $("#band-city"); - $city.empty(); - var selectedCountry = $("#band-country").val(); - var selectedRegion = $("#band-region").val(); - - var nilOption = $(''); - nilOption.text(nilOptionText); - $city.append(nilOption); - - if (selectedCountry && selectedRegion) { - rest.getCities({'country': selectedCountry, 'region': selectedRegion}) .done(function(response) { - $.each(response["cities"], function(index, city) { - if(!city) return; - var option = $(''); - option.text(city); - option.attr("value", city); - - if (initialCity === city) { - option.attr("selected", "selected"); - } - - $city.append(option); - }); - - context.JK.dropdown($city); - }); - } - } - - function friendSelectorCallback(newSelections) { - var keys = Object.keys(newSelections); - for (var i=0; i < keys.length; i++) { - addInvitation(newSelections[keys[i]].userName, newSelections[keys[i]].userId); - } - } - - function addInvitation(value, data) { - if ($('#selected-band-invitees div[user-id=' + data + ']').length === 0) { - var template = $('#template-band-invitation').html(); - var invitationHtml = context.JK.fillTemplate(template, {userId: data, userName: value}); - $('#selected-band-invitees').append(invitationHtml); - $('#band-invitee-input').select(); - selectedFriendIds[data] = true; - } - else { - $('#band-invitee-input').select(); - context.alert('Invitation already exists for this musician.'); - } - } - - function removeInvitation(evt) { - delete selectedFriendIds[$(evt.currentTarget).parent().attr('user-id')]; - $(evt.currentTarget).closest('.invitation').remove(); - } - - function events() { - $('#selected-band-invitees').on("click", ".invitation a", removeInvitation); - - // friend input focus - $('#band-invitee-input').focus(function() { $(this).val(''); }); - - $('#btn-band-setup-cancel').click(function() { - resetForm(); - context.location = "/client#/home"; - }); - - $('#btn-band-setup-next').click(function() { - if (validateGeneralInfo()) { - $("#band-setup-step-2").show(); - $("#band-setup-step-1").hide(); - } - }); - - $('#btn-band-setup-back').click(function() { - $("#band-setup-step-1").show(); - $("#band-setup-step-2").hide(); - }); - - $('#btn-band-setup-save').click(saveBand); - - $('#btn-band-choose-friends').click(function() { - friendSelectorDialog.showDialog(selectedFriendIds); - }); - - $('#band-country').on('change', function(evt) { - evt.stopPropagation(); - loadRegions(); - loadCities(); - return false; - }); - - $('#band-region').on('change', function(evt) { - evt.stopPropagation(); - loadCities(); - return false; - }); - - $('#band-change-photo').click(function(evt) { - evt.stopPropagation(); - $("#hdn-band-id").val(bandId); - context.location = '/client#/band/setup/photo'; - return false; - }); - - $('div[layout-id="band/setup"] .btn-email-invitation').click(function() { - invitationDialog.showEmailDialog(); - }); - - $('div[layout-id="band/setup"] .btn-gmail-invitation').click(function() { - invitationDialog.showGoogleDialog(); - }); - - $('div[layout-id="band/setup"] .btn-facebook-invitation').click(function() { - invitationDialog.showFacebookDialog(); - }); - } - - function initialize(invitationDialogInstance, friendSelectorDialogInstance) { - friendSelectorDialog = friendSelectorDialogInstance; - invitationDialog = invitationDialogInstance; - events(); - - var screenBindings = { - 'beforeShow': beforeShow, - 'afterShow': afterShow - }; - - app.bindScreen('band/setup', screenBindings); - } - - this.initialize = initialize; - this.afterShow = afterShow; - return this; - }; - - })(window,jQuery); \ No newline at end of file + }); + + $('#btn-band-setup-back').click(function () { + $("#band-setup-step-1").show(); + $("#band-setup-step-2").hide(); + }); + + $('#btn-band-setup-save').click(saveBand); + + $('#btn-band-choose-friends').click(function () { + friendSelectorDialog.showDialog(selectedFriendIds); + }); + + $('#band-country').on('change', function (evt) { + evt.stopPropagation(); + loadRegions(); + loadCities(); + return false; + }); + + $('#band-region').on('change', function (evt) { + evt.stopPropagation(); + loadCities(); + return false; + }); + + $('#band-change-photo').click(function (evt) { + evt.stopPropagation(); + $("#hdn-band-id").val(bandId); + context.location = '/client#/band/setup/photo'; + return false; + }); + + $('div[layout-id="band/setup"] .btn-email-invitation').click(function () { + invitationDialog.showEmailDialog(); + }); + + $('div[layout-id="band/setup"] .btn-gmail-invitation').click(function () { + invitationDialog.showGoogleDialog(); + }); + + $('div[layout-id="band/setup"] .btn-facebook-invitation').click(function () { + invitationDialog.showFacebookDialog(); + }); + } + + function initialize(invitationDialogInstance, friendSelectorDialogInstance) { + friendSelectorDialog = friendSelectorDialogInstance; + invitationDialog = invitationDialogInstance; + events(); + + var screenBindings = { + 'beforeShow': beforeShow, + 'afterShow': afterShow + }; + + app.bindScreen('band/setup', screenBindings); + } + + this.initialize = initialize; + this.afterShow = afterShow; + return this; + }; + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/jamkazam.js b/web/app/assets/javascripts/jamkazam.js index 389ec19ae..6356d15f7 100644 --- a/web/app/assets/javascripts/jamkazam.js +++ b/web/app/assets/javascripts/jamkazam.js @@ -1,395 +1,419 @@ -(function(context,$) { +(function (context, $) { - "use strict"; + "use strict"; - // Change underscore's default templating characters as they - // conflict withe the ERB rendering. Templates will use: - // {{ interpolate }} - // {% evaluate %} - // {{- escape }} - // - context._.templateSettings = { - evaluate : /\{%([\s\S]+?)%\}/g, - interpolate : /\{\{([\s\S]+?)\}\}/g, - escape : /\{\{-([\s\S]+?)\}\}/g + // Change underscore's default templating characters as they + // conflict withe the ERB rendering. Templates will use: + // {{ interpolate }} + // {% evaluate %} + // {{- escape }} + // + context._.templateSettings = { + evaluate: /\{%([\s\S]+?)%\}/g, + interpolate: /\{\{([\s\S]+?)\}\}/g, + escape: /\{\{-([\s\S]+?)\}\}/g + }; + + context.JK = context.JK || {}; + + var JamKazam = context.JK.JamKazam = function () { + var app; + var logger = context.JK.logger; + var heartbeatInterval = null; + var heartbeatMS = null; + var heartbeatMissedMS = 10000; // if 5 seconds go by and we haven't seen a heartbeat ack, get upset + var inBadState = false; + var lastHeartbeatAckTime = null; + var lastHeartbeatFound = false; + var heartbeatAckCheckInterval = null; + + var opts = { + inClient: true, // specify false if you want the app object but none of the client-oriented features + layoutOpts: { + layoutFooter: true // specify false if you want footer to be left alone + } }; - context.JK = context.JK || {}; + /** + * Dynamically build routes from markup. Any layout="screen" will get a route corresponding to + * his layout-id attribute. If a layout-arg attribute is present, that will be named as a data + * section of the route. + */ + function routing() { + var routes = context.RouteMap, + rules = {}, + rule, + routingContext = {}; + $('div[layout="screen"]').each(function () { + var target = $(this).attr('layout-id'), + targetUrl = target, + targetArg = $(this).attr('layout-arg'), + fn = function (data) { + app.layout.changeToScreen(target, data); + }; + if (targetArg) { + targetUrl += "/:" + targetArg; + } + rules[target] = {route: '/' + targetUrl + '/:d?', method: target}; + routingContext[target] = fn; + }); + routes.context(routingContext); + for (rule in rules) if (rules.hasOwnProperty(rule)) routes.add(rules[rule]); + $(context).bind('hashchange', routes.handler); + $(routes.handler); + } - var JamKazam = context.JK.JamKazam = function() { - var app; - var logger = context.JK.logger; - var heartbeatInterval=null; - var heartbeatMS=null; - var heartbeatMissedMS= 10000; // if 5 seconds go by and we haven't seen a heartbeat ack, get upset - var inBadState=false; - var lastHeartbeatAckTime=null; - var lastHeartbeatFound=false; - var heartbeatAckCheckInterval = null; + function _heartbeatAckCheck() { - var opts = { - inClient: true, // specify false if you want the app object but none of the client-oriented features - layoutOpts: { - layoutFooter: true // specify false if you want footer to be left alone + // if we've seen an ack to the latest heartbeat, don't bother with checking again + // this makes us resilient to front-end hangs + if (lastHeartbeatFound) { + return; + } + + // check if the server is still sending heartbeat acks back down + // this logic equates to 'if we have not received a heartbeat within heartbeatMissedMS, then get upset + if (new Date().getTime() - lastHeartbeatAckTime.getTime() > heartbeatMissedMS) { + logger.error("no heartbeat ack received from server after ", heartbeatMissedMS, " seconds . giving up on socket connection"); + context.JK.JamServer.close(true); + } + else { + lastHeartbeatFound = true; + } + } + + function _heartbeat() { + if (app.heartbeatActive) { + + var message = context.JK.MessageFactory.heartbeat(); + context.JK.JamServer.send(message); + lastHeartbeatFound = false; + } + } + + function loggedIn(header, payload) { + app.clientId = payload.client_id; + + // tell the backend that we have logged in + context.jamClient.OnLoggedIn(payload.user_id, payload.token); + + $.cookie('client_id', payload.client_id); + + heartbeatMS = payload.heartbeat_interval * 1000; + logger.debug("jamkazam.js.loggedIn(): clientId now " + app.clientId + "; Setting up heartbeat every " + heartbeatMS + " MS"); + heartbeatInterval = context.setInterval(_heartbeat, heartbeatMS); + heartbeatAckCheckInterval = context.setInterval(_heartbeatAckCheck, 1000); + lastHeartbeatAckTime = new Date(new Date().getTime() + heartbeatMS); // add a little forgiveness to server for initial heartbeat + } + + function heartbeatAck(header, payload) { + lastHeartbeatAckTime = new Date(); + } + + /** + * This occurs when the websocket gateway loses a connection to the backend messaging system, + * resulting in severe loss of functionality + */ + function serverBadStateError() { + if (!inBadState) { + inBadState = true; + app.notify({title: "Server Unstable", text: "The server is currently unstable, resulting in feature loss. If you are experiencing any problems, please try to use JamKazam later."}) + } + } + + /** + * This occurs when the websocket gateway loses a connection to the backend messaging system, + * resulting in severe loss of functionality + */ + function serverBadStateRecovered() { + if (inBadState) { + inBadState = false; + app.notify({title: "Server Recovered", text: "The server is now stable again. If you are still experiencing problems, either create a new music session or restart the client altogether."}) + } + } + + /** + * This occurs when a new download from a recording has become available + */ + function downloadAvailable() { + context.jamClient.OnDownloadAvailable(); + } + + /** + * Called whenever the websocket closes; this gives us a chance to cleanup things that should be stopped/cleared + * @param in_error did the socket close abnormally? + */ + function socketClosed(in_error) { + + // tell the backend that we have logged out + context.jamClient.OnLoggedOut(); + + // stop future heartbeats + if (heartbeatInterval != null) { + clearInterval(heartbeatInterval); + heartbeatInterval = null; + } + + // stop checking for heartbeat acks + if (heartbeatAckCheckInterval != null) { + clearTimeout(heartbeatAckCheckInterval); + heartbeatAckCheckInterval = null; + } + } + + function registerLoginAck() { + logger.debug("register for loggedIn to set clientId"); + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.LOGIN_ACK, loggedIn); + } + + function registerHeartbeatAck() { + logger.debug("register for heartbeatAck"); + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, heartbeatAck); + } + + function registerBadStateError() { + logger.debug("register for server_bad_state_error"); + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_BAD_STATE_ERROR, serverBadStateError); + } + + function registerBadStateRecovered() { + logger.debug("register for server_bad_state_recovered"); + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_BAD_STATE_RECOVERED, serverBadStateRecovered); + } + + function registerDownloadAvailable() { + logger.debug("register for download_available"); + context.JK.JamServer.registerMessageCallback(context.JK.MessageType.DOWNLOAD_AVAILABLE, downloadAvailable); + } + + + function registerSocketClosed() { + logger.debug("register for socket closed"); + context.JK.JamServer.registerOnSocketClosed(socketClosed); + } + + /** + * Generic error handler for Ajax calls. + */ + function ajaxError(jqXHR, textStatus, errorMessage) { + logger.error("Unexpected ajax error: " + textStatus); + + if (jqXHR.status == 404) { + app.notify({title: "Oops!", text: "What you were looking for is gone now."}); + } + else if (jqXHR.status = 422) { + // present a nicer message + try { + var text = "