processwire / processwire-requests

ProcessWire feature requests.
39 stars 0 forks source link

Proof of concept: Floating buttons and position storage #177

Open flipzoom opened 6 years ago

flipzoom commented 6 years ago

Short description of the enhancement

There is a UX problem that has been bothering me at ProcessWire for years. In the editing process of pages, especially those which, for example, become very long due to a repeater, the workflow of saving is very cumbersome.

You edit the content and have to scroll up or down each time to save it. In addition, after each saving, the scroll position is at the top again.

I changed this behavior in a proof of concept. In ProcessPageEdit I clone the current buttons and fade them in as floating buttons on the left side when the original buttons are no longer visible. This saves the user a lot of time.

In addition, I use a cookie to save the current scroll position of the page, which is restored after saving the page. When you leave the site, this cookie is removed.

This behavior makes the editing of pages from my experience and feedback from my customers much more effective. This allows users to change content faster, save and view their changes.

Here is a screencast:

proof-of-concept-float-buttons-and-position-saving

What do you think of this modification?

Here is the changed code, which of course can be improved significantly. But for a test hack, it works well for me.

Javascript extension

/**
 * ------------------------------------------------------------------------
 * + Clone save and draft buttons for better UX
 * + Save scroll position
 * ------------------------------------------------------------------------
 */
 $(function(){

    // ------------------------------------------------------------------------
    // Only on page edit
    // ------------------------------------------------------------------------
    if($("body[class*='ProcessPageEdit']").length > 0) {

        // ------------------------------------------------------------------------
        // Check if draft buttons present
        // ------------------------------------------------------------------------
        if(($("button#submit_save") && $("button#submit_save_draft")) || ($("button#Inputfield_submit_publish") && $("button#submit_save_draft"))) {

            // ------------------------------------------------------------------------
            // Create clone holder
            // ------------------------------------------------------------------------
            var cloned          = false, 
                topThreshold    = 100, 
                bottomThreshold = 1500;
            $cloneHolder        = $('<div id="submit_float"></div>');
            $cloneHolder.appendTo('form#ProcessPageEdit');

            // ------------------------------------------------------------------------
            // On scroll events
            // ------------------------------------------------------------------------
            $(document).on('scroll', function(){

                // ------------------------------------------------------------------------
                // If not cloned, create clones
                // ------------------------------------------------------------------------
                if($(document).scrollTop() > topThreshold && !cloned) {

                    $cloneSave      = $("button#submit_save").eq(0).clone(true);
                    $cloneDraft     = $("button#submit_save_draft").eq(0).clone(true);
                    $clonePublish   = $("button#Inputfield_submit_publish").eq(0).clone(true);
                    $clonePreview   = $("a#_ProcessPageEditView").eq(0).clone(true);
                    cloned          = true;

                    $clonePreview.addClass('ui-button ui-widget ui-corner-all pw-head-button ui-state-default preview_float');
                    $cloneSave.attr('id', 'submit_save_float').removeClass('pw-button-dropdown-main');
                    $cloneDraft.attr('id', 'submit_draft_float');
                    $clonePublish.attr('id', 'submit_publish_float').removeClass('pw-button-dropdown-main');

                    $clonePreview.appendTo('div#submit_float');
                    $cloneDraft.appendTo('div#submit_float');
                    $cloneSave.appendTo('div#submit_float');
                    $clonePublish.appendTo('div#submit_float');

                    $cloneDraft.bind('click', function(){
                        $(this).addClass('loader').children().find('i').addClass('fa-spin');
                    });
                    $cloneSave.bind('click', function(){
                        $(this).addClass('loader').children().find('i').addClass('fa-spin');
                    });
                    $clonePublish.bind('click', function(){
                        $(this).addClass('loader').children().find('i').addClass('fa-spin');
                    });
                }

                // ------------------------------------------------------------------------
                // Show clones on scroll
                // ------------------------------------------------------------------------
                if($(document).scrollTop() > topThreshold && !$cloneHolder.hasClass('showit') && $(document).scrollTop() < ($('html').prop('scrollHeight') - bottomThreshold)) {
                    $cloneHolder.addClass('showit');

                // ------------------------------------------------------------------------
                // Hide clones on top and bittom
                // -------------------------------------------------------------------------
                } else if($(document).scrollTop() > ($('html').prop('scrollHeight') - bottomThreshold) && $cloneHolder.hasClass('showit') || $(document).scrollTop() < topThreshold && $cloneHolder.hasClass('showit')) {
                    $cloneHolder.removeClass('showit');
                }
            });
        }

        // ------------------------------------------------------------------------
        // Get page edit id on process-page-edit
        // ------------------------------------------------------------------------
        var pageEditID  = $("body").attr("class").match(/ProcessPageEdit-id-[\w-]*\b/), 
            pageEditID  = pageEditID[0];

        // ------------------------------------------------------------------------
        // Check if position is present on page load
        // ------------------------------------------------------------------------
        if($.cookie("process-edit-scroll-position")) {

            var scrollData = $.cookie("process-edit-scroll-position");

            // ------------------------------------------------------------------------
            // If same page stored, scroll to position
            // ------------------------------------------------------------------------
            if(scrollData["id"] == pageEditID) {
                $(document).scrollTop(scrollData["position"]);

            // ------------------------------------------------------------------------
            // Else delete cookie
            // ------------------------------------------------------------------------
            } else {
                $.cookie("process-edit-scroll-position", null);
            }
        }

        // ------------------------------------------------------------------------
        // Bind scroll event and save current scroll position after scroll 
        // event ended
        // ------------------------------------------------------------------------
        $(window).scroll(function() {
            clearTimeout($.data(this, 'scrollTimer'));
            $.data(this, 'scrollTimer', setTimeout(function() {
                $.cookie("process-edit-scroll-position", {"id": pageEditID, "position": $(document).scrollTop()}, {expires: 21600});
            }, 250));
        });

    // ------------------------------------------------------------------------
    // No page edit, delete cookie
    // ------------------------------------------------------------------------
    } else {
        $.cookie("process-edit-scroll-position", null);
    }
 });

CSS (as LESS) extension

// ------------------------------------------------------------------------
// Backend floating save buttons
// ------------------------------------------------------------------------
div#submit_float {
    position: fixed;
    z-index: 100;
    left: 30px;
    bottom: 30px;
    opacity: 0;
    transition: opacity .3s ease;

    &.showit {
        opacity: 1;
    }

    button, 
    a {
        float: none;
        display: block;
        border-radius: 50%;
        width: 85px;
        height: 85px;
        font-size: .75rem !important;
        padding: .1rem !important;
        box-shadow: 0 0 20px rgba(0,0,0,.3) !important;

        &.preview_float {
            padding-top: 22px !important;
            background: #6d6d6d !important;
            border-color: #6d6d6d !important;

            &:hover {
                background: #828282 !important;
                border-color: #828282 !important;
            }

            &:before {
                display: block;
                font-size: 1.6rem!important;
                margin-bottom: .2rem;
                font: normal normal normal 14px/1 FontAwesome;
                content: "\f06e";
            }
        }

        span#_ProcessPageEditViewDropdownToggle {
            display: none;
        }

        span.ui-button-text {
            padding: 0 !important;

            i {
                display: block;
                font-size: 1.6rem !important;
                margin-bottom: .2rem;
            }
        }

        &.loader {
            span.ui-button-text {
                i:before {
                    content: "\f110";
                }
            }
        }

        &#submit_draft_float, 
        &.preview_float {
            margin-bottom: .5rem;
        }
    }
}
szabeszg commented 6 years ago

No doubt the core needs to address these issues! The Profields Page Table has a similar bad UX, so I also have a JS script to clone its buttons to the top of the table, for the very same reason: scrolling up-and-down all time is not fun.

BernhardBaumrock commented 6 years ago

looks great! this also annoys me from time to time :)

gmclelland commented 6 years ago

How about making this into a contrib module that is listed in the modules directory? Maybe called "Floating Buttons" module" I would rather see this gain popularity and under go more testing in contrib before adding it to core.

It's an interesting idea. I would suggest giving the user the option of which side to place the floating buttons on. The reason I suggest that is because Material design usually always has the floating buttons on the right.

Just an fyi... Admin on Steroids also has a similar feature called Sticky Header https://github.com/rolandtoth/AdminOnSteroids/wiki/AdminTweaks which moves the buttons into a sticky header. Note: currently the buttons don't get moved in the AdminThemeUikit theme, but it does with the Classic theme.

stickyheader

AOS also provides a keyboard shortcut Cmd + S that makes it easier to save the page, but I see what you're saying about it losing your place on the page and taking you to the top with each save. Maybe that could be a separate module as well? "Scroll Position" module?

On my Drupal sites, I use a similar module called https://www.drupal.org/project/sticky_edit_actions Here is a gif of how it works: https://www.drupal.org/files/project-images/sticky-edit-animation-100ms_0.gif

Anyways...just some thoughts

ethanbeyer commented 6 years ago

AOS also provides a keyboard shortcut Cmd + S that makes it easier to save the page, but I see what you're saying about it losing your place on the page and taking you to the top with each save. Maybe that could be a separate module as well? "Scroll Position" module?

I feel like adding cmd+S support to the Core and then giving the option of Saving via AJAX could solve at least half the problem. Is saving with AJAX a no-no?

Edit: a few words

ryancramerdesign commented 6 years ago

Looks very nice. I think it's likely something that some people might use and benefit from, and others not, depending on the case. While it could have potential as a core option, I think before that it would be nice to see it as a module available for people that want to use it. Maybe it would be a good addition to the AOS module too. If it becomes something that most are using, then maybe adding to the core would be a good idea. On the other hand, the cmd+S support seems like something that might be worth adding right away. I can't think of any downsides there?

szabeszg commented 6 years ago

On the other hand, the cmd+S support seems like something that might be worth adding right away. I can't think of any downsides there?

In AOS, ctrl/cms+S used to save+publish the page no matter what. I requested to change this behavior and @rolandtoth was kind enough to change it so right now it does not change the published state of the page. I think if it works that way then it is generally ok to have it. However, since I use two monitors, sometimes not realizing whether the browser or my IDE is active, I accidentally save a page in the browser from time to time. Usually it is not a big deal, but if some "custom automated process" is also triggered upon save, it can be problematic.

So what I mean is: there can be use cases when accidental keyboard shortcuts are not welcome and because of this it would be useful to be able to turn it off, maybe just temporarily. I do keep turning it off-and-on in AOS for this reason.

ryancramerdesign commented 6 years ago

Thanks, that makes sense. Given that, maybe it's better to leave cmd+s as a option from a module (AOS).

On Wed, Apr 18, 2018 at 10:01 AM, Szabesz notifications@github.com wrote:

On the other hand, the cmd+S support seems like something that might be worth adding right away. I can't think of any downsides there?

In AOS, ctrl/cms+S used to save+publish the page no matter what. I requested to change this behavior and @rolandtoth https://github.com/rolandtoth was kind enough to change it so right now it does not change the published state of the page. I think if it works that way then it is generally ok to have it. However, since I use two monitors, sometimes not realizing whether the browser or my IDE is active, I accidentally save a page in the browser from time to time. Usually it is not a big deal, but if some "custom automated process" is also triggered upon save, it can be problematic.

So what I mean is: there can be use cases when accidental keyboard shortcuts are not welcome and because of this it would be useful to be able to turn it off, maybe just temporarily. I do keep turning it off-and-on in AOS for this reason.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/processwire/processwire-requests/issues/177#issuecomment-382396729, or mute the thread https://github.com/notifications/unsubscribe-auth/AAUCUFkyS4qWgZkO5ODj7kxSeb4rIiMBks5tp0cdgaJpZM4TRk7R .

rolandtoth commented 6 years ago

cmd+S support seems like something that might be worth adding right away. I can't think of any downsides there?

Addiction - you'll end up hitting cmd+S in other web apps all the time :)

CKEditor is what makes the cmd+S feature harder to implement as it might seem at first. Users expect it to work when they are editing a CKE field but technically they are inside another document (in an iframe).

The ultimate cmd+S feature would be ajax-saving as others mentioned, but that would require a lot of work (validations).