kea-rails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/javascripts/kea/bindings/activity.js +24 -0
  6. data/app/assets/javascripts/kea/bindings/child_vm.js +32 -0
  7. data/app/assets/javascripts/kea/bindings/komplete.js +172 -0
  8. data/app/assets/javascripts/kea/bindings/sherlock.js +47 -0
  9. data/app/assets/javascripts/kea/bindings/sherlock_provider_search.js +57 -0
  10. data/app/assets/javascripts/kea/bindings/submit_button.js +33 -0
  11. data/app/assets/javascripts/kea/bindings/validation_state.js +38 -0
  12. data/app/assets/javascripts/kea/bindings/wait_for_vm.js +44 -0
  13. data/app/assets/javascripts/kea/extenders/equals.js +85 -0
  14. data/app/assets/javascripts/kea/extenders/external_validator.js +65 -0
  15. data/app/assets/javascripts/kea/extenders/min_length.js +39 -0
  16. data/app/assets/javascripts/kea/extenders/numeric_range.js +64 -0
  17. data/app/assets/javascripts/kea/extenders/required.js +39 -0
  18. data/app/assets/javascripts/kea/extenders/valid_date.js +32 -0
  19. data/app/assets/javascripts/kea/extenders/valid_time.js +41 -0
  20. data/app/assets/javascripts/kea/extenders/validator_base.js +75 -0
  21. data/app/assets/javascripts/kea/helpers/kea.activity_button.js +97 -0
  22. data/app/assets/javascripts/kea/helpers/kea.notify.js +46 -0
  23. data/app/assets/javascripts/kea/initializers/base.js +11 -0
  24. data/app/assets/javascripts/kea/kea.js +14 -0
  25. data/app/assets/javascripts/kea/kea_dependencies.js +20 -0
  26. data/app/assets/javascripts/kea/kea_init.js +24 -0
  27. data/app/assets/javascripts/kea/models/base.js +215 -0
  28. data/app/assets/javascripts/kea/overlay/child_vm_overlay.js +49 -0
  29. data/app/assets/javascripts/kea/overlay/overlay_control.js +87 -0
  30. data/app/assets/javascripts/kea/overlay/overlay_template.js +58 -0
  31. data/app/assets/javascripts/kea/services/base.js +227 -0
  32. data/app/assets/javascripts/kea/sherlock/base_provider.js +201 -0
  33. data/app/assets/javascripts/kea/viewmodels/base.js +150 -0
  34. data/app/assets/javascripts/kea/viewmodels/sherlock.js +230 -0
  35. data/app/assets/stylesheets/kea/kea.css.sass +16 -0
  36. data/app/helpers/kea/application_helper.rb +42 -0
  37. data/lib/generators/kea/install/USAGE +9 -0
  38. data/lib/generators/kea/install/install_generator.rb +197 -0
  39. data/lib/generators/kea/install/templates/_komplete.sass +17 -0
  40. data/lib/generators/kea/install/templates/_sherlock.sass +105 -0
  41. data/lib/generators/kea/install/templates/application.html.erb +38 -0
  42. data/lib/generators/kea/install/templates/global.js +7 -0
  43. data/lib/generators/kea/install/templates/init.js +18 -0
  44. data/lib/generators/kea/install/templates/main.js +18 -0
  45. data/lib/generators/kea/model/USAGE +7 -0
  46. data/lib/generators/kea/model/model_generator.rb +68 -0
  47. data/lib/generators/kea/model/templates/model.js.erb +96 -0
  48. data/lib/generators/kea/service/USAGE +7 -0
  49. data/lib/generators/kea/service/service_generator.rb +21 -0
  50. data/lib/generators/kea/service/templates/model.js.erb +58 -0
  51. data/lib/generators/kea/viewmodel/USAGE +6 -0
  52. data/lib/generators/kea/viewmodel/templates/viewmodel.js.erb +73 -0
  53. data/lib/generators/kea/viewmodel/viewmodel_generator.rb +21 -0
  54. data/lib/kea-rails/engine.rb +5 -0
  55. data/lib/kea-rails/version.rb +3 -0
  56. data/lib/kea-rails.rb +4 -0
  57. data/lib/tasks/kea_tasks.rake +4 -0
  58. metadata +115 -0
@@ -0,0 +1,201 @@
1
+ (function(app, sherlock, ko, Fuse) {
2
+ "use strict";
3
+
4
+ var Base;
5
+
6
+ Base = function Base() {
7
+ var that = this;
8
+
9
+ this.minimumConfidence = 0.8;
10
+ this.displayName = '';
11
+ this.defaultKeyword = '';
12
+ this.parameterName = '';
13
+ this.keywords = [];
14
+ this.activeFragments = ko.observableArray([]);
15
+ this.prefilledFragmentsAreChangeable = true;
16
+
17
+ this.liveSearchFragments = ko.observableArray([]);
18
+ this.liveSearchAjaxActivity = ko.observable(false);
19
+ this.liveSearchAlwaysVisible = ko.observable(false);
20
+
21
+ this.liveSearchVisible = ko.computed(function() {
22
+ return this.liveSearchAlwaysVisible() || this.liveSearchFragments().length > 0;
23
+ }, this, {deferEvaluation: true});
24
+
25
+ this.offersLiveSearch = ko.computed(function() {
26
+ return typeof this.liveSearch === 'function' && this.allowAnotherFragment();
27
+ }, this, {deferEvaluation: true});
28
+
29
+ this.fragmentsToParam = ko.computed(function() {
30
+ var result = {},
31
+ values = [];
32
+
33
+ this.activeFragments().forEach(function(activeFragment) {
34
+ var fragmentResult = activeFragment.toParam();
35
+
36
+ if (!fragmentResult) {
37
+ return;
38
+ }
39
+
40
+ values.push(fragmentResult);
41
+ });
42
+
43
+ if (values.length === 0) {
44
+ return;
45
+
46
+ } else if (values.length === 1) {
47
+ result[this.parameterName] = values[0];
48
+
49
+ } else {
50
+ result[this.parameterName] = values;
51
+ }
52
+
53
+ return result;
54
+
55
+ }, this, {deferEvaluation: true});
56
+ };
57
+
58
+ Base.prototype.allowAnotherFragment = function allowAnotherFragment() {
59
+ return this.activeFragments().length === 0;
60
+ };
61
+
62
+ Base.prototype.setStaticKeywords = function setStaticKeywords(stringOrArray) {
63
+ var that = this,
64
+ keywords,
65
+ defaultKeyword;
66
+
67
+ if (typeof stringOrArray === 'string') {
68
+ keywords = [stringOrArray];
69
+ } else {
70
+ keywords = stringOrArray;
71
+ }
72
+
73
+ this.keywords.push( {keyword: keywords.shift(), isDefaultKeyword: true} );
74
+
75
+ keywords.forEach(function(keyword) {
76
+ that.keywords.push( {keyword: keyword} );
77
+ });
78
+ };
79
+
80
+ Base.prototype.respondsTo = function respondsTo(searchTerm) {
81
+ if (this.offersLiveSearch()) {
82
+ this.liveSearch(searchTerm);
83
+ }
84
+ return this.fuzzyKeywordSearch(searchTerm);
85
+ };
86
+
87
+ Base.prototype.fuzzyKeywordSearch = function fuzzyKeywordSearch(searchTerm) {
88
+ var that = this,
89
+ fuse_results = new Fuse(that.keywords, {
90
+ keys: ['keyword'],
91
+ includeScore: true,
92
+ threshold: (1 - this.minimumConfidence)
93
+ }).search(searchTerm),
94
+ matches;
95
+
96
+ if (!fuse_results) {
97
+ return;
98
+ }
99
+
100
+ matches = fuse_results.map(function(fuse_result) { return new that.KeywordMatch(fuse_result); });
101
+
102
+ matches = matches.map(function(match) {
103
+ return that.fragmentForMatch(match);
104
+ });
105
+
106
+ return matches.length > 0 ? matches : null;
107
+ };
108
+
109
+ Base.prototype.fragmentForMatch = function fragmentForMatch(match) {
110
+ var fragment = new this.Fragment(this);
111
+
112
+ fragment.importMatch(match);
113
+
114
+ if (typeof this.fragmentDisplayStringForMatch === 'function') {
115
+ fragment.displayString = this.fragmentDisplayStringForMatch(match);
116
+
117
+ } else {
118
+ if (match.isDefaultKeyword) {
119
+ fragment.displayString = '<span class="keyword default-keyword">' + match.keyword + '</span>';
120
+ } else {
121
+ fragment.displayString = '<span class="keyword">' + match.keyword + '</span> — <span class="keyword default-keyword">' + this.displayName + '</span>';
122
+ }
123
+ }
124
+
125
+ return fragment;
126
+ };
127
+
128
+ Base.prototype.defaultFragment = function defaultFragment() {
129
+ return new this.Fragment(this, this.displayName, 1);
130
+ };
131
+
132
+ Base.prototype.KeywordMatch = function KeywordMatch(fuse_result) {
133
+ this.isDefaultKeyword = fuse_result.item.isDefaultKeyword || false;
134
+ this.item = fuse_result.item;
135
+ this.keyword = fuse_result.item.keyword;
136
+ this.searchValue = fuse_result.item.searchValue;
137
+ this.predicate = fuse_result.item.predicate;
138
+ this.confidence = 1 - fuse_result.score;
139
+ };
140
+
141
+ Base.prototype.Fragment = function BaseFragment(provider, displayString, confidence, searchValue, predicate) {
142
+ var that = this;
143
+
144
+ this.provider = provider;
145
+
146
+ this.displayString = displayString;
147
+ this.confidence = confidence;
148
+ this.searchValueDisplay = displayString;
149
+ this.searchValueOptions = ko.observableArray([]);
150
+ this.predicateOptions = ko.observableArray([]);
151
+ this.searchValue = ko.observable(searchValue);
152
+ this.predicate = ko.observable(predicate);
153
+ this.hasFocus = ko.observable(false);
154
+
155
+ if (typeof provider.searchValueOptions === 'function') {
156
+ this.searchValueOptions( provider.searchValueOptions() );
157
+ }
158
+
159
+ if (typeof provider.predicateOptions === 'function') {
160
+ this.predicateOptions( provider.predicateOptions() );
161
+ }
162
+
163
+ this.importMatch = function importMatch(match) {
164
+ if (match.searchValue) {
165
+ this.searchValue(match.searchValue);
166
+ }
167
+
168
+ if (match.predicate) {
169
+ this.searchValue(match.predicate);
170
+ }
171
+
172
+ this.confidence = match.confidence;
173
+ };
174
+
175
+ this.changeable = !this.searchValue() || provider.prefilledFragmentsAreChangeable;
176
+
177
+ this.toParam = ko.computed(function() {
178
+ if (!this.searchValue()) {
179
+ return;
180
+ }
181
+
182
+ if (this.predicate()) {
183
+ return {
184
+ predicate: this.predicate(),
185
+ value: this.searchValue()
186
+ };
187
+
188
+ } else {
189
+ return this.searchValue();
190
+ }
191
+
192
+ }, this, {deferEvaluation: true});
193
+
194
+ };
195
+
196
+ Base.prototype.fragment_template_name = 'sherlock-base-fragment-template';
197
+ Base.prototype.livesearch_template_name = 'sherlock-base-livesearch-template';
198
+
199
+ app.sherlock.BaseProvider = Base;
200
+
201
+ })(window.app, window.app.sherlock, ko, window.Fuse);
@@ -0,0 +1,150 @@
1
+ (function(app, kea, ko) {
2
+ "use strict";
3
+
4
+ // Sub-viewmodel handling. Use with borrowed constructor pattern
5
+
6
+ var Parent = function Parent() {
7
+ var that = this;
8
+
9
+ this._childVms = ko.observableArray();
10
+
11
+ this.addChildVm = function addChildVm(vm) {
12
+ var vmName = vm;
13
+
14
+ if (DEBUG) {
15
+ console.assert(app.viewmodels[vmName], vmName + " viewmodel could not be loaded into " + that._viewmodelName());
16
+ }
17
+
18
+ vm = new app.viewmodels[vmName]();
19
+
20
+ if (typeof vm.initChildFromParent === 'function') {
21
+ if (DEBUG) {
22
+ console.group("%s: init sub-vm %s", that._viewmodelName(), vm._viewmodelName());
23
+ }
24
+
25
+ vm.initChildFromParent(that);
26
+
27
+ if (DEBUG) { console.groupEnd(); }
28
+ }
29
+
30
+ that._childVms.push( vm );
31
+
32
+ return vm;
33
+ };
34
+
35
+ this.getVm = function getVm(name) {
36
+ return ko.utils.arrayFirst(that._childVms(), function(item) {
37
+ return item._viewmodelName() === name;
38
+ });
39
+ };
40
+ };
41
+
42
+ kea.viewmodels.Parent = Parent;
43
+
44
+ // Parent-viewmodel handling. Use with borrowed constructor pattern
45
+
46
+ var Child = function Child() {
47
+ var that = this;
48
+
49
+ this._viewmodelName = function _viewmodelName() {
50
+ throw "Viewmodel does not implement _viewmodelName";
51
+ };
52
+
53
+ this.parent = null;
54
+
55
+ this.initChildFromParent = function initChildFromParent(parentVm) {
56
+ that.parent = parentVm;
57
+
58
+ if (typeof that.init === 'function') {
59
+ that.init();
60
+ }
61
+ };
62
+ };
63
+
64
+ kea.viewmodels.Child = Child;
65
+
66
+ // Object validation handling. Use with borrowed constructor pattern
67
+
68
+ var Validatable = function Validatable() {
69
+ var that = this;
70
+
71
+ this.validatableName = ko.observable();
72
+
73
+ this.validatable = ko.computed(function() {
74
+ if (typeof that[ that.validatableName() ] !== 'undefined') {
75
+ return that[ that.validatableName() ]();
76
+ } else {
77
+ return undefined;
78
+ }
79
+ });
80
+
81
+ this.validatableFields = function validatableFields() {
82
+ if (DEBUG) {
83
+ console.assert(that.validatable().validatableFields, "%o does not implement validatableFields", that.validatable());
84
+ }
85
+
86
+ return that.validatable().validatableFields();
87
+ };
88
+
89
+ this.hasFieldsWithErrors = ko.computed(function() {
90
+ var result = false;
91
+
92
+ if (!that.validatable()) {
93
+ return false;
94
+ }
95
+
96
+ ko.utils.arrayForEach(that.validatableFields(), function(field) {
97
+ if ( that.validatable()[field].hasError() ) {
98
+ result = true;
99
+ }
100
+ });
101
+
102
+ return result;
103
+ });
104
+
105
+ this.hasUnvalidatedFields = ko.computed(function() {
106
+ var result = false;
107
+
108
+ if (!that.validatable()) {
109
+ return false;
110
+ }
111
+
112
+ ko.utils.arrayForEach(that.validatableFields(), function(field) {
113
+ if ( that.validatable()[field].isUnvalidated() ) {
114
+ result = true;
115
+ }
116
+ });
117
+
118
+ return result;
119
+ });
120
+
121
+ this.allFieldsValid = ko.computed(function() {
122
+ return !that.hasFieldsWithErrors() && !that.hasUnvalidatedFields();
123
+ });
124
+
125
+ this.hasValidationsInProgress = ko.computed(function() {
126
+ var result = false;
127
+
128
+ if (!that.validatable()) {
129
+ return false;
130
+ }
131
+
132
+ ko.utils.arrayForEach(that.validatableFields(), function(field) {
133
+ if ( that.validatable()[field].validationInProgress() ) {
134
+ result = true;
135
+ }
136
+ });
137
+
138
+ return result;
139
+ });
140
+
141
+ this.validateAllFields = function validateAllFields() {
142
+ ko.utils.arrayForEach(that.validatableFields(), function(field) {
143
+ that.validatable()[field].forceValidate();
144
+ });
145
+ };
146
+ };
147
+
148
+ kea.viewmodels.Validatable = Validatable;
149
+
150
+ })(window.app, window.kea, ko);
@@ -0,0 +1,230 @@
1
+ (function(app, ko) {
2
+ "use strict";
3
+
4
+ var SherlockVm;
5
+
6
+ SherlockVm = function SherlockVm() {
7
+ app.viewmodels.Parent.apply(this);
8
+ app.viewmodels.Child.apply(this);
9
+
10
+ var that = this;
11
+
12
+ this.providers = ko.observableArray([]);
13
+ this.providerSearchTerm = ko.observable('').extend({ rateLimit: { timeout: 500, method: "notifyWhenChangesStop" } });
14
+ this.providerSearchActive = ko.observable(false);
15
+ this.showLiveSearchWidgets = ko.observable(false);
16
+ this.activeFragments = ko.observableArray([]);
17
+
18
+ this.params = ko.computed(function() {
19
+ var params = {};
20
+
21
+ this.providers().forEach(function(provider) {
22
+ var param = provider.fragmentsToParam();
23
+
24
+ if (param) {
25
+ params = $.extend({}, params, param);
26
+ }
27
+ });
28
+
29
+ return params;
30
+
31
+ }, this, {deferEvaluation: true});
32
+
33
+ this.fragmentsForSearchTerm = ko.observableArray([]);
34
+ this.orderedFragmentsForSearchTerm = ko.computed(function() {
35
+ if (!this.fragmentsForSearchTerm()) {
36
+ return;
37
+ }
38
+
39
+ return this.fragmentsForSearchTerm.sort(function(left, right) {
40
+ if (left.confidence === right.confidence) {
41
+ return 0;
42
+ }
43
+
44
+ return left.confidence < right.confidence ? 1 : -1;
45
+ });
46
+
47
+ }, this, {deferEvaluation: true});
48
+
49
+ this.availableProviders = ko.computed(function() {
50
+ var available = [];
51
+
52
+ this.providers().forEach(function(provider) {
53
+ if (provider.allowAnotherFragment()) {
54
+ available.push(provider);
55
+ }
56
+ });
57
+
58
+ return available;
59
+
60
+ }, this, {deferEvaluation: true});
61
+
62
+ this.liveSearchProviders = ko.computed(function() {
63
+ var available = [];
64
+
65
+ this.providers().forEach(function(provider) {
66
+ if (provider.offersLiveSearch()) {
67
+ available.push(provider);
68
+ }
69
+ });
70
+
71
+ return available;
72
+
73
+ }, this, {deferEvaluation: true});
74
+
75
+ this.focuseableFragments = ko.computed(function() {
76
+ var fragments = [];
77
+
78
+ this.orderedFragmentsForSearchTerm().forEach(function(fragment) {
79
+ fragments.push(fragment);
80
+ });
81
+
82
+ this.liveSearchProviders().forEach(function(provider) {
83
+ provider.liveSearchFragments().forEach(function(fragment) {
84
+ fragments.push(fragment);
85
+ });
86
+ });
87
+
88
+ return fragments;
89
+
90
+ }, this, {deferEvaluation: true});
91
+
92
+ this.focusNextFragment = function focusNextFragment() {
93
+ if (that.focuseableFragments().length === 0) {
94
+ return;
95
+ }
96
+
97
+ var result = that.focuseableFragments().some(function(fragment, idx) {
98
+ var nextFragment;
99
+
100
+ if (!fragment.hasFocus()) {
101
+ return false;
102
+ }
103
+
104
+ nextFragment = that.focuseableFragments()[idx + 1];
105
+
106
+ if (nextFragment) {
107
+ fragment.hasFocus(false);
108
+ nextFragment.hasFocus(true);
109
+ }
110
+
111
+ return true;
112
+ });
113
+
114
+ if (!result) {
115
+ that.focuseableFragments()[0].hasFocus(true);
116
+ }
117
+
118
+ };
119
+
120
+ this.focusPreviousFragment = function focusPreviousFragment() {
121
+ if (that.focuseableFragments().length === 0) {
122
+ return;
123
+ }
124
+
125
+ var result = that.focuseableFragments().some(function(fragment, idx) {
126
+ var previousFragment;
127
+
128
+ if (!fragment.hasFocus()) {
129
+ return false;
130
+ }
131
+
132
+ previousFragment = that.focuseableFragments()[idx - 1];
133
+
134
+ if (previousFragment) {
135
+ fragment.hasFocus(false);
136
+ previousFragment.hasFocus(true);
137
+ }
138
+
139
+ return true;
140
+ });
141
+
142
+ if (!result) {
143
+ that.focuseableFragments()[0].hasFocus(true);
144
+ }
145
+ };
146
+
147
+ this.selectFocusedFragment = function selectFocusedFragment() {
148
+ var fragment = ko.utils.arrayFirst(that.focuseableFragments(), function(fragment) {
149
+ return fragment.hasFocus();
150
+ });
151
+
152
+ if (fragment) {
153
+ that.selectFragment(fragment);
154
+ }
155
+ };
156
+
157
+ this.removeLastActiveFragment = function removeLastActiveFragment() {
158
+ var fragment = that.activeFragments()[ that.activeFragments().length - 1 ];
159
+
160
+ if (fragment) {
161
+ that.removeFragment(fragment);
162
+ }
163
+ };
164
+
165
+ this.setDefaultFragments = function setDefaultFragments() {
166
+ that.availableProviders().forEach(function(provider) {
167
+ that.fragmentsForSearchTerm.push( provider.defaultFragment() );
168
+ });
169
+ };
170
+
171
+ this.selectFragment = function selectFragment(fragment) {
172
+ that.activeFragments.push(fragment);
173
+ fragment.provider.activeFragments.push(fragment);
174
+ that.providerSearchActive(false);
175
+ that.fragmentsForSearchTerm([]);
176
+ that.providerSearchTerm('');
177
+ };
178
+
179
+ this.removeFragment = function removeFragment(fragment) {
180
+ fragment.provider.activeFragments.remove(fragment);
181
+ that.activeFragments.remove(fragment);
182
+ };
183
+
184
+ this.providerSearchTerm.subscribe(function(searchTerm) {
185
+ var fragments;
186
+
187
+ that.fragmentsForSearchTerm([]);
188
+
189
+ if (searchTerm.length > 0) {
190
+ that.showLiveSearchWidgets(true);
191
+
192
+ that.availableProviders().forEach(function(provider) {
193
+ fragments = provider.respondsTo(searchTerm);
194
+
195
+ if (fragments) {
196
+ fragments.forEach(function(fragment) { that.fragmentsForSearchTerm.push(fragment); });
197
+ fragments[0].hasFocus(true);
198
+ }
199
+ });
200
+
201
+ } else {
202
+ if (that.providerSearchActive()) {
203
+ that.setDefaultFragments();
204
+ that.showLiveSearchWidgets(false);
205
+ }
206
+ }
207
+ });
208
+
209
+ this.providerSearchActive.subscribe(function(isActive) {
210
+ if (!isActive) {
211
+ return;
212
+ }
213
+
214
+ if (that.fragmentsForSearchTerm().length === 0) {
215
+ that.setDefaultFragments();
216
+ }
217
+ });
218
+
219
+ this.setup = function setup() {
220
+ for (var providerName in app.sherlock.providers) {
221
+ if (app.sherlock.providers.hasOwnProperty(providerName)) {
222
+ that.providers.push( new app.sherlock.providers[providerName]() );
223
+ }
224
+ }
225
+ };
226
+ };
227
+
228
+ app.sherlock.SherlockVm = SherlockVm;
229
+
230
+ })(window.app, ko);
@@ -0,0 +1,16 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require humane-js/themes/flatty
14
+ *= require pikaday
15
+ *= require_self
16
+ */
@@ -0,0 +1,42 @@
1
+ module Kea
2
+ module ApplicationHelper
3
+ def body_id
4
+ content_for?(:body_id) ? content_for(:body_id) : [body_class, params[:action]].join('-')
5
+ end
6
+
7
+ def body_class
8
+ controller.class.to_s.gsub('Controller', '').underscore.dasherize.gsub('/', '-')
9
+ end
10
+
11
+ def json_for(target, options = {})
12
+ options[:scope] ||= self
13
+ options[:url_options] ||= url_options
14
+
15
+ target.active_model_serializer.new(target, options).to_json
16
+ end
17
+
18
+ def cache_json(object, path = nil, options = {})
19
+ unless path
20
+ path = polymorphic_path(object)
21
+ end
22
+
23
+ content = json_for(object, options)
24
+
25
+ content_for :json_cache, "window.app.cache['#{path}'] = #{content};\n".html_safe
26
+ end
27
+
28
+ def overlay_template(name, partial: nil, &block)
29
+ content_for :knockout_templates do
30
+ if partial
31
+ content_tag :script, type: "text/html", id: name, "data-bind" => "overlayTemplate: '#{name}'" do
32
+ render partial: partial
33
+ end
34
+ else
35
+ content_tag :script, type: "text/html", id: name, "data-bind" => "overlayTemplate: '#{name}'" do
36
+ capture(&block)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,9 @@
1
+ Description:
2
+ Installs Kea, optionally into a namespace such as 'backend' or 'frontend'
3
+
4
+ Example:
5
+ rails generate kea:install
6
+
7
+ or
8
+
9
+ rails generate kea:install backend