All components extends from ViewComponent, and whenever gets rendered in the UI/browser can be assigned to the template variable as depicted below in lines 11 to 19.
import{ViewComponent}from"../../@still/component/super/ViewComponent.js";exportclassHomeComponentextendsViewComponent{/** * isPublic flag is needed for any component that is publicly accessible, therefore, * when dealing with authentication and permission scenario any component requiring * user permission the flag will be removed or turned to false */isPublic=true;template=` <div> <h2>Hello world!</h2> <p> I'm an easy component with a button </p> <button>I'm a button</button> </div> `;}
Run result:
Splitting Template (.html) from .js file
As long as both template/.html and .js files are in the same folder and have the are named similarly, we just need to remove the variable template from .js and it will be able to refer to the .html file instead. Tamplete splitting from .js file is quite usefull for complex template coding also making it more manageable and organized.
This component is placed under the app/components/splitting/ path
<div><h3> This is the header for the splitted template file
</h3><br/><hr/><br/><p> Template splitted from the .js file is quite usefull especially on
those situations that the template content is quite extensive,
normally template inside the .js file can be natually used for smaller
template cases.
</p></div>
Run result:
Event handling
For event handling, we can create a method inside the component class, then we just need to bind it to any html element by assigning it to the (click) notation/directive as follow and highlighet in lines 12 and 16 to 18:
This component is placed under the app/components/event/ path
import{ViewComponent}from"../../@still/component/super/ViewComponent.js";exportclassHomeWithEventextendsViewComponent{isPublic=true;template=` <div> <h2>Hello world from HomeWithEvent!</h2> <p> I'm an easy component with a button </p> <button (click)="callMe()">I'm a button</button> </div> `;callMe(){alert(`Hi, you clicked me, I'm a button`);}}
Run result:
State binding and reactive behavior
All dev defined variable are considered state which is managed by the components instance itself, when it comes to use it, any component calling it can use and listen to it. State binding can be achieved by using @stateVariableName inside the place where it's to be bound.
Access to the state value is done by calling .value property, assigning a value is is done straight to the property itself.
This component is placed under the app/components/counter/ path
import{ViewComponent}from"../../../@still/component/super/ViewComponent.js";exportclassCounterComponentextendsViewComponent{isPublic=true;/** * This is a state since no annotation or signature is put for it */count=0;template=` <div> <p> My counter state is @count </p> <button (click)="increment()">Increment (@count)</button> <button (click)="decrement()">Decrement (@count)</button> </div> `;increment(){this.count=this.count.value+1;}decrement(){this.count=this.count.value-1;}}
Run result:
Two-way data binding and Forms
When building a form, in several situations Two-way data binding is needed, the (value) notation/directive is provided in which we only need to assign the state in which we are binding out form input, also input needs to be wrapped by a form (<form></form>).
This component is placed under the app/components/form/ path
import{ViewComponent}from"../../../@still/component/super/ViewComponent.js";exportclassBasicFormextendsViewComponent{isPublic=true;firstName='';shoeSize;template=` <div> <form> <div class="form-group"> <label>First Name</label> <input (value)="firstName" type="text" placeholder="Enter first name"> </div> <br/> <div class="form-group"> <label>Shoe Size</label> <input (value)="shoeSize" (validator)="number" (validator-warn)="Invalid shoe size, number is required" placeholder="Enter valid shoe size" > </div> </form> <br/> <p>Welcome <b>@firstName</b></p> <br/> <button (click)="setFirstName('Michael')">Set Michael</button> <button (click)="setFirstName('Dario')">Set Dario</button> </div> `;/** Single line method using arrow function */setFirstName=(val)=>this.firstName=val;}
Run result:
Input/Form grouping Important consideration
Still.js adopts the Bootstrap approach when it comes to form group, though it's not needed, this is quite helpfull to organizing form component by adding it (Bootstrap) as part of the project, also it helps properly handle alignment/positioning like labels and validation messages.
Conditional rendering and Conditional Hide/Unhide
By creating a variable annotated with @Prop (using JSDoc approach) we can then use this as flags (or any other application flow value) thereby being possible to assigne it on the Still.js directive, in this case to render or not, or hide/unhide (renderIf) and (showIf) notations are provided respectively.
This component is placed under the app/components/conditoinal-render/ path
import{ViewComponent}from"../../../@still/component/super/ViewComponent.js";exportclassBasicConditionalViewextendsViewComponent{isPublic=true;/** * The props differ from state (which does not have any annotation) * in a way that state allow to trace changes which is also useful * in the component to component communication scenario in real time/reactively * @Prop */isAdminPermisison=false;//Annotation can be put in the same line of the prop/variable/** @Prop */shouldShowContent=true;addLabel='Hide';adminLabel='Unable';template=` <div> <div (renderIf)="self.isAdminPermisison"> Hello, this part of the content wont be rendered since the flag on (renderIf) is false, even if you click in the second button which turns flag to true </div> <p (showIf)="self.shouldShowContent"> If you click the button bellow this content will be unhide <br>in case flag is true, and hidden if false </p> <button (click)="hideOrUnhide()">@addLabel content</button> <button (click)="renderContent()">@adminLabel Admin</button> </div> `;hideOrUnhide(){this.addLabel='Hide';this.shouldShowContent=!this.shouldShowContent;if(!this.shouldShowContent)this.addLabel='Unhide';}renderContent(){this.adminLabel='Unable';this.isAdminPermisison=!this.isAdminPermisison;if(this.isAdminPermisison)this.adminLabel='Able';}}
Run result:
Adding CSS Styles
Everything is base in Vanilla web technologies, therefore we can just write CSS naturally by creating the scope, but it it also allows inline CSS if needed just like normall HTML with css in it.
This component is placed under the app/components/styled/ path
Bringing a component inside another in general is achievable by using the <st-element></st-element> tag where we can then specify the component name we want to embed as child (tag property as line 16), additional child component property (e.g. lines 18 and 19) and event handlers also can be passed the same way (in the tag) as long as they child difined it.
UserForm.js - This component is placed under the app/components/embed/ path
Whe using still-cli (@stilljs/cli - which is the recommended way) to generate the component, both route name (same as component name) and component URL will be added automatically in the route.map.js file in the project root folder, therefore, navigation can be done the way it workes in regular web pages. in the bellow code navigation is done by using route name.
EntryMenu.js - This component is placed under the app/components/menu/ path
import{ViewComponent}from"../../../@still/component/super/ViewComponent.js";exportclassEntryMenuextendsViewComponent{isPublic=true;template=` <div> This is Entry menu component, press the bellow <br/>button or in the link to navigate to User <br/> <br/> <button (click)="goto('UserRegistration')">Register user</button> </div> `;}
UserRegistration.js - This component is placed under the app/components/user/ path
Because Still.js is 100% pure/Vanilla JavaScript, DOM manipulation can be done straight as the native/regular DOM API, no workaround or additional layer/special coding is needed.
This component is placed under the app/components/dom/ path
import{ViewComponent}from"../../../@still/component/super/ViewComponent.js";exportclassLoginComponentextendsViewComponent{isPublic=true;userName;password;template=` <form onsubmit="return false;"> <div>Type the same for both user and password for <br>success login, something else for invalid<div> Username: <input type="text" (value)="userName"> <br><br> Password: <input type="password" (value)="password"><br> <span id="loginStatus"></span><br> <button (click)="processLogin()">Login</button> </form> `;processLogin(){constuser=this.userName.value;constpassword=this.password.value;constmessageContainer=document.getElementById('loginStatus');if(user!==password||user==''||password==''){/** Assignin new content via DOM manipulation */messageContainer.innerHTML='Invalid user or password';/** CSS updating through DOM */messageContainer.style='color: red; background-color: #ab1f1f38;';/** Changing inputs border via DOM manipulation */document.querySelector('input[type=text]').style='border: 1px solid red';document.querySelector('input[type=password]').style='border: 1px solid red';}else{/** Assignin new content via DOM manipulation */messageContainer.innerHTML='User login success! ☻';/** CSS updating through DOM */messageContainer.style='color: green; background-color: none;';/** Changing inputs border via DOM manipulation */document.querySelector('input[type=text]').style='border: 1px solid green';document.querySelector('input[type=password]').style='border: 1px solid green';}}}
Run result:
Looping and Rendering from a List
Lopping a list and rendering its items is quite simple, Still.js provides the (forEach) notation/directive, which can be pass to a top level container which is then used to wrap the template for the desired output of each list item.
This component is placed under the app/components/looping/ path
import{ViewComponent}from"../../../@still/component/super/ViewComponent.js";exportclassLoopingDirectiveextendsViewComponent{isPublic=true;/** This is the list of products ( data source ) */productList=[{name:'Orange',sold:3,stockAvail:7,price:'0.75$'},{name:'Apple',sold:1,stockAvail:5,price:'0.88$'},{name:'Banana',sold:10,stockAvail:50,price:'1.03$'},]template=` <div> <h5>Looping with HTML child</h5> <br> <span (forEach)="productList"> Stock Availability <div each="item"> <b>Name:</b> {item.name} - <b>Sock:</b> {item.stockAvail} - <b>Price:</b> {item.price} </div> <span> </div> <br><hr><br/> <div> <h5>Looping with child Component</h5> <br> <span (forEach)="productList"> Shipping Cart Checkout <!-- Fields are mapped one to one from data source (productList) to the child component state --> <st-element component="ShoppingItem" each="item"></st-element> <span> </div> `;}
This component is placed under the app/components/looping/ path