diff --git a/pom.xml b/pom.xml index d048fec3..231dc431 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,12 @@ hibernate-entitymanager 4.1.10.Final provided + + + dom4j + dom4j + + uaihebert.com @@ -200,6 +206,49 @@ wicket-cdi 6.6.0 + + + org.jboss + jboss-vfs + 3.0.1.GA + + + de.agilecoders.wicket + wicket-bootstrap-core + 0.8.1 + + + dom4j + dom4j + + + + + de.agilecoders.wicket + wicket-bootstrap-extensions + 0.8.1 + + + + org.webjars + angularjs + 1.0.5 + + + org.webjars + angular-ui + 0.3.2-1 + + + org.webjars + select2 + 3.3.1 + + + org.webjars + spin-js + 1.2.7 + diff --git a/src/main/java/com/commafeed/frontend/CommaFeedApplication.java b/src/main/java/com/commafeed/frontend/CommaFeedApplication.java index 09eedaad..11de2336 100644 --- a/src/main/java/com/commafeed/frontend/CommaFeedApplication.java +++ b/src/main/java/com/commafeed/frontend/CommaFeedApplication.java @@ -1,5 +1,9 @@ package com.commafeed.frontend; +import java.io.IOException; +import java.net.URL; +import java.util.jar.JarFile; + import javax.enterprise.inject.spi.BeanManager; import javax.naming.InitialContext; import javax.naming.NamingException; @@ -24,6 +28,11 @@ import org.apache.wicket.request.Request; import org.apache.wicket.request.Response; import org.apache.wicket.request.cycle.AbstractRequestCycleListener; import org.apache.wicket.request.cycle.RequestCycle; +import org.jboss.vfs.VirtualFile; +import org.reflections.ReflectionsException; +import org.reflections.vfs.SystemDir; +import org.reflections.vfs.Vfs; +import org.reflections.vfs.ZipDir; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +42,9 @@ import com.commafeed.frontend.pages.LogoutPage; import com.commafeed.frontend.pages.SettingsPage; import com.commafeed.frontend.utils.exception.DisplayExceptionPage; +import de.agilecoders.wicket.Bootstrap; +import de.agilecoders.wicket.settings.BootstrapSettings; + public class CommaFeedApplication extends AuthenticatedWebApplication { private Logger log = LoggerFactory.getLogger(CommaFeedApplication.class); @@ -74,7 +86,7 @@ public class CommaFeedApplication extends AuthenticatedWebApplication { new DisplayExceptionPage(ex)), policy); } }); - + Bootstrap.install(Application.get(), new BootstrapSettings()); } @Override @@ -113,4 +125,54 @@ public class CommaFeedApplication extends AuthenticatedWebApplication { return (CommaFeedApplication) Application.get(); } + /** + * Reflections fix for JbossAS7 + * + * https://code.google.com/p/reflections/wiki/JBossIntegration + */ + static { + + Vfs.addDefaultURLTypes(new Vfs.UrlType() { + public boolean matches(URL url) { + return url.getProtocol().equals("vfs"); + } + + public Vfs.Dir createDir(URL url) { + VirtualFile content; + try { + content = (VirtualFile) url.openConnection().getContent(); + } catch (Throwable e) { + throw new ReflectionsException( + "could not open url connection as VirtualFile [" + + url + "]", e); + } + + Vfs.Dir dir = null; + try { + dir = createDir(new java.io.File(content.getPhysicalFile() + .getParentFile(), content.getName())); + } catch (IOException e) { /* continue */ + } + if (dir == null) { + try { + dir = createDir(content.getPhysicalFile()); + } catch (IOException e) { /* continue */ + } + } + return dir; + } + + Vfs.Dir createDir(java.io.File file) { + try { + return file.exists() && file.canRead() ? file.isDirectory() ? new SystemDir( + file) : new ZipDir(new JarFile(file)) + : null; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + }); + } + } diff --git a/src/main/java/com/commafeed/frontend/pages/BasePage.java b/src/main/java/com/commafeed/frontend/pages/BasePage.java new file mode 100644 index 00000000..cd1f8d61 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/pages/BasePage.java @@ -0,0 +1,16 @@ +package com.commafeed.frontend.pages; + +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.html.WebPage; + +import de.agilecoders.wicket.Bootstrap; + +@SuppressWarnings("serial") +public class BasePage extends WebPage { + + @Override + public void renderHead(IHeaderResponse response) { + super.renderHead(response); + Bootstrap.renderHead(response); + } +} diff --git a/src/main/java/com/commafeed/frontend/pages/HomePage.html b/src/main/java/com/commafeed/frontend/pages/HomePage.html index 821c4f25..56385609 100644 --- a/src/main/java/com/commafeed/frontend/pages/HomePage.html +++ b/src/main/java/com/commafeed/frontend/pages/HomePage.html @@ -2,12 +2,6 @@ CommaFeed - - - - - -
@@ -34,21 +28,5 @@
- - - - - - - - - - - - - - - - diff --git a/src/main/java/com/commafeed/frontend/pages/HomePage.java b/src/main/java/com/commafeed/frontend/pages/HomePage.java index a7c5383b..08728349 100644 --- a/src/main/java/com/commafeed/frontend/pages/HomePage.java +++ b/src/main/java/com/commafeed/frontend/pages/HomePage.java @@ -1,12 +1,46 @@ package com.commafeed.frontend.pages; import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation; -import org.apache.wicket.markup.html.WebPage; +import org.apache.wicket.markup.head.CssHeaderItem; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.JavaScriptHeaderItem; import com.commafeed.frontend.pages.auth.Role; +import com.commafeed.frontend.references.angular.AngularReference; +import com.commafeed.frontend.references.angular.AngularResourceReference; +import com.commafeed.frontend.references.angular.AngularSanitizeReference; +import com.commafeed.frontend.references.angularui.AngularUIReference; +import com.commafeed.frontend.references.angularuibootstrap.AngularUIBootstrapReference; +import com.commafeed.frontend.references.csstreeview.CssTreeViewReference; +import com.commafeed.frontend.references.nginfinitescroll.NGInfiniteScrollReference; +import com.commafeed.frontend.references.ngupload.NGUploadReference; +import com.commafeed.frontend.references.select2.Select2Reference; +import com.commafeed.frontend.references.spinjs.SpinJSReference; @SuppressWarnings("serial") @AuthorizeInstantiation(Role.USER) -public class HomePage extends WebPage { +public class HomePage extends BasePage { + @Override + public void renderHead(IHeaderResponse response) { + super.renderHead(response); + AngularReference.renderHead(response); + AngularResourceReference.renderHead(response); + AngularSanitizeReference.renderHead(response); + AngularUIReference.renderHead(response); + AngularUIBootstrapReference.renderHead(response); + NGUploadReference.renderHead(response); + NGInfiniteScrollReference.renderHead(response); + Select2Reference.renderHead(response); + SpinJSReference.renderHead(response); + + CssTreeViewReference.renderHead(response); + + response.render(JavaScriptHeaderItem.forUrl("js/main.js")); + response.render(JavaScriptHeaderItem.forUrl("js/controllers.js")); + response.render(JavaScriptHeaderItem.forUrl("js/directives.js")); + response.render(JavaScriptHeaderItem.forUrl("js/services.js")); + + response.render(CssHeaderItem.forUrl("css/app.css")); + } } diff --git a/src/main/java/com/commafeed/frontend/references/angular/AngularReference.java b/src/main/java/com/commafeed/frontend/references/angular/AngularReference.java new file mode 100644 index 00000000..47a5e934 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/references/angular/AngularReference.java @@ -0,0 +1,20 @@ +package com.commafeed.frontend.references.angular; + +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.JavaScriptHeaderItem; + +import de.agilecoders.wicket.webjars.request.resource.WebjarsJavaScriptResourceReference; + +public class AngularReference extends WebjarsJavaScriptResourceReference { + private static final long serialVersionUID = 1L; + + public static final AngularReference INSTANCE = new AngularReference(); + + private AngularReference() { + super("/angularjs/current/angular.js"); + } + + public static void renderHead(final IHeaderResponse response) { + response.render(JavaScriptHeaderItem.forReference(INSTANCE)); + } +} \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/references/angular/AngularResourceReference.java b/src/main/java/com/commafeed/frontend/references/angular/AngularResourceReference.java new file mode 100644 index 00000000..93d632d8 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/references/angular/AngularResourceReference.java @@ -0,0 +1,31 @@ +package com.commafeed.frontend.references.angular; + +import java.util.Arrays; + +import org.apache.wicket.markup.head.HeaderItem; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.JavaScriptHeaderItem; + +import de.agilecoders.wicket.webjars.request.resource.WebjarsJavaScriptResourceReference; + +public class AngularResourceReference extends + WebjarsJavaScriptResourceReference { + private static final long serialVersionUID = 1L; + + public static final AngularResourceReference INSTANCE = new AngularResourceReference(); + + private AngularResourceReference() { + super("/angularjs/current/angular-resource.js"); + } + + @Override + public Iterable getDependencies() { + return Arrays.asList(JavaScriptHeaderItem + .forReference(AngularReference.INSTANCE)); + } + + public static void renderHead(final IHeaderResponse response) { + response.render(JavaScriptHeaderItem.forReference(INSTANCE)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/references/angular/AngularSanitizeReference.java b/src/main/java/com/commafeed/frontend/references/angular/AngularSanitizeReference.java new file mode 100644 index 00000000..0e1de73d --- /dev/null +++ b/src/main/java/com/commafeed/frontend/references/angular/AngularSanitizeReference.java @@ -0,0 +1,30 @@ +package com.commafeed.frontend.references.angular; + +import java.util.Arrays; + +import org.apache.wicket.markup.head.HeaderItem; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.JavaScriptHeaderItem; + +import de.agilecoders.wicket.webjars.request.resource.WebjarsJavaScriptResourceReference; + +public class AngularSanitizeReference extends + WebjarsJavaScriptResourceReference { + private static final long serialVersionUID = 1L; + + public static final AngularSanitizeReference INSTANCE = new AngularSanitizeReference(); + + private AngularSanitizeReference() { + super("/angularjs/current/angular-sanitize.js"); + } + + @Override + public Iterable getDependencies() { + return Arrays.asList(JavaScriptHeaderItem + .forReference(AngularReference.INSTANCE)); + } + + public static void renderHead(final IHeaderResponse response) { + response.render(JavaScriptHeaderItem.forReference(INSTANCE)); + } +} \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/references/angularui/AngularUIReference.java b/src/main/java/com/commafeed/frontend/references/angularui/AngularUIReference.java new file mode 100644 index 00000000..757d81c6 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/references/angularui/AngularUIReference.java @@ -0,0 +1,32 @@ +package com.commafeed.frontend.references.angularui; + +import java.util.Arrays; + +import org.apache.wicket.markup.head.HeaderItem; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.JavaScriptHeaderItem; + +import com.commafeed.frontend.utils.WicketUtils; + +import de.agilecoders.wicket.webjars.request.resource.WebjarsJavaScriptResourceReference; + +public class AngularUIReference extends WebjarsJavaScriptResourceReference { + private static final long serialVersionUID = 1L; + + public static final AngularUIReference INSTANCE = new AngularUIReference(); + + private AngularUIReference() { + super("/angular-ui/current/angular-ui.js"); + } + + @Override + public Iterable getDependencies() { + return Arrays + .asList(WicketUtils + .buildCssWebJarHeaderItem("/angular-ui/current/angular-ui.css")); + } + + public static void renderHead(final IHeaderResponse response) { + response.render(JavaScriptHeaderItem.forReference(INSTANCE)); + } +} \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/references/angularuibootstrap/AngularUIBootstrapReference.java b/src/main/java/com/commafeed/frontend/references/angularuibootstrap/AngularUIBootstrapReference.java new file mode 100644 index 00000000..68dfd25a --- /dev/null +++ b/src/main/java/com/commafeed/frontend/references/angularuibootstrap/AngularUIBootstrapReference.java @@ -0,0 +1,30 @@ +package com.commafeed.frontend.references.angularuibootstrap; + +import java.util.Arrays; + +import org.apache.wicket.markup.head.HeaderItem; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.JavaScriptHeaderItem; +import org.apache.wicket.request.resource.JavaScriptResourceReference; + +import com.commafeed.frontend.references.angular.AngularReference; + +public class AngularUIBootstrapReference extends JavaScriptResourceReference { + private static final long serialVersionUID = 1L; + + public static final AngularUIBootstrapReference INSTANCE = new AngularUIBootstrapReference(); + + private AngularUIBootstrapReference() { + super(AngularUIBootstrapReference.class, "ui-bootstrap-tpls-0.2.0.js"); + } + + @Override + public Iterable getDependencies() { + return Arrays.asList(JavaScriptHeaderItem + .forReference(AngularReference.INSTANCE)); + } + + public static void renderHead(final IHeaderResponse response) { + response.render(JavaScriptHeaderItem.forReference(INSTANCE)); + } +} \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/references/angularuibootstrap/ui-bootstrap-tpls-0.2.0.js b/src/main/java/com/commafeed/frontend/references/angularuibootstrap/ui-bootstrap-tpls-0.2.0.js new file mode 100644 index 00000000..7125100c --- /dev/null +++ b/src/main/java/com/commafeed/frontend/references/angularuibootstrap/ui-bootstrap-tpls-0.2.0.js @@ -0,0 +1,1818 @@ +angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.collapse","ui.bootstrap.dialog","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.popover","ui.bootstrap.tabs","ui.bootstrap.tooltip","ui.bootstrap.transition","ui.bootstrap.typeahead"]); + +angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/dialog/message.html","template/pagination/pagination.html","template/popover/popover.html","template/tabs/pane.html","template/tabs/tabs.html","template/tooltip/tooltip-popup.html","template/typeahead/typeahead.html"]); + +angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) + +.constant('accordionConfig', { + closeOthers: true +}) + +.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { + + // This array keeps track of the accordion groups + this.groups = []; + + // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to + this.closeOthers = function(openGroup) { + var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; + if ( closeOthers ) { + angular.forEach(this.groups, function (group) { + if ( group !== openGroup ) { + group.isOpen = false; + } + }); + } + }; + + // This is called from the accordion-group directive to add itself to the accordion + this.addGroup = function(groupScope) { + var that = this; + this.groups.push(groupScope); + + groupScope.$on('$destroy', function (event) { + that.removeGroup(groupScope); + }); + }; + + // This is called from the accordion-group directive when to remove itself + this.removeGroup = function(group) { + var index = this.groups.indexOf(group); + if ( index !== -1 ) { + this.groups.splice(this.groups.indexOf(group), 1); + } + }; + +}]) + +// The accordion directive simply sets up the directive controller +// and adds an accordion CSS class to itself element. +.directive('accordion', function () { + return { + restrict:'EA', + controller:'AccordionController', + transclude: true, + replace: false, + templateUrl: 'template/accordion/accordion.html' + }; +}) + +// The accordion-group directive indicates a block of html that will expand and collapse in an accordion +.directive('accordionGroup', ['$parse', '$transition', '$timeout', function($parse, $transition, $timeout) { + return { + require:'^accordion', // We need this directive to be inside an accordion + restrict:'EA', + transclude:true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl:'template/accordion/accordion-group.html', + scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope + controller: ['$scope', function($scope) { + this.setHeading = function(element) { + this.heading = element; + }; + }], + link: function(scope, element, attrs, accordionCtrl) { + var getIsOpen, setIsOpen; + + accordionCtrl.addGroup(scope); + + scope.isOpen = false; + + if ( attrs.isOpen ) { + getIsOpen = $parse(attrs.isOpen); + setIsOpen = getIsOpen.assign; + + scope.$watch( + function watchIsOpen() { return getIsOpen(scope.$parent); }, + function updateOpen(value) { scope.isOpen = value; } + ); + + scope.isOpen = getIsOpen ? getIsOpen(scope.$parent) : false; + } + + scope.$watch('isOpen', function(value) { + if ( value ) { + accordionCtrl.closeOthers(scope); + } + if ( setIsOpen ) { + setIsOpen(scope.$parent, value); + } + }); + } + }; +}]) + +// Use accordion-heading below an accordion-group to provide a heading containing HTML +// +// Heading containing HTML - +// +.directive('accordionHeading', function() { + return { + restrict: 'E', + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^accordionGroup', + compile: function(element, attr, transclude) { + return function link(scope, element, attr, accordionGroupCtrl) { + // Pass the heading to the accordion-group controller + // so that it can be transcluded into the right place in the template + // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] + accordionGroupCtrl.setHeading(transclude(scope, function() {})); + }; + } + }; +}) + +// Use in the accordion-group template to indicate where you want the heading to be transcluded +// You must provide the property on the accordion-group controller that will hold the transcluded element +//
+// +// ... +//
+.directive('accordionTransclude', function() { + return { + require: '^accordionGroup', + link: function(scope, element, attr, controller) { + scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { + if ( heading ) { + element.html(''); + element.append(heading); + } + }); + } + }; +}); + +angular.module("ui.bootstrap.alert", []).directive('alert', function () { + return { + restrict:'EA', + templateUrl:'template/alert/alert.html', + transclude:true, + replace:true, + scope:{ + type:'=', + close:'&' + } + }; +}); +angular.module('ui.bootstrap.buttons', []) + + .constant('buttonConfig', { + activeClass:'active', + toggleEvent:'click' + }) + + .directive('btnRadio', ['buttonConfig', function (buttonConfig) { + var activeClass = buttonConfig.activeClass || 'active'; + var toggleEvent = buttonConfig.toggleEvent || 'click'; + + return { + + require:'ngModel', + link:function (scope, element, attrs, ngModelCtrl) { + + var value = scope.$eval(attrs.btnRadio); + + //model -> UI + scope.$watch(function () { + return ngModelCtrl.$modelValue; + }, function (modelValue) { + if (angular.equals(modelValue, value)){ + element.addClass(activeClass); + } else { + element.removeClass(activeClass); + } + }); + + //ui->model + element.bind(toggleEvent, function () { + if (!element.hasClass(activeClass)) { + scope.$apply(function () { + ngModelCtrl.$setViewValue(value); + }); + } + }); + } + }; +}]) + + .directive('btnCheckbox', ['buttonConfig', function (buttonConfig) { + + var activeClass = buttonConfig.activeClass || 'active'; + var toggleEvent = buttonConfig.toggleEvent || 'click'; + + return { + require:'ngModel', + link:function (scope, element, attrs, ngModelCtrl) { + + var trueValue = scope.$eval(attrs.btnCheckboxTrue); + var falseValue = scope.$eval(attrs.btnCheckboxFalse); + + trueValue = angular.isDefined(trueValue) ? trueValue : true; + falseValue = angular.isDefined(falseValue) ? falseValue : false; + + //model -> UI + scope.$watch(function () { + return ngModelCtrl.$modelValue; + }, function (modelValue) { + if (angular.equals(modelValue, trueValue)) { + element.addClass(activeClass); + } else { + element.removeClass(activeClass); + } + }); + + //ui->model + element.bind(toggleEvent, function () { + scope.$apply(function () { + ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? falseValue : trueValue); + }); + }); + } + }; +}]); +/* +* +* AngularJS Bootstrap Carousel +* +* A pure AngularJS carousel. +* +* For no interval set the interval to non-number, or milliseconds of desired interval +* Template: {{anything}} +* To change the carousel's active slide set the active attribute to true +* Template: {{anything}} +*/ +angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) +.controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) { + var self = this, + slides = self.slides = [], + currentIndex = -1, + currentTimeout, isPlaying; + self.currentSlide = null; + + /* direction: "prev" or "next" */ + self.select = function(nextSlide, direction) { + var nextIndex = slides.indexOf(nextSlide); + //Decide direction if it's not given + if (direction === undefined) { + direction = nextIndex > currentIndex ? "next" : "prev"; + } + if (nextSlide && nextSlide !== self.currentSlide) { + if ($scope.$currentTransition) { + $scope.$currentTransition.cancel(); + //Timeout so ng-class in template has time to fix classes for finished slide + $timeout(goNext); + } else { + goNext(); + } + } + function goNext() { + //If we have a slide to transition from and we have a transition type and we're allowed, go + if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { + //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime + nextSlide.$element.addClass(direction); + nextSlide.$element[0].offsetWidth = nextSlide.$element[0].offsetWidth; //force reflow + + //Set all other slides to stop doing their stuff for the new transition + angular.forEach(slides, function(slide) { + angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); + }); + angular.extend(nextSlide, {direction: direction, active: true, entering: true}); + angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); + + $scope.$currentTransition = $transition(nextSlide.$element, {}); + //We have to create new pointers inside a closure since next & current will change + (function(next,current) { + $scope.$currentTransition.then( + function(){ transitionDone(next, current); }, + function(){ transitionDone(next, current); } + ); + }(nextSlide, self.currentSlide)); + } else { + transitionDone(nextSlide, self.currentSlide); + } + self.currentSlide = nextSlide; + currentIndex = nextIndex; + //every time you change slides, reset the timer + restartTimer(); + } + function transitionDone(next, current) { + angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); + angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); + $scope.$currentTransition = null; + } + }; + + /* Allow outside people to call indexOf on slides array */ + self.indexOfSlide = function(slide) { + return slides.indexOf(slide); + }; + + $scope.next = function() { + var newIndex = (currentIndex + 1) % slides.length; + return self.select(slides[newIndex], 'next'); + }; + + $scope.prev = function() { + var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; + return self.select(slides[newIndex], 'prev'); + }; + + $scope.select = function(slide) { + self.select(slide); + }; + + $scope.isActive = function(slide) { + return self.currentSlide === slide; + }; + + $scope.slides = function() { + return slides; + }; + + $scope.$watch('interval', restartTimer); + function restartTimer() { + if (currentTimeout) { + $timeout.cancel(currentTimeout); + } + function go() { + if (isPlaying) { + $scope.next(); + restartTimer(); + } else { + $scope.pause(); + } + } + var interval = +$scope.interval; + if (!isNaN(interval) && interval>=0) { + currentTimeout = $timeout(go, interval); + } + } + $scope.play = function() { + if (!isPlaying) { + isPlaying = true; + restartTimer(); + } + }; + $scope.pause = function() { + isPlaying = false; + if (currentTimeout) { + $timeout.cancel(currentTimeout); + } + }; + + self.addSlide = function(slide, element) { + slide.$element = element; + slides.push(slide); + //if this is the first slide or the slide is set to active, select it + if(slides.length === 1 || slide.active) { + self.select(slides[slides.length-1]); + if (slides.length == 1) { + $scope.play(); + } + } else { + slide.active = false; + } + }; + + self.removeSlide = function(slide) { + //get the index of the slide inside the carousel + var index = slides.indexOf(slide); + slides.splice(index, 1); + if (slides.length > 0 && slide.active) { + if (index >= slides.length) { + self.select(slides[index-1]); + } else { + self.select(slides[index]); + } + } + }; +}]) +.directive('carousel', [function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + controller: 'CarouselController', + require: 'carousel', + templateUrl: 'template/carousel/carousel.html', + scope: { + interval: '=', + noTransition: '=' + } + }; +}]) +.directive('slide', [function() { + return { + require: '^carousel', + restrict: 'EA', + transclude: true, + replace: true, + templateUrl: 'template/carousel/slide.html', + scope: { + active: '=' + }, + link: function (scope, element, attrs, carouselCtrl) { + carouselCtrl.addSlide(scope, element); + //when the scope is destroyed then remove the slide from the current slides array + scope.$on('$destroy', function() { + carouselCtrl.removeSlide(scope); + }); + + scope.$watch('active', function(active) { + if (active) { + carouselCtrl.select(scope); + } + }); + } + }; +}]); + +angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition']) + +// The collapsible directive indicates a block of html that will expand and collapse +.directive('collapse', ['$transition', function($transition) { + // CSS transitions don't work with height: auto, so we have to manually change the height to a + // specific value and then once the animation completes, we can reset the height to auto. + // Unfortunately if you do this while the CSS transitions are specified (i.e. in the CSS class + // "collapse") then you trigger a change to height 0 in between. + // The fix is to remove the "collapse" CSS class while changing the height back to auto - phew! + var fixUpHeight = function(scope, element, height) { + // We remove the collapse CSS class to prevent a transition when we change to height: auto + element.removeClass('collapse'); + element.css({ height: height }); + // It appears that reading offsetWidth makes the browser realise that we have changed the + // height already :-/ + var x = element[0].offsetWidth; + element.addClass('collapse'); + }; + + return { + link: function(scope, element, attrs) { + + var isCollapsed; + var initialAnimSkip = true; + scope.$watch(function (){ return element[0].scrollHeight; }, function (value) { + //The listener is called when scollHeight changes + //It actually does on 2 scenarios: + // 1. Parent is set to display none + // 2. angular bindings inside are resolved + //When we have a change of scrollHeight we are setting again the correct height if the group is opened + if (element[0].scrollHeight !== 0) { + if (!isCollapsed) { + if (initialAnimSkip) { + fixUpHeight(scope, element, element[0].scrollHeight + 'px'); + } else { + fixUpHeight(scope, element, 'auto'); + } + } + } + }); + + scope.$watch(attrs.collapse, function(value) { + if (value) { + collapse(); + } else { + expand(); + } + }); + + + var currentTransition; + var doTransition = function(change) { + if ( currentTransition ) { + currentTransition.cancel(); + } + currentTransition = $transition(element,change); + currentTransition.then( + function() { currentTransition = undefined; }, + function() { currentTransition = undefined; } + ); + return currentTransition; + }; + + var expand = function() { + if (initialAnimSkip) { + initialAnimSkip = false; + if ( !isCollapsed ) { + fixUpHeight(scope, element, 'auto'); + } + } else { + doTransition({ height : element[0].scrollHeight + 'px' }) + .then(function() { + // This check ensures that we don't accidentally update the height if the user has closed + // the group while the animation was still running + if ( !isCollapsed ) { + fixUpHeight(scope, element, 'auto'); + } + }); + } + isCollapsed = false; + }; + + var collapse = function() { + isCollapsed = true; + if (initialAnimSkip) { + initialAnimSkip = false; + fixUpHeight(scope, element, 0); + } else { + fixUpHeight(scope, element, element[0].scrollHeight + 'px'); + doTransition({'height':'0'}); + } + }; + } + }; +}]); + +// The `$dialogProvider` can be used to configure global defaults for your +// `$dialog` service. +var dialogModule = angular.module('ui.bootstrap.dialog', ['ui.bootstrap.transition']); + +dialogModule.controller('MessageBoxController', ['$scope', 'dialog', 'model', function($scope, dialog, model){ + $scope.title = model.title; + $scope.message = model.message; + $scope.buttons = model.buttons; + $scope.close = function(res){ + dialog.close(res); + }; +}]); + +dialogModule.provider("$dialog", function(){ + + // The default options for all dialogs. + var defaults = { + backdrop: true, + dialogClass: 'modal', + backdropClass: 'modal-backdrop', + transitionClass: 'fade', + triggerClass: 'in', + dialogOpenClass: 'modal-open', + resolve:{}, + backdropFade: false, + dialogFade:false, + keyboard: true, // close with esc key + backdropClick: true // only in conjunction with backdrop=true + /* other options: template, templateUrl, controller */ + }; + + var globalOptions = {}; + + var activeBackdrops = {value : 0}; + + // The `options({})` allows global configuration of all dialogs in the application. + // + // var app = angular.module('App', ['ui.bootstrap.dialog'], function($dialogProvider){ + // // don't close dialog when backdrop is clicked by default + // $dialogProvider.options({backdropClick: false}); + // }); + this.options = function(value){ + globalOptions = value; + }; + + // Returns the actual `$dialog` service that is injected in controllers + this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector", + function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition, $injector) { + + var body = $document.find('body'); + + function createElement(clazz) { + var el = angular.element("
"); + el.addClass(clazz); + return el; + } + + // The `Dialog` class represents a modal dialog. The dialog class can be invoked by providing an options object + // containing at lest template or templateUrl and controller: + // + // var d = new Dialog({templateUrl: 'foo.html', controller: 'BarController'}); + // + // Dialogs can also be created using templateUrl and controller as distinct arguments: + // + // var d = new Dialog('path/to/dialog.html', MyDialogController); + function Dialog(opts) { + + var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts); + + this.backdropEl = createElement(options.backdropClass); + if(options.backdropFade){ + this.backdropEl.addClass(options.transitionClass); + this.backdropEl.removeClass(options.triggerClass); + } + + this.modalEl = createElement(options.dialogClass); + if(options.dialogFade){ + this.modalEl.addClass(options.transitionClass); + this.modalEl.removeClass(options.triggerClass); + } + + this.handledEscapeKey = function(e) { + if (e.which === 27) { + self.close(); + e.preventDefault(); + self.$scope.$apply(); + } + }; + + this.handleBackDropClick = function(e) { + self.close(); + e.preventDefault(); + self.$scope.$apply(); + }; + } + + // The `isOpen()` method returns wether the dialog is currently visible. + Dialog.prototype.isOpen = function(){ + return this._open; + }; + + // The `open(templateUrl, controller)` method opens the dialog. + // Use the `templateUrl` and `controller` arguments if specifying them at dialog creation time is not desired. + Dialog.prototype.open = function(templateUrl, controller){ + var self = this, options = this.options; + + if(templateUrl){ + options.templateUrl = templateUrl; + } + if(controller){ + options.controller = controller; + } + + if(!(options.template || options.templateUrl)) { + throw new Error('Dialog.open expected template or templateUrl, neither found. Use options or open method to specify them.'); + } + + this._loadResolves().then(function(locals) { + var $scope = locals.$scope = self.$scope = locals.$scope ? locals.$scope : $rootScope.$new(); + + self.modalEl.html(locals.$template); + + if (self.options.controller) { + var ctrl = $controller(self.options.controller, locals); + self.modalEl.contents().data('ngControllerController', ctrl); + } + + $compile(self.modalEl)($scope); + self._addElementsToDom(); + body.addClass(self.options.dialogOpenClass); + + // trigger tranisitions + setTimeout(function(){ + if(self.options.dialogFade){ self.modalEl.addClass(self.options.triggerClass); } + if(self.options.backdropFade){ self.backdropEl.addClass(self.options.triggerClass); } + }); + + self._bindEvents(); + }); + + this.deferred = $q.defer(); + return this.deferred.promise; + }; + + // closes the dialog and resolves the promise returned by the `open` method with the specified result. + Dialog.prototype.close = function(result){ + var self = this; + var fadingElements = this._getFadingElements(); + + body.removeClass(self.options.dialogOpenClass); + if(fadingElements.length > 0){ + for (var i = fadingElements.length - 1; i >= 0; i--) { + $transition(fadingElements[i], removeTriggerClass).then(onCloseComplete); + } + return; + } + + this._onCloseComplete(result); + + function removeTriggerClass(el){ + el.removeClass(self.options.triggerClass); + } + + function onCloseComplete(){ + if(self._open){ + self._onCloseComplete(result); + } + } + }; + + Dialog.prototype._getFadingElements = function(){ + var elements = []; + if(this.options.dialogFade){ + elements.push(this.modalEl); + } + if(this.options.backdropFade){ + elements.push(this.backdropEl); + } + + return elements; + }; + + Dialog.prototype._bindEvents = function() { + if(this.options.keyboard){ body.bind('keydown', this.handledEscapeKey); } + if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.bind('click', this.handleBackDropClick); } + }; + + Dialog.prototype._unbindEvents = function() { + if(this.options.keyboard){ body.unbind('keydown', this.handledEscapeKey); } + if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.unbind('click', this.handleBackDropClick); } + }; + + Dialog.prototype._onCloseComplete = function(result) { + this._removeElementsFromDom(); + this._unbindEvents(); + + this.deferred.resolve(result); + }; + + Dialog.prototype._addElementsToDom = function(){ + body.append(this.modalEl); + + if(this.options.backdrop) { + if (activeBackdrops.value === 0) { + body.append(this.backdropEl); + } + activeBackdrops.value++; + } + + this._open = true; + }; + + Dialog.prototype._removeElementsFromDom = function(){ + this.modalEl.remove(); + + if(this.options.backdrop) { + activeBackdrops.value--; + if (activeBackdrops.value === 0) { + this.backdropEl.remove(); + } + } + this._open = false; + }; + + // Loads all `options.resolve` members to be used as locals for the controller associated with the dialog. + Dialog.prototype._loadResolves = function(){ + var values = [], keys = [], templatePromise, self = this; + + if (this.options.template) { + templatePromise = $q.when(this.options.template); + } else if (this.options.templateUrl) { + templatePromise = $http.get(this.options.templateUrl, {cache:$templateCache}) + .then(function(response) { return response.data; }); + } + + angular.forEach(this.options.resolve || [], function(value, key) { + keys.push(key); + values.push(angular.isString(value) ? $injector.get(value) : $injector.invoke(value)); + }); + + keys.push('$template'); + values.push(templatePromise); + + return $q.all(values).then(function(values) { + var locals = {}; + angular.forEach(values, function(value, index) { + locals[keys[index]] = value; + }); + locals.dialog = self; + return locals; + }); + }; + + // The actual `$dialog` service that is injected in controllers. + return { + // Creates a new `Dialog` with the specified options. + dialog: function(opts){ + return new Dialog(opts); + }, + // creates a new `Dialog` tied to the default message box template and controller. + // + // Arguments `title` and `message` are rendered in the modal header and body sections respectively. + // The `buttons` array holds an object with the following members for each button to include in the + // modal footer section: + // + // * `result`: the result to pass to the `close` method of the dialog when the button is clicked + // * `label`: the label of the button + // * `cssClass`: additional css class(es) to apply to the button for styling + messageBox: function(title, message, buttons){ + return new Dialog({templateUrl: 'template/dialog/message.html', controller: 'MessageBoxController', resolve: + {model: function() { + return { + title: title, + message: message, + buttons: buttons + }; + } + }}); + } + }; + }]; +}); + +/* + * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js + * @restrict class or attribute + * @example: + + */ + +angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', +['$document', '$location', '$window', function ($document, $location, $window) { + var openElement = null, close; + return { + restrict: 'CA', + link: function(scope, element, attrs) { + scope.$watch(function dropdownTogglePathWatch(){return $location.path();}, function dropdownTogglePathWatchAction() { + if (close) { close(); } + }); + + element.parent().bind('click', function(event) { + if (close) { close(); } + }); + + element.bind('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + + var iWasOpen = false; + + if (openElement) { + iWasOpen = openElement === element; + close(); + } + + if (!iWasOpen){ + element.parent().addClass('open'); + openElement = element; + + close = function (event) { + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + $document.unbind('click', close); + element.parent().removeClass('open'); + close = null; + openElement = null; + }; + + $document.bind('click', close); + } + }); + } + }; +}]); + +angular.module('ui.bootstrap.modal', ['ui.bootstrap.dialog']) +.directive('modal', ['$parse', '$dialog', function($parse, $dialog) { + var backdropEl; + var body = angular.element(document.getElementsByTagName('body')[0]); + return { + restrict: 'EA', + terminal: true, + link: function(scope, elm, attrs) { + var opts = angular.extend({}, scope.$eval(attrs.uiOptions || attrs.bsOptions || attrs.options)); + var shownExpr = attrs.modal || attrs.show; + var setClosed; + + // Create a dialog with the template as the contents of the directive + // Add the current scope as the resolve in order to make the directive scope as a dialog controller scope + opts = angular.extend(opts, { + template: elm.html(), + resolve: { $scope: function() { return scope; } } + }); + var dialog = $dialog.dialog(opts); + + elm.remove(); + + if (attrs.close) { + setClosed = function() { + $parse(attrs.close)(scope); + }; + } else { + setClosed = function() { + if (angular.isFunction($parse(shownExpr).assign)) { + $parse(shownExpr).assign(scope, false); + } + }; + } + + scope.$watch(shownExpr, function(isShown, oldShown) { + if (isShown) { + dialog.open().then(function(){ + setClosed(); + }); + } else { + //Make sure it is not opened + if (dialog.isOpen()){ + dialog.close(); + } + } + }); + } + }; +}]); +angular.module('ui.bootstrap.pagination', []) + +.constant('paginationConfig', { + boundaryLinks: false, + directionLinks: true, + firstText: 'First', + previousText: 'Previous', + nextText: 'Next', + lastText: 'Last' +}) + +.directive('pagination', ['paginationConfig', function(paginationConfig) { + return { + restrict: 'EA', + scope: { + numPages: '=', + currentPage: '=', + maxSize: '=', + onSelectPage: '&' + }, + templateUrl: 'template/pagination/pagination.html', + replace: true, + link: function(scope, element, attrs) { + + // Setup configuration parameters + var boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks; + var directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$eval(attrs.directionLinks) : paginationConfig.directionLinks; + var firstText = angular.isDefined(attrs.firstText) ? attrs.firstText : paginationConfig.firstText; + var previousText = angular.isDefined(attrs.previousText) ? attrs.previousText : paginationConfig.previousText; + var nextText = angular.isDefined(attrs.nextText) ? attrs.nextText : paginationConfig.nextText; + var lastText = angular.isDefined(attrs.lastText) ? attrs.lastText : paginationConfig.lastText; + + // Create page object used in template + function makePage(number, text, isActive, isDisabled) { + return { + number: number, + text: text, + active: isActive, + disabled: isDisabled + }; + } + + scope.$watch('numPages + currentPage + maxSize', function() { + scope.pages = []; + + //set the default maxSize to numPages + var maxSize = ( scope.maxSize && scope.maxSize < scope.numPages ) ? scope.maxSize : scope.numPages; + var startPage = scope.currentPage - Math.floor(maxSize/2); + + //adjust the startPage within boundary + if(startPage < 1) { + startPage = 1; + } + if ((startPage + maxSize - 1) > scope.numPages) { + startPage = startPage - ((startPage + maxSize - 1) - scope.numPages ); + } + + // Add page number links + for (var number = startPage, max = startPage + maxSize; number < max; number++) { + var page = makePage(number, number, scope.isActive(number), false); + scope.pages.push(page); + } + + // Add previous & next links + if (directionLinks) { + var previousPage = makePage(scope.currentPage - 1, previousText, false, scope.noPrevious()); + scope.pages.unshift(previousPage); + + var nextPage = makePage(scope.currentPage + 1, nextText, false, scope.noNext()); + scope.pages.push(nextPage); + } + + // Add first & last links + if (boundaryLinks) { + var firstPage = makePage(1, firstText, false, scope.noPrevious()); + scope.pages.unshift(firstPage); + + var lastPage = makePage(scope.numPages, lastText, false, scope.noNext()); + scope.pages.push(lastPage); + } + + + if ( scope.currentPage > scope.numPages ) { + scope.selectPage(scope.numPages); + } + }); + scope.noPrevious = function() { + return scope.currentPage === 1; + }; + scope.noNext = function() { + return scope.currentPage === scope.numPages; + }; + scope.isActive = function(page) { + return scope.currentPage === page; + }; + + scope.selectPage = function(page) { + if ( ! scope.isActive(page) && page > 0 && page <= scope.numPages) { + scope.currentPage = page; + scope.onSelectPage({ page: page }); + } + }; + } + }; +}]); +/** + * The following features are still outstanding: popup delay, animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html popovers, and selector delegatation. + */ +angular.module( 'ui.bootstrap.popover', [] ) +.directive( 'popoverPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { popoverTitle: '@', popoverContent: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html' + }; +}) +.directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', function ( $compile, $timeout, $parse, $window ) { + + var template = + ''+ + ''; + + return { + scope: true, + link: function ( scope, element, attr ) { + var popover = $compile( template )( scope ), + transitionTimeout; + + attr.$observe( 'popover', function ( val ) { + scope.tt_popover = val; + }); + + attr.$observe( 'popoverTitle', function ( val ) { + scope.tt_title = val; + }); + + attr.$observe( 'popoverPlacement', function ( val ) { + // If no placement was provided, default to 'top'. + scope.tt_placement = val || 'top'; + }); + + attr.$observe( 'popoverAnimation', function ( val ) { + scope.tt_animation = $parse( val ); + }); + + // By default, the popover is not open. + scope.tt_isOpen = false; + + // Calculate the current position and size of the directive element. + function getPosition() { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: element.prop( 'offsetWidth' ), + height: element.prop( 'offsetHeight' ), + top: boundingClientRect.top + $window.pageYOffset, + left: boundingClientRect.left + $window.pageXOffset + }; + } + + function show() { + var position, + ttWidth, + ttHeight, + ttPosition; + + // If there is a pending remove transition, we must cancel it, lest the + // toolip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + } + + // Set the initial positioning. + popover.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + element.after( popover ); + + // Get the position of the directive element. + position = getPosition(); + + // Get the height and width of the popover so we can center it. + ttWidth = popover.prop( 'offsetWidth' ); + ttHeight = popover.prop( 'offsetHeight' ); + + // Calculate the popover's top and left coordinates to center it with + // this directive. + switch ( scope.tt_placement ) { + case 'right': + ttPosition = { + top: (position.top + position.height / 2 - ttHeight / 2) + 'px', + left: (position.left + position.width) + 'px' + }; + break; + case 'bottom': + ttPosition = { + top: (position.top + position.height) + 'px', + left: (position.left + position.width / 2 - ttWidth / 2) + 'px' + }; + break; + case 'left': + ttPosition = { + top: (position.top + position.height / 2 - ttHeight / 2) + 'px', + left: (position.left - ttWidth) + 'px' + }; + break; + default: + ttPosition = { + top: (position.top - ttHeight) + 'px', + left: (position.left + position.width / 2 - ttWidth / 2) + 'px' + }; + break; + } + + // Now set the calculated positioning. + popover.css( ttPosition ); + + // And show the popover. + scope.tt_isOpen = true; + } + + // Hide the popover popup element. + function hide() { + // First things first: we don't show it anymore. + //popover.removeClass( 'in' ); + scope.tt_isOpen = false; + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( angular.isDefined( scope.tt_animation ) && scope.tt_animation() ) { + transitionTimeout = $timeout( function () { popover.remove(); }, 500 ); + } else { + popover.remove(); + } + } + + // Register the event listeners. + element.bind( 'click', function() { + if(scope.tt_isOpen){ + scope.$apply( hide ); + } else { + scope.$apply( show ); + } + + }); + } + }; +}]); + + +angular.module('ui.bootstrap.tabs', []) +.controller('TabsController', ['$scope', '$element', function($scope, $element) { + var panes = $scope.panes = []; + + this.select = $scope.select = function selectPane(pane) { + angular.forEach(panes, function(pane) { + pane.selected = false; + }); + pane.selected = true; + }; + + this.addPane = function addPane(pane) { + if (!panes.length) { + $scope.select(pane); + } + panes.push(pane); + }; + + this.removePane = function removePane(pane) { + var index = panes.indexOf(pane); + panes.splice(index, 1); + //Select a new pane if removed pane was selected + if (pane.selected && panes.length > 0) { + $scope.select(panes[index < panes.length ? index : index-1]); + } + }; +}]) +.directive('tabs', function() { + return { + restrict: 'EA', + transclude: true, + scope: {}, + controller: 'TabsController', + templateUrl: 'template/tabs/tabs.html', + replace: true + }; +}) +.directive('pane', ['$parse', function($parse) { + return { + require: '^tabs', + restrict: 'EA', + transclude: true, + scope:{ + heading:'@' + }, + link: function(scope, element, attrs, tabsCtrl) { + var getSelected, setSelected; + scope.selected = false; + if (attrs.active) { + getSelected = $parse(attrs.active); + setSelected = getSelected.assign; + scope.$watch( + function watchSelected() {return getSelected(scope.$parent);}, + function updateSelected(value) {scope.selected = value;} + ); + scope.selected = getSelected ? getSelected(scope.$parent) : false; + } + scope.$watch('selected', function(selected) { + if(selected) { + tabsCtrl.select(scope); + } + if(setSelected) { + setSelected(scope.$parent, selected); + } + }); + + tabsCtrl.addPane(scope); + scope.$on('$destroy', function() { + tabsCtrl.removePane(scope); + }); + }, + templateUrl: 'template/tabs/pane.html', + replace: true + }; +}]); + +/** + * The following features are still outstanding: popup delay, animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegatation. + */ +angular.module( 'ui.bootstrap.tooltip', [] ) +.directive( 'tooltipPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { tooltipTitle: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' + }; +}) +.directive( 'tooltip', [ '$compile', '$timeout', '$parse', '$window', function ( $compile, $timeout, $parse, $window) { + + var template = + ''+ + ''; + + return { + scope: true, + link: function ( scope, element, attr ) { + var tooltip = $compile( template )( scope ), + transitionTimeout; + + attr.$observe( 'tooltip', function ( val ) { + scope.tt_tooltip = val; + }); + + attr.$observe( 'tooltipPlacement', function ( val ) { + // If no placement was provided, default to 'top'. + scope.tt_placement = val || 'top'; + }); + + attr.$observe( 'tooltipAnimation', function ( val ) { + scope.tt_animation = $parse( val ); + }); + + // By default, the tooltip is not open. + scope.tt_isOpen = false; + + // Calculate the current position and size of the directive element. + function getPosition() { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: element.prop( 'offsetWidth' ), + height: element.prop( 'offsetHeight' ), + top: boundingClientRect.top + $window.pageYOffset, + left: boundingClientRect.left + $window.pageXOffset + }; + } + + // Show the tooltip popup element. + function show() { + var position, + ttWidth, + ttHeight, + ttPosition; + + //don't show empty tooltips + if (!scope.tt_tooltip) { + return; + } + + // If there is a pending remove transition, we must cancel it, lest the + // toolip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + } + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + element.after( tooltip ); + + // Get the position of the directive element. + position = getPosition(); + + // Get the height and width of the tooltip so we can center it. + ttWidth = tooltip.prop( 'offsetWidth' ); + ttHeight = tooltip.prop( 'offsetHeight' ); + + // Calculate the tooltip's top and left coordinates to center it with + // this directive. + switch ( scope.tt_placement ) { + case 'right': + ttPosition = { + top: (position.top + position.height / 2 - ttHeight / 2) + 'px', + left: (position.left + position.width) + 'px' + }; + break; + case 'bottom': + ttPosition = { + top: (position.top + position.height) + 'px', + left: (position.left + position.width / 2 - ttWidth / 2) + 'px' + }; + break; + case 'left': + ttPosition = { + top: (position.top + position.height / 2 - ttHeight / 2) + 'px', + left: (position.left - ttWidth) + 'px' + }; + break; + default: + ttPosition = { + top: (position.top - ttHeight) + 'px', + left: (position.left + position.width / 2 - ttWidth / 2) + 'px' + }; + break; + } + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + + // And show the tooltip. + scope.tt_isOpen = true; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + //tooltip.removeClass( 'in' ); + scope.tt_isOpen = false; + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( angular.isDefined( scope.tt_animation ) && scope.tt_animation() ) { + transitionTimeout = $timeout( function () { tooltip.remove(); }, 500 ); + } else { + tooltip.remove(); + } + } + + // Register the event listeners. + element.bind( 'mouseenter', function() { + scope.$apply( show ); + }); + element.bind( 'mouseleave', function() { + scope.$apply( hide ); + }); + } + }; +}]); + + +angular.module('ui.bootstrap.transition', []) + +/** + * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. + * @param {DOMElement} element The DOMElement that will be animated. + * @param {string|object|function} trigger The thing that will cause the transition to start: + * - As a string, it represents the css class to be added to the element. + * - As an object, it represents a hash of style attributes to be applied to the element. + * - As a function, it represents a function to be called that will cause the transition to occur. + * @return {Promise} A promise that is resolved when the transition finishes. + */ +.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { + + var $transition = function(element, trigger, options) { + options = options || {}; + var deferred = $q.defer(); + var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"]; + + var transitionEndHandler = function(event) { + $rootScope.$apply(function() { + element.unbind(endEventName, transitionEndHandler); + deferred.resolve(element); + }); + }; + + if (endEventName) { + element.bind(endEventName, transitionEndHandler); + } + + // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur + $timeout(function() { + if ( angular.isString(trigger) ) { + element.addClass(trigger); + } else if ( angular.isFunction(trigger) ) { + trigger(element); + } else if ( angular.isObject(trigger) ) { + element.css(trigger); + } + //If browser does not support transitions, instantly resolve + if ( !endEventName ) { + deferred.resolve(element); + } + }); + + // Add our custom cancel function to the promise that is returned + // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, + // i.e. it will therefore never raise a transitionEnd event for that transition + deferred.promise.cancel = function() { + if ( endEventName ) { + element.unbind(endEventName, transitionEndHandler); + } + deferred.reject('Transition cancelled'); + }; + + return deferred.promise; + }; + + // Work out the name of the transitionEnd event + var transElement = document.createElement('trans'); + var transitionEndEventNames = { + 'WebkitTransition': 'webkitTransitionEnd', + 'MozTransition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'msTransition': 'MSTransitionEnd', + 'transition': 'transitionend' + }; + var animationEndEventNames = { + 'WebkitTransition': 'webkitAnimationEnd', + 'MozTransition': 'animationend', + 'OTransition': 'oAnimationEnd', + 'msTransition': 'MSAnimationEnd', + 'transition': 'animationend' + }; + function findEndEventName(endEventNames) { + for (var name in endEventNames){ + if (transElement.style[name] !== undefined) { + return endEventNames[name]; + } + } + } + $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); + $transition.animationEndEventName = findEndEventName(animationEndEventNames); + return $transition; +}]); + +angular.module('ui.bootstrap.typeahead', []) + +/** + * A helper service that can parse typeahead's syntax (string provided by users) + * Extracted to a separate service for ease of unit testing + */ + .factory('typeaheadParser', ['$parse', function ($parse) { + + // 00000111000000000000022200000000000000003333333333333330000000000044000 + var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/; + + return { + parse:function (input) { + + var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source; + if (!match) { + throw new Error( + "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" + + " but got '" + input + "'."); + } + + return { + itemName:match[3], + source:$parse(match[4]), + viewMapper:$parse(match[2] || match[1]), + modelMapper:$parse(match[1]) + }; + } + }; +}]) + + //options - min length + .directive('typeahead', ['$compile', '$q', 'typeaheadParser', function ($compile, $q, typeaheadParser) { + + var HOT_KEYS = [9, 13, 27, 38, 40]; + + return { + require:'ngModel', + link:function (originalScope, element, attrs, modelCtrl) { + + var selected = modelCtrl.$modelValue; + + //minimal no of characters that needs to be entered before typeahead kicks-in + var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; + + //expressions used by typeahead + var parserResult = typeaheadParser.parse(attrs.typeahead); + + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + originalScope.$on('$destroy', function(){ + scope.$destroy(); + }); + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + }; + + var getMatchesAsync = function(inputValue) { + + var locals = {$viewValue: inputValue}; + $q.when(parserResult.source(scope, locals)).then(function(matches) { + + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + if (inputValue === modelCtrl.$viewValue) { + if (matches.length > 0) { + + scope.activeIdx = 0; + scope.matches.length = 0; + + //transform labels + for(var i=0; i= minSearch) { + getMatchesAsync(inputValue); + } + } + + return undefined; + }); + + modelCtrl.$render = function () { + var locals = {}; + locals[parserResult.itemName] = selected; + element.val(parserResult.viewMapper(scope, locals) || modelCtrl.$viewValue); + selected = undefined; + }; + + scope.select = function (activeIdx) { + //called from within the $digest() cycle + var locals = {}; + locals[parserResult.itemName] = selected = scope.matches[activeIdx].model; + + modelCtrl.$setViewValue(parserResult.modelMapper(scope, locals)); + modelCtrl.$render(); + }; + + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(9) + element.bind('keydown', function (evt) { + + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } + + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + + } else if (evt.which === 27) { + scope.matches = []; + scope.$digest(); + } + }); + + var tplElCompiled = $compile("")(scope); + element.after(tplElCompiled); + } + }; + +}]) + + .directive('typeaheadPopup', function () { + return { + restrict:'E', + scope:{ + matches:'=', + query:'=', + active:'=', + select:'&' + }, + replace:true, + templateUrl:'template/typeahead/typeahead.html', + link:function (scope, element, attrs) { + + scope.isOpen = function () { + return scope.matches.length > 0; + }; + + scope.isActive = function (matchIdx) { + return scope.active == matchIdx; + }; + + scope.selectActive = function (matchIdx) { + scope.active = matchIdx; + }; + + scope.selectMatch = function (activeIdx) { + scope.select({activeIdx:activeIdx}); + }; + } + }; + }) + + .filter('typeaheadHighlight', function() { + return function(matchItem, query) { + return (query) ? matchItem.replace(new RegExp(query, 'gi'), '$&') : query; + }; + }); +angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache){ + $templateCache.put("template/accordion/accordion-group.html", + "
" + + " " + + "
" + + "
" + + "
"); +}]); + +angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache){ + $templateCache.put("template/accordion/accordion.html", + "
"); +}]); + +angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache){ + $templateCache.put("template/alert/alert.html", + "
" + + " " + + "
" + + "
"); +}]); + +angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache){ + $templateCache.put("template/carousel/carousel.html", + "
" + + "
    " + + "
  1. " + + "
" + + "
" + + " " + + " " + + "
" + + ""); +}]); + +angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache){ + $templateCache.put("template/carousel/slide.html", + "
" + + ""); +}]); + +angular.module("template/dialog/message.html", []).run(["$templateCache", function($templateCache){ + $templateCache.put("template/dialog/message.html", + "
" + + "

{{ title }}

" + + "
" + + "
" + + "

{{ message }}

" + + "
" + + "
" + + " " + + "
" + + ""); +}]); + +angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache){ + $templateCache.put("template/pagination/pagination.html", + "
" + + "
" + + ""); +}]); + +angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache){ + $templateCache.put("template/popover/popover.html", + "
" + + "
" + + "" + + "
" + + "

" + + "
" + + "
" + + "
" + + ""); +}]); + +angular.module("template/tabs/pane.html", []).run(["$templateCache", function($templateCache){ + $templateCache.put("template/tabs/pane.html", + "
" + + ""); +}]); + +angular.module("template/tabs/tabs.html", []).run(["$templateCache", function($templateCache){ + $templateCache.put("template/tabs/tabs.html", + "
" + + " " + + "
" + + "
" + + ""); +}]); + +angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache){ + $templateCache.put("template/tooltip/tooltip-popup.html", + "
" + + "
" + + "
" + + "
" + + ""); +}]); + +angular.module("template/typeahead/typeahead.html", []).run(["$templateCache", function($templateCache){ + $templateCache.put("template/typeahead/typeahead.html", + "
" + + "
    " + + "
  • " + + " " + + "
  • " + + "
" + + "
"); +}]); diff --git a/src/main/webapp/vendor/angular-ui-bootstrap/ui-bootstrap-tpls-0.2.0.min.js b/src/main/java/com/commafeed/frontend/references/angularuibootstrap/ui-bootstrap-tpls-0.2.0.min.js similarity index 100% rename from src/main/webapp/vendor/angular-ui-bootstrap/ui-bootstrap-tpls-0.2.0.min.js rename to src/main/java/com/commafeed/frontend/references/angularuibootstrap/ui-bootstrap-tpls-0.2.0.min.js diff --git a/src/main/java/com/commafeed/frontend/references/csstreeview/CssTreeViewReference.java b/src/main/java/com/commafeed/frontend/references/csstreeview/CssTreeViewReference.java new file mode 100644 index 00000000..c2a0e4cf --- /dev/null +++ b/src/main/java/com/commafeed/frontend/references/csstreeview/CssTreeViewReference.java @@ -0,0 +1,19 @@ +package com.commafeed.frontend.references.csstreeview; + +import org.apache.wicket.markup.head.CssHeaderItem; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.request.resource.CssResourceReference; + +public class CssTreeViewReference extends CssResourceReference { + private static final long serialVersionUID = 1L; + + public static final CssTreeViewReference INSTANCE = new CssTreeViewReference(); + + private CssTreeViewReference() { + super(CssTreeViewReference.class, "css3-treeview.css"); + } + + public static void renderHead(final IHeaderResponse response) { + response.render(CssHeaderItem.forReference(INSTANCE)); + } +} \ No newline at end of file diff --git a/src/main/webapp/vendor/csstreeview/css3-treeview.css b/src/main/java/com/commafeed/frontend/references/csstreeview/css3-treeview.css similarity index 100% rename from src/main/webapp/vendor/csstreeview/css3-treeview.css rename to src/main/java/com/commafeed/frontend/references/csstreeview/css3-treeview.css diff --git a/src/main/webapp/vendor/csstreeview/icons.png b/src/main/java/com/commafeed/frontend/references/csstreeview/icons.png similarity index 100% rename from src/main/webapp/vendor/csstreeview/icons.png rename to src/main/java/com/commafeed/frontend/references/csstreeview/icons.png diff --git a/src/main/java/com/commafeed/frontend/references/nginfinitescroll/NGInfiniteScrollReference.java b/src/main/java/com/commafeed/frontend/references/nginfinitescroll/NGInfiniteScrollReference.java new file mode 100644 index 00000000..e8d552f3 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/references/nginfinitescroll/NGInfiniteScrollReference.java @@ -0,0 +1,30 @@ +package com.commafeed.frontend.references.nginfinitescroll; + +import java.util.Arrays; + +import org.apache.wicket.markup.head.HeaderItem; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.JavaScriptHeaderItem; +import org.apache.wicket.request.resource.JavaScriptResourceReference; + +import com.commafeed.frontend.references.angular.AngularReference; + +public class NGInfiniteScrollReference extends JavaScriptResourceReference { + private static final long serialVersionUID = 1L; + + public static final NGInfiniteScrollReference INSTANCE = new NGInfiniteScrollReference(); + + private NGInfiniteScrollReference() { + super(NGInfiniteScrollReference.class, "ng-infinite-scroll.js"); + } + + @Override + public Iterable getDependencies() { + return Arrays.asList(JavaScriptHeaderItem + .forReference(AngularReference.INSTANCE)); + } + + public static void renderHead(final IHeaderResponse response) { + response.render(JavaScriptHeaderItem.forReference(INSTANCE)); + } +} \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/references/nginfinitescroll/ng-infinite-scroll.js b/src/main/java/com/commafeed/frontend/references/nginfinitescroll/ng-infinite-scroll.js new file mode 100644 index 00000000..5d29d59a --- /dev/null +++ b/src/main/java/com/commafeed/frontend/references/nginfinitescroll/ng-infinite-scroll.js @@ -0,0 +1,61 @@ +/* ng-infinite-scroll - v1.0.0 - 2013-02-23 */ +var mod; + +mod = angular.module('infinite-scroll', []); + +mod.directive('infiniteScroll', [ + '$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) { + return { + link: function(scope, elem, attrs) { + var checkWhenEnabled, handler, scrollDistance, scrollEnabled; + $window = angular.element($window); + scrollDistance = 0; + if (attrs.infiniteScrollDistance != null) { + scope.$watch(attrs.infiniteScrollDistance, function(value) { + return scrollDistance = parseInt(value, 10); + }); + } + scrollEnabled = true; + checkWhenEnabled = false; + if (attrs.infiniteScrollDisabled != null) { + scope.$watch(attrs.infiniteScrollDisabled, function(value) { + scrollEnabled = !value; + if (scrollEnabled && checkWhenEnabled) { + checkWhenEnabled = false; + return handler(); + } + }); + } + handler = function() { + var elementBottom, remaining, shouldScroll, windowBottom; + windowBottom = $window.height() + $window.scrollTop(); + elementBottom = elem.offset().top + elem.height(); + remaining = elementBottom - windowBottom; + shouldScroll = remaining <= $window.height() * scrollDistance; + if (shouldScroll && scrollEnabled) { + if ($rootScope.$$phase) { + return scope.$eval(attrs.infiniteScroll); + } else { + return scope.$apply(attrs.infiniteScroll); + } + } else if (shouldScroll) { + return checkWhenEnabled = true; + } + }; + $window.on('scroll', handler); + scope.$on('$destroy', function() { + return $window.off('scroll', handler); + }); + return $timeout((function() { + if (attrs.infiniteScrollImmediateCheck) { + if (scope.$eval(attrs.infiniteScrollImmediateCheck)) { + return handler(); + } + } else { + return handler(); + } + }), 0); + } + }; + } +]); diff --git a/src/main/webapp/vendor/angular-infinite-scroll/ng-infinite-scroll.min.js b/src/main/java/com/commafeed/frontend/references/nginfinitescroll/ng-infinite-scroll.min.js similarity index 100% rename from src/main/webapp/vendor/angular-infinite-scroll/ng-infinite-scroll.min.js rename to src/main/java/com/commafeed/frontend/references/nginfinitescroll/ng-infinite-scroll.min.js diff --git a/src/main/java/com/commafeed/frontend/references/ngupload/NGUploadReference.java b/src/main/java/com/commafeed/frontend/references/ngupload/NGUploadReference.java new file mode 100644 index 00000000..6eee526c --- /dev/null +++ b/src/main/java/com/commafeed/frontend/references/ngupload/NGUploadReference.java @@ -0,0 +1,30 @@ +package com.commafeed.frontend.references.ngupload; + +import java.util.Arrays; + +import org.apache.wicket.markup.head.HeaderItem; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.JavaScriptHeaderItem; +import org.apache.wicket.request.resource.JavaScriptResourceReference; + +import com.commafeed.frontend.references.angular.AngularReference; + +public class NGUploadReference extends JavaScriptResourceReference { + private static final long serialVersionUID = 1L; + + public static final NGUploadReference INSTANCE = new NGUploadReference(); + + private NGUploadReference() { + super(NGUploadReference.class, "ng-upload.js"); + } + + @Override + public Iterable getDependencies() { + return Arrays.asList(JavaScriptHeaderItem + .forReference(AngularReference.INSTANCE)); + } + + public static void renderHead(final IHeaderResponse response) { + response.render(JavaScriptHeaderItem.forReference(INSTANCE)); + } +} \ No newline at end of file diff --git a/src/main/java/com/commafeed/frontend/references/ngupload/ng-upload.js b/src/main/java/com/commafeed/frontend/references/ngupload/ng-upload.js new file mode 100644 index 00000000..fae7a442 --- /dev/null +++ b/src/main/java/com/commafeed/frontend/references/ngupload/ng-upload.js @@ -0,0 +1,107 @@ +// Version 0.3.2 +// AngularJS simple file upload directive +// this directive uses an iframe as a target +// to enable the uploading of files without +// losing focus in the ng-app. +// +//
+//
+//
+// +// +//
+//
+//
+// +// angular.module('app', ['ngUpload']) +// .controller('mainCtrl', function($scope) { +// $scope.submited = function(content, completed) { +// if (completed) { +// console.log(content); +// } +// } +// }); +// +angular.module('ngUpload', []) + .directive('uploadSubmit', ['$parse', function($parse) { + return { + restrict: 'AC', + link: function(scope, element, attrs) { + // Options (just 1 for now) + // Each option should be prefixed with 'upload-options-' or 'uploadOptions' + // { + // // specify whether to enable the submit button when uploading forms + // enableControls: bool + // } + var options = {}; + options.enableControls = attrs.uploadOptionsEnableControls; + + // submit the form - requires jQuery + var form = element.parents('form[ng-upload]') || element.parents('form.ng-upload'); + + // Retrieve the callback function + var fn = $parse(attrs.uploadSubmit); + + if (!angular.isFunction(fn)) { + var message = "The expression on the ngUpload directive does not point to a valid function."; + throw message + "\n"; + } + + element.bind('click', function($event) { + // prevent default behavior of click + $event.preventDefault = true; + // create a new iframe + var iframe = angular.element("