As I have mentioned before, much of my application consists of custom elements. This is one of the major tools that Aurelia provides for managing complexity. However, there is a trade-off, and it comes from the asynchronous nature of the framework. Once I move some of my UI and code into a custom element, the relationship between the parent and the child becomes asynchronous, managed through the data binding process. This means that the custom element does not have access to the data in the parent in its constructor, but only once the attached() method is called.
I have a couple of custom elements with their own validation, and in some cases, I want the message for the validation to be dependent on properties that are data bound. If I set the validation up in the constructor, I won't get the correct message.
So I set it up in attached(). It's really that simple. Some of my validation is in the constructor, and some in attached().
Wednesday, December 30, 2015
Friday, December 18, 2015
Aurelia - Initializing properties in custom elements
I'm working on wrapping a couple of external controls into Aurelia custom elements. I'm nearly done, and about to put them out onto Github. In my initial implementation, I only exposed the capabilities of the underlying control that I was going to use, but now that I'm about to push them into the public, I thought I should probably make some effort at exposing a bit more.
In one case, I have a control that offers about 20 properties that can be configured, but in reality, most will be set only at initialization, so I decided as a first pass to make them one-time, initialization-only properties. But I didn't want to clutter my component with 20 properties, so an init property seemed the best solution (thanks, BTW, to @stoffeastrom for his help on getting the syntax right). Here's what I ended up with.
In the view model, I have:
Then, when I use the custom element, I write:
The properties size, onText and offText are properties of the underlying control.
One thing that caught me was the use of init.bind, rather than just init. When you have a bindable property on a custom element, but you want to bind it to a static string, you don't use .bind. Since my first thought was that the set of initial properties was a static entity, I didn't need .bind. Wrong. I want Aurelia's binding engine to parse the property into an object for me so that I can use it to initialize the underlying control. Thus, I have to use .bind, even though the property is not bound to my view model.
Again, is there anything I want to do that Aurelia won't help me with?
In one case, I have a control that offers about 20 properties that can be configured, but in reality, most will be set only at initialization, so I decided as a first pass to make them one-time, initialization-only properties. But I didn't want to clutter my component with 20 properties, so an init property seemed the best solution (thanks, BTW, to @stoffeastrom for his help on getting the syntax right). Here's what I ended up with.
In the view model, I have:
import {inject, bindingMode, bindable} from 'aurelia-framework'; @bindable ({ name: 'init', defaultBindingMode: bindingMode.oneTime }) @inject(Element) export class MyElementCustomElement { constructor(element, bindingEngine) { this.element = element; } bind() { let init = {}; if (this.init) { Object.assign(init, this.init); } // Use init to initialize the underlying control } }
Then, when I use the custom element, I write:
<my-element init.bind="{size: 'small', onText: 'Readonly', offText: 'Editable'}"></my-element>
The properties size, onText and offText are properties of the underlying control.
One thing that caught me was the use of init.bind, rather than just init. When you have a bindable property on a custom element, but you want to bind it to a static string, you don't use .bind. Since my first thought was that the set of initial properties was a static entity, I didn't need .bind. Wrong. I want Aurelia's binding engine to parse the property into an object for me so that I can use it to initialize the underlying control. Thus, I have to use .bind, even though the property is not bound to my view model.
Again, is there anything I want to do that Aurelia won't help me with?
Aurelia - binding and custom elements
One more thing that seems to bite me with custom elements. By default, attributes of custom elements are one-way bound. That means, if I do
Then if the custom element changes the value of attribute, that change will not be reflected in somethingFromMyViewModel. The simple fix is to do:
That has the downside of having to remember it every time. If you have a view model, then you can change the default for a binding. To simply change this one binding to two way, you need only do add:
Unfortunately, right now you have to have a view model to change the default binding. If you have an HTML-only component, you are stuck.
<my-element attribute.bind="somethingFromMyViewModel"></my-element>
Then if the custom element changes the value of attribute, that change will not be reflected in somethingFromMyViewModel. The simple fix is to do:
<my-element attribute.bind="somethingFromMyViewModel"></my-element>
That has the downside of having to remember it every time. If you have a view model, then you can change the default for a binding. To simply change this one binding to two way, you need only do add:
import {bindable, bindingMode} from 'aurelia-framework'; @bindable ({ name: 'attribute', defaultBindingMode: bindingMode.twoWay }) export class MyElementCustomElement { }
Unfortunately, right now you have to have a view model to change the default binding. If you have an HTML-only component, you are stuck.
Thursday, December 17, 2015
Aurelia - making custom element global
This isn't anything earth-shattering. In fact, you can probably find almost the exact same thing somewhere else. But part of my purpose in writing this blog is to centralize the knowledge that I have acquired while working on this Aurelia project so that my team can come here to learn. In that spirit, a short post on making resources globally available.
If you have a custom element (or attribute, but I have not yet worked with a custom attribute, weird), you can reference it in your html file with a simple require:
Simple enough, and you can then use the custom element in your view. However, that can get tedious, especially with elements that you use all over your web site. For that, Aurelia provides a very simple mechanism, aurelia.use.globalResources. Simply add it to your main.js chain, passing the path to your view/view-model pair, and you're good:
This is great for one or two custom elements, but as your app grows and you centralize more of your look and feel into re-usable custom elements, this can get unwieldy. For that, another method comes into play: aurelia.use.feature. Simply move all of your custom elements into a single folder (you already did that anyways, right?) add an index.js to that folder, and change the above to:
The index.js file in the components folder has a method configure with a call to globalResources, with all of the custom elements as parameters:
There is no limit on the number of parameters other than a javascript limit on the number of parameters. If you hit that limit, or for more practical reasons, you can have multiple calls to globalResources chained as well:
Currently, I have two folders of features: One contains my custom elements, the other my value converters. Each has its own index.js file and both are referenced in main.js (this is my actual main.js as of this writing):
One little side note. Don't forget the ./ at the beginning of paths to resources in the same folder. In my experience, nothing bad happens until you are close to deadline, then the templating engine fails to find your resource :-) Actually, it's just the right way to do it, and it will break some things if you omit it.
If you have a custom element (or attribute, but I have not yet worked with a custom attribute, weird), you can reference it in your html file with a simple require:
<require from="../components/my-custom-element"></require>
Simple enough, and you can then use the custom element in your view. However, that can get tedious, especially with elements that you use all over your web site. For that, Aurelia provides a very simple mechanism, aurelia.use.globalResources. Simply add it to your main.js chain, passing the path to your view/view-model pair, and you're good:
export function configure(aurelia) { aurelia.use .standardConfiguration() .developmentLogging() .globalResources('./components/my-custom-element'); aurelia.start().then(a => a.setRoot()); }
This is great for one or two custom elements, but as your app grows and you centralize more of your look and feel into re-usable custom elements, this can get unwieldy. For that, another method comes into play: aurelia.use.feature. Simply move all of your custom elements into a single folder (you already did that anyways, right?) add an index.js to that folder, and change the above to:
export function configure(aurelia) { aurelia.use .standardConfiguration() .developmentLogging() .feature('components'); aurelia.start().then(a => a.setRoot()); }
The index.js file in the components folder has a method configure with a call to globalResources, with all of the custom elements as parameters:
export function configure(aurelia) { aurelia.globalResources('./my-custom-element', './some-custom-attribute', './file-loader');
There is no limit on the number of parameters other than a javascript limit on the number of parameters. If you hit that limit, or for more practical reasons, you can have multiple calls to globalResources chained as well:
export function configure(aurelia) { aurelia .globalResources('./my-custom-element', './some-custom-attribute', './file-loader') .globalResources('./more-good-stuff'); }
Currently, I have two folders of features: One contains my custom elements, the other my value converters. Each has its own index.js file and both are referenced in main.js (this is my actual main.js as of this writing):
export function configure(aurelia) { aurelia.use .standardConfiguration() .developmentLogging() .plugin('aurelia-validation', (config) => { config.useViewStrategy(TWBootstrapViewStrategy.AppendToMessage); }) .plugin('aurelia-dialog') .feature('resources') .feature('components'); aurelia.start().then(a => a.setRoot()); }
One little side note. Don't forget the ./ at the beginning of paths to resources in the same folder. In my experience, nothing bad happens until you are close to deadline, then the templating engine fails to find your resource :-) Actually, it's just the right way to do it, and it will break some things if you omit it.
Aurelia - A use case for view-model.ref
A couple of days ago I blogged on how to get access to the view model of a custom element. When I wrote it, my use case was calling a method of the view model from the containing component. As happens so often, once I had done this, another use case came up. I think this one is sufficiently interesting to warrant another post.
I have two independent components. One displays the current version of a banner image, and the other is the styled file picker that I use to select a file. What I want is for the component that shows the current banner image to display an indicator when the user has selected a new image but hasn't saved it, since I cannot show the new image on the web page until they actually upload it.
What I don't want to do is add a property to the view model of the page. The page itself doesn't care about this interaction and doesn't need to do anything to mediate it. It is an accidental participant in the process, and so adding a property just to allow the two components to interact seems ugly. Fortunately, I don't have to. The file upload component already has a property hasFileSelected, and the display component needs a property imageChanged, so I can just write the following in the HTML:
Notice how the banner-image component binds the image-changed property to the file-picker's hasFileSelected using the ref. Simple. Everything is done in the view, where it makes the most sense.
This is one of the things that I am loving about Aurelia, the ability to leverage on HTML when it makes sense. Sometimes, the complexity of a javascript view model is more than you need, and with Aurelia, you don't need it.
I have two independent components. One displays the current version of a banner image, and the other is the styled file picker that I use to select a file. What I want is for the component that shows the current banner image to display an indicator when the user has selected a new image but hasn't saved it, since I cannot show the new image on the web page until they actually upload it.
What I don't want to do is add a property to the view model of the page. The page itself doesn't care about this interaction and doesn't need to do anything to mediate it. It is an accidental participant in the process, and so adding a property just to allow the two components to interact seems ugly. Fortunately, I don't have to. The file upload component already has a property hasFileSelected, and the display component needs a property imageChanged, so I can just write the following in the HTML:
<banner-image src.bind="bannerImageUrl" image-changed.bind="filePicker.hasFileSelected"></banner-image> <file-picker url.bind="ui.bannerImageUrl" view-model.ref="filePicker"></file-picker>
Notice how the banner-image component binds the image-changed property to the file-picker's hasFileSelected using the ref. Simple. Everything is done in the view, where it makes the most sense.
This is one of the things that I am loving about Aurelia, the ability to leverage on HTML when it makes sense. Sometimes, the complexity of a javascript view model is more than you need, and with Aurelia, you don't need it.
Tuesday, December 15, 2015
How I got an image to the backend of my Aurelia site
Yesterday's task was supposed to be easy enough: everything else in my configuration page is being saved to the backend, time to make the banner image save. I'm using the Aurelia fetch client, and I figured it couldn't be that hard.
You'll notice I said "yesterday's", because I went to be last night with my banner images still firmly locked into the front end. However, stepping away, and some sleep, and I have a solution. It is NOT idea, I think. There may be a better solution, and if I find one, I will blog about it here.
In the end, I was not able to coerce the fetch client to send the data in a way that I could consume it. I will keep looking. But all of the examples of sending form data via AJAX to a .NET backend used jquery, so I dropped into jquery.
My C# is pretty straightforward WebApi 2:
The javascript is a bit trickier. I know it is going to be possible to save the banner image and all of the other settings in one call to the backend, but right now it's done with two. So I end up with javascript that first uses the fetch client to save the other settings, represented by a JSON string, and then a jquery ajax call to save the image:
Amazingly, it works.
For which I am grateful, as I have a demo today, and I wanted this front-end configuration piece to be complete.
You'll notice I said "yesterday's", because I went to be last night with my banner images still firmly locked into the front end. However, stepping away, and some sleep, and I have a solution. It is NOT idea, I think. There may be a better solution, and if I find one, I will blog about it here.
In the end, I was not able to coerce the fetch client to send the data in a way that I could consume it. I will keep looking. But all of the examples of sending form data via AJAX to a .NET backend used jquery, so I dropped into jquery.
My C# is pretty straightforward WebApi 2:
[Route("user/{zid}/frontend/{feId}/banner")] [HttpPost] public IHttpActionResult SaveBanner(string zid, string feId) { if (!System.Web.HttpContext.Current.Request.Files.AllKeys.Any()) return Ok(); var httpPostedFile = System.Web.HttpContext.Current.Request.Files["BannerImage"]; if (httpPostedFile == null) return Ok(); using (var ms = new MemoryStream()) { httpPostedFile.InputStream.CopyTo(ms); var response = _repository.SetBanner(zid, feId, ms.GetBuffer() }); if (response.Success) return Ok(); return BadRequest(); } }
The javascript is a bit trickier. I know it is going to be possible to save the banner image and all of the other settings in one call to the backend, but right now it's done with two. So I end up with javascript that first uses the fetch client to save the other settings, represented by a JSON string, and then a jquery ajax call to save the image:
updateUISettings(feId, feSettings, bannerImage) { let url = `/vt/user/${this.zid}/frontend/${feId}`; let content = { Name: feName, Settings: feSettings }; return this.put(url, content) .then(response => { if (bannerImage) { let bannerUrl = `{$root}/${url}/banner`; var data = new FormData(); data.append("BannerImage", bannerImage); return new Promise((accept, reject) => { $.ajax({ type: "POST", url: bannerUrl, contentType: false, processData: false, data: data }) .done((data, textStatus, xhr) => { accept(response); }) .fail((xhr, textStatus) => { reject(textStatus); }); }); } else { return response; } }); }
Amazingly, it works.
For which I am grateful, as I have a demo today, and I wanted this front-end configuration piece to be complete.
Aurelia - Leveraging the view model of an included element.
Thanks to everyone on the Gitter channel who helped me get this to work.
I have a simple little component that happens to have its own "api", a method it exports from its viewmodel so that containers can interact with it. I needed to be able to call it, but couldn't figure out how.
The Cheat Sheet clearly indicates that you can use ref to create a reference to a view model, but I never thought about the implications, since the only time I had used ref was to use that reference in the HTML in a view-only control. But this ref that you create is also usable from the view model of the container. That means, I can do this:
and then reference it from the container javascript, like this:
It turns out that using ref just adds that name to your view model, as a reference to that entity. This is pretty cool, since you can do:
I think that I shall never plumb the depths of the binding system for Aurelia. There is almost nothing you cannot do, it seems.
I have a simple little component that happens to have its own "api", a method it exports from its viewmodel so that containers can interact with it. I needed to be able to call it, but couldn't figure out how.
The Cheat Sheet clearly indicates that you can use ref to create a reference to a view model, but I never thought about the implications, since the only time I had used ref was to use that reference in the HTML in a view-only control. But this ref that you create is also usable from the view model of the container. That means, I can do this:
<my-image url.bind="bannerImageUrl" view-model.ref="image"></my-image>
and then reference it from the container javascript, like this:
this.image.doSomething();
It turns out that using ref just adds that name to your view model, as a reference to that entity. This is pretty cool, since you can do:
ref="someIdentifier"
orelement.ref="someIdentifier"
- This gets you the HTML element from the DOM, which means that you can manipulate is from both your HTML and your javascript.attribute-name.ref="someIdentifier"
- This gets you the view model for a custom attribute. Since this is a javascript object, you can do anything with it you would expect.view-model.ref="someIdentifier"
- This gets you the view model for a custom attribute. Again, this is just javascript, hack away!view.ref="someIdentifier"
- This gets you the View object behind the custom element's view. As teh docs say, this is not the DOM element (you use ref for that) but rather a class from the Aurelia framework that you acn leverage to query and manipulate the view. This class is documented in the templating section of the docs.controller.ref="someIdentifier"
- This gets you the controller for the custom element. Again, this class is documented in the templating section of the docs.
I think that I shall never plumb the depths of the binding system for Aurelia. There is almost nothing you cannot do, it seems.
Friday, December 11, 2015
Aurelia - repeat like a for loop
It's a small thing, and it's even in the documentation, but I didn't notice it, and ended up asking on the channel and having Rob himself answer. I wanted to repeat a block, not based on item in an array, but just a fixed number of times.
Turns out, you can write:
or
and the contents are repeated the number of times indicated in the loop count.
Pretty cool.
Turns out, you can write:
<li repeat.for="index of 10">${index}</li>
or
<li repeat.for="index of itemCount">${index}</li>
and the contents are repeated the number of times indicated in the loop count.
Pretty cool.
ES6, arrow functions, semantic this, and Chrome debugger tools
I'm sure at some point parts of this post will be irrelevant. But for now, this captures what I learned and what has helped me.
One of the really nice things about ES6 is arrow functions and semantic this. Arrow functions are just lambda expressions in javascript for callbacks. For example, if I want to use the javascript array filter function, I can write this:
Which is fine. But both of these methods are subject to javascript's arcane rules for what gets passed as the this value to functions. If your callback needs to reference the surrounding context, then you are left using bind to make sure that you have the correct value of this when the function is called, or to adding the ubiquitous let self = this; somewhere before the filter operation.
But arrow functions and semantic this changes all of that. Let's expand the function to look for values in the array that match some member value of the containing class. Something more like this:
This won't work, because when the filter is called, the value of this will not be the surrounding class, it will likely be the array. The traditional solution is:
It works, but having code littered with stuff just to fix the context is clutter. Now, with ES6, you can write this:
And it works, because of semantic this.
That isn't actually the point of this blog, though. If you use arrow functions, when you step through in the debugger, you will see that inside the context of the arrow function, this is undefined. What's that about?
Well, I use bable as my transpiler. And it turns out that babel does the self = this trick for you in a closure that surrounds the arrow function. So, while this is the current context is undefined, if you look in the surrounding closure, you will see a variable named some variant of _thisn that has the correct value. That value is being used by the transpiled code as the value of this inside the arrow function. But you don't see it if you are using source maps, you just see the beautiful ES6 code, and a this that appears to be undefined.
I admit that I got fooled by this at first, and even today, if you look through my Aurelia apps, you will see a few places where I am setting self = this even though I am using arrow function.
Cleaning that up, though.
One of the really nice things about ES6 is arrow functions and semantic this. Arrow functions are just lambda expressions in javascript for callbacks. For example, if I want to use the javascript array filter function, I can write this:
function isNotNull(item) { return item !== null; } var filtered = someArray.filter(isNotNull);If isNotNull is universally useful, this is a great approach. However, if the function isn't generally so reusable, you could always write it inline:
let filtered = someArray.filter(function(item) { return item !== null; });
Which is fine. But both of these methods are subject to javascript's arcane rules for what gets passed as the this value to functions. If your callback needs to reference the surrounding context, then you are left using bind to make sure that you have the correct value of this when the function is called, or to adding the ubiquitous let self = this; somewhere before the filter operation.
But arrow functions and semantic this changes all of that. Let's expand the function to look for values in the array that match some member value of the containing class. Something more like this:
someMemberFunction() { var filtered = someArray.filter(function(item) { return item === this.memberValue; }); }
This won't work, because when the filter is called, the value of this will not be the surrounding class, it will likely be the array. The traditional solution is:
someMemberFunction() { var self = this; var filtered = someArray.filter(function(item) { return item === self.memberValue; }); }
It works, but having code littered with stuff just to fix the context is clutter. Now, with ES6, you can write this:
someMemberFunction() { var filtered = someArray.filter(item => item === this.memberValue); }
And it works, because of semantic this.
That isn't actually the point of this blog, though. If you use arrow functions, when you step through in the debugger, you will see that inside the context of the arrow function, this is undefined. What's that about?
Well, I use bable as my transpiler. And it turns out that babel does the self = this trick for you in a closure that surrounds the arrow function. So, while this is the current context is undefined, if you look in the surrounding closure, you will see a variable named some variant of _thisn that has the correct value. That value is being used by the transpiled code as the value of this inside the arrow function. But you don't see it if you are using source maps, you just see the beautiful ES6 code, and a this that appears to be undefined.
I admit that I got fooled by this at first, and even today, if you look through my Aurelia apps, you will see a few places where I am setting self = this even though I am using arrow function.
Cleaning that up, though.
Thursday, December 10, 2015
Aurelia - faking my service layer
In this new application, I am leveraging APIs that don't exist yet. But I don't want to have to re-code my client code once the API is done. Since I know what the API will look like, I can build the call from the client into the service layer, and just make a fake call. Since I want the asynchronous behavior of an AJAX call, I will return a Promise, just like I will get from the real API call once it's done.
I take the same parameters, and I return a Promise that resolves with the same shape of data the API will someday return. An example:
I take the same parameters, and I return a Promise that resolves with the same shape of data the API will someday return. An example:
export class Service { getDataFromServer(key) { return this.makeFakeServiceCall((accept, reject) => { accept('Data from server'); }, 1500); } makeFakeServiceCall(action, delay) { this.awaitingResponse = true; return new Promise((accept, reject) => { setTimeout(() => { this.awaitingResponse = false; action(accept, reject); }, delay); }); } }The idea is pretty simple. makeFakeServiceCall accepts an action which will implement the promise resolution and how long in milliseconds to hold the promise. Then, in the action, I can call accept() or reject() as needed to simulate the behavior I want. When it's time to replace with a real service call, I just take out the body and replace it with the call, something like:
getDataFromServer(key) { return this.makeRealServiceCall('get', 'url', key); }Where makeRealServiceCall uses the fetch client to call the API. My implementation of makeRealServiceCall wraps logic about the fetch client and issues a request, unwrapping the json response if there is one. So it returns the resulting data, just like makeFakeServiceCall. One last thing. The flag this.awaitingResponse is used to allow me to implement a common flag for both the fetch client (real API calls) and the fake calls. I have a little method:
get inServiceCall() { return this.awaitingResponse || this.fetchClient.isRequesting; }
Wednesday, December 09, 2015
Aurelia - the 5 hour hyphen search
One of my first custom components had a bindable property named entityId. So I had
I spent 5 hours. Finally, I literally stripped the code down to just the bare example from the Aurelia docs, and it worked fine. I then added one small piece at a a time back into the element until I had the full capabilities of my intended element, and everything worked fine! Now I was really frustrated.
Then I noticed that, in going back to the example, I had changed the attribute name from entityId to "to", the value in the Aurelia example. Quickly, I changed the attribute name and bindable property, and lo and behold, the component stopped working.
If you will forgive a small religious sidebar, I leaned back, prayed for a moment, and then remembered something I had read somewhere back about the conventions that Aurelia provides, and that one of them was to convert camel case to hyphen- (or snake-)case. I changed the attribute to read:
Wondering what might be up, I posted a comment on the Gitter channel, https://gitter.im/Aurelia/Discuss, and almost immediately I was presented with the explanation from PWKad: HTML doesn't like upper case in attribute names, so Aurelia does just this very conversion for you.
Having spent 5 hours trying to understand why this was happening, I thought I should blog the answer so that maybe someone else will find it here in less time.
import {bindable} from 'aurelia-framework'; export class MyFirstCustomElement { @bindable entityId; }Then, in my html, I was using it as:
<my-first entityId.bind="entity"></my-first>And (if you know the answer you already know this), it didn't work.
I spent 5 hours. Finally, I literally stripped the code down to just the bare example from the Aurelia docs, and it worked fine. I then added one small piece at a a time back into the element until I had the full capabilities of my intended element, and everything worked fine! Now I was really frustrated.
Then I noticed that, in going back to the example, I had changed the attribute name from entityId to "to", the value in the Aurelia example. Quickly, I changed the attribute name and bindable property, and lo and behold, the component stopped working.
If you will forgive a small religious sidebar, I leaned back, prayed for a moment, and then remembered something I had read somewhere back about the conventions that Aurelia provides, and that one of them was to convert camel case to hyphen- (or snake-)case. I changed the attribute to read:
<my-first entity-id.bind="entity"></my-first>and, of course, it all worked.
Wondering what might be up, I posted a comment on the Gitter channel, https://gitter.im/Aurelia/Discuss, and almost immediately I was presented with the explanation from PWKad: HTML doesn't like upper case in attribute names, so Aurelia does just this very conversion for you.
Having spent 5 hours trying to understand why this was happening, I thought I should blog the answer so that maybe someone else will find it here in less time.
Sunday, December 06, 2015
Aurelia - managing global state
One of the things I love about writing client-side web sites is that no longer do we have to write stateless applications, or leverage hidden tags to pass state back and forth to the server. State can live on the client, persisting as long as the user keeps the browser window open. As the user navigates around, nothing gets lost since in reality, the whole web site is one page, and the data associated with that page persists until the user closes it.
There is one small problem with this scenario: if the user refreshes the page (or BrowserSync does so), then this causes a new load of the page. In Aurelia, this will cause new singletons to be created for items in the DI system. If I am using the DI container to hold my singletons, then I will lose the user's data. The site will likely redirect the user to the login page, which is not likely the experience the user expects. How do we resolve this? In the past, we would place the data into a cookie, but modern browsers offer a better alternative: sessionStorage.
My pattern is to place objects that I want to have persist throughout a session into sessionStorage, and use that as a fallback if suddenly the object is not available (as would happen after a browser refresh). That comes out something like this:
There might be a solution for this in Aurelia's DI container, if I find one, I'll post it later.
There is one small problem with this scenario: if the user refreshes the page (or BrowserSync does so), then this causes a new load of the page. In Aurelia, this will cause new singletons to be created for items in the DI system. If I am using the DI container to hold my singletons, then I will lose the user's data. The site will likely redirect the user to the login page, which is not likely the experience the user expects. How do we resolve this? In the past, we would place the data into a cookie, but modern browsers offer a better alternative: sessionStorage.
My pattern is to place objects that I want to have persist throughout a session into sessionStorage, and use that as a fallback if suddenly the object is not available (as would happen after a browser refresh). That comes out something like this:
class GlobalState { constructor() { this._entity = null; } get entity { if (!this._entity) { let sessionEntity = sessionStorage._entity; if (sessionEntity) { this._entity = JSON.parse(sessionEntity); } else { this.entity = {}; } } return this._entity; } set entity(value) { this._entity = value; sessionStorage.entity = JSON.stringify(value); } }This way, every time I go to fetch the state, I verify its persistence, and return it. A couple of things to note: I use backing storage _entity to hold the value. The key name in sessionStorage is the same as the actual entity. Since sessionStorage has to be a string, we use JSON to stringify/parse it. Pay attention to the places where I use the backing sotrage vs. the actual entity. In the getter, I set the entity to a default value using the setter, rather than setting the backing value. This puts the default value into sessionStorage. In the case where the entity is simple to build, this may not be needed, but for consistency, I choose to go this way.
There might be a solution for this in Aurelia's DI container, if I find one, I'll post it later.
Saturday, December 05, 2015
Aurelia - a can't live without component
Obviously, I've got to show wait cursors while my backend code is running. Rather than write it a bunch of times, I built the tiniest of custom elements and I use it everywhere now:
The class named color-class is used to pick the standard color of the spinner. No javascript behind this one, just an HTML-only component using Font Awesome.
<template> <i class="fa fa-circle-o-notch fa-spin fa-2x payrazr-blue"></i> </template>
The class named color-class is used to pick the standard color of the spinner. No javascript behind this one, just an HTML-only component using Font Awesome.
Thursday, December 03, 2015
Aurelia - my config file
By the way, this isn't going to happen. Too much to do to try and write this post now.
Maybe later.
Maybe later.
Aurelia - Unknown routes
So, today, I was working on getting the handler for my main entry point on my site. Basically, the idea is that there is one initial route that succeeds, everything else is a 404. But if you come in on the magic route, a call to the backend will verify that it is a valid request, and all of the routes will then appear.
But how to set up a catch-all route? A google search found nothing except links into the Aurelia source code. A request on the gitter channel was not answered. So I dug through the source and found the answer myself (I admit, asking on gitter was cheating - I should have just searched the source in the first place).
You can add a catch-all by calling mapUnknownRoutes on the original config object. So my configureRouter has the one route, and the call to mapUnknowRoutes to set the view model that handles unknown routes. In my case, it looks kinda like this:
Simple.
But how to set up a catch-all route? A google search found nothing except links into the Aurelia source code. A request on the gitter channel was not answered. So I dug through the source and found the answer myself (I admit, asking on gitter was cheating - I should have just searched the source in the first place).
You can add a catch-all by calling mapUnknownRoutes on the original config object. So my configureRouter has the one route, and the call to mapUnknowRoutes to set the view model that handles unknown routes. In my case, it looks kinda like this:
configureRouter(config, router) { config.title = 'Wow! What a site'; config.map([ { route: [ 'entry/:id'], moduleId: 'public/entry' } ]); config.mapUnknownRoutes('public/404'); }My 404 view model is empty, and 404.html has the page I want everyone to see.
Simple.
Subscribe to:
Posts (Atom)