Plugin Tutorial » History » Version 5

Jean-Philippe Lang, 2008-08-10 18:14

1 3 Jean-Philippe Lang
{{>toc}}
2 3 Jean-Philippe Lang
3 1 Jean-Philippe Lang
h1. Plugin Tutorial
4 1 Jean-Philippe Lang
5 1 Jean-Philippe Lang
h2. Creating a new Plugin
6 1 Jean-Philippe Lang
7 1 Jean-Philippe Lang
Open up a command prompt and "cd" to your redmine directory, then execute the following command:
8 1 Jean-Philippe Lang
9 3 Jean-Philippe Lang
  % ruby script/generate redmine_plugin Pools
10 1 Jean-Philippe Lang
11 1 Jean-Philippe Lang
The plugin structure is created in @vendor/plugins/redmine_pools@:
12 1 Jean-Philippe Lang
13 1 Jean-Philippe Lang
<pre>
14 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/app/controllers
15 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/app/helpers
16 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/app/models
17 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/app/views
18 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/db/migrate
19 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/lib/tasks
20 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/assets/images
21 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/assets/javascripts
22 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/assets/stylesheets
23 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/lang
24 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/README
25 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/init.rb
26 1 Jean-Philippe Lang
      create  vendor/plugins/redmine_pools/lang/en.yml
27 1 Jean-Philippe Lang
</pre>
28 1 Jean-Philippe Lang
29 2 Jean-Philippe Lang
Edit @vendor/plugins/redmine_pools/init.rb@ too adjust plugin information (name, author, description and version):
30 1 Jean-Philippe Lang
31 1 Jean-Philippe Lang
<pre><code class="ruby">
32 1 Jean-Philippe Lang
require 'redmine'
33 1 Jean-Philippe Lang
34 1 Jean-Philippe Lang
Redmine::Plugin.register :redmine_pools do
35 1 Jean-Philippe Lang
  name 'Pools plugin'
36 1 Jean-Philippe Lang
  author 'John Smith'
37 1 Jean-Philippe Lang
  description 'A plugin for managing pools'
38 1 Jean-Philippe Lang
  version '0.0.1'
39 1 Jean-Philippe Lang
end
40 1 Jean-Philippe Lang
</code></pre>
41 1 Jean-Philippe Lang
42 2 Jean-Philippe Lang
Then restart the application and point your browser to http://localhost:3000/admin/info.
43 1 Jean-Philippe Lang
After logging in, you should see your new plugin in the plugins list:
44 2 Jean-Philippe Lang
45 4 Jean-Philippe Lang
p=. !plugins_list1.png!
46 2 Jean-Philippe Lang
47 1 Jean-Philippe Lang
h2. Generating a controller
48 1 Jean-Philippe Lang
49 3 Jean-Philippe Lang
For now, the plugin doesn't do anything. So let's create a controller for our plugin.
50 3 Jean-Philippe Lang
Go back to the command prompt and run:
51 3 Jean-Philippe Lang
52 3 Jean-Philippe Lang
<pre>
53 3 Jean-Philippe Lang
% ruby script/generate redmine_plugin_controller Pools pools index vote
54 3 Jean-Philippe Lang
      exists  app/controllers/
55 3 Jean-Philippe Lang
      exists  app/helpers/
56 3 Jean-Philippe Lang
      create  app/views/pools
57 3 Jean-Philippe Lang
      create  test/functional/
58 3 Jean-Philippe Lang
      create  app/controllers/pools_controller.rb
59 3 Jean-Philippe Lang
      create  test/functional/pools_controller_test.rb
60 3 Jean-Philippe Lang
      create  app/helpers/pools_helper.rb
61 3 Jean-Philippe Lang
      create  app/views/pools/index.html.erb
62 3 Jean-Philippe Lang
      create  app/views/pools/vote.html.erb
63 3 Jean-Philippe Lang
</pre>
64 3 Jean-Philippe Lang
65 3 Jean-Philippe Lang
A controller @PoolsController@ with 2 actions (@#index@ and @#vote@) is created.
66 3 Jean-Philippe Lang
67 5 Jean-Philippe Lang
Edit @app/controllers/pools_controller.rb@ in @redmine_pools@ directory to implement these 2 actions.
68 3 Jean-Philippe Lang
69 3 Jean-Philippe Lang
<pre><code class="ruby">
70 3 Jean-Philippe Lang
class PoolsController < ApplicationController
71 3 Jean-Philippe Lang
  unloadable
72 3 Jean-Philippe Lang
  
73 3 Jean-Philippe Lang
  @@pools = [ {:id => 1, :title => 'First pool', :question => 'This is the first pool question', :yes => 0, :no => 0},
74 3 Jean-Philippe Lang
              {:id => 2, :title => 'Second pool', :question => 'This is the second pool question', :yes => 0, :no => 0} ]
75 3 Jean-Philippe Lang
76 3 Jean-Philippe Lang
  def index
77 3 Jean-Philippe Lang
    @pools = @@pools
78 3 Jean-Philippe Lang
  end
79 3 Jean-Philippe Lang
80 3 Jean-Philippe Lang
  def vote
81 3 Jean-Philippe Lang
    pool = @@pools.find {|p| p[:id].to_s == params[:id]}
82 3 Jean-Philippe Lang
    # saves the vote
83 3 Jean-Philippe Lang
    pool[params[:answer].to_sym] += 1
84 3 Jean-Philippe Lang
    flash[:notice] = 'Vote saved.'
85 3 Jean-Philippe Lang
    redirect_to :action => 'index'
86 3 Jean-Philippe Lang
  end
87 3 Jean-Philippe Lang
end
88 1 Jean-Philippe Lang
</code></pre>
89 5 Jean-Philippe Lang
90 5 Jean-Philippe Lang
For the sake of this example, we simulate a pool model in our @@@pools@ class variable.
91 5 Jean-Philippe Lang
We could of course use a ActiveRecord model just like we do it in a regular Rails app.
92 3 Jean-Philippe Lang
93 3 Jean-Philippe Lang
Then edit @app/views/pools/index.html.erb@ that will display existing pools:
94 3 Jean-Philippe Lang
95 3 Jean-Philippe Lang
96 3 Jean-Philippe Lang
<pre>
97 3 Jean-Philippe Lang
<h2>Pools</h2>
98 3 Jean-Philippe Lang
99 3 Jean-Philippe Lang
<% @pools.each do |pool| %>
100 3 Jean-Philippe Lang
  <p>
101 3 Jean-Philippe Lang
  <%= pool[:question] %>?
102 3 Jean-Philippe Lang
  <%= link_to 'Yes', {:action => 'vote', :id => pool[:id], :answer => 'yes'}, :method => :post %> (<%= pool[:yes] %>) /
103 3 Jean-Philippe Lang
  <%= link_to 'No', {:action => 'vote', :id => pool[:id], :answer => 'no'}, :method => :post %> (<%= pool[:no] %>)
104 3 Jean-Philippe Lang
  </p>
105 3 Jean-Philippe Lang
<% end %>
106 3 Jean-Philippe Lang
</pre>
107 3 Jean-Philippe Lang
108 3 Jean-Philippe Lang
You can remove @vote.html.erb@ since no rendering is done by the corresponding action.
109 3 Jean-Philippe Lang
110 1 Jean-Philippe Lang
Now, restart the application and point your browser to http://localhost:3000/pools.
111 4 Jean-Philippe Lang
You should see the 2 pools and you should be able to vote for them:
112 4 Jean-Philippe Lang
113 4 Jean-Philippe Lang
p=. !pools1.png!
114 4 Jean-Philippe Lang
115 4 Jean-Philippe Lang
Note that pool results are reset on each request if you don't run the application in production mode, since our pool "model" is stored in a class variable in this example.
116 4 Jean-Philippe Lang
117 4 Jean-Philippe Lang
h2. Extending menus
118 4 Jean-Philippe Lang
119 4 Jean-Philippe Lang
Our controller works fine but users have to know the url to see the pools. Using the Redmine plugin API, you can extend standard menus.
120 4 Jean-Philippe Lang
So let's add a new item to the application menu.
121 4 Jean-Philippe Lang
122 4 Jean-Philippe Lang
h3. Extending the application menu
123 4 Jean-Philippe Lang
124 4 Jean-Philippe Lang
Edit @init.rb@ at the root of your plugin directory to add the following line at the end of the plugin registration block:
125 4 Jean-Philippe Lang
126 4 Jean-Philippe Lang
<pre><code class="ruby">
127 4 Jean-Philippe Lang
Redmine::Plugin.register :redmine_pools do
128 4 Jean-Philippe Lang
  [...]
129 4 Jean-Philippe Lang
  
130 4 Jean-Philippe Lang
  menu :application_menu, :pools, { :controller => 'pools', :action => 'index' }, :caption => 'Pools'
131 4 Jean-Philippe Lang
end
132 4 Jean-Philippe Lang
</code></pre>
133 4 Jean-Philippe Lang
134 4 Jean-Philippe Lang
Syntax is:
135 4 Jean-Philippe Lang
136 4 Jean-Philippe Lang
  menu(menu_name, item_name, url, options={})
137 4 Jean-Philippe Lang
138 4 Jean-Philippe Lang
There are 4 menus that you can extend:
139 4 Jean-Philippe Lang
140 4 Jean-Philippe Lang
* @:top_menu@ - the top left menu
141 4 Jean-Philippe Lang
* @:account_menu@ - the top right menu with sign in/sign out links
142 4 Jean-Philippe Lang
* @:application_menu@ - the main menu displayed when the user is not inside a project
143 4 Jean-Philippe Lang
* @:project_menu@ - the main menu displayed when the user is inside a project
144 4 Jean-Philippe Lang
145 4 Jean-Philippe Lang
Available options are:
146 4 Jean-Philippe Lang
147 4 Jean-Philippe Lang
* @:param@ - the parameter key that is used for the project id (default is @:id@)
148 4 Jean-Philippe Lang
* @:if@ - a Proc that is called before rendering the item, the item is displayed only if it returns true
149 4 Jean-Philippe Lang
* @:caption@ - the menu caption that can be:
150 4 Jean-Philippe Lang
151 4 Jean-Philippe Lang
  * a localized string Symbol
152 4 Jean-Philippe Lang
  * a String
153 4 Jean-Philippe Lang
  * a Proc that can take the project as argument
154 4 Jean-Philippe Lang
155 4 Jean-Philippe Lang
* @:before@, @:after@ - specify where the menu item should be inserted (eg. @:after => :activity@)
156 4 Jean-Philippe Lang
* @:last@ - if set to true, the item will stay at the end of the menu (eg. @:last => true@)
157 4 Jean-Philippe Lang
* @:html_options@ - a hash of html options that are passed to @link_to@ when rendering the menu item
158 4 Jean-Philippe Lang
159 4 Jean-Philippe Lang
In our example, we've added an item to the application menu which is emtpy by default.
160 4 Jean-Philippe Lang
Restart the application and go to http://localhost:3000:
161 4 Jean-Philippe Lang
162 4 Jean-Philippe Lang
p=. !application_menu.png!
163 4 Jean-Philippe Lang
164 4 Jean-Philippe Lang
Now you can access the pools by clicking the Pools tab from the welcome screen.
165 4 Jean-Philippe Lang
166 4 Jean-Philippe Lang
h3. Extending the project menu
167 4 Jean-Philippe Lang
168 4 Jean-Philippe Lang
TODO
169 4 Jean-Philippe Lang
170 4 Jean-Philippe Lang
h2. Adding new permissions
171 4 Jean-Philippe Lang
172 4 Jean-Philippe Lang
TODO
173 4 Jean-Philippe Lang
174 4 Jean-Philippe Lang
h2. Creating a project module
175 4 Jean-Philippe Lang
176 4 Jean-Philippe Lang
TODO