Today we're please to introduce one of our Aurelia community members, Giray Temel, to share with you about Codeline's choice of and experience with Aurelia. Take it away Giray!

What is Codeline? Codeline is an on demand software development platform. It offers an alternative to those who seek development services. Instead of turning to freelancing sites, posting jobs, and going through applications, you can bring your project to Codeline and we'll get experienced developers started on your tasks in no time. Codeline is an enterprise grade software product that contains real-time project management, timers and budgeting tools, finance, invoicing and payroll modules, an integrated payment system, a hiring pipeline, and more. We have a PHP / Laravel backend and a Socket.io setup for real-time functionality. Our entire front-end is written in Aurelia.

Choosing Aurelia To build Codeline, we needed to choose a modular, modern client-side framework that lends itself to robust single-page application development. Codeline is a data intensive, real-time software application. Therefore responsiveness, managing data flow, and handling complex routing were key concerns for us. Being already familiar with Aurelia from a previous client project, we knew the framework's strengths, extensibility, and ease with which it can be customized. We initially went for a Typescript + SystemJS setup. After the framework matured and the Aurelia CLI was released, we switched to that. Our build pipeline is now very simple and highly functional.

Less Constraints, More Customization Most frameworks require you to write code in a certain format. Vue has .vue files, React has JSX. On the other hand, in the Aurelia framework the ViewController is just a simple class. You don't need to extend a parent component class. There are no contrived data() or methods() methods. Your logic and your view live in two different files for each module. If there's no particular logic, you can even leave out the ViewController and just write the template. In our case, this flexibility allowed for a comprehensible architecture, enabled better separation of concerns, and resulted in a cleaner codebase.

Models and Role Based Authorization Every client app needs to talk to the server and Codeline is no exception. These exchanges must be authenticated and authorized based on the active user. In our case, the business rules around what actions can be taken on which models are very complex. Clients can create new tasks, but they can't run timers on them. A developer cannot delete a task, only a project manager is able to do that. When you consider the size of Codeline and the dozens of models we have, it's obvious that we needed a better solution than if else'ing all over the codebase. Plus, we had no intention of maintaining two copies of the same logic both on the server and the client-side. Our client-side strategy for authentication and authorization with Aurelia framework consists of these components: 1. Revive HTTP responses as Models A model is nothing but a simple class. We simply take the response from the backend and instantiate the relevant Model class with this data. One obvious advantage of this is being able to enrich the plain data with additional methods and getters. For instance, we have a Notification model and notifications come in levels. 1 is an info, 2 is a warning and 3 is an alert. In the UI, we display corresponding icons for each level. So we added a simple transformer to our Notification model that returns the CSS class of the icon dynamically. @ transient ( ) export class Notification extends Model { ... level : number ; private levelIcons : any = { 1 : "info-circle" , 2 : "warning" , 3 : "alert-circle" , } ; get levelIcon ( ) { return this . levelIcons [ this . level ] ; } ... } This way, we can simply do this in any of our views: < i class = " ${notification.levelIcon} " > </ i > ${notification.text} 2. Can Method on the Base Model The more interesting upside is that our base model (the parent class for all models) presents a can(action: string): boolean method. Using this, we can check if an action is authorized on the given model anywhere in the codebase. To pull this off, we include an actions array in the data returned by the server. It looks something like this: ['start', 'edit', 'delete'] Then the can(action) method simply checks if this array contains the given string. In our views, it looks something like this: < timer-start-button if.bind = " timer.can( ' start ' ) " > </ timer-start-button > 3. User Model and Role Checks The authenticated user's data is also revived as a User model and this instance exposes a hasRole method, much similar to the can method we mentioned earlier. We also added shortcut methods that just call hasRole for us with specific combinations, such as isEmployee and isClient .

Customizing Aurelia Codeline has unique needs when it comes to routing. The layout of the app is quite versatile and dynamic. Thankfully, Aurelia's powerful router went above and beyond our expectations when it came to customization. First, let's take a look at Codeline's full layout: _______________ Top Bar ________________ _______________ Nav Bar ________________ | Left Menu | Main View | Right Pane | ________________________________________ Here's where it gets tricky: We want the left menu to be displayed only for certain routes. We couldn't include the left menu with each view as this would cause unnecessary loads or jumps in scroll position. It had to stay in place and must be toggled based on the active route.

The right pane, which we call the "viewbar", is also only displayed with certain routes. It's basically a little view that slides in from the right and is typically used for quickly creating or updating records. We already had a jQuery plugin for this but we needed to keep it open when the page is refreshed, or hide it dynamically when the user navigates away to another page.

Routes need to be authorized. We don't want our clients to access the administrative routes. With all of these custom requirements in mind, we devised the following interface: export interface RouteConfig { name : string ; title ? : string ; icon ? : string ; nav ? : boolean ; viewbar ? : string ; hasLeftMenu ? : boolean ; roles ? : Array < string > ; login ? : boolean ; } Route Authorization When adding routes, we make sure that the settings property of the route complies with the above interface. To authorize the routes, we created a pipeline step called AuthorizeRoutes and added it to our configureRouter in app.ts like this: import { AuthorizeRoutes } from "./core/auth/auth-middleware" ; ... class App { configureRouter ( ) { config . addPipelineStep ( "authorize" , AuthorizeRoutes ) ; } } And auth-middleware.ts looks like this: import { inject } from "aurelia-dependency-injection" ; import { NavigationInstruction , Redirect } from "aurelia-router" ; import { Auth } from "./auth" ; @ inject ( Auth ) export class AuthorizeRoutes { constructor ( private auth : Auth ) { } run ( navigationInstruction : NavigationInstruction , next : any ) { const allInstructions = navigationInstruction . getAllInstructions ( ) ; if ( allInstructions . some ( i => this . requiresAuthMode ( i . config . settings . login ) ) ) { if ( ! this . auth . check ( ) ) { } let roles = navigationInstruction . config . settings . roles ; if ( roles . length && ! this . auth . user ( ) . hasRole ( roles . join ( '|' ) ) ) { } } if ( allInstructions . some ( i => this . requiresGuestMode ( i . config . settings . login ) ) ) { } return next ( ) ; } private requiresAuthMode ( login ) { return login === true ; } private requiresGuestMode ( login ) { return login === false ; } } Toggling the Left Menu Based on Active Route In our app.html we check if the left menu should be visible based on the current route: < div class = " page scrollable " id = " mainPageContainer " > < div class = " page-aside " if.bind = " router.currentInstruction.config.hasLeftMenu " > < compose view-model = " modules/left-menu/left-menu " > </ compose > </ div > < div class = " page-main " > < div class = " page-content " > < router-view swap-order = " after " > </ router-view > </ div > </ div > </ div > Using Aurelia's Enhance to Integrate jQuery With Aurelia Our right pane implementation had to work with the existing jQuery plugin as it already had the CSS animations we wanted. One problem with it was we needed to load Aurelia views using the plugin, not just static HTML. Aurelia had a solution ready for this, too. As Aurelia is a modular framework, you are able to leverage the functionality in each module individually to create any custom implementation you can imagine. Our solution works in two steps: Listening to router:navigation:success to capture page changes. If the activated route has the viewbar setting defined, trigger the jQuery plugin and load the relevant module inside the right pane. The first part is done through the event aggregator package that ships with Aurelia. The router package will emit useful events that let you hook into the router lifecycle. Here is a very useful article that lists all of these events. this . eventAggregator . subscribe ( 'router:navigation:success' , this . onNavigationSuccess ) The second step is to get the router instruction from the event and handle it: onNavigationSuccess ( $event ) { const { viewbar } = $event . instruction . getAllInstructions ( ) . pop ( ) . config . settings $ . slidePanel . show ( { content : '<compose view-model="' + viewbar + '" containerless></compose>' } , { afterLoad : ( ) => this . enhance ( ) } ) ; } enhance ( ) { this . viewbarView = this . templating . enhance ( { element : $ ( ".slidePanel" ) . get ( 0 ) , bindingContext : { ... } , } ) if ( "attached" in this . viewbarView ) { this . viewbarView . attached ( ) } }