(function (context, $) {
"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
};
context.JK = context.JK || {};
var JamKazam = context.JK.JamKazam = function () {
var app;
var logger = context.JK.logger;
var rest = context.JK.Rest();
var inBadState = false;
var userDeferred = 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
}
};
/**
* 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) {
data.screen = target;
app.layout.changeToScreen(target, data);
};
if (targetArg) {
targetUrl += "/:" + targetArg;
}
rules[target] = {route: '/' + targetUrl + '/:d?', method: target};
routingContext[target] = fn;
// allow dialogs to take an optional argument
rules[target+'opt'] = {route: '/' + targetUrl + '/:d?/d1:', method: target};
routingContext[target + 'opt'] = fn;
});
routes.context(routingContext);
for (rule in rules) if (rules.hasOwnProperty(rule)) routes.add(rules[rule]);
$(context).bind('hashchange', function(e) { app.layout.onHashChange(e, routes.handler)});
$(routes.handler);
}
/**
* 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();
}
/**
* This most likely occurs when multiple tabs in the same browser are open, until we make a fix for this...
*/
function duplicateClientError() {
context.JK.Banner.showAlert("Duplicate Window (Development Mode Only)", "You have logged in another window in this browser. This window will continue to work but with degraded functionality. This limitation will soon be fixed.")
context.JK.JamServer.noReconnect = true;
}
function registerBadStateError() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_BAD_STATE_ERROR, serverBadStateError);
}
function registerBadStateRecovered() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_BAD_STATE_RECOVERED, serverBadStateRecovered);
}
function registerDownloadAvailable() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.DOWNLOAD_AVAILABLE, downloadAvailable);
}
function registerDuplicateClientError() {
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SERVER_DUPLICATE_CLIENT_ERROR, duplicateClientError);
}
/**
* Generic error handler for Ajax calls.
*/
function ajaxError(jqXHR, textStatus, errorMessage) {
if (jqXHR.status === 404) {
logger.error("Unexpected ajax error: " + textStatus + ", msg:" + errorMessage);
app.notify({title: "Oops!", text: "What you were looking for is gone now."});
}
else if (jqXHR.status === 422) {
logger.error("Unexpected ajax error: " + textStatus + ", msg: " + errorMessage + ", response: " + jqXHR.responseText);
// present a nicer message
try {
var text = "
";
var errorResponse = JSON.parse(jqXHR.responseText)["errors"];
for (var key in errorResponse) {
var errorsForKey = errorResponse[key];
logger.debug("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, beforeDisconnect, 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);
logger.debug("Unprocessable entity sent from server:", errors)
this.notify({title: title, text: $errors, icon_url: "/assets/content/icon_alert_big.png"})
}
else if(jqXHR.status == 403) {
logger.debug("permission error sent from server:", jqXHR.responseText)
this.notify({title: 'Permission Error', text: 'You do not have permission to access this information', 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);
}
logger.debug("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) {
logger.debug("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.updateUserModel = function(userUpdateData) {
var update = rest.updateUser(userUpdateData)
update.done(function() {
logger.debug("updating user info")
userDeferred = update; // update the global user object if this succeeded
})
return update;
}
// call .done/.fail on this to wait for safe user data
this.user = function() {
return userDeferred;
}
this.activeElementEvent = function(evtName, data) {
return this.layout.activeElementEvent(evtName, data);
}
this.updateNotificationSeen = function(notificationId, notificationCreatedAt) {
context.JK.JamServer.updateNotificationSeen(notificationId, notificationCreatedAt);
}
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();
userDeferred = rest.getUserDetail();
if (opts.inClient) {
registerBadStateRecovered();
registerBadStateError();
registerDownloadAvailable();
registerDuplicateClientError();
context.JK.FaderHelpers.initialize();
context.window.onunload = this.unloadFunction;
userDeferred.fail(function(jqXHR) {
app.notify({title: "Unable to Load User", text: "You should reload the page."})
});
}
$(document).triggerHandler('JAMKAZAM_READY', {app:app})
};
// 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;
$(document).triggerHandler('JAMKAZAM_CONSTRUCTED', {app:this})
return this;
};
})(window, jQuery);