├── app
├── initialize.js
├── components
│ └── recipe
│ │ ├── list
│ │ ├── table-item.jst
│ │ ├── table.jst
│ │ └── index.js
│ │ ├── form
│ │ ├── template.jst
│ │ └── index.js
│ │ ├── tests
│ │ └── detail.js
│ │ ├── detail
│ │ ├── template.jst
│ │ └── index.js
│ │ └── models.js
├── theme
│ ├── application.css
│ ├── layout.js
│ └── index.html
└── routes
│ └── recipes
│ ├── recipes.js
│ ├── recipe.js
│ └── recipe-edit.js
├── webpack-prod.config.js
├── webpack-dev.config.js
├── .tmp
└── mocha-webpack
│ ├── a623dfc6e4489b534e6b83f665e3b973-entry.js
│ └── a623dfc6e4489b534e6b83f665e3b973
│ └── index.html
├── .gitignore
├── core
├── app_finder.js
├── router.js
├── extract_routes.js
└── init.js
├── karma.conf.js
├── .eslintrc
├── package.json
├── README.md
└── webpack.config-helper.js
/app/initialize.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 | export default async function resolveInitialData() {
3 | return;
4 | }
5 |
--------------------------------------------------------------------------------
/webpack-prod.config.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./webpack.config-helper')({
2 | isProduction: true,
3 | devtool: 'cheap-module-source-map'
4 | });
5 |
--------------------------------------------------------------------------------
/app/components/recipe/list/table-item.jst:
--------------------------------------------------------------------------------
1 |
2 | <%- title %>
3 | |
4 | <%- cook %> |
5 | <%- prep %> |
6 |
--------------------------------------------------------------------------------
/webpack-dev.config.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./webpack.config-helper')({
2 | isProduction: false,
3 | devtool: 'cheap-eval-source-map',
4 | port: 1337
5 | });
6 |
--------------------------------------------------------------------------------
/.tmp/mocha-webpack/a623dfc6e4489b534e6b83f665e3b973-entry.js:
--------------------------------------------------------------------------------
1 |
2 | var testsContext = require.context("../../app", false);
3 |
4 | var runnable = testsContext.keys();
5 |
6 | runnable.forEach(testsContext);
7 |
--------------------------------------------------------------------------------
/app/theme/application.css:
--------------------------------------------------------------------------------
1 | .fullwidth {
2 | width: 100%;
3 | }
4 |
5 | .navigation {
6 | background: #f4f5f6;
7 | }
8 |
9 | form {
10 | width: 100%;
11 | }
12 |
13 | .is-hidden {
14 | display: none;
15 | }
16 |
--------------------------------------------------------------------------------
/app/components/recipe/form/template.jst:
--------------------------------------------------------------------------------
1 | <% if(!loading) { %>
2 |
3 |
4 |
5 |
6 | <% } else { %>
7 | Loading...
8 | <% } %>
9 |
--------------------------------------------------------------------------------
/app/components/recipe/tests/detail.js:
--------------------------------------------------------------------------------
1 | import DetailView from '../detail/index.js';
2 | import { expect } from 'chai';
3 |
4 |
5 | describe('Detail View', () => {
6 | it('should exist', () => {
7 | expect(DetailView).to.exist;
8 | });
9 | it('and it should provide a constructor', () => {
10 | expect(new DetailView()).to.exist;
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/app/theme/layout.js:
--------------------------------------------------------------------------------
1 | import 'milligram';
2 | import './application.css';
3 |
4 |
5 | export default Mn.View.extend({
6 | el: 'body',
7 | regions: {
8 | 'region1': '#js-region-1',
9 | 'region2': '#js-region-2',
10 | 'region3': '#js-region-3',
11 | 'region4': '#js-region-4',
12 | 'region5': '#js-region-5',
13 | },
14 | template: false
15 | })
16 |
--------------------------------------------------------------------------------
/app/routes/recipes/recipes.js:
--------------------------------------------------------------------------------
1 | import RecipeList from 'recipe/list/index.js';
2 |
3 | //BEGIN ROUTES
4 | var routes = {
5 | '': 'recipes'
6 | }
7 | //END ROUTES
8 |
9 | export default (layout) => Backbone.Router.extend({
10 | routes,
11 | initialize(options) {
12 | },
13 | recipes(params) {
14 | layout.showChildView('region1', new RecipeList());
15 | },
16 | })
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Numerous always-ignore extensions
2 | *.diff
3 | *.err
4 | *.orig
5 | *.log
6 | *.rej
7 | *.swo
8 | *.swp
9 | *.vi
10 | *~
11 | *.sass-cache
12 |
13 | # OS or Editor folders
14 | .DS_Store
15 | .cache
16 | .project
17 | .settings
18 | .tmproj
19 | nbproject
20 | Thumbs.db
21 |
22 | # NPM packages folder.
23 | node_modules
24 |
25 | # Output folder.
26 | public/
27 |
28 | dist
29 |
--------------------------------------------------------------------------------
/app/routes/recipes/recipe.js:
--------------------------------------------------------------------------------
1 | import RecipeDetail from 'recipe/detail/index.js';
2 |
3 | //BEGIN ROUTES
4 | var routes = {
5 | 'recipe/:id': 'recipe'
6 | }
7 | //END ROUTES
8 |
9 | export default (layout) => Backbone.Router.extend({
10 | routes,
11 | initialize(options) {},
12 | recipe(params) {
13 | layout.showChildView('region1', new RecipeDetail({id: params}))
14 | },
15 | })
16 |
--------------------------------------------------------------------------------
/app/components/recipe/detail/template.jst:
--------------------------------------------------------------------------------
1 |
2 | <% if(!loading) { %>
3 |
4 |
<%- title %>
5 | Directions
6 | <%= marked(directions) %>
7 |
8 |
9 |
EDIT
10 |
Prep time: <%- prep %>
11 |
Cook time: <%- cook %>
12 |
Ingredients
13 | <%= marked(ingredients) %>
14 |
15 | <% } else { %>
16 | Loading...
17 | <% } %>
18 |
19 |
--------------------------------------------------------------------------------
/app/routes/recipes/recipe-edit.js:
--------------------------------------------------------------------------------
1 | import RecipeForm from 'recipe/form/index.js';
2 |
3 | //BEGIN ROUTES
4 | var routes = {
5 | 'recipe/:id/edit': 'recipeEdit',
6 | 'recipe/new': 'recipeNew'
7 | }
8 | //END ROUTES
9 |
10 | export default (layout) => Backbone.Router.extend({
11 | routes,
12 | recipeEdit(params) {
13 | layout.showChildView('region1', new RecipeForm({id: params}))
14 | },
15 | recipeNew(params) {
16 | layout.showChildView('region1', new RecipeForm())
17 | },
18 | })
19 |
--------------------------------------------------------------------------------
/app/components/recipe/detail/index.js:
--------------------------------------------------------------------------------
1 | import template from './template.jst';
2 | import models from '../models.js';
3 | import marked from 'marked';
4 |
5 | export default Mn.View.extend({
6 | template: template,
7 | initialize: function() {
8 | this.model = new models.RecipeModel({id: this.options.id});
9 | this.model.set('loading', true);
10 | this.model.fetch().then(() => this.model.set('loading', false));
11 | },
12 | modelEvents: {
13 | 'change': 'render',
14 | },
15 | templateContext: {
16 | marked: (text) => marked(text)
17 | },
18 | });
19 |
--------------------------------------------------------------------------------
/core/app_finder.js:
--------------------------------------------------------------------------------
1 | const customAppRoutes = {};
2 |
3 | // we need to convert Backbone routes to plain RegExps
4 | function routeToRegExp(route) {
5 | return Backbone.Router.prototype._routeToRegExp.call(null, route);
6 | }
7 |
8 | // Creating the index of routes' regexes
9 | _.each(__ROUTES__, (value, key) => {
10 | customAppRoutes[value.appName] = Object.keys(value.routes).map(routeToRegExp);
11 | });
12 |
13 | export default path => {
14 | if (!path) { path = ''; }
15 | const matcher = route => route.test(path);
16 | return _.findKey(customAppRoutes, routes => _.some(routes, matcher));
17 | }
18 |
--------------------------------------------------------------------------------
/core/router.js:
--------------------------------------------------------------------------------
1 | import appFinder from './app_finder.js';
2 |
3 | export default (layout) => Backbone.Router.extend({
4 | routes: {
5 | '*handleMissingRoute': 'handle404',
6 | },
7 | handle404(path) {
8 | const miniApp = appFinder(path);
9 | if (miniApp) {
10 | const handler = require('bundle!./../app/routes/' + miniApp);
11 | handler(bundle => {
12 | new (bundle.default(layout));
13 | Backbone.history.loadUrl(); // just refreshing the current path, because we've added new paths that we can handle
14 | });
15 | } else {
16 | console.log('404');
17 | }
18 | }
19 | });
20 |
--------------------------------------------------------------------------------
/core/extract_routes.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var path = require('path');
3 | var glob = require('glob');
4 | var fs = require('fs');
5 |
6 | module.exports = _.map(
7 | glob.sync(path.join(__dirname, '../app/routes/**/*.js')), function(file) {
8 | let appName = path.join(
9 | path.relative(
10 | path.join(__dirname, '..', 'app', 'routes'),
11 | path.dirname(file)
12 | ),
13 | path.basename(file)
14 | );
15 | let contents = fs
16 | .readFileSync(file)
17 | .toString()
18 | .match(/\/\/BEGIN\ ROUTES\nvar\ routes\ =\ ([\s\S]*)\n\/\/END\ ROUTES/m)
19 | return {
20 | appName,
21 | routes: JSON.parse(contents[1].replace(/\'/g,'\"'))
22 | }
23 | }
24 | )
25 |
--------------------------------------------------------------------------------
/app/components/recipe/list/table.jst:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 | | Title |
11 | Prep time |
12 | Cook time |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/core/init.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | import Router from './router';
3 | import Initial from './../app/initialize.js';
4 | import Layout from './../app/theme/layout.js';
5 |
6 | var App = new Marionette.Application({
7 | onStart: function(options) {
8 | Initial().then(() => {
9 | let layout = new Layout({el: 'body', regions: Initial.regions})
10 | layout.render();
11 | let router = new (Router(layout));
12 | Backbone.history.start();
13 | });
14 | }
15 | });
16 |
17 | $(document).ready(() => App.start());
18 | $(document).on('click', 'a[data-sref]', function(e) {
19 | e.preventDefault();
20 | Backbone.history.navigate(
21 | Bb.history.fragment + '/' +
22 | $(this).attr('data-sref'),
23 | {trigger: true}
24 | );
25 | });
26 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | var webpackConf = require('./webpack-dev.config.js');
2 | module.exports = function(config) {
3 | config.set({
4 | basePath: '',
5 | frameworks: ['mocha', 'chai', 'sinon'],
6 | files: [
7 | 'app/components/**/tests/*.js'
8 | ],
9 | exclude: [
10 | ],
11 | preprocessors: {
12 | 'app/components/**/tests/*.js': ['webpack']
13 | },
14 | webpack: {
15 | module: webpackConf.module,
16 | resolve: webpackConf.resolve,
17 | plugins: webpackConf.plugins
18 | },
19 | webpackMiddleware: {
20 | stats: 'errors-only'
21 | },
22 | reporters: ['spec'],
23 | port: 9876,
24 | colors: true,
25 | logLevel: config.LOG_INFO,
26 | autoWatch: true,
27 | browsers: ['PhantomJS'],
28 | singleRun: false,
29 | concurrency: Infinity
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/app/theme/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Webpack with MarionetteJS
8 |
9 |
10 |
11 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/components/recipe/form/index.js:
--------------------------------------------------------------------------------
1 | import 'backbone-forms';
2 | import template from './template.jst';
3 | import models from '../models.js';
4 |
5 | export default Mn.View.extend({
6 | template,
7 | ui: {
8 | 'save': '.js-save',
9 | },
10 | events: {
11 | 'click @ui.save': 'onSave',
12 | },
13 | regions: {
14 | form: '.js-form',
15 | },
16 | modelEvents: {
17 | 'change:loading': 'render',
18 | },
19 | initialize: function() {
20 | window.probe = this;
21 | this.model = new models.RecipeModel(this.options);
22 | this.model.set('loading', true);
23 |
24 | this.model.fetch().then(() => {
25 | this.form = new Backbone.Form({
26 | model: this.model
27 | });
28 | this.model.set('loading', false);
29 | });
30 | },
31 | onRender: function() {
32 | if (this.model.get('loading')) return;
33 | this.showChildView('form', this.form);
34 | },
35 | onSave: function() {
36 | this.form.commit();
37 | this.model.set('loading', true);
38 | this.model.save().then(() => {
39 | this.form = new Backbone.Form({
40 | model: this.model
41 | });
42 | this.model.set('loading', false);
43 | });
44 | }
45 | });
46 |
--------------------------------------------------------------------------------
/app/components/recipe/models.js:
--------------------------------------------------------------------------------
1 | import PageableCollection from 'backbone.paginator';
2 |
3 | let RecipeModel = Bb.Model.extend({
4 | urlRoot: 'http://localhost:3000/recipes',
5 | //url: 'http://localhost:3000/recipes',
6 | defaults: {
7 | prep: '-',
8 | cook: '-',
9 | portions: '-',
10 | },
11 | schema: {
12 | title: { type: 'Text', validators: ['required'] },
13 | prep: 'Text',
14 | cook: 'Text',
15 | directions: { type: 'TextArea', validators: ['required'] },
16 | ingredients: { type: 'TextArea', validators: ['required'] },
17 | },
18 | });
19 |
20 | let RecipesCollection = PageableCollection.extend({
21 | url: 'http://localhost:3000/recipes',
22 | model: RecipeModel,
23 | mode: 'infinite',
24 | state: {
25 | pageSize: 20,
26 | },
27 | queryParams: {
28 | currentPage: '_page',
29 | pageSize: '_limit'
30 | },
31 | parseState: function(response) {
32 | return { totalRecords: this.totalRecords || 0};
33 | },
34 | fetch: function(options) {
35 | var jqXHR = PageableCollection.prototype.fetch.call(this, options);
36 | jqXHR.done(() => {
37 | this.totalRecords = parseInt(
38 | jqXHR.getResponseHeader('X-Total-Count')
39 | );
40 | });
41 | return jqXHR;
42 | }
43 | });
44 |
45 | export default {
46 | RecipeModel,
47 | RecipesCollection,
48 | }
49 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true,
5 | "es6": true
6 | },
7 | "globals": {
8 | "$": true,
9 | "_": true,
10 | "Bb": true,
11 | "Backbone": true,
12 | "Mn": true,
13 | "Marionette": true,
14 | "__ROUTES__": true,
15 | "describe": true,
16 | "it": true,
17 | "expect": true
18 | },
19 | "parserOptions": {
20 | "sourceType": "module"
21 | },
22 | "rules": {
23 | "array-bracket-spacing": [ 2, "never" ],
24 | "block-scoped-var": 2,
25 | "brace-style": [ 2, "1tbs", { "allowSingleLine": true } ],
26 | "camelcase": [ 2, { "properties": "always" } ],
27 | "curly": [ 2, "all" ],
28 | "dot-notation": [ 2, { "allowKeywords": true } ],
29 | "eol-last": 2,
30 | "eqeqeq": [ 2, "allow-null" ],
31 | "guard-for-in": 2,
32 | "indent": [ 2, 2, { "SwitchCase": 1 } ],
33 | "key-spacing": [ 2,
34 | {
35 | "beforeColon": false,
36 | "afterColon": true
37 | }
38 | ],
39 | "keyword-spacing": [ 2 ],
40 | "new-cap": 2,
41 | "no-bitwise": 2,
42 | "no-caller": 2,
43 | "no-cond-assign": [ 2, "except-parens" ],
44 | "no-debugger": 2,
45 | "no-empty": 2,
46 | "no-eval": 2,
47 | "no-extend-native": 2,
48 | "no-irregular-whitespace": 2,
49 | "no-iterator": 2,
50 | "no-loop-func": 2,
51 | "no-mixed-spaces-and-tabs": 2,
52 | "no-multi-str": 2,
53 | "no-multiple-empty-lines": 2,
54 | "no-new": 2,
55 | "no-proto": 2,
56 | "no-script-url": 2,
57 | "no-sequences": 2,
58 | "no-shadow": 2,
59 | "no-spaced-func": 2,
60 | "no-trailing-spaces": 2,
61 | "no-undef": 2,
62 | "no-unused-vars": [ 1, { "args": "none" } ],
63 | "no-with": 2,
64 | "one-var": [ 2, "never" ],
65 | "operator-linebreak": [ 2, "after" ],
66 | "quotes": [ 2, "single" ],
67 | "semi": [ 0, "never" ],
68 | "space-before-blocks": [ 2, "always" ],
69 | "space-before-function-paren": [ 2, "never" ],
70 | "space-in-parens": [ 2, "never" ],
71 | "space-infix-ops": 2,
72 | "space-unary-ops": [ 2,
73 | {
74 | "nonwords": false,
75 | "overrides": {}
76 | }
77 | ],
78 | "strict": 0,
79 | "valid-jsdoc": 2,
80 | "valid-typeof": 2,
81 | "wrap-iife": [ 2, "inside" ]
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack-marionette",
3 | "description": "A small boilerplate introducing webpack and es6 features to a Marionette/Backbone application",
4 | "author": "alexpsi",
5 | "version": "0.4.1",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/alexpsi/webpack-marionette"
9 | },
10 | "scripts": {
11 | "build": "rimraf dist && webpack --config webpack-prod.config.js --colors",
12 | "analyze": "webpack --config webpack-prod.config.js --json | webpack-bundle-size-analyzer",
13 | "dev": "webpack-dev-server --config webpack-dev.config.js --watch --colors",
14 | "test": "karma start --single-run",
15 | "tdd": "karma start",
16 | "json": "json-server --watch ./app/db.json",
17 | "eject": "rimraf components/* && rimraf routes/*",
18 | "dash": "webpack-dashboard -- webpack-dev-server --config webpack-dev.config.js"
19 | },
20 | "dependencies": {
21 | "axios": "^0.15.2",
22 | "babel-polyfill": "^6.9.0",
23 | "backbone": "^1.3.3",
24 | "backbone-forms": "^0.14.1",
25 | "backbone.marionette": "^3.1.0",
26 | "backbone.paginator": "^2.0.5",
27 | "jquery": "^3.1.1",
28 | "marked": "^0.3.6",
29 | "milligram": "^1.2.3"
30 | },
31 | "devDependencies": {
32 | "babel-core": "^6.7.6",
33 | "babel-loader": "^6.2.4",
34 | "babel-plugin-syntax-async-functions": "^6.13.0",
35 | "babel-plugin-transform-regenerator": "^6.16.1",
36 | "babel-plugin-transform-runtime": "^6.15.0",
37 | "babel-preset-es2015": "^6.6.0",
38 | "bundle-loader": "^0.5.4",
39 | "chai": "^3.5.0",
40 | "compression-webpack-plugin": "^0.3.2",
41 | "copy-webpack-plugin": "^1.1.1",
42 | "css-loader": "^0.23.1",
43 | "eslint": "^2.11.1",
44 | "extract-text-webpack-plugin": "^1.0.1",
45 | "glob": "^7.1.1",
46 | "html-webpack-plugin": "^2.24.1",
47 | "isparta": "^4.0.0",
48 | "karma": "^1.3.0",
49 | "karma-chai": "^0.1.0",
50 | "karma-mocha": "^1.3.0",
51 | "karma-phantomjs-launcher": "^1.0.2",
52 | "karma-sinon": "^1.0.5",
53 | "karma-spec-reporter": "0.0.26",
54 | "karma-webpack": "^1.8.0",
55 | "mocha": "^2.5.3",
56 | "mocha-webpack": "^0.7.0",
57 | "phantomjs-prebuilt": "^2.1.14",
58 | "rimraf": "^2.4.3",
59 | "sinon": "^1.17.6",
60 | "sinon-chai": "^2.8.0",
61 | "style-loader": "^0.13.1",
62 | "underscore-template-loader": "^0.7.2",
63 | "webpack": "^1.14.0",
64 | "webpack-dashboard": "^0.2.0",
65 | "webpack-dev-server": "^1.16.2",
66 | "webpack-merge": "^0.15.0"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Webpack and MarionetteJS
2 |
3 | A small boilerplate introducing [Webpack](https://webpack.github.io/) and es6 features through [Babel](https://babel.github.io/) to a CRUD Marionette/Backbone application.
4 |
5 | ## Getting started
6 |
7 | * Install
8 | * Clone the repository: `git clone https://github.com/alexpsi/webpack-marionette`
9 | * Inside this folder run: `npm install`
10 |
11 | * Build
12 | * `npm run build` - builds you project inside the `/dist` directory, notice that each route has a separate bundle thus allowing for lazy loading.
13 | * `npm run analyze` - creates a size report for bundled libraries
14 |
15 | * Development
16 | * `npm run dev` - launches the project through webpack-dev-server utilizing
17 | the configuration from webpack-dev.config and hotreload for css assets.
18 | * `npm run dash` - As above but uses webpack-dashboard
19 | * `npm run eject` - Deletes sample application leaving only the core files.
20 |
21 | * Test
22 | * `npm test` - Searches inside the tests folder of each component directory for
23 | .js files and runs them with mocha and chai over Karma.
24 | * `npm run tdd` - As above but watches files for changes and reruns tests.
25 |
26 | ## Features
27 |
28 | * Utilizes Backbone router along with Webpack requireContext so additional libraries utilized by a certain route are lazily added when the route loads.
29 | The routes are defined in the routes directory and a custom webpack plugin collects routes definition from the comments inside the routes folder.
30 |
31 | * ES6 async/await syntax
32 |
33 | * A basic structure, consisting of 3 folders, `components` which is used as a
34 | place to store your views, `routes` which is where you define your routes and
35 | route initializations (check example app for sample route definition), `theme` which stores tha application layout as well as global stylesheets.
36 |
37 | ## Example app
38 | The example app is an editable Cookbook, it utilizes a list of recipes taken
39 | from https://github.com/mikeizbicki/ucr-cs100 and packaged as a json file which
40 | is server by json-server, to run the json-server run `npm run json`, then in another terminal run `npm run dev` and navigate to localhost:1337. Inside the example app
41 | you can find a full collection of views and models allowing to do a full set of CRUD operations on a REST resource. The example app uses [Backbone.paginator](https://github.com/backbone-paginator/backbone.paginator) and
42 | [Backbone.forms](https://github.com/backbone-paginator/backbone.paginator) for creating forms based on the schemas in the models.
43 |
--------------------------------------------------------------------------------
/app/components/recipe/list/index.js:
--------------------------------------------------------------------------------
1 | import template from './table.jst';
2 | import templateItem from './table-item.jst';
3 | import models from '../models.js';
4 |
5 | let TableItem = Mn.View.extend({
6 | tagName: 'tr',
7 | template: templateItem,
8 | ui: {
9 | 'item': '.js-item',
10 | },
11 | events: {
12 | 'click @ui.item': 'onClick',
13 | },
14 | onClick: function(e) {
15 | e.preventDefault();
16 | Backbone.history.navigate(
17 | `recipe/${this.model.get('id')}`,
18 | {trigger: true}
19 | );
20 | }
21 | });
22 |
23 | let TableBody = Mn.CollectionView.extend({
24 | tagName: 'tbody',
25 | childView: TableItem,
26 | onRender: function() {
27 | this.listenTo(this.collection, 'change', this.render);
28 | }
29 | })
30 |
31 | export default Mn.View.extend({
32 | className: 'fullwidth',
33 | template,
34 | regions: {
35 | body: {
36 | el: 'tbody',
37 | replaceElement: true
38 | }
39 | },
40 | ui: {
41 | 'previous': '.js-previous',
42 | 'next': '.js-next',
43 | 'search': '.js-search',
44 | },
45 | events: {
46 | 'keyup @ui.search': 'onSearch',
47 | 'click @ui.previous': 'onPrevious',
48 | 'click @ui.next': 'onNext',
49 | },
50 | initialize: function() {
51 | this.collection = new models.RecipesCollection();
52 | this._search = _.debounce(_.bind(this.search, this), 2000);
53 | window.probe = this.collection;
54 | this.collection.fetch();
55 | this.listenTo(this.collection, 'reset', () => {
56 | if (this.collection.hasPreviousPage()) {
57 | this.ui.previous.removeClass('is-hidden')
58 | } else {
59 | this.ui.previous.addClass('is-hidden')
60 | }
61 | if (this.collection.hasNextPage()) {
62 | this.ui.next.removeClass('is-hidden')
63 | } else {
64 | this.ui.next.addClass('is-hidden')
65 | }
66 | });
67 | },
68 | onSearch: function(e) {
69 | var query = e.target.value;
70 | this._search(query);
71 | },
72 | search: function(query) {
73 | this.ui.search.attr('disabled', true);
74 | if (query && query.length > 0) {
75 | this.collection.switchMode('infinite');
76 | this.collection.fetch({ data: {
77 | q: query
78 | }}).then(() => {
79 | this.ui.search.attr('disabled', false);
80 | });
81 | } else {
82 | this.collection.switchMode('client');
83 | this.collection.fetch().then(() => {
84 | this.ui.search.attr('disabled', false);
85 | });
86 | }
87 | },
88 | onRender: function() {
89 | this.showChildView('body', new TableBody({
90 | collection: this.collection
91 | }));
92 | },
93 | onNext: function() {
94 | this.collection.getNextPage();
95 | },
96 | onPrevious: function() {
97 | this.collection.getPreviousPage();
98 | },
99 | });
100 |
--------------------------------------------------------------------------------
/webpack.config-helper.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const ROUTES = require('./core/extract_routes.js');
3 | const Path = require('path');
4 | const Webpack = require('webpack');
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 | var DashboardPlugin = require('webpack-dashboard/plugin');
7 |
8 |
9 | module.exports = (options) => {
10 |
11 | let webpackConfig = {
12 | devtool: options.devtool,
13 | output: {
14 | path: Path.join(__dirname, 'dist'),
15 | filename: 'bundle.js'
16 | },
17 | plugins: [
18 | new Webpack.DefinePlugin({
19 | 'process.env': {
20 | NODE_ENV: JSON.stringify(options.isProduction ? 'production' : 'development')
21 | },
22 | '__ROUTES__': JSON.stringify(ROUTES)
23 | }),
24 | new HtmlWebpackPlugin({
25 | template: './app/theme/index.html'
26 | }),
27 | new Webpack.ProvidePlugin({
28 | _: 'underscore',
29 | $: 'jquery',
30 | jQuery: 'jquery',
31 | Backbone: 'backbone',
32 | Bb: 'backbone',
33 | Marionette: 'backbone.marionette',
34 | Mn: 'backbone.marionette',
35 | }),
36 | ],
37 | module: {
38 | loaders: [{
39 | test: /\.js$/,
40 | exclude: /(node_modules|bower_components)/,
41 | loader: 'babel',
42 | query: {
43 | presets: ['es2015'],
44 | plugins: ['syntax-async-functions', 'transform-regenerator'],
45 | },
46 | }, {
47 | test: /\.jst$/,
48 | loader: 'underscore-template-loader',
49 | }, {
50 | test: /\.s?css$/i,
51 | loaders: ['style', 'css']
52 | }],
53 | },
54 | resolve: {
55 | //fallback: path.resolve(__dirname, 'node_modules'),
56 | root: Path.join(__dirname, './app/components'),
57 | },
58 | resolveLoader: {
59 | root: Path.join(__dirname, './node_modules'),
60 | }
61 | };
62 |
63 | if (options.isProduction) {
64 |
65 | webpackConfig.plugins.push(
66 | new Webpack.optimize.DedupePlugin(),
67 | new Webpack.optimize.UglifyJsPlugin({
68 | compressor: {screw_ie8: true, keep_fnames: true, warnings: false},
69 | mangle: {screw_ie8: true, keep_fnames: true}
70 | }),
71 | new Webpack.optimize.OccurenceOrderPlugin(),
72 | new Webpack.optimize.AggressiveMergingPlugin()
73 | );
74 |
75 | /*
76 | webpackConfig.module.loaders.push({
77 | test: /\.scss$/i,
78 | loader: ExtractSASS.extract(['css', 'sass'])
79 | });*/
80 |
81 | } else {
82 | webpackConfig.plugins.push(
83 | new Webpack.HotModuleReplacementPlugin(),
84 | new DashboardPlugin()
85 | );
86 |
87 | webpackConfig.devServer = {
88 | contentBase: './dist',
89 | hot: true,
90 | port: options.port,
91 | inline: true,
92 | progress: true
93 | };
94 | }
95 |
96 | webpackConfig.entry = [];
97 | if (!options.isProduction) {
98 | webpackConfig.entry.push(`webpack-dev-server/client?http://localhost:${options.port}`);
99 | webpackConfig.entry.push('webpack/hot/dev-server');
100 | }
101 | webpackConfig.entry.push('./core/init');
102 |
103 | return webpackConfig;
104 |
105 | };
106 |
--------------------------------------------------------------------------------
/.tmp/mocha-webpack/a623dfc6e4489b534e6b83f665e3b973/index.html:
--------------------------------------------------------------------------------
1 | Html Webpack Plugin:
2 |
3 | Error: Child compilation failed:
4 | Entry module not found: Error: Cannot resolve 'file' or 'd irectory' /Users/alexpsi/Desktop/stuff/repos/m6/.tmp/mocha -webpack/app/theme/index.html in /Users/alexpsi/Desktop/st uff/repos/m6/.tmp/mocha-webpack:
5 | Error: Cannot resolve 'file' or 'directory' /Users/alexpsi /Desktop/stuff/repos/m6/.tmp/mocha-webpack/app/theme/index .html in /Users/alexpsi/Desktop/stuff/repos/m6/.tmp/mocha- webpack
6 |
7 | - compiler.js:76
8 | [m6]/[html-webpack-plugin]/lib/compiler.js:76:16
9 |
10 | - Compiler.js:214 Compiler.
11 | [m6]/[webpack]/lib/Compiler.js:214:10
12 |
13 | - Compiler.js:403
14 | [m6]/[webpack]/lib/Compiler.js:403:12
15 |
16 | - Tapable.js:67 Compiler.next
17 | [m6]/[tapable]/lib/Tapable.js:67:11
18 |
19 | - CachePlugin.js:40 Compiler.
20 | [m6]/[webpack]/lib/CachePlugin.js:40:4
21 |
22 | - Tapable.js:71 Compiler.applyPluginsAsync
23 | [m6]/[tapable]/lib/Tapable.js:71:13
24 |
25 | - Compiler.js:400 Compiler.
26 | [m6]/[webpack]/lib/Compiler.js:400:9
27 |
28 | - Compilation.js:577 Compilation.
29 | [m6]/[webpack]/lib/Compilation.js:577:13
30 |
31 | - Tapable.js:60 Compilation.applyPluginsAsync
32 | [m6]/[tapable]/lib/Tapable.js:60:69
33 |
34 | - Compilation.js:572 Compilation.
35 | [m6]/[webpack]/lib/Compilation.js:572:10
36 |
37 | - Tapable.js:60 Compilation.applyPluginsAsync
38 | [m6]/[tapable]/lib/Tapable.js:60:69
39 |
40 | - Compilation.js:567 Compilation.
41 | [m6]/[webpack]/lib/Compilation.js:567:9
42 |
43 | - Tapable.js:60 Compilation.applyPluginsAsync
44 | [m6]/[tapable]/lib/Tapable.js:60:69
45 |
46 | - Compilation.js:563 Compilation.
47 | [m6]/[webpack]/lib/Compilation.js:563:8
48 |
49 | - Tapable.js:60 Compilation.applyPluginsAsync
50 | [m6]/[tapable]/lib/Tapable.js:60:69
51 |
52 | - Compilation.js:525 Compilation.seal
53 | [m6]/[webpack]/lib/Compilation.js:525:7
54 |
55 | - Compiler.js:397 Compiler.
56 | [m6]/[webpack]/lib/Compiler.js:397:15
57 |
58 | - Tapable.js:103
59 | [m6]/[tapable]/lib/Tapable.js:103:11
60 |
61 | - Compilation.js:445 Compilation.
62 | [m6]/[webpack]/lib/Compilation.js:445:10
63 |
64 | - Compilation.js:344 Compilation.errorAndCallback
65 | [m6]/[webpack]/lib/Compilation.js:344:3
66 |
67 | - Compilation.js:358 Compilation.
68 | [m6]/[webpack]/lib/Compilation.js:358:11
69 |
70 | - NormalModuleFactory.js:29 onDoneResolving
71 | [m6]/[webpack]/lib/NormalModuleFactory.js:29:20
72 |
73 | - NormalModuleFactory.js:85
74 | [m6]/[webpack]/lib/NormalModuleFactory.js:85:20
75 |
76 | - async.js:726
77 | [m6]/[webpack]/[async]/lib/async.js:726:13
78 |
79 | - async.js:52
80 | [m6]/[webpack]/[async]/lib/async.js:52:16
81 |
82 | - async.js:241 done
83 | [m6]/[webpack]/[async]/lib/async.js:241:17
84 |
85 | - async.js:44
86 | [m6]/[webpack]/[async]/lib/async.js:44:16
87 |
88 | - async.js:723
89 | [m6]/[webpack]/[async]/lib/async.js:723:17
90 |
91 | - async.js:167
92 | [m6]/[webpack]/[async]/lib/async.js:167:37
93 |
94 | - UnsafeCachePlugin.js:24
95 | [m6]/[enhanced-resolve]/lib/UnsafeCachePlugin.js:24:19
96 |
97 | - Resolver.js:38 onResolved
98 | [m6]/[enhanced-resolve]/lib/Resolver.js:38:18
99 |
100 | - Resolver.js:127
101 | [m6]/[enhanced-resolve]/lib/Resolver.js:127:10
102 |
103 | - Resolver.js:191
104 | [m6]/[enhanced-resolve]/lib/Resolver.js:191:15
105 |
106 | - Resolver.js:110 applyPluginsParallelBailResult.createInn erCallback.log
107 | [m6]/[enhanced-resolve]/lib/Resolver.js:110:4
108 |
109 | - createInnerCallback.js:21 loggingCallbackWrapper
110 | [m6]/[enhanced-resolve]/lib/createInnerCallback.js:21:19
111 | - Tapable.js:134
112 | [m6]/[tapable]/lib/Tapable.js:134:6
113 |
114 | - DirectoryDescriptionFilePlugin.js:24 Tapable. [m6]/[enhanced-resolve]/lib/DirectoryDescriptionFilePlug in.js:24:12
115 |
116 | - CachedInputFileSystem.js:38 Storage.finished
117 | [m6]/[enhanced-resolve]/lib/CachedInputFileSystem.js:38: 16
118 |
119 | - graceful-fs.js:78 ReadFileContext.callback
120 | [m6]/[graceful-fs]/graceful-fs.js:78:16
121 |
122 |
--------------------------------------------------------------------------------