Friday, August 5, 2016

Building Your Startup: Customizing the Meeting View_part2(end)

Initializing New Meeting Sessions

Whenever the user creates a new meeting, we have to load their default settings and copy them to the individual meeting's settings. initializeMeetingSetting is called when a new meeting is created to do this:
  1. public function initializeMeetingSetting($meeting_id,$owner_id) {
  2.   // load meeting creator (owner) user settings to initialize meeting_settings
  3.   $user_setting = UserSetting::find()->where(['user_id' => $owner_id])->one();
  4.   $meeting_setting = new MeetingSetting();
  5.   $meeting_setting->meeting_id = $meeting_id;
  6.   $meeting_setting->participant_add_place=$user_setting->participant_add_place;
  7.   $meeting_setting->participant_add_date_time=$user_setting->participant_add_date_time;
  8.   $meeting_setting->participant_choose_place=$user_setting->participant_choose_place;
  9. $meeting_setting->participant_choose_date_time=$user_setting->participant_choose_date_time;
  10.   $meeting_setting->participant_finalize=$user_setting->participant_finalize; 
  11.   $meeting_setting->save();
  12. }
With meeting settings in place, we're ready to move on to what's actually the bulk of today's work, customizing the meeting views for the owner and participant.

Reviewing the Meeting Owner View
Now, let's consider the state of our meeting view based on the meeting creator or owner. Here's a meeting invitation I've recently created to invite my friend Rob to drinks:


The Command Bar

Before Send and Finalize should be enabled, there must be a person invited and at least one place and time. If there are more than one place and time, one must be chosen for the meeting to be finalized.

The Cancel (X icon) and Edit (pencil icon) meeting buttons are also enabled for creators.

People

For the MVP, we're limiting meeting invitations to one participant at first. So, once a person has been invited, the Add (plus icon) button is disabled.

Places and Date & Times

The creator can add Places and Date & Times up to our site's maximum (e.g. seven per meeting) and they can indicate their availability and acceptance. And, finally, when there is more than one, they can choose which location and time will be used.

Notes

The creator can always add notes to the meeting. Notes allow the creator and participants to communicate with each other.

Ultimately, we'll put the bulk of our work into improving the AJAX functionality so that as the owner chooses places and times, the Send and Finalize buttons are properly enabled (or disabled in some cases).

Here's an example of a meeting with two possible times. The Finalize button can't be enabled until one time is chosen:


Once the choice is made, we'd like to enable the Finalize button via AJAX, sparing the user a page refresh.

Reviewing the Participant View
When we view the invitation from the participant's point of view, there's a lot less initial capability:


The participant can cancel (X icon) their attendance to the meeting and they can specify whether the places and times are acceptable to them, but they can't choose the final place or Finalize the meeting. Also, the data in the You and Them columns are now switched. And, the participant panel is hidden as it's not needed.

Additionally, let's say the meeting was created with settings that allowed the participant to choose the location, date and time but not finalize the meeting. That would need to look like this:


Since there's only one Place, Herkimer Coffee, there's no need for a choice selector. But, where there are two possible times, you can now see the Choose selectors. Still, there is no Finalize button.

It turned out that supporting all of this required a lot of new code to update the system, but this is beginning to dive into the heart of the product—the scheduling meetings user experience. I'll walk you through a handful of the changes that were needed.

Coding the Meeting Requirements
Implementing the Meeting Settings

In the meeting-time and meeting-place panels, we need to use the meeting settings to determine if we need to show the choice selector. In the _panel.php view, it looks like this:
  1. <table class="table">
  2.      <thead>
  3.      <tr class="small-header">
  4.        <td></td>
  5.        <td ><?=Yii::t('frontend','You') ?></td>
  6.        <td ><?=Yii::t('frontend','Them') ?></td>
  7.        <td >
  8.          <?php
  9.           if ($timeProvider->count>1 && ($isOwner || $model->meetingSettings->participant_choose_date_time)) echo Yii::t('frontend','Choose');
  10.          ?>
  11.         </td>
  12.     </tr>
  13.     </thead>
  14.     <?= ListView::widget([ 
  15.            'dataProvider' => $timeProvider, 
  16.            'itemOptions' => ['class' => 'item'], 
  17.            'layout' => '{items}',
  18.            'itemView' => '_list', 
  19.            'viewParams' => ['timeCount'=>$timeProvider->count,'isOwner'=>$isOwner,'participant_choose_date_time'=>$model->meetingSettings['participant_choose_date_time']],           
  20.        ]) ?>
  21.   </table>
We're checking the participant settings and passing them as a parameter to the subsequent _list.php view, which looks like this:
  1. <td style>
  2.       <?php
  3.       if ($timeCount>1) {
  4.         if ($model->status == $model::STATUS_SELECTED) {
  5.             $value = $model->id;
  6.         }    else {
  7.           $value = 0;        
  8.         }      
  9.         if ($isOwner || $participant_choose_date_time) {
  10.           // value has to match for switch to be on
  11.           echo SwitchInput::widget([
  12.               'type' => SwitchInput::RADIO,
  13.               'name' => 'time-chooser',
  14.               'items' => [
  15.                   [ 'value' => $model->id],
  16.               ],
  17.               'value' => $value,
  18.               'pluginOptions' => [  'size' => 'mini','handleWidth'=>60,'onText' => '<i class="glyphicon glyphicon-ok"></i>','offText'=>'<i class="glyphicon glyphicon-remove"></i>'],
  19.               'labelOptions' => ['style' => 'font-size: 12px'],
  20.           ]);            
  21.         }
  22.       }
  23.       ?>
  24.   </td>
If the view is the creator or the participant is allowed to choose the final time, they'll see something like this, the ability to Choose in the right column:


Can the Viewer Send and Finalize the Meeting

I created canSend() and canFinalize() functions, which support the code generally and the AJAX requests to determine the active state of Send and Finalize buttons.
Here's canSend():
  1. public function canSend($sender_id) {
  2.        // check if an invite can be sent
  3.        // req: a participant, at least one place, at least one time
  4.        if ($this->owner_id == $sender_id      
  5.         && count($this->participants)>0
  6.         && count($this->meetingPlaces)>0
  7.         && count($this->meetingTimes)>0
  8.         ) {
  9.          $this->isReadyToSend = true;
  10.        } else {
  11.          $this->isReadyToSend = false;
  12.        }
  13.        return $this->isReadyToSend;
  14.       }
The organizer can't send the meeting invitation until there are participant(s), places and times.
Here's canFinalize():
  1. public function canFinalize($user_id) {
  2.         $this->isReadyToFinalize = false;
  3.         // check if meeting can be finalized by viewer
  4.         // check if overall meeting state can be sent by owner
  5.          if (!$this->canSend($this->owner_id)) return false;
  6.           $chosenPlace = false;
  7.           if (count($this->meetingPlaces)==1) {
  8.             $chosenPlace = true;
  9.           } else {
  10.             foreach ($this->meetingPlaces as $mp) {
  11.               if ($mp->status == MeetingPlace::STATUS_SELECTED) {
  12.                 $chosenPlace = true;
  13.                 break;
  14.               }
  15.             }
  16.           }
  17.           $chosenTime = false;
  18.           if (count($this->meetingTimes)==1) {
  19.             $chosenTime = true;
  20.           } else {
  21.             foreach ($this->meetingTimes as $mt) {
  22.               if ($mt->status == MeetingTime::STATUS_SELECTED) {
  23.                   $chosenTime = true;
  24.                   break;
  25.               }                
  26.             }
  27.           }
  28.           if ($this->owner_id == $user_id || 
  29.           $this->meetingSettings->participant_finalize) {
  30.             if ($chosenPlace && $chosenTime) {
  31.               $this->isReadyToFinalize = true;              
  32.             }
  33.           }                    
  34.         return $this->isReadyToFinalize;
  35.       }
This first checks if the meeting could be sent, because if not, it can't be finalized. Then, it checks to make sure that both a place and time have been chosen. And then, it checks if the viewer is the organizer or the meeting settings allow a participant to finalize the meeting.

Basically, as changes are made, you'll see the state of the Send and Finalize buttons change:


In the meeting view.php, I've embedded JavaScript to support AJAX updates to the state of the Send and Finalize buttons as users change settings for their meeting. When selections of places and times are made, refreshSend() and refreshFinalize() are called and the buttons are appropriately modified:
  1. <?php
  2. if (isset(Yii::$app->params['urlPrefix'])) { 
  3.   $urlPrefix = Yii::$app->params['urlPrefix'];
  4.   } else {
  5.     $urlPrefix ='';
  6.   }
  7. $script = <<< JS
  8. function refreshSend() {
  9.   $.ajax({
  10.      url: '$urlPrefix/meeting/cansend',   
  11.      data: {id: $model->id, 'viewer_id': $viewer},
  12.      success: function(data) {
  13.        if (data)
  14.          $('#actionSend').removeClass("disabled");
  15.         else
  16.         $('#actionSend').addClass("disabled");
  17.        return true;
  18.      }
  19.   });
  20. }
  21.  
  22. function refreshFinalize() {
  23.   $.ajax({
  24.      url: '$urlPrefix/meeting/canfinalize',   
  25.      data: {id: $model->id, 'viewer_id': $viewer},
  26.      success: function(data) {
  27.        if (data)
  28.          $('#actionFinalize').removeClass("disabled");
  29.         else
  30.         $('#actionFinalize').addClass("disabled");
  31.        return true;
  32.      }
  33.   });
  34. }
  35.  
  36. JS;
  37. $position = \yii\web\View::POS_READY;
  38. $this->registerJs($script, $position);
  39. ?>
Reversing the Place and Time Status Selectors

In the current user interface, we show the viewer's place and time selections in the leftmost or first column. The code has to be customized to reverse this when participants are viewing:


To support showing different data in the You and Them columns of the meeting view for Times and Places, the meeting-time and meeting-place _list.php files needed to be updated to dynamically determine what data to display:
  1. <td style>
  2.     <?php
  3.        if ($isOwner) {
  4.          showTimeOwnerStatus($model,$isOwner);
  5.        } else {
  6.          showTimeParticipantStatus($model,$isOwner);
  7.        }
  8.     ?>
  9.   </td>
  10.   <td style>
  11.       <?php
  12.         if (!$isOwner) {
  13.            showTimeOwnerStatus($model,$isOwner);
  14.          } else {
  15.            showTimeParticipantStatus($model,$isOwner);
  16.          }
  17.       ?>
  18.   </td>
For now, I placed these functions within the _panel.php view, which calls _list.php, as they rely on having the SwitchInput widget included in context:
  1. <?php
  2. use \kartik\switchinput\SwitchInput;
  3.  
  4.   function showTimeOwnerStatus($model,$isOwner) {
  5.     foreach ($model->meetingTimeChoices as $mtc) {
  6.       if ($mtc->user_id == $model->meeting->owner_id) {
  7.           if ($mtc->status == $mtc::STATUS_YES)
  8.             $value = 1;
  9.           else
  10.             $value =0;
  11.             echo SwitchInput::widget([
  12.             'type' => SwitchInput::CHECKBOX,              
  13.             'name' => 'meeting-time-choice',
  14.             'id'=>'mtc-'.$mtc->id,
  15.             'value' => $value,
  16.             'disabled' => !$isOwner,
  17.             'pluginOptions' => ['size' => 'mini','onText' => '<i class="glyphicon glyphicon-ok"></i>','offText'=>'<i class="glyphicon glyphicon-remove"></i>','onColor' => 'success','offColor' => 'danger',],
  18.             ]);          
  19.       }
  20.     }
  21.   }
  22.  
  23.   function showTimeParticipantStatus($model,$isOwner) {
  24.     foreach ($model->meetingTimeChoices as $mtc) {
  25.       if (count($model->meeting->participants)==0) break;
  26.       if ($mtc->user_id == $model->meeting->participants[0]->participant_id) {
  27.           if ($mtc->status == $mtc::STATUS_YES)
  28.             $value = 1;
  29.           else if ($mtc->status == $mtc::STATUS_NO)
  30.             $value =0;
  31.           else if ($mtc->status == $mtc::STATUS_UNKNOWN)
  32.             $value =-1;
  33.           echo SwitchInput::widget([
  34.             'type' => SwitchInput::CHECKBOX,          
  35.             'name' => 'meeting-time-choice',
  36.             'id'=>'mtc-'.$mtc->id,
  37.             'tristate'=>true,
  38.             'indeterminateValue'=>-1,
  39.             'indeterminateToggle'=>false,
  40.             'disabled'=>$isOwner,
  41.             'value' => $value,
  42.             'pluginOptions' => ['size' => 'mini','onText' => '<i class="glyphicon glyphicon-ok"></i>','offText'=>'<i class="glyphicon glyphicon-remove"></i>','onColor' => 'success','offColor' => 'danger',],
  43.         ]);          
  44.       }
  45.     }  
  46.   }
  47. ?>
Upcoming Adjustments
Ultimately, there are a lot of improvements to make to this code going forward. In places, I'm making AJAX calls to the server two or three times when I could code these more efficiently into a single request. In other places, I can do more locally with JavaScript. And the user interface will need to keep improving, and the code will need to change to adapt to that. But, from a functional perspective, today's work represents a lot of overall progress towards the MVP.
Written by Jeff Reifman

If you found this post interesting, follow and support us.
Suggest for you:

Learning PHP 7: From the Basics to Application Development

The Complete PHP 7 Guide for Web Developers

Learn PHP 7 This Way to Rise Above & Beyond Competion!

PHP MySQL Database Connections

The Complete PHP with MySQL Developer Course (New)


No comments:

Post a Comment