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:
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:
3. In Page_Connect() we add our component and a couple of buttons:
4. And our Get and Set code in the buttons:
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)
3. In Sub ABMComp_Build(InternalPage As ABMPage, internalID As String) As String remove the InteranlPage param:
4. In Page_Build(), load the css file you created:
Alwaysbusy
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"> </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
B4X:
Sub ABMComp_Build(internalID As String) As String
B4X:
page.AddExtraCSSFile("custom/star.rating.css")
Alwaysbusy