 Hello, everyone. I am Dave Desai. I work at Xenofits. I'm an engineer there, and today I'm going to talk about some design considerations for a modern data table. So the agenda for today is we'll start with some what the theme of the talk is going to be, and then show you Xenofits' old table solution, and what went wrong with it, and then we'll cover a new table components and their API design, and then we'll talk a little bit about managing data, and after a short demo and we'll conclude and then open it up for questions. So what is the theme of today? Notwithstanding the previous talk, I am going to ram DDAU down your throats, so apologies for that. But hopefully, I can convince you that it's a really good paradigm. So we are going to embrace data down actions up. We are going to get away from massive monolithic components, and views on observers, views because they don't work with new versions of Ember, and we are going to build and use LEGO-style small component blocks and then compose them together and then conquer the world. So a small disclaimer there are, this is our interpretation of some modern Ember paradigms for creating a table and collection components. There are many others like it. This is ours. Okay. So the benefits is, old data table was a heavily modified version, or somewhat modified version of Ember table. Many of you probably know this, know what Ember table is, but it's basically like, it's monolithic component where you just say data table and give it some columns. The columns are basically a configuration array that you write in your controller.js. So we did a lot with this and we have really modified it, and we were able to get a lot of views out of it. We added filters and detail panels and all sorts of stuff, and it was really profound because Ember table has some nice rendering optimizations built into it. But getting to this point was a pain. Some of the pain points are that the way data table or Ember table in general is like makes you write code is that you have to write lots of JavaScript to configure filters and columns. Even simple things like custom table cells, they often require their own handlebar and JavaScript. It relies on old patterns like views and observers, and what happens at the end is that the business logic for your app ends up in multiple places and directories and multiple files. Moreover, in order to customize any part of the data table, it's you have to know the code through and through. So it becomes hard to customize UI and features, and it's not compatible with newer versions of Ember. So last year, we set out some design principles that we wanted to follow for our replacement for this. Idealistically, we wanted to make it powerful but easy to use. We wanted to use reusable components, which are also composable, which means we heavily use yielded blocks, closure actions, and sensible defaults. Again, we use data on actions up through and through. We want to emphasize clear separation of business and display logic, and also maintain compatibility with future versions of Ember. So these were the principles we laid out for ourselves, and we ended up with this component called Z-Table. So here's a simple use case for Z-Table. It's pretty simple. It's a wrapper called Z-Table, which houses Z-Table column components. So whenever an engineer writes a table, they mostly think about columns of data. So that's what we went for. So in order to do the simplest use case, you just have inside your Z-Table component, you yield the employee again and again in a loop, and then the column component consumes this employee. Here it's consuming it via this API equal to API thing. So here there are three columns with first name, company, and department as the fields, and the headers are defined in there. So that's a simplest use case. Now if this, so we use Ember 113. So if you're wondering why aren't we using composable components, that's the reason. But this is easily upgradable to composable components once we get there. So that's the reason why you see API equal to API everywhere. Yeah, so that's the basic use case. It's pretty clear, but the best thing about this is that now you can yield inside Z-Table column also. So let's say we want to column, we want the salary to have a dollar sign in front of it, and we want like first name, last name for our name column, then we can just easily do it by yielding from the Z-Table column. And since we are yielding an employee in the Z-Table itself, can just use that employee and make a markup. You can even put components in here, right? So this is the basis for the Z-Table. Have easily customizable table cells in Z-Table column components. Now we use closure actions also. Suppose for example you wanted a detail panel. Now there's nothing specific about, this is just an example, but let's say you select a row and you want something to happen. Maybe you want to go to the server and fetch some data. Maybe you want to open a sub-route. Any of those things are possible, right? In fact, we have all those use cases at Z-Tables. So here let's say we have a very simple use case where we click on a row and it should open a detail panel. So here we have a one way binding of selected item to selected employee and then on select item we just throw an action with the item that got selected. In this case we are using the mute helper or the mute action to mutate selected employee into the selected item, right? And the detail panel here is just like a div which shows up when there's a selected employee present. And on the close of that div we just set the selected employee to null. Nice. So here we say selected employee to null which closes the detail panel. Right, so if you think this is too verbose just remember that we can actually have our custom actions in there instead of a simple mute, right? So that's the power of using closure actions. And again, this is like a classic example of data around actions. You don't modify the data. Never modify the data that's given to you inside your component. Now, it's not enough just to have a good data table. You usually want to do some operations on the data before presenting it in the table. Most importantly, you want to do filtering, sorting and pagination on the data, right? Now these are repeated patterns that you see across any collection component, right? So we also made a data manager component which perhaps around the table or any collection component for that matter. Like it could be a list view, it could be pie charts, anything, right? So that's what it does it. You use it as a container component that wraps around your collection component. In this case, we'll see examples of wrapping it around Z table. It exposes the relevant filtered, sorted, paged data and actions to call once these things change. And it doesn't need to support all three, it could support any subset of the above. And in order to specify what is to be filtered, what key is to be sorted, we use simple JSON objects that we call descriptors. And the reason they're simple JSON objects is that we want to implement server-side filtering, pagination and sorting in the near future, like in the coming weeks. So if you have played around with any table which has filtering, sorting and pagination, it becomes clear that it has to follow certain pattern, right? The pattern that it makes sort of the most sense is that you're given an input of all the items. You first filter them, then you sort the filtered items and then you page the sorted items and then display them, right? So it's filtering first, then sorting, then paging, display and each of them like depends. So filter items is a computer property. So it's very simple, right? I mean, in this one slide, I've explained to you what data manage is doing. There's a computer property filter items, which depends on items in filter descriptor and then sorted items depends on filter items in sort descriptor and so on and so forth, right? And literally, internally, all it's doing is yielding this. It's a non-tag component, it's just a container component that's yielding filter items, filter descriptor, and what action to call when your filter descriptor changes. Similarly, sorted items, sort descriptor and what action to call when you sort descriptor, your keys or the change. And similarly for page. What that allows you to do is you can plug and play different parts of these things and put your own subcomponents in, along with your collection component in order to achieve these things. For example, sorting, let's say you want to click on headers to start sorting your data, right? So all you need to do is wrap your Z-table around into a Z-data manager, which exposes the data, the yielded nine things that I showed you earlier, and then the Z-table just takes in a sort descriptor and it throws an action called sort action, which is data or on sort change, which takes in a modified sort descriptor, right? That's all it's doing. And the input for the Z-table is your data.sorted items, right? So with these three lines, we have enabled sorting. Similarly, pagination is also pretty easy. You could have a paging component. Let's say the paging component that I've created here has a previous link, a next link, and an input box to take the page number, right? So all I need to specify is a page size and then bind these left and right clicks and the input to the appropriate actions. In this case, for example, the right click is incrementing the pages descriptor.current page and passing it to the on-page change action, right? So that's basically an example. So in practice, this would be like, this entire div could be a paging component that you use across various different collections, collection components, okay? So I'll just show you a quick demo of all these things. Let me go back, okay? So this is a simple table with like around 50 employees. And it, I think it already has sorting in it, right? So you can sort these things and it also has a detail panel. Obviously, this looks pretty ugly because I'm not very good at this, but you get the idea. It also has a custom header if you want. So this thing, that's also easy to do. But anyway, so let's add paging to this, right? So this is my ZTable component. It has a select item. It has a sort descriptor. So it's already taking in page items, which if there is no page descriptor is just an alias for sort items, right? So that's fine. And then we'll just enable our paging component, our paging HTML. And then the data manager, we just need to pass a page size. Let's say it's 20. So we just need to pass a page size. Let's say it's 20. So we just added a paging component. We gave it a page size, yeah. So now we are only seeing 20 items and there's a pager, right? And the good thing about using a data manager is it follows normal design paradigms. Like if you are on a different page than the first page and you click on a sort, it goes back to the page first page, right? So this just works out of the box. Similarly, we can add some filtering logic. For example, let's add a search box to search by last name, okay? So let's say I search for last name which has E unit. So it filtered all these things and now I only have like around 30 entries here, right? And again, if I am doing any kind of sorting, it again research to page one. So it just works out of the box. Similarly, I could add what's the most common use case is doing some checkbox filters. So here I just have a list of checkboxes which are bound on some actions. So the action I'm using Composable Helpers here. So it pipes, it first changes the filter descriptor to the new checkbox value that I'm setting and then passes that new filter descriptor to the onFilterChange function of the data manager. So that also works as expected. So, yeah. So I have a bunch of checkboxes here. If I select that, it starts filtering all these things and these things just start working together. So that's a short demo of how it works. So in conclusion, I just want to restate that small Composable components, if you just write them properly and like use closure actions and all these good things, you can actually build very powerful stuff. So they win over large monolith components. And we have used data around actions up all the way through and these yielded blocks and closure actions actually make for very powerful integrations and more importantly, parameters are only passed to all the small components that actually need them. So you don't need to pass a huge list of parameters to the outer Z table if that's not needed. So the things that we are adding very shortly are like server-side filtering, sorting pagination, using Ember concurrency and support for custom filter functions and we are going to add some helper methods to deal with manipulating these data descriptors also. And finally, a support for power mode if you need it. And it's not just a thought exercise of like building this idealistic component. Like when we gave it to our engineers with just the readme file, it was universally loved. So some of the feedback was that it was very easy to use. Things that took people hours or days or weeks to do with data table just happen in minutes with the new, okay, not minutes, but maybe hours with Z table. So this is just some of the feedback that we have got from inside our engineering team. So it's got good response so far. Yeah, so these are some of the people that have helped us through making this component with having discussions and actually technically helping us. And we are hiring at Zenfits. Finally, I wanted to end on that. So that's it. That's my talk. Questions, correct? Yeah. Is that talking to a service that's actually always passing into the route and the routes handling the data loading? Okay, so the question was does the Z data manager pass the actual duties of pagination and filtering and sorting to a route or a service and does it do it on its own? So it does it on its own, right? It just needs... So if I... It basically implements that logic on the screen right there, right? You can change the descriptors and you can give it an initial items and then it just... There are the computer properties that depend on them which is sort, filter and basically interpret the descriptors and apply them to the items, basically. If you wanted to be really, really pragmatic about it, you would make three different data managers a filter manager, a page manager but I didn't feel the need to do that because this works even if you don't need some of them. It's not slower because you don't use a particular aspect of it. Does that answer your question? Yeah. Is this something that you would open source? We're thinking about it. There are certain small... stylistic things that... some styles that we use from inside benefits that we'll need to decouple. I'm embarrassed to admit that it's very badly tested right now so I'll have to write some more tests. It was super useful. Yeah. So I forgot to add a slide but there are actually... If you have a new version of Ember, like Liffey, then there are actually good add-ons which kind of do what we are doing here, right? Ember Composable Table is one at which basically almost implements some of the things that we have but they're kind of new so I don't know whether they have a lot of adoption but definitely check them out. Ember Composable Table. There's also Ember Light Table. It's not ideal. It does use some composability but it does have a configuration of columns also so it's good and annoying at the same time. There are some new add-ons that are helpful. If you open sources, then this will also be there as an option. Yeah. Column resizing. Okay. Have we looked at column resizing? Briefly. There's a jQuery plugin that you can install which gives you column resizing for free. So if you are into that then you can use that. Yeah. I have very low opinions about column resizing. Like you use divs instead of table and then you have to use all these things. There's some decisions that are like... Anyway, it could be something that you need. So definitely call resizable is the jQuery plugin that's the industry standard. I think Ember Composable Table also differs to them. If you want it, use them. Data manager is separate from this. I'm wondering what pagination helps versus like really strictly limiting... Correct. So the question is does a lot of data break something? Or what are the trade-offs with a lot of data? I think our design team doesn't really care about these... They are like, oh, we want pagination. So what I'm trying to say is that it might be up to someone else to suggest that you should use pagination versus something like incremental rendering. So this is a concept we have been playing around for some time in Zenfits. There was this other component that we built which actually had incremental rendering as part of it. And we saw some benefits there, but if you have pagination then you probably don't need more technology to kind of make it more performant. But I guess the answer is it depends on your use case. Like I was talking to a CTO the other day and he hated the idea of pagination on something like an employee directory. So it might be up to like different use cases. Yes. The question is do I have fixed headers? Yes, I do, Miguel. And thanks for helping me with that. Yeah. So if you're not using pagination then it's super useful to have fixed headers which is like a surprisingly difficult thing for me to solve. So I needed a lot of help, but we have fixed headers, yeah. Any other questions? Okay. Thank you.