Sproutcore CRUD Tutorial – Part 5 Delete

Modal View/Edit Dialog with Delete button

To delete a record, the user must first view the record as detailed in the previous post.

The user is then presented with a red Delete button. If the user clicks the button, the user record is deleted and the modal dialog is closed.

Engine

If the user clicks DeleteApp.userEngine.removed() is called.

App.userEngine = SC.Object.create(App.EngineMixin, {

  // Code left out for brevity ...

  /**
   * Removes the user record on the server
   *
   * @param {String} documentID id of record to delete
   * @param {Object} [callbackTarget] Optional callback object
   * @param {Function} [callbackFunction] Optional callback function in the callback object.
   * Signature is: function(documentID, callbackParams, error) {}.
   * documentId is set to the id of the user to be deleted.
   * If there is no error, error will be set to null.
   * @param {Hash} [callbackParams] Optional Hash to pass into the callback function.
   */
  remove: function(documentID, callbackTarget, callbackFunction, callbackParams) {
    var url = '/api/users/' + documentID;
    var context = { documentID: documentID, callbackTarget: callbackTarget, callbackFunction: callbackFunction, callbackParams: callbackParams
    };

    $.ajax({
      type: 'DELETE',
      url: url,
      dataType: 'json',
      contentType: 'application/json; charset=utf-8',
      context: context,
      headers: this._createAjaxRequestHeaders(),
      error: this._ajaxError,
      success: this._endRemove
    });

    return;
  },

  /**
   * Callback from remove()
   *
   * The 'this' object is the context data object.
   *
   * @param {Object} data Deserialized JSON returned form the server
   * @param {String} textStatus Hash of parameters passed into SC.Request.notify()
   * @param {jQueryXMLHttpRequest}  jQuery XMLHttpRequest object
   * @returns {Boolean} YES if successful and NO if not.
   */
  _endRemove: function(data, textStatus, jqXHR) {
    var error = null;
    try {
      var record = App.store.find(App.UserRecord, this.documentID);
      record.destroy();
      App.store.commitRecords();
    }
    catch (err) {
      error = err;
      SC.Logger.error('userEngine.endErase: ' + err);
    }

    // Callback
    if (!SC.none(this.callbackFunction)) {
      this.callbackFunction.call(this.callbackTarget, this.documentID, this.callbackParams, error);
    }

    // Return YES to signal handling of callback
    return YES;
  },

  // Code left out for brevity ...

)};

Template

The handlebar template is in admin_users.html and is the same as for create user. The delete button is defined as App.DialogRemoveButton.

{{#view App.Dialog id="userDialog" style="display:none" class="admin ui-dialog ui-widget ui-widget-content ui-corner-all"}}
  <div class="dialogContainer">
    <div style="height:50px;">
      <ul id="dialogTabs" class="tabs">
       <li class="active"><a id="dialogGeneralTab" href="#dialogGeneralTabContent">General</a></li>
       <li><a id="dialogRolesTab" href="#dialogRolesTabContent">Roles</a></li>
      </ul>
    </div>
    <form id="dialogTabContent" class="tabContent" style="height:290px;">
      <fieldset id="dialogGeneralTabContent" class="active">
        {{view App.DialogUserNameField id="dialogUserNameField" }}
        {{view App.DialogDisplayNameField id="dialogDisplayNameField" }}
        {{view App.DialogEmailAddressField id="dialogEmailAddressField" }}
        {{view App.DialogStatusField id="dialogStatusField" }}
        {{view App.DialogPasswordField id="dialogPasswordField" }}
        {{view App.DialogConfirmPasswordField id="dialogConfirmPasswordField" }}
      </fieldset>
      <fieldset id="dialogRolesTabContent">
        {{view App.DialogIsSystemAdministratorField id="dialogIsSystemAdministratorField" }}
        {{#view App.DialogRepoAccessField id="dialogRepoAccessField" }}
          {{view LabelView}}
          <div class="input">
            {{view DataView}}
            <span class="help-inline">{{help}}</span>
            <div style="padding-top: 8px;">
              {{view AddButtonView id="dialogAddRepositoryAccessButton" class="btn"}}
              {{view RemoveButtonView id="dialogRemoveRepositoryAccessButton" class="btn"}}
            </div>
          </div>
        {{/view}}
      </fieldset>
    </form>
    <div class="clearfix dialogBottomBar">
      <div style="float: left;">
        {{view App.DialogPreviousButton id="dialogPreviousButton" class="btn"}}
        {{view App.DialogNextButton id="dialogNextButton" class="btn"}}
      </div>
      <div style="float: left; padding-left: 250px;">
        {{view App.DialogRemoveButton id="dialogRemoveButton" class="btn danger"}}
      </div>
      <div style="float: right;">
        {{view App.DialogWorkingImage id="workingImage3"}}
        {{view App.DialogOkButton id="dialogOkButton" class="btn primary"}}
        {{view App.DialogCancelButton id="dialogCancelButton" class="btn"}}
        {{view App.DialogApplyButton id="dialogApplyButton" class="btn"}}
      </div>
    </div>
  </div>
{{/view}}

Views

App.DialogRemoveButton is defined in admin_users_page.js:

/**
 * @class
 * Button to show next log entry
 */
App.DialogRemoveButton = App.ButtonView.extend({
  label: '_remove'.loc(),
  title: '_removeTooltip'.loc(),
  isVisibleBinding: SC.Binding.from('App.pageController.canRemove').oneWay().bool(),

  click: function() {
    App.statechart.sendAction('remove');
    return;
  }
});

Bindings

The isVisible property of App.DialogRemoveButton is bound to the canRemove property of App.pageController.

The delete button will only be visible if the record has not been modified and we are not saving or removing the record.

App.pageController = SC.Object.create({
  // Code not displayed for brevity ...

  /**
   * Flag to indicate if we can show the delete button
   *
   * @type Boolean
   */
  canRemove: function() {
    var recordStatus = this.getPath('selectedRecord.status');
    if (!SC.none(recordStatus) && recordStatus === SC.Record.READY_CLEAN && !this.get('isSavingOrRemoving')) {
      return YES;
    }

    return NO;
  }.property('selectedRecord.status', 'isSavingOrRemoving').cacheable(),

  // Code not displayed for brevity ...

)};

State Chart

The state chart for this page is located in admin_users_page.js and defined as App.statechart. This diagram illustrates the states applicable to delete:

  • We start at Not Searching.
  • The Show User action triggers a state change to Showing Dialog.  The modal dialog is displayed.
  • If the user clicks Delete, the remove action is triggered. The state changes to Removing.  If the delete was successful, the state changes to Not Searching and the modal dialog is closed.  If error, the state changes to Showing Dialog and the error is displayed to the user.
  • Removing, the isSavingOrRemoving property of App.pageController is set to true. This will trigger the necessary changes in the views to let the user know that we are waiting on the server for a response.
App.statechart = SC.Statechart.create({
  rootState: SC.State.extend({
  initialSubstate: 'notSearching',

    /**
     * Prompt the user to enter criteria
     */
    notSearching: SC.State.extend({
      enterState: function() {
      },

      exitState: function() {
      },

      search: function() {
        App.pageController.set('errorMessage', '');
        this.gotoState('searching');
      },

      showMore: function() {
        App.pageController.set('errorMessage', '');
        this.gotoState('showingMore');
      },

      showUser: function(recordIndex) {
        recordIndex = parseInt(recordIndex);
        App.pageController.showDialog(recordIndex);
        this.gotoState('showingDialog');
      },

      createUser: function() {
        App.pageController.showDialog(-1);
        this.gotoState('showingDialog');
      }
    }),

    /**
     * Currently showing modal dialog containing selected record
     */
    showingDialog: SC.State.extend({
      enterState: function() {
      },

      exitState: function() {
      },

      /**
       * Delete clicked - delete and close dialog
       */
      remove: function() {
        if (confirm('_admin.user.confirmDelete'.loc())) {
          this.gotoState('removing');
        }
      },

      // More states not shown for brevity ...
    }),

    /**
     * Call server to delete our record
     */
    removing: SC.State.extend({

      enterState: function(ctx) {
        App.pageController.set('isSavingOrRemoving', YES);
        this._startRemove();
      },

      exitState: function() {
        App.pageController.set('isSavingOrRemoving', NO);
      },

      /**
       * Save selected record
       * @param {Boolean} closeWhenFinished If yes, we will exist the dialog of save is successful
       */
      _startRemove: function(closeWhenFinished) {
        try {
          var selectedRecord = App.pageController.get('selectedRecord');
          var documentID = selectedRecord.get(App.DOCUMENT_ID_RECORD_FIELD_NAME);
          App.userEngine.remove(documentID, this, this._endRemove);
        }
        catch (err) {
          // End search with error
          this._endRemove(null, null, err);
        }
      },

      /**
       * Called back when delete is finished
       * @param documentID DocumentID of the user record that was saved
       * @param params context params passed in startSave
       * @param error Error object. Null if no error.
       */
      _endRemove: function(documentID, params, error) {
        if (SC.none(error)) {
          App.pageController.hideDialog();
          this.gotoState('notSearching');
        } else {
          alert('Error: ' + error.message);
          this.gotoState('showingDialog');
        }
      }
    }),

    // More states not shown for brevity ...
  })
});
Categories: Sproutcore

Comments are closed on this post.