B4J Tutorial [ABMaterial] Star Rating ABMCustomComponent

alwaysbusy

Expert
Licensed User
Michał asked me in the feedback app to implement some kind of Star Rating component. As this is not so commonly needed, I thought this could be another good example of an ABMCustomComponent.

This is what we are going to build (we can 'hover' over the stars to set it):



I show you how to do it in ABMaterial 3.75 first with the new features (released soon), and will then explain what needs to be changed for previous versions.

1. We create a new standard class CompStarRating:
B4X:
'Class module
Sub Class_Globals
   Public ABMComp As ABMCustomComponent
   Dim myInternalName 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, radioName As String)
   myInternalName = radioName
   Dim CSS As String = $"
   #half-stars-example .rating-group {
       display: inline-flex;
   }
   #half-stars-example .rating__icon {
       pointer-events: none;
   }
   /* This is an important part: we MUST override the Materialize CSS implementation of the the radio button! */
   #half-stars-example .rating__input + label:before,
   #half-stars-example .rating__input + label:after {
       position: absolute !important;
       left: -9999px !important;           
   }   
   #half-stars-example .rating__label {
       cursor: pointer;
       padding: 0 0.1em;
       font-size: 2rem;
     margin-top: 1rem;
   }
   #half-stars-example .rating__label--half {
       padding-right: 0;
       margin-right: -0.6em;
       z-index: 2;
   }
   #half-stars-example .rating__icon--star {
       color: orange;
   }
   #half-stars-example .rating__icon--none {
       color: #eee;
   }
   #half-stars-example .rating__input--none:checked + .rating__label .rating__icon--none {
       color: red;
   }
   #half-stars-example .rating__input:checked ~ .rating__label .rating__icon--star {
       color: #ddd;
   }
   #half-stars-example .rating-group:hover .rating__label .rating__icon--star,
   #half-stars-example .rating-group:hover .rating__label--half .rating__icon--star {
       color: orange;
   }
   #half-stars-example .rating__input:hover ~ .rating__label .rating__icon--star,
   #half-stars-example .rating__input:hover ~ .rating__label--half .rating__icon--star {
       color: #ddd;
   }
   #half-stars-example .rating-group:hover .rating__input--none:not(:hover) + .rating__label .rating__icon--none {
       color: #eee;
   }
   #half-stars-example .rating__input--none:hover + .rating__label .rating__icon--none {
       color: red;
   }"$
   ABMComp.Initialize("ABMComp", Me, InternalPage, ID, CSS)
End Sub

Sub ABMComp_Build(InternalPage As ABMPage, internalID As String) As String
   Return $"<div id="half-stars-example">
  <div class="rating-group" id="${internalID}">
  <input class="rating__input rating__input--none" checked name="${myInternalName}" id="rating-0" value="0" type="radio">
  <label title="0 stars" class="rating__label" for="rating-0">&nbsp;</label>
     <input class="rating__input" id="rating-00" value="-1" type="radio">
  <label title="0.5 stars" class="rating__label rating__label--half" for="rating-05"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
  <input class="rating__input" name="${myInternalName}" id="rating-05" value="0.5" type="radio">
  <label title="1 star" class="rating__label" for="rating-10"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
  <input class="rating__input" name="${myInternalName}" id="rating-10" value="1" type="radio">
  <label title="1.5 stars" class="rating__label rating__label--half" for="rating-15"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
  <input class="rating__input" name="${myInternalName}" id="rating-15" value="1.5" type="radio">
  <label title="2 stars" class="rating__label" for="rating-20"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
  <input class="rating__input" name="${myInternalName}" id="rating-20" value="2" type="radio">
  <label title="2.5 stars" class="rating__label rating__label--half" for="rating-25"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
  <input class="rating__input" name="${myInternalName}" id="rating-25" value="2.5" type="radio">
  <label title="3 stars" class="rating__label" for="rating-30"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
  <input class="rating__input" name="${myInternalName}" id="rating-30" value="3" type="radio">
  <label title="3.5 stars" class="rating__label rating__label--half" for="rating-35"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
  <input class="rating__input" name="${myInternalName}" id="rating-35" value="3.5" type="radio">
  <label title="4 stars" class="rating__label" for="rating-40"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
  <input class="rating__input" name="${myInternalName}" id="rating-40" value="4" type="radio">
  <label title="4.5 stars" class="rating__label rating__label--half" for="rating-45"><i class="rating__icon rating__icon--star fa fa-star-half"></i></label>
  <input class="rating__input" name="${myInternalName}" id="rating-45" value="4.5" type="radio">
  <label title="5 stars" class="rating__label" for="rating-50"><i class="rating__icon rating__icon--star fa fa-star"></i></label>
  <input class="rating__input" name="${myInternalName}" id="rating-50" value="5" type="radio">
  </div>  
</div>"$
End Sub

public Sub GetCurrentRating(InternalPage As ABMPage) As Double
   Dim script As String = $"return $('input[name=${myInternalName}]:checked').val();"$
   Dim ret As Future = InternalPage.ws.EvalWithResult(script, Null)
   InternalPage.ws.Flush
 
   Return ret.Value
End Sub

public Sub SetCurrentRating(InternalPage As ABMPage, value As Double) {
   Dim script As String = $" $('input[name=${myInternalName}][value="${value}"]').prop('checked', 'checked');"$
   InternalPage.ws.Eval(script, Null)
   InternalPage.ws.Flush
End Sub

' Is useful to run some initalisation script.
Sub ABMComp_FirstRun(InternalPage As ABMPage, internalID As String)
   Dim script As String = $""$
   InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
   ' flush not needed, it's done in the refresh method in the lib
End Sub
' runs when a refresh is called
Sub ABMComp_Refresh(InternalPage As ABMPage, internalID As String)
   Dim script As String = $""$
   InternalPage.ws.Eval(script, Array As Object(ABMComp.ID))
End Sub
' do the stuff needed when the object is removed
Sub ABMComp_CleanUp(InternalPage As ABMPage, internalID As String)
End Sub
ABMaterial users will notice I have added the CSS directly into the component. By doing so, we do not need to use the page.AddExtraCSSFile method in the Page_Build() method. (you can still do it this way, and in some cases it may be better as this part can be big. And in the final version, compress it!). In ABMaterial before 3.75, using AddExtraCSSFile is the only way possible.

The only difficult part in the CSS was I had to override Materialize CSS's implementation of a radio button (the 'circle'). So we had to get rid of it.

2. Now we can make a variable for our StarComponent in the page Class_Globals, as we want to be able to Set and Get the value:
B4X:
Dim myRating As CompStarRating
3. In Page_Connect() we add our component and a couple of buttons:
B4X:
...
myRating.Initialize(page, "myRating", "rating")
page.Cell(1,1).AddComponent(myRating.ABMComp)
 
Dim btn As ABMButton
btn.InitializeFlat(page, "btn", "", "", "Get value", "")
page.Cell(2,1).AddComponent(btn)
   
Dim btn2 As ABMButton
btn2.InitializeFlat(page, "btn2", "", "", "Set 2.5 stars value", "")
page.Cell(3,1).AddComponent(btn2)
...
page.Refresh
...
4. And our Get and Set code in the buttons:
B4X:
Sub btn_Clicked(Target As String)
   Dim value As Double = myRating.GetCurrentRating(page)
   Log(value)
End Sub

Sub btn2_Clicked(Target As String)
   myRating.SetCurrentRating(page, 2.5)   
End Sub
That is all! :)

Changes for versions of ABMaterial before 3.75

1.
Save the CSS string into a text file (e.g. star.rating.css) and copy it to the /css/custom folder.
2. Remove the CSS param from ABMComp.Initialize("ABMComp", Me, InternalPage, ID, CSS)
B4X:
ABMComp.Initialize("ABMComp", Me, InternalPage, ID) '<-- CSS param deleted
3. In Sub ABMComp_Build(InternalPage As ABMPage, internalID As String) As String remove the InteranlPage param:
B4X:
Sub ABMComp_Build(internalID As String) As String
4. In Page_Build(), load the css file you created:
B4X:
page.AddExtraCSSFile("custom/star.rating.css")
Alwaysbusy
 
Top