Announcement

Collapse
No announcement yet.

I miss the old PageBuilder stuff

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • shill
    replied
    Originally posted by Michael McSharry View Post
    How about adding alerts in bootstrap\js\page_common.js to first observe response and then sResponse[i] and sResponse[i+1]?
    I dug around in the html used by a couple of native plugins and found that in some cases they use "AjaxPost", but in all the sample pages it's a the native jquery Ajax call. By switching to AjaxPost I just now got it to work, but only after finding out through trial and error that I had to have a function called after_postback() in my js code. How the !$#% are we supposed to figure this stuff out? Reverse engineering and experimentation is NOT the answer.

    (And the jquery Ajax examples showed how to do some error management, but can't tell with AjaxPost what's supposed to happen or how I know if it worked or not...)

    Leave a comment:


  • Michael McSharry
    replied
    How about adding alerts in bootstrap\js\page_common.js to first observe response and then sResponse[i] and sResponse[i+1]?

    Leave a comment:


  • shill
    replied
    Originally posted by Michael McSharry View Post
    Did you escape the quotes in your html with the backlash such as my example above where q is defined as quote character """"?

    Code:
    .Append(Replace(AjaxUpdateList(skvp), q, "\" & q))
    I believe the JSON.Stringify method will accomplish this too, but I have little experience with Newtonsoft libraries.
    I even tried sending back simply text and nothing...

    Leave a comment:


  • Michael McSharry
    replied
    Did you escape the quotes in your html with the backlash such as my example above where q is defined as quote character """"?

    Code:
     .Append(Replace(AjaxUpdateList(skvp), q, "\" & q))
    I believe the JSON.Stringify method will accomplish this too, but I have little experience with Newtonsoft libraries.

    Leave a comment:


  • shill
    replied
    Originally posted by Michael McSharry View Post

    It is the default if a PAGE_ is not recognized. I have it working as long as the update is in format ["div","html"]
    Is there something you need to do with the div or in the javascript handling the response from the Ajax command? I'm getting the postback in the plugin, and I build the HTML to return, but nothing changes in my page. The div's id is there and matches what I have, but something's not connected because the JS isn't updating the div's innerhtml.

    Leave a comment:


  • rmasonjr
    replied
    +1 on the examples Michael, it is very helpful and much appreciated.

    But... This is becoming an immense amount of work to create HS4/Jui pages.
    SettingsPages are a bomb since you cant do any server-side validation during the capture of individual user input. You also cant change the layout.
    FeaturePages appear to have 2 methods: HTML-only, or render the HTML in the plugin code, which gets messy.

    Ugh... We really need more examples, rjh or some best practices, or documentation...

    Leave a comment:


  • Michael McSharry
    replied
    According to page_common.js, divtoupdate isn't implemented:
    It is the default if a PAGE_ is not recognized. I have it working as long as the update is in format ["div","html"]

    HomeSeer owes us a more useful API and documentation!!! We shouldn't have to re-invent the wheel for every possible senario and re-code javascripts and postbacks as referenced above. This is a serious set back for me and I may never upgrade my plugins anytime soon as I can not justify the effort required for such a small sales price that is shared with HS.
    You are totally welcome to use clsPageBuilder for all the Ajax stuff. I just wanted to eliminate it so dug in to the HS4 Ajax handler to understand how to interface with it.

    The issue I had was that jQuery. styles don't work well with the HS4 styles. Choice then becomes to have HTML-focused pages or have the plugin build the page as was done with HS3. I wanted to keep my page construction logic in the plugin as it is very complex to provide a dynamic user experience. This means I needed to make the controls I use comply with the HS4 bootstrap/jui css. I'm certain I will tweak these as I learn more about using css and in particular what is available in the HS4 css files.

    I provided the write up to help others that may be trying to go down the same overall path that I took. There are various things I covered and tried to provide the rationale. It is not intended to be a final solution for everybody, but just a sample that has a little more documentation in areas than is not found in the HS samples.

    Leave a comment:


  • chrisgo
    replied
    Originally posted by lpitman View Post
    Would be much easier if I could just load a new page.html, I do a lot of processing and reading of XML file to build the list page shown above. When I click for example the edit button it should pass the the record number to edit using 'btnEditEvent56' which is the 56th record in file. With that info passed to PostBackProc I should be able to build an EditEvent.html page with textbox inputs for the record being edited, then save and return to EventList.html page.
    This is exactly how I build my pages, using a dataset table as the source to build a page/table with many rows, each having their own action buttons with IDs numbered by row. This is all done dynamically based on how many rows the source table contains. rjh an example of how to do this the new way would be great and would save me a ton of time.

    This is the page I am trying to re-create. Rows can be added and deleted, as well as re-ordered. The table DIV would update in the event the user changed something (add a row, reorder etc). Each button has a unique ID based on it's row just like how lpitman is doing it.

    Click image for larger version  Name:	Envcan_parse_screenshot.jpg Views:	0 Size:	138.4 KB ID:	1351390

    Leave a comment:


  • lpitman
    replied
    HomeSeer owes us a more useful API and documentation!!! We shouldn't have to re-invent the wheel for every possible senario and re-code javascripts and postbacks as referenced above. This is a serious set back for me and I may never upgrade my plugins anytime soon as I can not justify the effort required for such a small sales price that is shared with HS.

    Leave a comment:


  • shill
    replied
    Originally posted by rjh View Post
    If your postback is in a pagebuilder class you can use these calls to things like update divs, etc.:

    https://help.homeseer.com/help/HS3SD...#.pagecommands
    https://help.homeseer.com/help/HS3SD...m#.divtoupdate

    Note that HS4 only supports:

    divtoupdate
    newpage
    refresh
    popmessage

    I can add more if needed. The commands are in the ajaxpost function in page_common.js file in the html/bootstrap/js folder.

    I will need to investigate the guided process but you should be able to get a postback on each step and then just update the divs that need new content.
    According to page_common.js, divtoupdate isn't implemented:

    Code:
    switch(pageCmd.toLowerCase()) {
                            case 'popmessage':
                                alert(sResponse[i + 1]);
                                after_postback();
                                break;
                            case 'newpage':
                                location.assign(sResponse[i+1]);
                                //window.location.replace(sResponse[i+1]);
                                break;
                            case 'refresh':
                                if(sResponse[i+1].toLowerCase() == 'true') {
                                    returnTrue = true;
                                    location.reload();
                                }
                                break;
    Are we going to get real documentation on this stuff anytime soon? If it weren't for your posts in this thread, I don't know how anyone would know to use "PAGE_" at all...

    Leave a comment:


  • Michael McSharry
    replied
    I have been struggling with the lowest effort and risk approach to migrate mcsMQTT to HS4.
    This plugin is very UI intensive with extensive use of clsPageBuilder with some hyperlinks and javascript.
    Initially I was just going to continue to use jQuery but never could get it to look or play well with the HS4 headers.
    What I ended up doing is making equivalent jQuery functions using the HTML extracted from some of the sample plugins.
    An example is a checkbox
    Code:
        Private Function JuiCheckbox(ByVal name As String, ByVal label As String, ByVal bChecked As Boolean, ByVal hint As String) As String
            Dim s As String
            Dim sChecked As String = ""
            If bChecked Then
                sChecked = " checked"
            End If
            With New StringBuilder
                ' <!--ToggleView-Checkbox-->                    
                .Append("<div Class=""jui-view jui-toggle""><Label Class=""jui-toggle-text"" for=""")
                .Append(name)
                .Append(""">")
                .Append(label)
                .Append("</label><span Class=""jui-toggle-control""  title=""")
                .Append(hint)
                .Append("""><span Class=""form-check form-check-inline jui-toggle-checkbox"">")
                .Append("<input Type =""checkbox"" class=""form-check-input jui-input"" name=""")
                .Append(name)
                .Append(""" id=""")
                .Append(name)
                .Append(""" onClick=""PostBackFunction(this,'mcsMQTT/MQTT.html');""")
                .Append(sChecked)
                .Append("><Label Class=""form-check-label jui-toggle-checkbox-label"" For=""")
                .Append(name)
                .Append("""></label><span title=""")
                .Append(hint)
                .Append("""></span></span></div>")
                s = .ToString
            End With
            Return s
        End Function
    I then used the new function to replace the jQuery with something like the following where the original jQuery is commented out
    Code:
                'Dim c As New jqCheckBox(MyPageId & LOCKCHARTAXIS, "Sync to Left", MQTT_PAGE, True, False)
                'c.checked = gLockChartAxis
                'c.toolTip = "Check to force right Y axis to be scaled same as left scaled axis."
                '.Append(c.Build())
                .Append(JuiCheckbox(MyPageId & LOCKCHARTAXIS, "Sync to Left", gLockChartAxis, "Check to force right Y axis to be scaled same as left scaled axis."))
    The above could have been extended further to create a local jqCheckBox class and avoid editing the existing clsPageBuilder code. It would look something like the following. In retrospect it would have been easier and should not be that much of a performance penalty.

    Code:
    Class jqCheckBox
        Dim _checked as string = ""
        Dim _toolTip as string = ""
        Dim _label as string = ""
        Dim _id as string = ""
    
        Public Sub New(ByVal id as string, ByVal label as string, ByVal htmlPage as string, ByVal dontCare1 as boolean, ByVal dontCare2 as boolean)
            _label = label
            _id = id
        End Sub
    
        Public WriteOnly Property checked As String
            Set(ByVal c)
                _checked = c
            End Set
        End Property
    
        Public WriteOnly Property tooltip As String
            Set(ByVal t)
                _tooltip = t
            End Set
        End Property
    
        Public Function Build() as String
            Get
                Return JuiCheckbox(id,label,_checked="checked",tooltip)
            End Get
        End Function
    
    End Class
    I used upper vs lower case in table headers where upper is for the sort column. The HS4 css for buttons forces upper case.
    To supersede this I had to add another css style. I put it in another css file (mcs.css) so others could be added if the need arises

    Code:
    .text-samecase {text-transform: none;}
    It gets used in my button creation function where it is at the end of the css classes so that it has priority
    Code:
        Private Function JuiButton(ByVal name As String, ByVal label As String, ByVal hint As String) As String
            Dim s As String
            With New StringBuilder
                .Append("<span title = """)
                .Append(hint)
                .Append(""">")
                .Append("<button class=""btn btn-primary btn-block jui-view jui-button text-samecase"" type=""button"" onClick=""PostBackFunction(this,'mcsMQTT/MQTT.html');"" name=""")
                .Append(name)
                .Append(""">")
                .Append(label)
                .Append("</button></span>")
                s = .ToString
            End With
            Return s
        End Function
    I also use some css classes for table background colorization for my HS3 plugin that I want to also use in HS4. These are in the hs.css file in HS4 so I just included it in my html file with <link href="/css/hs.css" rel="stylesheet">.



    The second part of the struggle is with the Ajax and event part of the transition. While I have worked with basic javascript events I had never done anything directly with Ajax.
    The HS3 postback function is using "HttpUtility.ParseQueryString(data)" so I wanted the data returned from the HS4 pages to conform to this expectation. I created a javascript function PostBackFunction as shown below. Its use was illustrated in the JuiCheckbox code shown above as the recipient of the onClick event.
    This function receives the object (e.g. checkbox, textbox, etc.) and the html page name. The page name was added as a parameter so this same function could be used across multiple pages.
    It extracts the name and either the checked or value property of the object and passes it to the HS Ajax handler as a name-value-pair

    Code:
            function PostBackFunction(e,p) {
            //alert("pb " + e.name + e.value + e.type + e.checked);
                var jsonData;
                if (e.type=="checkbox"){jsonData = e.name + "=" + e.checked;}
                else {jsonData = e.name + "=" + e.value;}
                AjaxPost(jsonData,p);
        }
    The HS Ajax handler in bootstrap\js\page_common.js makes a call to "after_postback()" at completion of the postback so this function also needed to be provided on the javascript on the page.

    Code:
        function after_postback() {
        }
    The plugin method postBackProc will receive the name-value-pair from the user input and then it will return updates (or commands) to the HS Ajax handler after acting upon the user input.
    The HS Ajax handler is expecting a data format of ["n1","v1","n2","v2"]. For HTML updates that was done previously in HS3 with clsPageBuilder divtoUpdate calls the plugin now needs to accumulate these and return them in the format expected by HS Ajax handler.
    I did a global replace of "me.divtoUpdate" with "AjaxUpdate" and created the new function to accumulate the updates requests the plugin needed to make in the postback function.
    At the end of the postback function I then formatted the request per HS Ajax handler expectations and return the response. Note that the quotes needed to be escaped (" -> ") per the javascript formatting expectation.
    Code:
    Public Function postBackProc(page As String, data As String, user As String, userRights As Integer) As String
    :
            AjaxUpdateList = New Collections.Specialized.NameValueCollection
    :
        'when an update to page is desired, can be multiple times depending upon update needs
        AjaxUpdate(TOPICDIV & sIndex, TopicEntry(oMQTT, sIndex))
    :
    :
                With New StringBuilder
                    Dim bFirst As Boolean = True
                    For Each skvp As String In AjaxUpdateList
                        If (skvp IsNot Nothing) Then
                            If bFirst Then
                                .Append("[")
                                bFirst = False
                            Else
                                .Append(",")
                            End If
                            .Append(q)
                            .Append(skvp)
                            .Append(q)
                            .Append(COMMA)
                            .Append(q)
                            .Append(Replace(AjaxUpdateList(skvp), q, "\" & q))
                            .Append(q)
                        End If
                    Next
                    If Not bFirst Then
                        .Append("]")
                    End If
                    Return .ToString()
    End Function                
    :
    :
        Private Sub AjaxUpdate(ByVal sElementName As String, ByVal sValue As String)
            AjaxUpdateList.Add(sElementName, sValue)
        End Sub
    The HS Ajax handler traps the data returned from the plugin postback looking for something starting with "PAGE_". These become the broswer-side commands that it execute on the plguin's behalf. An example "[""PAGE_POPMESSAGE"",""Feedback to User""]"
    This means that the HS command such as below:
    Code:
    From:    Me.pageCommands.Add("popmessage", sValue & " duration needs to be in format dd hh:mm:ss (numer of days and time)")
    
    To: AjaxUpdate("PAGE_popmessage", sValue & " duration needs to be in format dd hh:mm:ss (numer of days and time)")
    Since I used the Ajax refresh timer with HS3, and I did not see how it was being provided in HS4, I added the function to my javascript and invoked it with the body onLoad event on any page that needs this capability
    In my case I always prefixed the Ajax responses with the internally-generated page identifier so if multipages were open I would know which needs the Ajax postback response. I put this in a form tag in HS4 so my timer update function could find it.
    The remainder of the postback logic based upon the periodic timer did not change for HS4

    Code:
    In HS3 GetPagePlugin
    Me.AddAjaxHandlerPost(MyPageId & UPDATECOUNTS & "=", MQTT_PAGE)
    Me.RefreshIntervalMilliSeconds = 2000
    
    In HS4 GetPagePlugin
    :
    .Append("<form id='pageId'><input type='hidden' value='" & MyPageId & "'></form>")
    
    In HS4 MQTT.html
    
    <body class="homeseer-skin" onLoad="update_timer('updatecounts=1', 'mcsMQTT/MQTT.html', 2000);">
    :
    :
        <script type="text/JavaScript">
        function update_timer(postbackName,pageName,interval) {
            var pageId;
            var formType = document.getElementById("pageId");
            if (typeof formType != "undefined") {
                pageId = formType[0].value + postbackName;
            }
            else {
                pageId = postbackName;
            }
            //alert (pageId);
            setInterval(function(){AjaxPost(pageId,pageName);},interval);
        }
        </script>
    Putting this together I created one .css file and one .js file to accumulate the customization that I have done that can be used across my plugins


    Code:
    File: \mcsMQTT\mcs.css
    ----------------------
    .text-samecase {text-transform: none;}
    
    File: \mcsMQTT\mcs.js
    ---------------------
        <script type="text/JavaScript">
            function PostBackFunction(e,p) {
            //alert("pb " + e.name + e.value + e.type + e.checked + e.selected + e.value +  e[e.value].label );
                var jsonData;
                if (e.type=="checkbox"){jsonData = e.name + "=" + e.checked;}
                else if (e.type=="select-one") {jsonData = e.name + "=" + e[e.value].label}
                else {jsonData = e.name + "=" + e.value;}
                AjaxPost(jsonData,p);
            }
    
            function after_postback() {
            }
            function update_timer(postbackName,pageName,interval) {
                var pageId;
                var formType = document.getElementById("pageId");
                if (typeof formType != "undefined") {
                    pageId = formType[0].value + postbackName;
                }
                else {
                    pageId = postbackName;
                }
                //alert (pageId);
                setInterval(function(){AjaxPost(pageId,pageName);},interval);
            }
    
        </script>
    
     File: \mcsMQTT\mcsMQTT.html
     ---------------------------
    
     <!DOCTYPE html>
     <html lang="en">
    
     <head>
         <meta charset="utf-8">
         <meta http-equiv="X-UA-Compatible" content="IE=edge">
         <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no">
         <meta name="description" content="">
         <meta name="author" content="HomeSeer">
    
         {{includefile 'bootstrap/css/page_common.css'}}
         <link href="/mcsMQTT/mcs.css" rel="stylesheet">
         <link href="/css/hs.css" rel="stylesheet">
    
         <title>MQTT Setup</title>
    
     </head>
    
     <body class="homeseer-skin" onLoad="update_timer('updatecounts=1', 'mcsMQTT/MQTT.html', 2000);">
    
         {{includefile 'header.html'}}
         {{includefile 'navbar.html'}}
    
         <div class="container" id="main_content">
         <ul class="nav nav-tabs hs-tabs" role="tablist">
         <li Class="nav-item">
         <a Class="nav-link waves-light waves-effect waves-light active" id="settings-page1.tab" data-toggle="tab" href="#settings-page1" role="tab" aria-controls="settings-page1" aria-selected="true">Assocations</a>
         <li Class="nav-item">
         <a Class="nav-link waves-light waves-effect waves-light" id="settings-page2.tab" data-toggle="tab" href="#settings-page2" role="tab" aria-controls="settings-page1" aria-selected="false">Edit/Add</a>
         <li Class="nav-item">
         <a Class="nav-link waves-light waves-effect waves-light" id="settings-page3.tab" data-toggle="tab" href="#settings-page3" role="tab" aria-controls="settings-page1" aria-selected="false">Publist/Sign</a>
         <li Class="nav-item">
         <a Class="nav-link waves-light waves-effect waves-light" id="settings-page4.tab" data-toggle="tab" href="#settings-page4" role="tab" aria-controls="settings-page1" aria-selected="false">General</a>
         <li Class="nav-item">
         <a Class="nav-link waves-light waves-effect waves-light" id="settings-page5.tab" data-toggle="tab" href="#settings-page5" role="tab" aria-controls="settings-page1" aria-selected="false">Statistics</a>
         <li Class="nav-item">
         <a Class="nav-link waves-light waves-effect waves-light" id="settings-page6.tab" data-toggle="tab" href="#settings-page6" role="tab" aria-controls="settings-page1" aria-selected="false">History</a>
         <li Class="nav-item">
         <a Class="nav-link waves-light waves-effect waves-light" id="settings-page7.tab" data-toggle="tab" href="#settings-page7" role="tab" aria-controls="settings-page1" aria-selected="false">Chart</a>
         </ul>
         </div>
    
         <div class="tab-content" container>
                 {{
                 parms = ['MQTT', '']
                 udn=queries['Edit']
                 if (udn != empty) && (udn != '')
                   parms = ['Edit', udn]
                 end
                 udn=queries['Chart']
                 if (udn != empty) && (udn != '')
                     parms = ['Chart', udn]
                 end
                 udn=queries['Payload']
                 if (udn != empty) && (udn != '')
                     parms = ['Payload', udn]
                 end
                 udn=queries['Sign']
                 if (udn != empty) && (udn != '')
                     parms = ['Sign', udn]
                 end
             }}
             {{plugin_function 'mcsMQTT' 'PagePreLoad' parms}}
         </div>
    
         {{includefile 'bootstrap/js/page_common.js'}}
         {{includefile 'mcsMQTT/mcs.js'}}
    
    
     </body>
    </html>

    Leave a comment:


  • shill
    replied
    Not sure if this is relevant or not (and it doesn't explain why the delete button would work), but I don't think your quotes are correct in your "response" variable:

    Code:
    response = "[""PAGE_REFRESH"",""TRUE]"""
    would resolve to:

    ["PAGE_REFRESH","TRUE]"

    so you need to move 2 of the final 3 double quotes inside the brackets:

    Code:
    response = "[""PAGE_REFRESH"",""TRUE""]"

    Leave a comment:


  • lpitman
    replied
    Still NO JOY, the web page makes successful callbacks to the PostBackProc, but any thing performed in PostBackProc does not return to web page, weather i use:

    response = "[""PAGE_REFRESH"",""TRUE]""" or Return "[""PAGE_NEWPAGE"",""Calendar/EditEvent.html]"""

    Here is my PostBackProc

    Code:
                Case "EventList.html"
                    name = parts("id")
    
                    Try
                        '===========================================
                        '===========================================
                        '-------------------------------------------
                        '        BUTTON ADD NEW EVENT
                        '-------------------------------------------
                        If name = "btnAddEvent" Then
    
                            OutputHSLog(LOG_DEBUG, "HSPI.PostBackProc-btnAddEvent BUTTON PRESSED.")
    
                            gPageName = "AddEvent Page"
                            xmlAddRecord = Nothing
                            BuildAddEvent()     ' Build the Add Page
                            response = "[""PAGE_REFRESH"",""TRUE]"""
    
                        End If
    
                        For i = 0 To gMaxRecords
                            '--------------------------------------
                            '        BUTTON EDIT EVENT
                            '--------------------------------------                      
                            If name = "btnEditEvent" & i.ToString Then
    
                                OutputHSLog(LOG_DEBUG, "HSPI.PostBackProc-btnEditEvent" & i.ToString & "  BUTTON PRESSED.")
    
                                gPageName = "EditEvent Page"
                                gRecordSelected = i
                                BuildEditEvent()    ' Build the Edit Page
                                Return "[""PAGE_NEWPAGE"",""Calendar/EditEvent.html]"""
    
                            End If
                            '---------------------------------------
                            '         BUTTON DELETE EVENT
                            '---------------------------------------
                            If name = "btnDeleteEvent" & i.ToString Then
    
                                OutputHSLog(LOG_DEBUG, "HSPI.PostBackProc-btnDeleteEvent" & i.ToString & "  BUTTON PRESSED.")
    
                                gPageName = "EventList Page"
                                gRecordSelected = i
                                DeleteEventByName(EVENTFILE, xmlRecords(gRecordSelected).EventName)
                                SaveAllXMLRecords(EVENTFILE)
                                response = "[""PAGE_REFRESH"",""TRUE]"""
                            End If
                        Next
    
                    Catch ex As Exception
                        OutputHSLog(LOG_ERROR, "Exception In HSPI.PostBackProc() - Buttons: " & ex.Message)
                    End Try
    btnDeleteEvent is the only one that works.

    Leave a comment:


  • rmasonjr
    replied
    Originally posted by rjh View Post
    GIve me an outline of what your page looks like and I will see if I can put together a framework as an example. To me, doing HTML pages is WAY better than the old way. I can put together a working page in a fraction of the time I could before. Maybe a video will help that shows the steps? The JUI controls are only for a basic settings page, which is really simple. I would guess you are trying to do a feature page using HTML?
    Yes - I am building a FeaturePage. I put everything in HTML at first, then moved it all to plugin code which was likely a mistake. I may go back to a pure HTML page.

    The idea is that if you have a plugin that requests some user settings, it needs to be interactive for the user.
    For example, if you ask the user for an IP address, you should include a 'test' button to make sure the IP is good, etc. This is not possible in a SettingsPage.

    I'll take another stab at a FeaturePage in pure HTML and see how far I get.

    We do need many more examples though...

    Leave a comment:


  • rjh
    replied
    I like to do as much as I can in the code and not in the javascript. You can use the handling in our ajax post handler by returning some JSON like this:

    return "[""PAGE_NEWPAGE"",""EditEvent.html]"""

    This tells the ajax handler to redirect to the given page.

    Since you are not using any pagebuilder code, you can also just do it in your HTML with the button press:

    Code:
    <button onclick="location.href = 'www.yoursite.com';" id="myButton" >Button Label</button>





    Originally posted by lpitman View Post
    Would be much easier if I could just load a new page.html, I do a lot of processing and reading of XML file to build the list page shown above. When I click for example the edit button it should pass the the record number to edit using 'btnEditEvent56' which is the 56th record in file. With that info passed to PostBackProc I should be able to build an EditEvent.html page with textbox inputs for the record being edited, then save and return to EventList.html page.

    So is the
    in-correct syntax then?

    Just for the record I'm not using pagebuilder anything, I just created my own code to imitate HS3 stuff, but attempting to use only HS4 code.

    Leave a comment:

Working...
X