/*! * jquery.compareFeed - The Adservice publisher feed jQuery plugin. * * Copyright (c) 2020 Adservice * https://www.adservice.com * * Version : 1.40 */ (function ($) { $.fn.adserviceFeed = function (options) { var obj = this; // Get options if (!options) options = {}; var searchForm = options.searchForm || this.attr('data-searchForm'); var publisherId = options.publisherId || this.attr('data-publisherId'); var categoryId = options.categoryId || this.attr('data-categoryId'); var template = options.template || this.attr('data-template'); var customFeed = options.customFeed || this.attr('data-customFeed'); var showRating = options.showRating || this.attr('data-showRating') != "false"; var minRating = options.minRating || this.attr('data-minRating') || 60; var cleanLink = options.cleanLink || this.attr('data-cleanLink') || "true"; var oneItemPerCampaign = options.oneItemPerCampaign || this.attr('data-oneItemPerCampaign') == "true"; var includeNewCampaigns = options.includeNewCampaigns || this.attr('data-includeNewCampaigns') != "false"; var sortBy = options.sortBy || this.attr('data-sortBy'); // Data field to sort the rows by. Defaults to customOrder then rating. var sortOrder = options.sortOrder || this.attr('data-sortOrder') || 'asc'; // The sort order to use. Must be 'asc' or 'desc'. var disableAdblockCheck = options.disableAdBlockCheck || this.attr('data-disableAdBlockCheck') == "true"; var sub = getHashParam('ascf-sub') || options.sub || this.attr('data-sub'); var debug = getHashParam('ascf-debug'); var initialRows = options.initialRows || this.attr('data-initialRows'); var maxRows = options.maxRows || this.attr('data-maxRows'); var initialMaxRows = maxRows; var lazyLoading = options.lazyLoading || this.attr('data-lazyLoading') != "false"; var minEpc = options.minEpc || this.attr('data-minEpc'); var countryCode = options.countryCode || this.attr('data-countryCode'); var productFeedId = options.productFeedId || this.attr('data-productFeedId'); var nocache = options.nocache || this.attr('data-nocache'); var showInteractiveFilter = options.showInteractiveFilter || this.attr('showInteractiveFilter') || 0; // If serverUrl is defined as TMPL_VAR then use that // otherwise default to feed domain. var serverUrlValue = "https://feedcontentcloud.com"; var serverUrl = (serverUrlValue.substr(0, 4) === 'http') ? serverUrlValue : "https://feedcontentcloud.com"; // If trackingUrl is defined as TMPL_VAR then use that // otherwise default to tracking domain. var trackingUrlValue = "https://online.adservicemedia.dk"; var trackingUrl = (trackingUrlValue.substr(0, 4) === 'http') ? trackingUrlValue : "https://online.adservicemedia.dk"; var asjsUri = "/cgi-bin/publisher/tools/asjs.pl"; var predubidUri = "/cgi-bin/publisher/tools/predubid.pl"; var scriptUri = "/v2/public/publisher/comparisonfeed/data"; var translationUri = "/v2/public/publisher/comparisonfeed/translations"; var translations; // Workaround to be able to use TMPL_VAR in javascript // while still getting the same result if it's not defined. var useTranslationsValue = Number("0"); var useTranslations = (useTranslationsValue !== 0); var useNewFilteringValue = Number("0"); var useNewFiltering = (useNewFilteringValue !== 0); var useNewConditionsValue = Number("0"); var useNewConditions = (useNewConditionsValue !== 0); try { var customRating = options.customRating || JSON.parse(jsonFix(this.attr('data-customRating'))); } catch (e) { try { var customRating = JSON.parse(jsonFix('{' + this.attr('data-customRating') + '}')); } catch (e) { if (debug) console.log(e.message); } } try { var customOrder = options.customOrder || JSON.parse(jsonFix(this.attr('data-customOrder'))); } catch (e) { try { var customOrder = JSON.parse(jsonFix('{' + this.attr('data-customOrder') + '}')); } catch (e) { if (debug) console.log(e.message); } } try { var customData = options.customData || JSON.parse(jsonFix(this.attr('data-customData'))); } catch (e) { if (debug) console.log(e.message); } try { var conditions = options.conditions || JSON.parse(jsonFix(this.attr('data-conditions'))); } catch (e) { if (debug) console.log(e.message); } try { var labels = options.labels || JSON.parse(jsonFix(this.attr('data-labels'))); } catch (e) { try { var labels = JSON.parse(jsonFix('{' + this.attr('data-labels') + '}')); } catch (e) { if (debug) console.log(e.message); } } try { var removedItems = options.removedItems || JSON.parse(jsonFix(this.attr('data-removedItems'))); } catch (e) { try { var removedItems = JSON.parse(jsonFix('[' + this.attr('data-removedItems') + ']')); } catch (e) { if (debug) console.log(e.message); } } try { var mediaId = options.mediaId || this.attr('data-media'); } catch (e) { if (debug) console.log(e.message); } // Make sure each object is only parsed once, even if both loading methods are used. if (!options.reload) { if (obj.attr('loadedFeed') == 'true') return; else obj.attr('loadedFeed', 'true'); } // Show loading spinner without having to render template. $('#tableHolder').html('
'); var tableData = []; var latestSearchData = []; var removedCampaigns; var loadedTmplPlugin = false; var loadedFeed = false; var loadedTranslations = false; var timeoutId; // Abort if we don't have any data. if ((!categoryId || !publisherId) && !customFeed && !customData && !productFeedId) { console.log('Adservice Feed: No data to display when categoryId, publisherId, customData, customFeed or productFeedId parameters are undefined.'); return; } // Adblock and timeout check if (!disableAdblockCheck) { timeoutId = window.setTimeout(function () { if (tableData.length == 0) { // only if the table is empty obj.html('
Unable to load content, please check your connection settings or disable your adblocker.
'); } }, 10000); } // Enabled caching for ajax requests. // This means getScript won't append a timestamped query parameter on // request URLs for cache busting. // Docs: https://api.jquery.com/jquery.getscript/ $.ajaxSetup({ cache: true }); // Load JsRender. if (!($ && $.render)) { $.getScript(serverUrl + '/js/jsrender.min.js', function () { loadedTmplPlugin = true; window.clearTimeout(timeoutId); // loading successful, clear the fallback warning: no adblock / connection errors }); } else { loadedTmplPlugin = true; } // Load IntersectionObserver polyfills for lazy loading. if (lazyLoading && !('IntersectionObserver' in window)) { $.getScript('https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver'); } // Store lazy loading option in window for later access. window.adserviceLazyLoading = lazyLoading; // Load asjs.pl third party tracking script. if ($('script[src*="asjs.pl"]').length === 0 && publisherId) { $.getScript(serverUrl + asjsUri + '?id=' + publisherId, function () { window.clearTimeout(timeoutId); // loading successful, clear the fallback warning: no adblock / connection errors }); } // Listen for 'predubidLoaded' event emitted by predubid.pl. $(document.body).on('predubidLoaded', function () { checkRejected(); }); // Load predubid.pl pre dublet check script. if ($('script[src*="' + predubidUri + '"]').length === 0 && categoryId && publisherId) { $.getScript(trackingUrl + predubidUri + '?id=' + publisherId + '&cat=' + categoryId + '&src=comparisonfeed'); } else { checkRejected(); } if (Number(initialRows) > 0) { // Override maxRows with initialRows. maxRows = initialRows; // Add click handler for view more button. $('#as-feed-view-more-button').on('click', function () { $(this).toggleClass('as-expanded'); $(this).find('span').toggleClass('hidden'); // Flip value of maxRows. maxRows = (maxRows == initialMaxRows) ? initialRows : initialMaxRows; // Merge latest search data with conditions to show // correct data after filter have been used. var searchData = latestSearchData; if (!conditions) conditions = []; $.merge(searchData, conditions); parseData(tableData, searchData); }); } // Get adserviceFeed if (categoryId && publisherId) { if (parseInt(publisherId) > 0) { $.getJSON(serverUrl + scriptUri + '?category_id=' + categoryId + '&pid=' + publisherId, function (response) { var data = response.data; // Check if new campaigns should be included or not. if (!includeNewCampaigns && customOrder) { // Filter new campaigns out of data. data = filterNewCampaigns(data, customOrder); } $.merge(tableData, data); // Mark feed data as loaded and render feed if translations are used and has finished loading. loadedFeed = true; if ((useTranslations && loadedTranslations) || (!useTranslations && !loadedTranslations)) { autoFill(searchForm, tableData); window.clearTimeout(timeoutId); // loading successful, clear the fallback warning: no adblock / connection errors parseData(tableData, conditions); } }); } else { // Mark feed data as loaded since we didn't have a valid publisher ID. loadedFeed = true; window.clearTimeout(timeoutId); // no loading, clear the fallback warning: no adblock / connection errors } // Get translations if (useTranslations) { loadTranslations(); } } // Get product feed products. if (parseInt(productFeedId) > 0) { // Load DOMPurify for HTML sanitizing. $.getScript('https://cdn.jsdelivr.net/npm/dompurify@2.3.1/dist/purify.min.js'); var cache = (nocache) ? '?nocache=' + nocache : ''; $.getJSON(serverUrl + '/v2/public/publisher/productfeeds/' + productFeedId + '/products' + cache, function (response) { var data = response.data; // Map naming to fit CF standards. $.each(data, function () { $(this)[0].cleanUrl = $(this)[0].clean_url; $(this)[0].trackingUrl = $(this)[0].tracking_url; }); $.merge(tableData, data); // Mark feed data as loaded and render feed. loadedFeed = true; if (loadedTranslations) { window.clearTimeout(timeoutId); // loading successful, clear the fallback warning: no adblock / connection errors parseData(tableData, conditions); } }); // Get translations loadTranslations(); } // Get the customFeed if (customFeed) { $.getJSON(customFeed, function (data) { $.merge(tableData, data); parseData(tableData, conditions); autoFill(searchForm, tableData); }); } // add customData if (customData) { $.merge(tableData, customData); parseData(tableData, conditions); autoFill(searchForm, tableData); } if (searchForm) { $(searchForm).submit(function (e) { var searchData = []; // loop through the form to generate the searchData $(this).find(':input').each(function () { var name = $(this).attr('name'); var operator = $(this).attr('operator'); var value = $(this).val(); var type = $(this).attr('type'); var checkboxOrNot = $(this).attr('type') == "checkbox" && $(this).is(':checked') || $(this).attr('type') != "checkbox"; if (name && value && operator && checkboxOrNot) { searchData.push({ name: name, operator: operator, value: value }); } // Look for sorting and store it in case we find one. var sortByVal = $(this).attr('data-sortBy'); var sortOrderVal = $(this).attr('data-sortOrder'); if (sortByVal !== undefined && sortByVal !== null) { sortBy = sortByVal.toString().trim(); } if (sortOrderVal !== undefined && sortOrderVal !== null) { sortOrderVal = sortOrderVal.toString().toLowerCase().trim(); if (sortOrderVal === 'asc' || sortOrderVal === 'desc') { sortOrder = sortOrderVal; } } }); latestSearchData = searchData; // Store latest search data. if (!conditions) conditions = []; $.merge(searchData, conditions); parseData(tableData, searchData); e.preventDefault(); }); } function parseData(data, searchData) { var tmpData = []; // Add SubID if (sub) { $.each(data, function () { if (($(this)[0].trackingUrl !== undefined && $(this)[0].trackingUrl.indexOf('&sub=' + sub) === -1) && ($(this)[0].realTrackingUrl === undefined || $(this)[0].realTrackingUrl.indexOf('&sub=' + sub) === -1)) { // Add SubID to tracking URL only if it haven't been done already. $(this)[0].trackingUrl = $(this)[0].trackingUrl + '&sub=' + sub; } }); } // Add media ID if (mediaId) { $.each(data, function () { if (($(this)[0].trackingUrl !== undefined && $(this)[0].trackingUrl.indexOf('&media_id=' + mediaId) === -1) && ($(this)[0].realTrackingUrl === undefined || $(this)[0].realTrackingUrl.indexOf('&media_id=' + mediaId) === -1)) { // Add media ID to tracking URL only if it haven't been done already. $(this)[0].trackingUrl = $(this)[0].trackingUrl + '&media_id=' + mediaId; } }); } // Cleanlinking cleanLink = JSON.parse(cleanLink); if (cleanLink) { $.each(data, function () { if ($(this)[0].cleanUrl && $(this)[0].trackingUrl !== undefined && $(this)[0].trackingUrl.indexOf($(this)[0].cleanUrl) === -1) { // Store real tracking URL and display clean URL instead. $(this)[0].realTrackingUrl = $(this)[0].trackingUrl; $(this)[0].trackingUrl = $(this)[0].cleanUrl; } }); // a click within the feed $(obj).on('mousedown click', function (event) { var target = $(event.target); var parent = $(target).parent(); // If target is a link then use that. // If not then check if the parent is a link with class 'btn-cta' and then use that parent. var link = null; if (target.is('a')) { link = target; } else if (parent.is('a') && (parent.hasClass('btn-cta') || parent.hasClass('as-link'))) { link = parent; } if (link !== null) { var clickUrl = link.prop('href').toLowerCase(); if (clickUrl.indexOf('cgi-bin/click.pl') !== -1) { // Cleanlink have already been switched with the real tracking link. return; } $.each(data, function () { var dataUrl = $(this)[0].trackingUrl.toLowerCase(); if (clickUrl.indexOf(dataUrl) !== -1) { link.prop('href', $(this)[0].realTrackingUrl); // change back to the cleanLink setTimeout(function () { link.prop('href', clickUrl); }, 500); // Return false to break out of the each loop. return false; } }); } }); } // Add labels to feed data if (labels) { $.each(data, function () { var label = labels[$(this)[0].id]; if (label !== undefined && label !== null) { // Merge label into feed data under 'customLabel' property. // Property name must be unique enough to avoid conflicts with // existing and future data fields. $(this)[0].customLabel = label; } }); } // Do low EPC campaign filtering // If minEpc is specifically set to 0 then no filtering is done. if (parseFloat(minEpc) !== 0) { // Make sure removedItems is initialized before using it. if (!removedItems) { removedItems = []; } if (!removedCampaigns) { removedCampaigns = []; } var lowEPCCampaigns = []; var availableCampaigns = []; // Make sure items are numbers. removedItems = $.map(removedItems, function (n) { return Number(n); }); removedCampaigns = $.map(removedCampaigns, function (n) { return Number(n); }); // Identify low EPC items and removed campaigns. $.each(data, function () { // Allow override of EPC limit and fallback to 0 if backend doesn't provide one. var limit = parseFloat(minEpc) || parseFloat($(this)[0].epcLimit) || 0; // Filter campaign if EPC is defined and below limit. if (Number($(this)[0].campEpc) >= 0 && Number($(this)[0].campEpc) < limit) { lowEPCCampaigns.push($(this)[0]); } if (removedCampaigns.indexOf(Number($(this)[0].campId)) === -1 && removedItems.indexOf(Number($(this)[0].id)) === -1) { availableCampaigns.push(Number($(this)[0].campId)); } }); // Get unique available campaigns. availableCampaigns = uniq(availableCampaigns); // Sort low EPC campaigns on EPC with lowest first. lowEPCCampaigns.sort(function (a, b) { if (a.campEpc < b.campEpc) return -1; if (a.campEpc > b.campEpc) return 1; return 0; }); // Map into array of just camp IDs. lowEPCCampaigns = $.map(lowEPCCampaigns, function (o) { return Number(o.campId); }); // Get only unique IDs. lowEPCCampaigns = uniq(lowEPCCampaigns); // Leave minimum 3 available campaigns. var availableForRemoval = Math.max(availableCampaigns.length - 3, 0); var end = Math.min(availableForRemoval, lowEPCCampaigns.length); // Adjust array to only those campaigns we can remove. var campaignsToRemove = lowEPCCampaigns.slice(0, end); $.merge(removedCampaigns, campaignsToRemove); } // If searchData, removedItems or removedCampaigns are defined. if (searchData || removedItems || removedCampaigns) { // loop through the tableData and searchData to generate a new data output $.each(data, function () { var dataObj = $(this)[0]; var valid = true; // if searchData if (searchData) { $.each(searchData, function () { var searchObj = $(this)[0]; // checking if dataObj's variable is null. // if null means filter failed and return valid false. if (dataObj[searchObj.name] === null || dataObj[searchObj.name] === undefined) { return valid = false; } // Always include data values '-' and '' as we don't know their true value. if (dataObj[searchObj.name].toString().trim() === "-" || dataObj[searchObj.name].toString().trim() === "") { return valid = true; } // Old plugin versions rely on '0' meaning always valid. if (useNewFiltering) { // Use negative numbers as a way to always include the data in the feed. if (Number(searchObj.value) < 0) { return valid = true; } } else { if (Number(searchObj.value) == 0) { return valid = true; } } switch (searchObj.operator) { case "lessThan": if (useNewConditions) { // Correctly filter data greater than for 'lessThan'. if (Number(dataObj[searchObj.name]) > Number(searchObj.value)) return valid = false; } else { // Old filtering for backwards compatibility. if (Number(dataObj[searchObj.name]) < Number(searchObj.value)) return valid = false; } break; case "greaterThan": if (useNewConditions) { // Correctly filter data less than for 'greaterThan'. if (Number(dataObj[searchObj.name]) < Number(searchObj.value)) return valid = false; } else { // Old filtering for backwards compatibility. if (Number(dataObj[searchObj.name]) > Number(searchObj.value)) return valid = false; } break; case "equals": if (dataObj[searchObj.name] != searchObj.value) return valid = false; break; case "notEquals": if (dataObj[searchObj.name] == searchObj.value) return valid = false; break; case "contains": if (dataObj[searchObj.name].toLowerCase().indexOf(searchObj.value.toLowerCase()) == -1) return valid = false; break; case "notContains": if (dataObj[searchObj.name].toLowerCase().indexOf(searchObj.value.toLowerCase()) != -1) return valid = false; break; } return valid = true; }); } // Filter removed items. if (removedItems) { $.each(removedItems, function () { var feedItemId = $(this)[0]; if (feedItemId == dataObj.id) return valid = false; }); } // Filter removed campaigns. if (removedCampaigns) { $.each(removedCampaigns, function () { var campId = $(this)[0]; if (campId == dataObj.campId) return valid = false; }); } if (valid) tmpData.push(dataObj); }); } else { tmpData = data; } /* Start of feed sorting. */ if (tmpData) { // sort the data according to rating and order tmpData.sort(function (a, b) { // get and set customRating if (customRating) { var tmpA = customRating[a.id]; var tmpB = customRating[b.id]; if (tmpA > 0) a.rating = tmpA; if (tmpB > 0) b.rating = tmpB; } // get and set customOrder if (customOrder) { var tmpA = customOrder[a.id]; var tmpB = customOrder[b.id]; if (tmpA > 0) a.order = tmpA; if (tmpB > 0) b.order = tmpB; } if (!a.rating) a.rating = 0; if (!b.rating) b.rating = 0; if (!a.order) a.order = 0; if (!b.order) b.order = 0; if (sortBy) { // Ordering from filter. if (sortOrder.toString().toLowerCase().trim() === 'desc') { // Get integer value or set to low number for invalid/undefined // values to sort those at the bottom. var tmpA = (isNaN(a[sortBy]) || a[sortBy].trim().length === 0) ? Number.NEGATIVE_INFINITY : Number(a[sortBy]); var tmpB = (isNaN(b[sortBy]) || b[sortBy].trim().length === 0) ? Number.NEGATIVE_INFINITY : Number(b[sortBy]); if (tmpA > tmpB) return -1; if (tmpA < tmpB) return 1; } else { // Always default to ascending even if sortOrder isn't set to 'asc'. // Get integer value or set to low number for invalid/undefined // values to sort those at the bottom. var tmpA = (isNaN(a[sortBy]) || a[sortBy].trim().length === 0) ? Number.POSITIVE_INFINITY : Number(a[sortBy]); var tmpB = (isNaN(b[sortBy]) || b[sortBy].trim().length === 0) ? Number.POSITIVE_INFINITY : Number(b[sortBy]); if (tmpA < tmpB) return -1; if (tmpA > tmpB) return 1; } } // Default ordering. // First based on customOrder, then on rating. if (Number(a.order) > Number(b.order)) return -1; if (Number(a.order) < Number(b.order)) return 1; if (Number(a.rating) > Number(b.rating)) return -1; if (Number(a.rating) < Number(b.rating)) return 1; return 0; }); // If there's only 1 element then sorting didn't run // so we will have to apply the custom rating here. if (tmpData.length === 1 && customRating) { var element = tmpData[0]; var rating = customRating[element.id]; element.rating = (rating >= 0) ? rating : element.rating; } // handle minRating if (minRating > 0) { $.each(tmpData, function () { var a = $(this)[0]; if (Number(a.rating) < Number(minRating)) a.rating = minRating; }); } } /* End of feed sorting. */ // Handle only showing one feed item per campaign if enabled. // This must happen after all other filtering/sorting have been // done to get the correct end result. if (oneItemPerCampaign) { var filteredData = []; var includedCampaigns = []; // Loop through data and include only the first feed item for each campaign. $.each(tmpData, function () { var dataObj = $(this)[0]; var campId = parseInt(dataObj.campId); // Check that campaign haven't been included yet, before adding data. if (includedCampaigns.indexOf(campId) === -1) { filteredData.push(dataObj); includedCampaigns.push(campId); } }); tmpData = filteredData; } // Save feed size for later so we know whether to display // the 'view more' button or not. var feedSize = tmpData.length; // Handle maxRows if (maxRows) tmpData = tmpData.slice(0, maxRows); // wait for the jquery.tmpl plugin to be loaded before using it checkReady(function ($) { // Register converter for HTML sanitizing. $.views.converters("safehtml", function (val) { if (val === undefined || val === null || val.toString().trim().length === 0) return val; if (window.DOMPurify === undefined) { // DOMPurify is not available so we fallback to HTML encoding. return $.views.converters.html(val); } else { return DOMPurify.sanitize(val); } }); obj.html($(template).render({ data: tmpData }, { numberWithCommas: numberWithCommas, extractUri: function (url) { var uri = /https?:\/\/.*?\/(.*)/g.exec(url); if (uri) uri = uri[1]; return uri; }, round: function (number) { return Math.round(number); }, roundTwoDecimals: roundTwoDecimals, replaceDotWithComma: replaceDotWithComma, getCountryCode: function () { return countryCode; }, showRating: function () { return showRating; }, getTranslation: getTranslation, calculateExpectedCost: calculateExpectedCost, //Only used in Finland, because Finland has different types of products calculateExpectedCostPrMonth: function(annualPrice, fixedFee) { var estMonthlyCost = Number(fixedFee) + Number(annualPrice) / 12; return estMonthlyCost; }, findPrice: function(a, b) { return Math.min(a, b); }, printInterval: function (lowerValue, upperValue, endToken, replaceDots, roundDecimals, useThousandSeparator, ignoreNaN) { var interval = '-'; var ignoreLowerValue = (ignoreNaN && isNaN(lowerValue) && lowerValue.toString().trim() !== '-'); var ignoreUpperValue = (ignoreNaN && isNaN(upperValue) && upperValue.toString().trim() !== '-'); // Parse values to make sure they are numbers. Skip if we should ignore non-numbers. if (!ignoreLowerValue) lowerValue = parseFloat(lowerValue); if (!ignoreUpperValue) upperValue = parseFloat(upperValue); // Round off decimals. if (roundDecimals && !ignoreLowerValue) lowerValue = roundTwoDecimals(lowerValue); if (roundDecimals && !ignoreUpperValue) upperValue = roundTwoDecimals(upperValue); if ((ignoreLowerValue || !isNaN(lowerValue)) && (ignoreUpperValue || !isNaN(upperValue))) { if (lowerValue === upperValue) { if (useThousandSeparator) lowerValue = numberWithCommas(lowerValue); interval = lowerValue; } else { if (useThousandSeparator) { lowerValue = numberWithCommas(lowerValue); upperValue = numberWithCommas(upperValue); } interval = lowerValue + ' - ' + upperValue; } } else if (ignoreLowerValue || !isNaN(lowerValue)) { if (useThousandSeparator) lowerValue = numberWithCommas(lowerValue); interval = lowerValue; } else if (ignoreUpperValue || !isNaN(upperValue)) { if (useThousandSeparator) upperValue = numberWithCommas(upperValue); interval = upperValue; } if (replaceDots && !useThousandSeparator) interval = replaceDotWithComma(interval); if (endToken && interval !== '-') interval += ' ' + endToken; return interval; }, isValidNumber: function (number) { return !isNaN(parseFloat(number)); }, isValid: function (string) { if (string === null || string === undefined) return false; return (string.toString().trim().length > 0 && string.toString().trim() !== '-'); }, printOrGetBoolean: function (string, getBoolean) { var result = (getBoolean) ? false : '-'; if (string === null || string === undefined) { return result; } if(string == '-') { return string; } if (string == 1 || string.toString().toLowerCase().indexOf('yes') !== -1 || string.toString().toLowerCase().indexOf('ja') !== -1) { result = (getBoolean) ? true : getTranslation('yes'); } else if (string == 0 || string.toString().toLowerCase().indexOf('no') !== -1 || string.toString().toLowerCase().indexOf('nej') !== -1) { result = (getBoolean) ? false : getTranslation('no'); } return result; }, printTimeUnit: function (timeUnit, defaultString, dayString, monthString) { if (timeUnit === null || timeUnit === undefined) { return defaultString; } var lowerTimeUnit = timeUnit.toString().toLowerCase(); if (lowerTimeUnit.indexOf('day') !== -1 || lowerTimeUnit.indexOf('dag') !== -1) { return dayString || getTranslation('days'); } else if (lowerTimeUnit.indexOf('month') !== -1 || lowerTimeUnit.indexOf('måned') !== -1) { return monthString || getTranslation('months'); } else { return defaultString; } }, getSavings: function (price, discountPrice) { price = parseFloat(price); discountPrice = parseFloat(discountPrice); if (price <= 0 || discountPrice <= 0 || discountPrice >= price) return 0; return Math.round(((price - discountPrice) / price) * 100); }, })); // Trigger 'renderedTemplate' event to mark that the feed has finished rendering. // This is used to initialize star ratings, tooltips, filter texts and values etc. obj.trigger('renderedTemplate'); // Check if feed size is larger than the number of rows we should display // If not then we don't need to display the 'view more' button. if (initialRows > 0 && feedSize > initialRows && (!(Number(initialMaxRows) > 0) || initialMaxRows > initialRows)) { $('#as-feed-view-more-button').css('display', 'block'); } else { $('#as-feed-view-more-button').css('display', 'none'); } }, function ($) { return loadedTmplPlugin; }); } function numberWithCommas(x) { var parts = x.toString().split("."); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, "."); return parts.join(","); } function roundTwoDecimals(number) { if (parseFloat(number) === parseInt(number)) { // Number doesn't contains decimals so just return it. return parseFloat(number); } else { // Number contains decimals, so round to two. return parseFloat(number).toFixed(2); } } function replaceDotWithComma(string) { return string.toString().replace(/\./g, ","); } // Get translation for the given string in the supplied country or in the publishers country // if no country is supplied. // All possible translations are listed in the translations variable. function getTranslation(string, country) { var myCountryCode = (country) ? country : countryCode; if (translations && translations[string]) { return translations[string][myCountryCode]; } else { return ''; } } function calculateExpectedCost(price, fixedFee) { var asEstUsagePrMonth; var selectedPoint; var amountOfPeople = $("#minPeople").val() || 1; asEstUsagePrMonth = [1000, 1200, 1400, 1600, 1800, 2000, 2200, 2400, 2600, 2800, 3000, 3200, 3400, 3600, 3800, 4000, 4200, 4400, 4600, 4800, 5000, 5200, 5400, 5600, 5800, 6000, 6200, 6400, 6600] selectedPoint = asEstUsagePrMonth[$("#estAmount").val()] || asEstUsagePrMonth[0]; return parseFloat(((parseFloat(price) * ( (parseFloat(amountOfPeople) * 240) + parseFloat(selectedPoint)) / 100)) / 12 + parseFloat(fixedFee)).toFixed(0); } function autoFill(searchForm, data) { if (searchForm) { $(searchForm).find('select[autoFill="true"]').each(function () { var select = $(this); /* Build array of values */ var values = []; $.each(data, function () { values.push($(this)[0][select.attr('name')]); }); values = uniq(values); /* Sort the values */ values.sort(function (a, b) { return a - b; }); /* append the values to the select */ $.each(values, function (e) { select.append(""); }); }); } } function checkReady(callback, cond) { if (cond()) { callback($); } else { window.setTimeout(function () { checkReady(callback, cond); }, 100); } } function filterNewCampaigns(feedData, customOrder) { var filteredData = []; var existingCampaigns = Object.keys(customOrder); // Only filter data if customOrder is non empty. if (existingCampaigns.length > 0) { // Loop through feed data and find the entries // that are defined in customOrder. $.each(feedData, function () { var dataObj = $(this)[0]; if (existingCampaigns.indexOf(dataObj.id.toString()) !== -1 || existingCampaigns.indexOf(dataObj.id) !== -1 ) { // Campaign exists in customOrder. Add to filteredData. filteredData.push(dataObj); } }); } else { filteredData = feedData; } return filteredData; } function checkRejected() { // Check if "rejected" set by predubid.pl is non-empty array. if (window.rejected && Array.isArray(window.rejected) && window.rejected.length > 0) { // Update removedCampaigns so we can hide them later on. removedCampaigns = window.rejected; // If feed data has already loaded then we most likely need to re-render. if (loadedFeed) { parseData(tableData, conditions); } } } function loadTranslations() { var cache = (nocache) ? '&nocache=' + nocache : ''; $.getJSON(serverUrl + translationUri + '?category_id=' + categoryId + '&pid=' + publisherId + cache, function (response) { if (response.data && response.data.countryCode) { var data = response.data; // Store country code, translations and categoryId internally and globally so they can be accessed with and without jsrender. countryCode = (countryCode) ? countryCode.toString().toUpperCase().trim() : data.countryCode; window.adserviceCountryCode = countryCode; translations = data.translations; window.adserviceTranslations = data.translations; window.adserviceCategoryId = categoryId; // Make getTranslation function available globally so it can be used in filters and other things outside the feed. window.adserviceGetTranslation = getTranslation; // Mark translations as loaded and render feed if data has finished loading. loadedTranslations = true; if (loadedFeed) { parseData(tableData, conditions); } } }); } // Energy/Electricity feed $('input[type=range]').on("input", function() { $(".as-energy-container").each(function (index) { var pricePoint = parseInt($(this).children().find('.as-energy-price-pr-kwh-value').html()); var fixedFee = parseInt($(this).children().find('.as-energy-fixed-fee').val()); $(this).children().find(".as-energy-kwh-price-per-month").html(calculateExpectedCost(pricePoint, fixedFee)); }) }) } // automatically create feeds from HTML parameters $(document).ready(function () { $('[data-adserviceFeed="true"]').each(function () { $(this).adserviceFeed(); }); var useMinValue = Number("0"); var useMin = (useMinValue !== 0); // Only add this eventlistener if you want to use the minified javascript, // since then we're sure they no longer use compare-theme.js, which contains the same code // and causes the 'read more' function to bug out. if (useMin) { /** Use the trigger for when the template is parsed and loaded to create the star-ratings. */ $('#tableHolder').on("renderedTemplate", function () { $('#loading').remove(); $('input[type="rating"]').rating({ max: 5, readonly: true, showClear: false, showCaption: false, glyphicon: false, size: "xxs" }); initReadMore('.as-read-more'); initReadMoreMag('.as-read-more2', '.as-read-more-data', '.ads-read-more-icon'); // Do filter translations. $('[data-adserviceTranslation]').each(function () { if (window.adserviceGetTranslation && typeof window.adserviceGetTranslation === 'function') { var translationName = this.getAttribute('data-adserviceTranslation'); var translation = adserviceGetTranslation(translationName); // Add the translation as inner html/value on the element. // We must set the value to work with input tags as well. $(this).html(translation); if ($(this).is('input')) { $(this).val(translation); } } }); // Show filter after translations are added and feed is rendered. $('#searchTable').css('display', 'block'); // Initianlize tooltips. if ($.fn.tooltip) { $('[data-toggle="tooltip"]').tooltip({ trigger: 'hover focus', viewport: { selector: '#tableHolder', padding: 10 } }); } // Find images with class 'as-lazy' for lazy loading. var lazyImages = [].slice.call(document.querySelectorAll("img.as-lazy")); // Check if any lazy images exists before handling. if (lazyImages.length > 0) { // Add 200px bottom buffer for images to load sooner // when scrolling down. var options = { rootMargin: "0px 0px 200px 0px" }; if ("IntersectionObserver" in window && window.adserviceLazyLoading) { var lazyImageObserver = new IntersectionObserver(function (entries, observer) { entries.forEach(function (entry) { if (entry.isIntersecting) { var lazyImage = entry.target; var newSrc = lazyImage.getAttribute('as-data-src'); if (newSrc !== undefined && newSrc !== null && newSrc.length > 0) { // Make sure as-data-src contains something before replacing src. lazyImage.setAttribute('src', newSrc); } lazyImage.className.replace(/\bas-lazy\b/g, ''); // Remove 'as-lazy' class. lazyImageObserver.unobserve(lazyImage); } }); }, options); lazyImages.forEach(function (lazyImage) { lazyImageObserver.observe(lazyImage); }); } else { // Fall back to no lazy loading. lazyImages.forEach(function (lazyImage) { var newSrc = lazyImage.getAttribute('as-data-src'); if (newSrc !== undefined && newSrc !== null && newSrc.length > 0) { // Make sure as-data-src contains something before replacing src. lazyImage.setAttribute('src', newSrc); } lazyImage.className.replace(/\bas-lazy\b/g, ''); }); } } // Initialize top 3 sliders. $('.comparisson-plg .as-top-3-container').each(function () { // Append slider indicator. $(this).parent().append( '
' + '' + '' + '' + '
' ); // Add click handler. $('.comparisson-plg .as-top-3-slider a').on('click', function (e) { $($(this).attr('data-id'))[0].scrollIntoView({ block: 'nearest', inline: 'nearest' }); $(this).siblings().removeClass('selected'); $(this).addClass('selected'); e.preventDefault(); return false; }); // Add observer to update indicator when scrolling. if ("IntersectionObserver" in window) { var observer = new IntersectionObserver(function (entries, observer) { // find the entry with the largest intersection ratio var activated = entries.reduce(function (max, entry) { return (entry.intersectionRatio > max.intersectionRatio) ? entry : max; }); if (activated.intersectionRatio > 0) { // Fallback to first entry if intersection ratio is less than 0.5 // This solves an issue where going from desktop to mobile size would // set the indicator to the 2nd entry because of how IntersectionObserver works. var id = (activated.intersectionRatio >= 0.5) ? activated.target.getAttribute('id') : 'as-top-3-id-0'; var indicator = $(activated.target).parent().parent().find('.as-top-3-slider a[data-id="#' + id + '"]'); indicator.siblings().removeClass('selected'); indicator.addClass('selected'); } }, { root: this, threshold: 0.5 }); $(this).children('.as-top-3-item').each(function () { observer.observe(this); }); } }); // Initialize custom styles. $('[as-data-style]').each(function () { var style = $(this).attr('as-data-style'); if (style !== undefined && style !== null && style.length > 0) { $(this).attr('style', style); } }); }); } // Always update the styles once. updateResposiveStyles(); // Re-update styles if the browser window is resized. var resizeTimer; $(window).resize(function () { clearTimeout(resizeTimer); resizeTimer = setTimeout(updateResposiveStyles, 100); }); }); function updateResposiveStyles() { if (750 < $('#tableHolder').width() && $('#tableHolder').width() < 970 && $(document).width() > 960) { $('.comparisson-plg').addClass('as-md-style'); $('.comparisson-plg').removeClass('as-sm-style'); $('.comparisson-plg').removeClass('as-xs-style'); } else if (560 < $('#tableHolder').width() && $('#tableHolder').width() <= 750 && $(document).width() > 960) { $('.comparisson-plg').addClass('as-sm-style'); $('.comparisson-plg').removeClass('as-md-style'); $('.comparisson-plg').removeClass('as-xs-style'); } else if ($('#tableHolder').width() <= 560 && $(document).width() > 960) { $('.comparisson-plg').addClass('as-xs-style'); $('.comparisson-plg').removeClass('as-md-style'); $('.comparisson-plg').removeClass('as-sm-style'); } else { $('.comparisson-plg').removeClass('as-md-style'); $('.comparisson-plg').removeClass('as-sm-style'); $('.comparisson-plg').removeClass('as-xs-style'); } } function initReadMore(el) { $(el).on('click', function (e) { e.preventDefault(); jQuerythis = $(this); var link = (jQuerythis.is('a')) ? jQuerythis : jQuerythis.find('a'); openText = link.data('open'); closeText = link.data('close'); jQueryitem = $(this).closest('.compare-row'); if (jQueryitem.hasClass('selected')) { jQueryitem.removeClass('selected'); } else { jQueryitem.addClass('selected'); } link.html((link.html() == closeText) ? openText : closeText); jQueryitem.find('.as-show-more-content').slideToggle('fast'); }); } function initReadMoreMag(ell, dataClass, iconClass) { $(ell).on('click', function (e) { e.preventDefault(); var jQuerythis = $(this); var dataElement = jQuerythis.find(dataClass); var openText = dataElement.data('open'); var closeText = dataElement.data('close'); var jQueryitem = $(this).closest('.compare-row'); jQueryitem.find(iconClass).each(function () { var icon = $(this); if (icon.hasClass('selected')) { icon.removeClass('selected'); } else { icon.addClass('selected'); } }); var wrapper = jQueryitem.find('.as-more-wrapper'); if (wrapper.hasClass('no-padding-top')) { wrapper.removeClass('no-padding-top'); } else { wrapper.addClass('no-padding-top'); } jQueryitem.find(dataClass).each(function () { $(this).html(($(this).html() == closeText) ? openText : closeText); }); jQueryitem.find('.as-show-more-content').slideToggle('fast'); }); } function jsonFix(json) { return json.replace(/(\d*\.*\d+):/gm, "\"$1\":"); } function uniq($this) { for (var i = 0, l = $this.length; i < l; ++i) { var item = $this[i]; var dublicateIdx = $this.indexOf(item, i + 1); while (dublicateIdx != -1) { $this.splice(dublicateIdx, 1); dublicateIdx = $this.indexOf(item, dublicateIdx); l--; } } return $this; } function getHashParam(name) { name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]"); var regexS = "[\\#&]" + name + "=([^&#]*)"; var regex = new RegExp(regexS); var results = regex.exec(window.location.hash); if (results == null) return ""; else return results[1]; } })(jQuery);