$(function(){ // Create Temboo namespace window.Temboo || (window.Temboo = function(){ var DEBUG = false; var init = function(){ // // Do...something, someday // // Notify the world $(window).trigger('Temboo.initialized'); } /* Constants */ const ASCII_BACKSPACE = 8; const ASCII_TAB = 9; const ASCII_CARRIAGE_RETURN = 13; const ASCII_SPACE = 32; const ASCII_COMMA = 188; /** * Public-facing methods */ var publicFacing = { /* Constants */ 'ASCII_BACKSPACE': ASCII_BACKSPACE, 'ASCII_TAB': ASCII_TAB, 'ASCII_CARRIAGE_RETURN': ASCII_CARRIAGE_RETURN, 'ASCII_SPACE': ASCII_SPACE, 'ASCII_COMMA': ASCII_COMMA, setEnv:function(env) { DEBUG = env == 'DEVELOPMENT'; }, error:function(){ if(DEBUG && typeof console !== 'undefined'){ var msg = arguments.length > 1 ? arguments[0] + ': ' + arguments[1] : arguments[0]; console.error(msg); } }, log:function(){ if(DEBUG && typeof console !== 'undefined'){ var msg = arguments.length > 1 ? arguments[0] + ': ' + arguments[1] : arguments[0]; //console.log(msg); } }, warn:function(){ if(DEBUG && typeof console !== 'undefined'){ var msg = arguments.length > 1 ? arguments[0] + ': ' + arguments[1] : arguments[0]; console.warn(msg); } }, trackEvent:function(/* polymorphic */) { var evt = ['_trackEvent']; for(var i in arguments) evt.push(arguments[i]); Temboo.log('Temboo.trackEvent', JSON.stringify(evt)); _gaq.push(evt); }, trackPageview:function(uri) { var pageview = ['_trackPageview']; pageview.push(uri); Temboo.log('Temboo.trackPageview', JSON.stringify(pageview)); _gaq.push(pageview); }, /** * For e.g.: the /myaccount/referrals invite pane, sending every address to the validation helper seems a bit * excessive. Here's a quick JS email validator. * * @param allegedAddress String to check. * @return true if address matches the pattern, false if it doesn't. */ quickEmailValidator:function(allegedAddress) { // 1. contains 1 and only 1 "@" // $emailParts = explode('@', $email); var emailParts = allegedAddress.split('@'); if(emailParts.length != 2) { return false; } // email part before @ is not empty if (emailParts[0].length == 0) {return false; } // 2. contains least one "." after the @ // and contains at least two non-empty domain parts var domainParts = emailParts[1].split('.'); for (var i = 0; i < domainParts.length; i++) { if (domainParts[i].length == 0) { return false; } } if (i < 2) { return false; } // 3. is at least 6 characters long if(allegedAddress.length < 6) { return false; } // 4. contains no commas if(allegedAddress.indexOf(',') > -1) { return false; } // 5. contains no spaces if(allegedAddress.indexOf(' ') > -1) { return false; } // hurray, this string is a minimally plausible email address! return true; }, /** * Say we want to gate a feature instantiated entirely in JS. What do we do? Unfortunately, this. * @param name String of name of feature about which we are curious. * @return true if the user has access to the feature, false if it doesn't. */ hiddenFeatureCheck:function(featureName) { var salientVal; $.ajax({ url: '/features/check/' + featureName + '/', async: false, success: function(data) { if (data == 'true') { salientVal = true; } else { salientVal = false; } } }); return salientVal; }, /** * Add an object to the window.Temboo namespace, by name. If the public namespace requested * is already defined an exception is thrown. * * @param name The name by which the object may be accessed, e.g. @ window.Temboo._name_here_ * @param obj The object itself */ register:function(name, obj, callback){ if(typeof this[name] !== 'undefined') throw "Object exists: window.Temboo." + name; this[name] = obj; if(typeof callback === 'function') callback.call(null); this.log('Temboo.register', 'Registered object: Temboo.' + name); $(window).trigger('Temboo.' + name + '.initialized'); }, /** * Vertically center callouts and arrows relative to the element it points to ('.anchor' sibling) * * @param callout The callout element */ // Vertically center callouts and arrows relative to the element it points to ('.anchor' sibling) alignCallout:function(callout) { callout.css('top', (((callout.height() - callout.parent().find('.anchor').height()) / 2)) * -1); callout.find('.head-callout').css('top', ((callout.innerHeight() + callout.height()) / 4) - 2); // half of average of height and height with padding }, /** * Display a brief status notification */ showStatus:function(status, message, options){ var layout, defaults = { anchor:'top', container:'body > .content', displayDuration:8000, fadeDuration:200, applyLayout:true }, options = $.extend({}, defaults, options), $container = $(options['container']), yPos = $container.offset().top; $view = $('
') .attr('id', 'widget-status') .addClass('status-alert ' + status) .append($('

').html(message)); // Inject into DOM in order to calculate width $container.append($view); if (options['applyLayout']) { // Position on top edge of the footer, unless that's off-screen // in which case we anchor it to the viewports lower edge if(yPos < $(window).scrollTop()) { layout = { position:'fixed', bottom:'auto', top:0 } } else{ layout = { position:'absolute', bottom:'auto', top:yPos } } layout.width = $view.outerWidth(true) + 'px'; // Apply layout $view.css(layout); } // Fade in, fade out, remove $view .fadeIn(options['fadeDuration']) .delay(options['displayDuration']) .fadeOut(options['fadeDuration'], function(){ $(this).remove(); }); }, /* * Open login callout in header */ showLogin: function() { $navLinks = $('#nav-links'); //$navLinks $('#nav-links').addClass('login-open'); // Restore initial login state restoreInitialLoginState($(this)); }, /** * A function to toggle the UI switch used to select binary units in library.js, plc.js, and activity.js * @param $clicked The switch element to toggle * @param duration The duration for the animation * @param callback A callback function for the animation */ toggleSwitch: function ($clicked, duration, callback) { if (!$clicked.hasClass('sliding')) { $clicked.addClass('sliding'); var $active = $('span.active', $clicked); var width = $active.outerWidth(); var delta = '-=' + width; if ($active.hasClass('right')) { delta = '+=' + width; } var $slider = $('.slider', $clicked); $slider.animate({'left': delta}, duration, 'swing', callback); } }, /** * Sets up the geocoder input field used when editing devices and creating devices. * @param validateOnSuccess A function to be called after a successful entry */ 'autocomplete': function autocomplete(validateOnSuccess) { $("#autocomplete").autocomplete({ "autoFocus": true, "source": function(request, response) { $.post("/device/autocomplete/" + request.term, function(myResponse) { if (myResponse.status === "success") { var myData = JSON.parse(myResponse.data); var result = []; for (var i = 0; i < myData.features.length; i++) { result.push(myData.features[i].place_name); } response(result); } else { // Error, do nothing } }); }, "select": function(e, ui) { $("#autocomplete").attr('data-state', 'validating'); var searchText = ""; if (e.keyCode === ASCII_CARRIAGE_RETURN) { if ($("#autocomplete").val().length === 0) { e.preventDefault(); return false; } searchText = $("#autocomplete").val(); } else { searchText = ui.item.value; } $.post("/device/autocomplete/" + searchText, function(myResponse) { if (myResponse.status == "success") { var myData = JSON.parse(myResponse.data); $("#autocomplete").val(myData.features[0].place_name).autocomplete("close"); $("input#latitude").val(myData.features[0].geometry.coordinates[1]); $("input#longitude").val(myData.features[0].geometry.coordinates[0]); $("#autocomplete").attr("data-state", "validated"); validateOnSuccess(); } else { // Error, do nothing } }); event.preventDefault(); return false; } }); }, /** * Toggle display of location or latitude/longitude fields */ 'toggleLatLon': function toggleLatLon(toggleElement) { if ($('#addressBox input#autocomplete').is(':visible')) { $('input.latlon').show(); $('input.latlon#latitude').focus(); $('#addressBox input#autocomplete').hide(); toggleElement.text('Use street address'); } else { $('input.latlon').hide(); $('#addressBox input#autocomplete').show().focus(); if ($("#addressBox input#autocomplete").val() == '') { $("#addressBox input#autocomplete").removeAttr("data-state"); } toggleElement.text('Use lat/long'); } }, /** * This function is used to validate emails for sensor/diagnostic alerts * @param $currInvite The .curr-invite element to validate */ 'validateAlertEmail': function($currInvite) { var newItem = $('').addClass('invite-address-outer'); var newEntry = $('').addClass('invite-address'); var address = $currInvite.val(); if (address === '') { return; } if ($currInvite.closest('.emails').hasClass('sms')) { // SMS if (address.match(/^\+?[0-9-]+?$/)) { newEntry.addClass('valid'); } else { newEntry.addClass('invalid'); } } else { // Email if (Temboo.quickEmailValidator(address)) { newEntry.addClass('valid'); } else { newEntry.addClass('invalid'); } } var addressText = $('').addClass('address-text'); var newButton = $('