/** * 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_guitar", "banjo":"banjo", "bass guitar":"bass_guitar", "cello":"cello", "clarinet":"clarinet", "computer":"computer", "default":"default", "drums":"drums", "electric guitar":"electric_guitar", "euphonium":"euphonium", "flute":"flute", "french horn":"french_horn", "harmonica":"harmonica", "keyboard":"keyboard", "piano":"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); } context.JK.bindHoverEvents = function() { function showBubble(bubble, $hoverElement) { $hoverElement.attr("bubble-id", bubble.id); bubble.showBubble(); } function hideBubble($hoverElement) { var bubbleSelector = $hoverElement.attr("bubble-id"); $(bubbleSelector).hover( function() { // do nothing when entering the bubble (this should never happen) }, function() { $(this).fadeOut(500); } ); } // MUSICIAN $("[hoveraction='musician']").hoverIntent({ over: function() { var bubble = new JK.MusicianHoverBubble($(this).attr('user-id'), $(this).offset()); showBubble(bubble, $(this)); }, out: function() { // this registers for leaving the hoverable element hideBubble($(this)); }, sensitivity: 1 }); // FAN $("[hoveraction='fan']").hoverIntent({ over: function() { var bubble = new JK.FanHoverBubble($(this).attr('user-id'), $(this).offset()); showBubble(bubble, $(this)); }, out: function() { // this registers for leaving the hoverable element hideBubble($(this)); }, sensitivity: 1 }); // BAND $("[hoveraction='band']").hoverIntent({ over: function() { var bubble = new JK.BandHoverBubble($(this).attr('band-id'), $(this).offset()); showBubble(bubble, $(this)); }, out: function() { // this registers for leaving the hoverable element hideBubble($(this)); }, sensitivity: 1 }); // SESSION $("[hoveraction='session']").hoverIntent({ over: function() { var bubble = new JK.SessionHoverBubble($(this).attr('session-id'), $(this).offset()); showBubble(bubble, $(this)); }, out: function() { // this registers for leaving the hoverable element hideBubble($(this)); }, sensitivity: 1 }); // RECORDING $("[hoveraction='recording']").hoverIntent({ over: function() { var bubble = new JK.RecordingHoverBubble($(this).attr('recording-id'), $(this).offset()); showBubble(bubble, $(this)); }, out: function() { // this registers for leaving the hoverable element hideBubble($(this)); }, sensitivity: 1 }); } context.JK.fetchUserNetworkOrServerFailure = function() { 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) { app.notify({ title: type + " Deleted", text: "The " + type + " no longer exists.", icon_url: "/assets/content/icon_alert_big.png" }); } // 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 context.JK.padString(date.getMonth()+1, 2) + "/" + context.JK.padString(date.getDate(), 2) + "/" + date.getFullYear() + " - " + 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) { 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 }); }; 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 = $('