Skip to content
Browse files
Update mozaic with new changes. #87
  • Loading branch information
andreip committed May 2, 2013
1 parent d4baa24 commit c2a0e723abfa9abf33ddc3b8575e8bdac4e0b70f
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 49 deletions.
@@ -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
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
@@ -23,11 +26,14 @@ define ['cs!tests/factories/master_factory'], (MasterFactory) ->
result[resource] = response
return result

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

getResourceRegExp: (resource) ->
endpoint = "#{App.general.FRONTAPI_URL}/.*/#{resource}/([^a-z]|$)"
@@ -45,9 +45,12 @@ define ['cs!layout', 'cs!mozaic_module'], (Layout, Module) ->

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
window.onerror = (errorMsg, url, lineNumber) ->
logger.error "#{url}, line ##{lineNumber}, error: #{errorMsg}"

loader.load_modules ['cs!pubsub'], =>
loader.load_modules ['cs!widget_starter'], =>
@@ -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
@@ -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
@@ -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)]
@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(
if @isNew()
[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
@@ -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)

cancel: (event) ->
@@ -384,4 +395,21 @@ define ['cs!widget', 'cs!channels_utils'], (Widget, channels_utils) ->

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 ($ 'textarea') or
$elem.attr('contenteditable') is 'true'


@@ -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)

@@ -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/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',
@@ -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', {})
@@ -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
@@ -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 () ->
# Let any possible uncaught exception run its course
result = method.apply(instance, arguments)
@@ -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
@@ -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
result = new Module(params...)
@@ -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)

# 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) ->
@@ -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.

# Hook to events from child widgets

# Publish to the datasource that there is a new widget which
# is interested in certain data channels
@@ -172,6 +175,24 @@ define ['cs!mozaic_module', 'cs!core/widget/aggregated_channels', 'cs!core/widge
# Trigger initial data 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: ->
@@ -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.

0 comments on commit c2a0e72

Please sign in to comment.