Skip to content

Commit

Permalink
Update mozaic with new changes. #87
Browse files Browse the repository at this point in the history
  • Loading branch information
andreip committed May 2, 2013
1 parent d4baa24 commit c2a0e72
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 49 deletions.
20 changes: 13 additions & 7 deletions core/api_mock.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ define ['cs!tests/factories/master_factory'], (MasterFactory) ->
###
result = {}
for resource, params of resources
response = @getMockedApiResponse(resource, params)
@mockResource(resource, response)
if params.response
response = params.response
else
response = @getMockedApiResponse(resource, params)
@mockResource(resource, response, params)

# Depending on the channel type (relational or api), populate
# the response object the same way as the objects would be
Expand All @@ -23,11 +26,14 @@ define ['cs!tests/factories/master_factory'], (MasterFactory) ->
result[resource] = response
return result

mockResource: (resource, response) ->
$.mockjax
url: @getResourceRegExp(resource)
response: ->
@responseText = response
mockResource: (resource, response, params = {}) ->
$.mockjax(
_.extend({}, params,
url: @getResourceRegExp(resource)
response: ->
@responseText = response
)
)

getResourceRegExp: (resource) ->
endpoint = "#{App.general.FRONTAPI_URL}/.*/#{resource}/([^a-z]|$)"
Expand Down
9 changes: 6 additions & 3 deletions core/application_controller.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ define ['cs!layout', 'cs!mozaic_module'], (Layout, Module) ->
new_controller_config.controller)

new_controller: (new_controller_config, url_params) =>
#before doing any loading, intercept uncaught errors
window.onerror = (errorMsg, url, lineNumber) ->
logger.error "#{url}, line ##{lineNumber}, error: #{errorMsg}"
# Catch all errors that weren't caught locally by module wrappers
# and reached the global scope, but not in development environments
# where exceptions are left uncaught on purpose
unless App.general.PASS_THROUGH_EXCEPTIONS
window.onerror = (errorMsg, url, lineNumber) ->
logger.error "#{url}, line ##{lineNumber}, error: #{errorMsg}"

loader.load_modules ['cs!pubsub'], =>
loader.load_modules ['cs!widget_starter'], =>
Expand Down
42 changes: 35 additions & 7 deletions core/base_widgets/base_form.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ define ['cs!widget', 'cs!channels_utils'], (Widget, channels_utils) ->

class BaseForm extends Widget

ENTER = 13 # Cross Browser character code for `Enter` key.

###
Execute the afterCommit event in your child classes
Render only executes if the model is new and doesn't
Expand All @@ -28,6 +30,7 @@ define ['cs!widget', 'cs!channels_utils'], (Widget, channels_utils) ->
"click .commit": "commit"
"click .cancel": "cancel"
"click .toggle": "toggle"
'keypress': 'onKeypress'

initialize: ->
# Only start the setup process after model module has
Expand Down Expand Up @@ -78,10 +81,17 @@ define ['cs!widget', 'cs!channels_utils'], (Widget, channels_utils) ->
Also setup aggregated channels for additional data that
is required in the form rendering process.
###
if @isNew()
@subscribed_channels = [channels_utils.formChannel(@channel_key)]
else
@subscribed_channels = [channels_utils.formChannel(@channel_key, @model_id)]
# Initialize subscribed_channels as array, if not already.
@subscribed_channels = [] if not @subscribed_channels
# Add an additional channel to subscribed channels, the one
# the form is going to do CRUD events on it.
@subscribed_channels = _.union(
@subscribed_channels,
if @isNew()
[channels_utils.formChannel(@channel_key)]
else
[channels_utils.formChannel(@channel_key, @model_id)]
)
# Setup subscribed channel handler (like /folders for a folder form)
@["#{channels_utils.widgetMethodForChannel(@, @channel_key)}"] = @get_subscribed_channel_events
# Setup aggregated channel handler for all required data for the form
Expand Down Expand Up @@ -320,12 +330,13 @@ define ['cs!widget', 'cs!channels_utils'], (Widget, channels_utils) ->
for el in @formHTMLElements()
el.prop('disabled', false).removeClass('disabled')

destroyForm: ->
destroyForm: (force_close=false) ->
###
Dispose this form by deleting this widget.
# TODO: dispose the form attrribute in destroyForm
###
if @destroy_after_commit? and @destroy_after_commit
if force_close or
(@destroy_after_commit? and @destroy_after_commit)
Utils.closeModal()

cancel: (event) ->
Expand Down Expand Up @@ -384,4 +395,21 @@ define ['cs!widget', 'cs!channels_utils'], (Widget, channels_utils) ->
@form.fields[name].$el.parent().toggle('fast')
false

return BaseForm
onKeypress: (event) =>
###
This method will trigger commit when the user hits `enter`.
To achive this, we listen for all `keypress` events, filter
only `Enters` and make sure the focus is not on a textarea,
as this will result in a horrible experience.
@param {Object} event - instance of jQuery.Event
###
return unless event.which is ENTER

$elem = ($ document.activeElement)
return if ($elem.is 'textarea') or
$elem.attr('contenteditable') is 'true'

event.stopPropagation()
event.preventDefault()

@commit()
2 changes: 2 additions & 0 deletions core/base_widgets/delete_form.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ define ['cs!widget/base_form'], (BaseForm) ->
###
params =
message: if @params.message? then @params.message else @message
yes_text: @params.yes_text or 'Yes'
no_text: @params.no_text or 'No'
model: @model.toJSON()
@renderLayout(params, @render_stringified)

Expand Down
15 changes: 15 additions & 0 deletions core/core_modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,25 @@ App.main_modules = {
'layout': 'core/layout',
'scrollable_widget': 'core/scrollable_widget',
'widget': 'core/widget/widget',
'core/widget/aggregated_channels': 'core/widget/aggregated_channels',
'core/widget/backbone_events': 'core/widget/backbone_events',
'core/widget/channels': 'core/widget/channels',
'core/widget/params': 'core/widget/params',
'core/widget/rendering': 'core/widget/rendering',
'core/widget/states': 'core/widget/states',
'profiler': 'core/profiler',
'widget_starter': 'core/widget_starter',
'channels_utils': 'core/channels_utils',
'datasource': 'core/datasource/datasource',
'core/datasource/channel': 'core/datasource/channel',
'core/datasource/channel/read':'core/datasource/channel/read',
'core/datasource/channel/create': 'core/datasource/channel/create',
'core/datasource/channel/update': 'core/datasource/channel/update',
'core/datasource/channel/destroy': 'core/datasource/channel/destroy',
'core/datasource/refresher': 'core/datasource/refresher',
'core/datasource/scheduler': 'core/datasource/scheduler',
'core/datasource/gc': 'core/datasource/gc',
'core/datasource/widget': 'core/datasource/widget',
'pubsub': 'core/pubsub',
'router': 'core/router',
'controller': 'core/controller',
Expand Down
2 changes: 2 additions & 0 deletions core/mocks.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ define ['cs!api_mock'], (ApiMock) ->
is_api: true
}]
)
# Mock stream refresh w/out any factory around it
ApiMock.mockResource('keywords/[0-9]+/refresh', {})
33 changes: 28 additions & 5 deletions core/module.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ define [], () ->
###
Base Mozaic module
Its only current function is to wrap all of its methods
Wraps all of its methods
into an anonymous function that catches all thrown
exceptions. The caught execptions are handled in
conformity with the application settings. @see #_wrapMethod
Allows child classes to add mixins to their prototypes, using
the `@includeMixin()` static method.
Allows for easy inheritance of class properties using
`@extendHash()` static method.
Note: it cannot be defined as `module` because
require.js already has an internal module with that
name.
Expand Down Expand Up @@ -41,17 +47,17 @@ define [], () ->
# been wrapped
if _.isFunction(member) and not member.__wrapped__
@[key] = @_wrapMethod(this, member)
# Mark wrapper method
@[key].__wrapped__ = true

_wrapMethod: (instance, method) =>
###
Pass own instance reference instead of using the
fat arrow to prevent from creating two new functions
with every wrapped one.
###
# Mark method as wrapped
method.__wrapped__ = true
return () ->
if App.general.THROW_UNCAUGHT_EXCEPTIONS
if App.general.PASS_THROUGH_EXCEPTIONS
# Let any possible uncaught exception run its course
result = method.apply(instance, arguments)
else
Expand Down Expand Up @@ -81,4 +87,21 @@ define [], () ->

# This one is for Ovidiu, so that we don't do return MyClass
# for classes with mixins enabled :)
return this
return this


@extendProperty: (property, extensions) ->
###
Defines a hash of values that will extend those of the
prototype property with the same name. If the property is not
defined, then this method will set it, so you can use it to
set prototype properties as well.
@param {String} property - the name of the prototype property
to create/extend. Ex: subscribed_channels, events, etc.
@param {Object} extensions - the values to be appended to the
existing prototype property.
Note! Currently only one-level deep extends are supported.
###
@::[property] = _.extend {}, @::[property], extensions
31 changes: 23 additions & 8 deletions core/utils.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ define ['cs!utils/urls', 'cs!utils/time', 'cs!utils/dom', 'cs!utils/images', 'cs
if !$.isFunction(Module)
throw "Trying to instantiate something uninstantiable: " + Module
return
if App.general.THROW_UNCAUGHT_EXCEPTIONS
if App.general.PASS_THROUGH_EXCEPTIONS
result = new Module(params...)
else
try
Expand Down Expand Up @@ -487,18 +487,33 @@ define ['cs!utils/urls', 'cs!utils/time', 'cs!utils/dom', 'cs!utils/images', 'cs
specified object, preserving its scope (which means it works
even for methods without a fat arrow)
###
obj[methodName] = _.wrap(obj[methodName], (fn, args...) ->
originalMethod = obj[methodName]

# Replace the original method with a wrapper function that
# intercepts any calls to the original one and passes them on,
# while honoring the given before/after callbacks
obj[methodName] = ->
# Send the intercepted arguments to the callbacks as well, just
# in case we might want to use that information
options.before(args...) if _.isFunction(options.before)
returnValue = fn.apply(obj, args)
options.after(args...) if _.isFunction(options.after)
options.before(arguments...) if _.isFunction(options.before)
returnValue = originalMethod.apply(obj, arguments)
options.after(arguments...) if _.isFunction(options.after)

# Restore original method automatically after first call
# (it it hasn't been already restored by hand)
if options.restore and _.isFunction(obj[methodName].restore)
obj[methodName].restore()

# Restore orignal method automatically after first call
obj[methodName] = fn if options.restore
# Make sure we preserve the return value
return returnValue
)

# Create a restore method attached to the wrapped method that
# can be called from anywhere, at any time
obj[methodName].restore = ->
obj[methodName] = originalMethod

# Return the wrapper method
return obj[methodName]

_buildDomElementByWidgetOptions: (options) ->
###
Expand Down
28 changes: 28 additions & 0 deletions core/widget/widget.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ define ['cs!mozaic_module', 'cs!core/widget/aggregated_channels', 'cs!core/widge
# translate the parameters from Backbone.Collections format to ours.
@_setupEventParamsTranslation()

# Hook to events from child widgets
@_setupChildEvents()

# Publish to the datasource that there is a new widget which
# is interested in certain data channels
@announceNewWidget()
Expand Down Expand Up @@ -172,6 +175,24 @@ define ['cs!mozaic_module', 'cs!core/widget/aggregated_channels', 'cs!core/widge
# Trigger initial data state
@changeState(@initial_state)

_setupChildEvents: ->
###
Hook to events from child widgets. We're currently only
listening to render events from child widgets, but more could
be implemented in the future.
TODO: Big refactoring, make all widgets run around a
pubsub channel of their own and have this sort of widget events
run on their respecive channel only, with the option to bubble
up to parent widgets up to the top widgets of the app
###
# Only add an event listener if a handler function
# for it appears to be defined
return unless typeof(@onChildRender) is 'function'
# Save handler method reference so it can be removed from pubsub
# when trying to GC widget
@_onChildRender = Utils.onWidgetRender(null, this, @onChildRender)

initialize: ->

render: ->
Expand All @@ -198,6 +219,13 @@ define ['cs!mozaic_module', 'cs!core/widget/aggregated_channels', 'cs!core/widge
name: @params['name']
widget: this})

# Remove the on child render event listener, if previously set
# TODO: There should be a more elegant way to handle more events
# of this type and unsubscribing them all at once through some
# sort of system (rather than addressing them individually)
if _.isFunction(@_onChildRender)
pipe.unsubscribe('/new_widget_rendered', @_onChildRender)

startBeingDetached: =>
###
Mark the fact that the widget is currently detached from DOM.
Expand Down
Loading

0 comments on commit c2a0e72

Please sign in to comment.