If skeleton.js made an effect on your projects, you are more than welcome to show support by buying me a coffee ;)
const RecordModel = 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.`);
}
});
const RecordsList = Skeleton.List({
model: RecordModel,
element: 'records',
templateId: 'record-template'
});
element: field that specifies the id of the DOM element that each list item should be rendered into.
<template id="record-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>
</template>
const RecordsList = 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>`
});
RecordsList.addFilter('helloFirst', (txt) => `Hello ${txt}`);
<div>{{ artist | helloFirst }}</div>
{
location: {
country: "Spain",
city: "Madrid",
friends_addresses: {
Jose: "Gran Villa 3",
Antonio: "La kukaracha 67"
}
}
}
<template id="abroad-friends-template">
<div data-id="{{ index }}">
<div class="head">
<span class="float-left">{{ location.country | capitalize }}</span>
<span class="float-right">{{ location.city | capitalize }}</span>
<div class="clear"></div>
</div>
<div class="body">
<span>Jose: {{ location.friends_addresses.Jose | upper }}</span>
<span>Antonio: {{ location.friends_addresses.Antonio | upper }}</span>
</div>
<div class="json">
<span>Whole json object: {{ location | json }}</span>
</div>
</div>
</template>
RecordsList.push({ artist: 'prince', song: 'purple Rain', album: 'Purple Rain', year: 1984, sold: 22000000 });
And if you want the model to appear first in the list, just use 'unshift' and it will render automatically
RecordsList.unshift({ artist: 'prince', song: 'purple Rain', album: 'Purple Rain', year: 1984, sold: 22000000 });
$.getJSON('/artists-records-api-path', (data) => {
RecordsList.pushAll(data); // The data is pushed and immediately renders
});
RecordsList.removeAll(); // The data is removed and immediately empties the list container
<div data-id="{{ index }}">
<button onClick="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
Now, you need to define a 'remove'/'edit' function, and use the built-in functionallity of a skeleton list:
window.remove = (index) => {
let modelToRemove = 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.
}
window.remove = (index) => {
let modelToRemove = 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);
}
window.edit = (index, options) => { // function exposed to window object for user interaction
RecordsList.edit(index, options); // built in functionallity of skeleton
}
// example usage
edit(2, { year: 1980, sold: 23000000 }); // edit 3rd model in the list, change and render specified fields
// If record sold less than 5 million, remove it from the list
RecordsList.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.
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 remove
RecordsList.subscribe(() => {
alert(`Right now there are ${RecordsList.size()} records in the list!`);
});
// This will only run on push
RecordsList.subscribe('push', (model) => {
console.log(`The model ${JSON.stringify(model)} was pushed!`);
});
// This will only run on remove
RecordsList.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.
/* A common use case when you would want to subscribe to an event would be I/O, for example: */
// add model to db
RecordsList.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 db
RecordsList.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 function
RecordsList.subscribe(
['push', 'pushAll', 'remove', 'filter', 'sort', 'edit'],
() => console.log('I work hard since many events are subscribed to me!')
);
let filteredRecords = 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 underneath
RecordsList.subscribe('filter', (filteredRecords) => {
alert(`After filtering, there are ${filteredRecords.length} records in the list!`);
});
// 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.
let unsub = 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! :)
<template id="record-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 class="selling-shops">
<!-- Here the loop declaration begins -->
<div class="shop" data-loop="shops">
<span>{{ #name }}</span>
<span>{{ #address }}</span>
<span>{{ #this | json }}</span>
</div>
<!-- Here it ends -->
</div>
</div>
</template>
You can use '#this' if you want to resolve what's in the array and not its fields. This is usefull if
const RecordModel = 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.`);
}
});
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'}
]
});
<div data-loop="people">
<p>{{ #name | capitalize }}</p>
<h2>Best Friend:</h2>
<div>
<p>{{ #friends.best.name | upper }}</p>
<p>{{ #friends.best.age }}</p>
</div>
<h3>Good Friend:</h3>
<div>
<p>{{ #friends.good.name | lower }}</p>
<p>{{ #friends.good.age }}</p>
</div>
</div>
// The 'people' array is an array of objects that looks something like this:
{
people: [
{
name: '',
friends: {
best: {
name: '',
age: ''
},
good: {
name: '',
age: ''
}
}
}
]
}
const comperator = (a,b) => {
if(a.artist > b.artist)
return 1;
if(a.artist < b.artist)
return -1;
return 0;
}
RecordsList.sort(comperator); // pass comperator function to 'sort' method
RecordsList.subscribe('sort', (sorted) => {
alert(`The sorted array is ${JSON.stringify(sorted)}`);
});
<template id="food-template">
<div data-id="{{ index }}">
<input type="checkbox" data-checked="isLiked" onChange="toggleFood({{ index }})" />
<span class="food">{{ food }}</span>
</div>
</template>
// list
const FoodList = Skeleton.List({
model: Skeleton.Model( { defaults: { food: '', isLiked: false } } ),
element: 'food-list',
templateId: 'food-template'
});
// on checkbox change
window.toggleFood = function(index) {
let isLiked = !FoodList.get(index).isLiked;
FoodList.edit(index, { isLiked }); // rerenders the model
}
// model
const FruitModel = Skeleton.Model({
defaults: {
name: '',
isYellow: false
}
});
<template id="fruit-template">
<div data-id="{{ index }}">
<p class="fruit-name">{{ name }}</p>
<p data-hide="isYellow">I am not yellow!</p>
<p data-show="isYellow">I am yellow and I know it!</p>
</div>
</template>
.italic {
font-style: italic;
}
.under {
text-decoration: underline;
}
<p data-class='{"italic": "isChosen", "under": "!isChosen"}'>Wowwwwwww!</p>
<p data-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.
-->
an efficient and easy-to-use router. Here is how you can use it, pretending we sell products online:
const router = Skeleton.Router(); // initialize router
// set paths and handler functions
router.path('/music', () => renderTemplate('music'));
router.path('/books', () => renderTemplate('books'));
router.path('/clothes', () => renderTemplate('clothes'));
router.path('/plants', () => renderTemplate('plants'));
<ul class="menu">
<li onClick="router.visit('/music')">Music</li>
<li onClick="router.visit('/books')">Books</li>
<li onClick="router.visit('/clothes')">Fashion</li>
<li onClick="router.visit('/plants')">Garden</li>
</ul>
router.path('/guy/:lastname/was/:where', (params) => renderTemplate(params.lastname, params.where));
router.visit('/guy/peer/was/here'); // params = {lastname:'peer',where:'here'}
// Save complex objects to localStorage
Skeleton.storage.save({
models: RecordsList.models(),
size: RecordsList.size()
});
// Fetch complex objects from localStorage
let models = Skeleton.storage.fetch('models');
let size = Skeleton.storage.fetch('size');
// Clear storage
Skeleton.storage.clear();
<!-- This is a form to submit a record to the list -->
<form name="record-form">
<input type="text" placeholder="album name" id="record-album" />
<input type="text" placeholder="artist name" id="record-artist" />
<input type="text" placeholder="song name" id="record-song" />
<input type="number" min="1920" max="2017" id="record-year" />
<input type="number" min="0" id="record-sold" />
<button type="submit" id="record-submit">Add Record</button>
</form>
Skeleton.form({
name: 'record-form',
inputs: {
album: 'record-album',
artist: 'record-artist',
song: 'record-song',
year: 'record-year',
sold: 'record-sold'
},
submit: 'record-submit',
onSubmit(e) {
RecordsList.push({
album: this.album.value,
artist: this.artist.value,
song: this.song.value,
year: Number(this.year.value),
sold: Number(this.sold.value)
});
Skeleton.form.clear(this.name); // clear form's input and textarea fields
}
});
to prevent the default browser behavior and let you use an ajax call instead which is the standart today.
The 'submit' field can be an id of a submit button, or an object with 'input' and 'keyCode' fields, if
you want the 'onSubmit' function to invoke on a specific key press like 'enter' key, which has the key code 13.
<form name="friends-form">
<input type="text" placeholder="friend name" id="friend-field" />
</form>
Skeleton.form({
name: 'friends-form',
inputs: {
friend: 'friend-field'
},
submit: {
input: 'friend',
keyCode: 13 // 'enter' key code
},
onSubmit(e) {
let friend = this.friend.value;
if(!friend)
return;
FriendsList.push({ friend });
Skeleton.form.clear(this.name); // clear form's input and textarea fields
}
});
<input type="text" placeholder="Search Artist" id="search-artist" />
// Just type this, and the input element will be cached
Skeleton.input('search-artist');
// If you want to get the input value:
let value = 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
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');
<input type="text" id="my-input" />
<p id="my-text"></p>
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');
<input type="text" id="first-name" />
<input type="text" id="last-name" />
<p id="full-name"></p>
Skeleton.bind('full-name')
.to('first-name', 'last-name')
.exec((firstName, lastName) => {
return `Your name is ${firstName} ${lastName}!!`;
});
const popup = Skeleton.Popup(); // initialize popup
{
overlay: {
bgcolor:'black',
opacity:'0.8'
},
popup: {
width:'400',
height:'400',
bgcolor:'white'
}
}
popup.setDefaults({
overlay: {
bgcolor:'blue',
opacity:'0.6'
},
popup: {
width:'500',
height:'400',
bgcolor:'green'
});
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'.
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'.
/* overlay */
#skeleton-overlay {}
/* popup */
#skeleton-popup {}
/* confirm buttons */
#skeleton-popup #confirm-yes-label {}
#skeleton-popup #confirm-no-label {}
/* message button */
#skeleton-popup #close-message-popup {}
const emitter = Skeleton.Event(); // initialize event emitter
// set events & listeners
emitter.on('calc', (a,b,c) => alert(a+b+c)); // set listener for 'calc' event
emitter.on('basketball', () => console.log('I love basketball!')); // set listener for 'basketball' event
emitter.on('calc', (a,b) => console.log(a*b)); // set another listener for 'calc' event
// emit events
emitter.emit('calc', 1, 2, 3); // alerts 6, logs 2
emitter.emit('basketball'); // logs 'I love basketball!'
// dispose event
emitter.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
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,
// All properties are optional, in case you
// do not provide one, the default is set
Skeleton.network({
// customized online message properties
online: {
message,
position,
width,
height,
color,
textAlign,
backgroundColor,
fontSize,
fontWeight,
fontFamily,
border
},
// customized offline message properties
offline: {
message,
position,
width,
height,
color,
textAlign,
backgroundColor,
fontSize,
fontWeight,
fontFamily,
border
}
});
// get cookies as object of cookie name and its value
let cookiesObject = Skeleton.cookies();
// get a specific cookie value
let specificCookieValue = Skeleton.cookies.get('name');
// set a cookie, with (name, value, expiration days).
// If you do not specify it, expiration days default value is 1
Skeleton.cookies.set('cookieName', 'cookieValue', 3);
// delete a cookie by its name
Skeleton.cookies.delete('cookieName');
const is = Skeleton.Type(); // initialize type checker
is.arr([1, 2, 3]) // true
is.str('hello!') // true
is.num(3) // true
is.func(() => alert('function!')) // true
is.obj({ a: 1, b: 2 }) // true
var a;
is.undef(a) // true
a = null;
is.null(a) // true
is.none(a) // true- if a variable is null or undefined
is.html(document.getElementById('dom-element')) // true
is.hex('#452A55') // true
is.rgb('rgb(10, 45, 63)') // true
is.rgba('rgba(52, 26, 158)') // true
is.color('#af4523') // true- if hex, rgb or rgba