Setup
Rebone will need you to manually import your components (views, collections, models). In your entry point, add the following:
import reducers from './reducers/combinedReducers';
export const requireAll = (r) => { r.keys().forEach(r); };
requireAll(require.context('./components/', true, /\.view\.js$/));
requireAll(require.context('./components/', true, /\.model\.js$/));
requireAll(require.context('./components/', true, /\.collection\.js$/));
import { Loader } from 'rebone';
Loader().init(reducers);
You may need to update the location of your component folder since the default is set to ./components
Creating a template
The system is using mustache as a templating engine, for more information please have a look at https://mustache.github.io/
<div class="component-posts" data-view="Posts" data-collection="Posts">
</div>
Note on data-view='Posts'
and data-collection='Posts'
All views should extend ComponentView which connects to the global redux store and registers the view to receive store updates for conditional rendering and prop validation:
import { View } from 'backbone';
import { _, app, uuid } from '../index';
import $ from 'jquery';
import { store } from '../store/AppStore';
import dispatchToProps from '../store/dispatchToProps';
import { viewAcknowledge } from '../reducers/app';
export class ComponentView extends View {
constructor (options) {
super(options);
this.propTypes = options.propTypes;
this.props = options.props;
this.uuid = uuid();
store.subscribe(this.handleStoreUpdate.bind(this));
this.appLoaded();
this.propTypes && this.checkProps();
}
handleMapDispatchToProps() {
if (this.mapDispatchToProps) {
this.props = { ...this.props, ...dispatchToProps(this.mapDispatchToProps) };
this.propTypes = this.propTypes ? this.propTypes : {};
Object.keys(this.mapDispatchToProps).forEach(item => {
this.propTypes[item] = 'function';
});
this.checkProps();
}
};
checkProps () {
Object.keys(this.props).map(propName => {
const type = this.propTypes[propName];
const propValue = this.props[propName];
typeof propValue !== type ?
console.warn(`${propName} requires a ${type}, instead passed ${typeof propValue}`)
: null;
});
}
store = store;
dispatch = store.dispatch;
appLoaded() {
const state = store.getState();
if (!this.loaded && state.app && state.app.loaded) {
this.handleMapDispatchToProps();
if (typeof this.onAppReady === 'function') {
this.onAppReady(store);
}
this.loaded = true;
}
}
renderAppView (viewName, options) {
const { $el } = new app.views[viewName]({ ...options });
return $el;
}
handleStoreUpdate = () => {
this.appLoaded();
if (this.uuid) {
const { app: { alertedListeners } } = store.getState();
if (alertedListeners.includes(this.uuid)) {
store.dispatch(viewAcknowledge(this.uuid));
if (typeof this.onViewNotified === 'function') {
this.onViewNotified();
}
this.render();
}
}
if (typeof this.onStoreUpdated === 'function') {
this.onStoreUpdated(store);
}
};
setTemplate = (templateId) => {
this.template = _.template($(`#${templateId}`).html());
};
getTemplateInstance = (templateId) => _.template($(`#${templateId}`).html());
_ = _;
}
Example View:
import { ComponentView, app } from 'rebone';
import { fetchCollection } from '../../reducers/fetch';
require('./Posts.scss');
class Posts extends ComponentView {
constructor(options) {
super({
...options,
events: {
'click .post-button': 'buttonClicked'
}
});
}
onAppReady () {
this.setTemplate('component-post');
this.props.fetchCollection(this.collection);
}
buttonClicked (element) {
const modelId = this.$(element.currentTarget).data('post-id');
let model = this.collection.get(modelId);
model.set({ title: 'random'});
}
render() {
const { filter: { currentFilter} } = this.store.getState();
const posts = this.collection.toJSON().filter(post => post.title.indexOf(currentFilter) > -1);
this.$('.pure-g').html(this.template({ data: posts }));
}
mapDispatchToProps = {
fetchCollection
};
}
app.views.Posts = Posts;