B4J Tutorial [Pakai] Creating a Blog

Introduction

This tutorial is a continuous to [Tutorial] Pakai framework v6
I assume you already have the default project successfully running on development.
In this tutorial, we will continue to work with 2 libraries, MiniHtml and MiniORMUtils.

Let's Start​

  1. So far, we have 2 entities or I will call them models. They are Categories and Products.
  2. Let's leave Categories as it is.
  3. We will remove Products and create a new model call Articles.
  4. You can name it as Posts if you like but I don't want to confuse with the HTTP POST method.
  5. Right click on the ProductsHandler and click Remove.
    1762944088117.png
  6. Then we right click on the Handlers group and add a Server Handler.
    1762944294309.png
  7. Give it a name ArticlesHandler and click Ok.
    1762944436729.png
  8. You will see a new handler class is created.
  9. Inside Class_Globals sub, start typing pakai until you see a dialog pops up showing the (code snippets) Pakai handler Globals v6.00.
    1762944770234.png
  10. Press enter and you will get the code added as showed below.
    1762944878001.png
  11. Now delete the code after line #9.
  12. Start typing pakai again and this time we select the second code snippet.
    1762945038170.png
  13. Now there are more code added for you and the word plural is highlighted in amber colour.
    1762945100106.png
  14. Press Backspace and type the word articles. Press Tab to go to next highlighted word.
    1762945469356.png
  15. Press Tab and now it is highlighting on the word Model.
    1762945578198.png
  16. Change it to Articles and press Tab.
    1762945686809.png
  17. Press Tab to skip on the word SERVER_URL.
    1762945841411.png
  18. Similarly, change the dbtable to tbl_articles and singular to article.
    1762946286814.png
  19. When the cursor returns to top of page, you know you can confirm the changes by pressing Enter.
  20. Now you have a new server handler for Articles.
 

aeric

Expert
Licensed User
Longtime User

Create Articles table​

  1. On the Modules tab, search for CreateDatabase. Click on the word Database.
    1762947172235.png
  2. Scroll down to the line where you see the code below.
    CreateDatabase:
    DB.Table = "tbl_products"
  3. We can modify the code for creating tbl_products to create our Articles table name tbl_articles.
  4. We can also replace the sample data in tbl_categories.
  5. You can replace the code inside CreateDatabase sub with the highlighted code below:
    CreateDatabase:
    Private Sub CreateDatabase
        ' Irrelevant code hidden
        DB.Table = "tbl_categories"
        DB.Columns.Add(DB.CreateColumn2(CreateMap("Name": "category_name", "Null": False)))
        DB.Create
    
        DB.Columns = Array("category_name")
        DB.Insert2(Array("Life"))
        DB.Insert2(Array("Jokes"))
        DB.Insert2(Array("News"))
    
        DB.Table = "tbl_articles"
        DB.Columns.Add(DB.CreateColumn2(CreateMap("Name": "category_id", "Type": DB.INTEGER, "Null": False)))
        DB.Columns.Add(DB.CreateColumn2(CreateMap("Name": "article_title", "Null": False)))
        DB.Columns.Add(DB.CreateColumn2(CreateMap("Name": "article_body", "Null": False)))
        DB.Columns.Add(DB.CreateColumn2(CreateMap("Name": "article_status", "Type": DB.INTEGER, "Null": False, "Default": "0")))
        DB.Columns.Add(DB.CreateColumn2(CreateMap("Name": "article_featured_image", "Type": DB.BLOB)))
        DB.Foreign("category_id", "id", "tbl_categories", "", "")
        DB.Create
    
        DB.Columns = Array("category_id", "article_title", "article_body", "article_status")
        DB.Insert2(Array(3, "New to Blogs", $"<H1>Welcome to My Blog</H1>
        <p>It's my first time writing a blog!<br>
        I am very excited to share more on my life journey. 😉</p>"$, 1))
    
        Wait For (DB.ExecuteBatch) Complete (Success As Boolean)
        ' Irrelevant code hidden
    End Sub
  6. Now go to Main module tab.
  7. Enable the Macro or press Ctrl+click on #Macro: Title, Objects to open the projects Objects directory.
  8. Open sqlite.ini using Notepad or any Text Editor.
    1762948869008.png
  9. Edit the value for DbFile to blog.db then save the file and close it.
  10. Back to B4J and open code tab for ArticlesHandler class.
  11. Under Handle sub, update the following code to point to "/".
    Before:
    Handle:
    If path = "/articles" Then
        RenderPage
    After:
    Handle:
    If path = "/" Then
        RenderPage
  12. Under CreateArticlesTable sub, modify the code as follow:
    CreateArticlesTable:
    Private Sub CreateArticlesTable As Tag
        Dim table1 As Tag = HtmlTable.cls("table table-bordered table-hover rounded small")
        Dim thead1 As Tag = Thead.cls("table-light").up(table1)
        thead1.add(Th.sty("text-align: right; width: 50px").text("#"))
        thead1.add(Th.text("Title"))
        thead1.add(Th.sty("text-align: center; width: 120px").text("Actions"))
        Dim tbody1 As Tag = Tbody.init.up(table1)
    
        DB.SQL = Main.DBOpen
        DB.Table = "tbl_articles"
        DB.Columns = Array("id", "article_title AS title")
        DB.OrderBy = CreateMap("id": "")
        DB.Query
        ' Irrelevant code hidden
    End Sub
  13. Under CreateArticlesRow sub, modify the code as follow:
    CreateArticlesRow:
    Private Sub CreateArticlesRow (data As Map) As Tag
        Dim id As Int = data.Get("id")
        Dim title As String = data.Get("title")
    
        Dim tr1 As Tag = Tr.init
        tr1.add(Td.cls("align-middle").sty("text-align: right").text(id))
        tr1.add(Td.cls("align-middle").text(title))
    
        Dim td3 As Tag = Td.cls("align-middle text-center px-1 py-1").up(tr1)
        ' Irrelevant code hidden
    End Sub
  14. Go to AppStart (Ctrl+6) and update the code for calling the server handlers.
    AppStart:
    Sub AppStart (Args() As String)
        App.Initialize
        ' Irrelevant code hidden
        App.Get("", "ArticlesHandler")
        App.Get("/categories", "CategoriesHandler")
        App.Rest("/api/articles/*", "ArticlesHandler")
        App.Rest("/api/categories/*", "CategoriesHandler")
        App.Start
        ' Irrelevant code hidden
        StartMessageLoop
    End Sub
  15. Now we can run the project again.
  16. A new database should be created with the new Articles table.
  17. Navigate to the index page and you should see a page looks like following:
    1762951865341.png
  18. The app is not fully working yet but we will fix it later.
  19. However, it is good to see our articles table is loaded successfully.
 
Last edited:

aeric

Expert
Licensed User
Longtime User

Update Articles Page​

  1. When we click on the "Add Articles" button, a modal dialog shows only a Name field.
    1762964333682.png
  2. We want to change this field to Title.
  3. Stop the debug if not already done so.
  4. Open HandleAddModal sub for ArticlesHandler.
  5. Find the following lines:
    B4X:
    Dim group1 As Tag = modalBody.add(Div.cls("form-group"))
    Label.forId("name").text("Name ").up(group1).add(Span.cls("text-danger").text("*"))
    Input.typeOf("text").up(group1).id("name").name("name").cls("form-control").attr3("required")
  6. Edit the word "name" to "title" for the following attributes.
    a. forId("name") -> forId("title")
    b. text("Name ") -> text("Title ")
    c. id("name") -> id("title")
    d. name("name") -> name("title")
  7. The updated code will look like following:
    HandleAddModal:
    Dim group1 As Tag = modalBody.add(Div.cls("form-group"))
    Label.forId("title").text("Title ").up(group1).add(Span.cls("text-danger").text("*"))
    Input.typeOf("text").up(group1).id("title").name("title").cls("form-control").attr3("required")
  8. We want to allow user to enter article Body using a bigger box.
  9. Highlight the above 3 lines and press Ctrl + D key to duplicate the lines.
  10. Change the variable from group1 to group2.
  11. Update the values from "title" to "body".
  12. Change the text input to Textarea type.
  13. We can also set the rows to "3" to give the textarea more height.
  14. Lastly we want to add a selection dropdown for the publishing Status.
  15. We can copy code from above group1 again and make changes.
  16. We add options with value "0" for "Draft" and value "1" for "Published".
  17. Remember to use Bootstrap "form-select" CSS class to apply for the Dropdown.
  18. Here is how the code looks like:
    HandleAddModal:
    Dim group1 As Tag = modalBody.add(Div.cls("form-group"))
    Label.forId("title").text("Title ").up(group1).add(Span.cls("text-danger").text("*"))
    Input.typeOf("text").up(group1).id("title").name("title").cls("form-control").attr3("required")
    
    Dim group2 As Tag = modalBody.add(Div.cls("form-group"))
    Label.forId("body").text("Body ").up(group2).add(Span.cls("text-danger").text("*"))
    Textarea.rows("3").up(group2).id("body").name("body").cls("form-control").attr3("required")
    
    Dim group3 As Tag = modalBody.add(Div.cls("form-group"))
    Label.forId("status").text("Status ").up(group3).add(Span.cls("text-danger").text("*"))
    Dim select1 As Tag = Dropdown.up(group3).id("status").name("status").cls("form-select").attr3("required")
    Option.valueOf("0").text("Draft").up(select1)
    Option.valueOf("1").text("Published").up(select1)
  19. Run the debug and click the Add Articles button to see the updated modal.
    1762967587742.png
  20. Nice! We now have 3 fields showing on the modal dialog.
 

aeric

Expert
Licensed User
Longtime User

Insert Article to Database​

  1. In the previous HandleAddModal sub, we have a form component created using MiniHTML that will be submitted to the URL "/api/articles" using POST method.
  2. This API endpoint is passed to HandleArticles sub inside Handle sub when it falls under the Else condition.
    Handle:
    Sub Handle (req As ServletRequest, resp As ServletResponse)
        ' Irrelevant code hidden'
        Dim path As String = req.RequestURI
        If path = "/" Then
            ' Irrelevant code hidden'
        Else If path = "/api/articles/table" Then
            ' Irrelevant code hidden'
        Else
            HandleArticles
        End If
    End Sub
  3. HandleArticles sub will handle the database operation for HTTP methods POST, PUT and DELETE which are corresponded to SQL INSERT, UPDATE and DELETE.
  4. From the code we can see the variable name is assigned from the Request.GetParameter("name")
    HandleArticles:
    Private Sub HandleArticles
        Select Method
            Case "POST"
                ' Create
                Dim name As String = Request.GetParameter("name")
                If name = "" Or name.Trim.Length < 2 Then
                    ShowAlert("Articles name must be at least 2 characters long.", "warning")
                    Return
                End If
  5. Let's change this to read the form data for Title, Body and Status.
  6. The code should look like the following:
    HandleArticles:
    Dim article_title As String = Request.GetParameter("title")
    Dim article_body As String = Request.GetParameter("body")
    Dim article_status As String = Request.GetParameter("status")
  7. Since Body is conflicting with a module in MiniHtml, we use article_body as the variable name.
  8. We standardized the variable naming for other fields with article_ prefix.
  9. Next, we use MiniORMUtils to check if an article with the same title already existed.
    B4X:
    DB.Table = "tbl_articles"
    DB.Where = Array("article_title = ?")
    DB.Parameters = Array(article_title)
    DB.Query
    If DB.Found Then
        DB.Close
        ShowAlert("Articles with same title already exists!", "warning")
        Return
    End If
  10. If it has already existed, we stop the operation from being proceed.
  11. We can show an alert message using ShowAlert sub.
  12. The code for inserting a new article should look like the following:
    HandleArticles:
    Private Sub HandleArticles
        Select Method
            Case "POST"
                ' Create
                Dim article_title As String = Request.GetParameter("title")
                Dim article_body As String = Request.GetParameter("body")
                Dim article_status As String = Request.GetParameter("status")
                If article_title = "" Or article_title.Trim.Length < 2 Then
                    ShowAlert("Article title must be at least 2 characters long.", "warning")
                    Return
                End If
                If article_body = "" Then
                    ShowAlert("Article body cannot be empty.", "warning")
                    Return
                End If
                If article_status = "" Then
                    ShowAlert("Article status cannot be empty.", "warning")
                    Return
                End If
                Try
                    DB.SQL = Main.DBOpen
                    DB.Table = "tbl_articles"
                    DB.Where = Array("article_title = ?")
                    DB.Parameters = Array(article_title)
                    DB.Query
                    If DB.Found Then
                        DB.Close
                        ShowAlert("Article with same title already exists!", "warning")
                        Return
                    End If
                Catch
                    Log(LastException)
                    ShowAlert($"Database error: ${LastException.Message}"$, "danger")
                End Try
    
                ' Insert new row
                Try
                    DB.Reset
                    DB.Columns = Array("article_title", "article_body", "article_status")
                    DB.Parameters = Array(article_title, article_body, article_status)
                    DB.Save
                    DB.Close
                    ShowToast("Articles", "created", "Article created successfully!", "success")
                Catch
                    ShowAlert($"Database error: ${LastException.Message}"$, "danger")
                End Try
            Case "PUT"
                ' Irrelevant code hidden
            Case "DELETE"
                ' Irrelevant code hidden
        End Select
    End Sub
  13. If the operation succeeded, we use ShowToast sub to close the modal dialog and show a Bootstrap toast. The table will also be refreshed using HTMX.
  14. Let's try adding an article.
  15. We get an error
    Error Logs:
    (SQLException) java.sql.SQLException: [SQLITE_CONSTRAINT]  Abort due to constraint violation (tbl_articles.category_id may not be NULL)
  16. It seems I have forgotten to add the input for category_id which is a Foreign Key to table tbl_categories.
  17. Let's fix this in the following tutorial.
 

aeric

Expert
Licensed User
Longtime User

Populate Categories Dropdown Dynamically​

  1. Unlike Status dropdown where we hardcoded the items, we can dynamically load items from the database.
  2. We can check the code in ProductsHandler which I already removed from the start.
  3. Let's add back ProductsHandler to the project to copy the sub.
  4. There is a sub name CreateCategoriesDropdown sub that we can copy to ArticlesHandler.
  5. Let's copy this sub and paste at the bottom of ArticlesHandler class.
    CreateCategoriesDropdown:
    Private Sub CreateCategoriesDropdown (selected As Int) As Tag
        Dim select1 As Tag = Dropdown.cls("form-select")
        select1.attr3("required")
        'select1.hxGet("/api/categories/list")
        Option.valueOf("").text("Select Category").attr3(IIf(selected < 1, "selected", "")).attr3("disabled").up(select1)
    
        DB.SQL = Main.DBOpen
        DB.Table = "tbl_categories"
        DB.Columns = Array("id", "category_name AS name")
        DB.Query
        For Each row As Map In DB.Results
            Dim catid As Int = row.Get("id")
            Dim catname As String = row.Get("name")
            If catid = selected Then
                Option.valueOf(catid).attr3("selected").text(catname).up(select1)
            Else
                Option.valueOf(catid).text(catname).up(select1)
            End If
        Next
        DB.Close
        Return select1
    End Sub
  6. Then we can also copy the code for group1 tag inside HandleAddModal sub from ProductsHandler to ArticlesHandler.
    1762972240294.png
  7. The code should now look like the following:
    HandleAddModal (ArticlesHandler):
    Private Sub HandleAddModal
        Dim form1 As Tag = Form.init
        form1.hxPost("/api/articles")
        form1.hxTarget("#modal-messages")
        form1.hxSwap("innerHTML")
    
        Dim modalHeader As Tag = Div.cls("modal-header").up(form1)
        H5.cls("modal-title").text("Add Articles").up(modalHeader)
        Button.typeOf("button").cls("btn-close").data("bs-dismiss", "modal").up(modalHeader)
    
        Dim modalBody As Tag = Div.cls("modal-body").up(form1)
        Div.id("modal-messages").up(modalBody)'.hxSwapOob("true")
    
        Dim group1 As Tag = Div.cls("form-group").up(modalBody)
        Label.forId("category1").text("Category ").up(group1).add(Span.cls("text-danger").text("*"))
        Dim select1 As Tag = CreateCategoriesDropdown(-1)
        select1.id("category1")
        select1.name("category")
        select1.up(group1)
    
        Dim group1 As Tag = modalBody.add(Div.cls("form-group"))
        Label.forId("title").text("Title ").up(group1).add(Span.cls("text-danger").text("*"))
        Input.typeOf("text").up(group1).id("title").name("title").cls("form-control").attr3("required")
    
        Dim group2 As Tag = modalBody.add(Div.cls("form-group"))
        Label.forId("body").text("Body ").up(group2).add(Span.cls("text-danger").text("*"))
        Textarea.rows("3").up(group2).id("body").name("body").cls("form-control").attr3("required")
    
        Dim group3 As Tag = modalBody.add(Div.cls("form-group"))
        Label.forId("status").text("Status ").up(group3).add(Span.cls("text-danger").text("*"))
        Dim select1 As Tag = Dropdown.up(group3).id("status").name("status").cls("form-select").attr3("required")
        Option.valueOf("0").text("Draft").up(select1)
        Option.valueOf("1").text("Published").up(select1)
    
        Dim modalFooter As Tag = Div.cls("modal-footer").up(form1)
        Button.typeOf("submit").cls("btn btn-success px-3").text("Create").up(modalFooter)
        Button.typeOf("button").cls("btn btn-secondary px-3").data("bs-dismiss", "modal").text("Cancel").up(modalFooter)
    
        App.WriteHtml(Response, form1.Build)
    End Sub
  8. It is fine if we reuse the variable name group1 here. You can rename the variables if you wish.
  9. We pass a value of -1 to CreateCategoriesDropdown so no item is selected when the modal is loaded.
    B4X:
    CreateCategoriesDropdown(-1)
  10. Let's try run the project in Debug to see the changes.
    1762973279055.png
  11. Stop debug and let's add the missing code for category_id when inserting a row for the articles table.
  12. Now the code should looks like the following:
    HandleArticles:
    Private Sub HandleArticles
        Select Method
            Case "POST"
                ' Create
                Dim article_title As String = Request.GetParameter("title")
                Dim article_body As String = Request.GetParameter("body")
                Dim article_status As String = Request.GetParameter("status")
                Dim category_id As Int = Request.GetParameter("category")
             
                If category_id = "" Then
                    ShowAlert("Please select a category for the Article.", "warning")
                    Return
                End If        
                If article_title = "" Or article_title.Trim.Length < 2 Then
                    ShowAlert("Article title must be at least 2 characters long.", "warning")
                    Return
                End If
                If article_body = "" Then
                    ShowAlert("Article body cannot be empty.", "warning")
                    Return
                End If
                If article_status = "" Then
                    ShowAlert("Please select a status for the Article.", "warning")
                    Return
                End If
                Try
                    DB.SQL = Main.DBOpen
                    DB.Table = "tbl_articles"
                    DB.Where = Array("article_title = ?")
                    DB.Parameters = Array(article_title)
                    DB.Query
                    If DB.Found Then
                        DB.Close
                        ShowAlert("Article with same title already exists!", "warning")
                        Return
                    End If
                Catch
                    Log(LastException)
                    ShowAlert($"Database error: ${LastException.Message}"$, "danger")
                End Try
    
                ' Insert new row
                Try
                    DB.Reset
                    DB.Columns = Array("article_title", "article_body", "article_status", "category_id")
                    DB.Parameters = Array(article_title, article_body, article_status, category_id)
                    DB.Save
                    DB.Close
                    ShowToast("Articles", "created", "Article created successfully!", "success")
                Catch
                    ShowAlert($"Database error: ${LastException.Message}"$, "danger")
                End Try
        ' The rest of code
  13. Let's try again to add an article.
    1762974064053.png
  14. It doesn't work. I really should not buy a lottery.
  15. This is because we are comparing Integer value with empty String.
  16. Let's fix it with the following code:
    HandleArticles:
    If category_id < 1 Then
        ShowAlert("Please select a category for the Article.", "warning")
        Return
    End If
  17. Now, let's finger crossed. Click on the Create button.
    1762974784013.png
  18. Well, it works this time.
 

aeric

Expert
Licensed User
Longtime User

Edit Articles from Database​

Up to now, can you follow this tutorial?
Each part has around 20 steps but most are explanation which I tried to make them as short as possible. The actual steps maybe only 5.
Let's continue with the Edit Articles modal dialog.
  1. When the Articles table is generated using CreateArticlesTable, we called CreateArticlesRow sub by passing a Map from the MiniORM for each row.
    CreateArticlesTable:
    For Each row As Map In DB.Results
        Dim tr1 As Tag = CreateArticlesRow(row)
        tr1.up(tbody1)
    Next
  2. In CreateArticlesRow sub, we pass the id at the end of URL to hxGet attribute of an anchor tag.
    1763008115241.png

  3. When the edit button (pencil icon) is clicked, it made a request to the backend to call the endpoint e.g /api/articles/edit/1
    1763008691669.png

  4. This will call HandleEditModal sub as you can see in the Handle sub.
    Handle:
    Sub Handle (req As ServletRequest, resp As ServletResponse)
    ' Irrelevant code hidden
        If path = "/" Then
            RenderPage
        Else If path = "/api/articles/table" Then
            HandleTable
        Else If path = "/api/articles/add" Then
            HandleAddModal
        Else If path.StartsWith("/api/articles/edit/") Then
            HandleEditModal
        Else If path.StartsWith("/api/articles/delete/") Then
            HandleDeleteModal
        Else
            HandleArticles
        End If
    End Sub
  5. We want to populate modal dialog with the data from database given the id from the endpoint.
  6. For now, we only have one name input field.
  7. We can copy the code from HandleAddModal sub.
    1763009679072.png
  8. Paste the code inside HandleEditModal sub.
    1763009967451.png

    Take note that a hidden input for "id" is already added in the modal form.
  9. We need to update the code for MiniORM for querying more columns from tbl_articles database table.
    Replace the following line:
    HandleEditModal:
    DB.Columns = Array("id", "article_name AS name")
    into this:
    HandleEditModal:
    DB.Columns = Array("article_title AS title", "article_body AS body", "article_status AS status", "category_id AS category")
  10. Replace the following line:
    HandleEditModal:
    Dim name As String = DB.First.Get("name")
    by this:
    HandleEditModal:
    Dim row As Map = DB.First
    We assign DB.First from MiniORM to a map call "row" so we can use it more than once later.
  11. Now we add variables for article_title, article_body, article_status and category_id.
    HandleEditModal:
    Dim row As Map = DB.First
    Dim article_title As String = row.Get("title")
    Dim article_body As String = row.Get("body")
    Dim article_status As Int = row.Get("status")
    Dim category_id As Int = row.Get("category")
  12. Then we can put the values to our modal form input fields.
  13. Start from the Dropdown component for category.
    Replace the code:
    HandleEditModal:
    Dim select1 As Tag = CreateCategoriesDropdown(-1)
    by this:
    HandleEditModal:
    Dim select1 As Tag = CreateCategoriesDropdown(category_id)
    So the Dropdown will select the category name based on the category id of that row.
  14. Next, for the Title input text, we add a new valueOf attribute to the line.
    HandleEditModal:
    Dim group1 As Tag = modalBody.add(Div.cls("form-group"))
    Label.forId("title").text("Title ").up(group1).add(Span.cls("text-danger").text("*"))
    Input.typeOf("text").up(group1).id("title").name("title").cls("form-control").valueOf(article_title).attr3("required")
  15. For article_body, to set the value to textarea component, instead of using valueOf, we use text attribute.
    HandleEditModal:
    Dim group2 As Tag = modalBody.add(Div.cls("form-group"))
    Label.forId("body").text("Body ").up(group2).add(Span.cls("text-danger").text("*"))
    Textarea.rows("3").up(group2).id("body").name("body").cls("form-control").text(article_body).attr3("required")
  16. For article_status, we need to check whether we want to make the item selected.
    HandleEditModal:
    If article_status = 0 Then
        Option.valueOf("0").attr3("selected").text("Draft").up(select1)
    Else
        Option.valueOf("0").text("Draft").up(select1)
    End If
    If article_status = 1 Then
        Option.valueOf("1").attr3("selected").text("Published").up(select1)
    Else
        Option.valueOf("1").text("Published").up(select1)
    End If
    Maybe this code can be simplified as one liner using IIf but here let's make this easy to understand.
  17. The code now should looks like the following:
    HandleEditModal:
    Private Sub HandleEditModal
        Dim id As String = Request.RequestURI.SubString("/api/articles/edit/".Length)
        Dim form1 As Tag = Form.init
        form1.hxPut($"/api/articles"$)
        form1.hxTarget("#modal-messages")
        form1.hxSwap("innerHTML")
       
        DB.SQL = Main.DBOpen
        DB.Table = "tbl_articles"
        DB.Columns = Array("article_title AS title", "article_body AS body", "article_status AS status", "category_id AS category")
        DB.WhereParam("id = ?", id)
        DB.Query
        If DB.Found Then
            Dim row As Map = DB.First
            Dim article_title As String = row.Get("title")
            Dim article_body As String = row.Get("body")
            Dim article_status As Int = row.Get("status")
            Dim category_id As Int = row.Get("category")
       
            Dim modalHeader As Tag = Div.cls("modal-header").up(form1)
            H5.cls("modal-title").text("Edit Articles").up(modalHeader)
            Button.typeOf("button").cls("btn-close").data("bs-dismiss", "modal").up(modalHeader)
       
            Dim modalBody As Tag = Div.cls("modal-body").up(form1)
            Div.id("modal-messages").up(modalBody)
            Input.typeOf("hidden").up(modalBody).name("id").valueOf(id)
       
            Dim group1 As Tag = Div.cls("form-group").up(modalBody)
            Label.forId("category1").text("Category ").up(group1).add(Span.cls("text-danger").text("*"))
            Dim select1 As Tag = CreateCategoriesDropdown(category_id)
            select1.id("category1")
            select1.name("category")
            select1.up(group1)
    
            Dim group1 As Tag = modalBody.add(Div.cls("form-group"))
            Label.forId("title").text("Title ").up(group1).add(Span.cls("text-danger").text("*"))
            Input.typeOf("text").up(group1).id("title").name("title").cls("form-control").valueOf(article_title).attr3("required")
    
            Dim group2 As Tag = modalBody.add(Div.cls("form-group"))
            Label.forId("body").text("Body ").up(group2).add(Span.cls("text-danger").text("*"))
            Textarea.rows("3").up(group2).id("body").name("body").cls("form-control").text(article_body).attr3("required")
    
            Dim group3 As Tag = modalBody.add(Div.cls("form-group"))
            Label.forId("status").text("Status ").up(group3).add(Span.cls("text-danger").text("*"))
            Dim select1 As Tag = Dropdown.up(group3).id("status").name("status").cls("form-select").attr3("required")
            If article_status = 0 Then
                Option.valueOf("0").attr3("selected").text("Draft").up(select1)
            Else
                Option.valueOf("0").text("Draft").up(select1)
            End If
            If article_status = 1 Then
                Option.valueOf("1").attr3("selected").text("Published").up(select1)
            Else
                Option.valueOf("1").text("Published").up(select1)
            End If
    
            Dim modalFooter As Tag = Div.cls("modal-footer").up(form1)
            Button.typeOf("submit").cls("btn btn-primary px-3").text("Update").up(modalFooter)
            Button.typeOf("button").cls("btn btn-secondary px-3").data("bs-dismiss", "modal").text("Cancel").up(modalFooter)
        End If
        DB.Close
    
        App.WriteHtml(Response, form1.Build)
    End Sub
  18. Let's save it and let the Hot code swap completed successfully.
  19. Now click the pencil button and see the updated modal dialog.
    1763014532581.png
  20. Well, give yourself a pat on the back.
  21. Wait, let's try to click the Update button.
    1763014749694.png
  22. Well, it's too early to celebrate. We actually got an error.
    Message Logs:
    (SQLException) java.sql.SQLException: [SQLITE_ERROR] SQL error or missing database (no such column: article_name)
    We haven't updated the code to update the database.
  23. Head to the HandleArticles sub and let's finish up the code.
  24. This time, we scroll to the line:
    HandleArticles:
    Case "PUT"
    where we are going to execute an UPDATE to the database.
  25. If you are lazy like I am, just copy the code we already have in POST section.
    1763015283612.png
  26. Then replace the code here:
    1763015652730.png
  27. We are not interested to check the name or title for duplicate here so we can safely remove the following code:
    HandleArticles:
    DB.Reset
    DB.Where = Array("article_name = ?", "id <> ?")
    DB.Parameters = Array(name, id)
    DB.Query
    If DB.Found Then
        ShowAlert("Articles already exists!", "warning")
        DB.Close
        Return
    End If
  28. The code for updating is very similar to Insert article. So again, we can take the code from POST section and paste it here.
  29. But we also want to update the "modified_date" column.
    HandleArticles:
    ' Update row
    Try
        DB.Reset
        'DB.Columns = Array("article_name", "modified_date")
        'DB.Parameters = Array(name, Main.CurrentDateTime)
        DB.Columns = Array("article_title", "article_body", "article_status", "category_id", "modified_date")
        DB.Parameters = Array(article_title, article_body, article_status, category_id, Main.CurrentDateTime)
        DB.Id = id
        DB.Save
        DB.Close
        ShowToast("Articles", "updated", "Articles updated successfully!", "info")
    Catch
        ShowAlert($"Database error: ${LastException.Message}"$, "danger")
    End Try
  30. The final code now should look like the following:
    HandleArticles:
    Private Sub HandleArticles
        Select Method
            Case "POST"
                ' Irrelevant code hidden
            Case "PUT"
                ' Update
                Dim id As Int = Request.GetParameter("id")
                Dim article_title As String = Request.GetParameter("title")
                Dim article_body As String = Request.GetParameter("body")
                Dim article_status As String = Request.GetParameter("status")
                Dim category_id As Int = Request.GetParameter("category")
             
                If category_id < 1 Then
                    ShowAlert("Please select a category for the Article.", "warning")
                    Return
                End If        
                If article_title = "" Or article_title.Trim.Length < 2 Then
                    ShowAlert("Article title must be at least 2 characters long.", "warning")
                    Return
                End If
                If article_body = "" Then
                    ShowAlert("Article body cannot be empty.", "warning")
                    Return
                End If
                If article_status = "" Then
                    ShowAlert("Please select a status for the Article.", "warning")
                    Return
                End If
                DB.SQL = Main.DBOpen
                DB.Table = "tbl_articles"
                DB.Find(id)
                If DB.Found = False Then
                    ShowAlert("Articles not found!", "warning")
                    DB.Close
                    Return
                End If
             
                ' Update row
                Try
                    DB.Reset
                    DB.Columns = Array("article_title", "article_body", "article_status", "category_id", "modified_date")
                    DB.Parameters = Array(article_title, article_body, article_status, category_id, Main.CurrentDateTime)
                    DB.Id = id
                    DB.Save
                    DB.Close
                    ShowToast("Articles", "updated", "Articles updated successfully!", "info")
                Catch
                    ShowAlert($"Database error: ${LastException.Message}"$, "danger")
                End Try
            Case "DELETE"
                ' Irrelevant code hidden
        End Select
    End Sub
  31. Now, save the changes and wait for B4J IDE Hot swap to complete.
  32. Click the pencil button again and make some changes.
    1763016769925.png
  33. Click the Update button.
    1763016902012.png
  34. Click the pencil button again to see the data is persisted.
  35. Voila, it works!
  36. We can verify this using DB Browser for SQLite.
    1763017090657.png
 
Last edited:
Top