/** * Common utility functions. */ (function (context, $) { "use strict"; context.JK = context.JK || {}; var logger = context.JK.logger; var AUDIO_DEVICE_BEHAVIOR = context.JK.AUDIO_DEVICE_BEHAVIOR; var ALERT_NAMES = context.JK.ALERT_NAMES; var ALERT_TYPES = context.JK.ALERT_TYPES; var EVENTS = context.JK.EVENTS; var days = new Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); var months = new Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"); var os = null; var reactHovers = [] context.JK.getGenreList = function() { return context.JK.Rest().getGenres(); } context.JK.stringToBool = function (s) { switch (s.toLowerCase()) { case "true": case "yes": case "1": return true; case "false": case "no": case "0": case null: return false; default: return Boolean(s); } }; // http://stackoverflow.com/a/8809472/834644 context.JK.generateUUID = function () { var d = new Date().getTime(); var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16); }); return uuid; }; // Build up two maps of images, for each instrument id. // This map is a simple base map of instrument id to the basic image name. // Below, a loop goes through this and builds two size-specific maps. // In the future, we should test Whether having a single larger image // available, and allowing the browser to resize offers better quality. var icon_map_base = { "accordion": "accordion", "acoustic guitar": "acoustic_guitar", "banjo": "banjo", "bass guitar": "bass_guitar", "cello": "cello", "clarinet": "clarinet", "computer": "computer", "_default": "default", "drums": "drums", "percussion": "drums", "electric guitar": "electric_guitar", "euphonium": "euphonium", "flute": "flute", "french horn": "french_horn", "harmonica": "harmonica", "keyboard": "keyboard", "mandolin": "mandolin", "oboe": "oboe", "other": "other", "piano": "piano", "saxophone": "saxophone", "trombone": "trombone", "trumpet": "trumpet", "tuba": "tuba", "ukulele": "ukulele", "upright bass": "upright_bass", "double bass": "double_bass", "viola": "viola", "violin": "violin", "voice": "voice" }; var instrumentIconMap24 = {}; var instrumentIconMap45 = {}; var instrumentIconMap256 = {}; var notSpecifiedText = "Not specified"; $.each(icon_map_base, function (instrumentId, icon) { instrumentIconMap24[instrumentId] = {asset: "/assets/content/icon_instrument_" + icon + "24.png", name: instrumentId}; instrumentIconMap45[instrumentId] = {asset: "/assets/content/icon_instrument_" + icon + "45.png", name: instrumentId}; instrumentIconMap256[instrumentId] = {asset: "/assets/content/icon_instrument_" + icon + "256.png", name: instrumentId}; }); context.JK.helpBubbleFunctionHelper = function(originalFunc) { var helpText = originalFunc.apply(this); var holder = $('
'); holder.append(helpText); return holder; } /** same as helpBubble(), but doesnt' go away until the user clicks somewhere */ context.JK.helpBubblePersist = function ($element, templateName, data, options) { if(!options) options = {}; options['trigger'] = 'none'; options['clickAnywhereToClose'] = true options['closeWhenOthersOpen']= true options['persist'] = true context.JK.helpBubble($element, templateName, data, options) } /** shows up once. Up to you to call btOff() when appropriate */ context.JK.onceBubble = function ($element, templateName, data, options) { if(!options) options = {}; options['trigger'] = 'none'; options['clickAnywhereToClose'] = false options['closeWhenOthersOpen']= false context.JK.helpBubble($element, templateName, data, options) $element.btOn() } /** * Associates a help bubble on hover (by default) with the specified $element, using jquery.bt.js (BeautyTips) * @param $element The element that should show the help when hovered * @param templateName the name of the help template (without the '#template-help' prefix). Add to _help.html.slim * @param data (optional) data for your template, if applicable * @param options (optional) You can override the default BeautyTips options: https://github.com/dillon-sellars/BeautyTips */ context.JK.helpBubble = function ($element, templateName, data, options) { if (!data) { data = {}; } if(!options) { options = {}; } var originalPostShow = options.postShow; options.postShow = function(container) { context.JK.popExternalLinks($(container)) if (originalPostShow) { originalPostShow($(container)); } } if(options.persist) { var timeout = null; $element.hoverIntent({ over: function() { $element.btOn(); }, out: function() { }}); } $element.on('remove', function() { $element.btOff(); // if the element goes away for some reason, get rid of the bubble too }) var holder = null; if (context._.isFunction(templateName)) { holder = function wrapper() { return context.JK.helpBubbleFunctionHelper.apply(this, [templateName]); } } else { try { var $template = $('#template-help-' + templateName) if ($template.length == 0) { var helpText = templateName; } else { var helpText = context._.template($template.html(), data, { variable: 'data' }); } } catch(e) { var helpText = templateName; } holder = $('
'); holder = holder.append(helpText).html() } context.JK.hoverBubble($element, holder, options); } /** * Meant to show a little bubble to confirm something happened, in a way less obtrusive than a app.notify * @param $element The element that should show the help when hovered * @param templateName the name of the help template (without the '#template-help' prefix). Add to _help.html.slim * @param data (optional) data for your template, if applicable * @param options (optional) You can override the default BeautyTips options: https://github.com/dillon-sellars/BeautyTips */ context.JK.ackBubble = function($element, templateName, data, options) { if(!options) options = {}; options.spikeGirth = 0; options.spikeLength = 0; options.shrinkToFit = true options.duration = 3000 options.cornerRadius = 5 return context.JK.prodBubble($element, templateName, data, options); } /** * Associates a help bubble immediately with the specified $element, using jquery.bt.js (BeautyTips) * By 'prod' it means to literally prod the user, to make them aware of something important because they did something else * * This will open a bubble immediately and show it for 4 seconds, * if you call it again before the 4 second timer is up, it will renew the 4 second timer. * @param $element The element that should show the help when hovered * @param templateName the name of the help template (without the '#template-help' prefix). Add to _help.html.slim * @param data (optional) data for your template, if applicable * @param options (optional) You can override the default BeautyTips options: https://github.com/dillon-sellars/BeautyTips */ context.JK.prodBubble = function($element, templateName, data, options) { if(!options) options = {}; options['trigger'] = 'none'; options['clickAnywhereToClose'] = false if(!options['duration']) options['duration'] = 6000; var existingTimer = $element.data("prodTimer"); if(existingTimer) { clearTimeout(existingTimer); $element.btOn(); } else { context.JK.helpBubble($element, templateName, data, options); $element.btOn(); } $element.data("prodTimer", setTimeout(function() { $element.data("prodTimer", null); $element.btOff(); }, options['duration'])); $element.on('remove', function() { var timer = $element.data('prodTimer') if(timer) { clearTimeout(timer); $element.data("prodTimer", null); $element.btOff(); } }) return $element; } /** Creates a hover element that does not dissappear when the user mouses over the hover. * * @param $element * @param text * @param options */ context.JK.interactReactBubble = function($element, reactElementName, reactPropsCallback, options) { if(!options) options = {}; context._.each(reactHovers, function(react) { reactHovers.btOff(); }) reactHovers = [] var reactElement = null var reactDomNode = null; function cleanupReact() { if(reactDomNode) { logger.debug() React.unmountComponentAtNode(reactDomNode) } } function waitForBubbleHover($bubble) { $bubble.hoverIntent({ over: function() { if(timeout) { clearTimeout(timeout); timeout = null; } }, out: function() { //$element.btOff(); }}); } var timeout = null; options.postHide = cleanupReact; options.trigger = 'none' options.clickAnywhereToClose = true options.closeWhenOthersOpen = true options.preShow = function(container) { var reactElement = context[reactElementName] if(!reactElementName) { throw "unknown react element" + reactElementName } reactElement= React.createElement(reactElement, reactPropsCallback()); var $container = $(container) reactDomNode = $container.find('.react-holder').get(0) $(reactDomNode).data('bt', $element) React.render(reactElement, reactDomNode) } options.postShow = function(container) { if(timeout) { clearTimeout(timeout); timeout = null; } waitForBubbleHover($(container)) timeout = setTimeout(function() {/**$element.btOff()*/}, 3000) } $element.hoverIntent({ over: function() { $element.btOn(); }, out: function() { }}); options.cssStyles = {} options.padding = 0; var extra = options.extraClasses || '' context.JK.hoverBubble($element, '
', options) return $element; } /** * Associates a bubble on hover (by default) with the specified $element, using jquery.bt.js (BeautyTips) * @param $element The element that should show the bubble when hovered * @param text the text or jquery element that should be shown as contents of the bubble * @param options (optional) You can override the default BeautyTips options: https://github.com/dillon-sellars/BeautyTips */ context.JK.hoverBubble = function ($element, text, options) { if (!text) { logger.error("hoverBubble: no text to attach to $element %o", $element); return; } $element.off('remove').on('remove', function() { $element.btOff(); }) if ($element instanceof jQuery) { if ($element.length == 0) { logger.error("hoverBubble: no element specified with text %o", text); return; } } var defaultOpts = { fill: '#333', strokeStyle: '#ED3618', spikeLength: 10, spikeGirth: 10, padding: 8, cornerRadius: 0, cssStyles: { fontSize: '11px', color: '#cccccc', whiteSpace: 'normal' } }; if (options) { options = $.extend(false, defaultOpts, options); } else { options = defaultOpts; } if(typeof text != 'string') { options.contentSelector = text; $element.bt(options); } else { $element.bt(text, options); } } context.JK.groupIdDisplay = function(mixer) { if(mixer && mixer.group_id) { return context.JK.ChannelGroupLookup[mixer.group_id] } else { return "?group?" } } context.JK.bindProfileClickEvents = function($parent, dialogsToClose) { if (!$parent) { $parent = $('body'); } function closeDialogs() { if (dialogsToClose) { $.each(dialogsToClose, function(index, val) { JK.app.layout.closeDialog(val); }); } } $("[profileaction='band']", $parent).unbind('click'); $("[profileaction='band']", $parent).click(function(evt) { closeDialogs(); window.location = "/client#/bandProfile/" + $(this).attr('band-id'); }); $("[profileaction='musician']", $parent).unbind('click'); $("[profileaction='musician']", $parent).click(function(evt) { closeDialogs(); window.location = "/client#/profile/" + $(this).attr('user-id'); }); } context.JK.bindInstrumentHover = function($parent, options) { context._.each($parent.find("[hoveraction='instrument']"), function(element) { var $element = $(element); var instrumentId = $element.attr('data-instrument-id'); if(instrumentId) { if (instrumentId === "null") { instrumentId = "not specified"; } context.JK.hoverBubble($element, instrumentId, options); } else { logger.warn("no instrument-id on hoveraction=instrument element") } }); } context.JK.calculateHoverPosition = function(mouseX, mouseY, hoverWidth, hoverHeight, $hoverElement) { var mouseLeft = mouseX < (window.innerWidth / 2); var mouseTop = mouseY < (window.innerHeight / 2); var hoverElementX = $hoverElement.offset().left; var hoverElementY = $hoverElement.offset().top; var hoverElementWidth = $hoverElement.width(); var hoverElementHeight = $hoverElement.height(); var css = {}; if (mouseLeft) { css.left = hoverElementX + hoverElementWidth + 5 + 'px'; } else { css.left = hoverElementX - hoverWidth - 5 + 'px'; } if (mouseTop) { css.top = hoverElementY + 'px'; } else { css.top = hoverElementY - hoverHeight + 'px'; } return css; } context.JK.bindHoverEvents = function ($parent, prefixData) { var timeout = 300; var fadeoutValue = 100; var sensitivity = 3; var interval = 500; var hoveractionAttr; var userIdAttr; var bandIdAttr; var recordingIdAttr; var sessionIdAttr; if(prefixData) { hoveractionAttr = 'data-hoveraction' userIdAttr = 'data-user-id' bandIdAttr = 'data-band-id' recordingIdAttr = 'data-recording-id' sessionIdAttr = 'data-session-id' } else { hoveractionAttr = 'hoveraction' userIdAttr = 'user-id' bandIdAttr = 'band-id' recordingIdAttr = 'recording-id' sessionIdAttr = 'session-id' } if (!$parent) { $parent = $('body'); } function showBubble(bubble, $hoverElement) { $hoverElement.attr("bubble-id", bubble.id); bubble.showBubble($hoverElement) .done(function() { $(bubble.id()).hover( function () { $(this).data('hovering', true) // do nothing when entering the bubble }, function () { $(this).data('hovering', false).fadeOut(100); } ); }) } function hideBubble($hoverElement) { var bubbleSelector = $hoverElement.attr("bubble-id"); // first check to see if the user isn't hovering over the hover bubble if (!$(bubbleSelector).data('hovering')) { $(bubbleSelector).fadeOut(fadeoutValue); } } // MUSICIAN $("[" + hoveractionAttr + "='musician']", $parent).hoverIntent({ over: function(e) { var bubble = new JK.MusicianHoverBubble($(this).attr(userIdAttr), e.pageX, e.pageY); showBubble(bubble, $(this)); }, out: function () { // this registers for leaving the hoverable element hideBubble($(this)); }, sensitivity: sensitivity, interval: interval, timeout: timeout }); // FAN $("[" + hoveractionAttr + "='fan']", $parent).hoverIntent({ over: function(e) { var bubble = new JK.FanHoverBubble($(this).attr(userIdAttr), e.pageX, e.pageY); showBubble(bubble, $(this)); }, out: function () { // this registers for leaving the hoverable element hideBubble($(this)); }, sensitivity: sensitivity, interval: interval, timeout: timeout }); // BAND $("[" + hoveractionAttr +"='band']", $parent).hoverIntent({ over: function(e) { var bubble = new JK.BandHoverBubble($(this).attr(bandIdAttr), e.pageX, e.pageY); showBubble(bubble, $(this)); }, out: function () { // this registers for leaving the hoverable element hideBubble($(this)); }, sensitivity: sensitivity, interval: interval, timeout: timeout }); // SESSION $("[" + hoveractionAttr + "='session']", $parent).hoverIntent({ over: function(e) { var bubble = new JK.SessionHoverBubble($(this).attr(sessionIdAttr), e.pageX, e.pageY); showBubble(bubble, $(this)); }, out: function () { // this registers for leaving the hoverable element hideBubble($(this)); }, sensitivity: sensitivity, interval: interval, timeout: timeout }); // RECORDING $("[" + hoveractionAttr + "='recording']", $parent).hoverIntent({ over: function(e) { var bubble = new JK.RecordingHoverBubble($(this).attr(recordIdAttr), e.pageX, e.pageY); showBubble(bubble, $(this)); }, out: function () { // this registers for leaving the hoverable element hideBubble($(this)); }, sensitivity: sensitivity, interval: interval, timeout: timeout }); } context.JK.fetchUserNetworkOrServerFailure = function () { JK.app.notify({ title: "Unable to communicate with server", text: "Please try again later", icon_url: "/assets/content/icon_alert_big.png" }); } context.JK.entityNotFound = function (type) { JK.app.notify({ title: type + " Deleted", text: "The " + type + " no longer exists.", icon_url: "/assets/content/icon_alert_big.png" }); } // http://stackoverflow.com/questions/4878756/javascript-how-to-capitalize-first-letter-of-each-word-like-a-2-word-city context.JK.toTitleCase = function(str) { return str.replace(/\w\S*/g, function(txt){ return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); } // Uber-simple templating // var template = "Hey {name}"; // var vals = { name: "Jon" }; // _fillTemplate(template, vals); // --> "Hey Jon" // // use context._.template for something more powerful context.JK.fillTemplate = function (template, vals) { if (template == null) throw 'null template in fillTemplate' for (var val in vals) template = template.replace(new RegExp('{' + val + '}', 'g'), vals[val]); return template; }; context.JK.resolveAvatarUrl = function (photo_url) { return photo_url ? photo_url.replace('https://', 'http://') : "/assets/shared/avatar_generic.png"; }; context.JK.resolveBandAvatarUrl = function (photo_url) { return photo_url ? photo_url.replace('https://', 'http://') : "/assets/shared/avatar_generic_band.png"; }; context.JK.getInstrumentIconMap24 = function () { return instrumentIconMap24; }; context.JK.getInstrumentIconMap45 = function () { return instrumentIconMap45; }; context.JK.getInstrumentIconMap256 = function () { return instrumentIconMap256; }; context.JK.getInstrumentIcon24 = function (instrument) { if (instrument in instrumentIconMap24) { return instrumentIconMap24[instrument].asset; } return instrumentIconMap24["_default"].asset; }; context.JK.getInstrumentIcon45 = function (instrument) { if (instrument in instrumentIconMap45) { return instrumentIconMap45[instrument].asset; } return instrumentIconMap45["_default"].asset; }; context.JK.getInstrumentIcon256 = function (instrument) { if (instrument in instrumentIconMap256) { return instrumentIconMap256[instrument].asset; } return instrumentIconMap256["_default"].asset; }; context.JK.getInstrumentId = function(instrumentId) { return instrumentId ? instrumentId : notSpecifiedText; } // meant to pass in a bunch of images with an instrument-id attribute on them. context.JK.setInstrumentAssetPath = function ($elements) { $.each($elements, function (index, item) { var $element = $(this); if (!$element.is('img')) { throw "expected to receive an in setInstrumentAssetPath" } var instrument = $element.attr('instrument-id'); $element.attr('src', context.JK.getInstrumentIcon24(instrument)) }) } // creates an array with entries like [{ id: "drums", description: "Drums"}, ] context.JK.listInstruments = function () { var instrumentArray = []; $.each(context.JK.server_to_client_instrument_map, function (key, val) { instrumentArray.push({"id": context.JK.server_to_client_instrument_map[key].client_id, "description": key}); }); return instrumentArray; } context.JK.convertClientInstrumentToServer = function(clientId) { var serverInstrument = context.JK.client_to_server_instrument_map[clientId] if (!serverInstrument) return 'other' else return serverInstrument.server_id } context.JK.showErrorDialog = function (app, msg, title) { app.layout.showDialog('error-dialog'); $('#error-msg', 'div[layout-id="error-dialog"]').html(msg); $('#error-summary', 'div[layout-id="error-dialog"]').html(title); } // TODO: figure out how to handle this in layout.js for layered popups context.JK.showOverlay = function () { $('.dialog-overlay').show(); } context.JK.hideOverlay = function () { $('.dialog-overlay').hide(); } // usage: context.JK.onBackendEvent(ALERT_NAMES.SOME_EVENT) context.JK.onBackendEvent = function(type, namespace, callback) { var alertData = ALERT_TYPES[type]; if(!alertData) {throw "onBackendEvent: unknown alert type " + type}; logger.debug("onBackendEvent: " + alertData.name + '.' + namespace) $(document).on(alertData.name + '.' + namespace, callback); } context.JK.offBackendEvent = function(type, namespace, callback) { var alertData = ALERT_TYPES[type]; if(!alertData) {throw "offBackendEvent: unknown alert type " + type}; logger.debug("offBackendEvent: " + alertData.name + '.' + namespace, alertData) $(document).off(alertData.name + '.' + namespace); } /* * Loads a listbox or dropdown with the values in input_array, setting the option value * to the id_field and the option text to text_field. It will preselect the option with * value equal to selected_id. */ context.JK.loadOptions = function (templateHtml, listbox_id, input_array, id_field, text_field, selected_id) { $.each(input_array, function (index, val) { var isSelected = ""; if (val[id_field] === selected_id) { isSelected = "selected"; } var html = context.JK.fillTemplate(templateHtml, { value: val[id_field], label: val[text_field], selected: isSelected }); listbox_id.append(html); }); } context.JK.trimString = function (str) { return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); }; context.JK.padString = function (str, max) { var retVal = '' + str; while (retVal.length < max) { retVal = '0' + retVal; } return retVal; } context.JK.formatDateTime = function (dateString) { var date = dateString instanceof Date ? dateString : new Date(dateString); return context.JK.padString(date.getMonth() + 1, 2) + "/" + context.JK.padString(date.getDate(), 2) + "/" + date.getFullYear() + " - " + date.toLocaleTimeString(); } context.JK.formatDateShort = function (dateString) { var date = dateString instanceof Date ? dateString : new Date(dateString); return months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear(); } // returns Fri May 20, 2013 context.JK.formatDate = function (dateString, suppressDay) { if (!dateString) { return 'N/A' } var date = new Date(dateString); return (suppressDay ? '' : (days[date.getDay()] + ' ')) + months[date.getMonth()] + ' ' + context.JK.padString(date.getDate(), 2) + ', ' + date.getFullYear(); } // returns June for months 0-11 context.JK.getMonth = function(monthNumber) { return months[monthNumber]; } context.JK.formatDateYYYYMMDD = function(dateString) { var date = dateString instanceof Date ? dateString : new Date(dateString); return date.getFullYear() + '-' + context.JK.padString((date.getMonth() + 1).toString(), 2) + '-' + context.JK.padString(date.getDate(), 2); } context.JK.formatTime = function (dateString) { var date = new Date(dateString); return date.toLocaleTimeString(); } context.JK.iconMapBase = function() { return icon_map_base } context.JK.formatUtcTime = function(date, change) { if (change) { date.setMinutes(Math.ceil(date.getMinutes() / 30) * 30); } var h12h = date.getHours(); var m12h = date.getMinutes(); var ampm; if (h12h >= 0 && h12h < 12) { if (h12h === 0) { h12h = 12; // 0 becomes 12 } ampm = "AM"; } else { if (h12h > 12) { h12h -= 12; // 13-23 becomes 1-11 } ampm = "PM"; } var timeString = ("00" + h12h).slice(-2) + ":" + ("00" + m12h).slice(-2) + " " + ampm; return timeString; } context.JK.prettyPrintElements = function ($elements) { $.each($elements, function (index, item) { var $item = $(item); $item.text(context.JK.prettyPrintSeconds(parseInt($item.attr('duration')))) }); } context.JK.prettyPrintSeconds = function (seconds) { // from: http://stackoverflow.com/questions/3733227/javascript-seconds-to-minutes-and-seconds // Minutes and seconds var mins = ~~(seconds / 60); var secs = seconds % 60; // Hours, minutes and seconds var hrs = ~~(seconds / 3600); var mins = ~~((seconds % 3600) / 60); var secs = seconds % 60; // Output like "1:01" or "4:03:59" or "123:03:59" var ret = ""; if (hrs > 0) ret += "" + hrs + ":" + (mins < 10 ? "0" : ""); ret += "" + mins + ":" + (secs < 10 ? "0" : ""); ret += "" + secs; return ret; } context.JK.alertSupportedNeeded = function(additionalContext) { var $item = context.JK.Banner.showAlert(additionalContext + '

Please contact support.'); context.JK.popExternalLinks($item); return $item; } // returns: // * Win32 // * MacOSX // * Unix context.JK.GetOSAsString = function() { if(!os) { if(context.jamClient && context.jamClient.IsNativeClient()) { os = context.jamClient.GetOSAsString(); } else { os = context.JK.detectOS(); } } return os; } context.JK.search = function (query, app, callback) { //logger.debug("search: "+ query) $.ajax({ type: "GET", dataType: "json", contentType: 'application/json', url: "/api/search?query=" + query, processData: false, success: function (response) { callback(response); }, error: app.ajaxError }); }; /* * Get the length of a dictionary */ context.JK.dlen = function (d) { var count = 0; for (var i in d) { if (d.hasOwnProperty(i)) { count++; } } return count; }; /* * Get the keys of a dictionary as an array (same as Object.keys, but works in all browsers) */ context.JK.dkeys = function (d) { var keys = [] for (var i in d) { if (d.hasOwnProperty(i)) { keys.push(i); } } return keys; }; /** * Finds the first error associated with the field. * @param fieldName the name of the field * @param errors_data response from a 422 response in ajax * @returns null if no error for the field name, or the 1st error associated with that field */ context.JK.get_first_error = function (fieldName, errors_data) { var errors = errors_data["errors"]; if (errors == null) return null; if (errors[fieldName] && errors[fieldName].length > 0) { return errors[fieldName][0] } else { return null; } } context.JK.getFullFirstError = function(fieldName, errors, field_mapper) { if (errors == null) return null; if (errors[fieldName] && errors[fieldName].length > 0) { var displayField = fieldName if (field_mapper) { displayField = field_mapper[fieldName] if (!displayField) { // in case mapper has no data for this field displayField = fieldName; } } return displayField + ' ' + errors[fieldName][0] } else { return null; } } /** * Returns a ul with an li per field name. * @param fieldName the name of the field * @param errors_data error data return by a 422 ajax response * @returns null if no error for the field name; otherwise a ul/li */ context.JK.format_errors = function (fieldName, errors_data) { var errors = errors_data["errors"]; if (errors == null) return null; var field_errors = errors[fieldName]; if (field_errors == null) { return null; } var ul = $(''); $.each(field_errors, function (index, item) { ul.append(context.JK.fillTemplate("
  • {message}
  • ", {message: item})) }) return ul; } context.JK.reset_errors = function($container) { $container.find('.error-text').remove() $container.find('.error').removeClass("error") } context.JK.append_errors = function($field, fieldName, errors_data) { var $ul = context.JK.format_errors(fieldName, errors_data); if($ul != null) { delete errors_data['errors'][fieldName]; $field.closest('div.field').addClass('error').end().after($ul); } } context.JK.format_all_errors = function (errors_data) { var errors = errors_data["errors"]; if (errors == null) return $(''); var ul = $(''); $.each(errors, function (fieldName, field_errors) { $.each(field_errors, function (index, item) { ul.append(context.JK.fillTemplate("
  • {field} {message}
  • ", {field: fieldName, message: item})) }); }); return ul; } context.JK.reactErrors = function (errors_data, fieldMapper) { var errors = errors_data && errors_data["errors"]; if (errors == null) return null; var items = [] $.each(errors, function (fieldName, field_errors) { var displayName = fieldMapper && fieldMapper[fieldName] if (!displayName) { displayName = fieldName; } $.each(field_errors, function (index, item) { items.push(React.DOM.li({key: fieldName + item}, displayName + ' ' + item)) }); }); return React.DOM.ul({className: 'error-text'}, null, items) } context.JK.reactSingleFieldErrors = function(fieldName, error_data) { if (!error_data) { return null; } if (!_.isArray(error_data)) { if (error_data.errors) { error_data = error_data.errors } error_data = error_data[fieldName] if (!error_data) { return null; } } var items = [] $.each(error_data, function (i, error) { items.push(React.DOM.li({key: error}, error)) }); return React.DOM.ul({className: 'error-text'}, null, items) } /** * Way to verify that a number of parallel tasks have all completed. * Provide a function to evaluate completion, and a callback to * invoke when that function evaluates to true. * NOTE: this does not pause execution, it simply ensures that * when the test function evaluates to true, the callback will * be invoked. */ context.JK.joinCalls = function (completionTestFunction, callback, interval) { function doneYet() { if (completionTestFunction()) { callback(); } else { context.setTimeout(doneYet, interval); } } doneYet(); }; context.JK.initJamClient = function(app) { // If no jamClient (when not running in native client) // create a fake one. if (!(window.jamClient)) { var p2pMessageFactory = new JK.FakeJamClientMessages(); window.jamClient = new JK.FakeJamClient(app, p2pMessageFactory); window.jamClient.SetFakeRecordingImpl(new JK.FakeJamClientRecordings(app, jamClient, p2pMessageFactory)); } else if(false) { // set to true to time long running bridge calls var originalJamClient = window.jamClient; var interceptedJamClient = {}; $.each(Object.keys(originalJamClient), function(i, key) { if(key.indexOf('(') > -1) { // this is a method. time it var jsKey = key.substring(0, key.indexOf('(')) console.log("replacing " + jsKey) interceptedJamClient[jsKey] = function() { var original = originalJamClient[key] var start = new Date(); if(key == "FTUEGetDevices()") { var returnVal = eval('originalJamClient.FTUEGetDevices(' + arguments[0] + ')'); } else { var returnVal = original.apply(originalJamClient, arguments); } var time = new Date().getTime() - start.getTime(); if(time >= 0) { // if 0, you'll see ALL bridge calls. If you set it to a higher value, you'll only see calls that are beyond that threshold console.error(time + "ms jamClient." + jsKey + ' returns=', returnVal); } return returnVal; } } else { // we need to intercept properties... but how? } }); window.jamClient = interceptedJamClient; } } // pass in 'arguments' in a fail callback of a $.ajax context.JK.isNetworkError = function(failArgs) { if(failArgs.length != 3) throw "expected 3 arguments from .fail of $ajax in isNetworkError" var xhr = failArgs[0]; return xhr.status == 0; } context.JK.clientType = function () { if (context.jamClient) { return context.jamClient.IsNativeClient() ? 'client' : 'browser'; } return 'browser'; } /** * Returns 'MacOSX' if the os appears to be macintosh, * 'Win32' if the os appears to be windows, * 'Unix' if the OS appears to be linux, * and null if unknown * @returns {*} */ context.JK.detectOS = function () { if (!navigator.platform) { return null; } if (navigator.platform.toLowerCase().indexOf('mac') !== -1) { return "MacOSX"; } if (navigator.platform.toLowerCase().indexOf('win') !== -1) { return "Win32" } if (navigator.platform.toLowerCase().indexOf('linux') !== -1) { return "Unix" } return null; } context.JK.makeAbsolute = function(path) { return window.location.protocol + '//' + window.location.host + path; } context.JK.popExternalLinks = function ($parent) { function popOpenBrowser (evt) { if (!context.jamClient) { return; } evt.preventDefault(); var href = $(this).attr("href"); if (href) { // make absolute if not already if (href.indexOf('http') != 0 && href.indexOf('mailto') != 0) { href = window.location.protocol + '//' + window.location.host + href; } context.jamClient.OpenSystemBrowser(href); } return false; } if(!$parent) $parent = $('body'); // Allow any a link with a rel="external" attribute to launch // the link in the default browser, using jamClient: var $links = $parent.find('a[rel="external"]') $links.off('click'); $links.on('click', popOpenBrowser); } context.JK.popExternalLink = function (href, login) { if (!context.jamClient) { return; } if (href) { // make absolute if not already if (href.indexOf('http') != 0 && href.indexOf('mailto') != 0) { href = window.location.protocol + '//' + window.location.host + href; } if(login) { var rememberToken = $.cookie("remember_token"); if(rememberToken) { href = window.location.protocol + '//' + window.location.host + "/passthrough?redirect-to=" + encodeURIComponent(href) + '&stoken=' + encodeURIComponent(rememberToken) } } context.jamClient.OpenSystemBrowser(href); } return false; } context.JK.popExternalLinkAndLogin = function(href) { var rememberToken = $.cookie("remember_token"); context.JK.popExternalLink("https://jamkazam.freshdesk.com/support/solutions/articles/66000122535-what-are-jamkazam-s-free-vs-premium-features-") if(rememberToken) { "https://" } } context.JK.checkbox = function ($checkbox, dark) { if (dark){ return $checkbox.iCheck({ checkboxClass: 'icheckbox_minimal dark', radioClass: 'iradio_minimal dark', inheritClass: true }); }else { return $checkbox.iCheck({ checkboxClass: 'icheckbox_minimal', radioClass: 'iradio_minimal', inheritClass: true }); } } context.JK.dropdown = function ($select, options) { var opts = options || {} opts = $.extend({}, {nativeTouch: !(context.jamClient && context.jamClient.IsNativeClient()) && gon.global.env != "test", cutOff: 7}, opts) $select.each(function (index) { var $item = $(this); if ($item.data('easydropdown-select')) { // if this has already been initialized, re-init it so it picks up any new $item.easyDropDown('destroy') } $item.easyDropDown(opts); }) } context.JK.currentTimezone = function() { var tz = window.jstz.determine().name() if (tz == 'America/Chicago') { tz = 'US Central Time' } else if(tz == 'America/Los_Angeles' || tz == 'America/Los Angeles') { tz = 'US Pacific Time' } else if(tz == 'America/New_York' || tz == 'America/New York') { tz = 'US Eastern Time' } else if (tz == 'America/Arizona') { tz = 'US Mountain Time' } return tz; } context.JK.flash = function(msg, options) { options = options || {} var $flash = $(context._.template($('#template-flash-notice').html(), {}, { variable: 'data' })); $flash.find('.flash-content').html(msg); $('body').prepend($flash) if(options.hide) { // slide down (take 1 sec to do it), sit for 5, then leave over 1 second setTimeout(function() {$flash.slideUp(1000, 'swing') }, options.hide * 1000) } } context.JK.hasFlash = function () { var hasFlash = false; if (!context.jamClient || !context.jamClient.IsNativeClient()) { try { var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); if (fo) hasFlash = true; } catch (e) { if (navigator.mimeTypes ["application/x-shockwave-flash"] != undefined) hasFlash = true; } } return hasFlash; } context.JK.getNameOfFile = function(filename) { var index = filename.lastIndexOf('/'); if(index == -1) { index = filename.lastIndexOf('\\'); } return index == -1 ? filename : filename.substring(index + 1, filename.length) } context.JK.hasOneConfiguredDevice = function () { var result = context.jamClient.FTUEGetGoodConfigurationList(); logger.debug("hasOneConfiguredDevice: ", result); return result.length > 0; }; context.JK.getGoodAudioConfigs = function () { var result = context.jamClient.FTUEGetGoodAudioConfigurations(); logger.debug("goodAudioConfigs=%o", result); return result; }; context.JK.getGoodConfigMap = function () { var goodConfigMap = []; var goodConfigs = context.JK.getGoodAudioConfigs(); $.each(goodConfigs, function (index, profileKey) { var friendlyName = context.jamClient.FTUEGetConfigurationDevice(profileKey); goodConfigMap.push({key: profileKey, name: friendlyName}); }); return goodConfigMap; } context.JK.getBadAudioConfigs = function () { var badAudioConfigs = []; var allAudioConfigs = context.jamClient.FTUEGetAllAudioConfigurations(); var goodAudioConfigs = context.JK.getGoodAudioConfigs(); for (var i = 0; i < allAudioConfigs.length; i++) { if ($.inArray(allAudioConfigs[i], goodAudioConfigs) === -1) { badAudioConfigs.push(allAudioConfigs[i]); } } logger.debug("badAudioConfigs=%o", badAudioConfigs); return badAudioConfigs; }; context.JK.getBadConfigMap = function () { var badConfigMap = []; var badConfigs = context.JK.getBadAudioConfigs(); $.each(badConfigs, function (index, profileKey) { var friendlyName = context.jamClient.FTUEGetConfigurationDevice(profileKey); badConfigMap.push({key: profileKey, name: friendlyName}); }); return badConfigMap; } context.JK.getFirstGoodDevice = function (preferredDeviceId) { var badConfigs = context.JK.getBadAudioConfigs(); function getGoodDevice() { var goodConfigs = context.JK.getGoodAudioConfigs(); if (goodConfigs && goodConfigs.length > 0) { deviceId = goodConfigs[0]; } else { deviceId = null; } return deviceId; } var deviceId = null; if (preferredDeviceId) { // if the preferred device is not in the list of invalid configs, use it if ($.inArray(preferredDeviceId, badConfigs) === -1) { deviceId = preferredDeviceId; } else { deviceId = getGoodDevice(); } } else { deviceId = getGoodDevice(); } return deviceId; } // returns /client#/home for http://www.jamkazam.com/client#/home context.JK.locationPath = function () { var bits = context.location.href.split('/'); return '/' + bits.slice(3).join('/'); } context.JK.nowUTC = function () { var d = new Date(); return new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds()); } context.JK.guardAgainstBrowser = function(app, args) { if(!gon.isNativeClient) { logger.debug("guarding against normal browser on screen that requires native client") app.layout.showDialog('launch-app-dialog', args) .one(EVENTS.DIALOG_CLOSED, function() { if(args && args.goHome) { window.location = '/client#/home'; } }) return false; } return true; } context.JK.createSession = function(app, data) { // auto pick an 'other' instrument var otherId = context.JK.server_to_client_instrument_map.Other.server_id; // get server ID var otherInstrumentInfo = context.JK.instrument_id_to_instrument[otherId]; // get display name var beginnerLevel = 1; // default to beginner var instruments = [ {id: otherId, name: otherInstrumentInfo.display, level: beginnerLevel} ]; $.each(instruments, function(index, instrument) { var slot = {}; slot.instrument_id = instrument.id; slot.proficiency_level = instrument.level; slot.approve = true; data.rsvp_slots.push(slot); }); data.isUnstructuredRsvp = true; return rest.createScheduledSession(data) } context.JK.privateSessionSettings = function(createSessionSettings) { createSessionSettings.genresValues = ['Pop']; createSessionSettings.genres = ['pop']; createSessionSettings.timezone = 'Central Time (US & Canada),America/Chicago' createSessionSettings.name = "Private Test Session"; createSessionSettings.description = "Private session set up just to test things out in the session interface by myself."; createSessionSettings.notations = []; createSessionSettings.language = 'eng' createSessionSettings.legal_policy = 'Standard'; createSessionSettings.musician_access = false createSessionSettings.fan_access = false createSessionSettings.fan_chat = false createSessionSettings.approval_required = false createSessionSettings.legal_terms = true createSessionSettings.recurring_mode = 'once'; createSessionSettings.start = new Date().toDateString() + ' ' + context.JK.formatUtcTime(new Date(), false); createSessionSettings.duration = "60"; createSessionSettings.open_rsvps = false createSessionSettings.rsvp_slots = []; } /* * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message * Digest Algorithm, as defined in RFC 1321. * Copyright (C) Paul Johnston 1999 - 2000. * Updated by Greg Holt 2000 - 2001. * See http://pajhome.org.uk/site/legal.html for details. */ /* * Convert a 32-bit number to a hex string with ls-byte first */ var hex_chr = "0123456789abcdef"; function rhex(num) { var str, j; str = ""; for (j = 0; j <= 3; j++) { str += hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F) + hex_chr.charAt((num >> (j * 8)) & 0x0F); } return str; } /* * Convert a string to a sequence of 16-word blocks, stored as an array. * Append padding bits and the length, as described in the MD5 standard. */ function str2blks_MD5(str) { var nblk, blks, i; nblk = ((str.length + 8) >> 6) + 1; blks = new Array(nblk * 16); for (i = 0; i < nblk * 16; i++) blks[i] = 0; for (i = 0; i < str.length; i++) blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8); blks[i >> 2] |= 0x80 << ((i % 4) * 8); blks[nblk * 16 - 2] = str.length * 8; return blks; } /* * Add integers, wrapping at 2^32. This uses 16-bit operations internally * to work around bugs in some JS interpreters. */ function add(x, y) { var lsw = (x & 0xFFFF) + (y & 0xFFFF); var msw = (x >> 16) + (y >> 16) + (lsw >> 16); return (msw << 16) | (lsw & 0xFFFF); } /* * Bitwise rotate a 32-bit number to the left */ function rol(num, cnt) { return (num << cnt) | (num >>> (32 - cnt)); } /* * These functions implement the basic operation for each round of the * algorithm. */ function cmn(q, a, b, x, s, t) { return add(rol(add(add(a, q), add(x, t)), s), b); } function ff(a, b, c, d, x, s, t) { return cmn((b & c) | ((~b) & d), a, b, x, s, t); } function gg(a, b, c, d, x, s, t) { return cmn((b & d) | (c & (~d)), a, b, x, s, t); } function hh(a, b, c, d, x, s, t) { return cmn(b ^ c ^ d, a, b, x, s, t); } function ii(a, b, c, d, x, s, t) { return cmn(c ^ (b | (~d)), a, b, x, s, t); } /* * Take a string and return the hex representation of its MD5. */ context.JK.calcMD5 = function (str) { var x, a, b, c, d, i, olda, oldb, oldc, oldd; x = str2blks_MD5(str); a = 1732584193; b = -271733879; c = -1732584194; d = 271733878; for (i = 0; i < x.length; i += 16) { olda = a; oldb = b; oldc = c; oldd = d; a = ff(a, b, c, d, x[i + 0], 7, -680876936); d = ff(d, a, b, c, x[i + 1], 12, -389564586); c = ff(c, d, a, b, x[i + 2], 17, 606105819); b = ff(b, c, d, a, x[i + 3], 22, -1044525330); a = ff(a, b, c, d, x[i + 4], 7, -176418897); d = ff(d, a, b, c, x[i + 5], 12, 1200080426); c = ff(c, d, a, b, x[i + 6], 17, -1473231341); b = ff(b, c, d, a, x[i + 7], 22, -45705983); a = ff(a, b, c, d, x[i + 8], 7, 1770035416); d = ff(d, a, b, c, x[i + 9], 12, -1958414417); c = ff(c, d, a, b, x[i + 10], 17, -42063); b = ff(b, c, d, a, x[i + 11], 22, -1990404162); a = ff(a, b, c, d, x[i + 12], 7, 1804603682); d = ff(d, a, b, c, x[i + 13], 12, -40341101); c = ff(c, d, a, b, x[i + 14], 17, -1502002290); b = ff(b, c, d, a, x[i + 15], 22, 1236535329); a = gg(a, b, c, d, x[i + 1], 5, -165796510); d = gg(d, a, b, c, x[i + 6], 9, -1069501632); c = gg(c, d, a, b, x[i + 11], 14, 643717713); b = gg(b, c, d, a, x[i + 0], 20, -373897302); a = gg(a, b, c, d, x[i + 5], 5, -701558691); d = gg(d, a, b, c, x[i + 10], 9, 38016083); c = gg(c, d, a, b, x[i + 15], 14, -660478335); b = gg(b, c, d, a, x[i + 4], 20, -405537848); a = gg(a, b, c, d, x[i + 9], 5, 568446438); d = gg(d, a, b, c, x[i + 14], 9, -1019803690); c = gg(c, d, a, b, x[i + 3], 14, -187363961); b = gg(b, c, d, a, x[i + 8], 20, 1163531501); a = gg(a, b, c, d, x[i + 13], 5, -1444681467); d = gg(d, a, b, c, x[i + 2], 9, -51403784); c = gg(c, d, a, b, x[i + 7], 14, 1735328473); b = gg(b, c, d, a, x[i + 12], 20, -1926607734); a = hh(a, b, c, d, x[i + 5], 4, -378558); d = hh(d, a, b, c, x[i + 8], 11, -2022574463); c = hh(c, d, a, b, x[i + 11], 16, 1839030562); b = hh(b, c, d, a, x[i + 14], 23, -35309556); a = hh(a, b, c, d, x[i + 1], 4, -1530992060); d = hh(d, a, b, c, x[i + 4], 11, 1272893353); c = hh(c, d, a, b, x[i + 7], 16, -155497632); b = hh(b, c, d, a, x[i + 10], 23, -1094730640); a = hh(a, b, c, d, x[i + 13], 4, 681279174); d = hh(d, a, b, c, x[i + 0], 11, -358537222); c = hh(c, d, a, b, x[i + 3], 16, -722521979); b = hh(b, c, d, a, x[i + 6], 23, 76029189); a = hh(a, b, c, d, x[i + 9], 4, -640364487); d = hh(d, a, b, c, x[i + 12], 11, -421815835); c = hh(c, d, a, b, x[i + 15], 16, 530742520); b = hh(b, c, d, a, x[i + 2], 23, -995338651); a = ii(a, b, c, d, x[i + 0], 6, -198630844); d = ii(d, a, b, c, x[i + 7], 10, 1126891415); c = ii(c, d, a, b, x[i + 14], 15, -1416354905); b = ii(b, c, d, a, x[i + 5], 21, -57434055); a = ii(a, b, c, d, x[i + 12], 6, 1700485571); d = ii(d, a, b, c, x[i + 3], 10, -1894986606); c = ii(c, d, a, b, x[i + 10], 15, -1051523); b = ii(b, c, d, a, x[i + 1], 21, -2054922799); a = ii(a, b, c, d, x[i + 8], 6, 1873313359); d = ii(d, a, b, c, x[i + 15], 10, -30611744); c = ii(c, d, a, b, x[i + 6], 15, -1560198380); b = ii(b, c, d, a, x[i + 13], 21, 1309151649); a = ii(a, b, c, d, x[i + 4], 6, -145523070); d = ii(d, a, b, c, x[i + 11], 10, -1120210379); c = ii(c, d, a, b, x[i + 2], 15, 718787259); b = ii(b, c, d, a, x[i + 9], 21, -343485551); a = add(a, olda); b = add(b, oldb); c = add(c, oldc); d = add(d, oldd); } return rhex(a) + rhex(b) + rhex(c) + rhex(d); }; /** validates that no changes are being made to tracks while recording */ context.JK.verifyNotRecordingForTrackChange = function (app) { if (context.RecordingStore.recordingModel.isRecording()) { app.notify({ title: "Currently Recording", text: "Tracks cannot be modified while recording.", icon_url: "/assets/content/icon_alert_big.png"}); return false; } return true; } })(window, jQuery);