Skip to content

Instantly share code, notes, and snippets.

@manumaticx
Last active July 17, 2017 16:10
Show Gist options
  • Save manumaticx/6125a8398fe9486f0eed8200ea3d2d2a to your computer and use it in GitHub Desktop.
Save manumaticx/6125a8398fe9486f0eed8200ea3d2d2a to your computer and use it in GitHub Desktop.
port of PagingControl Alloy Widget for Titanium Classic
/**
* Original Alloy Widget:
* https://github.com/manumaticx/pagingcontrol
*
* This implementation is a port for Classic Titanium.
* @author: Manuel Lehner
*/
var OS_ANDROID = Ti.Platform.osname === 'android';
var OS_IOS = !OS_ANDROID;
var PagingControl = function(args) {
this.view = Ti.UI.createScrollView({
id : 'pagingcontrol',
scrollType: 'horizontal',
width: Ti.UI.FILL,
contentWidth: 'auto',
contentHeight: Ti.UI.FILL,
showHorizontalScrollIndicator: false,
showVerticalScrollIndicator: false
});
this.scrollableView = undefined;
this.tabsCtrl = undefined;
this.indicator = undefined;
this.indicatorWidth = undefined;
this.previousPage = undefined;
// xml boolean args is string ("false" == true)
['tabs', 'findScrollableView'].forEach(function(key){
if (!args.hasOwnProperty(key)){
return;
}
try {
args[key] = JSON.parse(args[key]);
} catch (e) {
delete args[key];
Ti.API.error("Unable to set argument '" + key + "'. It must be boolean.");
}
});
// fill undefined args with defaults
this.args = Object.assign({
bottomColor: "#ededed",
dividerColor: "#ccc",
indicatorColor: "#000",
indicatorHeight: 5,
tabs: false,
scrollOffset: 40,
width: Ti.UI.FILL,
findScrollableView: true,
color: "#000",
font : {
fontSize : 13,
fontWeight : 'bold'
}
}, args);
this.args.height = this.args.tabs ? 48 : 5;
// additional adjustments for tabs
if (this.args.tabs) {
this.args.tabs = {
dividerColor: this.args.dividerColor,
width: this.args.tabWidth,
backgroundColor : this.args.backgroundColor,
activeColor: this.args.activeColor,
color: this.args.color,
font : this.args.font
};
}
// apply properties of Ti.UI.View that can be applied to paging control view
[
"backgroundColor",
"backgroundImage",
"backgroundLeftCap",
"backgroundRepeat",
"backgroundTopCap",
"borderRadius",
"borderWidth",
"bottom",
"height",
"horizontalWrap",
"left",
"opacity",
"right",
"top",
"visible",
"width",
"zIndex"
].forEach(function(prop){
this.args.hasOwnProperty(prop) && (this.view[prop] = this.args[prop]);
}.bind(this));
// assign passed reference of scrollable view
(this.args.hasOwnProperty("scrollableView")) && (this.scrollableView = this.args.scrollableView);
return this;
};
/**
* calls back after postlayout
* @param {Function} callback
* @param {Boolean} orientationChange wether called from oc callback
*/
PagingControl.prototype.postLayout = function(callback, orientationChange){
if (!orientationChange && this.view.size.width){
callback();
} else {
// wait for postlayout event to get the pagingcontrol size
this.view.addEventListener('postlayout', function onPostLayout(evt){
// callback
callback();
// remove eventlistener
evt.source.removeEventListener('postlayout', onPostLayout);
});
}
};
/**
* initialization method
*/
PagingControl.prototype.init = function(){
if (this.args.tabs) {
// create tabs
this.tabsCtrl = new TabsControl({
tabs: this.args.tabs,
titles: this.scrollableView.getViews().map(function(v){ return v.title; })
});
// add tabs
this.view.add(this.tabsCtrl.getView());
// add bottom border
this.view.add(Ti.UI.createView({
width: Ti.UI.FILL,
height: 2,
bottom: 0,
backgroundColor: this.args.bottomColor
}));
// add tab select listener
this.tabsCtrl.getView().addEventListener('select', this.onTabSelect.bind(this));
}
// create the indicator view
this.indicator = Ti.UI.createView({
backgroundColor: this.args.indicatorColor,
height: this.args.indicatorHeight,
width: Ti.UI.SIZE,
bottom: 0,
left: 0,
zIndex: 2
});
this.adjustPositions();
// add the indicator
this.view.add(this.indicator);
// add scroll listener to scrollable view
this.scrollableView.addEventListener('scroll', this.onScroll.bind(this));
this.scrollableView.addEventListener('scrollend', this.onScrollEnd.bind(this));
Ti.Gesture.addEventListener('orientationchange', this.onOrientationChange.bind(this));
};
PagingControl.prototype.onTabSelect = function(e){
this.view.fireEvent('select', { tab: e.tab, view: e.view });
this.scrollableView.currentPage = e.tab;
this.indicator.setLeft(e.tab * this.indicatorWidth);
};
/**
* Callback for scroll event
*/
PagingControl.prototype.onScroll = function(e){
// restrict this to $.scrollableView to support nesting scrollableViews
if(e.source !== this.scrollableView)
return;
// update the indicator position
this.indicator.setLeft(e.currentPageAsFloat * this.indicatorWidth);
this.args.tabs && this.updateOffset(e.currentPageAsFloat);
};
/**
* Callback for scrollend event
*/
PagingControl.prototype.onScrollEnd = function(e){
if (this.previousPage !== e.currentPage) {
this.tabsCtrl.selectColor(e.currentPage);
this.previousPage = e.currentPage;
}
};
/**
* sets the tab bar offset
* @param {Number} index
*/
PagingControl.prototype.updateOffset = function(index){
if (this.args.tabWidth === 'auto'){
return;
}
var dpToPX = function (val) {
if (OS_ANDROID) {
return val * Ti.Platform.displayCaps.dpi / 160;
} else if (OS_IOS) {
switch (Ti.Platform.displayCaps.density) {
case 'xhigh': return val * 3;
case 'high': return val * 2;
default: return val;
}
} else {
return val;
}
};
var width = this.view.size.width,
tabsWidth = this.tabsCtrl.getWidth(),
maxOffset = tabsWidth - width,
tabSpace = tabsWidth * index / this.scrollableView.views.length;
if (width < tabsWidth){
var offset = tabSpace - this.args.scrollOffset,
offsetDp = offset < maxOffset ? offset : maxOffset,
newOffset = OS_IOS ? (offsetDp < 0 ? 0 : offsetDp) : dpToPX(offsetDp);
this.view.setContentOffset(
{ x: newOffset, y: 0},
{ animated: false }
);
}
};
/**
* Callback for orientationchange event
*/
PagingControl.prototype.onOrientationChange = function(e){
this.postLayout(function(){
this.tabsCtrl.updateWidth();
this.adjustPositions();
}.bind(this), true);
};
/**
* Adjust initial layout positions
*/
PagingControl.prototype.adjustPositions = function() {
var totalWidth = this.args.tabs ? this.tabsCtrl.getWidth() : this.view.size.width;
this.indicatorWidth = Math.floor(totalWidth / this.scrollableView.views.length);
this.indicator.setWidth(this.indicatorWidth);
this.indicator.setLeft(this.scrollableView.getCurrentPage() * this.indicatorWidth);
};
/**
* update the paging control if views were added / removed from scrollableView
*/
PagingControl.prototype.refresh = function(){
this.view.removeAllChildren();
this.cleanup();
this.init();
};
/**
* if you need to set it in the controller
* @param {Ti.UI.Scrollableview} _scrollableView
*/
PagingControl.prototype.setScrollableView = function(_scrollableView){
if(this.scrollableView) {
Ti.API.error("Already initialized");
return;
}
this.scrollableView = _scrollableView;
this.postLayout(this.init);
};
/**
* removes orientationchange Listener
*/
PagingControl.prototype.cleanup = function(){
this.scrollableView.removeEventListener('scroll', this.onScroll);
this.scrollableView.removeEventListener('scrollend', this.onScrollEnd);
Ti.Gesture.removeEventListener('orientationchange', this.onOrientationChange);
this.args.tabs
&& this.tabsCtrl
&& this.tabsCtrl.getView().removeEventListener('select', PagingControl.prototype.onTabSelect)
&& (this.tabsCtrl = null);
this.indicator = null;
};
/**
* A getter for the paging control view
* @returns {*}
*/
PagingControl.prototype.getView = function(){
return this.view;
};
/** --------------------------------------------------------------------- **/
/** The following part belongs to the Tabs Control ---------------------- **/
/** --------------------------------------------------------------------- **/
function TabsControl(args){
this.view = Ti.UI.createView({
layout: 'horizontal'
});
this.tabs = [];
this.opts = {};
this.labels = [];
this.tabWidth = undefined;
function getTabWidth(num){
var displayWidth = Ti.Platform.displayCaps.platformWidth,
orientation = Ti.Gesture.orientation,
denominator,
width;
OS_ANDROID && (displayWidth /= Ti.Platform.displayCaps.logicalDensityFactor);
// there is more space in landscape, so we show more tabs then
if (orientation === Ti.UI.LANDSCAPE_LEFT || orientation === Ti.UI.LANDSCAPE_RIGHT){
denominator = num || 7;
} else {
denominator = num || 4;
}
width = Math.floor(displayWidth / denominator) - 1;
return width;
}
Object.assign(this.opts, args || {});
// 'auto' makes the tabs fill the available width
if (args.tabs.width === 'auto'){
this.opts.fitWidth = true;
}
if (this.opts.fitWidth){
this.tabWidth = getTabWidth(args.titles.length);
}else{
this.tabWidth = args.tabs.width || getTabWidth();
}
if(typeof this.tabWidth === "string" && this.tabWidth.indexOf('%')>0) {
var newWidth = parseInt(this.tabWidth.slice(0, this.tabWidth.indexOf('%')))/100;
OS_ANDROID && (newWidth /= Ti.Platform.displayCaps.logicalDensityFactor);
this.tabWidth = newWidth * Ti.Platform.displayCaps.platformWidth;
}
this.view.applyProperties({
left: 0,
width: this.getWidth(),
height: Ti.UI.FILL
});
for (var i = 0; i < args.titles.length; i++){
this.tabs[i] = Ti.UI.createView({
width: this.tabWidth,
height: Ti.UI.FILL
});
var label = Ti.UI.createLabel({
color: (i === 0 && !!args.tabs.activeColor) ? args.tabs.activeColor : args.tabs.color,
font: args.tabs.font,
text: args.titles[i]
});
this.labels.push(label);
this.tabs[i].add(label);
var addEvent = function(index){
var that = this;
this.tabs[i].addEventListener('click', function(){
var view = this;
that.view.fireEvent('select', { tab: index, view: view });
});
};
addEvent.apply(this, i);
this.view.add(this.tabs[i]);
if (i < args.titles.length - 1){
// add divider
this.view.add(Ti.UI.createView({
backgroundColor: args.tabs.dividerColor,
height: 32,
width: 1
}));
}
}
return this;
}
TabsControl.prototype.getWidth = function getWidth(){
return this.tabWidth * this.opts.titles.length + this.opts.titles.length;
};
TabsControl.prototype.updateWidth = function updateWidth(){
this.view.setWidth(Ti.UI.FILL);
if (this.opts.fitWidth){
this.tabWidth = this.getTabWidth(this.opts.titles.length);
this.tabs.forEach(function(tab){
tab.setWidth(this.tabWidth - 1);
}.bind(this));
}
};
TabsControl.prototype.selectColor = function selectColor(index) {
if (!this.opts.tabs.activeColor) {
return;
}
for (var j = 0; j < this.labels.length; j++) {
this.labels[j].color = (j === index) ? this.opts.tabs.activeColor : this.opts.tabs.color;
}
};
TabsControl.prototype.getView = function () {
return this.view;
};
module.exports = PagingControl;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment