");
- 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 = "
";
+ var errorResponse = JSON.parse(jqXHR.responseText)["errors"];
+ for (var key in errorResponse) {
+ var errorsForKey = errorResponse[key];
+ console.log("key: " + key);
+ var prettyKey = context.JK.entityToPrintable[key];
+ if (!prettyKey) {
+ prettyKey = key;
}
- };
+ for (var i = 0; i < errorsForKey.length; i++) {
- /**
- * 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);
- }
-
- function _heartbeatAckCheck() {
-
- // 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;
+ text += "
" + prettyKey + " " + errorsForKey[i] + "
";
}
+ }
+
+ text += "
";
+
+ app.notify({title: "Oops!", text: text, "icon_url": "/assets/content/icon_alert_big.png"});
}
-
- function _heartbeat() {
- if (app.heartbeatActive) {
-
- var message = context.JK.MessageFactory.heartbeat();
- context.JK.JamServer.send(message);
- lastHeartbeatFound = false;
- }
+ catch (e) {
+ // give up; not formatted correctly
+ app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
}
+ }
+ else {
+ app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
+ }
+ }
- function loggedIn(header, payload) {
- app.clientId = payload.client_id;
+ /**
+ * Expose ajaxError.
+ */
+ this.ajaxError = ajaxError;
- // 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 = "
";
- var errorResponse = JSON.parse(jqXHR.responseText)["errors"];
- for(var key in errorResponse) {
- var errorsForKey = errorResponse[key];
- console.log("key: " + key);
- var prettyKey = context.JK.entityToPrintable[key];
- if(!prettyKey) { prettyKey = key; }
- for(var i = 0; i < errorsForKey.length; i++) {
-
- text += "
" + prettyKey + " " + errorsForKey[i] + "
";
- }
- }
-
- text += "
";
-
- app.notify({title: "Oops!", text: text, "icon_url": "/assets/content/icon_alert_big.png"});
- }
- catch(e) {
- // give up; not formatted correctly
- app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
- }
- }
- else {
- app.notify({title: textStatus, text: errorMessage, detail: jqXHR.responseText});
- }
- }
-
- /**
- * Expose ajaxError.
- */
- this.ajaxError = ajaxError;
-
- /**
- * Provide a handler object for events related to a particular screen
- * being shown or hidden.
- * @screen is a string corresponding to the screen's layout-id attribute
- * @handler is an object with up to four optional keys:
- * beforeHide, afterHide, beforeShow, afterShow, which should all have
- * functions as values. If there is data provided by the screen's route
- * it will be provided to these functions.
- */
- this.bindScreen = function(screen, handler) {
- this.layout.bindScreen(screen, handler);
- };
- this.bindDialog = function(dialog, handler) {
- this.layout.bindDialog(dialog, handler);
- };
- /**
- * Allow individual wizard steps to register a function to be invokes
- * when they are shown.
- */
- this.registerWizardStepFunction = function(stepId, showFunction) {
- this.layout.registerWizardStepFunction(stepId, showFunction);
- };
-
- /**
- * Switch to the wizard step with the provided id.
- */
- this.setWizardStep = function(targetStepId) {
- this.layout.setWizardStep(targetStepId);
- };
-
- /**
- * Show a notification. Expects an object with a
- * title property and a text property.
- */
- this.notify = function(message, descriptor) {
- this.layout.notify(message, descriptor);
- };
-
- /** Shows an alert notification. Expects text, title */
- this.notifyAlert = function(title ,text) {
- this.notify({title:title, text:text, icon_url: "/assets/content/icon_alert_big.png"});
- }
-
- /** Using the standard rails style error object, shows an alert with all seen errors */
- this.notifyServerError = function(jqXHR, title) {
- if(!title) {
- title = "Server Error";
- }
- if(jqXHR.status == 422) {
- var errors = JSON.parse(jqXHR.responseText);
- var $errors = context.JK.format_all_errors(errors);
- this.notify({title:title, text:$errors, icon_url: "/assets/content/icon_alert_big.png"})
- }
- else
- {
- // we need to cehck more status codes and make tailored messages at this point
- this.notify({title:title, text:"status=" + jqXHR.status + ", message=" + jqXHR.responseText, icon_url: "/assets/content/icon_alert_big.png"})
- }
- }
-
- /**
- * Initialize any common events.
- */
- function events() {
- // Hook up the screen navigation controls.
- $(".content-nav .arrow-left").click(function(evt) {
- evt.preventDefault();
- context.history.back();
- return false;
- });
- $(".content-nav .arrow-right").click(function(evt) {
- evt.preventDefault();
- context.history.forward();
- return false;
- });
-
- context.JK.popExternalLinks();
- }
-
- // Due to timing of initialization, this must be called externally
- // after all screens have been given a chance to initialize.
- // It is called from index.html.erb after connecting, and initialization
- // of other screens.
- function initialRouting() {
- routing();
-
- var hash = context.location.hash;
-
- try { context.RouteMap.parse(hash); }
- catch(e) {
- console.log("ignoring bogus screen name: %o", hash)
- hash = null;
- }
-
- var url = '/client#/home';
- if (hash) {
- url = hash;
- }
-
- logger.debug("Changing screen to " + url);
- context.location = url;
- }
-
-
- this.unloadFunction = function() {
- logger.debug("window.unload function called.");
-
- context.JK.JamServer.close(false);
-
- if (context.jamClient) {
- // Unregister for callbacks.
- context.jamClient.RegisterRecordingCallbacks("", "", "", "", "");
- context.jamClient.SessionRegisterCallback("");
- context.jamClient.SessionSetAlertCallback("");
- context.jamClient.FTUERegisterVUCallbacks("", "", "");
- context.jamClient.FTUERegisterLatencyCallback("");
- context.jamClient.RegisterVolChangeCallBack("");
- }
- };
-
- this.initialize = function(inOpts) {
- var url, hash;
- app = this;
- this.opts = $.extend(opts, inOpts);
- this.layout = new context.JK.Layout();
- this.layout.initialize(this.opts.layoutOpts);
- events();
- this.layout.handleDialogState();
-
- if(opts.inClient) {
- registerLoginAck();
- registerHeartbeatAck();
- registerBadStateRecovered();
- registerBadStateError();
- registerSocketClosed();
- registerDownloadAvailable();
- context.JK.FaderHelpers.initialize();
- context.window.onunload = this.unloadFunction;
- }
- };
-
- // Holder for a function to invoke upon successfully completing the FTUE.
- // See createSession.submitForm as an example.
- this.afterFtue = null;
-
- // enable temporary suspension of heartbeat for fine-grained control
- this.heartbeatActive = true;
-
- /**
- * Expose clientId as a public variable.
- * Will be set upon LOGIN_ACK
- */
- this.clientId = null;
- this.initialRouting = initialRouting;
-
- return this;
+ /**
+ * Provide a handler object for events related to a particular screen
+ * being shown or hidden.
+ * @screen is a string corresponding to the screen's layout-id attribute
+ * @handler is an object with up to four optional keys:
+ * beforeHide, afterHide, beforeShow, afterShow, which should all have
+ * functions as values. If there is data provided by the screen's route
+ * it will be provided to these functions.
+ */
+ this.bindScreen = function (screen, handler) {
+ this.layout.bindScreen(screen, handler);
+ };
+ this.bindDialog = function (dialog, handler) {
+ this.layout.bindDialog(dialog, handler);
+ };
+ /**
+ * Allow individual wizard steps to register a function to be invokes
+ * when they are shown.
+ */
+ this.registerWizardStepFunction = function (stepId, showFunction) {
+ this.layout.registerWizardStepFunction(stepId, showFunction);
};
- })(window, jQuery);
\ No newline at end of file
+ /**
+ * Switch to the wizard step with the provided id.
+ */
+ this.setWizardStep = function (targetStepId) {
+ this.layout.setWizardStep(targetStepId);
+ };
+
+ /**
+ * Show a notification. Expects an object with a
+ * title property and a text property.
+ */
+ this.notify = function (message, descriptor) {
+ this.layout.notify(message, descriptor);
+ };
+
+ /** Shows an alert notification. Expects text, title */
+ this.notifyAlert = function (title, text) {
+ this.notify({title: title, text: text, icon_url: "/assets/content/icon_alert_big.png"});
+ }
+
+ /** Using the standard rails style error object, shows an alert with all seen errors */
+ this.notifyServerError = function (jqXHR, title) {
+ if (!title) {
+ title = "Server Error";
+ }
+ if (jqXHR.status == 422) {
+ var errors = JSON.parse(jqXHR.responseText);
+ var $errors = context.JK.format_all_errors(errors);
+ this.notify({title: title, text: $errors, icon_url: "/assets/content/icon_alert_big.png"})
+ }
+ else {
+ if (jqXHR.responseText.indexOf('') == 0 || jqXHR.responseText.indexOf('Show Error Detail');
+ showMore.data('responseText', jqXHR.responseText);
+ showMore.click(function () {
+ var self = $(this);
+ var text = self.data('responseText');
+ var bodyIndex = text.indexOf(' -1) {
+ text = text.substr(bodyIndex);
+ }
+ console.log("html", text);
+ $('#server-error-dialog .error-contents').html(text);
+ app.layout.showDialog('server-error-dialog')
+ return false;
+ });
+ this.notify({title: title, text: showMore, icon_url: "/assets/content/icon_alert_big.png"})
+ }
+ else {
+ this.notify({title: title, text: "status=" + jqXHR.status + ", message=" + jqXHR.responseText, icon_url: "/assets/content/icon_alert_big.png"})
+ }
+ }
+ }
+
+ /**
+ * Initialize any common events.
+ */
+ function events() {
+ // Hook up the screen navigation controls.
+ $(".content-nav .arrow-left").click(function (evt) {
+ evt.preventDefault();
+ context.history.back();
+ return false;
+ });
+ $(".content-nav .arrow-right").click(function (evt) {
+ evt.preventDefault();
+ context.history.forward();
+ return false;
+ });
+
+ context.JK.popExternalLinks();
+ }
+
+ // Due to timing of initialization, this must be called externally
+ // after all screens have been given a chance to initialize.
+ // It is called from index.html.erb after connecting, and initialization
+ // of other screens.
+ function initialRouting() {
+ routing();
+
+ var hash = context.location.hash;
+
+ try {
+ context.RouteMap.parse(hash);
+ }
+ catch (e) {
+ console.log("ignoring bogus screen name: %o", hash)
+ hash = null;
+ }
+
+ var url = '/client#/home';
+ if (hash) {
+ url = hash;
+ }
+
+ logger.debug("Changing screen to " + url);
+ context.location = url;
+ }
+
+
+ this.unloadFunction = function () {
+ logger.debug("window.unload function called.");
+
+ context.JK.JamServer.close(false);
+
+ if (context.jamClient) {
+ // Unregister for callbacks.
+ context.jamClient.RegisterRecordingCallbacks("", "", "", "", "");
+ context.jamClient.SessionRegisterCallback("");
+ context.jamClient.SessionSetAlertCallback("");
+ context.jamClient.FTUERegisterVUCallbacks("", "", "");
+ context.jamClient.FTUERegisterLatencyCallback("");
+ context.jamClient.RegisterVolChangeCallBack("");
+ }
+ };
+
+ this.initialize = function (inOpts) {
+ var url, hash;
+ app = this;
+ this.opts = $.extend(opts, inOpts);
+ this.layout = new context.JK.Layout();
+ this.layout.initialize(this.opts.layoutOpts);
+ events();
+ this.layout.handleDialogState();
+
+ if (opts.inClient) {
+ registerLoginAck();
+ registerHeartbeatAck();
+ registerBadStateRecovered();
+ registerBadStateError();
+ registerSocketClosed();
+ registerDownloadAvailable();
+ context.JK.FaderHelpers.initialize();
+ context.window.onunload = this.unloadFunction;
+ }
+ };
+
+ // Holder for a function to invoke upon successfully completing the FTUE.
+ // See createSession.submitForm as an example.
+ this.afterFtue = null;
+
+ // enable temporary suspension of heartbeat for fine-grained control
+ this.heartbeatActive = true;
+
+ /**
+ * Expose clientId as a public variable.
+ * Will be set upon LOGIN_ACK
+ */
+ this.clientId = null;
+ this.initialRouting = initialRouting;
+
+ return this;
+ };
+
+})(window, jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js
index 62fc5078f..b80f7aa48 100644
--- a/web/app/assets/javascripts/layout.js
+++ b/web/app/assets/javascripts/layout.js
@@ -671,7 +671,13 @@
function setNotificationInfo(message, descriptor) {
var $notify = $('[layout="notify"]');
$('h2', $notify).text(message.title);
- $('p', $notify).html(message.text instanceof jQuery ? message.text.html() : message.text);
+ if(message.text instanceof jQuery) {
+ $('p', $notify).append(message.text)
+ }
+ else {
+ $('p', $notify).html( message.text);
+ }
+
if (message.icon_url) {
$('#avatar', $notify).attr('src', message.icon_url);
diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css
index fb26f4c81..f4ae803ea 100644
--- a/web/app/assets/stylesheets/client/client.css
+++ b/web/app/assets/stylesheets/client/client.css
@@ -38,6 +38,7 @@
*= require ./hoverBubble
*= require ./recordingFinishedDialog
*= require ./localRecordingsDialog
+ *= require ./serverErrorDialog
*= require ./createSession
*= require ./genreSelector
*= require ./sessionList
diff --git a/web/app/assets/stylesheets/client/serverErrorDialog.css.scss b/web/app/assets/stylesheets/client/serverErrorDialog.css.scss
new file mode 100644
index 000000000..d56456f08
--- /dev/null
+++ b/web/app/assets/stylesheets/client/serverErrorDialog.css.scss
@@ -0,0 +1,19 @@
+#server-error-dialog {
+ body, p, ol, ul, td {
+ font-family: helvetica, verdana, arial, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+ }
+
+ pre {
+ background-color: #1C1C1C;
+ padding: 10px;
+ font-size: 11px;
+ white-space: pre-wrap;
+ color:#999999;
+ }
+
+ a { color: #000; }
+ a:visited { color: #666; }
+ a:hover { color: #fff; background-color:#000; }
+}
\ No newline at end of file
diff --git a/web/app/views/clients/_showServerErrorDialog.erb b/web/app/views/clients/_showServerErrorDialog.erb
new file mode 100644
index 000000000..21bc1889e
--- /dev/null
+++ b/web/app/views/clients/_showServerErrorDialog.erb
@@ -0,0 +1,14 @@
+
+