modularity-coffee-rails 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README.md +163 -0
- data/Rakefile +31 -0
- data/app/assets/javascripts/modularity/application.js +15 -0
- data/app/assets/javascripts/modularity/data/ajax_loader.coffee +77 -0
- data/app/assets/javascripts/modularity/data/cache.coffee +63 -0
- data/app/assets/javascripts/modularity/data/indexed_cache.coffee +32 -0
- data/app/assets/javascripts/modularity/data/persistence_manager.coffee +166 -0
- data/app/assets/javascripts/modularity/mixins/clickable.coffee +21 -0
- data/app/assets/javascripts/modularity/mixins/closable.coffee +21 -0
- data/app/assets/javascripts/modularity/modularity.coffee +128 -0
- data/app/assets/javascripts/modularity/modules/autogrow_textarea.coffee +32 -0
- data/app/assets/javascripts/modularity/modules/button.coffee +8 -0
- data/app/assets/javascripts/modularity/tools/array_tools.coffee +6 -0
- data/app/assets/javascripts/modularity/tools/object_tools.coffee +25 -0
- data/app/controllers/modularity-coffee-rails/application_controller.rb +4 -0
- data/app/helpers/modularity-coffee-rails/application_helper.rb +4 -0
- data/app/views/layouts/modularity-coffee-rails/application.html.erb +14 -0
- data/config/routes.rb +2 -0
- data/lib/modularity-coffee-rails.rb +4 -0
- data/lib/modularity-coffee-rails/engine.rb +5 -0
- data/lib/modularity-coffee-rails/version.rb +3 -0
- data/lib/tasks/modularity-coffee-rails_tasks.rake +4 -0
- metadata +165 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 YOURNAME
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
# Modularity::Rails [](http://travis-ci.org/#!/kevgo/modularity-rails) <a href="https://codeclimate.com/github/kevgo/modularity-rails" target="_blank"><img src="https://codeclimate.com/badge.png" /></a>
|
2
|
+
|
3
|
+
Makes the [Modularity CoffeeScript](http://github.com/kevgo/modularity-coffeescript) library and related modules available to
|
4
|
+
Rails 3.1 applications.
|
5
|
+
|
6
|
+
Modularity is a framework for lightweight component-oriented CoffeeScript.
|
7
|
+
It allows to compose functionally rich web pages in a clean, intuitive, and testable way
|
8
|
+
out of well structured and reusable components. It scales very well with complexity.
|
9
|
+
|
10
|
+
|
11
|
+
## Authors
|
12
|
+
* [Kevin Goslar](https://github.com/kevgo) (kevin.goslar@gmail.com)
|
13
|
+
* [Alex David](https://github.com/alexdavid)
|
14
|
+
|
15
|
+
|
16
|
+
# Installation
|
17
|
+
|
18
|
+
Load modularity in your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'modularity-rails'
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
```bash
|
27
|
+
$ bundle
|
28
|
+
```
|
29
|
+
|
30
|
+
Finally, you have to load the modularity file into your application's javascript.
|
31
|
+
The easiest way is to add it to `application.coffee`:
|
32
|
+
|
33
|
+
```coffeescript
|
34
|
+
# require jquery
|
35
|
+
# require modularity
|
36
|
+
```
|
37
|
+
|
38
|
+
|
39
|
+
# Usage
|
40
|
+
|
41
|
+
Modularity is a lightweight framework for building powerful AJAX applications.
|
42
|
+
Modularity avoids magic and heavyness. It focusses on providing a pragmatic and interoperable foundation
|
43
|
+
for clean hand-written code bases.
|
44
|
+
Modularity provides practices to create code bases of incredible complexity that are still
|
45
|
+
nicely manageable and perform very well.
|
46
|
+
|
47
|
+
|
48
|
+
## Modules
|
49
|
+
|
50
|
+
Modules are native CoffeeScript classes that are specialized for doing what most JavaScript running in browsers does:
|
51
|
+
managing a UI consisting of DOM elements, reacting to events that happen within that section,
|
52
|
+
representing application logic specific to that section, and providing high-level APIs for others to interact with the section.
|
53
|
+
|
54
|
+
Each module has a container. The container is the outermost DOM element of a section.
|
55
|
+
Everything the module does must happen inside this container.
|
56
|
+
The module is responsible for managing the inner DOM-structure of the container.
|
57
|
+
|
58
|
+
|
59
|
+
## Mixins
|
60
|
+
|
61
|
+
Similar to Ruby mixins, mixins in Modularity allow to include orthogonal functional aspects defined in separate objects into a class.
|
62
|
+
|
63
|
+
```coffeescript
|
64
|
+
myMixin =
|
65
|
+
|
66
|
+
# This will be called when an instance of a class that includes this mixin is created.
|
67
|
+
constructor: ->
|
68
|
+
|
69
|
+
# This method will be available in every class that includes
|
70
|
+
myMethod: ->
|
71
|
+
|
72
|
+
|
73
|
+
class MyModule extends Module
|
74
|
+
|
75
|
+
@mixin myMixin
|
76
|
+
|
77
|
+
constructor: (container) ->
|
78
|
+
|
79
|
+
# The super constructor will call the mixin constructors here.
|
80
|
+
super
|
81
|
+
|
82
|
+
# ...
|
83
|
+
```
|
84
|
+
|
85
|
+
## Hooks
|
86
|
+
|
87
|
+
Hooks are a more direct and easier way to interact with mixins. They are methods with predefined names that mixing modules can implement to hook into events of their mixins
|
88
|
+
without the need to wire up event handlers, and with the ability to interact with the workflow of the mixins.
|
89
|
+
|
90
|
+
|
91
|
+
# Reusable example modules and mixins.
|
92
|
+
|
93
|
+
Modularity comes bundled with a bunch of example modules and mixins that can be used in production code.
|
94
|
+
The example modules are located in `vendor/assets/javascripts/modules/` and _vendor/assets/javascripts/mixins_
|
95
|
+
and must be explicitly required in your Rails files using the `require` commands of the asset pipeline.
|
96
|
+
|
97
|
+
|
98
|
+
## Modules
|
99
|
+
|
100
|
+
* __button.coffee__: A simple button. Fires the `clicked` event when anything inside the container is clicked. Uses the `clickable` mixin.
|
101
|
+
* __counter_button.coffee__: Similar to button, but includes the click count as data payload in the fired event.
|
102
|
+
|
103
|
+
|
104
|
+
## Mixins
|
105
|
+
|
106
|
+
###clickable
|
107
|
+
Including this mixins adds a 'clickable' aspect to your module, i.e. turns it into a button. Clicking anywhere inside the container makes it fire the 'clicked' event.
|
108
|
+
|
109
|
+
###closable
|
110
|
+
Including this mixin makes a module closable. The mixin searches for an embedded DOM element with the class 'CloseButton'. When it is clicked, the following things happen:
|
111
|
+
|
112
|
+
* The _closable_closing_ hook of the closable class is called.
|
113
|
+
This hook could be used to display confirmation dialogs (and abort the close process) or to fire custom events that depend on the DOM still being present.
|
114
|
+
If this method returns a falsy value, the closing process is aborted.
|
115
|
+
* The closable module fires a local 'closing' event (with the DOM still present).
|
116
|
+
* The whole module including its container is removed from the DOM.
|
117
|
+
* The _closable_closed_ hook of the closable class is called.
|
118
|
+
|
119
|
+
|
120
|
+
## Tools
|
121
|
+
|
122
|
+
### Loader
|
123
|
+
A generic cached loader for parallel and repeated GET requests.
|
124
|
+
Prevents duplicate requests, caches the responses.
|
125
|
+
|
126
|
+
The first request triggers the ajax request. Subsequent requests while the resquest is running are accumulated without causing new requests.
|
127
|
+
Once the response arrives, all currently requesting clients are answered. Subsequent requests are answered immediately using the cached data.
|
128
|
+
|
129
|
+
```coffeescript
|
130
|
+
Module.loader.get '/test.json', (data) ->
|
131
|
+
# Use data here.
|
132
|
+
```
|
133
|
+
|
134
|
+
# Development
|
135
|
+
|
136
|
+
## Contributing
|
137
|
+
|
138
|
+
1. Fork it
|
139
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
140
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
141
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
142
|
+
5. Create new Pull Request
|
143
|
+
|
144
|
+
|
145
|
+
## Running the unit tests
|
146
|
+
|
147
|
+
```bash
|
148
|
+
$ evergreen run
|
149
|
+
```
|
150
|
+
|
151
|
+
|
152
|
+
## Automatically refreshing the browser during development.
|
153
|
+
|
154
|
+
Modularity-Rails comes with support for [LifeReload](https://github.com/mockko/livereload) via [Guard](https://github.com/guard/guard).
|
155
|
+
|
156
|
+
* Install the LiveReload browser extension: [Chrome](https://chrome.google.com/webstore/detail/jnihajbhpnppcggbcgedagnkighmdlei)
|
157
|
+
* Run the evergreen server: ```$ evergreen run```
|
158
|
+
* Run the guard server: ``` $ bundle exec guard ```
|
159
|
+
* Start the LiveReload plugin in Chrome (button in address bar).
|
160
|
+
* Navigate to the test page that you want to observe.
|
161
|
+
* Change and save code and see the browser reload.
|
162
|
+
|
163
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'ModularityCoffeeRails'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
24
|
+
load 'rails/tasks/engine.rake'
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
28
|
+
require 'rspec/core/rake_task'
|
29
|
+
RSpec::Core::RakeTask.new :spec
|
30
|
+
task :default => :spec
|
31
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require ./modularity
|
15
|
+
//= require_tree .
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#= require modularity/data/cache
|
2
|
+
|
3
|
+
|
4
|
+
# A generic ajax loader for parallel GET requests.
|
5
|
+
# Prevents duplicate requests, caches the responses
|
6
|
+
# to answer subsequent requests immediately
|
7
|
+
# without additional server requests.
|
8
|
+
#
|
9
|
+
# Warning: Caches the responses, so once a request is cached,
|
10
|
+
# any new content on the same URL will not be visible!
|
11
|
+
class window.modularity.AjaxLoader
|
12
|
+
|
13
|
+
constructor: (params = {}) ->
|
14
|
+
|
15
|
+
# Caches the callbacks.
|
16
|
+
@cb_cache = new modularity.Cache()
|
17
|
+
|
18
|
+
# Whether to perform caching of data.
|
19
|
+
# Default: no.
|
20
|
+
@caching = !!params.caching
|
21
|
+
|
22
|
+
# Caches the data loaded.
|
23
|
+
@data_cache = new modularity.Cache()
|
24
|
+
|
25
|
+
# The number of currently running loading operations.
|
26
|
+
@loader_count = 0
|
27
|
+
|
28
|
+
|
29
|
+
# The different events that this class can fire.
|
30
|
+
@events =
|
31
|
+
AJAX_LOADING: 'AJAX_LOADING'
|
32
|
+
AJAX_LOADED: 'AJAX_LOADED'
|
33
|
+
|
34
|
+
|
35
|
+
# Loads the data from the given URL asynchronously.
|
36
|
+
get: (url, callback) ->
|
37
|
+
|
38
|
+
# Return immediately if we have cached data.
|
39
|
+
if @caching
|
40
|
+
cached_data = @data_cache.get url
|
41
|
+
return callback(cached_data) if cached_data?
|
42
|
+
|
43
|
+
# Here, we have no cached data. Check
|
44
|
+
|
45
|
+
# If a GET call to this URL is already in progress -->
|
46
|
+
# add the given callback to the list of waiting callbacks.
|
47
|
+
request_in_progress = @cb_cache.get url
|
48
|
+
return request_in_progress.push(callback) if request_in_progress?
|
49
|
+
|
50
|
+
# Here, no request is currently in progress --> start a new one.
|
51
|
+
|
52
|
+
# Add callback to callback list.
|
53
|
+
@cb_cache.add url, [callback]
|
54
|
+
|
55
|
+
# Fire 'loading' event if this is the start of a loading operation.
|
56
|
+
@loader_count++
|
57
|
+
if @loader_count == 1
|
58
|
+
window.modularity.fire_global_event window.modularity.AjaxLoader.events.AJAX_LOADING
|
59
|
+
|
60
|
+
# Perform the request.
|
61
|
+
jQuery.get url, (data) =>
|
62
|
+
|
63
|
+
# Add result to cache.
|
64
|
+
if @caching
|
65
|
+
@data_cache.add url, data
|
66
|
+
|
67
|
+
# Call callbacks.
|
68
|
+
cb(data) for cb in @cb_cache.get url
|
69
|
+
|
70
|
+
# Remove request from callback list.
|
71
|
+
@cb_cache.remove url
|
72
|
+
|
73
|
+
# Fire 'loaded' event.
|
74
|
+
@loader_count--
|
75
|
+
if @loader_count == 0
|
76
|
+
window.modularity.fire_global_event window.modularity.AjaxLoader.events.AJAX_LOADED
|
77
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#= require modularity/tools/object_tools
|
2
|
+
|
3
|
+
# A generic cache.
|
4
|
+
# Stores key-value pairs.
|
5
|
+
class window.modularity.Cache
|
6
|
+
|
7
|
+
constructor: ->
|
8
|
+
@cache = {}
|
9
|
+
|
10
|
+
|
11
|
+
# Adds the given entry to the cache.
|
12
|
+
# Overwrites existing entries.
|
13
|
+
add: (key, value) ->
|
14
|
+
@cache[key] = value
|
15
|
+
|
16
|
+
|
17
|
+
# Returns the entry with the given key from the cache, or NULL if no entry exists.
|
18
|
+
get: (key) ->
|
19
|
+
@cache[key]
|
20
|
+
|
21
|
+
|
22
|
+
# Looks up several entries at once.
|
23
|
+
# Returns a hash of found entries, and a list of missing entries.
|
24
|
+
get_many: (keys) ->
|
25
|
+
result = { found: {}, missing: [] }
|
26
|
+
for key in keys
|
27
|
+
do (key) =>
|
28
|
+
value = @cache[key]
|
29
|
+
if value
|
30
|
+
result.found[key] = value
|
31
|
+
else
|
32
|
+
result.missing.push key
|
33
|
+
result
|
34
|
+
getMany: Cache::get_many
|
35
|
+
|
36
|
+
|
37
|
+
# Returns the number of cached objects.
|
38
|
+
length: () ->
|
39
|
+
modularity.object_length @cache
|
40
|
+
|
41
|
+
|
42
|
+
# Removes the entry with the given key.
|
43
|
+
remove: (key) =>
|
44
|
+
delete @cache[key]
|
45
|
+
|
46
|
+
|
47
|
+
# Removes all entries with the given keys.
|
48
|
+
remove_many: (keys) ->
|
49
|
+
@remove(key) for key in keys
|
50
|
+
|
51
|
+
|
52
|
+
# Replaces the cache with the given data.
|
53
|
+
# When 'key' is given, treats 'data' as an array of objects, and indexes each element by the given key.
|
54
|
+
# When 'key' is not given, treats 'data' as an already indexed hash object.
|
55
|
+
replace_all: (data, key) ->
|
56
|
+
if key
|
57
|
+
# Key given --> index the data array.
|
58
|
+
@add(entry[key], entry) for entry in data
|
59
|
+
else
|
60
|
+
# Key not given --> use data as the new cache.
|
61
|
+
@cache = data
|
62
|
+
replaceAll: Cache::replace_all
|
63
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#= require modularity/data/cache
|
2
|
+
|
3
|
+
# Provides fast and convenient retrieval of hash objects
|
4
|
+
# by indexing them on a given key column.
|
5
|
+
class modularity.IndexedCache
|
6
|
+
|
7
|
+
constructor: (@key) ->
|
8
|
+
@cache = new modularity.Cache
|
9
|
+
|
10
|
+
|
11
|
+
add: (entry) ->
|
12
|
+
@cache.add entry[@key], entry
|
13
|
+
|
14
|
+
|
15
|
+
add_all: (entries) ->
|
16
|
+
@add(entry) for entry in entries
|
17
|
+
|
18
|
+
|
19
|
+
remove: (entry) ->
|
20
|
+
@cache.remove entry[@key]
|
21
|
+
|
22
|
+
|
23
|
+
remove_many: (entries) ->
|
24
|
+
@cache.remove_many entries
|
25
|
+
|
26
|
+
|
27
|
+
get: (key) ->
|
28
|
+
@cache.get key
|
29
|
+
|
30
|
+
|
31
|
+
length: => @cache.length()
|
32
|
+
|
@@ -0,0 +1,166 @@
|
|
1
|
+
#= require modularity/data/ajax_loader
|
2
|
+
#= require modularity/data/indexed_cache
|
3
|
+
#= require modularity/tools/object_tools
|
4
|
+
|
5
|
+
# Provides persistence services for data models.
|
6
|
+
class modularity.PersistenceManager
|
7
|
+
|
8
|
+
constructor: (params) ->
|
9
|
+
|
10
|
+
# Copy of the data as it is on the server.
|
11
|
+
@server_data = new modularity.IndexedCache 'id'
|
12
|
+
|
13
|
+
# Copy of the data as it is on the client.
|
14
|
+
@client_data = new modularity.IndexedCache 'id'
|
15
|
+
|
16
|
+
# The base url on the server. Expected to be a fully RESTful API.
|
17
|
+
@base_url = params.url
|
18
|
+
|
19
|
+
@key = params.key or 'id'
|
20
|
+
|
21
|
+
# For handling parallel requests to the server.
|
22
|
+
@loader = new modularity.AjaxLoader { cache: no }
|
23
|
+
|
24
|
+
|
25
|
+
# Adds the given data objects to the server cache.
|
26
|
+
add_all: (data) ->
|
27
|
+
@server_data.add_all data
|
28
|
+
|
29
|
+
|
30
|
+
# Returns the URL to access the collection of objects.
|
31
|
+
collection_url: ->
|
32
|
+
"#{@base_url}.json"
|
33
|
+
|
34
|
+
|
35
|
+
# Creates the given object on the server.
|
36
|
+
create: (obj, callback) ->
|
37
|
+
jQuery.ajax
|
38
|
+
url: @collection_url()
|
39
|
+
type: 'POST'
|
40
|
+
data: obj
|
41
|
+
success: (server_obj) =>
|
42
|
+
@server_data.add server_obj
|
43
|
+
callback server_obj
|
44
|
+
|
45
|
+
|
46
|
+
delete: (obj, callback) ->
|
47
|
+
@client_data.remove obj
|
48
|
+
@server_data.remove obj
|
49
|
+
jQuery.ajax
|
50
|
+
url: @entry_url(obj)
|
51
|
+
type: 'DELETE'
|
52
|
+
success: ->
|
53
|
+
callback() if callback?
|
54
|
+
|
55
|
+
|
56
|
+
delete_many: (objects, callback) ->
|
57
|
+
@client_data.remove_many obj
|
58
|
+
@server_data.remove_many obj
|
59
|
+
jQuery.ajax
|
60
|
+
url: @base_url
|
61
|
+
type: 'DELETE'
|
62
|
+
data: (obj.id for obj in objects)
|
63
|
+
success: ->
|
64
|
+
callback() if callback?
|
65
|
+
|
66
|
+
|
67
|
+
# Returns the url to access a single entry.
|
68
|
+
entry_url: (entry) ->
|
69
|
+
"#{@base_url}/#{entry[@key]}.json"
|
70
|
+
|
71
|
+
|
72
|
+
# Returns the cached data object, or undefined.
|
73
|
+
get_cached: (key) ->
|
74
|
+
|
75
|
+
# Try to use client_data cache.
|
76
|
+
client_obj = @client_data.get key
|
77
|
+
return client_obj if client_obj
|
78
|
+
|
79
|
+
# No data in client cache --> try to use server cache.
|
80
|
+
server_obj = @server_data.get key
|
81
|
+
if server_obj
|
82
|
+
client_obj = modularity.clone_hash server_obj
|
83
|
+
@client_data.add client_obj
|
84
|
+
return client_obj
|
85
|
+
|
86
|
+
# Object not found in client or server cache.
|
87
|
+
return undefined
|
88
|
+
|
89
|
+
|
90
|
+
# Returns the entry with the given key.
|
91
|
+
load: (key, callback) ->
|
92
|
+
|
93
|
+
# Try to load from cache.
|
94
|
+
return callback(snippet) if snippet = @get_cached key
|
95
|
+
|
96
|
+
# No data on client at all --> load data from server.
|
97
|
+
@loader.get "#{@base_url}/#{key}", (server_entry) =>
|
98
|
+
@server_data.add server_entry
|
99
|
+
client_entry = modularity.clone_hash server_entry
|
100
|
+
@client_data.add client_entry
|
101
|
+
callback client_entry
|
102
|
+
|
103
|
+
|
104
|
+
# Loads all objects from the server.
|
105
|
+
# Provides the given params as parameters to the GET request.
|
106
|
+
load_all: (callback, params) ->
|
107
|
+
jQuery.ajax
|
108
|
+
url: @collection_url()
|
109
|
+
cache: no
|
110
|
+
data: params
|
111
|
+
success: (data) =>
|
112
|
+
@server_data.add_all data
|
113
|
+
callback()
|
114
|
+
|
115
|
+
|
116
|
+
# Loads all snippets with the given ids.
|
117
|
+
load_many: (ids, callback) ->
|
118
|
+
missing_ids = []
|
119
|
+
snippets = []
|
120
|
+
$.each ids, (pos, id) =>
|
121
|
+
snippet = @get_cached id
|
122
|
+
if snippet
|
123
|
+
snippets.push snippet
|
124
|
+
else
|
125
|
+
missing_ids.push id
|
126
|
+
|
127
|
+
if missing_ids.length == 0
|
128
|
+
return callback(snippets)
|
129
|
+
|
130
|
+
alert "uncached snippets found: #{missing_ids}"
|
131
|
+
|
132
|
+
|
133
|
+
# Saves the given object.
|
134
|
+
# Does the right thing (create or update) dependent on
|
135
|
+
# whether the object already has a key parameter.
|
136
|
+
save: (obj, callback) ->
|
137
|
+
if obj[@key]?
|
138
|
+
@update obj, callback
|
139
|
+
else
|
140
|
+
@create obj, callback
|
141
|
+
|
142
|
+
|
143
|
+
# Updates the given object.
|
144
|
+
# The given object must exist on the server already,
|
145
|
+
# and have a proper value in the key attribute.
|
146
|
+
update: (obj, callback) ->
|
147
|
+
|
148
|
+
# Create a new hash, containing only the changed attributes between obj and it's replica in @server_data.
|
149
|
+
diff_obj = modularity.object_diff @server_data.get(obj[@key]), obj
|
150
|
+
return if modularity.object_length(diff_obj) == 0
|
151
|
+
|
152
|
+
# Add key attribute.
|
153
|
+
diff_obj[@key] = obj[@key]
|
154
|
+
|
155
|
+
# Update server_data version.
|
156
|
+
@server_data.add obj
|
157
|
+
|
158
|
+
# Send to server
|
159
|
+
jQuery.ajax
|
160
|
+
url: @entry_url(obj)
|
161
|
+
type: 'PUT'
|
162
|
+
data: diff_obj
|
163
|
+
success: (server_obj) =>
|
164
|
+
@server_data.add server_obj
|
165
|
+
callback server_obj if callback
|
166
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This mixin adds a 'clickable' aspect to modules.
|
2
|
+
# This means clicking anywhere on the module fires the 'clicked' event.
|
3
|
+
window.modularity.clickable =
|
4
|
+
|
5
|
+
constructor: ->
|
6
|
+
@container.click @container_clicked
|
7
|
+
|
8
|
+
|
9
|
+
# Events that are fired by this mixin.
|
10
|
+
events:
|
11
|
+
clicked: 'clicked'
|
12
|
+
|
13
|
+
|
14
|
+
# Programmatically click this clickable element.
|
15
|
+
# For testing and scripting.
|
16
|
+
click: -> @container.click()
|
17
|
+
|
18
|
+
|
19
|
+
# Event handler for clicks on this clickable element.
|
20
|
+
container_clicked: -> @fire_event modularity.clickable.events.clicked
|
21
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
window.modularity.closable =
|
2
|
+
|
3
|
+
constructor: (container) ->
|
4
|
+
close_button = @container.find('.CloseButton')
|
5
|
+
unless close_button?.length > 0
|
6
|
+
window.alert 'Error: Close button not found'
|
7
|
+
close_button.click => @close_button_clicked()
|
8
|
+
|
9
|
+
|
10
|
+
# The events that can be fired by closable objects.
|
11
|
+
events:
|
12
|
+
closed: 'closed'
|
13
|
+
|
14
|
+
|
15
|
+
# Called when the button got clicked by the user or programmatically.
|
16
|
+
close_button_clicked: ->
|
17
|
+
if @closable_closing
|
18
|
+
return unless @closable_closing()
|
19
|
+
@fire_event 'closed'
|
20
|
+
@container.remove()
|
21
|
+
@closable_closed() if @closable_closed
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# The Modularity framework written specificially for CoffeeScript.
|
2
|
+
#
|
3
|
+
# Use UglifyJS (http://github.com/mishoo/UglifyJS) for compression.
|
4
|
+
#
|
5
|
+
# Please see https://github.com/kevgo/modularity for more information on Modularity,
|
6
|
+
# and https://github.com/kevgo/modularity-rails for Rails integration.
|
7
|
+
|
8
|
+
|
9
|
+
window.modularity = {
|
10
|
+
|
11
|
+
# Checks whether the given condition is true.
|
12
|
+
# Shows an alert with the given message if not.
|
13
|
+
assert: (condition, message) ->
|
14
|
+
if !condition or condition?.length == 0
|
15
|
+
alert message
|
16
|
+
return false
|
17
|
+
true
|
18
|
+
|
19
|
+
|
20
|
+
# GLOBAL EVENTS.
|
21
|
+
|
22
|
+
# Subscribes to the given global event,
|
23
|
+
# i.e. calls the given function when the given global event type happens.
|
24
|
+
bind_global_event: (event_type, callback) ->
|
25
|
+
return unless modularity.assert typeof event_type == 'string', "modularity.bind_global_event: parameter 'event_type' is empty"
|
26
|
+
return alert "modularity.bind_global_event: parameter 'callback' must be a function, #{callback} (#{typeof callback}) given." unless typeof callback == 'function'
|
27
|
+
modularity.global_event_container().bind event_type, callback
|
28
|
+
|
29
|
+
# Fires the given global event with the given data payload.
|
30
|
+
fire_global_event: (event_type, data) ->
|
31
|
+
modularity.assert event_type, 'Module.fire_global_event: You must provide the event type to fire.'
|
32
|
+
return alert("Module.fire_global_event: Event type must be a string, #{event_type} (#{typeof event_type}) given.") unless typeof event_type == 'string'
|
33
|
+
modularity.global_event_container().trigger event_type, data ?= []
|
34
|
+
|
35
|
+
# Returns the DOM object that is used to fire global events on.
|
36
|
+
global_event_container: -> modularity.global_event_container_cache or= $(window)
|
37
|
+
}
|
38
|
+
window.modularity.bindGlobalEvent = window.modularity.bind_global_event
|
39
|
+
window.modularity.fireGlobalEvent = window.modularity.fire_global_event
|
40
|
+
|
41
|
+
|
42
|
+
class window.modularity.Module
|
43
|
+
|
44
|
+
# The container variable is required. Provide 'testing' in tests.
|
45
|
+
constructor: (container) ->
|
46
|
+
container = $(container) if (typeof container == 'string') and container != 'testing'
|
47
|
+
@container = container
|
48
|
+
|
49
|
+
return alert 'Error in Module constructor: No container given.' unless @container?
|
50
|
+
if container != 'testing'
|
51
|
+
return alert 'Error in Module constructor: The given container must be a jQuery object.' unless typeof container.jquery == 'string'
|
52
|
+
return alert "Error in Module constructor: The given container ('#{container.selector}') is empty." unless container? and container.length > 0
|
53
|
+
return alert "Error in Module constructor: The given container ('#{container.selector}') has more than one element." unless container? and container.length == 1
|
54
|
+
|
55
|
+
|
56
|
+
# Attach mixins.
|
57
|
+
if @mixins?
|
58
|
+
for mixin_data in @mixins
|
59
|
+
|
60
|
+
# Attach all properties from mixin to the prototype.
|
61
|
+
for methodName, method of mixin_data.mixin
|
62
|
+
do (methodName, method) => unless @[methodName]
|
63
|
+
@[methodName] = => method.apply(@, arguments)
|
64
|
+
|
65
|
+
# Call constructor function from mixin.
|
66
|
+
mixin_data.mixin?.constructor?.apply(@, arguments)
|
67
|
+
|
68
|
+
|
69
|
+
# Runs the given query within the container element.
|
70
|
+
$: (query) ->
|
71
|
+
@container.find query
|
72
|
+
|
73
|
+
|
74
|
+
# MODULE EVENTS.
|
75
|
+
|
76
|
+
# Calls the given function when this widget fires the given local event.
|
77
|
+
bind_event: (event_type, callback) =>
|
78
|
+
return unless modularity.assert typeof event_type == 'string', "Module.bind_event: parameter 'event_type' is empty"
|
79
|
+
return alert "Module.bind_event: parameter 'callback' must be a function, #{callback} (#{typeof callback}) given." unless typeof callback == 'function'
|
80
|
+
@container.bind event_type, callback
|
81
|
+
bindEvent: Module::bind_event
|
82
|
+
|
83
|
+
# Fires the given local event with the given data payload.
|
84
|
+
fire_event: (event_type, data) =>
|
85
|
+
modularity.assert event_type, 'Module.fire_event: You must provide the event type to fire.'
|
86
|
+
return alert("Module.fire_event: Event type must be a string, #{event_type} (#{typeof event_type}) given.") unless typeof event_type == 'string'
|
87
|
+
@container.trigger event_type, data ?= {}
|
88
|
+
fireEvent: Module::fire_event
|
89
|
+
|
90
|
+
|
91
|
+
# Hides this module.
|
92
|
+
hide: ->
|
93
|
+
@container.hide()
|
94
|
+
|
95
|
+
|
96
|
+
# mixin = constructor of Draggable
|
97
|
+
# self = Card
|
98
|
+
@mixin: (mixin, p...) ->
|
99
|
+
alert("mixin not found") unless mixin
|
100
|
+
@prototype.mixins or= []
|
101
|
+
@prototype.mixins.push({mixin: mixin, params: p})
|
102
|
+
|
103
|
+
|
104
|
+
# Shows this module.
|
105
|
+
show: ->
|
106
|
+
@container.show()
|
107
|
+
|
108
|
+
|
109
|
+
# jQuery integration for creating Modules.
|
110
|
+
#
|
111
|
+
# Call like this: myModule = $('...').module(MyModuleClass)
|
112
|
+
#
|
113
|
+
# Parameters:
|
114
|
+
# * klass: the class of the Module to instantiate
|
115
|
+
# * any additional parameters are forwarded to the Module constructor.
|
116
|
+
# Returns the created module instance.
|
117
|
+
#
|
118
|
+
# Messages errors in alert boxes.
|
119
|
+
#
|
120
|
+
jQuery.fn.module = (klass, args...) ->
|
121
|
+
|
122
|
+
# Check parameters.
|
123
|
+
if typeof klass != 'function'
|
124
|
+
return alert "ERROR!\n\nYou must provide the Module class when calling $.module().\n\nExample: $('...').module(MyModuleClass)\n\nYou provided: #{klass} (#{typeof klass})"
|
125
|
+
|
126
|
+
# Instantiate the class and return the instance.
|
127
|
+
new klass(this, args...)
|
128
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Autogrowing textarea.
|
2
|
+
class window.modularity.AutogrowTextArea extends modularity.Module
|
3
|
+
|
4
|
+
constructor: (container) ->
|
5
|
+
super
|
6
|
+
@textarea = @container[0]
|
7
|
+
@textarea.style.height = "auto"
|
8
|
+
@textarea.style.overflow = "hidden"
|
9
|
+
@container.keyup @grow
|
10
|
+
@container.focus @grow
|
11
|
+
@container.blur @grow
|
12
|
+
|
13
|
+
# How many characters per line.
|
14
|
+
@characters_per_line = this.textarea.cols
|
15
|
+
|
16
|
+
# The initial (minimal) number of rows.
|
17
|
+
@min_rows = this.textarea.rows
|
18
|
+
|
19
|
+
@grow()
|
20
|
+
|
21
|
+
|
22
|
+
# Sets the height of the textarea according to the content.
|
23
|
+
grow: =>
|
24
|
+
@textarea.rows = Math.max modularity.AutogrowTextArea.lines(@characters_per_line, @textarea.value), @min_rows
|
25
|
+
|
26
|
+
# Returns the number of lines
|
27
|
+
@lines: (width, text) ->
|
28
|
+
lines_count = 0
|
29
|
+
lines = text.split(/\n/)
|
30
|
+
lines_count += Math.floor((line.length / width) + 1) for line in lines
|
31
|
+
lines_count
|
32
|
+
|
@@ -0,0 +1,8 @@
|
|
1
|
+
#= require modularity/mixins/clickable
|
2
|
+
|
3
|
+
# A button module.
|
4
|
+
# Responds to clicks on the container element.
|
5
|
+
# The container element is expected to already be populated.
|
6
|
+
class window.modularity.Button extends window.modularity.Module
|
7
|
+
@mixin window.modularity.clickable
|
8
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Returns a replica of the given hash.
|
2
|
+
# Don't use this method for real objects with superclasses, prototypes, and stuff.
|
3
|
+
modularity.clone_hash = (obj) ->
|
4
|
+
result = {}
|
5
|
+
result[key] = value for own key, value of obj
|
6
|
+
result
|
7
|
+
|
8
|
+
|
9
|
+
# Returns an object that contains only the attributes
|
10
|
+
# that are different between obj_1 and obj_2.
|
11
|
+
# Only looks for changed attributes, not missing attributes.
|
12
|
+
modularity.object_diff = (obj_1, obj_2) ->
|
13
|
+
result = {}
|
14
|
+
for own key, value_2 of obj_2
|
15
|
+
do (key, value_2) ->
|
16
|
+
value_1 = obj_1[key]
|
17
|
+
result[key] = value_2 if value_1 != value_2
|
18
|
+
result
|
19
|
+
|
20
|
+
|
21
|
+
# Returns the number of attributes of the given object.
|
22
|
+
# NOTE(KG): This doesn't work in IE8.
|
23
|
+
modularity.object_length = (obj) ->
|
24
|
+
Object.keys(obj).length
|
25
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>ModularityCoffeeRails</title>
|
5
|
+
<%= stylesheet_link_tag "modularity-coffee-rails/application", :media => "all" %>
|
6
|
+
<%= javascript_include_tag "modularity-coffee-rails/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: modularity-coffee-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kevin Goslar
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.12
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.2.12
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: coffee-rails
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: jquery-rails
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: konacha
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec-rails
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: sqlite3
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description: Makes the modularity-coffee framework available to Ruby on Rails applications.
|
111
|
+
email:
|
112
|
+
- kevin.goslar@gmail.com
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- app/assets/javascripts/modularity/application.js
|
118
|
+
- app/assets/javascripts/modularity/data/ajax_loader.coffee
|
119
|
+
- app/assets/javascripts/modularity/data/cache.coffee
|
120
|
+
- app/assets/javascripts/modularity/data/indexed_cache.coffee
|
121
|
+
- app/assets/javascripts/modularity/data/persistence_manager.coffee
|
122
|
+
- app/assets/javascripts/modularity/mixins/clickable.coffee
|
123
|
+
- app/assets/javascripts/modularity/mixins/closable.coffee
|
124
|
+
- app/assets/javascripts/modularity/modularity.coffee
|
125
|
+
- app/assets/javascripts/modularity/modules/autogrow_textarea.coffee
|
126
|
+
- app/assets/javascripts/modularity/modules/button.coffee
|
127
|
+
- app/assets/javascripts/modularity/tools/array_tools.coffee
|
128
|
+
- app/assets/javascripts/modularity/tools/object_tools.coffee
|
129
|
+
- app/controllers/modularity-coffee-rails/application_controller.rb
|
130
|
+
- app/helpers/modularity-coffee-rails/application_helper.rb
|
131
|
+
- app/views/layouts/modularity-coffee-rails/application.html.erb
|
132
|
+
- config/routes.rb
|
133
|
+
- lib/modularity-coffee-rails/engine.rb
|
134
|
+
- lib/modularity-coffee-rails/version.rb
|
135
|
+
- lib/modularity-coffee-rails.rb
|
136
|
+
- lib/tasks/modularity-coffee-rails_tasks.rake
|
137
|
+
- MIT-LICENSE
|
138
|
+
- Rakefile
|
139
|
+
- README.md
|
140
|
+
homepage: http://github.com/kevgo/modularity-rails
|
141
|
+
licenses: []
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
none: false
|
148
|
+
requirements:
|
149
|
+
- - ! '>='
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
requirements: []
|
159
|
+
rubyforge_project:
|
160
|
+
rubygems_version: 1.8.23
|
161
|
+
signing_key:
|
162
|
+
specification_version: 3
|
163
|
+
summary: A lightweight, object- and component-oriented CoffeeScript framework for
|
164
|
+
Ruby on Rails applications.
|
165
|
+
test_files: []
|