var p = { name: 'Unknown', version: 'Unknown', os: 'Unknown' }; if (typeof platform !== 'undefined') { p.name = platform.name; p.version = platform.version; p.os = platform.os.toString(); } if (['IE', 'Microsoft Edge', 'IE Mobile'].indexOf(p.name) > -1) { if (!getCookie('browserDismiss')) { var $browser_warning = $('
' + ' ' + 'Tautulli does not support Internet Explorer or Microsoft Edge! ' + 'Please use a different browser such as Chrome or Firefox.' + '' + '
'); $('body').prepend($browser_warning); var offset = $browser_warning.height(); warningOffset(offset); $browser_warning.one('click', 'button.close', function () { $browser_warning.remove(); warningOffset(-offset); setCookie('browserDismiss', 'true', 7); }); function warningOffset(offset) { var navbar = $('.navbar-fixed-top'); if (navbar.length) { navbar.offset({top: navbar.offset().top + offset}); } var container = $('.body-container'); if (container.length) { container.offset({top: container.offset().top + offset}); } } } } function initConfigCheckbox(elem, toggleElem, reverse) { toggleElem = (toggleElem === undefined) ? null : toggleElem; reverse = (reverse === undefined) ? false : reverse; var config = toggleElem ? $(toggleElem) : $(elem).closest('div').next(); config.addClass('hidden-settings'); if ($(elem).is(":checked")) { config.toggle(!reverse); } else { config.toggle(reverse); } $(elem).click(function () { var config = toggleElem ? $(toggleElem) : $(this).closest('div').next(); if ($(this).is(":checked")) { config.slideToggleBool(!reverse); } else { config.slideToggleBool(reverse); } }); } function refreshTab() { var url = $(location).attr('href'); var tabId = $('.ui-tabs-panel:visible').attr("id"); $('.ui-tabs-panel:visible').load(url + " #" + tabId, function () { initThisPage(); }); } function showMsg(msg, loader, timeout, ms, error) { var feedback = $("#ajaxMsg"); var update = $("#updatebar"); var token_error = $("#token_error_bar"); if (update.is(":visible") || token_error.is(":visible")) { var height = (update.is(":visible") ? update.height() : 0) + (token_error.is(":visible") ? token_error.height() : 0) + 35; feedback.css("bottom", height + "px"); } else { feedback.removeAttr("style"); } var message = $("
" + msg + "
"); if (loader) { message = $("
  " + msg + "
"); feedback.css("padding", "14px 10px"); } if (error) { feedback.css("background-color", "rgba(255,0,0,0.5)"); } $(feedback).html(message); feedback.fadeIn(); if (timeout) { setTimeout(function () { message.fadeOut(function () { $(this).remove(); feedback.fadeOut(); feedback.css("background-color", ""); }); }, ms); } } function confirmAjaxCall(url, msg, data, loader_msg, callback) { $("#confirm-message").html(msg); $('#confirm-modal').modal(); $('#confirm-modal').off('click', '#confirm-button').one('click', '#confirm-button', function () { if (loader_msg) { showMsg(loader_msg, true, false); } $.ajax({ url: url, type: 'POST', cache: false, async: true, data: data, complete: function (xhr, status) { var result = $.parseJSON(xhr.responseText); var msg = result.message; if (result.result == 'success') { showMsg('  ' + msg, false, true, 5000); } else { showMsg('  ' + msg, false, true, 5000, true); } if (typeof callback === "function") { callback(result); } } }); }); } function doAjaxCall(url, elem, reload, form, showMsg, callback) { // Set Message var feedback = (showMsg) ? $("#ajaxMsg") : $(); var update = $("#updatebar"); var token_error = $("#token_error_bar"); if (update.is(":visible") || token_error.is(":visible")) { var height = (update.is(":visible") ? update.height() : 0) + (token_error.is(":visible") ? token_error.height() : 0) + 35; feedback.css("bottom", height + "px"); } else { feedback.removeAttr("style"); } feedback.fadeIn(); // Get Form data var formID = "#" + url; var dataString; if (form === true) { dataString = $(formID).serialize(); } // Loader Image var loader = $("
  Saving...
"); // Data Success Message var dataSucces = $(elem).data('success'); if (typeof dataSucces === "undefined") { // Standard Message when variable is not set dataSucces = "Success!"; } // Data Error Message var dataError = $(elem).data('error'); if (typeof dataError === "undefined") { // Standard Message when variable is not set dataError = "There was an error"; } // Get Success & Error message from inline data, else use standard message var succesMsg = $("
  " + dataSucces + "
"); var errorMsg = $("
  " + dataError + "
"); // Check if checkbox is selected if (form) { if ($('td#select input[type=checkbox]').length > 0 && !$('td#select input[type=checkbox]').is(':checked') || $('#importLastFM #username:visible').length > 0 && $("#importLastFM #username").val().length === 0) { feedback.addClass('error'); $(feedback).prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(function () { $(this).remove(); feedback.fadeOut(function () { feedback.removeClass('error'); }); }); $(formID + " select").children('option[disabled=disabled]').attr('selected', 'selected'); }, 2000); return false; } } // Ajax Call $.ajax({ url: url, data: dataString, type: 'POST', beforeSend: function (jqXHR, settings) { // Start loader etc. feedback.prepend(loader); }, error: function (jqXHR, textStatus, errorThrown) { feedback.addClass('error'); feedback.prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(function () { $(this).remove(); feedback.fadeOut(function () { feedback.removeClass('error'); }); }); }, 2000); }, success: function (data, jqXHR) { feedback.prepend(succesMsg); feedback.addClass('success'); setTimeout(function (e) { succesMsg.fadeOut(function () { $(this).remove(); feedback.fadeOut(function () { feedback.removeClass('success'); }); if (reload === true) refreshSubmenu(); if (reload === "table") { refreshTable(); } if (reload === "tabs") refreshTab(); if (reload === "page") location.reload(); if (reload === "submenu&table") { refreshSubmenu(); refreshTable(); } if (form) { // Change the option to 'choose...' $(formID + " select").children('option[disabled=disabled]').attr( 'selected', 'selected'); } }); }, 2000); }, complete: function (jqXHR, textStatus) { // Remove loaders and stuff, ajax request is complete! $('.ajaxLoader-' + url).remove(); if (typeof callback === "function") { callback(jqXHR); } } }); } getBrowsePath = function (key, path, filter_ext) { var deferred = $.Deferred(); $.ajax({ url: 'browse_path', type: 'GET', data: { key: key, path: path, filter_ext: filter_ext }, success: function(data) { deferred.resolve(data); }, error: function() { deferred.reject(); } }); return deferred; }; function doSimpleAjaxCall(url) { $.ajax(url); } function resetFilters(text) { if ($(".dataTables_filter").length > 0) { $(".dataTables_filter input").attr("placeholder", "filter " + text + ""); } } function isPrivateIP(ip_address) { var defer = $.Deferred(); if (ipaddr.isValid(ip_address)) { var addr = ipaddr.process(ip_address); if (addr.range() === 'loopback' || addr.range() === 'private' || addr.range() === 'linkLocal') { defer.resolve(); } else { defer.reject(); } } else { defer.resolve('n/a'); } return defer.promise(); } function humanTime(seconds) { var d = Math.floor(moment.duration(seconds, 'seconds').asDays()); var h = Math.floor(moment.duration((seconds % 86400), 'seconds').asHours()); var m = Math.round(moment.duration(((seconds % 86400) % 3600), 'seconds').asMinutes()); var text = ''; if (d > 0) { text = '

' + d + '

day' + ((d > 1) ? 's' : '') + '

' + '

' + h + '

hr' + ((h > 1) ? 's' : '') + '

' + '

' + m + '

min' + ((m > 1) ? 's' : '') + '

'; } else if (h > 0) { text = '

' + h + '

hr' + ((h > 1) ? 's' : '') + '

' + '

' + m + '

min' + ((m > 1) ? 's' : '') + '

'; } else { text = '

' + m + '

min' + ((m > 1) ? 's' : '') + '

'; } return text } String.prototype.toProperCase = function () { return this.replace(/\w\S*/g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); }); }; function getPercent(value1, value2) { value1 = parseFloat(value1) | 0 value2 = parseFloat(value2) | 0 var percent = 0; if (value1 !== 0 && value2 !== 0) { percent = (value1 / value2) * 100 } return Math.round(percent) } function millisecondsToMinutes(ms, roundToMinute) { if (ms > 0) { var minutes = Math.floor(ms / 60000); var seconds = ((ms % 60000) / 1000).toFixed(0); if (roundToMinute) { return (seconds >= 30 ? (minutes + 1) : minutes); } else { return (seconds == 60 ? (minutes + 1) + ":00" : minutes + ":" + (seconds < 10 ? "0" : "") + seconds); } } else { if (roundToMinute) { return '0'; } else { return '0:00'; } } } function humanDuration(ms, sig='dhm', units='ms', return_seconds=300000) { var factors = { d: 86400000, h: 3600000, m: 60000, s: 1000, ms: 1 } ms = parseInt(ms); var d, h, m, s; if (ms > 0) { if (return_seconds && ms < return_seconds) { sig = 'dhms' } r = factors[sig.slice(-1)]; ms = Math.round(ms * factors[units] / r) * r; h = ms % factors['d']; d = Math.trunc(ms / factors['d']); m = h % factors['h']; h = Math.trunc(h / factors['h']); s = m % factors['m']; m = Math.trunc(m / factors['m']); ms = s % factors['s']; s = Math.trunc(s / factors['s']); var hd_list = []; if (sig >= 'd' && d > 0) { d = (sig === 'd' && h >= 12) ? d + 1 : d; hd_list.push(d.toString() + ' day' + ((d > 1) ? 's' : '')); } if (sig >= 'dh' && h > 0) { h = (sig === 'dh' && m >= 30) ? h + 1 : h; hd_list.push(h.toString() + ' hr' + ((h > 1) ? 's' : '')); } if (sig >= 'dhm' && m > 0) { m = (sig === 'dhm' && s >= 30) ? m + 1 : m; hd_list.push(m.toString() + ' min' + ((m > 1) ? 's' : '')); } if (sig >= 'dhms' && s > 0) { hd_list.push(s.toString() + ' sec' + ((s > 1) ? 's' : '')); } return hd_list.join(' ') } else { return '0' } } // Our countdown plugin takes a callback, a duration, and an optional message $.fn.countdown = function (callback, duration, message) { // If no message is provided, we use an empty string message = message || ""; // Get reference to container, and set initial content var container = $(this[0]).html(duration + message); // Get reference to the interval doing the countdown var countdown = setInterval(function () { // If seconds remain if (--duration) { // Update our container's message container.html(duration + message); // Otherwise } else { // Clear the countdown interval clearInterval(countdown); // And fire the callback passing our container as `this` callback.call(container); } // Run interval every 1000ms (1 second) }, 1000); }; function setCookie(cname, cvalue, exdays) { var d = new Date(); d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); var expires = "expires=" + d.toUTCString(); document.cookie = cname + "=" + cvalue + "; " + expires; } function getCookie(cname) { var name = cname + "="; var ca = document.cookie.split(';'); for (var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') c = c.substring(1); if (c.indexOf(name) === 0) return c.substring(name.length, c.length); } return ""; } var Accordion = function (el, multiple, close) { this.el = el || {}; this.multiple = multiple || false; this.close = (close === undefined) ? true : close; // Variables privadas var links = this.el.find('.link'); // Evento links.on('click', { el: this.el, multiple: this.multiple, close: this.close }, this.dropdown); }; Accordion.prototype.dropdown = function (e) { var $el = e.data.el; $this = $(this); $next = $this.next(); if (!e.data.close && $this.parent().hasClass('open')) { return } $next.slideToggle(); $this.parent().toggleClass('open'); if (!e.data.multiple) { $el.find('.submenu').not($next).slideUp().parent().removeClass('open'); } }; function clearSearchButton(tableName, table) { $('#' + tableName + '_filter').find('input[type=search]').wrap( '
').after( ''); $('#clear-search-' + tableName).click(function () { table.search('').draw(); }); } // Taken from https://github.com/Hellowlol/HTPC-Manager window.onerror = function (message, file, line) { var e = { 'page': window.location.href, 'message': message, 'file': file, 'line': line }; $.post("log_js_errors", e, function (data) { }); }; $('*').on('click', '.refresh_pms_image', function (e) { e.preventDefault(); e.stopPropagation(); var background_div = $(this).parent().siblings(['style*=pms_image_proxy']).first(); var pms_proxy_url = background_div.css('background-image'); pms_proxy_url = /^url\((['"]?)(.*)\1\)$/.exec(pms_proxy_url); pms_proxy_url = pms_proxy_url ? pms_proxy_url[2] : ""; // If matched, retrieve url, otherwise "" if (pms_proxy_url.indexOf('pms_image_proxy') === -1) { console.log('PMS image proxy url not found.'); } else { background_div.css('background-image', 'none') $.ajax({ url: pms_proxy_url, headers: { 'Cache-Control': 'no-cache' }, success: function () { background_div.css('background-image', 'url(' + pms_proxy_url + ')'); } }); } }); // Taken from http://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable#answer-14919494 function humanFileSize(bytes, si = true) { //var thresh = si ? 1000 : 1024; var thresh = 1024; // Always divide by 2^10 but display SI units if (Math.abs(bytes) < thresh) { return bytes + ' B'; } var units = si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; var u = -1; do { bytes /= thresh; ++u; } while (Math.abs(bytes) >= thresh && u < units.length - 1); return bytes.toFixed(1) + ' ' + units[u]; } // Force max/min in number inputs function forceMinMax(elem) { var min = parseInt(elem.attr('min')); var max = parseInt(elem.attr('max')); var val = parseInt(elem.val()); var default_val = parseInt(elem.data('default')); if (isNaN(val)) { elem.val(default_val); } else if (min !== undefined && val < min) { elem.val(min); } else if (max !== undefined && val > max) { elem.val(max); } else { elem.val(val); } } function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } $.fn.slideToggleBool = function(bool, options) { return bool ? $(this).slideDown(options) : $(this).slideUp(options); }; function openPlexXML(endpoint, plextv, params) { var data = $.extend({endpoint: endpoint, plextv: plextv || false}, params); var query = new URLSearchParams(data) window.open('open_plex_xml?' + query.toString(), '_blank'); } function PopupCenter(url, title, w, h) { // Fixes dual-screen position Most browsers Firefox var dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : window.screenX; var dualScreenTop = window.screenTop != undefined ? window.screenTop : window.screenY; var width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width; var height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height; var left = ((width / 2) - (w / 2)) + dualScreenLeft; var top = ((height / 2) - (h / 2)) + dualScreenTop; var newWindow = window.open(url, title, 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left); // Puts focus on the newWindow if (window.focus) { newWindow.focus(); } return newWindow; } function setLocalStorage(key, value, path) { var key_path = key; if (path !== false) { key_path = key_path + '_' + window.location.pathname; } localStorage.setItem(key_path, value); } function getLocalStorage(key, default_value, path) { var key_path = key; if (path !== false) { key_path = key_path + '_' + window.location.pathname; } var value = localStorage.getItem(key_path); if (value !== null) { return value } else if (default_value !== undefined) { setLocalStorage(key, default_value, path); return default_value } } function uuidv4() { return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function(c) { var cryptoObj = window.crypto || window.msCrypto; // for IE 11 return (c ^ cryptoObj.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) }); } function getPlexHeaders(clientID) { return { 'Accept': 'application/json', 'X-Plex-Product': 'Tautulli', 'X-Plex-Version': 'Plex OAuth', 'X-Plex-Client-Identifier': clientID ? clientID : getLocalStorage('Tautulli_ClientID', uuidv4(), false), 'X-Plex-Platform': p.name, 'X-Plex-Platform-Version': p.version, 'X-Plex-Model': 'Plex OAuth', 'X-Plex-Device': p.os, 'X-Plex-Device-Name': p.name + ' (Tautulli)', 'X-Plex-Device-Screen-Resolution': window.screen.width + 'x' + window.screen.height, 'X-Plex-Language': 'en' }; } var plex_oauth_window = null; const plex_oauth_loader = '' + '
' + '
' + '' + '
' + 'Redirecting to the Plex login page...' + '
' + '
'; function closePlexOAuthWindow() { if (plex_oauth_window) { plex_oauth_window.close(); } } getPlexOAuthPin = function (clientID) { var x_plex_headers = getPlexHeaders(clientID); var deferred = $.Deferred(); $.ajax({ url: 'https://plex.tv/api/v2/pins?strong=true', type: 'POST', headers: x_plex_headers, success: function(data) { deferred.resolve({pin: data.id, code: data.code}); }, error: function() { closePlexOAuthWindow(); deferred.reject(); } }); return deferred; }; var polling = null; function PlexOAuth(success, error, pre, clientID) { if (typeof pre === "function") { pre() } closePlexOAuthWindow(); plex_oauth_window = PopupCenter('', 'Plex-OAuth', 600, 700); $(plex_oauth_window.document.body).html(plex_oauth_loader); getPlexOAuthPin(clientID).then(function (data) { var x_plex_headers = getPlexHeaders(clientID); const pin = data.pin; const code = data.code; var oauth_params = { 'clientID': x_plex_headers['X-Plex-Client-Identifier'], 'context[device][product]': x_plex_headers['X-Plex-Product'], 'context[device][version]': x_plex_headers['X-Plex-Version'], 'context[device][platform]': x_plex_headers['X-Plex-Platform'], 'context[device][platformVersion]': x_plex_headers['X-Plex-Platform-Version'], 'context[device][device]': x_plex_headers['X-Plex-Device'], 'context[device][deviceName]': x_plex_headers['X-Plex-Device-Name'], 'context[device][model]': x_plex_headers['X-Plex-Model'], 'context[device][screenResolution]': x_plex_headers['X-Plex-Device-Screen-Resolution'], 'context[device][layout]': 'desktop', 'code': code } plex_oauth_window.location = 'https://app.plex.tv/auth/#!?' + encodeData(oauth_params); polling = pin; (function poll() { $.ajax({ url: 'https://plex.tv/api/v2/pins/' + pin, type: 'GET', headers: x_plex_headers, success: function (data) { if (data.authToken){ closePlexOAuthWindow(); if (typeof success === "function") { success(data.authToken) } } }, error: function (jqXHR, textStatus, errorThrown) { if (textStatus !== "timeout") { closePlexOAuthWindow(); if (typeof error === "function") { error() } } }, complete: function () { if (!plex_oauth_window.closed && polling === pin){ setTimeout(function() {poll()}, 1000); } }, timeout: 10000 }); })(); }, function () { closePlexOAuthWindow(); if (typeof error === "function") { error() } }); } function encodeData(data) { return Object.keys(data).map(function(key) { return [key, data[key]].map(encodeURIComponent).join("="); }).join("&"); } function page(endpoint, ...args) { let endpoints = { 'pms_image_proxy': pms_image_proxy, 'info': info_page, 'library': library_page, 'user': user_page }; var params = {}; if (endpoint in endpoints) { params = endpoints[endpoint](...args); } return endpoint + '?' + $.param(params).replace(/'/g, '%27'); } function pms_image_proxy(img, rating_key, width, height, opacity, background, blur, fallback, refresh, clip, img_format) { var params = {}; if (img != null) { params.img = img; } if (rating_key != null) { params.rating_key = rating_key; } if (width != null) { params.width = width; } if (height != null) { params.height = height; } if (opacity != null) { params.opacity = opacity; } if (background != null) { params.background = background; } if (blur != null) { params.blur = blur; } if (fallback != null) { params.fallback = fallback; } if (refresh != null) { params.refresh = true; } if (clip != null) { params.clip = true; } if (img_format != null) { params.img_format = img_format; } return params; } function info_page(rating_key, guid, history, live) { var params = {}; if (live && history) { params.guid = guid; } else { params.rating_key = rating_key; } if (history) { params.source = 'history'; } return params; } function library_page(section_id) { var params = {}; if (section_id != null) { params.section_id = section_id; } return params; } function user_page(user_id, user) { var params = {}; if (user_id != null) { params.user_id = user_id; } if (user != null) { params.user = user; } return params; } MEDIA_TYPE_HEADERS = { 'movie': 'Movies', 'show': 'TV Shows', 'season': 'Seasons', 'episode': 'Episodes', 'artist': 'Artists', 'album': 'Albums', 'track': 'Tracks', 'video': 'Videos', 'audio': 'Tracks', 'photo': 'Photos' } function short_season(title) { if (title.startsWith('Season ') && /^\d+$/.test(title.substring(7))) { return 'S' + title.substring(7) } return title } function loadAllBlurHash() { $('[data-blurhash]').each(function() { const elem = $(this); const src = elem.data('blurhash'); loadBlurHash(elem, src); }); } function loadBlurHash(elem, src) { const img = new Image(); img.src = src; img.onload = () => { const imgData = blurhash.getImageData(img); blurhash .encodePromise(imgData, img.width, img.height, 4, 4) .then(hash => { return blurhash.decodePromise( hash, img.width, img.height ); }) .then(blurhashImgData => { const imgObject = blurhash.getImageDataAsImage( blurhashImgData, img.width, img.height, (event, imgObject) => { elem.css('background-image', 'url(' + imgObject.src + ')') } ); }); } } function _toggleRevealToken(elem, click) { var input = elem.parent().siblings('input'); if ((input.prop('type') === 'password' && click) || !input.val()) { input.prop('type', 'text'); elem.children('.fa').removeClass('fa-eye-slash').addClass('fa-eye'); } else { input.prop('type', 'password'); elem.children('.fa').removeClass('fa-eye').addClass('fa-eye-slash'); } } function toggleRevealTokens() { $('.reveal-token').each(function () { _toggleRevealToken($(this)); }); } $('body').on('click', '.reveal-token', function() { _toggleRevealToken($(this), true); }); // https://stackoverflow.com/a/57414592 // prevent modal close when click starts in modal and ends on backdrop $(document).on('mousedown', '.modal', function(e){ window.clickStartedInModal = $(e.target).is('.modal-dialog *'); }); $(document).on('mouseup', '.modal', function(e){ if(!$(e.target).is('.modal-dialog *') && window.clickStartedInModal) { window.preventModalClose = true; } }); $('.modal').on('hide.bs.modal', function (e) { if(window.preventModalClose){ window.preventModalClose = false; return false; } }); $.fn.hasScrollBar = function() { return this.get(0).scrollHeight > this.get(0).clientHeight; } function paginateScroller(scrollerId, buttonClass) { $(buttonClass).click(function (e) { e.preventDefault(); var scroller = $(scrollerId + "-row-scroller"); var scrollerParent = scroller.parent(); var containerWidth = scrollerParent.width(); var scrollCurrent = scrollerParent.scrollLeft(); var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175; var scrollMax = scroller.width() - Math.abs(scrollAmount); var scrollTotal = Math.min(parseInt(scrollCurrent / 175) * 175 + scrollAmount, scrollMax); scrollerParent.animate({ scrollLeft: scrollTotal }, 250); }); } function highlightScrollerButton(scrollerId) { var scroller = $(scrollerId + "-row-scroller"); var scrollerParent = scroller.parent(); var buttonLeft = $(scrollerId + "-page-left"); var buttonRight = $(scrollerId + "-page-right"); var numElems = scroller.find("li").length; scroller.width(numElems * 175); $(buttonLeft).addClass("disabled").blur(); if (scroller.width() > scrollerParent.width()) { $(buttonRight).removeClass("disabled"); } else { $(buttonRight).addClass("disabled"); } scrollerParent.scroll(function () { var scrollCurrent = $(this).scrollLeft(); var scrollMax = scroller.width() - $(this).width(); if (scrollCurrent == 0) { $(buttonLeft).addClass("disabled").blur(); } else { $(buttonLeft).removeClass("disabled"); } if (scrollCurrent >= scrollMax) { $(buttonRight).addClass("disabled").blur(); } else { $(buttonRight).removeClass("disabled"); } }); }