FRIDAY TECH WORKSHOP: Building A Custom Widget Request Form With Tag Inputs! ️
hey welcome back to another Friday Tech Workshop I'm Joseph a senior developer Advocate with Appsmith and if you're new to these workshops every Friday at 10: a. we're going to have some type of Workshop whether that's live coding a community show and tell it could be demoing a new feature or ask of The A Team where we'll bring on other team members and go through do Q&A or maybe look at questions from Discord so tune in every Friday at 10: a.m. for something different today what we're going to do is use a custom widget to build an app to request custom widgets so if you're not familiar with custom widgets they came out recently and it's a single widget that can let you build just about any other widget we already have over 45 widgets in apps Smith but occasionally there's a request for something that is very specific that you know not a lot of users need so this one widget will let you make just about anything we've got a backlog of some uh requests in GitHub for different widgets that we haven't been able to build yet and this app that we're going to build today is going to let us prioritize those and figure out what the community needs and then either me or somebody else from the team or even people from the community can look at these requests see which one has the most votes and then submit your own custom widget to the community portal so right now we have uh templates that the community can submit and we're working towards being able to uh submit your own custom Widgets or building blocks so it's not just templates um will also involve packages and uh the reusability components that are coming so lots of cool things on the way there today we're just going to make a request form app basically um but since it's for custom widgets we're going to throw in a custom widget in this request form so with that in mind uh I wanted to uh just take a minute to talk about like the back end I'm going to use and why I chose it uh we we want to be able to upload some screenshots you know we uh want the ability for the user requesting a custom widget to give us more than just text and if they can show a similar widget or interface from another app and I I wanted some easy way to insert the images and a single backend um instead of you know Google Sheets plus uh an S3 bucket or something so I'm using bass row because it allows the file uploads and you don't have to go upload it somewhere else first um with air table you can also upload files but if you do it through the API you have to send a URL you can't just upload the file directly so with uh Baserow it's similar to air table in the structure in the use case and whatnot but the API will let you just upload the file directly so I'm using that for the backend today I'm going to skip past setting up a data source because I've got some other videos out there about that so check the link in the comments if you're looking to use Baserow for your data source and want to see how we got set up here to start so I already got a connected we're going to jump right into the build and uh with that in mind if you guys have any ideas for custom widgets let us know drop a comment below uh you can comment on the video here or in the portal or feel free to reach out to me on Discord all right let's get to building all right so before we start building the app just want to walk through the back end real quick I've set up just a couple of tables for a a request for a custom widget so like What's the title of the widget and the description who's requesting it and then if there's an existing issue um sometimes it's one that's already in GitHub and we're going to build a custom widget to solve that so you could put the link here if there is one and then some tags and this is what I'm going to use for the custom widget so we're building a form and uh we'll use a regular form like widget but then inside of there I'm going to try using a custom widget to create this style of tag input and then we've got a place to put a screenshot for whatever type of custom widget the user is requesting they might uh they might have like another library or like a different app they want to take a screenshot from to show what what their end goal is and the rest of this stuff is going to it won't be in the form it's like follow-up stuff um so I've got a field that I can set it active or if it's no longer an open request if we close it out you know then I can filter it out um and then there is somebody can submit a widget to uh to complete that request and so the widget submission table here will let somebody uh upload you know a link to their custom widget and which request are they filling here so I have the the one request um from the request table is selected and this particular request I chose because it's actually um there's already a widget for it and so I've got a record that I can have in the request table and in the table that's showing somebody's completed it so uh BAGI an engineer from our team here has created this Json editor uh Json custom widget and so this takes care of this request I'm I'm building this data on um this app on top of some data here where I've got one good uh request one submission and then votes so I've got a separate table where people can vote on a request you might uh you might go in there to enter yours and you see the request has already been made so you can just upload it so I've got those three tables that we're going to build on top of um and I have already connected the data source and just set up a single query so I'm getting back that one row for the request and we're going to build everything else uh I skip past creating the data source because there's already a couple videos on that for bass row so if you need a hand setting up your base rad data source just check out the link in the description we we have another video on that and uh so the UI here I've got an empty app the only thing I have is this one query pulling back the one table for requests and we'll go ahead and start building on top of that real quick I want to show um the custom widget that I'm going to use is a tag input and this is already built as well this um tag input I just submitted this template to the Community Library so this is a form this whole Outer container is a regular form widget but this one input here is a custom widget with tags so like rest apis is in the autocomplete list here and graphql and so um you can Define the list that you want to pre-populate this as the uh drop- down values and this is already built I'm just going to copy this custom widget and we'll build on top of that and just like the Bas row data source thing I'm using a few existing things that we've already done videos on so we're just going to going to jump into the build and uh again check out the link in the description for this template and for the Baserow data source so we'll go ahead and get started with the build then and uh also real quick I want to show this uh library that I'm using here this is coming from um that's not the one here we go uh J cubic on GitHub this is a tagger library from J cubic and uh just wanted to give him a shout out and thanks for making this open source it's a really cool library and it's given us a quick easy way to make this tag input in Appsmith All right so I'm going to start out with my blank app here I've got some data coming back and we'll go ahead and connect this to a table uh we could drag in a table widget but there's this assistive binding here uh you can just choose what type of widget you want to connect this data to and you'll notice that the data coming back it's an object it's not an array of Records directly it's an object first and then there's this results array inside of it so when you go to connect your data you have to reference the right part of that um this assist of assisted binding does that for you it knows that there's uh data do results so it just makes that connection for you so we don't need to see the IDS here I'm going to turn those off and so this would be where you could vote um the table here I think we're going to need a form for submitting a new one and um I'm going to put that in a modal so what I'm going to do is drag in the button for adding a new one and then we'll say that the onclick event is to open a modal show modal and we don't have a modal yet so you can make a new one right here and it connects it so this modal is where we're going to put our form um and to go ahead and get rid of these buttons because we'll have our own buttons from the form so I'm using a regular form widget instead of a Json form because we have that one field for the tag inputs and I'm going to copy both these inputs and the tag here even the buttons I think just going to copy all of that and put it inside of my form which is inside of the modal um get rid of these because I just copied some buttons all right so this form is going to be to submit uh a new request you know for a new custom widget so we'll have the the name of it there was a description um going to change this date picker we need the description field what am I doing I want the input and then we'll set it to a long text okay so this will be the description and then this custom widget here is our input for the tags um I think that field is called Tags over here let me double check yeah so we we'll have a custom widget to supply these values and let us pick from that um now let's start a query to create a new record so that we can connect it to the submit button and I want to get at least the name and the description and these tags working uh we may not add all the fields during this video but basically uh I want to at least get a new record created using this custom widget as an input and then we can go on to like uh the voting part maybe so we'll see how much we can get done in an hour here okay let's check out the Baserow API docs to get the right format uh for the body here to create a new row so we're sending a request to uh the the table for requests and so in this uh custom widget request table if we want to create a new row this is the expected structure here so the API endpoint is going to be zoom out a little here so you can see all of it uh create row here it is after SL API it's database rows table whichever table number and then this user field names equals true that's um so that you can reference everything by those names that you've given it instead of the ones that are coming from the uh back end that's like just a random ID so this is the right Endo now I need the body and you can also copy the entire thing here and import import the curl request um but I've already got a data source created and so I want to make the API underneath that data source so here's the the body this is what we would send if we were doing all of the fields paste this in here and this is just the the raw Json right now um so I can't reference and use JavaScript in here yet so I'm going to select all of this and just wrap it in double curly braces so that it'll evaluate and I can write you know and reference different things inside of this so all right uh we'll skip the screenshots for now we are going to get to the file upload here in a little bit and then votes tags that's what we want so I'll leave out the GitHub issue for now and we want to send an array of tags from that custom widget um we have a couple inputs that we can connect here which I should go name those before we reference them uh because it's you know you're trying to remember which one's input one or input two so I like to use a prefix um always three characters underscore and then whatever the purpose or the use case is here so this is the name this helps with autocomplete because you can just start typing the type of widget and then you'll get a list of just your inputs you know as long as you're consistent with with your prefix so then we have this custom widget for the tags and now I can reference those in the query so the title here is coming from our input name. text the description has an input um I could go ahead and set this to True automatically as they're created or just leave it out and it's uh then I would have to enable it before it shows up that's the idea is just to have a way to filter them out so that uh the table isn't showing ones that have been completed so we'll go ahead and and leave it active automatically but it doesn't need to be on the form we'll just uh add that value in ourselves same thing with the email here I'm going to reference the Appsmith user and we can get their email email um okay so the tags let's check out the custom widget um I kind of skipped over this because the widget's already built it's out there it's in the community library but just real quick let's take a look at how it works so I'm passing in an array a list of tags that I want in the autocomplete list and then this custom widget if you go to the edit source so there's not much the HTML uh it Imports a script and uh I'm using tagger and then there's also some CSS that goes with that and then just an outer div for the whole widget one for the label and a input so not a lot going on there um I added some styling that uses the variables to get the radius and the colors from the apps Smith theme so it'll change with your app um and so the Mo majority of the codes you know the JavaScript but it's still pretty simple so you identify the you know the tag the uh the component or the HTML element that you want to use for your input and then on ready when app Smith is ready you have this function that creates a new tagger instance so uh VAR tags is and then tagger is a method that's available because we imported this script here and it's uh input is the item you know which element are we initializing here and the rest of that's just config uh you know it's not a a function or anything this is just saying here's the setup options and that's all based on the the repo that I showed a minute ago from tagger so here I'm passing in the list of tags from the the model so outside of the custom widget editor there's this place to pass data in and also read it back out and then when the input is updated here it's going to save that value back to selected tags so it's coming in as tags just the available ones and when it gets edited then it gets saved to selected tags so let's try that out and if I put a value in there it's updated the model and now there's this selected tags property and it's got both values but it's a single string um because it's in inside HTML element it's just the value of an input right so it has to be a string or a number um so let's look back here and if I go ahead and edit it from this side now we'll see that the um the model this is the default model just what it gets initialized with but it's not the live model so if we go back to the query now here's an array we need to replace this with um an array coming from that string Ren so there's custom tags that's our custom widget it has a model and inside of there there's the tags array that I'm supplying and then the selected tags of the string which gets returned we're going to take that string and split it by commas and that should give us an array and so if I look at the evaluated value here you can see that the tags property it's getting that string splitting and now we have an array so I think we got just about everything we could run this and see if it creates a row um but what I want to do is connect it to the UI so we've got this submit button and I want that to First create the record so create a request if that's successful we want to see the results in the table so we have to get the entire list again so we'll get requests and one final call back here just to close the modal and that is modal one I haven't named it yet so now we should be able to create a new um request here see if the tag part works all right so we have one request got One queued up here let's see what happens ah first error got two errors so let's check out the logs here that's it okay and just in case there's nothing in there I'm going to use optional chaining so this doesn't give me an error when there's no value so you can see that there's a single value value there is no comma to split by and it gives me an array of just the one value and then if I come back and we'll add another value and it's splitting that string and now we have an array with two values so it's almost ready I think I could send a request right now and it would it would try but it's going to fail because these are not the right values um I need to pass in the values that are allow for this one field here in my tag inputs and I I created a couple values just based on the editor when you're creating a new widget there's there's sections here for um each type of widget you know there's inputs and buttons and so if you're building a custom widget what type would it fall under um just as an example something to use this U this tag input so let's take what we're we're going to need to do is make a new query to get those values um I could hardcode it and you know copy and paste all the values from the other um you know just by looking at it from here but I want them to be dynamic so if I change it from here I want the app to be able to read that new drop down option and make it a new value in the custom widget so to do that we can use the fields endpoint instead of listing um rows we want to list all the fields and inside of these fields let's see is it this one or this one here we are so this endpoint gives us the definition of the back end and you know what are these allowed values so I think we can copy that request where is it right here that's the that's the authentication one so we want to get the fields um it's API database Fields whereas here it's database rows right so same table but we're going to get the fields so what I'll do is make another git request on that same data source and we'll get the list of tags let's see if this is working so this gives us all the fields and in one of those is that array of the options that are allowed here we are select options so it's a multi multiple select field with select options and then inside of there is our list of inputs or buttons or whatever so before I can connect this API and make it Dynamic to feed that um we got to drill down to this one field find that array map over it and get just the values so I'm going to add a new Js object now and this will be a regular function not a async so first we'll just return the G tags. data and we want to I'll just
return it all directly just so we see what we're starting with so this is an array and I want to find the item the first um element in this list where the name equals tags so do find this will return a single object for the first one that matches a condition we want to look at all of the fields um so a single one would be a field and for each field if the field. name equals tags we want to return that item so now it's just a single object instead of the array of fields but I want to get down to this select options array almost there so now we'll return dot tags let's see that's the object type I don't why am I not seeing the tags field let run this again select options yeah that's the next level select options and then map over the array okay now I'm getting just the array so now that I have the list of fields uh rather the uh list of values allowed for this field now I can map over this one more time and get just the values um it's getting a little long so I'm not going to return this just yet we'll call this uh tag field it's actually the options and then we'll return that array mapped and we want to take each option and return the option. value all right so now we have an array of the actual values coming straight from the back end so if I go changing and adding a whole new Option here or rename one or even change the color those values are getting sent to apps Smith so we can use it so now what we'll do is I want this to run on page load make sure we always have that list um actually that's the function we also want the query that gets the tag so we want this to run on page loadad and now we can swap out this hardcoded spot and we'll reference our jsobject 1.g tags and then
this evaluated value here shows that it's taking the response from the API uh filtering it for that one field then mapping over the options and giving us just the values so it's still prepopulated with databases and that's part of the custom widget code we'll go edit that source and that's just part of the example um that I copied it from so the input here it's default value or when it's first initialized um it had that example in there I don't want that for this use case so I think we're about there this should be able to work and create a record now so we'll try adding a couple values here um input yep uh media I think was an option there we go and so when I submit I want this to um I want this button to First create the record and then get all of the records to fill the table so we can see it and also close the models because a table behind this so this button we'll do is the onclick its first action is to create uh I've already got the create API created um then I want to run the git requests so I can see the value and there's this final call back here to close the model so let's test this out and no errors it closed the modal and I see the second row right there cool so uh we're able to add a row it's got the two text Fields um this active thing is is auto stamped uh the creation time that's like a field automatically set in base row and it looks like let's see did our tags work it did so we should have some tags on this new row here oh I'm on the boats yes it worked uh cool so we can use a custom widget inside a form and capture those tag values so that it looks like tags in appmi uh and then send it to Basse row to another field that's also displaying like tags so I think that's good for the submission form um as far as like the regular Fields so next I want to get this um screenshot to upload and so with bass row you can upload files directly through the API um air table does not support that if you want to upload a file you have to do it through air table's website uh if you do it through the API you can only upload a file if you have the URL which means you've already uploaded it somewhere else first like S3 or whatever um so what we're going to do is upload the file directly to the files endpoint here and these are different tables um in a work space and then the file upload it's not part of a single table it's just the whole workspace um once you get that response it's going to give you back uh a name property here and then you can use that name put it into a row when you do a create or an update over here and that one field for the screenshots um you can send it the name um just the name and it will link up with that file so that's what we're going to do next we'll we'll use this file upload endpoint so it's API SL user files and then upload file and we're going to let see we should add a file picker to here as well so I'll grab a file picker widget and this will let us upload our screenshots all right I'm going to create another query under the same data source a post request and that was Slash API user fields or user files and then upload file um and then for the body let's look at the API docs here so it says multiart and file so we're going to set this to multiart uh the key is file and it is a file right we're sending you could send text or whatever uh data format here we're going to send an actual file with the key of file and then we want to reference the file picker here the files array and we're only going to upload one so it would be the first element there in the array and that's just about it I think this file picker um needs to be set to Binary based on U Bas Row's API it just depends on where you're sending it but for this particular one um you want to use multi-art form uh a file sent with that key of file and as as binary so let's see what we once we attach a file here we need it to be uploaded to base row get back that name and the URL so that we can attach it to air table and we just need to have the URL ready so that we can add it to the form when we submit so this file picker is going to do its own upload and get the result and then we'll use that value in this API when we submit the whole thing so let's go look at the file here um I need to name this so if we upload a file we should be able to reference that link here when we go to create um sorry we're creating a record here we go and so right here is the other field we want to add can't remember if that was plural yes so screenshots it's looking for an array um with an object that has a name property and then the value of that name is going to come from the upload file I have not ran this yet so there is no data but inside of there there's going to be a name so let's go ahead and and run it so we can get that value and have something extra should to bind right here um because it it doesn't know what's going to be inside data yet so do test three add some tags and I'll attach a file all right so once this gets uploaded um I want the action of uploading the file right here to run the API and get that URL for us so on the file picker I'm going to set the on file selected to just to upload the file we don't want to take the name and add it to the row yet we just want to kind of have that value on deck and ready to use it so I need to clear out that file I just added because there was no trigger yet let me start over and this should upload the image now just the file itself but not the whole um row oh we got so it failed but it did attempt there so let's check out the query uh upload file all right um and now it works okay maybe the file wasn't uh being read that first time because I refreshed I don't know so it uploaded that's uh what I was expecting it just didn't do it the first time um there it is the name so there's like lots of different URLs that come back here's the thumbnail and then that's an object um but what we what we want is the name here and to save that back to a row so that's on the create request go back to the body screenshots and so after upload file runs there's a DAT dotame and we're going to send an object with a name property in an array you could send more than one um but we're just going to do the one so it's just about there let's see when you upload a file when you insert it into the file picker that's going to get the name it'll upload it and get that value back so it's ready for us and then here the submit I think I've already got that linked and working so yeah we're ready to test it out Let me refresh all right so let's go ahead and try test four maybe I don't know where we're at so this time we will see if the file upload works it should upload the file to air table and get that link right away and so you'll see that loading that worked and so now can we create a row with this okay no error and there it is so we got our file to upload it created a new row we can use the tag input so pretty much got everything working that I wanted here on the form side um got like another text field or two I could add but I think I want to go on to the voting part now and like submitting a a widget so got uh only about 10 15 minutes to go here so we'll see how far we can get uh let's let's work on voting so I made a separate table for votes because I don't want somebody to be able to vote more than once or delete somebody else El's vote you know they should be able to remove their vote but not somebody else's um and I thought about just having a column for vote just a number and then you click a button and it increases that number but then there's no way to track who added it you know so we've got a separate votes table and it's just the time it came in which request uh is this vote for and who submitted it so that I can only allow you to edit your own so first we'll do a query to get all the votes and we want to be able to um have a button here that lets you vote on something but only if you haven't voted on it yet so I'm going to add the button We'll add a new column to the table call it up vote and I'm going to make it an icon button uh we don't have the query yet but I want to put this where I can see it so I'm dragging this button to the top here or to the left on the table okay and when we click this button it should take the row that we're on and our email and create a new vote so let's go to the new query this will also be a post request to create a a new record so we'll go to the vote table here and if we wanted to create a row in this table then it's this end point uh post request okay so we'll copy this from the docs here and into the body so it's a request uh it's an array but we're only going to have one and then the one value that we're sending here is going to be the selected Row from the table so I can go to oh I got to wrap all this in double curly braces so that I can reference the widgets and then right here we'll do table one. selected row um actually triggered row because they're clicking a button and that row might not even be selected so triggered row. that's the request that they're wanting to up vote and then we'll get their email and that should be it that will create an upvote um we also want to oh this should be a post and then we want to get all of the votes so I'm going to copy this API and we'll make this one get votes um I can take out the body because I copied that from a post so now we have an API to get the list of votes and one to create a new vote so next let's make that button actually create a vote and then get all the votes again so we can see it um also want a column here that um where you can see the number of votes and there is this uh relationship column from base row so it's an array of objects representing each vat we just want to see that as a number um um might just be able to get the length here yeah that works um and we'll say that's a number so now on this button uh back out of that column and go to the up vote button here I'm going to make the onclick run our new create vote query and on success get votes um I don't even know if I need to get the votes or even need a separate API for it I need to get this data again because it has the relationship in it um and I and I'm counting it right here so after I add a vote I want to get requests again that should work okay this one has one vote um I'll vote on this one cool that works but it is going to let me do it again and I don't want people to be able to vote on the same one twice so the next thing is I have to disable this button under certain conditions if that person has already voted on that particular request so let's work on the disabled State here uh we'll make this JS and I got a feeling this will end up being too much JS to put right here but I want to do the at least the first condition and see see how much we can do here so we're going to look at the current row uh for each one of these buttons is the current row ID equal to um and then we want to see if it's like if if any of the votes have this ID um and we'll disable it if it finds a match so actually I think I want to start with the votes and then we'll put this inside of a a find or a map um we have get votes. dat inside of there we'll look at the results array and then let's see if we can find a
vote where the vote dot which request it goes with which is an array so if the first request because there should only be one if its ID equals the current row do ID that would be the condition so if it finds something here um I want the button to be disabled this is returning an object instead of true or false but if it does return an object I can negate it and turn something that's truthy into two false and then negate it one more time and now it's actually true or false so since there's a vote for this one it's disabled um that one should be disabled too why is it not doing both so if it looks through and finds a request where the first one's ID is equal to the current row ID that looks right the other condition would be that um the request Z do user or email or something is going to be yours request it's just email so the other condition here is and the vote. request first element no vote. email equals and and then we'll say the appmi user. email all right so if it finds any object in the whole list of votes um where the vote request that it goes to is this the one that we're on here then if that's true and it's your email then that button should be disabled um I could make a toggle button so that it like unv maybe but that would get kind of complicated so I think I'll just have a separate button to delete your votes so this is like a list of uh the requests and it is showing how many votes it has and it's not letting me vote on that one why is it still letting me do this one that one should be disabled too let's look at the back end getting rid of this test junk okay so let's refresh that and try one from the start here so this already has a vote there it is it took a second and then it showed disabled um I think maybe the query to get new votes just hadn't ran that's what it is so once I do a vote I do have to to run that query so it has so it can look that uh that find my condition that I just added to Loop through and decide if this is disabled it has to be up to date um with all the votes which means right here when I create when I do the upvote I need to get the requests and get the votes um so I'll have one more call back here to get votes. run that should work so now let's add
a new request and then I'll vote on it and I should only get the vote once okay let's see what happens okay it added the new row got the image working so now I can vote on this new one yes and now it's disabled so uh we're about out of time I'm going to end up adding a button to delete your own vote um maybe like some more work on the UI and whatnot but I think we got everything working that uh you know functional wise you can create a row you can upload a file and the tags are working so that was the main thing I wanted to work on today um One More Time wanted to give a shout out to uh J cubic on GitHub for making this tagger uh input library that I used um it's really cool to be able to add this feature to our apps and make it in a easy way that people can just copy this custom widget so uh thanks again for making that open source thanks for joining the workshop today and look forward to seeing you next week let us know where you're interested in for the next Workshop feel free to uh comment below or reach out on Discord and uh just give us some ideas for the next event it could be a community show in tell or a a Q&A with the team so kind of an open mix to different topics for these workshops but every Friday at 10: a.m. eastern we'll have something uh so we will see you next week thanks again and don't forget to [Music] subscribe
2024-03-13 13:54