You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If skeleton.js made an effect on your projects, you are more than welcome to show support by buying me a coffee ;)
Skeleton makes rendering lists (add/remove/edit model) very easy.
Let's start. First, create a model:
constRecordModel=Skeleton.Model({defaults: {artist: '',song: '',album: '',year: '',sold: 0},init(){console.log(`The song ${this.get('song')} by ${this.get('artist')} sold ${this.get('sold')} records.`);}});
Required fields:
defaults: field to specify the default values of the model fields.
Optional fields:
init: function to be called everytime the model is initialized.
You can use 'set' passing an object or 2 arguments of key and value,
and 'get', to get values of fields.
You can extend the functionality of a model by defining your own functions.
model: field to specify the model of each element in the list.
element: field that specifies the id of the DOM element that each list item should be rendered into.
template or templateId: field that is either a string that represents the template or an id that
specifies the id of the template in the html document.
Now, lets define a template:
<templateid="record-template"><divclass="record" data-id="{{ index }}"><divclass="record-head"><spanclass="float-left">Album: {{ album }}</span><spanclass="float-right">Year: {{ year }}</span><divclass="clear"></div></div><divclass="record-body">
'{{ song | upper }}' is a song by {{ artist | capitalize }} from the album {{ album }} that sold {{ sold }} records.
</div><divclass="record-footer"><buttononClick="remove({{ index }})">x</button></div></div></template>
Notice that you can use different filters, by pipe | -
* upper
* lower
* capitalize
* currency
* json
Moreover, everytime a new model is rendered, it gets an index parameter generated for free.
Each model rendered has its unique index, and that is very usefull, especially when you
want to remove a model from the list, which I show how to do as we continue.
You can also pass a "template" attribute instead of the templateId attribute, if you
want to provide the template string directly, for example:
constRecordsList=Skeleton.List({model: RecordModel,element: 'records',template: `<div class="record" data-id="{{ index }}"> <div class="record-head"> <span class="float-left">Album: {{ album }}</span> <span class="float-right">Year: {{ year }}</span> <div class="clear"></div> </div> <div class="record-body"> '{{ song | upper }}' is a song by {{ artist | capitalize }} from the album {{ album }} that sold {{ sold }} records. </div> <div class="record-footer"> <button onClick="remove({{ index }})">x</button> </div> </div>`});
And what about adding your own filters? Yeah, piece of cake:
And maybe you want to push a whole array of objects that came back from an api or db-server:
$.getJSON('/artists-records-api-path',(data)=>{RecordsList.pushAll(data);// The data is pushed and immediately renders });
To remove all models from the list just type:
RecordsList.removeAll();// The data is removed and immediately empties the list container
Now, what if you want to have the ability to remove a single model on a button click ?
Well, pretty simple as well. notice the following in the template:
<divdata-id="{{ index }}"><buttononClick="remove({{ index }})">x</button></div>
Please notice that giving the wrapper element of the model a 'data-id' attribute this way is a must, if you
want to have the ability to remove/edit a model of the list.
Now, you need to define a 'remove'/'edit' function, and use the built-in functionallity of a skeleton list:
window.remove=(index)=>{letmodelToRemove=RecordsList.remove(index);// This will remove the model from the list and rerender, and it will return the model removed// Now, you can make an ajax call to remove the model from the db-server if you have one,// or use it for any other reason.}
If you want to get the model before removing it, you can do it by using 'get':
window.remove=(index)=>{letmodelToRemove=RecordsList.get(index);// This will return the model object// Now, you can make an ajax call to remove the model from the db-server if you have one,// and only after you make sure it succeeds, remove it from the eyes of the user.RecordsList.remove(index);}
Similarly you can edit a model:
window.edit=(index,options)=>{// function exposed to window object for user interactionRecordsList.edit(index,options);// built in functionallity of skeleton}// example usageedit(2,{year: 1980,sold: 23000000});// edit 3rd model in the list, change and render specified fields
You can also iterate over the models like this:
// If record sold less than 5 million, remove it from the listRecordsList.forEach((record,idx)=>{if(record.sold<5000000){remove(record.index);// The record is removed from the view}});
Let's say now, that we want some function to run each time something is pushed to the list or removed from it.
To acheive this, use the 'subscribe' function. You can pass one or two arguments:
A callback function to run on both push and remove events, or both the event and its callback. For example:
// This will run on both push or removeRecordsList.subscribe(()=>{alert(`Right now there are ${RecordsList.size()} records in the list!`);});// This will only run on pushRecordsList.subscribe('push',(model)=>{console.log(`The model ${JSON.stringify(model)} was pushed!`);});// This will only run on removeRecordsList.subscribe('remove',(model)=>{console.log(`The model ${JSON.stringify(model)} was removed!`);});
* You can also listen to 'pushAll', 'removeAll', 'push', 'remove', 'filter', 'edit' and 'sort' events.
* Be aware that 'push' listener also listens to when you call 'unshift'.
* If you only pass a callback function, the affected events will be 'push' and 'remove'.
* You can pass array of events if you have a function to run on all of them.
/* A common use case when you would want to subscribe to an event would be I/O, for example: */// add model to dbRecordsList.subscribe('push',(model)=>{$.ajax({type: 'post',dataType: 'json',data: model,url: '/add-model-api',success(){console.log('success');},error(){console.log('error');}});});// edit model on dbRecordsList.subscribe('edit',(model)=>{$.ajax({type: 'post',dataType: 'json',data: model,url: '/edit-model-api',success(){console.log('success');},error(){console.log('error');}});});// An example of many events subscribing to the same functionRecordsList.subscribe(['push','pushAll','remove','filter','sort','edit'],()=>console.log('I work hard since many events are subscribed to me!'));
How do I subscribe to filtering the list? Easy!
letfilteredRecords=RecordsList.filter(model=>model.year>1966);// Returns records that were released after 1966// Now, the view is automatically updated, and you can use the filtered list returned to updated other parts of your app,// or simply use the 'subscribe' method to listen to whenever the list is filtered like shown underneathRecordsList.subscribe('filter',(filteredRecords)=>{alert(`After filtering, there are ${filteredRecords.length} records in the list!`);});
Notice that the filtered list is passed to the listener and you can use it
Now, let's say that after a while we want to unsubscribe from any of our "events". Very easy:
// When we subscribe to an event, an unsubscribe function is returned so we can apply it later on.// Let's say that after we have 100 records we want to unsubscribe.letunsub=RecordsList.subscribe('push',()=>{RecordsList.size()===100 ? unsub() : console.log('A push occured! Ahhhahaha');// 'size' is a function you should call to determine how many models you have in the list});// And that's all there is to it! :)
Back to templates- How do we use loops inside the template?
<templateid="record-template"><divclass="record" data-id="{{ index }}"><divclass="record-head"><spanclass="float-left">Album: {{ album }}</span><spanclass="float-right">Year: {{ year }}</span><divclass="clear"></div></div><divclass="record-body">
'{{ song | upper }}' is a song by {{ artist | capitalize }} from the album {{ album }} that sold {{ sold }} records.
</div><divclass="record-footer"><buttononClick="remove({{ index }})">x</button></div><divclass="selling-shops"><!-- Here the loop declaration begins --><divclass="shop" data-loop="shops"><span>{{ #name }}</span><span>{{ #address }}</span><span>{{ #this | json }}</span></div><!-- Here it ends --></div></div></template>
As you can see, you just need to set 'data-loop' attribute to the part you want to loop over.
'shops' is an array of objects. To resolve the object, use the field name with '#' before the name.
You can use '#this' if you want to resolve what's in the array and not its fields. This is usefull if
your array consists of strings, or if you want to stringify json objects and show them to the user.
Now the records model should look like this:
constRecordModel=Skeleton.Model({defaults: {artist: '',song: '',album: '',year: '',sold: 0,shops: []// Array of objects or strings},init(){console.log(`The song ${this.get('song')} by ${this.get('artist')} sold ${this.get('sold')} records.`);}});
And pushing to render the template looks like this:
RecordsList.push({artist: 'queen',song: 'Bohemian Rhapsody',album: 'A night at the opera',year: 1975,sold: 26000000,shops: [{name: 'Disc',address: 'Washington 3'},{name: 'Musik',address: 'Barbara 5'},{name: 'Flow',address: 'Franklin 8'}]});
// The 'people' array is an array of objects that looks something like this:{people: [{name: '',friends: {best: {name: '',age: ''},good: {name: '',age: ''}}}]}
And sorting your list is also supported. The sorting works as it would work in plain
javascript. If you want to sort by the artist name for example:
constcomperator=(a,b)=>{if(a.artist>b.artist)return1;if(a.artist<b.artist)return-1;return0;}RecordsList.sort(comperator);// pass comperator function to 'sort' method
The view automagically rerenders, and the collection is returned as an array of objects.
And, you can subscribe to 'sort' event.
RecordsList.subscribe('sort',(sorted)=>{alert(`The sorted array is ${JSON.stringify(sorted)}`);});
If you want to check and uncheck a checkbox in a template, use 'data-checked' attribute.
Then you can edit the template when it changes, to rerender:
<templateid="food-template"><divdata-id="{{ index }}"><inputtype="checkbox" data-checked="isLiked" onChange="toggleFood({{ index }})" /><spanclass="food">{{ food }}</span></div></template>
And the list and model:
// listconstFoodList=Skeleton.List({model: Skeleton.Model({defaults: {food: '',isLiked: false}}),element: 'food-list',templateId: 'food-template'});// on checkbox changewindow.toggleFood=function(index){letisLiked=!FoodList.get(index).isLiked;FoodList.edit(index,{ isLiked });// rerenders the model}
To hide and show parts of the template, simply use 'data-hide' and 'data-show' attributes.
<templateid="fruit-template"><divdata-id="{{ index }}"><pclass="fruit-name">{{ name }}</p><pdata-hide="isYellow">I am not yellow!</p><pdata-show="isYellow">I am yellow and I know it!</p></div></template>
Please check the TodoMVC example in the examples folder to see a real life
scenario when it is used on editing a todo.
Now, how do I manipulate css styles as my model updates (usually when edited)?
You can use the 'data-style' or 'data-class' attribute:
<pdata-class='{"italic": "isChosen", "under": "!isChosen"}'>Wowwwwwww!</p><pdata-style='{"fontStyle" : "isChosen ? italic : normal", "textDecoration": "isChosen ? none : underline"}'>Wowwwwwww!</p><!-- ! Please notice that 'isChosen' is a boolean attribute of the model, ! and that in both cases you need to provide a stringified json object, ! since it gets parsed in the evaluation.-->
Please check the TodoMVC example in the examples folder to see it in action.
Today in the world of single page applications, a client side router is a must. Skeleton provides
an efficient and easy-to-use router. Here is how you can use it, pretending we sell products online:
constrouter=Skeleton.Router();// initialize router// set paths and handler functionsrouter.path('/music',()=>renderTemplate('music'));router.path('/books',()=>renderTemplate('books'));router.path('/clothes',()=>renderTemplate('clothes'));router.path('/plants',()=>renderTemplate('plants'));
Another thing built-in is an easy support for usage of browser localStorage:
// Save complex objects to localStorageSkeleton.storage.save({models: RecordsList.models(),size: RecordsList.size()});// Fetch complex objects from localStorageletmodels=Skeleton.storage.fetch('models');letsize=Skeleton.storage.fetch('size');// Clear storageSkeleton.storage.clear();
If you use 'save', please use 'fetch' to get back the data, and not 'localStorage.getItem',
since 'save' and 'fetch' take care of stringifying and parsing json.
Another very conveninet built-in is form and input cache support. You never have to use it,
but you should when you need it, since it gives the code a better structure and makes it
faster and easier to understand.
<!-- This is a form to submit a record to the list --><formname="record-form"><inputtype="text" placeholder="album name" id="record-album" /><inputtype="text" placeholder="artist name" id="record-artist" /><inputtype="text" placeholder="song name" id="record-song" /><inputtype="number" min="1920" max="2017" id="record-year" /><inputtype="number" min="0" id="record-sold" /><buttontype="submit" id="record-submit">Add Record</button></form>
Now, instead of taking the DOM elements, you can use 'Skeleton.form' to do it for you,
cache the elements and give your form a readable structure.
// Just type this, and the input element will be cachedSkeleton.input('search-artist');// If you want to get the input value:letvalue=Skeleton.input.get('search-artist');// If you want to set a value:Skeleton.input.set('search-artist','I am a new input value!');// If you want to clear the input:Skeleton.input.clear('search-artist');// If you want to clear all input values you have cached:Skeleton.input.clear();// call without parameters
You can also define a listener function like you would with 'addEventListener',
and an event to listen to. By default, the event is 'keyup'.
Skeleton.input('search-artist',(evt)=>{console.log(evt.target.value===Skeleton.input.get('search-artist'));// true });// If you want to listen to other event, for example change, just pass it as a third parameter:Skeleton.input('search-artist',(evt)=>console.log('I log on change!'),'change');
You can also bind a DOM node to an input or text area element, so the node will
Skeleton.bind('my-text').to('my-input').exec(value=>{return`My text is updated whenever ${value} updates!`});// By default, the event is 'keyup'. You can change it by passing your desired event to 'exec':Skeleton.bind('my-text').to('my-input').exec(value=>{return`My text is updated whenever ${value} changes!`;},'change');
And you can also bind a node to several input elements:
popup.message({title:'I am the message popup!',body:'I am sending out a love message to the world!',closeMessage:'Click me, and I\'ll disappear'});// required fields are 'title', 'body'.// optional fields are 'closeMessage', 'height', 'width'.
To create a confirm popup:
popup.confirm({title:'I am the confirm popup!',body:'Would you like to confirm me?',yesLabel:'Awsome',noLabel:'Go Away!',approve(){alert('You clicked Awsome!');},regret(){alert('Bye Bye!');popup.close();// close the popup and the overlay}});// required fields are 'title', 'body', 'approve' and 'regret'. // optional fields are 'yesLabel', 'noLabel', 'height', 'width'.
Here is how a stylesheet should look if you want to style the popup and the overlay, buttons, etc.:
The 3 functions provided are: 'on', 'emit' and 'dispose'.
// set events & listenersemitter.on('calc',(a,b,c)=>alert(a+b+c));// set listener for 'calc' eventemitter.on('basketball',()=>console.log('I love basketball!'));// set listener for 'basketball' eventemitter.on('calc',(a,b)=>console.log(a*b));// set another listener for 'calc' event// emit eventsemitter.emit('calc',1,2,3);// alerts 6, logs 2emitter.emit('basketball');// logs 'I love basketball!'// dispose eventemitter.dispose('calc');// 'calc' event can not be called anymore
Online and Offline messages support is built into Skeleton. You should use it, to let your user know
when the application can not perform network tasks because of poor internet connection. It looks like
this by default:
To apply the default behavior of the online/offline messages just type:
Skeleton.network();// Automatically invokes message when connection losts and stablizes
The online message fades away when the connection is stable. Now, this is the default view and messages,
but you can customize it:
// All properties are optional, in case you// do not provide one, the default is setSkeleton.network({// customized online message propertiesonline: {
message,
position,
width,
height,
color,
textAlign,
backgroundColor,
fontSize,
fontWeight,
fontFamily,
border
},// customized offline message propertiesoffline: {
message,
position,
width,
height,
color,
textAlign,
backgroundColor,
fontSize,
fontWeight,
fontFamily,
border
}});
All style properties are regular css properties except 'position', which can be set to
'top', 'bottom', or 'middle', according to where on the screen you want the message to pop.
The 'message' property is the message you want to show.
Cookie handling is also very easy with Skeleton:
// get cookies as object of cookie name and its valueletcookiesObject=Skeleton.cookies();// get a specific cookie valueletspecificCookieValue=Skeleton.cookies.get('name');// set a cookie, with (name, value, expiration days).// If you do not specify it, expiration days default value is 1Skeleton.cookies.set('cookieName','cookieValue',3);// delete a cookie by its nameSkeleton.cookies.delete('cookieName');
Checking types with Skeleton is much easier than it is with plain javascript:
constis=Skeleton.Type();// initialize type checkeris.arr([1,2,3])// trueis.str('hello!')// trueis.num(3)// trueis.func(()=>alert('function!'))// trueis.obj({a: 1,b: 2})// truevara;is.undef(a)// truea=null;is.null(a)// trueis.none(a)// true- if a variable is null or undefinedis.html(document.getElementById('dom-element'))// trueis.hex('#452A55')// trueis.rgb('rgb(10, 45, 63)')// trueis.rgba('rgba(52, 26, 158)')// trueis.color('#af4523')// true- if hex, rgb or rgba