breezy 0.11.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/lib/breezy.rb +10 -15
  3. data/lib/breezy/helpers.rb +541 -11
  4. data/lib/breezy/redirection.rb +30 -0
  5. data/lib/breezy/xhr_headers.rb +0 -10
  6. data/lib/generators/rails/breezy_generator.rb +13 -6
  7. data/lib/generators/rails/templates/_form.json.props +13 -0
  8. data/lib/generators/rails/templates/controller.rb.tt +0 -3
  9. data/lib/generators/rails/templates/edit.json.props +14 -0
  10. data/lib/generators/rails/templates/{index.js.props → index.json.props} +1 -2
  11. data/lib/generators/rails/templates/new.json.props +15 -0
  12. data/lib/generators/rails/templates/{show.js.props → show.json.props} +0 -2
  13. data/lib/generators/rails/templates/web/edit.html.erb +7 -0
  14. data/lib/generators/rails/templates/web/edit.jsx +21 -28
  15. data/lib/generators/rails/templates/web/index.html.erb +7 -0
  16. data/lib/generators/rails/templates/web/index.jsx +10 -7
  17. data/lib/generators/rails/templates/web/new.html.erb +7 -0
  18. data/lib/generators/rails/templates/web/new.jsx +18 -25
  19. data/lib/generators/rails/templates/web/show.html.erb +7 -0
  20. data/lib/generators/rails/templates/web/show.jsx +3 -4
  21. data/lib/install/templates/web/action_creators.js +14 -12
  22. data/lib/install/templates/web/actions.js +4 -1
  23. data/lib/install/templates/web/application.js +172 -46
  24. data/lib/install/templates/web/application.json.props +26 -0
  25. data/lib/install/templates/web/application_visit.js +65 -0
  26. data/lib/install/templates/web/initializer.rb +1 -6
  27. data/lib/install/templates/web/reducer.js +62 -9
  28. data/lib/install/web.rb +18 -55
  29. data/lib/tasks/install.rake +11 -7
  30. data/test/helpers_test.rb +9 -17
  31. data/test/render_test.rb +25 -92
  32. data/test/test_helper.rb +2 -2
  33. metadata +31 -34
  34. data/app/views/breezy/response.html.erb +0 -0
  35. data/lib/breezy/configuration.rb +0 -35
  36. data/lib/breezy/render.rb +0 -37
  37. data/lib/generators/rails/templates/edit.js.props +0 -16
  38. data/lib/generators/rails/templates/new.js.props +0 -4
  39. data/lib/generators/rails/templates/web/base.jsx +0 -11
  40. data/lib/generators/rails/templates/web/form.jsx +0 -31
  41. data/lib/install/templates/web/babelrc +0 -35
  42. data/test/breezy_test.rb +0 -125
  43. data/test/configuration_test.rb +0 -36
@@ -0,0 +1,30 @@
1
+ module Breezy
2
+ module Redirection
3
+ def _compute_redirect_to_location(request, options)
4
+ computed_location = URI.parse(super)
5
+ next_param = Rack::Utils
6
+ .parse_nested_query(computed_location.query)
7
+
8
+ if request.params[:__] == "0"
9
+ computed_location.query = next_param.merge({__: "0"}).to_query
10
+ end
11
+
12
+ computed_location.to_s
13
+ end
14
+
15
+ def redirect_back_with_bzq(opts)
16
+ if request.referrer && params[:bzq]
17
+ referrer_url = URI.parse(request.referrer)
18
+ referrer_url.query = Rack::Utils
19
+ .parse_nested_query(referrer_url.query)
20
+ .merge({bzq: params[:bzq]})
21
+ .to_query
22
+
23
+ redirect_to referrer_url.to_s, opts
24
+ else
25
+ redirect_back(opts)
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -17,17 +17,7 @@ module Breezy
17
17
  end
18
18
  end
19
19
 
20
- if request.xhr? && request.headers["X-BREEZY-REQUEST"]
21
- self.status = 200
22
- response.headers["X-BREEZY-LOCATION"] = url
23
- end
24
-
25
20
  url
26
21
  end
27
-
28
- private
29
- def set_response_url
30
- response.headers['X-RESPONSE-URL'] = request.fullpath
31
- end
32
22
  end
33
23
  end
@@ -21,6 +21,7 @@ module Rails
21
21
  filename = filename_with_extensions(view)
22
22
  template filename, File.join('app/views', controller_file_path, filename)
23
23
  end
24
+ template '_form.json.props', File.join('app/views', controller_file_path, '_form.json.props')
24
25
 
25
26
  %w(index show new edit).each do |view|
26
27
  @action_name = view
@@ -28,9 +29,11 @@ module Rails
28
29
  template 'web/' + filename, File.join('app/views', controller_file_path, filename)
29
30
  end
30
31
 
31
- js_filename = [plural_table_name, 'form.jsx'].map(&:camelcase).join
32
- template 'web/form.jsx', File.join('app/components', js_filename)
33
- template 'web/base.jsx', File.join('app/components', 'BaseScreen.jsx')
32
+ %w(index show new edit).each do |view|
33
+ @action_name = view
34
+ filename = filename_with_html_extensions(view)
35
+ template 'web/' + filename, File.join('app/views', controller_file_path, filename)
36
+ end
34
37
 
35
38
  %w(index show new edit).each do |view|
36
39
  append_mapping(view)
@@ -48,8 +51,8 @@ module Rails
48
51
  "\nimport #{component_name} from 'views/#{controller_file_path}/#{action}'"
49
52
  end
50
53
 
51
- inject_into_file app_js, after: 'const screenToComponentMapping = {' do
52
- "\n '#{[controller_file_path, action].join('/')}': #{component_name},"
54
+ inject_into_file app_js, after: 'identifierToComponentMapping = {' do
55
+ "\n '#{[controller_file_path, action].join('/')}': #{component_name},"
53
56
  end
54
57
  end
55
58
 
@@ -62,13 +65,17 @@ module Rails
62
65
  end
63
66
 
64
67
  def filename_with_extensions(name)
65
- [name, :js, :props] * '.'
68
+ [name, :json, :props] * '.'
66
69
  end
67
70
 
68
71
  def filename_with_jsx_extensions(name)
69
72
  [name, :jsx] * '.'
70
73
  end
71
74
 
75
+ def filename_with_html_extensions(name)
76
+ [name, :html, :erb] * '.'
77
+ end
78
+
72
79
  def attributes_list_with_timestamps
73
80
  attributes_list(attributes_names + %w(created_at updated_at))
74
81
  end
@@ -0,0 +1,13 @@
1
+ html = form_with(model: @<%= model_resource_name %>, local: true) do |form|
2
+ inner = "".html_safe
3
+
4
+ <%- attributes.each do |attr| -%>
5
+ inner << form.label(:<%= attr.column_name %>)
6
+ inner << form.<%= attr.field_type %>(:<%= attr.column_name %>)
7
+ <%- end -%>
8
+ inner << form.submit
9
+
10
+ inner
11
+ end
12
+
13
+ json.html html
@@ -5,9 +5,6 @@ require_dependency "<%= namespaced_path %>/application_controller"
5
5
  <% module_namespacing do -%>
6
6
  class <%= controller_class_name %>Controller < ApplicationController
7
7
  before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy]
8
- # `use_breezy` enables breezy functionality
9
- # on application.html.erb
10
- before_action :use_breezy
11
8
 
12
9
  # GET <%= route_url %>
13
10
  def index
@@ -0,0 +1,14 @@
1
+ if @post.errors.any?
2
+ content = {
3
+ explanation: "#{pluralize(@<%= singular_table_name %>.errors.count, "error")} prohibited this post from being saved:",
4
+ messages: @<%= singular_table_name %>.errors.full_messages.map{|msg| {body: msg}}
5
+ }
6
+
7
+ flash.now[:form_error] = content
8
+ end
9
+
10
+ json.form(partial: 'form') do
11
+ end
12
+
13
+ json.<%= singular_table_name%>_path <%= singular_table_name%>_path(@<%=singular_table_name%>)
14
+ json.<%= plural_table_name %>_path <%= plural_table_name %>_path
@@ -1,5 +1,3 @@
1
- json.flash flash.to_h
2
-
3
1
  json.<%= plural_table_name %> do
4
2
  json.array! @<%= plural_table_name %> do |<%= singular_table_name %>|
5
3
  <%- attributes_list_with_timestamps.each do |attr| -%>
@@ -7,6 +5,7 @@ json.<%= plural_table_name %> do
7
5
  <%- end -%>
8
6
  json.edit_<%=singular_table_name%>_path edit_<%=singular_table_name%>_path(<%=singular_table_name%>)
9
7
  json.<%=singular_table_name%>_path <%=singular_table_name%>_path(<%=singular_table_name%>)
8
+ json.delete_<%=singular_table_name%>_path <%=singular_table_name%>_path(<%=singular_table_name%>)
10
9
  end
11
10
  end
12
11
 
@@ -0,0 +1,15 @@
1
+ if @post.errors.any?
2
+ content = {
3
+ explanation: "#{pluralize(@<%= singular_table_name %>.errors.count, "error")} prohibited this post from being saved:",
4
+ messages: @<%= singular_table_name %>.errors.full_messages.map{|msg| {body: msg}}
5
+ }
6
+
7
+ flash.now[:form_error] = content
8
+ end
9
+
10
+ json.form(partial: 'form') do
11
+ end
12
+
13
+ json.<%= plural_table_name %>_path <%= plural_table_name %>_path
14
+
15
+
@@ -1,5 +1,3 @@
1
- json.flash flash.to_h
2
-
3
1
  <%- attributes_list_with_timestamps.each do |attr|-%>
4
2
  json.<%=attr%> @<%= singular_table_name %>.<%=attr%>
5
3
  <%- end -%>
@@ -0,0 +1,7 @@
1
+ <%% initial_state = controller.render_to_string(@virtual_path ,formats: [:json], locals: local_assigns, layout: true) %>
2
+
3
+ <script type="text/javascript">
4
+ window.BREEZY_INITIAL_PAGE_STATE=<%%= initial_state.html_safe %>;
5
+ </script>
6
+
7
+ <div id="app"></div>
@@ -1,39 +1,32 @@
1
1
  import React from 'react'
2
2
  import {mapStateToProps, mapDispatchToProps} from '@jho406/breezy'
3
3
  import {connect} from 'react-redux'
4
- import BaseScreen from 'components/BaseScreen'
5
- import <%= plural_table_name.camelize %>Form from 'components/<%= plural_table_name.camelize %>Form'
4
+ import RailsTag from '@jho406/breezy/dist/RailsTag'
6
5
  import * as applicationActionCreators from 'javascript/packs/action_creators'
7
6
 
8
- class <%= plural_table_name.camelize %>Edit extends BaseScreen {
9
- formRef = React.createRef()
10
-
11
- handleSubmit = (values, {setSubmitting}) => {
12
- this.props.clearFormErrors(this.props.pageKey)
13
-
14
- const options = {
15
- method:'PUT',
16
- body: JSON.stringify(values),
17
- }
18
-
19
- this.enhancedVisit(this.props.<%= singular_table_name.camelize(:lower) %>, options).then( rsp => {
20
- setSubmitting(false)
21
- if (this.props.errors) {
22
- this.formRef.current.setErrors(this.props.errors)
23
- }
24
- })
25
- }
26
-
7
+ class <%= plural_table_name.camelize %>Edit extends React.Component {
27
8
  render () {
9
+ const {
10
+ form,
11
+ flash,
12
+ <%= singular_table_name.camelize(:lower) %>Path,
13
+ <%= plural_table_name.camelize(:lower) %>Path,
14
+ } = this.props
15
+ const error = flash.form_error
16
+
17
+ const messagesEl = error && (
18
+ <div id="error_explanation">
19
+ <h2>{ error.explanation }</h2>
20
+ <ul>{ error.messages.map(({body})=> <li key={body}>{body}</li>) }</ul>
21
+ </div>
22
+ )
23
+
28
24
  return (
29
25
  <div>
30
- <<%= plural_table_name.camelize %>Form
31
- onSubmit={this.handleSubmit}
32
- initialValues={this.props.attributesForForm}
33
- ref={this.formRef}
34
- />
35
- <a onClick={ e => this.enhancedVisit(this.props.<%= singular_table_name.camelize(:lower) %>Path)}>Show</a>
36
- <a onClick={ e => this.enhancedVisit(this.props.<%= plural_table_name.camelize(:lower) %>Path)}>Back</a>
26
+ {messagesEl}
27
+ <RailsTag {...form} data-bz-visit={true}/>
28
+ <a href={<%= singular_table_name.camelize(:lower) %>Path} data-bz-visit={true}>Show</a>
29
+ <a href={<%= plural_table_name.camelize(:lower) %>Path} data-bz-visit={true}>Back</a>
37
30
  </div>
38
31
  )
39
32
  }
@@ -0,0 +1,7 @@
1
+ <%% initial_state = controller.render_to_string(@virtual_path ,formats: [:json], locals: local_assigns, layout: true) %>
2
+
3
+ <script type="text/javascript">
4
+ window.BREEZY_INITIAL_PAGE_STATE=<%%= initial_state.html_safe %>;
5
+ </script>
6
+
7
+ <div id="app"></div>
@@ -1,30 +1,33 @@
1
1
  import React from 'react'
2
2
  import {mapStateToProps, mapDispatchToProps} from '@jho406/breezy'
3
3
  import { connect } from 'react-redux'
4
- import BaseScreen from 'components/BaseScreen'
5
4
 
6
- class <%= plural_table_name.camelize %>Index extends BaseScreen {
5
+ class <%= plural_table_name.camelize %>Index extends React.Component {
7
6
  static defaultProps = {
8
7
  <%= plural_table_name.camelize(:lower) %>: []
9
8
  }
10
9
 
11
10
  render () {
11
+ const {
12
+ flash,
13
+ new<%= singular_table_name.camelize %>Path,
14
+ } = this.props
12
15
  const <%= singular_table_name.camelize(:lower) %>Items = this.props.<%= plural_table_name.camelize(:lower) %>.map((<%= singular_table_name.camelize(:lower) %>, key) => {
13
16
  return (
14
17
  <tr key={<%= singular_table_name.camelize(:lower) %>.id}>
15
18
  <%- attributes_list.select{|attr| attr != :id }.each do |attr| -%>
16
19
  <td>{<%=singular_table_name.camelize(:lower)%>.<%=attr.camelize(:lower)%>}</td>
17
20
  <%- end -%>
18
- <td><a onClick={ e => this.enhancedVisit(<%=singular_table_name%>.<%=singular_table_name.camelize(:lower)%>Path)}>Show</a></td>
19
- <td><a onClick={ e => this.enhancedVisit(<%=singular_table_name%>.edit<%=singular_table_name.camelize%>Path)}>Edit</a></td>
20
- <td><a onClick={ e => this.enhancedVisit(<%=singular_table_name%>.<%=singular_table_name.camelize(:lower)%>Path, {method: 'DELETE'})}>Delete</a></td>
21
+ <td><a href={ <%=singular_table_name%>.<%=singular_table_name.camelize(:lower)%>Path } data-bz-visit={true}>Show</a></td>
22
+ <td><a href={ <%=singular_table_name%>.edit<%=singular_table_name.camelize%>Path } data-bz-visit={true}>Edit</a></td>
23
+ <td><a href={ <%=singular_table_name%>.delete<%=singular_table_name.camelize%>Path }data-bz-visit={true} data-bz-method={"DELETE"}>Delete</a></td>
21
24
  </tr>
22
25
  )
23
26
  })
24
27
 
25
28
  return (
26
29
  <div>
27
- <p id="notice">{this.props.flash && this.props.flash.notice}</p>
30
+ <p id="notice">{flash.notice}</p>
28
31
 
29
32
  <h1><%= plural_table_name.capitalize %></h1>
30
33
 
@@ -43,7 +46,7 @@ class <%= plural_table_name.camelize %>Index extends BaseScreen {
43
46
  </tbody>
44
47
  </table>
45
48
  <br />
46
- <a onClick={ e => this.enhancedVisit(this.props.new<%= singular_table_name.camelize %>Path)}>New <%= singular_table_name.capitalize %></a>
49
+ <a href={new<%= singular_table_name.camelize %>Path} data-bz-visit={true}>New <%= singular_table_name.capitalize %></a>
47
50
  </div>
48
51
  )
49
52
  }
@@ -0,0 +1,7 @@
1
+ <%% initial_state = controller.render_to_string(@virtual_path ,formats: [:json], locals: local_assigns, layout: true) %>
2
+
3
+ <script type="text/javascript">
4
+ window.BREEZY_INITIAL_PAGE_STATE=<%%= initial_state.html_safe %>;
5
+ </script>
6
+
7
+ <div id="app"></div>
@@ -1,37 +1,30 @@
1
1
  import React from 'react'
2
2
  import {mapStateToProps, mapDispatchToProps} from '@jho406/breezy'
3
3
  import {connect} from 'react-redux'
4
- import BaseScreen from 'components/BaseScreen'
5
- import <%= plural_table_name.camelize %>Form from 'components/<%= plural_table_name.camelize %>Form'
4
+ import RailsTag from '@jho406/breezy/dist/RailsTag'
6
5
  import * as applicationActionCreators from 'javascript/packs/action_creators'
7
6
 
8
- class <%= plural_table_name.camelize %>New extends BaseScreen {
9
- formRef = React.createRef()
10
-
11
- handleSubmit = (values, {setSubmitting}) => {
12
- this.props.clearFormErrors(this.props.pageKey)
13
-
14
- const options = {
15
- method:'POST',
16
- body: JSON.stringify(values),
17
- }
7
+ class <%= plural_table_name.camelize %>New extends React.Component {
8
+ render () {
9
+ const {
10
+ form,
11
+ flash,
12
+ <%= plural_table_name.camelize(:lower) %>Path,
13
+ } = this.props
14
+ const error = flash.form_error
18
15
 
19
- this.enhancedVisit(this.props.<%= plural_table_name.camelize(:lower) %>Path, options).then( rsp => {
20
- setSubmitting(false)
21
- if (this.props.errors) {
22
- this.formRef.current.setErrors(this.props.errors)
23
- }
24
- })
25
- }
16
+ const messagesEl = error && (
17
+ <div id="error_explanation">
18
+ <h2>{ error.explanation }</h2>
19
+ <ul>{ error.messages.map(({body})=> <li key={body}>{body}</li>) }</ul>
20
+ </div>
21
+ )
26
22
 
27
- render () {
28
23
  return (
29
24
  <div>
30
- <<%= plural_table_name.camelize %>Form
31
- onSubmit={this.handleSubmit}
32
- ref={this.formRef}
33
- />
34
- <a onClick={ e => this.enhancedVisit(this.props.<%= plural_table_name.camelize(:lower) %>Path)}>Back</a>
25
+ {messagesEl}
26
+ <RailsTag {...this.props.form} data-bz-visit={true}/>
27
+ <a href={<%= plural_table_name.camelize(:lower) %>Path} data-bz-visit={true}>Back</a>
35
28
  </div>
36
29
  )
37
30
  }
@@ -0,0 +1,7 @@
1
+ <%% initial_state = controller.render_to_string(@virtual_path ,formats: [:json], locals: local_assigns, layout: true) %>
2
+
3
+ <script type="text/javascript">
4
+ window.BREEZY_INITIAL_PAGE_STATE=<%%= initial_state.html_safe %>;
5
+ </script>
6
+
7
+ <div id="app"></div>
@@ -1,9 +1,8 @@
1
1
  import React from 'react'
2
2
  import {mapStateToProps, mapDispatchToProps} from '@jho406/breezy'
3
3
  import { connect } from 'react-redux'
4
- import BaseScreen from 'components/BaseScreen'
5
4
 
6
- class <%= plural_table_name.camelize %>Show extends BaseScreen {
5
+ class <%= plural_table_name.camelize %>Show extends React.Component {
7
6
  render () {
8
7
  return (
9
8
  <div>
@@ -14,8 +13,8 @@ class <%= plural_table_name.camelize %>Show extends BaseScreen {
14
13
  {this.props.<%=attr.camelize(:lower)%>}
15
14
  </p>
16
15
  <%- end -%>
17
- <a onClick={ e => this.enhancedVisit(this.props.edit<%= singular_table_name.camelize %>Path)}>Edit</a>
18
- <a onClick={ e => this.enhancedVisit(this.props.<%= plural_table_name.camelize(:lower) %>Path )}>Back</a>
16
+ <a href={ this.props.edit<%= singular_table_name.camelize %>Path } data-bz-visit={true}>Edit</a>
17
+ <a href={ this.props.<%= plural_table_name.camelize(:lower) %>Path } data-bz-visit={true}>Back</a>
19
18
  </div>
20
19
  )
21
20
  }
@@ -1,12 +1,14 @@
1
- import {
2
- CLEAR_FORM_ERRORS
3
- } from './actions'
4
-
5
- export function clearFormErrors(pageKey) {
6
- return {
7
- type: CLEAR_FORM_ERRORS,
8
- payload: {
9
- pageKey,
10
- }
11
- }
12
- }
1
+ // Example:
2
+ //
3
+ // import {
4
+ // CLEAR_FORM_ERRORS
5
+ // } from './actions'
6
+ //
7
+ // export function clearFormErrors(pageKey) {
8
+ // return {
9
+ // type: CLEAR_FORM_ERRORS,
10
+ // payload: {
11
+ // pageKey,
12
+ // }
13
+ // }
14
+ // }
@@ -1 +1,4 @@
1
- export const CLEAR_FORM_ERRORS = 'CLEAR_FORM_ERRORS'
1
+ // Example:
2
+ //
3
+ // export const CLEAR_FORM_ERRORS = 'CLEAR_FORM_ERRORS'
4
+ export const REHYDRATE = 'persist/REHYDRATE'
@@ -4,61 +4,187 @@ import reduceReducers from 'reduce-reducers'
4
4
  import thunk from 'redux-thunk'
5
5
  import { Provider } from 'react-redux'
6
6
  import { render } from 'react-dom'
7
- import createHistory from 'history/createBrowserHistory'
8
- import Breezy from '@jho406/breezy'
7
+ import { createBrowserHistory, createMemoryHistory } from 'history'
8
+ import { start } from '@jho406/breezy'
9
9
  import Nav from '@jho406/breezy/dist/NavComponent'
10
- import applicationReducer from './reducer'
10
+ import ujsHandlers from '@jho406/breezy/dist/utils/ujs'
11
+ import { persistStore, persistReducer } from 'redux-persist'
12
+ import storage from 'redux-persist/lib/storage'
13
+ import { applicationRootReducer, applicationPagesReducer } from './reducer'
14
+ import { buildVisitAndRemote } from './application_visit'
11
15
 
16
+ if(typeof window !== 'undefined' ) {
17
+ document.addEventListener("DOMContentLoaded", function() {
18
+ const appEl = document.getElementById('app')
19
+ const location = window.location
12
20
 
13
- // Mapping between your props template to Component
14
- // e.g {'posts/new': PostNew}
15
- const screenToComponentMapping = {
21
+ if (appEl) {
22
+ render(
23
+ <Application
24
+ appEl={appEl}
25
+ // The base url is an optional prefix to all calls made by the `visit`
26
+ // and `remote` thunks.
27
+ baseUrl={''}
28
+ // The global var BREEZY_INITIAL_PAGE_STATE is set by your erb
29
+ // template, e.g., index.html.erb
30
+ initialPage={window.BREEZY_INITIAL_PAGE_STATE}
31
+ // The initial path of the page, e.g., /foobar
32
+ path={location.pathname + location.search + location.hash}
33
+ />, appEl)
34
+ }
35
+ })
16
36
  }
17
37
 
18
- const history = createHistory({})
19
- const initialPage = window.BREEZY_INITIAL_PAGE_STATE
20
- const baseUrl = ''
21
-
22
- //The Nav is pretty bare bones
23
- //Feel free to replace the implementation
24
- const {reducer, initialState, initialPageKey, connect} = Breezy.start({
25
- window,
26
- initialPage,
27
- baseUrl,
28
- history
29
- })
30
-
31
- const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
32
-
33
- const {
34
- breezy: breezyReducer,
35
- pages: pagesReducer,
36
- } = reducer
37
-
38
- const store = createStore(
39
- combineReducers({
40
- breezy: breezyReducer,
41
- pages: reduceReducers(pagesReducer, applicationReducer),
42
- }),
43
- initialState,
44
- composeEnhancers(applyMiddleware(thunk))
45
- )
46
-
47
- connect(store)
48
-
49
- class App extends React.Component {
38
+ export default class Application extends React.Component {
39
+ constructor(props) {
40
+ super(props)
41
+ this.hasWindow = typeof window !== 'undefined'
42
+
43
+ // Mapping between your props template to Component, you must add to this
44
+ // to register any new page level component you create. If you are using the
45
+ // scaffold, it will auto append the identifers for you.
46
+ //
47
+ // e.g {'posts/new': PostNew}
48
+ this.identifierToComponentMapping = {
49
+ }
50
+
51
+ // Create a navigator Ref for UJS attributes and to enhance the base `visit`
52
+ // and `visit` thunks
53
+ this.navigatorRef = React.createRef()
54
+
55
+ // Start Breezy and return an object to prepare the Redux store
56
+ const breezy = start({
57
+ initialPage: this.props.initialPage,
58
+ baseUrl: this.props.baseUrl,
59
+ path: this.props.path,
60
+ fetch: this.hasWindow ? window.fetch : undefined,
61
+ })
62
+ this.breezy = breezy
63
+
64
+ // Build the store and pass Breezy's provided reducer to be combined with
65
+ // your reducers located at `application_reducer.js`
66
+ const {initialState, reducer} = breezy
67
+ this.store = this.buildStore(initialState, reducer)
68
+
69
+ // Fire initial events and populate the store
70
+ breezy.prepareStore(this.store)
71
+
72
+ // Build visit and remote thunks
73
+ // Your modified `visit` and `remote` will get passed below to the
74
+ // NavComponent then to your components through the provided
75
+ // mapDispatchToProps.
76
+ //
77
+ // You can access them via `this.props.visit` or `this.props.remote`. In
78
+ // your page components
79
+ const {visit, remote} = buildVisitAndRemote(this.navigatorRef, this.store)
80
+ this.visit = visit
81
+ this.remote = remote
82
+ }
83
+
84
+ componentDidMount() {
85
+ const { appEl } = this.props
86
+ // Create the ujs event handlers. You can change the ujsAttributePrefix
87
+ // in the event the data attribute conflicts with another.
88
+ this.ujsHandlers = ujsHandlers({
89
+ visit: this.visit,
90
+ remote: this.remote,
91
+ store: this.store,
92
+ ujsAttributePrefix: 'data-bz'
93
+ })
94
+ const {onClick, onSubmit} = this.ujsHandlers
95
+
96
+ appEl.addEventListener('click', onClick)
97
+ appEl.addEventListener('submit', onSubmit)
98
+ }
99
+
100
+ componentWillUnmount() {
101
+ const { appEl } = this.props
102
+ const {onClick, onSubmit} = this.ujsHandlers
103
+
104
+ appEl.removeEventListener('click', onClick)
105
+ appEl.removeEventListener('submit', onSubmit)
106
+ this.breezy.stop()
107
+ }
108
+
109
+ buildStore(initialState, {breezy: breezyReducer, pages: pagesReducer}) {
110
+ // Create the store
111
+ // See `./reducer.js` for an explaination of the two included reducers
112
+ const composeEnhancers = (this.hasWindow && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
113
+ const reducer = this.wrapWithPersistReducer(
114
+ reduceReducers(
115
+ combineReducers({
116
+ breezy: breezyReducer,
117
+ pages: reduceReducers(pagesReducer, applicationPagesReducer),
118
+ }),
119
+ applicationRootReducer
120
+ )
121
+ )
122
+ const store = createStore(
123
+ reducer,
124
+ initialState,
125
+ composeEnhancers(applyMiddleware(thunk))
126
+ )
127
+
128
+ if(this.hasWindow) {
129
+ // Persist the store using Redux-Persist
130
+ persistStore(store)
131
+ }
132
+
133
+ return store
134
+ }
135
+
136
+ wrapWithPersistReducer(reducers) {
137
+ // Redux Persist settings
138
+ // The key is set to the stringified JS asset path to remove the need for
139
+ // migrations when hydrating.
140
+ if (!this.hasWindow) {
141
+ return reducers
142
+ }
143
+ const prefix = 'breezy'
144
+ const persistKey = prefix + this.props.initialPage.assets.filter( asset => asset.endsWith('.js')).join(",")
145
+ const persistConfig = {
146
+ key: persistKey,
147
+ storage,
148
+ }
149
+
150
+ // Remove older storage items that were used by previous JS assets
151
+ if (this.hasWindow) {
152
+ const storedKeys = Object.keys(localStorage)
153
+ storedKeys.forEach((key) => {
154
+ if (key.startsWith(`persist:${prefix}`) && key !== persistKey) {
155
+ localStorage.removeItem(key)
156
+ }
157
+ })
158
+ }
159
+
160
+ return persistReducer(persistConfig, reducers)
161
+ }
162
+
163
+ createHistory() {
164
+ if(this.hasWindow) {
165
+ // This is used for client side rendering
166
+ return createBrowserHistory({})
167
+ } else {
168
+ // This is used for server side rendering
169
+ return createMemoryHistory({})
170
+ }
171
+ }
172
+
50
173
  render() {
51
- return <Provider store={store}>
174
+ const history = this.createHistory()
175
+
176
+ // The Nav component is pretty bare and can be inherited from for custom
177
+ // behavior or replaced with your own.
178
+ return <Provider store={this.store}>
52
179
  <Nav
53
- store={store}
54
- mapping={this.props.mapping}
180
+ store={this.store}
181
+ ref={this.navigatorRef}
182
+ visit={this.visit}
183
+ remote={this.remote}
184
+ mapping={this.identifierToComponentMapping}
55
185
  history={history}
56
- initialPageKey={initialPageKey}
186
+ initialPageKey={this.breezy.initialPageKey}
57
187
  />
58
188
  </Provider>
59
189
  }
60
190
  }
61
-
62
- document.addEventListener("DOMContentLoaded", function() {
63
- render(<App mapping={screenToComponentMapping}/>, document.getElementById('app'))
64
- })