define([ "jquery", "underscore", "storage", "crel", "xregexp", "stacktrace", "FileSaver" ], function($, _, storage, crel, XRegExp, printStackTrace, saveAs) { var utils = {}; // Faster than setTimeout (see http://dbaron.org/log/20100309-faster-timeouts) utils.defer = (function() { var timeouts = []; var messageName = "deferMsg"; window.addEventListener("message", function(evt) { if(evt.source == window && evt.data == messageName) { evt.stopPropagation(); if(timeouts.length > 0) { timeouts.shift()(); } } }, true); return function(fn) { timeouts.push(fn); window.postMessage(messageName, "*"); }; })(); // Implements underscore debounce using our defer function utils.debounce = function(func, context) { var isExpected = false; function later() { isExpected = false; func.call(context); } return function() { if(isExpected === true) { return; } isExpected = true; utils.defer(later); }; }; // Generates a 24 chars length random string (should be enough to prevent collisions) utils.randomString = (function() { var max = Math.pow(36, 6); function s6() { // Linear [0-9a-z]{6} random string return ('000000' + (Math.random() * max | 0).toString(36)).slice(-6); } return function() { return [ s6(), s6(), s6(), s6() ].join(''); }; })(); // Return a parameter from the URL utils.getURLParameter = function(name) { // Parameter can be either a search parameter (&name=...) or a hash fragment parameter (#!name=...) var regex = new RegExp("(?:\\?|\\#\\!|&)" + name + "=(.+?)(?:&|\\#|$)"); try { return decodeURIComponent(regex.exec(location.search + location.hash)[1]); } catch(e) { return undefined; } }; // Transform a selector into a jQuery object function jqElt(element) { if(_.isString(element) || !element.val) { return $(element); } return element; } // For input control function inputError(element, event) { if(event !== undefined) { element.stop(true, true).addClass("error").delay(1000).queue(function() { $(this).removeClass("error"); $(this).dequeue(); }); event.stopPropagation(); } } // Return input value utils.getInputValue = function(element) { element = jqElt(element); return element.val(); }; // Set input value utils.setInputValue = function(element, value) { element = jqElt(element); element.val(value); }; // Return input text value utils.getInputTextValue = function(element, event, validationRegex) { element = jqElt(element); var value = element.val(); if(value === undefined) { inputError(element, event); return undefined; } // trim value = utils.trim(value); if((value.length === 0) || (validationRegex !== undefined && !value.match(validationRegex))) { inputError(element, event); return undefined; } return value; }; // Return input number value function getInputNumValue(isFloat, element, event, min, max) { element = jqElt(element); var value = utils.getInputTextValue(element, event); if(value === undefined) { return undefined; } value = isFloat ? parseFloat(value) : parseInt(value, 10); if(isNaN(value) || (min !== undefined && value < min) || (max !== undefined && value > max)) { inputError(element, event); return undefined; } return value; } // Return input integer value utils.getInputIntValue = _.partial(getInputNumValue, false); // Return input float value utils.getInputFloatValue = _.partial(getInputNumValue, true); // Return input value and check that it's a valid RegExp utils.getInputRegExpValue = function(element, event) { element = jqElt(element); var value = utils.getInputTextValue(element, event); if(value === undefined) { return undefined; } try { new RegExp(value); } catch(e) { inputError(element, event); return undefined; } return value; }; // Return input value and check that it's a valid JavaScript object utils.getInputJsValue = function(element, event) { element = jqElt(element); var value = utils.getInputTextValue(element, event); if(value === undefined) { return undefined; } try { /*jshint evil:true */ eval("var test=" + value); /*jshint evil:false */ } catch(e) { inputError(element, event); return undefined; } return value; }; // Return checkbox boolean value utils.getInputChecked = function(element) { element = jqElt(element); return element.prop("checked"); }; // Set checkbox state utils.setInputChecked = function(element, checked) { element = jqElt(element); element.prop("checked", checked).change(); }; // Get radio button value utils.getInputRadio = function(name) { return $("input:radio[name=" + name + "]:checked").prop("value"); }; // Set radio button value utils.setInputRadio = function(name, value) { $("input:radio[name=" + name + "][value=" + value + "]").prop("checked", true).change(); }; // Reset input control in all modals utils.resetModalInputs = function() { $(".modal input[type=text]:not([disabled]), .modal input[type=password], .modal textarea").val(""); $(".modal input[type=checkbox]").prop("checked", false).change(); }; // Basic trim function utils.trim = function(str) { return $.trim(str); }; // Slug function var nonWordChars = XRegExp('[^\\p{L}\\p{N}-]', 'g'); utils.slugify = function(text) { return text.toLowerCase().replace(/\s/g, '-') // Replace spaces with - .replace(nonWordChars, '') // Remove all non-word chars .replace(/\-\-+/g, '-') // Replace multiple - with single - .replace(/^-+/, '') // Trim - from start of text .replace(/-+$/, ''); // Trim - from end of text }; // Check an URL utils.checkUrl = function(url, addSlash) { if(!url) { return url; } if(url.indexOf("http") !== 0) { url = "http://" + url; } if(addSlash && url.indexOf("/", url.length - 1) === -1) { url += "/"; } return url; }; // Create the modal element and add to the body utils.addModal = function(id, content) { var modal = crel('div', { class: 'modal ' + id }); modal.innerHTML = content; document.body.appendChild(modal); }; // Create a backdrop and add to the body utils.createBackdrop = function(parent) { var result = crel('div', { 'class': 'modal-backdrop fade' }); parent = parent || document.body; parent.appendChild(result); result.offsetWidth; // force reflow result.className = result.className + ' in'; result.removeBackdrop = function() { result.className = 'modal-backdrop fade'; setTimeout(function() { result.parentNode.removeChild(result); }, 150); }; return result; }; var openedTooltip; utils.createTooltip = function(selector, content) { _.each(document.querySelectorAll(selector), function(tooltipElt) { var $tooltipElt = $(tooltipElt); $tooltipElt.tooltip({ html: true, container: $tooltipElt.parents('.modal-content'), placement: 'right', trigger: 'manual', title: content }).click(function() { var elt = this; if(openedTooltip && openedTooltip[0] === elt) { return; } utils.defer(function() { openedTooltip = $(elt).tooltip('show'); }); }); }); }; // Create an centered popup window utils.popupWindow = function(url, title, width, height) { var left = (screen.width / 2) - (width / 2); var top = (screen.height / 2) - (height / 2); return window.open(url, title, [ 'toolbar=no, ', 'location=no, ', 'directories=no, ', 'status=no, ', 'menubar=no, ', 'scrollbars=no, ', 'resizable=no, ', 'copyhistory=no, ', 'width=' + width + ', ', 'height=' + height + ', ', 'top=' + top + ', ', 'left=' + left ].join("")); }; utils.iframe = function(url, width, height, css) { $("