|
// id текущей вкладки. |
|
var id; |
|
// Является ли текущая вкладка мастером. |
|
var is_master = false; |
|
|
|
// Неизвестный пока id мастер-вкладки. |
|
var master_id = null; |
|
|
|
var ids; |
|
var handler; |
|
|
|
$(function() { |
|
|
|
// Максимальный использованный id лежит в localStorage с ключом 'id'. |
|
// Генерим новый id и сохраняем его в localStorage. |
|
id = ( +get('id') || 0 ) + 1; |
|
set('id', id); |
|
|
|
$('.id').text(id); |
|
|
|
// Эта функция запускается раз в 5 секунд. |
|
function check() { |
|
if (is_master) { return; } |
|
|
|
master_id = null; |
|
// Сюда будем собирать id-шники всех откликнувшихся вкладок. |
|
ids = [ id ]; |
|
|
|
// Кидаем глобальный 'ping'. Все вкладки (кроме этой) должны на него ответить |
|
// событием 'pong' со своим id-шником. |
|
trigger('ping'); |
|
|
|
// Если через 50 мс мастер-вкладка не объявится, то проводим выборы. |
|
handler = setTimeout(function() { |
|
if (!master_id) { |
|
// Выбираем минимальный id-шник из всех открытых вкладок. |
|
var min_id = ids.reduce( function(x, y) { return Math.min(x, y); } ); |
|
// Если это мы и есть, объявляем себя мастером. |
|
if (min_id === id) { |
|
is_master = true; |
|
// Сообщаем всем, что мы и есть мастер. |
|
trigger('master', id); |
|
|
|
$('.id').addClass('is-master'); |
|
} else { |
|
$('.id').removeClass('is-master'); |
|
} |
|
} |
|
}, 50); |
|
} |
|
|
|
setInterval(check, 5000); |
|
check(); |
|
|
|
}); |
|
|
|
// --------------------------------------------------------------------------------------------------------------- // |
|
|
|
var events = {} |
|
|
|
// Если пришел 'ping', отвечаем 'pong'-ом со своим id. |
|
events.ping = function() { |
|
// Если мы еще и мастер впридачу, объявляем об этом всем остальным. |
|
if (is_master) { |
|
trigger('master', id); |
|
} |
|
trigger('pong', id); |
|
}; |
|
|
|
// Коллекционируем все пришедшие id-шники. |
|
events.pong = function(id) { |
|
ids.push(+id); |
|
}; |
|
|
|
// Объявление о том, что мастер где-то там есть. |
|
events.master = function(id) { |
|
master_id = +id |
|
if (handler) { |
|
clearTimeout(handler); |
|
} |
|
}; |
|
|
|
// --------------------------------------------------------------------------------------------------------------- // |
|
|
|
// Тут дальше магия на тему общения между вкладками. |
|
// Чтобы послать всем событие 'foo', создаем (или изменяем) в localStorage'е |
|
// ключ foo#event. При посылке значения 'bar' дописываем к нему авто-инкрементный счетчик |
|
// (иначе может не случиться событие 'storage'. |
|
|
|
// FIXME: Вешать обработчик через jQuery, чтобы работало и в IE. |
|
// |
|
window.addEventListener('storage', function(e) { |
|
// Получаем реальное название события (выкидываем #event). |
|
var name = e.key.replace(/#.*$/, ''); |
|
var handler = events[name]; |
|
if (!handler) { return; } |
|
|
|
// Отрезаем от значения счетчик. |
|
var value = e.newValue.replace(/#.*$/, ''); |
|
// Зовем "обработчик" события. |
|
handler(value); |
|
}, false); |
|
|
|
function trigger(name, value) { |
|
// Генерим ключ в localStorage для события name. |
|
var key = name + '#event'; |
|
|
|
// В старом значении (если есть) ищем текущее значение счетчика. |
|
var oldValue = get(key); |
|
var counter = (oldValue) ? +oldValue.replace(/^.*#/, '') : 0; |
|
|
|
// "Отправляем" значение с увеличенным счетчиком. |
|
set( key, (value || '') + '#' + (counter + 1) ); |
|
} |
|
|
|
// --------------------------------------------------------------------------------------------------------------- // |
|
|
|
// Шоткаты для доступа к localStorage. |
|
|
|
function get(key) { |
|
return localStorage.getItem(key); |
|
} |
|
|
|
function set(key, value) { |
|
localStorage.setItem(key, value); |
|
} |