A framework and tools for building well architected client side applications.
Based on Backbone.js and Twitter Bootstrap.
DownloadLuca allows you to write most of your app in Coffeescript. Most of the structural markup is taken care of for you, and Bootstrap handles a lot of CSS styling for you already. We find that this allows us to develop apps and focus primarily on their functionality and interaction first.
This also makes it very easy to render your app on the client and on the server.
Luca encourages you to design your app as a library of components: re-usable, well encapsulated interface elements with their own concerns. Luca provides ways of joining components together to form more complex pieces which get integrated into the pages of your application.
As your app grows to a certain level, you will find that you have built up a large library of patterns and tools that you can begin to develop new features very quickly without having to solve the same design problems over and over.
Luca comes with a variety of tools that make developing large browser applications much easier.
Property | Defaults | Documentation |
---|
Method | Args | Documentation |
---|
Property | Defaults | Documentation |
---|
Method | Args | Documentation |
---|
Here is a collection of components developed using the Luca framework. They make use of the Github API as their data source, and show off some of the neat things you can do with the components you develop.
For each example, you can see the component in action as it is used, view the source code for the component, and see the documentation that gets generated from your component definition files.
The first example is the API Browser which is a simple component that allows you to view the formatted JSON output from an API endpoint that you enter. Use the tabs to the left to browse through the examples.
Loading...
# The `Docs.components.ApiBrowser` is an example of using # a `Luca.Container` with a `@componentEvents` configuration # to broker communication between two child components. view = Docs.register "Docs.components.ApiBrowser" view.extends "Luca.Container" view.configuration componentEvents: "* button:click" : "loadUrl" view.contains type: "container" rowFluid: true className: "url-selector" components:[ type: "text_field" name: "endpoint_url" label: "Enter a URL" span: 9 , type: "button_field" input_value: "Browse" span: 3 ] , tagName: "pre" className: "prettyprint pre-scrollable" role: "output" afterInitialize: ()-> @$el.html("Loading...") view.privateMethods runExample: ()-> @findComponentByName("endpoint_url", true).setValue("https://api.github.com/users/datapimp/gists") @loadUrl() loadUrl: ()-> url = @findComponentByName("endpoint_url", true).getValue() $.get url, (parsed, state, options)=> @getOutput().$html( options.responseText ) window.prettyPrint() view.register()
The Docs.components.ApiBrowser
is an example of using
a Luca.Container
with a @componentEvents
configuration
to broker communication between two child components.
Property | Defaults | Documentation |
---|---|---|
componentEvents |
Method | Args | Documentation |
---|
Property | Defaults | Documentation |
---|
Method | Args | Documentation |
---|---|---|
loadUrl | ||
runExample |
# The `Docs.views.BasicFormView` is an example of the `Luca.components.FormView`. # In this basic example, the form contains a range of different fields. They are # rendered one on top of another. You can get more advanced and nest containers within # your form, or use a `@bodyTemplate` and specify your own DOM structure, and assign # components to custom CSS selectors within it. form = Docs.register "Docs.views.BasicFormView" form.extends "Luca.components.FormView" form.privateConfiguration # Any values you specify in the `@defaults` property will be # set on each of the components in this container. defaults: type: "text" form.publicConfiguration # You can manually define a `@components` property, or in your component # definition you can use the special `contains` directive, the only difference # is your personal preference for readability. I did it this way components:[ label: "Text Field One" , type: "select" label: "Select Field One" collection: data:[ ['Alpha','Alpha'] ['Bravo','Bravo'] ['Charlie','Charlie'] ['Delta','Delta'] ] , type: "checkbox_field" label: "Checkbox Field" ] form.register()
The Docs.views.BasicFormView
is an example of the Luca.components.FormView
.
In this basic example, the form contains a range of different fields. They are
rendered one on top of another. You can get more advanced and nest containers within
your form, or use a @bodyTemplate
and specify your own DOM structure, and assign
components to custom CSS selectors within it.
Property | Defaults | Documentation |
---|---|---|
components | [ | You can manually define a |
Method | Args | Documentation |
---|
Property | Defaults | Documentation |
---|---|---|
defaults | Any values you specify in the |
Method | Args | Documentation |
---|
# The `Docs.views.ComplexLayoutForm` is an example of a `Luca.components.FormView` which contains # a nested container, and which uses the bootstrap grid helper properties `@rowFluid` and `@span` # to arrange the nested components inside of a grid layout. # # In addition to laying out the form components visually, there is a nested `Luca.containers.CardView` # component which shows / hides various field sets depending on what options you select on the form. # This is an example of how Luca framework components can be assembled together arbitrarily to build # whatever type of user interface you can imagine. form = Docs.register "Docs.views.ComplexLayoutForm" form.extends "Luca.components.FormView" form.privateConfiguration # By setting `@rowFluid` to true, this container # will support the twitter bootstrap grid layout. Applying # the `@span` property to the direct children of this component # will control their width rowFluid: true # Here is an example of using the `@componentEvents` property to listen # to the change event on the select field identified by the role 'group_selector'. # once that field emits its change event, we change the active display card in the # nested card selector. componentEvents: "group_selector on:change" : "selectGroup" form.privateMethods # The selectGroup method is bound to the componentEvent listener. Whenever # the group_selector field changes its value, we want to change which field # group is visible on the form. selectGroup: ()-> desiredGroup = @getGroupSelector().getValue() selector = @getGroupDisplaySelector() selector.activate(desiredGroup) form.contains type: "container" span: 6 components:[ type: "text" label: "Field One" , type: "text" label: "Field Two" , type: "text" label: "Field Three" ] , type: "container" span: 6 components:[ label: "Select a Group" type: "select" role: "group_selector" includeBlank: false valueType: "string" collection: data:[ ["alpha","Alpha Group"] ["bravo", "Bravo Group"] ["charlie","Charlie Group"] ] , type: "card" role: "group_display_selector" components:[ name: "alpha" defaults: type: "text" components:[ type: "view" tagName: "h4" bodyTemplate: ()-> "Group One" , label: "Alpha" , label: "Bravo" , label: "Charlie" ] , name: "bravo" defaults: type: "checkbox_field" components:[ type: "view" tagName: "h4" bodyTemplate: ()-> "Group Two" , label: "One" , label: "Two" ] , name: "charlie" defaults: type: "button_field" components:[ type: "view" tagName: "h4" bodyTemplate: ()-> "Group Three" , input_value: "Button One" icon_class: "chevron-up" , input_value: "Button Two" icon_class: "pencil" ] ] ]
The Docs.views.ComplexLayoutForm
is an example of a Luca.components.FormView
which contains
a nested container, and which uses the bootstrap grid helper properties @rowFluid
and @span
to arrange the nested components inside of a grid layout.
In addition to laying out the form components visually, there is a nested Luca.containers.CardView
component which shows / hides various field sets depending on what options you select on the form.
This is an example of how Luca framework components can be assembled together arbitrarily to build
whatever type of user interface you can imagine.
Property | Defaults | Documentation |
---|
Method | Args | Documentation |
---|
Property | Defaults | Documentation |
---|---|---|
rowFluid | true | By setting |
componentEvents | Here is an example of using the |
Method | Args | Documentation |
---|---|---|
selectGroup | The selectGroup method is bound to the componentEvent listener. Whenever the group_selector field changes its value, we want to change which field group is visible on the form. |
view = Docs.register "Docs.views.TableViewExample" view.extends "Luca.components.ScrollableTable" view.publicConfiguration # Only render 100 models at a time. The `Luca.CollectionView` has # automatic pagination control rendering, if you specify a pagination # view class and render area. paginatable: 100 # The scrollable table element has a max height. maxHeight: 300 # The string "github_repositories" is an alias for the collection manager # which is created by the `Docs.Application`. It represents a singular # global instance of the `Docs.collections.GithubRepositories` collection. collection: "github_repositories" # The `Luca.components.TableView` component accepts an array of column # configurations. Each column can specify the following properties: # - header # - reader ( a method, or attribute on the collection's model ) # - renderer ( a custom function which renders the model / reader ) # - width ( a percentage width for the column ) columns:[ reader: "name" renderer: (name, model)-> "#{ name }" , reader: "description" , reader: "language" , reader: "watchers" ] view.publicMethods runExample: ()-> @getCollection().fetch() view.register()
Property | Defaults | Documentation |
---|---|---|
paginatable | 100 | Only render 100 models at a time. The |
maxHeight | 300 | The scrollable table element has a max height. |
collection | "github_repositories" | The string "github_repositories" is an alias for the collection manager
which is created by the |
columns | [ | The |
Method | Args | Documentation |
---|---|---|
runExample |
Property | Defaults | Documentation |
---|
Method | Args | Documentation |
---|
view = Docs.register "Docs.views.GridLayoutViewExample" view.extends "Luca.components.GridLayoutView" view.publicConfiguration collection: "github_repositories" itemPerRow: 4 paginatable: 12 itemTemplate: "github_repository" view.publicMethods runExample: ()-> @getCollection().fetch() view.register()
Property | Defaults | Documentation |
---|---|---|
collection | "github_repositories" | |
itemPerRow | 4 | |
paginatable | 12 | |
itemTemplate | "github_repository" |
Method | Args | Documentation |
---|---|---|
runExample |
Property | Defaults | Documentation |
---|
Method | Args | Documentation |
---|
The depencies file includes underscore 1.4.4, backbone 0.9.9, jquery 1.9, underscore.string, and backbone-query. You will need to bring your own bootstrap.
<html>
<head>
...
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.0/css/bootstrap-combined.min.css" />
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/3.0.2/css/font-awesome.css" />
<link rel="stylesheet" href="//datapimp.github.com/luca/vendor/assets/stylesheets/luca-ui.css" />
</head>
<body>
...
<script type="text/javascript" src="//datapimp.github.com/luca/vendor/assets/javascripts/luca-dependencies.min.js"></script>
<script type="text/javascript" src="//datapimp.github.com/luca/vendor/assets/javascripts/luca.min.js"></script>
</body>
</html>
# Gemfile.lock
gem 'luca'
gem 'bootstrap-rails'
Including the luca gem makes everything available via the asset pipeline.
# application.coffee
#= require luca-dependencies.min
#= require luca.min
We depend on the bootstrap css externally.
/*
*= require twitter/bootstrap
*= require luca-ui.css
*/
The luca gem ships with a rails generator to generate an application skeleton. You can run it: rails g luca:application sample
Doing this will generate an application skeleton for you:
- app/assets/javascripts/sample
- collections
- lib
- collection_manager.coffee
- router.coffee
- models
- views
- application.coffee
- config.coffee
- index.coffee
The style we use to develop Luca applications is to develop all of our components and views as encpasulated modules. We start with the smallest units, and then build them up into components using containers, and then build them into pages which are controlled by the main Application.
One very painful aspect of developing single page applications is having to refresh the browser all the time, and then restore the state of your application to where it was when you were testing a particular feature.
Tools like LiveReload exist, and they're great, except they don't always work well with the asset pipeline.
The Luca gem ships with a Luca.CodeSyncManager and an easy command line utility which keeps your application code and stylehseets in sync with the running browser session.
The Luca.CodeSyncManager
is a client side component that works with the luca
executable that ships with the gem. It listens for notifications of asset changes
(scss,coffeescript,templates,etc) in your development directory, and applies them to the running session.
It works similar to tools like 'LiveReload' but without refreshing the entire page, and with direct integration
with your asset pipeline / sprockets configuration. For Luca apps specifically, it also handles changes to
component definitions more elegantly by updating live instances of your component prototypes and event handlers
so that you don't have to refresh so often.
Run the luca command from your project root, and specify the name of the application you are watching:
bundle exec luca sync app_name
The sync server runs a faye process on port 9295. You can specify options on the command line.
In your browser, you can control various settings by setting the Luca.config
values.
- Luca.config.codeSyncHost
- Luca.config.codeSyncChannel
- Luca.config.codeSyncStylesheetMode
After your Luca.Application renders, just call the Luca.CodeSyncManager.setup method in the context of your application.
app = Luca.getApplication()
app.on "after:render", Luca.CodeSyncManager.setup, app
Or in the initialize method of your application: ... initialize: ()-> @on "after:render", Luca.CodeSyncManager.setup, @ ...
Any assets named syncpad: syncpad.coffee, syncpad.css.css, syncpad.jst.ejs, etc are treated specially by the code sync utility. The syncpad assets are used to provide a scratch pad / test environment for your application. You can write coffeescript or sass and have them live evaluated in your running browser.
Your luca components are your documentation.
For an example of what i'm talking about, check out our documentation or the Examples Browser which shows the component documentation and source side by side.
The gem ships with a command line interface which allows you to generate a documentation source file that you can load into a special viewer component. This site is an example of the documentation generator in action.
bundle exec luca generate docs --name=application_name --export-location=./path/to/javascripts
The header for a Luca component provides you with a very readable description of what the component is, what it extends from, any custom mixins that it relies on, as well as special header comments which get rendered through markdown to generate documentation for you.
# The header comments in your Luca components get rendered as # markdown by the Luca documentation tool that ships with the gem. # #### Any type of Markdown is valid here # Here is an code example: # # view = new MyApp.views.CustomComponent() # # We start by creating a `Luca.ComponentDefinition` object, and # assigning it to the local variable 'view': view = MyApp.register "MyApp.views.CustomComponent" # We specify which component this view extends from. The default # component is `Luca.View`. When all is said and done, the # object we build will be passed to MyApp.views.BaseView.extend view.extends "MyApp.views.BaseView" # Mixins / Concerns are special objects which are used to decorate # your components with functionality. They provide an alternative to # inheritance. We can specify which mixins our component uses this way, # which places them at the top of the definition file and makes it very # easy to read and understand the behavior of this component. view.mixesIn "ViewConcernOne", "ViewConcernTwo", "SomeOtherViewConcern"
Rather than having a single large Backbone.View.extend call which passes all properties, methods, etc in one go, the Luca component definition offers you more fine grained control over definining your prototype so that you can specify the intent of each property or method. Is it public or private? This makes extending from your component or modifying them in the future much easier, as they are truly self documenting.
view.publicConfiguration # Documentation for this property is also markdown # Acceptable values are: # - this # - that # - something else someProperty: "value" # I love it. someOtherProperty: "value" view.publicMethods # The comments that exist above a method definition are also used by # the documentation generator to provide documentation for your methods. someMethod: ()-> @doSomething()
Private methods and properties are the same as everything else, but this lets the developer know that these are not necessarily meant to be modified or used by components which extend from this.
view.privateMethods somePrivateMethod: ()-> console.log "hi."
It is generally a good practice to finish off the component definition, by registering it with the component registry:
view.register()
Note:The final call to register()
is required. It is what ultimately makes the call to Backbone.View.extend
when all is said and done.
Each of these methods allow you to gradually build an object which gets passed to your extend call
In addition to specifying instance properties and methods, you can define class methods as well:
view.classMethods # This method will be available as MyApp.views.CustomComponent.customClassMethod() customClassMethod: ()-> console.log "Called MyApp.views.CustomComponent.customClassMethod()"
view.beforeDefinition (componentDefinitionProxy)-> console.log "This method will be called before the component is defined." view.afterDefinition (componentDefinitionProxy)-> console.log "This method will be called after the component is defined."
Immediately start developing your client side app without worrying about the backend data.