Locomotive-fundamentals
文件大小: unknow
源码售价: 5 个金币 积分规则     积分充值
资源说明:This repo is an ebook about the locomotive cms that is currently in work. Feel free to Fork !!
# LocomotiveCMS Guide

This book is incomplete and should evolve in the future. Any contribution is very welcomed !

For any questions or advices about this book, ask [mail@geraudmathe.com](mailto:mail@geraudmathe.com), and if you need support from LocomotiveCMS team, ask [support@locomotivecms.com](mailto:support@locomotivecms.com).


## Summary

1. __[Foreword](#foreword)__
  *  __[Why this guide ?](#foreword_1)__
  *  __[Philosophy](#foreword_2)__
  *  __[Why you should use LocomotiveCMS ?](#foreword_3)__
  *  __[Assumptions](#foreword_4)__
  *  __[Organization of this book](#foreword_5)__
2. __[Overview](#overview)__
  *  __[Anatomy of a LocomotiveCMS app](#overview_1)__
  *  __[Key features](#overview_2)__
3. __[Getting something running in 5 minutes](#getting_something_running)__
4. __[Templating](#templating)__
  *  __[Templating Logic](#templating_1)__
  *  __[Liquid syntax](#templating_2)__
  *  __[Creating a page](#templating_3)__
  *  __[Recipe : Create a RSS feed](#rss_feed)__
5. __[Models](#models)__
  *  __[Basics](#models_basics)__
  *  __[Models mapping](#models_mapping)__
  *  __[Rendering models](#models_rendering)__
  *  __[Templatize a model](#models_templatize)__
  *  __[Recipe: Public Submission](#public_submission)__
  *  __[Recipe: Create models and content via YAML](#models_yaml)__

6. __[LocomotiveCMS Editor](#locomotive_editor)__
7. __[Using LocomotiveCMS in an existing Rails app](#locomotive_rails_app)__
7. __[Tips](#tips)__
  *  __[Using multi-sites](#multi_sites)__
  *  __[Export site](#export_site)__
  *  __[Internationalization](#internationalization)__
  *  __[Customize TinyMCE](#customize_tiny_mce)__
  *  __[Writing custom Liquid tags](#custom_liquid_tag)__
8. __[Appendix](#appendix)__


##Foreword


### Why this guide ?

There is already an official documentation reference, which lists almost everything. Still, a pragmatic guide to LocomotiveCMS is missing, especially for beginners.

What's more, since there is a lot of goodness in the LocomotiveCMS's Google Group, it seems relevant to gather good practices & hacks in one place.

TBR: This guide isn't the official one, even if some members of the LocomotiveCMS core team have reviewed some parts of it.


### Philosophy

Let's start first with a little bit of history.

When I was a developer at a Chicago web agency, I built numerous custom and unique content management system applications for each of our clients. Even though I enjoyed crafting unique back-offices for our clients, it was clear to me that I should spend less time on those kinds of projects so that I may tackle larger ones. At this time, there were no open source Rails CMSes fitting both my needs and my vision of ideal CMS.

Upon returning to France, I kept thinking about what the perfect CMS should be. My thoughts were also enhanced by my experiences as a Rails developer in other companies. I began coding a prototype, and that protoype has been constantly improved ever since. I also spent a lot of time experimenting with many concepts and throughout the process, I learned a lot about what the perfect CMS should look like.

However, I stayed true to basic requirements of my dream CMS and never moved away from them. These requirements are:

- A single instance of LocomotiveCMS should host many sites. Once LocomotiveCMS is installed, setting up a new site has to be quick and will not require the help of a systems admin guy.
- It should be effortless for the content editors to edit the site without ruining the layout or, worse, crashing the site.
- Developing a LocomotiveCMS site should not require Ruby on Rails knowledge.
- It should be possible and easy to extend and customize LocomotiveCMS in elegant ways.
- The back-office should be sexy as hell.
- The code of LocomotiveCMS should not "smell". Thus, refactoring the code is a continuous process during all the development. That also means using standard components like for instance "Devise" for the authentication part.


### Why should you use LocomotiveCMS ?

LocomotiveCMS is a CMS that has been created with a single core concept: keep it simple!

- Keep it simple, for the developer who shouldn't have to go deep in architecture, and should be able to edit a website quickly.
- Keep it simple, for the author who needs to be focused on content, and shouldn't have to go through several pages to edit.

If one of the cases listed above applies to you, you should use LocomotiveCMS.

///

From a "business" point of view, LocomotiveCMS has several great selling points:

- Front-end editing of static texts, using Aloha editor
- Hosting on Heroku / AWS very cheap, almost free
- Finally, a great looking back-office!


### Assumptions

During this reading, it is assumed that:

- You know what Ruby and Rails is and you have a decent understanding of terms like Gem, Bundler, deployment
- You know what a data model is, and ideally you understand document-oriented storage, like Mongo
- You have basic knowledge of how to use a shell/command-line interface


### Organization of this book

This guide is structured as follows:

First, the *Overview* of the CMS aims to introduce the environment and the main things to know about.

*Getting something running in 5 minutes* may help LocomotiveCMS's beginners walking through the


## Overview


### Anatomy of a LocomotiveCMS app

LocomotiveCMS is crafted as an engine.


A Rails engine is an application packaged as a ruby gem that is able to be run or is mounted within another Rails application. An engine can have its own models, views, controllers, generators and publicly served static files.

([more about engines](http://guides.rubyonrails.org/engines.html))

What's inside ?

- [Rails 3](http://www.rubyonrails.org)
- [Mongoid](http://www.mongoid.org)
- [Devise](http://blog.plataformatec.com.br/tag/devise)
- [Liquid](http://www.liquidmarkup.org)
- [Haml](http://haml-lang.com)
- [Formtastic](https://github.com/justinfrench/formtastic)
- [Carrierwave](http://carrierwave.rubyforge.org)


### Key features

You have out of the box:

- Multi sites: manage multiple websites with one application instance
- Flexible content types
- Front-end inline editing (Aloha editor)
- Content localization
- Restful API
- Haml / Sass support
- Liquid templating langage
- A very nice User Interface


## Getting something running in 5 minutes

TODO: définir avec géraud et didier ce que l'on fait dans cette app, liste des choses à voir : editable texts, models, templates (héritage), tags liquid de base, … ?

il n'y a pas de template de base, sauf si on achète loco editor là il y en a mais sinon non, pas dans la version 2.0



## Templating


### Templating Logic

#### Basics of inheritance

The logic in LocomotiveCMS differs a bit from what you are used to, so it may be weird a first, but it's actually very simple.

In the classic 'Rails way', you have the following architecture, with page content integrated in the application layout using the ``` yield ``` statement :

    +- Views
        +- layout
            +- application
        +- mysite
            +- index
            +- first page
            +- second page

In Locomotive, it's a bit different :

    +- Pages
        +- index
            +- first page
            +- second page

All pages inherit from index. This way, the index contains the application's master layout and the content of the index page. How do you re-use the layout without re-using the index page's content? By introducing ```{% block 'block_name' %} ... {% endblock %}``` : since all pages inherit from index, you declare blocks of content inside the layout (index), which will be overwritten in child pages. Here is a simple example:

Index page:

```html

  
    My index page
  
  
    
layout header
{% block content %} the content of the index page {% endblock %}
layout footer
``` A page, which inherits from index: ```html {% extends parent %} {% block content %} the content of this page {% endblock %} ``` By extending index, 'a-page' re-uses all of its content, except the content inside the ``` {% block %} ``` tag which is overwritten. This tag is written using Liquid syntax which will be explained later. You can have as many ``` {% block %} ``` tags as you want, everywhere in the layout, as long as the name of each block is unique. For a basic application which only has one layout, that's all you need to know. src: http://doc.locomotivecms.com/templates/tags#block-section #### Going further **Several levels of inheritance** The principle of page inheritance can be applied to every page. When you create a page, it automatically inherits from index, but you can also make it inherit from another page, by specifying it's parent: ![Specifying parent](images/specifying_parent.png) By doing so, you can define as many levels as you want : +- Pages +- index +- first page +- child of first page +- child of first page's child +- second page **Inherit from an page other than the parent** When you extend the parent's layout, you use the tag ``` {% extends parent %} ```, but what if you would like to extends a page which isn't a direct parent? For example, how would you make "second page" extend "first page"? +- Pages +- index +- first page +- second page It's simple : ``` {% extends first_page %} ``` ! You specify the page you want to extend with its *slug*. src: http://doc.locomotivecms.com/templates/tags#extends-section **What about several layouts?** Let's say your website needs two layouts, how do you do it without putting the entire index in ``` {% block %} ``` tags? It's actually fairly simple: you are not forced to make a page inherit its content from another. Remember the previous page we created which inherited from index: ```html {% extends parent %} {% block content %} the content of this page {% endblock %} ``` Well, actually the tag ``` {% extends parent %} ``` can be removed, so the page doesn't extend any other page, letting you define a brand new layout if needed. Let's illustrate this with an example: - the main layout of the site will be defined in index - a second layout will be defined in a page called "alternate_layout" - we will have a page called "normal" which will use the main layout - and finally an other page called "alternate_page" which will use the alternate layout The skeleton will look like that: +- Pages +- index +- normal +- alternate_layout +- alternate_page Here we go: First, the index page: ```html The Main Layout
Main header
{% block main_content %} the content of the index page {% endblock %}
Main footer
``` The "normal" page, which inherits from index: ```html {% extends parent %} {% block main_content %} the content of the normal page {% endblock %} ``` Then the "alternate layout" page, which doesn't extend its parent, index: ```html The Alternate Layout
Alternate header
{% block alternate_content %} the content of the alternate layout page, it can be empty if you just want to define an empty layout {% endblock %}
Alternate footer
``` And finally, the "alternate page", which inherits from "alternate layout". You may notice the ``` {% extends alternate_layout %} ``` instead of ``` {% extends parent %} ```, as explained in the previous part. ```html {% extends alternate_layout %} {% block alternate_content %} the content of alternate page, using the layout defined in alternate_layout.liquid.html {% endblock %} ``` **Snippets** To conclude the templating basics section, it's worth it to know that LocomotiveCMS gives you the ability to put some blocks of code in a separate folder called snippet, in the same way Rails does with partials. Snippets are very useful when you want to build a modular layout without repeating code. Like Rails, you can pass a variable to the snippet, or simply include a static block of code. The following example will cover both cases, don't bother with the liquid syntax which will be explained in the next part. +- Pages +- index +- Snippets +- sidebar +- product_information Here is the index, which includes the sidebar, loops on the products model, and includes the snippet "product_information" for each product: ```html Snippet example
{% for product in contents.products %} {% include 'product_information' with product %} {% endfor %}
{% include 'sidebar' %}
``` Then the sidebar: ```html ``` And finally the product_information snippet which uses the context "product": ```html
{{ product.name }} : {{ product.price }}$
``` src: http://doc.locomotivecms.com/templates/tags#include-section ### Liquid syntax Liquid is a templating library extracted from Shopify. The project is hosted at http://liquidmarkup.org. LocomotiveCMS reuses a lot of the original library. #### Everything in 2 markups The liquid syntax is a templating engine based on a set of functions that allow the developer (or the designer, since you don't need strong coding skills to write it) to keep focus on the rendering of the data, not on the way it could render it. Liquid defines 2 types of markup, pretty close to what you are used to with Erb: **Output markup: matched pairs of curly brackets output the value of an object :** Erb: <%= @product.name %> Liquid : {{ product.name }} **Tag markup: matched pairs of curly brackets and percent, not resolved to text:** Erb : <% name = @product.name %> Liquid : {% assign name with product.name %} Liquid is extracted from http://www.shopify.com, but LocomotiveCMS extends it. To cover all, we will distinguish 3 cases: - Objects - Filters - Tags which are all in the LocomotiveCMS doc: http://doc.locomotivecms.com/templates/basics In the next part, we'll give some examples. original Liquid doc: https://github.com/Shopify/liquid/wiki/Liquid-for-Designers #### Objects When writing a liquid template, you will have access to a couple of basic objects, like the current site, page, logged in account, as well as collections, like your custom content types. These objects are also called 'drops'. Available objects and their attributes are listed here: http://doc.locomotivecms.com/templates/objects **SEO purpose** You can either use the object ``` site ``` and have the same meta all over your website : ```html {{ site.seo_title }} ``` Or you can define SEO meta for each ``` page ``` : ```html {{ page.seo_title }} ``` #### Filters img magick http://markevans.github.com/dragonfly/file.ImageMagick.html #### Tags editable file => https://groups.google.com/forum/#!topic/locomotivecms/hOaqFUcZCm8 only in backoffice for 2.0 example: promotion ### Creating a page You have several options when you're creating a page. Let's take a look. #### General information ![General Information](images/page_general_info.png) Nothing complex, just specify the name of the page. The slug field will be updated automatically. Be aware the slug will reference the url linked to the page you are creating, so if you change it later, it could break links in the website. Set the parent page, as explained in __[Templating Logic](#templating_1)__. #### SEO settings ![SEO settings](images/page_seo_settings.png) Edit the meta ```title```, ```keyword```, ```description``` for the page, or leave it empty if you want use the global meta. These meta values will then be available for use in the template with Liquid tags, like this: ```html {{ page.seo_title }} ``` #### Advanced options ![Advanced options](images/page_advanced_options.png) - Handle: Used when you integrate LocomotiveCMS with a Rails app, see [this chapter](#locomotive_rails_app). - Response type: You can choose between HTML, RSS, XML or JSON. You may use this to generate a RSS feed or build an simple API from your LocomotiveCMS site. - Templatized: Defines whether or not this page should be a template for a model instance, see [this chapter](#models_templatize). - Published : Since only authenticated accounts can view unpublished pages, this allows debugging on a page in a deployed site. - Listed: The Liquid ``` {% nav %} ``` generates a menu ([doc](http://doc.locomotivecms.com/templates/tags#nav-section)) based on your page. Use this to determine whether or not this page appears in the generated menu. - Redirect: If you check this, you can redirect the page to a url. LocomotiveCMS will perform a 301 redirect. From an SEO perspective, this is a permanent redirection, so you should use it when your URLs have changed. When search engines encounter a 301 redirect, they update the URLs in their database. ``` source: [https://groups.google.com/d/topic/locomotivecms/UoNFhChvpOQ/discussion](https://groups.google.com/d/topic/locomotivecms/UoNFhChvpOQ/discussion)``` - Cache strategy: Define the cache strategy for this page here. ### Recipe: Create an RSS feed You can create a page which will return an RSS feed of your blog. In the list of pages, it is tagged with the "RSS" label. ![RSS page](images/page_rss.png) Assumptions: - a "articles" page was created as well as a templatized page for an article. - an "article" model was also created. In order to create that kind of page, follow these steps: - click on the "new page" button. - select "RSS" as the response type in the "Advanced options". - fill the template - replace "example.com" with your real domain ```xml {{ site.seo_title }} {{ site.meta_description }} http://www.example.com en Example 30 {% for article in contents.articles %} {{ article.title }} http://www.example.com/articles/{{ article._permalink }} http://www.example.com/articles/{{ article._permalink }} {{ article.created_at | localized_date: '%a, %d %b %Y %H:%M:%S %z' }} example.com {% endfor %} ``` - click on the "create" button at the bottom of the screen Note: do not forget to set the "Published" flag to true and validate your feed [here](http://validator.w3.org/feed/). If you want the browsers and news readers to auto-detect your RSS feed, add the following statement within the "head" tag of your template. ``` {{ '/articles/rss.xml' | auto_discovery_link_tag }} ``` ## Models This chapter covers models, also known as the custom content LocomotiveCMS lets you build in the UI. Here we use the word ```model``` as it's what we are used to, but in the LocomotiveCMS [reference](http://doc.locomotivecms.com/) you will see ```content type``` for ```model```, and ```content entry``` for an instance of a ```model```. The [first subchapter](#models_basics) aims to introduce the very basic creation and usage of models. The [second subchapter](#models_mapping) is about building relationships between your models. In the [third subchapter](#models_rendering) we cover common use cases for rendering models with Liquid. In the [fourth subchapter](#models_templating) we will show the flexibility and functionality of the templating a model. Finally, we will cover the [public submission](#models_public_submission) of models, which will allow frontend users to create instances. ### Basics First step of model creation, specify the name of the model: ![Create model](images/models_basics_creation.png) As mentioned in the hint, you will reference your model in Liquid logic by its *slug*. Then, define the fields (attributes) of your model in the following section: ![Create fields](images/models_basics_fields.png) #### Fields types The following types of attributes (fields) are available: ![Types list](images/models_basics_types_list.png) Let's look at an overview of each one. The rendering of these types will be reviewed [later](#models_rendering). *Nota bene : you will encounter some unexplained properties. This is because they are common to all field types and will be covered later.* - Simple input: A string, max 255 chars (?) - Text: Text field, but you can choose the format. When you add a field: ![type text 1](images/models_basics_text_1.png) you will have a properties panel that appears when you click on the arrow on the right part of the line: ![type text 2](images/models_basics_text_2.png) If you choose ```Text formatting: HTML```, you will get TinyMCE, a WYSIWYG editor: ![type text tinymce](images/models_basics_text_tinymce.png) And if you choose ```Text formatting: none```, you will get a simple textarea: ![type text textarea](images/models_basics_text_textarea.png) - Select: Displays a select list of options for the field. ![type select](images/models_basics_select.png) You have to put the options of the list in the property of the field. In the example we have "frontend", "backend" and "api". This type of attribute is handy, since it may allow you to avoid creating another model to store simple lists. The options are both editable from the model editing page and the model instance creating/editing pages. - Checkbox: A simple boolean field. The "Required" attribute can not be applied to this field type. - Date: A date field, with the following date selector built-in: ![type date](images/models_basics_date.png) The "updated at" and "created at" fields already exist by default, they can be rendered with ```entry.created_at``` and ```entry.updated_at```. - File: A field of this type supports the upload of any kind of file. The other fields specifying a relationship with an other model (```belongs_to```, ```has_many``` and ```many_to_many```) will be explained in the next section, [Models mapping](#models_mapping). **Common fields properties :** When you define an attribute (or a field) for your model, you have some properties which are specific for each kind of attribute (detailed previously), and some which are common to every one. ![type properties](images/models_basics_properties.png) - Required / Optional: Defines whether this field is required when the form is validated. Obviously, a model must have at least one field. The first field you define will be considered as the mandatory one, and will be automatically saved as ```Required```. There is one exception though: you can't have a mandatory field whose type defines a relationship with an other model. - Name: Make sure the name of the field *highlighted in yellow here* matches the "Name" property below. As the tip explains "Name of the property for liquid templates", it will be this value you will have to use in the liquid template, and not the value highlighted in yellow. It seems obvious, but if you change the name of the field (the one highlighted in yellow) and forgot to update the value of the field below to match it, you will not be able to retrieve the object in Liquid and may wonder why... - Hint: Hint for the end user of the back-office. The text is displayed in the model form just below the field. - Localized: Used for internationalization, detailed [here](#internationalization). #### Presentation and Advanced options When the attributes of the model are defined, click on "Create" to edit advanced options of the model: ![models advanced props](images/models_basics_advanced.png) For the purpose of the example, the following model will be used in the following examples: ![models advanced model](images/models_basics_advanced_model.png) So let's say we have a 'Post' model with a title (string), some text (text), a category (select with options "frontend" and "backend") and a publishing_date (date). **Presentation** Theses options let you customize how entries of your model are displayed in the back-office page. - Label field: Choose the field of the model displayed for each entry. If you choose the field ```title```, you have: ![models advanced label 1](images/models_basics_advanced_label1.png) And if you choose ```publishing_date```, you end up with: ![models advanced label 2](images/models_basics_advanced_label2.png) - Group by field: Group entries by a common field value. This is available only for fields which have the type ```select```. So here we can group by category: ![models advanced group by](images/models_basics_advanced_groupby.png) - Item template: Let's you customize the string displayed for each entry in the list of model entries. For example, let's say I don't display my posts grouped by category, but I would still like the category to appear beside the post title, I would enter the following: ![models advanced item template](images/models_basics_advanced_itemtemplate.png) And my entries would display this way: ![models advanced item template1](images/models_basics_advanced_itemtemplate1.png) **Advanced options** And finally, the last properties of your model: - Order by, Order direction: The ordering of items in your model, both in frontend and in backend. - Public Submission: Let frontend users create entries for the model, so typically you would use that option in a model "messages" for a contact form. This is detailed [here](#models_public_submission). ### Model mappings LocomotiveCMS lets you define the relationships between models using the same style you are used to in Rails, but the creation and usage of these mapped models can sometimes be difficult, so let's examine each mapping type in detail. This part is dedicated to the models creation and mapping in the admin UI, and the template code shown here will be very concise and simple. For more about displaying models, read the next part. We will see the ```belongs to```, ```has many``` and ```many to many``` relationships and finally look at more complex mappings. #### Belongs to We have the model ```books``` belongs_to ```authors```. First, we create the model ```authors``` in its simplest form: ![authors](images/belongsto_authors.png) The mapping with the model ```books``` will be defined in ```books```. Let's create ```books```: ![books step 1](images/belongsto_books_1.png) We give him a ```title``` field, and a ```writer``` field which defines the ```belongs_to``` relationship. But wait, we haven't mapped ```books``` to ```authors``` yet, so click on the "add" button and then on the down arrow to specify more options concerning this field: ![books arrow](images/belongsto_arrow.png) You then have the option panel where you choose the ```Class name``` of the model targeted by the belongs_to relationship: ![books step 2](images/belongsto_books_2.png) We are done, so click on the "Create" button to save the model. *Nota Bene: the name of the field defining the belongs_to relationship (here 'writer') can be named as you want, you don't have to name it the singular of the targeted model (here it would be 'author'), even if it may be a best practice. (???)* Now we have our models defined, let's add some dummy entries. First, we'll create a new book entry: ![books entrie empty](images/belongsto_book_entrie_empty.png) We can give him a name, but the ```writer``` list is empty, right, because ```authors``` model hasn't any entries yet. So create an author: ![author add](images/belongsto_author_entrie.png) And then go back in the book creation page, the author appears in the ```writer``` list: ![book entrie valid](images/belongsto_book_entrie_valid.png) Great, save the entry and we will check if it works. In a dummy page, we loop on ```books``` entries, and for each one (here the only one), we display the title of the book and its writer: ``` {% for book in contents.books %} {{ book.title }} written by {{ book.writer.name }} is a great lecture. {% endfor %} ``` and it displays: ```Responsive Web design written by Ethan Marcotte is a great lecture.```. #### Has many Let's continue to use the same models from the previous section, and add that ```books``` have ```reviews```. A review is basically a piece of text, and it is often published in a media. What's more, a book has many reviews, but a review refers to one and only one book. So we are indeed in the relationship ```books``` has_many ```reviews```. First, we create the ```reviews``` model, which has a string field ```journal``` (in which the review is published) and a text field ```content```, and also a belongs_to field ```book```: ![book reviews](images/hasmany_reviews.png) **The ```belongs_to``` field targeting the parent model class name, ```books```, is required!** ![book reviews belongs to field](images/hasmany_reviews_belongsfield.png) Save the model, and then edit the ```books``` model. Now add a has_many field named ```reviews```, targeting the ```Class name``` ```reviews```, and ```Inverse of``` itself, so ```books```: ![book editing](images/hasmany_books.png) *Nota Bene: here again, the name of the field defining the has_many relationship (here 'reviews') can be named as you want.* Save the updated ```books``` model, and then edit the previous ```books``` entry: ![book entrie](images/hasmany_bookentrie_1.png) Let's add a review to this book, click on "Add a new entry" and fill it with dummy text: ![book entrie reviewed](images/hasmany_bookentrie_2.png) Click on "Save" to close the modal window and create the ```reviews``` entry related to this ```books``` entry. Click again on "Save" to update the ```book```. In the previous dummy page where we tested the belongs_to relationship, we add a loop on the reviews of a book: ``` {% for book in contents.books %} {{ book.title }} written by {{ book.writer.name }} is a great lecture.
Reviews:
{% for review in book.reviews %} Published in {{ review.journal }} : {{ review.content }} {% endfor %} {% endfor %} ``` and it displays: ``` Responsive Web design written by Ethan Marcotte is a great lecture. Published in Web design monthly: Awesome book, blablabla ... ``` ##### UI enabled When you added ```review``` to your ```book``` writer, it was possible because of the property "Ui enabled" of your ```has many``` field. This property is set to ```true``` by default: ![tips ui enable](images/models_tips_uienable.png) This property sets whether you can edit and create a child model entry from a parent model entry, or not. #### Many to many Finally, we will add the ability to associate tags to a book. Here, the model ```tags``` have many ```books``` and ```books``` have many ```tags```. Let's create the ```tags``` model. ![tags creation 1](images/manytomany_tags_1.png) ![tags creation 2](images/manytomany_tags_2.png) We have the ```books``` field referring to books via a ```many_to_many``` relationship. Like previously, we specify the ```Class name``` of the targeted model, which is ```books```. We also have to specify ```Inverse of``` (itself) ```tags``` here, but we can't, the select list is empty. For now, save the ```tags``` model, we will get back here soon. Go edit the ```books``` model and add, as you may guessed, the ```tags``` field referring to tags via a ```many_to_many``` relationship. The ```Class name``` of the targeted model is ```tags```. Yes I'm repeating myself a little, just in case. But here you can define the ```Inverse of``` (itself) which is ```books```, so do it please: ![books m2m update](images/manytomany_books.png) Then save the ```books``` model and go back editing ```tags``` model : magic, the ```Inverse of``` attribute of the many_to_many field ```books``` is field with the appropriate value ```tags``` : ![tags inverse of update](images/manytomany_tags_inverseof.png) Here it is, your many to many is settled. Now we will add some tags to our book, but unlike the previous cases, you can't create a tag entry in the book entry page : ![m2m enable ui problem](images/manytomany_enableui.png) We have to create a new tag entry separately, and then add it when editing the book entry. So we do : ![m2m tag available in book](images/manytomany_tag_entrie.png) You don't have to select here the book entry you want to connect the tag. And when we go back to the book entry we were editing, the tag is available in the select list : ![m2m tag available in book](images/manytomany_books_tags_available.png) So let's (finally !) add our tag to our book, save, and check if everything is okay back in frontend : ``` {% for book in contents.books %} {{ book.title }} written by {{ book.writer.name }} is a great lecture.
Tags :
{% for tag in book.tags %} "{{ tag.text }}" {% endfor %} {% endfor %} ``` displays : ``` Responsive Web design written by Ethan Marcotte is a great lecture. Tags : "responsive" ``` And if we do the opposite, it's okay too (as expected after so much pain) : ``` {% for tag in contents.tags %} Tag : "{{ tag.text }}" is related to the following books :
{% for book in tag.books %} {{ book.title }} written by {{ book.writer.name }} {% endfor %} {% endfor %} ``` displays : ``` Tag : "responsive" is related to the following books : Responsive Web design written by Ethan Marcotte ``` #### More complex mapping TODO: considerations about nested relationships and performance of associated mongo queries ### Rendering models In this subchapter, we will try to show the most common cases of rendering a model entries. It would be tedious to list every possible cases, the aim is only to give an overview of what's possible. First we will see the very basics of iterating over a collection of entries and the available logic you can add, then the pagination of results and finally the scoping the query of results. #### Basics The simplest loop is an iteration over your model entries. We loop here on the model ```posts```, the one from the previous [Models basics](#models_basics) subchapter: {% for item in contents.posts %}

{{ item.title }}

{% endfor %} Loop over ```contents.slug_of_your_model```, and for each entry you have access to the custom fields of your model, and also to a list of attributes : ![entries attributes](images/entries_attributes.png) [Reference](http://doc.locomotivecms.com/templates/tags#for-section) **Adding logic** Logic liquid tags let you put some logic inside your loops. - if / else / unless : [Reference](http://doc.locomotivecms.com/templates/tags#if-else-unless-section) - case : [Reference](http://doc.locomotivecms.com/templates/tags#case-section) What about empty fields ? You will certainly have some ```required``` fields, and some not, so how to not display an attribute if it's empty ? An empty field will actually return ```nil```, so you can test it in a ```if``` statement : ```html {% for item in contents.posts %} {% if item.category %}
{{ item.category }}
{% endif %} {% endfor %} ``` **Rendering model's attributes** We will see here how to render each type of attribute. - Simple input : Nothing tricky here, ```title``` is our simple input field : ```html {% for item in contents.posts %}

Title of the post : {{ item.title }}

{% endfor %} ``` - Text : This field has an property ```Text formatting``` HTML or none. In the first case, you can edit the content of it with a WYSIWYG editor, in the other case it's just a textarea. It doesn't change anything for the rendering, in one case the output will be HTML formatted and in the other it will be a simple long string. ```main_text``` is our text field : ```html {% for item in contents.posts %}

Content of the post : {{ item.main_text }}

{% endfor %} ``` - Select : Display the current value of an entry's select field. You will not be able to list all the select options of a model, just the current value. ```category``` is our select field : ```html {% for item in contents.posts %}
  • Category of the post : {{ item.category }}
{% endfor % ``` - Checkbox : This field is a simple boolean one. So you may use it mainly for templating logic, but you can also display it's raw values ('true' or 'false'). Here the checkbox field is ```is_a_report``, let's first use it for logic : ```html {% for item in contents.posts %} {% if item.is_a_report %} This item is a report. {% else %} This item is not a report {% endif %} {% endfor %} ``` And let's display it's raw value : ```html {% for item in contents.posts %} Is it a report ? {{ item.is_a_report }} {% endfor %} ``` - Date : Every entry has by default the properties ```created_at``` and ```updated_at```. But if you need an additional date field, like ```publishing``` in the above example, here is how to render it : ```html {% for item in contents.posts %} Publishing date : {{ item.publishing }} {% endfor %} ``` It will render a raw ISO date, if you need to format it, have a look at the next part "Don't forget Liquid filters". - File : The only thing you can do with a file field is display its url. You must specify the property ```url``` when you display it, like in the above example with an ```attached``` field : ```html {% for item in contents.posts %} Attached file url : {{ item.attached.url }} {% endfor %} ``` One of the usual case is displaying images. In this case, you would simply have to encapsulate the url in an image HTML tag, and if you need to resize it have a look at the next section. ```html {% for item in contents.posts %} {% endfor %} ``` **Usage of capture and assigns** - Capture : Combine a number of strings into a single string and save it to a variable. May be useful to clean your Liquid template if you have a lot of logic and formatting, or simply to keep it DRY. ```html {% for item in contents.posts %} {% capture full_titile %}{{ item.title }} - {{ item.category }}{% endcapture %} {{ full_title }} {% endfor %} ``` [Reference](http://doc.locomotivecms.com/templates/tags#capture-section) - Assigns : Can be used to assign a value to a variable. [Reference](http://doc.locomotivecms.com/templates/tags#assigns-section) **Don't forget Liquid filters** Some Liquid filters will allow you to format your entries attributes. - Resize image : An image rendered from a file's field is done this way : ```html ``` But the the DragonFly gem can resize any image on the fly behind the scene. The url to the dynamically resized image is returned. The processing relies on ImageMagick. Here is an example : ```html ``` Just add the "resize" filter, which takes the ImageMagick geometry string for argument. Here is a list of arguments examples taken from the [Dragonfly doc](http://markevans.github.com/dragonfly/file.ImageMagick.html): ``` '400x300' # resize, maintain aspect ratio '400x300!' # force resize, don't maintain aspect ratio '400x' # resize width, maintain aspect ratio 'x300' # resize height, maintain aspect ratio '400x300>' # resize only if the image is larger than this '400x300<' # resize only if the image is smaller than this '50x50%' # resize width and height to 50% '400x300^' # resize width, height to minimum 400,300, maintain aspect ratio '2000@' # resize so max area in pixels is 2000 '400x300#' # resize, crop if necessary to maintain aspect ratio (centre gravity) '400x300#ne' # as above, north-east gravity '400x300se' # crop, with south-east gravity '400x300+50+100' # crop from the point 50,100 with width, height 400,300 ``` Resized images are cached by default by the Rack::Cache middleware. If you host your LocomotiveCMS on Heroku, Varnish is used instead. For more information, please visit [this](http://markevans.github.com/dragonfly/file.Caching.html) page. [Reference](http://doc.locomotivecms.com/templates/filters#resize-section) - Format / Localize a date : If you have a ```date``` field, or if you simply use the properties ```created_at, updated_at``` of an entry, you may wanna format it. Here it is : ```{{ item.created_at | localized_date: '%d %B' }}``` The ```localized_date``` takes as argument the format string which depends on the current locale. There is also an optional argument 'locale', if you need to force an other locale than the current one : ```{{ item.created_at | localized_date: '%d %B', 'fr' }}``` [Reference](http://doc.locomotivecms.com/templates/filters#localized-date-section) - Format text : There is several filters allowing text formatting, - Underscore : Makes an underscored, lowercase form from the expression in the string. [Reference](http://doc.locomotivecms.com/templates/filters#underscore-section) - Dasherize : Replaces underscores with dashes in the string. [Reference](http://doc.locomotivecms.com/templates/filters#dasherize-section) - Multi_line : Inserts a
tag in front of every \n linebreak character. [Reference](http://doc.locomotivecms.com/templates/filters#multi-line-section) - Concat : Append strings passed in args to the input [Reference](http://doc.locomotivecms.com/templates/filters#concat-section) - Textile : Convert a Markdown-formatted string into HTML. [Reference](http://doc.locomotivecms.com/templates/filters#textile-section) - Embed Flash tag : Embed a flash movie into a page given an url [Reference](http://doc.locomotivecms.com/templates/filters#flash-section) **Displaying Grouped By** In the [Presentation and Advanced options](#presentation_and_advanced_options) of the Basics subchapter, we saw how customize the displaying of a model's entries. One of the options is to group entries by a field, at the condition the field you want group_by entries is a ```select``` type. In frontend, you also have this feature. Here is an example, using the ```posts``` model, the one from the previous [Basics](#models_basics) subchapter. {% for cat in contents.posts.group_by_category %}
{{ cat.name }} : {% for entrie in cat.entries %}
{{ entrie.title }} {% endfor %} {% endfor %} The syntax is the following : ```contents.slug_of_your_model.group_by_field_name```, with the field name corresponding to the ```select``` type field you group by entries. As explained in the documentation : *The method returns an ordered Array of Hash. Each Hash stores 2 keys, name which is the name of the option and entries which is the list of the ordered entries for the option. The Array is ordered based on the order of the options set in the back-office.* So we first loop on each value of the ```select``` type field, which is "cat", and then for each "cat" we loop on each entries of this category. #### Paginate entries LocomotiveCMS comes with a paginate tag. {% paginate contents.posts by 3 %} {% for post in paginate.collection %}

{{ post.title }}

{% endfor %} {% endpaginate %} It creates a ```paginate``` objects with the following attributes : ![paginate attributes](images/paginate_object.png) There are all pretty straightforward, but let's have a look at the ```parts``` attributes, it seems there is everything here to build the navigation of your paginated pages. Here is an example of how we would to it : {% paginate contents.posts by 1 %} {% for post in paginate.collection %}

{{ post.title }}

{% endfor %} {% for page in paginate.parts %} {% if page.is_link %} {{ page.title }} {% else %} {{ page.title }} {% endif %} {% endfor %} {% endpaginate %} Well, that's fine if you want have the control of your markup, but if you don't, there is the filter ```default_pagination``` for rendering a clean pagination navigation without pain : {% paginate contents.posts by 1 %} {% for post in paginate.collection %}

{{ post.title }}

{% endfor %} {{ paginate | default_pagination, next: 'Next', previous: 'Previous' }} {% endpaginate %} Which takes 2 arguments : ![paginate filter](images/paginate_filter.png) And which renders this kind of markup : ```html ``` [Reference : paginate](http://doc.locomotivecms.com/templates/tags#paginate-section) [Reference : default paginate](http://doc.locomotivecms.com/templates/filters#default-pagination-section) #### Scope results http://doc.locomotivecms.com/templates/tags#with-scope-section {% with_scope _slug: params.section %} {% assigns section = contents.sections.first %} {% endwith_scope %} {% for article in section.articles %} ... {% endfor %} with_scope: replace _permalink by _slug in the query https://github.com/locomotivecms/engine/issues/449 ### Templatize a model The idea of a templatized page is that's a view of one instance of a model you specify in the ```templatized``` option of a page. Here is how it works : let's say you have the model ```posts```, the one from the previous [Basics](#models_basics) subchapter. You need to have the following pages structure : ![templatized page archi](images/templatize_archi.png) With : - **posts** page : The templatized mechanism expects to have "models" under a parent folder which makes more sense for SEO purpose. This page can be used for instance to list the products, or could be a redirect page. So for the example, here are the parameters of the page, but there isn't anything specific here : ![templatized page posts](images/templatize_posts1.png) ![templatized page posts 2](images/templatize_posts2.png) Again for the example, we could list in this page the entries of ```posts``` model, and display the link of each entry. You have to build the relative url according this page's slug, and using the ```_permalink``` attribute of a model instance. ![templatized page posts liquid](images/templatize_posts_liquid.png) - **template** page : The template of the templatized model is defined as follow : ![templatized page 1](images/templatize_template1.png) You set the page **posts** as the *parent* of this one. A templatized page **must** have a *parent*, other than index. ![templatized page 2](images/templatize_template2.png) Set the parameter ```Templatized``` as true, and select bellow the model you want to templatize. To follow the example, we will display a full post, using directly the ```posts``` instance (notice the singular) : ![templatized page template liquid](images/templatize_template_liquid.png) **Note: in LocomotiveCMS 2.0, you can't have multiple nested levels of templatized pages. It will be possible with the 2.1 version, if you need this feature now, have a look at [this](https://github.com/locomotivecms/engine/tree/2.1-dev) branch and [this](https://github.com/locomotivecms/engine/pull/125) discussion.** ### Recipe : Public Submission https://github.com/locomotivecms/engine/blob/master/features/public/contact_form.feature //// Very big form, session problem hack : https://github.com/locomotivecms/engine/issues/418 ### Recipe: Create models and content via YAML Locomotive's ability to create models via the UI is really awesome. It's powerful and flexible. The only issue though is what if you maintain a Dev and Prod instance of your CMS (or say Dev, Staging and Prod)? Let's say further that this model doesn't even need a template. How do you keep your new models DRY and push whatever you created in Dev. Simple: you can create your model with a YAML file. You can even create initial content, either as a placeholder or as a default, first time content. Its really awesome. Let's say you want to create a model that allows content editors make small simple entries for news about their company. Our model will be called "News." First you need to create a directory inside your app directory called 'content_types'. Then create a .yml file named for your model. So, following our exmaple you would have this: ``` app/content_types/news.yml ``` Next you are going to represent your model in a similar way to how you do it in the UI. It helps to know YAML, but if your model is simple enough you can probably just follow this example. Here is what we need in our model: A title. A description. A URL to link to the actual news item. A tag (for the type of news items it is such as video, event or news), Whether the item should be featured on the home page. Whether the item should be published. Here is what your file should look like: ``` name: News slug: news description: "stories and news about AliveCor" order_by: pub_date order_direction: desc label_field_name: title fields: - title: label: Title type: string hint: "Short title will display as a heading, keep it short! around 40 characters" - description: label: Description type: text hint: "Short description that will display as text below heading, around 90 characters" - url: label: URL type: string hint: "Type or paste the full URL of the news story we are linking to" - tag: label: Tag type: select hint: "Pick the type of news category this is" select_options: - news - event - video - featured: label: Featured type: boolean hint: "Will appear on homepage, you can only have two news stories there so watch it!" - pub_date: label: Date type: date hint: "The date you enter will be used for display and ordering of the news stories" - publish: label: Publish type: boolean hint: "Set this to yes when you are ready to publish it to the site" ``` You can push this to your Dev instance and see it in the UI now! Badass. A few things to notice: At this time the 'order_direction' doesn't seem to work. For me it always defaulted to 'asc' so for now I just manually do it in the UI. I'm sure this bug will be fixed soon. The other thing to notice is how you create the choices that will appear in the select menu. They will appear in the order you list under 'select_options'. Everything else is quite straighforward. Now on to creating some default content! For this you need to create a directory as a sibling of 'app'. Call this one 'data'. Inside 'data' you must create a .yml file with a name that corresponds to the one you created in the 'content_types' directory. So you will end up with this: ``` data/news.yml ``` Here is an example news story in that file: ``` 'American Onion': title: 'American Onion' description: "Stoner Architect Drafts All-Foyer Mansion" url: 'http://www.theonion.com/articles/stoner-architect-drafts-allfoyer-mansion,1469/' tag: 'news' featured: true pub_date: 2000-08-09 publish: true ``` Now you can push this to your Dev server and it will be visible in the UI and ready for you to reference in your templates. Huzzah! ## LocomotiveCMS Editor lien: Carrier Wave seems to be locking down ThemeAsset to only allow images https://groups.google.com/forum/#!topic/locomotivecms/r7f-54gSg0U ## Using LocomotiveCMS in an existing Rails app sources : https://groups.google.com/d/topic/locomotivecms/suBZggHJ0OI/discussion https://groups.google.com/d/topic/locomotivecms/ZMhKPe78pZM/discussion ## Tips ### Using multi-sites notes : Well, each site is fully independent form the others: they have different pages, domains, content types, ...etc. The ONLY part they have in common is that they have a "administration" access point based on a common domain name as explained in the guide (http://doc.locomotivecms.com/guides/multisites) but since we can use domain aliases, it's not a problem at all. dev locally : https://groups.google.com/d/topic/locomotivecms/nmgDaCdb7Ts/discussion ### Export site *This tip was given [here](https://groups.google.com/d/topic/locomotivecms/odfojqSPeC4/discussion)* In LocomotiveCMS 1.0, there was an export feature, which allowed to export the site (template, models, entries) in a zip. This is no longer the case in LocomotiveCMS 2.0, since it now uses a REST API for push & pull commands. Pushing a site (build with LocomotiveCMS Editor) is described in [this](#locomotive_editor) section and [here](http://doc.locomotivecms.com/editor/deployment). For now, there isn't any pulling script, but still it's possible, following these steps : - Get an auth token ``` curl -d 'email=me@mysite.com&password=secret' 'http://mysite.com/locomotive/api/tokens.json' ``` Obviously change the email and password to be valid credientials. Response will be something like ``` {"token":"dtsjkqs1TJrWiSiJt2gg"} ``` - Use the token to retrieve needed data. Pages : ``` curl 'http://mysite.com/locomotive/api/pages.json?auth_token=dtsjkqs1TJrWiSiJt2gg' ``` Content Types : ``` curl 'http://mysite.com/locomotive/api/content_types.json?auth_token=dtsjkqs1TJrWiSiJt2gg' ``` Content Entries ( assume we have a "Projects" model ) : ``` curl 'http://mysite.com/locomotive/api/content_types/projects/entries.json?auth_token=dtsjkqs1TJrWiSiJt2gg' ``` ### Internationalization LocomotiveCMS handles internationalization easily. We will see how set up several languages, deal with localized pages and models, and add a custom language to an app. #### Setup First thing first, choose the languages you want support in the *Settings* panel : ![internationalization panel](images/internationalization_1.png) Select your locales and save. If you go back in the 'Contents' tab, you will see a locale switcher at the right part of the menu : ![internationalization locale switcher](images/internationalization_2.png) There is several kind of content you may wanna translate : - HTML template of pages - editable contents in pages - content entries (your models entries) #### Page translation A page has as many HTML templates as locales in the app. You can Let's try it : create a page named ```lambda``` with the following template : ```html That's it, a page in english The editable custom text in english. ``` At ```test.herokuapp.com/lambda``` we have : ```html That's it, a page in english The editable custom text in english. ``` Go back to the admin, and switch to the French locale. You will see that LocomotiveCMS indicates pages which are not yet translated : ![internationalizationeng page](images/internationalization_untranslated.png) **Notice : Pages which are not translated are not available in front, they generates a 404.** Edit the ```lambda``` page in French this time, we will edit both the template and the custom content (editable_short_text) : ```html Et voila, une page en francais. {% editable_short_text 'custom-content' %} Le contenu editable en francais. {% endeditable_short_text %} ``` And now at ```test.herokuapp.com/fr/lambda``` (notice the locale prefix) we have the translated page : ```html Et voila, une page en francais. Le contenu editable en francais. ``` Since the default locale of this app is English, the lambda page url is ```test.herokuapp.com/lambda``` but is also ```test.herokuapp.com/en/lambda```. It's your choice to redirect all url to the prefixed one, which may make sense for SEO purpose. #### Models translation Let's create a dummy model with a string field : ![internationalization model](images/internationalization_model.png) See the ```localized``` checkbox ? It's all it takes to localize a model field ! You can localize the fields you want, but not necessarily all custom fields. Let's create an entry in english, save it, and switch to French, and translate this entry. **Notice : LocomotiveCMS indicates which entries are not translated, as it does with pages. But unlike pages, a model entry which isn't translated yet will still appears in front.** Then we go back to our lambda page and display the dummy models entries. Don't forget to update the template in both locales : ```html {% for item in contents.dummy %} {{ item.title }} {% endfor %} ``` As expected, ```test.herokuapp.com/en/lambda``` displays ``` My dummy entry in english. ``` and ```test.herokuapp.com/fr/lambda``` displays ``` Mon entree en francais. ``` #### Internationalize the template **Locale switcher in front** The first thing you may need is a locale switcher, allowing front users to choose their language. There is a Liquid tag for that : ```{% locale_switcher %}```. The params of the tag are explained [here](http://doc.locomotivecms.com/templates/tags#locale-switcher-section). **Variables** There is also global variables relating to locales ([reference](http://doc.locomotivecms.com/templates/objects)) : - ```locale``` gives is the current locale, which you may use in logical statements : ```html {% if locale == 'en' %} English content {% else %} Contenu francais {% endif %} ``` - ```locales``` is an array of the site's locales : ```html {% for loc in locales %} {{ loc }} {% endfor %} ``` - ```default_locale``` is the default site's locale. **Duplicated templates** LocomotiveCMS's behavior allows a different page's template for each locale. So let's say you have developed your site in English only, everything is developed, and then you add a locale : pages will be duplicated in the new locale. It's not possible to specify one template for every locales, so if you don't want to duplicate every page template at each code update, it could be easier to validate the site in one locale, and then at the end, adding the other locales. **Localized links** Your template will have relative links to the app page, but if you don't localize them, they will point to the default localized page, and not the current localized one. Making them localized is pretty simple, just add the locale prefix : ```html click here ``` This way, each link will point to the current localized page. #### Add a language LocomotiveCMS comes with the following available locales : ![internationalization locales](images/internationalization_locales.png) What if you need for example to have a Chinese locale ? There is 2 things to consider with locales : - back-office language - url prefixes (for SEO purpose) There is a way to add custom locale without pain. 1. Install a LocomotiveCMS engine as described in the [reference](http://doc.locomotivecms.com/installation/getting_started) 2. Once you have run ```bundle exec rails g locomotive:install```, go in ```config/initializers/locomotive.rb``` ``` # default locale (for now, only en, de, fr, pt-BR, it and nb are supported) #config.default_locale = :en # available locales suggested to "localize" a site. You will have to pick up at least one among that list. #config.site_locales = %w{en tw} ``` The first line specifies the default locale, but as mentioned, you can't specify a locale which doesn't exists in LocomotiveCMS defaults locales. The second one specifies the list of available locales for the site. That's where you will add your custom locale. You can also remove all unwanted locales, so that they don't appear in the admin panel. 3. When your custom locale is added in the LocomotiveCMS config, you need to add the .png image of the locale flag in the application assets, which will appears in the admin panel (if you don't, it will raises an error) : ![internationalization locales](images/internationalization_locales.png) The .png image is 24x24px. Put this file in the main Rails app, in the folder ```/app/assets/images/locomotive/icons/flags/```. The name of the image must be your locale's name, for example Taiwanese flag must be named ```tw.png```. 4. Precompile your assets : ```bundle exec rake assets:precompile``` 5. Add translations. LocomotiveCMS backoffice have the following translation files (with LOCALE being the shortname of each locale) : ``` admin_ui.LOCALE.yml carrierwave.LOCALE.yml default.LOCALE.yml devise.LOCALE.yml flash.LOCALE.yml formtastic.LOCALE.yml ``` The first thing to know is i18n will look for ```default_locale``` translations if there isn't any translation for the current locale. In the case you want for example a backoffice in English for every locale, you don't need to add any custom translation, since it will fallback to the ```default_locale```. The second thing is, in every case, you need to add the translation of your custom locale name in the ```admin_ui.LOCALE.yml``` above the others : ``` locales: en: English de: German fr: French pt-BR: "Brazilian Portuguese" it: Italian nl: Dutch nb: Norwegian es: Spanish ru: Russian ``` Add here your custom locale, in order to display it in the backoffice panel. *To summarize :* If your custom locales doesn't have to be translated in the admin, define your ```default_locale``` and just add your custom locale name translation in ```admin_ui.DEFAULT_LOCALE.yml```. If you want translate the backoffice in your custom locale, copy these files : ``` admin_ui.LOCALE.yml carrierwave.LOCALE.yml default.LOCALE.yml devise.LOCALE.yml flash.LOCALE.yml formtastic.LOCALE.yml ``` and rename them with your custom locale shortname. Translate their content, and don't forget to add your custom locale name above in ```admin_ui.LOCALE.yml``` : ``` locales: en: English de: German fr: French pt-BR: "Brazilian Portuguese" it: Italian nl: Dutch nb: Nor

本源码包内暂不包含可直接显示的源代码文件,请下载源码包。