Initialize DataTable Data0:00
Let's create a DataTable. We're used to interacting with data tables in the browser, so we're used to a paginated data table that you can search through or jump to different pages. Let's build that in the terminal. So you can see here that I've set up a couple of things already. So we have our DataTable application class. We're registering the data table renderer. We have an array of headers, an array of rows, and I've set that up ahead of time. So we have a name, an email, and an address for the headers. And we just have this static set of data from this data table JSON.
So we have a name, an email, and an address for the headers. And we just have this static set of data from this data table JSON. Let's check that out real quick so we can just see what that is. So it's just an array of objects with a name, an email, and an address for each one. Just a bunch of fake information that we can play with. So we're loading that up into the rows array. And then we just have our renderer, and our renderer right now is not rendering anything. It's just the default renderer that's accepting the data table as an argument. First things first, let's set up our pagination. So we're going to have some public properties that our renderer can access them.
Add Pagination Properties1:04
First things first, let's set up our pagination. So we're going to have some public properties that our renderer can access them. We're going to have a perPage property, and I think we'll do 10 per page. We're going to need to know which page we're on. So public int page, and we'll default to 1. And then we'll need to know about the total pages of the data set. So we'll say public int totalPages. And then let's initialize that here now that we have rows loaded up. This totalPages equals this->getTotalPages(), and we'll pass in the rows. And we're going to extract this to a method because we're going to be using this in a
This total pages equals this getTotalPages, and we'll pass in the rows. And we're going to extract this to a method because we're going to be using this in a couple different ways in the app, so we might as well extract it to a method ahead of time. So say protected function getTotalPages. This accepts an array of records that we want to count, and it returns an int. So we're going to return int ceil(count($records)) divided by this perPage. So we're going to count the records divided by our perPage property. We're going to get the ceil of that, and we're going to make sure that we're returning an int. For now, let's just render this out to the terminal. So Chewy has a little table helper, useDrawsTables.
Render Table in Terminal2:22
For now, let's just render this out to the terminal. So Chewy has a little table helper, use draws tables. And we're just going to use that to render this out to the terminal. First, let's set up our width and height. It's always good practice to just set up the width and height. You'll almost always need it. width equals prompt terminal calls minus two. And our height is going to be equal to the prompt terminal lines minus six in this case. Okay, cool. So now we're just going to print this out to the terminal.
Okay, cool. So now we're just going to print this out to the terminal. So we're going to say this table, and this is from the draws tables Chewy helper. And we know what our rows are, the promptRows. And then we have our promptHeaders that returns a collection. And so we can just loop over that collection and print this out to the terminal. Let's dive into this table helper real quick and just see what's going on in here. So we're basically grabbing the symphony table terminal helper, and we are setting the characters for dividing and all of the border characters. And we're printing it to a buffer console output.
and we are setting the characters for dividing and all of the border characters. And we're printing it to a buffered console output. So we're basically gathering all the lines. We're adding it to this buffered console output. And then we're just exploding the resulting content to create a collection. So here we are, we're going to print this out to the terminal. Let's just see what that looks like HP lab data table. Cool. So here we are, we've actually got this huge data table. So we've got our name, email address, and all of the data is coming in correctly.
Show Only Visible Rows3:56
So here we are, we've actually got this huge data table. So we've got our name, email address, and all of the data is coming in correctly. And this looks good. But it's supposed to be a data table, not a regular table. So we need to paginate this. Let's get to work. So in order to paginate this, we need to know which rows are actually visible at any given time. So let's do that in our app class. Let's add a method called visible. So we'll say public function visible.
Let's add a method called visible. So we'll say public function visible. It's going to return an array of rows that are currently visible. And so we can actually calculate this based on the perPage that we have currently set. So we can say return array_slice($this->rows, ($currentPage - 1) * $perPage, $perPage). So this should return a slice that is at most our $perPage from the section of the array that we want based on our $currentPage. So now instead of doing promptRows, we're actually going to say promptVisible.
from the section of the array that we want based on our current page. So now instead of doing promptRows, we're actually going to say promptVisible. And let's see what that looks like. Awesome. So we've got 1, 2, 3, 4, 5, 6, 7, 8, 9, 10. We've got 10. These are our first 10 records. If we go back up to the top here, we can see these are our first 10 records. Great. So how do we switch pages?
Add Page Navigation Keys5:30
Great. So how do we switch pages? Let's add some key press listeners. So our initial state is going to be called browse. We're going to be browsing the table. So let's add a little method here that's called browse. We'll say protected function browse. And it's not going to return anything, but instead it's going to set up the state and the key press listeners for our browse state. So first of all, we can say this state is equal to browse.
the state and the keyPressListener for our browse state. So first of all, we can say this state is equal to browse. And state is something that comes straight out of the Prompt class. That is a property that we have access to. You can set it to anything arbitrary so that your renderer can render the various states as your application changes. Let's set up a property for our keyPressListener. So we'll say protected $keyPressListener. And this comes from the Chewy package. And for browsing, we're going to say this listener.
And this comes from the Chewy package. And for browsing, we're going to say this listener. And since we're going to be setting up different key press listeners for different states, a good rule of thumb is to always clear existing key press listeners out the gate. So let's say this clear existing. We're going to always listen for quit. So what do we want to do here? We want to navigate this table. We want to change pages using the left and right arrows. So as we press the right arrow, the page should increase.
We want to change pages using the left and right arrows. So as we press the right arrow, the page should increase. And as we press the left arrow, the page should decrease. So let's say this on right. And we'll provide a callback. And we'll say this.page equals Math.min(this.totalPages, this.page + 1). So we want to cap it at the total pages, but we want to add one to the page. And then on left, we want to do the opposite. I'm going to say this.page equals Math.max(1).
And then on left, we want to do the opposite. I'm going to say this page equals max one. This page minus one. Okay. And then we want to listen. Great. Clearing existing, listening for quit. We're set up our left and right arrow keys. And without doing anything to the renderer, this should show us the different pages of our table.
And without doing anything to the renderer, this should show us the different pages of our table. Let's check it out. Okay. So if we run our scripts, let's see. listener. Oh, we didn't set up our listener property before we accessed it. So we're going to say this listener is equal to keyPressListener for this class. Okay. That should work now.
Okay. That should work now. Let's try that again. Okay. You can see we have our first page. And if I press the right arrow, it changes to our next page. Next page. And if we go back, so our left and right arrows are working. This is great. So we're able to navigate records.
Fix Column Widths8:17
This is great. So we're able to navigate records. But there's a little thing here. You can see that it's jumping around as we navigate records. And that's a little bit of a bummer. It'd be nice if it stayed the same width, so it's easier to digest as we navigate pages. Let's fix that. In our renderer, we need to figure out what the max length of each of our columns is so that it stays the same as we change pages. All right.
that it stays the same as we change pages. All right. So let's set up this columnLength variable, this columnLength variable. So we have columnLength. It's going to be equal to collect of array_keys(promptHeaders). So it's going to give us the keys of the headers. And we're going to flatMap this. And we're going to say for each key, we want the key. And then we're going to collect the rows. And we are going to pluck that key.
And then we're going to collect the rows. And we are going to pluck that key. And we're going to map it to its length. mb_strlen width values. We're going to say, OK, grab all of the widths of the values and get the biggest one. So now we have a collection of column lengths that are keyed by the data key. And the value is the max length of that column for all of the rows. So now let's set up a new $rows variable that is going to be a collection of our prompt of our prompt visible rows. And we're going to go ahead and map these rows.
of our prompt visible rows. And we're going to go ahead and map these rows. And for each row, we are going to collect the row. We're going to map the values of that row by their key. And we're going to say mb_string_pad(value, columnLengths['key']); And then we're just going to get all to convert that back to an array and all to convert that back to an array. And now this is our variable that we're using for the table. So we're basically saying map over all of the rows for each of the values. Pad that value out to the max length of that column so that all of the values are the same.
So we're basically saying map over all of the rows for each of the values. Pad that value out to the max length of that column so that all of the values are the same length now. Let's see if that did anything for us. OK, so you see that already on this first page, there's some padding here that wasn't there before. See how that was so tight before? Now there's some padding. So you can see something has changed. So let's see.
Display Page Indicator10:42
So you can see something has changed. So let's see. As we navigate back and forth, the width of the table stays static. So we're able to navigate and comfortably look at the table as we navigate because it's not jumping around anymore. Great. This looks so much better. Let's continue by adding an indicator of what page we're on. Let's just simply add a new line. So we'll add a little spacer between the table and the indicator.
Let's just simply add new line. So we'll add a little spacer between the table and the indicator. And we'll just add some text down here that indicates which page we're on. This line, this dim, we'll just dim this text, page, prompt, page, so the current page, this dim of prompt total pages. OK, so we're just saying page blank of total pages. Let's see what that looks like. Cool. Page 1 of 10, 2 of 10, 3 of 10, and so on and so forth. Great.
Add Row Selection11:38
Page 1 of 10, 2 of 10, 3 of 10, and so on and so forth. Great. This is looking really solid. Let's keep going. Next up, we want to browse entries of this and ultimately select a value from this table. So we need to have some sense of a selected value and a visual indicator that a value is actually selected. So first things first, let's add an index to the state so that we can track the selected value. Up here, we'll say public int $index = 0;
value. Up here, we'll say public int index equals 0. And how will we select a value with the up and down arrow keys? So as we move up and down, it'll select a new value from the table. So we can say on up, we are going to set the index equal to max(0, this.index - 1). So as we go up a record, we'll go all the way down to 0, but not any further. And on down, we'll say this.index is equal to min(this.perPage - 1, this.index + 1). So all the way up to the max that we allow per page. Now as we switch pages, we want the index to go back to 0.
So all the way up to the max that we allow per page. Now as we switch pages, we want the index to go back to 0. So as we press left and right, by default, the first record of that page should be the one that's selected. So let's add that here. So we'll say this index equals 0. We can just move that down here as well. OK, so this is great for the interactivity. Let's now add a visual indicator to the table so that we know which record is actually selected. So we're going to set up a variable up here called selectedStyle.
Let's now add a visual indicator to the table so that we know which record is actually selected. So we're going to set up a variable up here called selectedStyle. And the selectedStyle is going to be a new table cell style. And this is something used by the Symfony library. We're going to set the background equal to white and the foreground equal to black. And that'll be our selectedStyle, so it'll be inverted. We now need to set that row equal to that style for each of the values in each cell. So we can do that by actually just directly accessing the array. So at the index that is currently selected, we're going to say collect(rows)->prompt[$index]->get(values)->map over them.
So at the index that is currently selected, we're going to say collect rows, prompt, index, get the values, and map over them. And we'll say for this cell, we're going to make it a new table cell with the contents of the cell. And we're going to set the style equal to the selected style. And this is something that Symfony understands. And since the table helper from Chewy is using the Symfony table under the hood, it understands how to interpret this data. So then we can say all and convert that back. And that should actually get us a selected style on our currently selected row.
So then we can say all and convert that back. And that should actually get us a selected style on our currently selected row. Let's see what it looks like. OK, great. So now we're here in our application. And as we go up and down, you can see that we have a row selected. And if we press right, it goes right back to that zero index. If you press left, it goes back to that zero index as well. So this is already looking pretty great. This is coming along really well.
So this is already looking pretty great. This is coming along really well.
