Two weeks ago I started a blog post series on how to create a thumbnail browsing module with AngularJS and our REST API. The first part was about getting familiar with the API. The second part was how to use it in AngularJS. The example was simple. A user goes to a URL with a tag and we display the search result with infinite scroll.
This third part focuses on getting the thumbnail with Busines Object adapters, making it look better, and deploying the project easily on Nuxeo.
Using the Business Object
In the first post we saw how to use a business object adapter with the new API. It’s really easy to plug into our app. All we have to do is tell the controller:
.controller("SlideshowCtrl", ['$scope','$routeParams','nxSearch' ($scope,$routeParams,nxSearch) -> $scope.searchTag = $routeParams.tag nxSearch.setQuery("SELECT * FROM Document WHERE ecm:tag = '" + $scope.searchTag + "'") # set the BOAdapter using its name nxSearch.setBOAdapter("SlideShowElement") $scope.items = nxSearch.items # Expose the busy variable $scope.busy = nxSearch.busy # Expose the nextPage method $scope.nextPage = ()-> nxSearch.nextPage() ])
This works because the nxSearch service was made to support this capability. All we have to do is add a bo object, a setter and one line of code in our nextPage method: if nxSearch.bo? then url += "/@bo/" + nxSearch.bo
angular.module('nuxeoAngularSampleApp') .factory "nxSearch", ["$http","$q","nxUrl",($http,$q,nxUrl)-> nxSearch = {} nxSearch.items = [] nxSearch.busy = false nxSearch.isNextPageAvailable = true nxSearch.currentPageIndex = 0 nxSearch.pageSize = 20 nxSearch.query = undefined nxSearch.bo = undefined nxSearch.setBOAdapter = (bo)-> nxSearch.bo = bo nxSearch.setPageSize = (pageSize)-> nxSearch.pageSize = pageSize nxSearch.setQuery = (query)-> nxSearch.query = query nxSearch.nextPage = ()-> if !nxSearch.query? $q.reject("You need to set a query") return if !nxSearch.isNextPageAvailable return if nxSearch.busy return nxSearch.busy = true url = nxUrl + "/path/@search" # if the bo object exist, add the @bo adapter to the URL if nxSearch.bo? then url += "/@bo/" + nxSearch.bo url += "?currentPageIndex="+nxSearch.currentPageIndex+"&pageSize="+nxSearch.pageSize+"&query=" + nxSearch.query; $http.get(url).then (response) -> docs = response.data if(angular.isArray(docs.entries)) nxSearch.currentPageIndex = docs.currentPageIndex + 1 nxSearch.isNextPageAvailable = docs.isNextPageAvailable nxSearch.items.push doc for doc in docs.entries nxSearch.busy = false else nxSearch.busy = false $q.reject("just because") nxSearch ]
And of course now we need to update our slideshow.html template to reflect these changes. I’ve added a div tag containing an img tag that shows our thumbnail. Note that we have to use item.value instead of simply item because we are manipulating business objects instead of simple documents. We might try to be more consistent in the next version of the API. Let me remind you that it’s still a beta version, so some things might still change a bit.
<h2>{{searchTag}}</h2> <ul infinite-scroll='nextPage()' infinite-scroll-disabled='busy' infinite-scroll-distance='1'> <li ng-repeat='item in items'> <span>{{item.value.title}}</span> <div><img src="{{item.value.thumbnailURL}}"></div> </li> </ul> <div ng-show='busy'>Loading data...</div> </div>
Now you should see something like this:
Beautifying the Thumbs
It looks a little raw, so we can use semantic-ui to make it nicer – it’s a little more WYSIWYM than bootstrap. Check out a comparison of the two on their home page. To install semantic-ui, we use bower and simply type:
bower install semantic-ui --save
The module is installed, but we still need to add the css stylesheet to our index.html page. We added this to our header:
<link rel="stylesheet" href="components/semantic-ui/build/packaged/css/semantic.css">
Now we can start using it on slideshow.html and immediately see the results:
<h2>{{searchTag}}</h2> <div class="main ui three items" infinite-scroll="nextPage()" infinite-scroll-disabled="busy" infinite-scroll-distance="1"> <div class="item" ng-repeat="item in items"> <div class="sides"> <div class="active side"> <div class="image"> <img ng-src="{{item.value.mediumURL}}" width="100%"> </div> <div class="content"> <div class="name">{{item.value.title}}</div> <p class="description">{{item.value.description}}</p> </div> </div> </div> </div> <div ng-show='busy'>Loading data...</div> </div>
But now we have bootstrap, which was already in the sample we started working on, and semantic-ui. So let’s clean this up. We can also remove all the parts we don’t use like the nxNavigation and nxSession services, or the controllers and pages we don’t use. We already did this work so you can check out the sources of the cleaned and package version on Damien’s GitHub repository.
About Packaging
Our goal is to deploy the angular project into nuxeo.war. To go further, we want to package the AngularJS app in a jar to deploy it in Nuxeo. The tool responsible for packaging the jar is Maven. We need to tell Maven to embed the app. To do so, we use the maven antrun plugin like this:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <version>1.7</version> <executions> <execution> <id>grunt-build</id> <phase>process-resources</phase> <configuration> <target> <ant dir="${basedir}/src/main/yo" antfile="${basedir}/src/main/yo/build.xml" /> </target> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin>
This configuration executes the ant using the ${basedir}/src/main/yo/build.xml ant build file. The build file actually calls Grunt to package the app. Grunt is a JavaScript task runner. As you can see in this file, ant executes the grunt build command.
<?xml version="1.0"?> <project name="Grunt build" default="build" basedir="."> <target name="init" description="Download all node and web dependencies"> <exec executable="npm"> <arg value="install" /> </exec> <exec executable="bower"> <arg value="install" /> </exec> </target> <target name="build" depends="init,build-with-tests,build-skip-tests" /> <target name="build-with-tests" unless="skipTests"> <echo message="build and test" /> <exec executable="grunt"> <arg value="build" /> </exec> <mkdir dir="../../../target/surefire-reports" /> <copy file="test-results.xml" todir="../../../target/surefire-reports" failonerror="false" /> </target> <target name="build-skip-tests" if="skipTests"> <exec executable="grunt"> <arg value="build-skip-tests" /> </exec> </target> </project>
We still need to do one thing, which is to tell Grunt where to build our app. This can be done easily by adding the following code to our GruntFile:
// configurable paths var yeomanConfig = { app: 'app', dist: '../../../target/classes/web/nuxeo.war/slideshow' };
Now when we run mvn clean install
, we get a jar file (target/nuxeo-slideshow-sample*.jar) containing everything we need. Take a look at the content of the jar:
. ├── META-INF │ ├── MANIFEST.MF │ └── maven │ └── org.nuxeo.ecm.angular.sample │ └── nuxeo-slideshow-sample │ ├── pom.properties │ └── pom.xml ├── OSGI-INF │ ├── authentication-contrib.xml │ └── deployment-fragment.xml ├── extensions │ └── org.nuxeo.slideshow.SlideShowElement.xml ├── nuxeo-slideshow-sample-1.0-SNAPSHOT.jar ├── org │ └── nuxeo │ └── slideshow │ ├── SlideShowElement.class │ └── SlideShowElementFactory.class └── web └── nuxeo.war └── slideshow ├── 404.html ├── favicon.ico ├── fonts │ ├── glyphiconshalflings-regular.eot │ ├── glyphiconshalflings-regular.otf │ ├── glyphiconshalflings-regular.svg │ ├── glyphiconshalflings-regular.ttf │ └── glyphiconshalflings-regular.woff ├── index.html ├── robots.txt ├── scripts │ ├── 22d0f6c5.scripts.js │ └── 4c97588d.components.js ├── styles │ ├── d2f2e407.main.css │ └── e1cdc6e0.components.css └── views └── slideshow.html
There are two XML files I have not talked about yet. Let’s start with deployment-fragment.xml. It does two things. First, it adds the URL pattern /slideshow/* to the NuxeoAuthenticationFilter. This is to make sure authentication is required when going to URLs like /slideshow/*. Next, unzip the app in nuxeo.war.
<?xml version="1.0"?> <fragment version="1"> <extension target="web#STD-AUTH-FILTER"> <filter-mapping> <filter-name>NuxeoAuthenticationFilter</filter-name> <url-pattern>/slideshow/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping> </extension> <install> <!-- Unzip the contents of our nuxeo.war into the server --> <unzip from="${bundle.fileName}" to="/" prefix="web"> <include>web/nuxeo.war/**</include> </unzip> </install> </fragment>
Now about authentication-contrib.xml. It defines slideshow as a startURLPattern. A start URL pattern is used the first time you browse the application. If you hit a URL for which a pattern is registered, you’ll see the login screen asking you to sign in. And if you do so, you’re redirected to the URL you’ve hit in the first place. If the URL you try to hit does not match any startURLPattern, you are still redirected to the login page, but once you sign in, you’re redirected to the default webapp.
<component name="org.nuxeo.angular.sample.authentication.contrib"> <extension target="org.nuxeo.ecm.platform.ui.web.auth.service.PluggableAuthenticationService" point="startURL"> <startURLPattern> <patterns> <pattern>slideshow/</pattern> </patterns> </startURLPattern> </extension> </component>
And now we’re ready to go. We can build the jar and copy it in our bundles directory. Then we go to http://localhost:8080/nuxeo/slideshow/ and try our app deployed in Nuxeo.
The post Creating a Thumbnail Browsing Module with AngularJS and the REST API – Part 3 appeared first on Nuxeo Blogs.