joosy 0.1.0.alpha → 1.0.0.RC1
Sign up to get free protection for your applications and to get access to all the features.
- data/.codoopts +5 -0
- data/.gitignore +2 -0
- data/Gemfile +15 -2
- data/Gemfile.lock +102 -81
- data/Guardfile +16 -16
- data/LICENSE +22 -0
- data/MIT-LICENSE +2 -2
- data/README.md +118 -0
- data/app/assets/javascripts/joosy/core/application.js.coffee +53 -0
- data/app/assets/javascripts/joosy/core/form.js.coffee +338 -0
- data/app/assets/javascripts/joosy/core/helpers/form.js.coffee +72 -0
- data/app/assets/javascripts/joosy/core/helpers/view.js.coffee +42 -0
- data/app/assets/javascripts/joosy/core/helpers/widgets.js.coffee +14 -0
- data/app/assets/javascripts/joosy/core/joosy.js.coffee +184 -0
- data/app/assets/javascripts/joosy/core/layout.js.coffee +168 -0
- data/app/assets/javascripts/joosy/core/modules/container.js.coffee +124 -0
- data/app/assets/javascripts/joosy/core/modules/events.js.coffee +122 -0
- data/app/assets/javascripts/joosy/core/modules/filters.js.coffee +39 -0
- data/app/assets/javascripts/joosy/core/modules/log.js.coffee +36 -0
- data/app/assets/javascripts/joosy/core/modules/module.js.coffee +117 -0
- data/app/assets/javascripts/joosy/core/modules/renderer.js.coffee +200 -0
- data/app/assets/javascripts/joosy/core/modules/time_manager.js.coffee +46 -0
- data/app/assets/javascripts/joosy/core/modules/widgets_manager.js.coffee +87 -0
- data/app/assets/javascripts/joosy/core/page.js.coffee +387 -0
- data/app/assets/javascripts/joosy/core/preloader.js.coffee +13 -0
- data/app/assets/javascripts/joosy/core/resource/collection.js.coffee +175 -0
- data/app/assets/javascripts/joosy/core/resource/generic.js.coffee +303 -0
- data/app/assets/javascripts/joosy/core/resource/rest.js.coffee +244 -0
- data/app/assets/javascripts/joosy/core/resource/rest_collection.js.coffee +24 -0
- data/app/assets/javascripts/joosy/core/router.js.coffee +201 -0
- data/app/assets/javascripts/joosy/core/templaters/rails_jst.js.coffee +37 -0
- data/app/assets/javascripts/joosy/core/widget.js.coffee +85 -0
- data/app/assets/javascripts/joosy/preloaders/caching.js.coffee +169 -0
- data/app/assets/javascripts/joosy/preloaders/inline.js.coffee +56 -0
- data/{vendor → app}/assets/javascripts/joosy.js.coffee +0 -1
- data/app/helpers/joosy/sprockets_helper.rb +39 -12
- data/joosy.gemspec +4 -3
- data/lib/joosy/rails/engine.rb +12 -1
- data/lib/joosy/rails/version.rb +2 -2
- data/lib/joosy.rb +9 -0
- data/lib/rails/generators/joosy/application_generator.rb +15 -3
- data/lib/rails/generators/joosy/joosy_base.rb +2 -2
- data/lib/rails/generators/joosy/layout_generator.rb +8 -1
- data/lib/rails/generators/joosy/page_generator.rb +21 -6
- data/lib/rails/generators/joosy/preloader_generator.rb +14 -7
- data/lib/rails/generators/joosy/resource_generator.rb +29 -0
- data/lib/rails/generators/joosy/templates/app/helpers/application.js.coffee +4 -0
- data/lib/rails/generators/joosy/templates/app/layouts/application.js.coffee +1 -0
- data/lib/rails/generators/joosy/templates/app/layouts/template.js.coffee +1 -1
- data/lib/rails/generators/joosy/templates/app/pages/application.js.coffee +1 -1
- data/lib/rails/generators/joosy/templates/app/pages/template.js.coffee +3 -3
- data/lib/rails/generators/joosy/templates/app/pages/welcome/index.js.coffee +22 -0
- data/lib/rails/generators/joosy/templates/app/resources/template.js.coffee +2 -0
- data/lib/rails/generators/joosy/templates/app/routes.js.coffee +7 -1
- data/lib/rails/generators/joosy/templates/app/templates/layouts/application.jst.hamlc +2 -0
- data/lib/rails/generators/joosy/templates/app/templates/pages/welcome/index.jst.hamlc +7 -0
- data/lib/rails/generators/joosy/templates/app/widgets/template.js.coffee +2 -2
- data/lib/rails/generators/joosy/templates/app.js.coffee +4 -0
- data/lib/rails/generators/joosy/templates/app_preloader.js.coffee.erb +9 -12
- data/lib/rails/generators/joosy/templates/app_resources_predefiner.js.coffee.erb +11 -0
- data/lib/rails/generators/joosy/templates/preload.html.erb +5 -6
- data/lib/rails/generators/joosy/templates/preload.html.haml +3 -5
- data/lib/rails/generators/joosy/widget_generator.rb +8 -1
- data/lib/rails/resources_with_joosy.rb +11 -0
- data/spec/javascripts/helpers/spec_helper.js.coffee +25 -0
- data/spec/javascripts/joosy/core/application_spec.js.coffee +40 -0
- data/spec/javascripts/joosy/core/form_spec.js.coffee +200 -0
- data/spec/javascripts/joosy/core/helpers/forms_spec.js.coffee +103 -0
- data/spec/javascripts/joosy/core/helpers/view_spec.js.coffee +10 -0
- data/spec/javascripts/joosy/core/joosy_spec.js.coffee +97 -0
- data/spec/javascripts/joosy/core/layout_spec.js.coffee +50 -0
- data/spec/javascripts/joosy/core/modules/container_spec.js.coffee +32 -27
- data/spec/javascripts/joosy/core/modules/events_spec.js.coffee +55 -18
- data/spec/javascripts/joosy/core/modules/filters_spec.js.coffee +28 -27
- data/spec/javascripts/joosy/core/modules/log_spec.js.coffee +3 -3
- data/spec/javascripts/joosy/core/modules/module_spec.js.coffee +6 -15
- data/spec/javascripts/joosy/core/modules/renderer_spec.js.coffee +203 -0
- data/spec/javascripts/joosy/core/modules/time_manager_spec.js.coffee +12 -7
- data/spec/javascripts/joosy/core/modules/widget_manager_spec.js.coffee +31 -17
- data/spec/javascripts/joosy/core/page_spec.js.coffee +178 -0
- data/spec/javascripts/joosy/core/resource/collection_spec.js.coffee +84 -0
- data/spec/javascripts/joosy/core/resource/generic_spec.js.coffee +149 -0
- data/spec/javascripts/joosy/core/resource/rest_collection_spec.js.coffee +31 -0
- data/spec/javascripts/joosy/core/resource/rest_spec.js.coffee +171 -0
- data/spec/javascripts/joosy/core/router_spec.js.coffee +143 -0
- data/spec/javascripts/joosy/core/templaters/rails_jst_spec.js.coffee +25 -0
- data/spec/javascripts/joosy/core/widget_spec.js.coffee +40 -0
- data/spec/javascripts/joosy/preloaders/caching_spec.js.coffee +36 -0
- data/spec/javascripts/joosy/preloaders/inline_spec.js.coffee +16 -0
- data/spec/javascripts/support/assets/coolface.jpg +0 -0
- data/spec/javascripts/support/assets/okay.jpg +0 -0
- data/spec/javascripts/support/assets/test.js +1 -0
- data/spec/javascripts/support/sinon-ie-1.3.1.js +82 -0
- data/vendor/assets/javascripts/jquery.form.js +978 -963
- data/vendor/assets/javascripts/metamorph.js +409 -0
- data/vendor/assets/javascripts/sugar.js +1057 -366
- metadata +95 -50
- data/README.rdoc +0 -3
- data/lib/joosy/forms.rb +0 -47
- data/lib/joosy-rails.rb +0 -5
- data/lib/rails/generators/joosy/templates/preload.html.slim +0 -21
- data/tmp/javascripts/.gitignore +0 -1
- data/tmp/spec/javascripts/helpers/.gitignore +0 -1
- data/vendor/assets/javascripts/base64.js +0 -135
- data/vendor/assets/javascripts/inflection.js +0 -656
- data/vendor/assets/javascripts/joosy/core/application.js.coffee +0 -26
- data/vendor/assets/javascripts/joosy/core/form.js.coffee +0 -87
- data/vendor/assets/javascripts/joosy/core/joosy.js.coffee +0 -62
- data/vendor/assets/javascripts/joosy/core/layout.js.coffee +0 -38
- data/vendor/assets/javascripts/joosy/core/modules/container.js.coffee +0 -51
- data/vendor/assets/javascripts/joosy/core/modules/events.js.coffee +0 -17
- data/vendor/assets/javascripts/joosy/core/modules/filters.js.coffee +0 -39
- data/vendor/assets/javascripts/joosy/core/modules/log.js.coffee +0 -12
- data/vendor/assets/javascripts/joosy/core/modules/module.js.coffee +0 -28
- data/vendor/assets/javascripts/joosy/core/modules/time_manager.js.coffee +0 -23
- data/vendor/assets/javascripts/joosy/core/modules/widgets_manager.js.coffee +0 -45
- data/vendor/assets/javascripts/joosy/core/page.js.coffee +0 -136
- data/vendor/assets/javascripts/joosy/core/resource/rest.js.coffee +0 -69
- data/vendor/assets/javascripts/joosy/core/resource/rest_collection.js.coffee +0 -42
- data/vendor/assets/javascripts/joosy/core/router.js.coffee +0 -75
- data/vendor/assets/javascripts/joosy/core/widget.js.coffee +0 -35
- data/vendor/assets/javascripts/joosy/preloader/development.js.coffee +0 -27
- data/vendor/assets/javascripts/joosy/preloader/production.js.coffee +0 -84
@@ -0,0 +1,338 @@
|
|
1
|
+
#= require joosy/core/joosy
|
2
|
+
#= require joosy/core/modules/module
|
3
|
+
#= require joosy/core/modules/log
|
4
|
+
#= require joosy/core/modules/events
|
5
|
+
#= require joosy/core/modules/container
|
6
|
+
|
7
|
+
#
|
8
|
+
# AJAXifies form including file uploads and stuff. Built on top of jQuery.Form.
|
9
|
+
#
|
10
|
+
# Joosy.Form automatically cares of form validation highlights. It can
|
11
|
+
# read common server error responses and add .field_with_errors class to proper
|
12
|
+
# field.
|
13
|
+
#
|
14
|
+
# If you don't have resource associated with form (#fill), it will try to find fields
|
15
|
+
# by exact keywords from response. Otherwise it will search for resource_name[field].
|
16
|
+
#
|
17
|
+
# @example Joosy.Form usage
|
18
|
+
# form = new Joosy.Form, -> (response)
|
19
|
+
# console.log "Saved and got some: #{response}"
|
20
|
+
#
|
21
|
+
# form.progress = (percent) -> console.log "Uploaded by #{percent}%"
|
22
|
+
# form.fill @resource
|
23
|
+
#
|
24
|
+
# @include Joosy.Modules.Log
|
25
|
+
# @include Joosy.Modules.Events
|
26
|
+
# @include Joosy.Modules.Container
|
27
|
+
#
|
28
|
+
class Joosy.Form extends Joosy.Module
|
29
|
+
@include Joosy.Modules.Log
|
30
|
+
@include Joosy.Modules.Events
|
31
|
+
@include Joosy.Modules.Container
|
32
|
+
|
33
|
+
#
|
34
|
+
# Marks the CSS class to use to mark invalidated fields
|
35
|
+
#
|
36
|
+
invalidationClass: 'field_with_errors'
|
37
|
+
|
38
|
+
#
|
39
|
+
# List of mappings for fields of invalidated data which comes from server
|
40
|
+
#
|
41
|
+
# If you have something like {foo: 'bar', bar: 'baz'} coming from server
|
42
|
+
# substitutions = {foo: 'foo_id'} will change it to {foo_id: 'bar', bar: 'baz'}
|
43
|
+
#
|
44
|
+
substitutions: {}
|
45
|
+
|
46
|
+
#
|
47
|
+
# List of elements for internal usage
|
48
|
+
#
|
49
|
+
elements:
|
50
|
+
'fields': 'input,select,textarea'
|
51
|
+
|
52
|
+
#
|
53
|
+
# Makes one AJAX request with form data without binding
|
54
|
+
#
|
55
|
+
# @see #constructor
|
56
|
+
#
|
57
|
+
# @param [Element] form Instance of HTML form element
|
58
|
+
# @param [Hash] options Options
|
59
|
+
#
|
60
|
+
@submit: (form, options={}) ->
|
61
|
+
form = new @(form, options)
|
62
|
+
form.container.submit()
|
63
|
+
form.unbind()
|
64
|
+
null
|
65
|
+
|
66
|
+
@attach: ->
|
67
|
+
new Joosy.Form arguments...
|
68
|
+
|
69
|
+
#
|
70
|
+
# During initialization replaces your basic form submit with AJAX request
|
71
|
+
#
|
72
|
+
# @note If method of form differs from POST or GET it will simulate it
|
73
|
+
# by adding hidden _method input. In this cases the method itself will be
|
74
|
+
# set to POST.
|
75
|
+
#
|
76
|
+
# @note For browsers having no support of HTML5 Forms it may do an iframe requests
|
77
|
+
# to handle file uploading.
|
78
|
+
#
|
79
|
+
# @param [jQuery] form Form element instance
|
80
|
+
# @param [Hash] options Options
|
81
|
+
#
|
82
|
+
# @option options [Function] before `(XHR) -> Bollean` to trigger right before submit.
|
83
|
+
# By default will run form invalidation cleanup. This behavior can be canceled
|
84
|
+
# by returning false from your own before callback. Both of callbacks will run if
|
85
|
+
# you return true.
|
86
|
+
#
|
87
|
+
# @option options [Function] success `(Object) -> null` triggers on 200 HTTP code from server.
|
88
|
+
# Pases in the parsed JSON.
|
89
|
+
#
|
90
|
+
# @option options [Function] progress `(Float) -> null` runs periodically while form is uploading
|
91
|
+
#
|
92
|
+
# @option options [Function] error `(Object) -> Boolean` triggers if server responded with anything but 200.
|
93
|
+
# By default will run form invalidation routine. This behavior can be canceled
|
94
|
+
# by returning false from your own error callback. Both of callbacks will run if
|
95
|
+
# you return true.
|
96
|
+
#
|
97
|
+
# @option options [Joosy.Resource.Generic] resource The resource to fill the form with
|
98
|
+
# @option options [String] resourceName The string to use as a resource name prefix for fields to match invalidation
|
99
|
+
# @option options [String] action Action URL for the form
|
100
|
+
#
|
101
|
+
constructor: (form, options={}) ->
|
102
|
+
if Object.isFunction options
|
103
|
+
@success = options
|
104
|
+
else
|
105
|
+
Object.each options, (key, value) =>
|
106
|
+
@[key] = value
|
107
|
+
|
108
|
+
@container = $(form)
|
109
|
+
return if @container.length == 0
|
110
|
+
|
111
|
+
@refreshElements()
|
112
|
+
@__delegateEvents()
|
113
|
+
|
114
|
+
method = @container.get(0).getAttribute('method')?.toLowerCase()
|
115
|
+
if method && !['get', 'post'].has method
|
116
|
+
@__markMethod method
|
117
|
+
@container.attr 'method', 'POST'
|
118
|
+
|
119
|
+
@container.ajaxForm
|
120
|
+
dataType: 'json'
|
121
|
+
beforeSend: =>
|
122
|
+
@__before arguments...
|
123
|
+
success: =>
|
124
|
+
@__success arguments...
|
125
|
+
error: =>
|
126
|
+
@__error arguments...
|
127
|
+
xhr: =>
|
128
|
+
xhr = $.ajaxSettings.xhr()
|
129
|
+
if xhr.upload? && @progress
|
130
|
+
xhr.upload.onprogress = (event) =>
|
131
|
+
if event.lengthComputable
|
132
|
+
@progress (event.position / event.total * 100).round 2
|
133
|
+
xhr
|
134
|
+
|
135
|
+
if options.resource?
|
136
|
+
@fill(options.resource, options)
|
137
|
+
delete @resource
|
138
|
+
|
139
|
+
#
|
140
|
+
# Resets form submit behavior to default
|
141
|
+
#
|
142
|
+
unbind: ->
|
143
|
+
@container.unbind('submit').find('input:submit,input:image,button:submit').unbind('click');
|
144
|
+
|
145
|
+
#
|
146
|
+
# Links current form with given resource and sets values of form inputs from with it.
|
147
|
+
# Form will use give resource while doing invalidation routine.
|
148
|
+
#
|
149
|
+
# @param [Resource] resource Resource to fill fields with
|
150
|
+
# @param [Hash] options Options
|
151
|
+
#
|
152
|
+
# @option options [Function] decorator `(Object) -> Object` decoration callback
|
153
|
+
# Pases in the parsed JSON.
|
154
|
+
#
|
155
|
+
# @option options [String] action Action URL for the form
|
156
|
+
#
|
157
|
+
fill: (resource, options) ->
|
158
|
+
resource = resource.build() if Object.isFunction(resource.build)
|
159
|
+
@__resource = resource
|
160
|
+
|
161
|
+
if options?.decorator?
|
162
|
+
data = options.decorator resource.data
|
163
|
+
else
|
164
|
+
data = resource.data
|
165
|
+
|
166
|
+
Object.each data, (key, val) =>
|
167
|
+
key = resource.__entityName + "[#{key}]"
|
168
|
+
input = @fields.filter("[name='#{key.underscore()}']:not(:file),[name='#{key.camelize(false)}']:not(:file)")
|
169
|
+
unless input.is ':checkbox'
|
170
|
+
input.val val
|
171
|
+
else
|
172
|
+
if val
|
173
|
+
input.attr 'checked', 'checked'
|
174
|
+
else
|
175
|
+
input.removeAttr 'checked'
|
176
|
+
|
177
|
+
@__markMethod(options?.method || 'PUT') if resource.id()
|
178
|
+
|
179
|
+
url = options?.action || (if resource.id()? then resource.memberPath() else resource.collectionPath())
|
180
|
+
|
181
|
+
@container.attr 'action', url
|
182
|
+
@container.attr 'method', 'POST'
|
183
|
+
|
184
|
+
#
|
185
|
+
# Submit the HTML Form
|
186
|
+
#
|
187
|
+
submit: ->
|
188
|
+
@container.submit()
|
189
|
+
|
190
|
+
#
|
191
|
+
# Serializes form into query string.
|
192
|
+
#
|
193
|
+
# @param [Boolean] skipMethod Determines if we should skip magical _method field
|
194
|
+
#
|
195
|
+
# @return [String]
|
196
|
+
#
|
197
|
+
serialize: (skipMethod=true) ->
|
198
|
+
data = @container.serialize()
|
199
|
+
data = data.replace /\&?\_method\=put/i, '' if skipMethod
|
200
|
+
|
201
|
+
data
|
202
|
+
|
203
|
+
#
|
204
|
+
# Inner success callback.
|
205
|
+
#
|
206
|
+
__success: (response, status, xhr) ->
|
207
|
+
if xhr
|
208
|
+
@success? response
|
209
|
+
else if response.status == 200
|
210
|
+
@success response.json
|
211
|
+
else
|
212
|
+
@__error response.json
|
213
|
+
|
214
|
+
#
|
215
|
+
# Inner before callback.
|
216
|
+
# By default will clean invalidation.
|
217
|
+
#
|
218
|
+
__before: (xhr, settings) ->
|
219
|
+
if !@before? || @before(arguments...) is true
|
220
|
+
@fields.removeClass @invalidationClass
|
221
|
+
|
222
|
+
#
|
223
|
+
# Inner error callback.
|
224
|
+
# By default will trigger basic invalidation.
|
225
|
+
#
|
226
|
+
__error: (data) ->
|
227
|
+
errors = if data.responseText
|
228
|
+
try
|
229
|
+
data = jQuery.parseJSON(data.responseText)
|
230
|
+
catch error
|
231
|
+
{}
|
232
|
+
else
|
233
|
+
data
|
234
|
+
|
235
|
+
if !@error? || @error(errors) is true
|
236
|
+
errors = @__stringifyErrors(errors)
|
237
|
+
|
238
|
+
Object.each errors, (field, notifications) =>
|
239
|
+
input = @findField(field).addClass @invalidationClass
|
240
|
+
@notification? input, notifications
|
241
|
+
|
242
|
+
return errors
|
243
|
+
|
244
|
+
return false
|
245
|
+
|
246
|
+
#
|
247
|
+
# Finds field by field name.
|
248
|
+
# This is not inlined since we want to override
|
249
|
+
# or monkeypatch it from time to time
|
250
|
+
#
|
251
|
+
# @param [String] field Name of field to find
|
252
|
+
#
|
253
|
+
findField: (field) ->
|
254
|
+
@fields.filter("[name='#{field}']")
|
255
|
+
|
256
|
+
#
|
257
|
+
# Simulates REST methods by adding hidden _method input with real method
|
258
|
+
# while setting POST as the transport method
|
259
|
+
#
|
260
|
+
# @param [String] method Real method to simulate
|
261
|
+
#
|
262
|
+
__markMethod: (method='PUT') ->
|
263
|
+
method = $('<input/>',
|
264
|
+
type: 'hidden'
|
265
|
+
name: '_method'
|
266
|
+
value: method
|
267
|
+
)
|
268
|
+
@container.append method
|
269
|
+
|
270
|
+
#
|
271
|
+
# Prepares server response for default error handler
|
272
|
+
# Turns all possible response notations into form notation (foo[bar])
|
273
|
+
# Every direct field of incoming data will be decorated by @substitutions
|
274
|
+
#
|
275
|
+
# @example Flat validation result
|
276
|
+
# { field1: ['error'] } # input
|
277
|
+
# { field1: ['error'] } # if form was not associated with Resource by {#fill}
|
278
|
+
# { "fluffy[field1]": ['error']} # if form was associated with Resource (named fluffy)
|
279
|
+
#
|
280
|
+
# @example Complex validation result
|
281
|
+
# { foo: { bar: { baz: ['error'] } } } # input
|
282
|
+
# { "foo[bar][bar]": ['error'] } # output
|
283
|
+
#
|
284
|
+
# @param [Object] errors Data to prepare
|
285
|
+
#
|
286
|
+
# @return [Hash<String, Array>] Flat hash with field names in keys and arrays
|
287
|
+
# of errors in values
|
288
|
+
#
|
289
|
+
__stringifyErrors: (errors) ->
|
290
|
+
result = {}
|
291
|
+
|
292
|
+
Object.each errors, (field, notifications) =>
|
293
|
+
if @substitutions[field]?
|
294
|
+
field = @substitutions[field]
|
295
|
+
|
296
|
+
if Object.isObject notifications
|
297
|
+
Object.each @__foldInlineEntities(notifications), (key, value) ->
|
298
|
+
result[field+key] = value
|
299
|
+
else
|
300
|
+
if field.indexOf(".") != -1
|
301
|
+
splited = field.split '.'
|
302
|
+
field = splited.shift()
|
303
|
+
if @__resource
|
304
|
+
name = @resourceName || @__resource.__entityName
|
305
|
+
field = name + "[#{field}]"
|
306
|
+
field += "[#{f}]" for f in splited
|
307
|
+
|
308
|
+
else if @__resource
|
309
|
+
name = @resourceName || @__resource.__entityName
|
310
|
+
field = name + "[#{field}]"
|
311
|
+
|
312
|
+
result[field] = notifications
|
313
|
+
|
314
|
+
result
|
315
|
+
|
316
|
+
#
|
317
|
+
# Flattens complex inline structures into form notation
|
318
|
+
#
|
319
|
+
# @example Basic flattening
|
320
|
+
# data = foo: { bar: { baz: [] } }
|
321
|
+
# inner = @__foldInlineEntities(data.foo, 'foo')
|
322
|
+
#
|
323
|
+
# inner # { "foo[bar][baz]": [] }
|
324
|
+
#
|
325
|
+
# @param [Object] hash Structure to fold
|
326
|
+
# @param [String] scope Prefix for resulting scopes
|
327
|
+
# @param [Object] result Context of result for recursion
|
328
|
+
#
|
329
|
+
# @return [Hash]
|
330
|
+
#
|
331
|
+
__foldInlineEntities: (hash, scope="", result={}) ->
|
332
|
+
Object.each hash, (key, value) =>
|
333
|
+
if Object.isObject(value)
|
334
|
+
@__foldInlineEntities(value, "#{scope}[#{key}]", result)
|
335
|
+
else
|
336
|
+
result["#{scope}[#{key}]"] = value
|
337
|
+
|
338
|
+
result
|
@@ -0,0 +1,72 @@
|
|
1
|
+
#= require joosy/core/joosy
|
2
|
+
|
3
|
+
#
|
4
|
+
# Form helper
|
5
|
+
#
|
6
|
+
Joosy.helpers 'Application', ->
|
7
|
+
|
8
|
+
description = (resource, method, extendIds) ->
|
9
|
+
if resource instanceof Joosy.Resource.Generic
|
10
|
+
id = resource.id()
|
11
|
+
resource = resource.__entityName
|
12
|
+
|
13
|
+
name: resource + "#{if method.match(/^\[.*\]$/) then method else "[#{method}]"}"
|
14
|
+
id: resource + (if id && extendIds then '_'+id else '') + "_#{method.parameterize().underscore()}"
|
15
|
+
|
16
|
+
input = (type, resource, method, options={}) =>
|
17
|
+
d = description(resource, method, options.extendIds)
|
18
|
+
delete options.extendIds
|
19
|
+
@tag 'input', Joosy.Module.merge {type: type, name: d.name, id: d.id}, options
|
20
|
+
|
21
|
+
class Form
|
22
|
+
constructor: (@context, @resource, @options) ->
|
23
|
+
label: (method, options={}, content='') -> @context.label(@resource, method, Joosy.Module.merge(extendIds: @options.extendIds, options), content)
|
24
|
+
radioButton: (method, tagValue, options={}) -> @context.radioButton(@resource, method, tagValue, Joosy.Module.merge(extendIds: @options.extendIds, options))
|
25
|
+
textArea: (method, options={}) -> @context.textArea(@resource, method, Joosy.Module.merge(extendIds: @options.extendIds, options))
|
26
|
+
checkBox: (method, options={}, checkedValue=1, uncheckedValue=0) ->
|
27
|
+
@context.checkBox(@resource, method, Joosy.Module.merge(extendIds: @options.extendIds, options), checkedValue, uncheckedValue)
|
28
|
+
|
29
|
+
['text', 'file', 'hidden', 'password'].each (type) =>
|
30
|
+
Form.prototype[type+'Field'] = (method, options={}) ->
|
31
|
+
@context[type+'Field'] @resource, method, Joosy.Module.merge(extendIds: @options.extendIds, options)
|
32
|
+
|
33
|
+
@formFor = (resource, options={}, block) ->
|
34
|
+
if Object.isFunction(options)
|
35
|
+
block = options
|
36
|
+
options = {}
|
37
|
+
|
38
|
+
uuid = Joosy.uuid()
|
39
|
+
form = @tag 'form', Joosy.Module.merge(options.html || {}, id: uuid), block?.call(this, new Form(this, resource, options))
|
40
|
+
|
41
|
+
@onRefresh? -> Joosy.Form.attach '#'+uuid, Joosy.Module.merge(options, resource: resource)
|
42
|
+
|
43
|
+
form
|
44
|
+
|
45
|
+
@label = (resource, method, options={}, content='') ->
|
46
|
+
if !Object.isObject(options)
|
47
|
+
content = options
|
48
|
+
options = {}
|
49
|
+
|
50
|
+
d = description(resource, method, options.extendIds)
|
51
|
+
delete options.extendIds
|
52
|
+
|
53
|
+
@tag 'label', Joosy.Module.merge(options, for: d.id), content
|
54
|
+
|
55
|
+
['text', 'file', 'hidden', 'password'].each (type) =>
|
56
|
+
@[type+'Field'] = (resource, method, options={}) -> input type, resource, method, options
|
57
|
+
|
58
|
+
@radioButton = (resource, method, tagValue, options={}) -> input 'radio', resource, method, Joosy.Module.merge(value: tagValue, options)
|
59
|
+
|
60
|
+
@checkBox = (resource, method, options={}, checkedValue=1, uncheckedValue=0) ->
|
61
|
+
spy = input 'hidden', resource, method, value: uncheckedValue, extendIds: options.extendIds
|
62
|
+
box = input 'checkbox', resource, method, Joosy.Module.merge(value: checkedValue, options)
|
63
|
+
|
64
|
+
spy+box
|
65
|
+
|
66
|
+
@textArea = (resource, method, options={}) ->
|
67
|
+
value = options.value
|
68
|
+
extendIds = options.extendIds
|
69
|
+
delete options.value
|
70
|
+
delete options.extendIds
|
71
|
+
|
72
|
+
@tag 'textarea', Joosy.Module.merge(description(resource, method, extendIds), options), value
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#= require joosy/core/joosy
|
2
|
+
|
3
|
+
#
|
4
|
+
# Rendering and string representation helpers
|
5
|
+
#
|
6
|
+
Joosy.helpers 'Application', ->
|
7
|
+
|
8
|
+
@tag = (name, options={}, content='') ->
|
9
|
+
content = content() if Object.isFunction(content)
|
10
|
+
|
11
|
+
element = document.createElement name
|
12
|
+
temp = document.createElement 'div'
|
13
|
+
|
14
|
+
Object.each options, (name, value) -> element.setAttribute name, value
|
15
|
+
element.innerHTML = content
|
16
|
+
|
17
|
+
temp.appendChild element
|
18
|
+
temp.innerHTML
|
19
|
+
|
20
|
+
#
|
21
|
+
# Converts \n into <br/> in your text
|
22
|
+
#
|
23
|
+
# @param [String] text Text to convert
|
24
|
+
#
|
25
|
+
@nl2br = (text) ->
|
26
|
+
text.toString().replace /\n/g, '<br/>'
|
27
|
+
|
28
|
+
#
|
29
|
+
# Wraps the inline block into given template
|
30
|
+
# Request template will receive the inline block as @yield parameter
|
31
|
+
#
|
32
|
+
# Example
|
33
|
+
# -# foo/baz template
|
34
|
+
# != @renderWrapped 'foo/bar', ->
|
35
|
+
# %b This string will be passed to 'foo/bar'
|
36
|
+
#
|
37
|
+
# -# foo/bar template
|
38
|
+
# %h1 I'm the wrapper here!
|
39
|
+
# != @yield
|
40
|
+
#
|
41
|
+
@renderWrapped = (template, lambda) ->
|
42
|
+
@render template, Joosy.Module.merge(this, yield: lambda())
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#= require joosy/core/joosy
|
2
|
+
|
3
|
+
#
|
4
|
+
# Widgets manipulation
|
5
|
+
#
|
6
|
+
Joosy.helpers 'Application', ->
|
7
|
+
|
8
|
+
@widget = (element, widget) ->
|
9
|
+
uuid = Joosy.uuid()
|
10
|
+
element = @tag element, id: uuid
|
11
|
+
|
12
|
+
@onRefresh -> @registerWidget '#'+uuid, widget
|
13
|
+
|
14
|
+
element
|
@@ -0,0 +1,184 @@
|
|
1
|
+
#
|
2
|
+
# All the tiny core stuff
|
3
|
+
#
|
4
|
+
# @mixin
|
5
|
+
#
|
6
|
+
@Joosy =
|
7
|
+
#
|
8
|
+
# Core modules container
|
9
|
+
#
|
10
|
+
Modules: {}
|
11
|
+
|
12
|
+
#
|
13
|
+
# Resources container
|
14
|
+
#
|
15
|
+
Resource: {}
|
16
|
+
|
17
|
+
#
|
18
|
+
# Templaters container
|
19
|
+
#
|
20
|
+
Templaters: {}
|
21
|
+
|
22
|
+
#
|
23
|
+
# If enabled Joosy will log to console a lot of 'in progress' stuff
|
24
|
+
#
|
25
|
+
debug: false
|
26
|
+
|
27
|
+
#
|
28
|
+
# Registeres anything inside specified namespace (objects chain starting from `window`)
|
29
|
+
#
|
30
|
+
# @example Basic usage
|
31
|
+
# Joosy.namespace 'foobar', ->
|
32
|
+
# class @RealThing
|
33
|
+
#
|
34
|
+
# foo = new foobar.RealThing
|
35
|
+
#
|
36
|
+
# @param [String] name Namespace keyword
|
37
|
+
# @param [Boolean] generator Namespace content
|
38
|
+
#
|
39
|
+
namespace: (name, generator=false) ->
|
40
|
+
name = name.split '.'
|
41
|
+
space = window
|
42
|
+
for part in name
|
43
|
+
space = space[part] ?= {}
|
44
|
+
|
45
|
+
if generator
|
46
|
+
generator = generator.apply space
|
47
|
+
for key, klass of space
|
48
|
+
if space.hasOwnProperty(key) &&
|
49
|
+
Joosy.Module.hasAncestor klass, Joosy.Module
|
50
|
+
klass.__namespace__ = name
|
51
|
+
|
52
|
+
#
|
53
|
+
# Registeres given methods as a helpers inside a given set
|
54
|
+
#
|
55
|
+
# @param [String] name Helpers set keyword
|
56
|
+
# @param [Boolean] generator Helpers content
|
57
|
+
#
|
58
|
+
helpers: (name, generator) ->
|
59
|
+
Joosy.namespace "Joosy.Helpers.#{name}", generator
|
60
|
+
|
61
|
+
#
|
62
|
+
# Scary `hello world`
|
63
|
+
#
|
64
|
+
test: ->
|
65
|
+
text = "Hi :). I'm Joosy. And everything is just fine!"
|
66
|
+
|
67
|
+
if console
|
68
|
+
console.log text
|
69
|
+
else
|
70
|
+
alert text
|
71
|
+
|
72
|
+
#
|
73
|
+
# Runs set of callbacks finializing with result callback
|
74
|
+
#
|
75
|
+
# @example Basic usage
|
76
|
+
# Joosy.synchronize (context) ->
|
77
|
+
# contet.do (done) -> done()
|
78
|
+
# contet.do (done) -> done()
|
79
|
+
# content.after ->
|
80
|
+
# console.log 'Success!'
|
81
|
+
#
|
82
|
+
# @param [Function] block Configuration block (see example)
|
83
|
+
#
|
84
|
+
synchronize: ->
|
85
|
+
Joosy.Modules.Events.synchronize arguments...
|
86
|
+
|
87
|
+
#
|
88
|
+
# Generates UUID
|
89
|
+
#
|
90
|
+
uuid: ->
|
91
|
+
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, (c) ->
|
92
|
+
r = Math.random() * 16 | 0
|
93
|
+
v = if c is 'x' then r else r & 3 | 8
|
94
|
+
v.toString 16
|
95
|
+
.toUpperCase()
|
96
|
+
|
97
|
+
#
|
98
|
+
# Preloads sets of images then runs callback
|
99
|
+
#
|
100
|
+
# @param [Array<String>] images Images paths
|
101
|
+
# @param [Function] callback Action to run when every picture was loaded (or triggered an error)
|
102
|
+
#
|
103
|
+
preloadImages: (images, callback) ->
|
104
|
+
unless Object.isArray(images)
|
105
|
+
images = [images]
|
106
|
+
if images.length == 0
|
107
|
+
callback()
|
108
|
+
|
109
|
+
ticks = images.length
|
110
|
+
result = []
|
111
|
+
checker = ->
|
112
|
+
if (ticks -= 1) == 0
|
113
|
+
callback?()
|
114
|
+
|
115
|
+
for p in images
|
116
|
+
result.push $('<img/>').load(checker).error(checker).attr('src', p)
|
117
|
+
|
118
|
+
result
|
119
|
+
|
120
|
+
#
|
121
|
+
# Basic URI builder. Joins base path with params hash
|
122
|
+
#
|
123
|
+
# @param [String] url Base url
|
124
|
+
# @param [Hash] params Parameters to join
|
125
|
+
#
|
126
|
+
# @example Basic usage
|
127
|
+
# Joosy.buildUrl 'http://joosy.ws/#!/test', {foo: 'bar'} # http://joosy.ws/?foo=bar#!/test
|
128
|
+
#
|
129
|
+
buildUrl: (url, params) ->
|
130
|
+
paramsString = []
|
131
|
+
|
132
|
+
Object.each params, (key, value) ->
|
133
|
+
paramsString.push "#{key}=#{value}"
|
134
|
+
|
135
|
+
hash = url.match(/(\#.*)?$/)[0]
|
136
|
+
url = url.replace /\#.*$/, ''
|
137
|
+
if !paramsString.isEmpty() && !url.has(/\?/)
|
138
|
+
url = url + "?"
|
139
|
+
|
140
|
+
paramsString = paramsString.join '&'
|
141
|
+
if !paramsString.isBlank() && url.last() != '?'
|
142
|
+
paramsString = '&' + paramsString
|
143
|
+
|
144
|
+
url + paramsString + hash
|
145
|
+
|
146
|
+
#
|
147
|
+
# Creates classes and collection classes for the given resources that might have been extracted from the routes
|
148
|
+
#
|
149
|
+
# @param [Hash] resources Resources declaration
|
150
|
+
#
|
151
|
+
# @example Basic usage
|
152
|
+
# Joosy.defineResources {'': {foo: '/foos'}, 'namespace': {bar: '/namespace/bars'}}
|
153
|
+
#
|
154
|
+
defineResources: (resources) ->
|
155
|
+
Object.extended(resources).each (namespace, resources) ->
|
156
|
+
if namespace.isBlank()
|
157
|
+
Object.extended(resources).each (resource, path) ->
|
158
|
+
Joosy.defineResource resource, path
|
159
|
+
else
|
160
|
+
Joosy.namespace namespace, ->
|
161
|
+
Object.extended(resources).each (resource, path) =>
|
162
|
+
Joosy.defineResource resource, path, @
|
163
|
+
|
164
|
+
#
|
165
|
+
#
|
166
|
+
# @param [String] resource Entity name in singular form
|
167
|
+
# @param [String] path Entity REST end-point
|
168
|
+
# @param [Object] space Namespace for new classes
|
169
|
+
#
|
170
|
+
# @example Basic usage
|
171
|
+
# Joosy.defineResource 'foo', '/foos'
|
172
|
+
#
|
173
|
+
defineResource: (resource, path, space=window) ->
|
174
|
+
className = resource.camelize()
|
175
|
+
collectionName = "#{resource.pluralize().camelize()}Collection"
|
176
|
+
unless space[className]
|
177
|
+
Joosy.Modules.Log.debugAs space, "Define #{className}"
|
178
|
+
space[className] = class extends Joosy.Resource.REST
|
179
|
+
@entity resource
|
180
|
+
@source path
|
181
|
+
unless space[collectionName]
|
182
|
+
Joosy.Modules.Log.debugAs space, "Define #{collectionName}"
|
183
|
+
space[collectionName] = class extends Joosy.Resource.RESTCollection
|
184
|
+
@model space[className]
|