Last active
November 21, 2018 18:46
-
-
Save freshcutdevelopment/c9c19fbe1e147fc39ac864cf0b694fd1 to your computer and use it in GitHub Desktop.
Aurelia Gist - Add book form
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<require from="book-form"></require> | |
<book-form></book-form> | |
<div class="notification" show.bind="notification.length > 0"> | |
${notification} | |
</div> | |
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {EventAggregator} from 'aurelia-event-aggregator'; | |
import {inject} from 'aurelia-framework'; | |
@inject(EventAggregator) | |
export class App { | |
constructor(eventAggregator){ | |
this.eventAggregator = eventAggregator; | |
} | |
bind(){ | |
this.eventAggregator.subscribe('book-added', _ => { | |
this.notification = "Added new book"; | |
setTimeout(_ => this.notification = "", 1000); | |
}); | |
} | |
unbind(){ | |
this.eventAggregator.dispose(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<require from="typeahead"></require> | |
<require from="star-rating"></require> | |
<form class="form card" submit.trigger="addBook()"> | |
<div class="card-block"> | |
<h4 class="card-title">Add book <i class="fa fa-book" aria-hidden="true"></i></h4> | |
<h6 class="card-subtitle mb-2 text-muted">add a book to your bookshelf</h6> | |
<hr/> | |
<div class="form-group"> | |
<label for="title">Title</label> | |
<input name="title" class="form-control" | |
placeholder='enter a title' | |
value.bind="title & validate"></input> | |
</div> | |
<div class="form-group"> | |
<label for="genre">Genre</label> | |
<input value.bind="genre" | |
typeahead="items.bind:genres" | |
type="text" | |
name="genre" | |
class="form-control" | |
ref="selectGenreElement"></input> | |
</div> | |
<div class="form-group"> | |
<label for="times-read">Times read</label> | |
<input name=times-read class="form-control" value.bind="timesRead & validate"></input> | |
</div> | |
<hr/> | |
<star-rating | |
view-model.ref="starRatingViewModel" | |
ref="ratingElement" | |
rating.bind="rating"> | |
</star-rating> | |
</form> | |
</div> | |
<div class="card-footer"> | |
<button type="submit" class="btn btn-primary col-sm-3 push-sm-9" | |
disabled.bind="title.length == 0"> | |
add | |
</button> | |
</div> | |
</div> | |
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {inject,NewInstance} from 'aurelia-framework'; | |
import {EventAggregator} from 'aurelia-event-aggregator'; | |
import {BooksApi} from 'books-api'; | |
import {BootstrapFormRenderer} from 'bootstrap-form-renderer'; | |
import {ValidationRules, ValidationController} from 'aurelia-validation'; | |
@inject(EventAggregator, BooksApi, NewInstance.of(ValidationController)) | |
export class BookForm{ | |
constructor(eventAggregator, bookApi, controller){ | |
this.title = ""; | |
this.eventAggregator = eventAggregator; | |
this.bookApi = bookApi; | |
this.controller = controller; | |
this.controller.addRenderer(new BootstrapFormRenderer()); | |
this.configureValidationRules(); | |
this.createEventListeners(); | |
} | |
configureValidationRules(){ | |
ValidationRules.customRule( | |
'positiveInteger', | |
(value, obj) => value === null || value === undefined | |
|| (Number.isInteger(value) || value >= 0), | |
`Books can only be read 0 or more times.` | |
); | |
ValidationRules | |
.ensure('title').required() | |
.ensure('timesRead') | |
.required() | |
.satisfiesRule('positiveInteger'). | |
on(this); | |
} | |
addBook(){ | |
this.controller.validate().then(result => { | |
if(result.valid) this.eventAggregator.publish('book-added'); | |
}); | |
} | |
bind(){ | |
this.bookApi.getGenres().then(genres => { | |
this.genres = genres; | |
}); | |
} | |
createEventListeners(){ | |
this.genreSelectedListener = e => { | |
if(e && e.detail){ | |
this.genre = e.detail.value; | |
} | |
}; | |
this.ratingChangedListener = e => this.rating = e.rating; | |
} | |
attached(){ | |
this.selectGenreElement.addEventListener("change", this.genreSelectedListener ); | |
this.selectGenreElement.addEventListener("change", this.ratingChangedListener ); | |
} | |
detached(){ | |
this.ratingElement.removeEventListener('change', this.ratingChangedListener); | |
this.selectGenreElement.removeEventListener('change', this.genreSelectedListener); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class BooksApi{ | |
constructor(){ | |
this.simulatedLatency = 500; | |
} | |
getGenres(){ | |
let genres = [ | |
'Art', | |
'Autobiographies', | |
'Drama', | |
'Childrens', | |
'Fantasy', | |
'History', | |
'Mystery', | |
'Romance', | |
'Science', | |
'Science Fiction' | |
]; | |
return this.simulateFetch(genres); | |
} | |
simulateFetch(fetchResult){ | |
return new Promise(resolve => { | |
setTimeout(() => { | |
resolve(fetchResult); | |
}, this.simulatedLatency); | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
ValidationRenderer, | |
RenderInstruction, | |
ValidateResult | |
} from 'aurelia-validation'; | |
export class BootstrapFormRenderer { | |
render(instruction) { | |
for (let { result, elements } of instruction.unrender) { | |
for (let element of elements) { | |
this.remove(element, result); | |
} | |
} | |
for (let { result, elements } of instruction.render) { | |
for (let element of elements) { | |
this.add(element, result); | |
} | |
} | |
} | |
add(element, result) { | |
if (result.valid) { | |
return; | |
} | |
const formGroup = element.closest('.form-group'); | |
if (!formGroup) { | |
return; | |
} | |
// add the has-error class to the enclosing form-group div | |
formGroup.classList.add('has-danger'); | |
// add help-block | |
const message = document.createElement('div'); | |
message.className = 'form-control-feedback mb-2 mr-sm-2 mb-sm-0'; | |
message.textContent = result.message; | |
message.id = `validation-message-${result.id}`; | |
element.classList.add('form-control-danger'); | |
formGroup.appendChild(message); | |
} | |
remove(element, result) { | |
if (result.valid) { | |
return; | |
} | |
const formGroup = element.closest('.form-group'); | |
if (!formGroup) { | |
return; | |
} | |
// remove help-block | |
const message = formGroup.querySelector(`#validation-message-${result.id}`); | |
if (message) { | |
formGroup.removeChild(message); | |
// remove the has-error class from the enclosing form-group div | |
if (formGroup.querySelectorAll('.help-block.validation-message').length === 0) { | |
formGroup.classList.remove('has-danger'); | |
formGroup.classList.add('has-success'); | |
element.classList.add('form-control-success'); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class FilterValueConverter{ | |
toView(array, searchTerm) { | |
if(!searchTerm || searchTerm.length == 0) return array; | |
return array.filter((item) => { | |
return searchTerm && searchTerm.length > 0 ? this.itemMaches(searchTerm,item): true; | |
}); | |
} | |
itemMaches(searchTerm, value){ | |
let itemValue = value; | |
if(!itemValue) return false; | |
return itemValue.toUpperCase().indexOf(searchTerm.toUpperCase()) !== -1; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class HighlightValueConverter{ | |
toView(value, searchTerm) { | |
if(!searchTerm) return value; | |
return value.replace(new RegExp(searchTerm, 'gi'), `<b>$&</b>`); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<title>Aurelia</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" | |
rel="stylesheet" > | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous"> | |
<link rel="stylesheet" href="style.css"></link | |
</head> | |
<body aurelia-app="main"> | |
<h1>Loading...</h1> | |
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script> | |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script> | |
<script src="https://freshcutdevelopment.github.io/rjs-bundle/node_modules/requirejs/require.js"></script> | |
<script src="https://freshcutdevelopment.github.io/rjs-bundle/config.js"></script> | |
<script src="https://freshcutdevelopment.github.io/rjs-bundle/bundles/aurelia.js"></script> | |
<script src="https://freshcutdevelopment.github.io/rjs-bundle/bundles/babel.js"></script> | |
<script> | |
require(['aurelia-bootstrapper']); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function configure(aurelia) { | |
aurelia.use.standardConfiguration().plugin('aurelia-validation'); | |
aurelia.start().then(() => aurelia.setRoot()); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<ul class="ratings"> | |
<li repeat.for="star of stars" | |
click.delegate="rate($index)" | |
mouseover.delegate="mouseOver($index) & debounce:100" | |
mouseout.delegate="mouseOut($index) & debounce:100"> | |
<span class="star ${star.displayType === '' ? 'rated' : ''}"> <i class="fa fa-star${star.displayType}" aria-hidden="true"></i> </span> | |
</li> | |
</ul> | |
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {inject, bindable} from 'aurelia-framework'; | |
@inject(Element) // Inject the instance of this element | |
export class StarRating{ | |
@bindable rating; | |
constructor(element){ | |
this.element = element; | |
this.stars = [ | |
{ type: '-o', displayType: '-o', rated : false }, | |
{ type: '-o', displayType: '-o', rated : false }, | |
{ type: '-o', displayType: '-o', rated : false }, | |
{ type: '-o', displayType: '-o', rated : false }, | |
{ type: '-o', displayType: '-o', rated : false } | |
]; | |
this.hovered = false; | |
} | |
bind(){ | |
this.applyRating(this.rating); | |
} | |
applyRating(rating){ | |
this.stars.forEach((star, index) => | |
this.rateStar(star, rating, index)); | |
} | |
rateStar(star, rating, index){ | |
if(index < rating) this.toggleOn(star); | |
else { | |
this.toggleOff(star); | |
} | |
} | |
toggleOn(star){ | |
star.displayType = ''; | |
star.type = ''; | |
star.rated = true; | |
} | |
toggleOff(star){ | |
star.displayType = '-o'; | |
star.type = '-o'; | |
star.rated = false; | |
} | |
ratingFromIndex(index, star){ | |
if(index === 0 && star.rated) return 0; | |
return index + 1; | |
} | |
rate(index){ | |
let rating = this.ratingFromIndex(index, this.stars[0]); | |
this.rating = rating; | |
this.applyRating(rating); | |
this.raiseChangedEvent(); | |
} | |
mouseOut(hoverIndex){ | |
if(!this.hovered) return; | |
this.hovered = false; | |
this.applyHoverState(hoverIndex); | |
} | |
applyHoverState(hoverIndex){ | |
this.stars.forEach((star, index) =>{ | |
if(!this.shouldApplyHover(index, hoverIndex, star)) return; | |
if(this.hovered){ | |
this.toggleDisplayOn(star); | |
} | |
else{ | |
this.toggleDisplayOff(star); | |
} | |
}); | |
} | |
mouseOver(hoverIndex){ | |
if(this.hovered) return; | |
this.hovered = true; | |
this.applyHoverState(hoverIndex); | |
} | |
toggleDisplayOff(star){ | |
star.displayType = star.type; | |
} | |
toggleDisplayOn(star){ | |
star.displayType = ''; | |
} | |
shouldApplyHover(starIndex, hoverIndex, star){ | |
return starIndex <= hoverIndex && !star.rated; | |
} | |
raiseChangedEvent(){ | |
let changeEvent = new CustomEvent('change', {rating: this.rating}); | |
this.element.dispatchEvent(changeEvent); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.notification { | |
bottom: 5px; | |
position: fixed; | |
right: 5px; | |
background-color:#0d904f; | |
color:white; | |
padding:20px; | |
width:300px; | |
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2); | |
} | |
.auto-complete-wrapper{ | |
} | |
.auto-complete.focused{ | |
border: 2px solid #27ae60; | |
} | |
.auto-complete-menu{ | |
position: absolute; | |
left: 2.5%; | |
z-index: 100; | |
width: 95%; | |
margin-bottom: 20px; | |
overflow: hidden; | |
background-color: #fff; | |
-webkit-border-radius: 8px; | |
-moz-border-radius: 8px; | |
border-radius: 8px; | |
box-shadow: 0px 0px 0px 1px green; | |
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); | |
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); | |
box-shadow: 0 5px 10px rgba(0,0,0,.2); | |
} | |
.auto-complete-menu li{ | |
list-style-type:none; | |
} | |
.auto-complete-menu ul li:hover{ | |
background-color: #2ecc71; | |
color:white; | |
} | |
/** ratings component **/ | |
ul.ratings li{ | |
display: inline; | |
list-style-type: none; | |
padding-right: 20px; | |
} | |
.star:hover{ | |
cursor: pointer; | |
font-weight: bold; | |
} | |
.star.rated{ | |
color:rgb(255, 204, 0); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<require from="filter"></require> | |
<require from="highlight"></require> | |
<ul show.bind="show" class="list-group"> | |
<li class="list-group-item" | |
innerhtml.bind="item | highlight:searchTerm" | |
repeat.for="item of items | filter:searchTerm" | |
click.delegate="itemSelected(item)"> | |
</li> | |
</ul> | |
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { inject, bindable, Container, ViewEngine, bindingMode} from 'aurelia-framework'; | |
import { DOM } from 'aurelia-pal'; | |
@inject(Element, Container, ViewEngine) | |
export class TypeaheadCustomAttribute{ | |
@bindable items; | |
@bindable searchTerm; | |
@bindable show = false; | |
constructor(element, container, viewEngine) { | |
this.element = element; | |
this.container = container; | |
this.viewEngine = viewEngine; | |
this.keyPressListener = e => { | |
this.searchTerm = this.element.value; | |
this.show = true; | |
}; | |
} | |
attached(){ | |
this.wrapper = this.wrapWithDiv(this.element); | |
this.element.addEventListener('keyup', this.keyPressListener); | |
this.createTypeahead(); | |
} | |
createTypeahead() { | |
this.viewEngine.loadViewFactory('typeahead-template.html').then(factory => { | |
let childContainer = this.container.createChild(); | |
let view = factory.create(childContainer); | |
let autoCompleteMenu = DOM.createElement('div'); | |
autoCompleteMenu.classList.add('auto-complete-menu'); | |
this.autoCompleteMenu = autoCompleteMenu; | |
view.bind(this); | |
view.appendNodesTo(this.autoCompleteMenu); | |
this.wrapper.appendChild(autoCompleteMenu, this.element); | |
}); | |
} | |
itemSelected(item){ | |
this.searchTerm = item; | |
this.show = false; | |
let changeEvent = new CustomEvent('change', {detail: {value: item}}); | |
this.element.dispatchEvent(changeEvent); | |
} | |
wrapWithDiv(){ | |
var wrapper = DOM.createElement('div'); | |
wrapper.classList.add('auto-complete-wrapper'); | |
let sibling = this.element.nextElementSibling; | |
if(sibling){ | |
this.element.parentElement.insertBefore(wrapper, sibling); | |
} | |
else{ | |
this.element.parentElement.appendChild(wrapper); | |
} | |
wrapper.appendChild(this.element); | |
return wrapper; | |
} | |
removeAutocomplete() { | |
const body = DOM.querySelectorAll('body')[0]; | |
body.removeChild(this.wrapper); | |
} | |
detached(){ | |
this.element.removeEventListener('focus', this.focusListener); | |
this.element.removeEventListener('blur', this.blurListener); | |
this.element.removeEventListener('keyup', this.keyPressListener); | |
this.removeAutocomplete(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment