Files
commafeed/src/main/webapp/js/directives.js

442 lines
11 KiB
JavaScript

var module = angular.module('commafeed.directives', []);
module.directive('focus', [ '$timeout', function($timeout) {
return {
restrict : 'A',
link : function(scope, element, attrs) {
scope.$watch(attrs.focus, function(value) {
if (!value)
return;
$timeout(function() {
$(element).focus();
});
});
}
};
} ]);
/**
* Open a popup window pointing to the url in the href attribute
*/
module
.directive(
'popup',
function() {
return {
link : function(scope, elm, attrs) {
elm
.bind(
'click',
function(event) {
window
.open(this.href, '',
'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=800');
event.preventDefault();
});
}
};
});
/**
* Reusable favicon component
*/
module
.directive(
'favicon',
function() {
return {
restrict : 'E',
scope : {
url : '='
},
replace : true,
template : '<img ng-src="{{url}}" class="favicon"></img>'
};
});
/**
* Support for the blur event
*/
module.directive('ngBlur', function() {
return {
restrict : 'A',
link : function(scope, elm, attrs) {
elm.bind('blur', function() {
scope.$apply(attrs.ngBlur);
});
}
};
});
/**
* Fired when the top of the element is not visible anymore
*/
module.directive('onScrollMiddle', function() {
return {
restrict : 'A',
link : function(scope, element, attrs) {
var w = $(window);
var e = $(element);
var d = $(document);
var down = function() {
var docTop = w.scrollTop();
var elemTop = e.offset().top;
var threshold = docTop === 0 ? elemTop - 1 : docTop
+ w.height() / 3;
return (elemTop > threshold) ? 'below' : 'above';
};
var up = function() {
var docTop = w.scrollTop();
var elemTop = e.offset().top;
var elemBottom = elemTop + e.height();
var threshold = docTop === 0 ? elemBottom - 1 : docTop
+ w.height() / 3;
return (elemBottom > threshold) ? 'below' : 'above';
};
if (!w.data.scrollInit) {
w.data.scrollPosition = d.scrollTop();
w.data.scrollDirection = 'down';
var onScroll = function(e) {
var scroll = d.scrollTop();
w.data.scrollDirection = (scroll
- w.data.scrollPosition > 0) ? 'down'
: 'up';
w.data.scrollPosition = scroll;
scope.$apply();
};
w.bind('scroll', _.throttle(onScroll, 500));
w.data.scrollInit = true;
}
scope.$watch(down, function downCallback(value, oldValue) {
if (value != oldValue && value == 'above')
scope.$eval(attrs.onScrollMiddle);
});
scope.$watch(up, function upCallback(value, oldValue) {
if (value != oldValue && value == 'below')
scope.$eval(attrs.onScrollMiddle);
});
}
};
});
/**
* Scrolls to the element if the value is true and the attribute is not fully visible,
* unless the attribute scroll-to-force is true
*/
module.directive('scrollTo', [ '$timeout', function($timeout) {
return {
restrict : 'A',
link : function(scope, element, attrs) {
scope.$watch(attrs.scrollTo, function(value) {
if (!value)
return;
var force = scope.$eval(attrs.scrollToForce);
// timeout here to execute after dom update
$timeout(function() {
var docTop = $(window).scrollTop();
var docBottom = docTop + $(window).height();
var elemTop = $(element).offset().top;
var elemBottom = elemTop + $(element).height();
if (!force && (elemTop > docTop) && (elemBottom < docBottom)) {
// element is entirely visible
return;
} else {
var offset = parseInt(attrs.scrollToOffset, 10);
var scrollTop = $(element).offset().top + offset;
$('html, body').animate({
scrollTop : scrollTop
}, 0);
}
});
});
}
};
} ]);
/**
* Prevent mousewheel scrolling from propagating to the parent when scrollbar
* reaches top or bottom
*/
module.directive('mousewheelScrolling', function() {
return {
restrict : 'A',
link : function(scope, elem, attr) {
elem.bind('mousewheel', function(e, d) {
var t = $(this);
if (d > 0 && t.scrollTop() === 0) {
e.preventDefault();
} else {
if (d < 0
&& (t.scrollTop() == t.get(0).scrollHeight
- t.innerHeight())) {
e.preventDefault();
}
}
});
}
};
});
/**
* Needed to use recursive directives. Wrap a recursive element with a
* <recursive> tag
*/
module.directive('recursive', [ '$compile', function($compile) {
return {
restrict : 'E',
priority : 100000,
compile : function(tElement, tAttr) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if (!compiledContents) {
compiledContents = $compile(contents);
}
iElement.append(compiledContents(scope, function(clone) {
return clone;
}));
};
}
};
} ]);
/**
* Reusable category component
*/
module.directive('category', [ function() {
return {
scope : {
node : '=',
level : '=',
selectedType : '=',
selectedId : '=',
showLabel : '=',
showChildren : '=',
unreadCount : '&'
},
restrict : 'E',
replace : true,
templateUrl : 'templates/_category.html',
controller : [
'$scope',
'$state',
'$dialog',
'FeedService',
'CategoryService',
'SettingsService',
'MobileService',
function($scope, $state, $dialog, FeedService, CategoryService,
SettingsService, MobileService) {
$scope.settingsService = SettingsService;
$scope.getClass = function(level) {
if ($scope.showLabel) {
return 'indent' + level;
}
};
$scope.categoryLabel = function(category) {
return $scope.showLabel !== true ? $scope.showLabel
: category.name;
};
$scope.categoryCountLabel = function(category) {
var count = $scope.unreadCount({
category : category
});
var label = '';
if (count > 0) {
label = '(' + count + ')';
}
return label;
};
$scope.feedCountLabel = function(feed) {
var label = '';
if (feed.unread > 0) {
label = '(' + feed.unread + ')';
}
return label;
};
$scope.feedClicked = function(id) {
MobileService.toggleLeftMenu();
if ($scope.selectedType == 'feed'
&& id == $scope.selectedId) {
$scope.$emit('emitReload');
} else {
$state.transitionTo('feeds.view', {
_type : 'feed',
_id : id
});
}
};
$scope.categoryClicked = function(id) {
MobileService.toggleLeftMenu();
if ($scope.selectedType == 'category'
&& id == $scope.selectedId) {
$scope.$emit('emitReload');
} else {
$state.transitionTo('feeds.view', {
_type : 'category',
_id : id
});
}
};
$scope.showFeedDetails = function(feed) {
$state.transitionTo('feeds.feed_details', {
_id : feed.id
});
};
$scope.showCategoryDetails = function(category) {
$state.transitionTo('feeds.category_details', {
_id : category.id
});
};
$scope.toggleCategory = function(category, event) {
event.preventDefault();
event.stopPropagation();
category.expanded = !category.expanded;
if (category.id == 'all') {
return;
}
CategoryService.collapse({
id : category.id,
collapse : !category.expanded
});
};
} ]
};
} ]);
/**
* Reusable spinner component
*/
module.directive('spinner', function() {
return {
scope : {
shown : '='
},
restrict : 'A',
link : function($scope, element) {
element.addClass('spinner');
var opts = {
lines : 11, // The number of lines to draw
length : 5, // The length of each line
width : 3, // The line thickness
radius : 8, // The radius of the inner circle
corners : 1, // Corner roundness (0..1)
rotate : 0, // The rotation offset
color : '#000', // #rgb or #rrggbb
speed : 1.3, // Rounds per second
trail : 60, // Afterglow percentage
shadow : false, // Whether to render a shadow
hwaccel : true, // Whether to use hardware acceleration
zIndex : 2e9, // The z-index (defaults to 2000000000)
top : 'auto', // Top position relative to parent in px
left : 'auto' // Left position relative to parent in px
};
var spinner = new Spinner(opts);
$scope.$watch('shown', function(shown) {
if (shown) {
spinner.spin();
element.append(spinner.el);
} else {
spinner.stop();
}
});
}
};
});
module.directive('draggable', function() {
return {
restrict : 'A',
link : function(scope, element, attrs) {
element.draggable({
revert: 'invalid',
helper: 'clone',
axis: 'y'
}).data('source', scope.$eval(attrs.draggable));
}
};
});
module.directive('droppable', [ 'CategoryService', 'FeedService', function(CategoryService, FeedService) {
return {
restrict : 'A',
link : function(scope, element, attrs) {
element.droppable({
tolerance: 'pointer',
over: function(event, ui) {
},
drop : function(event, ui) {
var draggable = angular.element(ui.draggable);
var source = draggable.data('source');
var target = scope.$eval(attrs.droppable);
if (angular.equals(source, target)) {
return;
}
var data = {
id: source.id,
name: source.name
};
if (source.children) {
// source is a category
/*
* TODO better handling of category dragging
*
if (target.children) {
// target is a category
data.parentId = target.id;
data.position = 0;
} else {
// target is a feed
data.parentId = target.categoryId;
}
CategoryService.modify(data, function() {
CategoryService.init();
});
*/
} else {
// source is a feed
if (target.children) {
// target is a category
data.categoryId = target.id;
data.position = 0;
} else {
// target is a feed
data.categoryId = target.categoryId;
data.position = target.position;
}
FeedService.modify(data, function() {
CategoryService.init();
});
}
scope.$apply();
}
});
}
};
}]);