/*!
* 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);