http://webcomponents.org/
2016-06-24 |
Shadow DOMDescribes a method of establishing and maintaining functional boundaries between DOM subtrees and how these subtrees interact with each other within a document tree. |
2016-06-21 |
Custom ElementsThis document describes the method for enabling the author to define and use new types of DOM elements in a document. |
2016-02-25 |
HTML ImportsThis document defines a way to include and reuse HTML documents in other HTML documents. |
https://www.polymer-project.org
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/paper-styles/paper-styles.html">
<link rel="import" href="bower_components/iron-icon/iron-icon.html">
<link rel="import" href="bower_components/iron-icons/iron-icons.html">
<link rel="import" href="bower_components/iron-icons/maps-icons.html">
<link rel="import" href="bower_components/iron-dropdown/iron-dropdown.html">
<link rel="import" href="bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="bower_components/paper-checkbox/paper-checkbox.html">
<link rel="import" href="bower_components/paper-radio-group/paper-radio-group.html">
<link rel="import" href="bower_components/paper-radio-button/paper-radio-button.html">
<link rel="import" href="bower_components/paper-button/paper-button.html">
<link rel="import" href="bower_components/paper-input/paper-input.html">
<link rel="import" href="bower_components/paper-item/paper-item.html">
<link rel="import" href="bower_components/paper-card/paper-card.html">
<link rel="import" href="bower_components/paper-spinner/paper-spinner.html">
<paper-card heading="{{title}}">
<div class="subtitle">{{subtitle}}</div>
<img src="thumbnail"/>
<div class="card-content">{{description}}</div>
<paper-button>Remove from collection</paper-button>
<paper-button>Add to collection</paper-button>
</paper-card>
https://angular.io
@Component({
moduleId: module.id,
selector: 'book-preview',
directives: [NgSwitch],
templateUrl: 'book.preview.html',
styleUrls: ['book.preview.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BookPreviewComponent {
public collectionAdd$ = new Subject<AddOutput>().map(e =>Object.assign(new AddOutput(), e));
public collectionRemove$ = new Subject<RemoveOutput>().map(e =>Object.assign(new RemoveOutput(), e));
@Input() book: Book;
@Output() collectionToggle$: Observable<AddOutput|RemoveOutput> = this.collectionAdd$
.merge(this.collectionRemove$);
constructor() {
console.log('BookPreviewComponent')
}
...
}
<book-preview (collectionToggle$)="onCollectionToggle($event)"></book-preview>
http://www.typescriptlang.org/
class Animal {
constructor(public name: string) { }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
Proposal to introduces
Observable type to the ECMAScript
const search: Observable<SearchOutput> = this.keyup$
.debounceTime(300)
.map(event => (event.target as HTMLInputElement).value)
.distinctUntilChanged();
http://reactivex.io/
Observable Toolkit
http://rxmarbles.com/
https://github.com/reactjs/redux
A predictable state container
for JavaScript apps.
Polymer is a UI Library, there are other
UI Libraries that you can choose from
Page (Smart)
Component (Dumb)
collectionCnt$
router links
Action + / - Collection
searchQuery$
searchPending$
book$
Action + / - Collection
book$
{
books: ...,
search: ...,
collection: ...,
}
---(snap-shot)---(snap-shot)---(snap-shot)---(snap-shot)---(snap-shot)->
---(snap-shot)---(snap-shot)---(snap-shot)---(snap-shot)---(snap-shot)->
{
books: bookReducer,
search: searchReducer,
collection: collectionReducer,
}
{
books: ...,
search: ...,
collection: ...,
}
{
books: data <== bookReducer,
search: data <== searchReducer,
collection: data <== collectionReducer,
}
books:{
entities:{
0BSOg0oHhZ0C:{
id: string;
isInCollection:boolean; <----- Attention
volumeInfo: {
title: string;
subtitle: string;
authors: string[];
publisher: string;
publishDate: string;
description: string;
averageRating: number;
ratingsCount: number;
imageLinks: {
thumbnail: string;
smallThumbnail: string;
};
};
},
0fdgsdfgsdfg:{...},
...
ids:[
"0BSOg0oHhZ0C",
"0fdgsdfgsdfg",
"sD7HBwAAQBAJ",
"Vf32PZXJ2gMC"
...
]
}
collection:{
ids:[
"0BSOg0oHhZ0C",
"0fdgsdfgsdfg",
"sD7HBwAAQBAJ",
"Vf32PZXJ2gMC"
...
]
}
collection:{
ids:[
"0BSOg0oHhZ0C",
"0fdgsdfgsdfg",
"sD7HBwAAQBAJ",
"Vf32PZXJ2gMC"
...
]
}
{
books: bookReducer,
search: searchReducer,
collection: collectionReducer,
}
books:{
entities:{
0BSOg0oHhZ0C:{
id: string;
isInCollection:boolean; <----- Attention
volumeInfo: {
title: string;
subtitle: string;
authors: string[];
publisher: string;
publishDate: string;
description: string;
averageRating: number;
ratingsCount: number;
imageLinks: {
thumbnail: string;
smallThumbnail: string;
};
};
},
0fdgsdfgsdfg:{...},
...
ids:[
"0BSOg0oHhZ0C",
"0fdgsdfgsdfg",
"sD7HBwAAQBAJ",
"Vf32PZXJ2gMC"
...
]
}
The search return an Array of Books
case BookActions.SEARCH_COMPLETE:
{
const searchRes: Book[] = action.payload;
//get the entities values as an array
const currentBooks: Book[] = Object.keys(state.entities).map(k => state.entities[k]);
//merge of search and current books
const newBooks: Book[] = _.uniqBy([...currentBooks, ...searchRes], 'id');
//same for ids
const newBookIds: string[] = _.uniqBy([...state.ids, ...newBooks.map(book => book.id)]);
const newEntitiesMap = newBooks.reduce((entities: { [id: string]: Book }, book: Book) => {
return Object.assign(entities, {[book.id]: book});
}, {});
return {
ids: newBookIds,
entities: newEntitiesMap
};
}
static SEARCH = '[Book] Search';
search(query:string):Action {
return {
type: BookActions.SEARCH,
payload: query
};
}
static SEARCH_COMPLETE = '[Book] Search Complete';
searchComplete(results:Book[]):Action {
return {
type: BookActions.SEARCH_COMPLETE,
payload: results
};
}
@Effect() search$ = this.appState$
.whenAction(BookActions.SEARCH)
.map<string>(toPayload)
.switchMap(query => this.googleBooks.searchBooks(query)
.map(books => this.bookActions.searchComplete(books))
.catch(() => Observable.of(this.bookActions.searchComplete([])))
);
//Search Reducer set pending to true
case BookActions.SEARCH: {
const query = action.payload;
return Object.assign(state, {
query,
pending: true
});
}
//Search Reducer set pending to false
case BookActions.SEARCH_COMPLETE: {
const books: Book[] = action.payload;
return {
ids: books.map(book => book.id),
pending: false,
query: state.query
};
}
//Book Reducer
case BookActions.SEARCH_COMPLETE: {
const searchRes: Book[] = action.payload;
const currentBooks = Object.keys(state.entities)
.map(k => state.entities[k]);
//merge books and current searchRes
const newBooks = _.uniqBy(
[...currentBooks ,
...searchRes],
'id');
const newBookIds = _.uniqBy(
[...state.ids,
...newBooks.map(book => book.id)
]);
return {
ids: newBookIds,
entities: createEntitiesMap(newBooks)
};
}
@Effect() Add$ = this.appState$
.whenAction(CollectionActions.ADD_TO_COLLECTION)
.map((o: {action: Action, state: AppState}) => {
return o.state.books.entities[o.action.payload];
})
.map(entity => this.collectionActions.addToCollectionComplete(entity));
//Book Reducer Update isInCollection: true on the selected Book
case CollectionActions.ADD_TO_COLLECTION: {
const bookId = action.payload;
const newBook = Object.assign(state.entities[bookId], {isInCollection: true});
const newBooks = applyNewBookToBooks(state, newBook);
return {
ids: state.ids,
entities: getNewEntities(newBooks)
};
}
//Collection Reducer add the Book id to the collection.ids
case CollectionActions.ADD_TO_COLLECTION_COMPLETE:
const addBookId = action.payload.id;
return {
ids: _.uniqBy([...state.ids, addBookId]),
};
this.store.dispatch(this.collectionActions.addToCollection(e.id));
this.store.dispatch(this.collectionActions.removeFromCollection(e.id));
export function getSearchResults() {
return (state$:Observable<AppState>)=>
state$
.map((s:AppState)=> {
let bookIds = s.search.ids;
let entities = s.books.entities;
return bookIds.map(id => entities[id]);
})
}
export function getCollectionCnt() {
return (state$:Observable<AppState>)=>
state$
.map((s:AppState)=>{
return s.collection.ids.length
})
}
this.books$ = store.let(getSearchResults());
this.collectionCnt$ = store.let(getCollectionCnt());
Book-find
@Input() searchQuery$
@Input() searchPending$
@Input() books$
Book
@Input() book
@Output() collectionToggle$
Book-List
@Input() books
@Output() collectionToggle$
public collectionAdd$ = new Subject<AddOutput>().map(e => Object.assign(new AddOutput(), e));
public collectionRemove$ = new Subject<RemoveOutput>().map(e => Object.assign(new RemoveOutput(), e));
@Input() book: Book;
@Output() collectionToggle$: Observable<AddOutput|RemoveOutput> = this.collectionAdd$
.merge(this.collectionRemove$);
get id() {
return this.book.id;
}
get title() {
return this.book.volumeInfo.title;
}
get description() {
return this.book.volumeInfo.description;
}
get inCollection(): boolean {
return !!this.book.inCollection;
}
<paper-card [heading]="title">
<paper-button (click)="collectionRemove$.next(book)">Remove from collection</paper-button>
<paper-button (click)="collectionAdd$.next(book)">Add to collection</paper-button>
</paper-card>
@Output() collectionToggle$ = new Subject<AddOutput|RemoveOutput>();
@Input() books: Books;
onCollectionToggle(e: AddOutput|RemoveOutput) {
this.collectionToggle$.next(e);
}
<div class="book-list">
<book (collectionToggle$)="onCollectionToggle($event)"
*ngFor="let book of books" [book]="book"></book>
</div>
onCollectionToggle(e) {
if (e instanceof AddOutput) {
this.store.dispatch(this.collectionActions.addToCollection(e.id));
}
if (e instanceof RemoveOutput) {
this.store.dispatch(this.collectionActions.removeFromCollection(e.id));
}
}
...
<book-list [books]="books$ | async"
(collectionToggle$)="onCollectionToggle($event)"></book-list>
...