1 // The Antville Project
  2 // http://code.google.com/p/antville
  3 //
  4 // Copyright 2007-2011 by Tobi Schäfer.
  5 //
  6 // Copyright 2001–2007 Robert Gaggl, Hannes Wallnöfer, Tobi Schäfer,
  7 // Matthias & Michael Platzer, Christoph Lincke.
  8 //
  9 // Licensed under the Apache License, Version 2.0 (the ``License'');
 10 // you may not use this file except in compliance with the License.
 11 // You may obtain a copy of the License at
 12 //
 13 //    http://www.apache.org/licenses/LICENSE-2.0
 14 //
 15 // Unless required by applicable law or agreed to in writing, software
 16 // distributed under the License is distributed on an ``AS IS'' BASIS,
 17 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 18 // See the License for the specific language governing permissions and
 19 // limitations under the License.
 20 //
 21 // $Revision$
 22 // $LastChangedBy$
 23 // $LastChangedDate$
 24 // $URL$
 25 
 26 /**
 27  * @fileOverview Defines the Poll prototype
 28  */
 29 
 30 markgettext("Poll");
 31 markgettext("poll");
 32 
 33 /**
 34  * @function
 35  * @param {String} ctor
 36  * @returns {String[]}
 37  * @see defineConstants
 38  */
 39 Poll.getStatus = defineConstants(Poll, markgettext("closed"), markgettext("open"));
 40 
 41 /**
 42  * 
 43  */
 44 Poll.remove = function() {
 45    if (this.constructor === Poll) {
 46       HopObject.remove.call(this);
 47       this.remove();
 48    }
 49    return;
 50 }
 51 
 52 /**
 53  * @name Poll
 54  * @constructor
 55  * @param {String} question
 56  * @property {Choice[]} _children
 57  * @property {String} closed
 58  * @property {Date} created
 59  * @property {User} creator
 60  * @property {Date} modified
 61  * @property {User} modifier
 62  * @property {String} question
 63  * @property {Site} site
 64  * @property {String} status
 65  * @property {Vote[]} votes
 66  * @extends HopObject
 67  */
 68 Poll.prototype.constructor = function(question) {
 69    this.question = question;
 70    this.creator = this.modifier = session.user;
 71    this.created = this.modified = new Date;
 72    return this;
 73 }
 74 
 75 /**
 76  * 
 77  * @param {String} action
 78  * @returns {Boolean}
 79  */
 80 Poll.prototype.getPermission = function(action) {
 81    if (!this.site.getPermission("main")) {
 82       return false;
 83    }
 84    switch (action) {
 85       case ".":
 86       case "main":
 87       return !!session.user;
 88       case "result":
 89       return true;
 90       case "edit":
 91       return Membership.require(Membership.OWNER) ||
 92             User.require(User.PRIVILEGED);
 93       case "rotate":
 94       case "delete":
 95       return this.creator === session.user || 
 96             Membership.require(Membership.MANAGER) ||
 97             User.require(User.PRIVILEGED);            
 98    }
 99    return false;
100 }
101 
102 /**
103  * 
104  * @param {String} name
105  * @returns {Object}
106  */
107 Poll.prototype.getFormOptions = function(name) {
108    switch (name) {
109       case "status":
110       return Poll.getStatus();
111    }
112    return;
113 }
114 
115 Poll.prototype.main_action = function() {
116    if (this.status !== Poll.OPEN) {
117       res.redirect(this.href("result"));
118       return;
119    }
120    if (req.postParams.vote) {
121       try {
122          this.vote(req.postParams);
123          res.message = gettext("Thanks, your vote was registered. You can change your mind until the poll is closed.");
124          res.redirect(this.href("result"));
125       } catch (ex) {
126          res.message = ex;
127          app.log(ex);
128       }
129    }
130    res.data.action = this.href();
131    res.data.title = gettext("Poll: {0}", this.question);
132    res.data.body = this.renderSkinAsString("$Poll#main", {header: true});
133    this.site.renderSkin("Site#page");
134    return;
135 }
136 
137 /**
138  * 
139  * @param {Object} data
140  */
141 Poll.prototype.vote = function(data) {
142 	if (!data.choice) {
143 	   throw Error(gettext("You did not vote, yet. You can vote until the poll is closed."));
144 	}
145 	var choice = this.get(data.choice);
146 	var vote = session.user && this.votes.get(session.user.name);
147 	if (vote) {
148 		vote.choice = choice;
149 		vote.modified = new Date;
150 	} else {
151 		this.votes.add(new Vote(choice));
152 	}
153 	return;
154 }
155 
156 Poll.prototype.edit_action = function() {
157    if (req.postParams.save) {
158       try {
159          this.update(req.postParams);
160          res.message = gettext("The poll was updated successfully.");
161          res.redirect(this.href());
162       } catch (ex) {
163          res.message = ex;
164          app.log(ex);
165       }
166    }
167    res.data.action = this.href(req.action);
168    res.data.title = gettext("Edit Poll: {0}", this.question);
169    res.data.body = this.renderSkinAsString("$Poll#edit");
170    this.site.renderSkin("Site#page");
171    return;
172 }
173 
174 /**
175  * 
176  * @param {Object} data
177  */
178 Poll.prototype.update = function(data) {
179    var choices = [];
180    for each (var title in data.title_array) {
181       if (title = title.trim()) {
182          choices.push(title);
183       }
184    }
185    if (choices.length < 2 || !data.question) {
186       throw Error(gettext("Please fill out the whole form to create a valid poll."));
187    }
188    var size = this.size();
189    // Update or remove choices
190    for (var i=size-1; i>-1; i-=1) {
191       var choice = this.get(i);
192       var title = choices[i];
193       if (title) {
194          choice.title = title;
195          choice.touch();
196       } else {
197          Choice.remove.call(choice);
198       }
199    }
200    // Add new choices
201    for (var i=size; i<choices.length; i+=1) {
202       this.add(new Choice(choices[i]));
203    }
204    if (data.save !== Poll.CLOSED) {
205       delete this.closed;
206    } else if (this.status === Poll.OPEN) {
207       this.closed = new Date;
208    }
209    this.status = data.save;
210    this.question = data.question;
211    this.touch();
212    return;
213 }
214 
215 Poll.prototype.result_action = function() {
216    res.data.title = gettext('Poll Results: {0}', this.question);
217    res.data.body = this.renderSkinAsString("$Poll#results", {header: true});
218    this.site.renderSkin("Site#page");
219    return;
220 }
221 
222 Poll.prototype.rotate_action = function() {
223    if (this.status === Poll.CLOSED) {
224       this.status = Poll.OPEN;
225    } else if (this.status === Poll.OPEN) {
226       this.status = Poll.CLOSED;
227       this.closed = new Date;
228    }
229    this.touch();
230    return res.redirect(this.href());
231 }
232 
233 /**
234  * @returns {String}
235  */
236 Poll.prototype.getConfirmText = function() {
237    return gettext("You are about to delete a poll by user {0}.", 
238          this.creator.name);
239 }
240 
241 /**
242  * 
243  * @param {Object} param
244  * @param {String} action
245  * @param {String} text
246  * @see HopObject#link_macro
247  */
248 Poll.prototype.link_macro = function(param, action, text) {
249    switch (action) {
250       case ".":
251       case "main":
252       if (this.status === Poll.CLOSED) {
253          return;
254       }
255       break;
256       case "rotate":
257       if (this.status === Poll.OPEN) {
258          text = gettext("Stop");
259       } else {
260          text = this.closed ? gettext("Re-run") : gettext("Run");  
261       }
262       break;
263   }
264    return HopObject.prototype.link_macro.call(this, param, action, text);
265 }
266 
267 /**
268  * 
269  * @param {Object} param
270  * @param {String} name
271  * @see HopObject#link_macro
272  */
273 Poll.prototype.input_macro = function(param, name) {
274    switch (name) {
275       case "choices":
276       var index = 0;
277       var add = function(choice) {
278          index += 1;
279          return choice.renderSkin("$Choice#edit", {index: index});
280       };
281       var choices;
282       if (choices = req.postParams.title_array) {
283          while (choices.length < 2) {
284             choices.push(null);
285          }
286          choices.forEach(function(title) {
287             return add(new Choice(title));
288          });
289       } else {
290          this.forEach(function() {
291             return add(this);
292          });
293       }
294       return;
295    }
296    return HopObject.prototype.input_macro.apply(this, arguments);
297 }
298 
299 /**
300  * 
301  */
302 Poll.prototype.votes_macro = function() {
303    return this.votes.size();
304 }
305 
306 /**
307  * 
308  * @param {Object} param
309  * @param {String} format
310  */
311 Poll.prototype.closed_macro = function(param, format) {
312    if (this.status === Poll.CLOSED) {
313       res.write(formatDate(this.closed, param.format || format));
314    }
315    return;
316 }
317