JavaScript/js/ui-utils/tab-manager.js
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
The tab manager takes care of URL routing, selecting and constructing tabs, |
highlighting the code blocks, binding the run button handler, scrolling to the results, etc. |
Almost all functionality that is common to the tabs is to be found here. |
*/ |
CKCatalog.tabManager = (function() { |
var self = {}; |
var page = document.getElementById('page'); |
var scrollView = page.parentNode; |
var menuItems = document.querySelectorAll('.menu-item'); |
var runButton = document.getElementById('run-button'); |
var expandButton = document.getElementById('expand-left-column'); |
var contractButton = document.getElementById('contract-left-column'); |
var leftPane = document.getElementById('left-pane'); |
var subTabMenuItems; |
var selectedTabName; |
var selectedSubTabIndex = 0; |
var subTabMenus = {}; |
var tabs = {}; |
var defaultRoute = ['readme']; |
leftPane.addEventListener('transitionend', function() { |
if(leftPane.classList.contains('expanded')) { |
contractButton.classList.remove('hide'); |
leftPane.style.overflow = 'visible'; |
} else { |
expandButton.classList.remove('hide'); |
} |
}); |
var expandLeftPane = function() { |
leftPane.classList.add('expanded'); |
expandButton.classList.add('hide'); |
}; |
var contractLeftPane = function() { |
leftPane.classList.remove('expanded'); |
contractButton.classList.add('hide'); |
leftPane.style.overflow = 'hidden'; |
for(var i=0; i < menuItems.length; i++) { |
menuItems[i].parentNode.classList.remove('expanded'); |
} |
}; |
window.addEventListener('resize',function(){ |
if(window.outerWidth < 1140) { |
contractLeftPane(); |
} |
}); |
expandButton.onclick = expandLeftPane; |
contractButton.onclick = contractLeftPane; |
leftPane.addEventListener('click',function(e){ |
var node = e.target; |
if(!e.target.classList.contains('caret') && !e.target.classList.contains('tab-menu-item')) { |
node = e.target.parentNode.parentNode; |
} |
if(node.classList.contains('caret')) { |
node.classList.toggle('expanded'); |
e.preventDefault(); |
if(leftPane.offsetWidth < 50) { |
expandLeftPane(); |
} |
} |
}); |
var codeHighlightingIsInitialized = false; |
// Highlight the sample code if possible. |
var highlightCode = function() { |
if(typeof hljs !== 'undefined') { |
codeHighlightingIsInitialized = true; |
try { |
var codeSamples = document.querySelectorAll('pre code'); |
for (var j = 0; j < codeSamples.length; j++) { |
hljs.highlightBlock(codeSamples[j]); |
} |
} catch (e) { |
console.error('Unable to highlight sample code: ' + e.message); |
} |
} |
}; |
var runCode = function() { |
if(typeof CloudKit === 'undefined') { |
CKCatalog.dialog.showError(new Error( |
'The variable CloudKit is not defined. The CloudKit JS library may still be loading or may have failed to load.' |
)); |
return; |
} |
if(selectedTabName) { |
var selectedTab = CKCatalog.tabs[selectedTabName]; |
var subTab = selectedTab[selectedSubTabIndex]; |
CKCatalog.dialog.show('Executing…'); |
var run = subTab.run ? subTab.run : subTab.sampleCode; |
try { |
run.call(subTab).then(function (content) { |
CKCatalog.dialog.hide(); |
if (content && content instanceof Node) { |
// Replace the DOM content with the code results. |
subTab.content.replaceChild(content,subTab.content.firstChild); |
var heading = document.createElement('h1'); |
heading.textContent = 'Result'; |
content.insertBefore(heading,content.firstChild); |
} |
// Animate scrolling the results up the page. |
var padding = 39; |
var change = subTab.content.offsetTop - padding; |
subTab.content.style.minHeight = (scrollView.offsetHeight - padding) + 'px'; |
var start = scrollView.scrollTop; |
var startTime = 0; |
var duration = 500; |
var easingValue = function(t) { |
var tc = (t/=duration)*t*t; |
return start + change*(tc); |
}; |
var animateScroll = function(timestamp) { |
if(!startTime) { |
startTime = timestamp; |
} |
var progress = timestamp - startTime; |
scrollView.scrollTop = easingValue(Math.min(progress,duration)); |
if(progress < duration) { |
window.requestAnimationFrame(animateScroll); |
} else { |
var results = subTab.content.firstChild; |
results.className = 'results'; |
} |
}; |
window.requestAnimationFrame(animateScroll); |
}, CKCatalog.dialog.showError); |
} catch(e) { |
CKCatalog.dialog.showError(e); |
} |
} |
}; |
runButton.onclick = runCode; |
runButton.onmousedown = function() { |
runButton.parentNode.classList.add('active'); |
}; |
runButton.onmouseup = function() { |
runButton.parentNode.classList.remove('active'); |
}; |
var createSampleCodeSegment = function(tabSegment,selected) { |
var el = document.createElement('div'); |
el.className = 'page-segment' + (selected ? ' selected' : ''); |
el.appendChild(tabSegment.description); |
if(tabSegment.sampleCode) { |
var sampleCode = document.createElement('pre'); |
sampleCode.className = 'javascript sample-code'; |
// Fix up indentation. |
var sampleCodeString = tabSegment.sampleCode.toString(); |
var indentationCorrection = sampleCodeString.lastIndexOf('}') - sampleCodeString.lastIndexOf('\n') - 1; |
var regExp = new RegExp('\n[ ]{' + indentationCorrection + '}', 'g'); |
// Insert sample code into HTML. |
sampleCode.innerHTML = '<code>' + sampleCodeString.replace(regExp, '\n') + '</code>'; |
if (!tabSegment.content) { |
tabSegment.content = document.createElement('div'); |
tabSegment.content.className = 'content'; |
tabSegment.content.innerHTML = '<div class="results"></div>'; |
} |
if(tabSegment.form) { |
tabSegment.form.onSubmit(runCode); |
var formContainer = document.createElement('div'); |
formContainer.className = 'input-fields'; |
formContainer.appendChild(tabSegment.form.el); |
el.appendChild(formContainer); |
sampleCode.classList.add('no-top-border'); |
} |
el.appendChild(sampleCode); |
el.appendChild(tabSegment.content); |
runButton.disabled = false; |
runButton.parentNode.classList.remove('disabled'); |
} else { |
runButton.disabled = true; |
runButton.parentNode.classList.add('disabled'); |
} |
return el; |
}; |
var createSubTabMenu = function(tabName) { |
var menu = document.createElement('div'); |
menu.className = 'tab-menu '; |
menu.setAttribute('data-tab',tabName); |
return menu; |
}; |
var createSubTabMenuItem = function(name,index) { |
var item = document.createElement('div'); |
item.className = 'tab-menu-item'; |
item.setAttribute('data-subtab',index); |
item.textContent = name; |
item.onclick = function() { |
var currentTabName = getRoute(); |
var targetTabName = item.parentNode.getAttribute('data-tab'); |
if(currentTabName !== targetTabName) { |
window.location.hash = targetTabName + '/' + item.getAttribute('data-subtab'); |
} |
// Scroll to top. |
scrollView.scrollTop = 0; |
}; |
return item; |
}; |
// Now insert the submenus into the DOM in the left-hand pane. |
for(var tabName in CKCatalog.tabs) { |
if(CKCatalog.tabs.hasOwnProperty(tabName)) { |
if(CKCatalog.tabs[tabName].length > 1) { |
var subMenuContainer = document.querySelector('.left-pane .menu-items .menu-item-container.' + tabName); |
var subTabMenu = createSubTabMenu(tabName); |
subTabMenus[tabName] = []; |
CKCatalog.tabs[tabName].forEach(function(subTab,index) { |
subTabMenus[tabName].push(subTabMenu.appendChild(createSubTabMenuItem(subTab.title,index))); |
}); |
subMenuContainer.appendChild(subTabMenu); |
subMenuContainer.classList.add('caret'); |
} |
} |
} |
var getRoute = function() { |
var hash = window.location.hash; |
if(!hash || hash[0] !== '#') return defaultRoute; |
return hash.substr(1).split('/') || defaultRoute; |
}; |
var selectTab = function() { |
var route = getRoute(); |
var tabName = route[0]; |
var subTabIndex = parseInt(route[1]) || 0; |
if(tabName !== selectedTabName) { |
var tab = CKCatalog.tabs[tabName]; |
if (!tab) { |
tabName = 'not-found'; |
tab = CKCatalog.tabs[tabName]; |
} |
for (var i = 0; i < menuItems.length; i++) { |
var menuItem = menuItems[i]; |
if (menuItem.attributes.href.value === '#' + tabName) { |
menuItem.parentNode.classList.add('selected'); |
} else { |
menuItem.parentNode.classList.remove('selected'); |
} |
} |
subTabMenuItems = subTabMenus[tabName]; |
if(!tabs.hasOwnProperty(tabName)) { |
tabs[tabName] = document.createElement('div'); |
var pageSegments = tabs[tabName]; |
pageSegments.className = 'page-segments'; |
var descriptions = document.getElementById(tabName); |
tab.forEach(function (tabSegment, index) { |
if (!tabSegment.description) { |
tabSegment.description = descriptions.firstElementChild; |
} |
pageSegments.appendChild(createSampleCodeSegment(tabSegment, index === selectedSubTabIndex)); |
}); |
page.replaceChild(pageSegments, page.firstElementChild); |
highlightCode(); |
} else { |
page.replaceChild(tabs[tabName], page.firstElementChild); |
} |
selectedTabName = tabName; |
} |
if(subTabIndex >= tabs[tabName].childElementCount || subTabIndex < 0) { |
subTabIndex = 0; |
} |
var subTabs = tabs[tabName].childNodes; |
for(var index=0; index<subTabs.length; index++) { |
if (index === subTabIndex) { |
subTabs[index].classList.add('selected'); |
} else { |
subTabs[index].classList.remove('selected'); |
} |
if(subTabMenuItems) { |
var subTabMenuItem = subTabMenuItems[index]; |
if (index === subTabIndex) { |
subTabMenuItem.classList.add('selected'); |
} else { |
subTabMenuItem.classList.remove('selected'); |
} |
} |
} |
selectedSubTabIndex = subTabIndex; |
if(leftPane.classList.contains('expanded')) { |
setTimeout(contractLeftPane,300); |
} |
// Scroll to top. |
scrollView.scrollTop = 0; |
}; |
// Set up URL routing. |
window.addEventListener('hashchange',selectTab); |
// select the default tab. |
selectTab(); |
// This is the only public method on this singleton. |
self.initializeCodeHighlighting = function() { |
// Insert the stylesheet. |
var link = document.createElement('link'); |
link.setAttribute('rel','stylesheet'); |
link.setAttribute('href','https://cdn.apple-cloudkit.com/cloudkit-catalog/xcode.css'); |
document.getElementsByTagName('head')[0].appendChild(link); |
// Highlight the code if necessary. |
link.onload = function() { |
if (!codeHighlightingIsInitialized) { |
highlightCode(); |
} |
} |
}; |
return self; |
})(); |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-13