
Security News
npm Adopts OIDC for Trusted Publishing in CI/CD Workflows
npm now supports Trusted Publishing with OIDC, enabling secure package publishing directly from CI/CD workflows without relying on long-lived tokens.
js-skeleton
Advanced tools
Skeleton is a small library that lets you create models, lists, templates, client-side router, popups, events and bindings. It also provides utilities for painless online/offline network support, cookies, local storage, type checking, form handling and in
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'
});
<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 });
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>
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
}
});
// 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!`);
});
/* 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>
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.
-->
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
}
});
<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
Skeleton.network(); // Automatically invokes message when connection losts and stablizes
// 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
FAQs
Skeleton is a small library that lets you create models, lists, templates, client-side router, popups, events and bindings. It also provides utilities for painless online/offline network support, cookies, local storage, type checking, form handling and in
The npm package js-skeleton receives a total of 0 weekly downloads. As such, js-skeleton popularity was classified as not popular.
We found that js-skeleton demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
npm now supports Trusted Publishing with OIDC, enabling secure package publishing directly from CI/CD workflows without relying on long-lived tokens.
Research
/Security News
A RubyGems malware campaign used 60 malicious packages posing as automation tools to steal credentials from social media and marketing tool users.
Security News
The CNA Scorecard ranks CVE issuers by data completeness, revealing major gaps in patch info and software identifiers across thousands of vulnerabilities.