app/assets/javascripts/luca/components/collection_view.coffee in luca-0.9.8 vs app/assets/javascripts/luca/components/collection_view.coffee in luca-0.9.9

- old
+ new

@@ -1,11 +1,53 @@ -# The CollectionView handles rendering a set of models from a -# collection -collectionView = Luca.register "Luca.components.CollectionView" +# The `Luca.CollectionView` renders models from a `Luca.Collection` into multiple +# elements, and provides methods for filtering, paginating, sorting the underlying +# collection and re-rendering the contents of its `@el` accordingly. +# +# #### Basic Example +# collectionView = Luca.register "App.views.Books" +# collectionView.extends "Luca.CollectionView" +# +# collectionView.defines +# itemProperty: "author" +# collection: new Luca.Collection([ +# author: "George Orwell" +# title: "Animal Farm" +# , +# author: "Noam Chomsky" +# title: "Manufacturing Consent" +# ]) +# +# view = new App.views.Books() +# #### Extending it to make it Filterable and Paginatable +# filterable = Luca.register "App.views.FilterableBooks" +# filterable.extends "App.views.Books" +# filterable.defines +# collection: "books" +# paginatable: 12 +# filterable: +# query: +# author: "George Orwell" +# +# view = new App.views.FilterableBooks() +# #### Filterable Collections +# +# The `Luca.CollectionView` will attempt to perform a local query against its +# collection which behaves like a `Backbone.QueryCollection`. It will do this +# by default without making a remote request to the API. +# +# If you do not want this behavior, you can configure the `Luca.CollectionView` to +# behave as if the filtering was happen remotely in your REST API. +# +# filterable: +# options: +# remote: true +collectionView = Luca.register "Luca.CollectionView" collectionView.extends "Luca.Panel" +collectionView.replaces "Luca.components.CollectionView" + collectionView.mixesIn "QueryCollectionBindings", "LoadMaskable", "Filterable", "Paginatable", "Sortable" @@ -13,40 +55,50 @@ collectionView.triggers "before:refresh", "after:refresh", "refresh", "empty:results" -# IDEA: -# -# For validation of component configuration, -# we could define a convention like: -# -# collectionView.validatesConfigurationWith -# requiresValidCollectionAt: "collection" -# requiresPresenceOf: -# either: ["itemTemplate", "itemRenderer", "itemProperty"] -# -# collectionView.publicConfiguration + # Specify which collection will be used to supply the models to be rendered. + # Accepts either a string alias for the Collection class, or an instance of + # any class which inherits from Backbone.Collection + collection: undefined + + # By default the CollectionView will be rendered inside of an OL tag. tagName: "ol" + + # The CollectionView behaves as a Luca.Panel which means it has an area for + # top and bottom toolbars. The actual content that gets rendered from the + # collection will be rendered inside an element with the specified class. bodyClassName: "collection-ui-panel" + + # Each item from the collection will be rendered inside of an element specified by @itemTagName itemTagName: 'li' + + # Each item element will be assigned a CSS class specified by @itemClassName itemClassName: 'collection-item' + + # Specify which template should be used to render each item in the collection. + # Accepts a string which will be passed to Luca.template(@itemTemplate). Your template + # can expect to be passed an object with the `model` and `index` properties on it. itemTemplate: undefined + + # Accepts a reference to a function, which will be called with an object with the `model` and `index` + # properties on it. This function should return a String which will be injected into the item DOM element. itemRenderer: undefined + + # Plucks the specified property from the model and inserts it into the item DOM element. itemProperty: undefined -collectionView.defines + # If @observeChanges is set to true, any change in an underlying model will automatically be re-rendered. + observeChanges: false + +collectionView.publicMethods initialize: (@options={})-> _.extend(@, @options) _.bindAll @, "refresh" - # IDEA: - # - # This type of code could be moved into a re-usable concern - # which higher order components can mixin to make it easier - # to extend them, instantiate them, etc. unless @collection? or @options.collection console.log "Error on initialize of collection view", @ throw "Collection Views must specify a collection" unless @itemTemplate? || @itemRenderer? || @itemProperty? @@ -60,13 +112,14 @@ unless Luca.isBackboneCollection(@collection) console.log "Missing Collection on #{ @name || @cid }", @, @collection throw "Collection Views must have a valid backbone collection" + # INVESTIGATE THIS BEING DOUBLE WORK @on "data:refresh", @refresh, @ - @on "collection:reset", @refresh, @ + @on "collection:remove", @refresh, @ @on "collection:add", @refresh, @ @on "collection:change", @refreshModel, @ if @observeChanges is true Luca.Panel::initialize.apply(@, arguments) @@ -75,14 +128,50 @@ if @getCollection()?.length > 0 @on "after:render", ()-> view.refresh() view.unbind "after:render", @ + # Given the id of a model, find the underlying DOM element which was rendered by this collection. + # Assumes that the data-model-id attribute is set, which it is by default by @attributesForItem. + locateItemElement: (id)-> + @$(".#{ @itemClassName }[data-model-id='#{ id }']") + # Refresh is responsible for applying any filtering, pagination, or sorting options that may be set + # from the various Luca.concerns mixed in by `Luca.CollectionView` and making a query to the underlying + # collection. It will then take the set of models returned by `@getModels` and pass them through the + # item rendering pipeline. + refresh: ()-> + query = @getLocalQuery() + options = @getQueryOptions() + models = @getModels(query, options) + + @$bodyEl().empty() + + @trigger("before:refresh", models, query, options) + + if models.length is 0 + @trigger("empty:results", query, options) + + @renderModels(models, query, options) + + @trigger("after:refresh", models, query, options) + + @ + + +collectionView.privateMethods + renderModels: (models, query, options)-> + index = 0 + for model in models + @$append @makeItem(model, index++) + + # Determines which attributes should be set on the item DOM element. attributesForItem: (item, model)-> _.extend {}, class: @itemClassName, "data-index": item.index, "data-model-id": item.model.get('id') + # Determines the content for the item DOM element. Will use the appropriate options + # specified by `@itemTemplate`, `@itemRenderer`, or `@itemProperty` contentForItem: (item={})-> if @itemTemplate? and templateFn = Luca.template(@itemTemplate) return content = templateFn.call(@, item) if @itemRenderer? and _.isFunction( @itemRenderer ) @@ -91,57 +180,32 @@ if @itemProperty and item.model? return content = item.model.read( @itemProperty ) "" + # Uses the various options passed to the `CollectionView` to assemble a call to `Luca.View::make`. makeItem: (model, index)-> item = if @prepareItem? then @prepareItem.call(@, model, index) else (model:model, index: index) attributes = @attributesForItem(item, model) content = @contentForItem(item) - # TEMP - # Figure out why calls to make are failing with an unexpected string error + try - make(@itemTagName, attributes, content) + Luca.View::make(@itemTagName, attributes, content) catch e console.log "Error generating DOM element for CollectionView", @, model, index - #no op - locateItemElement: (id)-> - @$(".#{ @itemClassName }[data-model-id='#{ id }']") - + # Given a model, attempt to re-render the contents of its item in this view's DOM contents. refreshModel: (model)-> index = @collection.indexOf( model ) @locateItemElement(model.get('id')).empty().append( @contentForItem({model,index}, model) ) @trigger("model:refreshed", index, model) - refresh: (query,options,models)-> - query ||= @getQuery() - options ||= @getQueryOptions() - models ||= @getModels(query, options) - @$bodyEl().empty() - - @trigger("before:refresh", models, query, options) - - if models.length is 0 - @trigger("empty:results") - - index = 0 - for model in models - @$append @makeItem(model, index++) - - @trigger("after:refresh", models, query, options) - - @ - registerEvent: (domEvent, selector, handler)-> if !handler? and _.isFunction(selector) handler = selector selector = undefined eventTrigger = _([domEvent,"#{ @itemTagName }.#{ @itemClassName }", selector]).compact().join(" ") Luca.View::registerEvent(eventTrigger,handler) -# Private Helpers - - -make = Luca.View::make +collectionView.register()