Plugin Tutorial » History » Version 4

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

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 3 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 3 Jean-Philippe Lang
</code></pre>
89 3 Jean-Philippe Lang
90 3 Jean-Philippe Lang
Then edit @app/views/pools/index.html.erb@ that will display existing pools:
91 3 Jean-Philippe Lang
92 3 Jean-Philippe Lang
93 3 Jean-Philippe Lang
<pre>
94 3 Jean-Philippe Lang
<h2>Pools</h2>
95 3 Jean-Philippe Lang
96 3 Jean-Philippe Lang
<% @pools.each do |pool| %>
97 3 Jean-Philippe Lang
  <p>
98 3 Jean-Philippe Lang
  <%= pool[:question] %>?
99 3 Jean-Philippe Lang
  <%= link_to 'Yes', {:action => 'vote', :id => pool[:id], :answer => 'yes'}, :method => :post %> (<%= pool[:yes] %>) /
100 3 Jean-Philippe Lang
  <%= link_to 'No', {:action => 'vote', :id => pool[:id], :answer => 'no'}, :method => :post %> (<%= pool[:no] %>)
101 3 Jean-Philippe Lang
  </p>
102 3 Jean-Philippe Lang
<% end %>
103 3 Jean-Philippe Lang
</pre>
104 3 Jean-Philippe Lang
105 3 Jean-Philippe Lang
You can remove @vote.html.erb@ since no rendering is done by the corresponding action.
106 3 Jean-Philippe Lang
107 1 Jean-Philippe Lang
Now, restart the application and point your browser to http://localhost:3000/pools.
108 4 Jean-Philippe Lang
You should see the 2 pools and you should be able to vote for them:
109 4 Jean-Philippe Lang
110 4 Jean-Philippe Lang
p=. !pools1.png!
111 4 Jean-Philippe Lang
112 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.
113 4 Jean-Philippe Lang
114 4 Jean-Philippe Lang
h2. Extending menus
115 4 Jean-Philippe Lang
116 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.
117 4 Jean-Philippe Lang
So let's add a new item to the application menu.
118 4 Jean-Philippe Lang
119 4 Jean-Philippe Lang
h3. Extending the application menu
120 4 Jean-Philippe Lang
121 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:
122 4 Jean-Philippe Lang
123 4 Jean-Philippe Lang
<pre><code class="ruby">
124 4 Jean-Philippe Lang
Redmine::Plugin.register :redmine_pools do
125 4 Jean-Philippe Lang
  [...]
126 4 Jean-Philippe Lang
  
127 4 Jean-Philippe Lang
  menu :application_menu, :pools, { :controller => 'pools', :action => 'index' }, :caption => 'Pools'
128 4 Jean-Philippe Lang
end
129 4 Jean-Philippe Lang
</code></pre>
130 4 Jean-Philippe Lang
131 4 Jean-Philippe Lang
Syntax is:
132 4 Jean-Philippe Lang
133 4 Jean-Philippe Lang
  menu(menu_name, item_name, url, options={})
134 4 Jean-Philippe Lang
135 4 Jean-Philippe Lang
There are 4 menus that you can extend:
136 4 Jean-Philippe Lang
137 4 Jean-Philippe Lang
* @:top_menu@ - the top left menu
138 4 Jean-Philippe Lang
* @:account_menu@ - the top right menu with sign in/sign out links
139 4 Jean-Philippe Lang
* @:application_menu@ - the main menu displayed when the user is not inside a project
140 4 Jean-Philippe Lang
* @:project_menu@ - the main menu displayed when the user is inside a project
141 4 Jean-Philippe Lang
142 4 Jean-Philippe Lang
Available options are:
143 4 Jean-Philippe Lang
144 4 Jean-Philippe Lang
* @:param@ - the parameter key that is used for the project id (default is @:id@)
145 4 Jean-Philippe Lang
* @:if@ - a Proc that is called before rendering the item, the item is displayed only if it returns true
146 4 Jean-Philippe Lang
* @:caption@ - the menu caption that can be:
147 4 Jean-Philippe Lang
148 4 Jean-Philippe Lang
  * a localized string Symbol
149 4 Jean-Philippe Lang
  * a String
150 4 Jean-Philippe Lang
  * a Proc that can take the project as argument
151 4 Jean-Philippe Lang
152 4 Jean-Philippe Lang
* @:before@, @:after@ - specify where the menu item should be inserted (eg. @:after => :activity@)
153 4 Jean-Philippe Lang
* @:last@ - if set to true, the item will stay at the end of the menu (eg. @:last => true@)
154 4 Jean-Philippe Lang
* @:html_options@ - a hash of html options that are passed to @link_to@ when rendering the menu item
155 4 Jean-Philippe Lang
156 4 Jean-Philippe Lang
In our example, we've added an item to the application menu which is emtpy by default.
157 4 Jean-Philippe Lang
Restart the application and go to http://localhost:3000:
158 4 Jean-Philippe Lang
159 4 Jean-Philippe Lang
p=. !application_menu.png!
160 4 Jean-Philippe Lang
161 4 Jean-Philippe Lang
Now you can access the pools by clicking the Pools tab from the welcome screen.
162 4 Jean-Philippe Lang
163 4 Jean-Philippe Lang
h3. Extending the project menu
164 4 Jean-Philippe Lang
165 4 Jean-Philippe Lang
TODO
166 4 Jean-Philippe Lang
167 4 Jean-Philippe Lang
h2. Adding new permissions
168 4 Jean-Philippe Lang
169 4 Jean-Philippe Lang
TODO
170 4 Jean-Philippe Lang
171 4 Jean-Philippe Lang
h2. Creating a project module
172 4 Jean-Philippe Lang
173 4 Jean-Philippe Lang
TODO