luca 0.9.2 → 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rvmrc +1 -1
- data/CHANGELOG +46 -2
- data/Gemfile +1 -1
- data/Gemfile.lock +2 -2
- data/Guardfile +1 -1
- data/README.md +64 -27
- data/ROADMAP +17 -2
- data/Rakefile +49 -1
- data/app.rb +38 -2
- data/assets/javascripts/luca-ui-base.coffee +1 -20
- data/assets/javascripts/luca-ui-full.js +3 -0
- data/assets/javascripts/luca-ui.coffee +0 -5
- data/assets/javascripts/sandbox/application.coffee +24 -18
- data/assets/javascripts/sandbox/router.coffee +16 -6
- data/assets/javascripts/sandbox/templates/builder/component_list.luca +1 -0
- data/assets/javascripts/sandbox/templates/builder.luca +2 -0
- data/assets/javascripts/sandbox/templates/main.luca +4 -3
- data/assets/javascripts/sandbox/templates/sandbox/docs_index.luca +1 -0
- data/assets/javascripts/sandbox/templates/sandbox/navigation.luca +6 -1
- data/assets/javascripts/sandbox/templates/sandbox/readme.luca +30 -0
- data/assets/javascripts/sandbox/views/builder/builder_canvas.coffee +3 -0
- data/assets/javascripts/sandbox/views/builder/builder_editor.coffee +6 -0
- data/assets/javascripts/sandbox/views/builder/component_list.coffee +38 -0
- data/assets/javascripts/sandbox/views/builder/project_browser.coffee +14 -0
- data/assets/javascripts/sandbox/views/builder.coffee +133 -0
- data/assets/javascripts/sandbox/views/docs_controller.coffee +7 -0
- data/assets/javascripts/sandbox/views/inspector/instance_filter.coffee +18 -0
- data/assets/javascripts/sandbox/{collections/sample.coffee → views/inspector/instance_list.coffee} +0 -0
- data/assets/javascripts/sandbox/views/inspector.coffee +11 -0
- data/assets/javascripts/sandbox.coffee +2 -0
- data/assets/stylesheets/luca-ui-full.css +3 -0
- data/assets/stylesheets/sandbox/builder.scss +79 -0
- data/assets/stylesheets/sandbox/sandbox.scss +2 -1
- data/docs/application.md +41 -0
- data/docs/collection.md +79 -0
- data/docs/collection_manager.md +76 -0
- data/docs/container_philosophy.md +122 -0
- data/docs/event_binding_helpers.md +164 -0
- data/docs/method_caching_and_computed_properties.md +77 -0
- data/docs/view.md +119 -0
- data/lib/luca/rails/version.rb +1 -1
- data/lib/luca/template.rb +9 -9
- data/site/assets/bootstrap.min.js +7 -0
- data/site/assets/luca-ui-bootstrap.css +19 -1
- data/site/assets/luca-ui-development-tools.css +10 -0
- data/site/assets/luca-ui-development-tools.min.js +15 -0
- data/site/assets/luca-ui-full.min.js +8 -0
- data/site/assets/luca-ui.min.js +4 -0
- data/site/assets/sandbox.css +52 -4
- data/site/assets/sandbox.js +368 -30
- data/site/docs/application.html +41 -0
- data/site/docs/caching.html +43 -0
- data/site/docs/collection.html +75 -0
- data/site/docs/collection_manager.html +71 -0
- data/site/docs/containers.html +118 -0
- data/site/docs/events.html +153 -0
- data/site/docs/view.html +128 -0
- data/site/img/glyphicons-halflings-white.png +0 -0
- data/site/img/glyphicons-halflings.png +0 -0
- data/site/source-map.js +1 -0
- data/spec/core/view_spec.coffee +5 -17
- data/spec/managers/collection_manager_spec.coffee +4 -7
- data/src/components/application.coffee +202 -77
- data/src/components/base_toolbar.coffee +1 -1
- data/src/components/collection_view.coffee +38 -10
- data/src/components/controller.coffee +24 -1
- data/src/components/fields/checkbox_field.coffee +9 -12
- data/src/components/fields/label_field.coffee +14 -0
- data/src/components/fields/select_field.coffee +2 -2
- data/src/components/fields/text_field.coffee +12 -7
- data/src/components/fields/type_ahead_field.coffee +1 -0
- data/src/components/form_view.coffee +44 -25
- data/src/components/page_controller.coffee +2 -0
- data/src/containers/card_view.coffee +4 -1
- data/src/containers/column_view.coffee +2 -1
- data/src/containers/modal_view.coffee +6 -2
- data/src/containers/page_view.coffee +2 -0
- data/src/containers/panel_toolbar.coffee +0 -5
- data/src/containers/viewport.coffee +28 -10
- data/src/core/collection.coffee +7 -1
- data/src/core/container.coffee +57 -30
- data/src/core/core.coffee +0 -186
- data/src/core/field.coffee +11 -3
- data/src/core/model.coffee +31 -16
- data/src/core/panel.coffee +6 -46
- data/src/core/registry.coffee +19 -2
- data/src/core/script_loader.coffee +32 -0
- data/src/core/view.coffee +112 -139
- data/src/define.coffee +110 -0
- data/src/framework.coffee +8 -2
- data/src/luca.coffee +22 -0
- data/src/managers/collection_manager.coffee +65 -31
- data/src/modules/load_mask.coffee +47 -0
- data/src/plugins/development_tool_helpers.coffee +21 -0
- data/src/plugins/events.coffee +54 -0
- data/src/stylesheets/components/viewport.scss +15 -0
- data/src/stylesheets/containers/container.scss +1 -4
- data/src/stylesheets/tools/component_tester.scss +18 -0
- data/src/templates/fields/select_field.luca +6 -5
- data/src/templates/fields/text_field.luca +10 -9
- data/src/tools/application_inspector.coffee +2 -0
- data/src/tools/coffee_script_editor.coffee +28 -6
- data/src/tools/collections/components.coffee +59 -0
- data/src/tools/collections/instances.coffee +15 -0
- data/src/tools/component_tester.coffee +12 -22
- data/src/tools/console.coffee +22 -4
- data/src/tools/models/components.coffee +16 -54
- data/src/tools/models/instance.coffee +2 -0
- data/src/{core/util.coffee → util.coffee} +10 -1
- data/vendor/assets/javascripts/luca-ui-base.js +132 -137
- data/vendor/assets/javascripts/luca-ui-development-tools.js +191 -219
- data/vendor/assets/javascripts/luca-ui-development-tools.min.js +2 -2
- data/vendor/assets/javascripts/luca-ui-full.js +4680 -0
- data/vendor/assets/javascripts/luca-ui-full.min.js +8 -0
- data/vendor/assets/javascripts/luca-ui-spec.js +291 -225
- data/vendor/assets/javascripts/luca-ui.js +1001 -724
- data/vendor/assets/javascripts/luca-ui.min.js +4 -4
- data/vendor/assets/stylesheets/luca-ui-bootstrap.css +19 -1
- data/vendor/assets/stylesheets/luca-ui-development-tools.css +10 -0
- data/vendor/assets/stylesheets/luca-ui-full.css +1334 -0
- data/vendor/assets/stylesheets/luca-ui-spec.css +19 -1
- data/vendor/assets/stylesheets/luca-ui.css +19 -1
- data/views/index.erb +2 -5
- metadata +58 -9
- data/lib/sprockets/luca_template.rb +0 -49
- data/src/tools/class_browser.coffee +0 -39
- data/src/tools/components/class_browser_detail.coffee +0 -10
- data/src/tools/components/class_browser_list.coffee +0 -74
@@ -0,0 +1,38 @@
|
|
1
|
+
_.def("Sandbox.views.ComponentList").extends("Luca.components.CollectionView").with
|
2
|
+
name: "component_list"
|
3
|
+
id: "component_list"
|
4
|
+
collection: "components"
|
5
|
+
itemTagName: "div"
|
6
|
+
autoBindEventHandlers: true
|
7
|
+
events:
|
8
|
+
"click div.collection-item a" : "clickHandler"
|
9
|
+
|
10
|
+
itemRenderer: (item, model, index)->
|
11
|
+
Luca.util.make("a",{"data-index":index}, model.className() )
|
12
|
+
|
13
|
+
filterByName: (name)->
|
14
|
+
models = @collection.query
|
15
|
+
className:
|
16
|
+
$likeI:name
|
17
|
+
|
18
|
+
@collection.reset( models, silent: true )
|
19
|
+
@refresh()
|
20
|
+
|
21
|
+
if name?.length is 0
|
22
|
+
@resetToDefault()
|
23
|
+
|
24
|
+
resetToDefault: ()->
|
25
|
+
@collection.reset( @initialComponents, silent: true )
|
26
|
+
@refresh()
|
27
|
+
|
28
|
+
beforeRender: ()->
|
29
|
+
success = (collection, response)=>
|
30
|
+
@initialComponents = response
|
31
|
+
|
32
|
+
@collection.fetch(success: success)
|
33
|
+
|
34
|
+
clickHandler: (e)->
|
35
|
+
e.preventDefault()
|
36
|
+
me = my = $( e.target )
|
37
|
+
component = @collection.at( my.data('index') )
|
38
|
+
@trigger "selected", component
|
@@ -0,0 +1,14 @@
|
|
1
|
+
_.def("Sandbox.views.ProjectBrowser").extends("Luca.core.Container").with
|
2
|
+
className: "project-browser"
|
3
|
+
components:[
|
4
|
+
type: "text_field"
|
5
|
+
name: "component_list_filter"
|
6
|
+
additionalClassNames: "well"
|
7
|
+
className: "component-list-filter-form"
|
8
|
+
placeHolder: "Find a component"
|
9
|
+
hideLabel: true
|
10
|
+
prepend: "?"
|
11
|
+
,
|
12
|
+
type: "component_list"
|
13
|
+
name: "component_list"
|
14
|
+
]
|
@@ -0,0 +1,133 @@
|
|
1
|
+
_.def("Sandbox.views.Builder").extends("Luca.core.Container").with
|
2
|
+
name: "builder"
|
3
|
+
id: "builder"
|
4
|
+
defaultCanvasPosition: 'below'
|
5
|
+
componentEvents:
|
6
|
+
"editor_container toggle:search:option" : "toggleSearchOption"
|
7
|
+
|
8
|
+
components:[
|
9
|
+
ctype: "container"
|
10
|
+
name:"editor_container"
|
11
|
+
additionalClassNames: 'row-fluid'
|
12
|
+
className: "builder-editor-container"
|
13
|
+
styles:
|
14
|
+
position: "absolute"
|
15
|
+
|
16
|
+
bottomToolbar:
|
17
|
+
buttons:[
|
18
|
+
group: true
|
19
|
+
align: "left"
|
20
|
+
buttons:[
|
21
|
+
eventId: "toggle:search:option"
|
22
|
+
icon:"search"
|
23
|
+
classes:"search-options component-search"
|
24
|
+
,
|
25
|
+
eventId: "toggle:search:option"
|
26
|
+
icon: "list-alt"
|
27
|
+
classes: "search-options saved-components"
|
28
|
+
]
|
29
|
+
,
|
30
|
+
eventId:"toggle:settings"
|
31
|
+
icon: "cog"
|
32
|
+
align: 'right'
|
33
|
+
]
|
34
|
+
components:[
|
35
|
+
ctype: "builder_editor"
|
36
|
+
name: "builder_editor"
|
37
|
+
className:"builder-editor"
|
38
|
+
styles:
|
39
|
+
position: "relative"
|
40
|
+
width: "100%"
|
41
|
+
top: "0"
|
42
|
+
left: "0"
|
43
|
+
,
|
44
|
+
type: "project_browser"
|
45
|
+
className:"project-browser"
|
46
|
+
name: "project_browser"
|
47
|
+
styles:
|
48
|
+
position: "relative"
|
49
|
+
width: "30%"
|
50
|
+
top: "0"
|
51
|
+
left: "0"
|
52
|
+
]
|
53
|
+
]
|
54
|
+
|
55
|
+
initialize: (@options={})->
|
56
|
+
Luca.core.Container::initialize.apply(@, arguments)
|
57
|
+
|
58
|
+
_.bindAll @, "toggleSearchOption"
|
59
|
+
|
60
|
+
canvas = type: "builder_canvas", className: "builder-canvas"
|
61
|
+
|
62
|
+
@state = new Backbone.Model
|
63
|
+
canvasLayout: "horizontal-split"
|
64
|
+
canvasPosition: (@defaultCanvasPosition || "above")
|
65
|
+
ratio: 0.4
|
66
|
+
|
67
|
+
@state.bind "change:canvasLayout", ()=>
|
68
|
+
@$el.removeClass().addClass @state.get("canvasLayout")
|
69
|
+
|
70
|
+
if @state.get('canvasPosition') is "above"
|
71
|
+
@components.unshift( canvas )
|
72
|
+
else
|
73
|
+
@components.push( canvas )
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
canvas: ()->
|
78
|
+
Luca("builder_canvas")
|
79
|
+
|
80
|
+
editor: ()->
|
81
|
+
Luca("builder_editor")
|
82
|
+
|
83
|
+
componentList: ()->
|
84
|
+
Luca("component_list")
|
85
|
+
|
86
|
+
toggleSearchOption: (button)->
|
87
|
+
button.toggleClass('active')
|
88
|
+
|
89
|
+
# TODO
|
90
|
+
# Find a pure CSS solution this is garbage.
|
91
|
+
fitToScreen: ()->
|
92
|
+
@$el.addClass("canvas-position-#{ @state.get('canvasPosition') }")
|
93
|
+
|
94
|
+
viewportHeight = $(window).height()
|
95
|
+
half = viewportHeight * @state.get('ratio')
|
96
|
+
|
97
|
+
toolbarHeight = 0
|
98
|
+
toolbarHeight += @$('.toolbar-container.top').height() * @$('.toolbar-container.top').length
|
99
|
+
|
100
|
+
filterHeight = 0
|
101
|
+
filterHeight += @$('.component-list-filter-form').height()
|
102
|
+
|
103
|
+
@canvas().$el.height( half - toolbarHeight - 40 )
|
104
|
+
|
105
|
+
@componentList().$el.height( half - filterHeight - 50 )
|
106
|
+
|
107
|
+
@editor().$el.height( half )
|
108
|
+
@editor().setHeight( half )
|
109
|
+
|
110
|
+
activation: ()->
|
111
|
+
@fitToScreen()
|
112
|
+
|
113
|
+
deactivation: ()->
|
114
|
+
# implement
|
115
|
+
|
116
|
+
afterRender: ()->
|
117
|
+
@_super "afterRender", @, arguments
|
118
|
+
componentList = Luca("component_list")
|
119
|
+
componentList.on "selected", (component)->
|
120
|
+
Luca("builder_editor").setValue( component.get('source') )
|
121
|
+
Luca("builder_editor").state.set('currentMode','coffeescript')
|
122
|
+
|
123
|
+
@$('.component-list-filter-form input[type="text"]').on "keydown", ()->
|
124
|
+
componentList.filterByName $(this).val()
|
125
|
+
|
126
|
+
@$('.component-list-filter-form input[type="text"]').on "keyup", ()->
|
127
|
+
val = $(this).val()
|
128
|
+
componentList.filterByName('') if val.length is 0
|
129
|
+
|
130
|
+
beforeRender: ()->
|
131
|
+
Luca.core.Container::beforeRender?.apply(@, arguments)
|
132
|
+
@$el.removeClass().addClass @state.get("canvasLayout")
|
133
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
_.def("Sandbox.views.InstanceFilter").extends("Luca.components.FormView").with
|
2
|
+
name: "instance_filter"
|
3
|
+
well: true
|
4
|
+
horizontal: true
|
5
|
+
inline: true
|
6
|
+
toolbar: false
|
7
|
+
components:[
|
8
|
+
ctype:"type_ahead_field"
|
9
|
+
label:"Find by name"
|
10
|
+
source: ()->
|
11
|
+
names = _( Luca.registry.instances() ).pluck('name')
|
12
|
+
_.uniq _( names ).compact()
|
13
|
+
,
|
14
|
+
ctype: "type_ahead_field"
|
15
|
+
label: "Find by class"
|
16
|
+
source: ()->
|
17
|
+
Luca.registry.classes(true)
|
18
|
+
]
|
data/assets/javascripts/sandbox/{collections/sample.coffee → views/inspector/instance_list.coffee}
RENAMED
File without changes
|
@@ -0,0 +1,11 @@
|
|
1
|
+
_.def("Sandbox.views.ApplicationInspector").extends("Luca.tools.ApplicationInspector").with
|
2
|
+
name: "application_inspector"
|
3
|
+
additionalClassNames: ["modal"]
|
4
|
+
|
5
|
+
toggle:(options=backdrop:false)->
|
6
|
+
@render() unless @rendered is true
|
7
|
+
@$el.modal(options)
|
8
|
+
|
9
|
+
components:[
|
10
|
+
ctype:"instance_filter"
|
11
|
+
]
|
@@ -0,0 +1,79 @@
|
|
1
|
+
#builder {
|
2
|
+
min-height: 100%;
|
3
|
+
height: auto !important;
|
4
|
+
height: 100%;
|
5
|
+
|
6
|
+
.project-browser-container {
|
7
|
+
margin: 0px;
|
8
|
+
.component-list-filter-form.well {
|
9
|
+
padding: 4px;
|
10
|
+
input {
|
11
|
+
width: 80%;
|
12
|
+
}
|
13
|
+
span.add-on {
|
14
|
+
cursor: pointer;
|
15
|
+
}
|
16
|
+
.control-group {
|
17
|
+
padding: 0px;
|
18
|
+
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
#component_list {
|
23
|
+
overflow: scroll;
|
24
|
+
height:100%;
|
25
|
+
.collection-item {
|
26
|
+
padding: 3px 0px 3px 4px;
|
27
|
+
cursor: pointer;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
.builder-editor-container {
|
33
|
+
.builder-editor {
|
34
|
+
margin-left: 0px;
|
35
|
+
}
|
36
|
+
.CodeMirror {
|
37
|
+
font-size: 1.2em;
|
38
|
+
line-height: 1.5;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
|
43
|
+
.builder-canvas + .builder-editor {
|
44
|
+
position: fixed;
|
45
|
+
bottom: 0;
|
46
|
+
width: 100%;
|
47
|
+
}
|
48
|
+
|
49
|
+
.builder-canvas {
|
50
|
+
padding: 20px;
|
51
|
+
}
|
52
|
+
|
53
|
+
.toolbar-container {
|
54
|
+
padding-right: 12px;
|
55
|
+
|
56
|
+
.luca-ui-toolbar.toolbar-bottom {
|
57
|
+
margin-top: 0px;
|
58
|
+
}
|
59
|
+
.luca-ui-toolbar.toolbar-top {
|
60
|
+
margin-bottom: 0px;
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
#builder.canvas-position-below {
|
66
|
+
.builder-editor {
|
67
|
+
border-bottom: 1px solid #999;
|
68
|
+
-moz-box-shadow: 0px 0px 1px #333;
|
69
|
+
-webkit-box-shadow: 0px 0px 1px #333;
|
70
|
+
box-shadow: 0px 0px 1px #333;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
.project-browser {
|
75
|
+
width: 30%;
|
76
|
+
top: 0px;
|
77
|
+
left: 0px;
|
78
|
+
}
|
79
|
+
|
data/docs/application.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Luca.Application
|
2
|
+
|
3
|
+
A large single-page app generally needs some sort of globally available state tracking object, as well as something which acts as a single entry point into the application, and some sort of gateway to important objects.
|
4
|
+
|
5
|
+
Luca.Application is a type of Viewport class which handles things such as:
|
6
|
+
|
7
|
+
- collection manager ( manages your collections for you )
|
8
|
+
- socket manager ( relays websocket events as Backbone.Events )
|
9
|
+
- url fragment router (`Backbone.Router`)
|
10
|
+
- global attributes and change event bindings
|
11
|
+
- page controller ( displays a unique page of the application)
|
12
|
+
- active view, active sub view helpers
|
13
|
+
|
14
|
+
The Luca.Application stores its state in a `Backbone.Model`, which means you can `get()` and `set()` attributes directly on the application, as well as bind to change events on the application itself, and expect the same API you would from a normal model.
|
15
|
+
|
16
|
+
The ability to treat the Luca.Application instance as both a view, and a model allows for some clean patterns. Your views can declaratively list its dependency on the global application state attributes.
|
17
|
+
|
18
|
+
```coffeescript
|
19
|
+
_.def("MyView").extends("Luca.View").with
|
20
|
+
name: "my_view"
|
21
|
+
|
22
|
+
applicationEvents:
|
23
|
+
"change:status" : "onStatusChange"
|
24
|
+
|
25
|
+
onStatusChange: (app, currentStatus)->
|
26
|
+
if currentStatus is "inactive"
|
27
|
+
@markInactive()
|
28
|
+
|
29
|
+
markInactive: ()->
|
30
|
+
# mark this view inactive if the application
|
31
|
+
# goes into inactive status
|
32
|
+
|
33
|
+
|
34
|
+
app = Luca.getApplication()
|
35
|
+
|
36
|
+
# this will cause the view named 'my_view' to
|
37
|
+
# to fire its markInactive() method
|
38
|
+
app.set("status", "inactive")
|
39
|
+
|
40
|
+
|
41
|
+
```
|
data/docs/collection.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# Luca.Collection
|
2
|
+
|
3
|
+
The `Luca.Collection` class is the base class for Luca components. A number of patterns and optimizations that are helpful in your collection classes have been extracted into the base class.
|
4
|
+
|
5
|
+
## Backbone Query Integration
|
6
|
+
|
7
|
+
Luca.Collection either extends from `Backbone.Collection`, or if it is available `Backbone.QueryCollection`. The Query Collection was developed by [Dave Tonge](https://github.com/davidgtonge) and the project can be found on [Github](https://github.com/datapimp/backbone_query).
|
8
|
+
|
9
|
+
`Luca.Collections` which extend from `Backbone.QueryCollection` will have a query method which provides you with an API for filtering your collection's models with an API similar to [MongoDB](http://www.mongodb.org/display/DOCS/Advanced+Queries)
|
10
|
+
|
11
|
+
## Bootstrapping your models on page load for performance
|
12
|
+
|
13
|
+
A good habit for any single page application is to not populate all of your collections via remote calls to your RESTful API. In a lot of cases it is better to put the data that needs to end up in your collections into the initial page load.
|
14
|
+
|
15
|
+
Luca.Collection classes optimize for this pattern through the bootstrap functionality that is baked into the component.
|
16
|
+
|
17
|
+
The bootstrap configuration for `Luca.Collection` classes depends on the collection being defined with a `@cache_key` property. `@cache_key` is either a function which returns a string, or a string, for simple cases.
|
18
|
+
|
19
|
+
To make an array of objects available as models for a collection, either store the objects in `Luca.Collection._bootstrapped_models` on a property matching the value of `@cache_key` or use the `Luca.Collection.cache()` method like such:
|
20
|
+
|
21
|
+
```html
|
22
|
+
<body>
|
23
|
+
<script type="text/javascript">
|
24
|
+
Luca.Collection.cache("books",[{author:"Jonathan Soeder"}]
|
25
|
+
</script>
|
26
|
+
```
|
27
|
+
|
28
|
+
This will work with the following collection:
|
29
|
+
|
30
|
+
```coffeescript
|
31
|
+
_.def("BooksCollection").extends("Luca.Collection").with
|
32
|
+
name:"books"
|
33
|
+
cache_key: "books"
|
34
|
+
```
|
35
|
+
|
36
|
+
Any calls to `(new BooksCollection()).fetch()` will look in the cached models first, and avoid an API call.
|
37
|
+
|
38
|
+
If you want to refresh the BooksCollection from your API, just pass in an options hash like such:
|
39
|
+
|
40
|
+
```coffeescript
|
41
|
+
booksCollection.fetch(refresh:true)
|
42
|
+
```
|
43
|
+
|
44
|
+
## Base Params for RESTful API
|
45
|
+
|
46
|
+
There are a lot of cases where you need every API call to pass along the same parameters ( authentication_tokens, keys, etc )
|
47
|
+
|
48
|
+
`Luca.Collection` will wrap the `@url` property or method with a function which appends the base parameters as an HTTP Query Parameter string.
|
49
|
+
|
50
|
+
```coffeescript
|
51
|
+
_.def("BooksCollection").extends("Luca.Collection").with
|
52
|
+
name: "books"
|
53
|
+
url: "/api/v1/books"
|
54
|
+
|
55
|
+
app = Luca.getApplication()
|
56
|
+
|
57
|
+
app.on "authenticated", ()->
|
58
|
+
Luca.Collection.baseParams =
|
59
|
+
auth_token: app.get("authentication_token")
|
60
|
+
|
61
|
+
app.collection("books").url() # => "/api/v1/books?auth_token=123456"
|
62
|
+
|
63
|
+
```
|
64
|
+
|
65
|
+
## onceLoaded and ifLoaded helpers
|
66
|
+
|
67
|
+
There are cases where you want to do something on a collection if it has data, but if it doesn't, you need to fetch that data, and bind a callback to the reset event. This can get tedious.
|
68
|
+
|
69
|
+
Passing a callback to `collection.ifLoaded(callback)` will eliminate the boilerplate in this pattern.
|
70
|
+
|
71
|
+
If you only want the callback to run once, use `collection.onceLoaded()`.
|
72
|
+
|
73
|
+
In either of these methods, if you don't watch to automatically call `@fetch()` on the collection, pass an autoFetch option set to false
|
74
|
+
|
75
|
+
```
|
76
|
+
app.collection("books").ifLoaded ()->
|
77
|
+
@doSomething()
|
78
|
+
, autoFetch: false
|
79
|
+
```
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# The Luca Collection Manager
|
2
|
+
|
3
|
+
The CollectionManager is a single instance which acts as a gateway to
|
4
|
+
the instances of Luca.Collection created in your app. The intention is
|
5
|
+
to provide a central place for creating one, and only one instance of a
|
6
|
+
given collection type.
|
7
|
+
|
8
|
+
You can use CollectionManager independently, or you will get one by default
|
9
|
+
when you use a Luca.Application with the default configuration.
|
10
|
+
|
11
|
+
A CollectionManager has a name property which is 'primary' by default. If
|
12
|
+
you call `Luca.CollectionManager.get()` it will return the CollectionManager
|
13
|
+
named 'primary' or the first one ever created. Attempting to create an additional
|
14
|
+
CollectionManager instance with a name that is already used, will throw an error.
|
15
|
+
|
16
|
+
## Named Collections and Auto-Registering
|
17
|
+
|
18
|
+
You can configure your Luca.Collection classes to have their instances automatically
|
19
|
+
register with the collection manager. By specifying a `@name` property on your collection prototypes, they will automatically attempt to register with the running collection manager instance ( via `Luca.CollectionManager.get()` ) as soon as they are initialized.
|
20
|
+
|
21
|
+
You can specify which manager you want a collection to register with by specifying a `@manager` property on your collection. This can either be a string, which will get resolved when needed to a variable, or a direct reference to the collection manager. The string is useful since, when declaring your Luca.Collection prototypes, the collection manager will most likely not be instantiated.
|
22
|
+
|
23
|
+
```coffeescript
|
24
|
+
_.def("MyCollection").extends("Luca.Collection").with
|
25
|
+
name: "my_collection"
|
26
|
+
manager: "AppInstance.collectionManager"
|
27
|
+
```
|
28
|
+
|
29
|
+
## Private Collections
|
30
|
+
|
31
|
+
You may not always want to use the global, single authoritative instance of a collection. In this case, you can specify a `@private` or `@anonymous` property on your collection, and it will skip registering with the collection manager.
|
32
|
+
|
33
|
+
## Collection Class Naming
|
34
|
+
|
35
|
+
Your custom Luca.Collection classes get named like MyApp.collections.SampleCollection. Through some string magic "SampleCollection" will get turned into "sample_collection". If you try to call collectionManager.getOrCreate("sample_collection") it will attempt to get a collection named "sample_collection", and if it fails, will create a new instance of MyApp.collections.SampleCollection. If you want to force your CollectionManager to look in a specific namespace, set a reference to MyApp.collections on Luca.Collection.namespace, otherwise it will look in all of the namespaces it knows about in the Luca.registry and find an appropriate collection.
|
36
|
+
|
37
|
+
## Initial Collections
|
38
|
+
|
39
|
+
The CollectionManager can be configured with an @initialCollections property, which is an array of names of collection classes, similar to "sample_collection", or actual references to Collection Classes, or strings with their names. The CollectionManager will create instances of the collection for you, and call fetch() on all of them.
|
40
|
+
|
41
|
+
```coffeescript
|
42
|
+
_.def("App.collections.SampleCollection").extends("Luca.Collection").with
|
43
|
+
name: "sample_collection"
|
44
|
+
|
45
|
+
_.def("App.collections.ExampleCollection").extends("Luca.Collection").with
|
46
|
+
name: "example_collection"
|
47
|
+
|
48
|
+
class App.CollectionManager extends Luca.CollectionManager
|
49
|
+
initialCollections:[
|
50
|
+
"sample_collection"
|
51
|
+
"example_collection"
|
52
|
+
]
|
53
|
+
|
54
|
+
# this will create instances of both of the above collections
|
55
|
+
# and call fetch() on all of them
|
56
|
+
collectionManager = new App.CollectionManager()
|
57
|
+
```
|
58
|
+
|
59
|
+
## Event Relaying
|
60
|
+
|
61
|
+
By default `@relayEvents` is set to true on the CollectionManager. This means that
|
62
|
+
any event that is triggered by a collection that is managed by the collection manager will be bubbled up to the manager. This feature is used by the collectionEvents configuration API used by Luca.View, but can also be used in custom situations as well. Simply bind to the CollectionManager instance.
|
63
|
+
|
64
|
+
Event triggers will look like `collection_name event`:
|
65
|
+
|
66
|
+
|
67
|
+
```coffeescript
|
68
|
+
collection = new App.collections.SampleCollection([],name:"sample_collection")
|
69
|
+
manager = new Luca.CollectionManager(collectionNamespace:App.collections)
|
70
|
+
|
71
|
+
manager.on "sample_collection reset", ()=> @doSomething()
|
72
|
+
|
73
|
+
# will trigger 'reset' and call doSomething()
|
74
|
+
collection.fetch()
|
75
|
+
```
|
76
|
+
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# Container Views in Luca.js
|
2
|
+
|
3
|
+
Containers are types of views which are made up of one or more components. A component
|
4
|
+
is simply another Backbone.View, Luca.View, or one of their descendants.
|
5
|
+
|
6
|
+
The purpose of a Container is to faciliate the communication between the components.
|
7
|
+
|
8
|
+
The classic example is a FormView. A FormView is a component which inherits from
|
9
|
+
Luca.core.Container and is made up of many Field components, and facilitates the communication between
|
10
|
+
the fields and a Backbone.Model. The internal implementation of the model and field classes should never know
|
11
|
+
or reference any other component. This is the job of the FormView.
|
12
|
+
|
13
|
+
## Containers are meant to generate your structural DOM elements
|
14
|
+
|
15
|
+
Containers generate the structural DOM elements which wrap the individual components, and the container renders these components to the DOM element
|
16
|
+
that is assigned to it. The various types of containers you use will each
|
17
|
+
have their own internal logic for the way these DOM elements are laid out, displayed, hidden, showed, etc.
|
18
|
+
|
19
|
+
For example, a ColumnView will show two components side by side and assign
|
20
|
+
each one to its own DIV element and use css to lay those columns out as configured.
|
21
|
+
A CardView will assign each component to a DIV element, show the active card, and hide the rest.
|
22
|
+
|
23
|
+
## Layout and Rendering Customization
|
24
|
+
|
25
|
+
The call to `render()` on a container will start a rendering chain on all of the nested components. You can customize this to your hearts content by tapping into
|
26
|
+
the method chain.
|
27
|
+
|
28
|
+
All render() methods on Luca.View are wrapped and will trigger `before:render` and `after:render` events, as well as call any beforeRender or afterRender methods defined on your component. For more about this, see the section about hooks on Luca.View.
|
29
|
+
|
30
|
+
The chain started by a call to `container.render()` is as follows:
|
31
|
+
|
32
|
+
```coffeescript
|
33
|
+
beforeRender()
|
34
|
+
|
35
|
+
# layout functions
|
36
|
+
@trigger "before:layout" # => or run beforeLayout() if it exists
|
37
|
+
@prepareLayout()
|
38
|
+
@trigger "after:layout" # => or run afterLayout() if it exists
|
39
|
+
|
40
|
+
```
|
41
|
+
|
42
|
+
prepareLayout is an internal method on Luca.core.Container which will iterate
|
43
|
+
over each of your components and call applyDOMconfig passing your components
|
44
|
+
configuration to this function. This will create a DOM element and apply
|
45
|
+
any configured inline style declarations, assign a DOM id, css class, as well
|
46
|
+
as some data attributes to the element.
|
47
|
+
|
48
|
+
It will put each DOM container elemement in a @componentContainers property on
|
49
|
+
your container object.
|
50
|
+
|
51
|
+
After prepareLayout is the components cycle:
|
52
|
+
|
53
|
+
```coffeescript
|
54
|
+
@trigger "before:components" # => or run beforeComponents() if it exists
|
55
|
+
@prepareComponents()
|
56
|
+
@createComponents()
|
57
|
+
@trigger "before:render:components"
|
58
|
+
@renderComponents()
|
59
|
+
@trigger "after:components" # => or run afterComponents() if it exists
|
60
|
+
```
|
61
|
+
|
62
|
+
## A Note on Container inheritance
|
63
|
+
|
64
|
+
If you end up customizing the methods above in the render chain, you may
|
65
|
+
want to call the same method on the component you are inheriting from. Luca
|
66
|
+
provides some syntactic sugar for this:
|
67
|
+
|
68
|
+
```coffeescript
|
69
|
+
_.def("MyContainer").extends("Luca.core.Container").with
|
70
|
+
prepareLayout: ()->
|
71
|
+
# This is the normal way you would do this
|
72
|
+
Luca.core.Container::prepareLayout.apply(@, arguments)
|
73
|
+
|
74
|
+
# This is the sugary version which you get if you
|
75
|
+
# use the _.def or Luca.define method for declaring
|
76
|
+
# your prototype definitions
|
77
|
+
@_super("prepareLayout", @, arguments)
|
78
|
+
|
79
|
+
@applyMyOwnLayoutCustomizations()
|
80
|
+
```
|
81
|
+
|
82
|
+
## The `ctype` property
|
83
|
+
|
84
|
+
Every Luca.View which gets registered through the Luca.registry will have a ctype value associated with it. The
|
85
|
+
ctype property is used when adding components to a Container.
|
86
|
+
|
87
|
+
```coffeescript
|
88
|
+
_.def("ComponentOne").extends("Luca.View").with()
|
89
|
+
|
90
|
+
_.def("ComponentTwo").extends("Luca.View").with()
|
91
|
+
|
92
|
+
_.def("ContainerOne").extends("Luca.core.Container").with
|
93
|
+
components:[
|
94
|
+
ctype: "component_one"
|
95
|
+
overriddenValue: "customValue"
|
96
|
+
,
|
97
|
+
ctype: "component_two"
|
98
|
+
thisGetsPassedToInitialize: "yep"
|
99
|
+
]
|
100
|
+
```
|
101
|
+
|
102
|
+
In the above example, a View class of ContainerOne will be available, and
|
103
|
+
any time you create an instance of it and call render on it, it will create
|
104
|
+
instances of ComponentOne and ComponentTwo.
|
105
|
+
|
106
|
+
Note, if you do not need to customize any of the properties on the component
|
107
|
+
views, you can just pass an array of ctype strings.
|
108
|
+
|
109
|
+
```coffeescript
|
110
|
+
_.def("ContainerTwo").extends("Luca.core.Container").with
|
111
|
+
components:["component_one","component_two"]
|
112
|
+
```
|
113
|
+
|
114
|
+
## Convenience Methods on the Container
|
115
|
+
|
116
|
+
You have access to several methods which work on the components which belong to your views. These methods are:
|
117
|
+
|
118
|
+
- pluck : plucks an attribute for each component
|
119
|
+
- invoke: invokes a method for each component
|
120
|
+
- each: run the passed iterator on eachComponent, recursively. You can turn off the recursion by passing false as your second argument.
|
121
|
+
- indexOf: get the index of a component by it's name property
|
122
|
+
- selectByAttribute: selects all components whose attribute matches a given value
|