spyro 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/OLDjavascripts/application.js +25 -0
  6. data/app/assets/OLDjavascripts/components.es6 +10 -0
  7. data/app/assets/OLDjavascripts/components/base/base.es6 +197 -0
  8. data/app/assets/OLDjavascripts/components/base/collection.es6 +199 -0
  9. data/app/assets/OLDjavascripts/components/base/element.es6 +134 -0
  10. data/app/assets/OLDjavascripts/components/base/field.es6 +26 -0
  11. data/app/assets/OLDjavascripts/components/base/inputs/text.es6 +21 -0
  12. data/app/assets/OLDjavascripts/components/base/pagination.es6 +23 -0
  13. data/app/assets/OLDjavascripts/vendor/lodash.js +121 -0
  14. data/app/assets/OLDjavascripts/vendor/moment-twitter.js +86 -0
  15. data/app/assets/OLDjavascripts/vendor/moment.js +80 -0
  16. data/app/assets/OLDjavascripts/vendor/pluralize.js +433 -0
  17. data/app/views/base/_associations.html.haml +11 -0
  18. data/app/views/base/_custom.html.haml +0 -0
  19. data/app/views/base/_details.html.haml +3 -0
  20. data/app/views/base/_edit_header.html.haml +3 -0
  21. data/app/views/base/_fields.html.haml +11 -0
  22. data/app/views/base/_form.html.haml +6 -0
  23. data/app/views/base/_index_header.html.haml +4 -0
  24. data/app/views/base/_new_header.html.haml +3 -0
  25. data/app/views/base/_other_form_fields.html.haml +0 -0
  26. data/app/views/base/_show_header.html.haml +9 -0
  27. data/app/views/base/_upload_fields.html.haml +5 -0
  28. data/app/views/base/copy.html.haml +10 -0
  29. data/app/views/base/edit.html.haml +4 -0
  30. data/app/views/base/index.csv.haml +8 -0
  31. data/app/views/base/index.html.haml +7 -0
  32. data/app/views/base/index.js.haml +2 -0
  33. data/app/views/base/index.xls.ruby +12 -0
  34. data/app/views/base/mailer.html.haml +46 -0
  35. data/app/views/base/new.html.haml +4 -0
  36. data/app/views/base/notify.html.haml +11 -0
  37. data/app/views/base/show.html.haml +8 -0
  38. data/app/views/base/stats.html.haml +36 -0
  39. data/app/views/base/trombi.html.haml +1 -0
  40. data/app/views/base/upload.html.haml +24 -0
  41. data/config/initializers/assets.rb +1 -0
  42. data/config/routes.rb +2 -0
  43. data/lib/spyro.rb +24 -0
  44. data/lib/spyro/active_record_add_on.rb +38 -0
  45. data/lib/spyro/application_controller_add_on.rb +24 -0
  46. data/lib/spyro/collections/outputs/admin_table.rb +28 -0
  47. data/lib/spyro/collections/outputs/bar_graph_table.rb +46 -0
  48. data/lib/spyro/collections/outputs/base.rb +23 -0
  49. data/lib/spyro/collections/outputs/csv.rb +34 -0
  50. data/lib/spyro/collections/outputs/fields.rb +118 -0
  51. data/lib/spyro/collections/outputs/flatui_table.rb +40 -0
  52. data/lib/spyro/collections/outputs/inplace_table.rb +29 -0
  53. data/lib/spyro/collections/outputs/map.rb +22 -0
  54. data/lib/spyro/collections/outputs/table.rb +126 -0
  55. data/lib/spyro/collections/outputs/xlsx.rb +39 -0
  56. data/lib/spyro/collections/parsers/active_ldap_relation.rb +22 -0
  57. data/lib/spyro/collections/parsers/active_record_relation.rb +138 -0
  58. data/lib/spyro/collections/parsers/array.rb +38 -0
  59. data/lib/spyro/collections/parsers/base.rb +60 -0
  60. data/lib/spyro/collections/parsers/kaminari_array.rb +38 -0
  61. data/lib/spyro/collections/parsers/model.rb +31 -0
  62. data/lib/spyro/controllers/strong_parameted.rb +33 -0
  63. data/lib/spyro/engine.rb +6 -0
  64. data/lib/spyro/filters_controller_add_on.rb +161 -0
  65. data/lib/spyro/helpers/action_view_extension.rb +726 -0
  66. data/lib/spyro/namespace_template_inheritance.rb +30 -0
  67. data/lib/spyro/usefull_attributes.rb +14 -0
  68. data/lib/spyro/version.rb +3 -0
  69. data/lib/tasks/spyro_tasks.rake +4 -0
  70. data/test/dummy/README.rdoc +28 -0
  71. data/test/dummy/Rakefile +6 -0
  72. data/test/dummy/app/assets/javascripts/application.js +13 -0
  73. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  74. data/test/dummy/app/controllers/application_controller.rb +5 -0
  75. data/test/dummy/app/helpers/application_helper.rb +2 -0
  76. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  77. data/test/dummy/bin/bundle +3 -0
  78. data/test/dummy/bin/rails +4 -0
  79. data/test/dummy/bin/rake +4 -0
  80. data/test/dummy/bin/setup +29 -0
  81. data/test/dummy/config.ru +4 -0
  82. data/test/dummy/config/application.rb +26 -0
  83. data/test/dummy/config/boot.rb +5 -0
  84. data/test/dummy/config/database.yml +25 -0
  85. data/test/dummy/config/environment.rb +5 -0
  86. data/test/dummy/config/environments/development.rb +41 -0
  87. data/test/dummy/config/environments/production.rb +79 -0
  88. data/test/dummy/config/environments/test.rb +42 -0
  89. data/test/dummy/config/initializers/assets.rb +11 -0
  90. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  91. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  92. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  93. data/test/dummy/config/initializers/inflections.rb +16 -0
  94. data/test/dummy/config/initializers/mime_types.rb +4 -0
  95. data/test/dummy/config/initializers/session_store.rb +3 -0
  96. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  97. data/test/dummy/config/locales/en.yml +23 -0
  98. data/test/dummy/config/routes.rb +4 -0
  99. data/test/dummy/config/secrets.yml +22 -0
  100. data/test/dummy/public/404.html +67 -0
  101. data/test/dummy/public/422.html +67 -0
  102. data/test/dummy/public/500.html +66 -0
  103. data/test/dummy/public/favicon.ico +0 -0
  104. data/test/integration/navigation_test.rb +8 -0
  105. data/test/spyro_test.rb +7 -0
  106. data/test/test_helper.rb +21 -0
  107. metadata +256 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b17cc91748c789c340ec83c1770b37dbc215d088
4
+ data.tar.gz: dbd5bca264895e282867fb877eb6cd0bf5734cf4
5
+ SHA512:
6
+ metadata.gz: e2ab546e75cd3f076aaf7b6482b7e9f750425f1594d3fc9a64993144d85825eb15c7ea7b097d6f4bbccf1ad9ec141738ac8ff45fbe68bf033f7a11fead0ac583
7
+ data.tar.gz: 26cece3c4ff8ebbc52f4f42c7837229d5a39c68175775d83834aeec06adbbde7dcb6ee7c3fd09c1dcbc90fea13843f6882bd7014458ca664bf300bc1a8c2ad5e
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Andre Aubin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,3 @@
1
+ = Spyro
2
+
3
+ This project rocks and uses MIT-LICENSE.
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Spyro'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
@@ -0,0 +1,25 @@
1
+
2
+ var Spyro = {}
3
+
4
+ Spyro.truc = "machin";
5
+
6
+ console.log(this);
7
+
8
+ var e = function(w) {
9
+ w.Spyro = Spyro;
10
+ window.Spyro = Spyro;
11
+ console.log("w", window);
12
+ }(window);
13
+
14
+ //= require react
15
+ //= require react_ujs
16
+ //= require lodash
17
+ //= require pluralize
18
+ //= require moment
19
+ //= require moment.twitter
20
+
21
+ //= require components/base/field
22
+ //= require_tree ./components/base/inputs
23
+ //= require components/base/base
24
+ //= require_tree components/base
25
+
@@ -0,0 +1,10 @@
1
+
2
+
3
+ console.log(Spyro, Spyro.truc)
4
+ //= require components/base/field
5
+ //= require_tree ./components/base/inputs
6
+ //= require components/base/base
7
+
8
+ //= require components/country
9
+ //= require components/user
10
+ //= require_tree ./components
@@ -0,0 +1,197 @@
1
+
2
+ let Spyro = (Spyro || {});
3
+ let Field = Spyro.Field
4
+ let Text = Spyro.Text
5
+
6
+ let e = function() {
7
+
8
+ class Base extends React.Component {
9
+ constructor(props) {
10
+ super(props);
11
+ this.handleTextChange = this.handleTextChange.bind(this)
12
+ this.handleEdition = this.handleEdition.bind(this)
13
+ this.save = this.save.bind(this)
14
+ this.modelName = _.snakeCase(this.constructor.name)
15
+ this.created = (_.keys(props).includes("id"))
16
+ this._setupProperties(props)
17
+
18
+ console.log("Constructor")
19
+ }
20
+
21
+ _setupProperties(props) {
22
+ let urlBase = `/${pluralize.plural(this.modelName)}`
23
+ if (this.created)
24
+ {
25
+ urlBase += `/${props.id}`
26
+ }
27
+
28
+ if (this.state === undefined)
29
+ {
30
+ this.state = {}
31
+ }
32
+ this.state = _.extend({attributes: props, editing: false, url: urlBase}, this.state)
33
+ return true;
34
+ }
35
+
36
+ componentDidMount () {
37
+ console.log("mounted")
38
+ }
39
+
40
+ handleTextChange (e) {
41
+ let new_attributes = this.state.attributes
42
+ new_attributes[e.target.attributes.getNamedItem("data-field").value] = e.target.value
43
+ this.setState({attributes: new_attributes});
44
+ e.target.focus();
45
+ return true;
46
+ }
47
+
48
+ handleEdition (e) {
49
+ // console.log(this)
50
+ if (this) {
51
+ this.setState({editing: (this.state.editing ? false : true)});
52
+ }
53
+ }
54
+
55
+ render () {
56
+ let className = `${this.modelName} object-component`
57
+ return (
58
+ <div className={className}>
59
+ {(this.state.editing ? this.updateForm() : this.displayDom())}
60
+ </div>
61
+ );
62
+ }
63
+
64
+ displayDom () {
65
+ console.log(this.state)
66
+ console.log(this.constructor.propTypes)
67
+ let self = this
68
+ let fieldsDom = _.map(this.state.attributes, (value, name) => {
69
+ return this._domForItem(name, value)
70
+ return (<p key={name}>
71
+ <b>{name}</b>:
72
+ <span onClick={this.handleEdition} className={className}>{value}</span>
73
+ </p>)
74
+ })
75
+ return (
76
+ <div>
77
+ {fieldsDom}
78
+ </div>
79
+ );
80
+ }
81
+
82
+ _domForItem(name, item) {
83
+
84
+ console.log("Looking for array: ", Object.prototype.toString.call(item) === '[object Array]', item)
85
+ console.log("Looking for object: ",Object.prototype.toString.call(item) === '[object Object]', item)
86
+ if (Object.prototype.toString.call(item) === '[object Array]')
87
+ {
88
+ return _.map(item, (e) => { return this._domForItem(name, e)})
89
+ }
90
+ else if (Object.prototype.toString.call(item) === '[object Object]')
91
+ {
92
+ let objectName = _.startCase(name).replace(/s$/, '').replace(' ', '');
93
+ console.log("Object name: ", objectName)
94
+ if (typeof(window[objectName]) === "function")
95
+ {
96
+ let Component = window[objectName]
97
+ console.log("component: ", Component)
98
+ return (<Component {...item} />)
99
+ }
100
+ }
101
+ let className = `${this.modelName}-edit-field-${item}`;
102
+
103
+ return (
104
+ <p key={name}>
105
+ <b>{name}</b>:
106
+ <span onClick={this.handleEdition} className={className}>{item}</span>
107
+ </p>
108
+ )
109
+ }
110
+
111
+ fieldFor(name, value, addons = {})
112
+ {
113
+ if (this.constructor.Types && this.constructor.Types[name])
114
+ {
115
+ let dataType = this.constructor.Types[name]
116
+ let CustomField = Field[_.startCase(dataType).replace(/s$/, '').replace(' ', '')]
117
+ console.log("CustomField: ", CustomField)
118
+ return (<CustomField name={name} value={value} {...addons} />)
119
+ }
120
+ else
121
+ {
122
+ console.log("Text: ", Text)
123
+ return (<Text name={name} value={value} {...addons} />)
124
+ }
125
+ }
126
+
127
+ updateForm () {
128
+ let fieldsDom = _.map(this.state.attributes, (value, name) => {
129
+ let className = `${this.modelName}-edit-field-${value} input__field`;
130
+ let inputName = `${this.modelName}[${name}]`;
131
+ return (this.fieldFor(name, value, {onChange: this.handleTextChange, 'data-field': name, className: className}))
132
+ // return <Field name={name} value={value} className={className} />
133
+ // return <p key={name}><b>{name}</b>: <input type="text" onChange={this.handleTextChange} data-field={name} name={inputName} value={value} className={className}/></p>
134
+ })
135
+ return (
136
+ <form action={this.state.url} onSubmit={this.save} method={this._updateMethod()}>
137
+ {fieldsDom}
138
+ <button type="submit">Save</button>
139
+ <button onClick={this.handleEdition} type="button">Back</button>
140
+ </form>
141
+ );
142
+ }
143
+
144
+ save (e = false) {
145
+ if (e) {
146
+ e.preventDefault()
147
+ }
148
+ let action = `${this.state.url}.json`
149
+ let method = this._updateMethod()
150
+ let data = {}
151
+ data[this.modelName] = this.state.attributes
152
+ $.signedAjax({
153
+ method: method,
154
+ url: action,
155
+ data: data
156
+ })
157
+ .success((data) =>
158
+ {
159
+ console.log("SUCCESS ! ", data)
160
+ console.log(this)
161
+ this.handleEdition()
162
+ })
163
+ .error((a, b, c) => {
164
+ console.warn("ERROR", a, b, c)
165
+ })
166
+ }
167
+
168
+ refresh () {
169
+
170
+ }
171
+
172
+ _fetch (url) {
173
+
174
+ }
175
+
176
+ _url () {
177
+
178
+ }
179
+
180
+ _updateMethod () {
181
+ return (this.created ? this._method().update : this._method().create)
182
+ }
183
+
184
+ _method () {
185
+ return {
186
+ show: "GET",
187
+ index: "GET",
188
+ update: "PATCH",
189
+ create: "POST",
190
+ destroy: "DELETE"
191
+ }
192
+ }
193
+ }
194
+
195
+ Spyro.Base = Base
196
+ }()
197
+
@@ -0,0 +1,199 @@
1
+
2
+ let Spyro = (Spyro || {});
3
+ let Field = Spyro.Field
4
+ let Text = Spyro.Text
5
+
6
+ class Collection extends React.Component {
7
+ constructor(props) {
8
+ super(props);
9
+ console.log("ON CONSTRUCT", props)
10
+ this.url = props.url || document.location.pathname
11
+ this.state = {
12
+ collection: props.collection || [],
13
+ columns: this.refreshColumns(props.collection) || [],
14
+ page: props.page || 1,
15
+ order: props.order || "id",
16
+ orderDirection: props.orderDirection || "asc",
17
+ search: "",
18
+ }
19
+ this.searchableFields = props.searchableFields || _.intersection(this.state.columns, ["name", "title"])
20
+ this.perPage = props.perPage || (this.state.collection.length > 0 ? this.state.collection.length : 30)
21
+ this.model = props.model || document.location.pathname.split("/").slice(-1)
22
+ this.actions = props.actions || true
23
+ this.exclude = props.exclude || []
24
+
25
+ // Si on a pas donné de collection initiale, on va la fetcher
26
+ if (this.state.collection.length === 0)
27
+ {
28
+ this.load((d) =>
29
+ {
30
+ this.setState({columns: this.refreshColumns(this.state.collection)})
31
+ })
32
+ }
33
+ }
34
+
35
+ refreshColumns(collection = false) {
36
+ if (!collection)
37
+ {
38
+ return [];
39
+ }
40
+ else if (collection && collection.length > 0)
41
+ {
42
+ return _.keys(collection[0])
43
+ }
44
+ }
45
+
46
+ getCurrentParams () {
47
+ let additionalParams = {}
48
+ if (!this.state)
49
+ return {}
50
+ if (this.state.order)
51
+ additionalParams[(this.state.orderDirection == "asc" ? "sort" : "rev_sort")] = this.state.order
52
+ if (this.state.search && this.state.search != "")
53
+ {
54
+ console.log(this.searchableFields)
55
+ let searchFields = this.searchableFields.map((e) => `in:${e}`).join(" ")
56
+ additionalParams["search"] = `${searchFields} ${this.state.search}`
57
+ }
58
+ return _.extend({
59
+ page: this.state.page,
60
+ }, additionalParams)
61
+ }
62
+
63
+ load (callback = false) {
64
+ // console.log("load with params: ", this.getCurrentParams())
65
+ $.signedAjax({
66
+ method: 'GET',
67
+ data: this.getCurrentParams(),
68
+ url: this.url,
69
+ dataType: 'json'
70
+ })
71
+ .success((data) =>
72
+ {
73
+ // console.log("SUCCESS ! ", data)
74
+ // console.log(this)
75
+ this.setState({collection: data})
76
+ if (callback)
77
+ callback(data)
78
+ })
79
+ .error((a, b, c) => {
80
+ console.warn("ERROR", a, b, c)
81
+ if (callback)
82
+ callback(data)
83
+ })
84
+ }
85
+
86
+ destroy (id) {
87
+ if (id) {
88
+ $.signedAjax({
89
+ method: 'DELETE',
90
+ url: `${this.url}/${id}`,
91
+ dataType: 'json'
92
+ })
93
+ .success((data) => { this.setState({collection: _.reject(this.state.collection, {id: id})}) })
94
+ .error((a, b, c) => { console.warn("ERROR", a, b, c) })
95
+ }
96
+ }
97
+
98
+ nextPage () {
99
+ this.setState({page: this.state.page + 1})
100
+ }
101
+
102
+ previousPage () {
103
+ this.setState({page: this.state.page - 1})
104
+ }
105
+
106
+ setOrder (field) {
107
+ // console.log("ordeer: ", field)
108
+ if (this.state.order == field)
109
+ {
110
+ this.setState({orderDirection: (this.state.orderDirection == "asc" ? "desc" : "asc"), page: 1})
111
+ }
112
+ else
113
+ {
114
+ this.setState({order: field, page: 1})
115
+ }
116
+ }
117
+
118
+ // ====================== Lifecycle hooks =====================
119
+
120
+ componentDidUpdate (props, state)
121
+ {
122
+ let requestFields = ['page', 'order', 'orderDirection', 'search']
123
+
124
+ console.log("[MAP] new props !", props, state)
125
+ if (_.filter(requestFields, ((e) => state[e] != this.state[e])).length > 0)
126
+ {
127
+ this.load()
128
+ }
129
+ }
130
+
131
+ componentDidMount () {
132
+ console.log("mounted")
133
+ loadImages(document);
134
+ }
135
+
136
+ // ==================== Rendering functions ====================
137
+
138
+
139
+ renderSearch () {
140
+ let onSearch = ((e) => this.setState({search: e.target.value}))
141
+ return <input type="search" placeholder="search..." onChange={onSearch} />
142
+ }
143
+
144
+ renderHeader (columns, actions) {
145
+ // console.log("renderHeader: ", columns)
146
+ let _columns = _.without(columns, ...this.exclude)
147
+ if (actions)
148
+ _columns = _columns.concat(["actions"])
149
+ return (_columns.map((e) => {
150
+ let changeOrder = ((ev) => this.setOrder(e))
151
+ let sortActive = (this.state.order == e && this.state.orderDirection)
152
+ return <th onClick={changeOrder} data-field={_.kebabCase(e)} data-sort={sortActive}>{e}</th>
153
+ }))
154
+ }
155
+
156
+ renderActions (item) {
157
+ let url = `${this.url}/${item.id}`
158
+ let editUrl = `${url}/edit`
159
+ let deleteWrapper = ((e) => this.destroy(item.id))
160
+ return (
161
+ <span className="item-actions">
162
+ <a href={url} title="show"><i className="material-icons">visibility</i></a>
163
+ <a href={editUrl} title="edit"><i className="material-icons">create</i></a>
164
+ <a onClick={deleteWrapper} title="delete"><i className="material-icons">delete</i></a>
165
+ </span>
166
+ )
167
+ }
168
+
169
+ renderRow (item) {
170
+ // console.log("render row: ", item)
171
+ let columns = _.omit(item, this.exclude)
172
+ if (this.actions)
173
+ columns["actions"] = this.renderActions(columns)
174
+ return (_.map(columns, function(v, k) {
175
+ return <td data-field={_.kebabCase(k)}><Element name={k} value={v} /></td>
176
+ }))
177
+ }
178
+
179
+ render () {
180
+ let className = `${this.model} object-collection`
181
+ let content = this.state.collection.map((e) => {
182
+ return (<tr>{this.renderRow(e)}</tr>)
183
+ })
184
+ return (
185
+ <div className={className}>
186
+ {this.renderSearch()}
187
+ <Pagination nextPage={this.nextPage.bind(this)} previousPage={this.previousPage.bind(this)} total={this.state.collection.length} perPage={this.perPage} page={this.state.page}/>
188
+ <table>
189
+ <thead><tr>{this.renderHeader(this.state.columns, this.actions)}</tr></thead>
190
+ <tbody>{content}</tbody>
191
+ </table>
192
+ <Pagination nextPage={this.nextPage.bind(this)} previousPage={this.previousPage.bind(this)} total={this.state.collection.length} perPage={this.perPage} page={this.state.page}/>
193
+ <a onClick={this.load.bind(this)}>Refresh all</a>
194
+ </div>
195
+ );
196
+ }
197
+ }
198
+
199
+