<!DOCTYPE html>
<html ng-app="app">
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container well" ng-controller="MainCtrl as app">
<custom-directive func-arg="app.add()" bindable-arg="app.arg">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</custom-directive>
</div>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.angularjs.org/1.4.0-rc.1/angular.js"></script>
<script src="mb-scrollbar.js"></script>
<script src="app.js"></script>
</body>
</html>
(function (ng) {
"use strict";
var MainCtrl, customDirective;
MainCtrl = function MainCtrlF() {
this.arg = {
a: 1,
b: 2,
c: {
d: 3,
e:4
}
};
this.add = function addF() {
console.log('add');
};
};
customDirective = function customDirectiveF() {
var link, template, directiveDefinitionObject;
template = [
"<div class=\"cutom-directive\">",
"<i class=\"glyphicon glyphicon-refresh\"></i>",
"<span>title here</span>",
"<div ng-transclude mb-scrollbar=\"scrollbarConfig\"></div>",
"</div>"
].join('');
link = function linkF(iScope) {
iScope.scrollbarConfig = {
autoResize: true,
scrollTo: 'start'
};
};
directiveDefinitionObject = {
priority: 100,
restrict: 'E',
replace: true,
transclude: true,
link: link,
template: template,
scope: {funcArg: "@", bindableArg: "="}
};
return directiveDefinitionObject;
};
ng.module("app", ["mb-scrollbar"])
.controller("MainCtrl", MainCtrl)
.directive("customDirective", customDirective);
}(angular));
/* Styles go here */
# Custom scrollbar
/*
* mb-scrollbar v2.2.0
* Plugin for AngularJS
* (c) 2014 Matthew Balmer http://mattbalmer.com
* License: MIT
*/
angular.module('mb-scrollbar', [])
.directive('mbScrollbar', function() {
return {
restrict: 'A',
transclude: true,
scope: {
config: '=mbScrollbar'
},
template: "<div class='ngscroll-resizable' style='position: relative; width: 100%; height: 100%;'> <div class='ngscroll-container' style='width: 100%; height: 100%;' ng-transclude></div> <div class='ngscroll-scrollbar-container' ng-style='styles.scrollbarContainer'><div class='ngscroll-scrollbar' ng-style='styles.scrollbar'></div></div> </div>",
link: function(scope, element) {
// Helper functions
function ifVertElseHor(vertical, horizontal) {
return config.direction == 'horizontal' ? horizontal : vertical;
}
function overwriteProperties(baseObject, newObject) {
for(var k in newObject) {
if( (newObject || {}).hasOwnProperty(k) && (baseObject || {}).hasOwnProperty(k) ) {
if( typeof baseObject[k] === 'object' ) {
baseObject[k] = overwriteProperties(baseObject[k], newObject[k]);
} else {
baseObject[k] = newObject[k];
}
}
}
return baseObject;
}
// Base Configuration
var config = {
autoResize: false,
direction: (scope.config || {}).direction || 'vertical',
scrollbar: {
width: 6,
hoverWidth: 8,
color: 'rgba(0,0,0,.6)',
show: false
},
scrollbarContainer: {
width: 12,
color: 'rgba(0,0,0,.1)'
},
dragSpeedModifier : 1,
firefoxModifier: 40,
scrollTo: (scope.config || {}).scrollTo || null
};
config.dimension = ifVertElseHor('height', 'width');
config.rDimension = ifVertElseHor('width', 'height');
config.position = ifVertElseHor('top', 'left');
config.rPosition = ifVertElseHor('right', 'bottom');
// Add user-input
config = overwriteProperties(config, scope.config || {});
// Computed configuration variables
config.scrollbar.margin = (config.scrollbarContainer.width - config.scrollbar.width) / 2;
config.scrollbar.hoverMargin = (config.scrollbarContainer.width - config.scrollbar.hoverWidth) / 2;
// Elements
var child = angular.element( element[0].querySelector('.ngscroll-container') ),
scrollbarContainer = angular.element( element[0].querySelector('.ngscroll-scrollbar-container') ),
scrollbar = angular.element( scrollbarContainer.children()[0] ),
containerSize = 0,
scrollbarLength,
length = 0;
// Set the initial css
scope.styles = {
scrollbar: {
position: 'absolute',
cursor: 'default',
opacity: config.scrollbar.show ? 1 : 0,
background: config.scrollbar.color,
'border-radius': config.scrollbar.width / 2 + 'px'
},
scrollbarContainer: {
position: 'absolute',
transition: 'background .3s ease-in-out',
'border-radius': config.scrollbarContainer.width / 2 + 'px'
}
};
// Set calculated CSS
scrollbar.css( config.rDimension , config.scrollbar.width+'px' );
scrollbar.css( config.rPosition , config.scrollbar.margin +'px' );
scrollbar.css( config.position , config.scrollbar.margin +'px' );
scrollbarContainer.css( config.rPosition , '0' );
scrollbarContainer.css( config.position , '0' );
scrollbarContainer.css( config.dimension , '100%' );
scrollbarContainer.css( config.rDimension , config.scrollbarContainer.width +'px' );
// Reusable scroll function
function scroll(distance) {
var margin = 'margin-'+config.position;
var newMargin = parseInt( child.css(margin) || 0 ) + distance;
scrollTo(newMargin);
}
function scrollTo(position) {
var margin = 'margin-'+config.position;
var newMargin = Math.min( 0, Math.max( position, -length + containerSize ) );
var pct = -newMargin / length;
child.css(margin, newMargin + 'px');
scrollbar.css(config.position, pct * containerSize + config.scrollbar.margin +'px');
scrollbarContainer.css(margin, ifVertElseHor(-newMargin, 0) + 'px');
}
// Hiding/showing the scrollbar
function hideScrollbar() {
scrollbar.css('opacity', 0);
}
function showScrollbar() {
scrollbar.css('opacity', 1);
}
// On item set change
var recalculate = function() {
ifVertElseHor(function() {
child.css('height', 'auto');
length = child[0].scrollHeight || 0;
}, function() {
length = 0;
var children = child.children();
for(var i = 0; i < children.length; i++) {
length += children[i].offsetWidth;
}
})();
// Bug that the containerSize is not known at the initialisation of the script. After a recalculate it is known, update and use it.
containerSize = ifVertElseHor( element[0].offsetHeight, element[0].offsetWidth);
// A higher drag-speed modifier on longer container sizes makes for more comfortable scrolling
config.dragSpeedModifier = Math.max(1, 1 / ( scrollbarLength / containerSize ));
child.css(config.dimension, length+'px');
// If scroll is not necessary, set the scrollbarLength to be containerSize (minus the margins)
if(containerSize >= length) {
length = containerSize;
element.addClass('no-scrollbar');
element.removeClass('has-scrollbar');
hideScrollbar();
} else {
element.addClass('has-scrollbar');
element.removeClass('no-scrollbar');
if(config.scrollbar.show)
showScrollbar();
}
scrollbarLength = ( containerSize / length ) * containerSize - config.scrollbar.margin * 2;
scrollbar.css(config.dimension, scrollbarLength + 'px');
scrollbar.css('transition', 'opacity .3s ease-in-out, border-radius .1s linear, ' +
config.rDimension+' .1s linear, ' +
config.rPosition+' .1s linear');
// Scroll to the start, end, or a pixel value given in the config. If null, just stay there
if (config.scrollTo == null)
scroll(0); // Moves the scroll area back into view if a resizing would have moved it out (eg. children removed)
else if (config.scrollTo == 'start')
scrollTo(0);
else if (config.scrollTo == 'end')
scrollTo( -length + containerSize );
else
scrollTo( -parseInt(config.scrollTo) ); // Negative to account for the negative margin
};
// listen to DOM modification, IE11+, FF, Chrome, Safari
// @added new MutationObserver
if (typeof MutationObserver === 'function' ) {
var observer = new MutationObserver(function (mutations) {
// delay recalculation, prevent recalculation before animation ends
setTimeout(function () { recalculate(); }, 200);
});
observer.observe(element[0], {
childList: true,
subtree: true,
characterData: true,
attributes: true
});
} else {
// fallback compatibility
if(config.autoResize === true) {
child.on('DOMNodeInserted', recalculate);
child.on('DOMNodeRemoved', recalculate);
}
}
// Listen to manual recalculate calls
scope.$on('recalculateMBScrollbars', function(event) {
setTimeout(function() {
recalculate();
}, 5);
});
scope.$on('scrollToMBScrollbars', function (event, offset) {
setTimeout(function () {
scrollTo(offset);
}, 5);
});
// Move on scroll
child.on('mousewheel', function(event) {
event.preventDefault();
// If jQuery hid the original event, retrieve it
if( event.originalEvent != undefined )
event = event.originalEvent;
var delta = ifVertElseHor(event.wheelDeltaY || event.wheelDelta, event.wheelDeltaX || event.wheelDeltaY || event.wheelDelta);
scroll( delta );
});
if( window.navigator.userAgent.toLowerCase().indexOf('firefox') >= 0) {
child.on('wheel', function(event) {
event.preventDefault();
// If jQuery hid the original event, retrieve it
if( event.originalEvent != undefined )
event = event.originalEvent;
var delta = ifVertElseHor(event.deltaY, event.deltaX > 0 ? event.deltaX : event.deltaY);
scroll( - delta * config.firefoxModifier );
return false;
});
}
// Scrollbar controls
var scrollbarMousedown, scrollbarOffset;
scrollbar.on('mousedown', function(event) {
event.preventDefault();
scrollbarMousedown = true;
// Set mouseup listener
angular.element(document).on('mouseup', function() {
scrollbarMousedown = false;
if(!config.scrollbar.show)
hideScrollbar();
});
scrollbarOffset = ifVertElseHor(event.screenY, event.screenX);
return false;
});
angular.element(document).on('mousemove', function(event) {
if(!scrollbarMousedown) return;
event.preventDefault();
var delta = ifVertElseHor(event.screenY, event.screenX) - scrollbarOffset;
delta *= config.dragSpeedModifier;
scrollbarOffset += delta * (scrollbarLength / containerSize);
scroll( -delta );
});
// Show scrollbar on hover
if(!config.scrollbar.show) {
element.on('mouseenter', showScrollbar);
scrollbarContainer.on('mouseenter', showScrollbar);
element.on('mouseleave', function() {
if(scrollbarMousedown) return;
hideScrollbar();
});
}
// On enter scrollbar container
scrollbarContainer.on('mouseenter', function() {
scrollbarContainer.css('background', config.scrollbarContainer.color);
scrollbar.css( config.rDimension, config.scrollbar.hoverWidth+'px' );
scrollbar.css( config.rPosition, config.scrollbar.hoverMargin +'px' );
scrollbar.css( 'border-radius', config.scrollbar.hoverWidth / 2 + 'px' );
});
scrollbarContainer.on('mouseleave', function() {
scrollbarContainer.css('background', 'none');
scrollbar.css( config.rDimension, config.scrollbar.width + 'px' );
scrollbar.css( config.rPosition, config.scrollbar.margin+'px' );
scrollbar.css( 'border-radius', config.scrollbar.width / 2 +'px' );
});
// Initial calculate
recalculate();
}
}
})
.service('mbScrollbar', function() {
// Provide a method that wraps the broadcast in a timeout, which allows use inside Controllers
this.recalculate = function($scope) {
setTimeout(function() {
$scope.$broadcast('recalculateMBScrollbars');
}, 5);
};
this.scrollTo = function ($scope, event) {
setTimeout(function() {
$scope.$broadcast('scrollToMBScrollbars', event);
}, 5);
};
});