diff options
28 files changed, 3069 insertions, 77 deletions
diff --git a/src/main/java/io/trygvis/esper/testing/core/badge/UnbreakableBadgeProgress.java b/src/main/java/io/trygvis/esper/testing/core/badge/UnbreakableBadgeProgress.java index dc9a5bc..4cba611 100644 --- a/src/main/java/io/trygvis/esper/testing/core/badge/UnbreakableBadgeProgress.java +++ b/src/main/java/io/trygvis/esper/testing/core/badge/UnbreakableBadgeProgress.java @@ -80,7 +80,6 @@ public class UnbreakableBadgeProgress extends BadgeProgress { return 1; } - @Override public String toString() { return "UnbreakableBadgeProgress{person=" + person + ", count=" + count + '}'; } diff --git a/src/main/java/io/trygvis/esper/testing/core/db/PersonDao.java b/src/main/java/io/trygvis/esper/testing/core/db/PersonDao.java index 6605999..62c4f2b 100644 --- a/src/main/java/io/trygvis/esper/testing/core/db/PersonDao.java +++ b/src/main/java/io/trygvis/esper/testing/core/db/PersonDao.java @@ -1,6 +1,5 @@ package io.trygvis.esper.testing.core.db; -import static io.trygvis.esper.testing.Util.toList; import io.trygvis.esper.testing.core.db.PersonBadgeDto.*; import io.trygvis.esper.testing.util.sql.*; import org.joda.time.*; @@ -8,6 +7,7 @@ import org.joda.time.*; import java.sql.*; import java.util.*; +import static io.trygvis.esper.testing.Util.*; import static io.trygvis.esper.testing.util.sql.SqlOption.*; import static java.lang.System.*; @@ -36,7 +36,7 @@ public class PersonDao { new DateTime(rs.getTimestamp(i++).getTime()), UUID.fromString(rs.getString(i++)), BadgeType.valueOf(rs.getString(i++)), - rs.getInt(i), + rs.getInt(i++), rs.getInt(i)); } }; @@ -63,16 +63,16 @@ public class PersonDao { // Person // ----------------------------------------------------------------------- - public SqlOption<PersonDto> selectPerson(String id) throws SQLException { - try (PreparedStatement s = c.prepareStatement("SELECT " + PERSON + " FROM person WHERE id=?")) { + public SqlOption<PersonDto> selectPerson(UUID uuid) throws SQLException { + try (PreparedStatement s = c.prepareStatement("SELECT " + PERSON + " FROM person WHERE uuid=?")) { int i = 1; - s.setString(i, id); + s.setString(i, uuid.toString()); return fromRs(s.executeQuery()).map(person); } } public List<PersonDto> selectPerson(PageRequest pageRequest) throws SQLException { - try (PreparedStatement s = c.prepareStatement("SELECT " + PERSON + " FROM person ORDER BY created_date LIMIT ? OFFSET ?")) { + try (PreparedStatement s = c.prepareStatement("SELECT " + PERSON + " FROM person ORDER BY created_date, name LIMIT ? OFFSET ?")) { int i = 1; s.setInt(i++, pageRequest.count.orSome(10)); s.setInt(i, pageRequest.startIndex.orSome(0)); @@ -80,6 +80,12 @@ public class PersonDao { } } + public int selectPersonCount() throws SQLException { + try (PreparedStatement s = c.prepareStatement("SELECT count(1) FROM person")) { + return fromRs(s.executeQuery()).map(ResultSetF.getInt).get(); + } + } + public SqlOption<PersonDto> selectPersonByJenkinsUuid(UUID jenkinsUser) throws SQLException { try (PreparedStatement s = c.prepareStatement("SELECT " + PERSON + " FROM person WHERE uuid=(SELECT person FROM person_jenkins_user WHERE jenkins_user=?)")) { int i = 1; diff --git a/src/main/java/io/trygvis/esper/testing/esper/Test1.java b/src/main/java/io/trygvis/esper/testing/esper/Test1.java index 842dcb3..43f857b 100644 --- a/src/main/java/io/trygvis/esper/testing/esper/Test1.java +++ b/src/main/java/io/trygvis/esper/testing/esper/Test1.java @@ -68,7 +68,6 @@ public class Test1 { Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { - @Override public void run() { logger.info("tick"); } diff --git a/src/main/java/io/trygvis/esper/testing/esper/Test3.java b/src/main/java/io/trygvis/esper/testing/esper/Test3.java index 70c4dc3..1c007b9 100644 --- a/src/main/java/io/trygvis/esper/testing/esper/Test3.java +++ b/src/main/java/io/trygvis/esper/testing/esper/Test3.java @@ -68,7 +68,6 @@ public class Test3 { Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { - @Override public void run() { logger.info("tick"); } diff --git a/src/main/java/io/trygvis/esper/testing/gitorious/GitoriousClient.java b/src/main/java/io/trygvis/esper/testing/gitorious/GitoriousClient.java index 0071b1f..745c36a 100644 --- a/src/main/java/io/trygvis/esper/testing/gitorious/GitoriousClient.java +++ b/src/main/java/io/trygvis/esper/testing/gitorious/GitoriousClient.java @@ -31,7 +31,6 @@ public class GitoriousClient { private final GitoriousAtomFeedParser parser; private final F<HTTPResponse, Option<List<GitoriousProjectXml>>> parseDocument = new F<HTTPResponse, Option<List<GitoriousProjectXml>>>() { - @Override public Option<List<GitoriousProjectXml>> f(HTTPResponse response) { MIMEType mimeType = MIMEType.valueOf(trimToEmpty(response.getHeaders().getFirstHeaderValue("Content-Type"))); if (!mimeType.getPrimaryType().equals("application") || !mimeType.getSubType().equals("xml")) { diff --git a/src/main/java/io/trygvis/esper/testing/gitorious/GitoriousProjectDiscovery.java b/src/main/java/io/trygvis/esper/testing/gitorious/GitoriousProjectDiscovery.java index 1429f83..9fe809c 100644 --- a/src/main/java/io/trygvis/esper/testing/gitorious/GitoriousProjectDiscovery.java +++ b/src/main/java/io/trygvis/esper/testing/gitorious/GitoriousProjectDiscovery.java @@ -50,7 +50,6 @@ public class GitoriousProjectDiscovery { }); ObjectUtil.scheduledActorWithFixedDelay(service, config.gitorious.projectListUpdateDelay, config.gitorious.projectListUpdateInterval, TimeUnit.MILLISECONDS, boneCp, "Gitorious", new TransactionalActor() { - @Override public void act(Connection c) throws Exception { try (Daos daos = new Daos(c)) { discoverProjects(daos); diff --git a/src/main/java/io/trygvis/esper/testing/nexus/NexusFeedParser.java b/src/main/java/io/trygvis/esper/testing/nexus/NexusFeedParser.java index eb89f30..fa5f2a1 100644 --- a/src/main/java/io/trygvis/esper/testing/nexus/NexusFeedParser.java +++ b/src/main/java/io/trygvis/esper/testing/nexus/NexusFeedParser.java @@ -50,7 +50,6 @@ public class NexusFeedParser { Option<String> guid = Option.fromNull(item.getChildText("guid")); Option<String> creator = Option.fromNull(item.getChildText("creator", dc)); Option<DateTime> date = Option.fromNull(item.getChildText("date", dc)).bind(new F<String, Option<DateTime>>() { - @Override public Option<DateTime> f(String s) { try { return some(ISODateTimeFormat.dateTimeNoMillis().parseDateTime(s)); diff --git a/src/main/java/io/trygvis/esper/testing/nexus/NexusImporter.java b/src/main/java/io/trygvis/esper/testing/nexus/NexusImporter.java index 4042108..024cde2 100644 --- a/src/main/java/io/trygvis/esper/testing/nexus/NexusImporter.java +++ b/src/main/java/io/trygvis/esper/testing/nexus/NexusImporter.java @@ -71,7 +71,6 @@ class NexusServer implements TransactionalActor { this.xmlParser = xmlParser; } - @Override public void act(Connection c) throws Exception { NexusDao dao = new NexusDao(c); diff --git a/src/main/java/io/trygvis/esper/testing/util/HttpClient.java b/src/main/java/io/trygvis/esper/testing/util/HttpClient.java index 69c74fe..1676d3f 100644 --- a/src/main/java/io/trygvis/esper/testing/util/HttpClient.java +++ b/src/main/java/io/trygvis/esper/testing/util/HttpClient.java @@ -34,7 +34,6 @@ public class HttpClient<A> { public static <A> F<HTTPResponse, Option<A>> inputStreamOnly(final F<InputStream, Option<A>> f) { return new F<HTTPResponse, Option<A>>() { - @Override public Option<A> f(HTTPResponse response) { InputStream inputStream = response.getPayload().getInputStream(); try { diff --git a/src/main/java/io/trygvis/esper/testing/util/XmlParser.java b/src/main/java/io/trygvis/esper/testing/util/XmlParser.java index 9c585fc..624785c 100644 --- a/src/main/java/io/trygvis/esper/testing/util/XmlParser.java +++ b/src/main/java/io/trygvis/esper/testing/util/XmlParser.java @@ -46,7 +46,6 @@ public class XmlParser { boolean seenStartDocument; - @Override public boolean accept(XMLStreamReader reader) { if(reader.getEventType() == SPACE && !seenStartDocument) { return false; diff --git a/src/main/java/io/trygvis/esper/testing/util/object/ObjectUtil.java b/src/main/java/io/trygvis/esper/testing/util/object/ObjectUtil.java index 143a181..26c9e14 100644 --- a/src/main/java/io/trygvis/esper/testing/util/object/ObjectUtil.java +++ b/src/main/java/io/trygvis/esper/testing/util/object/ObjectUtil.java @@ -80,7 +80,6 @@ public class ObjectUtil { future.cancel(true); } - @Override public void run() { actor.run(); } diff --git a/src/main/java/io/trygvis/esper/testing/util/sql/PageRequest.java b/src/main/java/io/trygvis/esper/testing/util/sql/PageRequest.java index 2d8edfe..91a00f8 100644 --- a/src/main/java/io/trygvis/esper/testing/util/sql/PageRequest.java +++ b/src/main/java/io/trygvis/esper/testing/util/sql/PageRequest.java @@ -15,7 +15,7 @@ public class PageRequest { this.count = count; } - public static PageRequest fromReq(HttpServletRequest req) { + public static PageRequest pageReq(HttpServletRequest req) { return new PageRequest( fromNull(req.getParameter("startIndex")).bind(Util.parseInt), fromNull(req.getParameter("count")).bind(Util.parseInt)); diff --git a/src/main/java/io/trygvis/esper/testing/web/AbstractResource.java b/src/main/java/io/trygvis/esper/testing/web/AbstractResource.java new file mode 100644 index 0000000..c811a2f --- /dev/null +++ b/src/main/java/io/trygvis/esper/testing/web/AbstractResource.java @@ -0,0 +1,26 @@ +package io.trygvis.esper.testing.web; + +import fj.data.*; +import io.trygvis.esper.testing.*; + +import javax.ws.rs.*; +import javax.ws.rs.core.*; +import java.sql.*; + +public class AbstractResource { + protected final DatabaseAccess da; + + public AbstractResource(DatabaseAccess da) { + this.da = da; + } + + public <T> T get(DatabaseAccess.DaosCallback<Option<T>> callback) throws SQLException { + Option<T> server = da.inTransaction(callback); + + if(server.isNone()) { + throw new WebApplicationException(Response.Status.NOT_FOUND); + } + + return server.some(); + } +} diff --git a/src/main/java/io/trygvis/esper/testing/web/CoreResource.java b/src/main/java/io/trygvis/esper/testing/web/CoreResource.java index 3132a62..f2fb12a 100644 --- a/src/main/java/io/trygvis/esper/testing/web/CoreResource.java +++ b/src/main/java/io/trygvis/esper/testing/web/CoreResource.java @@ -1,5 +1,6 @@ package io.trygvis.esper.testing.web; +import fj.data.*; import io.trygvis.esper.testing.*; import io.trygvis.esper.testing.core.badge.*; import io.trygvis.esper.testing.core.db.*; @@ -10,27 +11,31 @@ import javax.ws.rs.*; import javax.ws.rs.core.*; import java.sql.*; import java.util.*; +import java.util.List; + +import static io.trygvis.esper.testing.util.sql.PageRequest.*; +import static io.trygvis.esper.testing.web.JenkinsResource.*; @Path("/resource/core") -public class CoreResource { +@Produces(MediaType.APPLICATION_JSON) +public class CoreResource extends AbstractResource { - private final DatabaseAccess da; private final BadgeService badgeService; public CoreResource(DatabaseAccess da, BadgeService badgeService) { - this.da = da; + super(da); this.badgeService = badgeService; } @GET @Path("/person") - @Produces(MediaType.APPLICATION_JSON) - public List<PersonJson> getServers(@Context final HttpServletRequest req) throws Exception { + public List<PersonJson> getPersons(@Context final HttpServletRequest req) throws Exception { + final PageRequest pageRequest = pageReq(req); + return da.inTransaction(new DatabaseAccess.DaosCallback<List<PersonJson>>() { - @Override public List<PersonJson> run(Daos daos) throws SQLException { List<PersonJson> list = new ArrayList<>(); - for (PersonDto person : daos.personDao.selectPerson(PageRequest.fromReq(req))) { + for (PersonDto person : daos.personDao.selectPerson(pageRequest)) { list.add(getPersonJson(daos, person)); } return list; @@ -38,6 +43,36 @@ public class CoreResource { }); } + /** + * This is wrong, but Angular's $resource is a bit dumb. + */ + @GET + @Path("/person-count") + public int getPersonCount() throws Exception { + return da.inTransaction(new DatabaseAccess.DaosCallback<Integer>() { + public Integer run(Daos daos) throws SQLException { + return daos.personDao.selectPersonCount(); + } + }); + } + + @GET + @Path("/person/{uuid}") + public PersonJson getPerson(@PathParam("uuid") final String s) throws Exception { + final UUID uuid = parseUuid(s); + + return get(new DatabaseAccess.DaosCallback<Option<PersonJson>>() { + public Option<PersonJson> run(Daos daos) throws SQLException { + SqlOption<PersonDto> o = daos.personDao.selectPerson(uuid); + if (o.isNone()) { + return Option.none(); + } + + return Option.some(getPersonJson(daos, o.get())); + } + }); + } + private PersonJson getPersonJson(Daos daos, PersonDto person) throws SQLException { List<BadgeJson> badges = new ArrayList<>(); @@ -45,16 +80,19 @@ public class CoreResource { badges.add(new BadgeJson(badge.type.name(), badge.level, badge.count, 100, 100)); } + List<BadgeJson> badgesInProgress = new ArrayList<>(); + for (PersonBadgeProgressDto badgeProgressDto : daos.personDao.selectBadgeProgresses(person.uuid)) { UnbreakableBadgeProgress progress = badgeService.unbreakable(badgeProgressDto); - badges.add(new BadgeJson(progress.type.name(), progress.progressingAgainstLevel(), 0, - progress.progression(), progress.goal())); + badgesInProgress.add(new BadgeJson(progress.type.name(), progress.progressingAgainstLevel(), 0, + progress.progression(), progress.goal())); } return new PersonJson( - person.uuid, - person.name, - badges + person.uuid, + person.name, + badges, + badgesInProgress ); } @@ -62,11 +100,13 @@ public class CoreResource { public final UUID uuid; public final String name; public final List<BadgeJson> badges; + public final List<BadgeJson> badgesInProgress; - public PersonJson(UUID uuid, String name, List<BadgeJson> badges) { + public PersonJson(UUID uuid, String name, List<BadgeJson> badges, List<BadgeJson> badgesInProgress) { this.uuid = uuid; this.name = name; this.badges = badges; + this.badgesInProgress = badgesInProgress; } } diff --git a/src/main/java/io/trygvis/esper/testing/web/JenkinsResource.java b/src/main/java/io/trygvis/esper/testing/web/JenkinsResource.java index 6551cea..bd925b6 100644 --- a/src/main/java/io/trygvis/esper/testing/web/JenkinsResource.java +++ b/src/main/java/io/trygvis/esper/testing/web/JenkinsResource.java @@ -15,12 +15,10 @@ import java.util.List; import static fj.data.Option.*; @Path("/resource/jenkins") -public class JenkinsResource { - - private final DatabaseAccess da; +public class JenkinsResource extends AbstractResource { public JenkinsResource(DatabaseAccess da) { - this.da = da; + super(da); } @GET @@ -28,7 +26,6 @@ public class JenkinsResource { @Produces(MediaType.APPLICATION_JSON) public List<JenkinsServerJson> getServers() throws Exception { return da.inTransaction(new DatabaseAccess.DaosCallback<List<JenkinsServerJson>>() { - @Override public List<JenkinsServerJson> run(Daos daos) throws SQLException { List<JenkinsServerJson> list = new ArrayList<>(); for (JenkinsServerDto server : daos.jenkinsDao.selectServers(false)) { @@ -63,17 +60,7 @@ public class JenkinsResource { return new JenkinsServerJson(server.uuid, server.createdDate, server.url, server.enabled, count); } - private <T> T get(DatabaseAccess.DaosCallback<Option<T>> callback) throws SQLException { - Option<T> server = da.inTransaction(callback); - - if(server.isNone()) { - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - - return server.some(); - } - - private UUID parseUuid(String s) { + public static UUID parseUuid(String s) { try { return UUID.fromString(s); } catch (IllegalArgumentException e) { diff --git a/src/main/java/io/trygvis/esper/testing/web/JerseyApplication.java b/src/main/java/io/trygvis/esper/testing/web/JerseyApplication.java index 67fbf74..eded64c 100644 --- a/src/main/java/io/trygvis/esper/testing/web/JerseyApplication.java +++ b/src/main/java/io/trygvis/esper/testing/web/JerseyApplication.java @@ -15,13 +15,12 @@ public class JerseyApplication extends Application { BadgeService badgeService = new BadgeService(); - singletons = new HashSet<>(Arrays.asList( + singletons = new HashSet<Object>(Arrays.asList( new CoreResource(da, badgeService), new JenkinsResource(da) )); } - @Override public Set<Object> getSingletons() { return singletons; } diff --git a/src/main/webapp/WEB-INF/tags/common/head-element.tagx b/src/main/webapp/WEB-INF/tags/common/head-element.tagx index e552b7b..bb8804c 100644 --- a/src/main/webapp/WEB-INF/tags/common/head-element.tagx +++ b/src/main/webapp/WEB-INF/tags/common/head-element.tagx @@ -1,7 +1,6 @@ <!--suppress JspAbsolutePathInspection --> -<jsp:root version="2.0" - xmlns:c="http://java.sun.com/jsp/jstl/core" - xmlns:jsp="http://java.sun.com/JSP/Page"> +<jsp:root version="2.0" xmlns:jsp="http://java.sun.com/JSP/Page" + xmlns:c="http://java.sun.com/jsp/jstl/core"> <jsp:output omit-xml-declaration="yes"/> <jsp:directive.attribute name="title" type="java.lang.String" required="false"/> <jsp:directive.attribute name="app" type="java.lang.String" required="false"/> @@ -11,13 +10,16 @@ <title><c:if test="${not empty title }">${title } - </c:if>Yeah</title> <link type="text/css" rel="stylesheet" href="/external/bootstrap-2.2.2/css/bootstrap.css"/> + <link type="text/css" rel="stylesheet" href="/external/angular-ui/ng-grid-1.5.0/ng-grid.css"/> + <link type="text/css" rel="stylesheet" href="/apps/app.css"/> <script type="text/javascript" src="/external/headjs-0.99/head.min.js"><!-- --></script> <script type="text/javascript"> head.js( {jquery: "/external/jquery-1.7.1.js"}, {angularjs: "/external/angular-1.0.3/angular.js"}, - {angularjsResource: "/external/angular-1.0.3/angular-resource.js"} + {angularjsResource: "/external/angular-1.0.3/angular-resource.js"}, + {angularUiNgGrid: "/external/angular-ui/ng-grid-1.5.0/ng-grid-1.5.0.js"} ); </script> <script>var noCache = new Date().getTime();</script> diff --git a/src/main/webapp/WEB-INF/urlrewrite.xml b/src/main/webapp/WEB-INF/urlrewrite.xml index ff4bf10..e5f3338 100644 --- a/src/main/webapp/WEB-INF/urlrewrite.xml +++ b/src/main/webapp/WEB-INF/urlrewrite.xml @@ -15,4 +15,10 @@ <to type="forward">/index.jspx</to> </rule> + <rule match-type="regex"> + <from>^/person/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$</from> + <set type="parameter" name="uuid">$1</set> + <to type="forward">/person/person.jspx</to> + </rule> + </urlrewrite> diff --git a/src/main/webapp/apps/app.css b/src/main/webapp/apps/app.css new file mode 100644 index 0000000..761b765 --- /dev/null +++ b/src/main/webapp/apps/app.css @@ -0,0 +1,41 @@ +/* + bronze = #8c7853 + bronze ii = #a67d3d + */ + +.badge-level-1 { background-color: #a67d3d; } +.badge-level-2 { background-color: silver; } +.badge-level-3 { background-color: #ffd700; } + +#content { + background-color: #ffffff; + padding-bottom: 60px; +} + +#footer { + background-color: #f5f5f5; + border-top: 1px solid #ccc; + color: #000000; +} + +#footer .container { + padding: 60px 0; +} + +#footer abbr[title] { + border-bottom: 1px dotted #000; +} + +#footer p { + margin-bottom: 0; + color: #777; +} + +#footer-links { + margin: 10px 0; +} + +#footer-links li { + display: inline; + margin-right: 10px; +} diff --git a/src/main/webapp/apps/frontPageApp/frontPage.html b/src/main/webapp/apps/frontPageApp/frontPage.html index bfa2477..4523e6f 100644 --- a/src/main/webapp/apps/frontPageApp/frontPage.html +++ b/src/main/webapp/apps/frontPageApp/frontPage.html @@ -1,24 +1,15 @@ <div class="container"> -<div class="page-header"> - <h1>Users</h1> -</div> + <div class="page-header"> + <h1>Newcomers</h1> + </div> + + <style> + .personsGrid { + height: 400px; + } + </style> -<p> - <table> - <thead> - <th>Name</th> - <th>Level</th> - <th>Count</th> - <th>Progress</th> - <th>Goal</th> - </thead> - <tbody ng-repeat="person in persons"> - <tr> - <td>{{person.name}}</td> - <td>{{person.badges.length}}</td> - </tr> - </table> -</p> + <div class="personsGrid" ng-grid="personsGridOptions"><!-- --></div> </div> diff --git a/src/main/webapp/apps/frontPageApp/frontPageApp.js b/src/main/webapp/apps/frontPageApp/frontPageApp.js index 4c5df8b..d92a163 100644 --- a/src/main/webapp/apps/frontPageApp/frontPageApp.js +++ b/src/main/webapp/apps/frontPageApp/frontPageApp.js @@ -1,12 +1,68 @@ 'use strict'; -var frontPageApp = angular.module('frontPageApp', ['personService']).config(function ($routeProvider, $locationProvider) { +var frontPageApp = angular.module('frontPageApp', ['ngGrid', 'personService']).config(function ($routeProvider, $locationProvider) { $routeProvider. when('/', {controller: FrontPageCtrl, templateUrl: '/apps/frontPageApp/frontPage.html?noCache=' + noCache}); }); -function FrontPageCtrl($scope, $location, PersonService) { - PersonService.query(function (persons) { - $scope.persons = persons; +function FrontPageCtrl($scope, $http, PersonService) { + $scope.persons = []; + + $scope.pagingOptions = { + pageSizes: [10], + pageSize: 10, + totalServerItems: 0, + currentPage: 1 + }; + + $scope.personsGridOptions = { + data: 'persons', + displayFooter: true, + enablePaging: true, + showFilter: false, + showColumnMenu: false, + canSelectRows: false, + displaySelectionCheckbox: false, + pagingOptions: $scope.pagingOptions, + columnDefs: [ + { + field: 'name', + displayName: 'Name', + cellTemplate: '<a href="/person/{{row.getProperty(\'uuid\')}}">{{row.getProperty(col.field)}}</a>' + }, + { + field: 'badges', + displayName: 'Badges', + cellTemplate: '<div>{{row.getProperty(col.field).length}}</div>' + } + ] + }; + + $scope.setPagingData = function(data, page, pageSize){ +// $scope.persons = data.slice((page - 1) * pageSize, page * pageSize); + $scope.persons = data; + $scope.personsGridOptions.totalServerItems = data.length; + if (!$scope.$$phase) { + $scope.$apply(); + } + }; + + $scope.getPagedDataAsync = function (pageSize, page/*, searchText*/) { + setTimeout(function () { + + PersonService.query({startIndex: page * pageSize, count: pageSize}, function (persons) { + $scope.setPagingData(persons, page, pageSize); + }); + }, 100); + }; + + $scope.$watch('pagingOptions', function () { + $scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage); + }, true); + + $http.get('/resource/core/person-count').success(function(count) { + $scope.pagingOptions.totalServerItems = count; + + $scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage); }); } diff --git a/src/main/webapp/apps/personApp/person.html b/src/main/webapp/apps/personApp/person.html new file mode 100644 index 0000000..4189f20 --- /dev/null +++ b/src/main/webapp/apps/personApp/person.html @@ -0,0 +1,17 @@ +<div class="container"> + + <div class="page-header"> + <h1>{{person.name}}</h1> + </div> + + <h3>Badges</h3> + <p ng-repeat="badge in person.badges"> + <span class="badge-level-{{badge.level}} badge">{{badge.name}}</span><span ng-show="badge.count > 1"> x {{badge.count}}</span> + </p> + + <h3>Badges in progress</h3> + <p ng-repeat="badge in person.badgesInProgress"> + <span class="badge badge-level-{{badge.level}}">{{badge.name}}</span> progress: {{badge.progress}} of {{badge.goal}} + </p> + +</div> diff --git a/src/main/webapp/apps/personApp/personApp.js b/src/main/webapp/apps/personApp/personApp.js new file mode 100644 index 0000000..59f5a7d --- /dev/null +++ b/src/main/webapp/apps/personApp/personApp.js @@ -0,0 +1,12 @@ +'use strict'; + +var personApp = angular.module('personApp', ['personService']).config(function ($routeProvider, $locationProvider) { + $routeProvider. + when('/', {controller: PersonCtrl, templateUrl: '/apps/personApp/person.html?noCache=' + noCache}); +}); + +function PersonCtrl($scope, $location, PersonService) { + PersonService.get({uuid: uuid}, function (person) { + $scope.person = person; + }); +} diff --git a/src/main/webapp/external/angular-ui/ng-grid-1.5.0/ng-grid-1.5.0.debug.js b/src/main/webapp/external/angular-ui/ng-grid-1.5.0/ng-grid-1.5.0.debug.js new file mode 100644 index 0000000..4ceaa00 --- /dev/null +++ b/src/main/webapp/external/angular-ui/ng-grid-1.5.0/ng-grid-1.5.0.debug.js @@ -0,0 +1,2334 @@ +/*********************************************** + * ng-grid JavaScript Library + * Authors: https://github.com/angular-ui/ng-grid/blob/master/README.md + * License: MIT (http://www.opensource.org/licenses/mit-license.php) + * Compiled At: 12/21/2012 15:55:15 + ***********************************************/ + +(function(window) { + 'use strict'; + + /*********************************************** + * FILE: ..\src\namespace.js + ***********************************************/ + window.ng = {}; + window.ng.$http = undefined; + var ngGridServices = angular.module('ngGrid.services', []); + var ngGridDirectives = angular.module('ngGrid.directives', []); + var ngGridFilters = angular.module('ngGrid.filters', []); +// Declare app level module which depends on filters, and services + + /*********************************************** + * FILE: ..\src\constants.js + ***********************************************/ + var SELECTED_PROP = '__ng_selected__'; + var GRID_KEY = '__koGrid__'; +// the # of rows we want to add to the top and bottom of the rendered grid rows + var EXCESS_ROWS = 8; + var SCROLL_THRESHOLD = 6; + var ASC = "asc"; +// constant for sorting direction + var DESC = "desc"; +// constant for sorting direction + var NG_FIELD = '_ng_field_'; + var NG_DEPTH = '_ng_depth_'; + var NG_HIDDEN = '_ng_hidden_'; + var NG_COLUMN = '_ng_column_'; + var CUSTOM_FILTERS = /CUSTOM_FILTERS/g; + var TEMPLATE_REGEXP = /<.+>/; + + /*********************************************** + * FILE: ..\src\navigation.js + ***********************************************/ +//set event binding on the grid so we can select using the up/down keys + ng.moveSelectionHandler = function($scope, grid, evt) { + // null checks + if (grid === null || grid === undefined) { + return true; + } + if (grid.config.selectedItems === undefined) { + return true; + } + var charCode = evt.which || evt.keyCode; + // detect which direction for arrow keys to navigate the grid + var offset = (charCode == 38 ? -1 : (charCode == 40 ? 1 : null)); + if (!offset) { + return true; + } + var items = $scope.renderedRows; + var index = items.indexOf(grid.selectionService.lastClickedRow) + offset; + if (index < 0 || index >= items.length) { + return true; + } + grid.selectionService.ChangeSelection(items[index], evt); + if (index > items.length - EXCESS_ROWS) { + grid.$viewport.scrollTop(grid.$viewport.scrollTop() + (grid.config.rowHeight * 2)); + } else if (index < EXCESS_ROWS) { + grid.$viewport.scrollTop(grid.$viewport.scrollTop() - (grid.config.rowHeight * 2)); + } + if (!$scope.$$phase) { + $scope.$parent.$digest(); + } + return false; + }; + + /*********************************************** + * FILE: ..\src\utils.js + ***********************************************/ + if (!String.prototype.trim) { + String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g, ''); + }; + } + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function(elt /*, from*/) { + var len = this.length >>> 0; + var from = Number(arguments[1]) || 0; + from = (from < 0) ? Math.ceil(from) : Math.floor(from); + if (from < 0) { + from += len; + } + for (; from < len; from++) { + if (from in this && this[from] === elt) { + return from; + } + } + return -1; + }; + } + if (!Array.prototype.filter) { + Array.prototype.filter = function(fun /*, thisp */) { + "use strict"; + var t = Object(this); + var len = t.length >>> 0; + if (typeof fun !== "function") { + throw new TypeError(); + } + var res = []; + var thisp = arguments[1]; + for (var i = 0; i < len; i++) { + if (i in t) { + var val = t[i]; // in case fun mutates this + if (fun.call(thisp, val, i, t)) { + res.push(val); + } + } + } + return res; + }; + } + ng.utils = { + visualLength: function(node) { + var elem = document.getElementById('testDataLength'); + if (!elem) { + elem = document.createElement('SPAN'); + elem.id = "testDataLength"; + elem.style.visibility = "hidden"; + document.body.appendChild(elem); + } + $(elem).css('font', $(node).css('font')); + elem.innerHTML = $(node).text(); + return elem.offsetWidth; + }, + forIn: function(obj, action) { + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + action(obj[prop], prop); + } + } + }, + evalProperty: function(entity, path) { + var propPath = path.split('.'), i = 0; + var tempProp = entity[propPath[i]], links = propPath.length; + i++; + while (tempProp && i < links) { + tempProp = tempProp[propPath[i]]; + i++; + } + return tempProp; + }, + endsWith: function(str, suffix) { + if (!str || !suffix || typeof str != "string") { + return false; + } + return str.indexOf(suffix, str.length - suffix.length) !== -1; + }, + isNullOrUndefined: function(obj) { + if (obj === undefined || obj === null) { + return true; + } + return false; + }, + getElementsByClassName: function(cl) { + var retnode = []; + var myclass = new RegExp('\\b' + cl + '\\b'); + var elem = document.getElementsByTagName('*'); + for (var i = 0; i < elem.length; i++) { + var classes = elem[i].className; + if (myclass.test(classes)) { + retnode.push(elem[i]); + } + } + return retnode; + }, + newId: (function() { + var seedId = new Date().getTime(); + return function() { + return seedId += 1; + }; + })(), + + // we copy KO's ie detection here bc it isn't exported in the min versions of KO + // Detect IE versions for workarounds (uses IE conditionals, not UA string, for robustness) + ieVersion: (function() { + var version = 3, div = document.createElement('div'), iElems = div.getElementsByTagName('i'); + // Keep constructing conditional HTML blocks until we hit one that resolves to an empty fragment + while (div.innerHTML = '<!--[if gt IE ' + (++version) + ']><i></i><![endif]-->', + iElems[0]); + return version > 4 ? version : undefined; + })() + }; + + $.extend(ng.utils, { + isIe6: (function() { + return ng.utils.ieVersion === 6; + })(), + isIe7: (function() { + return ng.utils.ieVersion === 7; + })(), + isIe: (function() { + return ng.utils.ieVersion !== undefined; + })() + }); + + /*********************************************** + * FILE: ..\src\filters\ngColumns.js + ***********************************************/ + ngGridFilters.filter('ngColumns', function() { + return function(input) { + return input.filter(function(col) { + return !col.isAggCol; + }); + }; + }); + + /*********************************************** + * FILE: ..\src\filters\checkmark.js + ***********************************************/ + ngGridFilters.filter('checkmark', function() { + return function(input) { + return input ? '\u2714' : '\u2718'; + }; + }); + + /*********************************************** + * FILE: ..\src\services\SortService.js + ***********************************************/ + ngGridServices.factory('SortService', function() { + var sortService = {}; + sortService.colSortFnCache = {}; // cache of sorting functions. Once we create them, we don't want to keep re-doing it + sortService.dateRE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/; // nasty regex for date parsing + // this takes an piece of data from the cell and tries to determine its type and what sorting + // function to use for it + // @item - the cell data + sortService.guessSortFn = function(item) { + var sortFn, // sorting function that is guessed + itemType, // the typeof item + dateParts, // for date parsing + month, // for date parsing + day; // for date parsing + + if (item === undefined || item === null || item === '') { + return null; + } + itemType = typeof(item); + //check for numbers and booleans + switch (itemType) { + case "number": + sortFn = sortService.sortNumber; + break; + case "boolean": + sortFn = sortService.sortBool; + break; + default: + sortFn = undefined; + break; + } + //if we found one, return it + if (sortFn) { + return sortFn; + } + //check if the item is a valid Date + if (Object.prototype.toString.call(item) === '[object Date]') { + return sortService.sortDate; + } + // if we aren't left with a string, return a basic sorting function... + if (itemType !== "string") { + return sortService.basicSort; + } + // now lets string check.. + //check if the item data is a valid number + if (item.match(/^-?[�$�]?[\d,.]+%?$/)) { + return sortService.sortNumberStr; + } + // check for a date: dd/mm/yyyy or dd/mm/yy + // can have / or . or - as separator + // can be mm/dd as well + dateParts = item.match(sortService.dateRE); + if (dateParts) { + // looks like a date + month = parseInt(dateParts[1], 10); + day = parseInt(dateParts[2], 10); + if (month > 12) { + // definitely dd/mm + return sortService.sortDDMMStr; + } else if (day > 12) { + return sortService.sortMMDDStr; + } else { + // looks like a date, but we can't tell which, so assume that it's MM/DD + return sortService.sortMMDDStr; + } + } + //finally just sort the normal string... + return sortService.sortAlpha; + }; + //#region Sorting Functions + sortService.basicSort = function(a, b) { + if (a == b) { + return 0; + } + if (a < b) { + return -1; + } + return 1; + }; + sortService.sortNumber = function(a, b) { + return a - b; + }; + sortService.sortNumberStr = function(a, b) { + var numA, numB, badA = false, badB = false; + numA = parseFloat(a.replace(/[^0-9.-]/g, '')); + if (isNaN(numA)) { + badA = true; + } + numB = parseFloat(b.replace(/[^0-9.-]/g, '')); + if (isNaN(numB)) { + badB = true; + } + // we want bad ones to get pushed to the bottom... which effectively is "greater than" + if (badA && badB) { + return 0; + } + if (badA) { + return 1; + } + if (badB) { + return -1; + } + return numA - numB; + }; + sortService.sortAlpha = function(a, b) { + var strA = a.toLowerCase(), + strB = b.toLowerCase(); + return strA == strB ? 0 : (strA < strB ? -1 : 1); + }; + sortService.sortDate = function(a, b) { + var timeA = a.getTime(), + timeB = b.getTime(); + return timeA == timeB ? 0 : (timeA < timeB ? -1 : 1); + }; + sortService.sortBool = function(a, b) { + if (a && b) { + return 0; + } + if (!a && !b) { + return 0; + } else { + return a ? 1 : -1; + } + }; + sortService.sortDDMMStr = function(a, b) { + var dateA, dateB, mtch, m, d, y; + mtch = a.match(sortService.dateRE); + y = mtch[3]; + m = mtch[2]; + d = mtch[1]; + if (m.length == 1) { + m = '0' + m; + } + if (d.length == 1) { + d = '0' + d; + } + dateA = y + m + d; + mtch = b.match(sortService.dateRE); + y = mtch[3]; + m = mtch[2]; + d = mtch[1]; + if (m.length == 1) { + m = '0' + m; + } + if (d.length == 1) { + d = '0' + d; + } + dateB = y + m + d; + if (dateA == dateB) { + return 0; + } + if (dateA < dateB) { + return -1; + } + return 1; + }; + sortService.sortMMDDStr = function(a, b) { + var dateA, dateB, mtch, m, d, y; + mtch = a.match(sortService.dateRE); + y = mtch[3]; + d = mtch[2]; + m = mtch[1]; + if (m.length == 1) { + m = '0' + m; + } + if (d.length == 1) { + d = '0' + d; + } + dateA = y + m + d; + mtch = b.match(sortService.dateRE); + y = mtch[3]; + d = mtch[2]; + m = mtch[1]; + if (m.length == 1) { + m = '0' + m; + } + if (d.length == 1) { + d = '0' + d; + } + dateB = y + m + d; + if (dateA == dateB) { + return 0; + } + if (dateA < dateB) { + return -1; + } + return 1; + }; + //#endregion + // the core sorting logic trigger + sortService.sortData = function(data /*datasource*/, sortInfo) { + // first make sure we are even supposed to do work + if (!data || !sortInfo) { + return; + } + // grab the metadata for the rest of the logic + var col = sortInfo.column, + direction = sortInfo.direction, + sortFn, + item; + + //see if we already figured out what to use to sort the column + if (sortService.colSortFnCache[col.field]) { + sortFn = sortService.colSortFnCache[col.field]; + } else if (col.sortingAlgorithm != undefined) { + sortFn = col.sortingAlgorithm; + sortService.colSortFnCache[col.field] = col.sortingAlgorithm; + } else { // try and guess what sort function to use + item = data[0]; + if (!item) { + return; + } + sortFn = sortService.guessSortFn(item[col.field]); + //cache it + if (sortFn) { + sortService.colSortFnCache[col.field] = sortFn; + } else { + // we assign the alpha sort because anything that is null/undefined will never get passed to + // the actual sorting function. It will get caught in our null check and returned to be sorted + // down to the bottom + sortFn = sortService.sortAlpha; + } + } + //now actually sort the data + data.sort(function(itemA, itemB) { + var propA = ng.utils.evalProperty(itemA, col.field); + var propB = ng.utils.evalProperty(itemB, col.field); + // we want to force nulls and such to the bottom when we sort... which effectively is "greater than" + if (!propB && !propA) { + return 0; + } else if (!propA) { + return 1; + } else if (!propB) { + return -1; + } + //made it this far, we don't have to worry about null & undefined + if (direction === ASC) { + return sortFn(propA, propB); + } else { + return 0 - sortFn(propA, propB); + } + }); + return; + }; + sortService.Sort = function(sortInfo, data) { + if (sortService.isSorting) { + return; + } + sortService.isSorting = true; + sortService.sortData(data, sortInfo); + sortService.isSorting = false; + }; + return sortService; + }); + + /*********************************************** + * FILE: ..\src\services\DomUtilityService.js + ***********************************************/ + ngGridServices.factory('DomUtilityService', function() { + var domUtilityService = {}; + var getWidths = function() { + var $testContainer = $('<div></div>'); + $testContainer.appendTo('body'); + // 1. Run all the following measurements on startup! + //measure Scroll Bars + $testContainer.height(100).width(100).css("position", "absolute").css("overflow", "scroll"); + $testContainer.append('<div style="height: 400px; width: 400px;"></div>'); + domUtilityService.ScrollH = ($testContainer.height() - $testContainer[0].clientHeight); + domUtilityService.ScrollW = ($testContainer.width() - $testContainer[0].clientWidth); + $testContainer.empty(); + //clear styles + $testContainer.attr('style', ''); + //measure letter sizes using a pretty typical font size and fat font-family + $testContainer.append('<span style="font-family: Verdana, Helvetica, Sans-Serif; font-size: 14px;"><strong>M</strong></span>'); + domUtilityService.LetterW = $testContainer.children().first().width(); + $testContainer.remove(); + }; + domUtilityService.eventStorage = {}; + domUtilityService.AssignGridContainers = function(rootEl, grid) { + grid.$root = $(rootEl); + //Headers + grid.$topPanel = grid.$root.find(".ngTopPanel"); + grid.$groupPanel = grid.$root.find(".ngGroupPanel"); + grid.$headerContainer = grid.$topPanel.find(".ngHeaderContainer"); + grid.$headerScroller = grid.$topPanel.find(".ngHeaderScroller"); + grid.$headers = grid.$headerScroller.children(); + //Viewport + grid.$viewport = grid.$root.find(".ngViewport"); + //Canvas + grid.$canvas = grid.$viewport.find(".ngCanvas"); + //Footers + grid.$footerPanel = grid.$root.find(".ngFooterPanel"); + domUtilityService.UpdateGridLayout(grid); + }; + domUtilityService.UpdateGridLayout = function(grid) { + //catch this so we can return the viewer to their original scroll after the resize! + var scrollTop = grid.$viewport.scrollTop(); + grid.elementDims.rootMaxW = grid.$root.width(); + grid.elementDims.rootMaxH = grid.$root.height(); + //check to see if anything has changed + grid.refreshDomSizes(); + grid.adjustScrollTop(scrollTop, true); //ensure that the user stays scrolled where they were + }; + domUtilityService.numberOfGrids = 0; + domUtilityService.BuildStyles = function($scope, grid, digest) { + var rowHeight = grid.config.rowHeight, + $style = grid.$styleSheet, + gridId = grid.gridId, + css, + cols = $scope.visibleColumns(), + sumWidth = 0; + + if (!$style) { + $style = $('#' + gridId); + if (!$style[0]) { + $style = $("<style id='" + gridId + "' type='text/css' rel='stylesheet' />").appendTo(grid.$root); + } + } + $style.empty(); + var trw = $scope.totalRowWidth(); + css = "." + gridId + " .ngCanvas { width: " + trw + "px; }" + + "." + gridId + " .ngRow { width: " + trw + "px; }" + + "." + gridId + " .ngCanvas { width: " + trw + "px; }" + + "." + gridId + " .ngHeaderScroller { width: " + (trw + domUtilityService.scrollH + 2) + "px}"; + angular.forEach(cols, function(col, i) { + css += "." + gridId + " .col" + i + " { width: " + col.width + "px; left: " + sumWidth + "px; right: " + (trw - sumWidth - col.width) + "px; height: " + rowHeight + "px }" + + "." + gridId + " .colt" + i + " { width: " + col.width + "px; }"; + sumWidth += col.width; + }); + if (ng.utils.isIe) { // IE + $style[0].styleSheet.cssText = css; + } else { + $style[0].appendChild(document.createTextNode(css)); + } + grid.$styleSheet = $style; + if (digest) { + domUtilityService.digest($scope); + } + }; + + domUtilityService.digest = function($scope) { + if (!$scope.$$phase) { + $scope.$digest(); + } + }; + domUtilityService.ScrollH = 17; // default in IE, Chrome, & most browsers + domUtilityService.ScrollW = 17; // default in IE, Chrome, & most browsers + domUtilityService.LetterW = 10; + getWidths(); + return domUtilityService; + }); + + /*********************************************** + * FILE: ..\src\templates\gridTemplate.html + ***********************************************/ + ng.defaultGridTemplate = function(){ return '<div class="ngTopPanel" ng-class="{\'ui-widget-header\':jqueryUITheme, \'ui-corner-top\': jqueryUITheme}" ng-style="topPanelStyle()"><div class="ngGroupPanel" ng-show="showGroupPanel()" ng-style="headerStyle()"><div class="ngGroupPanelDescription" ng-show="configGroups.length == 0">Drag a column header here and drop it to group by that column</div><ul ng-show="configGroups.length > 0" class="ngGroupList"><li class="ngGroupItem" ng-repeat="group in configGroups"><span class="ngGroupElement"><span class="ngGroupName">{{group.displayName}}<span ng-click="removeGroup($index)" class="ngRemoveGroup">x</span></span><span ng-hide="$last" class="ngGroupArrow"></span></span></li></ul></div><div class="ngHeaderContainer" ng-style="headerStyle()"><div class="ngHeaderScroller" ng-style="headerScrollerStyle()" ng-header-row></div></div><div class="ngHeaderButton" ng-show="showColumnMenu || showFilter" ng-click="toggleShowMenu()"><div class="ngHeaderButtonArrow" ng-click=""></div></div><div ng-show="showMenu" class="ngColMenu"><div ng-show="showFilter"><input placeholder="Search..." type="text" ng-model="filterText"/></div><div ng-show="showColumnMenu"><span class="ngMenuText">Choose Columns:</span><ul class="ngColList"><li class="ngColListItem" ng-repeat="col in columns | ngColumns"><label><input type="checkbox" class="ngColListCheckbox" ng-model="col.visible"/>{{col.displayName}}</label><a title="Group By" ng-class="col.groupedByClass()" ng-show="col.groupable" ng-click="groupBy(col)"></a><span class="ngGroupingNumber" ng-show="col.groupIndex > 0">{{col.groupIndex}}</span></li></ul></div></div></div><div class="ngViewport" ng-class="{\'ui-widget-content\': jqueryUITheme}" ng-style="viewportStyle()"><div class="ngCanvas" ng-style="canvasStyle()"><div ng-style="rowStyle(row)" ng-repeat="row in renderedRows" ng-click="row.toggleSelected($event)" class="ngRow" ng-class="row.alternatingRowClass()" ng-row></div></div></div><div class="ngFooterPanel" ng-class="{\'ui-widget-content\': jqueryUITheme, \'ui-corner-bottom\': jqueryUITheme}" ng-style="footerStyle()"><div class="ngTotalSelectContainer" ng-show="footerVisible"><div class="ngFooterTotalItems" ng-class="{\'ngNoMultiSelect\': !multiSelect}" ><span class="ngLabel">Total Items: {{maxRows()}}</span><span ng-show="filterText.length > 0" class="ngLabel">(Showing Items: {{totalFilteredItemsLength()}})</span></div><div class="ngFooterSelectedItems" ng-show="multiSelect"><span class="ngLabel">Selected Items: {{selectedItems.length}}</span></div></div><div class="ngPagerContainer" style="float: right; margin-top: 10px;" ng-show="footerVisible && enablePaging" ng-class="{\'ngNoMultiSelect\': !multiSelect}"><div style="float:left; margin-right: 10px;" class="ngRowCountPicker"><span style="float: left; margin-top: 3px;" class="ngLabel">Page Size:</span><select style="float: left;height: 27px; width: 100px" ng-model="pagingOptions.pageSize" ><option ng-repeat="size in pagingOptions.pageSizes">{{size}}</option></select></div><div style="float:left; margin-right: 10px; line-height:25px;" class="ngPagerControl" style="float: left; min-width: 135px;"><button class="ngPagerButton" ng-click="pageToFirst()" ng-disabled="cantPageBackward()" title="First Page"><div class="ngPagerFirstTriangle"><div class="ngPagerFirstBar"></div></div></button><button class="ngPagerButton" ng-click="pageBackward()" ng-disabled="cantPageBackward()" title="Previous Page"><div class="ngPagerFirstTriangle ngPagerPrevTriangle"></div></button><input class="ngPagerCurrent" type="text" style="width:50px; height: 24px; margin-top: 1px; padding: 0px 4px;" ng-model="pagingOptions.currentPage"/><button class="ngPagerButton" ng-click="pageForward()" ng-disabled="cantPageForward()" title="Next Page"><div class="ngPagerLastTriangle ngPagerNextTriangle"></div></button><button class="ngPagerButton" ng-click="pageToLast()" ng-disabled="cantPageForward()" title="Last Page"><div class="ngPagerLastTriangle"><div class="ngPagerLastBar"></div></div></button></div></div></div>';}; + + /*********************************************** + * FILE: ..\src\templates\rowTemplate.html + ***********************************************/ + ng.defaultRowTemplate = function(){ return '<div ng-style="{\'cursor\': row.cursor}" ng-repeat="col in visibleColumns()" class="ngCell col{{$index}} {{col.cellClass}}" ng-cell></div>';}; + + /*********************************************** + * FILE: ..\src\templates\cellTemplate.html + ***********************************************/ + ng.defaultCellTemplate = function(){ return '<div class="ngCellText colt{{$index}}">{{row.getProperty(col.field) CUSTOM_FILTERS}}</div>';}; + + /*********************************************** + * FILE: ..\src\templates\aggregateTemplate.html + ***********************************************/ + ng.aggregateTemplate = function(){ return '<div ng-click="row.toggleExpand()" ng-style="{\'left\': row.offsetleft}" class="ngAggregate"><span class="ngAggregateText">{{row.label CUSTOM_FILTERS}} ({{row.totalChildren()}} items)</span><div class="{{row.aggClass()}}"></div></div>';}; + + /*********************************************** + * FILE: ..\src\templates\headerRowTemplate.html + ***********************************************/ + ng.defaultHeaderRowTemplate = function(){ return '<div ng-repeat="col in visibleColumns()" class="ngHeaderCell col{{$index}}" ng-header-cell></div>';}; + + /*********************************************** + * FILE: ..\src\templates\headerCellTemplate.html + ***********************************************/ + ng.defaultHeaderCellTemplate = function(){ return '<div ng-click="col.sort()" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{\'cursor\': col.cursor}" ng-class="{ \'ngSorted\': !noSortVisible }"><div class="ngHeaderText colt{{$index}}">{{col.displayName}}</div><div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div><div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div></div><div ng-show="col.resizable" class="ngHeaderGrip" ng-click="col.gripClick($event)" ng-mousedown="col.gripOnMouseDown($event)"></div>';}; + + /*********************************************** + * FILE: ..\src\classes\aggregate.js + ***********************************************/ + ng.Aggregate = function(aggEntity, rowFactory) { + var self = this; + self.index = 0; + self.offsetTop = 0; + self.entity = aggEntity; + self.label = aggEntity.gLabel; + self.field = aggEntity.gField; + self.depth = aggEntity.gDepth; + self.parent = aggEntity.parent; + self.children = aggEntity.children; + self.aggChildren = aggEntity.aggChildren; + self.aggIndex = aggEntity.aggIndex; + self.collapsed = true; + self.isAggRow = true; + self.offsetleft = aggEntity.gDepth * 25; + self.aggLabelFilter = aggEntity.aggLabelFilter; + self.toggleExpand = function() { + self.collapsed = self.collapsed ? false : true; + self.notifyChildren(); + }; + self.setExpand = function(state) { + self.collapsed = state; + self.notifyChildren(); + }; + self.notifyChildren = function() { + angular.forEach(self.aggChildren, function(child) { + child.entity[NG_HIDDEN] = self.collapsed; + if (self.collapsed) { + child.setExpand(self.collapsed); + } + }); + angular.forEach(self.children, function(child) { + child[NG_HIDDEN] = self.collapsed; + }); + rowFactory.rowCache = []; + var foundMyself = false; + angular.forEach(rowFactory.aggCache, function(agg, i) { + if (foundMyself) { + var offset = (30 * self.children.length); + agg.offsetTop = self.collapsed ? agg.offsetTop - offset : agg.offsetTop + offset; + } else { + if (i == self.aggIndex) { + foundMyself = true; + } + } + }); + rowFactory.renderedChange(); + }; + self.aggClass = function() { + return self.collapsed ? "ngAggArrowCollapsed" : "ngAggArrowExpanded"; + }; + self.totalChildren = function() { + if (self.aggChildren.length > 0) { + var i = 0; + var recurse = function(cur) { + if (cur.aggChildren.length > 0) { + angular.forEach(cur.aggChildren, function(a) { + recurse(a); + }); + } else { + i += cur.children.length; + } + }; + recurse(self); + return i; + } else { + return self.children.length; + } + }; + }; + + /*********************************************** + * FILE: ..\src\classes\eventProvider.js + ***********************************************/ + ng.EventProvider = function(grid, $scope, domUtilityService) { + var self = this; + // The init method gets called during the ng-grid directive execution. + self.colToMove = undefined; + self.groupToMove = undefined; + self.assignEvents = function() { + // Here we set the onmousedown event handler to the header container. + if (grid.config.jqueryUIDraggable) { + grid.$groupPanel.droppable({ + addClasses: false, + drop: function(event) { + self.onGroupDrop(event); + } + }); + $scope.$evalAsync(self.setDraggables); + } else { + grid.$groupPanel.on('mousedown', self.onGroupMouseDown).on('dragover', self.dragOver).on('drop', self.onGroupDrop); + grid.$headerScroller.on('mousedown', self.onHeaderMouseDown).on('dragover', self.dragOver); + if (grid.config.enableColumnReordering) { + grid.$headerScroller.on('drop', self.onHeaderDrop); + } + if (grid.config.enableRowReordering) { + grid.$viewport.on('mousedown', self.onRowMouseDown).on('dragover', self.dragOver).on('drop', self.onRowDrop); + } + } + $scope.$watch('columns', self.setDraggables, true); + }; + self.dragOver = function(evt) { + evt.preventDefault(); + }; + //For JQueryUI + self.setDraggables = function() { + if (!grid.config.jqueryUIDraggable) { + grid.$root.find('.ngHeaderSortColumn').attr('draggable', 'true'); + } else { + grid.$root.find('.ngHeaderSortColumn').draggable({ + helper: 'clone', + appendTo: 'body', + stack: 'div', + addClasses: false, + start: function(event) { + self.onHeaderMouseDown(event); + } + }).droppable({ + drop: function(event) { + self.onHeaderDrop(event); + } + }); + } + }; + self.onGroupMouseDown = function(event) { + var groupItem = $(event.target); + // Get the scope from the header container + if (groupItem[0].className != 'ngRemoveGroup') { + var groupItemScope = angular.element(groupItem).scope(); + if (groupItemScope) { + // set draggable events + if (!grid.config.jqueryUIDraggable) { + groupItem.attr('draggable', 'true'); + } + // Save the column for later. + self.groupToMove = { header: groupItem, groupName: groupItemScope.group, index: groupItemScope.$index }; + } + } else { + self.groupToMove = undefined; + } + }; + self.onGroupDrop = function(event) { + event.stopPropagation(); + // clear out the colToMove object + var groupContainer; + var groupScope; + if (self.groupToMove) { + // Get the closest header to where we dropped + groupContainer = $(event.target).closest('.ngGroupElement'); // Get the scope from the header. + if (groupContainer.context.className == 'ngGroupPanel') { + $scope.configGroups.splice(self.groupToMove.index, 1); + $scope.configGroups.push(self.groupToMove.groupName); + } else { + groupScope = angular.element(groupContainer).scope(); + if (groupScope) { + // If we have the same column, do nothing. + if (self.groupToMove.index != groupScope.$index) { + // Splice the columns + $scope.configGroups.splice(self.groupToMove.index, 1); + $scope.configGroups.splice(groupScope.$index, 0, self.groupToMove.groupName); + } + } + } + self.groupToMove = undefined; + grid.fixGroupIndexes(); + } else if (self.colToMove) { + if ($scope.configGroups.indexOf(self.colToMove.col) == -1) { + groupContainer = $(event.target).closest('.ngGroupElement'); // Get the scope from the header. + if (groupContainer.context.className == 'ngGroupPanel' || groupContainer.context.className == 'ngGroupPanelDescription') { + $scope.groupBy(self.colToMove.col); + } else { + groupScope = angular.element(groupContainer).scope(); + if (groupScope) { + // Splice the columns + $scope.removeGroup(groupScope.$index); + } + } + } + self.colToMove = undefined; + } + if (!$scope.$$phase) { + $scope.$apply(); + } + }; + //Header functions + self.onHeaderMouseDown = function(event) { + // Get the closest header container from where we clicked. + var headerContainer = $(event.target).closest('.ngHeaderSortColumn'); + // Get the scope from the header container + var headerScope = angular.element(headerContainer).scope(); + if (headerScope) { + // Save the column for later. + self.colToMove = { header: headerContainer, col: headerScope.col }; + } + }; + self.onHeaderDrop = function(event) { + if (!self.colToMove) { + return; + } + // Get the closest header to where we dropped + var headerContainer = $(event.target).closest('.ngHeaderSortColumn'); + // Get the scope from the header. + var headerScope = angular.element(headerContainer).scope(); + if (headerScope) { + // If we have the same column, do nothing. + if (self.colToMove.col == headerScope.col) { + return; + } + // Splice the columns + $scope.columns.splice(self.colToMove.col.index, 1); + $scope.columns.splice(headerScope.col.index, 0, self.colToMove.col); + grid.fixColumnIndexes(); + // Finally, rebuild the CSS styles. + domUtilityService.BuildStyles($scope, grid, true); + // clear out the colToMove object + self.colToMove = undefined; + } + }; + // Row functions + self.onRowMouseDown = function(event) { + // Get the closest row element from where we clicked. + var targetRow = $(event.target).closest('.ngRow'); + // Get the scope from the row element + var rowScope = angular.element(targetRow).scope(); + if (rowScope) { + // set draggable events + targetRow.attr('draggable', 'true'); + // Save the row for later. + domUtilityService.eventStorage.rowToMove = { targetRow: targetRow, scope: rowScope }; + } + }; + self.onRowDrop = function(event) { + // Get the closest row to where we dropped + var targetRow = $(event.target).closest('.ngRow'); + // Get the scope from the row element. + var rowScope = angular.element(targetRow).scope(); + if (rowScope) { + // If we have the same Row, do nothing. + var prevRow = domUtilityService.eventStorage.rowToMove; + if (prevRow.scope.row == rowScope.row) { + return; + } + // Splice the Rows via the actual datasource + var i = grid.sortedData.indexOf(prevRow.scope.row.entity); + var j = grid.sortedData.indexOf(rowScope.row.entity); + grid.sortedData.splice(i, 1); + grid.sortedData.splice(j, 0, prevRow.scope.row.entity); + grid.searchProvider.evalFilter(); + // clear out the rowToMove object + domUtilityService.eventStorage.rowToMove = undefined; + // if there isn't an apply already in progress lets start one + } + }; + + self.assignGridEventHandlers = function() { + grid.$viewport.on('scroll', function(e) { + var scrollLeft = e.target.scrollLeft, + scrollTop = e.target.scrollTop; + grid.adjustScrollLeft(scrollLeft); + grid.adjustScrollTop(scrollTop); + }); + var doingKeyDown = false; + grid.$viewport.on('keydown', function(e) { + if (!doingKeyDown) { + doingKeyDown = true; + var ret = ng.moveSelectionHandler($scope, grid, e); + doingKeyDown = false; + return ret; + } + return false; + }); + //Chrome and firefox both need a tab index so the grid can recieve focus. + //need to give the grid a tabindex if it doesn't already have one so + //we'll just give it a tab index of the corresponding gridcache index + //that way we'll get the same result every time it is run. + //configurable within the options. + if (grid.config.tabIndex === -1) { + grid.$viewport.attr('tabIndex', domUtilityService.numberOfGrids); + domUtilityService.numberOfGrids++; + } else { + grid.$viewport.attr('tabIndex', grid.config.tabIndex); + } + $(window).resize(function() { + domUtilityService.UpdateGridLayout(grid); + if (grid.config.maintainColumnRatios) { + grid.configureColumnWidths(); + } + }); + }; + // In this example we want to assign grid events. + self.assignGridEventHandlers(); + self.assignEvents(); + }; + + /*********************************************** + * FILE: ..\src\classes\column.js + ***********************************************/ + ng.Column = function(config, $scope, grid, domUtilityService) { + var self = this, + colDef = config.colDef, + delay = 500, + clicks = 0, + timer = null; + self.width = colDef.width; + self.groupIndex = 0; + self.isGroupedBy = false; + self.minWidth = !colDef.minWidth ? 50 : colDef.minWidth; + self.maxWidth = !colDef.maxWidth ? 9000 : colDef.maxWidth; + self.headerRowHeight = config.headerRowHeight; + self.displayName = colDef.displayName || colDef.field; + self.index = config.index; + self.isAggCol = config.isAggCol; + self.cellClass = colDef.cellClass; + self.cellFilter = colDef.cellFilter ? colDef.cellFilter : ""; + self.field = colDef.field; + self.aggLabelFilter = colDef.cellFilter || colDef.aggLabelFilter; + self.visible = ng.utils.isNullOrUndefined(colDef.visible) || colDef.visible; + self.sortable = false; + self.resizable = false; + self.groupable = ng.utils.isNullOrUndefined(colDef.groupable) || colDef.sortable; + if (config.enableSort) { + self.sortable = ng.utils.isNullOrUndefined(colDef.sortable) || colDef.sortable; + } + if (config.enableResize) { + self.resizable = ng.utils.isNullOrUndefined(colDef.resizable) || colDef.resizable; + } + self.sortDirection = undefined; + self.sortingAlgorithm = colDef.sortFn; + self.headerClass = colDef.headerClass; + self.headerCellTemplate = colDef.headerCellTemplate || ng.defaultHeaderCellTemplate(); + self.cursor = self.sortable ? 'pointer' : 'default'; + self.cellTemplate = colDef.cellTemplate || ng.defaultCellTemplate().replace(CUSTOM_FILTERS, self.cellFilter ? "|" + self.cellFilter : ""); + if (colDef.cellTemplate && !TEMPLATE_REGEXP.test(colDef.cellTemplate)) { + self.cellTemplate = $.ajax({ + type: "GET", + url: colDef.cellTemplate, + async: false + }).responseText; + } + if (colDef.headerCellTemplate && !TEMPLATE_REGEXP.test(colDef.headerCellTemplate)) { + self.headerCellTemplate = $.ajax({ + type: "GET", + url: colDef.headerCellTemplate, + async: false + }).responseText; + } + self.groupedByClass = function() { + return self.isGroupedBy ? "ngGroupedByIcon" : "ngGroupIcon"; + }; + self.toggleVisible = function() { + self.visible = !self.visible; + }; + self.showSortButtonUp = function() { + return self.sortable ? self.sortDirection === DESC : self.sortable; + }; + self.showSortButtonDown = function() { + return self.sortable ? self.sortDirection === ASC : self.sortable; + }; + self.noSortVisible = function() { + return !self.sortDirection; + }; + self.sort = function() { + if (!self.sortable) { + return true; // column sorting is disabled, do nothing + } + var dir = self.sortDirection === ASC ? DESC : ASC; + self.sortDirection = dir; + config.sortCallback(self); + return false; + }; + self.gripClick = function() { + clicks++; //count clicks + if (clicks === 1) { + timer = setTimeout(function() { + //Here you can add a single click action. + clicks = 0; //after action performed, reset counter + }, delay); + } else { + clearTimeout(timer); //prevent single-click action + config.resizeOnDataCallback(self); //perform double-click action + clicks = 0; //after action performed, reset counter + } + }; + self.gripOnMouseDown = function(event) { + if (event.ctrlKey) { + self.toggleVisible(); + domUtilityService.BuildStyles($scope, grid); + return true; + } + event.target.parentElement.style.cursor = 'col-resize'; + self.startMousePosition = event.clientX; + self.origWidth = self.width; + $(document).mousemove(self.onMouseMove); + $(document).mouseup(self.gripOnMouseUp); + return false; + }; + self.onMouseMove = function(event) { + var diff = event.clientX - self.startMousePosition; + var newWidth = diff + self.origWidth; + self.width = (newWidth < self.minWidth ? self.minWidth : (newWidth > self.maxWidth ? self.maxWidth : newWidth)); + domUtilityService.BuildStyles($scope, grid); + return false; + }; + self.gripOnMouseUp = function() { + $(document).off('mousemove'); + $(document).off('mouseup'); + event.target.parentElement.style.cursor = 'default'; + domUtilityService.digest($scope); + return false; + }; + }; + + /*********************************************** + * FILE: ..\src\classes\dimension.js + ***********************************************/ + ng.Dimension = function(options) { + this.outerHeight = null; + this.outerWidth = null; + $.extend(this, options); + }; + + /*********************************************** + * FILE: ..\src\classes\footer.js + ***********************************************/ + ng.Footer = function($scope, grid) { + $scope.maxRows = function () { + var ret = Math.max($scope.pagingOptions.totalServerItems, grid.sortedData.length); + return ret; + }; + + $scope.multiSelect = (grid.config.canSelectRows && grid.config.multiSelect); + $scope.selectedItemCount = grid.selectedItemCount; + $scope.maxPages = function () { + return Math.ceil($scope.maxRows() / $scope.pagingOptions.pageSize); + }; + + $scope.pageForward = function() { + var page = $scope.pagingOptions.currentPage; + $scope.pagingOptions.currentPage = Math.min(page + 1, $scope.maxPages()); + }; + + $scope.pageBackward = function() { + var page = $scope.pagingOptions.currentPage; + $scope.pagingOptions.currentPage = Math.max(page - 1, 1); + }; + + $scope.pageToFirst = function() { + $scope.pagingOptions.currentPage = 1; + }; + + $scope.pageToLast = function() { + var maxPages = $scope.maxPages(); + $scope.pagingOptions.currentPage = maxPages; + }; + + $scope.cantPageForward = function() { + var curPage = $scope.pagingOptions.currentPage; + var maxPages = $scope.maxPages(); + return !(curPage < maxPages); + }; + + $scope.cantPageBackward = function() { + var curPage = $scope.pagingOptions.currentPage; + return !(curPage > 1); + }; + }; + + /*********************************************** + * FILE: ..\src\classes\rowFactory.js + ***********************************************/ + ng.RowFactory = function(grid, $scope) { + var self = this; + // we cache rows when they are built, and then blow the cache away when sorting + self.rowCache = []; + self.aggCache = {}; + self.parentCache = []; // Used for grouping and is cleared each time groups are calulated. + self.dataChanged = true; + self.parsedData = []; + self.rowConfig = {}; + self.selectionService = grid.selectionService; + self.rowHeight = 30; + self.numberOfAggregates = 0; + self.groupedData = undefined; + self.rowHeight = grid.config.rowHeight; + self.rowConfig = { + canSelectRows: grid.config.canSelectRows, + rowClasses: grid.config.rowClasses, + selectedItems: grid.config.selectedItems, + selectWithCheckboxOnly: grid.config.selectWithCheckboxOnly, + beforeSelectionChangeCallback: grid.config.beforeSelectionChange, + afterSelectionChangeCallback: grid.config.afterSelectionChange, + jqueryUITheme: grid.config.jqueryUITheme + }; + + self.renderedRange = new ng.Range(0, grid.minRowsToRender() + EXCESS_ROWS); + // Builds rows for each data item in the 'filteredData' + // @entity - the data item + // @rowIndex - the index of the row + self.buildEntityRow = function(entity, rowIndex) { + var row = self.rowCache[rowIndex]; // first check to see if we've already built it + if (!row) { + // build the row + row = new ng.Row(entity, self.rowConfig, self.selectionService); + row.rowIndex = rowIndex + 1; //not a zero-based rowIndex + row.offsetTop = self.rowHeight * rowIndex; + row.selected = entity[SELECTED_PROP]; + // finally cache it for the next round + self.rowCache[rowIndex] = row; + } + return row; + }; + + self.buildAggregateRow = function(aggEntity, rowIndex) { + var agg = self.aggCache[aggEntity.aggIndex]; // first check to see if we've already built it + if (!agg) { + // build the row + agg = new ng.Aggregate(aggEntity, self); + self.aggCache[aggEntity.aggIndex] = agg; + } + agg.index = rowIndex + 1; //not a zero-based rowIndex + agg.offsetTop = self.rowHeight * rowIndex; + return agg; + }; + self.UpdateViewableRange = function(newRange) { + self.renderedRange = newRange; + self.renderedChange(); + }; + self.filteredDataChanged = function() { + // check for latebound autogenerated columns + if (grid.lateBoundColumns && grid.filteredData.length > 1) { + grid.config.columnDefs = undefined; + grid.buildColumns(); + grid.lateBoundColumns = false; + } + self.dataChanged = true; + self.rowCache = []; //if data source changes, kill this! + if (grid.config.groups.length > 0) { + self.getGrouping(grid.config.groups); + } + self.UpdateViewableRange(self.renderedRange); + }; + + self.renderedChange = function() { + if (!self.groupedData || grid.config.groups.length < 1) { + self.renderedChangeNoGroups(); + grid.refreshDomSizes(); + return; + } + self.parentCache = []; + var rowArr = []; + var dataArray = self.parsedData.filter(function(e) { + return e[NG_HIDDEN] === false; + }).slice(self.renderedRange.topRow, self.renderedRange.bottomRow); + angular.forEach(dataArray, function(item, indx) { + var row; + if (item.isAggRow) { + row = self.buildAggregateRow(item, self.renderedRange.topRow + indx); + } else { + row = self.buildEntityRow(item, self.renderedRange.topRow + indx); + } + //add the row to our return array + rowArr.push(row); + }); + grid.setRenderedRows(rowArr); + }; + + self.renderedChangeNoGroups = function() { + var rowArr = []; + var dataArr = grid.filteredData.slice(self.renderedRange.topRow, self.renderedRange.bottomRow); + angular.forEach(dataArr, function(item, i) { + var row = self.buildEntityRow(item, self.renderedRange.topRow + i); + //add the row to our return array + rowArr.push(row); + }); + grid.setRenderedRows(rowArr); + }; + + //magical recursion. it works. I swear it. I figured it out in the shower one day. + self.parseGroupData = function(g) { + if (g.values) { + angular.forEach(g.values, function(item) { + // get the last parent in the array because that's where our children want to be + self.parentCache[self.parentCache.length - 1].children.push(item); + //add the row to our return array + self.parsedData.push(item); + }); + } else { + for (var prop in g) { + // exclude the meta properties. + if (prop == NG_FIELD || prop == NG_DEPTH || prop == NG_COLUMN) { + continue; + } else if (g.hasOwnProperty(prop)) { + //build the aggregate row + var agg = self.buildAggregateRow({ + gField: g[NG_FIELD], + gLabel: prop, + gDepth: g[NG_DEPTH], + isAggRow: true, + '_ng_hidden_': false, + children: [], + aggChildren: [], + aggIndex: self.numberOfAggregates, + aggLabelFilter: g[NG_COLUMN].aggLabelFilter + }, 0); + self.numberOfAggregates++; + //set the aggregate parent to the parent in the array that is one less deep. + agg.parent = self.parentCache[agg.depth - 1]; + // if we have a parent, set the parent to not be collapsed and append the current agg to its children + if (agg.parent) { + agg.parent.collapsed = false; + agg.parent.aggChildren.push(agg); + } + // add the aggregate row to the parsed data. + self.parsedData.push(agg.entity); + // the current aggregate now the parent of the current depth + self.parentCache[agg.depth] = agg; + // dig deeper for more aggregates or children. + self.parseGroupData(g[prop]); + } + } + } + }; + //Shuffle the data into their respective groupings. + self.getGrouping = function(groups) { + self.aggCache = []; + self.rowCache = []; + self.numberOfAggregates = 0; + self.groupedData = {}; + // Here we set the onmousedown event handler to the header container. + var data = grid.filteredData; + var maxDepth = groups.length; + var cols = $scope.columns; + + angular.forEach(data, function(item) { + item[NG_HIDDEN] = true; + var ptr = self.groupedData; + angular.forEach(groups, function(group, depth) { + if (!cols[depth].isAggCol && depth <= maxDepth) { + cols.splice(item.gDepth, 0, new ng.Column({ + colDef: { + field: '', + width: 25, + sortable: false, + resizable: false, + headerCellTemplate: '<div class="ngAggHeader"></div>' + }, + isAggCol: true, + index: item.gDepth, + headerRowHeight: grid.config.headerRowHeight + })); + } + var col = cols.filter(function(c) { + return c.field == group; + })[0]; + var val = ng.utils.evalProperty(item, group); + val = val ? val.toString() : 'null'; + if (!ptr[val]) { + ptr[val] = {}; + } + if (!ptr[NG_FIELD]) { + ptr[NG_FIELD] = group; + } + if (!ptr[NG_DEPTH]) { + ptr[NG_DEPTH] = depth; + } + if (!ptr[NG_COLUMN]) { + ptr[NG_COLUMN] = col; + } + ptr = ptr[val]; + }); + if (!ptr.values) { + ptr.values = []; + } + ptr.values.push(item); + }); + grid.fixColumnIndexes(); + self.parsedData.length = 0; + self.parseGroupData(self.groupedData); + }; + + if (grid.config.groups.length > 0 && grid.filteredData.length > 0) { + self.getGrouping(grid.config.groups); + } + }; + + /*********************************************** + * FILE: ..\src\classes\grid.js + ***********************************************/ + ng.Grid = function($scope, options, sortService, domUtilityService, $filter) { + var defaults = { + //Callback for when you want to validate something after selection. + afterSelectionChange: function() { + }, + + /* Callback if you want to inspect something before selection, + return false if you want to cancel the selection. return true otherwise. + If you need to wait for an async call to proceed with selection you can + use rowItem.continueSelection(event) method after returning false initially. + Note: when shift+ Selecting multiple items in the grid this will only get called + once and the rowItem will be an array of items that are queued to be selected. */ + beforeSelectionChange: function() { + return true; + }, + + //To be able to have selectable rows in grid. + canSelectRows: true, + + //definitions of columns as an array [], if not defines columns are auto-generated. See github wiki for more details. + columnDefs: undefined, + + //*Data being displayed in the grid. Each item in the array is mapped to a row being displayed. + data: [], + + //Row selection check boxes appear as the first column. + displaySelectionCheckbox: true, + + //Enable or disable resizing of columns + enableColumnResize: true, + + //Enable or disable resizing of columns + enableColumnReordering: true, + + //Enables the server-side paging feature + enablePaging: false, + + //Enable drag and drop row reordering. Only works in HTML5 compliant browsers. + enableRowReordering: true, + + //Enables or disables sorting in grid. + enableSorting: true, + + /* filterOptions - + filterText: The text bound to the built-in search box. + useExternalFilter: Bypass internal filtering if you want to roll your own filtering mechanism but want to use builtin search box. + */ + filterOptions: { + filterText: "", + useExternalFilter: false + }, + + //Defining the height of the footer in pixels. + footerRowHeight: 55, + + //Show or hide the footer alltogether the footer is enabled by default + displayFooter: undefined, + footerVisible: true, // depricated + + //Initial fields to group data by. Array of field names, not displayName. + groups: [], + + //The height of the header row in pixels. + headerRowHeight: 30, + + //Define a header row template for further customization. See github wiki for more details. + headerRowTemplate: undefined, + + /*Enables the use of jquery UI reaggable/droppable plugin. requires jqueryUI to work if enabled. + Useful if you want drag + drop but your users insist on crappy browsers. */ + jqueryUIDraggable: false, + + //Enable the use jqueryUIThemes + jqueryUITheme: false, + + //Prevent unselections when in single selection mode. + keepLastSelected: true, + + /*Maintains the column widths while resizing. + Defaults to true when using *'s or undefined widths. Can be ovverriden by setting to false.*/ + maintainColumnRatios: undefined, + + //Set this to false if you only want one item selected at a time + multiSelect: true, + + // pagingOptions - + + pagingOptions: { + // pageSizes: list of available page sizes. + pageSizes: [250, 500, 1000], + //pageSize: currently selected page size. + pageSize: 250, + //totalServerItems: Total items are on the server. + totalServerItems: 0, + //currentPage: the uhm... current page. + currentPage: 1 + }, + + //Array of plugin functions to register in ng-grid + plugins: [], + + //Row height of rows in grid. + rowHeight: 30, + + //Define a row Template to customize output. See github wiki for more details. + rowTemplate: undefined, + + //all of the items selected in the grid. In single select mode there will only be one item in the array. + selectedItems: [], + + //Disable row selections by clicking on the row and only when the checkbox is clicked. + selectWithCheckboxOnly: false, + + /*Enables menu to choose which columns to display and group by. + If both showColumnMenu and showFilter are false the menu button will not display.*/ + showColumnMenu: true, + + /*Enables display of the filterbox in the column menu. + If both showColumnMenu and showFilter are false the menu button will not display.*/ + showFilter: true, + + //Show the dropzone for drag and drop grouping + showGroupPanel: false, + + /*Define a sortInfo object to specify a default sorting state. + You can also observe this variable to utilize server-side sorting (see useExternalSorting). + Syntax is sortinfo: { field: 'fieldName', direction: 'ASC'/'asc' || 'desc'/'DESC'}*/ + sortInfo: undefined, + + //Set the tab index of the Vieport. + tabIndex: -1, + /*Prevents the internal sorting from executing. + The sortInfo object will be updated with the sorting information so you can handle sorting (see sortInfo)*/ + useExternalSorting: false + }, + self = this; + + self.maxCanvasHt = 0; + //self vars + self.config = $.extend(defaults, options); + if (typeof options.columnDefs == "string") { + self.config.columnDefs = $scope.$eval(options.columnDefs); + } + self.gridId = "ng" + ng.utils.newId(); + self.$root = null; //this is the root element that is passed in with the binding handler + self.$groupPanel = null; + self.$topPanel = null; + self.$headerContainer = null; + self.$headerScroller = null; + self.$headers = null; + self.$viewport = null; + self.$canvas = null; + self.rootDim = self.config.gridDim; + self.sortInfo = self.config.sortInfo; + self.sortedData = []; + self.lateBindColumns = false; + self.filteredData = []; + if (typeof self.config.data == "object") { + self.sortedData = self.config.data; // we cannot watch for updates if you don't pass the string name + } + self.lastSortedColumn = undefined; + self.calcMaxCanvasHeight = function() { + return (self.config.groups.length > 0) ? (self.rowFactory.parsedData.filter(function(e) { + return e[NG_HIDDEN] === false; + }).length * self.config.rowHeight) : (self.filteredData.length * self.config.rowHeight); + }; + self.elementDims = { + scrollW: 0, + scrollH: 0, + rowIndexCellW: 25, + rowSelectedCellW: 25, + rootMaxW: 0, + rootMaxH: 0 + }; + //self funcs + self.setRenderedRows = function(newRows) { + $scope.renderedRows = newRows; + if (!$scope.$$phase) { + $scope.$digest(); + } + self.refreshDomSizes(); + $scope.$emit('ngGridEventRows', newRows); + }; + self.minRowsToRender = function() { + var viewportH = $scope.viewportDimHeight() || 1; + return Math.floor(viewportH / self.config.rowHeight); + }; + self.refreshDomSizes = function() { + var dim = new ng.Dimension(); + dim.outerWidth = self.elementDims.rootMaxW; + dim.outerHeight = self.elementDims.rootMaxH; + self.rootDim = dim; + self.maxCanvasHt = self.calcMaxCanvasHeight(); + }; + self.buildColumnDefsFromData = function() { + if (!self.config.columnDefs) { + self.config.columnDefs = []; + } + if (!self.sortedData || !self.sortedData[0]) { + self.lateBoundColumns = true; + return; + } + var item; + item = self.sortedData[0]; + + ng.utils.forIn(item, function(prop, propName) { + if (propName != SELECTED_PROP) { + self.config.columnDefs.push({ + field: propName + }); + } + }); + }; + self.buildColumns = function() { + var columnDefs = self.config.columnDefs, + cols = [], + indexOffset = 0; + + if (!columnDefs) { + self.buildColumnDefsFromData(); + columnDefs = self.config.columnDefs; + } + if (self.config.displaySelectionCheckbox) { + indexOffset = 1; + cols.push(new ng.Column({ + colDef: { + field: '\u2714', + width: self.elementDims.rowSelectedCellW, + sortable: false, + resizable: false, + groupable: false, + headerCellTemplate: '<input class="ngSelectionHeader" type="checkbox" ng-show="multiSelect" ng-model="allSelected" ng-change="toggleSelectAll(allSelected)"/>', + cellTemplate: '<div class="ngSelectionCell"><input class="ngSelectionCheckbox" type="checkbox" ng-checked="row.selected" /></div>' + }, + index: 0, + headerRowHeight: self.config.headerRowHeight, + sortCallback: self.sortData, + resizeOnDataCallback: self.resizeOnData, + enableResize: self.config.enableColumnResize, + enableSort: self.config.enableSorting + }, $scope, self, domUtilityService, $filter)); + } + if (columnDefs.length > 0) { + angular.forEach(columnDefs, function(colDef, i) { + i += indexOffset; + var column = new ng.Column({ + colDef: colDef, + index: i, + headerRowHeight: self.config.headerRowHeight, + sortCallback: self.sortData, + resizeOnDataCallback: self.resizeOnData, + enableResize: self.config.enableColumnResize, + enableSort: self.config.enableSorting + }, $scope, self, domUtilityService); + cols.push(column); + var indx = self.config.groups.indexOf(colDef.field); + if (indx != -1) { + $scope.configGroups.splice(indx, 0, column); + } + }); + $scope.columns = cols; + } + }; + self.configureColumnWidths = function() { + var cols = self.config.columnDefs; + var indexOffset = self.config.displaySelectionCheckbox ? $scope.configGroups.length + 1 : $scope.configGroups.length; + var numOfCols = cols.length + indexOffset, + asterisksArray = [], + percentArray = [], + asteriskNum = 0, + totalWidth = 0; + totalWidth += self.config.displaySelectionCheckbox ? 25 : 0; + angular.forEach(cols, function(col, i) { + i += indexOffset; + var isPercent = false, t = undefined; + //if width is not defined, set it to a single star + if (ng.utils.isNullOrUndefined(col.width)) { + col.width = "*"; + } else { // get column width + isPercent = isNaN(col.width) ? ng.utils.endsWith(col.width, "%") : false; + t = isPercent ? col.width : parseInt(col.width, 10); + } + // check if it is a number + if (isNaN(t)) { + t = col.width; + // figure out if the width is defined or if we need to calculate it + if (t == 'auto') { // set it for now until we have data and subscribe when it changes so we can set the width. + $scope.columns[i].width = col.minWidth; + totalWidth += $scope.columns[i].width; + var temp = $scope.columns[i]; + $scope.$evalAsync(function() { + self.resizeOnData(temp, true); + }); + return; + } else if (t.indexOf("*") != -1) { // we need to save it until the end to do the calulations on the remaining width. + asteriskNum += t.length; + col.index = i; + asterisksArray.push(col); + return; + } else if (isPercent) { // If the width is a percentage, save it until the very last. + col.index = i; + percentArray.push(col); + return; + } else { // we can't parse the width so lets throw an error. + throw "unable to parse column width, use percentage (\"10%\",\"20%\", etc...) or \"*\" to use remaining width of grid"; + } + } else { + totalWidth += $scope.columns[i].width = parseInt(col.width, 10); + } + }); + // check if we saved any asterisk columns for calculating later + if (asterisksArray.length > 0) { + self.config.maintainColumnRatios === false ? angular.noop() : self.config.maintainColumnRatios = true; + // get the remaining width + var remainigWidth = self.rootDim.outerWidth - totalWidth; + // calculate the weight of each asterisk rounded down + var asteriskVal = Math.floor(remainigWidth / asteriskNum); + // set the width of each column based on the number of stars + angular.forEach(asterisksArray, function(col) { + var t = col.width.length; + $scope.columns[col.index].width = asteriskVal * t; + //check if we are on the last column + if (col.index + 1 == numOfCols) { + var offset = 2; //We're going to remove 2 px so we won't overlflow the viwport by default + // are we overflowing? + if (self.maxCanvasHt > $scope.viewportDimHeight()) { + //compensate for scrollbar + offset += domUtilityService.ScrollW; + } + $scope.columns[col.index].width -= offset; + } + totalWidth += $scope.columns[col.index].width; + }); + } + // Now we check if we saved any percentage columns for calculating last + if (percentArray.length > 0) { + // do the math + angular.forEach(percentArray, function(col) { + var t = col.width; + $scope.columns[col.index].width = Math.floor(self.rootDim.outerWidth * (parseInt(t.slice(0, -1), 10) / 100)); + }); + } + }; + self.init = function() { + //factories and services + self.selectionService = new ng.SelectionService(self); + self.rowFactory = new ng.RowFactory(self, $scope); + self.selectionService.Initialize(self.rowFactory); + self.searchProvider = new ng.SearchProvider($scope, self, $filter); + self.styleProvider = new ng.StyleProvider($scope, self, domUtilityService); + self.buildColumns(); + $scope.$watch('configGroups', function(a) { + var tempArr = []; + angular.forEach(a, function(item) { + tempArr.push(item.field || item); + }); + self.config.groups = tempArr; + self.rowFactory.filteredDataChanged(); + $scope.$emit('ngGridEventGroups', a); + }, true); + $scope.$watch('columns', function(a) { + domUtilityService.BuildStyles($scope, self, true); + $scope.$emit('ngGridEventColumns', a); + }, true); + self.maxCanvasHt = self.calcMaxCanvasHeight(); + if (self.config.sortInfo) { + self.config.sortInfo.column = $scope.columns.filter(function(c) { + return c.field == self.config.sortInfo.field; + })[0]; + self.config.sortInfo.column.sortDirection = self.config.sortInfo.direction.toUpperCase(); + self.sortData(self.config.sortInfo.column); + } + }; + self.prevScrollTop = 0; + self.prevScrollIndex = 0; + self.adjustScrollTop = function(scrollTop, force) { + if (self.prevScrollTop === scrollTop && !force) { + return; + } + var rowIndex = Math.floor(scrollTop / self.config.rowHeight); + // Have we hit the threshold going down? + if (self.prevScrollTop < scrollTop && rowIndex < self.prevScrollIndex + SCROLL_THRESHOLD) { + return; + } + //Have we hit the threshold going up? + if (self.prevScrollTop > scrollTop && rowIndex > self.prevScrollIndex - SCROLL_THRESHOLD) { + return; + } + self.prevScrollTop = scrollTop; + self.rowFactory.UpdateViewableRange(new ng.Range(Math.max(0, rowIndex - EXCESS_ROWS), rowIndex + self.minRowsToRender() + EXCESS_ROWS)); + self.prevScrollIndex = rowIndex; + }; + self.adjustScrollLeft = function(scrollLeft) { + if (self.$headerContainer) { + self.$headerContainer.scrollLeft(scrollLeft); + } + }; + self.resizeOnData = function(col) { + // we calculate the longest data. + var longest = col.minWidth; + var arr = ng.utils.getElementsByClassName('col' + col.index); + angular.forEach(arr, function(elem, index) { + var i; + if (index === 0) { + var kgHeaderText = $(elem).find('.ngHeaderText'); + i = ng.utils.visualLength(kgHeaderText) + 10; // +10 some margin + } else { + var ngCellText = $(elem).find('.ngCellText'); + i = ng.utils.visualLength(ngCellText) + 10; // +10 some margin + } + if (i > longest) { + longest = i; + } + }); + col.width = col.longest = Math.min(col.maxWidth, longest + 7); // + 7 px to make it look decent. + domUtilityService.BuildStyles($scope, self, true); + }; + self.sortData = function(col) { + self.config.sortInfo = { + column: col, + field: col.field, + direction: col.sortDirection + }; + self.clearSortingData(col); + if (!self.config.useExternalSorting) { + sortService.Sort(self.config.sortInfo, self.sortedData); + } + self.lastSortedColumn = col; + self.searchProvider.evalFilter(); + $scope.$emit('ngGridEventSorted', col); + }; + self.clearSortingData = function(col) { + if (!col) { + angular.forEach($scope.columns, function(c) { + c.sortDirection = ""; + }); + } else if (self.lastSortedColumn && col != self.lastSortedColumn) { + self.lastSortedColumn.sortDirection = ""; + } + }; + self.fixColumnIndexes = function() { + //fix column indexes + angular.forEach($scope.columns, function(col, i) { + col.index = i; + }); + }; + self.fixGroupIndexes = function() { + angular.forEach($scope.configGroups, function(item, i) { + item.groupIndex = i + 1; + }); + }; + //$scope vars + $scope.elementsNeedMeasuring = true; + $scope.columns = []; + $scope.renderedRows = []; + $scope.headerRow = null; + $scope.rowHeight = self.config.rowHeight; + $scope.jqueryUITheme = self.config.jqueryUITheme; + $scope.footer = null; + $scope.selectedItems = self.config.selectedItems; + $scope.multiSelect = self.config.multiSelect; + $scope.footerVisible = ng.utils.isNullOrUndefined(self.config.displayFooter) ? self.config.footerVisible : self.config.displayFooter; + $scope.footerRowHeight = $scope.footerVisible ? self.config.footerRowHeight : 0; + $scope.showColumnMenu = self.config.showColumnMenu; + $scope.showMenu = false; + $scope.configGroups = []; + //Paging + $scope.enablePaging = self.config.enablePaging; + $scope.pagingOptions = self.config.pagingOptions; + //Templates + $scope.rowTemplate = self.config.rowTemplate || ng.defaultRowTemplate(); + $scope.headerRowTemplate = self.config.headerRowTemplate || ng.defaultHeaderRowTemplate(); + + if (self.config.rowTemplate && !TEMPLATE_REGEXP.test(self.config.rowTemplate)) { + $scope.rowTemplate = $.ajax({ + type: "GET", + url: self.config.rowTemplate, + async: false + }).responseText; + } + if (self.config.headerRowTemplate && !TEMPLATE_REGEXP.test(self.config.headerRowTemplate)) { + $scope.headerRowTemplate = $.ajax({ + type: "GET", + url: self.config.headerRowTemplate, + async: false + }).responseText; + } + + //scope funcs + $scope.visibleColumns = function() { + return $scope.columns.filter(function(col) { + return col.visible; + }); + }; + $scope.toggleShowMenu = function() { + $scope.showMenu = !$scope.showMenu; + }; + $scope.toggleSelectAll = function(a) { + self.selectionService.toggleSelectAll(a); + }; + $scope.totalFilteredItemsLength = function() { + return self.filteredData.length; + }; + $scope.showGroupPanel = function() { + return self.config.showGroupPanel; + }; + $scope.topPanelHeight = function() { + return self.config.showGroupPanel === true ? self.config.headerRowHeight * 2 : self.config.headerRowHeight; + }; + + $scope.viewportDimHeight = function() { + return Math.max(0, self.rootDim.outerHeight - $scope.topPanelHeight() - $scope.footerRowHeight - 2); + }; + $scope.groupBy = function(col) { + if (self.sortedData.length < 1 || !col.groupable) { + return; + } + var indx = $scope.configGroups.indexOf(col); + if (indx == -1) { + col.isGroupedBy = true; + $scope.configGroups.push(col); + col.groupIndex = $scope.configGroups.length; + } else { + $scope.removeGroup(indx); + } + }; + $scope.removeGroup = function(index) { + var col = $scope.columns.filter(function(item) { + return item.groupIndex == (index + 1); + })[0]; + col.isGroupedBy = false; + col.groupIndex = 0; + if ($scope.columns[index].isAggCol) { + $scope.columns.splice(index, 1); + $scope.configGroups.splice(index, 1); + self.fixGroupIndexes(); + } + if ($scope.configGroups.length === 0) { + self.fixColumnIndexes(); + domUtilityService.digest($scope); + } + }; + $scope.totalRowWidth = function() { + var totalWidth = 0, + cols = $scope.visibleColumns(); + angular.forEach(cols, function(col) { + totalWidth += col.width; + }); + return totalWidth; + }; + $scope.headerScrollerDim = function() { + var viewportH = $scope.viewportDimHeight(), + maxHeight = self.maxCanvasHt, + vScrollBarIsOpen = (maxHeight > viewportH), + newDim = new ng.Dimension(); + + newDim.autoFitHeight = true; + newDim.outerWidth = $scope.totalRowWidth(); + if (vScrollBarIsOpen) { + newDim.outerWidth += self.elementDims.scrollW; + } else if ((maxHeight - viewportH) <= self.elementDims.scrollH) { //if the horizontal scroll is open it forces the viewport to be smaller + newDim.outerWidth += self.elementDims.scrollW; + } + return newDim; + }; + //call init + self.init(); + }; + + /*********************************************** + * FILE: ..\src\classes\range.js + ***********************************************/ + ng.Range = function(top, bottom) { + this.topRow = top; + this.bottomRow = bottom; + }; + + /*********************************************** + * FILE: ..\src\classes\row.js + ***********************************************/ + ng.Row = function(entity, config, selectionService) { + var self = this, // constant for the selection property that we add to each data item + canSelectRows = config.canSelectRows; + + self.jqueryUITheme = config.jqueryUITheme; + self.rowClasses = config.rowClasses; + self.entity = entity; + self.selectionService = selectionService; + self.selected = false; + self.cursor = canSelectRows ? 'pointer' : 'default'; + self.continueSelection = function(event) { + self.selectionService.ChangeSelection(self, event); + }; + self.toggleSelected = function(event) { + if (!canSelectRows) { + return true; + } + var element = event.target || event; + //check and make sure its not the bubbling up of our checked 'click' event + if (element.type == "checkbox" && element.parentElement.className != "ngSelectionCell ng-scope") { + return true; + } + if (config.selectWithCheckboxOnly && element.type != "checkbox") { + return true; + } else { + if (self.beforeSelectionChange(self)) { + self.continueSelection(event); + return self.afterSelectionChange(); + } + } + return false; + }; + self.rowIndex = 0; + self.offsetTop = 0; + self.rowDisplayIndex = 0; + self.alternatingRowClass = function () { + var isEven = (self.rowIndex % 2) === 0; + var classes = { + 'selected': self.selected, + 'ui-state-default': self.jqueryUITheme && isEven, + 'ui-state-active': self.jqueryUITheme && !isEven, + 'even': isEven, + 'odd': !isEven + }; + return classes; + }; + self.beforeSelectionChange = config.beforeSelectionChangeCallback; + self.afterSelectionChange = config.afterSelectionChangeCallback; + self.propertyCache = {}; + self.getProperty = function(path) { + return self.propertyCache[path] || ng.utils.evalProperty(self.entity, path); + }; + //selectify the entity + if (self.entity[SELECTED_PROP] === undefined) { + self.entity[SELECTED_PROP] = false; + } else if (self.entity[SELECTED_PROP]) { + // or else maintain the selection set by the entity. + self.selectionService.setSelection(self, self.entity[SELECTED_PROP]); + } + }; + + /*********************************************** + * FILE: ..\src\classes\searchProvider.js + ***********************************************/ + ng.SearchProvider = function($scope, grid, $filter) { + var self = this, + searchConditions = []; + self.extFilter = grid.config.filterOptions.useExternalFilter; + $scope.showFilter = grid.config.showFilter; + $scope.filterText = grid.config.filterOptions.filterText; + + self.fieldMap = {}; + + self.evalFilter = function() { + if (searchConditions.length === 0) { + grid.filteredData = grid.sortedData; + } else { + grid.filteredData = grid.sortedData.filter(function(item) { + for (var i = 0, len = searchConditions.length; i < len; i++) { + var condition = searchConditions[i]; + //Search entire row + if (!condition.column) { + for (var prop in item) { + if (item.hasOwnProperty(prop)) { + if (prop == SELECTED_PROP) { + continue; + } + var c = self.fieldMap[prop]; + var f = (c && c.cellFilter) ? $filter(c.cellFilter) : null; + var pVal = item[prop]; + if (pVal && (condition.regex.test(pVal.toString()) || (f && condition.regex.test(f(pVal).toString())))) { + return true; + } + } + } + return false; + } + //Search by column. + var col = self.fieldMap[condition.columnDisplay]; + if (!col) { + return false; + } + var filter = col.cellFilter ? $filter(col.cellFilter) : null; + var value = item[condition.column] || item[col.field]; + if ((!value || !condition.regex.test(value.toString())) && !(typeof filter == "function" && condition.regex.test(filter(value)))) { + return false; + } + } + return true; + }); + } + grid.rowFactory.filteredDataChanged(); + }; + var getRegExp = function(str, modifiers) { + try { + return new RegExp(str, modifiers); + } catch(err) { + //Escape all RegExp metacharacters. + return new RegExp(str.replace(/(\^|\$|\(|\)|\<|\>|\[|\]|\{|\}|\\|\||\.|\*|\+|\?)/g, '\\$1')); + } + }; + var buildSearchConditions = function(a) { + //reset. + searchConditions = []; + var qStr = ''; + if (!(qStr = $.trim(a))) { + return; + } + var columnFilters = qStr.split(";"); + $.each(columnFilters, function(i, filter) { + var args = filter.split(':'); + if (args.length > 1) { + var columnName = $.trim(args[0]); + var columnValue = $.trim(args[1]); + if (columnName && columnValue) { + searchConditions.push({ + column: columnName, + columnDisplay: columnName.replace(/\s+/g, '').toLowerCase(), + regex: getRegExp(columnValue, 'i') + }); + } + } else { + var val = $.trim(args[0]); + if (val) { + searchConditions.push({ + column: '', + regex: getRegExp(val, 'i') + }); + } + } + }); + }; + $scope.$watch('filterText', function(a) { + if (!self.extFilter) { + buildSearchConditions(a); + self.evalFilter(); + } + }); + if (!self.extFilter) { + $scope.$watch('columns', function(a) { + angular.forEach(a, function(col) { + self.fieldMap[col.field] = col; + self.fieldMap[col.displayName.toLowerCase().replace(/\s+/g, '')] = col; + }); + }); + } + }; + + /*********************************************** + * FILE: ..\src\classes\selectionService.js + ***********************************************/ + ng.SelectionService = function(grid) { + var self = this; + self.multi = grid.config.multiSelect; + self.selectedItems = grid.config.selectedItems; + self.selectedIndex = grid.config.selectedIndex; + self.lastClickedRow = undefined; + self.ignoreSelectedItemChanges = false; // flag to prevent circular event loops keeping single-select var in sync + + self.rowFactory = {}; + self.Initialize = function(rowFactory) { + self.rowFactory = rowFactory; + }; + + // function to manage the selection action of a data item (entity) + self.ChangeSelection = function(rowItem, evt) { + if (evt && evt.shiftKey && self.multi) { + if (self.lastClickedRow) { + var thisIndx = grid.filteredData.indexOf(rowItem.entity); + var prevIndx = grid.filteredData.indexOf(self.lastClickedRow.entity); + if (thisIndx == prevIndx) { + return false; + } + prevIndx++; + if (thisIndx < prevIndx) { + thisIndx = thisIndx ^ prevIndx; + prevIndx = thisIndx ^ prevIndx; + thisIndx = thisIndx ^ prevIndx; + } + var rows = []; + for (; prevIndx <= thisIndx; prevIndx++) { + rows.push(self.rowFactory.rowCache[prevIndx]); + } + if (rows[rows.length - 1].beforeSelectionChange(rows, evt)) { + $.each(rows, function(i, ri) { + ri.selected = true; + ri.entity[SELECTED_PROP] = true; + if (self.selectedItems.indexOf(ri.entity) === -1) { + self.selectedItems.push(ri.entity); + } + }); + rows[rows.length - 1].afterSelectionChange(rows, evt); + } + self.lastClickedRow = rows[rows.length - 1]; + return true; + } + } else if (!self.multi) { + if (self.lastClickedRow && self.lastClickedRow != rowItem) { + self.setSelection(self.lastClickedRow, false); + } + self.setSelection(rowItem, grid.config.keepLastSelected ? true : !rowItem.selected); + } else { + self.setSelection(rowItem, !rowItem.selected); + } + self.lastClickedRow = rowItem; + return true; + }; + + // just call this func and hand it the rowItem you want to select (or de-select) + self.setSelection = function(rowItem, isSelected) { + rowItem.selected = isSelected; + rowItem.entity[SELECTED_PROP] = isSelected; + if (!isSelected) { + var indx = self.selectedItems.indexOf(rowItem.entity); + self.selectedItems.splice(indx, 1); + } else { + if (self.selectedItems.indexOf(rowItem.entity) === -1) { + self.selectedItems.push(rowItem.entity); + } + } + }; + + // @return - boolean indicating if all items are selected or not + // @val - boolean indicating whether to select all/de-select all + self.toggleSelectAll = function(checkAll) { + var selectedlength = self.selectedItems.length; + if (selectedlength > 0) { + self.selectedItems.splice(0, selectedlength); + } + angular.forEach(grid.filteredData, function(item) { + item[SELECTED_PROP] = checkAll; + if (checkAll) { + self.selectedItems.push(item); + } + }); + angular.forEach(self.rowFactory.rowCache, function(row) { + row.selected = checkAll; + }); + }; + }; + + /*********************************************** + * FILE: ..\src\classes\styleProvider.js + ***********************************************/ + ng.StyleProvider = function($scope, grid, domUtilityService) { + $scope.headerCellStyle = function(col) { + return { "height": col.headerRowHeight + "px" }; + }; + $scope.rowStyle = function(row) { + return { "top": row.offsetTop + "px", "height": $scope.rowHeight + "px" }; + }; + $scope.canvasStyle = function() { + return { "height": grid.maxCanvasHt.toString() + "px" }; + }; + $scope.headerScrollerStyle = function() { + return { "height": grid.config.headerRowHeight + "px" }; + }; + $scope.topPanelStyle = function() { + return { "width": grid.rootDim.outerWidth + "px", "height": $scope.topPanelHeight() + "px" }; + }; + $scope.headerStyle = function() { + return { "width": (grid.rootDim.outerWidth - domUtilityService.ScrollW) + "px", "height": grid.config.headerRowHeight + "px" }; + }; + $scope.viewportStyle = function() { + return { "width": grid.rootDim.outerWidth + "px", "height": $scope.viewportDimHeight() + "px" }; + }; + $scope.footerStyle = function() { + return { "width": grid.rootDim.outerWidth + "px", "height": $scope.footerRowHeight + "px" }; + }; + }; + + /*********************************************** + * FILE: ..\src\directives\ng-grid.js + ***********************************************/ + ngGridDirectives.directive('ngGrid', ['$compile', '$filter', 'SortService', 'DomUtilityService', function($compile, $filter, sortService, domUtilityService) { + var ngGrid = { + scope: true, + compile: function() { + return { + pre: function($scope, iElement, iAttrs) { + var $element = $(iElement); + var options = $scope.$eval(iAttrs.ngGrid); + options.gridDim = new ng.Dimension({ outerHeight: $($element).height(), outerWidth: $($element).width() }); + var grid = new ng.Grid($scope, options, sortService, domUtilityService, $filter); + // if columndefs are a string of a property ont he scope watch for changes and rebuild columns. + if (typeof options.columnDefs == "string") { + $scope.$parent.$watch(options.columnDefs, function(a) { + $scope.columns = []; + grid.config.columnDefs = a; + grid.buildColumns(); + grid.configureColumnWidths(); + domUtilityService.BuildStyles($scope, grid); + grid.eventProvider.assignEvents(); + }); + } + + // if it is a string we can watch for data changes. otherwise you won't be able to update the grid data + if (typeof options.data == "string") { + var prevlength = 0; + var dataWatcher = function (a) { + prevlength = a ? a.length:0; + grid.sortedData = $scope.$eval(options.data) || []; + grid.searchProvider.evalFilter(); + grid.configureColumnWidths(); + grid.refreshDomSizes(); + if (grid.config.sortInfo) { + if (!grid.config.sortInfo.column) { + grid.config.sortInfo.column = $scope.columns.filter(function(c) { + return c.field == grid.config.sortInfo.field; + })[0]; + if (!grid.config.sortInfo.column) { + return; + } + } + grid.config.sortInfo.column.sortDirection = grid.config.sortInfo.direction.toLowerCase(); + grid.sortData(grid.config.sortInfo.column); + } + }; + $scope.$parent.$watch(options.data, dataWatcher); + $scope.$parent.$watch(options.data + '.length', function(a) { + if (a != prevlength) { + dataWatcher($scope.$eval(options.data)); + } + }); + } + var htmlText = ng.defaultGridTemplate(grid.config); + grid.footerController = new ng.Footer($scope, grid); + //set the right styling on the container + iElement.addClass("ngGrid").addClass(grid.gridId.toString()); + if (options.jqueryUITheme) { + iElement.addClass('ui-widget'); + } + iElement.append($compile(htmlText)($scope)); // make sure that if any of these change, we re-fire the calc logic + //walk the element's graph and the correct properties on the grid + domUtilityService.AssignGridContainers(iElement, grid); + grid.configureColumnWidths(); + //now use the manager to assign the event handlers + grid.eventProvider = new ng.EventProvider(grid, $scope, domUtilityService); + //initialize plugins. + angular.forEach(options.plugins, function(p) { + p.init($scope.$new(), grid, { SortService: sortService, DomUtilityService: domUtilityService }); + }); + return null; + } + }; + } + }; + return ngGrid; + }]); + + /*********************************************** + * FILE: ..\src\directives\ng-row.js + ***********************************************/ + ngGridDirectives.directive('ngRow', ['$compile', function($compile) { + var ngRow = { + scope: false, + compile: function() { + return { + pre: function($scope, iElement) { + if ($scope.row.isAggRow) { + var html = ng.aggregateTemplate(); + if ($scope.row.aggLabelFilter) { + html = html.replace(CUSTOM_FILTERS, '| ' + $scope.row.aggLabelFilter); + } else { + html = html.replace(CUSTOM_FILTERS, ""); + } + iElement.append($compile(html)($scope)); + } else { + iElement.append($compile($scope.rowTemplate)($scope)); + } + } + }; + } + }; + return ngRow; + }]); + + /*********************************************** + * FILE: ..\src\directives\ng-cell.js + ***********************************************/ + ngGridDirectives.directive('ngCell', ['$compile', function($compile) { + var ngCell = { + scope: false, + compile: function() { + return { + pre: function($scope, iElement) { + iElement.append($compile($scope.col.cellTemplate)($scope)); + } + }; + } + }; + return ngCell; + }]); + + /*********************************************** + * FILE: ..\src\directives\ng-header-row.js + ***********************************************/ + ngGridDirectives.directive('ngHeaderRow', ['$compile', function($compile) { + var ngHeaderRow = { + scope: false, + compile: function() { + return { + pre: function($scope, iElement) { + if (iElement.children().length === 0) { + iElement.append($compile($scope.headerRowTemplate)($scope)); + } + } + }; + } + }; + return ngHeaderRow; + }]); + + /*********************************************** + * FILE: ..\src\directives\ng-header-cell.js + ***********************************************/ + ngGridDirectives.directive('ngHeaderCell', ['$compile', function($compile) { + var ngHeaderCell = { + scope: false, + compile: function() { + return { + pre: function($scope, iElement) { + iElement.append($compile($scope.col.headerCellTemplate)($scope)); + } + }; + } + }; + return ngHeaderCell; + }]); + + /*********************************************** + * FILE: ..\src\init.js + ***********************************************/ +// initialization of services into the main module + angular.module('ngGrid', ['ngGrid.services', 'ngGrid.directives', 'ngGrid.filters']); +}(window)); diff --git a/src/main/webapp/external/angular-ui/ng-grid-1.5.0/ng-grid-1.5.0.js b/src/main/webapp/external/angular-ui/ng-grid-1.5.0/ng-grid-1.5.0.js new file mode 100644 index 0000000..858e65f --- /dev/null +++ b/src/main/webapp/external/angular-ui/ng-grid-1.5.0/ng-grid-1.5.0.js @@ -0,0 +1,7 @@ +/*********************************************** + * ng-grid JavaScript Library + * Authors: https://github.com/angular-ui/ng-grid/blob/master/README.md + * License: MIT (http://www.opensource.org/licenses/mit-license.php) + * Compiled At: 12/21/2012 15:55:15 + ***********************************************/ +(function(g){g.ng={};g.ng.$http=undefined;var n=angular.module("ngGrid.services",[]);var k=angular.module("ngGrid.directives",[]);var d=angular.module("ngGrid.filters",[]);var b="__ng_selected__";var j="__koGrid__";var m=8;var a=6;var c="asc";var e="desc";var p="_ng_field_";var h="_ng_depth_";var o="_ng_hidden_";var i="_ng_column_";var l=/CUSTOM_FILTERS/g;var f=/<.+>/;ng.moveSelectionHandler=function(t,v,r){if(v===null||v===undefined){return true;}if(v.config.selectedItems===undefined){return true;}var q=r.which||r.keyCode;var w=(q==38?-1:(q==40?1:null));if(!w){return true;}var s=t.renderedRows;var u=s.indexOf(v.selectionService.lastClickedRow)+w;if(u<0||u>=s.length){return true;}v.selectionService.ChangeSelection(s[u],r);if(u>s.length-m){v.$viewport.scrollTop(v.$viewport.scrollTop()+(v.config.rowHeight*2));}else{if(u<m){v.$viewport.scrollTop(v.$viewport.scrollTop()-(v.config.rowHeight*2));}}if(!t.$$phase){t.$parent.$digest();}return false;};if(!String.prototype.trim){String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"");};}if(!Array.prototype.indexOf){Array.prototype.indexOf=function(r){var q=this.length>>>0;var s=Number(arguments[1])||0;s=(s<0)?Math.ceil(s):Math.floor(s);if(s<0){s+=q;}for(;s<q;s++){if(s in this&&this[s]===r){return s;}}return -1;};}if(!Array.prototype.filter){Array.prototype.filter=function(r){var w=Object(this);var q=w.length>>>0;if(typeof r!=="function"){throw new TypeError();}var v=[];var u=arguments[1];for(var s=0;s<q;s++){if(s in w){var x=w[s];if(r.call(u,x,s,w)){v.push(x);}}}return v;};}ng.utils={visualLength:function(r){var q=document.getElementById("testDataLength");if(!q){q=document.createElement("SPAN");q.id="testDataLength";q.style.visibility="hidden";document.body.appendChild(q);}$(q).css("font",$(r).css("font"));q.innerHTML=$(r).text();return q.offsetWidth;},forIn:function(r,q){for(var s in r){if(r.hasOwnProperty(s)){q(r[s],s);}}},evalProperty:function(t,v){var r=v.split("."),u=0;var q=t[r[u]],s=r.length;u++;while(q&&u<s){q=q[r[u]];u++;}return q;},endsWith:function(r,q){if(!r||!q||typeof r!="string"){return false;}return r.indexOf(q,r.length-q.length)!==-1;},isNullOrUndefined:function(q){if(q===undefined||q===null){return true;}return false;},getElementsByClassName:function(r){var q=[];var v=new RegExp("\\b"+r+"\\b");var u=document.getElementsByTagName("*");for(var t=0;t<u.length;t++){var s=u[t].className;if(v.test(s)){q.push(u[t]);}}return q;},newId:(function(){var q=new Date().getTime();return function(){return q+=1;};})(),ieVersion:(function(){var q=3,s=document.createElement("div"),r=s.getElementsByTagName("i");while(s.innerHTML="<!--[if gt IE "+(++q)+"]><i></i><![endif]-->",r[0]){}return q>4?q:undefined;})()};$.extend(ng.utils,{isIe6:(function(){return ng.utils.ieVersion===6;})(),isIe7:(function(){return ng.utils.ieVersion===7;})(),isIe:(function(){return ng.utils.ieVersion!==undefined;})()});d.filter("ngColumns",function(){return function(q){return q.filter(function(r){return !r.isAggCol;});};});d.filter("checkmark",function(){return function(q){return q?"\u2714":"\u2718";};});n.factory("SortService",function(){var q={};q.colSortFnCache={};q.dateRE=/^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;q.guessSortFn=function(t){var u,w,s,v,r;if(t===undefined||t===null||t===""){return null;}w=typeof(t);switch(w){case"number":u=q.sortNumber;break;case"boolean":u=q.sortBool;break;default:u=undefined;break;}if(u){return u;}if(Object.prototype.toString.call(t)==="[object Date]"){return q.sortDate;}if(w!=="string"){return q.basicSort;}if(t.match(/^-?[�$�]?[\d,.]+%?$/)){return q.sortNumberStr;}s=t.match(q.dateRE);if(s){v=parseInt(s[1],10);r=parseInt(s[2],10);if(v>12){return q.sortDDMMStr;}else{if(r>12){return q.sortMMDDStr;}else{return q.sortMMDDStr;}}}return q.sortAlpha;};q.basicSort=function(s,r){if(s==r){return 0;}if(s<r){return -1;}return 1;};q.sortNumber=function(s,r){return s-r;};q.sortNumberStr=function(t,r){var w,v,u=false,s=false;w=parseFloat(t.replace(/[^0-9.-]/g,""));if(isNaN(w)){u=true;}v=parseFloat(r.replace(/[^0-9.-]/g,""));if(isNaN(v)){s=true;}if(u&&s){return 0;}if(u){return 1;}if(s){return -1;}return w-v;};q.sortAlpha=function(t,r){var u=t.toLowerCase(),s=r.toLowerCase();return u==s?0:(u<s?-1:1);};q.sortDate=function(s,r){var u=s.getTime(),t=r.getTime();return u==t?0:(u<t?-1:1);};q.sortBool=function(s,r){if(s&&r){return 0;}if(!s&&!r){return 0;}else{return s?1:-1;}};q.sortDDMMStr=function(t,s){var v,u,w,r,x,z;w=t.match(q.dateRE);z=w[3];r=w[2];x=w[1];if(r.length==1){r="0"+r;}if(x.length==1){x="0"+x;}v=z+r+x;w=s.match(q.dateRE);z=w[3];r=w[2];x=w[1];if(r.length==1){r="0"+r;}if(x.length==1){x="0"+x;}u=z+r+x;if(v==u){return 0;}if(v<u){return -1;}return 1;};q.sortMMDDStr=function(t,s){var v,u,w,r,x,z;w=t.match(q.dateRE);z=w[3];x=w[2];r=w[1];if(r.length==1){r="0"+r;}if(x.length==1){x="0"+x;}v=z+r+x;w=s.match(q.dateRE);z=w[3];x=w[2];r=w[1];if(r.length==1){r="0"+r;}if(x.length==1){x="0"+x;}u=z+r+x;if(v==u){return 0;}if(v<u){return -1;}return 1;};q.sortData=function(v,r){if(!v||!r){return;}var s=r.column,w=r.direction,u,t;if(q.colSortFnCache[s.field]){u=q.colSortFnCache[s.field];}else{if(s.sortingAlgorithm!=undefined){u=s.sortingAlgorithm;q.colSortFnCache[s.field]=s.sortingAlgorithm;}else{t=v[0];if(!t){return;}u=q.guessSortFn(t[s.field]);if(u){q.colSortFnCache[s.field]=u;}else{u=q.sortAlpha;}}}v.sort(function(z,y){var x=ng.utils.evalProperty(z,s.field);var A=ng.utils.evalProperty(y,s.field);if(!A&&!x){return 0;}else{if(!x){return 1;}else{if(!A){return -1;}}}if(w===c){return u(x,A);}else{return 0-u(x,A);}});return;};q.Sort=function(r,s){if(q.isSorting){return;}q.isSorting=true;q.sortData(s,r);q.isSorting=false;};return q;});n.factory("DomUtilityService",function(){var q={};var r=function(){var s=$("<div></div>");s.appendTo("body");s.height(100).width(100).css("position","absolute").css("overflow","scroll");s.append('<div style="height: 400px; width: 400px;"></div>');q.ScrollH=(s.height()-s[0].clientHeight);q.ScrollW=(s.width()-s[0].clientWidth);s.empty();s.attr("style","");s.append('<span style="font-family: Verdana, Helvetica, Sans-Serif; font-size: 14px;"><strong>M</strong></span>');q.LetterW=s.children().first().width();s.remove();};q.eventStorage={};q.AssignGridContainers=function(t,s){s.$root=$(t);s.$topPanel=s.$root.find(".ngTopPanel");s.$groupPanel=s.$root.find(".ngGroupPanel");s.$headerContainer=s.$topPanel.find(".ngHeaderContainer");s.$headerScroller=s.$topPanel.find(".ngHeaderScroller");s.$headers=s.$headerScroller.children();s.$viewport=s.$root.find(".ngViewport");s.$canvas=s.$viewport.find(".ngCanvas");s.$footerPanel=s.$root.find(".ngFooterPanel");q.UpdateGridLayout(s);};q.UpdateGridLayout=function(s){var t=s.$viewport.scrollTop();s.elementDims.rootMaxW=s.$root.width();s.elementDims.rootMaxH=s.$root.height();s.refreshDomSizes();s.adjustScrollTop(t,true);};q.numberOfGrids=0;q.BuildStyles=function(B,t,z){var s=t.config.rowHeight,u=t.$styleSheet,w=t.gridId,y,A=B.visibleColumns(),v=0;if(!u){u=$("#"+w);if(!u[0]){u=$("<style id='"+w+"' type='text/css' rel='stylesheet' />").appendTo(t.$root);}}u.empty();var x=B.totalRowWidth();y="."+w+" .ngCanvas { width: "+x+"px; }."+w+" .ngRow { width: "+x+"px; }."+w+" .ngCanvas { width: "+x+"px; }."+w+" .ngHeaderScroller { width: "+(x+q.scrollH+2)+"px}";angular.forEach(A,function(C,D){y+="."+w+" .col"+D+" { width: "+C.width+"px; left: "+v+"px; right: "+(x-v-C.width)+"px; height: "+s+"px }."+w+" .colt"+D+" { width: "+C.width+"px; }";v+=C.width;});if(ng.utils.isIe){u[0].styleSheet.cssText=y;}else{u[0].appendChild(document.createTextNode(y));}t.$styleSheet=u;if(z){q.digest(B);}};q.digest=function(s){if(!s.$$phase){s.$digest();}};q.ScrollH=17;q.ScrollW=17;q.LetterW=10;r();return q;});ng.defaultGridTemplate=function(){return'<div class="ngTopPanel" ng-class="{\'ui-widget-header\':jqueryUITheme, \'ui-corner-top\': jqueryUITheme}" ng-style="topPanelStyle()"><div class="ngGroupPanel" ng-show="showGroupPanel()" ng-style="headerStyle()"><div class="ngGroupPanelDescription" ng-show="configGroups.length == 0">Drag a column header here and drop it to group by that column</div><ul ng-show="configGroups.length > 0" class="ngGroupList"><li class="ngGroupItem" ng-repeat="group in configGroups"><span class="ngGroupElement"><span class="ngGroupName">{{group.displayName}}<span ng-click="removeGroup($index)" class="ngRemoveGroup">x</span></span><span ng-hide="$last" class="ngGroupArrow"></span></span></li></ul></div><div class="ngHeaderContainer" ng-style="headerStyle()"><div class="ngHeaderScroller" ng-style="headerScrollerStyle()" ng-header-row></div></div><div class="ngHeaderButton" ng-show="showColumnMenu || showFilter" ng-click="toggleShowMenu()"><div class="ngHeaderButtonArrow" ng-click=""></div></div><div ng-show="showMenu" class="ngColMenu"><div ng-show="showFilter"><input placeholder="Search..." type="text" ng-model="filterText"/></div><div ng-show="showColumnMenu"><span class="ngMenuText">Choose Columns:</span><ul class="ngColList"><li class="ngColListItem" ng-repeat="col in columns | ngColumns"><label><input type="checkbox" class="ngColListCheckbox" ng-model="col.visible"/>{{col.displayName}}</label><a title="Group By" ng-class="col.groupedByClass()" ng-show="col.groupable" ng-click="groupBy(col)"></a><span class="ngGroupingNumber" ng-show="col.groupIndex > 0">{{col.groupIndex}}</span></li></ul></div></div></div><div class="ngViewport" ng-class="{\'ui-widget-content\': jqueryUITheme}" ng-style="viewportStyle()"><div class="ngCanvas" ng-style="canvasStyle()"><div ng-style="rowStyle(row)" ng-repeat="row in renderedRows" ng-click="row.toggleSelected($event)" class="ngRow" ng-class="row.alternatingRowClass()" ng-row></div></div></div><div class="ngFooterPanel" ng-class="{\'ui-widget-content\': jqueryUITheme, \'ui-corner-bottom\': jqueryUITheme}" ng-style="footerStyle()"><div class="ngTotalSelectContainer" ng-show="footerVisible"><div class="ngFooterTotalItems" ng-class="{\'ngNoMultiSelect\': !multiSelect}" ><span class="ngLabel">Total Items: {{maxRows()}}</span><span ng-show="filterText.length > 0" class="ngLabel">(Showing Items: {{totalFilteredItemsLength()}})</span></div><div class="ngFooterSelectedItems" ng-show="multiSelect"><span class="ngLabel">Selected Items: {{selectedItems.length}}</span></div></div><div class="ngPagerContainer" style="float: right; margin-top: 10px;" ng-show="footerVisible && enablePaging" ng-class="{\'ngNoMultiSelect\': !multiSelect}"><div style="float:left; margin-right: 10px;" class="ngRowCountPicker"><span style="float: left; margin-top: 3px;" class="ngLabel">Page Size:</span><select style="float: left;height: 27px; width: 100px" ng-model="pagingOptions.pageSize" ><option ng-repeat="size in pagingOptions.pageSizes">{{size}}</option></select></div><div style="float:left; margin-right: 10px; line-height:25px;" class="ngPagerControl" style="float: left; min-width: 135px;"><button class="ngPagerButton" ng-click="pageToFirst()" ng-disabled="cantPageBackward()" title="First Page"><div class="ngPagerFirstTriangle"><div class="ngPagerFirstBar"></div></div></button><button class="ngPagerButton" ng-click="pageBackward()" ng-disabled="cantPageBackward()" title="Previous Page"><div class="ngPagerFirstTriangle ngPagerPrevTriangle"></div></button><input class="ngPagerCurrent" type="text" style="width:50px; height: 24px; margin-top: 1px; padding: 0px 4px;" ng-model="pagingOptions.currentPage"/><button class="ngPagerButton" ng-click="pageForward()" ng-disabled="cantPageForward()" title="Next Page"><div class="ngPagerLastTriangle ngPagerNextTriangle"></div></button><button class="ngPagerButton" ng-click="pageToLast()" ng-disabled="cantPageForward()" title="Last Page"><div class="ngPagerLastTriangle"><div class="ngPagerLastBar"></div></div></button></div></div></div>';};ng.defaultRowTemplate=function(){return'<div ng-style="{\'cursor\': row.cursor}" ng-repeat="col in visibleColumns()" class="ngCell col{{$index}} {{col.cellClass}}" ng-cell></div>';};ng.defaultCellTemplate=function(){return'<div class="ngCellText colt{{$index}}">{{row.getProperty(col.field) CUSTOM_FILTERS}}</div>';};ng.aggregateTemplate=function(){return'<div ng-click="row.toggleExpand()" ng-style="{\'left\': row.offsetleft}" class="ngAggregate"><span class="ngAggregateText">{{row.label CUSTOM_FILTERS}} ({{row.totalChildren()}} items)</span><div class="{{row.aggClass()}}"></div></div>';};ng.defaultHeaderRowTemplate=function(){return'<div ng-repeat="col in visibleColumns()" class="ngHeaderCell col{{$index}}" ng-header-cell></div>';};ng.defaultHeaderCellTemplate=function(){return'<div ng-click="col.sort()" class="ngHeaderSortColumn {{col.headerClass}}" ng-style="{\'cursor\': col.cursor}" ng-class="{ \'ngSorted\': !noSortVisible }"><div class="ngHeaderText colt{{$index}}">{{col.displayName}}</div><div class="ngSortButtonDown" ng-show="col.showSortButtonDown()"></div><div class="ngSortButtonUp" ng-show="col.showSortButtonUp()"></div></div><div ng-show="col.resizable" class="ngHeaderGrip" ng-click="col.gripClick($event)" ng-mousedown="col.gripOnMouseDown($event)"></div>';};ng.Aggregate=function(s,r){var q=this;q.index=0;q.offsetTop=0;q.entity=s;q.label=s.gLabel;q.field=s.gField;q.depth=s.gDepth;q.parent=s.parent;q.children=s.children;q.aggChildren=s.aggChildren;q.aggIndex=s.aggIndex;q.collapsed=true;q.isAggRow=true;q.offsetleft=s.gDepth*25;q.aggLabelFilter=s.aggLabelFilter;q.toggleExpand=function(){q.collapsed=q.collapsed?false:true;q.notifyChildren();};q.setExpand=function(t){q.collapsed=t;q.notifyChildren();};q.notifyChildren=function(){angular.forEach(q.aggChildren,function(u){u.entity[o]=q.collapsed;if(q.collapsed){u.setExpand(q.collapsed);}});angular.forEach(q.children,function(u){u[o]=q.collapsed;});r.rowCache=[];var t=false;angular.forEach(r.aggCache,function(u,v){if(t){var w=(30*q.children.length);u.offsetTop=q.collapsed?u.offsetTop-w:u.offsetTop+w;}else{if(v==q.aggIndex){t=true;}}});r.renderedChange();};q.aggClass=function(){return q.collapsed?"ngAggArrowCollapsed":"ngAggArrowExpanded";};q.totalChildren=function(){if(q.aggChildren.length>0){var t=0;var u=function(v){if(v.aggChildren.length>0){angular.forEach(v.aggChildren,function(w){u(w);});}else{t+=v.children.length;}};u(q);return t;}else{return q.children.length;}};};ng.EventProvider=function(t,s,r){var q=this;q.colToMove=undefined;q.groupToMove=undefined;q.assignEvents=function(){if(t.config.jqueryUIDraggable){t.$groupPanel.droppable({addClasses:false,drop:function(u){q.onGroupDrop(u);}});s.$evalAsync(q.setDraggables);}else{t.$groupPanel.on("mousedown",q.onGroupMouseDown).on("dragover",q.dragOver).on("drop",q.onGroupDrop);t.$headerScroller.on("mousedown",q.onHeaderMouseDown).on("dragover",q.dragOver);if(t.config.enableColumnReordering){t.$headerScroller.on("drop",q.onHeaderDrop);}if(t.config.enableRowReordering){t.$viewport.on("mousedown",q.onRowMouseDown).on("dragover",q.dragOver).on("drop",q.onRowDrop);}}s.$watch("columns",q.setDraggables,true);};q.dragOver=function(u){u.preventDefault();};q.setDraggables=function(){if(!t.config.jqueryUIDraggable){t.$root.find(".ngHeaderSortColumn").attr("draggable","true");}else{t.$root.find(".ngHeaderSortColumn").draggable({helper:"clone",appendTo:"body",stack:"div",addClasses:false,start:function(u){q.onHeaderMouseDown(u);}}).droppable({drop:function(u){q.onHeaderDrop(u);}});}};q.onGroupMouseDown=function(w){var u=$(w.target);if(u[0].className!="ngRemoveGroup"){var v=angular.element(u).scope();if(v){if(!t.config.jqueryUIDraggable){u.attr("draggable","true");}q.groupToMove={header:u,groupName:v.group,index:v.$index};}}else{q.groupToMove=undefined;}};q.onGroupDrop=function(u){u.stopPropagation();var v;var w;if(q.groupToMove){v=$(u.target).closest(".ngGroupElement");if(v.context.className=="ngGroupPanel"){s.configGroups.splice(q.groupToMove.index,1);s.configGroups.push(q.groupToMove.groupName);}else{w=angular.element(v).scope();if(w){if(q.groupToMove.index!=w.$index){s.configGroups.splice(q.groupToMove.index,1);s.configGroups.splice(w.$index,0,q.groupToMove.groupName);}}}q.groupToMove=undefined;t.fixGroupIndexes();}else{if(q.colToMove){if(s.configGroups.indexOf(q.colToMove.col)==-1){v=$(u.target).closest(".ngGroupElement");if(v.context.className=="ngGroupPanel"||v.context.className=="ngGroupPanelDescription"){s.groupBy(q.colToMove.col);}else{w=angular.element(v).scope();if(w){s.removeGroup(w.$index);}}}q.colToMove=undefined;}}if(!s.$$phase){s.$apply();}};q.onHeaderMouseDown=function(w){var v=$(w.target).closest(".ngHeaderSortColumn");var u=angular.element(v).scope();if(u){q.colToMove={header:v,col:u.col};}};q.onHeaderDrop=function(w){if(!q.colToMove){return;}var v=$(w.target).closest(".ngHeaderSortColumn");var u=angular.element(v).scope();if(u){if(q.colToMove.col==u.col){return;}s.columns.splice(q.colToMove.col.index,1);s.columns.splice(u.col.index,0,q.colToMove.col);t.fixColumnIndexes();r.BuildStyles(s,t,true);q.colToMove=undefined;}};q.onRowMouseDown=function(w){var v=$(w.target).closest(".ngRow");var u=angular.element(v).scope();if(u){v.attr("draggable","true");r.eventStorage.rowToMove={targetRow:v,scope:u};}};q.onRowDrop=function(y){var x=$(y.target).closest(".ngRow");var u=angular.element(x).scope();if(u){var z=r.eventStorage.rowToMove;if(z.scope.row==u.row){return;}var w=t.sortedData.indexOf(z.scope.row.entity);var v=t.sortedData.indexOf(u.row.entity);t.sortedData.splice(w,1);t.sortedData.splice(v,0,z.scope.row.entity);t.searchProvider.evalFilter();r.eventStorage.rowToMove=undefined;}};q.assignGridEventHandlers=function(){t.$viewport.on("scroll",function(w){var x=w.target.scrollLeft,v=w.target.scrollTop;t.adjustScrollLeft(x);t.adjustScrollTop(v);});var u=false;t.$viewport.on("keydown",function(w){if(!u){u=true;var v=ng.moveSelectionHandler(s,t,w);u=false;return v;}return false;});if(t.config.tabIndex===-1){t.$viewport.attr("tabIndex",r.numberOfGrids);r.numberOfGrids++;}else{t.$viewport.attr("tabIndex",t.config.tabIndex);}$(g).resize(function(){r.UpdateGridLayout(t);if(t.config.maintainColumnRatios){t.configureColumnWidths();}});};q.assignGridEventHandlers();q.assignEvents();};ng.Column=function(s,x,q,u){var y=this,v=s.colDef,t=500,w=0,r=null;y.width=v.width;y.groupIndex=0;y.isGroupedBy=false;y.minWidth=!v.minWidth?50:v.minWidth;y.maxWidth=!v.maxWidth?9000:v.maxWidth;y.headerRowHeight=s.headerRowHeight;y.displayName=v.displayName||v.field;y.index=s.index;y.isAggCol=s.isAggCol;y.cellClass=v.cellClass;y.cellFilter=v.cellFilter?v.cellFilter:"";y.field=v.field;y.aggLabelFilter=v.cellFilter||v.aggLabelFilter;y.visible=ng.utils.isNullOrUndefined(v.visible)||v.visible;y.sortable=false;y.resizable=false;y.groupable=ng.utils.isNullOrUndefined(v.groupable)||v.sortable;if(s.enableSort){y.sortable=ng.utils.isNullOrUndefined(v.sortable)||v.sortable;}if(s.enableResize){y.resizable=ng.utils.isNullOrUndefined(v.resizable)||v.resizable;}y.sortDirection=undefined;y.sortingAlgorithm=v.sortFn;y.headerClass=v.headerClass;y.headerCellTemplate=v.headerCellTemplate||ng.defaultHeaderCellTemplate();y.cursor=y.sortable?"pointer":"default";y.cellTemplate=v.cellTemplate||ng.defaultCellTemplate().replace(l,y.cellFilter?"|"+y.cellFilter:"");if(v.cellTemplate&&!f.test(v.cellTemplate)){y.cellTemplate=$.ajax({type:"GET",url:v.cellTemplate,async:false}).responseText;}if(v.headerCellTemplate&&!f.test(v.headerCellTemplate)){y.headerCellTemplate=$.ajax({type:"GET",url:v.headerCellTemplate,async:false}).responseText;}y.groupedByClass=function(){return y.isGroupedBy?"ngGroupedByIcon":"ngGroupIcon";};y.toggleVisible=function(){y.visible=!y.visible;};y.showSortButtonUp=function(){return y.sortable?y.sortDirection===e:y.sortable;};y.showSortButtonDown=function(){return y.sortable?y.sortDirection===c:y.sortable;};y.noSortVisible=function(){return !y.sortDirection;};y.sort=function(){if(!y.sortable){return true;}var z=y.sortDirection===c?e:c;y.sortDirection=z;s.sortCallback(y);return false;};y.gripClick=function(){w++;if(w===1){r=setTimeout(function(){w=0;},t);}else{clearTimeout(r);s.resizeOnDataCallback(y);w=0;}};y.gripOnMouseDown=function(z){if(z.ctrlKey){y.toggleVisible();u.BuildStyles(x,q);return true;}z.target.parentElement.style.cursor="col-resize";y.startMousePosition=z.clientX;y.origWidth=y.width;$(document).mousemove(y.onMouseMove);$(document).mouseup(y.gripOnMouseUp);return false;};y.onMouseMove=function(A){var B=A.clientX-y.startMousePosition;var z=B+y.origWidth;y.width=(z<y.minWidth?y.minWidth:(z>y.maxWidth?y.maxWidth:z));u.BuildStyles(x,q);return false;};y.gripOnMouseUp=function(){$(document).off("mousemove");$(document).off("mouseup");event.target.parentElement.style.cursor="default";u.digest(x);return false;};};ng.Dimension=function(q){this.outerHeight=null;this.outerWidth=null;$.extend(this,q);};ng.Footer=function(q,r){q.maxRows=function(){var s=Math.max(q.pagingOptions.totalServerItems,r.sortedData.length);return s;};q.multiSelect=(r.config.canSelectRows&&r.config.multiSelect);q.selectedItemCount=r.selectedItemCount;q.maxPages=function(){return Math.ceil(q.maxRows()/q.pagingOptions.pageSize);};q.pageForward=function(){var s=q.pagingOptions.currentPage;q.pagingOptions.currentPage=Math.min(s+1,q.maxPages());};q.pageBackward=function(){var s=q.pagingOptions.currentPage;q.pagingOptions.currentPage=Math.max(s-1,1);};q.pageToFirst=function(){q.pagingOptions.currentPage=1;};q.pageToLast=function(){var s=q.maxPages();q.pagingOptions.currentPage=s;};q.cantPageForward=function(){var t=q.pagingOptions.currentPage;var s=q.maxPages();return !(t<s);};q.cantPageBackward=function(){var s=q.pagingOptions.currentPage;return !(s>1);};};ng.RowFactory=function(s,r){var q=this;q.rowCache=[];q.aggCache={};q.parentCache=[];q.dataChanged=true;q.parsedData=[];q.rowConfig={};q.selectionService=s.selectionService;q.rowHeight=30;q.numberOfAggregates=0;q.groupedData=undefined;q.rowHeight=s.config.rowHeight;q.rowConfig={canSelectRows:s.config.canSelectRows,rowClasses:s.config.rowClasses,selectedItems:s.config.selectedItems,selectWithCheckboxOnly:s.config.selectWithCheckboxOnly,beforeSelectionChangeCallback:s.config.beforeSelectionChange,afterSelectionChangeCallback:s.config.afterSelectionChange,jqueryUITheme:s.config.jqueryUITheme};q.renderedRange=new ng.Range(0,s.minRowsToRender()+m);q.buildEntityRow=function(t,v){var u=q.rowCache[v];if(!u){u=new ng.Row(t,q.rowConfig,q.selectionService);u.rowIndex=v+1;u.offsetTop=q.rowHeight*v;u.selected=t[b];q.rowCache[v]=u;}return u;};q.buildAggregateRow=function(v,u){var t=q.aggCache[v.aggIndex];if(!t){t=new ng.Aggregate(v,q);q.aggCache[v.aggIndex]=t;}t.index=u+1;t.offsetTop=q.rowHeight*u;return t;};q.UpdateViewableRange=function(t){q.renderedRange=t;q.renderedChange();};q.filteredDataChanged=function(){if(s.lateBoundColumns&&s.filteredData.length>1){s.config.columnDefs=undefined;s.buildColumns();s.lateBoundColumns=false;}q.dataChanged=true;q.rowCache=[];if(s.config.groups.length>0){q.getGrouping(s.config.groups);}q.UpdateViewableRange(q.renderedRange);};q.renderedChange=function(){if(!q.groupedData||s.config.groups.length<1){q.renderedChangeNoGroups();s.refreshDomSizes();return;}q.parentCache=[];var u=[];var t=q.parsedData.filter(function(v){return v[o]===false;}).slice(q.renderedRange.topRow,q.renderedRange.bottomRow);angular.forEach(t,function(w,v){var x;if(w.isAggRow){x=q.buildAggregateRow(w,q.renderedRange.topRow+v);}else{x=q.buildEntityRow(w,q.renderedRange.topRow+v);}u.push(x);});s.setRenderedRows(u);};q.renderedChangeNoGroups=function(){var u=[];var t=s.filteredData.slice(q.renderedRange.topRow,q.renderedRange.bottomRow);angular.forEach(t,function(w,v){var x=q.buildEntityRow(w,q.renderedRange.topRow+v);u.push(x);});s.setRenderedRows(u);};q.parseGroupData=function(u){if(u.values){angular.forEach(u.values,function(w){q.parentCache[q.parentCache.length-1].children.push(w);q.parsedData.push(w);});}else{for(var v in u){if(v==p||v==h||v==i){continue;}else{if(u.hasOwnProperty(v)){var t=q.buildAggregateRow({gField:u[p],gLabel:v,gDepth:u[h],isAggRow:true,_ng_hidden_:false,children:[],aggChildren:[],aggIndex:q.numberOfAggregates,aggLabelFilter:u[i].aggLabelFilter},0);q.numberOfAggregates++;t.parent=q.parentCache[t.depth-1];if(t.parent){t.parent.collapsed=false;t.parent.aggChildren.push(t);}q.parsedData.push(t.entity);q.parentCache[t.depth]=t;q.parseGroupData(u[v]);}}}}};q.getGrouping=function(t){q.aggCache=[];q.rowCache=[];q.numberOfAggregates=0;q.groupedData={};var u=s.filteredData;var w=t.length;var v=r.columns;angular.forEach(u,function(x){x[o]=true;var y=q.groupedData;angular.forEach(t,function(A,C){if(!v[C].isAggCol&&C<=w){v.splice(x.gDepth,0,new ng.Column({colDef:{field:"",width:25,sortable:false,resizable:false,headerCellTemplate:'<div class="ngAggHeader"></div>'},isAggCol:true,index:x.gDepth,headerRowHeight:s.config.headerRowHeight}));}var z=v.filter(function(D){return D.field==A;})[0];var B=ng.utils.evalProperty(x,A);B=B?B.toString():"null";if(!y[B]){y[B]={};}if(!y[p]){y[p]=A;}if(!y[h]){y[h]=C;}if(!y[i]){y[i]=z;}y=y[B];});if(!y.values){y.values=[];}y.values.push(x);});s.fixColumnIndexes();q.parsedData.length=0;q.parseGroupData(q.groupedData);};if(s.config.groups.length>0&&s.filteredData.length>0){q.getGrouping(s.config.groups);}};ng.Grid=function(s,t,w,r,v){var u={afterSelectionChange:function(){},beforeSelectionChange:function(){return true;},canSelectRows:true,columnDefs:undefined,data:[],displaySelectionCheckbox:true,enableColumnResize:true,enableColumnReordering:true,enablePaging:false,enableRowReordering:true,enableSorting:true,filterOptions:{filterText:"",useExternalFilter:false},footerRowHeight:55,displayFooter:undefined,footerVisible:true,groups:[],headerRowHeight:30,headerRowTemplate:undefined,jqueryUIDraggable:false,jqueryUITheme:false,keepLastSelected:true,maintainColumnRatios:undefined,multiSelect:true,pagingOptions:{pageSizes:[250,500,1000],pageSize:250,totalServerItems:0,currentPage:1},plugins:[],rowHeight:30,rowTemplate:undefined,selectedItems:[],selectWithCheckboxOnly:false,showColumnMenu:true,showFilter:true,showGroupPanel:false,sortInfo:undefined,tabIndex:-1,useExternalSorting:false},q=this;q.maxCanvasHt=0;q.config=$.extend(u,t);if(typeof t.columnDefs=="string"){q.config.columnDefs=s.$eval(t.columnDefs);}q.gridId="ng"+ng.utils.newId();q.$root=null;q.$groupPanel=null;q.$topPanel=null;q.$headerContainer=null;q.$headerScroller=null;q.$headers=null;q.$viewport=null;q.$canvas=null;q.rootDim=q.config.gridDim;q.sortInfo=q.config.sortInfo;q.sortedData=[];q.lateBindColumns=false;q.filteredData=[];if(typeof q.config.data=="object"){q.sortedData=q.config.data;}q.lastSortedColumn=undefined;q.calcMaxCanvasHeight=function(){return(q.config.groups.length>0)?(q.rowFactory.parsedData.filter(function(x){return x[o]===false;}).length*q.config.rowHeight):(q.filteredData.length*q.config.rowHeight);};q.elementDims={scrollW:0,scrollH:0,rowIndexCellW:25,rowSelectedCellW:25,rootMaxW:0,rootMaxH:0};q.setRenderedRows=function(x){s.renderedRows=x;if(!s.$$phase){s.$digest();}q.refreshDomSizes();s.$emit("ngGridEventRows",x);};q.minRowsToRender=function(){var x=s.viewportDimHeight()||1;return Math.floor(x/q.config.rowHeight);};q.refreshDomSizes=function(){var x=new ng.Dimension();x.outerWidth=q.elementDims.rootMaxW;x.outerHeight=q.elementDims.rootMaxH;q.rootDim=x;q.maxCanvasHt=q.calcMaxCanvasHeight();};q.buildColumnDefsFromData=function(){if(!q.config.columnDefs){q.config.columnDefs=[];}if(!q.sortedData||!q.sortedData[0]){q.lateBoundColumns=true;return;}var x;x=q.sortedData[0];ng.utils.forIn(x,function(z,y){if(y!=b){q.config.columnDefs.push({field:y});}});};q.buildColumns=function(){var z=q.config.columnDefs,y=[],x=0;if(!z){q.buildColumnDefsFromData();z=q.config.columnDefs;}if(q.config.displaySelectionCheckbox){x=1;y.push(new ng.Column({colDef:{field:"\u2714",width:q.elementDims.rowSelectedCellW,sortable:false,resizable:false,groupable:false,headerCellTemplate:'<input class="ngSelectionHeader" type="checkbox" ng-show="multiSelect" ng-model="allSelected" ng-change="toggleSelectAll(allSelected)"/>',cellTemplate:'<div class="ngSelectionCell"><input class="ngSelectionCheckbox" type="checkbox" ng-checked="row.selected" /></div>'},index:0,headerRowHeight:q.config.headerRowHeight,sortCallback:q.sortData,resizeOnDataCallback:q.resizeOnData,enableResize:q.config.enableColumnResize,enableSort:q.config.enableSorting},s,q,r,v));}if(z.length>0){angular.forEach(z,function(D,A){A+=x;var B=new ng.Column({colDef:D,index:A,headerRowHeight:q.config.headerRowHeight,sortCallback:q.sortData,resizeOnDataCallback:q.resizeOnData,enableResize:q.config.enableColumnResize,enableSort:q.config.enableSorting},s,q,r);y.push(B);var C=q.config.groups.indexOf(D.field);if(C!=-1){s.configGroups.splice(C,0,B);}});s.columns=y;}};q.configureColumnWidths=function(){var E=q.config.columnDefs;var A=q.config.displaySelectionCheckbox?s.configGroups.length+1:s.configGroups.length;var x=E.length+A,C=[],F=[],D=0,B=0;B+=q.config.displaySelectionCheckbox?25:0;angular.forEach(E,function(H,J){J+=A;var K=false,I=undefined;if(ng.utils.isNullOrUndefined(H.width)){H.width="*";}else{K=isNaN(H.width)?ng.utils.endsWith(H.width,"%"):false;I=K?H.width:parseInt(H.width,10);}if(isNaN(I)){I=H.width;if(I=="auto"){s.columns[J].width=H.minWidth;B+=s.columns[J].width;var G=s.columns[J];s.$evalAsync(function(){q.resizeOnData(G,true);});return;}else{if(I.indexOf("*")!=-1){D+=I.length;H.index=J;C.push(H);return;}else{if(K){H.index=J;F.push(H);return;}else{throw'unable to parse column width, use percentage ("10%","20%", etc...) or "*" to use remaining width of grid';}}}}else{B+=s.columns[J].width=parseInt(H.width,10);}});if(C.length>0){q.config.maintainColumnRatios===false?angular.noop():q.config.maintainColumnRatios=true;var z=q.rootDim.outerWidth-B;var y=Math.floor(z/D);angular.forEach(C,function(G){var H=G.width.length;s.columns[G.index].width=y*H;if(G.index+1==x){var I=2;if(q.maxCanvasHt>s.viewportDimHeight()){I+=r.ScrollW;}s.columns[G.index].width-=I;}B+=s.columns[G.index].width;});}if(F.length>0){angular.forEach(F,function(G){var H=G.width;s.columns[G.index].width=Math.floor(q.rootDim.outerWidth*(parseInt(H.slice(0,-1),10)/100));});}};q.init=function(){q.selectionService=new ng.SelectionService(q);q.rowFactory=new ng.RowFactory(q,s);q.selectionService.Initialize(q.rowFactory);q.searchProvider=new ng.SearchProvider(s,q,v);q.styleProvider=new ng.StyleProvider(s,q,r);q.buildColumns();s.$watch("configGroups",function(x){var y=[];angular.forEach(x,function(z){y.push(z.field||z);});q.config.groups=y;q.rowFactory.filteredDataChanged();s.$emit("ngGridEventGroups",x);},true);s.$watch("columns",function(x){r.BuildStyles(s,q,true);s.$emit("ngGridEventColumns",x);},true);q.maxCanvasHt=q.calcMaxCanvasHeight();if(q.config.sortInfo){q.config.sortInfo.column=s.columns.filter(function(x){return x.field==q.config.sortInfo.field;})[0];q.config.sortInfo.column.sortDirection=q.config.sortInfo.direction.toUpperCase();q.sortData(q.config.sortInfo.column);}};q.prevScrollTop=0;q.prevScrollIndex=0;q.adjustScrollTop=function(y,x){if(q.prevScrollTop===y&&!x){return;}var z=Math.floor(y/q.config.rowHeight);if(q.prevScrollTop<y&&z<q.prevScrollIndex+a){return;}if(q.prevScrollTop>y&&z>q.prevScrollIndex-a){return;}q.prevScrollTop=y;q.rowFactory.UpdateViewableRange(new ng.Range(Math.max(0,z-m),z+q.minRowsToRender()+m));q.prevScrollIndex=z;};q.adjustScrollLeft=function(x){if(q.$headerContainer){q.$headerContainer.scrollLeft(x);}};q.resizeOnData=function(y){var z=y.minWidth;var x=ng.utils.getElementsByClassName("col"+y.index);angular.forEach(x,function(E,B){var C;if(B===0){var D=$(E).find(".ngHeaderText");C=ng.utils.visualLength(D)+10;}else{var A=$(E).find(".ngCellText");C=ng.utils.visualLength(A)+10;}if(C>z){z=C;}});y.width=y.longest=Math.min(y.maxWidth,z+7);r.BuildStyles(s,q,true);};q.sortData=function(x){q.config.sortInfo={column:x,field:x.field,direction:x.sortDirection};q.clearSortingData(x);if(!q.config.useExternalSorting){w.Sort(q.config.sortInfo,q.sortedData);}q.lastSortedColumn=x;q.searchProvider.evalFilter();s.$emit("ngGridEventSorted",x);};q.clearSortingData=function(x){if(!x){angular.forEach(s.columns,function(y){y.sortDirection="";});}else{if(q.lastSortedColumn&&x!=q.lastSortedColumn){q.lastSortedColumn.sortDirection="";}}};q.fixColumnIndexes=function(){angular.forEach(s.columns,function(x,y){x.index=y;});};q.fixGroupIndexes=function(){angular.forEach(s.configGroups,function(y,x){y.groupIndex=x+1;});};s.elementsNeedMeasuring=true;s.columns=[];s.renderedRows=[];s.headerRow=null;s.rowHeight=q.config.rowHeight;s.jqueryUITheme=q.config.jqueryUITheme;s.footer=null;s.selectedItems=q.config.selectedItems;s.multiSelect=q.config.multiSelect;s.footerVisible=ng.utils.isNullOrUndefined(q.config.displayFooter)?q.config.footerVisible:q.config.displayFooter;s.footerRowHeight=s.footerVisible?q.config.footerRowHeight:0;s.showColumnMenu=q.config.showColumnMenu;s.showMenu=false;s.configGroups=[];s.enablePaging=q.config.enablePaging;s.pagingOptions=q.config.pagingOptions;s.rowTemplate=q.config.rowTemplate||ng.defaultRowTemplate();s.headerRowTemplate=q.config.headerRowTemplate||ng.defaultHeaderRowTemplate();if(q.config.rowTemplate&&!f.test(q.config.rowTemplate)){s.rowTemplate=$.ajax({type:"GET",url:q.config.rowTemplate,async:false}).responseText;}if(q.config.headerRowTemplate&&!f.test(q.config.headerRowTemplate)){s.headerRowTemplate=$.ajax({type:"GET",url:q.config.headerRowTemplate,async:false}).responseText;}s.visibleColumns=function(){return s.columns.filter(function(x){return x.visible;});};s.toggleShowMenu=function(){s.showMenu=!s.showMenu;};s.toggleSelectAll=function(x){q.selectionService.toggleSelectAll(x);};s.totalFilteredItemsLength=function(){return q.filteredData.length;};s.showGroupPanel=function(){return q.config.showGroupPanel;};s.topPanelHeight=function(){return q.config.showGroupPanel===true?q.config.headerRowHeight*2:q.config.headerRowHeight;};s.viewportDimHeight=function(){return Math.max(0,q.rootDim.outerHeight-s.topPanelHeight()-s.footerRowHeight-2);};s.groupBy=function(x){if(q.sortedData.length<1||!x.groupable){return;}var y=s.configGroups.indexOf(x);if(y==-1){x.isGroupedBy=true;s.configGroups.push(x);x.groupIndex=s.configGroups.length;}else{s.removeGroup(y);}};s.removeGroup=function(y){var x=s.columns.filter(function(z){return z.groupIndex==(y+1);})[0];x.isGroupedBy=false;x.groupIndex=0;if(s.columns[y].isAggCol){s.columns.splice(y,1);s.configGroups.splice(y,1);q.fixGroupIndexes();}if(s.configGroups.length===0){q.fixColumnIndexes();r.digest(s);}};s.totalRowWidth=function(){var x=0,y=s.visibleColumns();angular.forEach(y,function(z){x+=z.width;});return x;};s.headerScrollerDim=function(){var y=s.viewportDimHeight(),z=q.maxCanvasHt,A=(z>y),x=new ng.Dimension();x.autoFitHeight=true;x.outerWidth=s.totalRowWidth();if(A){x.outerWidth+=q.elementDims.scrollW;}else{if((z-y)<=q.elementDims.scrollH){x.outerWidth+=q.elementDims.scrollW;}}return x;};q.init();};ng.Range=function(r,q){this.topRow=r;this.bottomRow=q;};ng.Row=function(s,t,u){var r=this,q=t.canSelectRows;r.jqueryUITheme=t.jqueryUITheme;r.rowClasses=t.rowClasses;r.entity=s;r.selectionService=u;r.selected=false;r.cursor=q?"pointer":"default";r.continueSelection=function(v){r.selectionService.ChangeSelection(r,v);};r.toggleSelected=function(w){if(!q){return true;}var v=w.target||w;if(v.type=="checkbox"&&v.parentElement.className!="ngSelectionCell ng-scope"){return true;}if(t.selectWithCheckboxOnly&&v.type!="checkbox"){return true;}else{if(r.beforeSelectionChange(r)){r.continueSelection(w);return r.afterSelectionChange();}}return false;};r.rowIndex=0;r.offsetTop=0;r.rowDisplayIndex=0;r.alternatingRowClass=function(){var w=(r.rowIndex%2)===0;var v={selected:r.selected,"ui-state-default":r.jqueryUITheme&&w,"ui-state-active":r.jqueryUITheme&&!w,even:w,odd:!w};return v;};r.beforeSelectionChange=t.beforeSelectionChangeCallback;r.afterSelectionChange=t.afterSelectionChangeCallback;r.propertyCache={};r.getProperty=function(v){return r.propertyCache[v]||ng.utils.evalProperty(r.entity,v);};if(r.entity[b]===undefined){r.entity[b]=false;}else{if(r.entity[b]){r.selectionService.setSelection(r,r.entity[b]);}}};ng.SearchProvider=function(t,u,w){var s=this,q=[];s.extFilter=u.config.filterOptions.useExternalFilter;t.showFilter=u.config.showFilter;t.filterText=u.config.filterOptions.filterText;s.fieldMap={};s.evalFilter=function(){if(q.length===0){u.filteredData=u.sortedData;}else{u.filteredData=u.sortedData.filter(function(H){for(var C=0,E=q.length;C<E;C++){var B=q[C];if(!B.column){for(var y in H){if(H.hasOwnProperty(y)){if(y==b){continue;}var F=s.fieldMap[y];var D=(F&&F.cellFilter)?w(F.cellFilter):null;var x=H[y];if(x&&(B.regex.test(x.toString())||(D&&B.regex.test(D(x).toString())))){return true;}}}return false;}var A=s.fieldMap[B.columnDisplay];if(!A){return false;}var z=A.cellFilter?w(A.cellFilter):null;var G=H[B.column]||H[A.field];if((!G||!B.regex.test(G.toString()))&&!(typeof z=="function"&&B.regex.test(z(G)))){return false;}}return true;});}u.rowFactory.filteredDataChanged();};var r=function(z,x){try{return new RegExp(z,x);}catch(y){return new RegExp(z.replace(/(\^|\$|\(|\)|\<|\>|\[|\]|\{|\}|\\|\||\.|\*|\+|\?)/g,"\\$1"));}};var v=function(x){q=[];var z="";if(!(z=$.trim(x))){return;}var y=z.split(";");$.each(y,function(C,D){var B=D.split(":");if(B.length>1){var A=$.trim(B[0]);var F=$.trim(B[1]);if(A&&F){q.push({column:A,columnDisplay:A.replace(/\s+/g,"").toLowerCase(),regex:r(F,"i")});}}else{var E=$.trim(B[0]);if(E){q.push({column:"",regex:r(E,"i")});}}});};t.$watch("filterText",function(x){if(!s.extFilter){v(x);s.evalFilter();}});if(!s.extFilter){t.$watch("columns",function(x){angular.forEach(x,function(y){s.fieldMap[y.field]=y;s.fieldMap[y.displayName.toLowerCase().replace(/\s+/g,"")]=y;});});}};ng.SelectionService=function(r){var q=this;q.multi=r.config.multiSelect;q.selectedItems=r.config.selectedItems;q.selectedIndex=r.config.selectedIndex;q.lastClickedRow=undefined;q.ignoreSelectedItemChanges=false;q.rowFactory={};q.Initialize=function(s){q.rowFactory=s;};q.ChangeSelection=function(v,t){if(t&&t.shiftKey&&q.multi){if(q.lastClickedRow){var u=r.filteredData.indexOf(v.entity);var s=r.filteredData.indexOf(q.lastClickedRow.entity);if(u==s){return false;}s++;if(u<s){u=u^s;s=u^s;u=u^s;}var w=[];for(;s<=u;s++){w.push(q.rowFactory.rowCache[s]);}if(w[w.length-1].beforeSelectionChange(w,t)){$.each(w,function(y,x){x.selected=true;x.entity[b]=true;if(q.selectedItems.indexOf(x.entity)===-1){q.selectedItems.push(x.entity);}});w[w.length-1].afterSelectionChange(w,t);}q.lastClickedRow=w[w.length-1];return true;}}else{if(!q.multi){if(q.lastClickedRow&&q.lastClickedRow!=v){q.setSelection(q.lastClickedRow,false);}q.setSelection(v,r.config.keepLastSelected?true:!v.selected);}else{q.setSelection(v,!v.selected);}}q.lastClickedRow=v;return true;};q.setSelection=function(u,s){u.selected=s;u.entity[b]=s;if(!s){var t=q.selectedItems.indexOf(u.entity);q.selectedItems.splice(t,1);}else{if(q.selectedItems.indexOf(u.entity)===-1){q.selectedItems.push(u.entity);}}};q.toggleSelectAll=function(s){var t=q.selectedItems.length;if(t>0){q.selectedItems.splice(0,t);}angular.forEach(r.filteredData,function(u){u[b]=s;if(s){q.selectedItems.push(u);}});angular.forEach(q.rowFactory.rowCache,function(u){u.selected=s;});};};ng.StyleProvider=function(r,s,q){r.headerCellStyle=function(t){return{height:t.headerRowHeight+"px"};};r.rowStyle=function(t){return{top:t.offsetTop+"px",height:r.rowHeight+"px"};};r.canvasStyle=function(){return{height:s.maxCanvasHt.toString()+"px"};};r.headerScrollerStyle=function(){return{height:s.config.headerRowHeight+"px"};};r.topPanelStyle=function(){return{width:s.rootDim.outerWidth+"px",height:r.topPanelHeight()+"px"};};r.headerStyle=function(){return{width:(s.rootDim.outerWidth-q.ScrollW)+"px",height:s.config.headerRowHeight+"px"};};r.viewportStyle=function(){return{width:s.rootDim.outerWidth+"px",height:r.viewportDimHeight()+"px"};};r.footerStyle=function(){return{width:s.rootDim.outerWidth+"px",height:r.footerRowHeight+"px"};};};k.directive("ngGrid",["$compile","$filter","SortService","DomUtilityService",function(r,t,u,q){var s={scope:true,compile:function(){return{pre:function(B,w,z){var C=$(w);var D=B.$eval(z.ngGrid);D.gridDim=new ng.Dimension({outerHeight:$(C).height(),outerWidth:$(C).width()});var v=new ng.Grid(B,D,u,q,t);if(typeof D.columnDefs=="string"){B.$parent.$watch(D.columnDefs,function(E){B.columns=[];v.config.columnDefs=E;v.buildColumns();v.configureColumnWidths();q.BuildStyles(B,v);v.eventProvider.assignEvents();});}if(typeof D.data=="string"){var y=0;var A=function(E){y=E?E.length:0;v.sortedData=B.$eval(D.data)||[];v.searchProvider.evalFilter();v.configureColumnWidths();v.refreshDomSizes();if(v.config.sortInfo){if(!v.config.sortInfo.column){v.config.sortInfo.column=B.columns.filter(function(F){return F.field==v.config.sortInfo.field;})[0];if(!v.config.sortInfo.column){return;}}v.config.sortInfo.column.sortDirection=v.config.sortInfo.direction.toLowerCase();v.sortData(v.config.sortInfo.column);}};B.$parent.$watch(D.data,A);B.$parent.$watch(D.data+".length",function(E){if(E!=y){A(B.$eval(D.data));}});}var x=ng.defaultGridTemplate(v.config);v.footerController=new ng.Footer(B,v);w.addClass("ngGrid").addClass(v.gridId.toString());if(D.jqueryUITheme){w.addClass("ui-widget");}w.append(r(x)(B));q.AssignGridContainers(w,v);v.configureColumnWidths();v.eventProvider=new ng.EventProvider(v,B,q);angular.forEach(D.plugins,function(E){E.init(B.$new(),v,{SortService:u,DomUtilityService:q});});return null;}};}};return s;}]);k.directive("ngRow",["$compile",function(r){var q={scope:false,compile:function(){return{pre:function(s,u){if(s.row.isAggRow){var t=ng.aggregateTemplate();if(s.row.aggLabelFilter){t=t.replace(l,"| "+s.row.aggLabelFilter);}else{t=t.replace(l,"");}u.append(r(t)(s));}else{u.append(r(s.rowTemplate)(s));}}};}};return q;}]);k.directive("ngCell",["$compile",function(r){var q={scope:false,compile:function(){return{pre:function(s,t){t.append(r(s.col.cellTemplate)(s));}};}};return q;}]);k.directive("ngHeaderRow",["$compile",function(q){var r={scope:false,compile:function(){return{pre:function(s,t){if(t.children().length===0){t.append(q(s.headerRowTemplate)(s));}}};}};return r;}]);k.directive("ngHeaderCell",["$compile",function(q){var r={scope:false,compile:function(){return{pre:function(s,t){t.append(q(s.col.headerCellTemplate)(s));}};}};return r;}]);angular.module("ngGrid",["ngGrid.services","ngGrid.directives","ngGrid.filters"]);}(window)); diff --git a/src/main/webapp/external/angular-ui/ng-grid-1.5.0/ng-grid.css b/src/main/webapp/external/angular-ui/ng-grid-1.5.0/ng-grid.css new file mode 100644 index 0000000..a8ab555 --- /dev/null +++ b/src/main/webapp/external/angular-ui/ng-grid-1.5.0/ng-grid.css @@ -0,0 +1,456 @@ + +/******** Grid Global ********/ +.nglabel { + display: block; + float: left; + font-weight: bold; + padding-right: 5px; +} +.ngNoSelect{ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +/******** Grid ********/ + +.ngGrid{ + background-color: rgb(253, 253, 253); + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +/******** Header ********/ + +.ngGroupPanel{ + background-color: rgb(234, 234, 234); + overflow: hidden; + border-bottom: 1px solid rgb(212,212,212); +} + +.ngGroupPanelDescription{ + margin-top: 5px; + margin-left: 5px; +} + +.ngGroupList { + list-style-type: none; + margin: 0; + padding: 0; +} + +.ngGroupItem { + float: left; +} + +.ngGroupElement { + float: left; + height: 100%; + width: 100%; +} + +.ngGroupName { + background-color: rgb(247,247,247); + border: 1px solid rgb(212,212,212); + padding: 3px 10px; + float: left; + margin-left: 0; + margin-top: 2px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + font-weight: bold; +} + +.ngGroupItem:first-child{ + margin-left: 2px; +} + +.ngRemoveGroup { + width: 5px; + float: right; + -moz-opacity: 0.4; + opacity: 0.4; + margin-top: -1px; + margin-left: 5px; +} +.ngRemoveGroup:hover { + color: black; + text-decoration: none; + cursor: pointer; + -moz-opacity: 0.7; + opacity: 0.7; +} +.ngGroupArrow { + width: 0; + height: 0; + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + border-left: 6px solid black; + margin-top: 10px; + margin-left: 5px; + margin-right: 5px; + float: right; +} + +.ngTopPanel { + position: relative; + z-index:5; + background-color: rgb(234, 234, 234); + border-bottom: 1px solid rgb(212,212,212); +} +.ngHeaderContainer { + position: relative; + overflow: hidden; + font-weight: bold; +} + +.ngHeaderScroller { + position:absolute; +} +.ngHeaderSortColumn{ + position:absolute; + overflow: hidden; +} + +.ngHeaderCell{ + border-left: 1px solid rgb(212,212,212); + position: absolute; + top: 0; + bottom: 0; +} + +.ngHeaderCell:first-child{ + border-left: 0; +} + +.ngHeaderCell:last-child{ + border-right: 1px solid rgb(212,212,212); +} + +.ngSortButtonUp { + position: absolute; + top: 3px; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + border-color: gray transparent; + border-style: solid; + border-width: 0 5px 5px 5px; + height: 0; + width: 0; +} +.ngSortButtonDown { + position: absolute; + top: 3px; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + border-color: gray transparent; + border-style: solid; + border-width: 5px 5px 0 5px; + height: 0; + width: 0; +} +.ngHeaderGrip { + cursor: col-resize; + width: 10px; + right: -5px; + top: 0; + height: 100%; + position: absolute; + z-index: 5; +} +.ngHeaderText { + padding: 5px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + white-space: nowrap; + -ms-text-overflow: ellipsis; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + overflow: hidden; +} + +/******** Viewport ********/ +.ngViewport{ + overflow: auto; + min-height: 20px; +} + +.ngCanvas{ + position: relative; +} + +/******** Rows ********/ +.ngRow { + position: absolute; + border-bottom: 1px solid rgb(229, 229, 229); +} +.ngRow.even { + background-color: rgb(243, 243, 243); +} +.ngRow.odd { + background-color: rgb(253, 253, 253); +} +.ngRow.selected { + background-color: rgb(189, 208, 203); +} +.ngRow.canSelect { + cursor: pointer; +} + +/******** Cells ********/ + +.ngCell { + overflow: hidden; + position: absolute; + border-left: 1px solid rgb(212,212,212); + top: 0; + bottom: 0; +} + +.ngCell:first-child{ + border-left: 0; +} + +.ngCell:last-child { + border-right: 1px solid rgb(212,212,212); +} + +.ngCellText { + padding: 5px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + white-space: nowrap; + -ms-text-overflow: ellipsis; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + overflow: hidden; +} +.ngSelectionHeader { + position: absolute; + top: 11px; + left: 6px; +} +input[type="checkbox"] { + margin: 0; + padding: 0; +} +input { + vertical-align:top; +} +.ngSelectionCell{ + margin-top: 9px; + margin-left: 6px; +} +.ngNoSort { + cursor:default; +} + +/******** Footer ********/ +.ngFooterPanel{ + background-color: rgb(234, 234, 234); + padding: 0; + border-top: 1px solid rgb(212,212,212); + position: relative; +} +.ngTotalSelectContainer { + float: left; + margin: 5px; + margin-top: 7px; +} +.ngFooterSelectedItems { + padding: 2px; +} +.ngFooterTotalItems { + padding: 2px; +} +.ngFooterTotalItems.ngnoMultiSelect { + padding: 0 !important; +} + +/* Aggregates */ +.ngAggHeader { + position: absolute; + border: none; +} +.ngAggregate { + position: absolute; + background-color: rgb(179, 191, 188); + border-bottom: 1px solid beige; + overflow: hidden; + top: 0; + bottom: 0; + right: -1px; + left: 0; +} +.ngAggregateText { + position: absolute; + left: 27px; + top: 5px; + line-height: 20px; + white-space:nowrap; +} +.ngAggArrowExpanded { + position: absolute; + left: 8px; + bottom: 10px; + width: 0; + height: 0; + border-style: solid; + border-width: 0 0 9px 9px; + border-color: transparent transparent #000000 transparent; +} +.ngAggArrowCollapsed { + position: absolute; + left: 8px; + bottom: 10px; + width: 0; + height: 0; + border-style: solid; + border-width: 5px 0 5px 8.7px; + border-color: transparent transparent transparent #000000; +} + +.ngHeaderButton { + position: absolute; + right: 2px; + top: 8px; + -moz-border-radius: 50%; + -webkit-border-radius: 50%; + border-radius: 50%; + width: 14px; + height: 14px; + z-index: 5; + background-color: rgb(179, 191, 188); + cursor: pointer; + /* width and height can be anything, as long as they're equal */ +} +.ngHeaderButtonArrow { + position: absolute; + top: 4px; + left: 3px; + width: 0; + height: 0; + border-style: solid; + border-width: 6.5px 4.5px 0 4.5px; + border-color: #000 transparent transparent transparent; + /* width and height can be anything, as long as they're equal */ +} +.ngColMenu { + right: 2px; + padding: 5px; + top: 25px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + background-color: #BDD0CB; + position: absolute; + border: 2px solid rgb(212,212,212); + z-index: 5; +} +.ngMenuText { + position: relative; + top: 2px; + left: 2px; +} +.ngColList { + list-style-type: none; +} + +.ngColListItem { + position: relative; + right: 17px; + top: 2px; + white-space:nowrap; +} +.ngColListCheckbox { + position: relative; + right: 3px; + top: 4px; +} + +/********Paging Styles **********/ + +.ngPagerButton{ + height: 25px; + min-width: 26px; +} + +.ngPagerFirstTriangle{ + width: 0; + height: 0; + border-style: solid; + border-width: 5px 8.7px 5px 0; + border-color: transparent #000000 transparent transparent; + margin-left: 2px; +} + +.ngPagerFirstBar{ + width: 10px; + border-left: 2px solid black; + margin-top: -6px; + height: 12px; + margin-left: -3px; +} + +.ngPagerLastTriangle{ + width: 0; + height: 0; + border-style: solid; + border-width: 5px 0 5px 8.7px; + border-color: transparent transparent transparent #000000; + margin-left: -1px; +} + +.ngPagerLastBar{ + width: 10px; + border-left: 2px solid black; + margin-top: -6px; + height: 12px; + margin-left: 1px; +} + +.ngPagerPrevTriangle{ + margin-left: 0; +} + +.ngPagerNextTriangle{ + margin-left: 1px; +} +.ngGroupIcon { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAYAAACZ3F9/AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAEFJREFUKFNjoAhISkr+h2J5JDZODNXGwGBsbPwfhIGAA8bGh6HaGBiAGhxAGJmND4M1gQCSM0adCsVQbcPcqQwMALWDGyDvWPefAAAAAElFTkSuQmCC); + background-repeat:no-repeat; + height: 15px; + width: 15px; + position: absolute; + right: -2px; + top: 2px; +} + +.ngGroupedByIcon { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAANCAYAAACZ3F9/AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAElJREFUKFNjoAhISkr+R8LyaHwMDNXGwGBsbPwfhoGAA5mPDUO1oWpE52PDYE0gALTFAYbR+dgwWBMIoPlh1I9ADNU2NPzIwAAAFQYI9E4OLvEAAAAASUVORK5CYII=); + background-repeat:no-repeat; + height: 15px; + width: 15px; + position: absolute; + right: -2px; + top: 2px; +} + +.ngGroupingNumber { + position: absolute; + right: -10px; + top: -2px; +} diff --git a/src/main/webapp/login.jspx b/src/main/webapp/login.jspx index 84e24fc..b04099d 100644 --- a/src/main/webapp/login.jspx +++ b/src/main/webapp/login.jspx @@ -1,5 +1,5 @@ -<html xmlns:common="urn:jsptagdir:/WEB-INF/tags/common" - xmlns:jsp="http://java.sun.com/JSP/Page" +<html jsp:version="2.0" xmlns:jsp="http://java.sun.com/JSP/Page" + xmlns:common="urn:jsptagdir:/WEB-INF/tags/common" xmlns:c="http://java.sun.com/jsp/jstl/core"> <jsp:output doctype-root-element="HTML" doctype-system="about:legacy-compat"/> <jsp:directive.page contentType="text/html;charset=UTF-8"/> diff --git a/src/main/webapp/person/person.jspx b/src/main/webapp/person/person.jspx new file mode 100644 index 0000000..1dea34e --- /dev/null +++ b/src/main/webapp/person/person.jspx @@ -0,0 +1,22 @@ +<html xmlns:jsp="http://java.sun.com/JSP/Page" jsp:version="2.0" + xmlns:common="urn:jsptagdir:/WEB-INF/tags/common" + xmlns:c="http://java.sun.com/jstl/core" + ng-app="personApp"> +<jsp:output doctype-root-element="HTML" doctype-system="about:legacy-compat"/> +<jsp:directive.page contentType="text/html;charset=UTF-8"/> + +<common:head-element> + <script>var uuid = '${param.uuid}';</script> + <common:headjs label="PersonService" resource="/apps/core/PersonService.js"/> + <common:headjs label="personApp" resource="/apps/personApp/personApp.js"/> +</common:head-element> + +<body> + +<div id="content" ng-view=""><!-- --></div> + +<common:footer/> + +</body> + +</html> |