Refactor /integrations state, routing, and rendering logic.

A large portion of the diff for landing-page.js is due to refactoring the
contents of integrations_search into top level UI update functions.

State flows as follows: dispatch(action) -> render(state) -> update UI

Routes now use pushState instead of hashes.

On transition between categories scrolling position is fixed,
and on transition between catalog and integration sub-pages the page
scrolls to the top.
This commit is contained in:
Jack Zhang 2017-07-31 18:15:45 -07:00 committed by Tim Abbott
parent c0328de03d
commit d3cc201332
4 changed files with 281 additions and 210 deletions

View File

@ -55,26 +55,216 @@ function path_parts() {
});
}
// these are events that are only to run on the integrations page.
// check if the page location is integrations.
var integration_events = function () {
var integrations = $('.integration-lozenges').children().toArray();
var scroll_top = 0;
var INITIAL_STATE = {
category: 'all',
integration: null,
query: '',
};
$("a.title")
.addClass("show-integral")
.prepend($("<span class='integral'>∫</span>"))
.hover(function () {
$(".integral").css("display", "inline");
var width = $(".integral").width();
$("a.title").css("left", -1 * width);
},
function () {
$(".integral").css("display", "none");
$("a.title").css("left", 0);
var state = Object.assign({}, INITIAL_STATE);
function update_path() {
var next_path;
if (state.integration) {
next_path = $('.integration-lozenge[data-name="' + state.integration + '"]')
.closest('a').attr('href');
} else if (state.category) {
next_path = $('.integration-category[data-category="' + state.category + '"]')
.closest('a').attr('href');
} else {
next_path = '/';
}
window.history.pushState(state, '', next_path);
}
function update_categories() {
$('.integration-lozenges').css('opacity', 0);
$('.integration-category').removeClass('selected');
$('[data-category="' + state.category + '"]').addClass('selected');
$('.integration-lozenges').animate(
{ opacity: 1 },
{ duration: 400 }
);
}
var update_integrations = _.debounce(function () {
var max_scrollY = window.scrollY;
var integrations = $('.integration-lozenges').children().toArray();
integrations.forEach(function (integration) {
var $integration = $(integration).find('.integration-lozenge');
var $integration_category = $integration.find('.integration-category');
if (state.category !== 'all') {
$integration_category.css('display', 'none');
$integration.addClass('without-category');
} else {
$integration_category.css('display', '');
$integration.removeClass('without-category');
}
if (!$integration.hasClass('integration-create-your-own')) {
var display =
fuzzysearch(state.query, $integration.data('name').toLowerCase()) &&
($integration.data('categories').indexOf(CATEGORIES[state.category]) !== -1 ||
state.category === 'all');
if (display) {
$integration.css('display', 'inline-block');
} else {
$integration.css('display', 'none');
}
}
document.body.scrollTop = Math.min(window.scrollY, max_scrollY);
});
}, 50);
function hide_catalog_show_integration() {
var $lozenge_icon = $(".integration-lozenge.integration-" + state.integration).clone(false);
function show_integration(doc) {
$('#integration-instructions-group').css({
opacity: 0,
display: 'block',
});
$('.integration-instructions').css('display', 'none');
$('#' + state.integration + '.integration-instructions .help-content').html(doc);
$('#integration-instruction-block .integration-lozenge').remove();
$("#integration-instruction-block")
.append($lozenge_icon)
.css('display', 'block');
$('.integration-instructions#' + state.integration).css('display', 'block');
$("#integration-list-link").css('display', 'block');
$("html, body").animate(
{ scrollTop: 0 },
{ duration: 200 }
);
$('#integration-instructions-group').animate(
{ opacity: 1 },
{ duration: 300 }
);
}
function hide_catalog(doc) {
$(".integration-categories-dropdown").css('display', 'none');
$(".integrations .catalog").addClass('hide');
$(".extra, #integration-main-text, #integration-search").css("display", "none");
show_integration(doc);
}
$.get({
url: '/integrations/doc-html/' + state.integration,
dataType: 'html',
success: hide_catalog,
error: function (err) {
blueslip.error("Integration documentation for '" + state.integration + "' not found.", err);
},
});
}
function hide_integration_show_catalog() {
function show_catalog() {
$("html, body").animate(
{ scrollTop: 0 },
{ duration: 200 }
);
$(".integration-categories-dropdown").css('display', '');
$(".integrations .catalog").removeClass('hide');
$(".extra, #integration-main-text, #integration-search").css("display", "block");
}
function hide_integration() {
$('#integration-instruction-block').css('display', 'none');
$('#integration-instructions-group').css('display', 'none');
$('.inner-content').css({ padding: '' });
$("#integration-instruction-block .integration-lozenge").remove();
show_catalog();
}
hide_integration();
}
function get_state_from_path() {
var result = Object.assign({}, INITIAL_STATE);
result.query = state.query;
var parts = path_parts();
if (parts[1] === 'doc' && INTEGRATIONS[parts[2]]) {
result.integration = parts[2];
} else if (CATEGORIES[parts[1]]) {
result.category = parts[1];
}
return result;
}
function render(next_state) {
var previous_state = Object.assign({}, state);
state = next_state;
if (previous_state.integration !== next_state.integration) {
if (next_state.integration !== null) {
hide_catalog_show_integration();
} else {
hide_integration_show_catalog();
}
}
if (previous_state.category !== next_state.category) {
update_categories();
update_integrations();
}
if (previous_state.query !== next_state.query) {
update_integrations();
}
}
function dispatch(action, payload) {
switch (action) {
case 'CHANGE_CATEGORY':
render(Object.assign({}, state, {
category: payload.category,
}));
update_path();
break;
case 'SHOW_INTEGRATION':
render(Object.assign({}, state, {
integration: payload.integration,
}));
update_path();
break;
case 'HIDE_INTEGRATION':
render(Object.assign({}, state, {
integration: null,
}));
update_path();
break;
case 'UPDATE_QUERY':
render(Object.assign({}, state, {
query: payload.query,
}));
break;
case 'LOAD_PATH':
render(get_state_from_path());
break;
default:
blueslip.error('Invalid action dispatched on /integrations.');
break;
}
}
var integration_events = function () {
function adjust_font_sizing() {
$('.integration-lozenge').toArray().forEach(function (integration) {
var $integration_name = $(integration).find('.integration-name');
@ -96,106 +286,10 @@ var integration_events = function () {
}
});
}
adjust_font_sizing();
$(window).resize(adjust_font_sizing);
var $lozenge_icon;
var currentblock;
var instructionbox = $("#integration-instruction-block");
var hashes = $('.integration-instructions').map(function () {
return this.id || null;
}).get();
var show_integration = function (hash) {
// the version of the hash without the leading "#".
var _hash = hash.replace(/^#/, "");
var integration_name = _hash;
$.get({
url: '/integrations/doc-html/' + integration_name,
dataType: 'html',
success: function (doc) {
$('#' + integration_name + '.integration-instructions .help-content').html(doc);
},
error: function (err) {
blueslip.error("Integration documentation for '" + integration_name + "' not found.", err);
},
});
// clear out the integrations instructions that may exist in the instruction
// block from a previous hash.
$("#integration-instruction-block .integration-instructions")
.appendTo("#integration-instructions-group");
if (hashes.indexOf(_hash) > -1) {
$lozenge_icon = $(".integration-lozenges .integration-lozenge.integration-" + _hash).clone(true);
currentblock = $(hash);
instructionbox.hide().children(".integration-lozenge").replaceWith($lozenge_icon);
instructionbox.append($lozenge_icon);
$(".inner-content").removeClass("show");
setTimeout(function () {
instructionbox.hide();
$(".integration-categories-dropdown").css('display', 'none');
$(".integrations .catalog").addClass('hide');
$(".extra, #integration-main-text, #integration-search").css("display", "none");
instructionbox.append(currentblock);
instructionbox.show();
$("#integration-list-link").css("display", "block");
$(".inner-content").addClass("show");
}, 300);
$("html, body").animate({ scrollTop: 0 }, 200);
}
};
function update_hash() {
var hash = window.location.hash;
if (hash && hash !== '#' && hash !== '#hubot-integrations') {
scroll_top = $("body").scrollTop();
show_integration(window.location.hash);
} else if (currentblock && $lozenge_icon) {
$(".inner-content").removeClass("show");
setTimeout(function () {
$("#integration-list-link").css("display", "none");
$(".extra, #integration-main-text, #integration-search").show();
instructionbox.hide();
$lozenge_icon.remove();
currentblock.appendTo("#integration-instructions-group");
$(".inner-content").addClass("show");
$(".integration-categories-dropdown").css('display', '');
$(".integrations .catalog").removeClass('hide');
$('html, body').animate({ scrollTop: scroll_top }, 0);
}, 300);
} else {
$(".inner-content").addClass("show");
$(".integration-categories-dropdown").removeClass('hide');
$(".integrations .catalog").removeClass('hide');
}
adjust_font_sizing();
}
window.onhashchange = update_hash;
update_hash();
// this needs to happen because when you link to "#" it will scroll to the
// top of the page.
$("#integration-list-link").click(function (e) {
var scroll_height = $("body").scrollTop();
window.location.hash = "#";
$("body").scrollTop(scroll_height);
e.preventDefault();
});
$('#integration-search input[type="text"]').keypress(function (e) {
var integrations = $('.integration-lozenges').children().toArray();
if (e.which === 13 && e.target.value !== '') {
for (var i = 0; i < integrations.length; i += 1) {
var integration = $(integrations[i]).find('.integration-lozenge');
@ -224,85 +318,49 @@ var integration_events = function () {
}
});
$(window).scroll(function () {
if (document.body.scrollTop > 330) {
$('.integration-categories-sidebar').addClass('sticky');
} else {
$('.integration-categories-sidebar').removeClass('sticky');
$('.integrations a .integration-category').on('click', function (e) {
var category = $(e.target).data('category');
dispatch('CHANGE_CATEGORY', { category: category });
return false;
});
$('.integrations a .integration-lozenge').on('click', function (e) {
if (!$(e.target).closest('.integration-lozenge').hasClass('integration-create-your-own')) {
var integration = $(e.target).closest('.integration-lozenge').data('name');
dispatch('SHOW_INTEGRATION', { integration: integration });
return false;
}
});
};
function integration_search() {
var integrations = $('.integration-lozenges').children().toArray();
var current_category = 'All';
var current_query = '';
function update_categories() {
$('.integration-category').removeClass('selected');
$('[data-category="' + current_category + '"]').addClass('selected');
}
var update_integrations = _.debounce(function () {
integrations.forEach(function (integration) {
var $integration = $(integration).find('.integration-lozenge');
var $integration_category = $integration.find('.integration-category');
if (current_category !== 'All') {
$integration_category.css('display', 'none');
$integration.addClass('without-category');
} else {
$integration_category.css('display', '');
$integration.removeClass('without-category');
}
if (!$integration.hasClass('integration-create-your-own')) {
var display =
fuzzysearch(current_query, $integration.data('name').toLowerCase()) &&
($integration.data('categories').indexOf(current_category) !== -1 ||
current_category === 'All');
if (display) {
$integration.css('display', 'inline-block');
} else {
$integration.css('display', 'none');
}
}
});
}, 50);
function change_category(category) {
$('.integration-lozenges').css('opacity', 0);
current_category = category;
update_categories();
update_integrations();
$('.integration-lozenges').animate(
{ opacity: 1 },
{ duration: 400 }
);
}
function run_search(query) {
current_query = query.toLowerCase();
update_integrations();
}
$('.integrations .integration-category').on('click', function (e) {
var category = $(e.target).data('category');
if (category !== current_category) {
change_category(category);
}
$('a#integration-list-link span, a#integration-list-link i').on('click', function () {
dispatch('HIDE_INTEGRATION');
return false;
});
$(".integrations .searchbar input[type='text']").on('input', function (e) {
run_search(e.target.value);
dispatch('UPDATE_QUERY', { query : e.target.value.toLowerCase() });
});
}
$(window).scroll(function () {
if (document.body.scrollTop > 330) {
$('.integration-categories-sidebar').addClass('sticky');
} else {
$('.integration-categories-sidebar').removeClass('sticky');
}
});
$(window).on('resize', function () {
adjust_font_sizing();
});
$(window).on('popstate', function () {
if (window.location.pathname.startsWith('/integrations')) {
dispatch('LOAD_PATH');
} else {
window.location = window.location.href;
}
});
};
var hello_events = function () {
var counter = 0;
@ -442,7 +500,7 @@ var events = function () {
if (path_parts().includes('integrations')) {
integration_events();
load_data();
integration_search();
dispatch('LOAD_PATH');
}
if (path_parts().includes('hello')) {

View File

@ -1816,10 +1816,8 @@ nav ul li.active::after {
}
.portico-landing.integrations .padded-content .inner-content {
opacity: 0;
pointer-events: none;
transition: all 0.3s ease;
min-height: 800px;
}
.portico-landing.integrations .padded-content .inner-content.show {
@ -1948,6 +1946,7 @@ nav ul li.active::after {
margin: 3px 0;
border-radius: 5px;
transition: all 0.3s ease;
color: #414e67;
}
.portico-landing.integrations .integration-categories-sidebar h4.selected,
@ -2032,6 +2031,11 @@ nav ul li.active::after {
/* -- integration instructions -- */
.portico-landing.integrations #integration-instructions-group {
padding: 0 50px;
display: none;
}
.portico-landing.integrations .integration-instruction-block .integration-lozenge {
width: 125px;
height: 170px;
@ -2716,6 +2720,10 @@ nav ul li.active::after {
padding: 40px 0;
}
.portico-landing.integrations #integration-instructions-group {
padding: 0 25px;
}
.main .padded-content {
padding: 20px;
}

View File

@ -644,10 +644,6 @@ a.bottom-signup-button {
margin-left: 180px;
}
#integration-instructions-group {
display: none;
}
.api-page-header,
.authors-page-header,
.integrations-page-header,
@ -715,8 +711,6 @@ a.bottom-signup-button {
.integration-instruction-block {
position: relative;
margin-top: 15px;
padding: 0 40px;
}
#integration-list-link {

View File

@ -31,10 +31,10 @@
<h2 class="portico-page-subheading">
{% trans %}
And hundreds more through
<a href="#hubot">Hubot</a>,
<a href="#zapier">Zapier</a>,
<a href="/integrations/doc/hubot">Hubot</a>,
<a href="/integrations/doc/zapier">Zapier</a>,
and
<a href="#ifttt">IFTTT</a>.
<a href="/integrations/doc/iftt">IFTTT</a>.
{% endtrans %}
</h2>
</div>
@ -48,20 +48,21 @@
</div>
</div>
<div id="integration-instruction-block" class="integration-instruction-block">
<a href="#!" id="integration-list-link"><i class="icon-vector-circle-arrow-left"></i><span>Back to list</span></a>
</div>
<div class="clear-float"></div>
<div class="integration-categories-dropdown">
<div class="dropdown-toggle">
<h3>{% trans %}View all categories{% endtrans %}</h3>
<i class="icon-vector-angle-right" aria-hidden="true"></i>
</div>
<div class="dropdown-list">
<h4 class="integration-category selected" data-category="All">All</h4>
{% for category in categories_dict.values() %}
<h4 class="integration-category" data-category="{{ category }}">{{ category }}</h4>
<a href="/integrations">
<h4 class="integration-category selected" data-category="all">All</h4>
</a>
{% for category in categories_dict.keys() %}
<a href="/integrations/{{ category }}">
<h4 class="integration-category" data-category="{{ category }}">
{{ categories_dict[category] }}
</h4>
</a>
{% endfor %}
</div>
</div>
@ -69,19 +70,25 @@
<div class="catalog">
<div class="integration-categories-sidebar">
<h3>{% trans %}Categories{% endtrans %}</h3>
<h4 data-category="All" class="integration-category selected">{% trans %}All{% endtrans %}</h4>
{% for category in categories_dict.values() %}
<h4 data-category="{{ category }}" class="integration-category">{{ category }}</h4>
<a href="/integrations">
<h4 data-category="all" class="integration-category selected">{% trans %}All{% endtrans %}</h4>
</a>
{% for category in categories_dict.keys() %}
<a href="/integrations/{{ category }}">
<h4 data-category="{{ category }}" class="integration-category">
{{ categories_dict[category] }}
</h4>
</a>
{% endfor %}
</div>
<div class="integration-lozenges">
{% for integration in integrations_dict.values() %}
{% if integration.is_enabled() %}
<a href="#{{ integration.name }}" class="no-style">
<a href="/integrations/doc/{{ integration.name }}" class="no-style">
<div class="integration-lozenge integration-{{ integration.name }}"
data-categories="{{ integration.categories }}"
data-name="{{ integration.display_name }}">
data-name="{{ integration.name }}">
<img class="integration-logo" src="/{{ integration.logo }}"
alt="{{ integration.display_name }} logo"/>
{% if integration.secondary_line_text %}
@ -109,6 +116,10 @@
</div>
<div id="integration-instructions-group">
<div id="integration-instruction-block" class="integration-instruction-block">
<a href="/integrations" id="integration-list-link"><i class="icon-vector-circle-arrow-left"></i><span>Back to list</span></a>
</div>
{% for integration in integrations_dict.values() %}
{% if integration.is_enabled() %}
<div id={{ integration.name }} class="integration-instructions">