Skip navigation
All People > ddraper > Dave Draper's Blog > 2017 > January
2017

Deep Nesting React Components

Posted by ddraper Jan 24, 2017
This is a personal blog post that is primarily intended for tracking my own learning rather than provided to the Alfresco Community for educational purposes. However if you find it useful, informative or have any comments on it then please comment below.

Introduction

In a previous blog post I described one of the major challenges involved in providing re-usable components, where those components could be deeply nested (without the need to update the dependencies listed for their parents). The post also described a way that you could combine Surf, Aikau and Vue.js to solve the problem. I've since spent a couple of days experimenting with React and have discovered that it is also well equipped to solve this problem.

 

The solution is to make use of the "composition" capabilities provided by React. I'd already built a small prototype client for testing some of the new V1 REST APIs and realised that most of the components that I had created should be highly re-usable.

 

Starting Point

The prototype client called the "people" REST APIs to allow you to display the users registered on the Alfresco Repository. One of the best things about the V1 REST APIs is that the response schema is consistent across all the APIs and this means that the same Collection component can be used to retrieve data from any of the URLs that return multiple items (users, nodes, comments, favourite sites, etc). 

 

I've got into the habit of building UIs using the presentation and container components pattern described in this blog post (and not just for React, but for any UI framework). So whilst my outer Collection container component could easily be configured to use a different URL, the presentation components that it used needed to be swapped out according to the data being retrieved (i.e. the user view I had created would be no use for rendering node data).

 

My initial implementation (which you can test by checking out this tag) had the main Collection component (which was called List at the time) explicitly declared dependencies on the sub-components it wanted to use and then referenced them in the render function, like this:

 

List Component:

render() {
   return (
      <div ref="list" >
         <Toolbar list={this.state.list}
                  pageBackHandler={this.pageBack}
                  pageForwardHandler={this.pageForward}></Toolbar>
         <Filter />
         <ListView list={this.state.list}
                   navigationHandler={this.navigate}
                   orderBy={this.state.orderBy}
                   orderDirection={this.state.orderDirection}></ListView>
      </div>)
}

 

Single Level Nesting

The first step was to change the allow sub-components to be provided by the "children" props:

 

Collection Component (renamed from List):

render() {
   const childrenWithProps = React.Children.map(this.props.children, (child) => React.cloneElement(child, {
      list: this.state.list,
      orderBy: this.state.orderBy,
      orderDirection: this.state.orderDirection,
      relativePath: this.state.relativePath
   }));
   return (
      <div ref="list" >
         {childrenWithProps}
      </div>)
}

 

Note that it was necessary to pass any relevant state properties into any children provided (which I discovered via this Stack Overflow question). This then made it possible to use my Collection component to build my user list display like this:

 

Home Component:

<Collection skipCount={0}
            maxItems={10}
            orderBy="firstName"
            orderDirection="DESC">

   <CreateUserButton/>
   <Filter />
   <UserTableView />
</Collection>

 

Notice that this enabled me to configure relative sorting and pagination properties (mapped directly to the REST API parameters) as attributes of Collection. In this example the CreateUserButton, Filter and UserTableView are custom components passed as children to the Collection component.

 

Deep Nesting

The majority of the UserTableView component was also ripe for reuse so I further abstracted that to a TableView component that would accept child components as properties for the header, body and footer.

 

Because this component had 3 different children properties it is necessary to use a slightly more complex syntax but the Collection component could then nest an abstract TableView with nested TableHeading, TableCell and Pagination sub-components configured to display metadata relevant to the data being displayed. Therefore a node list display can be defined like this:

 

Home Component:

<Collection url="/api/-default-/public/alfresco/versions/1/nodes/-root-/children" 
            orderBy="name">

   <BreadcrumbTrail/>
   <TableView
      headerChildren={
         [<TableHeading label="Name"
                        orderById="name" />
,
          <TableHeading label="Created By"
                        orderById="createdByUser.displayName" />
]
      }
      bodyChildren={
         [<TableCell property="name" navigation={true}/>,
          <TableCell property="createdByUser.displayName" />]
      }
      footerChildren={
         <Pagination colspan="2"/>
      }
   >
   </TableView>
</Collection>

 

Notice that the CreateUserButton and Filter sub-components have been swapped out for a BreadcrumbTrail with no issues and that the url for retrieving the data has been configured for accessing nodes for the Repository root location.

 

Further Refinements

Although the multiple children property approach was working I didn't like the way the syntax looked. So I decided to further abstract the TableView to create separate components for TableViewHead, TableViewBody and TableViewFoot.

 

Making use of these gives a much cleaner, standard HTML appearance:

 

Home Component:

<Collection url="/api/-default-/public/alfresco/versions/1/nodes/-root-/children" 
            orderBy="name">

  
   <BreadcrumbTrail/>
  
   <TableView>

      <TableViewHead>
         <TableHeading label="Name" orderById="name" />
         <TableHeading label="Created By" orderById="createdByUser.displayName" />
      </TableViewHead>

      <TableViewBody>
         <TableCell property="name" navigation={true} />
         <TableCell property="createdByUser.displayName" />
      </TableViewBody>
     
      <TableViewFoot>
         <Pagination colspan="2"/>
      </TableViewFoot>

   </TableView>
</Collection>

 

Try It Out

To test this out you can check out this tag from this GitHub repository then build and start the application by running:

npm install
npm start

...and open a browser at http://localhost:3000/home (make sure that you have a local Alfresco Repository running that provides the V1 REST APIs such as a recent 5.2 Community release).

 

This is a screenshot of what you should see:

 

Summary

I think that this is an excellent example of how the consistent response schema in the V1 REST APIs is going to significantly improve custom user interface development for Alfresco. It's also an interesting exploration of one of the capabilities that React is able to offer that none of the other UI libraries/frameworks I've experimented with has been able to provide out-of-the-box.

 

In my opinion I think it would be very fast to build out applications using this approach but I'd be interested to know other peoples thoughts on it!

Re-visiting Vue.js

Posted by ddraper Jan 12, 2017
This is a personal blog post that is primarily intended for tracking my own learning rather than provided to the Alfresco Community for educational purposes. However if you find it useful, informative or have any comments on it then please comment below.

I've now experimented with creating the same simple Alfresco client in Vue.js, React, Aurelia and Angular. When I created the first client in Vue.js I built up the project from scratch rather than using the CLI in order that I could better understand Node.js, Express, webpack, etc. This resulted in me creating an NPM package to configure Node.js middleware to handle authentication against an Alfresco Repository. When using the various CLIs for the other frameworks I found that they all were aimed at creating a Single Page Application and I ended up creating proxy configuration to re-route REST API requests to Alfresco. As this approach is also available in Vue I wanted to explore this option as I begin to move all the implementation forwards. 

 

Setting up the proxy was very simply a case of adding a "proxyTable" entry to the index.js file:

proxyTable: {
   '/proxy/alfresco': {
      target: 'http://localhost:8080/alfresco',
      changeOrigin: true,
      pathRewrite: {
         '^/proxy/alfresco': ''
      }
   }
},

 

...but getting the touter configured was a little more tricky because changes between versions 1 and 2 of Vue.js meant that some of the blog posts are out of date. However, following the official documentation ultimately proved most useful. 


Defining the basic application routing structure was quite straightforward as well... with a main HTML page containing:

<div id="app">
   <router-view></router-view>
</div>

...it was possible to setup up the main.js file to bind the a router with a Login and Home components as follows...

const routes = [
   {
      path: '/',
      redirect: '/home'
   },
   {
      path: '/home',
      component: Home,
      beforeEnter: (to, from, next) => {
         if (auth.loggedIn()) {
            next();
         } else {
            next('/login');
         }
      }
   },
   { path: '/login', component: Login }
];

export const router = new VueRouter({
   routes: routes
});

new Vue({
   router
}).$mount('#app');

 

The "/home" route is protected with a "beforeEnter" guard that uses my "alfresco-js-utils" NPM package for ensuring that the current user is logged in.

 

Having completed the authentication handling it was just a case of porting my previously created components to the new project (you can view the code at the time of writing by checking out this tag).

 

I was interested to see how I would feel about coming back to Vue.js having subsequently looked at React, Aurelia and Angular and in my opinion its the most satisfying to code in. There are still frustrating elements but the documentation and tooling is excellent and the CLI gives you an excellent starting point for development. 

Vanilla Angular 2 Client

Posted by ddraper Jan 12, 2017
This is a personal blog post that is primarily intended for tracking my own learning rather than provided to the Alfresco Community for educational purposes. However if you find it useful, informative or have any comments on it then please comment below.

Having previously created my basic Alfresco Repository browsing application in Vue.js, React and Aurelia I'm on something of a roll. The next framework I wanted to look at was Angular 2 (except that you can't call it that anymore, it's just Angular). Initially I'm going to look at vanilla Angular 2 before reviewing Alfresco's own ADF.

 

Once again there is a CLI that aims to get you up and running as quickly as possible, and as with React and Aurelia it's geared around Single Page Application creation so there was a requirement to handle authentication against the Alfresco Repository. 

 

The authentication strategy is the same (a familiar theme in reviewing all these frameworks) where a router is used to ensure that only authenticated users gain access to Alfresco content and the approach is well described in this blog post.

 

I had a few issues initially working through the instructions but most (such as this issue) were solved by a quick Google. The error messages seem good, but you still have to know what they mean... for example I hadn't declared my "HomeComponent" and "LoginComponent" in my declarations array - the error message made a lot more sense after I'd resolved the problem!

 

Creating the authentication router was quite straightforward (from app.routes.ts):

export const routes = [
  { path: 'login', component: LoginComponent },
  { path: '', component: HomeComponent, pathMatch: 'full', canActivate: [LoggedInGuard] }
];

 

...and the UserService approach (from the previously mentioned blog post) was easily adapted to make use of the Authentication utility functions from my alfresco-js-utils NPM package (despite me needing to use "require" rather than "import" for some reason I couldn't resolve).

 

It was definitely harder to get the template for the login component working (although I'm sure there are alternative ways in which it could have been done)...

@Component({
  selector: 'login',
  template: `<form (ngSubmit)="onSubmit()">
      <label><input name="username" [(ngModel)]="username" placeholder="username" /></label>
      <label><input name="password" [(ngModel)]="password" placeholder="password" /></label><br/>
      <button type="submit">login</button>
   </form>`
})

 

Compare with the template in Aurelia: 

<template>
   <form submit.trigger="handleSubmit()">
      <label><input ref="username" placeholder="username" /></label>
      <label><input ref="pass" placeholder="password" /></label><br/>
      <button type="submit">login</button>
   </form>
</template>

 

It wasn't intuitive to have to use "ngSubmit" and "ngModel" but again this is probably something that you get used to over time.

 

It was necessary once again to configure proxies for the server to redirect REST API requests to the Alfresco Repository. This was done following the instructions in this video to create a proxy.config.json file and to update the npm script for "start" to include " --proxy-config proxy.config.json".

 

With authentication handled it was time to start building out the same set of components as before.... this was when things began to get frustrating. Due to a combination of TypeScript and the opinionated coding style enforced by Angular I became quickly bogged down in problems that were not fast to resolve. I wasn't able to easily reuse the NodeService from alfresco-js-utils and instead had to follow the patterns described in the documentation.


I also had issues with getting changes to my bound data to trigger a reactive rendering of the list (something that I'd had no problems with in the other frameworks) and on the whole I found the development process frustrating and restrictive. Passing data and events were more usefully described in this post than in the official documentation.

 

Once I'd implemented the List and ListView components it was much more straightforward to create the Toolbar and Breadcrumb. Working in Angular does feel restrictive but I can see how those restrictions might ultimately result in more robust code. Although there is lots of documentation and blog posts it can be quite difficult to find the right answer to problems as there were so many changes through the beta and release candidates phase of Angular development. 

 

Some things I found confusing (but I'm sure there will be a simple explanation for) such as why all my component dependencies needed to be in the app.module.ts "declarations" array and couldn't be declared in the appropriate location (so ListView, Breadcrumb and Toolbar should just be dependencies of List).

 

One advantage I found in Aurelia over Angular was in change detection of bound properties. In Angular you have to implement the "ngOnChanges" life-cycle function and figure out for yourself if the property you're interested in has changed, but in Aurelia you can just create a function with "Changed" appended to the property, for example compare...

 

Angular:

ngOnChanges(changes: any) {
   let newPath = changes["relativePath"];
   if (newPath)
   {
      let breadcrumbData = BreadcrumbUtil.createBreadcrumbs({
         relativePath: newPath.currentValue
      });
      this.breadcrumbs = breadcrumbData.breadcrumbs;
   }
}

 

...with Aurelia:

relativePathChanged(newPath, oldPath) {
   let breadcrumbData = BreadcrumbUtil.createBreadcrumbs({
      relativePath: newPath
   });
   this.breadcrumbs = breadcrumbData.breadcrumbs;
}

 

You can review the state of this project at the time of writing by checking out this tag from this GitHub repository.

First Steps with Aurelia

Posted by ddraper Jan 11, 2017
This is a personal blog post that is primarily intended for tracking my own learning rather than provided to the Alfresco Community for educational purposes. However if you find it useful, informative or have any comments on it then please comment below.

I've previously created a simple Alfresco Repository browser using both Vue.js and React and now wanted to explore using Aurelia. Again a CLI is provided and the documentation initially seems to be very good.

 

I wanted to follow the same implementation of components as before but the first hurdle was to configure authentication in order to be able to access the V1 REST APIs provided by the Alfresco Repository.

 

My first problem was accessing the additional modules that I had installed - namely "axios" (for XHR requests) and my "alfresco-js-utils" module. The issue was that my standard ES6 import was not working... I was getting errors with the following import code:

 

import auth from "alfresco-js-utils/lib/Authentication";

 

It turns out that the issue is that it is necessary to configure the aurelia.json file to manage the dependencies.  Once I'd added the following:

{
   "name": "alfresco-js-utils",
   "path": "../node_modules/alfresco-js-utils",
   "main": "index"
}

 

...then the import statement worked correctly. I'm sure there is a good reason for this, but I don't fully understand it yet. 

 

The next setup was to configure the proxy for handling REST API calls. This was done (following these instructions) by inserting the following code into the run.js file:

var proxyMiddleware = require("http-proxy-middleware");
var proxy = proxyMiddleware("/service", {
    target: "http://localhost:8080/alfresco",
    changeOrigin: true // for vhosted sites, changes host header to match to target's host
});

 

The next stage was to setup an router to ensure that users were logged in before they could access the Alfresco Repository data.

 

It was simple enough to setup routing in the main app.js component:

import {Redirect} from "aurelia-router";

class AuthorizeStep {
   run(navigationInstruction, next) {
      if (navigationInstruction.getAllInstructions().some(i => i.config.settings.roles.indexOf("authenticated") !== -1)) {
         var isLoggedIn = !!localStorage.ticket;
         if (!isLoggedIn) {
           return next.cancel(new Redirect("login"));
         }
      }
      return next();
   }
}

export class App {
   configureRouter(config, router) {
      config.title = "Aurelia Test";
      config.addPipelineStep("authorize", AuthorizeStep);
      config.map([
         { route: ["login"], moduleId: "components/login", title: "Login", settings: { roles: [] } },
         { route: ["", "home"], moduleId: "components/home", title: "Home", settings: { roles: ["authenticated"] } }
      ]);
   }
}

 

...however I did struggle in how to handle the login operation. The login component used the Authentication export from alfresco-js-utils. The solution was to ensure that the router was injected and that I used "navigate" rather than "navigateToRoute" (as described here).

import { inject } from "aurelia-framework";
import { Router } from "aurelia-router";
import auth from "alfresco-js-utils/lib/Authentication";

@inject(Router)
export class Login {

   constructor(router) {
      this.router = router;
   }

   handleSubmit() {
      const username = this.username.value;
      const pass = this.pass.value;

      auth.login(username, pass).then((loggedIn) => {
         if (!loggedIn)
         {
           
         }
         else
         {
            this.router.navigate("home");
         }
      });
   }
}

 

Having handled authentication it was just a case of building out the components as before and at this stage I have to admit that I found Aurelia less intuitive than both Vue.js and React. Some aspects are component development are very good like the simple association between similarly named JavaScript and HTML files (so List.js and List.html are implicitly part of the same component) which means that no additional syntax handling is required.

 

The templates in the HTML files are relatively straight-forward (but perhaps not quite so much as in Vue.js)... an example of the ListView component template looking like this:

<template bindable="list">
   <ul>
      <li repeat.for="item of list.entries" click.delegate="navigate(item)">${item.entry.name}</li>
   </ul>
</template>

 

The part that caused confusion was in nesting components and passing data between them. This required a lot of additional annotations that I found weren't so well documented.  The associated ListView.js file looking like this:

import {bindable, customElement, inject} from "aurelia-framework";

@customElement("listview")
@inject(Element)
export class ListView {
  
   constructor(element) {
      this.element = element;
   }

   navigate(item) {
      if (item.entry.isFolder)
      {
         let changeEvent = new CustomEvent("navigate", {
            detail: item,
            bubbles: true
         });
         this.element.dispatchEvent(changeEvent);
      }
   }

   @bindable list;
}

 

Although Aurelia recommends the use of a publication/subscription model via the the Event Aggregator (as described late on in this blog post) I actually opted for a custom event solution (as described in this blog post) as I think this is a better model - and because this was just using native custom events the solution would be able to bubble through multiple components. An alternative option would have been to use the 2-way binding features provided but I don't thing that this is such a good model so have avoided it for the time being.

 

By the time I'd successfully implemented the ListView and Breadcrumb components I found implementing the Toolbar very straightforward. I think that once you get a feel for how to build components Aurelia becomes more straightforward but there is definitely something of a learning curve and although there is a lot of documentation it is not necessarily easy to find exactly what you need.

 

Once again the actual structure of the components was almost identical to the implementation in both Vue.js and React. The main difference being the names of the life-cycle functions, the templating syntax and data binding. The fact that the Aurelia components are just plain ES6 classes is nice (and you have the option to work in TypeScript if you prefer) and the templating syntax is very readable and easy to understand.

 

You can review the state of the client at the time of writing by checking out this tag from this GitHub repository.

This is a personal blog post that is primarily intended for tracking my own learning rather than provided to the Alfresco Community for educational purposes. However if you find it useful, informative or have any comments on it then please comment below.

 

At the end of my last blog post I'd failed to make use of my alfresco-auth-router NPM package with the React "create-react-app" CLI. Instead I'd configured an http-middleware-proxy to route API requests (including those required for authentication) via a local Alfresco Repository.

 

The main difference was the approach to creating the application. I could just as easily as built my React project with a custom Express server that made use of the alfresco-auth-router but I wanted to experiment with the app building tooling provided for each UI framework. You can find the state of the React project at the time of writing in this GitHub repository by checking out this tag.

 

The "create-react-app" CLI is very much geared up to building Single Page Applications (SPAs) to it was necessary to use react-router to ensure that only authenticated users can access the Alfresco data. Again, this approach could have been implemented in Vue if necessary, in fact at some point I may go back and experiment with vue-router to see how it compares.

 

With authentication handled in both Vue and React applications it was now a case of building out my simple application for browsing "Company Home" in the Alfresco Repository. My intention was to implement the same component structure in both frameworks.... essentially a List component that contains Breadrcumb, Toolbar and View sub-components. 

 

As an aside I think that this is one of my main regrets about the way in which I implemented lists in Aikau. I had not foreseen the data down / events up model that is now becoming so popular and opted for a pub/sub model (although this does offer some advantages).

 

A better model is that the list manages the state and all state changing components (including pagination and breadcrumbs) are included within the list such that events can bubble up out to the list. I think that this fits with the model described in this post.

 

Both Vue and React have issues in this area in that... whilst Vue offers the ability to emit custom events these only go from child to immediate parent (and do not bubble) whereas React doesn't offer custom events at all. Instead the pattern for React is to pass callbacks as properties to child components (as described here). This presents an issue with deeper nesting of components - one suggested solution for React is described here (although I think this creates stronger coupling which should be avoided).

 

The other major difference is in how iteration is handled. Vue takes a very similar approach introduced in the original Angular library to provide an custom HTML attribute "v-for" to iterate over elements whereas React makes use of JSX to use a have a JavaScript loop to provide the iteration.

 

Vue.js:

<ul>
   <li class="components-lists-ListView__item"
       v-for="item in list.entries"
       @click="navigate(item, $event)">
{{item.entry.name}}</li>
</ul>

 

React:

render() {
   return (<ul> {this.props.list.entries.map((entry) =>
      <li onClick={() => this.props.navigationHandler(entry)} key={entry.entry.id}>{entry.entry.name}</li>
   )}</ul>);
}

 

I have to admit that I preferred the Vue.js approach for for this particular use-case, however I can see where the power of having full control over the JavaScript in a JSX implementation may be beneficial in other cases.

 

Those code snippets also show the differences in event handling of clicking on an item in the list. Again, the Vue.js approach "feels" cleaner and is easier to read in my opinion. The other major problem with the React solution is the necessity to create a "this" binding to these callback functions in the constructor for the component, i.e:

this.pageBack = this.pageBack.bind(this);
this.pageForward = this.pageForward.bind(this);
this.navigate = this.navigate.bind(this);
this.setRelativePath = this.setRelativePath.bind(this);

 

Other than these differences the end implementations were incredibly similar, although perhaps that is to be expected for such a simple use case. One thing that did immediately jump out was the necessity to do some post processing of node data in order to construct the breadcrumb trail and I plan to elaborate on that in my next post.

 

It might also be interesting to note that I created another NPM package to contain JavaScript code that can be shared between both implementations (and indeed implementations using other frameworks). I intend to keep adding to this as necessary with this research.

This is a personal blog post that is primarily intended for tracking my own learning rather than provided to the Alfresco Community for educational purposes. However if you find it useful, informative or have any comments on it then please comment below.

At the end of my last blog I'd just published my first NPM package that provided some basic Express middleware configuration for Passport.js and Express Router. Having got this working successfully with the Vue.js CLI I decided to see if it could just as easily be applied to a React development environment.

 

Something I frequently read when people are discussing the differences between Angular and React is that the former is a "framework" and the latter is a "library". The suggestion being that Angular comes with everything you need to develop an application whereas React is something that you can plug into any number of different environments. This suggestion is also frequently debunked as there are now well established patterns for building React applications however it was pretty late in providing it's own standard application development template with the "create-react-app" project.

 

I was hoping that this would be just as simple to plug the Alfresco Auth Router into as the Vue CLI but there were a couple of hoops that needed to be jumped through first.

 

The main issue was that all of the server configuration was part of a separate NPM package called "react-scripts" that setup a webpack-dev-server for serving the web application. It is however possible to "eject" from the standard project to get access to the full server configuration, which can be done by running:

npm run eject

 

...you then get access to the various scripts and configuration files for setting up the server (however I later discovered that this actually isn't required).

 

The "scripts/start.js" file provides a "runDevServer" function in which it is possible to add in the required dependencies (after installing them)...

npm install --save-dev passport flash express-session alfresco-auth-router

 

However the problem I was finding was that all my XHR requests were getting routed back to "/index.html" and reloading the page.

 

It seems that the approach recommended for handling API calls in an application built with "create-react-app" is to configure a proxy to handle the requests (interestingly I found this is also the approach taken with the Aurelia as both use http-proxy-middleware).

 

Whereas my Vue.js project had separate pages for login and content the approach taken with "create-react-app" is around Single Page Applications (SPAs). This meant that when authentication failed I was always going back to the same page - and authentication was failing when I was trying to login!


With an SPA for React the solution seemed to be to use react-router to set up path based routes. An example React authentication flow is provided here. In this scenario it would seem that my "alfresco-auth-router" solution is not applicable. Instead the approach to use is to configure a proxy to your Alfresco Repository in the "package.json" file and manage the session token and requests within the application itself.

 

Working with JSX meant it was necessary to configure Sublime Text with additional syntax handling. The recommended package appears to be "babel-sublime".

 

I was able to get back REST API calls working and rendering the contents of company home.... the next stage is to break this into re-usable components that can be used across multiple UI frameworks.