Link to home
Start Free TrialLog in
Avatar of Bruce Gust
Bruce GustFlag for United States of America

asked on

How can one button be used for two different functionalities in the same page?

I've got a form that looks like this:

<form id="user-form" class="needs-validation" method="post" action="/profile/save" novalidate>

...

      <div class="password-wrapper">
        <button type="button" class="btn btn-primary btn-change-password"><i class="fas fa-fw fa-key"></i> Change Password</button>
        <div class="password-template d-none">
			<div class="row">
				<div class="col-lg-12">
				To change your password, begin by entering your current password first...<br><br>
					<div class="md-form">
						<input type="password" id="current-password" name="current_password" class="form-control" value="">
						<label for="current_password"><a id="expose-current" href="#" style="cursor:text;" tabindex="-1">Current Password</a></label>
					</div>
				</div>
			</div>
			<!-- new row -->
			<div class="row">
				<div class="col-lg-6" style="margin-top:-25px;">
					<div class="md-form">              
						<input type="password" id="user-password" name="user[password]" class="form-control" required />
						<label for="user-password"><a id="exposure" href="#" style="cursor:text;" tabindex="-1">Password*</a></label>
					</div>
				</div>
				<div class="col-lg-6" style="margin-top:-25px;">
					<div class="md-form">
						<input type="password" id="user-confirm" class="form-control" required />
						<label for="user-password">Confirm*</label>
					</div>
				</div>
			</div>
			<div class="row">
				<div class="col-lg-12">
					<div style="font-size:9pt; background-color:#3d5fb5; border-radius:10pt; width:90%; margin:auto; height:auto; padding:5px; text-align:center; color:#fff;">Hover over the "Password" and "Current Password" labels to see the actual characters that you have inputted.</div>
				</div>
			</div>
			<div class="row">
				<div class="col-lg-12">
					<div class="form-group">
						<label>&nbsp;</label><br />
						<button type="submit" class="btn btn-primary save"><i class="fas fa-fw fa-key"></i> Change Password</button>
						<button type="button" class="btn btn-danger btn-cancel-password">Cancel</button>
					</div>
				</div>
			</div><!--end of row -->
			<p>Please ensure that your password meets the following requirements:</p>
			<ul class="password-requirements">
			<li data-reg="upper">At least 1 uppercase letter.</li>
			<li data-reg="lower">At least 1 lowercase letter.</li>
			<li data-reg="number">At least 1 number.</li>
			<li data-reg="special">At least 1 of the following special characters: !@#$%^&*</li>
			<li data-reg="total">At least 8 total characters.</li>
			</ul>
		</div><!-- end of password-wrapper -->

...


<button type="submit" id="btn-save" class="btn btn-success"><i class="fas fa-fw fa-check"></i> Save</button>

</form>

Open in new window


It's a form that has a hidden section which you can reveal by clicking on "change password." Here's how it looks:

User generated image
And then once you click on "change password,"  you get this:

User generated image
After you've clicked on "change password," that same button can now be used to submit the form content. You can also use the "save" button.

My question is: How can the same "change password" button be used in two different ways?

The answer lies in the following "user-form.js" file.

/*

HOW TO USE:

To Show: $(window).trigger('user-form-show', ['userId']);
To Hide: $(window).trigger('user-form-hide');

On Complete:

$(window).on('user-form-complete', function(e, data){
  console.log('Form Data: ', data);
});

Be aware that any reference to a specific id on this page will most likely have to be identified in the context of "$form." Since it's not a part of the DOM, but it's being generated dynamically, you're going to have to be more specific that you might otherwise be normally. For example, the code that changes the "user-password" field from "password" are triggered on the "user-from.html.twig" file using "<label for="user-password"><a id="exposure" href="#" style="cursor:text;" tabindex="-1">Passwords*</a></label>." For the system to "see" the "exposure" id, I had to write it like "$form.on('mouseover', '#exposure', function(e){." 

*/

$(document).ready(function(){
	
    $("#chk-user-active").on('change', function () {
        $('#user-active').val(this.checked);        
    });
  var $uModal = $('#modal-user-form'),
      $form = $('#user-form'),
      $passwordButton = $form.find('.btn-change-password').clone(),
      $passwordForm = $form.find('.password-template').clone().removeClass('d-none'),
      data = [],
      loadedUserId = '',
      passwordRegex = {
        upper: /[A-Z]{1,}/,
        lower: /[a-z]{1,}/,
        number: /[0-9]{1,}/,
        special: /[\!\@\#\$\%\^\&\*]{1,}/,
        total: /.{8,}/
      };

  $form.find('.field-phone').mask('000-000-0000');

  $form.find('.password-template').remove();

  $(window).on('user-form-show', function(e, userId, opts){
    // prep the modal
    $uModal.find('.loading').show();
    $uModal.find('.main').hide();

    // show the modal loading screen
    $.magnificPopup.open({
      alignTop: true,
      items: {
        src: $uModal, // can be a HTML string, jQuery object, or CSS selector
        type: 'inline'
      }
    });

    // clear the cached data
    data = {};

    // clear the form from any previous data
    clearForm();
           

    // load the data if a valid id is provided
    if (typeof userId != 'undefined' && userId != 'new') {
      loadFormData(userId);
    } else {
      // set the id for a new user
        loadedUserId = 'new';
        // load drag & drop members 
        teamDragula(userId);

        $form.find('#user-role').attr('data-user-id', loadedUserId);
      // force the password fields
      $form.find('.btn-change-password').replaceWith( $passwordForm.clone() );
      $form.find('.btn-cancel-password').closest('.col').remove();

      // show the form
      $uModal.find('.loading').hide();
      $uModal.find('.main').show();
    }
  });

  $(window).on('user-form-hide', function(){
    $.magnificPopup.close();
  });

    $form.on('submit', function (e) {
		console.log("here");
        let teamArray = [];
        $form.find('#drag-team').find('a').each(function () {
            let uid = $(this).attr('data-user-id');
            if (uid) {teamArray.push(uid);}
        });
        teamArray = $.unique(teamArray);
        $form.find('#user-team').val(JSON.stringify(teamArray));
        $form.find('#user-avatar').val() ? $form.find('#user-avatar').val() : $form.find('#user-avatar').val('https://ui-avatars.com/api/?name=' + $form.find('#user-first-name').val() + '%20' + $form.find('#user-last-name').val()+'&amp;background=33b5e5&amp;size=200&amp;color=000000');
    var $f = $(this),
        $btn = $f.find('button[type="submit"]'),
        html = $btn.html(),
        formData = $f.serialize(),
          route = (loadedUserId !== '') ? $form.attr('action') + '/' + loadedUserId : $form.attr('action');
      // include unchecked checkboxes. use filter to only include unchecked boxes.
      $.each($('.permission-section input[type=checkbox]').filter(function (idx) {
          return $(this).prop('checked') === false;
      }),
          function (idx, el) {
              // attach matched element names to the formData with a chosen value.                
              formData += '&' + $(el).attr('name') + '=' + false;
          }
      );
      

    e.preventDefault();
    e.stopPropagation();

    $f.addClass('was-validated');

    if ($f[0].checkValidity() === false) {
      addMsg('Please check the highlighted fields!');
      return false;
    }

    $btn.html('<i class="fas fa-fw fa-sync-alt fa-spin"></i> Saving...');

    // validate the form and save
    $.post(route, formData, function(resp){
      if (resp.error) {
        clearMsg();
        addMsg(resp.msg);
        return false;
      }

      // finish up
      $(window).trigger('user-form-hide');
      $(window).trigger('user-form-complete', [formData]);
    }, 'json').fail(function(){
      clearMsg();
      addMsg('An unexpected error was encountered while trying to process your request. Please try again.');
    }).always(function(){
      $btn.html(html);
    });
  });

  $form.on('click', '.btn-change-password', function(){
    $(this).replaceWith( $passwordForm.clone() );
  });

  $form.on('click', '.btn-cancel-password', function(){
    $(this).closest('.password-template').replaceWith( $passwordButton.clone() );
  });

  $form.on('keyup', '#user-password', function(e){
    for (var k in passwordRegex) {
      if ( !passwordRegex[k].test(this.value) ) {
        $form.find('ul.password-requirements > li[data-reg="'+k+'"]').removeClass('text-success').addClass('text-danger');
      } else {
        $form.find('ul.password-requirements > li[data-reg="'+k+'"]').removeClass('text-danger').addClass('text-success');
      }
    }
  });

  $uModal.on('click', '#user-form-cancel', function(){
    $(window).trigger('user-form-hide');
  });
        
    var reportsTo=dragula({
        revertOnSpill: true
    }).on('drop', function (el, target, source, sibling) {               
        // something goes here
        });
    $form.find('.dragula').each(function (i) {
        reportsTo.containers.push(this);
    });

  function loadFormData(userId) {
    $.get('/users/' + userId, {}, function(resp){
      if (resp.error) {
        $(window).trigger('user-form-hide');
        return CAMS.alert('Error', resp.msg);
      }

      // deconstruct from ease of use
      var u = resp.data,
          $sRow, html, file;

      // set the id for saving
      loadedUserId = userId;

      // load the form
      $form.find('#user-first-name').val( u.firstName );
      $form.find('#user-last-name').val( u.lastName );
      $form.find('#user-email').val( u.email );
      $form.find('#user-mobile').val( (typeof u.mobile != 'undefined') ? u.mobile : '' );
        $form.find('#user-settings-timezone').val(u.settings.timezone);
        let avtrSrc = '/assets/images/upload-avatar.png';  
        if (typeof u.avatar != 'undefined') {
            if (u.avatar.length > 0) {
                $form.find('.avatar-picker').find('img').attr('src', u.avatar);  
            }
        }
        $form.find('#user-avatar').val(u.avatar);
              
        $form.find('#user-signature').val(u.signature);
        if (typeof u.signature != 'undefined') {
            $form.find('.signature-link').html('<img src="' + u.signature + '"/>');
        } else {
            $form.find('.signature-link').html('<span>______________</span>');
        }
        $form.find('#user-active').val(u.active);
        if (u.active == true) {
            $form.find('#chk-user-active').prop('checked',true);
        } else {
            $form.find('#chk-user-active').prop('checked', false);
        }
        if (typeof u.role != 'undefined') {
            rolePermissions(u.role, u.permissions);
            $form.find('#user-role').attr('data-user-id', userId);
            $form.find('#user-role').val(u.role);
            teamDragula(userId, u.role);
        }
        if (typeof $form.find('#user-account') != 'undefined') {
            $form.find('#user-account').val(u.account);
        }

      // show the form
      $uModal.find('.loading').hide();
      $uModal.find('.main').show();
    }, 'json').fail(function(){
      $(window).trigger('user-form-hide');
      addMsg('An unexpected error was encountered while trying to load your user. Please refresh the page to try again.');
    });
  }

  function clearForm() {
    clearMsg();
    $form.removeClass('was-validated');
    $form.find('input[type!="hidden"]').val('');
      $form.find('.password-wrapper').empty().append($passwordButton.clone());
      $form.find('.avatar-picker').find('img').attr('src', '/assets/images/upload-avatar.png');   
      $form.find('.signature-link').html('<span>______________</span>');
      $form.find('#user-avatar').val('');//hidden field
      $form.find('#user-signature').val('');//hidden field
      $form.find('#user-active').prop('checked', false);
      $form.find('#user-role').val('Sales Representative');
      rolePermissions('Sales Representative');
  }

  function addMsg(txt, type) {
    type = (typeof type == 'undefined') ? 'danger' : type;

    $form.find('.user-messages').append('<div class="alert alert-'+type+'">'+txt+'</div>');
  }

  function clearMsg() {
    $form.find('.user-messages').empty();
  }

    $('#modal-user-form').find('#user-role').on('change', function () {
        teamDragula($(this).attr('data-user-id'),$(this).val());
        rolePermissions($(this).val());
    });
    function teamDragula(userId, role = 'Sales Representative') {
        let teamTitle = '', availableTitle = '', roleMember = '',
            $dragDropSection = $form.find('.team-dragula');
        $dragDropSection.html('Please wait...');
        if (role == 'Sales Manager') {
            teamTitle='Team';
            availableTitle = 'Members';
            roleMember = 'Sales Representative';
        } else {
            teamTitle='Reports to';
            availableTitle = 'Managers';
            roleMember = 'Sales Manager';
        }
        $dragDropSection.html('<div class="col-6 col-small-12  animated zoomIn">'+
                '<h3 class="team-title">'+teamTitle+'</h3><hr />'+
                '<div class="dragula" id="drag-team" style="height: 100px; border: 2px dashed #eef0f3; overflow: auto; min-height: 100px; cursor:grab;"></div>'+                
            '<div class="text-muted"><small>Drag available users avatar and Drop into above to <span class="text-success">ADD</span>. Drag avatar & Drop on available members to <span class="text-danger">REMOVE</span>.</small></div>'+
            '</div>' +
            '<div class="col-2 col-small-12"></div>' +
            '<div class="col-4 col-small-12 animated zoomIn">' +
                '<h3>Available <span class="available-title">'+availableTitle+'</span></h3><hr />'+
                '<div class="scrollbar-macosx drag-avb-col" style="height: 100px; border: 2px dashed #eef0f3; overflow: auto; min-height: 100px; cursor:grab;"><div class="dragula" id="drag-members" style="cursor:grab;"></div></div>'+
            '</div>');
        let $team = $form.find('#drag-team'), $members = $form.find('#drag-members');
        $team.html('Loading...');
        $members.html('Loading...');
        $.post('/users/team/' + userId, { role: role }, function (resp) {
            if (resp.error) {
                $(window).trigger('user-form-hide');
                return CAMS.alert('Error', resp.msg);
            }
            // deconstruct from ease of use
            var u = resp.data;
            // load the members available
            $members.html('');
            u.members.forEach((user) => {
                user.avatar = user.avatar ? user.avatar : 'https://ui-avatars.com/api/?name=' + user.firstName + ' ' + user.lastName + '&background=' + getRandomColor() + '&size=200&color=000000';
                $members.append(
                    '<a href="javascript:void(0);" data-user-id="' + user._id + '" data-toggle="tooltip" title="' + user.firstName + ' ' + user.lastName + '" style="cursor:grab;">' +
                    '<img src="' + user.avatar + '" title="' + user.firstName + ' ' + user.lastName + '" class="rounded-circle m-1" width="32px"/></a>'
                );
            });
            // load the team
            $team.html('');
            u.team.forEach((user) => {
                user.avatar = user.avatar ? user.avatar : 'https://ui-avatars.com/api/?name=' + user.firstName + ' ' + user.lastName + '&background=' + getRandomColor() + '&size=200&color=000000';
                $team.append(
                    '<a href="javascript:void(0);" data-user-id="' + user._id + '" data-toggle="tooltip" title="' + user.firstName + ' ' + user.lastName + '" style="cursor:grab;">' +
                    '<img src="' + user.avatar + '" title="' + user.firstName + ' ' + user.lastName + '" class="rounded-circle m-1" width="32px" style="border: 1px solid #c1c1c1;"/></a>'
                );
                $members.find('a[data-user-id="' + user._id + '"]').remove();
            });
            
            var reportsTo = dragula({
                revertOnSpill: true
            }).on('drop', function (el, target, source, sibling) {
                // something goes here
            });
            $form.find('.dragula').each(function (i) {
                reportsTo.containers.push(this);
            });
            //$('[data-toggle="tooltip"]').tooltip(); // Not working here
        }, 'json').fail(function () {
            $(window).trigger('user-form-hide');
            addMsg('An unexpected error was encountered while trying to load your user. Please refresh the page to try again.');
        });
    }
    $('.permission-section').find('input[type="checkbox"]').on('change', function () {
        $(this).val($(this).prop('checked'));
    });
    function getRandomColor() {
        var letters = '0123456789ABCDEF';
        var color = '';
        for (var i = 0; i < 6; i++) {
            color += letters[Math.floor(Math.random() * 16)];
        }
        return color;
    }
    function rolePermissions(role, permissions = {}) {
        $.get('/account-management/permissions/roles/' + role, function (res) {
            if (res.error) { CAMS.alert('Error', res.msg, 'error'); }
            let perns = typeof permissions.dashboard != 'undefined' ? permissions : res.data.permissions;
            $.each(perns, function (key, value) {
                $.each(value, function (k, v) {
                    let elmId = 'user-permissions-' + key + '-' + k;
                    let chkElm = $('#modal-user-form').find('#' + elmId);
                    let lblElm = $('#modal-user-form').find('label[for="' + elmId + '"]');
                    if (chkElm) {
                        chkElm.prop('checked', v);
                        chkElm.val(v);
                        $('.permission-section').fadeIn(1, function () {
                            lblElm.addClass('animated zoomIn');
                        }).delay(10).show(0, function () {
                            lblElm.removeClass('animated zoomIn');
                        });                       
                    }
                });
            });
        });
    }
	
	$form.on('mouseover', '#exposure', function(e){
		e.preventDefault();
		$form.find('#user-password').attr('type', 'text');
		$form.find('#user-confirm').attr('type', 'text');
	  });
	  $form.on('mouseout', '#exposure', function(e){
		e.preventDefault();
		$form.find('#user-password').attr('type', 'password');
		$form.find('#user-confirm').attr('type', 'password');
	  });
	
});

Open in new window


On line #142, you see this:

$form.on('click', '.btn-change-password', function(){
      $(this).replaceWith( $passwordForm.clone() );
});


$passwordForm.clone() boils down to this:

$passwordForm = $form.find('.password-template').clone().removeClass('d-none'),

So, I'm completely cool with HOW the "change password" is being used to reveal those fields that are otherwise hidden.

But how can it also be used as a way to submit the form?

Perhaps it comes down to "click" versus "submit," but I don't know.

What do you think?
SOLUTION
Avatar of Julian Hansen
Julian Hansen
Flag of South Africa image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Bruce Gust

ASKER

That's it, Julian!

I was typing out the "I found my own solution" text when you responded.

There are two buttons, both of them with the same text and styling which is partially why I missed it.

Here's the twig file:

<div class="password-wrapper">
        <button type="button" class="btn btn-primary btn-change-password"><i class="fas fa-fw fa-key"></i> Change Password</button>
        <div class="password-template d-none">
			<div class="row">
				<div class="col-lg-12">
				To change your password, begin by entering your current password first...<br><br>
					<div class="md-form">
						<input type="password" id="current-password" name="current_password" class="form-control" value="">
						<label for="current_password"><a id="expose-current" href="#" style="cursor:text;" tabindex="-1">Current Password</a></label>
					</div>
				</div>
			</div>
			<!-- new row -->
			<div class="row">
				<div class="col-lg-6" style="margin-top:-25px;">
					<div class="md-form">              
						<input type="password" id="user-password" name="user[password]" class="form-control" required />
						<label for="user-password"><a id="exposure" href="#" style="cursor:text;" tabindex="-1">Password*</a></label>
					</div>
				</div>
				<div class="col-lg-6" style="margin-top:-25px;">
					<div class="md-form">
						<input type="password" id="user-confirm" class="form-control" required />
						<label for="user-password">Confirm*</label>
					</div>
				</div>
			</div>
			<div class="row">
				<div class="col-lg-12">
					<div style="font-size:9pt; background-color:#3d5fb5; border-radius:10pt; width:90%; margin:auto; height:auto; padding:5px; text-align:center; color:#fff;">Hover over the "Password" and "Current Password" labels to see the actual characters that you have inputted.</div>
				</div>
			</div>
			<div class="row">
				<div class="col-lg-12">
					<div class="form-group">
						<label>&nbsp;</label><br />
						<button type="submit" class="btn btn-primary save"><i class="fas fa-fw fa-key"></i> Change Password</button>
						<button type="button" class="btn btn-danger btn-cancel-password">Cancel</button>
					</div>
				</div>
			</div><!--end of row -->
			<p>Please ensure that your password meets the following requirements:</p>
			<ul class="password-requirements">
			<li data-reg="upper">At least 1 uppercase letter.</li>
			<li data-reg="lower">At least 1 lowercase letter.</li>
			<li data-reg="number">At least 1 number.</li>
			<li data-reg="special">At least 1 of the following special characters: !@#$%^&*</li>
			<li data-reg="total">At least 8 total characters.</li>
			</ul>
		</div><!-- end of password-wrapper -->

Open in new window


Line #2 and line #37.

Can I ask you this, though...

Line #3 - <div class="password-template d-none"> I'm thinking "d-none" means that everything in the "password-wrapper" div isn't a part of the DOM at all. It's completely absent.

When you click on "Change Password" button, you're triggering the code associated with the "btn-change-password" class which is line #143 of the "user-form.js" file:

  $form.on('click', '.btn-change-password', function(){
    $(this).replaceWith( $passwordForm.clone() );
  });

$passwordForm is a var that was established at the top of the page which is this:

 $passwordForm = $form.find('.password-template').clone().removeClass('d-none'),

Why do we have to "clone" all of the elements and not just remove the "d-none" class?

And how does removing the "d-none" qualifier from the "password-template" class make the original "Change Password" button go away?

Thanks!
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial