/* * jQuery postMessage - v0.5 - 9/11/2009 * http://benalman.com/projects/jquery-postmessage-plugin/ * * Copyright (c) 2009 "Cowboy" Ben Alman * Dual licensed under the MIT and GPL licenses. * http://benalman.com/about/license/ */ (function($){var g,d,j=1,a,b=this,f=!1,h="postMessage",e="addEventListener",c,i=b[h]&&!$.browser.opera;$[h]=function(k,l,m){if(!l){return}k=typeof k==="string"?k:$.param(k);m=m||parent;if(i){m[h](k,l.replace(/([^:]+:\/\/[^\/]+).*/,"$1"))}else{if(l){m.location=l.replace(/#.*$/,"")+"#"+(+new Date)+(j++)+"&"+k}}};$.receiveMessage=c=function(l,m,k){if(i){if(l){a&&c();a=function(n){if((typeof m==="string"&&n.origin!==m)||($.isFunction(m)&&m(n.origin)===f)){return f}l(n)}}if(b[e]){b[l?e:"removeEventListener"]("message",a,f)}else{b[l?"attachEvent":"detachEvent"]("onmessage",a)}}else{g&&clearInterval(g);g=null;if(l){k=typeof m==="number"?m:typeof k==="number"?k:100;g=setInterval(function(){var o=document.location.hash,n=/^#?\d+&/;if(o!==d&&n.test(o)){d=o;l({data:o.replace(n,"")})}},k)}}}})(jQuery); ;; /* * jQuery Plugin: Tokenizing Autocomplete Text Entry * Version 1.5.0 * * Copyright (c) 2009 James Smith (http://loopj.com) * Licensed jointly under the GPL and MIT licenses, * choose which one suits your project best! * */ (function ($) { // Default settings var DEFAULT_SETTINGS = { hintText: "Type in a search term", noResultsText: "No results", searchingText: "Searching...", deleteText: "×", searchDelay: 300, minChars: 1, tokenLimit: null, jsonContainer: null, method: "GET", contentType: "json", queryParam: "q", tokenDelimiter: ",", preventDuplicates: false, prePopulate: null, processPrePopulate: false, animateDropdown: true, onResult: null, onAdd: null, onDelete: null, idPrefix: "token-input-", allowNewItems: false, newItemFilter: null, searchFields: ["name"] }; // Default classes to use when theming var DEFAULT_CLASSES = { tokenList: "token-input-list", token: "token-input-token", tokenDelete: "token-input-delete-token", selectedToken: "token-input-selected-token", highlightedToken: "token-input-highlighted-token", dropdown: "token-input-dropdown", dropdownItem: "token-input-dropdown-item", dropdownItem2: "token-input-dropdown-item2", selectedDropdownItem: "token-input-selected-dropdown-item", inputToken: "token-input-input-token" }; // Input box position "enum" var POSITION = { BEFORE: 0, AFTER: 1, END: 2 }; // Keys "enum" var KEY = { BACKSPACE: 8, TAB: 9, ENTER: 13, ESCAPE: 27, SPACE: 32, PAGE_UP: 33, PAGE_DOWN: 34, END: 35, HOME: 36, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40, NUMPAD_ENTER: 108, COMMA: 188 }; // Additional public (exposed) methods var methods = { init: function(url_or_data_or_function, options) { var settings = $.extend({}, DEFAULT_SETTINGS, options || {}); return this.each(function () { $(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings)); }); }, clear: function() { this.data("tokenInputObject").clear(); return this; }, add: function(item) { this.data("tokenInputObject").add(item); return this; }, remove: function(item) { this.data("tokenInputObject").remove(item); return this; } } // Expose the .tokenInput function to jQuery as a plugin $.fn.tokenInput = function (method) { // Method calling and initialization logic if(methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else { return methods.init.apply(this, arguments); } }; // TokenList class for each input $.TokenList = function (input, url_or_data, settings) { // // Initialization // // Configure the data source if(typeof(url_or_data) === "string") { // Set the url to query against settings.url = url_or_data; // Make a smart guess about cross-domain if it wasn't explicitly specified if(settings.crossDomain === undefined) { if(settings.url.indexOf("://") === -1) { settings.crossDomain = false; } else { settings.crossDomain = (location.href.split(/\/+/g)[1] !== settings.url.split(/\/+/g)[1]); } } } else if(typeof(url_or_data) === "object") { // Set the local data to search through settings.local_data = url_or_data; } // Build class names if(settings.classes) { // Use custom class names settings.classes = $.extend({}, DEFAULT_CLASSES, settings.classes); } else if(settings.theme) { // Use theme-suffixed default class names settings.classes = {}; $.each(DEFAULT_CLASSES, function(key, value) { settings.classes[key] = value + "-" + settings.theme; }); } else { settings.classes = DEFAULT_CLASSES; } // Save the tokens var saved_tokens = []; // Keep track of the number of tokens in the list var token_count = 0; // Basic cache to save on db hits var cache = new $.TokenList.Cache(); // Keep track of the timeout, old vals var timeout; var input_val; // Create a new text input an attach keyup events var input_box = $("") .css({ outline: "none" }) .attr("id", settings.idPrefix + input.id) .focus(function () { if (settings.tokenLimit === null || settings.tokenLimit !== token_count) { show_dropdown_hint(); } }) .blur(function () { hide_dropdown(); $(this).val(""); }) .bind("keyup keydown blur update", resize_input) .keydown(function (event) { var previous_token; var next_token; switch(event.keyCode) { case KEY.LEFT: case KEY.RIGHT: case KEY.UP: case KEY.DOWN: if(!$(this).val()) { previous_token = input_token.prev(); next_token = input_token.next(); if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) { // Check if there is a previous/next token and it is selected if(event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) { deselect_token($(selected_token), POSITION.BEFORE); } else { deselect_token($(selected_token), POSITION.AFTER); } } else if((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) { // We are moving left, select the previous token if it exists select_token($(previous_token.get(0))); } else if((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) { // We are moving right, select the next token if it exists select_token($(next_token.get(0))); } } else { var dropdown_item = null; if(event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) { dropdown_item = $(selected_dropdown_item).next(); } else { dropdown_item = $(selected_dropdown_item).prev(); } if(dropdown_item.length) { select_dropdown_item(dropdown_item); } return false; } break; case KEY.BACKSPACE: previous_token = input_token.prev(); if(!$(this).val().length) { if(selected_token) { delete_token($(selected_token)); } else if(previous_token.length) { select_token($(previous_token.get(0))); } return false; } else if($(this).val().length === 1) { hide_dropdown(); } else { // set a timeout just long enough to let this function finish. setTimeout(function(){do_search();}, 5); } break; case KEY.TAB: case KEY.ENTER: case KEY.NUMPAD_ENTER: case KEY.COMMA: if(selected_dropdown_item && event.keyCode != KEY.COMMA) { add_token($(selected_dropdown_item).data("tokeninput")); return false; } else if (settings.allowNewItems) { var new_item_name = $(this).val(); if (new_item_name.length > 0) { if (typeof(settings.newItemFilter) === "function") { settings.newItemFilter(add_token, new_item_name); } else { add_token({id: new_item_name, name: new_item_name}); } } return false; } break; case KEY.ESCAPE: hide_dropdown(); return true; default: if(String.fromCharCode(event.which)) { // set a timeout just long enough to let this function finish. setTimeout(function(){do_search();}, 5); } break; } }); // Keep a reference to the original input box var hidden_input = $(input) .hide() .val("") .focus(function () { input_box.focus(); }) .blur(function () { input_box.blur(); }); // Keep a reference to the selected token and dropdown item var selected_token = null; var selected_token_index = 0; var selected_dropdown_item = null; // The list to store the token items in var token_list = $("
"+ item.name +"
"+settings.searchingText+"
"); show_dropdown(); } } function show_dropdown_hint () { if(settings.hintText) { dropdown.html(""+settings.hintText+"
"); show_dropdown(); } } // Highlight the query part of the search term function highlight_term(value, term) { var escaped = term.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + escaped + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); } // Populate the results dropdown with some results function populate_dropdown (query, results) { if(results && results.length) { dropdown.empty(); var dropdown_ul = $(""+settings.noResultsText+"
"); show_dropdown(); } } } // Highlight an item in the results dropdown function select_dropdown_item (item) { if(item) { if(selected_dropdown_item) { deselect_dropdown_item($(selected_dropdown_item)); } item.addClass(settings.classes.selectedDropdownItem); selected_dropdown_item = item.get(0); } } // Remove highlighting from an item in the results dropdown function deselect_dropdown_item (item) { item.removeClass(settings.classes.selectedDropdownItem); selected_dropdown_item = null; } // Do a search and show the "searching" dropdown if the input is longer // than settings.minChars function do_search() { var query = input_box.val().toLowerCase(); if(query && query.length) { if(selected_token) { deselect_token($(selected_token), POSITION.AFTER); } if(query.length >= settings.minChars) { show_dropdown_searching(); clearTimeout(timeout); if(settings.searchDelay > 0) { timeout = setTimeout(function(){ run_search(query); }, settings.searchDelay); } else { run_search(query); } } else { hide_dropdown(); } } } // Do the actual search function run_search(query) { // trim any leading/trailing white space or commas query = query.replace(/(^[,\s]+|[,\s]+$)/g, ""); query = query.toLowerCase(); var cached_results = cache.get(query); if(cached_results) { populate_dropdown(query, cached_results); } else { // Are we doing an ajax search or local data search? if(settings.url) { // Extract exisiting get params var ajax_params = {}; ajax_params.data = {}; if(settings.url.indexOf("?") > -1) { var parts = settings.url.split("?"); ajax_params.url = parts[0]; var param_array = parts[1].split("&"); $.each(param_array, function (index, value) { var kv = value.split("="); ajax_params.data[kv[0]] = kv[1]; }); } else { ajax_params.url = settings.url; } // Prepare the request ajax_params.data[settings.queryParam] = query; ajax_params.type = settings.method; ajax_params.dataType = settings.contentType; if(settings.crossDomain) { ajax_params.dataType = "jsonp"; } // Attach the success callback ajax_params.success = function(results) { if($.isFunction(settings.onResult)) { results = settings.onResult.call(hidden_input, results); } cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results); // only populate the dropdown if the results are associated with the active search query if(input_box.val().toLowerCase() === query) { populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results); } }; // Make the request $.ajax(ajax_params); } else if(settings.local_data) { // Do the search through local data var results = $.grep(settings.local_data, function (row) { for(i = settings.searchFields.length - 1; i >= 0; i--) { var searchField = row[settings.searchFields[i]]; if(searchField && searchField.toLowerCase().indexOf(query) > -1) return true; } }); if($.isFunction(settings.onResult)) { results = settings.onResult.call(hidden_input, results); } cache.add(query, results); populate_dropdown(query, results); } } } }; // Really basic cache for the results $.TokenList.Cache = function (options) { var settings = $.extend({ max_size: 500 }, options); var data = {}; var size = 0; var flush = function () { data = {}; size = 0; }; this.add = function (query, results) { if(size > settings.max_size) { flush(); } if(!data[query]) { size += 1; } data[query] = results; }; this.get = function (query) { return data[query]; }; }; }(jQuery)); ;; tf = window.tf || {}; /** * * Scales an tag so it fits within a maximum width/height bounding box, while * maintaining the image's aspect ratio. * * @param {jQuery} img an tag (or multiple such tags) * @param {Number} imageWidth the native width in pixels of the image * @param {Number} imageHeight the native height in pixels of the image * @param {Number} desiredWidth the maximum allowed width in pixels, can be null or 0 if no constraints * @param {Number} desiredHeight the maximum allowed height in pixels, can be null or 0 if no constraints */ tf.fitImage = function(img, imageWidth, imageHeight, desiredWidth, desiredHeight) { var widthExcess = desiredWidth ? 1.0 * imageWidth / desiredWidth : 0, heightExcess = desiredHeight ? 1.0 * imageHeight / desiredHeight : 0, finalWidth, finalHeight; if(widthExcess < 1 && heightExcess < 1) { // no scaling needed, just use native dimensions finalWidth = imageWidth; finalHeight = imageHeight; } else if(widthExcess > heightExcess) { // in this case the width is the limiting factor, so set an explicit width finalWidth = desiredWidth; // we could calculate the height explicitly, but we just let the browser do it; // upside: won't change aspect ratio b/o rounding issues; downside: page layout takes longer // finalHeight = 1.0 * imageHeight * desiredWidth / imageWidth; } else { // in this case the height is the limiting factor, so set an explicit height // (see description of explicit height above) // finalWidth = 1.0 * imageWidth * desiredHeight / imageHeight; finalHeight = desiredHeight; } if(finalWidth) img.attr("width", finalWidth); else img.removeAttr("width"); if(finalHeight) img.attr("height", finalHeight); else img.removeAttr("height"); }; ;; tf = window.tf || {}; tf.Mutex = function () { var lock = false, shared = 0; // Acquire exclusive lock, returns true on success. this.a = function() { return !lock && !shared && (lock = true); } // Release exclusive lock this.r = function () { lock = false; } // is exclusive lock held? this.h = function () { return lock; } // increment semaphore (blocks exclusive lock) this.i = function () { return !lock && ++shared > 0; } // decrement semaphore this.d = function () { if (shared > 0) shared--; } }; ;; tf = window.tf || {}; tf.isDebug = function() { return !location.href.match(/trefis/i) || location.href.match(/qa\.trefis/i) }; ;; // @include tf/isDebug.js /* * wrappers around some Google Analytics methods */ /** * Tracks a pageview in Google Analytics. * * @param {String} page the URL that was viewed. * See GA's docs on _trackPageview for more info. */ function gaTrack(page) { if (localStorage.getItem('disableTfAnalytics')) return _gaq = window._gaq || []; _gaq.push(["_trackPageview", page]); } /** * Tracks an event in Google Analytics. * * @param {Array} arr the array of data on the event. * See GA's docs on _trackEvent for more info */ function gaEvent(arr) { if (localStorage.getItem('disableTfAnalytics')) return _gaq = window._gaq || []; arr.splice(0, 0, "_trackEvent"); _gaq.push(arr); if (window.tf && window.tf.isDebug()){ console.log("GA Event: " + arr); } } function gapv(page, title){ if (localStorage.getItem('disableTfAnalytics')) return gtag('config', window.gaToken, { 'page_title' : title, 'page_path': page }); if (window.tf && window.tf.isDebug()){ console.log("GA PV: " + page + " | " + title); } } function gavpv(page, title){ if (localStorage.getItem('disableTfAnalytics')) return gtag('config', window.gaToken, { 'page_title' :'Virtual-' + title, 'page_path': 'vpv/' + page }); if (window.tf && window.tf.isDebug()){ console.log("GA VPV: " + page + " | " + title); } } function gavpvl(page, title){ if(properties.loggedInUser()){ var user = properties.loggedInUser(); var userType = 'other'; if(user.email.endsWith('trefis.com')){ userType = 'trefis'; } gavpv(page + '/loggedin/' + userType + '/' + user.userId, title); } else { gavpv(page + '/loggedout',title); } } ;; // lib/jquery.min.js // @include lib/jquery.postMessage.js // @include lib/jquery.tokeninput.js // @include tf/fitImage.js // @include tf/mutex.js // @include tf/ga.js /* * This is the code that lives inside the clipping tool's iframe */ /** * Initializes the inside of the clipping tool popup. Should be called only once on document ready. * * @param {String} parentUrl URL of the parent page that we're trying to clip; cannot be null * @param {String} userIdCookie the keyname of the cookie that should contain the user's id; cannot be null * @param {String} userCheckCookie the keyname of the cookie that should contain the user's hash check; cannot be null * @param {int} windowHeight the height of the parent page's window */ function initClippingTool(parentUrl, userIdCookie, userCheckCookie, windowHeight) { var MAX_TITLE_LENGTH = 110, imageSrcs = [], imageTitles = [], selectedImageIndex = -1, previewImageWidth = 250, previewImageHeight = 150, submissionMutex = new tf.Mutex(); $('.share').click(submit); $('.noimages').click(function() { setSelectedImageIndex(selectedImageIndex < 0 ? 0 : -1); return false; }); $('.images .arrowPrev').click(function() { if($(this).hasClass('arrowEnabled')) setSelectedImageIndex(selectedImageIndex < 0 ? 0 : selectedImageIndex - 1); return false; }); $('.images .arrowNext').click(function() { if($(this).hasClass('arrowEnabled')) setSelectedImageIndex(selectedImageIndex < 0 ? 0 : selectedImageIndex + 1); return false; }); $.receiveMessage( function(e){ var data = e.data.split(',,'); var message = data[0]; var temp; if(message == 'set') { temp = data.length - 1; imageTitles = data.slice(1 + temp / 2, 1 + temp); imageSrcs = data.slice(1, 1 + temp / 2); $('.images .imgWrapper').append($('', {text: 'No Image', display: 'none'})); $.each(imageSrcs, function(i, src) { var img = $('', { load: imageFitter }) $('.images .imgWrapper').append(img); img.attr('src', src); }); setSelectedImageIndex(imageSrcs.length ? 0 : -1); } else if(message == 'pick') { setSelectedImageIndex($.inArray(data[1], imageSrcs)); } }, parentUrl.replace(/([^:]+:\/\/[^\/]+).*/,"$1") ); if(!getCookie(userIdCookie) || !getCookie(userCheckCookie)) { $('.loginBoxes').show(); } if(windowHeight < 600) { $('textarea').attr('rows', 3); previewImageWidth = 150; previewImageHeight = 90; } $('.images table').css({width: previewImageWidth, height: previewImageHeight}); $.postMessage('askimages', parentUrl); $.ajax({ type: 'GET', url: getHost() + '/libraryupload/tags', data: {companies: 1}, dataType: 'json', success: function(data, status) { if(data && data.status == 'success') { $('.tags').tokenInput( data.data, { theme: 'trefis', preventDuplicates: true, searchFields: ['name', 'symbol'], searchDelay: 0, hintText: 'Ticker or Company Name' } ); } } }); $.timer(5000, updateOnSizeChange); updateOnSizeChange(); gaEvent(['clip', 'open', parentUrl]); function cookiesEnabled() { var t = 'testcookie',r; document.cookie = t + '=; path=/'; r = document.cookie.indexOf(t) != -1; document.cookie = t + '=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/'; return r; } function getCookie(name) { var result; $.each(document.cookie.split(';'), function(i, cookie) { cookie = cookie.split('='); if(cookie[0].replace(/^\s+/g, '') == name) { result = cookie[1]; return false; } }); return result; } function setSelectedImageIndex(newSelectedImageIndex) { var img; selectedImageIndex = newSelectedImageIndex; if(selectedImageIndex < 0) { // remove $('.images .imgWrapper') .find('img') .hide() .end() .find('span') .show(); $('.noimages').text('Include an image'); $('.images .arrowPrev,.images .arrowNext').addClass('arrowEnabled'); $('.imageText').hide(); } else { $('.images .imgWrapper') .find('span') .hide() .end() .find('img') .hide() .eq(selectedImageIndex) .show(); $('.noimages').text('I don\'t want to select any image'); $('.imageText').show(); $('.imageCtr').text(selectedImageIndex + 1); $('.imageLen').text(imageSrcs.length); $('.images .arrowPrev').toggleClass('arrowEnabled', selectedImageIndex > 0); $('.images .arrowNext').toggleClass('arrowEnabled', selectedImageIndex < imageSrcs.length - 1); } // set the background back to white, because it may have been set to red as an error condition // if the user tried to save w/o an image selected $('.images table').css({backgroundColor: '#fff'}); // update view / arrows $.postMessage('images' + (selectedImageIndex < 0 ? '' : ',,' + imageSrcs[selectedImageIndex]), parentUrl); updateOnSizeChange(); } function imageFitter() { var img = $(this); img.show(); tf.fitImage(img, img.width(), img.height(), previewImageWidth, previewImageHeight); setSelectedImageIndex(selectedImageIndex); } function updateOnSizeChange() { var bottomestThing = $('.buttons'); $.postMessage('resize,,' + (bottomestThing.offset().top + bottomestThing.height()), parentUrl) } function announceError(html) { $.postMessage('error,,' + html, parentUrl); } function authCheck(callback) { if(getCookie(userIdCookie) && getCookie(userCheckCookie)) { callback(); } else if(cookiesEnabled()) { announceError('You must be logged in to Trefis to clip. Also check that you have 3rd party cookies enabled.'); } else { announceError('You must have 3rd party cookies enabled to clip. Learn how to fix this here.'); } } function submit() { var symbols = $('.tags').val().split(','), userId = getCookie(userIdCookie), userCheck = getCookie(userCheckCookie), hasCookies = userId && userCheck, email = $('.email').val(), password = $('.password').val(), hasLogin = email.length && password.length; if(symbols.length == 0 || symbols[0].length == 0) { announceError('Please tag this clip to at least one company'); $('.token-input-list-trefis,.token-input-list-trefis input').css({backgroundColor: '#ffe0e0'}); } else if(!hasCookies && !hasLogin) { announceError('Enter your Trefis account email/password to clip. To skip this next time, log in on Trefis and enable 3rd party cookies.'); $('.email,.password').css({backgroundColor: '#ffe0e0'}); } else if(submissionMutex.a()) { $('.spinner').show(); // the symbols we get from the tokenized inputter plugin are of the form // 'co|AAPL,co|GOOG', so we need to split on commas, and then strip the co| symbols = $.map(symbols, function(id) { return id.length ? id.split('\|')[1] : null; }); $.ajax({ type: 'POST', url: getHost()+'/clippingtool/submit', data: { share: 1, cliptext: $('.cliptext').val(), takeaway: $('.takeaway').val(), imageSrc: selectedImageIndex < 0 ? undefined : imageSrcs[selectedImageIndex], imageTitle: selectedImageIndex < 0 ? undefined : imageTitles[selectedImageIndex], userId: getCookie(userIdCookie), userCheck: getCookie(userCheckCookie), email: email, password: password, symbols: symbols, url: parentUrl, trefisTeam: $('.trefisTeam').is(':checked') }, dataType: 'json', success: function(data, status) { if(data && data.status == 'success') { $.postMessage( 'share,,' + data.id, parentUrl); gaEvent(['clip', 'share', parentUrl]); } else if(data && data.status == 'errorauthfailed') { announceError('Sorry, that email/password combination is not valid. If you think this is in error, email help@trefis.com'); } else if(data && data.status == 'error') { announceError('Sorry, your clip could not be saved. Please try again later or email help@trefis.com'); } submitFinished(); }, error: function (){ announceError('Sorry, your clip could not be saved. Please try again later or email help@trefis.com'); submitFinished(); } }); } return false; } function submitFinished() { submissionMutex.r(); $('.spinner').hide(); } } ;;