Skip to content

Commit

Permalink
Merge pull request #2121 from tfennelly/JENKINS-33495
Browse files Browse the repository at this point in the history
[JENKINS-33495 and JENKINS-33496] Separate scrollspy widget code out from tab widget code
  • Loading branch information
tfennelly committed Mar 18, 2016
2 parents 8c1f8cf + 07073f5 commit 904d9b1
Show file tree
Hide file tree
Showing 24 changed files with 1,041 additions and 460 deletions.
12 changes: 6 additions & 6 deletions core/src/main/resources/hudson/model/Job/configure.jelly
Expand Up @@ -27,14 +27,14 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<l:layout title="${it.displayName} Config" cssClass="config j-hide-left" norefresh="true" permission="${it.EXTENDED_READ}">
<st:include page="sidepanel.jelly" />
<l:layout title="${it.displayName} Config" cssclass="main-panel-only" norefresh="true" permission="${it.EXTENDED_READ}">
<f:breadcrumb-config-outline />
<l:main-panel>
<l:js src="jsbundles/config-tabbar.js" />
<l:css src="jsbundles/jenkins-widgets.css" />
<div class="behavior-loading">${%loading}</div>
<f:form method="post" action="configSubmit" name="config" tableClass="job-config tabbed">
<l:js src="jsbundles/config-scrollspy.js" />
<l:css src="jsbundles/config-scrollspy.css" />

<div class="behavior-loading">${%LOADING}</div>
<f:form method="post" action="configSubmit" name="config" tableClass="config-table scrollspy">
<j:set var="descriptor" value="${it.descriptor}" />
<j:set var="instance" value="${it}" />

Expand Down
5 changes: 2 additions & 3 deletions core/src/main/resources/lib/form/select/select.js
Expand Up @@ -44,8 +44,7 @@ Behaviour.specify("SELECT.select", 'select', 1000, function(e) {

function hasChanged(selectEl, originalValue) {
// seems like a race condition allows this to fire before the 'selectEl' is defined. If that happens, exit..

if(!selectEl || !selectEl.options || !selectEl.options[0])
if(!selectEl || !selectEl.options || !selectEl.options.length > 0)
return false;
var firstValue = selectEl.options[0].value;
var selectedValue = selectEl.value;
Expand Down Expand Up @@ -83,4 +82,4 @@ Behaviour.specify("SELECT.select", 'select', 1000, function(e) {
}
});
});
});
});
12 changes: 3 additions & 9 deletions core/src/main/resources/lib/layout/layout.jelly
Expand Up @@ -38,7 +38,7 @@ THE SOFTWARE.
This is necessary for pages that include forms.
</st:attribute>
<st:attribute name="cssclass">
specify a css class name to include in the top level of this layout dom.
A css class name to include in the body element.
</st:attribute>
<st:attribute name="css" deprecated="true">
specify path that starts from "/" for loading additional CSS stylesheet.
Expand Down Expand Up @@ -99,13 +99,7 @@ ${h.initPageVariables(context)}
</j:if>
<link rel="shortcut icon" href="${resURL}/favicon.ico" type="image/vnd.microsoft.icon" />
<link rel="mask-icon" href="${rootURL}/images/mask-icon.svg" color="black" />

<!-- should the sidebar be hidden on load? -->
<style>
body.j-hide-left #side-panel{width:0; padding:0; overflow:hidden;}
body.j-hide-left #main-panel{margin:0 auto; max-width:75em;}
</style>


<!-- are we running as an unit test? -->
<script>var isRunAsTest=${h.isUnitTest}; var rootURL="${rootURL}"; var resURL="${resURL}";</script>

Expand Down Expand Up @@ -174,7 +168,7 @@ ${h.initPageVariables(context)}
<script src="${resURL}/jsbundles/page-init.js" type="text/javascript"/>

</head>
<body id="jenkins" class="yui-skin-sam jenkins-${h.version} ${attrs.cssClass}" data-version="jenkins-${h.version}" data-model-type="${it.class.name}">
<body id="jenkins" class="yui-skin-sam jenkins-${h.version} ${attrs.cssclass}" data-version="jenkins-${h.version}" data-model-type="${it.class.name}">
<!-- for accessibility, skip the entire navigation bar and etc and go straight to the head of the content -->
<a href="#skip2content" class="skiplink">Skip to content</a>

Expand Down
11 changes: 10 additions & 1 deletion war/gulpfile.js
Expand Up @@ -28,5 +28,14 @@ builder.bundle('src/main/js/pluginSetupWizard.js')
//
builder.bundle('src/main/js/config-tabbar.js')
.withExternalModuleMapping('jquery-detached', 'core-assets/jquery-detached:jquery2')
.less('src/main/js/widgets/jenkins-widgets.less')
.less('src/main/js/config-tabbar.less')
.inDir('src/main/webapp/jsbundles');

//
// Bundle the Config Scrollspy.
// See https://github.com/jenkinsci/js-builder#bundling
//
builder.bundle('src/main/js/config-scrollspy.js')
.withExternalModuleMapping('jquery-detached', 'core-assets/jquery-detached:jquery2')
.less('src/main/js/config-scrollspy.less')
.inDir('src/main/webapp/jsbundles');
155 changes: 155 additions & 0 deletions war/src/main/js/config-scrollspy.js
@@ -0,0 +1,155 @@
var $ = require('jquery-detached').getJQuery();
var page = require('./util/page.js');
var windowHandle = require('window-handle');
var isScrolling = false;
var ignoreNextScrollEvent = false;
var pageHeaderHeight = page.pageHeaderHeight();
var breadcrumbBarHeight = page.breadcrumbBarHeight();

// Some stuff useful for testing.
exports.tabbars = [];
exports.scrollspeed = 500;
var eventListeners = [];
exports.on = function(listener) {
eventListeners.push(listener);
};
function notify(event) {
for (var i = 0; i < eventListeners.length; i++) {
eventListeners[i](event);
}
}

$(function() {
var tabBarWidget = require('./widgets/config/tabbar.js');

tabBarWidget.addPageTabs('.config-table.scrollspy', function(tabBar) {
exports.tabbars.push(tabBar);

tabBarWidget.addFinderToggle(tabBar);
tabBar.onShowSection(function() {
// Scroll to the section.
scrollTo(this, tabBar);
// Hook back into hudson-behavior.js
page.fireBottomStickerAdjustEvent();
});

autoActivateTabs(tabBar);
page.onWinScroll(function () {
autoActivateTabs(tabBar);
});
page.onWinScroll(function () {
stickTabbar(tabBar);
});
}, {trackSectionVisibility: true});
});

function scrollTo(section, tabBar) {
var $header = section.headerRow;
var scrollTop = $header.offset().top - ($('#main-panel .jenkins-config-widgets').outerHeight() + 15);

isScrolling = true;
$('html,body').animate({
scrollTop: scrollTop
}, exports.scrollspeed, function() {
if (isScrolling) {
notify({
type: 'click_scrollto',
section: section
});
isScrolling = false;
ignoreNextScrollEvent = stickTabbar(tabBar);
}
});
}

/**
* Watch page scrolling, changing the active tab as needed.
* @param tabBar The tabbar.
*/
function autoActivateTabs(tabBar) {
if (isScrolling === true) {
// Ignore window scroll events while we are doing a scroll.
// See scrollTo function.
return;
}
if (ignoreNextScrollEvent === true) {
// Things like repositioning of the tabbar (see stickTabbar)
// can trigger scroll events that we want to ignore.
ignoreNextScrollEvent = false;
return;
}

var winScrollTop = page.winScrollTop();
var sections = tabBar.sections;

// calculate the top and height of each section to know where to switch the tabs...
$.each(sections, function (i, section) {
if (!section.isVisible()) {
return;
}

// each section enters the viewport at its distance down the page, less the height of
// the toolbar, which hangs down the page. Or it is zero if the section doesn't
// match or was removed...
var viewportEntryOffset = section.getViewportEntryOffset();
// height of this one is the top of the next, less the top of this one.
var sectionHeight = 0;
var nextSection = nextVisibleSection(section);
if (nextSection) {
sectionHeight = nextSection.getViewportEntryOffset() - viewportEntryOffset;
}

// the trigger point to change the tab happens when the scroll position passes below the height of the section...
// ...but we want to wait to advance the tab until the existing section is 75% off the top...
// ### < 75% ADVANCED
if (winScrollTop < (viewportEntryOffset + (0.75 * sectionHeight))) {
section.markAsActive();
notify({
type: 'manual_scrollto',
section: section
});
return false;
}
});
}

/**
* Stick the scrollspy tabbar to the top of the visible area as the user
* scrolls down the page.
* @param tabBar The tabbar.
*/
function stickTabbar(tabBar) {
var win = $(windowHandle.getWindow());
var winScrollTop = page.winScrollTop();
var widgetBox = tabBar.configWidgets;
var configTable = tabBar.configTable;
var configForm = tabBar.configForm;
var setWidth = function() {
widgetBox.width(configForm.outerWidth() - 2);
};

if (winScrollTop > pageHeaderHeight - 5) {
setWidth();
widgetBox.css({
'position': 'fixed',
'top': (breadcrumbBarHeight - 5 ) + 'px',
'margin': '0 auto !important'
});
configTable.css({'margin-top': widgetBox.outerHeight() + 'px'});
win.resize(setWidth);
return true;
} else {
widgetBox.removeAttr('style');
configTable.removeAttr('style');
win.unbind('resize', setWidth);
return false;
}
}

function nextVisibleSection(section) {
var next = section.getSibling(+1);
while (next && !next.isVisible()) {
next = next.getSibling(+1);
}
return next;
}
36 changes: 36 additions & 0 deletions war/src/main/js/config-scrollspy.less
@@ -0,0 +1,36 @@
@import "widgets/variables";

/*
* Mixins
* http://lesscss.org/features/#mixins-feature
*/
@import "widgets/layout-mixins";
@import "widgets/form/form-mixins";

/*
* Widget styles
*/
@import "widgets/config/tabbar";


/*
* ----------------------------------------------------------------------------------------
* Scrollspy specific styles.
* ----------------------------------------------------------------------------------------
*/

.jenkins-config {
// The Section header for the General section was manufactured,
// so the default is to not display it.
.section-header-row.config_general .section-header {
display: none;
}
}

.jenkins-config-widgets {
.find-container {
.find {
display: none;
}
}
}

0 comments on commit 904d9b1

Please sign in to comment.