S'abonner au Flux RSS

samedi 12 juin 2010

Deletion confirmation with ASP.NET MVC

Here is my approach for deleting stuff with ASP.NET MVC 2.

As mentioned here, the default Delete view isn't quite correct. What I wanted is a deletion solution that:

  • is powered with Ajax for a better user experience
    • we will make a HTTP DELETE on /Items/Delete/12 to delete item #12
  • is accessible for non-JS enabled browsers (we need a confirmation form)
    • when confirmed, the deletion occurs with HTTP POST on /Items/Delete/12 to delete item #12

With those specifications, we can write the following (wrong) controller code:

  1. public class ItemsController : Controller
  2. {
  3. [HttpGet]
  4. public ActionResult Delete(int id) {
  5.  
  6. ViewData.Model = new ConfirmFormModel {
  7. Id = id.ToString(),
  8. BackAction = "Index"
  9. };
  10.  
  11. return View();
  12. }
  13.  
  14. [HttpDelete]
  15. [HttpPost]
  16. public ActionResult Delete(ConfirmFormModel model) {
  17. ViewData.Model = model;
  18.  
  19. // business action here
  20.  
  21. // if we use Ajax, backaction will be null and have
  22. // to prevent a secondary GET request
  23. return model.BackAction == null ? null : RedirectToAction(model.BackAction);
  24. }
  25.  
  26. }
  1. /// <summary>
  2. /// Simple model class to store the Id for a confirmation form.
  3. /// </summary>
  4. public class ConfirmFormModel {
  5.  
  6. /// <summary>
  7. /// Item's ID
  8. /// </summary>
  9. public string Id { get; set; }
  10.  
  11. /// <summary>
  12. /// Confirmation message.
  13. /// </summary>
  14. public string Message { get; set; }
  15.  
  16. /// <summary>
  17. /// The controller action to redirect to if the user wants to cancel.
  18. /// </summary>
  19. public string BackAction { get; set; }
  20.  
  21. }

A created a little model class to store the confirmation message and the item ID; this way I can use it all over my project. You can notice the [HttpDelete] and [HttpPost] on the second Delete() method. Obviously this won't work as asked here. You have to create your own attribute.

So I created the related views with (not-included) javascript. Here is the index:

  1. <!-- Index page -->
  2.  
  3. <ul>
  4. <% for (int i = 0; i < 10; i++) { %>
  5. <li><%= Html.ActionLink("Delete #" + i, "Delete", new { id = i }, new { @class = "deleteLink" })%></li>
  6. <% } %>
  7. </ul>

The delete page where I include a generic partial-view:

  1. <!-- Delete page -->
  2.  
  3. <h3>Confirmation</h3>
  4.  
  5. <% Html.RenderPartial("ConfirmForm", Model); %>

Finally, the partial view:

  1. <!-- Delete form in a partial view -->
  2.  
  3. <% Html.BeginForm(); %>
  4. <fieldset>
  5. <legend>Confirmation</legend>
  6.  
  7. <p><%= Html.Encode(Model.Message ?? "Are you sure you want to delete this?")%></p>
  8.  
  9. <p>
  10. <%= Html.HiddenFor(m => m.Id) %>
  11. <%= Html.HiddenFor(m => m.BackAction) %>
  12. <%= Html.ActionLink("Cancel", Model.BackAction ?? "Index") %>
  13. <input type="submit" name="Continue" value="Continue" />
  14. </p>
  15. </fieldset>
  16. <% Html.EndForm(); %>

Now we have a working solution to confirm some action on a website. Sources attached...

mercredi 6 août 2008

Codes PHP en vrac...

App_Form_Element_DateWithJdatepicker

Un Zend_Form_Element utilisant le datepicker de jQuery françisé :

  1. class App_Form_Element_DateWithJdatepicker extends Zend_Form_Element_Xhtml {
  2.  
  3. public function init() {
  4. $this->addValidator('Date')
  5. ->setAttrib('class', 'datepicker');
  6. }
  7.  
  8. }
  1. $(".datepicker").datepicker({
  2. clearText: 'Effacer', clearStatus: '', closeText: 'Fermer', closeStatus: 'Fermer sans modifier', prevText: '&lt;Préc', prevStatus: 'Voir le mois précédent', nextText: 'Suiv&gt;', nextStatus: 'Voir le mois suivant', currentText: 'Courant', currentStatus: 'Voir le mois courant', monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin', 'Juillet','Août','Septembre','Octobre','Novembre','Décembre'], monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun', 'Jul','Aoû','Sep','Oct','Nov','Déc'], monthStatus: 'Voir un autre mois', yearStatus: 'Voir un autre année', weekHeader: 'Sm', weekStatus: '', dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'], dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'], dayNamesMin: ['Di','Lu','Ma','Me','Je','Ve','Sa'], dayStatus: 'Utiliser DD comme premier jour de la semaine', dateStatus: 'Choisir le DD d MM', dateFormat: 'dd/mm/yy', firstDay: 0, initStatus: 'Choisir la date', isRTL: false,
  3. dateFormat: "yy-mm-dd",
  4. firstDay: 1,
  5. showStatus: true,
  6. showOn: "both",
  7. buttonImage: "<?php echo $front->getBaseUrl() ?>/js/lib/jquery-ui-1.5.2/images/calendar.gif",
  8. buttonImageOnly: true
  9. }
  10. );

Un FlashMessenger personnalisé

...Pour inclure un type aux messages (typiquement une class css pour un affichage personnalisé pour les erreurs, les confirmations, les alertes...).

  1. class App_Controller_Action_Helper_FlashMessenger extends Zend_Controller_Action_Helper_FlashMessenger implements IteratorAggregate, Countable {
  2.  
  3. /**
  4.   * addMessage() - Add a message to flash message
  5.   *
  6.   * @param string $message
  7.   * @return Zend_Controller_Action_Helper_FlashMessenger Provides a fluent interface
  8.   */
  9. public function addMessage($message, $class)
  10. {
  11. $message = array(
  12. 'message' => $message,
  13. 'class' => $class);
  14.  
  15. if (self::$_messageAdded === false) {
  16. self::$_session->setExpirationHops(1, null, true);
  17. }
  18.  
  19. if (!is_array(self::$_session->{$this->_namespace})) {
  20. self::$_session->{$this->_namespace} = array();
  21. }
  22.  
  23. self::$_session->{$this->_namespace}[] = $message;
  24.  
  25. return $this;
  26. }
  27.  
  28. }
  29.  
  30. // Un Zend_Controller_Action personnalisé
  31.  
  32. class App_Controller_Action extends Zend_Controller_Action {
  33.  
  34.  
  35.  
  36. /**
  37. * FlashMessenger
  38. *
  39. * @var Zend_Controller_Action_Helper_FlashMessenger
  40. */
  41. protected $_flashMessenger = null;
  42.  
  43. /**
  44. * Class constructor
  45. *
  46. * @param Zend_Controller_Request_Abstract $request
  47. * @param Zend_Controller_Response_Abstract $response
  48. * @param array $invokeArgs Any additional invocation arguments
  49. * @return void
  50. */
  51. public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $invokeArgs = array()) {
  52. $this->setRequest($request)
  53. ->setResponse($response)
  54. ->_setInvokeArgs($invokeArgs);
  55. $this->_helper = new Zend_Controller_Action_HelperBroker($this);
  56.  
  57. //$this->_flashMessenger = $this->_helper->getHelper('FlashMessenger');
  58. $this->_flashMessenger = new App_Controller_Action_Helper_FlashMessenger();
  59. $this->view->flashMessenger = $this->_flashMessenger;
  60.  
  61. $this->init();
  62. }
  63.  
  64. }

Exemple d'utilisation :

  1. $this->_flashMessenger->addMessage('Les calculs ont été mis à jour.', 'notice');
  2. $this->_flashMessenger->addMessage('Impossible d\'effectuer l'opération demandée.', 'error');
  1. <!-- layout.phtml -->
  2. <div id="messenger">
  3. <?php
  4. if (is_object($this->flashMessenger)) :
  5. $messages = $this->flashMessenger->getMessages();
  6. foreach ($messages as $m) {
  7. if (is_array($m)) {
  8. echo "\t",'<p class="',$m['class'],'">',$m['message'],'</p>',"\n";
  9. } else {
  10. echo "\t",$m,"\n";
  11. }
  12. }
  13. endif;
  14. ?>
  15. </div>