Check if my website is open in another tab - javascript

I want to check with JavaScript if the user has already opened my website in another tab in their browser.
It seems I cannot do that with pagevisibility...
The only way I see is to use WebSocket based on a session cookie, and check if the client has more than one socket. But by this way, from current tab, I have to ask my server if this user has a tab opened right next to their current browser tab. It is a little far-fetched!
Maybe with localstorage?

The shorter version with localStorage and Storage listener
<script type="text/javascript">
// Broadcast that you're opening a page.
localStorage.openpages = Date.now();
var onLocalStorageEvent = function(e){
if(e.key == "openpages"){
// Listen if anybody else is opening the same page!
localStorage.page_available = Date.now();
}
if(e.key == "page_available"){
alert("One more page already open");
}
};
window.addEventListener('storage', onLocalStorageEvent, false);
</script>
Update:
Works on page crash as well.
Stimulate page crash in chrome: chrome://inducebrowsercrashforrealz
Live demo

Using local storage I created a simple demo that should accomplish what your looking to do. Basically, it simply maintains a count of currently opened windows. When the window is closed the unload events fire and remove it from the total window count.
When you first look at it, you may think there's more going on than there really is. Most of it was a shotty attempt to add logic into who was the "main" window, and who should take over as the "main" window as you closed children. (Hence the setTimeout calls to recheck if it should be promoted to a main window) After some head scratching, I decided it would take too much time to implement and was outside the scope of this question. However, if you have two windows open (Main, and Child) and you close the Main, the child will be promoted to a main.
For the most part you should be able to get the general idea of whats going on and use it for your own implementation.
See it all in action here:
http://jsbin.com/mipanuro/1/edit
Oh yeah, to actually see it in action... Open the link in multiple windows. :)
Update:
I've made the necessary changes to have the the local storage maintain the "main" window. As you close tabs child windows can then become promoted to a main window. There are two ways to control the "main" window state through a parameter passed to the constructor of WindowStateManager. This implementation is much nicer than my previous attempt.
JavaScript:
// noprotect
var statusWindow = document.getElementById('status');
(function (win)
{
//Private variables
var _LOCALSTORAGE_KEY = 'WINDOW_VALIDATION';
var RECHECK_WINDOW_DELAY_MS = 100;
var _initialized = false;
var _isMainWindow = false;
var _unloaded = false;
var _windowArray;
var _windowId;
var _isNewWindowPromotedToMain = false;
var _onWindowUpdated;
function WindowStateManager(isNewWindowPromotedToMain, onWindowUpdated)
{
//this.resetWindows();
_onWindowUpdated = onWindowUpdated;
_isNewWindowPromotedToMain = isNewWindowPromotedToMain;
_windowId = Date.now().toString();
bindUnload();
determineWindowState.call(this);
_initialized = true;
_onWindowUpdated.call(this);
}
//Determine the state of the window
//If its a main or child window
function determineWindowState()
{
var self = this;
var _previousState = _isMainWindow;
_windowArray = localStorage.getItem(_LOCALSTORAGE_KEY);
if (_windowArray === null || _windowArray === "NaN")
{
_windowArray = [];
}
else
{
_windowArray = JSON.parse(_windowArray);
}
if (_initialized)
{
//Determine if this window should be promoted
if (_windowArray.length <= 1 ||
(_isNewWindowPromotedToMain ? _windowArray[_windowArray.length - 1] : _windowArray[0]) === _windowId)
{
_isMainWindow = true;
}
else
{
_isMainWindow = false;
}
}
else
{
if (_windowArray.length === 0)
{
_isMainWindow = true;
_windowArray[0] = _windowId;
localStorage.setItem(_LOCALSTORAGE_KEY, JSON.stringify(_windowArray));
}
else
{
_isMainWindow = false;
_windowArray.push(_windowId);
localStorage.setItem(_LOCALSTORAGE_KEY, JSON.stringify(_windowArray));
}
}
//If the window state has been updated invoke callback
if (_previousState !== _isMainWindow)
{
_onWindowUpdated.call(this);
}
//Perform a recheck of the window on a delay
setTimeout(function()
{
determineWindowState.call(self);
}, RECHECK_WINDOW_DELAY_MS);
}
//Remove the window from the global count
function removeWindow()
{
var __windowArray = JSON.parse(localStorage.getItem(_LOCALSTORAGE_KEY));
for (var i = 0, length = __windowArray.length; i < length; i++)
{
if (__windowArray[i] === _windowId)
{
__windowArray.splice(i, 1);
break;
}
}
//Update the local storage with the new array
localStorage.setItem(_LOCALSTORAGE_KEY, JSON.stringify(__windowArray));
}
//Bind unloading events
function bindUnload()
{
win.addEventListener('beforeunload', function ()
{
if (!_unloaded)
{
removeWindow();
}
});
win.addEventListener('unload', function ()
{
if (!_unloaded)
{
removeWindow();
}
});
}
WindowStateManager.prototype.isMainWindow = function ()
{
return _isMainWindow;
};
WindowStateManager.prototype.resetWindows = function ()
{
localStorage.removeItem(_LOCALSTORAGE_KEY);
};
win.WindowStateManager = WindowStateManager;
})(window);
var WindowStateManager = new WindowStateManager(false, windowUpdated);
function windowUpdated()
{
//"this" is a reference to the WindowStateManager
statusWindow.className = (this.isMainWindow() ? 'main' : 'child');
}
//Resets the count in case something goes wrong in code
//WindowStateManager.resetWindows()
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div id='status'>
<span class='mainWindow'>Main Window</span>
<span class='childWindow'>Child Window</span>
</div>
</body>
</html>
CSS:
#status
{
display:table;
width:100%;
height:500px;
border:1px solid black;
}
span
{
vertical-align:middle;
text-align:center;
margin:0 auto;
font-size:50px;
font-family:arial;
color:#ba3fa3;
display:none;
}
#status.main .mainWindow,
#status.child .childWindow
{
display:table-cell;
}
.mainWindow
{
background-color:#22d86e;
}
.childWindow
{
background-color:#70aeff;
}

(2021) You can use BroadcastChannel to communicate between tabs of the same origin.
For example, put the following at the top level of your js code, then test by opening 2 tabs:
const bc = new BroadcastChannel("my-awesome-site");
bc.onmessage = (event) => {
if (event.data === `Am I the first?`) {
bc.postMessage(`No you're not.`);
alert(`Another tab of this site just got opened`);
}
if (event.data === `No you're not.`) {
alert(`An instance of this site is already running`);
}
};
bc.postMessage(`Am I the first?`);

I know it is late, but maybe help someone
This snippet of code, will detect how many tabs are open and how many are active (visible) and if none of tabs is active, it will choose last opened tab, as active one.
This code will handle windows/tab crash too and it will refresh the count at crash.
Because localStorage is not supported on Stack Overflow currently, please test here.
<html>
<body>
Open in several tabs or windows
<div id="holder_element"></div>
<script type="text/javascript">
//localStorage.clear();
manage_crash();
//Create a windows ID for each windows that is oppened
var current_window_id = Date.now() + "";//convert to string
var time_period = 3000;//ms
//Check to see if PageVisibility API is supported or not
var PV_API = page_visibility_API_check();
/************************
** PAGE VISIBILITY API **
*************************/
function page_visibility_API_check ()
{
var page_visibility_API = false;
var visibility_change_handler = false;
if ('hidden' in document)
{
page_visibility_API = 'hidden';
visibility_change_handler = 'visibilitychange';
}
else
{
var prefixes = ['webkit','moz','ms','o'];
//loop over all the known prefixes
for (var i = 0; i < prefixes.length; i++){
if ((prefixes[i] + 'Hidden') in document)
{
page_visibility_API = prefixes[i] + 'Hidden';
visibility_change_handler = prefixes[i] + 'visibilitychange';
}
}
}
if (!page_visibility_API)
{
//PageVisibility API is not supported in this device
return page_visibility_API;
}
return {"hidden": page_visibility_API, "handler": visibility_change_handler};
}
if (PV_API)
{
document.addEventListener(PV_API.handler, function(){
//console.log("current_window_id", current_window_id, "document[PV_API.hidden]", document[PV_API.hidden]);
if (document[PV_API.hidden])
{
//windows is hidden now
remove_from_active_windows(current_window_id);
//skip_once = true;
}
else
{
//windows is visible now
//add_to_active_windows(current_window_id);
//skip_once = false;
check_current_window_status ();
}
}, false);
}
/********************************************
** ADD CURRENT WINDOW TO main_windows LIST **
*********************************************/
add_to_main_windows_list(current_window_id);
//update active_window to current window
localStorage.active_window = current_window_id;
/**************************************************************************
** REMOVE CURRENT WINDOWS FROM THE main_windows LIST ON CLOSE OR REFRESH **
***************************************************************************/
window.addEventListener('beforeunload', function ()
{
remove_from_main_windows_list(current_window_id);
});
/*****************************
** ADD TO main_windows LIST **
******************************/
function add_to_main_windows_list(window_id)
{
var temp_main_windows_list = get_main_windows_list();
var index = temp_main_windows_list.indexOf(window_id);
if (index < 0)
{
//this windows is not in the list currently
temp_main_windows_list.push(window_id);
}
localStorage.main_windows = temp_main_windows_list.join(",");
return temp_main_windows_list;
}
/**************************
** GET main_windows LIST **
***************************/
function get_main_windows_list()
{
var temp_main_windows_list = [];
if (localStorage.main_windows)
{
temp_main_windows_list = (localStorage.main_windows).split(",");
}
return temp_main_windows_list;
}
/**********************************************
** REMOVE WINDOWS FROM THE main_windows LIST **
***********************************************/
function remove_from_main_windows_list(window_id)
{
var temp_main_windows_list = [];
if (localStorage.main_windows)
{
temp_main_windows_list = (localStorage.main_windows).split(",");
}
var index = temp_main_windows_list.indexOf(window_id);
if (index > -1) {
temp_main_windows_list.splice(index, 1);
}
localStorage.main_windows = temp_main_windows_list.join(",");
//remove from active windows too
remove_from_active_windows(window_id);
return temp_main_windows_list;
}
/**************************
** GET active_windows LIST **
***************************/
function get_active_windows_list()
{
var temp_active_windows_list = [];
if (localStorage.actived_windows)
{
temp_active_windows_list = (localStorage.actived_windows).split(",");
}
return temp_active_windows_list;
}
/*************************************
** REMOVE FROM actived_windows LIST **
**************************************/
function remove_from_active_windows(window_id)
{
var temp_active_windows_list = get_active_windows_list();
var index = temp_active_windows_list.indexOf(window_id);
if (index > -1) {
temp_active_windows_list.splice(index, 1);
}
localStorage.actived_windows = temp_active_windows_list.join(",");
return temp_active_windows_list;
}
/********************************
** ADD TO actived_windows LIST **
*********************************/
function add_to_active_windows(window_id)
{
var temp_active_windows_list = get_active_windows_list();
var index = temp_active_windows_list.indexOf(window_id);
if (index < 0)
{
//this windows is not in active list currently
temp_active_windows_list.push(window_id);
}
localStorage.actived_windows = temp_active_windows_list.join(",");
return temp_active_windows_list;
}
/*****************
** MANAGE CRASH **
******************/
//If the last update didn't happened recently (more than time_period*2)
//we will clear saved localStorage's data and reload the page
function manage_crash()
{
if (localStorage.last_update)
{
if (parseInt(localStorage.last_update) + (time_period * 2) < Date.now())
{
//seems a crash came! who knows!?
//localStorage.clear();
localStorage.removeItem('main_windows');
localStorage.removeItem('actived_windows');
localStorage.removeItem('active_window');
localStorage.removeItem('last_update');
location.reload();
}
}
}
/********************************
** CHECK CURRENT WINDOW STATUS **
*********************************/
function check_current_window_status(test)
{
manage_crash();
if (PV_API)
{
var active_status = "Inactive";
var windows_list = get_main_windows_list();
var active_windows_list = get_active_windows_list();
if (windows_list.indexOf(localStorage.active_window) < 0)
{
//last actived windows is not alive anymore!
//remove_from_main_windows_list(localStorage.active_window);
//set the last added window, as active_window
localStorage.active_window = windows_list[windows_list.length - 1];
}
if (! document[PV_API.hidden])
{
//Window's page is visible
localStorage.active_window = current_window_id;
}
if (localStorage.active_window == current_window_id)
{
active_status = "Active";
}
if (active_status == "Active")
{
active_windows_list = add_to_active_windows(current_window_id);
}
else
{
active_windows_list = remove_from_active_windows(current_window_id);
}
console.log(test, active_windows_list);
var element_holder = document.getElementById("holder_element");
element_holder.insertAdjacentHTML("afterbegin", "<div>"+element_holder.childElementCount+") Current Windows is "+ active_status +" "+active_windows_list.length+" window(s) is visible and active of "+ windows_list.length +" windows</div>");
}
else
{
console.log("PageVisibility API is not supported :(");
//our INACTIVE pages, will remain INACTIVE forever, you need to make some action in this case!
}
localStorage.last_update = Date.now();
}
//check storage continuously
setInterval(function(){
check_current_window_status ();
}, time_period);
//initial check
check_current_window_status ();
</script>
</body>
</html>

Related

Hide div if agents offline with Live Chat Inc

I am using LiveChat on my website and trying to show a div if no agents are available, using their guide here
Javascript
LC_API.on_after_load = function() {
if (LC_API.agents_are_available()) {
$("#ChatLink2").hide();
} else {
$("#ChatLink2").show();
}
};
HTML
<div class='ChatLink2' id='ChatLink2'>Currently Unavailable</div>
However, the div ChatLink2 is not showing when no agents are available
It appears to be race conditions with both LiveChat and JQuery. Below checks for LC availability and uses vanilla JS instead of jQuery.
Also included state change callback if agents go offline after init.
var waitForLC = setInterval(function () {
if (window.LC_API === undefined) {
return;
}
clearInterval(waitForLC);
var showUnavailableStatus = function(show){
var statusDiv = document.getElementById("ChatLink2");
var style = show ? "block" : "none";
statusDiv.style.display = style;
}
LC_API.on_after_load = function() {
if (LC_API.agents_are_available()) {
showUnavailableStatus(false);
} else {
showUnavailableStatus(true);
}
};
LC_API.on_chat_state_changed = function(data) {
showUnavailableStatus(data.state==="offline");
};
}, 100);

How can I recreate Squarespace's Product Quick View in standard Product View?

Overview
So I'm trying to take functionality from one part of Squarespace's Galapagos commerce template and add it to another but it's proving to be more difficult than I thought.
I need the image-swapping capability of the "Quick View" (example - mouse over any image and click Quick View ) to replace the column of full sized zoomable images in the "Product View" (example - you see this once you click on a product).
So I found the code for each section:
Product View
This code simply goes through each image in the array and spits it out with the id jsProductItemImages which allows it to be hovered and zoomed.
<div class="productitem-images" id="jsProductItemImages">
{.repeated section items}
{.image?}
<div class="productitem-image-zoom-wrapper sqs-image-zoom-area"><img data-load="false" class="productitem-image loading" {#|image-meta} /></div>
{.end}
{.video?}
{#|video}
{.end}
{.end}
</div>
Quick View
I'm not 100% on the logic here, but essentially it's grabbing the first image and making it a hover/zoomable primary image then listing the entire array of images beneath it as thumbnails. I read that the # symbol is the equivalent of saying this in javascript, but I don't get why it's being used to spit out only the first image in the array.
<figure class="ProductItem-gallery">
{.section items}
<div class="ProductItem-gallery-slides">
{.repeated section #}
{.image?}
<div class="ProductItem-gallery-slides-item" data-slide-index="{#index}"><img class="ProductItem-gallery-slides-item-image" data-load="false" {#|image-meta} /></div>
{.end}
{.video?}
{#|video}
{.end}
{.end}
</div>
{.end}
<div class="ProductItem-gallery-thumbnails">
{.if items.1}{.repeated section items}<div class="ProductItem-gallery-thumbnails-item"><img class="ProductItem-gallery-thumbnails-item-image" data-load="false" {#|image-meta} /></div>{.end}{.end}
</div>
</figure>
Associated JS
First off, it should be noted that I went through and console logged every function to see what was giving the Quick View it's functionality - to no avail. Which is subsequently why I'm here. So it's easy to see where the zoom function is originating: the Product View in the Galapagos.ProductItem function on line 103 $imageContainer = Y.one('#jsProductItemImages');
But I don't see anything out of the ordinary pop up when I look at the Quick View. I've got to be missing something!
var Galapagos = {};
Y.use('node', function(Y) {
Galapagos.Site = (function(){
console.log("Galapagos.Site");
var $productPage;
function init() {
console.log("Galapagos.Site init()");
$productPage = Y.one('.collection-type-products');
if( $productPage && $productPage.hasClass('view-list') ) Galapagos.ProductList.init();
if( $productPage && $productPage.hasClass('view-item') ) Galapagos.ProductItem.init();
addDesktopTouchscreenClass();
addMediaQueryBreakpointClass();
bindEventListeners();
}
function addDesktopTouchscreenClass() {
console.log("Galapagos.Site addDesktopTouchscreenClass()");
if (Y.one('html').hasClass('touch')) {
var mousemoveDetection = Y.on('mousemove', function(){
Y.one('body').addClass('galapagos-desktop-touchscreen');
mousemoveDetection.detach();
});
}
}
function addMediaQueryBreakpointClass() {
console.log("Galapagos.Site addMediaQueryBreakpointClass()");
if( document.documentElement.clientWidth <= 724 ) {
if (Y.one('.catnav-container')) Y.one('.nav-container').prepend(Y.one('.catnav-list'));
Y.one('html').addClass('tablet-breakpoint-mixin');
} else {
if (Y.one('.catnav-container')) Y.one('.catnav-container').prepend(Y.one('.catnav-list'));
Y.one('html').removeClass('tablet-breakpoint-mixin');
}
}
function bindEventListeners() {
console.log("Galapagos.Site bindEventListeners()");
Y.on('resize', addMediaQueryBreakpointClass);
}
function getDocWidth() {
console.log("Galapagos.Site getDocWidth()");
return Y.one(document).get('docWidth');
}
function getDocHeight() {
console.log("Galapagos.Site getDocHeight()");
return Y.one(document).get('docHeight');
}
return {
init:init,
getDocWidth: getDocWidth,
getDocHeight: getDocHeight
}
}());
Galapagos.TweakListener = (function(){
console.log("Galapagos.TweakListener");
function listen(tweakName, callBack) {
if (Y.Global) {
Y.Global.on('tweak:change', Y.bind(function(f){
if ((f.getName() == tweakName) && (typeof callBack === 'function')) {
callBack(f.getValue());
}
}));
}
}
return {
listen:listen
}
}());
Galapagos.ProductItem = (function(){
console.log("Galapagos.ProductItem");
var cat;
var $imageContainer;
var $images;
var imageZoomInstances = [];
function init() {
console.log("Galapagos.ProductItem init()");
cat = Y.QueryString.parse(location.search.substring(1)).category;
$imageContainer = Y.one('#jsProductItemImages');
$images = $imageContainer.all('img[data-src]');
if ( cat ) setCatCrumb();
loadProductDetailImages();
bindEventListeners();
bindTweakListeners();
buildProductDetailImagesLightbox();
}
function bindEventListeners() {
console.log("Galapagos.ProductItem bindEventListeners()");
Y.on('resize', function(){
loadProductDetailImages();
});
}
function setCatCrumb() {
console.log("Galapagos.ProductItem setCatCrumb()");
var $catCrumb = Y.one('#jsCategoryCrumb');
var $catCrumbLink = $catCrumb.one('a');
var catCrumbHref = $catCrumbLink.getAttribute('href');
//var $mobileCatCrumbLink = Y.one('#jsMobileCategoryCrumb');
$catCrumbLink.set('text', cat).setAttribute('href', catCrumbHref + '?category=' + encodeURIComponent(cat));
//$mobileCatCrumbLink.setAttribute('href', catCrumbHref + '?category=' + encodeURIComponent(cat));
$catCrumb.removeClass('galapagos-display-none');
}
function loadProductDetailImages() {
console.log("Galapagos.ProductItem loadProductDetailImages()");
var imageZoomEnabled = Y.one('.tweak-product-item-image-zoom-enabled');
$images.each(function(image) {
ImageLoader.load(image.removeAttribute('data-load'), { load:true });
if (imageZoomEnabled) {
image.on('load', function() {
instantiateImageZoom(image);
});
}
});
}
function instantiateImageZoom(image) {
console.log("Galapagos.ProductItem instantiateImageZoom()");
imageZoomInstances.push(new Y.Squarespace.ImageZoom({
host: image.get('parentNode'),
behavior: 'hover',
zoom: parseFloat(Y.Squarespace.Template.getTweakValue('tweak-product-item-image-zoom-factor'))
}));
}
function destroyImageZoomInstances() {
console.log("Galapagos.ProductItem destroyImageZoomInstances()");
if (!imageZoomInstances || imageZoomInstances.length < 1) {
return;
}
Y.Array.each(imageZoomInstances, function(zoomInstance){
zoomInstance.destroy(true);
});
}
function buildProductDetailImagesLightbox() {
console.log("Galapagos.ProductItem buildProductDetailImagesLightbox()");
if ($images.size() >= 1 ) {
var lightboxSet = [];
$images.each(function(image) {
lightboxSet.push({
content: image
});
});
// Only show controls for size > 1
var hasControls = $images.size() > 1;
$imageContainer.delegate('click', function(e) {
var lightbox = new Y.Squarespace.Lightbox2({
controls: {
previous: hasControls,
next: hasControls
},
set: lightboxSet,
currentSetIndex: $images.indexOf(e.target)
});
lightbox.render();
}, 'img', this);
}
}
function bindTweakListeners() {
console.log("Galapagos.ProductItem bindTweakListeners()");
if (Y.Global) {
Y.Global.on('tweak:close', function() {
if (Y.one('.collection-type-products.view-item')) {
destroyImageZoomInstances();
if (Y.one('.tweak-product-item-image-zoom-enabled')) {
$images.each(function(image){
instantiateImageZoom(image);
});
}
}
}, this);
}
}
return {
init:init
}
}());
Galapagos.ProductList = (function(){
console.log("Galapagos.ProductList");
var $catNav,
$productGrid,
$productGridOrphans,
$productGridImages,
$orphanProducts,
productCount,
maxGridUnit,
orphanProductCount,
isGridBuilt;
function init() {
console.log("Galapagos.ProductList init()");
$catNav = Y.one('#jsCatNav');
$productGrid = Y.one('#jsProductGrid');
$productGridOrphans = Y.one('#jsProductGridOrphans');
if (!Y.UA.mobile && Y.one('.show-alt-image-on-hover:not(.product-info-style-overlay)')) {
$productGridImages = $productGrid.all('img[data-src]');
} else {
$productGridImages = $productGrid.all('img.productlist-image--main[data-src]');
}
productCount = $productGrid.all('.productlist-item').size();
maxGridUnit = 8;
orphanProductCount;
isGridBuilt = false;
bindEventListeners();
bindTweakListeners();
if($catNav) setActiveCategory();
if(Y.one('body').hasClass('product-grid-style-organic')) {
buildGrid();
} else {
$productGrid.removeClass('loading').removeClass('loading-height');
loadGridImages($productGridImages);
}
}
function bindEventListeners() {
console.log("Galapagos.ProductList bindEventListeners()");
Y.on('resize', function(){
loadGridImages($productGridImages);
});
}
function buildGrid() {
console.log("Galapagos.ProductList buildGrid()");
for (var i = maxGridUnit; i > 0; i--) {
orphanProductCount = productCount % i;
if(productCount <= maxGridUnit || i > 4) {
if(0 === orphanProductCount) {
$productGrid.addClass('item-grid-' + i);
isGridBuilt = true;
break;
}
} else {
if(0 === productCount % 9) { // if productCount is a multiple of 9, use the 9-grid. we use 9-grid only for multiples of 9 because 8-grid looks more interesting.
$productGrid.addClass('item-grid-' + 9);
} else { // otherwise, use the 8-grid and put the remainder into the orphan div
$productGrid.addClass('item-grid-' + maxGridUnit);
$orphanProducts = Y.all('.productlist-item').slice((productCount % maxGridUnit) * -1);
$productGridOrphans
.append($orphanProducts)
.addClass('item-grid-' + productCount % maxGridUnit);
}
isGridBuilt = true;
break;
}
}
if(isGridBuilt) {
$productGrid.removeClass('loading').removeClass('loading-height');
loadGridImages();
}
}
function setActiveCategory() {
console.log("Galapagos.ProductList setActiveCategory()");
var catNavItemCount = $catNav.all('.catnav-item').size();
for (var i = catNavItemCount - 1; i > 0; i--) {
var $item = $catNav.all('.catnav-item').item(i);
var $link = $item.one('.catnav-link');
var category = Y.QueryString.parse(location.search.substring(1)).category;
var href = Y.QueryString.parse($link.getAttribute('href').substring(2)).category;
if(category && href && category === href) {
$item.addClass('active-link');
}
else if(!category) {
$catNav.one('#jsCatNavRoot').addClass('active-link');
}
}
}
function loadGridImages() {
console.log("Galapagos.ProductList loadGridImages()");
$productGridImages.each(function(image) {
ImageLoader.load(image.removeAttribute('data-load'), { load: true });
image.on('load', function(){
if (image.hasClass('productlist-image--main.has-alt-image')) {
image.siblings('.productlist-image--alt').removeClass('galapagos-hidden');
}
});
});
}
function bindTweakListeners() {
console.log("Galapagos.ProductList bindTweakListeners()");
if (Y.Global) {
Y.Global.on(['tweak:beforeopen', 'tweak:close', 'tweak:reset'], function() {
setTimeout(function(){
Galapagos.ProductList.init();
}, 1000);
});
Y.Global.on(['tweak:beforeopen'], function() {
setTimeout(function(){
Galapagos.ProductList.init();
$productGrid.one('.productlist-item').addClass('is-hovered');
}, 1000);
});
Y.Global.on(['tweak:close'], function() {
setTimeout(function(){
Galapagos.ProductList.init();
$productGrid.one('.productlist-item').removeClass('is-hovered');
}, 1000);
});
}
Galapagos.TweakListener.listen('product-grid-style', function(value) {
if('Organic' === value) {
buildGrid();
} else {
$productGrid.append($orphanProducts);
loadGridImages();
}
});
Galapagos.TweakListener.listen('product-info-style', function(value) {
if('Overlay' === value) {
$productGrid.one('.productlist-item').addClass('is-hovered');
} else {
$productGrid.one('.productlist-item').removeClass('is-hovered');
}
});
Galapagos.TweakListener.listen('productImageAspectRatio', function(value) {
loadGridImages();
});
Galapagos.TweakListener.listen('productImageSpacing', function(value) {
loadGridImages();
});
}
return {
init:init
}
}());
Y.on('domready', function() {
Galapagos.Site.init();
});
});
My Attempts
My first few attempts have been dropping the jsProductItemImages div from the Product view and dumping in the entire figure block from the Quick View then updating the associated css. While it pulls in the images (I can see them in the inspector and they take up space on the page) it shows up as being blank.
I also tried only using the thumbnails section from the Quick View and limiting the Product View to only show the first image by using {.section items.0} but then any thumbnail I clicked wouldn't swap out without writing the script for it (obviously) but I didn't want to write something like that when I know it exists in the code already!
Any help would be greatly appreciated!
UPDATE:
After replacing the product view markup with the quick view markup I ran into these errors
Uncaught TypeError: Cannot read property 'all' of null site.js:104
init # site.js:104
init # site.js:17
(anonymous function) # site.js:432
_notify # common-2a739bf…-min.js:1479
notify # common-2a739bf…-min.js:1479
_notify # common-2a739bf…-min.js:1475
_procSubs # common-2a739bf…-min.js:1476
fireSimple # common-2a739bf…-min.js:1476
_fire # common-2a739bf…-min.js:1476
fire # common-2a739bf…-min.js:1489
_load # common-2a739bf…-min.js:1463
f # common-2a739bf…-min.js:1457
Unsure why it's hitting an error with .all because it should be addressing the same array of images in both situations?
There's a few questions buried in the this post but let me answer the Quick View question specifically since that's what you're looking to "fix".
Squarespace uses a modular system of JavaScript/CSS add-ons called "rollups". If you pull the source code you'll see a window object that contains the current configuration of any given page. When visiting a Products page, the system triggers the use of their quick view JS and accommodating CSS file. This is where you'll want to be looking. The JS you're digging into is not relevant to Quick View (I don't believe).
Quick View Rollup JS: http://static.squarespace.com/universal/scripts-compressed/product-quick-view-6a1e5642b473ebbb5944-min.js
Quick View Rollup CSS: http://static.squarespace.com/universal/styles-compressed/product-quick-view-eb4b900ac0155bed2f175aa82e2a7c17-min.css
These rollups are triggered off of JavaScript hooks in the template files. What you'll need to do is experiment with using the Galapagos product template word and word so it has the same classes and data-attributes, and see if that works. It would take far too long to cover all of the details of what you need to do without actually working on the project. I would start here first and see if you can setup your product template to triggers the Quick View JS as is, without customization.

JS - Window Focus and Bring to front in Firefox

I am trying to set up a tiny script for Firefox that runs in a javascript add-on (Greasemonkey). We have a queue that we monitor for arriving tickets, I coded something that is supposed to refresh the page every 2 minutes and do the following:
if tickets are found by this condition
if (isEmpty($('#incident_table > tbody'))&isEmpty($('#task_table > tbody')))
then I want the following to happen:
- task-bar blinks with a message so it is visible
- if the window is focused it will display "Tickets in the queue!" alert right away
- if the window is not focused, it will wait for 10 seconds, if still not focused - display "Tickets in the queue!" alert and bring the window to front.
I've got the refresh and blinking part, but I cannot get the focus part to work... I've been looking around and I see that Firefox is having some "issues" with window.focus() and all the "bring to front", most of the code below is inspired by stuff I've found on this site.
Any input is appreciated! I am also opened to alternatives - in the end, what this needs to do is refresh, check the condition and notify if I am already looking at it or if it is not focused wait 10 seconds with a "soft notify" (blink) then bring it to the front if I don't notice it.
Regards,
Dan
{
newExcitingAlerts = (function () {
var oldTitle = document.title;
var msg = "***NEW***";
var timeoutId;
var blink = function() { document.title = document.title == msg ? 'Tickets in queue!' : msg; };
var clear = function() {
clearInterval(timeoutId);
document.title = oldTitle;
window.onmousemove = null;
timeoutId = null;
};
return function () {
if (!timeoutId) {
timeoutId = setInterval(blink, 1000);
window.onmousemove = clear;
}
};
}());
$(document).ready(function(){
function isEmpty( el ){
return !$.trim(el.html());
}
if (isEmpty($('#incident_table > tbody'))&isEmpty($('#task_table > tbody'))) {
}
else{
newExcitingAlerts();
}
setTimeout(function() {
location.reload();
}, 120000);
});
}
Here is the alternative I've used, works like a charm. Web API notifications.
document.addEventListener('DOMContentLoaded', function () {
if (Notification.permission !== "granted")
Notification.requestPermission();
});
function notifyMe() {
if (!Notification) {
alert('Desktop notifications not available in your browser. Try Chromium.');
return;
}
if (Notification.permission !== "granted")
Notification.requestPermission();
else {
var notification = new Notification('Tickets in queue!', {
icon: 'http://siliconangle.com/files/2014/05/servicenow-icon.png',
body: "There are new tickets in queue, please acknowledge!",
});
notification.onclick = function () {
window.open("https://cchprod.service-now.com/task_list.do?sysparm_query=assignment_group%3D3139519437b7f1009654261953990e1f^ORassignment_group%3D31de85e337818a00ef8898a543990e99^ORassignment_group%3Da40029e937ec420065aa261953990eb5^ORassignment_group%3De903ad2d37ec420065aa261953990ecb^ORassignment_group%3Dd25fe5323779c24065aa261953990e54^ORassignment_group%3D508639363779c24065aa261953990e29^ORassignment_group%3D51fe5a37379e0a00ef8898a543990ea2^ORassignment_group%3D3d8171b23779c24065aa261953990e21^ORassignment_group%3Decfe5a37379e0a00ef8898a543990e6c^ORassignment_group%3D48c0b9723779c24065aa261953990e5d^ORassignment_group%3De5fde9fe3739c24065aa261953990e75^ORassignment_group%3D15fe5a37379e0a00ef8898a543990e99^ORassignment_group%3D15fe5a37379e0a00ef8898a543990ea7^ORassignment_group%3D1ed3f1f23779c24065aa261953990e47^active%3Dtrue^sys_class_name%3Dincident^ORsys_class_name%3Dsc_req_item^assigned_toISEMPTY&sysparm_first_row=1&sysparm_view=");
};
}
}
{
newExcitingAlerts = (function () {
var oldTitle = document.title;
var msg = "***NEW***";
var timeoutId;
var blink = function() {
document.title = document.title == msg ? 'Tickets in queue!' : msg;
};
var clear = function() {
clearInterval(timeoutId);
document.title = oldTitle;
window.onmousemove = null;
timeoutId = null;
};
return function () {
if (!timeoutId) {
timeoutId = setInterval(blink, 1000);
window.onmousemove = clear;
}
};
}
());
$(document).ready(function () {
function isEmpty(el) {
return !$.trim(el.html());
}
var x = document.getElementById("task_table").getAttribute("grand_total_rows");
if( x != "0" ) {
newExcitingAlerts();
notifyMe();
}
else
{
}
setTimeout(function() {
location.reload();
}
, 120000);
});
}
In Windows operating system you cannot focus the window while the window of another process is focused. You have to use js-ctypes to get around this.
On Mac OS X and Linux, I'm not sure if you can make your process steal focus from another with the normal functions. But if you can't you can for sure use js-ctypes to get the job done.
Here is how to do it in Windows - https://stackoverflow.com/a/32038880/1828637
It is harder on Windows then on OS X and Linux. I have focused windows on all systems using js-ctypes. So if you can't find out how to do with the functions available to you, let me know.

How to keep Skrollr-Menu at an even speed?

I'm using Skrollr-menu to animate down a page on a button press using the following
HTML
<div class="trigger-scroll left">></div>
... the page i want to reveal, using scrolling ...
<section id="End" class="scroll-here">
<div class="hsContainer bottom"></div>
</section>
JavaScript
var s = skrollr.init();
skrollr.menu.init(s, {
animate: true,
//How long the animation should take in ms.
duration: function(currentTop, targetTop) {
//By default, the duration is hardcoded at 500ms.
return 18000;
//But you could calculate a value based on the current scroll position (`currentTop`) and the target scroll position (`targetTop`).
//return Math.abs(currentTop - targetTop) * 10;
},
//This event is triggered right before we jump/animate to a new hash.
change: function(newHash, newTopPosition) {
//Do stuff
},
//Add hash link (e.g. `#foo`) to URL or not.
updateUrl: false //defaults to `true`.
});
What happens when I click the button is that it works, that is not the problem.
The problem is that it seems to change speed as skrollr-menu animates the page. It starts off quite quickly, which means that the first few elements on the page (about the first 2000px) flash past without being readable. Then the speed evens out and is fine right until the last 3000px (approximately) where skrollr-menu is very slow. What I want is for the click of the button to resemble holding the down arrow on the keyboard or the scroll sidebar, which by default it seems skrollr-menu does not do.
I've tried using math equations to change the speed but the issue persists no matter what i try, and there doesn't seem to be any "simple" way to change the acceleration speed, and I suspect the problem is somewhere within the Skrollr.menu.js file, but I can't see where.
Is there any way which I can make the scrolling an even speed, rather than fast at the start and slow at the end?
Note: I'm not very experienced in JavaScript or jQuery, so it's probably something simple I've overlooked.
skrollr menu on github
https://github.com/Prinzhorn/skrollr-menu
Skrollr.menu.js
/*!
* Plugin for skrollr.
* This plugin makes hashlinks scroll nicely to their target position.
*
* Alexander Prinzhorn - https://github.com/Prinzhorn/skrollr
*
* Free to use under terms of MIT license
*/
(function(document, window) {
'use strict';
var DEFAULT_DURATION = 500;
var DEFAULT_EASING = 'sqrt';
var DEFAULT_SCALE = 1;
var MENU_TOP_ATTR = 'data-menu-top';
var MENU_OFFSET_ATTR = 'data-menu-offset';
var MENU_DURATION_ATTR = 'data-menu-duration';
var MENU_IGNORE_ATTR = 'data-menu-ignore';
var skrollr = window.skrollr;
var history = window.history;
var supportsHistory = !!history.pushState;
/*
Since we are using event bubbling, the element that has been clicked
might not acutally be the link but a child.
*/
var findParentLink = function(element) {
//We reached the top, no link found.
if(element === document) {
return false;
}
//Yay, it's a link!
if(element.tagName.toUpperCase() === 'A') {
return element;
}
//Maybe the parent is a link.
return findParentLink(element.parentNode);
};
/*
Handle the click event on the document.
*/
var handleClick = function(e) {
//Only handle left click.
if(e.which !== 1 && e.button !== 0) {
return;
}
var link = findParentLink(e.target);
//The click did not happen inside a link.
if(!link) {
return;
}
if(handleLink(link)) {
e.preventDefault();
}
};
/*
Handles the click on a link. May be called without an actual click event.
When the fake flag is set, the link won't change the url and the position won't be animated.
*/
var handleLink = function(link, fake) {
var hash;
//When complexLinks is enabled, we also accept links which do not just contain a simple hash.
if(_complexLinks) {
//The link points to something completely different.
if(link.hostname !== window.location.hostname) {
return false;
}
//The link does not link to the same page/path.
if(link.pathname !== document.location.pathname) {
return false;
}
hash = link.hash;
} else {
//Don't use the href property (link.href) because it contains the absolute url.
hash = link.getAttribute('href');
}
//Not a hash link.
if(!/^#/.test(hash)) {
return false;
}
//The link has the ignore attribute.
if(!fake && link.getAttribute(MENU_IGNORE_ATTR) !== null) {
return false;
}
//Now get the targetTop to scroll to.
var targetTop;
var menuTop;
//If there's a handleLink function, it overrides the actual anchor offset.
if(_handleLink) {
menuTop = _handleLink(link);
}
//If there's a data-menu-top attribute and no handleLink function, it overrides the actual anchor offset.
else {
menuTop = link.getAttribute(MENU_TOP_ATTR);
}
if(menuTop !== null) {
//Is it a percentage offset?
if(/p$/.test(menuTop)) {
targetTop = (menuTop.slice(0, -1) / 100) * document.documentElement.clientHeight;
} else {
targetTop = +menuTop * _scale;
}
} else {
var scrollTarget = document.getElementById(hash.substr(1));
//Ignore the click if no target is found.
if(!scrollTarget) {
return false;
}
targetTop = _skrollrInstance.relativeToAbsolute(scrollTarget, 'top', 'top');
var menuOffset = scrollTarget.getAttribute(MENU_OFFSET_ATTR);
if(menuOffset !== null) {
targetTop += +menuOffset;
}
}
if(supportsHistory && _updateUrl && !fake) {
history.pushState({top: targetTop}, '', hash);
}
var menuDuration = parseInt(link.getAttribute(MENU_DURATION_ATTR), 10);
var animationDuration = _duration(_skrollrInstance.getScrollTop(), targetTop);
if(!isNaN(menuDuration)) {
animationDuration = menuDuration;
}
//Trigger the change if event if there's a listener.
if(_change) {
_change(hash, targetTop);
}
//Now finally scroll there.
if(_animate && !fake) {
_skrollrInstance.animateTo(targetTop, {
duration: animationDuration,
easing: _easing
});
} else {
defer(function() {
_skrollrInstance.setScrollTop(targetTop);
});
}
return true;
};
var jumpStraightToHash = function() {
if(window.location.hash && document.querySelector) {
var link = document.querySelector('a[href="' + window.location.hash + '"]');
if(!link) {
// No link found on page, so we create one and then activate it
link = document.createElement('a');
link.href = window.location.hash;
}
handleLink(link, true);
}
};
var defer = function(fn) {
window.setTimeout(fn, 1);
};
/*
Global menu function accessible through window.skrollr.menu.init.
*/
skrollr.menu = {};
skrollr.menu.init = function(skrollrInstance, options) {
_skrollrInstance = skrollrInstance;
options = options || {};
_easing = options.easing || DEFAULT_EASING;
_animate = options.animate !== false;
_duration = options.duration || DEFAULT_DURATION;
_handleLink = options.handleLink;
_scale = options.scale || DEFAULT_SCALE;
_complexLinks = options.complexLinks === true;
_change = options.change;
_updateUrl = options.updateUrl !== false;
if(typeof _duration === 'number') {
_duration = (function(duration) {
return function() {
return duration;
};
}(_duration));
}
//Use event bubbling and attach a single listener to the document.
skrollr.addEvent(document, 'click', handleClick);
if(supportsHistory) {
skrollr.addEvent(window, 'popstate', function(e) {
var state = e.state || {};
var top = state.top || 0;
defer(function() {
_skrollrInstance.setScrollTop(top);
});
}, false);
}
jumpStraightToHash();
};
//Expose the handleLink function to be able to programmatically trigger clicks.
skrollr.menu.click = function(link) {
//We're not assigning it directly to `click` because of the second ("private") parameter.
handleLink(link);
};
//Private reference to the initialized skrollr.
var _skrollrInstance;
var _easing;
var _duration;
var _animate;
var _handleLink;
var _scale;
var _complexLinks;
var _change;
var _updateUrl;
//In case the page was opened with a hash, prevent jumping to it.
//http://stackoverflow.com/questions/3659072/jquery-disable-anchor-jump-when-loading-a-page
defer(function() {
if(window.location.hash) {
window.scrollTo(0, 0);
}
});
}(document, window));
The problem was the easing function found here
//Now finally scroll there.
if(_animate && !fake) {
_skrollrInstance.animateTo(targetTop, {
duration: animationDuration,
easing: _easing
});
} else {
defer(function() {
_skrollrInstance.setScrollTop(targetTop);
});
}
return true;
It seems that, even though Skrollr states that easing's default is linear (no easing), the default is ACTUALLY set to sqrt (or at least it was in my case). The problem can be solved by forcing easing to linear in skrollr.menu.init, or chaning the skrollr.menu.js file to remove easing from the function. The first of these two solutions is cleaner, and won't cause issues later.
skrollr.menu.init(s, {
duration: function(currentTop, targetTop) {return 20000;},
easing: 'linear'
});

append to div one time

I am revisiting this code I made a year ago with the help of another person. Unfortunately I don't have contact with them anymore to get more help. Basically It dynamically adds classs to the tb and b nodes of a document coming from namesToChange. Now what I am trying to do is append some text to the div with class dtxt node but still use this code below. I am using the code $('td.pn_adm_jeff').children('div.dtxt').append('zzz'); and it works but it constantly appends more than once as seen in the photo below. How do I go about making it add once and stop?
Photo
http://img6.imageshack.us/img6/5392/7c23ddb145954aefadb1b9f.png
Code
function customizefields(a) {
$('td b').each(function () {
name = $(this).text();
if (name.indexOf(" ") != -1) {
name = name.substring(0, name.indexOf(" "))
}
if (a[name]) {
this.className = a[name].class;
this.parentNode.className = a[name].img
}
})
$('td.pn_adm_jeff').children('div.dtxt').append('zzz');
}
var namesToChange = {
'Jeff' :{'class':'pn_adm','img':'pn_adm_jeff'}
};
setInterval(function () {
customizefields(namesToChange)
}, 1000);
Update
var needsUpdate = true;
function customizefields(a) {
$('td b').each(function () {
name = $(this).text();
if (name.indexOf(" ") != -1) {
name = name.substring(0, name.indexOf(" "));
}
if (a[name]) {
this.className = a[name].class;
this.parentNode.className = a[name].img;
}
});
if (needsUpdate) {
$('td.pn_adm_jeff').children('div.dtxt').append('testing');
needsUpdate = false;
}
}
var namesToChange = {
'jeff' :{'class':'pn_adm','img':'pn_adm_jeff'};
};
setTimeout(function () {
customizefields(namesToChange);
}, 1000);
use setTimeout rather than setInterval (interval is for repeating a timer task, timeout is a single timer task)
To prevent a certain task from occuring more than once in a repeated task, there is a simple fix.
// global variable
var needsUpdate = true;
// now in the timer task
if (needsUpdate) {
$('td.pn_adm_jeff').children('div.dtxt').append('zzz');
needsUpdate = false;
}
Does that work for you?
Define a global variable to hold the input flag
var appended = false;
function appendthestring() {
if(!appended) $('td.pn_adm_jeff').children('div.dtxt').append('zzz');
appended = true;
}

Categories