/** * Common utility functions. */ (function(context,$) { "use strict"; context.JK = context.JK || {}; var logger = context.JK.logger; 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"); 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", "banjo":"banjo", "bass guitar":"bass", "cello":"cello", "clarinet":"clarinet", "computer":"computer", "default":"default", "drums":"drums", "electric guitar":"guitar", "euphonium":"euphonium", "flute":"flute", "french horn":"frenchhorn", "harmonica":"harmonica", "keyboard":"keyboard", "mandolin":"mandolin", "oboe":"oboe", "other":"other", "saxophone":"saxophone", "trombone":"trombone", "trumpet":"trumpet", "tuba":"tuba", "ukulele":"ukelele", "viola":"viola", "violin":"violin", "voice":"vocals" }; var instrumentIconMap24 = {}; var instrumentIconMap45 = {}; $.each(context._.keys(icon_map_base), function(index, instrumentId) { var icon = icon_map_base[instrumentId]; instrumentIconMap24[instrumentId] = "../assets/content/icon_instrument_" + icon + "24.png"; instrumentIconMap45[instrumentId] = "../assets/content/icon_instrument_" + icon + "45.png"; }); /** * 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.erb * @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 = {} } var helpText = context._.template($('#template-help-' + templateName).html(), data, { variable: 'data' }); var holder = $('
'); holder.append(helpText); context.JK.hoverBubble($element, helpText, options); } /** * 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; } 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: { fontFamily: 'Raleway, Arial, Helvetica, sans-serif', fontSize: '11px', color:'white', whiteSpace:'normal' } }; if(options) { options = $.extend(false, defaultOpts, options); } else { options = defaultOpts; } $element.bt(text, options); } // Uber-simple templating // var template = "Hey {name}"; // var vals = { name: "Jon" }; // _fillTemplate(template, vals); // --> "Hey Jon" context.JK.fillTemplate = function(template, vals) { 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 : "/assets/shared/avatar_generic.png"; }; context.JK.resolveBandAvatarUrl = function(photo_url) { return photo_url ? photo_url : "/assets/shared/avatar_generic_band.png"; }; context.JK.getInstrumentIconMap24 = function() { return instrumentIconMap24; }; context.JK.getInstrumentIconMap45 = function() { return instrumentIconMap45; }; context.JK.getInstrumentIcon24 = function(instrument) { if (instrument in instrumentIconMap24) { return instrumentIconMap24[instrument]; } return instrumentIconMap24["default"]; }; context.JK.getInstrumentIcon45 = function(instrument) { if (instrument in instrumentIconMap45) { return instrumentIconMap45[instrument]; } return instrumentIconMap45["default"]; }; context.JK.listInstruments = function(app, callback) { var url = "/api/instruments"; $.ajax({ type: "GET", dataType: "json", url: url, success: function(response) { callback(response); }, error: app.ajaxError }); }; 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(); } /* * 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 = new Date(dateString); return date.getFullYear() + "-" + context.JK.padString(date.getMonth()+1, 2) + "-" + context.JK.padString(date.getDate(), 2) + " @ " + date.toLocaleTimeString(); } // returns Fri May 20, 2013 context.JK.formatDate = function(dateString) { var date = new Date(dateString); return days[date.getDay()] + ' ' + months[date.getMonth()] + ' ' + context.JK.padString(date.getDate(), 2) + ', ' + date.getFullYear(); } context.JK.formatTime = function(dateString) { var date = new Date(dateString); return date.toLocaleTimeString(); } 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.search = function(query, app, callback) { $.ajax({ type: "GET", dataType: "json", contentType: 'application/json', url: "/api/search?query=" + query, processData: false, success: function(response) { callback(response); }, error: app.ajaxError }); }; context.JK.sendFriendRequest = function(app, userId, callback) { var url = "/api/users/" + context.JK.currentUserId + "/friend_requests"; $.ajax({ type: "POST", dataType: "json", contentType: 'application/json', url: url, data: '{"friend_id":"' + userId + '"}', processData: false, success: function(response) { callback(userId); context.JK.GA.trackFriendConnect(context.JK.GA.FriendConnectTypes.request); }, 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; } } /** * 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.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; } /** * 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(); }; /** * 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.popExternalLinks = function() { // Allow any a link with a rel="external" attribute to launch // the link in the default browser, using jamClient: $('body').on('click', 'a[rel="external"]', function(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; }); } /* * 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.JK.CurrentSessionModel.recordingModel.isRecording()) { app.notify({ title: "Currently Recording", text: "Tracks can not be modified while recording.", icon_url: "/assets/content/icon_alert_big.png"}); return false; } return true; } })(window,jQuery);