Improving Course Navigation User Experience [Part 3]: Implementing the Navigation Model

In Part 1 of this series, we discussed possible mental models that could be used to enhance or replace the current lesson navigation component in LifterLMS.

In Part 2, we went a step ahead and planned the markup and the copy that would be involved in implementing such revisions.

This concluding part discusses the actual code needed to implement the ideas discussed in the earlier parts.

Before Copy Pasting

The code in this post isn’t written for direct copy pasting. This isn’t a straightforward exact tutorial but a thought experiment. If you aren’t a developer, please get a developer to review this series and use it to write code specific to your site or theme.

Planning the Code

We could review the existing templates and modify them. That’s one way to do this. Another way to implement our navigation model is to think of how it would work and then use that to rewrite or rearrange the existing templates.

On any given lesson, before rendering the navigation, we need to know everything about all the navigation actions available to a learner unlike the alternative where we make decisions just before loading every button.

We use this approach because we want to know not only what an action does (next lesson, mark complete, etc) but also what it says (Take Quiz vs Retake Quiz, Next Lesson vs Next Lesson without Completing). We also need to dynamically change the order of navigation actions.

To be able to do that, we’ll need to make decisions not just based on what this action is like but also based on what all the other actions are like. So, before we come to the markup and styling, we need to create a variable that will provide such information. Let’s call this variable $nav_actions. From our discussion so far, what we expect this variable to provide is:

  • What is the previous action?
  • What is the primary next action?
  • What are the secondary next actions and in what order?
  • Is there a quiz attached? If yes, which one?
  • Is the attached quiz done?
  • Is there an assignment attached? If yes, which one?
  • Is the attached assignment done?
  • Is this the first or the last lesson?
<?php

$actions = array(
    'previous' => array(
        'key' => '',
        'label' => '',
    ),
    'next_primary' => array(
        'key' => '',
        'label' => '',
    ),
    'next_secondary' => array(),
    'quiz_id'=> false,
    'quiz_complete' => true,
    'assignment_id' => false,
    'assignment_complete' => true,
    'previous_id' => false,
    'next_id' => false,
);

Here’s a preview of what $nav_actions looks like (with a print_r()) on the first lesson of a course. This lesson has both a quiz and an assignment:

Array
(
    [previous] => Array
        (
            [key] => course
            [label] => Back to Course
        )

    [next_primary] => Array
        (
            [key] => quiz
            [label] => Take Quiz
        )

    [next_secondary] => Array
        (
            [0] => Array
                (
                    [key] => assignment
                    [label] => Complete Assignment
                )

            [1] => Array
                (
                    [key] => lesson
                    [label] => Next Lesson without Completing
                )

        )

    [assignment_id] => 276
    [assignment_complete] => 
    [quiz_id] => 67
    [quiz_complete] => 
    [previous_id] => 
    [next_id] => 14
)

Here’s $nav_actions on the same lesson with the quiz and assignment complete and the mark incomplete action enabled.

Array
(
    [previous] => Array
        (
            [key] => course
            [label] => Back to Course
        )

    [next_primary] => Array
        (
            [key] => lesson
            [label] => Next Lesson
        )

    [next_secondary] => Array
        (
            [0] => Array
                (
                    [key] => mark-incomplete
                    [label] => Mark Incomplete
                )

            [1] => Array
                (
                    [key] => quiz
                    [label] => Retake Quiz
                )

            [2] => Array
                (
                    [key] => assignment
                    [label] => Redo Assignment
                )

        )

    [assignment_id] => 276
    [assignment_complete] => 1
    [quiz_id] => 67
    [quiz_complete] => 1
    [previous_id] => 
    [next_id] => 14
)

We also need a list of labels identified by the current status of the action. This is how I envision this to be:

<?php

$action_labels = array(

    'course' => array(
        'previous' => __( 'Back to Course', 'lifterlms-navigation-lab' ),
	'next_primary' => __( 'Back to Course', 'lifterlms-navigation-lab' ),
	'next_secondary' => __( 'Back to Course without Completing', 'lifterlms-navigation-lab' ),
    ),

    'lesson' => array(
        'previous' => __( 'Previous Lesson', 'lifterlms-navigation-lab' ),
        'next_primary' => __( 'Next Lesson', 'lifterlms-navigation-lab' ),
        'next_secondary' => __( 'Next Lesson without Completing', 'lifterlms-navigation-lab' ),
    ),

    // will never be a secondary next action or a previous action
    'mark-complete' => array(
        'next_primary' => __( 'Mark Complete & Go to Next Lesson', 'lifterlms-navigation-lab' ),
        'next_primary_last' => __( 'Mark Complete & Go Back to Course', 'lifterlms-navigation-lab' ),
    ),

    // will never be a primary next action or a previous action
    'mark-incomplete' => array(
        'next_secondary' => __('Mark Incomplete', 'lifterlms-navigation-lab' ),
    ),

    // will never be a previous action
    'quiz' => array(
        'next_primary' => __( 'Take Quiz', 'lifterlms-navigation-lab' ),
        'next_secondary' => __( 'Retake Quiz', 'lifterlms-navigation-lab' ),
    ),

    // will never be a previous action
    'assignment' => array(
        'next_primary' => __( 'Complete Assignment', 'lifterlms-navigation-lab' ),
	'next_secondary' => __( 'Complete Assignment', 'lifterlms-navigation-lab' ),
        'next_secondary_complete' => __( 'Redo Assignment', 'lifterlms-navigation-lab' ),
    ),
);

Almost everything in this label array is self-explanatory. Lines 20 and 38 represent special cases that will become clearer when we work out the rest of the code.

We can now divide our code into two conceptual parts:

  1. The code that calculates the navigation action information. This is the logical part and ideally should go into your theme’s functions.php or a custom plugin as an include.
  2. The template that consumes this information to render this information. This should be used to override and extend LifterLMS’s templating using instructions here: https://lifterlms.com/docs/lifterlms-templates/

The easiest way to use whatever’s discussed here is using a child theme.

Overriding Templates

We look at the template first because it is less complicated than calculating the action information. Additionally, it will also help inform our design thinking behind the action calculation logic. So, without further adieu, here’s the new lesson-navigation.php:

<?php

defined( 'ABSPATH' ) || exit;

// get global $post object.
global $post;

// get the LifterLMS object.
// see https://github.com/gocodebox/lifterlms/blob/3.36.5/includes/models/model.llms.lesson.php
$lesson = llms_get_post( $post );

// bail, if this is not a LifterLMS lesson.
if ( ! $lesson || ! is_a( $lesson, 'LLMS_Lesson' ) ) {
    return;
}

$nav_actions = llms_nav_get_actions( $lesson );

?>
<nav class="llms-course-navigation">

    <div class="llms-course-nav-step llms-previous-step">
        <div class="llms-nav-action llms-prev-action">
            <?php llms_nav_action_component_previous( $lesson, $nav_actions ); ?>
        </div>
    </div>

    <div class="llms-course-nav-step llms-next-step">
        <div class="llms-nav-action llms-next-action llms-next-action-primary">	
            <?php llms_nav_action_component_next_primary( $lesson, $nav_actions ); ?>
        </div>
        <?php
            if ( isset( $nav_actions[ 'next_secondary' ] ) ) {

                foreach ( $nav_actions[ 'next_secondary' ] as $next_secondary ) {
                    ?>
                    <div class="llms-nav-action llms-next-action llms-next-action-secondary">
                        <?php llms_nav_action_component_next_secondary( $lesson, $nav_actions, $next_secondary ); ?>
                    </div>
                    <?php
                }
            }
        ?>
    </div>
</nav>
  • The code till line 15 makes sure that this template is only loaded on lessons.
  • Line 10 fetches the LifterLMS lesson object.
  • Line 16 fetches the navigation actions information from a function that we’ll call llms_nav_get_actions().
  • The markup structure is pretty simple:
    • .llms-course-navigation
      • .llms-course-nav-step.llms-previous-step
        • .llms-nav-action.llms-prev-action
      • .llms-course-nav-step.llms-next-step
        • .llms-nav-action.llms-next-action.llms-next-action-primary
        • .llms-nav-action.llms-next-action.llms-next-action-secondary
  • On line 24 is llms_nav_component_previous() that’ll render the previous action. It needs the lesson object from line 10 and the navigation actions array from line 16.
  • On line 30 is llms_nav_component_next_primary() that’ll render the primary next action. It also needs the lesson object and navigation actions array as parameters.
  • Since there can be only one previous action and one primary next action, we don’t need to pass this information separately on lines 24 and 30. Those functions know what action are they dealing with.
  • Line 33 ensures that nothing is rendered if there are no secondary next actions.
  • Line 35-41 loops through available secondary actions to render them.
  • On Line 38 is llms_nav_component_next_secondary() which apart from the lesson and navigation actions also needs to know the specific secondary action because there can be more than one of them.

Template Functions

  • From our discussion so far, there’s only one key structural difference between a primary and a secondary next action.
  • Primary next action has the action text and a title (of the following lesson, quiz or assignment).
  • Secondary next actions just have the action text and no title.
  • The previous action is structurally the same as the next primary action.

So these three functions can be expected to be wrappers of a common function that renders all the different kinds of links and buttons. With a simple flag, you could hide the title, if it is a secondary next action and create an internal function like this:

<?php

function _llms_nav_action_component( $lesson, $action, $show_title = false ) {

    $action_key = $action['key'];

    $action_label = $action['label'];

    switch ( $action_key ){

        case 'course':
            break;

        case 'lesson':        
            break;

        case 'mark-complete':
            break;

        case 'mark-incomplete':
            break;

        case 'quiz':
            break;

        case 'assignment':
            break;

    }
}

Line 9 onwards, we can render the specific button based on the action.

To be able to display a title inside the previous and primary next action, we also need the id of the previous or next, that is, adjacent lesson. Of course, if this is the first or the last lesson, there is no adjacent lesson and we’ll be rendering a Back to Course link:

<?php

function _llms_nav_action_component( $lesson, $action, $adjacent_id = false, $show_title = false ) {

    $action_key = $action['key'];

    $action_label = $action['label'];

    $action_title = '';

    if ( false === $show_title ) {
        if ( 'course' === $action_key || ! $adjacent_id ) {
            $title_id = $lesson->get_parent_course();
        } else {
            $title_id = $adjacent_id;
        }
        $action_title = get_the_title( $title_id );
    }

    switch ( $action_key ){

        case 'course':
            break;

        case 'lesson':        
            break;

        case 'mark-complete':
            break;

        case 'mark-incomplete':
            break;

        case 'quiz':
            break;

        case 'assignment':
            break;

    }
}

Lines 11-18 populate the title accordingly. In case it’s a quiz or assignment button, for consistency sake, we could output the title of the quiz/assignment instead. I think it is okay to use the term barrier to refer to both quizzes and assignments because they act like a barrier in navigating to the next lesson.

<?php

function _llms_nav_action_component( $lesson, $action, $adjacent_id = false, $show_title = false, $barrier_id = false ) {

    $action_key = $action['key'];

    $action_label = $action['label'];

    if ( false === $show_title ) {
        $action_title = '';
    } else{
        if ( 'course' === $action_key || ! $adjacent_id ) {
            $title_id = $lesson->get_parent_course();
        } else if ( in_array( $action_key, array( 'assignment', 'quiz' ) ) ) {
            $title_id = $barrier_id; 
        } else {
            $title_id = $adjacent_id;
        }
        $action_title = get_the_title( $title_id );
    }

    switch ( $action_key ){

        case 'course':
            break;

        case 'lesson':        
            break;

        case 'mark-complete':
            break;

        case 'mark-incomplete':
            break;

        case 'quiz':
            break;

        case 'assignment':
            break;

    }
}

Finally, if you actually compare the code for the mark complete and mark incomplete buttons, they’re similar enough to merge into a single template. The same is true for the quiz and assignment buttons. So, we can combine those.

Additionally, since we already get this information in navigation actions, whether a barrier (quiz/assignment) is complete can be used to add a class so that it can be used for styling.

With that in mind, we can now copy over the templates and modify them a bit to get this:

<?php

/**
 * Renders a navigation action.
 * 
 * @param LLMS_Lesson $lesson         The Lesson object
 * @param string      $action         The current action
 * @param bool|int    $adjacent_id    The previous or next lesson's ID
 * @param bool        $show_title     Whether to display a title. False for secondary actions
 * @param bool|int    $barrier_id     ID of a quiz or assignment, false if a lesson doesn't have them
 * @param bool        $barrier_status Completion status of an attached quiz or assignment
 */
function _llms_nav_action_component( $lesson, $action, $adjacent_id = false, $show_title = false, $barrier_id = false, $barrier_status = true ) {

    $action_key = $action['key'];

    $action_label = $action['label'];

    // don't show a title for secondary actions
    if ( false === $show_title ) {
        $action_title = '';
    } else{
        // if the action is back to course, display the course title
        if ( 'course' === $action_key || ! $adjacent_id ) {
            $title_id = $lesson->get_parent_course();
        } else if ( in_array( $action_key, array( 'assignment', 'quiz' ) ) ) {
            // for quizzes and assignments, show their title instead of the next lesson
            $title_id = $barrier_id; 
        } else {
            // by default show the title of the adjacent lesson
            $title_id = $adjacent_id;
        }
        $action_title = get_the_title( $title_id );
    }

    switch ( $action_key ) {

        case 'course':
            ?>
            <div class="llms-nav-course llms-nav-action-wrapper">
                <a class="llms-lesson-link" href="<?php echo get_permalink( $lesson->get_parent_course() ); ?>">
                    <section class="llms-main">
                        <h6 class="llms-pre-text"><?php echo $action_label ?></h6>
                        <?php if ( $show_title ): ?>
                            <h5 class="llms-h5 llms-lesson-title"><?php echo $action_title; ?></h5>
                        <?php endif; ?>
                    </section>
                </a>
            </div>
            <?php
            break;

        case 'lesson':
            $adjacent_lesson = new LLMS_Lesson( $adjacent_id );
            $restrictions = llms_page_restricted( $adjacent_id, get_current_user_id() );
            $data_msg     = $restrictions['is_restricted'] ? ' data-tooltip-msg="' . esc_html( strip_tags( llms_get_restriction_message( $restrictions ) ) ) . '"' : '';
            ?>
            
            <div class="llms-adjacent-lesson<?php echo $adjacent_lesson->get_preview_classes(); ?> llms-nav-action-wrapper">
                <a class="llms-lesson-link<?php echo $restrictions['is_restricted'] ? ' llms-lesson-link-locked' : ''; ?>" href="<?php echo ( ! $restrictions['is_restricted'] ) ? get_permalink( $adjacent_id ) : '#llms-lesson-locked'; ?>"<?php echo $data_msg; ?>>
                    <section class="llms-main">
                        <h6 class="llms-pre-text"><?php echo $action_label; ?></h6>
                        <?php if ( $show_title ): ?>
                            <h5 class="llms-h5 llms-lesson-title"><?php echo get_the_title( $adjacent_id ); ?></h5>
                        <?php endif; ?>
                    </section>            
                </a>
            </div>
            <?php
        
            break;

        case 'mark-complete':
        case 'mark-incomplete':
            $completion_action = str_replace( 'mark-', '', $action_key );
            ?>
                <form action="" class="llms-<?php echo $completion_action; ?>-lesson-form llms-nav-action-wrapper" method="POST" name="mark_<?php echo $completion_action; ?>">

                    <?php do_action( "lifterlms_before_mark_{$completion_action}_lesson" ); ?>

                    <input type="hidden" name="mark-<?php echo $completion_action; ?>" value="<?php echo esc_attr( $lesson->get( 'id' ) ); ?>" />
                    <input type="hidden" name="action" value="mark_<?php echo $completion_action; ?>" />

                    <?php wp_nonce_field( "mark_{$completion_action}" ); ?>

                    <button id="llms_mark_<?php echo $completion_action; ?>" class="llms-button-action auto button" name="mark_<?php echo $completion_action; ?>" type="submit">
                        <section class="llms-main">
                            <h6 class="llms-pre-text"><?php echo apply_filters( "lifterlms_mark_lesson_{$completion_action}_button_text", $action_label, $lesson ); ?></h6>
                            <?php if ( $show_title ): ?>
                                <h5 class="llms-h5 llms-lesson-title"><?php echo $action_title; ?></h5>
                            <?php endif; ?>
                        </section>
                    </button>

                    <?php do_action( "lifterlms_after_mark_{$completion_action}_lesson" ); ?>

                </form>
            <?php
            break;

        case 'quiz':
        case 'assignment':

            $action_link = esc_url( get_permalink( $barrier_id ) );

            $action_label = apply_filters( "lifterlms_start_{$action_key}_button_text", $action_label, $barrier_id, $lesson );

            $action_status_class = $barrier_status ? 'llms-complete' : 'llms-incomplete';
            ?>
                <div class="llms-nav-action-wrapper llms-nav-barrier <?php echo $action_status_class; ?>">
                    <?php do_action( "llms_before_start_{$action_key}_button" ); ?>

                    <a class="llms-button-action auto button" id="llms_start_<?php echo $action_key; ?>" href="<?php echo $action_link; ?>">
                        <section class="llms-main">
                            <h6 class="llms-pre-text"><?php echo $action_label; ?></h6>
                            <?php if ( $show_title ): ?>
                                <h5 class="llms-h5 llms-lesson-title"><?php echo $action_title; ?></h5>
                            <?php endif; ?>
                        </section> 
                    </a>

                    <?php do_action( "llms_after_start_{$action_key}_button" ); ?>
                </div>
            <?php
            break;

    }
}

You could of course place them in separate template files for cleaner and better quality code: https://gist.github.com/actual-saurabh/7659fb5bfa21f3275510372fd71c4c33#file-lesson-navigation-alt-php.

We can now write our original template functions that we’ve used inside lesson-navigation.php: https://gist.github.com/actual-saurabh/7659fb5bfa21f3275510372fd71c4c33#file-llms-navigation-functions-php.

Removing Mark Complete Template

From what we’ve seen so far, we won’t need complete-lesson-link.php anymore. If you use the block editor, this won’t be an issue since you can simply remove the block. If you don’t use the block editor, you will need to remove this template as follows:

<?php

remove_action( 'lifterlms_single_lesson_after_summary', 'lifterlms_template_complete_lesson_link', 10 );

Calculating Navigation Actions

Now, we can focus on the navigation action logic that will inform our templates. Let’s recap what we already know and have discussed earlier:

  1. The least navigation is needed on free lessons. If a lesson is free, we don’t even need to go into enrollment status, completion and so on.
  2. Whether a lesson is free or not, the previous action doesn’t get affected much. So, we calculate this first. It’ll either be a lesson or on the first lesson, back to the course link. For brevity, let’s ignore that distinction and call it a lesson. ($nav_actions['previous']['key']='lesson').
  3. Then we calculate the primary next action as the next lesson without worrying about completion status or the presence of barriers. This can be recalculated, if needed. ($nav_actions['previous']['key']='lesson', $nav_actions['next_primary']['key']='lesson')
  4. At this stage, if it is a free lesson being accessed by a non-enrolled, non-admin user, we’re done!
  5. After this, we’re left with enrolled students. So, we now calculate if the lesson is complete.
  6. If the lesson is incomplete, we need to make the next lesson action a secondary action (which was until now primary) and set the primary action to mark complete. ($nav_actions['previous']['key']='lesson', $nav_actions['next_primary']['key']='mark-complete',$nav_actions['next_secondary'][0]['key']='lesson')
  7. If a lesson is complete, we check if retaking is allowed and set it as a secondary key. ($nav_actions['previous']['key']='lesson', $nav_actions['next_primary']['key']='lesson',$nav_actions['next_secondary'][0]['key']='mark-incomplete')
  8. If there’s no quiz or assignment, we’re done!
  9. Since a lesson can have both a quiz and an assignment, calculating the next part can get complicated. First, we need to decide on an order of priority. That is, if a lesson has both a quiz and an assignment, which one should be the primary action? It might be the assignment for you but here we’ll stick to what LifterLMS does and make quizzes rank higher than assignments.
  10. So, if a lesson has both a quiz and an assignment, and both are incomplete the primary action will be the quiz while assignment becomes another secondary action.
  11. If a quiz has been completed, the assignment will become the primary action and quiz will become secondary.
  12. If both the quiz and assignment are complete, the lesson will get auto-completed and the primary action will stay as the lesson while the quiz and assignment actions become secondary actions.
  13. If a lesson has just one of them, the logic would be simpler.

I created a class (class-llms-nav-actions.php)using this logic. It’s a lot of code to add here so I have placed it in a gist here: https://gist.github.com/actual-saurabh/7659fb5bfa21f3275510372fd71c4c33#file-class-llms-nav-actions-php. Instead of describing this class separately, I have added a lot of inline docs and comments that should help make sense of it

Now, all we need is a little wrapper function to load this class and provide the actions to the template:

/**
 * Fetches navigation actions
 * 
 * @param LLMS_Lesson The lesson object
 */
function llms_nav_get_actions( $lesson ) {
    
    $nav_actions = new LLMS_Nav_Actions();

    $actions = $nav_actions->get_actions( $lesson );

    return $actions;

}

Customising

Of course, you could remove the Secondary Next actions and even the Primary Next Action to implement other variations. Feel free to hack around!

Styling the Navigation

You can style this navigation the way you like. However, instead of making subjective decisions based on what you like or what looks good, it does make sense to give this a little thought.

Decisions based on Visual Hierarchy

For styling this navigation, I have used the following visual hierarchy:

  1. Previous Action is the least important and hence should be least highlighted.
  2. Previous Action on hover should get highlighted like all hovers are but still will be less visually important than all the other actions.
  3. Secondary Next Actions are visually more important than the Previous Action even though they’re going to be smaller in size and presence.
  4. Primary Next Action is visually the most important even though it’s similar in size and presence to the Previous Action.

Within the Previous and Primary Next actions, there are two elements – the label and the title. From how I see it, they both occupy almost similar position in the visual hierarchy. While the label contains the actual call to action, the title represents the expected unique result of the action. So, while the label is going to be similar and repetitive as the student navigates from one lesson to another, the lesson title will obviously change. In that sense:

  • The label can be smaller in size than the title.
  • While being smaller, the label should be bold for easy readability but slightly lower in contrast to the background to not become visually more important than the title.
  • The title should be larger in size, normal (= not bold) and higher in contrast to the background.

However, the next lesson’s title may be more important for you than the label and vice versa. If that is the case, feel free to make your own decisions about how you want them to look.

Choosing Colors

Using the logic above, we can use one bright color to provide the most highlight and visual weight. Depending on your theme (or even your course), a different color may work for you, but a deep blue works for this example: #0073bb. This is what I will use for the Primary Next Action’s background color and for the Secondary Next Actions’ text.

I can now use a tool like https://maketintsandshades.com/#0073bb to generate a few shades and tints for this color.

For the previous action, I can use tints of black (greys):

Using this, I can imagine the Previous Action like this:

The next action using the same logic for font-sizes and contrasts could look like this:

Here’s how they would look side by side:

No hover
Previous Action on hover
Next Action on hover

We can use the background colors of the Primary Next Action as text color for the Secondary Next Actions, while keeping the size the same as the label in the Primary Next Action. I also feel that adding a background on hover makes more sense:

Secondary Action
Secondary Action on hover

Here’s the SCSS that will implement this: https://gist.github.com/actual-saurabh/7659fb5bfa21f3275510372fd71c4c33#file-nav-style-scss. If plain CSS is your jam, use a tool like this to convert this SCSS into CSS https://jsonformatter.org/scss-to-css

Adding Animation for interaction

I also like to add some animation on hover to make the actions a bit more interactive.

With interactive animation

The scss is here: https://gist.github.com/actual-saurabh/7659fb5bfa21f3275510372fd71c4c33#file-nav-style-animation-scss.

Adding Icons

Finally, like the Astra theme does, why not add some nifty icons:

Animation with Icons

Do note that I had to change the color inheritance around so that the pseudo-elements get the same color as the label: https://gist.github.com/actual-saurabh/7659fb5bfa21f3275510372fd71c4c33#file-nav-style-animation-icons-scss.

Matching Height with JS

Finally, LifterLMS uses a little JS to match the height of navigation buttons (https://github.com/gocodebox/lifterlms/blob/3.36.5/assets/js/llms.js#L496-L504 and https://github.com/gocodebox/lifterlms/blob/3.36.5/assets/js/llms.js#L549-L560.

With the change in markup, the existing matchHeight won’t work. If you want to continue using that, a bit of custom JS will be needed.

(function ($) {
    LLMS.wait_for_matchHeight( function() {

        $( '.llms-prev-action a, .llms-prev-action button, .llms-next-action-primary a, .llms-next-action-primary button' ).matchHeight();

    } );
})(jQuery);

Just make sure that while enqueuing this script, add a dependency for both 'llms'(https://github.com/gocodebox/lifterlms/blob/3.36.5/includes/class.llms.frontend.assets.php#L177-L178) and 'llms-jquery-matchheight'(https://github.com/gocodebox/lifterlms/blob/3.36.5/includes/class.llms.frontend.assets.php#L171-L174). Otherwise, this won’t work.

Where to go from here?

Limitations

This is only limited to lesson navigation and doesn’t affect the way the quiz and assignment screens work. However, I’m sure that a decent developer can work that out quite easily.

Within lessons, this is limited in a few more ways. I haven’t given any special attention to prerequisites and drips. You might want to accommodate restriction information within the label itself instead of depending on the popup that LifterLMS shows by default.

In the same vein, I haven’t given any special attention to the video addon or form plugins. However, just like the other limitations, a developer can overcome these ones too.

As noted in the disclaimer, this is not meant to be finished working code that you can copy paste onto your LifterLMS site. It’s meant as a tutorial/recipe for a developer to use as a starter to build alternative learner navigation experiences.

Effects on Dependent Functionality

While writing the code, I have tried to write it as if it is going to be included in LifterLMS core. That means

  • Javascript dependant on the markup continues working as expected.
  • The CSS has been written so that it works with a standard theme with no special support for LifterLMS.
  • Almost all hooks are maintained with a few exceptions. (See Action Buttons below)

The last point means that most functionality will continue working as expected. However, things that depend on the way the navigation looks or the ones that add more elements to it may produce weird results.

Action Buttons

Some hooks and filters around the Mark Complete/Incomplete and Quiz/Assignment buttons get removed when they get integrated into the navigation itself. As explained above, this might cause some functionality that depends on these hooks to break.

Navigation Block

The Navigation Block available inside the WP block editor (Gutenberg) will also look broken since we haven’t really added any special styling for the editor side rendering. A bit more work would be needed to include the styles on the editor side.

Broken Navigation Block on Editor

UX Opportunities

Obviously, you might have even better or more radical UX ideas or needs. You could easily write your own CSS and JS and make this work whatever way you like.

For example, I have often wondered if a floating action button (especially on mobile screens) is a good UX decision. If you don’t care so much for backward navigation, something like this for just the primary action would be awesome.

You could easily club the secondary actions into such a button and get something similar to these: https://github.com/Nightonke/BoomMenu and https://github.com/yavski/fab-speed-dial.

Conclusion

I do hope that this is useful for your site. If you have any questions or need clarifications, do let me know in the comments. Also let us know if you do use it to change your navigation.

Happy hacking!

Leave a Comment

Your email address will not be published. Required fields are marked *