My daughter is turning 4 next week and we are hosting a unicorn party this weekend, hence my inspirational header image.
Pretty recently I wrote blog post about coming up with a mobile UI element that could handle selecting items from very large lists. I came up with something I think is really cool, so I wrote a blog post about it that was featured on NativeScript.org.
I knew it would make a great plugin, but all I'd ever done is Cocoapods integrations and exposing some cool native iOS APIs. This plugin utilizes NativeScript views, no platform specific APIs needed at all. It's basically a functional wrapper around NativeScript functionality relatively easily achievable with out of the box NativeScript components.
I had no idea how to do it, and frankly, the documentation on creating plugins of this nature leaves a lot to be desired.
Said documentation got me part of the way there. I'll try and parse some of it for you, as I understand it. But to quote my friend and relunctant occasional mentor Brad Martin, "I have no idea what I'm doing." If you notice things that I say that are entirely incorrect or represent a misunderstanding of norms and best practices, get in touch with me or comment below.
Basically, your class should extend a NativeScript layout class, like GridLayout, or View, or StackLayout etc. So if you do something simple like
And register your element in your xml namespace, you can do
and treat it exactly like a StackLayout. So doing
It will stack just like a StackLayout on both iOS and Android.
But why would I want to do that? Well, if you continue reading that documentation, you can load other NativeScript elements into your class container, so you can provide functionality in your plugin that sets up layouts and views for users of your plugin.
The Filterable List Picker is a perfect example of this. What I wanted was to allow the user to place some simple xml in their app and get a fully functonal list picker.
So, based on that tutorial, I started with something like this:
You'll notice I am using builder to load in some xml from my plugin code. Here's that xml:
So now if we put
in our app, it will load up our
GridLayout with the contents of our XML file already in it. So we have a
StackLayout that holds our
The next challenge was to get an array of strings into our
ListView. This is handled by registering a property on our class. You'll notice in the plugin TypeScript code above, we do
That sets up a Property, which we need to register on our class so we can grab the
source we set on our custom UI element.
We do that like this (make sure to do it after the class is defined):
Properties are a little confusing, I still don't totally understand them. Learn more about Properties here.
Now we can add
source as a property to our UI element:
listitems is an array of strings in our page's binding context. Pretty cool! Now we get a list containing the array we set up in our app!
To add the filtering capability we need a
TextField, and this is a modal (a UI element that sits on top of other UI elements), so we need a Cancel control too. We just need to add some more NativeScript elements to our XML file that creates the FilterableListpicker. I also want to "dim" the content below it so acts similar to a dialog. Here's how I want it to look in the end:
Heres my final xml in filterable-listpicker.xml (the xml thats loaded into the custom UI component)
To enable filtering, we need to setup a listener on the
TextField. To accomplish this, I save the array the user set up (the source property) to another array that we do not filter, so we can safely set the source to a filtered array. We can take care of this in the constructor of our class. At the time the constructor is called though, the source property is set to the default property we set in the Property declaration.
This is hacky, and somebody please tell me how to do this correctly, but I grab the source in a setTimeout, which allows the property to be initialized with the array from the app:
Then you'll notice I grab the textfield and watch the textChange event, and set the source to the filtered array.
This works awesome. Since source is an
ObservableArray, the array filters in front of our eyes.
But obviously, we need to handle some events, like the user tapped something in the
ListView, and the user tapped the cancel button. This was the most confusing part about this, and the reason I decided to write this blog post, there are no resources that I could find out there describing how this is done. You have to parse through open source plugin code that does this to figure it out. I used Brad Martin's nativescript-videoplayer plugin to see how he did it. And I also bugged him on Slack in the NativeScript community and he helped me out. He's the man.
Here's how its done. You need to declare the property as a
public static in your class. The name of the property in your class needs to be yourpropertyEvent. So your property name + 'Event'. Weird, I know. So if I wanted a
canceled property in my UI component that will call a function if the modal is canceled, I need to declare it like this:
public static canceledEvent = "canceled";.
I need an event for canceled and itemTapped:
And then I set the properties on my UI Component:
Then, when the event occurs at which you'd like to call the function defined in the UI component, you need to
notify the component of that event. Notice that in my plugin xml, I have the Cancel button call a
cancel() function. When the user taps cancel, I want to hide the modal, but also call the function the user defined.
notify does that for us. The UI Component is notified, and the function is called. Amazing. We need to do the same for itemTapped.
itemTapfunction in my ListView in the plugin XML (the standard event in NativeScript when a
ListViewitem is tapped), I am calling a function in my class called
So, I get the item selected in the source, hide the modal, and notify the component which calls
itemTapped, defined in the app. The event arguments are the object included in notify, so I've created the
itemSelected key to include the string the user tapped.
I felt compelled to add some other cool things like the option to customize the dimmer color, blur the background on iOS, customize the placeholder text in the textfield, and let the user control the width and height of the modal.
Reach out with any questions, I'm happy to help anyone and everyone. And follow me on twitter!