B4J Question [ABMaterial] Can I override the input abmaterial events?

Joan Paz

Member
Hello!

Im trying to use a external javascript component (chosen.js) but then i refresh one element in the page, the abmaterial input events override the chosen event. This component (chosen) is a select with filter, and if i cant handle de input dosent work.

Thanks!
 

Mashiane

Expert
Licensed User
Hi, I was looking at this last week because I wanted to use its "tagging" feature. The select input controls for material based on the materialcss website need to be initialized via javascript for them to work. I had tried earlier to create a multi-select component to no avail, it just didnt work. Perhaps what you need to do is after you refresh your element, you also need to refresh your content.js based component.

you can pm me your custom component I can have a look for you. Im glad that you raised this, it might mean I have to look at another "tagging" component that might be input and not select based.
 

Joan Paz

Member
Hello, thank you very much for responding, I think I do not have the option to send private messages but I explain you here, first try another library for filterable select to which I made a custom component but precisely because of this problem of the override of the. js abandon its use. Subsequently, I tried with this library chosen.js which is the one I made the comment about, but I applied it directly with a ws.eval and doing this in the testing phase was that I realized this problem in the events and the override after doing any refresh on page or on any abmcomponent.

This is the code I use to place the object with the chose.js library, which is just a test:

B4X:
Dim chosen As String = $"      
            <select id="prueba" data-placeholder="Choose a Country..."  tabindex="2" class="chosen-select" ">
            <option value=""></option>
            <option value="United States">United States</option>
            <option value="United Kingdom">United Kingdom</option>
            <option value="Afghanistan">Afghanistan</option>
            <option value="Aland Islands">Aland Islands</option>
            <option value="Albania">Albania</option>
            <option value="Algeria">Algeria</option>
            <option value="American Samoa">American Samoa</option>
            <option value="Andorra">Andorra</option>
            <option value="Angola">Angola</option>
            <option value="Anguilla">Anguilla</option>
            <option value="Antarctica">Antarctica</option>
            <option value="Antigua and Barbuda">Antigua and Barbuda</option>
            <option value="Argentina">Argentina</option>
            <option value="Armenia">Armenia</option>
            <option value="Aruba">Aruba</option>
            <option value="Australia">Australia</option>
            <option value="Austria">Austria</option>
            <option value="Azerbaijan">Azerbaijan</option>
            <option value="Bahamas">Bahamas</option>
            <option value="Bahrain">Bahrain</option>
          </select>
      "$
   
    ABM.AddHTML(page,"r10c1",chosen)
    page.Cell(10,1).SetOffsetSize(0,0,0,12,12,4)
 
    page.Refresh
   
    Dim script As String = $"
                                $(document).ready(function () {
                                    $(".chosen-select").chosen({
                                    disable_search_threshold: 10,
                                    no_results_text: "Oops, nothing found!",
                                    width: "110%"
                                      });        
                                    $(".chosen-search-input").css("width","90%")
                                    $(".chosen-search-input").prop("type","")            
                                });
                               "$

    ' Tell the browser we finished loading
    page.FinishedLoading
    ' restoring the navigation bar position
    page.RestoreNavigationBarPosition
    page.ws.Eval(script,Null)
In BuildPage():

B4X:
page.AddExtraJavaScriptFile("custom/chosen.jquery.min.js")
page.AddExtraCSSFile("custom/chosen.min.css")

Sorry for delaying the answer, yesterday I was very busy and did not check the forum. Again, thank you very much for answering.
 

alwaysbusy

Expert
Licensed User
You should always use an ABMCustomComponent. The way used above is only a component on the browserside, with no equivalent on the server side and will never work. (it has no refresh, no FirstRun etc) all stuff ABM does really need to function correctly. e.g. doing a cell refresh alone can undo whatever you have written.
 

Joan Paz

Member
Hello, in the files that I am adding are the custom component that gives me the same failure with the override of the .js on input when refreshing the page, and additionally I tried a way to do it without custom component, directly evaluating the javascript with page.ws .eval. but the same thing happens to me.

The way I invoke the custom component is:
B4X:
Dim cont As ABMContainer
cont.Initialize(page,"cont","")
cont.AddRows(1,False,"").AddCells12(2,"")
cont.BuildGrid
page.Cell(4,1).AddComponent(cont)
 
Dim selectize As SelectizeCus
 
Dim lista As List
lista.Initialize
 
lista = listaselectize(lista, "1a", "Prueba1", "Texto1")
lista = listaselectize(lista, "2b", "Prueba2", "Texto2")
lista = listaselectize(lista, "3c", "Prueba3", "Texto3")
lista = listaselectize(lista, "4f", "Prueba4", "Texto4")
 
selectize.Initialize(page, "selectize1", lista, "Select something", "asc")

cont.Cell(1,1).AddComponent(selectize.ABMComp)
Aditional:

B4X:
Sub listaselectize(lista As List, id As String, campo1 As String, campo2 As String) As List
  
   Dim elementos As Map
   elementos.Initialize
   elementos.Put("id",id)
   elementos.Put("campo1",campo1)
   elementos.Put("campo2",campo2)
  
   lista.Add(elementos)
  
   Return lista  
End Sub
In BuildPage():

B4X:
page.AddExtraJavaScriptFile("custom/selectize.min.js")
page.AddExtraCSSFile("custom/selectize.css")
page.AddExtraCSSFile("custom/selectize.bootstrap3.css")
page.AddExtraCSSFile("custom/selectize-doble.css")

Thanks a lot.
 

Attachments

Last edited:

Joan Paz

Member
You should always use an ABMCustomComponent. The way used above is only a component on the browserside, with no equivalent on the server side and will never work. (it has no refresh, no FirstRun etc) all stuff ABM does really need to function correctly. e.g. doing a cell refresh alone can undo whatever you have written.
Hello, the especific problem is, when i refresh any abmcomponent like a abminput for example (or the page.refresh), all the inputs in the page, including the autogenerated input by selectized js library take the abmaterial events. This overriding in js events broke the filter functionality of my custom component because this object loses the events of the js library that I am using for the new custom component (is the same event override problem with all custom components with an in code generated input).

Excuse me if I'm not giving the best explanation, my english is not the best :rolleyes:. Thank you very much to all!
 
Last edited:

alwaysbusy

Expert
Licensed User
You were pretty close, except you used some wrong IDs. Because you've put it in a container, the ID you pass is not necessary the same as the internalID passed. (you can see it in the logs in ABMComp_Build). So whenever you want to do some jQuery stuff (like a selector), you will have to use the passed InternalID (as this is the real one in the HTML).

To make it simple in this example I created two variables: myB4JID and myHTMLID.

We only use the original passed ID in Initialize() for 'B4J' stuff, like defining an event or running a page.ws.eval(). In this case I also used it for the variable in javascript that will hold the selectize var '_initialize1' because the interalID contains a dash (cont-initialize1) and that would create an invalid javascript variable '_cont-initialize1'.

This component may not look 100% as intended. This is because it uses Bootstrap and their CSS may conflict with Materialize CSS.

It is also better to use the typical ABM b4j_raiseEvent('page_parseevent', {'eventname': '${OriginalID}' + '_onchange','eventparams': 'value', 'value': value}); syntax as then it passes smoothly through ABMs event parser.

Our class Selectisize
B4X:
' Class module
Sub Class_Globals
   Dim ABM As ABMaterial 'ignore
   Dim ABMComp As ABMCustomComponent
   Dim options As String
   Dim placeh As String
   Dim orden As String
   Dim myB4JID As String
   Dim myHTMLID As String
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(InternalPage As ABMPage, ID As String, lista_valores As List, placeholder As String, ordenamiento As String)
   Dim CSS As String = $""$
 
   myB4JID = ID.ToLowerCase ' for our javascript variable (1), our event (2) and ws.eval (3)
 
   ABMComp.Initialize("ABMComp", Me, InternalPage, ID, CSS)
 
  ' see further in the text why we do this
   ABMComp.Tag = Me
 
   Dim json As JSONGenerator
   Dim optionsmap As Map
   optionsmap.Initialize
   optionsmap.Put("options",lista_valores)
   json.Initialize(optionsmap)
   options = json.ToPrettyString(1)
   placeh = placeholder
   orden = ordenamiento
End Sub

Sub ABMComp_Build(InternalPage As ABMPage, internalID As String) As String
   myHTMLID = internalID
   Log("myB4JID: " & myB4JID)
   Log("myHTMLID: " & myHTMLID)
 
   Return $"<div class="control-group">
               <select id="${internalID}" class="contacts" placeholder="${placeh}"></select>
           </div>var _${myB4JID};"$ ' (1) don't forget making a variable if you want to use it later (e.g. to call a method of the javascript component
End Sub

' Is useful to run some initalisation script.
Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
   Dim script As String = $"_${myB4JID} = $('#${myHTMLID}').selectize({
    maxItems: 1,
    valueField: 'id',
    labelField: 'campo1',
    searchField: ['campo1','campo2'],
    sortField: [
        {field: 'campo1', direction: '${orden}'},
        {field: 'campo2', direction: '${orden}'}
    ],
   ${options.SubString2(1,options.Length-1)},
   render: {
        item: function(item, escape) {
            var name = item.campo1;
           return '<div>' + (name ? '<span class="campo1">' + escape(name) + '</span>' : '') +   '</div>';
        },
        option: function(item, escape) {
            var name = item.campo1;
            var label = name || item.campo2;
            var caption = name ? item.campo2 : null;
            return '<div>' +
   '<span class="label">' + escape(label) + '</span>' +
                   (caption ? '<span class="caption">' + escape(caption) + '</span>' : '') +
   '</div>';
           }
       },
       onChange: function (value) {
           b4j_raiseEvent('page_parseevent', {'eventname': '${myB4JID}' + '_onchange','eventparams': 'value', 'value': value}); // (2)    
       }
   });

   if (!($('#${myHTMLID}').hasClass('contacts'))) {
       $('#${myHTMLID}').addClass('contacts');
   }

   if (!($('.selectize-control').hasClass('contacts'))) {
       $('.selectize-control').addClass('contacts');
   }"$
 
   InternalPage.ws.Eval(script, Array As Object(myB4JID)) ' (3)
   ' here we don't need to flush as it passes through ABM
End Sub

' runs when a refresh is called
Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)
 
End Sub

' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)
   ABMComp.Tag = null
End Sub

Public Sub Visibility(InternalPage As ABMPage, hide As Boolean)
   Dim script As String = ""
   If Not(hide) Then
       script = $"$("#${myHTMLID}-selectized").parent('div').parent('div').attr("hidden","true");"$
    
   Else
       script = $"$("#${myHTMLID}-selectized").parent('div').parent('div').removeAttr( "hidden");"$
   End If
   InternalPage.ws.Eval(script, Null)
   InternalPage.ws.Flush ' here we must manually flush as this does not pass through ABM
End Sub

Public Sub Enabled(InternalPage As ABMPage, enable As Boolean)
   Dim script As String = ""
   If enable Then
       script = $"var $select = $("#${myHTMLID}").selectize();
                  $select[0].selectize.enable();"$
    
   Else
       script = $"var $select = $("#${myHTMLID}").selectize();
                  $select[0].selectize.disable();"$
   End If
   InternalPage.ws.Eval(script, Null) ' here we must manually flush as this does not pass through ABM
End Sub
I also took the opportunity to demonstrate a good use of the .Tag to run our Visibility and Enabled methods: we attach the parent class of the component to our ABMComp component.

In the page, we add a couple of variables to simulate a toggle:
B4X:
Sub Class_Globals
   ...
   Private visibleToggle As Boolean = True
   Private enableToggle As Boolean = True
End Sub
in BuildPage we add the custom css/js files (TIP: try to merge the CSS files in one file, it will be faster)
B4X:
page.AddExtraJavaScriptFile("custom/selectize.min.js")
page.AddExtraCSSFile("custom/selectize.css")
page.AddExtraCSSFile("custom/selectize.bootstrap3.css")
page.AddExtraCSSFile("custom/selectize-doble.css")
In ConnectPage() we add out component and a couple of buttons:
B4X:
public Sub ConnectPage()        
   '   connecting the navigation bar
   ABMShared.ConnectNavigationBar(page)
 
   Dim cont As ABMContainer
   cont.Initialize(page,"cont","")
   cont.AddRows(1,False,"").AddCells12(2,"")
   cont.BuildGrid
   page.Cell(1,1).AddComponent(cont)
   Dim lista As List
   lista.Initialize
   lista = listaselectize(lista, "1a", "Prueba1", "Texto1")
   lista = listaselectize(lista, "2b", "Prueba2", "Texto2")
   lista = listaselectize(lista, "3c", "Prueba3", "Texto3")
   lista = listaselectize(lista, "4f", "Prueba4", "Texto4")

   Dim selectize As Selectisize
   selectize.Initialize(page, "selectize1", lista, "Select something", "asc")

   cont.Cell(1,1).AddComponent(selectize.ABMComp)

   Dim btnToggleVisibility As ABMButton
   btnToggleVisibility.InitializeFlat(page, "btnToggleVisibility", "", "", "Toggle Visibility", "")
   page.Cell(3,1).AddComponent(btnToggleVisibility)
 
   Dim btnToggleEnabled As ABMButton
   btnToggleEnabled.InitializeFlat(page, "btnToggleEnabled", "", "", "Toggle Enabled", "")
   page.Cell(4,1).AddComponent(btnToggleEnabled)

   ' refresh the page
   page.Refresh
 
   ' Tell the browser we finished loading
   page.FinishedLoading
   ' restoring the navigation bar position
   page.RestoreNavigationBarPosition

End Sub

Sub listaselectize(lista As List, id As String, campo1 As String, campo2 As String) As List
   Dim elementos As Map
   elementos.Initialize
   elementos.Put("id",id)
   elementos.Put("campo1",campo1)
   elementos.Put("campo2",campo2)
 
   lista.Add(elementos)
 
   Return lista
End Sub
We define our event:
B4X:
Sub selectize1_OnChange(value As String)
   Log(value)
End Sub
And in our buttons we make use of the .Tag value we've set before to get our ABMCustomComponents 'wrapper' class:
B4X:
Sub btnToggleVisibility_Clicked(Target As String)
   Dim cont As ABMContainer = page.Component("cont")
   Dim selectizeABMComp As ABMCustomComponent = cont.Component("selectize1")
   Dim selectize As Selectisize = selectizeABMComp.Tag
 
   visibleToggle = Not(visibleToggle)
   selectize.Visibility(page, visibleToggle)
End Sub

Sub btnToggleEnabled_Clicked(Target As String)
   Dim cont As ABMContainer = page.Component("cont")
   Dim selectizeABMComp As ABMCustomComponent = cont.Component("selectize1")
   Dim selectize As Selectisize = selectizeABMComp.Tag
 
   enableToggle = Not(enableToggle)
   selectize.Enabled(page, enableToggle)
End Sub
I hope this will get you started!

Alwaysbusy
 
Last edited:

Joan Paz

Member
Hello! I feel very grateful for the whole explanation, I see it extremely useful.

I only have one problem with the new component, when I refresh an input on the page, whatever it is, I lose the filter function of the new component. It seems that the abmaterial events are overriding the events of the new component (I infer this by analyzing the situation with the browser's inspector).


Again, thank you very much for all the information, it is very helpful and shows me how much I still need to learn.
 
Last edited:

Joan Paz

Member
You were pretty close, except you used some wrong IDs. Because you've put it in a container, the ID you pass is not necessary the same as the internalID passed. (you can see it in the logs in ABMComp_Build). So whenever you want to do some jQuery stuff (like a selector), you will have to use the passed InternalID (as this is the real one in the HTML).

To make it simple in this example I created two variables: myB4JID and myHTMLID.

We only use the original passed ID in Initialize() for 'B4J' stuff, like defining an event or running a page.ws.eval(). In this case I also used it for the variable in javascript that will hold the selectize var '_initialize1' because the interalID contains a dash (cont-initialize1) and that would create an invalid javascript variable '_cont-initialize1'.

This component may not look 100% as intended. This is because it uses Bootstrap and their CSS may conflict with Materialize CSS.

It is also better to use the typical ABM b4j_raiseEvent('page_parseevent', {'eventname': '${OriginalID}' + '_onchange','eventparams': 'value', 'value': value}); syntax as then it passes smoothly through ABMs event parser.

Our class Selectisize
B4X:
' Class module
Sub Class_Globals
   Dim ABM As ABMaterial 'ignore
   Dim ABMComp As ABMCustomComponent
   Dim options As String
   Dim placeh As String
   Dim orden As String
   Dim myB4JID As String
   Dim myHTMLID As String
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize(InternalPage As ABMPage, ID As String, lista_valores As List, placeholder As String, ordenamiento As String)
   Dim CSS As String = $""$
 
   myB4JID = ID.ToLowerCase ' for our javascript variable (1), our event (2) and ws.eval (3)
 
   ABMComp.Initialize("ABMComp", Me, InternalPage, ID, CSS)
 
  ' see further in the text why we do this
   ABMComp.Tag = Me
 
   Dim json As JSONGenerator
   Dim optionsmap As Map
   optionsmap.Initialize
   optionsmap.Put("options",lista_valores)
   json.Initialize(optionsmap)
   options = json.ToPrettyString(1)
   placeh = placeholder
   orden = ordenamiento
End Sub

Sub ABMComp_Build(InternalPage As ABMPage, internalID As String) As String
   myHTMLID = internalID
   Log("myB4JID: " & myB4JID)
   Log("myHTMLID: " & myHTMLID)
 
   Return $"<div class="control-group">
               <select id="${internalID}" class="contacts" placeholder="${placeh}"></select>
           </div>var _${myB4JID};"$ ' (1) don't forget making a variable if you want to use it later (e.g. to call a method of the javascript component
End Sub

' Is useful to run some initalisation script.
Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
   Dim script As String = $"_${myB4JID} = $('#${myHTMLID}').selectize({
    maxItems: 1,
    valueField: 'id',
    labelField: 'campo1',
    searchField: ['campo1','campo2'],
    sortField: [
        {field: 'campo1', direction: '${orden}'},
        {field: 'campo2', direction: '${orden}'}
    ],
   ${options.SubString2(1,options.Length-1)},
   render: {
        item: function(item, escape) {
            var name = item.campo1;
           return '<div>' + (name ? '<span class="campo1">' + escape(name) + '</span>' : '') +   '</div>';
        },
        option: function(item, escape) {
            var name = item.campo1;
            var label = name || item.campo2;
            var caption = name ? item.campo2 : null;
            return '<div>' +
   '<span class="label">' + escape(label) + '</span>' +
                   (caption ? '<span class="caption">' + escape(caption) + '</span>' : '') +
   '</div>';
           }
       },
       onChange: function (value) {
           b4j_raiseEvent('page_parseevent', {'eventname': '${myB4JID}' + '_onchange','eventparams': 'value', 'value': value}); // (2)   
       }
   });

   if (!($('#${myHTMLID}').hasClass('contacts'))) {
       $('#${myHTMLID}').addClass('contacts');
   }

   if (!($('.selectize-control').hasClass('contacts'))) {
       $('.selectize-control').addClass('contacts');
   }"$
 
   InternalPage.ws.Eval(script, Array As Object(myB4JID)) ' (3)
   ' here we don't need to flush as it passes through ABM
End Sub

' runs when a refresh is called
Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)
 
End Sub

' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)
   ABMComp.Tag = null
End Sub

Public Sub Visibility(InternalPage As ABMPage, hide As Boolean)
   Dim script As String = ""
   If Not(hide) Then
       script = $"$("#${myHTMLID}-selectized").parent('div').parent('div').attr("hidden","true");"$
   
   Else
       script = $"$("#${myHTMLID}-selectized").parent('div').parent('div').removeAttr( "hidden");"$
   End If
   InternalPage.ws.Eval(script, Null)
   InternalPage.ws.Flush ' here we must manually flush as this does not pass through ABM
End Sub

Public Sub Enabled(InternalPage As ABMPage, enable As Boolean)
   Dim script As String = ""
   If enable Then
       script = $"var $select = $("#${myHTMLID}").selectize();
                  $select[0].selectize.enable();"$
   
   Else
       script = $"var $select = $("#${myHTMLID}").selectize();
                  $select[0].selectize.disable();"$
   End If
   InternalPage.ws.Eval(script, Null) ' here we must manually flush as this does not pass through ABM
End Sub
I also took the opportunity to demonstrate a good use of the .Tag to run our Visibility and Enabled methods: we attach the parent class of the component to our ABMComp component.

In the page, we add a couple of variables to simulate a toggle:
B4X:
Sub Class_Globals
   ...
   Private visibleToggle As Boolean = True
   Private enableToggle As Boolean = True
End Sub
in BuildPage we add the custom css/js files (TIP: try to merge the CSS files in one file, it will be faster)
B4X:
page.AddExtraJavaScriptFile("custom/selectize.min.js")
page.AddExtraCSSFile("custom/selectize.css")
page.AddExtraCSSFile("custom/selectize.bootstrap3.css")
page.AddExtraCSSFile("custom/selectize-doble.css")
In ConnectPage() we add out component and a couple of buttons:
B4X:
public Sub ConnectPage()       
   '   connecting the navigation bar
   ABMShared.ConnectNavigationBar(page)
 
   Dim cont As ABMContainer
   cont.Initialize(page,"cont","")
   cont.AddRows(1,False,"").AddCells12(2,"")
   cont.BuildGrid
   page.Cell(1,1).AddComponent(cont)
   Dim lista As List
   lista.Initialize
   lista = listaselectize(lista, "1a", "Prueba1", "Texto1")
   lista = listaselectize(lista, "2b", "Prueba2", "Texto2")
   lista = listaselectize(lista, "3c", "Prueba3", "Texto3")
   lista = listaselectize(lista, "4f", "Prueba4", "Texto4")

   Dim selectize As Selectisize
   selectize.Initialize(page, "selectize1", lista, "Select something", "asc")

   cont.Cell(1,1).AddComponent(selectize.ABMComp)

   Dim btnToggleVisibility As ABMButton
   btnToggleVisibility.InitializeFlat(page, "btnToggleVisibility", "", "", "Toggle Visibility", "")
   page.Cell(3,1).AddComponent(btnToggleVisibility)
 
   Dim btnToggleEnabled As ABMButton
   btnToggleEnabled.InitializeFlat(page, "btnToggleEnabled", "", "", "Toggle Enabled", "")
   page.Cell(4,1).AddComponent(btnToggleEnabled)

   ' refresh the page
   page.Refresh
 
   ' Tell the browser we finished loading
   page.FinishedLoading
   ' restoring the navigation bar position
   page.RestoreNavigationBarPosition

End Sub

Sub listaselectize(lista As List, id As String, campo1 As String, campo2 As String) As List
   Dim elementos As Map
   elementos.Initialize
   elementos.Put("id",id)
   elementos.Put("campo1",campo1)
   elementos.Put("campo2",campo2)
 
   lista.Add(elementos)
 
   Return lista
End Sub
We define our event:
B4X:
Sub selectize1_OnChange(value As String)
   Log(value)
End Sub
And in our buttons we make use of the .Tag value we've set before to get our ABMCustomComponents 'wrapper' class:
B4X:
Sub btnToggleVisibility_Clicked(Target As String)
   Dim cont As ABMContainer = page.Component("cont")
   Dim selectizeABMComp As ABMCustomComponent = cont.Component("selectize1")
   Dim selectize As Selectisize = selectizeABMComp.Tag
 
   visibleToggle = Not(visibleToggle)
   selectize.Visibility(page, visibleToggle)
End Sub

Sub btnToggleEnabled_Clicked(Target As String)
   Dim cont As ABMContainer = page.Component("cont")
   Dim selectizeABMComp As ABMCustomComponent = cont.Component("selectize1")
   Dim selectize As Selectisize = selectizeABMComp.Tag
 
   enableToggle = Not(enableToggle)
   selectize.Enabled(page, enableToggle)
End Sub
I hope this will get you started!

Alwaysbusy
Hello, in the previous post i did a description and a video with the problem. Thanks a lot!
 
Top